本SDIO例程,测试过程中会将SD卡的数据直接删除,相当于将SD卡格式化,请使用一张没有数据的SD卡进行测试。 数据丢失后果自负。
SD卡是我们日常使用的电子设备,例如用在相机、个人数据存储。部分手机也会使用TF卡作扩展存储使用。
SD存储卡是一种基于半导体快闪记忆器的新一代记忆设备,由于它体积小、数据传输速度快、可热插拔等优良的特性,被广泛地于便携式装置上使用,例如数码相机、个人数码助理(外语缩写PDA)和多媒体播放器等。
SD卡标准使用SDIO接口通信,也支持SPI接口。在日常使用情景,例如相机、读卡器等,都是使用SDIO通信,SDIO使用4个数据线,速度比SPI要快。 SD卡使用SDIO口通信过程遵循SD卡规范,这个规范不简单,涉及到较多的命令交互和状态转换。通常芯片厂家都会提供相关例程,用户不需要重新开发SDIO通信程序。本例程通过移植ST官方的例程实现与SD卡通信。
下图是SDIO框图,
这是一个总框图,主要说明外部连接。 对于SDIO适配器,并没有详细说明。我猜测原因是,其实SDIO是一个比较复杂的设备,类似USB。 通常这种复杂外设,我们都不会自己开发驱动,都是用官方提供的例程。
总线的通信基于命令和数据传输。其实这是大部分外设的相同之处。
SDIO接口总共有6根线(4位位宽),其中命令只通过CMD线传输。因此在调试的时候如果命令正常,读写数据不正常,说明仅仅是数据线问题。
SDID接口除了用于控制SD卡等卡之外,也可以控制SDIO接口的其他模块,例如SDIO接口的WIFI模块
其实在SD卡之前,最先出现的是MMC卡。 下图这种就是MMC卡,只有SD卡一半大小,通常我们都用一个卡尾拼成SD卡用。MMC(MULTI-MEDIA CARD,多媒体卡)由西门子公司SIEMENS和SANDISK于1997年推出。
这些卡的协议基本都是兼容的。
SDIO接口使用6根管脚。 CLK是通信时钟 CMD是命令串行通信线。 DATA数据线有四根。 右边的SD_CARD_DET_N是卡插入检测 SDIO接口,CLK不需要上拉电阻,其他5根IO需要加上拉电阻。
前面说到,SDIO是一个复杂的协议,SD卡、TF卡是一个较复杂设备。要完全弄清楚,非常不容易,更加不要说自己写一套了。 通常,这种复杂的协议,我们不自己开发,都是芯片厂提供驱动代码。
前面几个章节的调试,我们直接参考标准库接口,再根据参考手册,就可以进行编码了。 但是类似USB,SD卡,网络等较复杂的设备,通常做法是从官方提供的库入手。 特别是USB通信,个人基本不可能写一套库出来,也没这个必要。
移植调试
在移植之前,先对官方例程进行分析学习。
例程分析
在标准库下面有SD卡例程,路径如下
STM32F4XX_DSP_STDPERIPH_LIB_V1.8.0PROJECTSTM32F4XX_STDPERIPH_EXAMPLESSDIOSDIO_USDCARD
在目录下有一个README.TXT文件。看东西,先从README入手。 从README中可以看出,STM32F407芯片的SDIO的驱动在 STM32F4XX_DSP_STDPERIPH_LIB_V1.8.0UTILITIESSTM32_EVALSTM3240_41_G_EVAL目录下, 名字叫STM324XG_EVAL_SDIO_SD.C和STM324XG_EVAL.C。 其中STM324XG_EVAL.C仅仅提供了较底层的初始化。 看来主要代码都在STM324XG_EVAL_SDIO_SD.C里面。
STM324XG_EVAL_SDIO_SD.C 例程还包括以下文件
路径:STM32F4XX_DSP_STDPERIPH_LIB_V1.8.0PROJECTSTM32F4XX_STDPERIPH_EXAMPLESSDIOSDIO_USDCARD
SDIO/SDIO_USDCARD/SYSTEM_STM32F4XX.C STM32F4XX SYSTEM CLOCK CONFIGURATION FILE
SDIO/SDIO_USDCARD/STM32F4XX_CONF.H LIBRARY CONFIGURATION FILE
SDIO/SDIO_USDCARD/STM32F4XX_IT.C INTERRUPT HANDLERS
SDIO/SDIO_USDCARD/STM32F4XX_IT.H INTERRUPT HANDLERS HEADER FILE
SDIO/SDIO_USDCARD/MAIN.C MAIN PROGRAM
SDIO/SDIO_USDCARD/MAIN.H MAIN PROGRAM HEADER FILE
下一步,我们就浏览这六个文件,熟悉其中的流程与调用关系。 使用SI创建一个SDIO_EVAL工程,将以上提到的文件添加到工程,用SI分析程序相当方便。 为了防止无意中修改库文件,我们把文件拷贝一份。
SD卡例程源文件 从MAIN函数入手,程序首先对SD进行初始化。然后进行块测试,分别进行擦块,单块测试,多块测试。
/*------------------------------ SD INIT ----------------- */ IF((STATUS = SD_INIT()) != SD_OK) { STM_EVAL_LEDON(LED4); } WHILE((STATUS == SD_OK) && (UWSDCARDOPERATION != SD_OPERATION_END) && (SD_DETECT()== SD_PRESENT)) { SWITCH(UWSDCARDOPERATION) { /*-------------------------- SD ERASE TEST --------- */ CASE (SD_OPERATION_ERASE): { SD_ERASETEST(); UWSDCARDOPERATION = SD_OPERATION_BLOCK; BREAK; } /*------------------- SD SINGLE BLOCK TEST --------- */ CASE (SD_OPERATION_BLOCK): { SD_SINGLEBLOCKTEST(); UWSDCARDOPERATION = SD_OPERATION_MULTI_BLOCK; BREAK; } /*------------------- SD MULTI BLOCKS TEST --------- */ CASE (SD_OPERATION_MULTI_BLOCK): { SD_MULTIBLOCKTEST(); UWSDCARDOPERATION = SD_OPERATION_END; BREAK; } } }
从函数SD_ERROR SD_INIT(VOID)跟下去。 函数首先调用了SDIO底层初始化,然后读卡,进行POWER ON了,上电成功就初始化。 先看看SDIO底层初始化了什么。
SD_ERROR SD_INIT(VOID){ __IO SD_ERROR ERRORSTATUS = SD_OK; /* SDIO PERIPHERAL LOW LEVEL INIT */ SD_LOWLEVEL_INIT(); SDIO_DEINIT(); ERRORSTATUS = SD_POWERON();
VOID SD_LOWLEVEL_INIT(VOID)函数就在前面提到过的STM324XG_EVAL.C文件内,也就是官方的DEMO板的初始化文件。 我个人觉得这些代码应该放到MCU_SDIO驱动内。 这个文件与SD卡有关的也就四个函数,前面两个是SDIO口初始化,后面两个是SDIO使用DMA的初始化。
VOID SD_LOWLEVEL_DEINIT(VOID);
VOID SD_LOWLEVEL_INIT(VOID);
VOID SD_LOWLEVEL_DMA_TXCONFIG(UINT32_T *BUFFERSRC, UINT32_T BUFFERSIZE);
VOID SD_LOWLEVEL_DMA_RXCONFIG(UINT32_T *BUFFERDST, UINT32_T BUFFERSIZE);
回到SD_INIT(VOID),后续调用的函数全部都在本文件了。 再回到MAIN,看测试程序在哪里。 就放在MAIN函数下面,这些函数我倒觉得应该放在SD卡驱动里面。当然,个人意见。
SD卡测试函数 其他几个文件,前面例程已经在使用,我们就对比一下看看差异。
STM32F4XX_CONF.H,有差别,但是与SD卡无关。
STM32F4XX_IT.C,多了两个中断处理,移植的时候记得拷贝。
STM32F4XX_IT.C差异
STM32F4XX_IT.H多一个SDIO中断的声明,无关紧要
STM32F4XX_IT.H差异
SYSTEM_STM32F4XX.C,差别较大,但是很多都是为了兼容其他芯片的条件编译。 但是这个文件主要是初始化时钟,如果移植后调试不顺利,有问题,需要回来认真分析这个文件。
SYSTEM_STM32F4XX.C差异
OK,下一步我们就开始移植了。
建立一个MCU_SDIO驱动,将STM324XG_EVAL.C里的四个函数作为MCU_SDIO驱动,拷贝到MCU_SDIO.C。 STM324XG_EVAL.H里面与SD卡相关的定义也要拷贝到MCU_SDIO.H里面。
STM324XG_EVAL_SDIO_SD.C跟STM324XG_EVAL_SDIO_SD.H我们就直接使用。 作为BOARD_DEV驱动。
将MAIN.C里面的SD卡测试程序拷贝到STM324XG_EVAL_SDIO_SD.C最后,作为驱动测试程序使用。 原来的测试程序使用了一些官方硬件上的LED,我们全部改为串口调试信息输出。
有一个需要特别关注的地方就是STATIC VOID NVIC_CONFIGURATION(VOID)函数,这个函数的第一行就设置了NVIC的分组,也就是中断优先级分组,这个是ARM核相关。关于NVIC,前面章节有说明。
把STM32F4XX_IT.C里面的两个中断入口拷贝到我们的STM32F4XX_IT.C文件。
将新文件添加到工程,编译。21个错误,一个一个解决。挑几个看看 ..MCU_DEVMCU_SDIO.C(67): ERROR: #20: IDENTIFIER “SD_DETECT_GPIO_CLK” IS UNDEFINED 因为MCU_SDIO.C没有包含MCU_SDIO.H。增加#INCLUDE “MCU_SDIO.H”。 其实在我的个人认识当中,H文件应该是提供给外部使用。 哪些仅仅是驱动内部使用的定义,最好是定义到C文件内,不让其他文件看到。减少不必要的耦合。
重新编译,还有一个错误 ..BOARD_DEVSTM324XG_EVAL_SDIO_SD.H(39): ERROR: #5: CANNOT OPEN SOURCE INPUT FILE “STM324XG_EVAL.H”: NO SUCH FILE OR DIRECTORY 改为包含MCU_SDIO.H。
又有38个错误了。 ..BOARD_DEVSTM324XG_EVAL_SDIO_SD.H(128): ERROR: #20: IDENTIFIER “UINT32_T” IS UNDEFINED 应该是没包含STM32的头文件, 官方STM324XG_EVAL_SDIO_SD.H包含了#INCLUDE “STM324XG_EVAL.H”, 然后STM324XG_EVAL.H包含了#INCLUDE “STM32F4XX.H”和#INCLUDE “STM32_EVAL_LEGACY.H”,我们在MCU_SDIO.H中包含#INCLUDE “STM32F4XX.H”。
编译没有错误了。
开始做硬件移植,在MCU_SDIO.H第20行,例程用PH13做SD卡检测,我们都没有H口。我们用的是PC13.
在MCU_SDIO.C的初始化中,进行了初始化,个人认为这些跟硬件相关的,还是放到一处用宏定义较好,方便移植修改。 数据线,和我们的硬件一样,不需要修改。
/* CONFIGURE PC.08, PC.09, PC.10, PC.11 PINS: D0, D1, D2, D3 PINS */
命令线,也跟我们一样。
/* CONFIGURE PD.02 CMD LINE */
时钟线也一样。
/* CONFIGURE PC.12 PIN: CLK PIN */
那么就是检测脚不一样,我们看看这个管脚怎么用的。搜索后发现有一个SD_DETECT函数在使用。
/** * @BRIEF DETECT IF SD CARD IS CORRECTLY PLUGGED IN THE MEMORY SLOT. * @PARAM NONE * @RETVAL RETURN IF SD IS DETECTED OR NOT */UINT8_T SD_DETECT(VOID){
__IO UINT8_T STATUS = SD_PRESENT;
/*!< CHECK GPIO TO DETECT SD */
IF (GPIO_READINPUTDATABIT(SD_DETECT_GPIO_PORT, SD_DETECT_PIN) != BIT_RESET)
{
STATUS = SD_NOT_PRESENT;
}
RETURN STATUS;}
SDCARDSTATE SD_GETSTATE(VOID)和测试程序会使用SD_DETECT。到这里看出,检测管脚就是一个普通IO口,没有使用中断等其他功能,直接修改为我们的管脚即可。
重新编译,插上TF卡,下载程序运行。 初始化不成功。在初始化函数添加调试信息。 成功上电,进入初始化,卡信息也获取成功了,选卡也成功。
选卡成功 我们先看一下卡信息,卡信息结构体如下:
/** * @BRIEF SD CARD INFORMATION */TYPEDEF STRUCT{
SD_CSD SD_CSD;
SD_CID SD_CID;
UINT64_T CARDCAPACITY; /*!< CARD CAPACITY */
UINT32_T CARDBLOCKSIZE; /*!< CARD BLOCK SIZE */
UINT16_T RCA;
UINT8_T CARDTYPE;} SD_CARDINFO;
CSD跟CID是卡信息,包含比较多信息。例如卡什么卡,什么版本,V1.0,还是2.0等。 本处我们先把容量CAPACITY跟BLOCK SIZE打印出来,看跟我们的卡是不是一致。 调试代码如下,要注意的地方是容量的处理:如果直接使用UART_PRINTF打印容量,打印出来一个10,不正确,要拆分为两部分打印。
/*----------------- READ CSD/CID MSD REGISTERS ------------------*/
ERRORSTATUS = SD_GETCARDINFO(&SDCARDINFO);
UART_PRINTF("RN-------SD_GETCARDINFO OK----------RN");
UINT32_T *P;
P = (UINT32_T *)&(SDCARDINFO.CARDCAPACITY);
UART_PRINTF("RN-------CARDCAPACITY:%08X----------RN", *(P+1));
UART_PRINTF("RN-------CARDCAPACITY:%08X----------RN", *(P+0));
UART_PRINTF("RN-------CARDBLOCKSIZE:%D ----------RN", SDCARDINFO.CARDBLOCKSIZE);
UART_PRINTF("RN-------RCA:%D ----------RN", SDCARDINFO.RCA);
UART_PRINTF("RN-------CARDTYPE:%D ----------RN", SDCARDINFO.CARDTYPE);
卡信息调试LOG:
——-DEV_SDIO_TEST———- ——-SD_POWERON OK———- ——-SD_INITIALIZECARDS OK———- ——-SD_CSD.DEVICESIZE:15271———- ——-SD_GETCARDINFO OK———- ——-CARDCAPACITY:00000001———- ——-CARDCAPACITY:DD400000———- ——-CARDBLOCKSIZE:512 ———- ——-RCA:2 ———- ——-CARDTYPE:2 ———-
还有,刚刚等了很久发现,初始化返回结果4,数据超时。
查看SD_ERROR SD_ENABLEWIDEBUSOPERATION(UINT32_T WIDEMODE),分析大概流程后加上调试信息。 应该是陷入STATIC SD_ERROR SDENWIDEBUS(FUNCTIONALSTATE NEWSTATE)超时。 源码1103行
ELSE IF (SDIO_BUSWIDE_4B == WIDEMODE)
{
ERRORSTATUS = SDENWIDEBUS(ENABLE);
UART_PRINTF("SDENWIDEBUS:%DRN", ERRORSTATUS);
经查,在FINDSCR处,源码2438行。
UART_PRINTF("SDIO_GETRESPONSE OKRN");
/*!< GET SCR REGISTER */
ERRORSTATUS = FINDSCR(RCA, SCR);
UART_PRINTF("FINDSCR:%DRN", ERRORSTATUS);
FINDSCR函数内有个WHILE,估计程序就是卡在这里,源码2760行
WHILE (!(SDIO->STA & (SDIO_FLAG_RXOVERR | SDIO_FLAG_DCRCFAIL
| SDIO_FLAG_DTIMEOUT | SDIO_FLAG_DBCKEND | SDIO_FLAG_STBITERR))){
IF (SDIO_GETFLAGSTATUS(SDIO_FLAG_RXDAVL) != RESET)
{
*(TEMPSCR + INDEX) = SDIO_READDATA();
INDEX++;
}}
硬件修改后,SD卡初始化成功,但是测试失败。
——-DEV_SDIO_TEST———- ——-SD_POWERON OK———- ——-SD_INITIALIZECARDS OK———- ——-SD_CSD.DEVICESIZE:15271———- ——-SD_GETCARDINFO OK———- ——-CARDCAPACITY:00000001———- ——-CARDCAPACITY:DD400000———- ——-CARDBLOCKSIZE:512 ———- ——-RCA:2 ———- ——-CARDTYPE:2 ———- ——-SD_SELECTDESELECT OK———- SDIO_GETRESPONSE OK FINDSCR:0 2453 MDRESP1ERROR:0 2468 CMDRESP1ERROR:0 SDENWIDEBUS:0 ——-SD_ENABLEWIDEBUSOPERATION:0———- ——-SD_INIT OK———- ——-SD_ERASETEST….———-
查后,发现卡在等待传输结束的等待上。 SD_ERROR SD_WAITREADOPERATION(VOID)函数第一个等待就过不去。 前面读写已经没问题,唯一的区别就是这里使用了DMA。 这里的WHILE(1),就是等待中断或者DMA标志。源码349行
__IO SD_ERROR TRANSFERERROR = SD_OK;__IO UINT32_T TRANSFEREND = 0;//SDIO中断中会赋值0X01;__IO UINT32_T DMAENDOFTRANSFER = 0;//DMA中断中会赋值0X01;SD_CARDINFO SDCARDINFO;
网上百度,发现很多人说ST的DMA BUG,其中一个BUG是,在读写之前要发CMD16设置BLOCK大小,我们使用的库已经修改了这个BUG,源码1335行
/*!< SET BLOCK SIZE FOR CARD */
SDIO_CMDINITSTRUCTURE.SDIO_ARGUMENT = (UINT32_T) BLOCKSIZE;
SDIO_CMDINITSTRUCTURE.SDIO_CMDINDEX = SD_CMD_SET_BLOCKLEN;
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);
还有就是中断处理函数SD_ERROR SD_PROCESSIRQSRC(VOID),以前没有处理出错信息,现在已经处理了。 (从这里可以学一点,我们自己写的中断处理处理函数,最好也响应错误中断) 本处是DMA传输,DMA传输一般都要求字节对齐,否则会出错或者是死机。我们看下到底是死机了还是一直在等待。
既然可能死机,我们就使用CMSIS DAP调试一下,发现死在中断入口了。
未修改对 说明有一个中断源一直在进中断,或者是我们没有处理。 我们在DMA中断中增加了调试信息,但是却没有输出,很奇怪。 源码2065行
VOID SD_PROCESSDMAIRQ(VOID){
UART_PRINTF("-2-");
IF(DMA2->LISR & SD_SDIO_DMA_FLAG_TCIF)
{
DMAENDOFTRANSFER = 0X01;
DMA_CLEARFLAG(SD_SDIO_DMA_STREAM, SD_SDIO_DMA_FLAG_TCIF|SD_SDIO_DMA_FLAG_FEIF);
}}
搜索中断入口函数VOID SD_SDIO_DMA_IRQHANDLER(VOID); 发现,这个并没有在中断向量中定义,而是在MCU_SDIO.H中用宏定义。 真正的中断句柄是DMA2_STREAM3_IRQHANDLER,在移植的时候我们并没有处理,而且也不了解这样做要如何处理。 先改回DMA2_STREAM3_IRQHANDLER试试。MCU_SDIO.H第46行
#IFDEF SD_SDIO_DMA_STREAM3
#DEFINE SD_SDIO_DMA_STREAM DMA2_STREAM3
#DEFINE SD_SDIO_DMA_CHANNEL DMA_CHANNEL_4
#DEFINE SD_SDIO_DMA_FLAG_FEIF DMA_FLAG_FEIF3
#DEFINE SD_SDIO_DMA_FLAG_DMEIF DMA_FLAG_DMEIF3
#DEFINE SD_SDIO_DMA_FLAG_TEIF DMA_FLAG_TEIF3
#DEFINE SD_SDIO_DMA_FLAG_HTIF DMA_FLAG_HTIF3
#DEFINE SD_SDIO_DMA_FLAG_TCIF DMA_FLAG_TCIF3
#DEFINE SD_SDIO_DMA_IRQN DMA2_STREAM3_IRQN
#DEFINE SD_SDIO_DMA_IRQHANDLER DMA2_STREAM3_IRQHANDLER#ELIF DEFINED SD_SDIO_DMA_STREAM6
测试通过。SDIO硬件测试完成。
SDIO测试通过 程序测试通过,那到底是不是真的测试成功了呢? 我们可以用WINHEX软件查看TF卡内容,开头的几个数据块,已经被我们改为0X00–00XFF顺序增加的数据了,说明操作是成功的。
调试SD卡这一段写了很多,主要是让大家看看平时调试的方法,很多时候就是加调试信息,顺藤摸瓜。
遗留问题,SDIO驱动用了一个很大的BUF,这个要修改优化。 目前我们只是为了验证硬件,没有挂载文件系统,后续再加上。
上一篇:SPI FLASH