STM32H743-ARM例程13-SDIO

目录

实验平台

硬件:银杏科技GT7000双核心开发板-ARM-STM32H743XIH6,银杏科技iToolXE仿真器

软件:最新版本STM32CubeH7固件库STM32CubeMX v6.10.0,开发板环境MDK v5.35,串口工具putty

SDIO

SDIO简介

SD 卡(Secure Digital Memory Card) 在我们生活中已经非常普遍了,控制器对 SD 卡进行读写通信操作一般有两种通信接口可选,一种是 SPI 接口,另外一种就是 SDIO 接口。SDIO 全称是安全数字输入/输出接口,多媒体卡(MMC)、SD 卡、SD I/O 卡都有 SDIO 接口。 MMC 卡可以说是 SD 卡的前身,现阶段已经用得很少。STM32H743x系列控制器有两个SDIO主机接口,它可以与MMC卡、SD卡、SD I/O卡以及CE-ATA设备进行数据传输。SD I/O卡是在SD内存卡接口的基础上发展起来的外设接口,SDIO接口兼容以前的SD内存卡,并且可以连接SDIO接口的设备,目前根据SDIO协议的SPEC,SDIO接口支持的设备总类有蓝牙,网卡,电视卡等。CE-ATA是一种使用MMC接口界面,ATA指令集的接口,CE-ATA相较于ATA节省了很多管脚,有助于简化消费电子(CE)和ATA硬盘的结合,例如轻薄笔记本硬盘设计的硬盘高速通讯接口。

各种媒体卡都有自己的系统规范,想了解的读者可以参考他们的官网:多媒体卡协会网站www.mmca.orgSD卡协会网站www.sdcard.orgCE-ATA工作组网站www.ce-ata.org

到目前为止,SDIO接口的设备整体概括图如下图所示:

本章实验将针对SD卡进行读写测试,STM32H743x系列控制器只支持SD卡规范版本2.0, 即只支持标准容量SD和高容量SDHC标准卡,不支持超大容量SDXC标准卡,所以可以支持的最高卡容量是32GB。

SD卡物理结构

SD卡也有着自己的寄存器,但是并不能直接进行读写,只能通过对于的命令访问,SDIO协议定义64个命令来实现一些特殊功能,SD卡接收到命令后,根据接受到的命令对内部寄存器进行修改,

下图就是我们对SD卡发送命令的数据通道:

SDIO总线

SD卡一般支持两种接口,一种SDIO,另一种是SPI,本章实验我们用的SDIO接口操作方式,SPI我们后续章节我详细介绍。另外,STM32H743x系列控制器的SDIO是不支持SPI通信模式的,如果需要用到SPI通信只能使用SPI外设。

SD总线上的通信以命令/数据的比特流为基础,起始位表示数据的传输的开始,终止位表示数据传输的结束。

  • 命令(Command):命令是开启一个操作的标志。命令可以从主机发送到单个卡(寻址命令),也可以发送到所有已连接的卡(广播命令),命令通过CMD线以串行方式传输
  • 响应(Response):响应是卡收到命令后,需要发送到主机的命令的应答,响应通过CMD线以串行方式传输
  • 数据:数据的传输是双向的,通过Data线传输。

命令

SD命令由主机发出,以广播命令和寻址命令为例,广播命令是针对与SD主机总线连接的所有从设备发送的,寻址命令是指定某个地址设备进行命令传输。命令字段的组成如下:

  • 起始位是1bit的 0
  • 传输标志是1bit的 1,该位为1时表示命令,方向为主机传输到SD卡,该位为0时表示响应,方向为SD卡传输到主机。
  • 命令号长度是6bit,代表命令的编号它固定占用6bit,例如对于CMD17(代号:CMD0~CMD63),这6bit就应该是 17 的二进制表示 010001,每个命令都有特定的用途, 部分命令不适用于SD卡操作,只是专门用于MMC卡或者SD I/O卡
  • 参数/地址长度是32bit,不同命令有不同的参数要求
  • CRC7是校验码,长度是7bit,需要根据命令包的 [47:8] 位来计算 (起始位、方向位、命令号、参数都参与 CRC7 的计算)
  • 结束为是1bit的 1

