SD 协议与协议栈源码分析(SD 内存卡)

  • 本文结合 SD Spec v2.0 和 Spec v3.0,分析了以下几个协议栈中的一些重要实现部分,由于大部分协议栈都没有实现完全,后面挑选了典型的实现进行分析,
      1. esp32 SD 协议栈
      1. rt-thread SD 协议栈
      1. vxWorks 6. 9 SD 协议栈
      1. linux 4.19 SD 协议栈

0. SD 协议

0.0 SD 内存卡的分类

  • SD卡,即 Secure Digital Memory Card,按照尺寸分类,SD卡分为:标准尺寸,Mini SD,Micro SD(TF卡),在嵌入式领域,尤其是小设备,通常使用TF卡

在这里插入图片描述- SD 卡按照容量分类,可以分为

简称全称容量大小SD版本
SDSCStandard Capacity SD Memory Card<=2GB2.0版本以上
SDHCHigh Capacity SD Memory Card2GB~32GB2.0版本以上
SDXCExtended Capacity SD Memory Card32GB~2TB3.0版本以上
SDUCUltra Capacity SD Memory Card2TB~128TB8.0版本以上
  • 按照工作电压范围,可以分为一种是“高电压SD卡”,额定电压为2.7~3.6V;另一种为“双电压SD卡”,可以工作在低电范围(T.B.D,1.70 ~ 1.95V)和2.7~3.6V两种电压范围
High Voltage SD Memory Card: 操作的电压范围在2.7-3.6V
UHS-II SD Memory Card: 操作的电压范围VDD1: 2.7-3.6V, VDD2: 1.70-1.95V
  • 按照速度等级,可以分为
      1. Class 0 - 这种卡不定义具体性能,代表了这种规范出来之前的所有卡
      1. Class 2 - 要求在 Default Speed mode 下,性能至少要达到(大于等于) 2MB/sec
      1. Class 4 - 要求在 Default Speed mode 下,性能至少要达到 4MB/sec
      1. Class 6 - 要求在 Default Speed mode 下,性能至少要达到 6MB/sec
      1. Class 10 - 要求在 High Speed mode 下,性能至少要达到 10MB/sec

数据大小的MB单位指的是1024x1024字节

  • Class 信息一般标记在卡上,如下图

在这里插入图片描述

  • (UHS-I) Card,即 Ultra High Speed Phase I,在4位SD总线上提供高达104MB/秒的性能,其操作模式如下所示,
Default Speed mode: 3.3V供电模式,频率上限25MHz,速度上限 12.5MB/sec
High Speed mode: 3.3V供电模式,频率上限50MHz,速度上限 25MB/sec
SDR12: UHS-I卡, 1.8V供电模式,频率上限25MHz,速度上限 12.5MB/sec
SDR25: UHS-I卡, 1.8V供电模式,频率上限50MHz,速度上限 25MB/sec
SDR50: UHS-I卡, 1.8V供电模式,频率上限100MHz,速度上限 50MB/sec
SDR104: UHS-I卡, 1.8V供电模式,频率上限208MHz,速度上限 104MB/sec
DDR50: UHS-I卡, 1.8V供电模式,频率上限50MHz,性能上限 50MB/sec
UHS156: UHS-II RCLK Frequency Range 26MHz - 52MHz, up to 1.56Gbps per lane.

值得注意的是 1.8v 和 3.3v 的信号时许是由差异的

在这里插入图片描述- DDR50模式 是 microSD (tf) 卡强制要求实现的,但是标准尺寸的SD卡不一定实现

  • DDR模式和SDR模式下的读写频率和吞吐量支持情况
    在这里插入图片描述在这里插入图片描述

  • 其中,SDR(Single Date Rate), 一个周期只能采集一次数据,即一个bit,由于SD卡是4条数据线并行传输,所以一个周期能传输4bit,如果频率是50MHz(即1秒传输次数为50 000 000),那么1秒能传输的数据量为25MB(这里1MB为1 000 000 Byte)。所以这就是为什么各种SDR模式里面,频率上限是速度上限的两倍

  • 而对于DDR(Double Data Rate),在时钟上升沿和下降沿都可以采集数据,也就是单一周期内可读取或写入2次,因此4条并行数据线在一个周期内能传输8bit

  • 有一类 SD 内存卡是只读的,被称为 ROM Card,ROM Card 不支持写相关的命令,具体参考SD3.0 spec. 的 3.8 ROM Card

0.1 SD 内存卡的结构

  • 标准SD卡主要由硬件接口、卡接口控制器、内部寄存器、存储器以及电源检测单元等部分组成
    在这里插入图片描述
  • SD 卡管脚要比 TF 卡多,TF卡插入卡套可作为SD卡使用,反之则不行

在这里插入图片描述

  • 如图所示,SD 卡的信号线包括
      1. CMD:命令/响应线,用于主机向SD卡发送命名和接收SD卡响应
      1. CLK:时钟信号线,每个周期传输一个命令或数据位,频率为0~50Mhz;
      1. DAT0~3:数据线,用于主机与SD卡之间传输数据,除此之外DAT3还具有卡检测功能

在这里插入图片描述

  • 实际使用时,通常在DAT0~3和CMD引脚加上拉电阻,保护数据免受到总线浮动影响,当没有卡插入,或者所有卡驱动都处于高阻抗模式, DAT0-3 线应处上拉至高电平

0.3 SD 内存卡的操作模式

  • 控制器对 SD卡进行读写通信操作一般有两种通信接口可选,一种是 SPI 接口,另外一种就是 SDIO 接口,SDIO 全称是安全数字输入/输出接口,多媒体卡(MMC)、SD 卡、SD I/O 卡都有 SDIO 接口

在这里插入图片描述在这里插入图片描述- SPI 模式下,SD 卡运行在单线模式,只使用 DAT0,SD 卡命令请求按照下面的格式通过 SPI 发送
在这里插入图片描述

0.4 SD 内存卡的内部寄存器

