平台: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;
}
https://blog.csdn.net/zyxhangiian123456789/article/details/79098483