平台:STM32ZET6(核心板)+ST-LINK/V2+SD卡+USB串口线
工程介绍:主要文件在USER组中,bsp_sdio_sdcard.c,bsp_sdio_sdcard.h和main.c,另外FatFs是用来后面移植文件系统使用的,对于本节内容暂时不需要。bsp_sdio_sdcard.c和bsp_sdio_sdcard.h文件主要参考教材《STM32库开发实战指南——基于STM32F03》。另外就是本教材用到的FatFs系统源代码,这里温馨提示一下,关于FatFs目前网上笔者找到的最新版的参考资料不是太多,所以笔者在用最新版做的时候,一些函数原型发生了变化,虽然变化不大,也给我造成了一定的阻碍,所以建议大家下载稍微老一点的版本,这样资料较多,出问题好解决,我最终用的版本是R0.09,因为在使用函数f_mkfs()的过程中遇到问题,无法格式化SD卡,最终选择较老的版本。本文有些内容来自于其他网友总结,在此表示感谢。
整体的项目和上一节中的SDIO读写SD卡类似,细节上在添加了FatFs组,专门存放FatFs移植的内容。对于移植过程,就是对diskio.c文件的修改的过程。这个文件完成了与底层有多的操作,我们只需要实现一下函数即可,不同的平台函数实现略有不同,但是提供给用户最终的接口是相同的,也正是因为这一点,使得FatFs文件系统有了很好的可移植性。
再次申明一下,FatFs文件系统与存储设备的接口函数再diskio.c文件中,我们只需要完善该文件即可。这也就是所谓的软件移植裁剪过程吧。我们需要完善五个函数disk_status,disk_initialize,disk_read,disk_write,disk_ioctl等,还有一个获取时间戳的函数,可以直接return 0;
1.宏定义
#define SD_CARD 0 //SD卡,卷标为0 #define EX_FLASH 1 //外部flash,卷标为1,为以后外部Flash扩展预留的 #define SD_BLOCKSIZE 512 //声明外部变量 extern SD_CardInfo SDCardInfo;2.dis_status 获取磁盘的状态
//获得磁盘状态 DSTATUS disk_status ( BYTE pdrv /* Physical drive nmuber to identify the drive */ ) { DSTATUS status = STA_NOINIT; switch(pdrv) { case SD_CARD://SD卡 status &= ~STA_NOINIT; break; case EX_FLASH://外部flash break; default: status = STA_NOINIT; break; } return status; }3. disk_initialize 初始化磁盘,当遇到无法挂载SD卡,或者其他问题时,优先检查该函数,该函数会调用SD_Init函数,需要检查
SD_Init函数的返回结果是否为SD_OK。
//初始化磁盘 DSTATUS disk_initialize ( BYTE pdrv /* Physical drive nmuber to identify the drive */ ) { DSTATUS status = STA_NOINIT; switch(pdrv) { case SD_CARD://SD卡 if(SD_OK == SD_Init())//SD卡初始化 { status &= ~STA_NOINIT; } else { status = STA_NOINIT; } break; case EX_FLASH://外部flash break; default: status = STA_NOINIT; break; } return status; }4. disk_read 读取SD卡
//读扇区 //pdrv:磁盘编号0~9 //*buff:数据接收缓冲首地址 //sector:扇区地址 //count:需要读取的扇区数 DRESULT disk_read ( BYTE pdrv, /* Physical drive nmuber to identify the drive */ BYTE *buff, /* Data buffer to store read data */ DWORD sector, /* Sector address in LBA */ UINT count /* Number of sectors to read */ ) { DRESULT status = RES_PARERR; SD_Error SD_Status = SD_OK; switch(pdrv) { case SD_CARD://SD卡 if((DWORD)buff & 3) { DRESULT res = RES_OK; DWORD scratch[SD_BLOCKSIZE / 4]; while(count--) { res = disk_read(SD_CARD, (void*)scratch, sector++, 1); if(res != RES_OK) { break; } memcpy(buff, scratch, SD_BLOCKSIZE); buff += SD_BLOCKSIZE; } return res; } SD_Status = SD_ReadMultiBlocks(buff, sector*SD_BLOCKSIZE,SD_BLOCKSIZE,count); if(SD_Status == SD_OK) { //检查传输是否完成 SD_Status = SD_WaitReadOperation(); while(SD_GetStatus() != SD_TRANSFER_OK); } if(SD_Status != SD_OK) { status = RES_PARERR; } else { status = RES_OK; } break; case EX_FLASH://外部flash break; default: status = RES_PARERR; break; } return status; }5.disk_write 写数据到SD卡
//写扇区 //pdrv:磁盘编号0~9 //*buff:发送数据首地址 //sector:扇区地址 //count:需要写入的扇区数 #if _USE_WRITE DRESULT disk_write ( BYTE pdrv, /* Physical drive nmuber to identify the drive */ const BYTE *buff, /* Data to be written */ DWORD sector, /* Sector address in LBA */ UINT count /* Number of sectors to write */ ) { DRESULT status = RES_PARERR; SD_Error SD_Status = SD_OK; //检查参数 if(!count) { return RES_PARERR; } switch(pdrv) { case SD_CARD://SD卡 if((DWORD)buff & 3) { DRESULT res = RES_OK; DWORD scratch[SD_BLOCKSIZE / 4]; while(count--) { memcpy(scratch, buff, SD_BLOCKSIZE); res = disk_write(SD_CARD, (void*)scratch, sector++, 1); if(res != RES_OK) { break; } buff += SD_BLOCKSIZE; } return res; } SD_Status = SD_WriteMultiBlocks((uint8_t *)buff, sector*SD_BLOCKSIZE,SD_BLOCKSIZE,count); if(SD_Status == SD_OK) { //检查传输是否完成 SD_Status = SD_WaitReadOperation(); while(SD_GetStatus() != SD_TRANSFER_OK); } if(SD_Status != SD_OK) { status = RES_PARERR; } else { status = RES_OK; } break; case EX_FLASH://外部flash break; default: status = RES_PARERR; break; } return status; }6.disk_ioctl 函数,获取SD卡的某些参数,如块大小等。
//其他表参数的获得 //pdrv:磁盘编号0~9 //ctrl:控制代码 //*buff:发送/接收缓冲区指针 #if _USE_IOCTL DRESULT disk_ioctl ( BYTE pdrv, /* Physical drive nmuber (0..) */ BYTE cmd, /* Control code */ void *buff /* Buffer to send/receive control data */ ) { DRESULT res = RES_ERROR; switch(pdrv)//SD卡 { case SD_CARD: switch(cmd) { case GET_SECTOR_SIZE: *(WORD*)buff = SD_BLOCKSIZE; break; case GET_BLOCK_SIZE: *(DWORD*)buff = SDCardInfo.CardBlockSize; break; case GET_SECTOR_COUNT: *(DWORD*)buff = SDCardInfo.CardCapacity / SD_BLOCKSIZE; break; case CTRL_SYNC: break; } res = RES_OK; break; case EX_FLASH://外部flash break; default: res = RES_PARERR; break; } return res; }7.get_fattime函数,获取时间戳。没有实现。
//获得时间 DWORD get_fattime (void) { //返回当前时间戳 return 0; }8.另外需要修改ffconf.h文件,另外注意,新版本的该文件都会在宏的前面加上FF前缀,例如对于_USE_LEN,新版本的可能是
FF_USE_LLEN,本节的内容都是按照旧版本来说的,因此宏没有以FF为前缀。
#define _USE_LEN 2 //长文件名支持,默认不支持长文件名 #define _USE_MKFS 1 //格式化功能选择,使能FatFs的格式化功能 #define _CODE_PAGE 936 //语言功能选择,需要同时把语言文件添加到工程中,为支持简体中文,我们需要添加cc963.c文件 #define _VOLUMES 2 //物理设备数量,这里设置为2,包括SD卡和预留的外部Flash芯片 #define _MIN_SS 512 //指定扇区大小的最小值 #define _MAX_SS 4096 //指定扇区大小的最大值以上 就完成了FatFs的移植过程,接下来是对功能的测试,直接上代码了。
测试:
//FAT功能测试:格式化测试,文件写入测试,文件读取测试(基本功能) FATFS fs; //FatFs文件系统对象 FIL fnew; //文件对象 FRESULT res_sd;//文件操作结果 UINT fnum; //文件成功读写数量 BYTE ReadBuffer[1024] = {0}; BYTE WriteBuffer[] = "成功移植了FatFs文件系统!\r\n"; //写缓存区main 主函数,包含格式化测试和文件读写测试。
int main() { //串口配置 USART_Config(); //初始化LED LED_GPIO_Config(); //初始化SD卡 if(SD_Init() == SD_OK) { printf("SD卡初始化成功,即将挂载SD卡。\r\n"); } //挂载SD卡 res_sd = f_mount(&fs, "0:", 1); //***********************格式化测试**************************** if(res_sd == FR_NO_FILESYSTEM) { printf("SD卡没有文件系统,即将进行格式化...\r\n"); //格式化 res_sd = f_mkfs("0:", 0, 0); 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("文件格式化失败!错误代码:%d\r\n",res_sd); while(1); } } else if(res_sd != FR_OK) { printf("挂载文件系统失败!可能是因为文件初始化失败!错误代码:%d\r\n", res_sd); } else { printf("文件系统挂载成功, 可进行读写测试!\r\n"); } //***********************写测试**************************** //打开文件,如果文件不存在则创建它 printf("即将进行文件写入测试....\r\n"); //打开文件,若不存在就创建 res_sd = f_open(&fnew, "0:FatFs读写测试文件.txt", FA_CREATE_ALWAYS | FA_WRITE); //文件打开成功 if(res_sd == FR_OK) { printf("打开文件成功!开始写入数据!\r\n"); res_sd= f_write(&fnew, WriteBuffer, sizeof(WriteBuffer), &fnum); if(res_sd == FR_OK) { printf("数据写入成功!\r\n"); printf("数据:%s。共写入%d个字符\r\n", WriteBuffer, fnum); } else { printf("数据写入失败!\r\n"); } //关闭文件 f_close(&fnew); } //***********************读测试**************************** //打开文件,如果文件不存在则创建它 printf("即将进行文件读取测试....\r\n"); //打开文件,若不存在就创建 res_sd = f_open(&fnew, "0:FatFs读写测试文件.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("数据读取成功!\r\n"); printf("数据:%s\r\n", ReadBuffer); } else { printf("数据读取失败!\r\n"); } //关闭文件 f_close(&fnew); } scan_files("FatFs读写测试文件.txt"); //其他功能测试 file_check(); //多项功能测试 miscellaneous(); //取消挂载文件系统 f_mount(NULL, "0:", 1); while(1); }补充,如有需要可以借鉴,其他功能测试。
//多项功能测试 static FRESULT miscellaneous() { DIR dir; FATFS *pfs; DWORD fre_clust, fre_sect, tot_sect; printf("\r\n*************************设备信息获取***************************\r\n"); //获取设备信息和空簇大小 res_sd = f_getfree("0:", &fre_clust, &pfs); //计算得到总的扇区个数和空扇区个数 tot_sect = (pfs->n_fatent - 2) * pfs->csize; fre_sect = fre_clust * pfs->csize; printf("设备总空间:%10lu KB\r\n可用空间:%10lu KB。\r\n", tot_sect*4, fre_sect*4); printf("\r\n*************************文件定位和格式化写入功能测试***************************\r\n"); //打开文件,若不存在就创建 res_sd = f_open(&fnew, "0:FatFs多项功能测试文件.txt", FA_CREATE_ALWAYS | FA_WRITE | FA_READ); //文件打开成功 if(res_sd == FR_OK) { printf("打开文件成功!开始读取数据!\r\n"); res_sd= f_write(&fnew, WriteBuffer, sizeof(WriteBuffer), &fnum); if(res_sd == FR_OK) { printf("数据写入成功!\r\n"); printf("数据:%s\r\n", WriteBuffer); //文件定位,定位到文件末尾,f_size获取文件大小 res_sd = f_lseek(&fnew, f_size(&fnew) - 1); if(res_sd == FR_OK) { //在原文件中追加一行内容 f_printf(&fnew, "在原文件中追加一行内容。\n"); f_printf(&fnew, "设备总空间:%10lu KB\r\n可用空间:%10lu KB。\r\n", tot_sect*4, fre_sect*4); //文件定位到起始位置 res_sd = f_lseek(&fnew, 0); if(res_sd == FR_OK) { //打开文件,若不存在就创建 res_sd = f_open(&fnew, "0:FatFs多项功能测试文件.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("数据读取成功!\r\n"); printf("数据:%s\r\n", ReadBuffer); } else { printf("数据读取失败!\r\n"); } //关闭文件 f_close(&fnew); } } } } else { printf("数据读取失败!\r\n"); } //关闭文件 f_close(&fnew); } printf("\r\n*************************目录创建和重命名功能测试***************************\r\n"); //尝试打开目录 res_sd = f_opendir(&dir, "0:TestDir"); if(res_sd != FR_OK) { //打开目录失败,开始创建目录 res_sd = f_mkdir("0:TestDir"); } else { //如果目录已经存在,关闭它 res_sd = f_closedir(&dir); //删除文件 f_unlink("0:FatFs读写测试文件.txt"); } if(res_sd == FR_OK) { //重命名并移动文件 res_sd = f_rename("0:FatFs多项功能测试文件.txt", "0:/TestDir/FatFs多项功能测试文件.txt"); if(res_sd == FR_OK) { printf("重命名并移动文件成功!\r\n"); } else { printf("重命名并移动文件失败!\r\n"); } } } //文件信息获取 static FRESULT file_check() { //文件信息 static FILINFO fno; //获取文件信息,必须确保文件存在 res_sd = f_stat("0:FatFs读写测试文件.txt", &fno); if(res_sd == FR_OK) { printf("0:FatFs读写测试文件.txt的信息如下:\r\n"); printf("文件大小:%ld\r\n", fno.fsize); printf("时间戳:%u/%02u/%02u, %02u:%02u\r\n", (fno.fdate >> 9) + 1980, fno.fdate >> 5 & 15, fno.fdate & 31, fno.ftime >> 11, fno.ftime >> 5 &63); printf("属性:%c%c%c%c%c\r\n", (fno.fattrib & AM_DIR) ? 'D' : '-', //目录 (fno.fattrib & AM_RDO) ? 'R' : '-', //只读 (fno.fattrib & AM_HID) ? 'H' : '-', //隐藏 (fno.fattrib & AM_SYS) ? 'S' : '-', //系统文件 (fno.fattrib & AM_ARC) ? 'A' : '-');//档案文件 } } //路径扫描 static FRESULT scan_files(char* path) { FRESULT res;//在递归过程中被修改,不用全局变量 FILINFO fno; DIR dir; int i; char* fn; #if _USE_LEN//长文件名支持 //简体中文需要两个字节保存一个字 static char lfn[_MAX_LFN*2+1]; fno.lfname = lfn; fno.lfsize = sizeof(lfn); #endif //打开目录 res = f_opendir(&dir, path); if(res == FR_OK) { i = strlen(path); while(1) { //读取目录下的内容 res = f_readdir(&dir, &fno); if(res != FR_OK || fno.fname[0] == 0) { break; } #if _USE_LEN fn = *fno.lfname ? fno.lfname : fno.fname; #else fn = fno.fname; #endif //点表示当前目录,跳过 if(*fn == '.') continue; //目录,递归读取 if(fno.fattrib & AM_DIR) { //合成完整目录名 sprintf(&path[i], "/%s", fn); //递归遍历 res = scan_files(path); path[i] = 0; if(res != FR_OK) { break; } else { printf("%s/%s\r\n", path, fn);//输出文件名,可以在这里提取特定格式的文件路径 } } } } return res; }感谢大家的耐心阅读,如果有问题,欢迎一起讨论。