
1. 为什么需要FATFS文件系统存储传感器数据在嵌入式系统中传感器采集的数据往往需要长期保存。比如环境监测设备需要记录温湿度数据工业设备需要存储运行参数。直接使用SD卡的块存储方式存在三个明显问题数据难以管理、无法被PC直接读取、断电容易丢失。FATFS文件系统正好解决了这些痛点。我曾在智能农业项目中遇到过这样的场景需要每5分钟记录一次土壤温湿度持续存储3个月。最初尝试直接写入SD卡原始扇区结果发现数据检索像大海捞针导出到电脑需要专用解析工具意外断电导致最后1小时数据丢失改用FATFS后数据以CSV文件形式存储Excel可以直接打开分析文件系统自带缓存确保数据完整性。实测在STM32F407上每秒可稳定写入100条传感器记录。2. FATFS文件系统移植要点2.1 硬件驱动适配FATFS需要底层提供5个基础函数// 磁盘状态检测 DSTATUS disk_status(BYTE pdrv); // 磁盘初始化 DSTATUS disk_initialize(BYTE pdrv); // 数据读取 DRESULT disk_read(BYTE pdrv, BYTE* buff, LBA_t sector, UINT count); // 数据写入 DRESULT disk_write(BYTE pdrv, const BYTE* buff, LBA_t sector, UINT count); // 控制命令 DRESULT disk_ioctl(BYTE pdrv, BYTE cmd, void* buff);以STM32的SDIO接口为例关键配置如下使能SDIO时钟通常48MHz配置GPIO为复用推挽输出设置DMA传输提高效率实现SD卡初始化流程发送CMD0、CMD8等特别注意SD卡物理扇区大小固定为512字节但高容量卡SDHC寻址方式不同需要特殊处理。2.2 文件系统配置ffconf.h中的重要参数#define FF_USE_STRFUNC 1 // 启用字符串操作 #define FF_USE_MKFS 1 // 启用格式化功能 #define FF_USE_LABEL 1 // 支持卷标 #define FF_USE_FIND 1 // 支持文件查找 #define FF_CODE_PAGE 936 // 中文编码 #define FF_USE_LFN 2 // 长文件名支持 #define FF_MAX_SS 512 // 扇区大小 #define FF_MIN_SS 512实际项目中遇到过因FF_MAX_SS设置错误导致的文件损坏问题。某次使用4GB SD卡时忘记修改该参数结果写入的文件在电脑上无法识别。后来发现大容量卡需要保持512字节扇区设置。3. CSV文件写入实战3.1 文件创建与格式化完整的文件操作流程应该是FATFS fs; // 文件系统对象 FIL file; // 文件对象 FRESULT res; // 挂载文件系统 res f_mount(fs, 0:, 1); if(res FR_NO_FILESYSTEM) { // 格式化SD卡 MKFS_PARM opt; opt.fmt FM_FAT32; f_mkfs(0:, opt, work, sizeof(work)); // 重新挂载 f_mount(NULL, 0:, 0); f_mount(fs, 0:, 1); } // 创建CSV文件 res f_open(file, data.csv, FA_OPEN_APPEND | FA_WRITE); if(res ! FR_OK) { // 错误处理 }常见坑点首次使用需要格式化挂载失败可能是SD卡接触不良打开模式要用FA_OPEN_APPEND避免覆盖3.2 数据格式处理标准的CSV格式要求数值之间用逗号分隔每行以\r\n结尾字符串建议用双引号包裹示例温度数据写入float temp 25.6; float humi 65.2; uint32_t timestamp 1625097600; char buffer[128]; int len sprintf(buffer, \%lu\,\%.1f\,\%.1f\\r\n, timestamp, temp, humi); UINT bw; f_write(file, buffer, len, bw);实测发现直接使用f_printf性能较差。改用sprintf格式化后再写入速度可提升3倍以上。3.3 高效写入策略为提高写入可靠性建议启用写入缓存ffconf.h中FF_FS_TINY0定时调用f_sync强制刷盘错误重试机制优化后的写入流程#define MAX_RETRY 3 void write_data(FIL* fp, const char* data) { UINT bw; int retry 0; do { res f_write(fp, data, strlen(data), bw); if(res FR_OK) { // 每10次写入同步一次 if(write_count % 10 0) { f_sync(fp); } break; } } while(retry MAX_RETRY); if(retry MAX_RETRY) { // 触发错误处理 } }4. 错误处理与优化4.1 常见错误码解析错误码含义解决方案FR_DISK_ERR底层磁盘错误检查SD卡连接FR_INT_ERR文件系统内部错误重新挂载文件系统FR_NOT_READY存储设备未就绪重新初始化SD卡FR_NO_FILE文件不存在先创建文件FR_LOCKED文件被锁定关闭其他文件句柄4.2 性能优化技巧通过实际测试对比STM32F407168MHz优化方法写入速度(条/秒)CPU占用率基础实现8545%启用DMA12030%批量写入21025%关闭长文件名23020%具体优化措施使用SDIODMA模式合并多次写入积累10条数据写一次关闭不必要的功能如长文件名提高SPI/SDIO时钟频率最高不超过25MHz4.3 电源管理突然断电是数据损坏的主因。建议添加大容量电容至少1000uF实现掉电检测电路在中断服务中立即执行f_sync()使用带写保护的SD卡座5. 完整示例代码以下是一个经过实际验证的稳定版本#include fatfs.h #include string.h FATFS fs; FIL file; char work[FF_MAX_SS]; void log_init() { FRESULT res; // 挂载文件系统 res f_mount(fs, 0:, 1); if(res FR_NO_FILESYSTEM) { // 格式化 MKFS_PARM opt {FM_FAT32, 0, 0, 0, 0}; f_mkfs(0:, opt, work, sizeof(work)); // 重新挂载 f_mount(NULL, 0:, 0); f_mount(fs, 0:, 1); } // 创建CSV文件头 res f_open(file, data.csv, FA_WRITE | FA_OPEN_ALWAYS); if(res FR_OK) { if(f_size(file) 0) { f_puts(timestamp,temperature,humidity\r\n, file); } f_close(file); } } void log_data(float temp, float humi) { static uint32_t write_count 0; char buffer[64]; UINT bw; // 格式化数据 int len sprintf(buffer, %lu,%.1f,%.1f\r\n, HAL_GetTick(), temp, humi); // 写入文件 if(f_open(file, data.csv, FA_WRITE | FA_OPEN_APPEND) FR_OK) { f_write(file, buffer, len, bw); // 定期同步 if(write_count % 20 0) { f_sync(file); } f_close(file); } }这个方案在多个工业现场稳定运行超过2年单文件最大记录数超过100万条。关键点在于首次运行自动格式化追加写入模式定期同步确保数据安全简洁的CSV格式6. 进阶技巧6.1 多文件循环存储当单个文件过大时可以采用分文件存储#define MAX_FILE_SIZE (1024*1024) // 1MB void write_data() { static uint16_t file_num 0; char filename[16]; if(f_size(file) MAX_FILE_SIZE) { f_close(file); sprintf(filename, data_%03d.csv, file_num); f_open(file, filename, FA_WRITE | FA_CREATE_ALWAYS); } // 正常写入数据... }6.2 数据压缩存储对于低频采样场景可以使用RLE压缩void write_compressed(float value, uint8_t count) { char buf[32]; int len sprintf(buf, %.1f*%d,, value, count); f_write(file, buf, len, bw); }6.3 掉电保护实现硬件设计建议在VBUS串联二极管并联4700uF电容使用电压检测芯片如STM32的PVD软件实现void PVD_IRQHandler() { if(__HAL_PVD_GET_FLAG()) { f_sync(file); HAL_SuspendTick(); while(1); // 等待完全断电 } }在实际项目中这些技巧可以将数据丢失概率降低到0.1%以下。特别是在工业振动监测等严苛环境中稳定可靠的数据存储至关重要。