在这里插入图片描述

  • 有一部分寄存器是强制要求在 SD 卡中实现的
      1. CID, Card identification number,卡识别号寄存器
      1. RCA, Relative card address, 卡地址寄存器,是主机驱动在卡初始化过程中动态分配的,SPI模式下,RCA寄存器不是必要
      1. CSD,Card Specific Data,卡标定参数寄存器
      1. SCR,SD Configuration Register,SD Configuration Register,卡配置寄存器
      1. OCR,Operation conditions register,卡操作工况寄存器
      1. SSR,SD Status,卡专有功能信息
      1. CSR,Card Status,卡状态寄存器

0.5 SD 总线协议

  • SD总线上传输的是命令、响应和数据,如下图所示,总线上传输的可能是命令 + 响应回复,或者命令 + 发送/接收数据 + 响应回复,数据以块为单位,通过 CRC校验进行传输
    在这里插入图片描述在这里插入图片描述
    在这里插入图片描述
  • SD总线上的通信基于由开始位启动并由停止位终止的命令和数据位流,如下图所示的命令包,一共48位,命令包的传输方向是主机到卡

在这里插入图片描述- 响应包由4种编码方式,长度为 48 位或者 136 位,响应包的传输方向是卡到主机

在这里插入图片描述

  • 命令包和响应包都通过 CMD 信号线传输,CMD 信号线会先传 MSB,最后传 LSB

  • 数据包通过 DAT 信号线传输,每条 DAT 独自计算 CRC 校验位,数据包的传输有两种方式,

      1. 单线模式发送,LSB 先发送,MSB 后发送,但是每一个 byte 中,MSB 先到,LSB 后到
      1. 多线模式,从 MSB 开始进行移位发送
  • 例如,发送数据如下所示
    在这里插入图片描述- 单线模式下,第一个 byte 先发,但是每个 byte 中 是 MSB 先发

在这里插入图片描述- 四线模式下,最高字节先发

在这里插入图片描述

1. SD 内存卡协议栈

  • 关于操作模式和卡状态,有以下的对应关系,初始化前卡处于 inactive 状态,主机也处于 inactive 模式,初始化后,首先主机进入卡识别模式,卡依次进入 idle(CMD0)、ready(CMD8)和 identification (CMD3) 状态,进入数据传输模式,卡又有 stand-by、transfer、sending、receiving、programming 和 disconnect 等状态

在这里插入图片描述

  • SD 卡的上电过程,上电后到发送 CMD0 前需要等待一段时间, 然后在 CMD8 之后循环发送一段时间的

在这里插入图片描述

1.1 卡识别模式

  • 在卡识别模式下,主机重置所有处于卡识别模式的卡,验证操作电压范围,识别卡,并要求它们发布相对卡地址(RCA),此操作将在每个卡自己的CMD线上分别执行,卡识别模式下的所有数据通信仅使用CMD线,在卡识别过程中,SD 总线时钟一般为 400kHz
  • SD 2.0 和 SD 3.0 的卡识别模式状态机存在差异,如下图,分别是 SD2.0 和 SD3.0 的状态机

在这里插入图片描述在这里插入图片描述

1.1.1 SD2.0 和 3.0 共有流程

  • 发送 GO_IDLE_STATE (CMD0),处于 inactive 状态的卡进入 idle 状态,但是对于其他状态的卡,CMD0 是无效的。CMD0会将卡的参数恢复默认值,如 RCA = 0x0,highest driving current、lowest speed,命令成功后卡进入 idle 模式
  • 在 rt-thread 中,发送 CMD0,等待卡进入 idle 状态即可
