3. SDIO 命令与响应:搞懂主机和设备怎么“对话”

这一章,我们来聊聊 SDIO 通信中最核心的部分——命令与响应。

说白了,SDIO 总线上所有的操作,都是靠主机发命令、设备回响应来完成的。我早期调 SD 卡驱动时,有一半的 Bug 都是因为没理解透命令格式或者响应类型。这块儿要是啃不下来,后面做验证、调性能,基本就是两眼一抹黑。

咱们先看一张总览图,把今天要讲的知识结构搭起来。

SDIO 命令与响应体系 SDIO 命令与响应体系 主机发出命令 命令格式 48 位命令帧结构 起始位(1) + 方向(1) + 索引(6) + 参数(32) + CRC7(7) + 结束位(1) 命令分类 三类命令 广播 (bc/bcr) 点对点 (ac/adtc) 应用相关 (ACMD) 响应类型 5 种标准响应 R1 / R1b / R2 / R3 / R4 R5 / R6 / R7(扩展类型) 长度 48 或 136 位 数据事务(读/写) 关键点:CMD 线发送命令 DAT 线传输数据,不要混淆

3.1 命令格式:48 位里藏着什么?

SDIO 总线上跑的命令,全都是 48 位长的串行帧。主机通过 CMD 线,一位一位地发给设备。

这 48 位怎么分配的?我列个表格,你一看就明白。

字段宽度值/说明
起始位1 位固定为 '0'
方向位1 位'1' 表示主机到设备(命令方向)
命令索引6 位0~63,对应 CMD0 ~ CMD63
命令参数32 位具体含义由命令号定义
CRC77 位对命令索引 + 参数做校验
结束位1 位固定为 '1'

举个例子,CMD17(读单块)发出去时,命令索引是 17(二进制 010001),参数里填的是块地址。设备收到后,先检查 CRC7,对上了才解析。

这里有个坑,我提一下。

我曾经遇到一个案例:某款 WiFi 模块经常丢命令。排查了很久,发现是主机的 CRC7 生成逻辑在边界条件(参数全 0)下算错了。SDIO 协议中的 CRC7 生成多项式是 x^7 + x^3 + 1,起始值为 0x00,你别想当然用别的多项式。

另外,命令参数虽然有 32 位,但具体怎么用,看命令定义。比如 CMD5(IO 发送操作条件),它的参数高 24 位保留,低 8 位填电压范围和访问模式。你想想看,如果软件层随便填个值,设备可能就挂在那不响应了。

我个人习惯:在搭建验证环境时,先搞一个“黄金命令帧”的参考模型,把每个字段的生成逻辑单独拆出来验证。这样后面出问题了,可以直接定位是命令生成环节还是响应解析环节出的错。

3.2 响应类型:R1、R2、R3……一个都不能混

设备收到命令后,会根据命令类型返回响应。响应也是在 CMD 线上传输的。长度有两种:48 位或 136 位。

记不清?没关系。我常用的区分方法是:5 种标准响应打好基础,剩下的几种扩展类型用到时再查表。我有个习惯,在设计验证用例时,会列一张“命令-响应映射表”。

下表是常用的响应类型,你直接抄走用。

类型长度内容典型命令
R148 位32 位状态寄存器 + CRC7CMD3, CMD7, CMD9, CMD13 等
R1b48 位同 R1,但带忙信号期CMD5 (IO 模式), CMD0 (Go_Idle)
R2136 位128 位 CID/CSD 寄存器CMD2, CMD10
R348 位32 位 OCR 寄存器CMD5 (内存模式)
R448 位32 位 RCA + 状态CMD4? (此处 SDIO 专用)
R548 位32 位 IO 中断状态CMD52 (直接读/写寄存器)
R648 位32 位 RCA + 状态CMD3 (发送 RCA)
R748 位32 位电压范围 + 校验CMD8 (电压验证)

你注意看 R1b,它返回的是 48 位响应,但老设备可能在后边拖一段“忙信号”(DAT0 线被拉低)。验证时,如果你读响应就收工,不检查忙信号,后面发下一条命令肯定失败。

实用建议:在写测试用例时,对 R1b 的响应处理,我习惯在收到 48 位响应后,再多等一个超时周期检查 DAT0 线的状态。超时时间建议设为 500ms 或按协议中的超时定义来。

