历史上的今天
返回首页

历史上的今天

今天是:2024年09月19日(星期四)

正在发生

2019年09月19日 | 第37章 基于SD卡的FatFs文件系统—零死角玩转STM32-F429系列

2019-09-19 来源:eefocus

上一章我们已经全面介绍了SD卡的识别和简单的数据读写,也进行了简单的读写测试,不过像这样直接操作SD卡存储单元,在实际应用中是不现实的。SD卡一般用来存放文件,所以都需要加载文件系统到里面。类似于串行Flash芯片,我们移植FatFs文件系统到SD卡内。


对于FatFs文件系统的介绍和具体移植过程参考"基于串行Flash的FatFs文件系统",这里就不做过多介绍,重点放在SD卡与FatFs接口函数编写上。与串行Flash的FatFs文件系统移植例程相比,FatFs文件系统部分的代码只有diskio.c文件有所不同,其他的不用修改,所以一个简易的移植方法是利用原来工程进行修改。下面讲解利用原来工程实现SD卡的FatFs文件系统。


37.1 FatFs移植步骤

上一章我们已经完成了SD卡驱动程序以及进行了简单的读写测试。该工程有很多东西是现在可以使用的,所以我们先把上一章的工程文件完整的拷贝一份,并修改文件夹名为"SDIO-FatFs移植与读写测试",如果此时使用KEIL软件打开该工程,应该是编译无错误并实现上一章的测试功能。


接下来,我们到串行Flash文件系统移植工程文件的"SPI—FatFs移植与读写测试User"文件夹下拷贝"FATFS"整个文件夹到现在工程文件的"SDIO—FatFs移植与读写测试User"文件夹下,如图 371。该文件夹是FatFs文件系统的所有代码文件,在串行Flash移植FatFs文件系统时我们对部分文件做了修改,这里主要是想要保留之前的配置,而不是使用FatFs官方

图 371 拷贝FatFs文件夹


现在就可以使用KEIL软件打开"SDIO-FatFs移植与读写测试"工程文件,并把FatFs相关文件添加到工程内,同时把sdio_test.c文件移除,参考图 372。

图 372 FatFs工程文件结构


添加文件之后还必须打开工程选项对话框添加相关路径,参考图 373。


图 373 添加FatFs路径到工程


操作到这来,工程文件结构就算完整了,接下来就是修改文件代码了。这来有两个文件需要修改,为diskio.c文件和main.c文件。main.c文件内容可以参考"SPI—FatFs移植与读写测试"工程中的main.c文件,只有做小细节修改而已。这来重点讲解diskio.c文件,也是整个移植的重点。


37.2 FatFs接口函数

FatFs文件系统与存储设备的连接函数在diskio.c文件中,主要有5个函数需要我们编写的。


宏定义和存储设备状态获取函数

代码清单 371 宏定义和disk_status函数


1 //宏定义


2 #define ATA 0 // SD卡


3 #define SPI_FLASH 1 // 预留外部SPI Flash使用


4 // SD卡块大小


5 #define SD_BLOCKSIZE 512


6


7 //存储设备状态获取


8 DSTATUS disk_status (


9 BYTE pdrv /* 物理编号 */


10 )


11 {


12 DSTATUS status = STA_NOINIT;


13 switch (pdrv) {


14 case ATA: /* SD CARD */


15 status &= ~STA_NOINIT;


16 break;


17


18 case SPI_FLASH: /* SPI Flash */


19 break;


20


21 default:


22 status = STA_NOINIT;


23 }


24 return status;


25 }


FatFs支持同时挂载多个存储设备,通过定义为不同编号以区别。SD卡一般定义为编号0,编号1预留给串行Flash芯片使用。使用宏定义方式给出SD卡块大小,方便修改。实际上,SD卡块大小一般都是设置为512字节的,不管是标准SD卡还是高容量SD卡。


disk_status函数要求返回存储设备的当前状态,对于SD卡一般返回SD卡插入状态,这里直接返回正常状态。


存储设备初始化函数

代码清单 372 disk_initialize函数


1 DSTATUS disk_initialize (


2 BYTE pdrv /* 物理编号 */


3 )


