STM32 SD 卡 + FatFS 实战:掉电丢数据?f_sync 和簇对齐写救你
给温控器加数据记录功能那次客户要求断电前至少保留最近 1000 条记录。我一开始用片上 Flash 轮流擦两页存每条 16 字节两页一共只能存 128 条。后来换了 SD 卡FatFS 一挂f_open 一个 CSV 文件一行行 f_write感觉空间无限。结果第一次现场测试就翻车客户拔电源去搬设备插回来一看日志文件大小是 0KB。一千多条温度记录全没了。原因很简单我只在程序正常退出时调了f_close()突然断电的时候文件系统的缓存还在 RAM 里根本没落盘。后来加了f_sync()每 10 条刷一次再也没丢过数据。原理人话版SD 卡就是一块带控制器的 NAND Flash。它自己内部管磨损均衡和坏块屏蔽对外只暴露标准的块设备接口以 512 字节扇区为单位读写。你不用操心它里面怎么擦怎么搬只管按扇区读写。STM32 接 SD 卡有两条路SDIO推荐四根数据线 D0-D3 加一根命令线 CMD最高 48MHz 时钟下理论带宽 24MB/s。速度快、CPU 占用低支持 DMA但占用的 GPIO 多。SPI 兼容模式只用 MOSI/MISO/SCK/CS 四根线速度慢很多SPI 一般跑 25MHz 以下但省引脚。适合 GPIO 紧张或者芯片没有 SDIO 外设的情况。FatFS 是一个开源的嵌入式 FAT 文件系统实现。它把底层的按扇区读写翻译成你熟悉的f_open/f_read/f_write/f_close这套文件操作。你只需要提供两个函数一个读扇区、一个写扇区FatFS 剩下的目录管理、簇链追踪、FAT 表维护全帮你搞定。CubeMX 怎么配以 SDIO 4-bit FatFS 为例第一步开 SDIO 外设在 Connectivity 里找到SDIO模式选SD 4 bits Wide busClock Divider (CLKDIV)初始化阶段 SD 卡要求时钟在 400kHz 以内CubeMX 生成的代码会自动在初始化流程里先设低速、握手完成后再切高速。这里填个初始分频值比如 118 对应 400kHz 左右。Flow Control勾上。DMA 传输满或空时自动插入等待周期防止数据溢出。DMA Settings开启 SDIO 的 TX 和 RX DMA 通道选 Circular 或 Normal 模式都行单次传输用 Normal 就够了。引脚分配确认CMDPC12CKPC12注意 CK 和 CMD 在某些封装上是复用的D0-D3PC8-PC11CD卡检测可选 GPIO检测卡是否插入WP写保护可选 GPIO第二步开 FatFS 中间件Middleware 里找到FATFS模式选SD CardUse DMA Template勾上让 FatFS 底层走 DMA 读写扇区。Use Long Filename看你需要。开了之后每个文件对象多占几十字节堆内存但文件名可以超过 8.3 格式。日志文件建议开LOG_20260805.csv这种名字方便检索。Code Pages填 936简体中文 GBK否则中文文件名乱码。Max Sector Size保持默认 512SD 卡标准扇区大小。Min Allocation Unit Size也就是簇大小。512 字节适合小文件频繁写的场景4096 或 8192 适合大文件顺序写。日志场景建议 4096。代码挂卡、写日志、安全落盘CubeMX 生成的MX_FATFS_Init()会调用f_mount()把 SD 卡挂载到指定逻辑盘默认/就是 “0:”。你直接用#includefatfs.h#includesd_diskio.hFATFS fs;// 文件系统对象FIL fil;// 文件对象charlog_buf[256];// 上电挂载 SD 卡intsd_card_init(void){FRESULT frf_mount(fs,0:,1);// 1 立即挂载, 不延迟if(fr!FR_OK)return-1;// 可选: 检查剩余空间DWORD free_clusters,free_sectors;FATFS*pfs;frf_getfree(0:,free_clusters,pfs);if(frFR_OK){free_sectorsfree_clusters*fs.csize;printf(SD free: %lu KB\r\n,(uint32_t)free_sectors/2);}return0;}写日志的核心循环打开文件追加模式、格式化一行、写入、定期同步。// 写一条温度记录, 追加到 LOG_YYYYMMDD.csvintlog_temperature(floattemp,floatsetpoint){staticuint16_tline_count0;// 打开文件: FA_OPEN_APPEND 不覆盖已有内容, 从末尾追加FRESULT frf_open(fil,0:/LOG_20260816.csv,FA_WRITE|FA_OPEN_ALWAYS|FA_OPEN_APPEND);if(fr!FR_OK)return-1;intlensnprintf(log_buf,sizeof(log_buf),%.3f,%.3f,%lu\r\n,temp,setpoint,(unsignedlong)(HAL_GetTick()/1000));UINT written;frf_write(fil,log_buf,len,written);if(fr!FR_OK||written!(UINT)len){f_close(fil);return-2;}line_count;// ★ 每 10 条 sync 一次, 断电最多丢 9 条if(line_count%100){f_sync(fil);// 强制把文件缓存刷到 SD 卡物理介质}f_close(fil);// 正常关闭也触发一次 flushreturn0;}f_sync()和f_close()的区别f_sync()刷完缓存后文件仍然处于打开状态下次f_write继续往后面追f_close()刷缓存并释放文件句柄。高频写日志的场景下反复f_open/f_close开销大且容易耗尽文件描述符保持文件打开、定期f_sync()是正确做法。簇对齐写入提速FatFS 默认的f_write内部会处理跨簇边界的情况当前簇空间不够了就分配新簇、更新 FAT 表、可能还要读旧簇内容做合并。如果你每次写的长度恰好对齐到簇边界比如簇大小 4096 字节每次写 4096 的整数倍这些额外开销全部消失写入速度能快两到三倍。// 高速批量写: 先攒够一簇再一次性 f_write#defineCLUSTER_SIZE4096staticuint8_tcluster_buf[CLUSTER_SIZE];staticuint32_tcluster_offset0;voidlog_batch_add(constchar*line,uint16_tlen){if(cluster_offsetlenCLUSTER_SIZE){// 当前簇满了, 刷出去flush_cluster();}memcpy(cluster_bufcluster_offset,line,len);cluster_offsetlen;}voidflush_cluster(void){if(cluster_offset0)return;FRESULT frf_open(fil,0:/LOG_BATCH.csv,FA_WRITE|FA_OPEN_ALWAYS|FA_OPEN_APPEND);if(fr!FR_OK){cluster_offset0;return;}UINT written;f_write(fil,cluster_buf,cluster_offset,written);f_sync(fil);f_close(fil);cluster_offset0;}10 个真实翻车点1. 断电没调 f_sync日志全丢。我踩过的坑也是新手第一坑。f_write只是把数据写进了 FatFS 在 RAM 里的文件缓存不碰物理介质。突然断电缓存里的东西跟着电压一起消失。高频写数据的场景必须定期f_sync()频率取决于你能接受的最大丢失量每条都 sync 最安全但最慢每 10 到 50 条一次是常见折中。2. f_open 没检查返回值后续操作全是废动作。SD 卡没插好、卡坏了、文件系统损坏、路径不存在f_open会返回错误码。你不检查返回值直接f_write写到一个无效的文件对象上要么静默失败要么 HardFault。每个f_open后面跟一个if (fr ! FR_OK)分支。3. SPI 模式初始化时钟太快卡不响应。SD 卡规范要求初始化阶段CMD0 到 ACMD41 握手完成之前时钟不得超过 400kHz。你一上来就给 SPI 配 18MHz卡根本不认你。正确做法先低速发 CMD0 复位收到正确的 R1 响应后再提频。CubeMX 的 SDIO 模板一般自动处理了这个流程但 SPI 模式你得自己管。4. 写入没对齐簇边界速度慢三倍。你每次f_write写 30 字节的一行 CSVFatFS 内部要做读当前簇尾部 - 合并新数据 - 写回整簇的操作频繁跨簇时还要更新 FAT 表。攒够一整簇比如 4096 字节再一次性写这些开销全消掉。实测 30 字节逐行写大约 50KB/s4KB 对齐写能到 150KB/s 以上。5. 开了长文件名但堆不够f_open 直接返回 FR_NOT_ENOUGH_CORE。FF_USE_LFN 0 时每个文件对象需要额外的堆空间来存长文件名转换缓冲区。你堆才给了 2KB开几个文件就爆了。要么关掉长文件名用 8.3 格式要么加大_HEAP_SIZE链接脚本或启动文件里改。6. 反复 f_open 不 f_close文件句柄耗尽。FatFS 默认的_FS_LOCK定义了同时打开的文件数上限通常是 8 左右。你在循环里每次f_open都不f_close跑到第 N 次返回FR_TOO_MANY_OPEN_FILES。日志场景保持一个文件常开更好见上面的log_temperature做法。7. 写的过程中拔卡文件系统损坏。f_sync()正在往 SD 卡写 FAT 表的时候用户把卡拔了轻则丢几条记录重则整个分区变成 RAW 格式FAT 表损坏。硬件上加卡检测引脚CD软件里在检测到卡移除后立刻停止一切文件操作并f_mount(NULL, ...)卸载。8. FatFS 默认不是线程安全的。你在主循环写日志中断回调里也想读配置文件两个地方同时调 FatFS API文件系统状态会被搞乱。开启_FS_REENTRANT并提供一个互斥锁osMutex 或简单的开关中断或者在架构上约定所有文件操作只在主循环执行。9. 扇区缓冲区定义在栈上栈溢出。FatFS 的底层 disk I/O 函数需要一个扇区大小的缓冲区通常 512 字节。你把它定义为局部变量放在 ISR 或者深层函数调用里栈帧叠加起来超了。把它定义成全局静态数组或放到外部 SRAM 里。10. SD 卡本身寿命到了突然变砖。SD 卡内部的 NAND Flash 有擦写寿命MLC 大约 3000 到 10000 次 P/E 循环。你每秒写一次日志每次修改 FAT 表 数据簇一天就是 86400 次擦写操作高强度使用下一两年就可能坏。做法增大写入批次减少 FAT 表更新频率、选用工业级高 endurance 卡、或者考虑换 eMMC/NOR Flash 方案。完整例程骨架intmain(void){HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_SDIO_SD_Init();MX_FATFS_Init();MX_USART1_UART_Init();if(sd_card_init()!0){printf(SD card mount failed\r\n);// 降级运行: 只显示, 不记录}while(1){floattread_temperature();floatspget_setpoint();display_temp(t,sp);if(sd_mountedtick_1s){log_temperature(t,sp);// 内部含 f_synctick_1s0;}HAL_Delay(100);}}硬件清单普通 microSD 卡一张Class 10 以上保证写入速度、SD 卡槽带 CD 引脚最好还带 WP 引脚、去耦电容 100nF 靠近卡槽供电引脚。走线尽量短SDIO 高频信号线等长匹配更好48MHz 下要求没那么严但别绕大弯。适合需要持久化存储任意容量数据的产品温度/压力/振动记录仪、事件日志、固件升级包暂存、多媒体文件播放也适合需要通过 PC 直接读取数据拔卡插电脑就能看到 CSV/TXT 文件的场景。不适合只需存几十字节参数片上 Flash 更可靠没有机械接触风险也不适合极端振动环境SD 卡触点可能瞬断那种该用焊接式 eMMC 或 NOR Flash。铁律四条写数据后必须定期 f_sync 防断电丢数据、f_open 必须查返回值、SPI 模式初始化必须先低速后提速、高频小数据写入必须攒批对齐簇边界。这四条做到SD 卡 FatFS 就是一块插拔自如的大号硬盘。文件系统这层抽象最骗人的地方在于它让你以为写了就是存了。其实f_write只是写进了 RAM 缓存f_sync才是真正落盘的那一刻。记住这个区别断电就不会再吓你了。

