1. 三个存储颗粒,三种角色:工业控制器为什么不能只用一种存储介质
做工业控制器的硬件设计,存储这块儿最容易被低估。很多刚入行的朋友觉得,不就是存数据吗,一颗Flash芯片搞定,大不了再加个SD卡。但等板子真正跑到现场,问题就来了:参数老丢、日志写坏、掉电瞬间数据全毁、程序更新到一半变砖。你真去排查,会发现不是程序写得差,而是从选型那天起,存储架构就是错的。
我这些年经手的工业控制器项目,基本都是STM32+FPGA的组合架构,涉及的数据大致可以分为三类。第一类是配置参数,比如PID系数、校准值、设备地址、操作权限等级,这类数据的特点是单条体积很小,但更新频率可能很高,而且绝对不允许丢。第二类是固件本体和FPGA的逻辑文件,也就是还没转成执行流的程序代码,容量在几兆到几十兆之间,更新频率极低,但一旦写入错误或者写到一半断电,整台设备直接趴窝。第三类是运行数据,包括实时采样波形、诊断日志、事件记录、统计数据,数据量可以每天增长几十兆甚至几百兆,丢失一小段不算致命,但不能长期缺失。
这三类数据有一个共同点——它们的写入频率、单次体积、可靠性要求,互相之间完全不一样。如果用一颗大容量Flash做全部存储,小参数每次要按扇区擦写,磨损极快;大日志频繁写入又容易把程序区和参数区搞乱;掉电瞬间更是防不胜防。用SD卡存参数,插入拔出不讲道理,控制器什么时候被拔出SD卡完全不可控。所以分级存储不是"为了省钱"或者"看着专业",它是工业控制器在可靠性和成本之间做博弈之后,最能兼顾各方诉求的做法。
这套方案的核心思路很简单:把每类数据放到最擅长存储它的介质里,让数据特征和介质特性匹配起来。具体到我常用的分配就是:EEPROM管配置参数,NOR Flash管固件和FPGA逻辑,SD卡管运行日志和采样数据。文章标题里说的"STM32+FPGA分级存储方案",本质上是围绕这三个存储颗粒,把硬件电路、驱动层、掉电保护策略以及上位机的数据交换方式整体做一次规划。这篇我就从硬件篇的角度,从头到尾拆一遍这套方案的设计过程,包括为什么这么分工、电路上要注意哪些位置、驱动怎么配合、实测中容易踩哪些坑。
2. EEPROM、NOR Flash、SD卡的物理特性决定了分工逻辑
先说一个常见误区:很多人觉得NOR Flash容量比EEPROM大、价格也不贵,直接用NOR Flash替代EEPROM存参数不就行了吗?这就是没看清两种介质的物理特性。选存储介质,最先看的不是容量,而是擦写最小单元和擦写寿命。
EEPROM的优势在于可以按字节擦写。你改一个PID参数,I2C总线发个字节就写进去了,不需要先擦除整个扇区,也没有误伤相邻数据的问题。更关键的是它的擦写寿命,常规I2C EEPROM可以达到100万次,工业级的大厂型号甚至能到1000万次。参数区在控制器运行过程中是频繁被改写的,今天校准一次、明天调个参数,一个调度周期内可能写十几次,这种情况下EEPROM的字节擦写能力和长寿命是刚需。
NOR Flash就不一样了。它的物理结构决定了擦除必须按扇区进行,一个扇区一般是4KB,哪怕你只想改其中的一个字,也得把整个扇区先读出来、改好、擦掉、再全部写回去。这带来两个后果:一是写参数流程繁琐,还要自己管理备份区;二是每个扇区的擦写寿命通常只有10万次左右,远低于EEPROM。但NOR Flash也有它不可替代的地方——读速度快、支持随机读取、支持XIP(片上执行),工业控制器的固件和FPGA逻辑文件很适合放在这里。程序上电后可以直接从NOR Flash中执行指令,省去"先拷贝到RAM再跑"这一步,系统启动时间能快出不少,这在工控领域经常是硬指标。
SD卡则完全是另一个量级的存储设备。它的容量优势太明显,一张普通工业级SD卡就是几十GB,成本比同容量的NOR Flash低一个数量级。SD卡和闪存介质一样有擦写寿命问题,但它内部有FTL层,会自动做磨损均衡和坏块管理,咱们不需要像用NOR Flash那样手动关心扇区磨损。不过SD卡有两个弱点需要注意:一是写延迟不稳定,遇到GC(垃圾回收)的时候一梭子写下去能卡几百毫秒;二是掉电有风险,写入过程中断电很可能损坏FAT表或当前文件。所以它适合存运行数据——这类数据允许丢失一小段,也允许写的时候稍微慢一点,但不能占着宝贵的NOR Flash空间。
三种介质放在一起对比,各自的角色一目了然:
| 对比维度 | EEPROM | NOR Flash | SD卡 |
|---|---|---|---|
| 典型接口 | I2C/SPI | SPI/QSPI | SDIO/SPI |
| 最小擦写单元 | 字节 | 扇区(4KB) | 页/块(由内部FTL管理) |
| 典型擦写寿命 | 100万~1000万次 | 10万次 | 受磨损均衡影响,整体较高 |
| 典型容量 | 2Kb~1Mb | 8MB~128MB | 几百MB~几十GB |
| 是否支持随机读 | 支持 | 支持,可XIP | 支持但劣势 |
| 写延迟 | 极低(ms级) | 低(擦除较慢) | 不稳定(GC时会卡顿) |
| 掉电风险 | 低(单字节原子性) | 中(需管理擦写状态) | 高(FAT可能损坏) |
| 适合数据 | 配置参数、关键状态 | 固件、FPGA逻辑、只读表 | 日志、采样数据、历史记录 |
这张表是我选型时反复对照的基准表。具体的选型思路可以这么说:凡是"不能丢、经常改、容量小"的数据,交给EEPROM;凡是"不能错、容量中等、读取频繁"的数据,交给NOR Flash;凡是"量大、允许部分丢失、追求性价比"的数据,交给SD卡。这套分工逻辑定下来之后,后面的硬件设计才有方向。
3. 硬件架构分工:STM32做数据管家,FPGA做实时采集缓冲
确定了用哪三种介质,接下来就是它们挂在谁下面、以什么通道访问的问题。这里要先理清STM32和FPGA在数据链路中的角色。
工业控制器里,FPGA干的活通常是高速采样、多路并行IO控制、硬件实时逻辑,比如抓取编码器信号、控制IGBT的PWM、采集高速ADC的数据。这些任务要求的实时性,STM32靠中断和DMA跑也追不上,FPGA的优势就在于所有通道并行处理,一拍之内完成全部数据锁存。但FPGA本身不适合做复杂的数据管理,它的逻辑资源多、但没有操作系统层面的文件系统概念,你要让它去直接操作SD卡的FAT文件系统,实现起来痛苦且不稳定。
STM32的优势正好补在这里。它跑着裸机或RTOS,有文件系统库可以用,有丰富的通信外设(I2C、SPI、SDIO、USART),主频几百兆,适合做上层控制逻辑和数据调度。所以我的做法是:FPGA负责采数和实时控制,把需要留存的数据先放进内部的Block RAM或者外挂SRAM里暂存,然后通过总线交给STM32,由STM32统一负责向三种存储介质写数据。
具体到数据流,看几个典型场景:
配置参数流:上位机通过RS485/以太网下发新的PID参数,STM32收到数据帧,校验CRC无误后,先更新RAM中的运行副本让控制逻辑立即生效,随后启动I2C写EEPROM的流程。这里有个时序点需要注意:FPGA内部的控制逻辑用的是RAM副本,和EEPROM中的持久化副本是两套,互通点放在STM32的应用层,不会出现FPGA正在用PID算控制量,结果STM32把EEPROM读出来的旧值又灌进去的问题。
固件升级流:STM32通过Bootloader从串口或网口接收新的固件镜像,一边接收一边写入NOR Flash。写入完成后做整体校验,校验通过则设置启动标志,否则回滚到备份分区。FPGA的逻辑文件也是一样的流程,可以由STM32通过SPI直接写NOR Flash,也可以用FPGA的SelectMAP或JTAG接口写入独立分区,但前者省掉一个烧录器,实际项目中用得更多。
运行日志/采样数据流:FPGA内部的Block RAM存够一个批次的数据(比如8KB),发一个中断或者置位一个标志,STM32通过FSMC/FMC总线把数据块搬出来,加上时间戳和数据类型,写入SD卡的文件系统。这么做有三个好处:批量写入提高效率;STM32不必实时响应每一次采集中断,降低CPU占用率;万一掉电,丢失的只是FPGA的Block RAM里尚未转存的一小块缓冲数据,对运行日志的完整性影响可以接受。
硬件拓扑上,我习惯这样接:EEPROM挂在STM32的I2C1上,NOR Flash挂在STM32的SPI2上,SD卡用SDIO接口跑到四线模式(比SPI模式快不少)。FPGA与STM32之间用FSMC的16位并口连接,FPGA把自己模拟成STM32的一个外部存储设备,同时FPGA的同bank引脚和STM32的FMC引脚接在同一排总线扩展座上。如果是更复杂的控制器,也可以让FPGA直接通过SPI连NOR Flash,承担部分QSPI口的XIP启动功能,但这是后话,硬件篇里先按下不表。
这样分工之后,一个关键的好处是存储层级清晰:存储操作全部收口在STM32的驱动层,FPGA不用关心EEPROM的I2C时序,也不用关心SD卡的FAT表结构,它只负责把数据块放在约定的地址,发个握手信号。两边的调试边界一目了然,哪边出问题都很容易定位。
4. 电路设计的关键细节:上拉、写保护、去耦与电平匹配
有些朋友画板子的时候,存储部分照着芯片手册的典型原理图一抄就完了,实际上工业控制器跟开发板的差别就在这些"典型原理图没有告诉你的位置"。
4.1 EEPROM的I2C总线不是你接上就能用
I2C总线是开漏结构,必须要有上拉电阻才能工作。这个电阻的选择有讲究:太大(比如100kΩ),总线上升沿太慢,在400kHz快速模式下容易出时序问题;太小(比如1kΩ),灌电流偏大,总线电平可能达不到逻辑高电平标准。我常用的经验值是4.7kΩ,供电电压3.3V、总线上挂2~3个设备的时候非常稳。如果总线要穿过排线、连接器到另一块板,可以降到2.2kΩ增强驱动能力,同时把I2C速率降到100kHz标准模式。
EEPROM的地址引脚A0/A1/A2要预留上拉或下拉电阻位。很多批量生产的板子,如果确定只用一颗EEPROM,直接全部接地就行,但如果你做了多板堆叠、或者希望同一板卡可以插在不同的底板上通过地址来区分,就得把这三个引脚做成可配置的,PCB上留0欧电阻的焊盘,避免改版。
还有一个很多人忽略的——WP写保护引脚。EEPROM制造商会建议接GND禁用写保护,以方便在线写入。但工业控制器上,我始终建议WP引脚用一个GPIO来控制。正常情况下置高,禁止I2C写入;只有在需要更新参数的时候,STM32先把WP拉低、再执行写操作、写完立即拉高。这样做的好处是:一旦程序跑飞或者总线上出现异常波形,EEPROM的内容不会被误改。这种情况虽然概率不高,但在现场真的发生过——干扰脉冲打进I2C总线,同事的板子参数莫名其妙被改掉,排查了好几天。
4.2 NOR Flash引脚不能只看着JEDEC标准接
SPI NOR Flash一般有#WP(写保护)和#HOLD(保持)两个引脚。有些人觉得用不上,直接接VCC或者GND。以我的经验,这两根引脚都接一个对地100kΩ下拉电阻并拉到可控GPIO上更稳妥。#WP接低可以启用硬件写保护,防止bootloader程序跑飞时误擦除固件区域;#HOLD拉低会让Flash暂停响应,一般不用,但复位逻辑如果和它产生竞争,容易导致Flash卡在奇怪状态。把它们引出来,至少留个测试点,调试时会省很多事。
SPI的SCK速率在布局上也要注意。很多人觉得3.3V的SPI速率又不高,走线随意点没关系。但工业控制器的Flash擦写频繁,如果SCK数据线走线过长、过孔过多,反射造成的波形畸变会在高速模式下偶尔触发误读。我一般控制在40MHz以内,CLK线串一个33Ω电阻,DIO和DO尽量同一层、等长走线,切忌在flash底下铺地铜又不打地孔,高频回流不好很容易出偶发错误。
NOR Flash的供电去耦也有讲究。擦写时flash内部电荷泵会拉大电流,VCC引脚上我习惯放两个电容:一个100nF的靠近引脚滤高频,一个10μF的钽电容稍远一点给瞬态电流兜底。这个组合在批量板上实测,偶发擦除失败率明显比只放100nF的方案低。不能省的还有VCC的0欧或磁珠隔离——防止flash的电流波动通过电源轨干扰STM32的ADC参考源。
4.3 SD卡座子的那些脚,每一根都要有说法
SD卡如果走SDIO四线模式,原理图跟SPI模式差距很大。SDIO模式需要四个数据线(DAT0~DAT3)、一条时钟(CLK)、一条命令线(CMD)。这里最常见的问题是DAT3引脚的双重角色——它同时也是卡的探测脚(CD/DAT3)。很多参考设计里DAT3接了一个上拉电阻,同时把上拉电压引到卡检测逻辑上,但这样在卡未插入时DAT3是空的,初始化可能会失败。稳妥做法是:每个数据线都接47kΩ上拉到VCC,CLK和CMD同样上拉,插入检测引脚单独用一个10kΩ上拉到VCC并通过去抖RC网络接到STM32的GPIO上,卡片插入瞬间是机械抖动,如果不做RC滤波,系统会误判卡反复插拔。
SD卡座子的机械固定和ESD保护同样重要。工业环境静电多,人的手去插拔SD卡很容易把静电打进球壳里。我习惯在卡座的CLK和DAT线上加ESD二极管(比如USBLC6-2SC6多路集成件),或者至少加一串几十欧的串联电阻,再配合机壳接地设计,能挡掉大多数实际的ESD场景。SD卡座子本身也要选带锁扣的,工业设备震动是常态,卡松了会引起FAT文件系统崩溃,这种错误一旦发生,日志全废。
4.4 电平匹配和总线访问时序
STM32和FPGA都有各自的供电电压。STM32F4/F7一般3.3V,FPGA如果核心和IO分开供电,IO bank可以设成2.5V或3.3V。两者之间走FSMC并口总线时,一定要确认bank电压电平一致,或者加电平转换芯片。我曾经犯过一个错,FPGA的bank设成2.5V,STM32的FMC引脚用3.3V输出,正常通信似乎没大事,但频率一拉高或者温度一变,信号畸变就出来了,偶发读回数据错误,一度以为是代码问题,查了很久才发现是电平不匹配导致的信号劣化。
另一个是总线时序。STM32的FSMC对外部存储设备的访问时序是可以通过寄存器配置的——地址建立时间、数据建立时间、片选有效时间。FPGA模拟的存储接口,时序要求跟真实的SRAM可能略有差别,所以FSMC时序必须按FPGA的实际约束来调。我建议先跑一个简单的循环读测试,用示波器抓FSMC的地址线和读信号,对比FPGA输出的数据有效窗口,把ADDST和DATAST这两个参数调到余量最合适的位置。这个步骤值得花时间,它决定了后续所有数据交互的稳定性。
5. 驱动分层实现:时序、缓冲、状态机的配合
硬件设计定稿之后,真正的工程量在驱动层。我尽量把每个存储芯片的驱动做到标准可复用,这样后面项目换芯片型号,只改配置不改逻辑。
5.1 EEPROM驱动:页面写入和总线错误恢复
I2C EEPROM的页写入是重点。以Microchip的24LC256为例,它一页是64字节,I2C写入如果跨页,第二个地址会回绕到页首,把前面的数据覆盖掉。所以驱动里必须做页边界检测——每次写入前,先计算当前地址到页末尾的剩余字节数,取跟待写数据长度的较小值,分多次启动写操作。这是我的驱动里雷打不动的部分。
EEPROM的写入时间手册上写5ms,但工业级芯片在温度低、电压略低等条件下可能会更慢,驱动不能死等固定延时。正确做法是:写完一页后,检查ACK——EEPROM在内部写周期内不响应总线,直到写完成才回ACK。利用这个特性,写下一页之前反复发起始条件+设备地址,收到ACK再进行后续操作,没有就重试。我见过不少人在这一步偷懒用延时,结果批量生产时偶发写失败率能到千分之几,很烦人。
I2C总线错误恢复也值得说。工业环境中总线可能被干扰,SDA被拉死的情况真发生过——器件在写周期中突然断电或时序错乱,导致设备锁死了总线。驱动里要在初始化流程加一套总线恢复序列:把GPIO模拟成开漏模式,在SCL上连续发出至少9个时钟脉冲,同时确保SDA保持高电平,这样可以把任何卡在半途的从设备复位。之后再重新初始化I2C外设。这套恢复序列我放在上电自检里,一旦检测到错误就触发。
5.2 NOR Flash驱动:读、写、擦除、状态轮询
NOR Flash的驱动比EEPROM复杂,核心在三个操作:读ID、扇区擦除、页编程。读ID要发9F命令,返回制造商ID和设备ID,驱动初始化时一定要校验这三个字节,防止焊接错料或者芯片没贴好。批量生产时这个校验能拦截掉很多低级问题。
写入路径上,先发WREN(写使能),再发页编程命令和地址、数据。NOR Flash编程时,每次最多写一页(通常256字节),跨页同样要分多组。编程完成后,读状态寄存器,轮询WIP位直到变成0,才能继续下一步。我还会额外检查编程结果——对每个写入页做一次随机读回,比对数据,发现不一致就报错并触发重写。这一步在批量固件升级场景下尤其重要,NOR Flash内部电荷泵在极端温度、电压不稳定的情况下编程可能出错,读回校验收掉了80%的"固件升级后启动异常"类问题。
擦除操作要特别小心。擦除是不可逆的命令,发错地址就可能把程序区擦掉。所以我的驱动里有一层保护:写入操作前必须调用eflash_unlock()函数,用一把"软件锁"保护,只有明确要更新的区域才能挂锁;写完立即重新上锁。这把软件锁配合硬件#WP引脚拉低,双保险。做FPGA逻辑升级时,固件区和FPGA逻辑区物理上分开两个扇区组,交叉访问时必须先计算扇区编号,防止互相覆盖。
5.3 SD卡驱动与文件系统:缓存、对齐和异常处理
SD卡要跑FATFS文件系统,但是不能裸用。SD卡一个块是512字节,FATFS在文件写入时如果不做块对齐,小块频繁写入会产生大量的读-改-写操作,既慢又费磨损。我一般让应用层提供一块缓冲区,日志类数据攒到至少一个块大小(512字节)才写一次文件。实时性要求高的场景,也可以先用f_sync周期性同步,避免断电丢太多数据。
SD卡初始化顺序要规范:先发送CMD0进入SPI或SDIO模式,然后CMD8获取电压信息,CMD55+ACMD41反复查询OCR直到卡Ready,这样才能把卡拉到SDHC模式。很多初始化失败都是因为ACMD41的超时时间设太短,有些低速工业卡在启动时初始化可能耗几百毫秒甚至更多。我这边把ACMD41的重试上限设置成100次,每次等待10ms,实测下来各种厂牌卡都能过。
文件布局上,SD卡按功能分区是很有必要的。FATFS同一张卡上,我习惯建两个目录:/log存运行日志和采样数据,/conf存历史配置备份,同时给每个文件加序号和时间戳。每次控制器启动时,扫描目录找最新的文件序号,避免文件覆盖。另一个问题是FATFS的挂载和卸载时机:卡是热插拔的,驱动最好用一个任务周期检测CD引脚,如果卡被拔出,立即f_mount(NULL)卸载文件系统,防止文件系统缓存里挂着一堆脏数据,下次插入时FAT表已经乱了。日志文件的大小也要做管理,写到预设上限(比如32MB)就切换下一个文件,这样既方便导出,又能控制单文件损坏的影响范围。
5.4 FPGA侧的存储访问配合
FPGA侧的存储相关代码,主要还是围绕FMC总线接口和Block RAM缓冲。我把FPGA与STM32的交互接口设计成一个双口RAM结构:一侧是STM32通过FSMC访问的寄存器区,一侧是FPGA逻辑往里写采样数据的缓冲区。寄存器区里定义了两类寄存器,一类是只读的状态寄存器(如缓冲满标志、数据帧计数),一类是写控制的配置寄存器(如启动采集、批量清零)。
值得强调的是双口RAM的跨时钟域处理。FPGA内部采集时钟可能是几十兆甚至上百兆,STM32的FSMC读写频率相对低,两个时钟域交接处的信号至少要打两拍同步,避免亚稳态。用Block RAM本身是双口同步的,跨时钟域问题相对可控,但控制信号(比如缓冲满标志)必须明确在哪个时钟域产生、在哪个时钟域消费,这个要写清楚。FPGA侧写FMC接口的Verilog,本质上是一个带地址译码的存储从机模型,状态机就三个状态——IDLE、READ、WRITE,地址锁存后响应FSMC的读/写时序,并产生RDY信号给STM32做等待握手。具体到时序约束,把FMC总线信号约束进时序分析里,然后按setup/hold要求调FSMC的时序参数就行。
6. 掉电保护与数据可靠性:现场不出事才是真本事
很多板子实验室里跑没任何问题,一到现场就暴雷,九成原因出在掉电时序处理上。工业控制器掉电不像开发板那样"断个电而已",掉电过程可能持续几百毫秒,系统电压逐步下降,期间CPU和外设可能仍在运行,但供电已经处于极限状态。如果此时正在写EEPROM、NOR Flash或SD卡,要么写入不完整,要么彻底损坏数据。
6.1 掉电监测:要赶在CPU饿死之前动手
我的标准方案是在电源输入端加一颗电压监测芯片(比如TLC1543或者更常用的SGM809,配合一个分压电阻网络设定阈值)。监测芯片的输出接到STM32的一个外部中断引脚,当VCC跌破阈值(比如3.0V)时,立刻触发掉电中断。这个中断的优先级高于一切业务中断,ISR里要做的动作就一件:把关键数据紧急写入EEPROM,或者把正在写的NOR Flash/SD卡操作标记为终止。
这里有个关键前提——从掉电中断触发到CPU完全失去工作能力,中间的时间窗口决定了你能做多少事。中断触发阈值通常设定在3.0V,STM32最低工作电压是2.0V左右,中间有1V的余量,配合合理的电源电容(一般100μF级别),能争取到几十毫秒到上百毫秒的处理时间。写入EEPROM一个页要5ms,写一两个关键参数绰绰有余。但如果此时正在写SD卡,就不要再尝试新操作了,SD卡块写入本身就要几十毫秒,还要维护FAT表,窗口不够,强行写只会让文件系统更乱——这种情况应该立刻把写操作标记为中断,并在下次上电时统一修复。
6.2 掉电期间的电源保持:用一个电容争抢时间
电源保持电容的选择是一门经验活。电池太贵也没必要,MLCC(多层陶瓷电容)容量上限有限,我一般做法是:在5V输入的主电源轨上并一个470μF的电解电容,同时在3.3V LDO输出侧放一个100μF的钽电容。这样掉电瞬间,3.3V轨能维持更长的时间。实测下来,轻负载(只有MCU+FPGA待机)时能撑200ms以上,足够把关键参数写完。
还有一些要求高的项目,会加一个"超级电容"方案——用法拉级电容给掉电保存电路单独供电,甚至支持在掉电后把FPGA里的大块暂存数据全部导到SD卡。这是高端做法,成本和PCB面积都上去了,一般只有列车、电网这类项目才值得。普通工控设备,容量适中、逻辑简单、准备充分,已经能够覆盖绝大多数现场场景。
6.3 备份与恢复:掉电写入中途失败也不怕
就算加了监测和电容,也不能保证每次写入都完整。所以我给EEPROM和NOR Flash都设计了双备份区方案。以EEPROM为例,地址空间划分成两个参数区PARAM_A和PARAM_B,每次写入前先写备份区,写完再更新主区,并在EEPROM末尾的"有效标志"字节里标记哪一个是当前有效版本。上电启动时,驱动先读取两个区的CRC,选择有效且CRC正确的那个作为运行参数;如果两者都损坏,就使用编译时固件里内置的出厂默认参数,并记录一个"参数异常"事件。这套方案其实不复杂,但能救命——它把"掉电写入损坏"从"设备故障"降级为"自动恢复"。
NOR Flash的固件区我也用同样的双区思想:固件A区、固件B区,启动时读启动标志决定跑哪个区。升级流程是:先把新固件写到备用区(不动的那个区),全部写完、校验无误后,修改启动标志,再执行系统复位。即使升级到一半掉电,备用区写坏了,启动标志还是指向旧区,设备依然能正常启动。这套OTA安全方案在工控圈已经接近标配了,但在硬件篇里还是要强调:双区不只是软件概念,硬件上NOR Flash容量规划一开始就要往两倍去算。
6.4 磨损均衡和坏块管理:越写越稳的基本功
EEPROM按字节擦写虽然寿命长,但参数更新集中在固定地址也不行。我习惯把参数区的大小铺大一点,比如实际参数只需要200字节,我给EEPROM分配2KB,写入时把参数帧加上帧号、时间戳轮流写到不同偏移,每次启动时取帧号最新的有效帧。这样100万次寿命乘以10倍容量扩展,实际擦写寿命等效提升到1000万次级别,一般设备终身不用换芯片。
NOR Flash的磨损管理更重要,因为它的寿命本来就短(10万次擦写),如果某几个扇区频繁被当作日志区使用,很快报废。我的做法是把NOR Flash划分成三类:程序区、配置备份区、日志环形区。日志环形区固定几个备用扇区,写满一个就切换到下一个,全部写满再滚动擦除最早的——相当于一个环形缓冲。每个扇区写入前检查状态标志,发现擦写失败或有ECC错误,立刻把它标记为坏块,后续不再写入。虽然NOR Flash的坏块管理没有SD卡FTL层那么完善,但自己手动管理这几个扇区还是完全可控的。
6.5 SD卡层面的可靠性设计
SD卡最怕的是写入过程中掉电导致FAT表损坏。除了上面提到的缓冲对齐、卡拔出检测,我这里再补充三个措施:
一是写日志前先修改目录项。FATFS的f_open、f_write、f_sync这套流程,本质上是在改FAT表和目录项。如果掉电发生在修改FAT表的过程中,文件系统的结构就可能损坏。比较粗糙的解决方式是完全不用目录——直接把数据写到原始扇区,自己管理索引。这样做虽然绕开FAT,但读取和导出又很痛苦。实际项目我折中:长时间运行的日志文件用扩展名标记,启动时扫描目录项,发现文件名异常的条目就重建,而不是直接格式化。
二是定期做文件系统完整性检查。控制器空闲时,比如上电启动后、没有任务的时候,挂载文件系统后执行一次f_mount的检查逻辑,扫描关键FAT表项是否有逻辑错误,发现异常就尝试用备份FAT恢复。成本不高,但能拦截很多事故苗头。
三是写关键数据时用文件加锁标记。我在写SD卡目录里放一个全局状态文件,每次写日志前先写"开始写",写完同步后再写"写完成"。下电后重启,如果发现状态文件停在"开始写"半截,说明上次写日志没写完,软件自动放弃这个未完成文件,从下一个序号开始新的日志文件。这样做虽然会丢一小段(通常几十KB),但换来了整个文件系统的健康。
7. 实测数据与常见问题定位
讲逻辑讲原理都清楚了,最后放一些我实测过的一组数据和踩坑记录,方便对照。
7.1 实际写入耗时参考
以下数据来自我最近一个项目:STM32F407作为主控,FPGA是A7系列的ice40型号,EEPROM用24LC256,NOR Flash用W25Q64JV,SD卡是SLC工业级32GB。运行环境是24V供电、开关电源降压到5V再转3.3V。
| 操作 | 实测耗时 | 说明 |
|---|---|---|
| EEPROM写1页(64字节) | 5~6ms | 含地址设置和ACK等待 |
| NOR Flash写1页(256字节) | 0.8~1.2ms | 不含擦除时间 |
| NOR Flash擦除1个扇区(4KB) | 45~80ms | 受温度和电压影响明显 |
| NOR Flash整体写16MB固件 | 约90秒 | 含校验和擦除,不含回滚时间 |
| SD卡写512字节 | 0.3~0.5ms | 不含GC时间 |
| SD卡写32MB日志文件 | 约8秒 | 正常情况,遇到GC可能翻倍 |
| 掉电紧急写EEPROM(1页) | 6ms | 中断触发后实测 |
这些数据别当成硬指标,不同批次、不同电源质量下会浮动,但量级可以用来估算系统响应时间和掉电处理窗口。
7.2 常见故障及定位思路
故障一:控制器上电后参数偶尔丢失。先别急着怀疑EEPROM坏了。多数情况是上电时序问题——STM32的复位信号释放时,I2C外设还没完全配置好,EEPROM被一个意外的总线事务改写了。排查方法:示波器抓SDA和SCL,看复位释放后的100ms内有没有异常波形;同时检查WP引脚在上电期间是不是处于高阻态,高阻态下WP无效,总线上的异常信号能直接写入。解决办法:上电早期把WP拉到高电平,等驱动初始化完成、配置好总线之后,再拉低允许写。
故障二:NOR Flash偶发擦除失败,日志区数据错乱。大概率是供电问题。擦除时Flash内部电荷泵拉电流,如果VCC瞬间压降超过200mV,擦除时序会被破坏。先量Flash VCC引脚上的纹波,再检查去耦电容——之前就遇到过PCB布局把去耦电容放得太远、引线过长导致电感效应,换电容位置就稳定了。另一个因素是擦除时要避免其他外设在同一时刻从同一电源轨拉大电流,比如SD卡正在写、同时Flash在擦除,这种情况我可以在软件上错峰调度,用一个互斥锁管理存储总线。
故障三:SD卡文件系统损坏,插到电脑上提示需要格式化。绝大多数原因是掉电时正在写FAT表。排查和修复手段上面已经说了:备份目录、加锁标记、定期扫描。如果项目要更保险,可以换带掉电保护功能的工业级SD卡控制器,它们内部有更强的电容和固件管理,能扛住异常掉电;但成本高,大多数情况用软件手段已经可以在统计上把故障率降到可忽略。
故障四:FPGA和STM32的FSMC通信偶发数据错位。别盲目去调FSMC寄存器。先用示波器把FPGA侧的接口信号抓全,确认地址建立时间、数据保持时间是否满足FPGA的约束。常见问题是FSMC的NADV信号时序没对齐,导致FPGA内部的地址锁存器和写数据寄存器发生竞争。定位方法是在FPGA内部加一个计数器寄存器,STM32每次访问时读回来比对,如果偶发不正确,计数会不均匀,抓信号就能定位。解决办法通常是微调FSMC的ADDST和DATAST参数,必要时在FPGA接口的输入侧加上寄存器打拍,让信号有明确的建立窗口。
8. 这套方案还能怎么扩展:掉电日志区、远程升级与安全存储
文章最后,顺着硬件篇的思路聊聊这套分级存储方案后续能扩展的方向。如果一个项目做完了基础版本,想继续增强可靠性和功能性,可以从这几个位置入手。
一是独立掉电日志区。前面说掉电窗口内写EEPROM或SD卡都有风险,其实可以在NOR Flash里单独划一个小型环形日志区,专门存"掉电原因"和"最近操作记录"。每次掉电中断触发时,因为NOR Flash支持扇区擦除后在微秒级写入少量数据,写一条几十字节的事件记录比写EEPROM容量大,比写SD卡安全,是排查现场故障的利器。
二是把远程升级协议和安全存储结合。现在很多工业控制器要支持网口升级,固件镜像下载后先暂存在SD卡的升级目录里,校验通过后系统重启进入Bootloader,由Bootloader把镜像从SD卡搬到NOR Flash备用区。这套流程的好处是下载和写入分离,下载中断不影响当前运行系统;但要注意Bootloader访问SD卡的时间,启动时挂载SD卡如果卡太慢,可能拖慢整个启动时间,所以Bootloader里要有超时机制,超时直接跳到正常启动流程,让升级在下一次尝试时再来。
三是密钥和数据完整性保护。工业数据越来越讲究安全,EEPROM里存的校准数据、Factory flags,SD卡里的日志文件,如果担心被篡改,可以在写入时带上HMAC签名,启动时或者导出前验签。STM32的硬件CRC和内置加密引擎都能消化这个开销,但对硬件设计的影响是EEPROM容量、NOR Flash存储空间、SD卡日志格式都要预留签名域——这属于硬件规划时就要想清楚的问题。
这些扩展方向,我建议在项目架构设计初期就先留接口,宁可硬件上多留几个GPIO、多铺几个焊盘,别等板子定型了再往里面硬塞——工业控制器一旦进了现场,再想改板子,成本和风险都是几何级上升的。
存储这块儿没有银弹,用的不是最新、最贵、容量最大的芯片,而是最合适的芯片各司其职。EEPROM、NOR Flash、SD卡这三种介质,各管各的活,再配合掉电保护和磨损管理,这套分级存储方案,我在多个工业控制器项目里反复用过,稳定性和可维护性都经受住了现场考验。希望这篇硬件篇的内容对正在做同类设计的朋友有参考价值。