4 {


5 DSTATUS status = STA_NOINIT;


6 switch (pdrv) {


7 case ATA: /* SD CARD */


8 if (SD_Init()==SD_OK) {


9 status &= ~STA_NOINIT;


10 } else {


11 status = STA_NOINIT;


12 }


13


14 break;


15


16 case SPI_FLASH: /* SPI Flash */


17 break;


18


19 default:


20 status = STA_NOINIT;


21 }


22 return status;


23 }


该函数用于初始化存储设备,一般包括相关GPIO初始化、外设环境初始化、中断配置等等。对于SD卡,直接调用SD_Init函数实现对SD卡初始化,如果函数返回SD_OK说明SD卡正确插入,并且控制器可以与之正常通信。


存储设备数据读取函数

代码清单 373 disk_read函数


1 DRESULT disk_read (


2 BYTE pdrv, /* 设备物理编号(0..) */


3 BYTE *buff, /* 数据缓存区 */


4 DWORD sector, /* 扇区首地址 */


5 UINT count /* 扇区个数(1..128) */


6 )


7 {


8 DRESULT status = RES_PARERR;


9 SD_Error SD_state = SD_OK;


10


11 switch (pdrv) {


12 case ATA: /* SD CARD */


13 if ((DWORD)buff&3) {


14 DRESULT res = RES_OK;


15 DWORD scratch[SD_BLOCKSIZE / 4];


16


17 while (count--) {


18 res = disk_read(ATA,(void *)scratch, sector++, 1);


19


20 if (res != RES_OK) {


21 break;


22 }


23 memcpy(buff, scratch, SD_BLOCKSIZE);


24 buff += SD_BLOCKSIZE;


25 }


26 return res;


27 }


28


29 SD_state=SD_ReadMultiBlocks(buff,sector*SD_BLOCKSIZE,


30 SD_BLOCKSIZE,count);


31 if (SD_state==SD_OK) {


32 /* Check if the Transfer is finished */


33 SD_state=SD_WaitReadOperation();


34 while (SD_GetStatus() != SD_TRANSFER_OK);


35 }


36 if (SD_state!=SD_OK)


37 status = RES_PARERR;


38 else


39 status = RES_OK;


40 break;


41


42 case SPI_FLASH:


43 break;


44


45 default:


46 status = RES_PARERR;


47 }


48 return status;


49 }


disk_read函数用于从存储设备指定地址开始读取一定的数量的数据到指定存储区内。对于SD卡,最重要是使用SD_ReadMultiBlocks函数读取多块数据到存储区。这里需要注意的地方是SD卡数据操作是使用DMA传输的,并设置数据尺寸为32位大小,为实现数据正确传输,要求存储区是4字节对齐。在某些情况下,FatFs提供的buff地址不是4字节对齐,这会导致DMA数据传输失败,所以为保证数据传输正确,可以先判断存储区地址是否是4字节对齐,如果存储区地址已经是4字节对齐,无需其他处理,直接使用SD_ReadMultiBlocks函数执行多块读取即可。如果判断得到地址不是4字节对齐,则先申请一个4字节对齐的临时缓冲区,即局部数组变量scratch,通过定义为DWORD类型可以使得其自动4字节对齐,scratch所占的总存储空间也是一个块大小,这样把一个块数据读取到scratch内,然后把scratch存储器内容拷贝到buff地址空间上就可以了。


SD_ReadMultiBlocks函数用于从SD卡内读取多个块数据,它有四个形参,分别为存储区地址指针、起始块地址、块大小以及块数量。为保证数据传输完整,还需要调用SD_WaitReadOperation函数和SD_GetStatus函数检测和保证传输完成。


存储设备数据写入函数

代码清单 374 disk_write函数


1 #if _USE_WRITE


2 DRESULT disk_write (


3 BYTE pdrv, /* 设备物理编号(0..) */


4 const BYTE *buff, /* 欲写入数据的缓存区 */


5 DWORD sector, /* 扇区首地址 */


6 UINT count /* 扇区个数(1..128) */


7 )


