https://blog.csdn.net/zyxhangiian123456789/article/details/79098483
平台: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;
-
}
感谢大家的耐心阅读,如果有问题,欢迎一起讨论。