
做工业控制器的工程师十有八九会被同一个问题缠住现场数据到底存哪。前几年我手里有个运动控制卡的项目因为存储方案设计得太随意结果一次现场掉电客户设备的几十个校准参数全没了售后电话从下午打到深夜。从那以后我就确定了一件事所谓STM32FPGA分级存储不是锦上添花的功能而是设备的底线。把参数交给EEPROM把运行日志和黑匣子交给NOR Flash把大容量采集数据交给SD卡三种介质各司其职配合得当能把设备售后里的存储故障去掉大半。这篇文章继续硬件篇第12期适合正在做控制器硬件、固件或者还在选型阶段的工程师参考。1. 为什么工业控制器的存储要分级1.1 先给你的数据分个类我一般上来先问一句你控制器里到底存几类数据如果答不上来那多半后面会出问题。工业控制器的数据其实可以粗暴分成三类。第一类是配置参数类包括PID系数、校准系数、设备序列号、通信地址这些总量通常只有几十到几百字节特点是频繁改写、断电不能丢、改完要立即生效。第二类是运行日志类包括报警记录、操作记录、状态变化属于黑匣子数据总量从几百KB到几十MB持续追加掉电后应该恢复到最近状态坏一条都很难跟客户交代。第三类是批量采集类比如高速ADC采样的趋势数据、波形数据按512MB往上走要求容量大、方便导出最好能直接插到电脑上读。很多工程师刚开始喜欢把所有数据全塞EEPROM容量不够就换大容量NOR Flash结果参数写到NOR里头每条都要先擦除再编程寿命和可靠性都不对反过来有人想用SD卡存参数每次改一个PID就写一遍文件SD卡的寿命直接报废。分级不是为了显得专业而是因为这三类数据对容量、速度、寿命、掉电安全的要求完全不同硬塞进一种介质里必然顾此失彼。1.2 三种介质性格完全不同EEPROM的最小擦写单位是字节寿命大约100万次容量几十KB掉电数据保持能力很强。NOR Flash的最小擦写单位是扇区寿命大约10万次容量从4MB到256MB读快写慢按扇区管理。SD卡按块管理容量大FAT文件系统用起来方便但掉电瞬间正在写的扇区很可能损坏而且卡的寿命受写放大影响很大。三者的关系可以用生活里的东西类比EEPROM是便利贴参数随手记、随手改NOR Flash是笔记本每天的过程记录写上去按页翻查SD卡是档案柜存大件按文件夹归档。如果非要把档案柜当便利贴用每次记个参数就归档一次柜子很快塞满反过来用便利贴存档案也根本塞不下。工业控制器环境温度高、振动大、电源波动多存储介质比商用环境更娇气分级本身就是在降低风险让每一类数据都住在最合适的房子里。2. STM32侧把EEPROM和NOR Flash做得踏实2.1 EEPROMAT24C系列的选型与I2C接线细节项目里我最常用的还是AT24C系列从AT24C02到AT24C16容量从2Kbit到16Kbit工业场合先看温度等级后缀带-20或者-40的工业级型号别贪便宜买商规。选型时还要注意一个容易忽略的点A0、A1、A2三个地址引脚是硬接线不是软件配置的。I2C总线上如果还有温湿度传感器、RTC这类设备这几个地址必须提前排好避免冲突。地址冲突的表现很怪写的时候正常读的时候回全FF或者两个设备交替响应排查起来相当费劲。上拉电阻方面4.7k是教科书值但不是所有场合都适合。总线短、设备少10k也行总线长、节点多上拉要降到2.2k甚至1k。判断标准很简单用示波器看SCL的上升沿如果边沿出现明显的缓慢爬坡或者回勾说明上拉电阻太大、总线电容太重。多设备挂在同一条I2C上时上拉电阻只需要在总线端点加一份不要每个设备都加一份否则等效并联电阻太小低电平都拉不下去。写时序本身不难照着数据手册的时序图操作就行。需要注意每个字节写入后有一个内部写周期tWRAT24C系列大概5ms。这个阶段芯片不理会外部命令操作完不能马上去读或者写下一字节要么死等5ms要么轮询ACK。轮询ACK其实更高效发一个起始条件加器件地址加写位芯片写完会回ACK没写完则不响应主控等到响应再继续。别忘了写完后做读回校验EEPROM坏字节的概率不高但一旦坏了没人知道设备就可能带着错误参数跑几个月。顺带说一句MCU内部Flash模拟EEPROM的用法这几年很流行优点是省钱省器件。但在工业控制器上我还是偏向独立EEPROM芯片温度特性和掉电一致性更可控。如果坚持要用模拟EEPROM必须自己做磨损均衡否则会把你存参数的那几个地址先写穿到时候不是换芯片能解决的问题而是整个MCU都得换。2.2 NOR FlashW25Q系列接线与擦写时序NOR Flash我常用的W25Q64和W25Q128后缀选工业级容量从8MB到16MB。有些人问为什么不用NAND工业控制器日志数据量不大NOR的随机读快、按扇区管理简单、掉电后坏块率低W25Q这类SPI NOR的时序也容易调没必要上NAND给自己找麻烦。容量不是拍脑袋定的日志保留时间乘以每小时日志量再留出30%余量反推空闲扇区数。接线相对固定CS、CLK、MOSI、MISO四根信号线再加上WP和HOLD。我自己画板时习惯把WP和HOLD都拉高到3.3V绝对不允许悬空。这两个引脚一旦悬空现场电磁干扰稍微强一点芯片就可能进入写保护或者保持状态表现为莫名其妙写不进去。很多工程师第一天就栽在这里芯片明明发命令了状态寄存器也读了就是写不进数据最后拿示波器一量WP脚发现是个浮空电平。擦写完整流程三步擦除、编程、校验。W25Q的扇区擦除按4KB块擦除按64KB页编程一次最多256B数据超过一页就得拆成多页而且每页地址必须按256字节对齐。跨页连续写是新手最容易错的点比如从0xFFF0开始写64字节如果只按线性地址累加而不管页边界数据会写到错误位置读回来一堆脏数据。正确做法是每页拆开发送地址按照页基地址加页内偏移来计算。还有状态寄存器的问题。芯片出厂或者上次异常操作后状态寄存器的写保护位可能处于置位状态需要先读SR看BP0/BP1/BP2/BP3。如果保护了先用写状态寄存器命令清除。正常操作时WP拉高执行写SR命令才能生效。以后每次上电初始化都主动清一下保护位虽然多花几个时钟周期但能避免很多诡异的写失败。2.3 存储布局与驱动状态机NOR Flash里的地址布局要提前画好不要等代码写完了再临时塞。我常用的分区方式大概是这样的地址区段用途大小0x000000 ~ 0x0FFFFF日志区A1MB0x100000 ~ 0x1FFFFF日志区B1MB0x200000 ~ 0x3FFFFF固件升级缓存2MB0x400000 ~ 0x4FFFFF出厂校准只读区1MB0x500000 ~ 0xFFFFFF保留剩余固件升级缓存放NOR是有道理的现场升级时万一新固件写了一半掉电老固件还有地方可以兜底不至于整机变砖。出厂校准数据单独划一个只读区运行期禁止写入校准参数初期调试时通过专用命令写入运行后锁死防止现场误操作。STM32侧驱动别用阻塞延时。我建议把NOR Flash驱动写成状态机发起擦除等待状态寄存器BUSY清零发起编程等待BUSY读回校验整个流程用DMA收发。主循环该干什么干什么状态机靠一个周期任务去推进。一次4KB擦除可能耗时上百毫秒如果主循环卡在这里看门狗都要叫了电机控制或者通信任务也会跟着抖动。所有存储操作加互斥不要在中断里直接读写FlashDMA完成后置个标志让任务层去处理就够了。3. FPGA在存储链路中的角色高速采集数据的缓冲与调度3.1 为什么大容量连续采集必须交给FPGA如果ADC是10MHz采样、16bit一秒产生20MB数据。STM32就算主频再高每字节都经过CPU干预加上中断响应、DMA搬移存储调度很快就会捉襟见肘。FPGA的作用是在前端把数据整理成块比如按1KB一个块FPGA先把连续采样写入内部FIFO再通知STM32来取。STM32不再逐字节忙活而是搬运整块数据存储效率和CPU占用都明显改善。FPGA还不只是搬运工。很多现场信号需要同步采集、触发捕捉多通道数据要对齐时间戳这些在STM32里做会消耗大量中断资源在FPGA里就是几行逻辑的事。数据经过预处理之后存储系统只需要关心块不用关心每一次采样。这也是为什么图像采集、相控阵相位控制、多端口DDR读写这类高速应用里FPGA几乎是标配先把数据流理顺后面挂什么存储介质都好办。3.2 乒乓FIFO与存储请求仲裁乒乓FIFO是把两个FIFO并排使用给FIFO_A写入时FIFO_B正在被存储端读走下一轮反过来。这样存储的读操作不会阻塞采集的写操作两个FIFO交替工作等于给连续采集和突发写打了个时间差。如果只有一个FIFO存储端一忙采集端就只能丢数据系统对外表现就是采样率不稳定。FIFO深度必须按最坏的写延迟来算不是随便选一个512或者2048。举个例子采样速率1MB/s存储介质最坏情况下一次写操作要等10ms比如SD卡正在做垃圾回收或者擦除那FIFO至少需要1MB/s × 10ms 10KB再留余量就得做到16KB以上。如果选得太小采集这边FIFO满了只能丢数据存储那边还没忙完系统就出现数据空洞选得太大又浪费FPGA内部资源这个平衡要靠计算不要靠感觉。两块FIFO什么时候切换用状态机在一个块边界处切换。内部把FIFO非空且达到块阈值作为请求条件向STM32发中断STM32搬运完成后回一个ACKFPGA再释放当前FIFO。这样可以避免STM32被频繁中断打扰也避免FIFO水位忽高忽低。这里还能做一个流量整形存储介质忙的时候FPGA先不发请求让数据继续积压到当前FIFO两个FIFO都满了就启动降采样或者报警。这个优先级调节逻辑在FPGA里很容易写换成MCU代码就麻烦得多。3.3 数据通路握手与仿真验证FPGA和STM32之间的接口实际项目常用16bit并口或者SPI配合DataReady、ACK、Busy三根握手线。FPGA内部产生的握手信号要跨时钟域送到STM32先做两级触发器同步避免亚稳态。握手逻辑不复杂但一旦两边时钟频率不一样信号不同步就会出现明明数据到了但主控没收到或者重复收到同一个数据块的问题。写Testbench时不要只写理想连续输入。我一般会覆盖三类场景正常连续块传输、FIFO接近满的时候、存储端Busy拉高导致请求被推迟。把这些波形拉出来看FIFO水位变化验证深度是否够。很多人都问FPGA怎么正确写Testbench和ST的串口仿真一个道理激励要先设计再写代码。把fifo_usage作为调试信号引出来在仿真里用monitor打印关键状态跳变上板后再用逻辑分析仪ILA观测同样的信号前后对照问题一目了然。4. SD卡大容量存储文件系统、掉电保护与现场惨案4.1 SDIO还是SPI很多工程师选SDIO是因为手册上写着几十MB/s的带宽但工业控制器日志存储真用不了这么高。SDIO初始化逻辑复杂CMD/ACMD序列、时钟频率切换、卡容量类型判断每个环节都要仔细做不对就是各种挂载失败。SPI模式接线少CS、CLK、MOSI、MISO四根线调试时用逻辑分析仪也方便。连SD卡座都不用选带焊盘的普通卡座加几个电容就够。SPI模式下SD卡上电后先要至少74个时钟周期的上电延时然后CMD0进入SPI模式CMD8对协议版本ACMD41完成初始化。卡的类型不同ACMD41的返回也略有不同老卡和新卡行为有差异。如果有任何一个响应超时都不建议继续读写先查供电和信号质量。所谓SD卡内部寄存器锁死很多并不是卡真坏了而是VDD跌落导致内部状态机挂住重新上电都恢复不了。解决办法是硬件上保证卡电源干净卡座附近加10uF和100nF去耦电容必要时复位电路把卡电源彻底切断再重新上电。4.2 FATFS怎么挂才稳FATFS现在是事实标准f_mount、f_open、f_read、f_write、f_close这几个接口足够完成90%的需求。注意三个细节一是挂载参数建议在首次f_open前主动触发一次挂载不要每次开机都全盘扫描二是写缓冲区要和簇大小对齐SD卡默认512字节一个扇区你的f_write缓冲区最好按2KB或4KB对齐配置否则文件系统会做很多不必要的扇区拆分性能差距明显三是文件打开后别频繁关闭日志文件保持打开状态攒一批数据再flush一次性能能差出好几倍。但是工业控制器有个矛盾缓冲攒多了掉电丢得也多。我的做法是把最后一次日志单独放到NOR Flash里SD卡可以允许丢最后几个扇区但关键的断电原因必须记在NOR中。这个思路其实就是分级存储的延伸按数据的可靠性要求选择不同的存法而不是把全部鸡蛋放一个篮子里。掉电保护最经典的方式是A/B双文件交替。写A文件时如果掉电下次启动B还是完整的如果A写完整了就把A标记为当前有效文件再清理B。文件系统层面再加一层CRC启动时扫描目录里的文件头选版本号最大的有效文件继续写。具体实现里不要把当前有效文件标记放在SD卡上因为掉电那一刻SD卡往往正在写标记本身就写不进去。放在EEPROM里更靠谱每次切换文件时更新EEPROM里的文件序号启动时先读序号再打开对应文件。4.3 数据格式别用文本日志侵蚀SD卡寿命很多项目用文本日志打开文件一看全是逗号分隔的字段好看是好看但代价很高。一个2字节的数值用文本表示就得占4到6个字节日志量翻两三倍SD卡寿命也跟着遭殃。我建议用二进制记录文件头放magic字和格式版本每条记录有固定布局最后再写一个上位机解析工具把一个二进制文件转成CSV或者表格。记录布局举例magic和长度各2字节时间戳4字节日志类型1字节data区最长248字节CRC32占4字节整条记录256字节对齐。对齐是为了让每一条记录恰好占整数个扇区减少写放大SD卡内部的分页机制对这种顺序写入最友好。单文件不宜无限写按64MB或者按天轮换超过就开新文件。文件名用日期时间比如LOG_20250601_0.bin这样SD卡空间用尽之后还能按时间清理旧日志现场也好找证据。5. 给数据上三道保险校验、冗余和健康监控5.1 分层校验逐层兜底很多人觉得校验就是CRC32其实要分层来看。SD卡在硬件层已经有CRC和ECC但那是卡自己内部纠错MCU读出来的数据如果被总线干扰文件系统层的CRC校验才能兜底。我的习惯是每个关键块、每条记录都算一次CRC32对FATFS的连续写场景来说速度影响很小。写后读回校验是另一个层面EEPROM和NOR Flash写完就读回跟原数据比对不对就当写入失败处理连续失败一定次数就上报存储异常。SD卡没法每次写都读回但可以定期把已写文件打开读一遍做完整性抽查。5.2 双镜像与日志轮替参数区用双镜像主区加备份区加帧头。帧头记录有效标志、内容CRC、写入序号。启动流程先读帧头主区校验通过就用主区主区坏了就读备份区两个都坏就恢复出厂默认参数并且记录一条日志。每写一次先把备份区更新好再更新主区这样掉电时最多丢掉最后一次变更不会出现两个区同时不一致。NOR日志的A/B区轮替和SD卡文件轮替思路一致当前写A区A区写满后清空B区再写B区每个区开头有magic和日志序号启动时选序号更大且校验成功的区继续写。这里有个容易忽略的细节区写满后不要急着擦除先确认另一个区是好的再动手否则擦到一半掉电两个区都可能不可用。5.3 磨损均衡和容量预算EEPROM百万次寿命看起来多算一下就知道不经用。如果参数每10秒写一次一年315万次一年多就报废。解决办法是磨损均衡把参数块的写入地址循环推进每次写到下一个槽位读取时遍历所有槽位找最新帧。这样有效寿命可以提升几十上百倍代价只是读的时候多扫描几个字节完全可以接受。NOR Flash按10万次擦除寿命算。假设日志每小时1MB4KB一个扇区每天24MB一年8760MB折算约220万次扇区擦写远超10万次寿命。所以不能只用一个扇区反复擦要A/B区轮替加多扇区分配或者把日志大头放到SD卡NOR只保留近期要点。SD卡的磨损均衡由卡内部控制器做但应用层要减少写放大尽量顺序写、减少随意删除文件、文件系统碎片少一点。选卡时优先选MLC或者企业级卡寿命和稳定性比普通消费卡强不少。6. 现场常见故障排查与调试经验6.1 常见问题速查表以下是我这些年整理的一套排查路径照着走基本能定位问题。症状可能原因检查方法EEPROM读回全FFI2C地址不对、器件未上电逻辑分析仪确认START和地址字节I2C卡死无ACK总线上拉电阻不合适、某设备拉死SDA量SDA静态电平逐个拆设备排查NOR Flash写不进WP脚没拉高、状态寄存器保护读SR寄存器量WP电平NOR数据错乱跨页连续写、命令间隙太短检查地址计算按页拆分发送SD卡挂载失败供电不足、初始化时序、卡座氧化示波器看CMD响应换卡测试SD卡锁死VDD跌落、内部状态机挂住切卡电源重启保证3.3V稳定掉电后文件损坏写缓冲未落盘、没有轮替升级为A/B文件加EEPROM序号FATFS打不开文件簇大小配置不对、卡文件系统损坏备份数据后重新格式化6.2 调试验证逻辑分析仪与随机掉电测试抓时序用逻辑分析仪20MHz采样率起步I2C和SPI都能直接解码对照数据手册检查时序参数。SD卡抓波形要看响应窗口检查CMD0、CMD8、ACMD41的响应时间是不是在规格范围内。很多挂载失败是初始化的时候就错了后面再加多少重试都没用。随机掉电测试是我强烈建议的验证手段。用继电器或者电子负载控制板卡电源让板卡在写入过程中随机断电每隔几十次检查EEPROM和日志文件的CRC。我第一次做这种测试不到一百次就复现了文件损坏后来加了A/B轮替之后再跑五百次都没出问题。测试要覆盖写入中途掉电、擦除中途掉电、连续快速掉电几种场景不要只做一次两次掉电故障是概率性的样本量不够根本看不出来。6.3 批量生产时的烧录与序列号管理产线效率很重要逐台上位机写参数太慢。EEPROM可以在测试工装里通过专用下载器直接写NOR Flash用整片镜像烧录SD卡用标准镜像直接拷。批量镜像做法先在一张基准卡上做好文件系统、目录结构和默认配置用读卡器做成img镜像产线再批量写入。每台设备唯一的序列号不能写在镜像里要在工装里单独写入EEPROM的序列号区域并且读回确认写错序列号在售后阶段会非常痛苦。做镜像时注意卡容量差异同一型号镜像文件要兼容不同批次卡的容量差异。文件系统格式化时选择FAT32单个文件不要超过4GB限制。产线工装的上位机脚本应该打印每一步的结果烧录失败自动标记不要声光报警都省了不然批量生产时根本分不清哪台烧坏了。我个人这几年的体会是存储模块在控制器硬件设计里往往排不上优先级但现场故障十有八九都跟它有关。先把数据分好类再按可靠性需求把数据放到对的介质上把掉电测试做成常态化比任何花哨方案都管用。最后分享一个小习惯每次改完存储相关代码我都习惯在版本发布之前专门跑一轮掉电测试哪怕只是重复掉电五十次也能挡掉很多原本要去现场才能发现的问题。