2、SDIO控制器硬件架构

SDIO控制器,说白了就是CPU和SD卡之间的「翻译官」。你想想看,CPU跑的是AHB总线协议,SD卡跑的是SDIO协议,这两者怎么对话?全靠这个控制器在中间做转换。

我个人习惯,拿到一个新平台的SDIO控制器,第一件事就是看它的内部结构框图。不看清楚数据流怎么走的,后面调试起来会非常痛苦。我记得有一次,一个同事调了三天SD卡读写失败,最后发现是DMA路径上有个FIFO水位配置错了——这就是典型的「不懂架构,瞎调参数」。

2.1 控制器内部结构

一个典型的SDIO控制器,内部大概长这样:

SDIO控制器内部结构框图 AHB总线接口 寄存器组 DMA引擎 FIFO缓冲区 命令通道 (CMD) 数据通道 (DAT0-DAT7) 时钟管理 电源控制 中断控制器 图例说明: 总线接口 DMA引擎 FIFO 命令/数据通道 时钟管理 电源控制 中断控制器 控制信号

这个图我建议你多看几遍。数据流从AHB总线进来,经过DMA引擎,暂存在FIFO里,最后通过数据通道发出去。命令通道走的是另一条路,直接由寄存器组控制。

这里有个关键点:命令通道和数据通道是独立的。这意味着你可以同时发命令和传数据,互不干扰。我在项目中遇到过,有人把命令超时和数据处理混在一起调试,结果越调越乱——其实分开看就清楚了。

2.2 DMA引擎与FIFO

DMA引擎,说白了就是「数据搬运工」。没有DMA的时候,CPU得亲自把数据从内存搬到控制器,再搬到SD卡——这太浪费了。有了DMA,CPU只需要告诉DMA:「从地址A搬N个字节到控制器」,然后就可以去干别的事了。

FIFO呢?它是DMA和SDIO协议之间的「缓冲池」。为什么需要缓冲?因为DMA的传输速率和SDIO总线的速率不匹配。DMA可能一次搬64字节,但SDIO总线一次只传4字节。没有FIFO,数据就会丢。

FIFO深度配置要点:

  • FIFO深度通常为32-512字节,具体看芯片手册
  • 水位线(Watermark)设置很关键——设高了容易溢出,设低了DMA频繁触发
  • 我一般设在水位线为FIFO深度的1/2到2/3之间

DMA传输模式有两种:

模式 特点 适用场景
单块传输 一次传输一个数据块,块大小固定 小数据量、控制命令
多块传输 连续传输多个数据块,效率高 大文件读写、音视频流
无限传输 持续传输直到收到停止命令 流式数据、实时采集

嗯,这里要注意:多块传输时,DMA的地址必须连续。如果内存不连续,你得用SG-DMA(Scatter-Gather DMA)。我踩过这个坑——有一次用多块传输读SD卡,结果DMA地址跨页了,数据全乱套。后来改成SG-DMA才解决。

2.3 时钟管理与电源控制

时钟管理这块,其实就两件事:频率选择和时钟门控。

SDIO的时钟频率不是随便设的。SD卡初始化时,时钟必须低于400KHz——这是协议规定的。初始化完成后,才能切换到高速模式,最高能到208MHz(SD 6.0规范)。

我的经验:时钟切换时,一定要先等卡准备好。我习惯的做法是:

  1. 发CMD6切换时钟
  2. 读卡的状态寄存器,确认切换完成
  3. 再更新控制器的时钟分频系数

顺序搞反了,卡就会死掉。

电源控制呢?SDIO卡支持三种电源状态:

  • 全速运行:正常工作,时钟全开
  • 休眠:时钟关闭,但寄存器保持
  • 断电:VDD关闭,完全掉电

我建议你在驱动里实现电源管理。为什么?因为很多嵌入式设备是电池供电的。SD卡不读写的时候,让它休眠能省不少电。我曾经在一个物联网项目里,靠这个把待机功耗从5mA降到了0.5mA——效果很明显。

2.4 中断机制

SDIO控制器的中断,我把它分成三类:

中断类型 触发条件 处理方式
命令完成中断 CMD发送完成,收到响应 检查响应状态,继续下一命令
数据完成中断 数据传输完成 检查CRC,释放DMA
错误中断 超时、CRC错误、命令失败 记录错误,重试或恢复

中断处理有个原则:快进快出。中断服务程序里只做最必要的事——读状态、清标志、唤醒等待队列。真正的数据处理,放到下半部(Bottom Half)或工作队列里做。

我曾经犯过的错:在中断里直接调用了DMA传输函数,结果中断嵌套导致栈溢出。后来改成在中断里只设置一个标志位,主循环里再处理DMA传输——问题就解决了。

还有一个容易被忽略的点:中断共享。很多SoC里,多个外设共用一个中断线。你的中断服务程序得先判断是不是自己的中断,不是就赶紧返回。否则会影响其他外设的正常工作。

嗯,关于中断优先级,我建议SDIO中断设得高一些。为什么?因为SD卡有超时机制——你响应慢了,卡就认为你挂了,然后进入错误恢复流程。这个恢复流程很耗时,能避免就避免。

好了,SDIO控制器的硬件架构就聊到这儿。记住一句话:理解架构,才能写好驱动。别急着写代码,先把控制器的数据流、时钟、中断这些搞清楚,后面调试会省很多时间。