×
嵌入式 > 技术百科 > 详情

零死角玩转stm32-高级篇之SDIO(4bit + DMA、支持SDHC、带协议分析)

发布时间:2020-06-08 发布时间:
|

    1、SDIO(4bit + DMA、支持SDHC)

 1.1 实验描述及工程文件清单

实验描述:MicroSD卡(SDIO模式)测试实验,采用4bit数据线模式。没有跑文件系统,只是单纯地读block并将测试信息通过串口1在电脑的超级终端上 打印出来。

硬件连接:MicroSD卡(SDIO模式)测试实验,采用4bit数据线模式。没有跑文件系统,只是单纯地读block并将测试信息通过串口1在电脑的超级终端上 打印出来。

用到的库文件:startup/start_stm32f10x_hd.cCMSIS/core_cm3.cCMSIS/system_stm32f10x.cFWlib/stm32f10x_gpio.cFWlib/stm32f10x_rcc.cFWlib/stm32f10x_usart.cFWlib/ stm32f10x_sdio.cFWlib/ stm32f10x_dma.cFWlib/ misc.c

用户编写的文件:USER/main.cUSER/stm32f10x_it.cUSER/usart1.cUSER/ sdio_sdcard.c

野火STM32开发板 MicroSD卡硬件原理图:

   1.2 SDIO简介

野火STM32开发板的CPU ( STM32F103VET6 )具有一个SDIO接口。SD/SDIO/MMC主机接口可以支持MMC卡系统规范4.2版中的3个不同的数据总线模式:1位(默认)、4位和8位。在8位模式下,该接口可以使数据传输速率达到48MHz,该接口兼容SD存储卡规范2.0版。SDIO存储卡规范2.0版支持两种数据总线模式:1位(默认)和4位。

目前的芯片版本只能一次支持一个SD/SDIO/MMC 4.2版的卡,但可以同时支持多个MMC 4.1版或之前版本的卡。除了SD/SDIO/MMC,这个接口完全与CE-ATA数字协议版本1.1兼容。

   1.3 SD协议

大多数人原来没有了解过SD协议,又看到SDIO的驱动有2000多行,感觉无从下手。所以野火重新写了这个文档进行详细的解释,帮助大家更快地跨过这道槛。


下面野火结合STM32的SDIO,分析SD协议,让大家对它先有个大概了解,更具体的说明在代码中展开。

  SDIO接口图

 一.从SDIO的时钟说起。

SDIO_CK时钟是通过PC12引脚连接到SD卡的,是SDIO接口与SD卡用于同步的时钟。

SDIO选配器挂载到AHB总线上,通过HCLK二分频输入到适配器得到SDIO_CK的时钟,这时SDIO_CK = HCLK/(2+CLKDIV)。其中CLKDIV是SDIO_CLK(寄存器)中的CLKDIV位。

另外,SDIO_CK也可以由SDIOCLK通过设置bypass模式直接得到,这时SDIO_CK = SDIOCLK=HCLK

通过下面的库函数来配置时钟:

SDIO_Init(&SDIO_InitStructure);

对SD卡的操作一般是大吞吐量的数据传输,所以采用DMA来提高效率,SDIO采用的是DMA2中的通道4。在数据传输的时候SDIO可向DMA发出请求。

二.讲解SDIO的命令、数据传输方式。

SDIO的所有命令及命令响应,都是通过SDIO-CMD引脚来传输的。

命令只能由host即STM32的SDIO控制器发出。SDIO协议把命令分成了11种,包括基本命令,读写命令还有ACMD系列命令等。其中,在发送ACMD命令前,要先向卡发送编号为CMD55的命令

参照下面的命令格式图,其中的start bit,transmission bit ,crc7,endbit,都是由STM32中的SDIO硬件完成,我们在软件上配置的时候只需要设置command index和命令参数argument。Command index就是命令索引(编号),如CMD0,CMD1…被编号成0,1...。有的命令会包含参数,读命令的地址参数等,这个参数被存放在argument段。

  SD卡命令格式

可以通过下面的函数来配置、发送命令:

SDIO_SendCommand(&SDIO_CmdInitStructure);   //发送命令

SD卡对host的各种命令的回复称为响应,除了CMD0命令外,SD卡在接收到命令都会返回一个响应。对于不同的命令,会有不同的响应格式,共7种,分为长响应型(136bit)短响应型(48bit)。以下图,响应6(R6)为例:

  SD卡命令响应格式(R6)

