12

Nuoeriris · 2022年02月18日

使用MM32F3270的SDIO驱动SD卡

SDIO(Secure Digital Input and Output)中文名称:安全数字输入输出,SDIO在SD标准上定义了一种外设接口。SDIO主要有两类应用——可移动和不可移动。可移动设备作为Palm和Windows Mobile的扩展设备,用来增加蓝牙、照相机、GPS和802.11b功能。不可移动设备遵循相同的电气标准,但不要求符合物理标准。某些手机内包含通过SDIO连接CPU的802.11芯片。此举将“珍贵”的I/ O管脚资源用于更重要的功能。
蓝牙、照相机、GPS和802.11b设备有专为它们定义的应用规范。这些应用规范与为PCI和USB设备定义的类规范很相像。它们允许任何宿主设备与任意外设“通话”,只要它们都支持应用规范。
SDIO和SD卡规范间的一个重要区别是增加了低速标准。SDIO卡只需要SPI和1位SD传输模式。低速卡的目标应用是以最小的硬件开支支持低速I/ O能力。低速卡支持类似调制解调器、条码扫描仪和GPS接受器等应用。对“组合”卡(存储器+ SDIO)而言,全速和4位操作对卡内存储器和SDIO部分都是强制要求的。
MM32F3270系列控制器支持SDIO接口,本文在接下来会对MM32F3270的SDIO进行介绍,并通过实验演示SDIO驱动SD卡。

1. SDIO 简介

SD/MMC/SDIO 控制器是 AMBA AHB 从外设,用于控制外部 SD/MMC/SDIO 卡,并支持 DSP/MCU的读/写访问。它作为主机与连接的 SD/MMC/SDIO 卡进行通信。所述控制器是基于 AMBA HCLK 域的完全同步设计。

1.1 SDIO 功能框图

 title=

图1 SDIO功能框图

  1. AHB 从模式接口:为 32 位 AHB 总线提供接口。
  2. FIFO 控制:产生握手信号到 DMA 硬件接口,并控制对外部数据 FIFO(128x32)的读/写访问。
  3. 总线接口单元:包括控制寄存器和命令缓冲单元。
  4. 多重块控制:控制多块数据的读写。
  5. 时钟控制:通用时钟基于寄存器中定义的分频值。
  6. 命令通路:从总线接口单元或 irq 响应中加载新的命令,然后发送命令,并接收响应 crc7 检查和 8 个空时钟。
  7. 数据通路:发送和接收数据,用 crc16 检查。
  8. 缓冲接口:控制对数据 FIFO 的读写控制信号。
1.2 SDIO 功能描述
  1. 完全兼容 SD 记忆卡规格 1.0
  2. 完全兼容 SD 存储卡规格 1.1(高速)
  3. 完全兼容 SD 记忆卡规格 2.0(SDHC)
  4. 完全兼容 MMC 系统规格 2.0~4.2
  5. 完全兼容 SDIO 存储卡规格 1.1.0
  6. 标准的 MMC 模式接口支持
  7. 可编程时钟速率
  8. 自动命令/响应 CRC 生成/检查
  9. 自动数据 CRC 生成/检查
  10. 可编程超时检测
  11. AMBA 2.0 32 位 AHB 接口
  12. 用于 AHB 数据访问的总共外部 128*32 数据 FIFO
  13. 32 位 DMA 硬件接口,用于更快的 DMA 访问
  14. DMA 接口可以配置为启用/禁用
  15. DMA 请求是可配置的
  16. 组合中断输出

2. SD 存储卡

2.1 Read-Write 属性

根据 Read-Write 属性不同可分为两种类型的 SD 存储卡:

  1. 可读可写(RW)卡(FLASH, OTP, MTP)。这些卡通常作为空白媒介来售卖,用于海量终端用户的视频、音频或数字图像记录的存储。
  2. 只读存储卡(ROM)。这些卡是用固定的数据内容生产出来的,它们通常用作软件、音频、视频等的传播媒介。
2.2 电源电压

根据工作电源电压不同可分为两种类型的 SD 存储卡:

  1. 高电压 SD 存储卡,可以在 2.7-3.6V 的电压范围内工作;
  2. 双电压 SD 存储卡,可以在 1.6-3.6V 的电压范围内工作。
2.3 卡容量