rt_int32_t mmcsd_go_idle(struct rt_mmcsd_host *host)
{
 ...

    rt_memset(&cmd, 0, sizeof(struct rt_mmcsd_cmd));

    cmd.cmd_code = GO_IDLE_STATE;
    cmd.arg = 0;
    cmd.flags = RESP_SPI_R1 | RESP_NONE | CMD_BC;

    err = mmcsd_send_cmd(host, &cmd, 0);

    mmcsd_delay_ms(1);
  • 发送 SEND_IF_COND (CMD8),验证SD存储卡接口的操作状态,主要是检查卡和主机的电源域的支持是否匹配,后面会对 CMD8 做详细分析,SD 2.0 及以上的协议中,需要 CMD8 和 ACMD41 配合使用,SD 1.0的卡不支持 CMD8,不会进行回复
  • 发送 SD_SEND_OP_COND (ACMD41),是一条给高容量卡用的扩展命令,旨在为SD存储卡主机提供一种机制来识别和拒绝与主机所需的VDD范围不匹配的存储卡,后面会进行详细分析,命令成功后会返回卡的OCR寄存器内容,包括卡电源域和卡高容量支持HCS等位域,命令成功后卡进入 ready 模式
  • 发送 ALL_SEND_CID (CMD2),以命令回复获取卡的识别号(CID),命令成功后卡进入 identify 模式
  • 在 rt-thread 中,发送 CMD2,获取回复卡 CID 寄存器的内容,一共有 128 个字节,解析可以获取卡的标识信息

在这里插入图片描述

rt_int32_t mmcsd_all_get_cid(struct rt_mmcsd_host *host, rt_uint32_t *cid)
{
    rt_int32_t err;
    struct rt_mmcsd_cmd cmd;

    rt_memset(&cmd, 0, sizeof(struct rt_mmcsd_cmd));

    cmd.cmd_code = ALL_SEND_CID;
    cmd.arg = 0;
    cmd.flags = RESP_R2 | CMD_BCR;

    err = mmcsd_send_cmd(host, &cmd, 3);
    if (err)
        return err;

    rt_memcpy(cid, cmd.resp, sizeof(rt_uint32_t) * 4);
  • 发送 CMD3 (SEND_RELATIVE_ADDR),请求卡发布一个新的相对地址(RCA),后续主机与这张卡之间的交互都会用 RCA 来寻址,命令成功后卡进入 stand-by 模式,值得注意的是,主机可以多次向卡申请 RCA,以最后一个 RCA 为准
  • RCA 是卡寄存器的内容,一共16位,RCA 的存在运行一个主机挂多张卡
  • 值得注意的是,对于 MMC 卡,不是由 SD 卡自动返回 RCA,而是主机主动为 MMC 卡设置一个 RCA
    在这里插入图片描述
rt_err_t mmcsd_get_card_addr(struct rt_mmcsd_host *host, rt_uint32_t *rca)
{
    rt_err_t err;
    struct rt_mmcsd_cmd cmd;

    rt_memset(&cmd, 0, sizeof(struct rt_mmcsd_cmd));

    cmd.cmd_code = SD_SEND_RELATIVE_ADDR;
    cmd.arg = 0;
    cmd.flags = RESP_R6 | CMD_BCR;

    err = mmcsd_send_cmd(host, &cmd, 3);
    if (err)
        return err;

    *rca = cmd.resp[0] >> 16;
    ...

1.1.2 SD 3.0 新增流程

  • 相比 SD 2.0,为了配合 UHS-I 卡, 3.0 在卡识别过程中 ,新增了CMD11等流程

在这里插入图片描述在这里插入图片描述

  • 发送 Voltage Switch Command (CMD11),在 1.8v 和 3.3v 之间切换电压
  • 可以选择发送 CMD42 锁定卡
  • 除CMD42外,UHS-I只支持4位总线模式,如果卡被锁定,主机需要以1位模式通过CMD42解锁卡,然后需要发出ACMD6来改变4位总线模式。不能保证在1位模式下操作。
  • 可以选择发送 CMD19 配置卡的采样时机,并补偿了时间预算中的静态延迟,包括工艺、电压和不同的PCB负载和偏差,CMD19 主要是指定读取一块数据大概需要的 clock 数目,从而帮助主机估算多块读写的耗时,更精准地控制总线上数据的持续时间

1.2 卡传输模式

  • 在卡传输模式下,主要的工作是获取卡的参数和读写卡的内容,下图是 SD 2.0 和 3.0 的卡传输模式状态机,

在这里插入图片描述
在这里插入图片描述- 可以看到,大部分流程是 SD 2.0 和 3.0 共有的

1.2.1 SD 2.0 和 3.0 共有流程

  • 进入数据传输模式后,还有一系列卡参数需要获取,包括SD卡的容量和扇区大小等十分重要的信息
  • 发送 SEND_CSD (CMD9),获取 Card status data,CSD 的结构有 1.0 版本和 2.0 版本,现在市面上绝大部分是高容量卡,用 2.0 标准

在这里插入图片描述- 以 vxworks 为例,定义了 CSD 的若干字段,其中包含了卡传输速率、class信息、最大读块大小等状态信息

typedef struct
    {
    UINT8       structure;         /* csd struct */
    UINT8       taac;              /* data read access-time */
    UINT8       nsac;              /* data read access-time in CLK */
    UINT8       tranSpeed;         /* data transfer rate */
    UINT16      commClass;         /* command class */
    UINT8       resvData0[4];      /* reserved data */
    UINT16      eraseSize;         /* erase size */
    UINT16      r2wFactor;         /* write speed factor */
    UINT16      fileFormat;        /* file format */
    } SDMMC_CSD;

在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 发送 SET_DSR (CMD4),配置驱动电流等级,不过这条命令好像很少用

  • 发送 SELECT_CARD (CMD7),选择指定 RCA 的卡,进入 transfer 模式,每一个主机只能选择一张卡进入 transfer 模式,主机可以通过先发送 CMD7,RCA=0x0 将所有的卡置成 standby 模式,再选择一张卡进入 transfer 模式,这样来切换数据传输用的卡

  • 读写卡是数据传输模式的主要工作,读写开始后,卡进入 sending 模式或者 receiving 模式,以下几条命令的配合使用可以控制整个读写过程

  • CMD12 用于停止当前的读写操作, 使得卡进入 transfer 模式

  • block write (CMD24 and CMD25)

  • block read (CMD17), multiple block read (CMD18)

  • send write protect (CMD30),数据读写成功后,卡进入 programing 状态,如果读写失败,进入 transfer 状态

  • send scr (ACMD51), 请求获取卡的SCR寄存器信息,其中比较重要的是 SD 卡的版本和卡支持的 bus width

在这里插入图片描述在这里插入图片描述
在这里插入图片描述

  • program CSD (CMD27), lock/unlock command (CMD42),general
    command in write mode (CMD56),这几条命令也较少使用

1.2.2 SD 3.0 新增流程

  • SD 2.0 在数据传输模式中新增了两条命令,CMD20 和 CMD23
  • CMD23用于设置多块读写的块数目,这条命令增加后,主机就不需要去控制 CMD12(STOP_TRANS)的发送事件,CMD12发送的时机不对会造成多块读写失败

在这里插入图片描述

CMD12已用于停止多块读/写操作。然而,CMD12是时间依赖性的,很难控制在精确的时间上发出CMD12的时间。由于UHS104卡在时钟和数据之间有很大的延迟变化,CMD23有利于主机停止多次读/写操作,而不是CMD12。主机不需要控制CMD12的时间。此命令适用于总是512字节块长度的读/写操作,然后SDSC卡不支持此命令。UHS104卡必须支持CMD23。

  • CMD20 用于支持速度 class

在这里插入图片描述

1.2.3 总线宽度设置

1.2.4 总线时钟速度

  • 总线时钟部分,具体可以参考 2.0 spec. 的 4.4 Clock Control 章节
  • 总线时钟控制可以降低时钟频率甚至关闭时钟,主要目的有两个
      1. 调节卡的功耗
      1. 控制数据流,避免总线上 under-run 和 over-run 情形
  • 例如,如果具有512字节的数据缓冲区的主机希望将数据传输到具有1 KByte写块的卡上。因此,为了保持连续的数据传输,从卡的角度来看,到卡的时钟应在前512字节后停止;然后,主机将用另外512字节填充其内部缓冲区;在主机中写块的后一半准备好后,它将通过重新启动时钟供应来继续将数据传输到卡,这样在传输数据过程就不需要被打断了
  • 总线时钟可以随时改变,出来 ACMD41 相关的情形下,

1.2.5 多块读写分析

  • 发送 CMD16 设置数据块大小,一般是 512 字节,通常,SD 卡的 CSD 寄存器中字段 READ_BL_LEN 指定了默认的块大小,一旦通过 CMD16 设置了块大小,READ_BL_LEN 下电前就失效了
  • 发送 CMD17 (READ_SINGLE_BLOCK) 开始进行块读,卡进入 receive 模式,读完成后才进入 transfer 模式
  • 发送 CMD18 (READ_MULTIPLE_BLOCK) 开始进行连续的多块读,读的过程以发送 STOP_TRANSMISSION(CMD12)结束,CDM12 的执行存在一个延迟,实际的数据传输在终止位之后结束,CRC 在每一块读后进行

在这里插入图片描述

  • 不按整块读在某些情况下也是支持的,具体可以参考 2.0 spec. 4.3.4 Data Write,这里不考虑了
  • 值得注意的是,有些卡写块数据的时间是不可预测的,在接收到一块数据,进行 CRC 检查后,可能会因为卡的写缓冲区满而无法放置新数据,这种情况下需要通过 SEND_STATUS (CMD13) 轮询卡的状态,等待之前的写操作完成,卡响应了 READY_FOR_DATA 再发送新数据。某些情况下,主机可能需要发送 CMD7 将卡的状态置为 disconnect 去释放 DAT 线,然后重新发送 CMD7 选中卡,将 DAT 线拉低

在这里插入图片描述
在这里插入图片描述- 为了加快多块写的速度,可以使用 ACMD23 在写之前对将要处理的块进行擦除,这样主机也可以提前直到有多少块马上要进行写操作;当然,如果 ACMD23 的块比实际写的块少,在多块写命令的时候,还是会对多的块进行擦除;如果,多块写还没有完成 CMD 12 就已经执行,有多少块数据写进介质是 undefined 的。总之,协议推荐在多块写之前发送 ACMD23 命令

  • 如果多块写的过程中断,可以通过 ACMD22 可以获取多块写成功的块数目,

在这里插入图片描述

  • 很多情况下,我们只是想擦除多块中原有的数据,而没有新的数据要写入,这时不需要用多块写命令,可以使用ERASE_WR_BLK_START (CMD32) 和 ERASE_WR_BLK_END (CMD33) 指定需要擦除的块范围,然后发送 ERASE (CMD38) 进行擦除,这样效率更高,也不需要总线带宽传输数据,需要注意的是,ERASE (CMD38) 会跳过写保护区域,这个区域是用写保护命令设置的,以上的命令在 2.0 spec 中都是支持的

  • 关于 CRC ,在命令传输过程中,CMD 线上每条命令及其响应都会进行 CRC 检查,块传输中,每块传输的数据都会进行 CRC 检查,如果 CRC 检查不通过,卡不会执行命令,也不会响应命令,在卡的状态寄存器中,会置位 COM_CRC_ERROR,具体的情况可以参考 2.0 spec 4.5 Cyclic Redundancy Code (CRC)

1.2.6 卡状态信息

  • SD存储卡支持以下两个状态信息,
      1. “卡状态”,Card Status:响应中显示的已执行命令的错误和状态信息
  • 通过发送 (SEND_STATUS) CMD13 主机可以请求卡返回状态寄存器的信息 (Card Status),卡状态是 32位信息,具体可以参考 2.0 spec Table 4-35: Card Status
      1. “SD状态”,SD Status:512位的扩展状态字段,支持SD存储卡的特殊功能和应用程序特定功能,SD Status 具体可以参考 2.0 spec Table 4-37: SD Status
  • 通过发送 CMD 13 主机可以请求卡 Card Status,下面是 Card Status 中几个常用字段,
    在这里插入图片描述
  • 通过发送 ACMD13 主机可以请求卡 SD Status,下面是 SD Status 中几个常用字段,

在这里插入图片描述在这里插入图片描述- 在 vxworks 中,(1) 获取 32 位 card status,(2) 检查 bit-8 判断卡是否可以接收数据

STATUS sdCmdSendStatus
    (
    VXB_DEVICE_ID pDev,
    UINT32 * stsData
    )
    {
...

    SD_CMD_FORM (cmd, SDMMC_CMD_SEND_STATUS,
                 SDMMC_CMD_ARG_RCA (pSdHardWare->rcaValue),
                 SDMMC_CMD_RSP_R1, FALSE);
    pSdHardWare->dataErr = 0x0;
    pSdHardWare->cmdErr = 0x0;

    rc = pSdHardWare->vxbSdCmdIssue(pSdHardWare->pHostDev, &cmd);
    if (rc == ERROR)
        return (ERROR);

    *stsData = cmd.cmdRsp[0]; (1)
    ..
 }

    while (1)
        {
        rc = sdCmdSendStatus (pDev, &rspValue);
        if (rc == ERROR)
            return ERROR;
        if (rspValue & CARD_STS_READY_FOR_DATA) (2)
            break;
        }
    return OK;
  • 在 vxworks 中,(1) 获取 64 位的 SD Status,在(2)处通过解析 64 位的 SD Status 信息,可以获得卡的 class 等信息
STATUS sdACmdSendSsr
    (
    VXB_DEVICE_ID  pDev,
    UINT8 * ssrData
    )
    {
...

    rc = sdCmdAppCmd (pDev);
    if (rc == ERROR)
        {
        (void)cacheDmaFree((void *)ssr);
        return ERROR;
        }

    SD_CMD_FORM (cmd, SD_ACMD_SD_STATUS, 0, SDMMC_CMD_RSP_R1, TRUE);
    SD_DATA_FORM (cmd, ssr, 1, sizeof(UINT8) * SD_SSR_SIZE, TRUE);
    pSdHardWare->dataErr = 0x0;
    pSdHardWare->cmdErr = 0x0;

    rc = pSdHardWare->vxbSdCmdIssue(pSdHardWare->pHostDev, &cmd);
    if (rc == ERROR)
        {
        (void)cacheDmaFree((void *)ssr);
        return ERROR;
        }

    bcopy ((char *)ssr, (char *)(ssrData), sizeof(UINT8) * SD_SSR_SIZE); (1)
...
}

    ret = sdACmdSendSsr (pDev, &(pSdInfo->ssr.ssrData[0]));
    if (ret == ERROR)
        {
        SD_CARD_DBG (SD_CARD_DBG_ERR,
                    "sdDecodeSsr() - send sdACmdSendSsr fault\n",
                    0, 0, 0, 0, 0 ,0);
        return ERROR;
        }
    pSdInfo->speedClass = pSdInfo->ssr.ssrData[8]; (2)
    pSdInfo->auSize = pSdInfo->ssr.ssrData[10] & 0xF0;
    pSdInfo->eraseSize = (UINT32)(pSdInfo->ssr.ssrData[11] << 8) | 
                                    pSdInfo->ssr.ssrData[12];
    pSdInfo->eraseTimeout = pSdInfo->ssr.ssrData[13] & 0xFC;
    pSdInfo->eraseOffset = pSdInfo->ssr.ssrData[13] & 0x03;

1.3 部分重要命令分析

1.3.1 Send Interface Condition Command (CMD8)

  • CMD8 主要安排了两个功能,

      1. 电压检查:检查板卡是否能在主机电源电压上工作。
      1. 启用现有命令和响应的扩展:恢复CMD8允许通过重新定义以前保留的位来将新的功能扩展到一些现有的命令。ACMD41被扩展到支持SDHC卡的初始化,该扩展也应用于SDXC卡。
  • CMD8 的参数格式如下图所示,bit19~16给出了主机支持的电压,bit15~8给了一个 check pattern,一般为 0xaa

在这里插入图片描述

  • 卡接收到 CMD8 后,会检查是否支持主机的电压,如果支持,并回复卡支持的电压范围和check pattern,如果不支持,卡不会回复,保持在 idle 状态

  • CMD8 常和 ACMD41 配合使用,通过 CMD8 的 VHS (bit19 ~ 16),主机告诉 SD 卡自己的供电范围,如果卡支持该供电范围就会返回,随后,通过 ACMD41,主机进一步确认卡的供电范围,并且通过 HCS 位,告诉卡主机是否支持高容量卡类型

  • 在 vxworks 中,设置主机电压的支持范围为 2.7v ~ 3.6 v,check pattern 为 0xaa,发送CMD8,如果等待 CMD8 回复超时,可能卡不支持或者是sd 1.0 版本的卡,如果收到回复,检查 check pattern,标记为 sd 2.0 卡

STATUS sdCmdSendIfCond
    (
    VXB_DEVICE_ID pDev,
    UINT32 capbility
    )
    {
...

    arg = (SD_CMD8_ARG_VHS_27_36 << SD_CMD8_ARG_VHS_SHIFT) |
           SD_CMD8_ARG_CHK_PATTERN;

    SD_CMD_FORM (cmd, SD_CMD_SEND_IF_COND, arg, SDMMC_CMD_RSP_R7, FALSE);
...

    if (cmd.cmdErr & SDMMC_CMD_ERR_TIMEOUT)
        {
        pSdHardWare->version = SD_VERSION_100; /* cmd-8 would failed for sd v1.0 */
        SD_LIB_DBG (SD_LIB_DBG_CMD,
                    "sdCmdSendIfCond() - sd target version is SD_VERSION_100\n",
                    0, 0, 0, 0, 0 ,0);
        return (OK);
        }
...

    if ((cmd.cmdRsp[0] & 0xff) != SD_CMD8_ARG_CHK_PATTERN)
        {
        SD_LIB_DBG (SD_LIB_DBG_CMD,
                    "sdCmdSendIfCond() - sd target version is SD_VERSION_UNKNOWN\n",
                    0, 0, 0, 0, 0 ,0);
        pSdHardWare->version = SD_VERSION_UNKNOWN;
        }
    else
        {
        SD_LIB_DBG (SD_LIB_DBG_CMD,
                    "sdCmdSendIfCond() - sd target version is SD_VERSION_200\n",
                    0, 0, 0, 0, 0 ,0);
        pSdHardWare->version = SD_VERSION_200;
        }

1.3.2 SD_APP_OP_COND(ACMD41)

  • SD_SEND_OP_COND(ACMD41)是一条给高容量卡用的扩展命令,旨在为SD存储卡主机提供一种机制来识别和拒绝与主机所需的VDD范围不匹配的存储卡,这是通过主机发送所需的VDD电压支持范围作为该命令的arg来完成的
  • 在发送 CMD8 后,需要发送 ACMD41 去初始化高容量卡,
  • 在 vxworks 中,ACMD41发送前已经发送了CMD8,发送时,首先发送 CMD_APP,如果 host 支持高容量卡,将 bit30 = OCR_CARD_CAP_STS 置位,然后在 arg 中设置主机支持的电压域,

在这里插入图片描述

  • ACMD41 回复的是卡的OCR寄存器内容,从 vxworks 的实现可以看到,通过 bit31 OCR_CARD_PWRUP_STS 判断 ACMD41 执行是否成功,通过 bit23-0,获取卡支持的电压域,通过 bit30 判断是否是高容量卡

在这里插入图片描述

STATUS sdMmcACmdSendOpCond
    (
    SDMMC_CARD * card
    )
    {
...

    rc = sdMmcCmdAppCmd (card);
    if (rc == ERROR)
        return ERROR;

    arg = card->host->capbility & OCR_VDD_VOL_MASK;
    if (card->info.version == SD_VERSION_200)
        {
        if (card->host->capbility & OCR_CARD_CAP_STS)
            arg |= OCR_CARD_CAP_STS;
        }

   /*
    * Set ACMD41 argument to 3.3V if both 3.0V & 3.3V are not reported
    * by host controller's capbilities register
    */

    if (!(arg & (OCR_VDD_VOL_29_30 | OCR_VDD_VOL_30_31 | OCR_VDD_VOL_32_33 | OCR_VDD_VOL_33_34)))
        arg |= (OCR_VDD_VOL_32_33 | OCR_VDD_VOL_33_34);
    SDMMC_CMD_FORM (cmd, SD_ACMD_SEND_OP_COND, arg, SDMMC_CMD_RSP_R3, FALSE);
...

    if (rc == OK && (cmd->cmdRsp[0] & OCR_CARD_PWRUP_STS))
        {
        card->info.voltage = cmd->cmdRsp[0] & OCR_VDD_VOL_MASK;
        card->info.highCapacity = ((cmd->cmdRsp[0] & OCR_CARD_CAP_STS) > 0) ? TRUE : FALSE;
        return OK;
        }

1.3.3 Switch function command (CMD6)

  • CMD6是SD卡用来扩展功能的一条重要命令。在SD协议1.00和2.00的版本是,该命令主要用于切换卡进入高速模式,其次用于扩展命令。而在3.00的协议中,该命令被赋予了更多的功能

  • CMD6命令共有查询模式和设置模式两种,查询模式主要用于查询是否支持该功能,而设置模式主要用于对该功能的设置

  • CMD6只在传输状态 "Transfer State"有效,一旦 card select 完成,使用 CMD6 只会返回默认功能值
    在这里插入图片描述

  • 作为对CMD6的响应,SD存储卡将在CMD行上发送R1响应,在DAT行上发送512位状态。从SD总线事务的角度来看,这是一个标准的单块读取事务,该命令的超时时间值为100 ms,与读取命令中的超时时间值相同。

在这里插入图片描述

  • CMD6支持6个功能组,每个功能组支持16个分支(功能)。在一个给定的功能组中,只能选择并激活一个函数,为了保持兼容性,功能0是默认功能,截至到 SD Spec 3.0,已经定义了4个功能组,如下所示

在这里插入图片描述

  • CMD6可在两种模式下使用:

      1. 模式0(检查功能)用于查询卡是否支持特定的功能或特定的功能,并识别所选功能下的该卡的最大电流消耗量。
      1. 模式1(设置功能)用于切换卡的功能。
  • 如下图所示,是 CMD6 argument 的定义,bit32 指定工作模式,bit23~bit0指定group的值

  • 在 vxworks 中实现了 CMD6 的上述功能

STATUS sdACmdSwitchFunc
    (
    VXB_DEVICE_ID pDev,
    void * buffer,
    UINT32 len,
    int    mode, /* mode set/inquiry */
    int    group, /* group 1~6 */
    int    val /* group function */
    )
...

    arg = mode; /* set mode bits */
    arg |= 0xffffff;
    arg &= ~(0xf << (group * 4));
    arg |= (val << (group * 4)); /* select one function group */

    SD_CMD_FORM (cmd, SD_CMD_SWITCH_FUNC, arg, SDMMC_CMD_RSP_R1, TRUE);
    SD_DATA_FORM (cmd, buffer, 1, len, TRUE); /* buffer to get status data */
...

在这里插入图片描述

  • CMD6 的返回值是一整块的数据(e.g 512字节),数据域定义如下,

在这里插入图片描述
在这里插入图片描述

  • 在 rt-thread 中,首先是查询卡支持的速率,然后设置卡支持的速率为 high speed
static rt_int32_t mmcsd_switch(struct rt_mmcsd_card *card)
{

...

    cmd.cmd_code = SD_SWITCH;
    cmd.arg = 0x00FFFFF1; /* group 1: access mode, inquiry mode */1)
    cmd.flags = RESP_R1 | CMD_ADTC;

    rt_memset(&data, 0, sizeof(struct rt_mmcsd_data));

    mmcsd_set_data_timeout(&data, card);

    data.blksize = 64;
    data.blks = 1;
    data.flags = DATA_DIR_READ;
    data.buf = (rt_uint32_t *)buf;

    rt_memset(&req, 0, sizeof(struct rt_mmcsd_req));

    req.cmd = &cmd;
    req.data = &data;

    mmcsd_send_request(host, &req);

    if (cmd.err || data.err)
    {
        goto err1;
    }

    if (buf[13] & 0x02) /* SDR50 supported */
        card->hs_max_data_rate = 50000000;

    rt_memset(&cmd, 0, sizeof(struct rt_mmcsd_cmd));

    cmd.cmd_code = SD_SWITCH;
    cmd.arg = 0x80FFFFF1; /* group 1: access mode, set mode */
    cmd.flags = RESP_R1 | CMD_ADTC;

    rt_memset(&data, 0, sizeof(struct rt_mmcsd_data));

    mmcsd_set_data_timeout(&data, card);

    data.blksize = 64;
    data.blks = 1;
    data.flags = DATA_DIR_READ;
    data.buf = (rt_uint32_t *)buf;

    rt_memset(&req, 0, sizeof(struct rt_mmcsd_req));

    req.cmd = &cmd;
    req.data = &data;

    mmcsd_send_request(host, &req);

    if (cmd.err || data.err)
    {
        goto err1;
    }

    if ((buf[16] & 0xF) != 1) /* check if high speed is enabled */
    {
        LOG_I("switching card to high speed failed!");
        goto err;
    }

    card->flags |= CARD_FLAG_HIGHSPEED;


在这里插入图片描述在这里插入图片描述

2. RT-Thread 协议栈分析

2.1 代码文件

  • mmc/sd 主机控制器驱动

drv_sdio.c
drv_sdio.h

  • mmc/sd 通用,主机和卡数据结构,命令定义

mmcsd_core.h
mmcsd_core.c
mmcsd_host.h
mmcsd_card.h
mmcsd_cmd.h

  • 块设备接口

block_dev.c

  • sd 内存卡相关

sd.c
sd.h

  • sdio 卡相关

sdio.c
sdio.h
sdio_func_ids.h

  • emmc 卡相关

mmc.c
mmc.h

  • spi sd 相关

spi_msd.c
spi_msd.h

  • 下面从块设备向下进行分析

2.2 mmc/sd 块设备(block_dev.c)

  • (1) 是 sd 卡实例,(2) 是块设备对应的文件系统分区信息,包括文件系统类型,分区的位置等,(3)是块设备的 sector 信息和一次 erase 的 sector 数目,(4)指定了块设备一次读写的块数目限制
struct mmcsd_blk_device
{
    struct rt_mmcsd_card *card; (1)
    rt_list_t list;
    struct rt_device dev;
    struct dfs_partition part; (2)
    struct rt_device_blk_geometry geometry; (3)
    rt_size_t max_req_size; (4)
};
  • 外部调用 rt_mmcsd_blk_probe 为 sd 卡创建一个块设备,(1)发送 CMD16,固定 block size 为 512,(2)读取 sd 卡的第一个 sector,(3)为每一个分区创建一个块设备
rt_int32_t rt_mmcsd_blk_probe(struct rt_mmcsd_card *card)
{
...

    err = mmcsd_set_blksize(card); (1)
    if(err)
    {
        return err;
    }

...

    status = rt_mmcsd_req_blk(card, 0, sector, 1, 0); (2)
    if (status == RT_EOK)
    {

...

        /* Always create the super node, given name is with allocated host id. */
        rt_snprintf(dname, sizeof(dname), "sd%d", host_id);
        blk_dev = rt_mmcsd_create_blkdev(card, (const char*)dname, RT_NULL); (3)
        if ( blk_dev == RT_NULL )
        {
            err = -RT_ENOMEM;
            goto exit_rt_mmcsd_blk_probe;
        }

}
  • 在创建块设备时,(1)单次请求可以从块设备读写的数据大小为 max_dma_segs * max_seg_size * 512 个字节,(2)注册了块设备的 device_ops,其中,(3)init、open、close 都没有实现功能,read 和 write 调用 sd 卡的块读块写,control 中支持获取块设备的信息的命令 RT_DEVICE_CTRL_BLK_GETGEOME
const static struct rt_device_ops mmcsd_blk_ops =
{
    rt_mmcsd_init,
    rt_mmcsd_open,
    rt_mmcsd_close, (3)
    rt_mmcsd_read,
    rt_mmcsd_write,
    rt_mmcsd_control
};

static struct mmcsd_blk_device * rt_mmcsd_create_blkdev(struct rt_mmcsd_card *card, const char* dname, struct dfs_partition* psPart)
{
...
    blk_dev->max_req_size = BLK_MIN((card->host->max_dma_segs *
                                     card->host->max_seg_size) >> 9,
                                    (card->host->max_blk_count *
                                     card->host->max_blk_size) >> 9);1/* register mmcsd device */
    blk_dev->dev.type = RT_Device_Class_Block;
    blk_dev->dev.ops  = &mmcsd_blk_ops; (2)

2.3 mmcsd 公用代码 (mmcsd_core.c)

  • mmcsd_core.c 主要提供 sd 卡的热插拔支持,对外提供访问 sd/emmc/sdio/spi_sd 等不同设备的统一接口
  • mmcsd 模块创建了一个 sd 卡检测线程和两个 mailbox,(1)通过 INIT_PREV_EXPORT 注册到 rt-thread 启动流程调用
int rt_mmcsd_core_init(void)
{
    rt_err_t ret;

    /* initialize detect SD cart thread */
    /* initialize mailbox and create detect SD card thread */
    ret = rt_mb_init(&mmcsd_detect_mb, "mmcsdmb",
        &mmcsd_detect_mb_pool[0], sizeof(mmcsd_detect_mb_pool) / sizeof(mmcsd_detect_mb_pool[0]),
        RT_IPC_FLAG_FIFO); (2)
    RT_ASSERT(ret == RT_EOK);

   ret = rt_mb_init(&mmcsd_hotpluge_mb, "mmcsdhotplugmb",
        &mmcsd_hotpluge_mb_pool[0], sizeof(mmcsd_hotpluge_mb_pool) / sizeof(mmcsd_hotpluge_mb_pool[0]),
        RT_IPC_FLAG_FIFO);
    RT_ASSERT(ret == RT_EOK);
     ret = rt_thread_init(&mmcsd_detect_thread, "mmcsd_detect", mmcsd_detect, RT_NULL,
                 &mmcsd_stack[0], RT_MMCSD_STACK_SIZE, RT_MMCSD_THREAD_PREORITY, 20);
    if (ret == RT_EOK)
    {
        rt_thread_startup(&mmcsd_detect_thread);
    }

    rt_sdio_init();

    return 0;
}
INIT_PREV_EXPORT(rt_mmcsd_core_init);1
  • mmcsd_detect_mb 用于传递和等待相关事件,其中,mmcsd_change 提供给其他模块,手动发送在 sd 卡状态改变事件,(2)之前在 rt_mmcsd_core_init 创建的 mmcsd_detect_thread 线程中,会阻塞等待 sd 卡状态改变事件,等到后开始卡的初始化流程
void mmcsd_change(struct rt_mmcsd_host *host) (1)
{
    rt_mb_send(&mmcsd_detect_mb, (rt_ubase_t)host);
}

void mmcsd_detect(void *param)
{
    struct rt_mmcsd_host *host;
    rt_uint32_t  ocr;
    rt_int32_t  err;

    while (1)
    {
        if (rt_mb_recv(&mmcsd_detect_mb, (rt_ubase_t *)&host, RT_WAITING_FOREVER) == RT_EOK) (2)
        {
        ...
  • mmcsd_hotpluge_mb 是另一个 mailbox,和 mmcsd_detect_mb 的方向相反,用于 mmcsd_detect 线程对外发出 sd 卡热插拔完成事件,外部模块调用 mmcsd_wait_cd_changed 等待 mmcsd_detect 里的 sd 卡插入/拔出处理完成
int mmcsd_wait_cd_changed(rt_int32_t timeout) (1)
{
    struct rt_mmcsd_host *host;
    if (rt_mb_recv(&mmcsd_hotpluge_mb, (rt_ubase_t *)&host, timeout) == RT_EOK)
    {
        if(host->card == RT_NULL)
        {
            return MMCSD_HOST_UNPLUGED;
        }
        else
        {
            return MMCSD_HOST_PLUGED;
        }
    }
    return -RT_ETIMEOUT;
}
RTM_EXPORT(mmcsd_wait_cd_changed);
  • 由此,mmcsd_core.c 对外封装了一系列接口,用于发送数据读写请求,发送命令请求等,同时还提供了 go_idle、get_cid、get_csd、select_card 等通用命令
void mmcsd_send_request(struct rt_mmcsd_host *host, struct rt_mmcsd_req *req);
rt_int32_t mmcsd_send_cmd(struct rt_mmcsd_host *host,
                          struct rt_mmcsd_cmd  *cmd,
                          int                   retries);
  • mmcsd_detect 线程在处理卡插入事件后,后尝试各种方式初始化卡,直到有一种成功,init_sd 是 sd 卡的初始化入口,初始化前先要设置 host 支持的电压域,(1) 在主机驱动中会通过 ocr 寄存器的位域指定主机支持的电压,0xFFFF80 表示 bit23-7 为1,支持 1.8 到 3.3 v 的电源域

在这里插入图片描述

  • (2)通过 __rt_ffs 从地位开始,找到最低的1所在的位,这里是 7,设置的电源域为 VDD_165_195 ,随后如果找到电源域(3), 在(4)开始初始化卡 ,初始化完成后为 sd 卡创建一个或多个块设备(5)
#define VDD_165_195     (1 << 7)    /* VDD voltage 1.65 - 1.95 */
#define VDD_20_21       (1 << 8)    /* VDD voltage 2.0 ~ 2.1 */
#define VDD_21_22       (1 << 9)    /* VDD voltage 2.1 ~ 2.2 */
#define VDD_22_23       (1 << 10)   /* VDD voltage 2.2 ~ 2.3 */
#define VDD_23_24       (1 << 11)   /* VDD voltage 2.3 ~ 2.4 */
#define VDD_24_25       (1 << 12)   /* VDD voltage 2.4 ~ 2.5 */
#define VDD_25_26       (1 << 13)   /* VDD voltage 2.5 ~ 2.6 */
#define VDD_26_27       (1 << 14)   /* VDD voltage 2.6 ~ 2.7 */
#define VDD_27_28       (1 << 15)   /* VDD voltage 2.7 ~ 2.8 */
#define VDD_28_29       (1 << 16)   /* VDD voltage 2.8 ~ 2.9 */
#define VDD_29_30       (1 << 17)   /* VDD voltage 2.9 ~ 3.0 */
#define VDD_30_31       (1 << 18)   /* VDD voltage 3.0 ~ 3.1 */
#define VDD_31_32       (1 << 19)   /* VDD voltage 3.1 ~ 3.2 */
#define VDD_32_33       (1 << 20)   /* VDD voltage 3.2 ~ 3.3 */
#define VDD_33_34       (1 << 21)   /* VDD voltage 3.3 ~ 3.4 */
#define VDD_34_35       (1 << 22)   /* VDD voltage 3.4 ~ 3.5 */
#define VDD_35_36       (1 << 23)   /* VDD voltage 3.5 ~ 3.6 */

		/* drv_sdio.c */
    host->valid_ocr = 0X00FFFF80; /* The voltage range supported is 1.65v-3.6v */ (1)

rt_uint32_t mmcsd_select_voltage(struct rt_mmcsd_host *host, rt_uint32_t ocr)
{
    int bit;
    extern int __rt_ffs(int value);

    ocr &= host->valid_ocr;

    bit = __rt_ffs(ocr); (2)
    if (bit)
    {
        bit -= 1;

        ocr &= 3 << bit;

        host->io_cfg.vdd = bit;
        mmcsd_set_iocfg(host);
    }

rt_int32_t init_sd(struct rt_mmcsd_host *host, rt_uint32_t ocr)
{
...

    current_ocr = mmcsd_select_voltage(host, ocr);

    /*
     * Can we support the voltage(s) of the card(s)?
     */
    if (!current_ocr)3{
        err = -RT_ERROR;
        goto err;
    }

   /*
     * Detect and init the card.
     */
    err = mmcsd_sd_init_card(host, current_ocr); (4)
    if (err)
        goto err;

    err = rt_mmcsd_blk_probe(host->card);5if (err)
        goto remove_card;

2.4 sd 卡相关 (sd.c)

  • 发送 CMD0 (1) 和 CMD8(2),(3)支持高容量卡,发送 ACMD41 (4),发送 CMD9 获取 CID,发送 CMD3 设置 RCA,发送 CMD9 获取 CSD
static rt_int32_t mmcsd_sd_init_card(struct rt_mmcsd_host *host,
                                     rt_uint32_t           ocr)
{
    struct rt_mmcsd_card *card;
    rt_int32_t err;
    rt_uint32_t resp[4];
    rt_uint32_t max_data_rate;

    mmcsd_go_idle(host); (1)

    /*
     * If SD_SEND_IF_COND indicates an SD 2.0
     * compliant card and we should set bit 30
     * of the ocr to indicate that we can handle
     * block-addressed SDHC cards.
     */
    err = mmcsd_send_if_cond(host, ocr); (2)
    if (!err)
        ocr |= 1 << 30;3)

    err = mmcsd_send_app_op_cond(host, ocr, RT_NULL);4if (err)
        goto err;

		err = mmcsd_all_get_cid(host, resp);

    err = mmcsd_get_card_addr(host, &card->rca);
    if (err)
        goto err1;

    err = mmcsd_get_csd(card, card->resp_csd);
    if (err)
        goto err1;
  • 5
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值