SDIO通过CMD接收到响应后,硬件去除头尾的信息,把command index 保存到SDIO_RESPCMD寄存器,把argument field内容保存存储到SDIO_RESPx寄存器中。这两个值可以分别通过下面的库函数得到。

SDIO_GetCommandResponse();  //卡返回接收到的命令

SDIO_GetResponse(SDIO_RESP1); //卡返回的argument field内容

数据写入,读取。请看下面的写数据时序图,在软件上,我们要处理的只是读忙。另外,我们的实验中用的是Micro SD卡,有4条数据线,默认的时候SDIO采用1条数据线的传输方式,更改为4条数据线模式要通过向卡发送命令来更改。

   SD卡的多块写入时序图

三.卡的种类。

STM32的SDIO支持SD存储卡,SD I/O卡 ,MMC卡。

其中SDI/O卡SD存储卡是有区别的,SDI/O卡实际上就是利用SDIO接口的一些模块,插入SD的插槽中,扩展设备的功能,如:SDI/O wifi, SDI/O cmos相机等。而SD存储卡就是我们平时常见的单纯用于存储数据的卡。

可使用SDIO接口类型的卡

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

下面进入代码展开具体讲解。

1.4 代码分析

首先要添加用的库文件,在工程文件夹下Fwlib下我们需添加以下库文件:

FWlib/stm32f10x_gpio.c

FWlib/stm32f10x_rcc.c

FWlib/stm32f10x_usart.c

FWlib/stm32f10x_sdio.c

FWlib/stm32f10x_dma.c

FWlib/misc.c

还要在 stm32f10x_conf.h中把相应的头文件添加进来:

#include "stm32f10x_dma.h"

#include "stm32f10x_gpio.h"

#include "stm32f10x_rcc.h"

#include "stm32f10x_sdio.h"

#include "stm32f10x_usart.h"

#include "misc.h"

保持良好的习惯,从main函数开始分析:

int main(void)

{

/*进入到main函数前,启动文件startup(startup_stm32f10x_xx.s)已经调用了在

system_stm32f10x.c中的SystemInit(),配置好了系统时钟,在外部晶振8M的条件下,

设置HCLK = 72M  */

/* Interrupt Config */

NVIC_Configuration();

/* USART1 config */

USART1_Config();

/*------------------------------ SD Init ---------------------------------- */

Status = SD_Init();

printf( "\r\n 这是一个MicroSD卡实验(没有跑文件系统).........\r\n " );

if(Status == SD_OK) //检测初始化是否成功

{

printf( " \r\n SD_Init 初始化成功 \r\n " );

}

else

{

printf("\r\n SD_Init 初始化失败 \r\n" );

printf("\r\n 返回的Status的值为: %d \r\n",Status );

}

printf( " \r\n CardType is :%d ", SDCardInfo.CardType );

printf( " \r\n CardCapacity is :%d ", SDCardInfo.CardCapacity );

printf( " \r\n CardBlockSize is :%d ", SDCardInfo.CardBlockSize );

printf( " \r\n RCA is :%d ", SDCardInfo.RCA);

printf( " \r\n ManufacturerID is :%d \r\n", SDCardInfo.SD_cid.ManufacturerID );

SD_EraseTest();    //擦除测试

SD_SingleBlockTest();  //单块读写测试

SD_MultiBlockTest();  //多块读写测试

while (1)

{}

}

main函数的流程简单明了:

1、用NVIC_Configuration()初始化好SDIO的中断;

2、用USART1_Config()配置好用于返回调试信息的串口,SD_Init()开始进行SDIO的初始化;

3、最后分别用SD_EraseTest()SD_SingleBlockTest()SD_MultiBlockTest()进行擦除,单数据块读写,多数据块读写测试。

下面我们先进入SDIO驱动函数的大头——SD_Init()进行分析:

/*

* 函数名:SD_Init

* 描述  :初始化SD卡,使卡处于就绪状态(准备传输数据)

* 输入  :无

* 输出  :-SD_Error SD卡错误代码

*         成功时则为 SD_OK

* 调用  :外部调用

*/

SD_Error SD_Init(void)

