3. eMMC协议基础:命令帧、响应与数据包

做eMMC嵌入式开发,打交道最多的就是这三样东西:命令、响应和数据。我们先把它们拆开讲清楚,后面排查时序问题时就心里有数了。

3.1 命令帧格式:从第一个bit讲起

主机发给eMMC的任何指令,都是一个45bit的帧。你想想看,这45bit其实挺紧凑的。具体结构如下表:

位域 宽度 说明
起始位 1 bit 恒为0,用于帧同步
传输方向 1 bit 主机→设备为1
命令索引 6 bits 指定操作,如CMD8、CMD18
参数 32 bits 命令的附加数据
CRC7 7 bits 校验前40bit数据
终止位 1 bit 恒为1,标记帧结束

举个例子,CMD8的索引是8(二进制001000),参数里包含了VHS电压范围和Check Pattern。我调试时经常要手动拆解命令帧来对比逻辑分析仪抓到的波形。嗯,这里要注意,CRC7只覆盖命令索引和参数,不包含起始位和终止位。起始位和终止位是硬件自动插入的,你写软件时不用操心这个。

// 举个例子:手动构造CMD8命令帧
// 索引=8,参数=0x1AA(VHS=2.7-3.6V, pattern=0xAA)
// 计算前40bit [0:起始][1:方向][2:7索引][8:39参数]的CRC7

uint8_t cmd_index = 8;
uint32_t argument = 0x000001AA;
uint8_t crc7 = calc_crc7(cmd_index, argument);
// 输出帧:0 1 001000 00000000000000000000000110101010 <crc7> 1

调试经验:我个人习惯把命令帧的bit0(起始)和bit44(终止)单独拿出来看波形。如果逻辑分析仪看到这俩bit跳变不正常,基本是主机信号质量问题,跟设备没关系。

3.2 响应类型:R1、R2、R3、R4、R5

eMMC设备发回的响应,根据命令不同分为5种。别看种类多,其实就是响应内容和CRC策略的排列组合。我做HS400调试时经常用到的是R1和R2。

类型 长度 是否带CRC 典型用途 我的看法
R1 48bit 是(7bit CRC) 大多数CMD的通用响应 这个最常用,结构跟命令帧是一样的,只是方向相反
R2 136bit 是(7bit CRC) 获取CID、CSD寄存器 长度最长,我早期被它坑过,因为中间没有暂停位
R3 48bit 否 OCR寄存器(如CMD1) 说白了,上电握手专用,硬件层面就不校验它
R4 48bit 否 CMD5(IO操作) V4规范后才引入,HS400模式下很少用
R5 48bit 否 中断 不用太纠结,我做项目几年都没用过它

为什么R2有136bit?因为它要一次性返回CID(128bit)外加CRC和终止位。我记得有一次调试DDR模式,设备就是不给R2,最后发现是板子上的电压纹波太大,设备没法稳定输出。

3.3 数据包结构与CRC

eMMC的数据传输是以扇区为单位的。数据传输模式下来回的每个数据块,结构是这样的:

  1. 主机发送单块或多块读/写命令(如CMD18、CMD25)
  2. 设备确认命令后,数据线开始传数据块
  3. 每个数据块512字节(默认),尾部跟2字节CRC16
  4. 多块传输时,块与块之间没有间隔,直到主机发停止命令

数据块内部结构:

字段 长度 说明
起始位 1 bit 恒为0
数据方向 1 bit 设备→主机为0,主机→设备为1
数据体 512 bytes 用户数据
CRC16 16 bits 多项式0x1021
终止位 1 bit 恒为1

CRC16用的是CCITT标准多项式。别问我为什么不用CRC32,这就是规范定的,咱照着做就行。单块读的操作步骤如下:

// 单块读操作数据包流程:
// 1. 主机发送CMD17 (读单块) + 块地址
// 2. 设备返回R1响应(状态+CRC7)
// 3. 主机收到R1后,开启数据线接收
// 4. 设备发起数据包:[0][0][512字节数据][CRC16][1]
// 5. 主机计算CRC16,与收到的CRC16对比

核心要点:命令和R1/R4/R5响应用CRC7校验,R2也用CRC7但数据更长,R3/R4/R5的OCR部分不校验。只有数据块才用CRC16。CRC7算法可以复用SD/MMC通用的查找表方式,我一般直接把预计算表写在头文件里。

3.4 CRC校验的实用技巧

CRC在实际调试中非常有用。为什么?逻辑分析仪抓到错误数据时,通过CRC校验结果可以快速定位问题是出在主机发送端还是设备接收端。

CRC7的计算:

  • 生成多项式:x⁷ + x³ + 1(即0x89左移一位,消除最高位)
  • 输入数据按MSB first方式移位
  • 初始值:全0
  • 输出不取反,不需要xor out

下面是我常用的CRC7查找表实现:

static const uint8_t crc7_table[256] = {
    0x00, 0x09, 0x12, 0x1B, 0x24, 0x2D, 0x36, 0x3F,
    // ... 一共256个预计算值
};

uint8_t calc_crc7(const uint8_t *data, size_t len) {
    uint8_t crc = 0;
    for (size_t i = 0; i < len; i++) {
        crc = crc7_table[crc ^ data[i]];
    }
    return crc;
}

曾经踩过的坑:我曾经因为忘记把CRC7的初始值设为0,结果CMD8的响应死活不对。后来查了半天的spec才发现:SD/eMMC的CRC7初始值是固定全0,跟一些其他协议不一样。建议你们把这个初始值写死在代码里,不要留参数接口,避免后面接手的人踩坑。

CRC16相比CRC7要正规得多:

  • 生成多项式:x¹⁶ + x¹² + x⁵ + 1(即0x1021)
  • 初始值:0x0000
  • 不取反,不进位
  • 输入输出都是MSB first

这里有个小技巧:无论单块还是多块传输,CRC16都是在每个数据块完成后计算并发送。多块模式中,每个块的数据线和CRC线之间是严格连续的,没有空闲周期。我见过有些控制器在CRC计算上偷懒,导致HS400高速模式下CRC校验通过率下降,最后只能降频保稳定。

eMMC协议基础核心结构 命令帧(主机→设备) 45bit结构 起始(1) + 方向(1) 索引(6) + 参数(32) CRC7(7) + 终止(1) 用于:CMD8, CMD18等 响应类型(设备→主机) R1/R2/R3/R4/R5 R1:48bit,带CRC7 R2:136bit,带CRC7 R3:48bit,无CRC R4/R5:IO/中断 数据包结构 块传输模式 起始(1) + 方向(1) 数据体(512字节) CRC16(16bit) 单块/多块连续 核心校验机制 命令→CRC7 (多项式0x89) | 数据→CRC16 (多项式0x1021) 调试实用场景 CMD8握手 → R1响应 | CMD17读单块 → R1 + 数据块 + CRC16

调试建议:逻辑分析仪抓HS400数据时,我习惯同时看命令线(CMD)和数据线的CRC状态。CMD线出现CRC错误往往是主机信号反射问题,数据线CRC错误多半是DQS时序偏移或Vref调节不到位。别乱改驱动代码,优先排查硬件信号完整性。