8 {


9 DRESULT status = RES_PARERR;


10 SD_Error SD_state = SD_OK;


11


12 if (!count) {


13 return RES_PARERR; /* Check parameter */


14 }


15


16 switch (pdrv) {


17 case ATA: /* SD CARD */


18 if ((DWORD)buff&3) {


19 DRESULT res = RES_OK;


20 DWORD scratch[SD_BLOCKSIZE / 4];


21


22 while (count--) {


23 memcpy( scratch,buff,SD_BLOCKSIZE);


24 res = disk_write(ATA,(void *)scratch, sector++, 1);


25 if (res != RES_OK) {


26 break;


27 }


28 buff += SD_BLOCKSIZE;


29 }


30 return res;


31 }


32


33 SD_state=SD_WriteMultiBlocks((uint8_t *)buff,sector*SD_BLOCKSIZE,


34 SD_BLOCKSIZE,count);


35 if (SD_state==SD_OK) {


36 /* Check if the Transfer is finished */


37 SD_state=SD_WaitReadOperation();


38


39 /* Wait until end of DMA transfer */


40 while (SD_GetStatus() != SD_TRANSFER_OK);


41 }


42 if (SD_state!=SD_OK)


43 status = RES_PARERR;


44 else


45 status = RES_OK;


46 break;


47


48 case SPI_FLASH:


49 break;


50


51 default:


52 status = RES_PARERR;


53 }


54 return status;


55 }


56 #endif


disk_write函数用于向存储设备指定地址写入指定数量的数据。对于SD卡,执行过程与disk_read函数是非常相似,也必须先检测存储区地址是否是4字节对齐,如果是4字节对齐则直接调用SD_WriteMultiBlocks函数完成多块数据写入操作。如果不是4字节对齐,申请一个4字节对齐的临时缓冲区,先把待写入的数据拷贝到该临时缓冲区内,然后才写入到SD卡。


SD_WriteMultiBlocks函数是向SD卡写入多个块数据,它有四个形参,分别为存储区地址指针、起始块地址、块大小以及块数量,它与SD_ReadMultiBlocks函数执行相互过程。最后也是需要使用相关函数保存数据写入完整才退出disk_write函数。


其他控制函数

代码清单 375 disk_ioctl函数


1 #if _USE_IOCTL


2 DRESULT disk_ioctl (


3 BYTE pdrv, /* 物理编号 */


4 BYTE cmd, /* 控制指令 */


5 void *buff /* 写入或者读取数据地址指针 */


6 )


7 {


8 DRESULT status = RES_PARERR;


9 switch (pdrv) {


10 case ATA: /* SD CARD */


11 switch (cmd) {


12 // Get R/W sector size (WORD)


13 case GET_SECTOR_SIZE :


14 *(WORD * )buff = SD_BLOCKSIZE;


15 break;


16 // Get erase block size in unit of sector (DWORD)


17 case GET_BLOCK_SIZE :


18 *(DWORD * )buff = SDCardInfo.CardBlockSize;


19 break;


20


21 case GET_SECTOR_COUNT:


22 *(DWORD*)buff = SDCardInfo.CardCapacity/SDCardInfo.CardBlockSize;


23 break;


24 case CTRL_SYNC :


25 break;


26 }


27 status = RES_OK;


28 break;


29


30 case SPI_FLASH:


31 break;


32


33 default:


34 status = RES_PARERR;


35 }


36 return status;


37 }


38 #endif


disk_ioctl函数有三个形参,pdrv为设备物理编号,cmd为控制指令,包括发出同步信号、获取扇区数目、获取扇区大小、获取擦除块数量等等指令,buff为指令对应的数据指针。


对于SD卡,为支持格式化功能,需要用到获取扇区数量(GET_SECTOR_COUNT)指令和获取块尺寸(GET_BLOCK_SIZE)。另外,SD卡扇区大小为512字节,串行Flash芯片一般设置扇区大小为4096字节,所以需要用到获取扇区大小(GET_SECTOR_SIZE)指令。


至此,基于SD卡的FatFs文件系统移植就已经完成了,最重要就是diskio.c文件中5个函数的编写。接下来就编写FatFs基本的文件操作检测移植代码是否可以正确执行。


37.3 FatFs功能测试

主要的测试包括格式化测试、文件写入测试和文件读取测试三个部分,主要程序都在main.c文件中实现。


变量定义

代码清单 376 变量定义


1 FATFS fs; /* FatFs文件系统对象 */


2 FIL fnew; /* 文件对象 */