{

/*重置SD_Error状态*/

SD_Error errorstatus = SD_OK;

/* SDIO 外设底层引脚初始化 */

GPIO_Configuration();

/*对SDIO的所有寄存器进行复位*/

SDIO_DeInit();

/*上电并进行卡识别流程,确认卡的操作电压  */

errorstatus = SD_PowerON();

/*如果上电,识别不成功,返回“响应超时”错误 */

if (errorstatus != SD_OK)

{

/*!< CMD Response TimeOut (wait for CMDSENT flag) */

return(errorstatus);

}

/*卡识别成功,进行卡初始化    */

errorstatus = SD_InitializeCards();

if (errorstatus != SD_OK)   //失败返回

{

/*!< CMD Response TimeOut (wait for CMDSENT flag) */

return(errorstatus);

}

/*!< Configure the SDIO peripheral

上电识别,卡初始化都完成后,进入数据传输模式,提高读写速度

速度若超过24M要进入bypass模式

!< on STM32F2xx devices, SDIOCLK is fixed to 48MHz

!< SDIOCLK = HCLK, SDIO_CK = HCLK/(2 + SDIO_TRANSFER_CLK_DIV) */

SDIO_InitStructure.SDIO_ClockDiv = SDIO_TRANSFER_CLK_DIV;

SDIO_InitStructure.SDIO_ClockEdge = SDIO_ClockEdge_Rising;         //上升沿采集数据

SDIO_InitStructure.SDIO_ClockBypass = SDIO_ClockBypass_Disable;  //时钟频率若超过24M,要开启此模式

SDIO_InitStructure.SDIO_ClockPowerSave = SDIO_ClockPowerSave_Disable;    //若开启此功能,在总线空闲时关闭sd_clk时钟

SDIO_InitStructure.SDIO_BusWide = SDIO_BusWide_1b;                        //1位模式

SDIO_InitStructure.SDIO_HardwareFlowControl = SDIO_HardwareFlowControl_Disable; //硬件流,若开启,在FIFO不能进行发送和接收数据时,数据传输暂停

SDIO_Init(&SDIO_InitStructure);

if (errorstatus == SD_OK)

{

/*----------------- Read CSD/CID MSD registers ------------------*/

errorstatus = SD_GetCardInfo(&SDCardInfo);   //用来读取csd/cid寄存器

}

if (errorstatus == SD_OK)

{

/*----------------- Select Card --------------------------------*/

errorstatus = SD_SelectDeselect((uint32_t) (SDCardInfo.RCA << 16));   //通过cmd7  ,rca选择要操作的卡

}

if (errorstatus == SD_OK)

{

errorstatus = SD_EnableWideBusOperation(SDIO_BusWide_4b);   //开启4bits模式

}

return(errorstatus);

}

先从整体上了解这个SD_Init()函数:

1.用 GPIO_Configuration()进行SDIO的端口底层配置

2.分别调用了SD_PowerON()和SD_InitializeCards()函数,这两个函数共同实现了上面提到的卡检测、识别流程。

3.调用SDIO_Init(&SDIO_InitStructure)库函数配置SDIO的时钟,数据线宽度,硬件流(在读写数据的时候,开启硬件流是和很必要的,可以减少出错)

4. 调用SD_GetCardInfo(&SDCardInfo)获取sd卡的CSD寄存器中的内容,在main函数里输出到串口的数据就是这个时候从卡读取得到的。

5. 调用SD_SelectDeselect()选定后面即将要操作的卡。

6.调用SD_EnableWideBusOperation(SDIO_BusWide_4b)开启4bit数据线模式

如果SD_Init()函数能够执行完整个流程,并且返回值是SD_OK的话则说明初始化成功,就可以开始进行擦除、读写的操作了。

下面进入SD_PowerON()函数,分析完这个函数大家就能了解SDIO如何接收、发送命令了。

/*

* 函数名:SD_PowerON

* 描述  :确保SD卡的工作电压和配置控制时钟

* 输入  :无

* 输出  :-SD_Error SD卡错误代码

*         成功时则为 SD_OK

* 调用  :在 SD_Init() 调用

*/

SD_Error SD_PowerON(void)