相关新闻

深入解析JavaScript原型链继承:从原理到ES6 Class的底层实现

深入解析JavaScript原型链继承:从原理到ES6 Class的底层实现

1. 从“对象”到“继承”:为什么我们需要原型链? 如果你写过一段时间的JavaScript,尤其是从其他语言(比如Java、C#)转过来的,大概率会对JS的“面向对象”感到困惑。我们明明可以用 function 定义一个“类…

2026/8/23 8:52:40 阅读更多 →
大模型后训练评估缺失:从静态测试到动态监控的工程实践

大模型后训练评估缺失:从静态测试到动态监控的工程实践

在实际的大模型应用开发中,我们常常遇到一个令人困惑的现象:一个在标准测试集上表现优异的模型,一旦部署到真实业务场景,其表现就可能大打折扣,甚至出现一些意想不到的“愚蠢”错误。这背后不仅仅是数据分布差异的问题…

2026/8/23 8:52:40 阅读更多 →
数学规划全解析:线性、非线性、整数与0-1规划的核心区别与应用

数学规划全解析:线性、非线性、整数与0-1规划的核心区别与应用

1. 从“规划”说起:数学建模中的决策艺术在数学建模和运筹优化的世界里,“规划”这个词听起来可能有点抽象,但它本质上就是一种在约束条件下寻找最优决策的艺术。无论是企业决定生产多少产品才能利润最大化,还是物流公司规划配送路…

2026/8/23 8:51:40 阅读更多 →

最新新闻

十分钟搞懂 macFUSE:macOS 免内核代码自建文件系统的免费保姆级攻略

十分钟搞懂 macFUSE:macOS 免内核代码自建文件系统的免费保姆级攻略

十分钟搞懂 macFUSE:macOS 免内核代码自建文件系统的免费保姆级攻略 【免费下载链接】osxfuse macFUSE umbrella repository 项目地址: https://gitcode.com/gh_mirrors/os/osxfuse macFUSE 是 macOS 上的一套用户态文件系统框架:文件系统代码跑在…

2026/8/23 10:57:26 阅读更多 →
UI-TARS 桌面应用:用自然语言指挥电脑,从零部署到跑通第一个任务

UI-TARS 桌面应用:用自然语言指挥电脑,从零部署到跑通第一个任务

UI-TARS 桌面应用:用自然语言指挥电脑,从零部署到跑通第一个任务 【免费下载链接】UI-TARS-desktop The Open-Source Multimodal AI Agent Stack: Connecting Cutting-Edge AI Models and Agent Infra 项目地址: https://gitcode.com/GitHub_Trending/…

2026/8/23 10:57:26 阅读更多 →
多模态智能体在开放3D世界中的端到端任务执行与评测实践

多模态智能体在开放3D世界中的端到端任务执行与评测实践

1. 先搞清楚这个“多模态智能体”到底解决了什么实际问题 如果你看到“多模态智能体”、“开放3D世界”这些词,第一反应可能是“又一个炫酷的AI概念”。但这次不一样,它解决的是一个非常具体且棘手的工程问题: 如何让一个AI智能体&#xff0…

2026/8/23 10:57:26 阅读更多 →
CSP-J/S经典真题解析:队列优化公交换乘模拟算法

CSP-J/S经典真题解析:队列优化公交换乘模拟算法

1. 项目概述:从一道经典真题看算法竞赛中的模拟与优化如果你正在准备CSP-J/S(信息学奥赛入门级/提高级)或者类似的算法竞赛,那么“公交换乘”这道题绝对是一个绕不开的经典。它源自2019年CSP-J(原NOIP普及组&#xff0…

2026/8/23 10:57:26 阅读更多 →
大语言模型面试题库:从理论到实践的50个关键问题

大语言模型面试题库:从理论到实践的50个关键问题

1. 项目背景与核心价值 最近在帮团队面试AI方向候选人时,发现很多同学对大语言模型的理解还停留在表面调用API的阶段。这促使我系统整理了这份覆盖LLM全知识栈的面试题库,包含从基础理论到生产实践的50个关键问题。不同于网上零散的面试题集合&#xff0…

2026/8/23 10:57:26 阅读更多 →
Chat2DB 完整指南:一座「数据库控制塔」,快速连接 40+ 数据库,让 AI 帮你写 SQL

Chat2DB 完整指南:一座「数据库控制塔」,快速连接 40+ 数据库,让 AI 帮你写 SQL

Chat2DB 完整指南:一座「数据库控制塔」,快速连接 40 数据库,让 AI 帮你写 SQL 【免费下载链接】Chat2DB Chat2DB is a free, cross-platform, local-first database client and SQL workspace for developers, DBAs, analysts, and data tea…

2026/8/23 10:56:26 阅读更多 →

日新闻

[光学原理与应用-521]:对光的错误理解与纠偏

[光学原理与应用-521]:对光的错误理解与纠偏

首先光是一种能量的载体和形态,宏观上观察到的光是由无数个微观的光量子组成的,每个光子在产生的瞬间,其在真空的空间中以确定不变的速度沿着一个初始的方向一直向前,在微观层面,每个光量子的运动轨迹是以波函数所展现…

2026/8/23 0:00:50 阅读更多 →
SIP通话转接原理与REFER方法实战解析

SIP通话转接原理与REFER方法实战解析

1. 通话转接不是“挂断再拨号”,而是SIP会话的动态重定向你有没有遇到过这样的场景:客服坐席A正在和客户通电话,突然需要把这通对话无缝转给专家坐席B,客户完全感知不到中间的断连——既没听到忙音,也没被要求重新拨号…

2026/8/23 0:00:50 阅读更多 →
Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

1. 为什么选择Kolla-ansible来部署单节点OpenStack?如果你正在寻找一种能把OpenStack从“概念”快速变成“可用的实验环境”的方法,那么Kolla-ansible几乎是当前最主流、最省心的选择。我见过太多人卡在手动编译依赖、配置服务、处理版本冲突的泥潭里&am…

2026/8/23 0:00:50 阅读更多 →

周新闻

[光学原理与应用-521]:对光的错误理解与纠偏

[光学原理与应用-521]:对光的错误理解与纠偏

首先光是一种能量的载体和形态,宏观上观察到的光是由无数个微观的光量子组成的,每个光子在产生的瞬间,其在真空的空间中以确定不变的速度沿着一个初始的方向一直向前,在微观层面,每个光量子的运动轨迹是以波函数所展现…

2026/8/23 0:00:50 阅读更多 →
SIP通话转接原理与REFER方法实战解析

SIP通话转接原理与REFER方法实战解析

1. 通话转接不是“挂断再拨号”,而是SIP会话的动态重定向你有没有遇到过这样的场景:客服坐席A正在和客户通电话,突然需要把这通对话无缝转给专家坐席B,客户完全感知不到中间的断连——既没听到忙音,也没被要求重新拨号…

2026/8/23 0:00:50 阅读更多 →
Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

Kolla-ansible单节点OpenStack部署实战:从环境准备到排坑指南

1. 为什么选择Kolla-ansible来部署单节点OpenStack?如果你正在寻找一种能把OpenStack从“概念”快速变成“可用的实验环境”的方法,那么Kolla-ansible几乎是当前最主流、最省心的选择。我见过太多人卡在手动编译依赖、配置服务、处理版本冲突的泥潭里&am…

2026/8/23 0:00:50 阅读更多 →

月新闻

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南

免费解锁百度网盘SVIP加速:macOS用户必备的下载提速终极指南 【免费下载链接】BaiduNetdiskPlugin-macOS For macOS.百度网盘 破解SVIP、下载速度限制~ 项目地址: https://gitcode.com/gh_mirrors/ba/BaiduNetdiskPlugin-macOS 还在为百度网盘macOS版的龟速下…

2026/8/22 18:08:39 阅读更多 →
终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换

终极ncmdump指南:3分钟实现网易云NCM音乐解密与格式转换 【免费下载链接】ncmdump 项目地址: https://gitcode.com/gh_mirrors/ncmd/ncmdump 还在为网易云音乐下载的NCM格式文件无法在其他播放器播放而烦恼吗?ncmdump解密工具帮你轻松解决这个困…

2026/8/22 7:31:03 阅读更多 →
HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

HarmonyOS 应用开发《掌上英语》第81篇: 智能体卡片:为英语学习 App 打造桌面级学习助手

AgentCard 智能体卡片:为英语学习 App 打造桌面级学习助手适用平台:HarmonyOS 7.0 (API 26 Beta)一、引言 HarmonyOS 7.0(API 26 Beta)新增了 AgentCard 智能体卡片能力,这是继 HMAF(鸿蒙智能体框架&#x…

2026/8/22 3:22:48 阅读更多 →