再来说 R2,它是 136 位。说白了就是 128 位数据(16 字节)加上校验。CID 和 CSD 都是 128 位的寄存器。我调试 SDIO 设备时,第一步就是发 CMD10 读 CID,确认设备和主机通信有没通。CID 里包含了厂商 ID、OEM ID、产品名,看一眼就知道设备对不对。

3.3 命令分类:广播、点对点、应用相关

SDIO 协议把命令分成几大类。搞清楚了,你就知道哪些命令是“群发”,哪些是“单聊”。

广播命令(bc / bcr)

主机发出去,所有连在总线上的设备都能收到。bc 是不带响应的,bcr 是需要响应的。

  • bc 例子:CMD0 (GO_IDLE_STATE)。复位所有设备,不要求响应。
  • bcr 例子:CMD2 (ALL_SEND_CID)。所有设备都要返回 CID,用开漏模式避免冲突。

这里面有个细节。CMD2 为啥是 bcr?因为在卡识别阶段,多个卡同时在线,需要用开漏驱动,让它们做“仲裁”。我早先以为所有命令都是推挽,结果验证环境里多卡场景测出来全是 CRC 错误,查了半天才明白。

点对点命令(ac / adtc)

主机发给指定设备。设备通过 RCA(相对地址)来标志自己。

  • ac 例子:CMD7 (SELECT/DESELECT_CARD),选中某张卡,带 R1 响应。
  • adtc 例子:CMD17 (READ_SINGLE_BLOCK)、CMD24 (WRITE_BLOCK)。这些命令后面跟着数据在 DAT 线上传输。

你想想看,CMD7 选中卡后,后面发 CMD17 读数据,设备才知道“哦,这是在叫我”。如果一开始没选卡,发的 CMD17 会被忽略。

应用相关命令(ACMD)

这类命令比较特殊,它不单独存在,前面必须先发一个 CMD55(APP_CMD)做前缀。CMD55 告诉设备:“接下来的命令,请用应用相关的方式解析。

常见的 ACMD 有:

  • ACMD6 (SET_BUS_WIDTH):设置 SDIO 数据总线宽度。
  • ACMD51 (SEND_SCR):读 SD 配置寄存器(SDIO 卡专用)。

我曾经遇到一个坑:发 ACMD 前忘了发 CMD55,结果设备一直返回 illegal command。我排查了半天才发现,顺序错了。记住,ACMD 不独立存在。验证环境里必须将“CMD55 + ACMD”作为一个完整原子事务来建模。

3.4 命令-响应序列:实战验证重点

搞完基础知识,咱们来串一下完整的命令-响应序列。我一般会将测试脚本拆成“命令发送 → 响应接收 → 状态检查”三个步骤。

// 伪代码示意:CMD7 选卡
1. 构造 48 位命令帧
   // 起始位:0, 方向:1, 索引:CMD7(0x07), 参数:RCA值, CRC7, 结束位:1
2. CMD 线上串行发送
3. 等待设备响应(R1, 48位)
4. 解析响应:
   // 检查起始位 0, 方向位 0(设备到主机)
   // 提取 32 位状态,检查 bit[19] (CURRENT_STATE)
   // 验证 CRC7 是否匹配
5. 如果 R1b,再等忙信号释放
6. 更新卡状态机

在实际验证中,我会特别关注:

  • 响应超时:如果主机发了命令但设备没回,超过 NCR(命令响应时间)周期后要报错。
  • CRC 错误:如果 CRC7 不对,放弃这帧响应,发下一条命令前要重发。
  • 状态一致性:设备返回的 R1 状态,要和主机维护的卡状态机一致。比如主机以为卡是“Standby”,设备却报“Transfer”,说明状态不同步了。

做验证时,我习惯在 FPGA 原型上加一个“命令响应记录器”,抓下 CMD 线上的完整波形。然后写个脚本自动对比实际帧和期望帧。这样跑完一轮回归,能自动报出是命令送错了,还是设备回错了。

好了,命令和响应这块儿就这么多。下一层,咱们要深入数据的事务了——怎么读、怎么写、怎么用 DMA。但那是后话,今天先把命令和响应吃透,后面才能跑得快。