{

SD_Error errorstatus = SD_OK;

uint32_t response = 0, count = 0, validvoltage = 0;

uint32_t SDType = SD_STD_CAPACITY;

/*!< Power ON Sequence -----------------------------------------------------*/

/*!< Configure the SDIO peripheral */

/*!< SDIOCLK = HCLK, SDIO_CK = HCLK/(2 + SDIO_INIT_CLK_DIV) */

/*!< on STM32F2xx devices, SDIOCLK is fixed to 48MHz */

/*!< SDIO_CK for initialization should not exceed 400 KHz */

/*初始化时的时钟不能大于400KHz*/

SDIO_InitStructure.SDIO_ClockDiv = SDIO_INIT_CLK_DIV; /* HCLK = 72MHz, SDIOCLK = 72MHz, SDIO_CK = HCLK/(178 + 2) = 400 KHz */

SDIO_InitStructure.SDIO_ClockEdge = SDIO_ClockEdge_Rising;

SDIO_InitStructure.SDIO_ClockBypass = SDIO_ClockBypass_Disable;  //不使用bypass模式,直接用HCLK进行分频得到SDIO_CK

SDIO_InitStructure.SDIO_ClockPowerSave = SDIO_ClockPowerSave_Disable; // 空闲时不关闭时钟电源

SDIO_InitStructure.SDIO_BusWide = SDIO_BusWide_1b;                    //1位数据线

SDIO_InitStructure.SDIO_HardwareFlowControl = SDIO_HardwareFlowControl_Disable;//硬件流

SDIO_Init(&SDIO_InitStructure);

/*!< Set Power State to ON */

SDIO_SetPowerState(SDIO_PowerState_ON);

/*!< Enable SDIO Clock */

SDIO_ClockCmd(ENABLE);

/*下面发送一系列命令,开始卡识别流程*/

/*!< CMD0: GO_IDLE_STATE ---------------------------------------------------*/

/*!< No CMD response required */

SDIO_CmdInitStructure.SDIO_Argument = 0x0;

SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_GO_IDLE_STATE; //cmd0

SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_No;  //无响应

SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;

SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;  //则CPSM在开始发送命令之前等待数据传输结束。

SDIO_SendCommand(&SDIO_CmdInitStructure);         //写命令进命令寄存器

errorstatus = CmdError();//检测是否正确接收到cmd0

if (errorstatus != SD_OK) //命令发送出错,返回

{

/*!< CMD Response TimeOut (wait for CMDSENT flag) */

return(errorstatus);

}

/*!< CMD8: SEND_IF_COND ----------------------------------------------------*/

/*!< Send CMD8 to verify SD card interface operating condition */

/*!< Argument: - [31:12]: Reserved (shall be set to '0')

- [11:8]: Supply Voltage (VHS) 0x1 (Range: 2.7-3.6 V)

- [7:0]: Check Pattern (recommended 0xAA) */

/*!< CMD Response: R7 */

SDIO_CmdInitStructure.SDIO_Argument = SD_CHECK_PATTERN;   //接收到命令sd会返回这个参数

SDIO_CmdInitStructure.SDIO_CmdIndex = SDIO_SEND_IF_COND;  //cmd8

SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;     //r7

SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;            //关闭等待中断

SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;

SDIO_SendCommand(&SDIO_CmdInitStructure);

/*检查是否接收到命令*/

errorstatus = CmdResp7Error();

if (errorstatus == SD_OK)     //有响应则card遵循sd协议2.0版本

{

CardType = SDIO_STD_CAPACITY_SD_CARD_V2_0; /*!< SD Card 2.0 ,先把它定义会sdsc类型的卡*/

SDType = SD_HIGH_CAPACITY;  //这个变量用作acmd41的参数,用来询问是sdsc卡还是sdhc卡

}

else  //无响应,说明是1.x的或mmc的卡

{

/*!< CMD55 */

SDIO_CmdInitStructure.SDIO_Argument = 0x00;

SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_CMD;

SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;

SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;

SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;

SDIO_SendCommand(&SDIO_CmdInitStructure);

errorstatus = CmdResp1Error(SD_CMD_APP_CMD);

}

/*!< CMD55 */     //为什么在else里和else外面都要发送CMD55?

//发送cmd55,用于检测是sd卡还是mmc卡,或是不支持的卡

SDIO_CmdInitStructure.SDIO_Argument = 0x00;

SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_CMD;

SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; //r1

SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;

SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;

SDIO_SendCommand(&SDIO_CmdInitStructure);

errorstatus = CmdResp1Error(SD_CMD_APP_CMD);  //是否响应,没响应的是mmc或不支持的卡

/*!< If errorstatus is Command TimeOut, it is a MMC card */

/*!< If errorstatus is SD_OK it is a SD card: SD card 2.0 (voltage range mismatch)

or SD card 1.x */

if (errorstatus == SD_OK) //响应了cmd55,是sd卡,可能为1.x,可能为2.0

{

/*下面开始循环地发送sdio支持的电压范围,循环一定次数*/

/*!< SD CARD */

/*!< Send ACMD41 SD_APP_OP_COND with Argument 0x80100000 */

while ((!validvoltage) && (count < SD_MAX_VOLT_TRIAL))

{

//因为下面要用到ACMD41,是ACMD命令,在发送ACMD命令前都要先向卡发送CMD55

/*!< SEND CMD55 APP_CMD with RCA as 0 */

SDIO_CmdInitStructure.SDIO_Argument = 0x00;

SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_CMD;   //CMD55

SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;

SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;

SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;

SDIO_SendCommand(&SDIO_CmdInitStructure);

errorstatus = CmdResp1Error(SD_CMD_APP_CMD); //检测响应

if (errorstatus != SD_OK)

{

return(errorstatus);//没响应CMD55,返回

}

//acmd41,命令参数由支持的电压范围及HCS位组成,HCS位置一来区分卡是SDSc还是sdhc

SDIO_CmdInitStructure.SDIO_Argument = SD_VOLTAGE_WINDOW_SD | SDType;    //参数为主机可供电压范围及hcs位

SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SD_APP_OP_COND;

SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;  //r3

SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;

SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;

SDIO_SendCommand(&SDIO_CmdInitStructure);

errorstatus = CmdResp3Error();    //检测是否正确接收到数据

if (errorstatus != SD_OK)

{

return(errorstatus);  //没正确接收到acmd41,出错,返回

}

/*若卡需求电压在SDIO的供电电压范围内,会自动上电并标志pwr_up位*/

response = SDIO_GetResponse(SDIO_RESP1);   //读取卡寄存器,卡状态

validvoltage = (((response >> 31) == 1) ? 1 : 0); //读取卡的ocr寄存器的pwr_up位,看是否已工作在正常电压

count++;            //计算循环次数

}

if (count >= SD_MAX_VOLT_TRIAL) //循环检测超过一定次数还没上电

{

errorstatus = SD_INVALID_VOLTRANGE;      //SDIO不支持card的供电电压

return(errorstatus);

}

/*检查卡返回信息中的HCS位*/

if (response &= SD_HIGH_CAPACITY)  //判断ocr中的ccs位 ,如果是sdsc卡则不执行下面的语句

{

CardType = SDIO_HIGH_CAPACITY_SD_CARD;  //把卡类型从初始化的sdsc型改为sdhc型

}

}/*!< else MMC Card */

return(errorstatus);

}

