上一节,完成了eCos STM32 SD driver代码的编写,展现了如何借鉴第三方驱动快速部署驱动代码的实例。与时同时,再次看到了eCos DMA的运用。
本节主要介绍eCos STM32 SD卡驱动的调试,再次完整地呈现了eCos驱动的调试过程。如果说编写代码大多数是复制粘贴,那么通过调试,则让我了解了SD卡存储结构、FAT文件系统知识及其数据分析,最重要的是了解了eCos的FAT和Block library(块缓存)程序。
调试必备
1. 调试之前,建议先了解下SD卡存储结构和FAT文件系统的知识,可参考本博客的文章:
2. 调试开关,调试开关有disk设备驱动、disk IO、FAT、Block lib等调试开关。
disk设备驱动位于mmc_stm32.c文件中开关定义的宏:#define DEBUG 0,这是个分级的调试开关,为1时,打印一般的调试信息;为2时,会打印SD block读写数据。
disk IO、FAT、Block lib等调试开关,分别位于相应源文件中:diskio.c、fatfs.h、blib.c,可视需要打开。
3. 调试方法,主要采用打印调试方法,可根据需要使用仿真器进行调试。
4. 调试顺序,总共有3个阶段:
- 初步运行驱动,如果没有问题,进入第2阶段;
- SD卡mount,这是调试重点,涉及SD卡初始化及其识别调试、STM32 SDIO DMA传输、FAT文件系统初始化等;
- 在SD卡中调试目录、文件的打开、数据读写等操作,以及SD卡磁盘信息的读取等调试;
限于篇幅,本节主要介绍前面2个阶段的调试过程。
初步运行
编译eCos STM32 SD卡驱动及其测试程序,生成可执行映像文件并启动运行。我是通过往串口输入字符来启动测试程序的。
启动运行后,观察mmc_stm32_disk_init()函数运行情况。然后启动应用测试程序,执行mount(),进入第2个阶段的调试。
mount阶段调试
开始调试的时候,mount SD卡百分百出问题。测试程序执行mount后会返回以下错误信息(基于eCos自带的fatfs.c测试程序):
<FAIL>: mount() returned -1 Invalid argument
对于这个错误信息,开始的时候,我是一头误水,甚至还从mount()函数一层层去追。对于fat文件系统,mount的真正入口点是fatfs.c源文件中的fatfs_mount()函数(类似,open/read/write/close等也对应该源文件中的相关函数)。
在fatfs_mount()函数中,先lookup对应的设备,实际上就是执行mmc_stm32.c中的mmc_stm32_disk_lookup()函数。OK,从这里开始慢慢查找出现上述错误的原因吧。
程序卡死
在执行stm32_mmc_init()函数中,会有卡死现象(因为驱动中有很多死循环)。对于卡死的问题,根据打印信息先定位被卡住的代码段,然后与STM32官方库代码实现进行对比,这样基本上可以解决该问题。
FILE FORMAT GROUP问题
在下面代码中会直接返回:
// There is information available about the file format, e.g. // partitioned vs. simple FAT. With the current version of the // generic disk code this needs to be known statically, via // the mbr field of the disk channel structure. If the card // is inappropriately formatted, reject the mount request. if ((0 != MMC_CSD_REGISTER_FILE_FORMAT_GROUP(csd)) || (0 != MMC_CSD_REGISTER_FILE_FORMAT(csd))) { DEBUG1("Error, NOTDIR!\n"); // return -ENOTDIR; }
这段代码是从mmc_spi.c中拷贝过来的。但在我的驱动中,由于MMC_CSD_REGISTER_FILE_FORMAT_GROUP(csd)值为1,所以就直接返回了。至于是什么错误,没有去研究,反正把return注释掉也没有影响。
DMA传输问题
在stm32_mmc_check_for_disk()函数中,执行完SD卡初始化和识别后,就会调用stm32_mmc_read_disk_block()函数读取block 0数据块。但读SD卡数据块时,出现了异常现象:表面现象是DMA传输不稳定(一会儿可以一会儿失败),实质是DMA读取数据失败。DMA打印信息如下(注意加粗部分):
DMA: stm32_dma_channel_setup[1639]: Bytes to transfer : 128 * 4, (count = 512)
DMA: stm32_dma_channel_setup[1641]: ctlr 40020400 stream 4 chan 0
DMA: stm32_dma_channel_setup[1642]: vector 60 stream->ccr 00002a81
DMA: stm32_dma_channel_setup[1645]: DMA ISR: 00000000
DMA: stm32_dma_channel_setup[1647]: DMA 4 CCR: 00002a81
DMA: stm32_dma_channel_setup[1648]: DMA 4 CNDTR: 00000067
DMA: stm32_dma_channel_setup[1649]: DMA 4 CPAR: 40018080
DMA: stm32_dma_channel_setup[1650]: DMA 4 CMAR: 20000c0c
***************************************************************
DMA 4 CNDTR: 00000067
ctlr 40020400 stream 4 chan 0 isr 00000000
STM32 SDIO DMA transfer RXOVERR(Received FIFIO overrun error)
STM32 SDIO : Transfer error! status = 0x00202020
或者如下:
DMA: stm32_dma_channel_setup[1639]: Bytes to transfer : 128 * 4, (count = 512)
DMA: stm32_dma_channel_setup[1641]: ctlr 40020400 stream 4 chan 0
DMA: stm32_dma_channel_setup[1642]: vector 60 stream->ccr 00002a81
DMA: stm32_dma_channel_setup[1645]: DMA ISR: 00007000
DMA: stm32_dma_channel_setup[1647]: DMA 4 CCR: 00002a81
DMA: stm32_dma_channel_setup[1648]: DMA 4 CNDTR: 00000000
DMA: stm32_dma_channel_setup[1649]: DMA 4 CPAR: 40018080
DMA: stm32_dma_channel_setup[1650]: DMA 4 CMAR: 20000c0c
***************************************************************
DMA Callbacks, count = 0
DMA 4 CNDTR: 00000000
ctlr 40020400 stream 4 chan 0 isr 00000000
STM32 SDIO DMA transfer DATAEND(Data end (data counter, SDIO_DCOUNT, is zero))
STM32 SDIO DMA transfer(read) OK!
DMA焦点问题是,为什么出现RXOVERR错误(偶尔有DCRCFAIL错误)。
网上大多数的说法是,是SDIO的CLK频率过高的原因,但即使我把它从24MHz改成18MHz、从4bit传输改成1bit传输,问题依旧。后来找到一个十分有用的帖子:http://www.eefocus.com/bbs/article_244_189560.html
受该帖子的启发,从3方面着手尝试解决该问题:
- 改写DMA实现,原先是参考eCos STM32 SPI DMA以及自己写的I2S DMA的实现。改写成:在发送CMD17命令给SD卡以启动SD卡发送数据之前,先启动STM32 SDIO的DMA通道,对应现有驱动的stm32_dma_transaction_begin()函数,然后再发送CMD17命令,最后等待DMA传输完成和数据处理工作,分别对应stm32_dma_transaction()函数和stm32_dma_transaction_end()函数。
- 根据上述帖子的方法,开启SDIO的硬件流控功能。
- 降低SDIO的CLK频率,使用18MHz频率。
以上3方面,主要是避免SDIO FIFO出现溢出即避免RXOVERR错误。其中第1条,对于read,先启动SDIO的DMA通道传输数据,然后再启动SD卡的数据传输;对于write,则先启动SD卡准备接收数据,然后再启动SDIO的DMA通道传输数据,最后使能SDIO开始发送数据。经过上述改进后,DMA传输问题终于解决了,能正确读取到SD卡block 0数据了。但并没有解决mount失败的问题。
扇区大小问题
stm32_mmc_check_for_disk()函数中,会从SD卡的CSD数据中提取出SD卡的读写块长度,但发觉有异常,所以就修改了下,如下代码所示:
// Assume for now that the block length is 512 bytes. This is // probably a safe assumption since we have just got the card // initialized out of idle state. If it ever proves to be a problem // the SET_BLOCK_LEN command can be used. // Nevertheless store the underlying block sizes #if 0 // mmc_spi.c中的代码 disk->mmc_read_block_length = 0x01 << MMC_CSD_REGISTER_READ_BL_LEN(csd); disk->mmc_write_block_length = 0x01 << MMC_CSD_REGISTER_WRITE_BL_LEN(csd); #else // 修改的代码 disk->mmc_read_block_length = MMC_STM32_BLOCK_SIZE; disk->mmc_write_block_length= MMC_STM32_BLOCK_SIZE; #endif
LBA格式与CHS格式
在stm32_mmc_check_for_disk()函数中,读取SD卡block 0数据(对应SD卡MBR)后,会把MBR中的分区信息提取出来,分别取出CHS格式(cylinder/head/sector)和LBA格式(Logic Block Address)数据。调试时,因为这些数据在mmc_stm32_disk_lookup()中有用到,所以我一度以为mount失败问题与此有关。实际上是没用的。
read_boot_record()函数的bug
分析完上述几个问题后,mount依然是失败。在fatfs_mount()函数中执行完lookup后,会执行fatfs_init(),之后调用read_boot_record()函数,即去读取SD卡DBR数据(引导扇区数据)。发现执行完这个函数后,就返回出错了。看来问题点应该就是在这里了。
很幸运的是,当我以read_boot_record()关键字搜索的时候,发现有人遇到了同样的问题。见文章:http://blog.csdn.net/rill_zhen/article/details/9365477
eCos默认是从SD卡的第0个sector读取boot record,但是并不是所有的SD卡的文件系统格式化信息都存在那里,所以,如果你的SD卡的文件系统信息没有存在第0个sector,eCos就无法挂载。
如果按照上述链接文章所给的方法进行修改,我觉得修改的并不是很完整。所以我的修改如下:
修改fatfs_supp.c源文件的read_boot_record()函数,在该函数局部变量定义后面添加如下代码:
// Added by reille 2013.07.26 len = 4; disk->start = (0 * 512);// Read MBR resided block 0; err = disk_read(disk, (void*)data, &len, 0x01c6); if (err != ENOERR) { return err; } disk->start_block_num = data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); disk->start = (disk->start_block_num * 512); // CYG_TRACE2(TDE, "Start: block %d, offset: 0x%08X)", start, (start * 512));
同时在结构体fatfs_disk_s(fatfs.h文件中)中添加两个成员变量的定义,如下:
cyg_uint32 start; // first sector byte address | Added by reille 2013.07.27 cyg_uint32 start_block_num;
修改fatfs_supp.c源文件中的disk_write()和disk_read()两函数,如下:
// ------------------------------------------------------------------------- // disk_write() // Writes data to disk. static __inline__ int disk_write(fatfs_disk_t *disk, void *buf, cyg_uint32 *len, cyg_uint32 pos) { // return cyg_blib_write(&disk->blib, buf, len, 0, pos);// Mask by reille 2013.07.27 // Added by reille 2013.07.27 //diag_printf("disk_write, (pos + disk->start) = %d\n", (pos + disk->start)); return cyg_blib_write(&disk->blib, buf, len, 0, (pos + disk->start)); } // ------------------------------------------------------------------------- // disk_read() // Reads data from disk. static __inline__ int disk_read(fatfs_disk_t *disk, void *buf, cyg_uint32 *len, cyg_uint32 pos) { // return cyg_blib_read(&disk->blib, buf, len, 0, pos);// Mask by reille 2013.07.27 // Added by reille 2013.07.27 //diag_printf("disk_read, (pos + disk->start) = %d\n", (pos + disk->start)); return cyg_blib_read(&disk->blib, buf, len, 0, (pos + disk->start)); }
经上述修改后,运行fatfs1.c测试程序或者我写的测试程序(见http://52ecos.net/thread-600-1-1.html),不仅可以mount成功,而且能成功打开SD卡目录和列出SD卡的目录文件信息。但是遗憾的是,当在SD卡中建立目录或者文件时,则会破坏SD卡中的文件系统格式数据,导致原SD卡的目录和文件都找不到了。这个问题我定位了很久,到最后才发现,原来是在DMA函数中范了个错误,详细情况请关注下节。