
做工业控制器最容易被低估的就是数据存储。你可能以为一颗带大Flash的STM32就能搞定所有事直到你同时遇到掉电保存参数、固件升级、连续记录传感器日志、FPGA采集的高速数据需要搬运——这些需求凑在一起的时候单片机的内部存储是根本不够用的。我去年做的一套运动控制板就是这种状态STM32H743 FPGAH7片上已经有2MB Flash、1MB RAM听着很大可一旦把实时采样日志、参数备份、FPGA配置镜像、远程升级包全部塞进去很快就见底了。后来我把存储拆成了EEPROM、NOR Flash、SD卡三层做成分级存储方案才算是真正把系统稳定跑起来。这篇是硬件篇的第12篇专门把“工业控制器的数据到底怎么存”这件事拆开讲清楚。1. 为什么“一颗芯片搞定存储”的想法必须放弃1.1 四类数据四本完全不同的账工业控制器在运行过程中要存的数据其实是四种完全不同的东西参数配置类PID参数、零点标定、电子齿轮比、设备序列号、通讯地址。几十到几百字节写入不频繁但掉电必须不丢而且最好能保存十年以上。程序固件类MCU应用固件、FPGA比特流。几百KB到几十MB更新频率低但升级过程不能断电否则设备变砖。运行日志类传感器采样值、报警记录、IO状态变化。数据量随运行时间线性增长跑个几天就能上GB要求持续写、顺序写。临时缓冲类采集过程中的中间数据、FIFO队列周期毫秒级值域大要求写入快不需要长期保存。这四类数据对容量、写入频率、保存寿命的要求完全不一样。参数要“稳”固件要“大”日志要“久”缓冲要“快”。任何单一存储介质都没法同时满足这四点。你让EEPROM存日志容量和速度都不够你让SD卡存参数SD卡随机掉电损坏时连PID都没了设备直接废掉。所以分级不是炫技是被需求逼出来的。1.2 工业环境里的三个存储杀手实验室里调得好好的存储方案一上工业现场就容易出问题因为这里有三个场景是Datasheet里面不会写的掉电。控制器不是IPC它随时可能被人拉闸、被电网闪断、被维修工误拔线。批量生产调试时我见过有人直接关空气开关跑测试。这种掉电没有优雅退出过程存储系统必须假定“任何时刻都可能断电”并且保证断电后数据还能恢复。温度。很多控制柜夏季内部温度轻松到60℃以上昼夜温差可能超过40℃。Flash类的数据保持能力受温度影响很大高温下电荷泄漏更快标称“20年保持”一般指常温在工业温度范围里要打折扣。连续写入。设备可能24小时不间断记录数据。NOR Flash的擦写寿命一般是10万次左右如果日志每条都触发一次扇区擦写几天就能把寿命磨完。所以存储方案必须考虑磨损均衡和写放大不能只把数据塞进去完事。1.3 STM32FPGA双核架构存储问题只会更突出加了FPGA之后系统的数据入口速度往往高一个数量级。ADC连续采样、编码器四倍频计数、高速串口解析FPGA先用内部BRAM或外部DDR做数据缓冲再交给STM32去处理和存储。这时候STM32面对的真正问题是如果底层存储器写得慢高优先级中断就会堆积采样的连续性就断了。如果底层存储没有掉电保护FPGA辛苦攒了一堆数据写一半断电可能连已经稳定的参数区都一起损坏。所以STM32FPGA这套架构的存储设计不单是选芯片还要回答“数据从哪来、经谁手、落到哪个介质”的问题。下面就是我的整个分层设计和落地细节。2. 分级存储的分层逻辑与数据流转路径2.1 四层存储体系是怎么划出来的我给自己定了一个存储分层的原则越重要的数据放越可靠的介质越大量的数据放越大容量的介质越快的数据放越快的介质。具体分四层层级介质容量写入频率典型数据L0FPGA BRAM / MCU SRAMKB级微秒级ADC采样FIFO、中间计算结果L1EEPROMI2C2KB~256KB低频写参数、标定值、掉电状态L2NOR FlashSPI8MB~64MB中频写FPGA比特流、固件备份、轻量日志L3SD卡SDIO/SPIGB级持续写完整采样日志、历史趋势、升级包数据从产生到落地永远先经过L0再按“重要程度容量”向下落。这个顺序不是拍脑袋定的而是倒推出来的L3的SD卡最容易坏所以重要参数不能等落到L3才安全L1的EEPROM虽然可靠但容量小且写入有5ms周期所以不能拿它做持续日志L2的NOR Flash正好卡在中间又可靠又有点容量适合做程序镜像和中等频率的日志缓冲。2.2 数据从采集到落盘的完整路径以我做的四轴运动控制板为例一条数据的完整旅程大概是这样的FPGA实时采集4路编码器计数和8路ADC采样值数据先进入FPGA内部RAM构成的环形FIFO这个缓冲深度大约64KB保证STM32处理不过来时数据不丢。STM32每隔10ms通过SPI/并行总线一次性读走这批数据在SRAM里做数据解析、格式压缩把原始值换成工程单位。压缩后的数据根据类型走不同分支用户修改PID或零偏参数立刻触发事件把参数写入EEPROM的参数区每100ms一条的运行状态记录攒满一个4KB的NOR Flash扇区后一次性写入完整原始运动曲线每天几十MB写SD卡日志文件按日期分文件。FPGA比特流需要升级时先把新镜像文件放到SD卡由bootloader校验完毕再将固件写入NOR Flash的FPGA配置区最后通过拉低nCONFIG触发FPGA重新加载。这套流程里最关键的一点是数据先是属于L0的然后被“分类”而不是“一股脑往下写”。分类逻辑写在STM32的应用层这也意味着分层存储不只是硬件选型更是软件架构的一部分。2.3 FPGA在存储链路里的真实角色缓冲与预处理很多人以为FPGA在存储方案里就是个数据源其实它能干的更多。首先FPGA是缓冲器。当STM32还在处理上次的数据新一批数据已经来了没有FPGA的FIFO就得靠DMA和中断硬扛系统一旦有其他任务优先级更高数据就漏了。其次FPGA可以做预处理。比如在AD采样值里做滑动滤波、在编码器脉冲里做四倍频和零位捕获这些操作直接在采样端完成传给STM32的数据量能减少一个数量级。数据量变小了后面所有存储层的压力都变小。第三FPGA还能直接控制存储介质。比如FPGA要对NOR Flash做并行读取、回放采样波形那Flash控制器就得在FPGA内部实现而STM32只做高层命令下发。这里牵扯到多个请求源对同一个存储器的访问仲裁跟热词里提到的“多端口DDR读写”是同一个思路只是介质换成了NOR Flash。3. EEPROM层参数写入的可靠性设计3.1 为什么“关键的少量数据”首选EEPROM而不是片上FlashSTM32片上Flash其实也能存参数写之前要先整页擦除而且擦写寿命一般只有1万次左右。我把参数存在片上Flash里的那次教训是频繁改PID标定时几个月后Flash的某页就开始校验不过了。EEPROM则完全不同它不需要擦除就能直接按字节改写单字节寿命普遍在100万次级别虽然大容量型号寻址复杂些但存参数来说绰绰有余。另一个优势是掉电场景EEPROM写周期自定时的容错窗口相对友好。在掉电瞬间如果MCU靠电容维持的电量只够写几十个字节EEPROM可以单字节逐个写而片上Flash要先把整页搬到内存、擦除、再写没个几百微秒下不来。工业现场“随时断电”的环境下这个差异就是生与死的距离。3.2 I2C EEPROM的读写细节器件地址、页边界与5ms写周期我常用的EEPROM是AT24C16和AT24C256。别看都是I2C接口细节上有几个大家容易踩的坑器件地址。AT24Cxx家族的设备地址是1010 A2 A1 A0 R/W。如果板上同时挂了多个EEPROM靠A2/A1/A0引脚区分。但要注意容量超过一定范围时内部地址位会“借用”器件地址的低位比如AT24C16只有A2引脚有效A1和A0在内部作为高地址位参与寻址——这个很多人刚上手时会搞混读出来的数据跟预期地址对不上。页写入边界。EEPROM内部有页缓冲AT24C02一页8字节AT24C16一页16字节AT24C256一页64字节。一次连续写入不能跨页边界如果你写的数据超过页剩余空间数据会回卷覆盖到本页开头。我当初就踩过把64字节的数据结构直接write到一个64字节页的EEPROM看起来没事但结构体里另一个字段被覆盖了。解决办法是软件里自己按页大小拆分写入。5ms写周期。EEPROM每完成一次页写内部要自定时编程期间总线不响应。大多数驱动会直接Delay 5ms但这很浪费更好的做法是发一个“无操作的读命令”如果收到ACK就代表写完了这叫ACK Polling实测能省掉不少等待时间。3.3 掉电瞬间不丢数据CRC双备份与写保护策略EEPROM本身可靠但掉电操作却是用户最容易翻车的地方。我在多块板子上遇到过参数偶发恢复默认的问题排查到最后基本都是掉电瞬间写了一半。完整方案是三层掉电检测。用MCU内部PVD或者外部电压监控芯片MAX809、TPS3839电压掉到阈值就触发掉电中断。注意阈值要选得比LDO稳压输出低一点但又要高于MCU最低工作电压否则检测到了也来不及写。掉电缓冲。硬件上在电源轨加470uF到1000uF的电容给MCU和EEPROM一个5~20ms的“最后窗口”。这个窗口在3.3V系统里够把几十字节参数写完。但别贪多大电容上电时冲击电流也大。双备份CRC校验。参数区我分成主区和备份区写入顺序是“先写备份再写主区”每次写都带上CRC32。上电时读两份哪份校验通过用哪份如果都坏了才恢复出厂默认参数并做一次告警。这个设计专门对付“写了一半就断电”的最坏情况保证任何时刻至少有一个完整副本在介质上。掉电检测中断里只写关键状态字节比如故障码、当前运行计数其他参数平时修改后就同步写不攒着。如果FPGA也想访问EEPROM里的标定参数I2C控制器可以放在FPGA里用Verilog实现STM32做总线仲裁防止两边同时发起写操作导致总线竞争。我见过有人把EEPROM挂在两边共享的I2C总线上不做切换协议结果写数据时偶发总线卡死表现就是参数随机丢。4. NOR Flash层程序、配置和轻量日志的存储4.1 SPI NOR Flash在工业控制器里的定位在STM32FPGA的组合里NOR Flash一般承担三个角色我都在这块板子上同时用了存放FPGA配置比特流。很多FPGA没有内部非易失存储上电要从外部Flash加载配置。SPI NOR Flash是AS模式最常用的介质。这个文件不能弄丢丢了FPGA变砖。存放MCU应用固件的备份区。程序升级时新固件先写到备份区校验通过后再切换启动升级失败还能回滚。存放中等颗粒度的运行日志。比如开机记录、异常告警、温度曲线每天可能就几个扇区NOR Flash的10万次擦写寿命完全用不完。容量选择上W25Q648MB和W25Q12816MB最常见。Fpga的比特流一般几MBMCU固件几百KB轻量日志预留几MB8MB能勉强转16MB更从容。4.2 擦除、页编程和状态位管理的完整流程NOR Flash比EEPROM麻烦一点它必须先擦除才能写而且擦除的最小单位是扇区4KB不是字节。按下图这组命令操作读状态寄存器1等待WIP写进行中位变成0发Write Enable0x06发Sector Erase0x20 24位地址再等WIP清0之后对目标地址发Page Program0x02一次最多256字节继续等WIP清0然后下一条。两个最常见的BUG一是擦除后不查状态直接写结果写进去全是0xFF二是一次写入超过256字节NOR Flash不允许跨页连续写硬件会把超出的部分忽略或乱写。我的经验是底层的写接口固定接收“地址长度缓冲区”内部自行把缓冲区按256字节切片逐页写上层根本不用关心页边界。4.3 日志写NOR Flash要“攒扇区”不要每条都写NOR Flash的4KB扇区擦除时间是毫秒级的频繁擦写速度上不去寿命也扛不住。如果你把每条日志都实时写NOR Flash10万次擦写寿命几天就能磨完。我采用的做法是攒扇区。在STM32 SRAM里开一个4KB的环形日志缓存每次产生的日志先放缓存存满4KB再一次性整扇区写入NOR Flash的日志区。这样好处有三点写入次数降低几个数量级每次写入是顺序地址读取时按扇区解析很容易掉电时最多丢一个扇区的日志但关键事件早就在EEPROM里留了故障标志。攒扇区机制没有什么神秘技术含量本质就是一个固定大小的环形缓冲满了就触发一次底层写但它是整个NOR Flash寿命设计里性价比最高的一件事。4.4 FPGA和NOR Flash的关系配置侧与运行侧很多初学者问NOR Flash挂在STM32的SPI上不就完了FPGA那边怎么办问题出在上电加载和运行访问两个阶段有冲突。如果FPGA用AS主动配置模式上电后FPGA自己通过专用引脚去读SPI Flash的比特流。这种模式下SPI Flash最好只接FPGA不能和STM32的SPI并接否则上电瞬间总线冲突。我的实际做法是SPI Flash挂在FPGA配置侧用AS模式STM32升级FPGA时通过一个MOS管加模拟开关切换SPI总线到STM32的SPI主机。STM32写完新的比特流后释放总线切回FPGA侧然后拉低nCONFIG让FPGA重新加载。这样FPGA上电自动加载STM32也能远程升级两边不冲突。如果运行阶段FPGA也需要访问NOR Flash做高速回放那就得在FPGA内部实现一个Flash控制器STM32通过命令接口下发“读哪段、写哪段”FPGA内部做多个请求源的仲裁。这和“多端口DDR读写”是同一类设计思路只不过介质换成了NOR时序简单一些但仲裁逻辑一样要细心。5. SD卡层大容量日志的文件系统方案与坑5.1 SDIO还是SPI速度、接线、可靠性的权衡SD卡接入方式我纠结过很久。两种方案各有利弊SPI模式兼容性最好、引脚最少CLK/MOSI/MISO/CSSTM32任何一个SPI外设都能驱动但速度上限大约20~25Mbps实际写文件时FATFS开销一算连续写几十MB要等挺久。适合日志量不大、板子空间紧的系统。SDIO模式速度快很多4位数据线并行传输理论能上百Mbps适合大日志、固件升级包这类高吞吐场景。但代价是引脚多、布线要求高而且SDIO的IO时序比SPI敏感走线稍长或者没加上拉电阻初始化就容易失败。我的经验是每天日志量超过50MB或者设备需要快速导出大文件直接上SDIO。如果只是几十MB的临时记录和升级包SPI模式更省心。选SDIO的话GPIO务必配置成高速输出并在数据线和时钟线上加10kΩ~47kΩ上拉电阻这个细节救过我很多次。5.2 FATFS配置要点LFN、扇区对齐和f_sync日志文件我用FatFS但不建议直接f_open、f_write完事几个配置宏要提前调好FF_USE_LFN要开不开就只能8.3短文件名日志文件按日期命名会很难受。建议开1动态分配缓冲区注意堆空间。FF_FS_MINIMIZE如果不需要复杂的目录遍历调到2或3能省不少代码空间。FF_FS_EXFATSD卡大于32GB通常格式化成exFAT要开启这个宏才能挂载。但工业场景我强烈建议还是用32GB以内的FAT32卡兼容性最稳。FF_FS_NORTC如果板子没有实时时钟关掉RTC功能可以少写不少代码。写日志时还有一个关键认知f_write返回FR_OK不代表数据已经落盘FatFS有内部缓冲分配和目录更新可能还在缓存里。如果这时候掉电文件系统很可能损坏。所以重要的日志节点比如换日、异常事件后要主动f_sync。虽然每次sync都有额外开销但和掉电丢整个目录相比这点开销太值得了。5.3 日志文件预分配一个少数人会做但很关键的优化连续写SD卡的人一定遇到过这种问题日志写几天后文件系统开始卡顿或者掉电后日志文件打不开。根因往往是FAT表和目录项频繁更新。我的办法是预分配日志文件。每天启动时在卡上创建当天的日志文件并按估算容量一次性写满比如今天预计500MB就写满500MB的0x00相当于把FAT表的分配关系全部固化。之后写入时只在预分配区域内部顺序写不触发新的FAT分配写速度稳定掉电时也几乎不会出现目录项和FAT不一致。这个技巧初期会浪费一点卡容量但换来的可靠性完全值得。5.4 卡死、寄存器锁死与镜像制作工业SD卡三件套SD卡在工业环境里最容易被低估的三个问题我逐个说。卡死锁寄存器。异常掉电后有些SD卡会进入不响应CMD0的状态软件怎么复位都没用只能物理断电重来。硬件设计上我把SD卡供电单独用一个MOS管控制软件初始化失败时直接把卡断电再上电重新走一遍完整的SD卡初始化序列CMD0带80个时钟、ACMD41多次重试、CMD2/CMD3获取CID和RCA、CMD7选中目标卡。这套流程能救回大部分假死卡。镜像制作。SD卡备份最好用整卡镜像方式Win32DiskImager或者Linux下的dd都能做。批量生产时把一张配好测试程序和目录结构的卡做成镜像逐张烧录避免每张卡手工格式化导致文件系统参数不一致。镜像烧录时有个小坑卡分区表必须是MBR不能是GPT否则FatFS不认。用Windows自带格式化有时会默认GPT插到设备上就“挂载不了”这时候用SD Card Formatter或者diskpart重新初始化能解决。掉电写保护。SD卡保存的都是低价值、可重新生成的日志数据这也再次印证分级存储的原则关键参数永远不该放在SD卡上。6. 联调实测数据丢失、写坏、速度不达标的真实排查6.1 一次掉电丢参数的完整排查链路客户反馈控制器断电后偶尔PID参数变回默认值。我没急着改代码先做了三步排查第一步确认参数是否真的写入成功。在EEPROM写函数的返回值和写完后的回读值上加了断点连续断电重启几十次发现每次写得都对读出来也是对的。第二步复现掉电瞬间。把示波器探针接在电源轨和EEPROM的SDA线上断电瞬间观察波形发现一个问题软件收到掉电中断后进保存流程但EEPROM的页写周期还没结束电源已经掉到MCU最低工作电压以下了。换句话说“写了”和“写完”是两回事。第三步改进方案。掉电中断里只保存最关键的几个字节故障码、状态标志参数区改成修改时实时写入而非断电时集中写同时把维持电容加大到1000uF给写周期留足时间再配合双备份CRC校验。改完做了200次随机掉电测试才算是把这个偶发问题彻底按下去。这件事让我意识到存储方案选型只是30%的工作掉电时序设计才是剩下的70%。6.2 连续写入性能下降从2MB/s掉到几百KB/s还有一次SD卡日志跑了几天后速度暴跌。查看底层代码发现问题不在卡而在写放大FatFS每次小块写入都要更新FAT表导致同一时间既写了数据区又写了FAT区寻道一直在来回跑。优化分三步第一单次写入块加大到512字节甚至4KB减少FAT表更新频率第二开启FatFS的写缓冲第三采用上面说的预分配文件方案。改完速度稳定回到2MB/s以上。速度下降这类问题基本都能在这种“写入模式”里找到原因不是卡本身老化。6.3 这套方案跑下来我最想留给大家的几条经验参数永远不要放SD卡。哪怕某张卡看起来质量非常好它坏的时候不会提前通知你。关键参数只放在EEPROM或者带掉电保护的RAM里。掉电保存要设计成“事件驱动双保险”。没有掉电检测、只靠定时写参数的方案早晚丢数据而且问题极其隐蔽。写日志前先想清楚磨损。不是所有日志都值得写进Flash数据量小用NOR Flash数据量大才转SD卡。日志分级能省掉一堆存储寿命问题。FPGA和STM32共用一个存储介质时仲裁规则要在架构图里就画清楚。谁主谁从、总线冲突怎么办、升级时怎么切换这些问题靠画板时定好比事后打补丁省事得多。这套分级存储方案跑了大半年现场稳定性比之前单靠STM32片上Flash硬扛的方案强了很多。存储这件事说到底是在“快、准、稳、省”之间找平衡没有银弹方案只有根据数据类型量体裁衣的合理分配。这一篇先聊到这下一篇硬件篇我准备专门写FPGA配置升级流程里的坑欢迎大家一起交流。