这个函数的流程就是卡的上电、识别操作,如下图:

卡的上电,识别流程:

截图来自《Simplified_Physical_Layer_Spec.pdf》 page27

代码中所有的判断语句都是根据这个图的各个识别走向展开的,最终把卡分为1.0版的SD存储卡,2.0版的SDSC卡和2.0版的SDHC卡。

在这个代码流程中有两点要注意一下:

1.初始化的时钟。SDIO_CK的时钟分为两个阶段,在初始化阶段SDIO_CK的频率要小于400KHz,初始化完成后可把SDIO_CK调整成高速模式,高速模式时超过24M要开启bypass模式,对于SD存储卡即使开启bypass,最高频率不能超过25MHz

2.CMD8命令。

CMD8命令格式。

CMD8命令中的VHS是用来确认主机SDIO是否支持卡的工作电压的。Check pattern部分可以是任何数值,若SDIO支持卡的工作电压,卡会把接收到的check pattern数值原样返回给主机。

CMD8命令的响应格式R7:

在驱动程序中调用了CmdResp7Error()来检验卡接收命令后的响应。

3.ACMD41命令。

这个命令也是用来进一步检查SDIO是否支持卡的工作电压的,协议要它在调用它之前必须先调用CMD8,另外还可以通过它命令参数中的HCS位来区分卡是SDHC卡还是SDSC卡。

确认工作电压时循环地发送ACMD41,发送后检查在SD卡上的OCR寄存器中的pwr_up位,若pwr_up位置为1,表明SDIO支持卡的工作电压,卡开始正常工作。

同时把ACMD41中的命令参数HCS位置1,卡正常工作的时候检测OCR寄存器中的CCS位,若CCS位为1则说明该卡为SDHC卡,为零则为SDSC卡。

因为ACMD41命令属于ACMD命令,在发送ACMD命令前都要先发送CMD55.

ACMD41命令格式

ACMD41命令的响应(R3),返回的是OCR寄存器的值

OCR寄存器的内容

SD卡上电确认成功后,进入SD_InitializeCards()函数:

