做工业控制器的朋友应该都有过这种经历:设备在现场跑得好好的,突然断电,再上电,参数丢了;或者客户反馈日志文件损坏,一查是 SD 卡写坏了;更头疼的是程序版本自己乱跳,配置被改得面目全非,最后却查不出是代码问题还是存储介质的问题。
STM32+FPGA 这种架构在工业控制器里太常见了,STM32 管通信和运算,FPGA 管时序和高速采集,但数据最终要落到存储介质上,绕不开三样东西:EEPROM、NOR Flash、SD 卡。这三样东西的电气特性、寿命、容量、读写方式完全不同,把它们组织成一套分级存储方案,让配置不丢、固件能升、日志能查、掉电不坏,是整个硬件设计的核心命题。
这篇文章就是来拆解这套方案的。我不会讲太多手册里有的东西,更多是项目调试中踩过的坑和最终沉淀下来的架构思路,适合正在做工业控制器、边缘网关,或者刚把手伸进 STM32+FPGA 联合调试的工程师参考。
1. 分级存储的由来:先把数据分好类,再谈怎么存
很多新手拿到"NAND 便宜容量大"的说法,就想着把什么数据都往一块大 Flash 里塞。这种思路在做消费级产品可能勉强够用,放到工业现场基本是给自己埋雷。
1.1 工业控制器的三类数据,生命周期完全不同
工业控制器里流转的数据,按生命周期可以分成三类,这是我做方案时的第一层划分依据。
第一类是配置参数类。包括设备地址、通信波特率、校准系数、增益值、用户设定的温度曲线、报警阈值等。这类数据的特征是字节量小,通常在几十字节到几 KB 之间;改写频率极低,可能一天改一次,也可能一年都不动;但要求极高可靠性,掉电不能丢,重复改写不能失效。
第二类是运行状态与日志类。包括实时采集的温度、压力、速度、故障事件、操作记录。特征是个头大,一天跑下来可能产生几十 MB 到几百 MB;持续写入,系统上电就在写;允许按时间翻查,掉电后要保留。
第三类是程序与固件类。包括 Bootloader、应用程序镜像、FPGA 配置文件。特征是大块数据,几百 KB 到几 MB;更新频率很低,只有在现场升级时才写入;要求原子性,升级到一半掉电不能变砖。
这三类数据,写入频率、容量要求、可靠性要求完全不是一个数量级的。用同一块存储介质处理,等于让一个仓库同时存档案文件、流水账本和大型设备,管理效率和安全性都无从谈起。
1.2 分级存储的核心理念:合适的数据进合适的介质
分级存储的核心判断依据有三个参数:写入频率、单次数据量、可靠性要求。
配置参数类,写入频率极低但可靠性要求极高,适合放进 EEPROM。EEPROM 可以按字节擦写,寿命通常标称 100 万次擦写,数据保持能力是温度相关的,工业级芯片在 85 度环境下也能保持几十年。关键是它的"按字节改写"特性,让修改单个配置项不需要整体擦除重写。
运行日志类,数据量大、写入频繁、要求掉电保留,适合放进 SD 卡。SD 卡容量以 GB 计,内置磨损均衡算法,配合文件系统可以做时间片轮转、循环覆盖,这是 EEPROM 和 NOR Flash 完全做不到的。
程序固件类,块擦写、可靠性高、可随机读取,适合放进 NOR Flash。NOR Flash 支持 XIP(Execute in Place),可以像内存一样直接在 Flash 里执行代码,缺点是写前必须擦除、擦除粒度大(通常是 4KB 扇区),寿命约 10 万次擦写。
1.3 STM32 和 FPGA 在存储分工上各管哪一段
STM32 和 FPGA 的组合里,我习惯把存储访问的"控制权"统一交给 STM32,FPGA 只做数据搬运和缓存,不做存储介质的主控。
原因很简单:STM32 上有现成的 I2C、SPI、SDIO 外设,有成熟的 FATFS 文件系统移植经验,调试工具链也完善。FPGA 虽然也能写 SPI 控制器、I2C 控制器,甚至能挂 eMMC 的 IP 核,但那是把简单问题复杂化——除非你有特殊需求,比如要用 FPGA 直接实时采样并落盘、绕开 STM32 参与,否则存储管理这件事交给 STM32 是性价比最高的。
FPGA 的职责收缩为:接收模拟前端或编码器的高速数据流,拼接时间戳、通道号、校验码,缓存进内部 RAM/双口 RAM,然后通知 STM32 来取。STM32 拿到打包好的数据块,再按类型分发到对应的存储介质。
2. 存储介质选型:EEPROM、NOR Flash、SD 卡只有分工不同
选型不该看哪个"先进"哪个"过时",而是看哪个最匹配你的数据特征。我把这三样介质从工程师视角重新拆一遍,你会发现在工业场景下它们各自有不可替代的位置。
2.1 EEPROM:配置参数的小仓库,按字节改写的底气
EEPROM 最常见的封装是 SOIC-8 或 TSSOP-8,典型型号 AT24C256、CAT24C256、M24M02,容量从 2Kbit 到 2Mbit 不等,接口基本都是 I2C。工业控制器里选型我通常用 256Kbit(32KB),存配置参数足够了,价格也就一两块钱。
EEPROM 最大的价值是按字节读写。NOR Flash 要改一个字节,得先把整个扇区擦了再写,而 EEPROM 可以直接对任意地址改写,不需要擦除动作。这意味着配置参数的每次修改,只需要一次写操作,没有"先擦后写"的中间状态,配合掉电检测,几乎不会出现参数写到一半被断电打乱的局面。
另一个被忽略的参数是写周期时间(Write Cycle Time)。I2C EEPROM 每写一页(Page,常见 8 字节或 32 字节),内部需要大约 5ms 的编程时间。很多新手不管这个,连续对同一页做两次写操作,第二次写直接失败或者写错,因为没有等待上一次编程完成。解决方法是每条写命令后加应答轮询(Acknowledge Polling):持续发起始条件加器件地址,直到从机返回 ACK,说明内部编程已完成,再进行下一条写。
注意 WP 引脚。EEPROM 的 WP(Write Protect)高电平有效,拉高后整个芯片只读。工业产品里我会把 WP 接 STM32 的 GPIO,平时拉低允许写,只有在固件升级或者参数批量下发时才拉高锁死,防止干扰脉冲把配置打乱。
2.2 NOR Flash:程序代码的常住地,也可做关键数据中转
NOR Flash 的代表型号是 Winbond 的 W25Q64/W25Q128、Macronix 的 MX25L 系列,SPI 接口,容量 8MB 到 64MB 很常见。工业控制器里,STM32 片内 Flash 空间不够时,程序镜像就放 NOR Flash,上电由 Bootloader 搬运到 STM32 RAM 或直接 XIP 执行。
NOR Flash 的写入必须遵循"先擦除,后编程"的规则。擦除最小单位是扇区(Sector),W25Q 系列一个扇区 4KB,擦除时间典型值 45ms 到 400ms;编程最小单位是页(Page),一页 256 字节,页编程时间典型值 0.4ms 到 3ms。这个时间参数直接决定了你升一次级的耗时,更决定了你做掉电保护时"必须覆盖多长时间窗口"。
SPI NOR Flash 还有一组容易被忽略的状态寄存器。SR1 里的 BUSY 位表示内部擦写是否完成,WEL 位表示写使能锁存。每次写操作前必须先发 Write Enable(0x06),写完再清掉。很多人单独测试读写正常,一到高低温就打不稳,多半是 WEL 时序不规范,或者中断打断了正在进行的页编程。
NOR Flash 的 WP 保护比 EEPROM 复杂一点,除了硬件 WP 引脚(低电平有效,注意跟 EEPROM 相反),还有块保护位(BP0-BP3),可以软件锁定指定地址范围的扇区。工业产品里,Bootloader 所在的低地址扇区必须设块保护,防止应用在异常时把引导区冲掉。
2.3 SD 卡:日志记录的主战场,容量与便携的平衡
SD 卡在工业控制器里是用来扛大流量的。SPI 模式兼容性好、接线简单,但带宽有限,实测在 STM32F4 上 SPI 模式读能到 10-15Mbps,写因为文件系统和擦写开销,稳定速度更低;SDIO 模式是 4 位并行,理论带宽能到 48MHz 时钟下的 24MB/s,实际稳定写能到 3-8MB/s,这个差距决定了一天能攒下多少日志。
工业级 SD 卡和消费级 SD 卡的价格差距可达十倍以上。工业卡用了 SLC 颗粒,磨损均衡和掉电保护电路更完善,写寿命长、温度范围宽(-40 到 85 度)。如果你的设备每天写几百 MB 日志,消费卡用不了几个月就会因为 ECC 失效开始丢数据。这个钱省不得。
SD 卡最怕的是边写边掉电。文件系统(FATFS)在写数据时,FAT 表和目录项是分开更新的,掉电点落在哪个环节,决定了丢一个簇、坏一个文件、还是整个文件系统崩溃。后文我会讲怎么从机制上规避。
2.4 参数速查表与选型建议
我把三种介质的关键差异整理成一张表,方便你在方案评审时直接对照:
| 介质 | 典型容量 | 接口 | 擦写方式 | 典型寿命 | 掉电保留 | 适合的数据 |
|---|---|---|---|---|---|---|
| EEPROM | 2Kbit-2Mbit | I2C | 按字节写、无需擦除 | 100 万次擦写 | 10-100 年 | 配置参数、校准系数 |
| NOR Flash | 1MB-64MB | SPI | 扇区擦除(4KB)+ 页编程(256B) | 10 万次擦写 | 20 年以上 | 固件镜像、FPGA 配置、关键日志 |
| SD 卡 | 512MB-32GB | SPI/SDIO | 内部 FTL 磨损均衡 | 取决于 P/E 周期 | 静态数据可靠 | 运行日志、历史曲线、升级包缓存 |
选型建议很直接:配置进 EEPROM,固件进 NOR Flash,日志进 SD 卡。有的方案为了省一颗芯片,用 NOR Flash 模拟 EEPROM 存参数——不是不行,但你要自己处理"读-改-擦-写"的原子性问题,还要保证 10 万次擦写寿命足够,复杂度远高于直接加一颗 EEPROM。EEPROM 也就几毛钱的事,别省。
3. 硬件架构与接口规划:STM32+FPGA 怎么把三块存储组织起来
介质选完了,接下来是电路和总线层面的组织。这个章节只讲架构相关的关键决策,具体时序配置留到下一节。
3.1 接口分配与总线带宽预算
STM32 这边,资源分配大概是这个格局:
- I2C1:挂 EEPROM,400kHz 快速模式。EEPROM 数据量小,400kHz 足够,别为了跑更快用 1MHz 高速模式,对 PCB 布线更敏感,工业现场干扰下更容易出错。
- SPI1:挂 NOR Flash,时钟 20-40MHz。这个 SPI 只服务 NOR Flash,不要跟其他设备共享,防止片选切换时残留数据搞坏 Flash 状态。
- SDIO 或 SPI2:挂 SD 卡。优先用 SDIO 四线模式,因为日志写入是大流量;如果引脚资源紧张或对速度不敏感,SPI2 也能用,但带宽会差一个数量级。
FPGA 这边,它需要跟 STM32 之间有一个高速数据通路。我会用 FSMC 或者 FMC 接口把 FPGA 映射成 STM32 的外部存储器,STM32 直接读写 FPGA 内部的双口 RAM,速度可以跑到 10MB/s 以上。采集数据从 FPGA 的采集逻辑进双口 RAM,STM32 用 DMA 搬运到内存,再落 SD 卡。这条链路的好处是 STM32 的 CPU 几乎不参与数据搬运,中断负载极小。
3.2 掉电检测与硬件写保护电路
工业产品掉电是常态,不是异常。存储方案的成败就看掉电那一瞬间系统能做什么。
我的标准做法是 STM32 的PVD(可编程电压检测)加外部掉电检测组合。PVD 阈值设置在 2.9V 左右,当 VDD 跌到阈值以下,PVD 中断触发,STM32 立即停止一切非关键任务,进入掉电处理流程:把当前配置参数写回 EEPROM、把日志 buffer 刷到 SD 卡、关闭 NOR Flash 写使能。这个过程需要时间,所以电源后端必须有一个"续命电容"——超级电容 0.1F-1F,或者电解电容加大容量的组合,确保 PVD 触发后还能维持 5-50ms 的稳定供电。
写保护电路上,EEPROM 的 WP 和 NOR Flash 的 WP 都必须接可控引脚,不要直接接死。正常运行时 WP 置为允许写,PVD 掉电中断触发后,第一个动作不是去写数据,而是先把 WP 置为禁止写。这样做的好处是:哪怕后续电源跌落导致 MCU 跑飞,也无法对存储介质产生写操作,数据不会因为总线上的毛刺被改写。
3.3 地址空间与数据通路设计
逻辑上,把存储资源做一个统一抽象:
- 配置参数区:EEPROM 从 0x0000 到 0x7FFF,按固定偏移区分参数版本、设备信息、用户配置、校准数据。每个逻辑段头部放 CRC 和状态字。
- 固件区:NOR Flash 的低 1MB 放 Bootloader 和 FPGA 配置镜像,高地址区放应用镜像 A/B。做 A/B 双备份,升级时写非活动区,完成后切标志位。
- 日志区:SD 卡用 FAT32,分区里建 LOG 目录,按天生成文件,单文件超过 50MB 自动滚动到下一个文件,保留最近 30 天。
这三块存储的物理地址空间完全独立,STM32 侧的驱动层按设备抽象。上层业务代码只调param_set()、log_write()这类接口,底层是 EEPROM 还是 NOR Flash,业务层完全不关心。这是架构层面最重要的分层,否则后期加一块存储就要改所有业务逻辑,会非常痛苦。
4. 实操细节:EEPROM 和 NOR Flash 读写时的关键几步
选型是纸面工作,真正决定产品可靠性的,是底层驱动的每一个时序和边界条件。
4.1 EEPROM I2C 读写:页写入、应答轮询与磨损管理
EEPROM 的驱动有三个关键点。
第一,页写入边界。AT24C256 的页大小是 64 字节,但 I2C 单次写操作一次最多写一页。这一页的物理地址必须对齐,如果你从地址 0x3F 开始写 10 个字节,第 1 个字节落在页 0 的末尾,剩下的 9 个字节会回卷写到页 0 的开头,而不是页 1。这是无数"EEPROM 数据错乱" bug 的来源。驱动里必须做分页处理:计算当前页剩余空间,逐页切分写入。
第二,应答轮询代替固定延时。写周期时间 tWR 典型值 5ms,最高到 10ms。如果用delay_ms(10)等,效率太低;用应答轮询,时序更紧凑且自适应。代码写法是:写入后发一个起始条件 + 器件地址 + 写位,收到 ACK 说明内部编程完成,继续下一条;NO ACK 就循环等待。
第三,磨损均衡。EEPROM 虽然寿命 100 万次,但如果每次都写同一个地址(比如计数器的当前值),这个地址很快耗尽,而整个芯片其他地址还是全新的。工业产品的计数器、运行时间累计这类高频参数,我建议用一种简单的轮转存储:把同一参数散落到 N 个地址,每次写下一个地址,读时扫描最近有效的那个,N 取 64 或 128,等于把寿命乘以 N。
4.2 NOR Flash SPI 操作:扇区擦除、写使能与状态轮询
NOR Flash 的驱动细节主要集中在状态处理上。
先强调最基本的顺序:写使能(0x06)→ 页编程(0x02)→ 等待 BUSY 清除 → 读状态确认。三条指令缺一不可,顺序错了或者中间被高优先级中断打断,极大概率写进去的是乱码。
扇区擦除是另一个耗时大户。擦除前务必做好两件事:一是确认擦除地址对齐到 4KB 边界,二是确认 WEL 位已经置 1。擦除过程中要轮询状态寄存器的 BUSY 位,不要用固定延时,因为擦除时间随温度和芯片批次变化很大,固定延时要么过长拖慢升级,要么过短直接擦除失败。
NOR Flash 还有一个"编程前检查"的细节:在写一页 256 字节之前,读取目标区的当前内容,确认全是 0xFF 或者确认旧数据已擦除。如果目标区有旧数据,页编程并不会覆盖,Flash 位只能从 1 变 0,0 不能变回 1,所以旧数据所在位直接写不进去。这个问题在调试的时候非常隐蔽:代码逻辑没问题,就是写一次成功,第二次再写同样地址直接失败,原因就是没擦除。
4.3 数据完整性与掉电恢复策略
存储驱动的更高一级要求是:任何时刻掉电,重启后系统都知道"上次写到哪一步了,这步数据是否完整"。
我的做法是"双区交替 + CRC + 状态标志"三层结构。以配置参数为例:
- 把参数区划分为 A/B 两个区域,大小相同。区头放格式版本号、数据长度、CRC32、完整标志。
- 写参数时,先写备用区,数据写完后再更新完整标志。标志有两种状态:
COMMITTED(已提交)和PENDING(未完成)。 - 上电读参数时,先检查 A/B 两区的完整标志,读 CRC 有效的那个。如果 A 区完整,B 区不完整,说明上次在写 B 中间掉电,直接用 A 区,下次写时先覆盖 B。
这个策略的核心是把"写数据"和"写完整标志"拆成两个不可分割的动作,而完整标志本身只占一个字节,单个字节的编程在掉电窗口里失败概率极低,加上 WP 保护,基本能做到 100% 掉电安全。
5. FPGA 在存储环节中扮演的加速与缓冲角色
存储虽然主控在 STM32,但 FPGA 不是旁观者。它在数据流入存储之前做了大量关键工作。
5.1 数据先入 FPGA RAM,再统一搬运的原因
工业采集系统的数据是源源不断的。如果 STM32 直接接管 AD 采样数据再写 SD 卡,会有两个问题:一是 STM32 的 CPU 会被中断和大块数据搬运占据太多时间,影响通信和实时控制;二是采样数据到达的节奏和 SD 卡写的节奏完全不同步,直接耦合会导致丢数据。
FPGA 做的事就是加一个缓冲水池。它把高速采集的数据按固定长度打成一个一个帧,存进内部的双口 RAM 或 FIFO;当积累了足够的数据块(比如 4KB),通过中断通知 STM32。STM32 用 DMA 一次性搬走,再落 SD 卡。这个异步过程消除了双方的速度失配,数据不会丢。
5.2 多通道采集的打包、定帧与时钟域处理
FPGA 端的数据帧格式要在方案阶段就定死。我常用的帧结构是:
- 帧头 2 字节(固定值,如 0xAA55,用于同步)
- 时间戳 4 字节(百微秒或毫秒计数,由 FPGA 内部时基产生,或从外部 GPS/PPS 同步)
- 通道号 1 字节
- 数据长度 2 字节
- 数据负载 N 字节(对齐到 4 字节边界)
- CRC32 4 字节
定帧的好处是 STM32 解析简单,掉电恢复时定位坏块容易。时钟域处理上,ADC 采样时钟通常是独立时钟域,而双口 RAM 的写入端、读取端各用一个时钟,全部走异步 FIFO,避免跨时钟域采样造成的数据错位。
5.3 与 STM32 的握手协议和状态机设计
FPGA 和 STM32 之间的交互不要做太复杂,我的经验是一个简单的寄存器组加两个中断就够了:
- 状态寄存器:FPGA 报告 FIFO 空/半满/满、当前帧序号
- 控制寄存器:STM32 控制 FPGA 启动采集、复位 FIFO、清中断
- 数据寄存器:STM32 按地址读取双口 RAM
- 中断信号:数据块准备好中断、FIFO 溢出中断
状态机方面,STM32 侧是标准"查询空闲 → 启动 DMA 搬运 → 等待完成 → 解析帧 → 落盘"循环;FPGA 侧是"半满置中断 → 等待 STM32 读取 → 释放空间 → 继续写入"。注意 FIFO 的半满阈值设置不要超过 STM32 DMA 单次搬运能力的 80%,否则高优先级任务抢占了搬运用时会溢。
6. SD 卡存储与文件系统的长周期验证
SD 卡是整套存储方案里最容易出问题的环节,因为文件系统把问题抽象得太高,出了问题反而不容易定位。这一节全是长期运行验证出来的实战细节。
6.1 FATFS 移植要点与格式化陷阱
FATFS 是小型嵌入式系统的事实标准,移植本身不难,但要配置好三个参数:
_FS_TINY:选 0 或 1 取决于 RAM 是否紧张,但日志写入场景我建议_FS_TINY=0,缓存大一点,批量写效率高很多。_MAX_SS:如果 SD 卡容量超过 2GB,FAT32 要求扇区大小 512 字节,_MAX_SS设置成 4096 兼容大扇区卡。但要注意:如果 SD 卡实际扇区是 512,而_MAX_SS=4096,FATFS 会在初始化时按 4096 去读,可能读不到正确的引导扇区,导致挂载失败。稳妥做法是_MAX_SS=512,扇区大小只设 512。_FS_LOCK:日志系统会同时管理目录和多个文件,把这个值设成文件句柄数的两倍,避免资源不足。
格式化陷阱:SPI 模式下的 SD 卡,如果直接用 STM32 程序初始化,遇到"这张卡在电脑上好好的,插到设备上挂载失败",大概率是卡的分区格式不对。SD 卡用专用工具格式化成 FAT32、簇大小 32KB、分区起始对齐到 1MB,不要用 Windows 默认的快速格式化(它有时候会写一个只被 Windows 认识的隐藏分区结构)。
6.2 掉电时丢文件问题的排查
掉电丢文件,行业里叫"FAT 表撕裂"。FATFS 在追加写数据时,先更新数据区,再更新 FAT 表,最后更新目录项。掉电点如果刚好落在"数据区已写、FAT 表还没更新"的窗口,重启后这个文件的簇链就断了,表现为文件存在但实际内容是空或者乱码。
解决思路有两个层面。应用层:日志文件不要攒到最后统一 flush,定期(比如每写完 4KB)调用f_sync(),把 FATFS 内部缓存强制落盘;另一个实用技巧是日志系统只操作固定两个文件名交替写(LOG1.LOG / LOG2.LOG),每次打开文件时先检查文件长度,异常长度就切换另一个文件,损失最多一个文件的数据。
驱动层:SD 卡写入用多块写命令(CMD25),比单块写(CMD24)效率高,但掉电风险窗口更长。权衡下来,我建议日志写入用单块写+外部缓存,写入频率做限速。测试下来,单块写模式下掉电损坏概率显著低于多块写,而且速度也足够工业日志使用。
6.3 写卡性能瓶颈与缓存策略
SD 卡标称速度的坑在于,那是顺序写大块数据的最优工况。日志系统如果每次只写几十字节,实际吞吐会跌到标称值的十分之一以下。原因是 SD 卡内部按页组织写入,每次小写都会触发读-改-写周期。
解决方式是分层缓存。STM32 内存开一个 8-16KB 的日志缓冲池,日志数据先填充缓冲池,到 4KB 对齐时调用一次写操作,用多块或单块连写。FPGA 端也做了同步配合,采集数据累积到一整个日志帧(比如 1KB 的整数倍)再交给 STM32,避免碎片化小写。实测下来,这个策略能稳定达到 SD 卡标称写速度的 60%-80%。
7. 常见问题排查与避坑记录
这个章节不是理论,是我在这些年调试工业控制器存储问题时,反复踩过的坑的总结。
7.1 问题速查表
| 现象 | 可能原因 | 排查方向 |
|---|---|---|
| EEPROM 写入后读回部分数据错误 | 页写跨页回卷 | 检查写入地址是否跨页,分页写入 |
| EEPROM 偶发写不进 | 漏掉应答轮询,连续写 | 分析 I2C 波形,确认每页写间隔 |
| NOR Flash 写入失败,但读正常 | 未先擦除,或 WEL 未置位 | 读状态寄存器,确认擦除动作 |
| NOR Flash 高低温下程序起不来 | 扇区擦除时序不满足,固定延时太短 | 换用 BUSY 位轮询,别用固定延时 |
| SD 卡上电挂载失败 | 分区格式不对或 SPI 模式初始化失败 | 换读卡器格式化,或逻辑分析仪抓 CMD 响应 |
| 掉电后配置参数随机丢 | WP 未接管,或参数区未做双区备份 | 检查掉电时序,确认 WP 置位顺序 |
| 掉电后日志文件损坏 | FAT 撕裂,掉电时未 flush | 增加 f_sync 频率,双文件轮转 |
| FPGA 缓存数据错位 | 跨时钟域未处理 | 检查异步 FIFO 是否规范,帧头是否同步 |
7.2 我踩过的几个坑
第一个坑是 EEPROM 页写回卷。当年做校准系统,工程师写了一个通用的 "写入任意长度" 函数,逻辑看着没问题,数据库遍历测试也好,但现场客户反馈校准参数偶尔错乱,重新校准一次又好了。查到最后,是函数没处理页边界,7100 系列芯片页大小 8 字节,写 10 字节就有大概率回卷。后来所有 EEPROM 驱动都强制做分页切分,不再允许裸写。
第二个坑是 NOR Flash 的擦除延时。当时用固定延时 100ms 等擦除完成,常温没问题,低温试验 -40 度时,擦除时间翻了倍,程序直接擦到一半就返回来继续写页编程,结果固件上电起不来。改成 BUSY 位轮询后,高低温一律通过。从此我给自己立了一条规矩:凡是有内部编程状态的芯片,一律用状态轮询,不用固定延时。
第三个坑是 SD 卡连续写死机。设备连跑 72 小时,SD 卡写入突然卡住,表现出来是f_write返回 OK 但等待时间无限长。查下来是 FATFS 的f_sync在高频调用时和 DMA 搬运冲突,一个在写 FAT 表,一个在覆盖 buffer,二者没有互斥。加一个信号量保护文件系统操作后,问题彻底消失。多任务下文件系统不加锁,迟早出事。
7.3 调试工具与排查方法
存储相关的疑难问题,光用调试器看变量是不够的,我常用的工具组合是逻辑分析仪 + 示波器。
逻辑分析仪抓 I2C 和 SPI 时序最有效率,尤其适合确认 ACK/NACK 时序是否符合预期。20MHz 采样率就能覆盖常见存储接口。遇到 EEPROM 写失败,直接抓 I2C 总线上有没有应答轮询和等待时间;遇到 NOR Flash 擦除失败,抓 SPI 的读状态寄存器命令是否正常发出。
示波器则用来测掉电相关的问题。PVD 中断触发电平设置、掉电后供电曲线是否满足 5ms 以上的稳定窗口、WP 置位的时序和电源跌落是否在允许窗口内,这些都需要示波器来量。掉电保护做得好不好,最终就是看这两条曲线:系统供电下降沿,和 WP 引脚的动作沿,两者时间差一定要留够余量。
7.4 给方案定型前的最后检查清单
送样前我会对照着检查一遍,这几项都过了,存储部分的可靠性才有底气:
- EEPROM 驱动做了页写切分,且写周期用应答轮询
- NOR Flash 擦写全部走状态轮询,Bootloader 区块保护已使能
- 配置参数区双区交替 + CRC,掉电标志完整
- SD 卡日志在每 4KB 数据后强制同步,文件采用双文件轮转
- 掉电检测触发后,WP 引脚动作早于数据写入
- SD 卡格式化规范,簇大小 32KB,分区起始对齐
- FPGA 到 STM32 的帧格式有 CRC 校验,FIFO 溢出有告警
- 文件系统操作加互斥锁,DMA 和 CPU 访问不冲突
这套分级存储方案在我经手的几个工业控制器项目里运行下来,EEPROM 部分几乎没有返修,SD 卡日志在最极端情况下也只出现过一次坏文件,靠双文件轮转自动恢复了。做工业硬件,存储方案的架构价值要在现场跑几个月后才能体现出来——前期多花点心思在这上面,比后期去现场救火要划算得多。