命令类型:

SD 命令有 4 种类型:

  • 无响应广播命令(bc),发送到所有卡,不返回任务响应;
  • 带响应广播命令(bcr),发送到所有卡,同时接收来自所有卡响应;
  • 寻址命令(ac),发送到选定卡,DAT 线无数据传输;
  • 寻址数据传输命令(adtc),发送到选定卡,DAT线有数据传输。

详细命令描述本章不再赘述,有兴趣读者可以参考文章【SDIO】SD2.0协议分析总结(三)-- SD卡相关命令介绍

响应

响应 (response) 的方向都是从SD卡向主机发出,发生在 sdcmd 信号上,且只可能紧随一个命令后。部分命令要求SD卡作出响应,这些响应多用于反馈SD卡的状态。SDIO响应有7种类型 (R1-7)。其中 SD卡没有 R4、R5类型响应。各个类型响应具体情况如下表所示。

SD卡操作模式

SD卡识别模式

在系统复位后,主机处于卡识别模式,认其工作电压范围,识别SD卡类型, 并且获取SD卡的相对地址(卡相对地址较短,便于寻址)。

在主机和SD卡开始通信时,主机可能不知道SD卡支持的电压,SD卡也可能不知道它是否支持当前提供的电压。主机发出一个复位命令(CMD0),同时假定它电压可能被SD卡支持。为了验证电压,SD卡标准V2.0版本文档中定义了以下新命令,SEND_IF_COND(CMD8)命令就是用于验证卡接口操作条件的(主要是电压支持)。卡会根据命令的参数来检测操作条件匹配性,如果卡支持主机电压就产生响应,否则不响应。

SD_SEND_OP_COND (ACMD41)被设计为为主机提供一种机制来识别和拒绝与主机所提供的VDD范围不匹配的卡。不能在指定范围内进行数据传输的SD卡,应放弃总线操作,进入Inactive 状态。OCR寄存器定义了相关的电压等级。卡在响应ACMD41之后进入准备状态,不响应ACMD41的卡为不可用卡,进入无效状态。

注意:ACMD41是特定应用程序的命令,因此APP_CMD (CMD55)应该始终在ACMD41之前被发送。在idle_state中用于CMD55的RCA将是SD卡的默认值(RCA = 0 x0000)。

ALL_SEND_CID(CMD2)用来控制所有卡返回它们的卡识别号(CID),处于准备状态的卡在发送CID之后就进入识别状态。之后主机就发送SEND_RELATIVE_ADDR(CMD3)命令, 让卡自己推荐一个相对地址(RCA)并响应命令。这个RCA是16bit地址,而CID是128bit地址,使用RCA简化通信。卡在接收到CMD3并发出响应后就进入数据传输模式, 并处于待机状态,主机在获取所有卡RCA之后也进入数据传输模式。

数据传输模式

在数据模式下我们可以对SD卡的存储块进行读写访问操作。SD卡数据包有两种格式,一种是常规数据(8bit宽),它先发低字节再发高字节,而每个字节则是先发高位再发低位,4线传输示意如图 8位宽数据包传输。

当使用4线模式传输8-bit结构的数据时,数据仍按MSB先发送的原则,DAT[3:0]的高位发送高数据位,低位发送低数据位。硬件支持的情况下,使用4线传输可以提升传输速率。

另一种数据包发送格式是宽位数据包格式,宽数据(SD内存寄存器):宽数据以MSB位优先发送。一次按512字节传输,主机发出ACMD13命令后SD卡将SSR寄存器内容通过DAT线发送给主机。

只有SD卡系统处于数据传输模式下才可以进行数据读写操作。数据传输模式下可以将主机SD时钟频率设置为FPP,默认最高为25MHz,频率切换可以通过CMD4命令来实现。数据传输模式下,SD卡状态转换过程见下图

