目录
初学单片机,分享一些使用心得,如有错误,敬请包涵。
一、FATFS文件系统介绍
1.文件系统简介
FatFs是面向小型嵌入式系统的一种通用的FAT文件系统。它完全是由ANSI C语言编写并且完全独立于底层的I/O介质。 因此它可以很容易地不加修改地移植到其他的处理器当中,如8051、PIC、AVR、SH、Z80、H8、ARM等。 FatFs支持FAT12、FAT16、FAT32等格式,
支持多个存储媒介;有独立的缓冲区,可以对多个文件进行读/写,并特别对8 位单片机和16 位单片机做了优化。 所以我们利用前面写好的SPI模式SD卡驱动,把FatFs文件系统代码移植到工程之中, 就可以利用文件系统的各种函数,对SD卡以“文件”格式进行读写操作了。
特征:
- DOS/Windows 兼容的 FAT/exFAT 文件系统。
- 平台独立。易于移植。
- 程序代码和工作区占用空间非常小。
- 支持的各种配置选项:
- ANSI/OEM 或 Unicode 中的长文件名。
- exFAT 文件系统、64 位 LBA 和用于大型存储的 GPT。
- 适用于 RTOS 的线程安全。
- 多个卷。(物理驱动器和分区)
- 可变扇区大小。
- 多个代码页,包括 DBCS。
- 只读、可选 API、I/O 缓冲区等.
2.SD卡简介
SD存储卡(Secure Digital Memory Card)是一种基于半导体快闪存储器的新一代高速存储设备。SD存储卡的技术是从MMC卡(MultiMedia Card格式上发展而来,在兼容SD存储卡基础上发展了SDIO(SD Input/ Output)卡,此兼容性包括机械,电子,电力,信号和软件,通常将SD、SDIO卡俗称SD存储卡
SD卡有两种驱动模式:SPI模式与SDIO模式。它们所使用的接口信号是不同的。在SPI模式下,只会用到SD卡的4根信号线,即CS、DI、SCLK与DO(分别是SD卡的片选、数据输入、时钟与数据输出)。
SD卡共支持三种传输模式:SPI模式(独立序列输入和序列输出),1位SD模式(独立指令和数据通道,独有的传输格式),4位SD模式(使用额外的针脚以及某些重新设置的针脚。支持四位宽的并行传输)。
参考连接:STM32使用SPI方式读写SD 卡
3.文件系统资源包下载地址
官网已更新到0.15a版本
4.fatfs文件资源包结构
(1)ff15a文件结构
documents文件:是帮助文件,当作是一个官方的参考手册、说明书来看,里面有对文件系统的各个API的使用进行介绍说明
source文件:是整个文件系统的核心所在,包含了文件系统的全部源文件和头文件。
LICENSE文档:是开源许可文件说明。
(2)source文件结构

00history | 介绍了FatFs的版本更新情况 | 不需要修改 |
00readme | 说明了当前目录下 diskio.c 、diskio.h、ff.c、ff.h等的功能 | 不需要修改 |
diskio.c | IO模块接口层文件 | 需要配置API接口 |
diskio.h | IO模块接口层文件 | 不需要修改 |
ff.c | FATFS和应用模块文件 | 不需要修改 |
ff.h | FATFS和应用模块文件 | 不需要修改 |
ffonf.c | FATFS模块配置文件 | 根据需求修改参数 |
ffsystem.c | 根据是否有操作系统来修改这个文件 | |
ffunicode.c | 可选,根据 ffconf.h 的配置,进行 Unicode 编码转换 | 不需要修改 |
注:0.13版本之前的文件如下,0.13版本之后的文件将option文件夹整合到了ffunicode.c中,主要会用到cc936.c文件,它是用来配置中文格式的。


二、0.11a版本使用
1.单片机开发板
我是用的是野火的STM32F103RCT6min开发板,硬件资源如下图,
2.DS3231模块及原理图
3.代码部分
目前实现的功能是利用DS3231时钟模块和FATFS文件系统,实现时间的连续存储
目前只用到sd卡,所以设备只用到一个,SD卡设备用“0”宏定义
diskio.c
#include <string.h>
#include "diskio.h"
#include "stm32f10x.h"
#include "bsp_spi_sdcard.h"
/* 为每个设备定义一个物理编号 */
#define SD_CARD 0 // SD卡
#define SPI_FLASH 1 // 预留外部SPI Flash使用
//固定只支持blocksize大小为512的卡,兼容大于512的卡时,该卡容量会变小
#define SD_BLOCKSIZE 512//SDCardInfo.CardBlockSize
/*-----------------------------------------------------------------------*/
/* 获取设备状态 */
/*-----------------------------------------------------------------------*/
DSTATUS disk_status (
BYTE pdrv /* 物理编号 */
)
{
DSTATUS status = STA_NOINIT;
switch (pdrv) {
case SD_CARD: /* SD CARD */
status &= ~STA_NOINIT;
break;
case SPI_FLASH: /* SPI Flash */
break;
default:
status = STA_NOINIT;
}
return status;
}
/*-----------------------------------------------------------------------*/
/* 设备初始化 */
/*-----------------------------------------------------------------------*/
DSTATUS disk_initialize (
BYTE pdrv /* 物理编号 */
)
{
DSTATUS status = STA_NOINIT;
switch (pdrv) {
case SD_CARD: /* SD CARD */
if(SD_Init()==SD_RESPONSE_NO_ERROR)
{
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) */
)
{
DRESULT status = RES_PARERR;
SD_Error SD_state = SD_RESPONSE_NO_ERROR;
switch (pdrv) {
case SD_CARD: /* SD CARD */
SD_state=SD_ReadMultiBlocks(buff,(uint64_t)sector*SD_BLOCKSIZE,SD_BLOCKSIZE,count);
if(SD_state!=SD_RESPONSE_NO_ERROR)
status = RES_PARERR;
else
status = RES_OK;
break;
case SPI_FLASH:
break;
default:
status = RES_PARERR;
}
return status;
}
/*-----------------------------------------------------------------------*/
/* 写扇区:见数据写入指定扇区空间上 */
/*-----------------------------------------------------------------------*/
#if _USE_WRITE
DRESULT disk_write (
BYTE pdrv, /* 设备物理编号(0..) */
const BYTE *buff, /* 欲写入数据的缓存区 */
DWORD sector, /* 扇区首地址 */
UINT count /* 扇区个数(1..128) */
)
{
DRESULT status = RES_PARERR;
SD_Error SD_state = SD_RESPONSE_NO_ERROR;
if (!count) {
return RES_PARERR; /* Check parameter */
}
switch (pdrv) {
case SD_CARD: /* SD CARD */
SD_state=SD_WriteMultiBlocks((uint8_t *)buff,(uint64_t)sector*SD_BLOCKSIZE,SD_BLOCKSIZE,count);
if(SD_state!=SD_RESPONSE_NO_ERROR)
status = RES_PARERR;
else
status = RES_OK;
break;
case SPI_FLASH:
break;
default:
status = RES_PARERR;
}
return status;
}
#endif
/*-----------------------------------------------------------------------*/
/* 其他控制 */
/*-----------------------------------------------------------------------*/
#if _USE_IOCTL
DRESULT disk_ioctl (
BYTE pdrv, /* 物理编号 */
BYTE cmd, /* 控制指令 */
void *buff /* 写入或者读取数据地址指针 */
)
{
DRESULT status = RES_PARERR;
switch (pdrv) {
case SD_CARD: /* 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 = 1;
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;
}
#endif
__weak DWORD get_fattime(void) {
/* 返回当前时间戳 */
return ((DWORD)(2025 - 1980) << 25) /* Year 2015 */
| ((DWORD)1 << 21) /* Month 1 */
| ((DWORD)1 << 16) /* Mday 1 */
| ((DWORD)0 << 11) /* Hour 0 */
| ((DWORD)0 << 5) /* Min 0 */
| ((DWORD)0 >> 1); /* Sec 0 */
}
fat.c
包含SD卡格式化
#include "stm32f10x.h" // Device header
#include "ff.h"
#include "DS3231.h"
#include "delay.h"
#include "bsp_led.h"
#include "Serial.h"
#include "string.h"
#include "stdio.h"
FATFS fs; /* FatFs文件系统对象 */
FIL fnew; /* 文件对象 */
FRESULT res_sd; /* 文件操作结果 */
UINT fnum; /* 文件成功读写数量 */
char ReadBuffer[1024]={0}; /* 读缓冲区 */
char WriteBuffer[1024]={0}; /* 写入的数据*/
char fileName[100]; //创建的文件名
char TimeData[30]={0}; //缓存时间数据
char stringBuffer[72]={0}; //缓存传感器数据
uint8_t state=0;//定位在这里:写入次数
void SET_FAT(void) //文件系统格式化,创建文件系统
{
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); /* 格式化 */
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("《《格式化失败。》》res_sd =%d\r\n",res_sd);
while(1);
}
}
else if(res_sd!=FR_OK)
{
printf("!!SD卡挂载文件系统失败。(%d)\r\n",res_sd);
printf("!!可能原因:SD卡初始化不成功。\r\n");
while(1);
}
else
{
printf("》文件系统挂载成功,可以进行读写测试\r\n");
}
}
/*********************************************/
void CreatFile(void)
{
Get_DS3231_Time();
printf("\r\n****** 即将进行文件写入测试... ******\r\n");
sprintf(fileName,"0:测量数据_%04d%02d%02d.txt",calendar.w_year+2000,calendar.w_month,calendar.w_date);
printf("》测量数据_%04d%02d%02d.txt\r\n",calendar.w_year+2000,calendar.w_month,calendar.w_date);
}
写入数据,追加数据代码
追加数据主要使用f_lseek函数,通过移动指针位置的方法来实现追加数据,原因是0.11a版本中没有常用的FA_OPEN_APPEND函数来实现追加数据,fatfs官网在0.13版本之后,加入了FA_OPEN_APPEND函数
void WRITE_FAT(void) //写入数据
{
/*----------------------- 文件系统测试:写入数据 -----------------------------*/
Get_DS3231_Time();
res_sd = f_open(&fnew, fileName,FA_WRITE |FA_OPEN_ALWAYS ); //打开成功
if ( res_sd == FR_OK )
{
sprintf(WriteBuffer,"%04d-%02d-%02d %02d:%02d:%02d\n", calendar.w_year + 2000, calendar.w_month, calendar.w_date, calendar.hour, calendar.min, calendar.sec);
res_sd = f_write(&fnew, WriteBuffer, strlen(WriteBuffer), &fnum);
if(res_sd==FR_OK)
{
printf("》文件写入成功,写入字节数据:%d\r\n",fnum);
printf("》向文件写入的数据为:\r\n%s",WriteBuffer);
}
else
{
printf("!!文件写入失败:(%d)\n",res_sd);
}
/* 不再读写,关闭文件 */
f_close(&fnew);
}
else
{
printf("!!打开/创建文件失败。\r\n");
}
}
void REMOVE_FAT(void) //移动指针位置,追加数据
{ delay_ms(1000);
Get_DS3231_Time();
printf("\r\n****** 追加数据测试.. ******\r\n");
f_open(&fnew, fileName, FA_WRITE |FA_OPEN_ALWAYS );//区分写入成功 //打开文件,指针为0
res_sd = f_lseek(&fnew,20*(state+1));//移动31*(state+1)个字节,从state=0开始,移动一次,指针增加112
sprintf(stringBuffer, "%04d-%02d-%02d %02d:%02d:%02d\n", calendar.w_year + 2000, calendar.w_month, calendar.w_date,
calendar.hour, calendar.min, calendar.sec); //时间数据缓存至WriteBuffer
stringBuffer[20] = '\0';
printf("Data:%s\n",stringBuffer);
//将字符串写入SD卡
res_sd=f_write(&fnew,stringBuffer,strlen(stringBuffer),&fnum);
printf("state:%d",state);
state++; //写入次数
/* 不再读写,关闭文件 */
f_close(&fnew);
}
void READ_FAT(void) //读数据
{
/*------------------- 文件系统测试:读数据 ------------------------------------*/
printf("****** 即将进行文件读取测试... ******\r\n");
res_sd = f_open(&fnew, fileName, FA_READ |FA_OPEN_EXISTING );
if(res_sd == FR_OK){
printf("》打开文件成功。\r\n");
}
res_sd = f_read(&fnew, ReadBuffer, strlen(ReadBuffer), &fnum);
/* 实测SPI_SD驱动下读取大于512字节的数据在SD卡里打开会显示乱码,如需读取大量数据使用f_read_co替代上面f_read即可 */
// res_sd = f_read_co(&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);
}
/* 不再读写,关闭文件 */
f_close(&fnew);
/* 不再使用文件系统,取消挂载文件系统 */
f_mount(NULL,"0:",1);
/* 操作完成,停机 */
}
读取文件内容的函数,没有在主函数中用到
main.c
有一些函数的初始化时暂时用不到的,我没删除,但不影响使用,
int main(void)
{
uint8_t state1;
delay_init();
MyI2C1_Init();
DS3231_Init(); //DS3231初始化
LED_GPIO_Config();
Serial_Init();
OLED_Init();
SET_FAT();
// Set_DS3231_Time(25,3,15,12,00,00,6);//设置时间为2025年3月15日12时00分00秒星期6,下载一次之后,注释掉重新下载
CreatFile();
// Get_DS3231_Time(); //获取DS3231的时间
while(1)
{
if(state==0)
{
WRITE_FAT();
REMOVE_FAT();
}
else
{
REMOVE_FAT();
}
Num=0;
delay_ms(1000);
}
// READ_FAT();
}
4.运行结果
串口助手显示
SD卡中创建的文件显示,存储的时间无问题,但想要每秒存储还需调整main.c中的代码
5.遇到的问题
问题1:
追加数据时,我使用如下方法来实现移动指针的位置,20是时间的长度
res_sd = f_lseek(&fnew,20*(state+1));.
下面的方法应该也是可以的,不知道为什么实现不了连续存储,我也没有深入研究,
f_lseek(&fnew f_size(&fnew));
结尾:以上就是在FATAS文件系统中存储时间的代码,原本的代码是用来存储磁传感器数据及时间的,去掉了存储传感器数据 代码,目前实现时间存储目前什么问题,后续,我会继续更新存储传感器数据及时间的代码,还有加入蓝牙模块来控制单片机实现创建、存储等