根据卡容量不同可分为两种类型的 SD 存储卡:

  1. 标准容量的 SD 存储卡支持最大 2G 字节的容量。所有版本的物理规格都定义了标准容量 SD 存储卡;
  2. 高容量 SD 存储卡支持超过 2G 字节的容量,此版本规范限制容量高达 32GB。高容量 SD 存储卡是物理层规范版本 2.00 中新定义的。
2.4 传输速度

定义了四种速度等级,表示 SD 卡的最低性能:

  1. Class0:这类卡不指定性能;
  2. Class2:速度不低于 2MB/s;
  3. Class4:速度不低于 4MB/s;
  4. Class6:速度不低于 6MB/s;

大容量 SD 存储卡应支持速度等级规范,性能大于或等于 2 级。

2.5 总线拓扑

SD 卡系统定义了两种通信协议: SD 和 SPI。主机系统可以选择任意一种。当收到 reset 命令的时候, SD 卡通过主机的信息来决定使用何种模式,并且之后的通讯都会使用相同模式。不推荐多卡槽用共同的总线信号。一个单独的 SD 总线应该连接一个单独的 SD 卡。 SD 总线包含下面的信号:

  1. CLK:时钟信号;
  2. CMD:双向命令/响应信号;
  3. DAT0-DAT3:双向数据信号;
  4. Vdd, Vss1, Vss2:电源和地信号。
2.6 总线协议

SD 总线通信:

  1. Command:命令是一次操作开始的令牌,从主机发送到一个卡片(编址命令)或者连接到主机的所有卡片(广播命令)。命令在 CMD 线上连续传输。
  2. Response:响应是从已寻址的卡或从所有连接上的卡发送到主机的令牌,作为对先前接收到指令的应答。响应在 CMD 线上连续传输。
  3. Data:数据可以通过 DATA 线双向传输。

卡片寻址通过使用会话地址来实现,会话地址会在初始化阶段分配给卡。 SD 总线上的基本交互是命令/响应交互。这种总线交互直接在命令或者响应的结构里面传输他们的信息。此外,某些操作还有数据令牌。 SD 卡发送或接收的数据在块(block)中完成。数据块以 CRC 位来保证传输成功。目前有单块和多块操作。

注:多块操作模式在快速写操作时更好一点。多块传输在 CMD 线上产生 stop 命令时结束。主机端可以配置数据传输是单线还是多线。

 title=

SDIO“无响应”和“无数据”操作

 title=

SDIO(多)数据块读操作

 title=

SDIO(多)数据块写操作

注:当有 Busy 信号时, SDIO DAT0 被拉低, SDIO 将不会发送任何数据。

 title=

SDIO连续读操作

 title=

SDIO连续写操作

3. 卡的初始化以及识别过程

初始化进程以命令 ACMD41 作为开始,通过设置工作条件和 OCR 来进行。 HCS(HighCapacitySupport)位为 1 表示主机支持高容量 SD 卡。卡通过 OCR 的 busy 位来通知主机 ACMD41 的初始化完成了。 busy 位为 0表示卡仍然在初始化;为 1 表示已经完成初始化。主机会重复发送 ACMD41,直到 busy 位被置 1。卡片旨在第一个 ACMD41 的命令时,检查工作条件和 OCR 里面的 HCS 位。当重复 ACMD41 的时候,除了 CMD0,主机不再发其他命令。接着主机会发送命令 ALL\_SEND\_CID(CMD2),来获得卡的 CID 号。未识别的卡(处于Ready 状态的)发送自己的 CID 作为响应。当卡发送 CID 后,进入卡识别(Identification)状态。之后主机发送 SEND\_RELATIVE\_ADDR(CMD3)命令要求卡发布新的相对地址(RCA),一旦收到 RCA,卡就会变为等待(Stand-by)状态。主机会重复识别进程,为系统中每个卡循环发送 CMD2 和 CMD3。对于 SDI/O 卡而言,总线被激活后 SDIO 卡主机先发送 IO\_SEND\_OP\_COND(CMD5)命令,得到的响应是卡的工作条件寄存器的内容,之后再同上发送 CMD3 命令,执行后续操作。

 title=

SDIO流程图

4. MM32F3270 SDIO驱动SD卡

MM32的SDIO支持SD/MMC/SDIO卡,其中SDIO卡与SD存储卡是有区别的。SDIO卡实际上就是利用SDIO接口的一些模块,插入SD的插槽中,扩展设备的功能,如:SDIO wifi, SDIO CMOS相机等。SD存储卡就是平时常见的用于存储数据的卡。