CMD7用于选择一张SD卡并将其置于传输状态,在同一时刻只能有一张卡处于传输状态。如果先前选择的卡处于传输状态,它与主机的连接将被释放,它将返回到待机状态。当CMD7以保留的相对卡片地址"0x0000"发出时,所有卡片都被退回到待机状态。

据传输模式下的数据通信都是主机和目标卡之间通过寻址命令点对点进行的。卡处于传输状态下可以通过命令对卡进行数据读写、擦除。CMD12可以中断正在进行的数据通信,让卡返回到传输状态。CMD0和CMD15会中止任何数据编程操作, 返回卡识别模式,这可能导致卡数据被损坏。

SDMMC

SDMMC框图

SDMMC 由三部分组成:

  • AHB 从接口访问 SDMMC 适配器寄存器,并且生成中断信号和 IDMA 控制信号。
  • SDMMC 适配器块提供特定于 MMC/SD/SD I/O 卡的所有功能,如时钟生成单元、命令
    和数据传输。
  • 内部 DMA(IDMA)模块及其 AHB 主接口。

      图中,右侧的信号为SDMMC对外引脚(信号),如下图所示:

SDMMC_CK、SDMMC_D[3:0]、SDMMC_CMD这几根线即可正常驱动SD卡,复位后默认情况下SDMMC_D0用于数据传输。初始化后主机可以改变数据总线的宽度(通过ACMD6命令设置)。

SDMMC_CMD有两种操作模式:

①用于初始化时的开路模式(仅用于MMC版本V3.31或之前版本)

②用于命令传输的推挽模式(SD/SD I/O卡和MMC V4.2在初始化时也使用推挽驱动)

STM32H7支持DS(Default Speed)、HS(High Speed),SDR以及DDR50等多种模式,见表格 SDIO总线速度模式。

SDR表示单倍速率(时钟单边沿采样),DDR表示双倍速率(时钟双边沿采样),由表可知,STM32H7支持最高的SDR104总线速度模式,理论上传输速度最高可达104MB/s(实际上由于IO口速度无法达到208Mhz,所以,是达不到104MB/s的传输速度的)。这里的DS、HS、SDR12、SDR25、DDR50、SDR50、SDR104等速度模式,实际上是超高速I卡所支持的一些速度等级。

我们的SD卡接口是3.3V供电的,所以,仅支持DS和HS模式(最高25MB/s的传输速度)。

SDMMC时钟

在SDMMC功能框图中我们可以发现,SDMMC总共有3个时钟,分别是:
卡时钟(SDMMC_CK): SDMMC(Secure Digital MultiMediaCard)控制器输出的时钟信号,用于同步主机与SD卡/设备之间的通信。每个时钟周期在命令和数据线上传输1位命令或数据‌。标准模式下:频率范围≤25MHz,卡识别:‌频率范围≤400kHz。
SDMMC适配器内核时钟(sdmmc_ker_ck): SDMMC控制器的内核时钟输入,用于驱动SDMMC适配器内部逻辑。它通常与AHB总线时钟(sdmmc_hclk)协同工作,为SDMMC控制器提供时钟基准‌,默认选择来自pll1_q_ck,其频率一般为240Mhz,并用于产生SDMMC_CK时钟。
AHB3总线接口时钟(sdmmc_hclk): SDMMC控制器的AHB总线接口时钟,用于驱动SDMMC控制器的AHB总线接口。其频率通常为HCLK(AHB总线时钟)的一半。

原理图

STM32CubeMX生成工程

我们参考前面章节STM32H743-结合CubeMX新建HAL库MDK工程,打开CubeMX软件,重复步骤不再展示,我们来看配置SDMMC部分如下图所示:


实验代码

1. 主函数