3 FRESULT res_sd; /* 文件操作结果 */


4 UINT fnum; /* 文件成功读写数量 */


5 BYTE ReadBuffer[1024]= {0}; /* 读缓冲区 */


6 BYTE WriteBuffer[] = /* 写缓冲区*/


7 "欢迎使用野火STM32 F429开发板今天是个好日子,新建文件系统测试文件rn";


FATFS是在ff.h文件定义的一个结构体类型,针对的对象是物理设备,包含了物理设备的物理编号、扇区大小等等信息,一般我们都需要为每个物理设备定义一个FATFS变量。


FIL也是在ff.h文件定义的一个结构体类型,针对的对象是文件系统内具体的文件,包含了文件很多基本属性,比如文件大小、路径、当前读写地址等等。如果需要在同一时间打开多个文件进行读写,才需要定义多个FIL变量,不然一般定义一个FIL变量即可。


FRESULT是也在ff.h文件定义的一个枚举类型,作为FatFs函数的返回值类型,主要管理FatFs运行中出现的错误。总共有19种错误类型,包括物理设备读写错误、找不到文件、没有挂载工作空间等等错误。这在实际编程中非常重要,当有错误出现是我们要停止文件读写,通过返回值我们可以快速定位到错误发生的可能地点。如果运行没有错误才返回FR_OK。


fnum是个32位无符号整形变量,用来记录实际读取或者写入数据的数组。


buffer和textFileBuffer分别对应读取和写入数据缓存区,都是8位无符号整形数组。


主函数

代码清单 377 main函数


1 int main(void)


2 {


3 /* 禁用WiFi模块 */


4 BL8782_PDN_INIT();


5


6 /* 初始化LED */


7 LED_GPIO_Config();


8 LED_BLUE;


9


10 /* 初始化调试串口,一般为串口1 */


11 Debug_USART_Config();


12 printf("rn****** 这是一个SD卡文件系统实验 ******rn");


13


14 //在外部SPI Flash挂载文件系统,文件系统挂载时会对SPI设备初始化


15 res_sd = f_mount(&fs,"0:",1);


16


17 /*----------------------- 格式化测试 ---------------------------*/


18 /* 如果没有文件系统就格式化创建创建文件系统 */


19 if (res_sd == FR_NO_FILESYSTEM) {


20 printf("》SD卡还没有文件系统,即将进行格式化...rn");


21 /* 格式化 */


22 res_sd=f_mkfs("0:",0,0);


23


24 if (res_sd == FR_OK) {


25 printf("》SD卡已成功格式化文件系统。rn");


26 /* 格式化后,先取消挂载 */


27 res_sd = f_mount(NULL,"0:",1);


28 /* 重新挂载 */


29 res_sd = f_mount(&fs,"0:",1);


30 } else {


31 LED_RED;


32 printf("《《格式化失败。》》rn");


33 while (1);


34 }


35 } else if (res_sd!=FR_OK) {


36 printf("!!SD卡挂载文件系统失败。(%d)rn",res_sd);


37 printf("!!可能原因:SD卡初始化不成功。rn");


38 while (1);


39 } else {


40 printf("》文件系统挂载成功,可以进行读写测试rn");


41 }


42


43 /*--------------------- 文件系统测试:写测试 -----------------------*/


44 /* 打开文件,如果文件不存在则创建它 */


45 printf("rn****** 即将进行文件写入测试... ******rn");


46 res_sd=f_open(&fnew,"0:FatFs读写测试文件.txt",FA_CREATE_ALWAYS|FA_WRITE);


47 if ( res_sd == FR_OK ) {


48 printf("》打开/创建FatFs读写测试文件.txt文件成功,向文件写入数据。rn");


49 /* 将指定存储区内容写入到文件内 */


50 res_sd=f_write(&fnew,WriteBuffer,sizeof(WriteBuffer),&fnum);


51 if (res_sd==FR_OK) {


52 printf("》文件写入成功,写入字节数据:%dn",fnum);


53 printf("》向文件写入的数据为:rn%srn",WriteBuffer);


54 } else {


55 printf("!!文件写入失败:(%d)n",res_sd);


56 }


57 /* 不再读写,关闭文件 */


58 f_close(&fnew);


59 } else {


60 LED_RED;


61 printf("!!打开/创建文件失败。rn");


62 }


63


64 /*------------------ 文件系统测试:读测试 --------------------------*/


65 printf("****** 即将进行文件读取测试... ******rn");


66 res_sd=f_open(&fnew,"0:FatFs读写测试文件.txt",FA_OPEN_EXISTING|FA_READ);


67 if (res_sd == FR_OK) {


68 LED_GREEN;


69 printf("》打开文件成功。rn");


70 res_sd = f_read(&fnew, ReadBuffer, sizeof(ReadBuffer), &fnum);


71 if (res_sd==FR_OK) {


72 printf("》文件读取成功,读到字节数据:%drn",fnum);


73 printf("》读取得的文件数据为:rn%s rn", ReadBuffer);


74 } else {


75 printf("!!文件读取失败:(%d)n",res_sd);


76 }


77 } else {


78 LED_RED;


79 printf("!!打开文件失败。rn");


80 }


81 /* 不再读写,关闭文件 */


82 f_close(&fnew);


83


84 /* 不再使用文件系统,取消挂载文件系统 */


85 f_mount(NULL,"0:",1);


86


87 /* 操作完成,停机 */


88 while (1) {


89 }


90 }


