1. 工业控制器为什么需要三级存储
1.1 一类数据一种脾气
这个系列写到第十二篇,终于轮到说说“数据落地”这件事。工业控制器的存储方案,我自己在一套 STM32+FPGA 双核架构的控制器上的设计是三级分工:EEPROM 管参数,NOR Flash 管固件和关键配置,SD 卡管日志和历史数据。很多做硬件的朋友一开始都想得很简单——控制器嘛,上电读写、掉电不丢,不就行了吗?真做进去就会发现完全不是那么回事。
工业控制器里的数据至少分三类,它们的脾气完全不一样。一类是运行参数,比如 PID 系数、温度校准值、累计运行时间、通讯地址、报警阈值。这些数据的特点是:量很小,几百字节到头了,但是会经常改,而且必须经得起反复写。第二类是固件和工厂配置,bootloader、App 镜像、出厂序列号、网络参数。这些数据的特点是:容量中等,可能几百 KB 到几 MB,平时几乎不写,但要求断电后必须稳定存在,启动的时候读取要快,最好还能直接从上边执行代码。第三类是运行日志和历史数据,比如电机电流曲线、温度趋势、报警记录、事件序列。这些数据的体量是滚雪球式的,每天能攒出几十 MB,必须持续追加写入,而且最好能让现场工程师导出分析。
把这三类数据压到同一种介质里,就是灾难。用一片大容量 NOR Flash 全搞定?擦写寿命先不说,按扇区擦除这种粒度去频繁改一个 PID 参数,本来就是杀鸡用牛刀。用 SD 卡全搞定?文件系统一折腾,启动时间直接多出两秒,而且参数区每天被日志写覆盖,寿命损耗根本扛不住。所以分级存储不是炫技,而是顺着数据本身的需求去做匹配。
那为什么偏偏是 STM32+FPGA 这套组合?因为在这个控制器里,FPGA 负责高速采集和实时信号处理,比如编码器正交解码、高速 ADC 采样、多路串口报文解析,这些活儿如果全让 CPU 中断扛,响应时间根本没法保证。STM32 负责上层逻辑、通讯和存储调度。数据从 FPGA 出来,到 STM32 手里,最终分流到三种存储介质里。这个架构下,存储方案的设计核心就变成了三条路各走各的、互不拖累。
1.2 三级分工:参数、镜像、日志各得其位
先给出一个总览,方便后面每一节展开时你脑子里有个地图。
| 数据类别 | 典型内容 | 容量需求 | 更新频率 | 可靠性目标 | 首选介质 |
|---|---|---|---|---|---|
| 运行参数 | PID、校准系数、地址、计数值 | 几百字节 | 按天/按操作/按停机触发 | 掉电不丢、可频繁改写 | EEPROM |
| 固件与配置 | bootloader、App 镜像、工厂参数 | 数百 KB 到数 MB | 升级或出厂时写一次 | 随机读取快、长期稳定 | NOR Flash |
| 历史数据 | 温度/电流曲线、报警、事件记录 | 数十 MB 到数 GB | 秒级追加写入 | 循环覆盖、掉电尽量少损坏 | SD 卡 |
这个表格看着简单,但每条选择背后都是权衡出来的。EEPROM 按字节擦写,寿命动辄百万次,适合做频繁更新的小参数存储。NOR Flash 随机读快、可 XIP 直读执行,适合放固件镜像,但擦写寿命只有大概十万次量级,不能拿它当日志跑。SD 卡容量大、成本低、方便插拔,但掉电容易丢文件,需要靠软件层做保护。三者刚好把“频繁改的小数据”、“稳定存的大镜像”、“持续长的海量记录”瓜分干净。
1.3 掉电不丢只是底线,寿命和性能才是门槛
工业控制器和消费电子最大的区别,就是“掉电”这件事会非常密集地发生。现场有电动设备启停,有大功率接触器吸合,还有雷击浪涌,电源波动的频率远超你想象。所以存储方案首先要回答的不是“能不能存”,而是“在这种恶劣供电环境下,数据能不能活下来”。
另一个容易被低估的指标是写入寿命。EEPROM 标称百万次擦写,听着很多,但如果程序里有一个 bug 导致某个参数被每秒写一次,一百万次也就是十几天的事。NOR Flash 更夸张,扇区擦除寿命十万次,如果固件升级逻辑有缺陷,同一块反复擦除,产品没出厂就先报废了。SD 卡也一样,日志系统如果做不到循环覆盖和磨损均衡,一个区块会先被写穿。
所以我把这个方案的设计原则定成一句话:能用 EEPROM 的绝不用 NOR Flash,能用 NOR Flash 的绝不上 SD 卡,让每种介质只干自己最擅长的事。后面各节我就按这个原则,把选型、电路、代码和坑都拆开讲。
2. 存储选型与关键参数拆解
2.1 EEPROM:按字节折腾的小本本
EEPROM 在工业控制器里的地位,就像随手放在工具柜里那个经常掏出来用的笔记本。不用的时候嫌它占地方,真正要找参数的时候必须立刻翻到正确那页。选型上我见得最多的就是 AT24C 系列,比如 AT24C02、AT24C04、AT24C64,都是 I2C 接口,硬件设计非常成熟,驱动代码满天飞,但要用好它,下面几个细节必须抠清楚。
第一是器件地址的确定。AT24C 系列器件地址的高四位固定,低三位由硬件引脚 A0/A1/A2 决定,最后一位是读写位。我常用的是 4Kbit 到 64Kbit 的型号,地址引脚多,一个总线上可以挂多个器件,但如果你只挂一片,最好把 A0/A1/A2 全接地,避免以后扩容时改硬件。地址搞错,I2C 通信时 ACK 都等不到,这是新手最容易卡的第一个点。
第二是“页写”的使用。AT24C02 的页大小是 8 字节,AT24C64 是 32 字节。一次连续写多个字节,只要不超过页边界,内部写周期只有一次,5 毫秒左右就完成了;如果跨页写,控制器必须拆分,而且跨页那部分容易踩进页边界 bug。我自己的习惯是写参数结构体时,先算好长度,确保不会跨页,能一个结构体拼一个页写完,绝不拆两次。
第三是写周期的等待方式。很多例程写完之后HAL_Delay(5)就完事,这写法在出厂测试时看着没问题,可一旦系统这时候被中断打断,延时超时后接着去操作 EEPROM,内部可能还没写完,数据就丢了。正确做法是写完以后持续轮询 ACK,直到 EEPROM 内部写周期结束,它才会对地址再次作出响应。这段逻辑我后面放在代码示例里。
第四,写操作前关闭中断。EEPROM 的 I2C 写过程不能被打断,否则时序会乱。凡是写参数的操作,我都会加一个临界区保护,把中断优先级设到最低、关中断执行,写完了再恢复。代价是几微秒到几毫秒,对工业控制器的正常运行节奏来说完全没关系。
2.2 NOR Flash:能当内存用的镜像仓库
NOR Flash 在工业控制器里的地位更像是书架上的装订档案。它按扇区擦除,擦完以后整片是 0xFF,写只能是 1 变 0。这个“先擦后写”的机制,决定了每次更新数据都是一个重写流程。选型上最经典的还是 W25Q64/ W25Q128 这一族,SPI/QSPI 接口,工作模式 0,四线 QSPI 能把读速度做到很高。
W25Q 系列有几个固定指令码务必要熟悉:0x9F 读 JEDEC ID,0x06 写使能,0x02 页编程,0x20 扇区擦除(4KB),0xD8 块擦除(64KB)。这些指令我每次调试都会先跑一遍,确认芯片能正确响应,再去做上层逻辑。芯片的忙状态通过读状态寄存器 1 的 bit0 判断,BUSY=1 时必须等待。这个等待比 EEPROM 长得多,擦一个 4KB 扇区可能要几百毫秒,写的时候心里要有这个概念。
NOR Flash 最大的优势是随机读快,可以做 XIP,也就是把 Flash 映射到微控制器的地址空间里,CPU 直接在 Flash 上取指执行。但在 STM32 上做 XIP 一般要 MCU 本身支持外部存储器映射总线,比如 FMC/FSMC 接口或者带 QSPI 内存映射模式的型号。如果 MCU 不支持 XIP,那就老老实实把镜像拷到 SDRAM 或者内部 RAM 里运行,不要硬撑着追求“直接在 Flash 里跑”,反正中断响应和 Flash 操作已经足够复杂,不值得为了省一点拷贝时间增加时序风险。
另一个关键点是坏块管理。W25Q 系列在多次擦写后可能出现坏扇区,工业场景下必须要做坏块记录。最简单的办法是在每个扇区头写一个“有效标志”,上电扫描时发现擦写失败就标记坏块,把数据挪到备用区。这个逻辑虽然老套,但是可靠,比依赖厂商隐藏的坏块表踏实。
2.3 SD 卡:容量大,但没那么省心
SD 卡是这三类介质里“人性”最重的一个。它内部是 NAND Flash 加上一个控制器,FAT 文件系统又是在上位机体系里长出来的,用在嵌入式环境里从一开始就是门妥协的艺术。工业现场我建议直接上工业级 SD 卡,宽温、SLC 颗粒、带掉电保护固件,和普通消费卡价格差不少,但是现场丢数据的代价更大。
通信方式上,SPI 模式接线少、调试方便,但速度上限不算高,适合日志量不太大的场景。SDIO 模式速度快,能到几十 MB/s,但需要占用更多引脚,对布线和驱动要求也更高。我的习惯是:控制器日志量如果预估每天小于 200MB,SPI 模式足够;再往上,老老实实上 SDIO。
文件系统层面,嵌入式环境最常用的是 FatFS。这个库简单、稳定、资源占用可控,但有几个坑必须知道。第一,写文件后要主动f_sync(),否则数据可能还停留在文件系统的写入缓存里,掉电就丢。第二,频繁创建小文件会很快消耗目录项,而且容易造成碎片,日志这种连续追加型数据,尽量设计成大文件、按块写、按天切分。第三,异常掉电后 FAT 表会损坏,程序启动时要主动检查挂载状态,一旦发现故障要能自动重新格式化并重建日志文件,这也是实际产品能不能在现场活下来的重要分界线。
2.4 三种介质的寿命-容量-速度对照表
把这三兄弟放一起看,它们各自的边界会更直观。
| 参数 | EEPROM(AT24C64) | NOR Flash(W25Q128) | SD 卡(工业级) |
|---|---|---|---|
| 接口 | I2C | SPI / QSPI | SPI / SDIO |
| 最小操作单位 | 1 字节 | 4KB 扇区 | 1 扇区(512B) |
| 典型容量 | 64Kbit(8KB) | 128Mbit(16MB) | 8GB~32GB |
| 擦写寿命 | 约 100 万次 | 约 10 万次/扇区 | 约 1~10 万次/块 |
| 写一个扇区耗时 | 约 5ms(页) | 约 300ms~2s(擦+写) | 约 50ms~几百ms |
| 随机读性能 | 字节随机读,I2C 限制 | 高,支持 XIP | 慢,FAT 表查找拖累明显 |
| 掉电可靠性 | 中等,需字节级保证 | 较高,需防擦写中断 | 低,文件系统风险大 |
这张表的结论很明确:EEPROM 负责“小而频”,NOR Flash 负责“稳而快”,SD 卡负责“大而全”。后面整个存储架构的设计,本质上就是让数据按照自己的特性,准确掉到对应的介质里。
3. STM32 和 FPGA 各管哪一段
3.1 FPGA 负责“生产数据”,STM32 负责“归档数据”
在工业控制器里,FPGA 一般承担的是那些 CPU 干不了或者干起来太吃力的活。比如同步采集多路编码器信号、高速 ADC 连续采样、对串行总线报文做实时过滤和校验。这些任务的特点是:数据不断产生,速率高,实时性要求强,而且哪怕 CPU 中断再快,也扛不住每次都进中断去处理一帧高速数据流。
所以这套架构的职责划分逻辑就一句话:FPGA 负责把原始信号变成有结构的数据包,STM32 负责把数据包变成存储介质上真正落盘的内容。FPGA 侧处理完的每一帧数据,加上时间戳、通道号、序号,放进内部 FIFO 或双口 RAM,然后通知 STM32 来搬运。STM32 拿到数据后,先做协议解析,再决定这批数据是进 EEPROM、NOR Flash 还是 SD 卡。比如一个校准命令,校验通过后写 EEPROM;固件升级包的一小块数据,攒够一扇区后写 NOR Flash;温度曲线这种连续数据,攒够一批后写 SD 卡。
这种分工带来一个额外好处:存储操作再慢,也不会打断 FPGA 的实时采集。FPGA 只管往 FIFO 里塞数据,STM32 的存储任务按自己的节奏往后排。反过来,SD 卡写卡导致 STM32 卡顿几十毫秒,也不会影响 FPGA 侧已经完成的采集工作。两个芯各管各的,天然形成了解耦。
3.2 数据从 FPGA 到 STM32 的搬运通道怎么搭
搬运数据的方式有很多种,最简单的是 GPIO 中断通知 + 并行总线读取。FPGA 把 16 位数据总线挂到 STM32 的 FSMC 接口上,写满一个 FIFO 就拉高一个“数据就绪”信号,STM32 检测到后开 DMA,把整块 FIFO 数据搬进内存。这种方案的好处是带宽高、CPU 占用低,缺点是两边的时序必须严格对齐。
更常见的做法是 FPGA 提供一个串行接口,用 UART 或者 SPI 把打包好的数据发出来。SPI 可以做从机,STM32 做主机定时轮询读取。UART 适合低速率但逻辑简单的场景。不管用哪种接口,通信协议必须固定成帧格式,我的习惯是这样:
帧头(0xAA 0x55) | 长度(2字节) | 类型(1字节) | 数据(N字节) | CRC16(2字节)帧头的作用是同步,CRC16 的作用是校验。STM32 接收端维护一个环形缓冲区,逐字节做状态机解析,一旦收到完整帧就校验、提交给上层任务。帧头占两个字节能有效降低误同步概率,但是也不要太依赖帧头,因为总线上的随机干扰也可能伪造帧头,所以帧类型字段必须设计成天然带非法值,收到不认识的类型直接丢弃整个帧并重新同步。
如果让 FPGA 自己实现 UART 接收仿真,几个要点可以记一下:检测起始位下降沿以后,用 16 倍波特率时钟不断采样数据位的中点,每个位取多次采样的中间值,滤掉毛刺。发送侧则用波特率时钟逐位移位输出。这套逻辑在 testbench 里验证时,记得用带毛刺的输入信号做测试,只看理想波形是测不出问题的。
3.3 通信帧协议与校验设计
工业控制器里的通信帧,我坚持一个原则:任何一帧进入 STM32 的数据都默认是不可信的。所以不只是 FPGA 和 STM32 之间的链路要做校验,从存储介质读出来的数据也要做校验。EEPROM 里的参数结构体带 CRC,NOR Flash 里的镜像和配置带 CRC,SD 卡里的日志文件每条记录带 CRC,这一条贯穿整个存储方案。CRC 算法用什么多项式其实不重要,16 位 CRC 在工业控制器场景下已经够用,重要的是“读出来必须验,不能直接信”。
另外帧协议里最好带上序号。FPGA 每发一帧就把序号加一,STM32 收到后记录连续帧数。如果序号出现跳变,说明丢帧了,这时候日志文件里要打一条事件记录,方便事后分析是采集链路问题还是存储链路问题。我踩过一次很深的坑:FPGA 侧 FIFO 溢出时静默丢了几十帧数据,上位机曲线图出现一个看不懂的“跳崖”,排查了三天才用序号才发现是丢帧而不是传感器真的突变。
4. 分区规划、双备份与掉电保护实操
4.1 存储布局:像规划城市功能区分区
每次拿到一个存储介质,第一步不是写驱动,而是画分区图。分区设计的好坏直接决定后面代码维护的舒服程度。我的习惯是,把 NOR Flash 当成整个系统的“固定存储盘”,用地址把用途划分明确。
一个参考布局可以做成这样:
0x000000 - 0x07FFFF App 主镜像(512KB) 0x080000 - 0x0FFFFF App 备份镜像(512KB) 0x100000 - 0x1007FF 工厂配置区(2KB) 0x100800 - 0x100FFF 运行参数区 A(2KB) 0x101000 - 0x1017FF 运行参数区 B(2KB) 0x200000 - 0x1FFFFF 运行日志/临时存储区(剩余空间)主镜像和备份镜像分开,是为了给固件升级失败留一条后路。运行参数区 A/B 两份并列,是双备份的基础。工厂配置区单独划出来,防止运行参数写穿时把出厂数据冲掉。整个分区规划完,代码里每个区的起始地址和长度都是宏定义,不许任何裸地址出现在业务代码里,这是存储代码可维护性的最低要求。
EEPROM 的空间小,不需要像 NOR Flash 那样划分复杂区域,但也要定义结构体和校验。我的典型做法是:EEPROM 开头放一个版本号字段,每次结构体布局有变化就递增版本号;后面按固定偏移放参数结构体,末尾放 CRC32。读取时先校验版本号和 CRC,不匹配就用默认参数重新初始化。SD 卡的目录我也建议设计成固定结构,比如/sys/放配置备份,/log/放按日期命名的日志文件,/data/放历史曲线,避免日志文件散落在根目录里。
4.2 写 EEPROM 的完整流程:页写+ACK 等待+回读校验
EEPROM 写入函数看起来简单,但正确写法有一个标准模板。以 AT24C64 为例,我提供一个可直接参考的版本:
// 返回 0 表示成功,非 0 表示失败 uint8_t EEPROM_WriteBytes(uint16_t addr, uint8_t *buf, uint16_t len) { uint8_t tmp[64]; uint16_t offset = 0; uint16_t page_size = EEPROM_PAGE_SIZE; // 32 字节 uint16_t remain; HAL_StatusTypeDef status; while (offset < len) { // 计算当前页可写的最大字节数 remain = page_size - (addr + offset) % page_size; if (remain > len - offset) remain = len - offset; status = HAL_I2C_Mem_Write(&hi2c1, EEPROM_ADDR, addr + offset, I2C_MEMSIZE_16BIT, buf + offset, remain, 50); if (status != HAL_OK) return 1; // 等待内部写周期结束:轮询 ACK,而不是盲等固定延时 while (HAL_I2C_IsDeviceReady(&hi2c1, EEPROM_ADDR, 100, 50) != HAL_OK); offset += remain; } // 回读校验:读出来必须和写入内容完全一致 status = HAL_I2C_Mem_Read(&hi2c1, EEPROM_ADDR, addr, I2C_MEMSIZE_16BIT, tmp, len, 100); if (status != HAL_OK) return 2; if (memcmp(tmp, buf, len) != 0) return 3; return 0; }这段代码的连接细节我都踩过:第一,页写必须处理“地址正好落在页边界”的情况,否则会多写出一个页,后面的数据全乱。第二,等 ACK 而不是等延时,这一个改动让我现场参数丢失的概率直接降了一个数量级。第三,回读校验不写,等于把可靠性寄托在“芯片永远不会写错”上。
4.3 Flash 和 SD 卡的掉电保护设计
NOR Flash 和 SD 卡在掉电防护上的思路不太一样,但核心都是“把写操作做成可恢复的”。NOR Flash 侧,关键是避免擦写过程中掉电导致扇区数据半生不熟。我的做法是:写镜像前,先把新镜像整体搬到一个临时区域,全部写完并校验通过,再把目标扇区擦掉,临时区数据搬过去。虽然耗时更长,但任何时刻系统掉电,都存在一个完整的旧版本或新版本,不会出现“半个文件”状态。
SD 卡侧的掉电保护主要靠 FatFS 的f_sync和文件设计的原子性。写日志时,我的节奏是:每次往文件里追加一批数据后,不立刻f_close,但立刻f_sync,把缓存刷到卡上。正常情况下每次日志批次间隔几秒到几十秒,掉电时最多丢一个批次,不会整个文件破坏。还需要配合上电自检:开机挂载 SD 卡后检查日志文件头尾,如果发现文件长度异常或者 CRC 校验失败,就把当前文件加上.bad后缀改名,新建一个文件继续记录。这套逻辑虽然粗暴,但在无人值守的现场非常实用。
4.4 开机自检与数据完整性恢复
每次上电,存储系统都要做一次“体检”。顺序是固定的:先读 EEPROM 参数区,校验版本号和 CRC,失败则使用默认参数并打一条“参数恢复为默认”的日志;再读 NOR Flash 里的 App 镜像头,校验整个镜像 CRC,判断主镜像是否合法,不合法就切换到备份镜像启动;最后挂载 SD 卡,检查文件系统状态,并把“上电时间”“上次关机标志”写入日志。
“上次关机标志”是一个特别有价值的小设计。正常关机流程里,系统会先写一个“正常关机”标志到 EEPROM 或 NOR Flash。如果上电时发现上次不是正常关机,说明发生过掉电或者死机,日志里就多一条异常事件。很多诡异的现场问题,靠这个标志能从“找不到原因”变成“知道大概发生时间点”,再用时间点去对日志曲线,马上就有线索。
5. 常见问题与排查技巧实录
5.1 EEPROM 参数凭空丢
现象:产品出厂测试一切正常,到现场跑几天,部分设备上电后参数变成出厂默认值。原因排下来,最常见的不是芯片坏,而是写周期被干扰。I2C 是慢速总线,如果写 EEPROM 时 CPU 正处理高优先级中断,或者电源电压在临界值附近抖动,ACK 可能永远等不到,甚至写进错误地址。
排查和解决建议:先用逻辑分析仪抓 I2C 时序,确认器件地址、寄存器地址、数据有没有错位。然后用示波器看 VDD 波形,重点观察写操作瞬间有没有塌陷。软件侧把“写前关中断、写后轮询 ACK、回读校验”这三步全部补上,缺一步都不行。电源侧给 EEPROM 的 VDD 加一个 100nF + 10uF 的电容,再检查 I2C 上拉电阻,阻值取 4.7k 还是需要根据 I2C 速率计算,速率越高电阻要越小。
5.2 NOR Flash 写入失败和坏块判断
现象:固件升级时卡在擦除阶段,或者写完后拿起一片读回来全是 0xFF。原因可能是 SPI 时序不稳定、电源瞬态跌落、擦写次数到寿命边界,也可能是芯片型号不对导致指令里地址字节数配置错误。W25Q 系列在 16MB 以上容量时地址是 4 字节模式,代码里如果用 3 字节地址,写高地址区必然会出错。
排查思路:先读 JEDEC ID(9F 指令),确认芯片型号和容量,返回 0xEF 开头表示 Winbond 系列。再看写使能指令有没有发,写完一个扇区后读状态寄存器,确认 BUSY 位已经清零。最后把一个已知的测试数据模式写入整个扇区再读回来,定位坏块。坏块一旦确认,就把它加进坏块表,不再使用。这里有一个建议:不要把 0xFF 当成“没数据”,很多新扇区擦完就是 0xFF,读回来是 0xFF 不代表写入成功,要用回读比对来判断。
5.3 SD 卡文件损坏、速度变慢、卡“锁死”
现象:日志文件打不开,或者写入速度越来越慢,极端情况 FAT 表彻底损坏,重新插到电脑上提示格式化。嵌入式 SD 卡最容易出的问题就是异常掉电导致 FAT 表更新不完整。另一个不常见但更恶心的现象是“SD 卡内部寄存器锁死”,特别是某些山寨扩容卡或劣质工业卡,供电纹波大的时候内部控制器状态机卡死,任何命令都无响应。
应对手段:软件上坚持f_sync批次落盘,启动时主动检查文件系统状态,损坏后能自动重建日志文件;硬件上给 SD 卡供电加独立 LDO 和大电容,避免电机启停瞬间电压塌陷;如果遇到卡死,可以在复位时尝试 SD 卡自身的软件复位命令,但在我的经验里用处有限,劣质卡只能换卡。做工业产品时,SD 卡这种“可信任”的外设其实是最不省心的,卡的质量直接决定现场故障率,这个成本不能省。
5.4 FPGA 与 STM32 之间的数据错位
现象:STM32 解析出来的数据帧偶尔出现整帧偏移,CRC 校验失败的概率上升。原因基本上是 UART 接收端起始位判断不严,或者 SPI 收发时序没有对齐。做 FPGA 接收时,哪怕只是从 UART 的 RX 引脚到 STM32 之间多了一段长走线,信号边沿变缓,16 倍采样也可能在边界抖动时判定错误。
解决技巧是 FPGA 侧把位采样点放在每个位的中点附近,并且用多个周期做毛刺滤除;STM32 侧不要只信帧头,要加入帧长度上限,超过最大长度就丢帧重新同步。最重要的一步:FPGA 到 STM32 的链路必须自带序号和 CRC,任何一帧校验失败都不要尝试“修复”,直接丢弃并等待下一帧,让上层靠连续两帧序号判断是否丢帧。
5.5 一张排查方法速查表
| 故障现象 | 最可能原因 | 优先排查手段 |
|---|---|---|
| 参数变默认值 | EEPROM 写周期被中断 | 检查 I2C ACK 时序、写前关中断 |
| Flash 写后读全 0xFF | 擦除失败或地址边界错误 | 读 JEDEC ID、确认 3/4 字节地址 |
| 日志文件打不开 | FAT 表异常掉电损坏 | 启动时检查文件系统,损坏自动重建 |
| SD 卡写入越来越慢 | 碎片或劣质卡发热后降速 | 检查卡质量,日志按块大文件设计 |
| 数据帧错位 | UART 起始位误判或时序偏差 | FPGA 中值采样,STM32 加帧长度上限 |
| 系统上电偶尔死在初始化 | 上电时序不满足芯片要求 | 检查存储芯片 VDD 爬坡时间和复位时序 |
6. 下一步还能怎么改
这套三级存储方案目前在我项目里的状态已经稳定运行了不短时间,但每次画版本规划,我脑子里其实还有几个明确想改造的方向。一个是把 NOR Flash 的镜像区做成完整的双备份 OTA 升级体系,现在的备份镜像属于“能启动就行”,以后想做到“AB 区动态切换、差分升级、升级过程可回滚”,代码层面需要把升级状态机再拉细一层。
另一个改造方向是日志文件系统从 FatFS 换到更适配 Flash 的方案,比如 LittleFS,它在掉电恢复和磨损均衡上都比传统 FAT 文件系统更适合嵌入式场景,代价是上位机读数据时不再能直接插电脑看文件,需要额外做导出工具。这两个方向我个人其实还没有完全下决心,因为 FatFS 的“简单直接可读”对现场工程师太友好了。
最后再分享一个我反复验证过的体会:存储方案设计这件事,最忌讳“以后再说”。分区、校验、掉电流程这些机制,必须在硬件设计阶段就定下来。等固件写了几万行再回头补,改动成本不是翻倍,是翻几倍。如果你也在设计类似的工业控制器,建议先把 EEPROM、NOR Flash、SD 卡这三块的空间布局画出来,哪怕初期只实现最简版,也要让协议和校验框架从一开始就长对地方。后面每一版硬件的改动,都会轻松很多。