恒美微站 Logo 恒美微站
  • 首页
  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心
  • 联系我们

STM32 SD卡数据落盘实战:fatfs移植与CSV文件生成

  • 首页
  • 资讯中心
  • /
  • STM32 SD卡数据落盘实战:fatfs移植与CSV文件生成

相关资讯

BP-PID残差补偿控制:Simulink中可解释的神经网络增强型PID实现 2026/10/19 23:20:33
Flutter 库鸿蒙适配实战:m_package 从崩溃到跑通的完整流程 2026/10/19 23:05:32
AI视频生成工作流:从产品拆解到批量成片的完整指南 2026/10/19 23:05:32

最新资讯

DeskcommCRM实战:轻量级客户管理与销售跟进全流程落地指南
以沟通为核心的轻量级CRM系统设计与实践
Kafka全链路性能调优:吞吐量与延迟的实战权衡
ZXing.Net 0.16.8.0 条码识别实战:从解码链路到性能调优
从线程池失控到全局限流:高并发系统稳定性实战指南
STC单片机范例程序实战指南:从点灯到多外设工程搭建

今日推荐

本周热门

基于GMM的语音识别系统原理与MATLAB实现详解
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

本月精选

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证
2026 大模型集体涨价:用 Python 做企业 Token 成本测算与选型避坑(附配置)

STM32 SD卡数据落盘实战:fatfs移植与CSV文件生成

