• 正文
  • 相关推荐
申请入驻 产业图谱

STM32N6 + FileX做SD卡读写,这几个坑我一个没躲过去

7小时前
180
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

这段时间我在 STM32N6 上做一个带SD卡读写的项目,最开始想得还挺简单,CubeMX 配一下 SDMMC,文件系统挂上 FileX,能读目录,能写文件,不就差不多了吗。

结果真跑起来才发现,SD 卡这个东西,特别像那种你以为已经很成熟了,实际上总能在最不讲道理的地方给你一拳的模块。

尤其是放到 STM32N6 这种新一点的平台上,再叠上 FileX、Cache、块设备读写、JPEG 文件扫描这些真实业务,问题就不是HAL_SD_Init() 成功不成功那么简单了。

我这次就不讲那种教科书式的 SD 卡原理了,没啥意思。直接聊我在 STM32N6 + SDMMC2 + FileX 这套组合里,遇到的几个典型问题,还有最后是怎么把它们一个个按住的。

先说第一个坑,也是最烦人的那个。

1、STM32CubeMX

CubeMX中开启SDMMC2外设用于连接SD卡,同时挂载FILEX文件系统方便使用SD卡。CubeMX的配置没有太多好说的,我们主要来说一下在STM32N6使用中碰到的很多问题。

1、使用的不稳定性

这种问题最恶心。

因为如果它一上电就失败,反而好查。你去看时钟、引脚、上拉、电源、卡槽检测,排就是了。

但我遇到的不是这个路数。

系统起来以后,FileX 能挂载,目录也能扫出来,文件也不是完全不能读写。可一旦连续操作,尤其是多块读写,偶发就会出问题。有时候是读失败,有时候是写失败,有时候表面上 HAL_SD_ReadBlocks() 返回了HAL_OK,结果卡的状态一直回不到 TRANSFER,光从日志调试完全判断不了问题。

后面我才慢慢意识到,SD 卡驱动不能只看 API 返回值,还得盯住卡状态有没有真正回到可继续传输的状态。

所以我后来专门补了一个等待函数,本质上就是不停查HAL_SD_GetCardState(),确认它真的回到 HAL_SD_CARD_TRANSFER,否则哪怕前一个调用看起来没报错,后面的操作也很容易跟着一起乱掉。

这件事听着很小,但很关键。

因为很多时候,不是你这次调用失败了,而是上一次调用其实没收干净。

2、OK不等于结束

这个坑我一开始也有点大意。

我们平时写驱动,容易有一种很顺手的思路,函数返回 HAL_OK 了,那这一步就过去了,接着走后面的逻辑。

但 SD 不是这么听话的。

尤其在块设备读写里,你看到 HAL_SD_ReadBlocks() 或 HAL_SD_WriteBlocks() 返回成功,不代表卡已经完全回到稳定状态。后面如果马上接下一次访问,特别是文件系统这种上层会连续触发多个块操作的场景,就很容易踩到边界时序。

所以我后面做的第一件事,不是去疯狂改上层逻辑,而是在底层胶水层里把这件事收紧。

每次 read、write 之后,都再等一轮 TRANSFER 状态。等到了,算这次事务真正结束。等不到,就别硬撑,直接走错误处理。

这一步加上去以后,系统的稳定性会立刻好很多。

但还没完。

3、总线卡死

这个我当时真被搞得有点烦。

现象大概是这样,某次读写之后,卡状态长时间不恢复。你继续重试当前操作,没用。你再多调几次 ReadBlocks 或 WriteBlocks,还是没用。

它不是这次超时了,它是整个 SD 总线状态已经不太对了。这种时候,如果还抱着再试一次就好的心态,基本就是在浪费时间。

我后来比较务实,直接在底层加了一个恢复动作,思路很粗暴,也很有效,就是:

HAL_SD_DeInit()
重新 MX_SDMMC2_SD_Init()再重新等卡回到 TRANSFER说白了,就是把这条总线重新拉起来。这个办法不优雅,坦率地讲,甚至有点像修家电时拍两下机箱。但你真做嵌入式,尤其做这种外设堆起来的系统,很多时候你要的不是论文级优雅,而是现场真能活。

而且这招不是我乱拍脑袋加的。

因为从现象上看,问题已经不是单次命令失败,而是控制器和卡之间的会话状态出了岔子。这种时候继续在原状态里硬试,成功率很低。反而是明确做一次 DeInit + ReInit,让状态机回到已知起点,系统更稳。