/*

* 函数名:SD_InitializeCards

* 描述  :初始化所有的卡或者单个卡进入就绪状态

* 输入  :无

* 输出  :-SD_Error SD卡错误代码

*         成功时则为 SD_OK

* 调用  :在 SD_Init() 调用,在调用power_on()上电卡识别完毕后,调用此函数进行卡初始化

*/

SD_Error SD_InitializeCards(void)

{

SD_Error errorstatus = SD_OK;

uint16_t rca = 0x01;

if (SDIO_GetPowerState() == SDIO_PowerState_OFF)

{

errorstatus = SD_REQUEST_NOT_APPLICABLE;

return(errorstatus);

}

if (SDIO_SECURE_DIGITAL_IO_CARD != CardType)//判断卡的类型

{

/*!< Send CMD2 ALL_SEND_CID */

SDIO_CmdInitStructure.SDIO_Argument = 0x0;

SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_ALL_SEND_CID;

SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Long;

SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;

SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;

SDIO_SendCommand(&SDIO_CmdInitStructure);

errorstatus = CmdResp2Error();

if (SD_OK != errorstatus)

{

return(errorstatus);

}

CID_Tab[0] = SDIO_GetResponse(SDIO_RESP1);

CID_Tab[1] = SDIO_GetResponse(SDIO_RESP2);

CID_Tab[2] = SDIO_GetResponse(SDIO_RESP3);

CID_Tab[3] = SDIO_GetResponse(SDIO_RESP4);

}

/*下面开始SD卡初始化流程*/

if ((SDIO_STD_CAPACITY_SD_CARD_V1_1 == CardType) ||  (SDIO_STD_CAPACITY_SD_CARD_V2_0 == CardType) ||  (SDIO_SECURE_DIGITAL_IO_COMBO_CARD == CardType)

||  (SDIO_HIGH_CAPACITY_SD_CARD == CardType))  //使用的是2.0的卡

{

/*!< Send CMD3 SET_REL_ADDR with argument 0 */

/*!< SD Card publishes its RCA. */

SDIO_CmdInitStructure.SDIO_Argument = 0x00;

SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_REL_ADDR;  //cmd3

SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short; //r6

SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;

SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;

SDIO_SendCommand(&SDIO_CmdInitStructure);

errorstatus = CmdResp6Error(SD_CMD_SET_REL_ADDR, &rca); //把接收到的卡相对地址存起来。

if (SD_OK != errorstatus)

{

return(errorstatus);

}

}

if (SDIO_SECURE_DIGITAL_IO_CARD != CardType)

{

RCA = rca;

/*!< Send CMD9 SEND_CSD with argument as card's RCA */

SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)(rca << 16);

SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SEND_CSD;

SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Long;

SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;

SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;

SDIO_SendCommand(&SDIO_CmdInitStructure);

errorstatus = CmdResp2Error();

if (SD_OK != errorstatus)

{

return(errorstatus);

}

CSD_Tab[0] = SDIO_GetResponse(SDIO_RESP1);

CSD_Tab[1] = SDIO_GetResponse(SDIO_RESP2);

CSD_Tab[2] = SDIO_GetResponse(SDIO_RESP3);

CSD_Tab[3] = SDIO_GetResponse(SDIO_RESP4);

}

errorstatus = SD_OK; /*!< All cards get intialized */

return(errorstatus);

}

这个函数向卡发送了CMD2和CMD3命令

1.CMD2

CMD2命令是要求卡返回它的CID寄存器的内容。

命令的响应格式(R2)。

因为命令格式是136位的,属于长响应。软件接收的信息有128位。在长响应的时候通过SDIO_GetResponse(SDIO_RESP4);中的不同参数来获取CID中的不同数据段的数据。

2.CMD3

CMD3命令是要求卡向主机发送卡的相对地址。在接有多个卡的时候,主机要求接口上的卡重新发一个相对地址,这个地址跟卡的实际ID不一样。比如接口上接了5个卡,这5个卡的相对地址就分别为1,2,3,4,5.以后主机SDIO对这几个卡寻址就直接使用相对地址。这个地址的作用就是为了寻址更加简单

接下来我们回到SD_Init()函数。分析到这里大家应该对SDIO的命令发送和响应比较清楚了。在SD_InitializeCards()之后的SD_GetCardInfo(&SDCardInfo)、 SD_SelectDeselect()和

SD_EnableWideBusOperation(SDIO_BusWide_4b)的具体实现就不再详细分析了,实际就是发送相应的命令,对卡进行相应的操作。

接下来分析main函数中的SD_MultiBlockTest()多块数据读写函数,让大家了解SDIO是怎样传输数据的。

