前言
提示::
这篇文章记录一下移植FatFs遇到的问题,不限于如下问题:
- 在SDIO驱动移植成功并使用SD_test函数成功读取到SD卡各种数据的情况下,使用f_mount挂载文件系统失败返回0x0C。
- 使用f_open函数在SD卡上找不到文件系统返回0x0C。
- check_fs函数鉴定引导签名错误。
······
以下是本篇文章正文内容
这里我就先把我的FatFs和SDIO驱动直接打包了,下面也会有对应源码的介绍,如果还有问题可以私信和评论。
链接:https://pan.baidu.com/s/1tCtjA2udAmpf_rhS1iZkiQ?pwd=adfs
提取码:adfs
一、FatFs
FatFs是一种嵌入式型文件系统,它本身用于解决当代内存容量逐渐增加,内存管理难度逐渐上升的情况下,提供一种高效的内存管理方式。使用文件系统能更加系统的管理SD卡,TF卡等的内存。
二、使用步骤
- 首先,现在在移植FatFs的人还请静下心来,看了这篇文章一定能成功移植,因为如果你出现了我开头讲的那几个问题,一定就是你没冷静下来分析和调试。
- 这个是最主要的原因,很多人估计就是直接照着野火的移植,估计代码也没自己分析过就直接移植进来了,这里就是最主要的问题。
- 估计也没分析过FatFs的项目结构和接口文档,哎,所以不能太急躁。
- 这么说吧,问题出现在diskio.c文件中,这个文件中给的5个函数就是FatFs和单片机的底层驱动的中间层,你移植的时候估计连官方给的SDIO驱动都没验证过吧。所以接下来的第一件事就是跟着我来验证驱动。
1.SDIO驱动库不完善
- 首先我使用的SDIO是ST官方给的最新v1.9版本的固件库,首先这个驱动库本身确实没什么问题,我在使用官方驱动的SD_test(就是main函数改名)能正常擦写单块和多块。那么问题会在哪呢?
- 我那个固件库在经过测试之后是正常输入输出的,就直接去ST官网下就行,我这里指个官方固件路。进去点f4那个,就会进下图界面往下翻就选中v1.9直接下载就行。
https://www.st.com.cn/zh/embedded-software/stm32-standard-peripheral-libraries.html
2.函数介绍
- 接下来我就一个一个介绍我的库里面后面要用到的函数,如果急着看问题所在我也没办法,反正往后翻吧,但我还是建议你看完函数介绍再去直接看问题。
- int SD_test(void)
- 首先这个函数本身没啥改的,知道这个函数是用于初始化SDIO驱动以及验证SD卡是否正常读写的函数,status参数就是直接返回的初始化是否正常的参数。
- 而要正常读写还要uwSDCardOperation 参数正常,这个参数直接被固件全局声明了,想看可以找一下,同时还有SD_Detect用于硬件方面直接检验引脚电压,查看SD卡是否插入。
int SD_test(void)
{
NVIC_Configuration();
/*------------------------------ SD Init ---------------------------------- */
if((Status = SD_Init()) != SD_OK)
{
printf("%s","SDtestOK\r\n");
}
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;
}
}
}
return 0;
/* Infinite loop */
}
- 单块多块读写函数,这里函数有四个但都不用更改,对后面介绍并没有实质影响,主要是记得每个形参是干啥用,方便以后代码编写。但有一点要注意,因为官方库是直接给的DMA传输,故而一定要记得在多块读写函数后面加标志位等待函数!!!
- SD_Error SD_ReadBlock(uint8_t *readbuff, uint64_t ReadAddr, uint16_t BlockSize);
- SD_Error SD_ReadMultiBlocks(uint8_t *readbuff, uint64_t ReadAddr, uint16_t BlockSize, uint32_t NumberOfBlocks);
- SD_Error SD_WriteBlock(uint8_t *writebuff, uint64_t WriteAddr, uint16_t BlockSize);
- SD_Error SD_WriteMultiBlocks(uint8_t *writebuff, uint64_t WriteAddr, uint16_t BlockSize, uint32_t NumberOfBlocks);
- 其中readbuff,writebuff这些就是你读写缓存数组,这点要注意一下,你在main.c中声明这些缓冲区的时候,一个是栈内存大小是否足够,如果不够就会在进入这些函数的时候直接段错误跳Hardfalt,这时候就直接去startup_stm32f40_41xxx.s修改堆栈大小,主播的堆栈都调成0x00001000了,因为我们一般申请512字节就是0x00000200,故而反正用的zgt6它sram足够大,我直接搞大点了。
- 第二个就是这些内存本身没问题,但是很多人估计会在打印数据到电脑是空行,这里就是一个比较恶心的点,我后面用到了再说。
- 然后readaddr,writeaddr这些就是表示扇区地址(如果你不知道SD卡的扇区,簇这些知识直接搜),SD卡存储本体是一个个扇区,每个扇区512字节,所有SD卡都是如此,而扇区地址就是我们要写到的第几个扇区乘上扇区大小。
- BlockSize就是扇区大小,512字节这些都是diskio中应该宏定义好的,没定义直接写512
- NumberBlock就是要写多少个扇区,按照需求来。
提示:接下来是diskio.c中最主要的5个函数,这5个函数能否正常运行决定了你的FatFs能否正常运行
- DSTATUS disk_status (BYTE pdrv /* 物理编号 */)
- 这个函数用于存储设备类型的获取,如果你是直接从FatFs中直接移植的最新版R0.15,那么这里会有几种那个存储类型,MMC,SD,SPI-FLASH,TF,每个函数的初始化参数都被放在了状态机中,用于声明SD是否初始化,未初始化就返回对应参数。
- 其中pdrv后面都会有,这个就是为了明确你是什么设备,如果你是SD卡就宏定义SD卡参数为0,像我的ATA一样,TF就宏定义为对应的数字。
//宏定义
#define ATA 0 // SD卡
#define SPI_FLASH 1 // 预留外部SPI Flash使用
// SD卡块大小
#define SD_BLOCKSIZE 512
//存储设备状态获取
DSTATUS disk_status (
BYTE pdrv /* 物理编号 */
)
{
DSTATUS status = STA_NOINIT;
switch (pdrv) {
case ATA: /* SD CARD */
status &= ~STA_NOINIT;
break;
case SPI_FLASH: /* SPI Flash */
break;
default:
status = STA_NOINIT;
}
return status;
}
- DSTATUS disk_initialize (BYTE pdrv /* 物理编号 */)
- 这个函数就是对应存储类型的初始化函数,进入到对应状态后,执行对应的初始化参数。
DSTATUS disk_initialize (
BYTE pdrv /* 物理编号 */
)
{
DSTATUS status = STA_NOINIT;
switch (pdrv) {
case ATA: /* SD CARD */
if (SD_Init()==SD_OK) {
status &= ~STA_NOINIT;
} else {
status = STA_NOINIT;
}
break;
case SPI_FLASH: /* SPI Flash */
break;
default:
status = STA_NOINIT;
}
return status;
}
- DRESULT disk_read (
BYTE pdrv, /* 设备物理编号(0…) */
BYTE buff, / 数据缓存区 /
DWORD sector, / 扇区首地址 /
UINT count / 扇区个数(1…128) */
)
- 这个函数就是FatFs中用于读取SD卡的,本身的设计理念就是和官方驱动库中的读取函数相同,只不过为了适配多内存设备改为中间层和状态机形式,每个参数也和前面的SDIO读写参数差不多。
- 不过,这个函数里面也要改为状态机模式或者不改也行,反正我改了,在要写的扇区个数为1个时就直接使用SD_ReadBlock参数单独写一个扇区,若是多个扇区就使用多块读函数。
DRESULT disk_read (
BYTE pdrv, /* 设备物理编号(0..) */
BYTE *buff, /* 数据缓存区 */
DWORD sector, /* 扇区首地址 */
UINT count /* 扇区个数(1..128) */
)
{
DRESULT status = RES_PARERR;
SD_Error SD_state = SD_OK;
switch (pdrv) {
case ATA: /* SD CARD */
if(count==1){
SD_state=SD_ReadBlock(buff,sector *SD_BLOCKSIZE,SD_BLOCKSIZE);
}
else{
SD_state=SD_ReadMultiBlocks(buff,sector* SD_BLOCKSIZE,SD_BLOCKSIZE,count);
}
if (SD_state==SD_OK) {
/* Check if the Transfer is finished */
SD_state=SD_WaitReadOperation();
while (SD_GetStatus() != SD_TRANSFER_OK);
}
if (SD_state!=SD_OK)
status = RES_PARERR;
else
status = RES_OK;
break;
case SPI_FLASH:
break;
default:
status = RES_PARERR;
}
return status;
}
- DRESULT disk_write (
BYTE pdrv, /* 设备物理编号(0…) */
const BYTE buff, / 欲写入数据的缓存区 /
DWORD sector, / 扇区首地址 /
UINT count / 扇区个数(1…128) */
)
- 这个函数的构建思想和上面的读取相同,但是这里有一点要特别注意!!就是SD卡的多块写参数在写2块及其以上的扇区的时候要在你要写的块数基础上+1,否则就无法正常写入,这点原因未知。
DRESULT disk_write (
BYTE pdrv, /* 设备物理编号(0..) */
const BYTE *buff, /* 欲写入数据的缓存区 */
DWORD sector, /* 扇区首地址 */
UINT count /* 扇区个数(1..128) */
)
{
DRESULT status = RES_PARERR;
SD_Error SD_state = SD_OK;
if (!count) {
return RES_PARERR; /* Check parameter */
}
switch (pdrv) {
case ATA: /* SD CARD */
if(count==1){
SD_state=SD_WriteBlock((uint8_t *)buff,sector *SD_BLOCKSIZE,SD_BLOCKSIZE);
}
else{
SD_state=SD_WriteMultiBlocks((uint8_t *)buff,sector* SD_BLOCKSIZE,SD_BLOCKSIZE,count+1);
}
if (SD_state==SD_OK) {
/* Check if the Transfer is finished */
SD_state=SD_WaitReadOperation();
/* Wait until end of DMA transfer */
while (SD_GetStatus() != SD_TRANSFER_OK);
}
if (SD_state!=SD_OK)
status = RES_PARERR;
else
status = RES_OK;
break;
case SPI_FLASH:
break;
default:
status = RES_PARERR;
}
return status;
}
- DRESULT disk_ioctl (
BYTE pdrv, /* 物理编号 /
BYTE cmd, / 控制指令 */
void buff / 写入或者读取数据地址指针 */
)
-disk_ioctl 函数来检查和控制磁盘的状态。disk_ioctl 是一个用于执行磁盘 I/O 控制操作的函数,我们这里基本不用使用,和野火一样这里就直接放一点代码凑个数。
DRESULT disk_ioctl (
BYTE pdrv, /* 物理编号 */
BYTE cmd, /* 控制指令 */
void *buff /* 写入或者读取数据地址指针 */
)
{
DRESULT status = RES_PARERR;
switch (pdrv) {
case ATA: /* SD CARD */
switch (cmd) {
// Get R/W sector size (WORD)
case GET_SECTOR_SIZE :
*(WORD * )buff = SD_BLOCKSIZE;
break;
// Get erase block size in unit of sector (DWORD)
case GET_BLOCK_SIZE :
*(DWORD * )buff = SDCardInfo.CardBlockSize;
break;
case GET_SECTOR_COUNT:
*(DWORD*)buff = SDCardInfo.CardCapacity/SDCardInfo.CardBlockSize;
break;
case CTRL_SYNC :
break;
}
status = RES_OK;
break;
case SPI_FLASH:
break;
default:
status = RES_PARERR;
}
return status;
}
3.SDIO驱动测试
- 我写这一栏主要就是让你自己去检测SDIO底层驱动,因为只有底层驱动没验证正确读写就会导致开头那几个问题。
- 基本上测试驱动就先从SD_WriteBlock开始,用writeblock函数写一段字符串进去,再用readblock函数读出来字符串,并使用串口打印到屏幕上面。
- 这里有一点要特别注意!!!就是估计是keil对堆栈空间优化的问题,比如说,初始化如下两个字符串:
BYTE WriteBuffer[] = /* 写缓冲区*/
"fdfdfdasdfghhjg111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 \
111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 \
111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 \
111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 \
1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111zzz";
BYTE write[] = /* 写缓冲区*/
"dddsdsdjfpojtio";
- 如果你在调试的过程中,能成功完成writeblock和readblock函数,但是却在读write数组而非writebuff数组时在屏幕上打印出来的是一行空格,这个时候你再去观察你的读缓存数组,我敢打赌是这个缓冲数组的第一位被置为" \0 "了,这点去keil的memory窗口中直接查看读缓冲数组的地址,比如我的0x2000023A,也能看到,第一位被设成0x00了。
- 并且我尝试了一下,这一点跟前面数组长度有关,一般怎么解决呢?我就是将writebuff缩短了一点,然后就能正常读写write数组了,有时候就是writebuff出问题,就把writebuff前面那个先定义的数据缩短一点就行,理论上不应该出现这种问题,不可能是栈内存爆了,我设置了将近4KB栈内存。反正也不清楚具体问题,但是这么写肯定没问题,要是出了类似的问题照我这样改就行。
FATFS fs; /* FatFs文件系统对象 */
FIL fnew; /* 文件对象 */
FRESULT res_sd; /* 文件操作结果 */
UINT fnum; /* 文件成功读写数量 */
BYTE ReadBuffer[1024]= {0}; /* 读缓冲区 */
BYTE WriteBuffer[] = /* 写缓冲区*/
"fdfdfdasdfghhjg111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 \
111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 \
111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 \
111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 \
1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111zzz";
BYTE write[] = /* 写缓冲区*/
"dddsdsdjfpojtio";
extern SD_CardInfo SDCardInfo;
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("\r\nSD卡信息\r\n");
printf("Card ManufacturerID:%d\r\n",SDCardInfo.SD_cid.ManufacturerID); //制造商ID
printf("Card RCA:%d\r\n",SDCardInfo.RCA); //卡相对地址
printf("Card Capacity:%d MB\r\n",(u32)(SDCardInfo.CardCapacity>>20)); //显示容量
printf("Card BlockSize:%d\r\n\r\n",SDCardInfo.CardBlockSize); //显示块大小
}
int main(void)
{
uart_init(115200);
printf("1");
delay_init(84);
SD_test();
show_sdcard_info();
printf("\r\n****** 这是一个SD卡 文件系统实验 ******\r\n");
while(1){
SD_WriteBlock(write,0,512);
SD_ReadBlock(ReadBuff,0,512);
printf("%s",ReadBuff);
}
}
4.中间层测试
- 因为底层驱动我们已经测试过了,既然没有问题的话,就要使用diskio.c文件中的函数来进行测试了,只要这个测试通过,基本上文件系统就大功告成了。
- 这个测试也没啥好说的,只要你按我那个diskio.c的框架来设计,基本上就不会出问题。
- 唯一要注意的就是我一开始说的多块写函数,这个函数要在count的基础上+1,否则后面用正常的多块读参数就无法正常读取。
int main(void)
{
uart_init(115200);
printf("1");
delay_init(84);
SD_test();
show_sdcard_info();
printf("\r\n****** 这是一个SD卡 文件系统实验 ******\r\n");
while(1){
DRESULT SD_state;
SD_state=disk_write(0,WriteBuffer,0,3);
printf("%d",SD_state);
SD_state=disk_read(0,ReadBuffer,0,2);
printf("%d",SD_state);
printf("%s",ReadBuffer);
delay_ms(3000);
}
}
5.文件系统测试
而到这里的话,如果上面都测试没问题,就直接上野火的测试代码,基本上就能直接用了。肯定能读取到文件,返回FR_OK。
#include "stm32f4xx.h"
#include "usart.h"
#include "delay.h"
#include "SD_test.h"
#include "ff.h"
#include "diskio.h"
FATFS fs; /* FatFs文件系统对象 */
FIL fnew; /* 文件对象 */
FRESULT res_sd; /* 文件操作结果 */
UINT fnum; /* 文件成功读写数量 */
BYTE ReadBuffer[1024]= {0}; /* 读缓冲区 */
BYTE WriteBuffer[] = /* 写缓冲区*/
"fdfdfdasdfghhjg111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 \
111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 \
111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 \
111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111 \
1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111zzz";
BYTE write[] = /* 写缓冲区*/
"dddsdsdjfpojtio";
extern SD_CardInfo SDCardInfo;
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("\r\nSD卡信息\r\n");
printf("Card ManufacturerID:%d\r\n",SDCardInfo.SD_cid.ManufacturerID); //制造商ID
printf("Card RCA:%d\r\n",SDCardInfo.RCA); //卡相对地址
printf("Card Capacity:%d MB\r\n",(u32)(SDCardInfo.CardCapacity>>20)); //显示容量
printf("Card BlockSize:%d\r\n\r\n",SDCardInfo.CardBlockSize); //显示块大小
}
int main(void)
{
uart_init(115200);
printf("1");
delay_init(84);
SD_test();
show_sdcard_info();
printf("\r\n****** 这是一个SD卡 文件系统实验 ******\r\n");
//在外部SPI Flash挂载文件系统,文件系统挂载时会对SPI设备初始化
res_sd = f_mount(&fs,"0:",1);
/*----------------------- 格式化测试 ---------------------------*/
/* 如果没有文件系统就格式化创建创建文件系统 */
if (res_sd == FR_NO_FILESYSTEM) {
printf("》SD卡还没有文件系统,即将进行格式化...\r\n");
/* 格式化 */
res_sd=f_mkfs("0:",0,0);
BYTE *i=0;
disk_read(0,i,0,1);
printf("%s",i);
if (res_sd == FR_OK) {
printf("》SD卡已成功格式化文件系统。\r\n");
/* 格式化后,先取消挂载 */
res_sd = f_mount(NULL,"0:",1);
/* 重新挂载 */
res_sd = f_mount(&fs,"0:",1);
} else {
printf("《《格式化失败。》》\r\n");
}
} else if (res_sd!=FR_OK) {
printf("!!SD卡挂载文件系统失败。(%d)\r\n",res_sd);
printf("!!可能原因:SD卡初始化不成功。\r\n");
} else {
printf("》文件系统挂载成功,可以进行读写测试\r\n");
}
printf("\r\n****** 即将进行文件写入测试... ******\r\n");
res_sd=f_open(&fnew,"0:abc.txt",FA_CREATE_ALWAYS|FA_WRITE);
if ( res_sd == FR_OK ) {
printf("》打开/abc.txt文件成功,向文件写入数据。\r\n");
/* 将指定存储区内容写入到文件内 */
res_sd=f_write(&fnew,WriteBuffer,sizeof(WriteBuffer),&fnum);
if (res_sd==FR_OK) {
printf("》文件写入成功,写入字节数据:%d\n",fnum);
printf("》向文件写入的数据为:\r\n%s\r\n",WriteBuffer);
} else {
printf("!!文件写入失败:(%d)\n",res_sd);
}
/* 不再读写,关闭文件 */
f_close(&fnew);
} else {
printf("!!打开/创建文件失败。\r\n");
}
// /*------------------ 文件系统测试:读测试 --------------------------*/
// printf("****** 即将进行文件读取测试... ******\r\n");
// res_sd=f_open(&fnew,"0:abc.txt",FA_OPEN_EXISTING|FA_READ);
// if (res_sd == FR_OK) {
// printf("》打开文件成功。\r\n");
// res_sd = f_read(&fnew, ReadBuffer, sizeof(ReadBuffer), &fnum);
// if (res_sd==FR_OK) {
// printf("》文件读取成功,读到字节数据:%d\r\n",fnum);
// printf("》读取得的文件数据为:\r\n%s \r\n", ReadBuffer);
// } else {
// printf("!!文件读取失败:(%d)\n",res_sd);
// }
// } else {
// printf("!!打开文件失败。\r\n");
// }
// /* 不再读写,关闭文件 */
// f_close(&fnew);
// /* 不再使用文件系统,取消挂载文件系统 */
// f_mount(NULL,"0:",1);
while(1){
DRESULT SD_state;
SD_state=disk_write(0,WriteBuffer,0,3);
printf("%d",SD_state);
SD_state=disk_read(0,ReadBuffer,0,2);
printf("%d",SD_state);
printf("%s",ReadBuffer);
delay_ms(3000);
}
}
总结
提示:这里对文章进行总结:
说实话,这玩意能卡我四天我是真想不明白,这东西的移植关键就在底层驱动和中间层diskio.c那几个函数是否能正常使用,也是太急躁了,只想快点移植完,开始下一个项目,哎无语了。所以干这行还得是心平气和。