首先,调用BL8782_PDN_INIT函数禁用WiFi模块,接下来初始化RGB彩灯和调试串口,用来指示程序进程。


FatFs的第一步工作就是使用f_mount函数挂载工作区。f_mount函数有三个形参,第一个参数是指向FATFS变量指针,如果赋值为NULL可以取消物理设备挂载。第二个参数为逻辑设备编号,使用设备根路径表示,与物理设备编号挂钩,在代码清单 371中我们定义SD卡物理编号为0,所以这里使用"0:"。第三个参数可选0或1,1表示立即挂载,0表示不立即挂载,延迟挂载。 f_mount函数会返回一个FRESULT类型值,指示运行情况。


如果f_mount函数返回值为FR_NO_FILESYSTEM,说明SD卡没有FAT文件系统。我们就必须对SD卡进行格式化处理。使用f_mkfs函数可以实现格式化操作。f_mkfs函数有三个形参,第一个参数为逻辑设备编号;第二参数可选0或者1,0表示设备为一般硬盘,1表示设备为软盘。第三个参数指定扇区大小,如果为0,表示通过代码清单 375中disk_ioctl函数获取。格式化成功后需要先取消挂载原来设备,再重新挂载设备。


在设备正常挂载后,就可以进行文件读写操作了。使用文件之前,必须使用f_open函数打开文件,不再使用文件必须使用f_close函数关闭文件,这个跟电脑端操作文件步骤类似。f_open函数有三个形参,第一个参数为文件对象指针。第二参数为目标文件,包含绝对路径的文件名称和后缀名。第三个参数为访问文件模式选择,可以是打开已经存在的文件模式、读模式、写模式、新建模式、总是新建模式等的或运行结果。比如对于写测试,使用FA_CREATE_ALWAYS和FA_WRITE组合模式,就是总是新建文件并进行写模式。

推荐阅读

史海拾趣

Embedded Planet公司的发展小趣事

Embedded Planet公司深知人才是企业发展的核心驱动力。因此,公司一直致力于培养和吸引优秀人才,建立了一支高效、专业的团队。公司为员工提供丰富的培训和发展机会,鼓励员工不断学习和创新。同时,公司还注重团队建设和文化塑造,营造了一个积极向上、团结协作的工作氛围。这些努力使得Embedded Planet公司成为了一个充满活力和创造力的企业。

Gumstix公司的发展小趣事
检查电路连接是否松动或断裂,重新连接或更换损坏的部件。
国盛科技(BOCHEN)公司的发展小趣事

随着市场竞争的加剧,国盛科技意识到传统生产方式已难以满足市场需求。于是,公司开始积极探索自动化生产之路。经过不懈努力,国盛科技成功自主研发了电位器自动包装设备、自动测试设备等多种自动化设备,实现了产品流水化生产。这一突破不仅大大提高了生产效率,还使得电位器年产量突破一亿只,为国盛科技赢得了市场的广泛认可。

达晶微(CREATEK)公司的发展小趣事