本实验中使用的Micro SD卡属于SDSC(标准容量,最大2G)卡。介绍卡的种类是因为SD协议中的命令也支持这三种类型的卡,因此对MM32中的SDIO接口进行初始化后,上电后就要对接入的卡进行检测、分类,这个过程是通过向卡发送一系列不同的命令,根据卡不同的响应来进行分类。

本实验使用MM32F3270的SDIO对SD卡进行读写测试,首先填充一个块大小的存储器,通过写入操作把数据写入到 SD卡内,然后通过读取操作读取数据到另外的存储器,然后再对比存储器内容,判断读写操作是否正确。

实现的大概流程包括:初始化SDIO 外设以及GPIO,配置 SDIO 基本通信环境进入卡识别模式,通过命令处理后获取卡信息及状态。如果是SD卡正常则进行数据传输,接下来就可以进行读、写以及擦除操作,否则打印SD卡错误信息,不再进行后续操作。

硬件设计

实验使用MB-039开发板,主控芯片为MM32F3277G9P,如图是MB-039开发板的SDIO/TF卡接口部分,完整原理图可以通过官网下载。

 title=

各个信号引脚对应如下:

 title=

程序设计

根据 SD 卡识别过程和数据传输过程理解 SD 卡驱动函数代码。这部分代码内容也较多,在本文中只对部分核心函数介绍其功能,详细代码可到灵动官网下载参考。

SPIO配置初始化

void SDIO_ConfigInit(void)
{
    SDIO_InitTypeDef SDIO_InitStruct;

    SDIO_PIN_GPIO_Config();
    SDIO_Detect_Pin_Config();

    RCC_AHBPeriphClockCmd(RCC_AHBENR_SDIO, ENABLE);
    SDIO_DeInit();
    RCC_AHBPeriphClockCmd(RCC_AHBENR_SDIO, DISABLE);
    RCC_AHBPeriphClockCmd(RCC_AHBENR_SDIO, ENABLE);

    SDIO_ClockSet(0x2F);
    SDIO_StructInit(&SDIO_InitStruct);

    SDIO_InitStruct.SDIO_OPMSel = SDIO_MMC_CTRL_OPMSel;
    SDIO_InitStruct.SDIO_SelPTSM = SDIO_MMC_CTRL_SelSM;
    SDIO_InitStruct.SDIO_DATWT = SDIO_MMC_CTRL_DATWT;
    SDIO_Init(&SDIO_InitStruct);

    SDIO_CRCConfig(SDIO_MMC_CRCCTL_CMD_CRCEN | SDIO_MMC_CRCCTL_DAT_CRCEN, ENABLE);
}
void show_sdcard_info(void)
{
    switch(SDCardInfo.CardType) {
        case SDIO_STD_CAPACITY_SD_CARD_V1_1:
            printf("Card Type:SDSC V1.1\r\n");
            break;
        case SDIO_STD_CAPACITY_SD_CARD_V2_0:
            printf("Card Type:SDSC V2.0\r\n");
            break;
        case SDIO_HIGH_CAPACITY_SD_CARD:
            printf("Card Type:SDHC V2.0\r\n");
            break;
        case SDIO_MULTIMEDIA_CARD:
            printf("Card Type:MMC Card\r\n");
            break;
    }
    printf("Card ManufacturerID:%d\r\n", SDCardInfo.SD_cid.ManufacturerID); //The manufacturer ID
    printf("Card RCA:%d\r\n", SDCardInfo.RCA);                          //Card relative address
    printf("Card Capacity:%d MB\r\n", (u32)(SDCardInfo.CardCapacity >> 20));
    printf("Card BlockSize:%d\r\n\r\n", SDCardInfo.CardBlockSize);
}

SD卡的初始化主要进行卡识别和卡状态获取,定义SD\_Init()如下:

SD_Error SD_Init(void)
{
    u32 clk;
    RCC_ClocksTypeDef bclk;
    u32 targetFreq;
    __IO SD_Error errorstatus = SD_OK;
    u8 clkdiv = 0;
    INTX_DISABLE();


    errorstatus = SD_PowerON();                 //SD Power On
    if (errorstatus != SD_OK) {
        return errorstatus;
    }
    errorstatus = SD_InitializeCards();         //Initialize SD Card
    if (errorstatus != SD_OK) {
        return errorstatus;
    }
    errorstatus = SD_GetCardInfo(&SDCardInfo);  //Get card information
    if (errorstatus != SD_OK) {
        return errorstatus;
    }
    errorstatus = SD_SelectDeselect((u32)(SDCardInfo.RCA << 16)); //Select the SD card
    if (errorstatus != SD_OK) {
        return errorstatus;
    }
    errorstatus = SD_EnableWideBusOperation(1); //4 bit width, if it is an MMC card, you cannot use 4 bit mode
    if ((errorstatus != SD_OK)) {
        if( (SDIO_MULTIMEDIA_CARD == CardType)) {
            if (SDCardInfo.CardType == SDIO_STD_CAPACITY_SD_CARD_V1_1 || SDCardInfo.CardType == SDIO_STD_CAPACITY_SD_CARD_V2_0) {
                clkdiv = 0; //V1.1/V2.0 card with the maximum setting of 48/4=12Mhz
            }
            else {
                clkdiv = 1; //For other cards such as SDHC, the maximum setting is 48/2=24Mhz
            }
            if(clkdiv != 0) {
                targetFreq = 24000000;

            }
            else {
                targetFreq = 12000000;
            }
            RCC_GetClocksFreq(&bclk);
            clk = (bclk.HCLK_Frequency / 2 / 2 / targetFreq - 1);
            SDIO_ClockSet(clk);

        }
        else {
            __NOP();
        }
    }
    else {
        if (SDCardInfo.CardType == SDIO_STD_CAPACITY_SD_CARD_V1_1 || SDCardInfo.CardType == SDIO_STD_CAPACITY_SD_CARD_V2_0) {
            clkdiv = 0;
        }
        else {
            clkdiv = 1;
        }
        if(clkdiv != 0) {
            targetFreq = 24000000;

        }
        else {
            targetFreq = 12000000;
        }
        RCC_GetClocksFreq(&bclk);
        clk = (bclk.HCLK_Frequency / 2 / 2 / targetFreq - 1);
        SDIO_ClockSet(clk);

    }
    INTX_ENABLE();
    return errorstatus;
}
  1. SD\_PowerON()函数用于查询卡的工作电压和时钟控制配置,并返回 SD\_Error 类型错误,该函数是整个 SD 识别的关键函数。
  2. SD\_InitializeCards()函数初始化SD卡,并将其置入就绪状态;
  3. SD\_GetCardInfo()函数获取SD卡的信息;
  4. SD\_SelectDeselect()选择SD卡,发送CMD7命令,选择具有相对地址(RCA)的卡作为ADDR;
  5. SD\_EnableWideBusOperation()配置SDIO数据宽度;
  6. SDIO\_ClockSet()配置SDIO的时钟频率。

获取SD卡信息及数据的函数定义如下:

void read_sd_card_info(void)
{    u16 i = 5;
    SD_Error result;
    u32 sd_size;

    SDIO_ConfigInit();
    printf("SDCARD TEST\r\n");
    while(1) {

        result = SD_Init();

        if(result == SD_OK) {
            break;
        }
        printf("SD Card Error!\r\n");

        DELAY_Ms(1);
    }
    show_sdcard_info();
    i = 5;
    while(i--) {
        if(SD_ReadDisk(&vbuf[0], 0, 1) == 0) { 
            printf("UART Sending Data...\r\n");
            printf("SECTOR 0 DATA:\r\n"); 
            for(sd_size = 0; sd_size < 512; sd_size++) {
                printf("%02x ", vbuf[sd_size]);  
            }
            printf("\r\nDATA ENDED\r\n");
            printf("UART Send Data Over!\r\n");
        }
        DELAY_Ms(50);
    }
}
实验演示

在MB-039开发板的SDIO/TF卡槽插入SD卡,运行程序,串口调试助手显示如下: 

 title=

如果SD卡可用,串口调试助手会打印SD卡的信息,包括卡类型、生产ID、RCA、容量和块大小,接着打印扇区0的数据,会连续打印5次该部分内容。

本次实验的例程可以通过MindMotion的官网下载MM32F3270 Lib\_Samples:
https://www.mindmotion.com.cn/products/mm32mcu/mm32f/mm32f\_mainstream/mm32f3270/

工程路径如下:
~MM32F327x\_Samples\ LibSamples\SDIO\SDIO\_ReadSDCardInfo 可以看到详细的样例与功能操作。

推荐阅读
关注数
6151
内容数
276
灵动MM32 MCU相关技术知识,欢迎关注~
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息