/*

* 函数名:SD_MultiBlockTest

* 描述  :    多数据块读写测试

* 输入  :无

* 输出  :无

*/

void SD_MultiBlockTest(void)

{

/*--------------- Multiple Block Read/Write ---------------------*/

/* Fill the buffer to send */

Fill_Buffer(Buffer_MultiBlock_Tx, MULTI_BUFFER_SIZE, 0x0);

if (Status == SD_OK)

{

/* Write multiple block of many bytes on address 0 */

Status = SD_WriteMultiBlocks(Buffer_MultiBlock_Tx, 0x00, BLOCK_SIZE, NUMBER_OF_BLOCKS);

/* Check if the Transfer is finished */

Status = SD_WaitWriteOperation();

while(SD_GetStatus() != SD_TRANSFER_OK);

}

if (Status == SD_OK)

{

/* Read block of many bytes from address 0 */

Status = SD_ReadMultiBlocks(Buffer_MultiBlock_Rx, 0x00, BLOCK_SIZE, NUMBER_OF_BLOCKS);

/* Check if the Transfer is finished */

Status = SD_WaitReadOperation();

while(SD_GetStatus() != SD_TRANSFER_OK);

}

/* Check the correctness of written data */

if (Status == SD_OK)

{

TransferStatus2 = Buffercmp(Buffer_MultiBlock_Tx, Buffer_MultiBlock_Rx, MULTI_BUFFER_SIZE);

}

if(TransferStatus2 == PASSED)

printf("\r\n 多块读写测试成功! " );

else

printf("\r\n 多块读写测试失败! " );

}

把这个函数拿出来分析最重要的一点就是让大家注意在调用了SD_WriteMultiBlocks()这一类读写操作的函数后,一定要调用SD_WaitWriteOperation()[在读数据时调用SD_WaitReadOperation]SD_GetStatus()来确保数据传输已经结束再进行其它操作。其中的SD_WaitWriteOperation()是用来等待DMA把缓冲的数据传输到SDIO的FIFO的;而SD_GetStatus()是用来等待卡与SDIO之间传输数据完毕的。

最后进入SD_WriteMultiBlocks()函数分析:

/*

* 函数名:SD_WriteMultiBlocks

* 描述  :从输入的起始地址开始,向卡写入多个数据块,

只能在DMA模式下使用这个函数

注意:调用这个函数后一定要调用

SD_WaitWriteOperation()来等待DMA传输结束

和   SD_GetStatus() 检测卡与SDIO的FIFO间是否已经完成传输

* 输入  :

* @param  WriteAddr: Address from where data are to be read.

* @param  writebuff: pointer to the buffer that contain the data to be transferred.

* @param  BlockSize: the SD card Data block size. The Block size should be 512.

* @param  NumberOfBlocks: number of blocks to be written.

* 输出  :SD错误类型

*/

SD_Error SD_WriteMultiBlocks(uint8_t *writebuff, uint32_t WriteAddr, uint16_t BlockSize, uint32_t NumberOfBlocks)

{

SD_Error errorstatus = SD_OK;

__IO uint32_t count = 0;

TransferError = SD_OK;

TransferEnd = 0;

StopCondition = 1;

SDIO->DCTRL = 0x0;

if (CardType == SDIO_HIGH_CAPACITY_SD_CARD)

{

BlockSize = 512;

WriteAddr /= 512;

}

/*******************add,没有这一段容易卡死在DMA检测中*************************************/

/*!< Set Block Size for Card,cmd16,若是sdsc卡,可以用来设置块大小,若是sdhc卡,块大小为512字节,不受cmd16影响 */

SDIO_CmdInitStructure.SDIO_Argument = (uint32_t) BlockSize;

SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_BLOCKLEN;

SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;   //r1

SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;

SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;

SDIO_SendCommand(&SDIO_CmdInitStructure);

errorstatus = CmdResp1Error(SD_CMD_SET_BLOCKLEN);

if (SD_OK != errorstatus)

{

return(errorstatus);

}

/*********************************************************************************/

/*!< To improve performance  */

SDIO_CmdInitStructure.SDIO_Argument = (uint32_t) (RCA << 16);

SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_APP_CMD; // cmd55

SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;

SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;

SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;

SDIO_SendCommand(&SDIO_CmdInitStructure);

errorstatus = CmdResp1Error(SD_CMD_APP_CMD);

if (errorstatus != SD_OK)

{

return(errorstatus);

}

/*!< To improve performance *///  pre-erased,在多块写入时可发送此命令进行预擦除

SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)NumberOfBlocks;  //参数为将要写入的块数目

SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_SET_BLOCK_COUNT;  //cmd23

SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;

SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;

SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;

SDIO_SendCommand(&SDIO_CmdInitStructure);

errorstatus = CmdResp1Error(SD_CMD_SET_BLOCK_COUNT);

if (errorstatus != SD_OK)

{

return(errorstatus);

}

/*!< Send CMD25 WRITE_MULT_BLOCK with argument data address */

SDIO_CmdInitStructure.SDIO_Argument = (uint32_t)WriteAddr;

SDIO_CmdInitStructure.SDIO_CmdIndex = SD_CMD_WRITE_MULT_BLOCK;

SDIO_CmdInitStructure.SDIO_Response = SDIO_Response_Short;

SDIO_CmdInitStructure.SDIO_Wait = SDIO_Wait_No;

SDIO_CmdInitStructure.SDIO_CPSM = SDIO_CPSM_Enable;

SDIO_SendCommand(&SDIO_CmdInitStructure);

errorstatus = CmdResp1Error(SD_CMD_WRITE_MULT_BLOCK);

if (SD_OK != errorstatus)

{

return(errorstatus);

}

SDIO_DataInitStructure.SDIO_DataTimeOut = SD_DATATIMEOUT;

SDIO_DataInitStructure.SDIO_DataLength = NumberOfBlocks * BlockSize;

SDIO_DataInitStructure.SDIO_DataBlockSize = (uint32_t) 9 << 4;

SDIO_DataInitStructure.SDIO_TransferDir = SDIO_TransferDir_ToCard;

SDIO_DataInitStructure.SDIO_TransferMode = SDIO_TransferMode_Block;

SDIO_DataInitStructure.SDIO_DPSM = SDIO_DPSM_Enable;

SDIO_DataConfig(&SDIO_DataInitStructure);

SDIO_ITConfig(SDIO_IT_DATAEND, ENABLE);

SDIO_DMACmd(ENABLE);

SD_DMA_TxConfig((uint32_t *)writebuff, (NumberOfBlocks * BlockSize));

return(errorstatus);

}

写操作在发送正式的多块写入命令CMD25前调用了CMD23进行预写,这样有利于提高写入的速度。在代码的最后调用了SDIO_ITConfig(),SDIO的数据传输结束中断就是这个时候开启的,数据传输结束时,就进入到stm32f10x_it.c文件中的中断服务函数SDIO_IRQHandler()中处理了,中断服务函数主要就是负责清中断。

最后讲一下官方原版的驱动中的一个bug。

在官方原版的SDIO驱动的SD_ReadBlock()、SD_ReadMultiBlocks()、SD_WriteBlock()和SD_WriteMultiBlocks()这几个函数中,发送读写命令前,漏掉了发送一个CMD16命令,这个命令用于设置读写SD卡的块大小。缺少这个命令很容易导致程序运行时卡死在循环检测DMA传输结束的代码中,网上很多人直接移植ST官方例程时,用3.5版库函数和这个4.5版的SDIO驱动移植失败,就是缺少了这段用CMD16设置块大小的代码。

到这里,终于讲解完毕啦!这个讲解如果能让你从对SDIO一无所知到大概了解的话,我的目标就达到啦,想要更深入了解还是要好好地配合这个例程中我在代码中的注释和附带资料SD2.0协议《Simplified_Physical_Layer_Spec.pdf》好好研究一番! ^_^

注意:这个例程是没有跑文件系统的,而是直接就去读卡的block,这样的话就会破坏卡的分区,在实验完成之后,你再把卡插到电脑上时,电脑会提示你要重新初始化卡,这是正常想象,并不是本实验把你的卡弄坏了,如果卡原来有资料的请先把数据备份了再进行测试。但跑文件系统时就不会出现这种问题,有关文件系统的操作将在下一讲的教程中讲解。

1.5实验现象

将野火STM32开发板供电(DC5V),插上JLINK,插上串口线(两头都是母的交叉线),插上MicroSD卡( 我用的是1G ,经测试,本驱动也适用于2G以上的卡(sdhc卡)),打开超级终端,配置超级终端为115200 8-N-1,将编译好的程序下载到开发板,即可看到超级终端打印出如下信息:


『本文转载自网络,版权归原作者所有,如有侵权请联系删除』

热门文章 更多
Semtech的LoRa技术携手Chipsafer将牧场连接至云端