为了加强研发实力和应用能力,达晶微在上海设立了研发中心和应用中心。这些中心汇聚了来自全球的优秀人才和先进技术,为公司提供了强大的技术支持和创新动力。通过不断研发新产品和拓展新领域,达晶微在电子行业中保持了领先地位。

AW Industries Inc公司的发展小趣事

在国内市场取得成功后,AW Industries Inc开始寻求国际化发展。公司首先选择了几个具有潜力的海外市场进行拓展,并通过与当地企业合作、建立销售网络等方式逐步打开市场。随着国际化战略的深入实施,公司的业务范围不断扩大,国际市场份额也逐步增加。

Dailywell Electronics Co Ltd公司的发展小趣事

进入21世纪后,德利威电子加速了其国际化布局的步伐。2000年,公司在中国大陆东莞成立了德利威电子厂,并成功推动了5S运动(整理、整顿、清扫、清洁、素养),进一步提升了公司的管理水平。同时,公司还取得了ISO-9001国际认证标准,标志着其质量管理体系的进一步完善。此外,德利威电子还取得了德国TUV产品安规认证,进一步增强了其产品的国际竞争力。

问答坊 | AI 解惑

共享一份论文的评分规则

本帖最后由 paulhyde 于 2014-9-15 09:34 编辑 一份整理出来的论文评分规则!与大家分享!大赛即将来临,大家一起加油!  …

查看全部问答>

三路同步循环怎么做

三路流水循环,123123123式的,两盏各自的灯能同时亮灭。 我用CD4024从电网中分频提取同步信号,再用CD4017做三路输出,能实现。 觉的实现这么简单的原理要用两个IC有点麻烦,而且空脚很多, 请问大吓们,有没有更好的方法。…

查看全部问答>

项目前期分析可行性报告

大家好。小弟做数字也有2年多了。前面都是做的底层实现既从详细方案开始一直到最后的FPGA上板都经历过,对于细节比较清楚。但缺少项目整体的经验,特别是功耗,面积,规模,电压电流这种前期分析阶段。  目前公司给了小弟一个任务,做某 ...…

查看全部问答>

wince5.0 nand flash 驱动问题

    最近要向一个基于mips的soc上移植nand flash驱动,对wince了解不深,nand flash也不懂。有几个疑问不明白,请达人解惑。 1.我看到wince提供了WINCE500\\PUBLIC\\COMMON\\OAK\\DRIVERS\\BLOCK\\MSFLASHFMD目录里的一系列代码。看注释 ...…

查看全部问答>

单片机

   本人现在做毕业设计,题目是用AT89051和ADC0809设计多路数字电压表,望高手能够指点一下,不甚感激!…

查看全部问答>

杂牌的PCI GPIB 卡可以用NI或者agilent的DLL吗?

刚接到 一个任务,是实现PC与一个信号源进行通信。 信号源的接口是IEEE488接口。 公司给我买了一个杂牌的PCI GPIB卡, 现在我将卡装在电脑上,随卡的驱动也装好了, 可是没有大家所说的任何编程开发界面和配置界面, 只是在设备管理器里的树状 ...…

查看全部问答>

主轴自己摆动,震荡?

我司有台车削中心,带C轴,现在一开机主轴就自己正反反复不停摆动(没有进行任何操作),无任何报警,是哪里的问题呀?主轴是电主轴,只有一个海德汉编码器,系统是西门子840D的。另本以为是个别案例,不料另一台也是一样的问题,两台机器08年生产 ...…

查看全部问答>

FPGA设计入门学习(基本概念认识)

1 数字电路设计中的几个基本概念: 1.1 建立时间和保持时间:建立时间(setup time)是指在触发器的时钟信号上升沿到来以前,数据稳定不变的时间,如果建立时间不够,数据将不能在这个时钟上升沿被打入触发器;保持时间(hold time)是指在触发器 ...…

查看全部问答>

全国赛PCB板可以代工不?

本帖最后由 paulhyde 于 2014-9-15 09:39 编辑 参加全国电子设计大赛,想做四层板(自己设计板子、布局、布线),但学校的设备不能做出四层板,如果交给相应的PCB制作工厂制板,是不是违反了比赛规则?  …

查看全部问答>