ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

工业控制器三级存储架构:EEPROM、NOR Flash与SD卡分工实战

工业控制器三级存储架构:EEPROM、NOR Flash与SD卡分工实战 1. 为什么工业控制器要把存储拆成三级EEPROM、NOR Flash、SD卡的基本分工1.1 现场设备的真实困境参数、日志、历史数据不能挤在一个介质里工业控制器里的数据存储我一直觉得是被低估的环节。CPU选型时大家会花大量时间比主频、比外设但到了数据存储很多方案就是反正弄个Flash存一下结果设备在客户现场跑几个月参数掉失、日志损坏、SD卡数据读取不到的情况全冒出来。这篇继续我们STM32FPGA硬件篇的系列聊聊我在工业控制器上怎么把存储拆成EEPROM、NOR Flash、SD卡三级各管一摊。先说一个很容易被绕进去的问题一块容量大的Flash不是能把所有东西都装下吗参数存到SD卡里不也行吗理论上能工程上不行。因为工业控制器里的数据按生命周期和访问模式大致分三类。第一类是运行参数和配置信息比如设备编号、IP地址、校准系数、PID参数这些数据量很小几十字节到几KB但更新频率可能很低可能一天改几次也可能一年才改一次可一旦丢失整个设备直接瘫掉。第二类是运行日志和固件备份这类数据有几百KB到几十MB持续追加设备生命周期内会被反复擦写。第三类是过程数据和批量记录比如传感器采样历史、配方、统计报表这类数据量最大常以几百MB甚至GB计算有时还需要从设备上取走分析。三种数据放在同一个介质里必然要互相迁就频繁擦写会影响可靠性大容量和小容量做在同一个芯片上又会牺牲成本和体积。所以我的选择是拆开。1.2 EEPROM、NOR Flash、SD卡的底层差异决定了各自岗位EEPROM是按字节擦写的这是它和Flash最本质的区别。Flash必须按扇区或者块擦除擦除之后才能写而EEPROM像一块普通的RAM一样单个字节写进去就完了。虽然EEPROM容量普遍不大常见的主流型号在4Kbit到1Mbit之间但它的写寿命按字节算通常能到100万次而且数据保持时间手册上都是几十年起步。这正好匹配参数存储修改量小、修改次数多、不允许出现跨扇区重写带来的复杂性。NOR Flash则是按扇区/块擦除的4KB一个扇区很常见读写速度比EEPROM快得多单颗容量也做到128Mbit甚至更大。但它最要命的是擦写寿命多数SPI NOR Flash整体擦写次数在10万次级别100万次算很稀有的工业级型号。NOR Flash适合存日志和固件备份因为可以顺序追加、按页写入、偶尔做磨损均衡比存参数更符合它的天性。SD卡本质上是NAND Flash加了一个控制器控制器替我们处理了坏块和ECC容量和成本都到了另一个量级。但代价是写延迟不稳定、依赖文件系统、对掉电敏感而且它本质上是可拆卸介质。在工业控制器里SD卡更适合做大容量过程数据的最终落盘不适合做关键参数和实时日志的唯一副本。有了这个底层差异三级存储的定位就很清晰了EEPROM承担最后一道保险的角色即时保存关键参数掉电不丢失NOR Flash承担频繁追加、就地保存的角色存日志和固件备份SD卡承担大容量、可搬移的角色存批量数据和离线分析记录。1.3 分级存储不是堆硬件而是拆开三个约束我再展开说一下为什么三级存储方案不是费用堆叠。如果只看硬件成本EEPROM加NOR Flash加SD卡确实比单独一块大容量Flash贵一点但真正贵的是现场故障成本。一个参数在掉电瞬间丢掉的设备可能直接导致产线停机一张SD卡的FATFS目录结构在异常断电后损坏可能损失几个小时甚至一天的数据。分级存储的本质是把可靠小数据耐擦写中量数据海量大数据三个约束放到不同的介质上让每一种介质的短板都能被其他介质补齐。STM32FPGA的方案里还有一个额外的好处FPGA侧不管是做并行采集还是运动控制存储相关的模块只需要把关键参数通过约定寄存器交给STM32由STM32统一负责持久化FPGA就不用关心Flash擦除、FATFS这些耗时又不确定的操作整个架构的实时性更好维护。后面我会从总线分配开始逐个模块讲实践细节。2. STM32FPGA架构下的存储总线分配挂在谁哪一侧更合理2.1 存储器件统一挂在STM32侧FPGA不直接管持久化在STM32FPGA的板卡上我习惯把EEPROM、NOR Flash、SD卡全部放在STM32这一侧FPGA不直接接任何非易失存储。原因是持久化操作天然伴随时序流程EEPROM写一字节要等写周期NOR Flash擦一个扇区要几十毫秒SD卡写文件要走文件系统状态机。这些操作如果放在FPGA侧要么把Flash状态机做成一个外设IP要么和FPGA的主时序逻辑抢占资源复杂度是成倍上升的。而STM32的优势本来就在通用外设和处理流程控制它跑这些状态机比FPGA合适得多。我见过一些参考设计把NOR Flash挂在FPGA侧用来存FPGA固件这个特殊场景是合理的但运行时的日志和数据记录统一由STM32管会更省事。FPGA如果确实需要主动访问EEPROM比如从EEPROM里读自己的校准参数可以用Verilog写一个简单的I2C Master IP这个逻辑本身不复杂重点是把跨时钟域处理、总线仲裁和超时重试做好不要让FPGA侧的状态机和STM32侧同时发I2C命令否则总线上会出现竞争。我在实际项目里会在FPGA和STM32之间加一组握手寄存器FPGA要读EEPROM时先向STM32申请总线使用权STM32空闲之后释放总线两边轮流使用避免冲突。2.2 EEPROM选I2C还是SPI、NOR Flash选SPI、SD卡选SDIO还是SPIEEPROM的接口在STM32平台上最常见的是I2C其次是SPI。I2C只需要两根线从机地址还能做级联多数控制器板子一个I2C总线可以挂温度传感器、RTC、EEPROM等多个器件。2Mbit以上的大容量EEPROM比如M24M02这类I2C也支持页大小会变成256字节代码处理跨页写需要按对应页大小切分。如果是对实时性特别敏感的高速参数存取可以考虑SPI EEPROM比如AT25系列但SPI EEPROM在市场上比I2C少价格也高。工业控制器里参数量级就那么几KB100KHz到1MHz的I2C完全足够。NOR Flash这里就很统一了主流方案是SPI NOR Flash从W25Q32到W25Q256系列Quad SPI和Dual SPI都是加分项常规软件用标准SPI轮询方式就够。唯一要提醒的是如果选型时选了Quad SPI型号PCB上建议把所有IO引脚都连出来哪怕固件第一版不用四线模式后面做固件升级或者要提速时不用改板子。SD卡的选择要稍微纠结一下SDIO接口速度快4bit模式下读速度能到几十MB/s但CMD线、DAT线、时钟各自的时序和电平都要处理占用的引脚也多SPI模式只需要4根线兼容性最好缺点是写速度慢。对工业控制器来说数据记录的大头往往是周期性采样的几十到几百KB每秒SPI模式在多数场景够用代码量小很多排查问题也方便。我自己的板子上如果没有高速录制需求SD卡一律优先选软件复杂度低的方案。2.3 电平域与隔离存储总线最常见的隐性坑STM32和FPGA的工作电压不同现在STM32主流是3.3VFPGA可能有1.8V、2.5V、3.3V多组供电这些存储芯片几乎都是3.3V。FPGA侧需要访问存储时比如自身配置加载必须确认电平转换。这个问题我踩过一开始直接把FPGA的3.3V IO和STM32的I2C挂在同一个总线上结果两边输出驱动能力不同总线出现了明显的尖峰把EEPROM的数据写坏了。后来加了带方向控制的电平转换芯片比如TXS0108E并在所有总线上串了33Ω电阻问题就消失了。如果板卡还涉及隔离通常会把隔离放在STM32和外部接口之间存储器件本身不需要隔离但I2C、SPI信号穿越隔离边界时处理不当会在上电瞬间输出不确定电平。尤其要注意EEPROM的写保护引脚和NOR Flash的/WP引脚隔离器件在上电未使能阶段如果把这些脚拉低可能造成意外写入。经验做法是写保护脚用默认上拉电阻配合板级开机时序在上电全部稳定之后再释放。这个细节在批量生产时尤其重要产线瞬时上电的电压爬坡不一致容易触发偶发写坏。3. EEPROM参数存储实战I2C读写、页写与掉电保护3.1 选型与容量逻辑别一上来就选最大的那颗参数存储我常用的型号是AT24C256和AT24C512容量32KB到64KB对绝大多数工业控制器来说整个参数区加上配置文件模板都够了。选容量时有一个容易忽视的点EEPROM是按页写入的AT24C256的页是64字节超过页边界写器件可能会回绕到本页起始地址把已经写好的数据覆盖掉。这个是EEPROM的经典坑代码里必须做跨页处理。选型时还要注意工业级型号温度范围要覆盖-40到85很多消费级EEPROM也标-40但工业现场的电压波动和EMC环境还是建议优先考虑汽车级或工业级的原厂型号比如Microchip、ST、ON的产品线。我自己在批量产品中用过国产的GT24系列替代AT24硬件兼容但它在高低温下的写周期时间明显变长后面还是保留了一组出厂自检项去量测写周期不会盲目信任标称值。3.2 I2C读写流程跨页写、轮询ACK、速率与重试参数写操作的推荐流程是这样的。先把一帧要写的数据按页大小切分比如AT24C256页64字节先在内存里构建目标地址和缓冲然后逐页发送。发送时用硬件I2C或软件I2C都行但要注意一个关键点EEPROM在内部写周期内不会响应外部的任何I2C命令总线上表现为ACK丢失。所以写完一页之后必须轮询检查设备是否恢复ACK或者直接延时5ms再发下一帧。用轮询ACK的方式比固定延时更快更可靠特别是在批量写大量参数时轮询效率更高。代码里我一般这样组织写操作带状态位主流程只管提交写请求写请求在I2C中断里被消费一旦ACK丢失就设置一个超时重试计数器重试3次之后上报错误事件。这个状态机的细节决定了EEPROM写入在恶劣环境下的鲁棒性不能是简单的阻塞式写。实际测试中如果总线上还挂了其他I2C设备建议给每个设备的读写操作都加一个超时看门狗防止某个从机拉死总线导致整条I2C挂掉。// 伪代码EEPROM跨页写 uint8_t eeprom_write_bytes(uint16_t addr, uint8_t *buf, uint16_t len) { while (len 0) { uint16_t page_size 64 - (addr % 64); // 当前页剩余空间 uint16_t chunk (len page_size) ? len : page_size; if (i2c_write_bytes(EEPROM_ADDR, addr, buf, chunk) ! OK) { return ERROR; } // 轮询ACK等内部写周期结束 if (i2c_poll_ack(EEPROM_ADDR) ! OK) { return ERROR; } addr chunk; buf chunk; len - chunk; } return OK; }3.3 掉电保存的工程做法双备份区、CRC校验和版本号真正的工业现场对参数保存还有一个硬要求掉电瞬间必须能保存或者至少不能存错。单片机的VDD检测和掉电中断可以做但更稳妥的做法是用双区备份。我的方案是EEPROM里划出两个相等的参数区每一区开头放魔数、长度、CRC32和版本号。上电时先读A区CRC通过且版本合法就用A区A区损坏就读B区B区合法就恢复A区两个区都坏就进入出厂默认配置模式。写入时永远先写备用区校验通过后再把主区的期望状态标记更新。这个过程把“写一半掉电”导致的问题隔离在单区全局不会出现两个区都是半截数据的情况。工业控制器的参数保存还有个细节很多参数是经过运算得到的比如ADC校准系数写错一个字节设备也能工作但精度偏移会造成现场很难排查的疑难杂症。所以CRC校验在这里不是软工洁癖而是必须项。我把参数区的版本号设计成单调递增每次写入都检查版本号如果新参数版本比旧版本低说明上一次写入没有完成这样就可以有依据地回退到备份区。4. NOR Flash日志与固件备份实战SPI时序、坏块管理与磨损均衡4.1 SPI NOR Flash的擦写单位扇区、块、页的关系之前说过EEPROM按字节写NOR Flash则必须按扇区擦除。以W25Q128为例最小擦除单位是4KB的扇区写则是按256字节的页一个扇区包含16页。Flash的位只能从1变成0要恢复成1只能做擦除操作把整片扇区都变成FF。这意味着任何对Flash中一个字节的修改本质上都是“读出整个扇区-修改-擦除-整扇区写回”的过程。如果没有磨损均衡频繁擦写同一个扇区几万次下来这个扇区就废了。这也是为什么NOR Flash不适合当EEPROM用低速还好说寿命和复杂度都不匹配。在工业控制器日志场景里我会刻意让日志记录按扇区整块写避免随意再去修改旧数据。如果一次日志写入只改了扇区中间几个字节整个扇区的擦写代价也是全量的所以日志设计要尽量追加整块写。4.2 状态寄存器轮询、写保护和掉电正在擦除怎么办SPI NOR Flash的擦除不是马上完成的以W25Q128为例扇区擦除典型时间45ms大块擦除可能到100ms以上。软件必须轮询状态寄存器里的BUSY位等Flash内部擦写结束再发下一步命令。如果掉电正好发生在擦除过程中那这个扇区的数据基本就是废的同时在擦除时Flash内部会进入一种特殊状态有些型号在重新上电后工作正常有些会锁死在一半擦除的状态。硬件设计上NOR Flash的/WP引脚和HOLD引脚不要直接接地或悬空需要拉上拉电阻接到MCU的GPIO软件初始化时先把写保护和HOLD释放掉。固件备份场景里我还会用一个双bank策略固件A区和B区交替存储配合一个在EEPROM里的启动计数器。如果A区升级中途掉电重启后检测到A区固件不完整直接启动B区旧固件设备能正常跑升级任务在后台重试。这套组合拳比单纯依赖Flash的状态位靠谱得多。4.3 简易磨损均衡与日志追加策略让Flash寿命用尽之前机器先报废日志的写入模式是“追加”天然适合做顺序写。我的策略是把Flash划分成一个日志区日志区由N个扇区组成。写日志时先找到当前活动扇区如果剩余空间够就按页写入不够就把扇区擦除然后从头开始写。这样一来每个扇区的擦写次数是均衡的都差不多只要总的写次数除以扇区数没有超过寿命上限就没事。进一步还可以在头部维护一个扇区写计数表定期把最旧的扇区和新空扇区轮换。对于不需要随时改写的运行日志甚至可以整个日志区不做随机写只做顺序追加掉电风险就大大降低。实际项目中我见过有人把NOR Flash当FIFO用用SD卡的方式在上面跑FATFS结果频繁擦写、目录项损坏没跑几个月就废了。NOR Flash上做轻量级日志系统自己维护扇区头部和索引比文件系统更可控这也是我在这个项目里没有在NOR Flash上跑文件系统的原因。日志区的头部我建议放一个魔数加当前写指针写指针的更新尽量放在备份扇区避免日志区头部被频繁擦写。5. SD卡数据记录实战FATFS、文件切割与异常断电恢复5.1 文件系统选型什么时候用FATFS什么时候用裸扇区存储SD卡最正统的用法是用FATFS管理文件系统因为数据要拿到PC上分析FAT文件系统是通用的。FATFS虽然代码量只有几KB但它在掉电场景下的表现完全取决于调用方式。f_write只是把数据拷到了FATFS的内部缓冲什么时候真正落盘要看f_sync和f_close而f_sync之后的落盘时机还要看SD卡控制器。在工业记录仪里我要求每写满4KB或者每隔500ms就调用一次f_sync保证异常断电最多丢几百毫秒的数据不会出现大量脏数据。另一个思路是在没有文件系统需求的时候直接按物理扇区写比如从0扇区开始顺序写头部保存记录索引。这种方式对掉电最免疫但数据要配套上位机工具才能离线分析适合设备内部原始数据回读场景。我做过一个振动监测终端就是直接用裸扇区写上位机通过读卡器把SD卡镜像读到PC再解析比FATFS稳定得多。但产品要交付给客户客户希望把SD卡直接插电脑就能读文件那FATFS还是绕不开。绝大多数产品最终选了FATFS加定期sync的平衡方案。5.2 写缓冲、双文件轮换与扇区对齐让连续写不卡顿SD卡最怕的是写入过程中出现长时间延迟尤其是切文件、建目录、移动FAT表项的时候。如果主程序在采集循环里直接调f_write系统就会因为一次卡顿丢数据。我的做法是开一个环形缓冲区采集线程把数据写入缓冲区写卡线程从缓冲区拿数据攒够一个扇区或者固定时间间隔再写。写卡线程的优先级低于采集线程保证采集不丢。文件方面我会维护两个固定大小文件比如DATA01.BIN和DATA02.BIN写满前一个就打开下一个。文件建立时一次性写一个大文件用f_lseek先扩展大小再不断f_write填充避免FATFS每次写都要更新FAT表导致性能下降。扇区对齐也重要文件系统默认以512字节为扇区按4KB大小对齐数据块可以减少底层擦除的跨块开销。实测下来这套组合在标准class10的SD卡上能稳定维持1MB/s以上的连续写入远远超过大多数工业控制器的采样需求。// 定期同步防止异常断电丢大量数据 while (record_running) { fill_buffer_from_fifo(); if (buffer_full || timer_expired) { f_write(file, buffer, BLOCK_SIZE, bw); f_sync(file); // 强制落盘 buffer_reset(); } }5.3 异常断电自检上电先扫描文件系统再决定继续写还是换文件掉电自检我把它当成一个完整状态机来做。上电后首先挂载文件系统如果挂载失败说明SD卡FAT表可能已经损坏。这时不能直接格式化我会尝试退回到最后一个有效扇区的镜像进行恢复如果连这个都做不到就把当前文件标记为损坏新建一个以时间戳命名的新文件继续记录。这个流程必须在设备开机初始化阶段完成不能在发现文件损坏时才去修复否则数据写入过程中再掉一次电文件系统可能彻底崩掉。工业产品里建议在SD卡座侧面加一个门控开关检测卡是否被拔出拔卡瞬间不管写卡线程在干什么先同步停止写操作避免写一半拔卡损坏文件系统。这个习惯帮我省了非常多的售后问题。还有一个容易被忽略的点SD卡座本身有插入检测脚但很多设计没有把卡座检测脚接到MCU或者接了却不处理这等于把一道保险丢了。另外SD卡的电源端要在卡座附近加一个大电容比如100uF钽电容防止拔卡瞬间电压跌落导致写操作异常。6. 实测中的坑与验证方法从逻辑分析仪到长稳测试6.1 用示波器和逻辑分析仪抓时序一个地址线毛刺的排查案例存储系统排查最怕的是时好时坏。我之前遇到过EEPROM偶发写错字节代码审了几遍都没问题后来用逻辑分析仪抓I2C总线才发现SCL线上在上升沿期间有个约20ns的毛刺导致EEPROM采样到错误位。排查下来是PCB上I2C线太长且没有加串联电阻布线跨过了FPGA的时钟区域耦合噪声。处理方法是把I2C上拉电阻从2.2K改到4.7K加33Ω串联电阻并调整走线远离时钟信号。这种毛刺在逻辑分析仪上可能只出现一次所以不能只抓一两分钟要让设备跑起来持续抓。对于SPI NOR Flash和SD卡我也会在长跑测试里用类似的逻辑分析仪观察时钟和片选信号确保上电和掉电过程中存储器件的控制信号电平稳定。遇到SPI读回来的数据偶发错误优先检查SCK的占空比和MOSI数据的建立保持时间很多是GPIO配置成了开漏模式或者速度档太低导致的。6.2 长稳测试与数据完整性统计工业化产品怎么验证存储可靠性存储方案定型后不能只看功能。我在批产前会设计三个环节的测试。第一是参数区压力测试反复写入、掉电、上电1000次以上每次上电都校验CRC记录任何一次校验失败。第二是NOR Flash日志测试用一个模拟程序持续往日志区追加直到写满寿命预期值的30%再拷出全部日志验证连续性重点看有没有空洞和乱序。第三是SD卡记录测试连续录制24小时数据断电重启后逐块比对文件内容统计坏块率。这三个测试任何一个出现异常都要回溯到代码或硬件设计不能等客户现场出问题。我还习惯在批量固件里嵌入一段测试代码通过串口或者网络导出长期运行统计比如EEPROM写次数、NOR Flash擦除计数、SD卡写失败计数这些数据是现场判断存储健康度的重要依据。STM32的USB虚拟串口在这里很好用一根Type-C线就能把设备统计信息导出到PC做离线分析比用JTAG方便得多。ST-Link Utility在量产测试时可以用来烧录整个Flash镜像但运行期的健康度数据还是建议通过串口或网络导出。6.3 存储方案成本与容量的经验清单最后给一套我常用的经验值不一定适用于所有项目但作为起点参考足够。参数区如果只有几百字节变更用AT24C32就够如果参数区要放配方、历史报警几十KB上AT24C512或AT25系列512KB。日志区如果只是简单的设备事件记录8MB到16MB的SPI NOR Flash够跑好几年如果需要保存较长时间的运行趋势和故障追忆建议32MB以上。SD卡容量跟着实际需求走如果只是采样保存8GB很宽裕如果需要保存大量视频或波形64GB也不嫌多。成本敏感的产品可以做两档配置基础版不带SD卡只有EEPROM加NOR Flash增强版加SD卡座。硬件布局上预留SD卡座的位置软件上用条件编译一套固件兼容两种硬件现场升级也很方便。如果你和我一样用的是STM32FPGA架构还有个额外的收益FPGA侧的调试信息也能统一写到STM32的存储系统里出现故障时把FPGA寄存器快照和STM32日志一起拉出来定位问题快很多。这套分级存储方案我用了几个项目回头来看最值的投入其实是那些掉电保护和CRC校验代码它们平时看不见但现场少跑一次维护就把成本全都赚回来了。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进