基于FATFS的SD卡存储
目录
- FATFS介绍
- 配置流程
- SDIO_Init()
- diskio.c的配置
- malloc()与free()
- 文件操作函数
- Simple Demo
- 注意事项
- 缓冲队列
FATFS介绍
FAT File System是一个通用的文件系统(FAT/exFAT)模块,用于在小型嵌入式系统中实现FAT文件系统。 FatFs 组件的编写遵循ANSI C(C89),完全分离于磁盘 I/O 层,因此不依赖于硬件平台。它可以嵌入到资源有限的微控制器中,如 8051, PIC, AVR, ARM, Z80, RX等等,不需要做任何修改。
配置流程
SDIO_Init()
SDIO是底层驱动,要想使用FATFS需要配置底层驱动,可以是SD卡,也可以是外部SRAM,当然SD卡也可以试用SPI通讯。根据硬件原理图配置接口以及中断优先级
SD_Error SD_Init(void)
{NVIC_InitTypeDef NVIC_InitStructure;GPIO_InitTypeDef GPIO_InitStructure;u8 clkdiv=0;SD_Error errorstatus=SD_OK; //SDIO IO口初始化RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC|RCC_APB2Periph_GPIOD,ENABLE);//使能PORTC,PORTD时钟RCC_AHBPeriphClockCmd(RCC_AHBPeriph_SDIO|RCC_AHBPeriph_DMA2,ENABLE);//使能SDIO,DMA2时钟 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_9|GPIO_Pin_10|GPIO_Pin_11|GPIO_Pin_12; //PC.8~12 复用输出GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHzGPIO_Init(GPIOC, &GPIO_InitStructure); //根据设定参数初始化PC.8~12 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; //PD2 复用输出GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHzGPIO_Init(GPIOD, &GPIO_InitStructure); //根据设定参数初始化PD2GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7; //PD7 上拉输入GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //复用推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //IO口速度为50MHzGPIO_Init(GPIOD, &GPIO_InitStructure); //根据设定参数初始化PD7//SDIO外设寄存器设置为默认值 SDIO_DeInit();NVIC_InitStructure.NVIC_IRQChannel = SDIO_IRQn; //SDIO中断配置NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //抢占优先级0 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //子优先级0 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //使能外部中断通道NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器errorstatus=SD_PowerON(); //SD卡上电if(errorstatus==SD_OK)errorstatus=SD_InitializeCards(); //初始化SD卡 if(errorstatus==SD_OK)errorstatus=SD_GetCardInfo(&SDCardInfo); //获取卡信息if(errorstatus==SD_OK)errorstatus=SD_SelectDeselect((u32)(SDCardInfo.RCA<<16));//选中SD卡if(errorstatus==SD_OK)errorstatus=SD_EnableWideBusOperation(1); //4位宽度,如果是MMC卡,则不能用4位模式 if((errorstatus==SD_OK)||(SDIO_MULTIMEDIA_CARD==CardType)){ if(SDCardInfo.CardType==SDIO_STD_CAPACITY_SD_CARD_V1_1||SDCardInfo.CardType==SDIO_STD_CAPACITY_SD_CARD_V2_0){clkdiv=SDIO_TRANSFER_CLK_DIV+6; //V1.1/V2.0卡,设置最高72/12=6Mhz}else clkdiv=SDIO_TRANSFER_CLK_DIV; //SDHC等其他卡,设置最高72/6=12MhzSDIO_Clock_Set(clkdiv); //设置时钟频率,SDIO时钟计算公式:SDIO_CK时钟=SDIOCLK/[clkdiv+2];其中,SDIOCLK固定为48Mhz //errorstatus=SD_SetDeviceMode(SD_DMA_MODE); //设置为DMA模式errorstatus=SD_SetDeviceMode(SD_POLLING_MODE); //设置为查询模式}return errorstatus;
}
diskio.c的配置
这个.c文件里主要是一些API函数,需要我们自己把接口补充上,共有以下五个函数:
DSTATUS disk_initialize (BYTE pdrv);
DSTATUS disk_status (BYTE pdrv);
DRESULT disk_read (BYTE pdrv, BYTE* buff, DWORD sector, UINT count);
DRESULT disk_write (BYTE pdrv, const BYTE* buff, DWORD sector, UINT count);
DRESULT disk_ioctl (BYTE pdrv, BYTE cmd, void* buff);
下面是具体实现代码
#define SD_CARD 0 //SD卡,卷标为0//获得磁盘状态
DSTATUS disk_status (BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{ return RES_OK;
}
//初始化磁盘
DSTATUS disk_initialize (BYTE pdrv /* Physical drive nmuber to identify the drive */
)
{u8 res=0; switch(pdrv){case SD_CARD://SD卡res=SD_Init();//SD卡初始化 break;default:res=1; } if(res)return STA_NOINIT;else return 0; //初始化成功
}
//读扇区
//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 */
)
{u8 res=0; if (!count)return RES_PARERR;//count不能等于0,否则返回参数错误 switch(pdrv){case SD_CARD://SD卡res=SD_ReadDisk(buff,sector,count); while(res)//读出错{SD_Init(); //重新初始化SD卡res=SD_ReadDisk(buff,sector,count); //printf("sd rd error:%d\r\n",res);}break;default:res=1; }//处理返回值,将SPI_SD_driver.c的返回值转成ff.c的返回值if(res==0x00)return RES_OK; else return RES_ERROR;
}
//写扇区
//pdrv:磁盘编号0~9
//*buff:发送数据首地址
//sector:扇区地址
//count:需要写入的扇区数
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 */
)
{u8 res=0; if (!count)return RES_PARERR;//count不能等于0,否则返回参数错误 switch(pdrv){case SD_CARD://SD卡res=SD_WriteDisk((u8*)buff,sector,count);while(res)//写出错{SD_Init(); //重新初始化SD卡res=SD_WriteDisk((u8*)buff,sector,count); //printf("sd wr error:%d\r\n",res);}break;default:res=1; }//处理返回值,将SPI_SD_driver.c的返回值转成ff.c的返回值if(res == 0x00)return RES_OK; else return RES_ERROR;
}
//其他表参数的获得
//pdrv:磁盘编号0~9
//ctrl:控制代码
//*buff:发送/接收缓冲区指针
DRESULT disk_ioctl (BYTE pdrv, /* Physical drive nmuber (0..) */BYTE cmd, /* Control code */void *buff /* Buffer to send/receive control data */
)
{DRESULT res; if(pdrv==SD_CARD)//SD卡{switch(cmd){case CTRL_SYNC:res = RES_OK; break; case GET_SECTOR_SIZE:*(DWORD*)buff = 512; res = RES_OK;break; case GET_BLOCK_SIZE:*(WORD*)buff = SDCardInfo.CardBlockSize;res = RES_OK;break; case GET_SECTOR_COUNT:*(DWORD*)buff = SDCardInfo.CardCapacity/512;res = RES_OK;break;default:res = RES_PARERR;break;}}return res;
}
//获得时间
//User defined function to give a current time to fatfs module */
//31-25: Year(0-127 org.1980), 24-21: Month(1-12), 20-16: Day(1-31) */
//15-11: Hour(0-23), 10-5: Minute(0-59), 4-0: Second(0-29 *2) */
DWORD get_fattime (void)
{ return 0;
}
//动态分配内存
void *ff_memalloc (UINT size)
{return (void*)mymalloc(SRAMIN,size);
}
//释放内存
void ff_memfree (void* mf)
{myfree(SRAMIN,mf);
}
malloc()与free()
这里有个问题是malloc()与free()不能使用的问题,也有可能是可以使用,但是不能申请大内存,或是其他 BUG,具体原因未知。按照网络上的步骤,添加了<stdlib.h>与<rt_heap.h>,修改.s文件中heap的大小,去除option中的USE MicroLIB之后,理论上是可以使用malloc()与free()的,但是在使用f_open函数后会提示内存不足,具体原因尚未考究,使用别人的mymalloc()与myfree()。
具体实现参照malloc.c
文件操作函数
FRESULT f_open (FIL* fp, const TCHAR* path, BYTE mode); /* Open or create a file */
FRESULT f_close (FIL* fp); /* Close an open file object */
FRESULT f_read (FIL* fp, void* buff, UINT btr, UINT* br); /* Read data from a file */
FRESULT f_write (FIL* fp, const void* buff, UINT btw, UINT* bw); /* Write data to a file */
FRESULT f_lseek (FIL* fp, FSIZE_t ofs); /* Move file pointer of a file object */
FRESULT f_truncate (FIL* fp); /* Truncate file */
FRESULT f_sync (FIL* fp); /* Flush cached data of a writing file */
FRESULT f_opendir (DIR* dp, const TCHAR* path); /* Open a directory */
FRESULT f_closedir (DIR* dp); /* Close an open directory */
FRESULT f_readdir (DIR* dp, FILINFO* fno); /* Read a directory item */
FRESULT f_findfirst (DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern); /* Find first file */
FRESULT f_findnext (DIR* dp, FILINFO* fno); /* Find next file */
FRESULT f_mkdir (const TCHAR* path); /* Create a sub directory */
FRESULT f_unlink (const TCHAR* path); /* Delete an existing file or directory */
FRESULT f_rename (const TCHAR* path_old, const TCHAR* path_new); /* Rename/Move a file or directory */
FRESULT f_stat (const TCHAR* path, FILINFO* fno); /* Get file status */
FRESULT f_chmod (const TCHAR* path, BYTE attr, BYTE mask); /* Change attribute of the file/dir */
FRESULT f_utime (const TCHAR* path, const FILINFO* fno); /* Change timestamp of the file/dir */
FRESULT f_chdir (const TCHAR* path); /* Change current directory */
FRESULT f_chdrive (const TCHAR* path); /* Change current drive */
FRESULT f_getcwd (TCHAR* buff, UINT len); /* Get current directory */
FRESULT f_getfree (const TCHAR* path, DWORD* nclst, FATFS** fatfs); /* Get number of free clusters on the drive */
FRESULT f_getlabel (const TCHAR* path, TCHAR* label, DWORD* vsn); /* Get volume label */
FRESULT f_setlabel (const TCHAR* label); /* Set volume label */
FRESULT f_forward (FIL* fp, UINT(*func)(const BYTE*,UINT), UINT btf, UINT* bf); /* Forward data to the stream */
FRESULT f_expand (FIL* fp, FSIZE_t szf, BYTE opt); /* Allocate a contiguous block to the file */
FRESULT f_mount (FATFS* fs, const TCHAR* path, BYTE opt); /* Mount/Unmount a logical drive */
FRESULT f_mkfs (const TCHAR* path, BYTE sfd, UINT au); /* Create a file system on the volume */
FRESULT f_fdisk (BYTE pdrv, const DWORD szt[], void* work); /* Divide a physical drive into some partitions */
int f_putc (TCHAR c, FIL* fp); /* Put a character to the file */
int f_puts (const TCHAR* str, FIL* cp); /* Put a string to the file */
int f_printf (FIL* fp, const TCHAR* str, ...); /* Put a formatted string to the file */
TCHAR* f_gets (TCHAR* buff, int len, FIL* fp); /* Get a string from the file */
主要讲一下几个常用的函数:
1、FRESULT f_mount (FATFS* fs, const TCHAR* path, BYTE opt);
主要用于挂载硬盘,初始化时调用即可。
2、FRESULT f_mkdir (const TCHAR* path);
主要用于创建文件夹,但是要一级一级创建,不能创建两级文件夹会报错。
3、FRESULT f_open (FIL* fp, const TCHAR* path, BYTE mode);
主要用于打开文件,fp是文件指针,path是路径,个人认为绝对路径比较好用,虽然占字节较多,但是可以省去操作文件夹的过程,mode为模式,读、写此类。
4、FRESULT f_lseek (FIL* fp, FSIZE_t ofs);
这个函数是移动指针函数,在UI界面我们可以通过操作鼠标移动输入位置,而诸如此类cmd命令没有光标,是通过变量实现的。光标在文件打开时指向最顶端,可以通过f_size()函数获取函数尾部光标,也可以直接给ofs赋值操作光标。
5、FRESULT f_read (FIL* fp, void* buff, UINT btr, UINT* br);
读文件函数,buff为读取缓冲区,btr为读取长度,br为实际读取长度。
6、FRESULT f_write (FIL* fp, const void* buff, UINT btw, UINT* bw);
写文件函数,buff为写缓冲区,btw为写长度,bw为实际写入长度。
7、FRESULT f_sync (FIL* fp);
同步函数,类似于电脑操作的ctrl+s,如果不同步的话可能造成文件丢失。
8、FRESULT f_close (FIL* fp);
关闭文件,同时保存,所以是包含f_sync的操作,但是文件关闭再打开后会将光标移动至初始位置,使用的时候要考虑好是否有再次打开的时候。
Simple Demo
这是一个简易的写TXT文件demo,旨在阐述流程。
FIL file; //文件对象
FATFS fatfs; //逻辑驱动器的工作区
FRESULT res; //FRESULT函数公共结果代码
unsigned int counter;
unsigned char write_buffer[25]; //写文件缓冲区
unsigned char read_buffer[25]; //读文件缓冲区
char str[] = "0:/test2.txt";
int i = 0;int main()
{ SysTick_Init(72);NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断优先级分组 分2组LED_Init();while(SD_Init())//检测不到SD卡{LED2=!LED2;delay_ms(500); } f_mount(&fatfs,"0:",1); //挂载SD卡 res = f_open(&file,str,FA_READ | FA_WRITE | FA_OPEN_ALWAYS); while(res!=0);res = f_lseek(&file,0); //移动写指针到文件首while(res!=0);for(i = 0;i < 25;i++){write_buffer[i] = 0x31 + i % 7;}res = f_write(&file,write_buffer,25,&counter); //将缓冲器中的内容写入源文件while(res!=0);res = f_lseek(&file,0); //移动读指针到文件首while(res!=0);res = f_read(&file,read_buffer,25,&counter);while(res!=0);f_close(&file); //关闭源文件while(1){LED1=!LED1;delay_ms(100); }
}
注意事项
这两个变量应为全局变量,或者静态局部变量,否则会造成存储失败。
FIL file; //文件对象
FATFS fatfs; //逻辑驱动器的工作区
缓冲队列
瞬时数据量可能大于存储速度,所以利用环形队列原理,写了一个环形缓冲,当缓冲满了之后会报错。正常调用存储函数即可,在空闲时调用写入函数。
/*** @brief 环形缓冲读写任务* @param fatbuf :环形缓冲* @param buff[] :数据 需要为字符串* @retval FS文件枚举,只用于判断是否写入成功*/
uint16_t head_index = 0;//头指针,读数据
uint16_t rear_index = 0;//尾指针,写数据
uint8_t circle_buff_write(char *data)//写接口写在SD卡数据写入函数里
{//存入数据格式:data_len(1 byte) + data + /r/n(2 byte),数据长度用于修改hear_index指向位置uint8_t data_len = strlen(data) + 3;//加3是因为要存入数据长度信息、回车符、换行符uint8_t i = 0;//数据缓冲区满了,存储失败if(head_index < rear_index && CIRCLE_BUFF_SIZE - rear_index + head_index - 1< data_len)return 1;else if(head_index > rear_index && head_index - rear_index - 1 < data_len)return 1;fatbuf[rear_index] = data_len;//单次写入数据长度for(i = 0;i < data_len - 3;i++)//复制数据{rear_index = (rear_index + 1) % CIRCLE_BUFF_SIZE;//更新指针fatbuf[rear_index] = data[i];}rear_index = (rear_index + 1) % CIRCLE_BUFF_SIZE;fatbuf[rear_index] = 0x0d;// \rrear_index = (rear_index + 1) % CIRCLE_BUFF_SIZE;fatbuf[rear_index] = 0x0a;// \nrear_index = (rear_index + 1) % CIRCLE_BUFF_SIZE;return 0;
}
uint8_t circle_buff_to_sd_card(void)//读接口
{unsigned int counter = 0;//计数器,用于记录写入数据的个数,但是并没有使用FRESULT res = FR_OK;//用来判断是否写入if(head_index == rear_index)return 0;if(fatbuf[head_index] + head_index > CIRCLE_BUFF_SIZE){//数据存入了零点附近,被分成了两段res += f_write(file,Time_Stamp,22,&counter);//写时间戳res += f_write(file,&fatbuf[(head_index + 1) % CIRCLE_BUFF_SIZE],CIRCLE_BUFF_SIZE - head_index - 1,&counter);//写入前半段数据res += f_write(file,&fatbuf[0],(uint8_t)fatbuf[head_index] - (CIRCLE_BUFF_SIZE - head_index),&counter);//写入后半段数据}else{res += f_write(file,Time_Stamp,22,&counter);//写时间戳res += f_write(file,&fatbuf[(head_index + 1) % CIRCLE_BUFF_SIZE],(uint8_t)fatbuf[head_index] - 1,&counter);//写入数据}if(res == 0){head_index = (head_index + fatbuf[head_index]) % CIRCLE_BUFF_SIZE;//写入成功,更新头指针}return res;
}
基于FATFS的SD卡存储相关推荐
- 基于FPGA的数据采集、通讯和存储系统设计(即FPGA+RTL8211千兆以太网+SD卡存储+RTC+Uart+AD7606数模转换+电流放大采集等硬件设计及程序验证)
本文主要介绍了学生期间自己做的一个小项目,便于学习初期对fpga的整体把握,涉及了很多常见.常用.常考和面试常问的知识点. 可以作为入门后的拓展学习和应对一些找工作的项目面试. 下面对硬件及软件代码进 ...
- 新唐NUC980使用记录:U-Boot Linux 编译与烧录(基于SD1位置SD卡)
文章目录 目的 SD卡分区 U-Boot编译 U-Boot环境变量 Linux编译 默认设置 使用SD卡剩余分区 使用SD卡分区存放rootfs 制作系统镜像 总结 目的 这篇文章中将测试在 NUC9 ...
- html音频从10秒播放至30秒,基于Arduino制作SD卡音乐播放器
一.项目介绍 前面用ATtiny85制作SD卡音乐播放器,本次主要利用Arduino UNO 和SD卡制作音乐播放器.这个播放器不需要添加多余的模块,只需要SD读卡器和Arduino UNO开发板就可 ...
- stm32 Fatfs 读写SD卡
源:stm32 Fatfs 读写SD卡 转载于:https://www.cnblogs.com/LittleTiger/p/4864052.html
- STM32CubeMX+SPI+FATFS读写SD卡
一.软件硬件说明 软件:STM32CubeMX V6.6.1 /KEIL5 V5.29 硬件:正点原子mini开发板,SD卡,通过SPI方式驱动SD卡,用的是SPI1接口 以上内容来源于正点原子min ...
- android的SP存储和SD卡存储
在android中有着很多的存储方式,例如数据库存储,SD卡存储以及SharedPreferences(以下简称SP)等,下面我们来主要的讲解一下SP以及SD卡存储. 首先是SP,SP存储是一个采用K ...
- 3.30 haas506 2.0开发教程-example - SD卡存储数据读写
SD卡存储数据读写 案例说明 数据的写入与读取 串口工具读取数据 接收数据 CSV格式 案例说明 部分设备使用过程中需要保存大量数据到TF卡中,大部分场景拔插TF卡有不太方便. 所以本案例介绍一种使用 ...
- 基于FPGA的SD卡音乐播放器之WM8731篇
基于FPGA的SD卡音乐播放器之WM8731篇 目录 前言 一.I2C驱动模块 二.WM8731寄存器配置模块 三.WM8731时钟生成模块 四.音频发送模块 总结 前言 这个题目是我之前7月初做的一 ...
- 关于Android读取SD卡存储的动态申请
关于Android读取SD卡存储的动态申请 介绍 Android的目录结构 数据的主要存储方式 疑惑 原来的代码:MainActivity.java 修改后代码: 介绍 这篇文章主要关于我学习SD卡的 ...
最新文章
- Windows 下用VS2012(Visual Studio 2012)编译librtmp
- WinsockExpert+NC抓包上传之拿WEBSHELL
- java.lang.NoClassDefFoundError
- html选择器_css的9个常用选择器
- [html] websocket和http2有什么区别?http2能取代websocket吗?为什么?
- Docker CEO Ben Golub:Docker借助开源、天时走向成功
- 2018 年,NLP 研究与应用进展到什么水平了?
- mysql中的各种函数(日期函数、字符串函数、数学函数...)
- SpringMVC 中整合JSON、XML视图二
- android webview打印,javascript - 如何在Android Webview中使网站上的打印按钮工作? - 堆栈内存溢出...
- AAAI'22 | 基于Profile信息的口语语言理解基准
- 基于与非门和多路开关结构的一位全加器实现方法
- 身份证地址码码表MySQL
- java 通过经纬度计算巨鹿,中国主要城市经纬度查询
- java公寓报修管理系统_学生公寓报修管理系统.pdf
- fastDFS上传文件过大
- 惠普HP OEM XP SP3镜像文件高速下载
- Hadoop2.x Yarn作业提交(客户端)
- FFmpeg —— 15.示例程序(九):音频编码器(PCM编码为MP3)
- 暴雪即将公布《暗黑破坏神3》新职业
热门文章
- c++ 11 std::recursive_timed_mutex递归锁定的互斥,并实现有时限锁定
- java反射机制之数组转对象
- 《苔花如米小,也学牡丹开》和《“精日”分子》观后感 焦帅宾
- Android自定义View(一) - View与ViewGroup
- 2019.09.21 多校联合训练(普及组)
- 【总结整理】已读功能---摘自《馒头商学院》
- MFC基础,MFC自绘控件学习总结.---转
- linux 组群设置组群密码,Linux 组群账户管理
- 从零开始的pytorch小白使用指北
- 请结合物联网的应用,解释为什么物联网提供的是行业性、专业性、区域性的服务