python 复制代码
在这里插入代码片`int main(void)
{

    HAL_Init();

    SystemClock_Config();

    MX_GPIO_Init();
    MX_SDMMC1_SD_Init();
    MX_USART6_UART_Init();

    uart6.printf("Micro SD Card Test... \r\n"); 
    uint8_t read_buf[512];      // 读数据缓存
    uint8_t write_buf[512];     // 写数据缓存
    HAL_SD_ConfigWideBusOperation(&hsd1, SDMMC_BUS_WIDE_4B);
    /* SD卡状态 */
    int sdcard_status = 0;
    HAL_SD_CardCIDTypeDef sdcard_cid;
    /* 获取SD卡状态 */
    sdcard_status = HAL_SD_GetCardState(&hsd1);
    // 处于数据传输模式的传输状态
    if(sdcard_status == HAL_SD_CARD_TRANSFER)
    {
        uart6.printf("SD card init ok!\r\n\r\n");
        // 打印SD卡基本信息
        uart6.printf("SD card information! \r\n");
        // 容量信息
        uart6.printf("CardCapacity(Byte): %llu \r\n",((unsigned long long)hsd1.SdCard.BlockSize * hsd1.SdCard.BlockNbr));
        // 块大小 
        uart6.printf("CardBlockSize(Byte): %d \r\n", hsd1.SdCard.BlockSize);
        // 有多少个块
        uart6.printf("CardBlockNumber: %d \r\n", hsd1.SdCard.BlockNbr); 
        uart6.printf("RCA: %d \r\n", hsd1.SdCard.RelCardAdd);   
        uart6.printf("CardType: %d \r\n", hsd1.SdCard.CardType);
        // 读取并打印SD卡的CID信息
        HAL_SD_GetCardCID(&hsd1, &sdcard_cid);
        // 制造商
        uart6.printf("ManufacturerID: %d \r\n",sdcard_cid.ManufacturerID);
    }
    else
    {
        uart6.printf("SD card init fail! \r\n" );
        return 0;
    }
        
    /* 读取未操作之前的数据 */
    uart6.printf("------------------- Read SD card block data Test ------------------\r\n");
    /*
        读一个扇区的数据:
        0: 从第0个扇区开始。
        1:读一个扇区的数据。
        0xffff:等待时间。
        note:也就是只读了第0个扇区。
    */
    sdcard_status = HAL_SD_ReadBlocks(&hsd1, (uint8_t *)read_buf, 0, 1, 0xffff);
    if(sdcard_status == HAL_OK)
    { 
        uart6.printf("Read block data ok! \r\n");
        for(int i = 0; i < 512; i++)
        {
            uart6.printf("0x%02x ", read_buf[i]);
            if((i+1)%16 == 0)
            {
                uart6.printf("\r\n");
            }
        }
    }
    else
    {
        uart6.printf("Read block data fail! status = %d \r\n", sdcard_status);
    }
        

    /* 向SD卡块写入数据 */
    uart6.printf("------------------- Write SD card block data Test ------------------\r\n");
    /* 填充缓冲区数据 */
    for(int i = 0; i < 512; i++)
    {
        write_buf[i] = i % 256;
    }
    // 开始写入数据
    /*
        写一个扇区的数据:
        0: 从第0个扇区开始。
        1:写一个扇区的数据。
        0xffff:等待时间。
        note:也就是只写了第0个扇区。
    */
    sdcard_status = HAL_SD_WriteBlocks(&hsd1, (uint8_t *)write_buf, 0, 1, 0xffff);
    if(sdcard_status == HAL_OK)
    { 
        /* 传输完成不代表写入完成,因此要等待SD卡状态变为可传输状态。擦除操作也是一样。 */
        uart6.printf("Writing block data. state = %d \r\n", HAL_SD_GetCardState(&hsd1));
        while (HAL_SD_GetCardState(&hsd1) == HAL_SD_CARD_PROGRAMMING);
        uart6.printf("Write block data ok,state = %d \r\n", HAL_SD_GetCardState(&hsd1));
    }
    else
    {
        uart6.printf("Write block data fail! status = %d \r\n", sdcard_status);
    }
        
    /* 读取写入之后的数据 */
    uart6.printf("------------------- Read SD card block data after Write ------------------\r\n");
    sdcard_status = HAL_SD_ReadBlocks(&hsd1, (uint8_t *)read_buf, 0, 1, 0xffff);
    if(sdcard_status == HAL_OK)
    { 
        uart6.printf("Read block data ok! \r\n");
        for(int i = 0; i < 512; i++)
        {
            uart6.printf("0x%02x ", read_buf[i]);
            if((i+1)%16 == 0)
            {
                uart6.printf("\r\n");
            }
        }
    }
    else
    {
        uart6.printf("Read block data fail! status = %d \r\n", sdcard_status);
    }
        
    /* 擦除SD卡块 */
    uart6.printf("------------------- Block Erase -------------------------------\r\n");
    /*
        擦除512个扇区的数据:
        0: 从第0个扇区开始。
        1:一直擦除到512扇区。
        note:擦除第0到第512个扇区数据,也包括0和512,也就是一共512个。
    */
    sdcard_status = HAL_SD_Erase(&hsd1, 0, 512);
    // 等待擦除完毕
    if (sdcard_status == HAL_OK)
    {   
        uart6.printf("Erasing block. state = %d \r\n", HAL_SD_GetCardState(&hsd1));
        while (HAL_SD_GetCardState(&hsd1) == HAL_SD_CARD_PROGRAMMING);
        uart6.printf("Erase block ok state = %d \r\n", HAL_SD_GetCardState(&hsd1));
    }
    else
    {
        uart6.printf("Erase block fail! status = %d \r\n", sdcard_status);
    }

    /* 读取擦除之后的数据 */
    uart6.printf("------------------- Read SD card block data after Erase ------------------\r\n");
    sdcard_status = HAL_SD_ReadBlocks(&hsd1, (uint8_t *)read_buf, 0, 1, 0xffff);
    if(sdcard_status == HAL_OK)
    { 
        uart6.printf("Read block data ok \r\n" );
        for(int i = 0; i < 512; i++)
        {
            uart6.printf("0x%02x ", read_buf[i]);
            if((i+1)%16 == 0)
            {
                uart6.printf("\r\n");
            }
        }
    }
    else
    {
        uart6.printf("Read block data fail! status = %d \r\n", sdcard_status);
    }

    uart6.printf("------------------- Over ------------------\r\n");
        
    while (1)
    {

    }

}

2. SDMMC初始化函数

c 复制代码
void MX_SDMMC1_SD_Init(void)
{

    hsd1.Instance = SDMMC1;
    hsd1.Init.ClockEdge = SDMMC_CLOCK_EDGE_RISING;                                                          //上升沿
    hsd1.Init.ClockPowerSave = SDMMC_CLOCK_POWER_SAVE_DISABLE;                              //空闲时不关闭时钟电源
    hsd1.Init.BusWide = SDMMC_BUS_WIDE_4B;                                                                              //4位数据线
    hsd1.Init.HardwareFlowControl = SDMMC_HARDWARE_FLOW_CONTROL_DISABLE;        //关闭硬件流控制
    hsd1.Init.ClockDiv = 10;                                                                                                                    //分频
    if (HAL_SD_Init(&hsd1) != HAL_OK)
    {
    Error_Handler();
    }

}

3. SD卡写数据块函数

c 复制代码
HAL_SD_WriteBlocks(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks, uint32_t Timeout)

//*hsd:指向SD卡的指针
//*pData:指向要写入的数据的指针
//BlockAdd:数据块地址
//NumberOfBlocks:写入的块数
//Timeout:写入超时设置

4. SD读数据块函数

c 复制代码
HAL_SD_ReadBlocks(SD_HandleTypeDef *hsd, uint8_t *pData, uint32_t BlockAdd, uint32_t NumberOfBlocks, uint32_t Timeout)

//*hsd:指向SD卡的指针
//*pData:指向数据读取后存放区的指针
//BlockAdd:数据块地址
//NumberOfBlocks:读取的块数
//Timeout:读取超时设置

5. SDMMC外设管理结构体

c 复制代码
typedef struct 
{
    SD_TypeDef *Instance;                       /*!< SDMMC 寄存器基地址*/
    SD_InitTypeDef Init;                         /*!< SD 初始化结构体*/
    HAL_LockTypeDef Lock;                     /*!< SD 锁资源*/
    uint32_t *pTxBuffPtr;                        /*!< 存放发送数据地址的指针*/
    uint32_t TxXferSize;                         /*!< 发送数据的大小 */
    uint32_t *pRxBuffPtr;                        /*!< 存放接受数据地址的指针*/
    uint32_t RxXferSize;                         /*!< 接受数据的大小*/
    __IO uint32_t Context;                        /*!< SDMMC 的工作模式 */
    __IO HAL_SD_StateTypeDef State;             /*!< SD 卡的状态值*/
    __IO uint32_t ErrorCode;                      /*!< SD 错误操作返回值*/
    HAL_SD_CardInfoTypeDef SdCard;             /*!< SD 卡的信息*/
    uint32_t CSD[4];                             /*!< SD 卡的 CSD 寄存器值*/
    uint32_t CID[4];                             /*!< SD 卡的 CID 寄存器值*/
} SD_HandleTypeDef;

6. SDMMC 数据初始化结构体

c 复制代码
typedef struct 
{
    uint32_t DataTimeOut;       // 数据传输超时
    uint32_t DataLength;        // 数据长度
    uint32_t DataBlockSize;     // 数据块大小
    uint32_t TransferDir;        // 数据传输方向
    uint32_t TransferMode;      // 数据传输模式
    uint32_t DPSM;            // 数据路径状态机
} SDMMC_DataInitTypeDef;

7. SD卡信息结构体

c 复制代码
typedef struct
{
    uint32_t CardType;                                /*卡种类*/
    uint32_t CardVersion;                              /*卡版本*/
    uint32_t Class;                                    /*卡类*/
    uint32_t RelCardAdd;                              /*相对卡地址*/
    uint32_t BlockNbr;                                /*以块为单位指定卡容量*/
    uint32_t BlockSize;                                /*一个块大小(以字节为单位)*/
    uint32_t LogBlockNbr;                             /*以块为单位指定卡逻辑容量*/
    uint32_t LogBlockSize;                             /*逻辑块大小(以字节为单位)*/
    uint32_t CardSpeed;                               /*卡速度*/
}HAL_SD_CardInfoTypeDef;

实验现象

我们插入SD卡,运行程序,终端显示出SD卡的相关信息,并输出写入SD卡的数据。

相关推荐
机器视觉知识推荐、就业指导2 小时前
STM32 外设驱动模块:DHT11温湿度传感器模块
stm32·单片机·嵌入式硬件
GilgameshJSS2 小时前
STM32H743-ARM例程8-EXTI外部中断
c语言·arm开发·stm32·单片机·嵌入式硬件·学习
月盈缺2 小时前
学习嵌入式的第四十三天——ARM——I2C
arm开发·学习
charlie1145141912 小时前
精读 C++20 设计模式:行为型设计模式——观察者模式
c++·学习·观察者模式·设计模式·程序设计·c++20
lingzhilab2 小时前
零知IDE——STM32F407VET6与GP2Y1014AU的粉尘监测系统实现
stm32·单片机·嵌入式硬件
熊猫钓鱼>_>2 小时前
深度解析学习率:梯度下降中的“速度与激情“
学习
charlie1145141913 小时前
精读 C++20 设计模式:行为型设计模式 — 备忘录模式
c++·学习·设计模式·c++20·备忘录模式
迎風吹頭髮3 小时前
UNIX下C语言编程与实践15-UNIX 文件系统三级结构:目录、i 节点、数据块的协同工作机制
java·c语言·unix
迎風吹頭髮3 小时前
UNIX下C语言编程与实践6-Make 工具与 Makefile 编写:从基础语法到复杂项目构建实战
运维·c语言·unix