我自己现在看,这算是这次 SD 驱动里最值钱的一处补丁。

再往下,还有一个特别容易被忽略,但一旦中招就会怀疑人生的问题。

4、Cache 维护

这个坑真的是经典老坑了。

表面看,代码没问题,地址也对,块号也对,HAL 返回值也没炸,结果写完以后数据就是不对。你再去读回来,发现文件内容怪怪的,甚至目录项都有概率异常。

这种时候特别容易开始怀疑文件系统,怀疑扇区对齐,怀疑卡质量,怀疑人生。其实很多时候,问题就在 Cache。

我这边最后在写路径上专门加了 DCache Clean,而且不是随便 clean 一下,是按 32 字节对齐把地址范围展开以后再 clean。

这个细节很重要。

因为 Cache 维护如果地址和长度没对齐,效果可能并不完整。你以为自己 clean 了,实际上脏数据还留了一部分。SDMMC 再通过总线把旧内容搬出去,最后文件当然乱。

这事在 STM32N6 上尤其值得小心,因为大家一看到 N6,脑子里全是算力、NPU、图像,反而容易忘了,这种平台一旦把性能开起来,Cache 行为就已经不是能随便糊弄过去的东西了。

说白了,你只要让 CPU 和外设同时碰一块缓存区,就必须认真对待一致性。

不然最后调半天,根本不是 SD 卡不行,是你自己写出去的根本不是你以为的那份数据。

顺着这个话题,再说一个我觉得很真实的问题。

5、文件系统问题

这个也挺有意思。

因为我们项目里上层不是只做一个裸块读写测试,而是真的挂了 FileX,然后去扫文件、找 JPEG、做文件存在性判断。

这时候很多人会自然地把问题切成两层,底层是 SDMMC,上层是 FileX。理论上没错。

但真查问题的时候,你会发现它俩缠得挺紧。

比如上层做一个目录扫描,看起来只是扫目录。可底下其实会触发一串块读。只要这串读里有一次状态没收干净,或者总线进入忙状态没恢复好,最后暴露出来的症状就会像是目录遍历偶发失败、JPEG 列表不完整、文件属性读取异常。

你如果只盯着 FileX 接口,很容易被带偏。

我后来给自己的经验就是,文件系统报出来的问题,先别急着把锅扣给文件系统。

先看底层是不是每次块读写都真的闭环了。

有没有超时。
有没有状态没回来。
有没有总线恢复。
有没有 Cache 处理。
有没有对齐。

这些东西一旦没收住,上层再正常也会被拖下水。

还有一个看起来不高级,但特别有用的办法。

6、日志一定要打到能定位那次事务为止

我以前也会偷懒,报错就打一句 SD read error,差不多得了。

后来发现这玩意儿真没法用。

因为你面对的不是读失败这四个字,而是一次具体的事务失败。你至少得知道,HAL 返回了什么,HAL_SD_GetError() 是多少,当前卡状态是多少,失败时访问的是哪个 block,一共读写了多少 block。

这些信息一旦补齐,排查效率会高很多。

比如我后面就是按这种思路打的日志,类似读失败、写失败、忙超时,都会把 err、state、blk、cnt 一起带出来。别看土,这些信息在现场真的是救命。

因为嵌入式很多问题不是这个模块错了,而是某个时刻、某组参数、某次时序组合出了错。你没有事务级上下文,基本就是闭眼调参。

说到这,其实可以聊聊我这次最大的一个感受。

很多朋友做 SD 卡驱动,最开始都容易把目标定成,能挂载、能创建文件、能写个 Hello.txt,这就算通了。

坦率地讲,这只算过了入门线。

真到项目里,尤其是 STM32N6 这种芯片上,SD 卡能不能用,从来不是看你第一次 mount 成没成功,而是看它在连续读写、文件扫描、异常恢复、长时间运行这些场景里会不会慢慢变得不正常。

前者叫例程跑通。

后者才叫驱动能用。

这两者差得还挺远的。

而且很有意思的是,真正把这件事做好以后,你会发现修的并不是什么惊天大 bug。更多时候,修的是一堆差一点。

差一点没等状态回来。
差一点没把 Cache 处理干净。
差一点没把错误日志打全。
差一点没在总线卡死时果断重初始化。

就是这些差一点,最后决定了这个 SD 卡驱动是演示级,还是项目级。

相关推荐