3. SDIO命令与响应
这一章,我们来聊聊 SDIO 通信中最核心的部分——命令与响应。
说白了,SDIO 总线上所有的操作,都是靠主机发命令、设备回响应来完成的。我早期调 SD 卡驱动时,有一半的 Bug 都是因为没理解透命令格式或者响应类型。这块儿要是啃不下来,后面做验证、调性能,基本就是两眼一抹黑。
咱们先看一张总览图,把今天要讲的知识结构搭起来。
3.1 命令格式:48 位里藏着什么?
SDIO 总线上跑的命令,全都是 48 位长的串行帧。主机通过 CMD 线,一位一位地发给设备。
这 48 位怎么分配的?我列个表格,你一看就明白。
| 字段 | 宽度 | 值/说明 |
|---|---|---|
| 起始位 | 1 位 | 固定为 '0' |
| 方向位 | 1 位 | '1' 表示主机到设备(命令方向) |
| 命令索引 | 6 位 | 0~63,对应 CMD0 ~ CMD63 |
| 命令参数 | 32 位 | 具体含义由命令号定义 |
| CRC7 | 7 位 | 对命令索引 + 参数做校验 |
| 结束位 | 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 种标准响应打好基础,剩下的几种扩展类型用到时再查表。我有个习惯,在设计验证用例时,会列一张“命令-响应映射表”。
下表是常用的响应类型,你直接抄走用。
| 类型 | 长度 | 内容 | 典型命令 |
|---|---|---|---|
| R1 | 48 位 | 32 位状态寄存器 + CRC7 | CMD3, CMD7, CMD9, CMD13 等 |
| R1b | 48 位 | 同 R1,但带忙信号期 | CMD5 (IO 模式), CMD0 (Go_Idle) |
| R2 | 136 位 | 128 位 CID/CSD 寄存器 | CMD2, CMD10 |
| R3 | 48 位 | 32 位 OCR 寄存器 | CMD5 (内存模式) |
| R4 | 48 位 | 32 位 RCA + 状态 | CMD4? (此处 SDIO 专用) |
| R5 | 48 位 | 32 位 IO 中断状态 | CMD52 (直接读/写寄存器) |
| R6 | 48 位 | 32 位 RCA + 状态 | CMD3 (发送 RCA) |
| R7 | 48 位 | 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。但那是后话,今天先把命令和响应吃透,后面才能跑得快。