发布时间:2026/10/19 23:20:33
STM32 SD卡数据落盘实战:fatfs移植与CSV文件生成 简介这份资源面向嵌入式开发学习者与STM32工程师聚焦在STM32F429平台上集成FatFS文件系统、驱动SD卡写入数据并生成.csv文件同时结合以太网驱动搭建TCP服务器接收网络数据。内容覆盖FatFS的移植与参数配置、SDIO或SPI方式下的SD卡初始化与读写、CSV格式数据的文件创建与写入以及基于lwIP的TCP服务器连接建立、数据接收与处理流程适合具备一定C语言与嵌入式基础、希望打通存储与网络通信链路的开发者参考。资源包共1482个文件以629个c源文件、382个h头文件为主另含144个d依赖文件、144个o目标文件、142个crf交叉引用文件及少量库文件、工程配置与说明文档压缩包约59.43MB工程结构完整便于直接编译与二次开发。目前已有8150人学习下载可帮助读者快速理解SD卡文件系统与TCP数据落盘的完整实现思路。1. 从一块 SD 卡说起为什么 fatfs 是 STM32 数据落盘绕不开的一环手上有个 STM32 的采集板传感器数据在 RAM 里滚了几百个字节一断电全没了。想把数据存下来最直接的办法就是插一张 SD 卡让它像 U 盘一样能被电脑读走。但 STM32 本身不认识 FAT32 文件系统它只会按扇区读写原始数据。这时候 fatfs 就派上用场了——它是一个轻量级的 FAT 文件系统模块专门给单片机用能把裸扇区操作封装成f_open、f_write、f_close这种文件级接口。这个资源做的事很明确在 STM32 上通过 SDIO 或 SPI 驱动 SD 卡移植 fatfs然后周期性地把采集数据写成.csv文件。csv 的好处是电脑端 Excel、Python pandas 直接能打开不用自己写解析器。适合谁做数据采集、工业记录仪、离线日志存储的嵌入式开发者。如果你正在纠结数据怎么落盘、csv 怎么生成、fatfs 怎么配这篇可以跟着走一遍。2. fatfs 移植与 SD 卡底层驱动从扇区到文件句柄2.1 先搞清楚 fatfs 的分层结构fatfs 的设计是分层解耦的。最上层是ff.c提供f_open、f_read、f_write这些 API跟具体硬件无关。中间是diskio.c这是一层薄薄的适配层把 fatfs 的disk_read、disk_write、disk_initialize映射到你自己的 SD 卡驱动。最底下才是 SDIO 或 SPI 的寄存器操作。常见做法是SDIO 模式跑 4 位总线理论速率能到几十 Mbps适合高速采集SPI 模式接线简单但速率受限于 SPI 时钟一般也就几 Mbps。我一般优先选 SDIO除非引脚实在不够。移植的核心工作就三件事实现diskio.c里的五个函数、配置ffconf.h里的宏、确保 SD 卡驱动能正常初始化并读写扇区。2.2 diskio.c 的五个函数怎么写diskio.c里必须实现disk_initialize、disk_status、disk_read、disk_write、disk_ioctl。前两个是状态查询后三个是实际读写。下面是一个基于 SDIO 的典型实现骨架#include diskio.h #include sdio_driver.h // 你自己的 SDIO 驱动头文件 #define DEV_SD 0 DSTATUS disk_initialize(BYTE pdrv) { if (pdrv ! DEV_SD) return RES_PARERR; if (SDIO_Init() ! 0) return STA_NOINIT; // 初始化失败 return 0; // 成功返回 0 } DSTATUS disk_status(BYTE pdrv) { if (pdrv ! DEV_SD) return RES_PARERR; return 0; // 简化处理实际可查卡是否插入 } DRESULT disk_read(BYTE pdrv, BYTE *buff, DWORD sector, UINT count) { if (pdrv ! DEV_SD) return RES_PARERR; if (SDIO_ReadBlocks((uint8_t *)buff, sector, count) ! 0) return RES_ERROR; return RES_OK; } DRESULT disk_write(BYTE pdrv, const BYTE *buff, DWORD sector, UINT count) { if (pdrv ! DEV_SD) return RES_PARERR; if (SDIO_WriteBlocks((uint8_t *)buff, sector, count) ! 0) return RES_ERROR; return RES_OK; } DRESULT disk_ioctl(BYTE pdrv, BYTE cmd, void *buff) { if (pdrv ! DEV_SD) return RES_PARERR; switch (cmd) { case CTRL_SYNC: return RES_OK; // 等待写完成SDIO 阻塞模式下可直接返回 case GET_SECTOR_COUNT: *(DWORD *)buff SDIO_GetSectorCount(); return RES_OK; case GET_SECTOR_SIZE: *(WORD *)buff 512; return RES_OK; case GET_BLOCK_SIZE: *(DWORD *)buff 1; // 擦除块大小SD 卡填 1 即可 return RES_OK; default: return RES_PARERR; } }逻辑说明disk_initialize里调用你的 SDIO 初始化函数返回 0 表示成功。disk_read和disk_write直接转发给底层块读写函数注意sector是扇区号不是字节地址。disk_ioctl里GET_SECTOR_COUNT必须返回正确的扇区总数否则 fatfs 格式化或挂载时会算错容量。参数说明pdrv是物理驱动号单卡场景固定为 0。buff是数据缓冲区count是扇区数。CTRL_SYNC在阻塞写模式下可以直接返回 OK但如果你用了 DMA 或非阻塞写这里必须等写完成。2.3 ffconf.h 里必须改的几个宏ffconf.h是 fatfs 的配置文件默认值不一定适合 STM32。下面几个宏我每次都会检查宏名默认值建议值原因_FS_TINY00设为 1 会省 RAM 但性能下降采集场景不差这点内存_FS_READONLY00要写 csv 必须为 0_USE_MKFS01卡没格式化时可以直接在板子上格式化_CODE_PAGE932936932 是日文936 是简体中文避免文件名乱码_USE_LFN01 或 2支持长文件名csv 文件名可以写完整日期_MAX_SS512512扇区大小SD 卡固定 512_VOLUMES11单卡场景一个卷够了_USE_LFN设为 1 时用静态缓冲区设为 2 时用栈上动态分配设为 3 时用堆。STM32 上我一般用 1省得折腾内存管理。2.4 挂载与格式化第一次用卡必须做的事新卡或者电脑上格式化过的卡挂载前最好先确认文件系统类型。如果卡是 exFAT 或 NTFSfatfs 默认不支持需要f_mkfs重新格式化成 FAT32。下面是一段挂载加自动格式化的代码FATFS fs; // 文件系统对象 FIL fil; // 文件对象 FRESULT fres; fres f_mount(fs, 0:, 1); // 1 表示立即挂载 if (fres ! FR_OK) { // 挂载失败尝试格式化 fres f_mkfs(0:, FM_FAT32, 0, NULL, 0); if (fres ! FR_OK) { printf(Format failed: %d\r\n, fres); return; } fres f_mount(fs, 0:, 1); if (fres ! FR_OK) { printf(Mount after format failed: %d\r\n, fres); return; } }逻辑说明f_mount的第三个参数为 1 时会立即执行挂载操作为 0 时只注册不挂载。f_mkfs的第二个参数指定格式类型FM_FAT32适合 32GB 以下的卡。第四个参数是工作缓冲区传 NULL 时 fatfs 会用内部缓冲区但有些版本要求外部提供传 NULL 前先确认版本。参数说明0:是逻辑驱动器号对应diskio.c里的DEV_SD。f_mkfs的第三个参数是簇大小传 0 表示自动选择。3. 生成 csv 文件从 f_open 到 f_printf 的完整链路3.1 csv 文件的写入流程csv 本质是纯文本每行一条记录字段用逗号分隔。fatfs 提供f_printf函数可以直接往文件里写格式化字符串比手动拼接再f_write方便得多。但f_printf需要开启_FS_READONLY为 0 且_USE_STRFUNC不为 0。典型流程是打开文件追加模式→ 写表头如果是新文件→ 写数据行 → 关闭文件。下面是一个完整的写入函数FRESULT write_csv_record(FIL *fil, uint32_t timestamp, float temp, float humi) { FRESULT fres; UINT bw; // 以追加模式打开如果文件不存在则创建 fres f_open(fil, 0:data.csv, FA_OPEN_ALWAYS | FA_WRITE); if (fres ! FR_OK) return fres; // 移动到文件末尾 fres f_lseek(fil, f_size(fil)); if (fres ! FR_OK) { f_close(fil); return fres; } // 如果文件为空先写表头 if (f_size(fil) 0) { f_printf(fil, timestamp,temperature,humidity\r\n); } // 写数据行 f_printf(fil, %lu,%.2f,%.2f\r\n, timestamp, temp, humi); f_close(fil); // 必须关闭否则数据可能还在缓存里 return FR_OK; }逻辑说明FA_OPEN_ALWAYS表示文件存在就打开不存在就创建。f_lseek移动到末尾是为了追加而不是覆盖。f_size返回当前文件大小用来判断是否需要写表头。f_printf的格式化语法和标准 C 的printf基本一致但浮点数支持取决于_USE_STRFUNC的配置。参数说明fil是文件对象指针调用前需要先f_mount。timestamp用%lu因为它是 32 位无符号数。%.2f保留两位小数csv 里够用了。\r\n是 Windows 换行符Excel 打开不会乱行。3.2 写入频率与缓冲策略每次采集都f_openf_close一次在低频场景比如每秒一条没问题。但如果每秒要写几十上百条频繁开关文件会拖慢速度因为每次f_close都会触发 FAT 表更新和扇区写入。我一般会这样做文件保持打开状态每隔 N 条或者每隔几秒调用一次f_sync把缓存刷到卡里。f_sync比f_close轻量只刷新数据不关闭文件。掉电风险高的场景N 取小一点比如 10 条同步一次对掉电不敏感的场景可以 100 条同步一次。// 保持文件打开定期 sync static FIL csv_fil; static uint32_t record_count 0; void csv_append(uint32_t ts, float t, float h) { f_printf(csv_fil, %lu,%.2f,%.2f\r\n, ts, t, h); record_count; if (record_count % 10 0) { f_sync(csv_fil); // 每 10 条刷一次 } }逻辑说明f_sync会把文件缓存区的脏数据写回 SD 卡但不关闭文件句柄。这样既减少了开关开销又控制了掉电丢数据的窗口。参数说明record_count是静态变量记录自上次 sync 以来写了多少条。取 10 是经验值具体看你的数据重要程度和 SD 卡写入速度。3.3 文件名带时间戳避免覆盖和历史追溯固定叫data.csv有个问题每次上电都往同一个文件追加时间长了文件巨大而且分不清哪次采集是哪次。更好的做法是用 RTC 的时间生成文件名比如20250101_120000.csv。这样每次上电新建一个文件电脑上按文件名就能排序。void create_csv_by_time(FIL *fil, RTC_TimeTypeDef *time, RTC_DateTypeDef *date) { char fname[32]; snprintf(fname, sizeof(fname), 0:%04d%02d%02d_%02d%02d%02d.csv, date-Year 2000, date-Month, date-Date, time-Hours, time-Minutes, time-Seconds); f_open(fil, fname, FA_CREATE_ALWAYS | FA_WRITE); }逻辑说明snprintf拼出文件名FA_CREATE_ALWAYS表示每次都新建如果同名文件存在就覆盖。RTC 的年份寄存器通常存的是 2000 年起的偏移所以要加 2000。参数说明fname缓冲区至少 32 字节0:前缀不能少。snprintf比sprintf安全能防止缓冲区溢出。4. 避坑与排查SD 卡 fatfs 最容易翻车的五个地方4.1 挂载返回 FR_NOT_READY 或 FR_DISK_ERR现象f_mount返回FR_NOT_READY或者f_open返回FR_DISK_ERR。原因九成是disk_initialize没成功或者disk_read读不到扇区 0。SD 卡上电后需要一段初始化时间如果 MCU 启动太快卡还没准备好就发命令就会失败。解决在disk_initialize里加延时上电后至少等 1ms 再发 CMD0。另外检查 SDIO 时钟分频初始化阶段必须降到 400kHz 以下初始化完成后再切到高速。SPI 模式同理先低速再高速。4.2 写 csv 后电脑打开乱码或列错位现象csv 文件在 Excel 里打开中文乱码或者所有数据挤在一列。原因Excel 默认按系统区域设置解析 csv如果文件里没有 BOM中文可能乱码。列错位通常是分隔符问题有些区域设置用分号而不是逗号。解决表头和数据里不要用中文字段名用英文。如果一定要中文在文件开头写入 UTF-8 BOM0xEF 0xBB 0xBF。列错位的话确认f_printf里用的是逗号且字段值里没有多余的逗号。4.3 f_write 返回成功但数据没落盘现象f_write和f_close都返回FR_OK但拔卡插电脑上发现文件是空的或者少了最后几条。原因fatfs 有缓存机制f_write只是写到缓存f_close才真正刷到卡。如果f_close之前掉电缓存里的数据就丢了。另外有些 SD 卡内部也有缓存f_close返回后卡可能还在写。解决对掉电敏感的场景每次写完调用f_sync并且f_sync之后加一句disk_ioctl(0, CTRL_SYNC, NULL)确保卡内部也写完。如果还不行换一张卡试试有些杂牌卡缓存策略很激进。4.4 长时间写入后速度越来越慢现象刚开始写 csv 很快写了几千条之后越来越慢甚至卡死。原因FAT 表碎片化。频繁创建删除文件、或者文件反复增长会导致 FAT 表里簇链变得很长每次写都要遍历簇链。另外如果 SD 卡质量差坏块重映射也会拖慢速度。解决定期整理比如每天新建一个文件而不是往同一个文件无限追加。格式化时选大一点的簇比如 32KB减少簇链长度。如果卡已经用了很久用f_mkfs重新格式化一次。4.5 多任务环境下 fatfs 重入问题现象在 RTOS 里一个任务写 csv另一个任务读配置偶尔会死机或者文件损坏。原因fatfs 默认不是线程安全的多个任务同时调用f_open、f_write会破坏内部状态。解决给 fatfs 加互斥锁。简单做法是用一个全局标志位访问前检查并置位访问完清除。更规范的做法是用 RTOS 的互斥量在f_mount之后创建每次文件操作前 take操作完 give。另外_FS_REENTRANT宏可以开启 fatfs 自带的重入保护但需要你实现ff_req_grant和ff_rel_grant两个信号量函数。5. 进阶技巧用 f_printf 直接输出带表头的结构化 csv前面写的 csv 是手动拼表头和数据行其实 fatfs 的f_printf可以更灵活。比如你要记录多种传感器字段数量不固定可以用一个格式化函数统一处理。下面这个技巧我经常用把表头和数据行都封装成函数字段用数组传入避免每次改字段都要改代码。// 写表头fields 是字段名数组count 是字段数 FRESULT csv_write_header(FIL *fil, const char **fields, uint8_t count) { for (uint8_t i 0; i count; i) { f_printf(fil, %s, fields[i]); if (i count - 1) f_printf(fil, ,); } f_printf(fil, \r\n); return FR_OK; } // 写一行 float 数据 FRESULT csv_write_row(FIL *fil, const float *values, uint8_t count) { for (uint8_t i 0; i count; i) { f_printf(fil, %.3f, values[i]); if (i count - 1) f_printf(fil, ,); } f_printf(fil, \r\n); return FR_OK; }逻辑说明csv_write_header遍历字段名数组用逗号分隔最后换行。csv_write_row同理但输出的是浮点数。这样字段增减只需要改数组不用动写入逻辑。参数说明fields是字符串指针数组values是浮点数组count是字段数量。%.3f保留三位小数按需调整。验证方法写完之后把卡拔下来插电脑用 Excel 或文本编辑器打开确认列对齐、行数正确、没有多余空行。更严格的验证是用 Python 的csv模块读一遍import csv with open(data.csv, r) as f: reader csv.reader(f) for row in reader: print(row)如果 Python 能正常解析说明格式没问题。我一般还会检查文件大小和记录数是否匹配比如每条记录约 30 字节1000 条就是 30KB 左右差太多说明有丢数据。从那以后我每次写完 csv 都强制走一遍「拔卡→电脑读→Python 解析」的流程确认无误再批量部署。希望帮到你。本文还有配套的精品资源点击获取

关于恒美微站

恒美微站专注于为个体商户、工作室提供极简自助建站服务,让每个人都能轻松拥有专业网站。

快速链接

  • 关于我们
  • 建站服务
  • 主题模板
  • 案例展示
  • 资讯中心

服务项目

  • 可视化建站
  • 拖拽编辑
  • 主题定制
  • SEO 优化
  • 网站托管

联系方式

  • 📍 地址:北京市朝阳区建国路 88 号
  • 📞 电话:400-888-8888
  • ✉️ 邮箱:info@hmyw.cn
  • 🕐 时间:周一至周日 9:00-18:00

© 2024 恒美微站 hmyw.cn 版权所有 | 京 ICP 备 12345678 号