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 功能框图
图1 SDIO功能框图
- AHB 从模式接口:为 32 位 AHB 总线提供接口。
- FIFO 控制:产生握手信号到 DMA 硬件接口,并控制对外部数据 FIFO(128x32)的读/写访问。
- 总线接口单元:包括控制寄存器和命令缓冲单元。
- 多重块控制:控制多块数据的读写。
- 时钟控制:通用时钟基于寄存器中定义的分频值。
- 命令通路:从总线接口单元或 irq 响应中加载新的命令,然后发送命令,并接收响应 crc7 检查和 8 个空时钟。
- 数据通路:发送和接收数据,用 crc16 检查。
- 缓冲接口:控制对数据 FIFO 的读写控制信号。
1.2 SDIO 功能描述
- 完全兼容 SD 记忆卡规格 1.0
- 完全兼容 SD 存储卡规格 1.1(高速)
- 完全兼容 SD 记忆卡规格 2.0(SDHC)
- 完全兼容 MMC 系统规格 2.0~4.2
- 完全兼容 SDIO 存储卡规格 1.1.0
- 标准的 MMC 模式接口支持
- 可编程时钟速率
- 自动命令/响应 CRC 生成/检查
- 自动数据 CRC 生成/检查
- 可编程超时检测
- AMBA 2.0 32 位 AHB 接口
- 用于 AHB 数据访问的总共外部 128*32 数据 FIFO
- 32 位 DMA 硬件接口,用于更快的 DMA 访问
- DMA 接口可以配置为启用/禁用
- DMA 请求是可配置的
- 组合中断输出
2. SD 存储卡
2.1 Read-Write 属性
根据 Read-Write 属性不同可分为两种类型的 SD 存储卡:
- 可读可写(RW)卡(FLASH, OTP, MTP)。这些卡通常作为空白媒介来售卖,用于海量终端用户的视频、音频或数字图像记录的存储。
- 只读存储卡(ROM)。这些卡是用固定的数据内容生产出来的,它们通常用作软件、音频、视频等的传播媒介。
2.2 电源电压
根据工作电源电压不同可分为两种类型的 SD 存储卡:
- 高电压 SD 存储卡,可以在 2.7-3.6V 的电压范围内工作;
- 双电压 SD 存储卡,可以在 1.6-3.6V 的电压范围内工作。
2.3 卡容量
根据卡容量不同可分为两种类型的 SD 存储卡:
- 标准容量的 SD 存储卡支持最大 2G 字节的容量。所有版本的物理规格都定义了标准容量 SD 存储卡;
- 高容量 SD 存储卡支持超过 2G 字节的容量,此版本规范限制容量高达 32GB。高容量 SD 存储卡是物理层规范版本 2.00 中新定义的。
2.4 传输速度
定义了四种速度等级,表示 SD 卡的最低性能:
- Class0:这类卡不指定性能;
- Class2:速度不低于 2MB/s;
- Class4:速度不低于 4MB/s;
- Class6:速度不低于 6MB/s;
大容量 SD 存储卡应支持速度等级规范,性能大于或等于 2 级。
2.5 总线拓扑
SD 卡系统定义了两种通信协议: SD 和 SPI。主机系统可以选择任意一种。当收到 reset 命令的时候, SD 卡通过主机的信息来决定使用何种模式,并且之后的通讯都会使用相同模式。不推荐多卡槽用共同的总线信号。一个单独的 SD 总线应该连接一个单独的 SD 卡。 SD 总线包含下面的信号:
- CLK:时钟信号;
- CMD:双向命令/响应信号;
- DAT0-DAT3:双向数据信号;
- Vdd, Vss1, Vss2:电源和地信号。
2.6 总线协议
SD 总线通信:
- Command:命令是一次操作开始的令牌,从主机发送到一个卡片(编址命令)或者连接到主机的所有卡片(广播命令)。命令在 CMD 线上连续传输。
- Response:响应是从已寻址的卡或从所有连接上的卡发送到主机的令牌,作为对先前接收到指令的应答。响应在 CMD 线上连续传输。
- Data:数据可以通过 DATA 线双向传输。
卡片寻址通过使用会话地址来实现,会话地址会在初始化阶段分配给卡。 SD 总线上的基本交互是命令/响应交互。这种总线交互直接在命令或者响应的结构里面传输他们的信息。此外,某些操作还有数据令牌。 SD 卡发送或接收的数据在块(block)中完成。数据块以 CRC 位来保证传输成功。目前有单块和多块操作。
注:多块操作模式在快速写操作时更好一点。多块传输在 CMD 线上产生 stop 命令时结束。主机端可以配置数据传输是单线还是多线。
SDIO“无响应”和“无数据”操作
SDIO(多)数据块读操作
SDIO(多)数据块写操作
注:当有 Busy 信号时, SDIO DAT0 被拉低, SDIO 将不会发送任何数据。
SDIO连续读操作
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 命令,执行后续操作。
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卡接口部分,完整原理图可以通过官网下载。
各个信号引脚对应如下:
程序设计
根据 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;
}
- SD\_PowerON()函数用于查询卡的工作电压和时钟控制配置,并返回 SD\_Error 类型错误,该函数是整个 SD 识别的关键函数。
- SD\_InitializeCards()函数初始化SD卡,并将其置入就绪状态;
- SD\_GetCardInfo()函数获取SD卡的信息;
- SD\_SelectDeselect()选择SD卡,发送CMD7命令,选择具有相对地址(RCA)的卡作为ADDR;
- SD\_EnableWideBusOperation()配置SDIO数据宽度;
- 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卡,运行程序,串口调试助手显示如下:
如果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 可以看到详细的样例与功能操作。