首页 / 资讯中心 / 文章详情

STM32挂载FatFs文件系统:SD卡生成.csv的完整实现与避坑指南

STM32挂载FatFs文件系统:SD卡生成.csv的完整实现与避坑指南 ★ FEATURED ARTICLE
简介这份资源面向嵌入式开发学习者与STM32工程师聚焦在STM32F429平台上集成FatFS文件系统、驱动SD卡写入数据并生成.csv文件同时结合以太网驱动搭建TCP服务器接收网络数据。内容覆盖FatFS的移植与参数配置、SD卡SPI/SDIO通信初始化、f_open与f_write等API的文件读写流程以及基于lwIP的TCP连接建立、数据接收与落盘处理适合具备一定C语言与嵌入式基础、希望打通存储与网络通信链路的开发者参考。资源包共1482个文件以629个c源文件、382个h头文件为主另含144个d依赖文件、144个o目标文件及crf、lib、uvprojx等工程与库文件压缩包约59.43MB工程结构完整。目前已有8150人学习下载可帮助读者快速理解SD卡文件系统与TCP服务器协同工作的实现思路并对照源码排查移植与调试中的常见问题。1. 从一张“读不出数据”的SD卡说起STM32挂载FatFs并生成.csv的完整路径手里有一块STM32板子SD卡插上去能识别容量但用读卡器拔下来插电脑文件夹是空的或者只有一个乱码文件名——这种场景做数据采集的同行基本都遇到过。问题往往不在SD卡本身而在文件系统层没跑通。STM32实现SD卡的FatFs文件系统核心目标就是让单片机像PC一样对SD卡做格式化、创建目录、写文件最终把传感器或运行日志按行追加进一个.csv文件拔卡即用Excel打开。这套方案适合做离线数据记录、设备运行日志、简易黑匣子的嵌入式开发者尤其是那些不想上RTOS文件系统、又需要通用格式落盘的场景。FatFs是ChaN维护的轻量级FAT/exFAT文件系统模块纯C编写、与磁盘IO层解耦配合STM32的SDIO或SPI驱动就能跑起来。下面按“底层驱动→中间件移植→写.csv→排错→进阶”的顺序拆一遍每一步都给出可复现的配置和代码。2. SD卡底层驱动与FatFs移植从SDIO初始化到diskio对接2.1 先定接口SDIO还是SPI选型直接决定后面怎么写STM32接SD卡有两条路。SDIO走4位并行总线理论速率高适合高频写日志SPI只占4根线速率低但引脚好布线适合引脚紧张的小板子。常见做法是数据量大于100KB/s或者要连续写几小时优先SDIO只是偶尔写配置或几十字节日志SPI足够。选SDIO时注意STM32的SDIO时钟不能超过卡的上限初始化阶段必须降到400kHz以下识别完成后再切到高速。SPI模式下卡必须工作在SPI模式初始化时先发至少74个时钟让卡进入就绪再发CMD0复位到idleCMD8判断电压ACMD41轮询OCR直到busy位清零。这一步翻车最多的是上电时序卡座供电不稳或上电太慢CMD0会一直无响应示波器看CLK有波形但DAT0没回基本就是供电或上拉电阻问题。2.2 移植FatFsdiskio.c里四个函数必须自己填FatFs不直接碰硬件它通过diskio.c里的disk_initialize、disk_status、disk_read、disk_write、disk_ioctl与底层对接。STM32CubeMX生成的工程里如果勾了FatFs会生成模板但底层读写还是空的必须自己接。下面是我一般会写的SDIO版本disk_read/disk_write骨架基于HAL库/* diskio.c 中对接SDIO的读写实现基于HAL */ #include diskio.h #include sdio_driver.h /* 自己封装的SDIO初始化与读写 */ DSTATUS disk_initialize(BYTE pdrv) { if (pdrv ! 0) return STA_NOINIT; if (SDIO_Init() ! 0) /* 返回0表示初始化成功 */ return STA_NOINIT; return 0; /* 卡就绪 */ } DRESULT disk_read(BYTE pdrv, BYTE *buff, DWORD sector, UINT count) { if (pdrv ! 0 || count 0) 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 ! 0 || count 0) 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) { switch (cmd) { case CTRL_SYNC: /* 等待写完成SDIO写是阻塞的直接返回OK */ return RES_OK; case GET_SECTOR_COUNT: /* 返回卡容量扇区数f_mkfs要用 */ *(DWORD *)buff SDIO_GetSectorCount(); return RES_OK; case GET_SECTOR_SIZE: /* 扇区大小通常512 */ *(WORD *)buff 512; return RES_OK; case GET_BLOCK_SIZE: /* 擦除块大小单位扇区填1最保险 */ *(DWORD *)buff 1; return RES_OK; default: return RES_PARERR; } }逻辑说明disk_initialize负责把卡从断电状态带到传输状态返回STA_NOINIT时FatFs会拒绝挂载disk_read/disk_write的sector参数是逻辑扇区号不是字节地址count是扇区个数SDIO读写要按块操作不能跨块拆disk_ioctl里GET_SECTOR_COUNT和GET_BLOCK_SIZE在f_mkfs格式化时必用填错会导致格式化出来的FAT表异常。参数上GET_BLOCK_SIZE返回1是保守做法有些卡要求擦除块对齐返回0让FatFs自己判断也行但老卡容易出问题我一般固定填1。2.3 ffconf.h关键配置别用默认值直接跑FatFs的配置文件ffconf.h里几个宏直接决定能不能写.csv。_FS_READONLY必须设0否则f_write直接返回FR_WRITE_PROTECTED_FS_MINIMIZE设0保留f_lseek和f_truncate写日志要定位_USE_STRFUNC设1或2才能用f_puts/f_printf_FS_RPATH设0省空间_CODE_PAGE选936支持中文文件名但.csv内容里如果有中文编码要自己保证是GBK或UTF-8带BOM否则Excel打开乱码。_MAX_SS保持512_MIN_SS也是512除非卡是4K扇区。_FS_LOCK设0单任务写日志不需要锁。_VOLUMES设1只挂一个卡。这些改完再编译不然会出现“挂载成功但写不进去”的玄学现象。3. 在SD卡上生成.csvf_open、f_write与f_sync的落地写法3.1 挂载、创建、追加三步走通.csv写入写.csv不是简单f_write就完事要处理文件不存在时创建、存在时追加、写完刷新。下面这段是采集任务里常用的写一行CSV的函数/* 向SD卡追加一行CSV字段用逗号分隔行尾\r\n */ #include ff.h FRESULT csv_append(const char *filename, const char *line) { FIL fil; FRESULT fr; UINT bw; fr f_open(fil, filename, FA_OPEN_ALWAYS | FA_WRITE); if (fr ! FR_OK) return fr; /* 打开或创建失败 */ fr f_lseek(fil, f_size(fil)); /* 定位到文件末尾实现追加 */ if (fr ! FR_OK) { f_close(fil); return fr; } fr f_write(fil, line, strlen(line), bw); if (fr ! FR_OK || bw ! strlen(line)) { f_close(fil); return (fr FR_OK) ? FR_INT_ERR : fr; } fr f_sync(fil); /* 强制刷到卡防掉电丢数据 */ f_close(fil); return fr; }逻辑说明FA_OPEN_ALWAYS表示文件存在就打开、不存在就创建这是追加日志的标准打开方式f_lseek到f_size位置实现追加不能直接用FA_OPEN_APPEND因为部分版本对追加支持不完整f_write返回实际写入字节数bw必须和请求长度比对不等就是卡写失败f_sync把缓存刷到物理卡代价是每次写都触发一次写操作频繁调用会拖慢速度但掉电安全。参数上filename要带路径比如“0:/log/data.csv”0:是逻辑驱动器号对应_VOLUMES里的卷。line建议在调用前拼好包含逗号和\r\nExcel对\r\n识别最好。3.2 格式化与目录f_mkfs和f_mkdir什么时候用新卡或文件系统损坏时需要f_mkfs。注意f_mkfs会清空卡只在确认无数据时调用。常见做法是上电时先f_mount返回FR_NO_FILESYSTEM再提示格式化而不是每次上电都格。格式化参数用FM_FAT32或FM_ANY簇大小让FatFs自动算。目录用f_mkdir创建比如“0:/log”创建前先f_stat判断是否存在避免重复创建返回FR_EXIST。下面是一个上电初始化的顺序/* 上电初始化挂载失败则格式化再挂载确保log目录存在 */ FATFS fs; FRESULT fr f_mount(fs, 0:, 1); if (fr FR_NO_FILESYSTEM) { BYTE work[512]; /* 格式化工作区至少512字节 */ f_mkfs(0:, FM_FAT32, 0, work, sizeof(work)); fr f_mount(fs, 0:, 1); } if (fr FR_OK) { f_mkdir(0:/log); /* 已存在返回FR_EXIST忽略即可 */ }逻辑说明f_mount第三个参数1表示立即挂载0表示延迟挂载f_mkfs的work缓冲区必须提供大小至少等于扇区大小否则返回FR_NOT_ENOUGH_COREFM_FAT32在卡容量大于2GB时更稳小卡用FM_FAT。f_mkdir返回FR_EXIST不算错误直接继续。3.3 写.csv的性能与安全f_sync频率和缓冲策略每写一行就f_sync写1000行就是1000次物理写SD卡寿命和速度都受影响。我一般按两种策略数据量小、掉电要求高每行sync数据量大、允许丢最后几行每N行sync一次N取10到50。还可以用f_printf直接格式化但f_printf内部有缓冲不如自己拼字符串可控。另外写之前确认卡没写保护卡座WP引脚如果接地或悬空disk_status要返回STA_PROTECT否则f_write会失败。实测中SDIO 4位模式下写512字节约1到2msSPI模式约10到20ms按这个估算sync频率对实时性的影响。4. 避坑与排查SD卡FatFs写.csv最常见的五个翻车点4.1 挂载返回FR_NOT_READY或FR_DISK_ERR现象f_mount一直失败返回FR_NOT_READY或FR_DISK_ERR。原因SD卡上电时序不对或disk_initialize里没等卡稳定就返回。解决在disk_initialize里加100ms延时CMD0之前发至少74个时钟检查卡座供电用示波器看VDD是否在2.7到3.6V之间SPI模式下确认CS拉低后有时钟。4.2 文件创建成功但内容为空现象f_open返回FR_OKf_write也返回FR_OK拔卡看文件0字节。原因f_write后没f_sync或f_close数据还在FatFs缓存里。解决每次写完必须f_sync再f_close如果用了FA_OPEN_ALWAYS但没lseek到末尾可能覆盖了开头。检查f_size和lseek返回值。4.3 Excel打开.csv乱码或所有字段挤在一列现象文件能打开但中文乱码或逗号没被识别为分隔符。原因编码不是Excel默认的GBK或行尾只有\n没有\r\n。解决写中文时用GBK编码或者写UTF-8 BOM头行尾统一用\r\n字段里如果有逗号用双引号包起来。常见做法是先用英文和数字测试确认分隔正常再加中文。4.4 写一段时间后返回FR_DISK_ERR现象连续写几十分钟后f_write开始报错。原因SD卡内部垃圾回收或写平衡导致某次写超时disk_write没重试。解决在disk_write里加超时和重试比如HAL_SD_WriteBlocks返回错误后重新初始化卡再写一次降低sync频率减少物理写次数换工业级卡。4.5 拔卡后电脑提示需要格式化现象设备上写得好好的拔下来插电脑提示“需要格式化”。原因写过程中直接断电FAT表没更新完整。解决设备端加掉电检测检测到电压下降立即f_sync并f_close或者用f_sync定期刷减少丢失窗口。已经损坏的卡可以用f_mkfs重新格式化但数据没了。5. 进阶把.csv写卡和以太网TCP服务器串起来5.1 为什么要把SD卡和以太网放一起很多采集设备既要本地存.csv又要通过网络把数据推给上位机。STM32上FatFs和以太网驱动可以共存但要注意中断优先级和内存占用。常见做法是采集任务写SD卡网络任务用TCP服务器监听端口收到请求后读取最近的文件或实时转发。这样即使网络断了数据还在卡里网络恢复后可以补传。关键词里的“以太网驱动 tcp服务器”就是这条链路。5.2 一个简单的TCP服务器与SD卡协同框架下面是一个基于LwIP的TCP服务器回调收到命令后从SD卡读.csv并发送/* LwIP TCP接收回调收到GET则读取csv并发送 */ static err_t tcp_recv_cb(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err) { if (p NULL) { /* 客户端关闭 */ tcp_close(tpcb); return ERR_OK; } if (p-len 3 memcmp(p-payload, GET, 3) 0) { FIL fil; if (f_open(fil, 0:/log/data.csv, FA_READ) FR_OK) { char buf[512]; UINT br; while (f_read(fil, buf, sizeof(buf), br) FR_OK br 0) { tcp_write(tpcb, buf, br, TCP_WRITE_FLAG_COPY); tcp_output(tpcb); } f_close(fil); } } pbuf_free(p); return ERR_OK; }逻辑说明tcp_recv_cb在收到数据时触发判断命令后打开文件循环读取并tcp_write发送TCP_WRITE_FLAG_COPY让LwIP拷贝数据避免缓冲区被覆盖每次tcp_write后tcp_output立即发送。参数上buf大小根据可用内存调整512是保守值发送大文件时注意tcp_sndbuf空间不足要等待。SD卡读和网络发送在同一任务里会阻塞建议分任务或用DMA。5.3 验证与习惯验证方法先用小数据量测试写10行.csv拔卡用Excel打开确认格式再开TCP服务器用网络调试工具发GET看能否收到完整文件最后做掉电测试写的过程中断电重新上电看文件是否可读。从那以后我每次做SD卡项目都强制先跑一遍“格式化→写10行→拔卡验证→再上电追加”的闭环确认无误再上业务逻辑。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?
咨询建站