敢说自己做过工业控制器的人,大概率都吃过存储的亏。我见过不少现场事故:设备在产线上跑了三个月,一次断电,校准参数全部归零,客户直接拍桌子;还有的控制器,运行日志一直在写,等售后去现场拷数据,发现SD卡里全是乱码,时间戳错成一团。这些问题的根子不在程序逻辑,而在最容易被忽视的存储方案本身。
这篇文章我想把工业控制器里最常见的一套存储架构一次讲透:STM32做主控、FPGA做实时逻辑,数据分级落到EEPROM、NOR Flash和SD卡上。不是什么高深理论,就是实打实的设计思路、选型依据、电路细节和现场踩坑记录。这一篇是硬件篇的第12篇,前面聊过接口、电源、时钟,这次专门讲数据怎么存。
1. 为什么工业控制器必须用"分级存储"而不是一块芯片走天下
1.1 三类数据特征完全不同,单一介质根本接不住
很多初学者觉得存储嘛,无非就是Flash读读写写。但真把工业控制器的需求列一遍,你会发现完全不是一回事。
第一类数据是系统配置与标定参数。设备序列号、以太网MAC地址、IP配置、PID参数、模拟量校准系数、传感器零点、报警阈值,这些都属于这一类。容量需求很小,通常不到1KB,甚至几百字节就够了,但有两个硬性要求:掉电必须长期保存,而且用户调试时会反复修改。一台设备可能一天被调试人员改几十次参数,每次都要求可靠写入、立即生效。
第二类数据是固件与逻辑镜像。STM32的应用程序、FPGA的配置比特流,大小从几百KB到几十MB不等。这类数据写入频率极低,一年可能就升级一两次,但绝不能在升级时掉链子。一旦烧坏,设备直接变砖,产线停摆,这种风险对工业设备来说完全不能接受。
第三类数据是运行记录与采集数据。运行日志、报警历史、操作记录、过程曲线、故障快照,甚至高速采样波形。这类数据的特点恰好相反:容量需求巨大,几十MB到几十GB都可能;写入几乎是连续的;但单个数据点的可靠性要求相对宽松,丢几秒钟日志不影响大局。可是如果不能持续写入,前面记录的所有数据都白搭。
把这三类数据放在一起看,任何单块存储芯片都无能为力。EEPROM容量做不大,NOR Flash怕频繁擦写,SD卡则要面对可靠性和接口复杂度的问题。最务实的做法就是分级:让每种介质去干自己最擅长的事。
1.2 三种介质的"性格差异"决定了分工
这里要补一个基础知识,很多工程师其实分不清EEPROM和Flash的区别。
EEPROM(电可擦除可编程只读存储器)可以按字节擦写,写寿命通常在100万次以上,写入周期在几毫秒级别,非常适合"小数据+频繁改"的场景。但它的产能密度低、价格贵,做到几Mbit就顶天了。
NOR Flash则是按扇区擦除,典型扇区是4KB或64KB,擦除寿命通常标称10万次,读取速度快,支持XIP(片上执行),适合存固件。但它的擦除操作比EEPROM慢得多,一个4KB扇区擦除要几十到几百毫秒,而且对"频繁改写小数据"这件事很不友好——你改一个字节,也得先把整个扇区的数据搬到RAM、擦掉扇区、再写回去,这个开销在生产频繁参数修改的场景下不可接受。
至于SD卡,本质是一个带主控的小型NAND Flash存储系统,容量可以做到GB级,写速度可以到几十MB/s,内部还有磨损均衡算法。但它作为可移动设备,接口标准复杂,上电时序、卡识别、文件系统都有讲究。在工业环境里,SD卡的可靠性取决于主控芯片的质量和厂家的固件策略,劣质杂牌卡本身就是故障源。
这三兄弟放在一起,正好覆盖了容量、寿命、速度的三个极端。工业控制器的存储设计,本质就是数据特征和介质特性做匹配。
1.3 分级存储的总体架构
先给一张硬核的总览表,后面所有内容都围绕这张表展开:
| 存储介质 | 典型容量 | 写寿命 | 典型写入粒度 | 适合数据 |
|---|---|---|---|---|
| EEPROM | 2Kb ~ 1Mb | 100万次以上 | 字节/页 | 配置参数、标定值、序列号 |
| NOR Flash | 1MB ~ 64MB | 10万次 | 扇区/页 | FPGA配置、固件镜像、备份 |
| SD卡 | 1GB以上 | 取决于主控 | 块/文件 | 日志、历史数据、采集波形 |
这张表看起来简单,真正落地的时候,电路、时序、固件策略每一步都是坑。接下来我逐个拆解。
2. 选型逻辑:EEPROM、NOR Flash、SD 卡各自该扛什么活
2.1 EEPROM:小数据、高频改写、必须断电保持
EEPROM是我在早期项目中踩坑最少的介质,但也是被使用最不合理的介质。我见过有人拿一块4Mbit的EEPROM去存大量报文,原因只是"它支持字节写",这是完全用错了场景。
EEPROM的每个存储单元是浮栅晶体管,可以独立擦写,所以才有百万次级别的寿命。但百万次看起来多,如果设计时不做任何磨损保护,调试时频繁写同一个字节,几个月就会到寿命边界。我在实际项目中通常按下面几个原则来用:
- 只在EEPROM里放"必须断电保持且经常变化"的数据,别拿它当普通RAM用
- 用页写入而不是逐字节写。AT24C02的页是8字节,AT24C256的页是64字节,一次页写比多次单字节写更快,也减少总线交互
- 参数区做成循环缓冲区,例如在EEPROM里划分256个参数槽,每次更新写下一个槽,写完一圈再循环,这样写入地址均匀分布,等效寿命可以提高上百倍
另一个容易忽视的点是EEPROM的写周期。I2C写操作发出命令后,从机需要几毫秒完成内部写入,这时候总线上表现为不响应ACK,主机必须正确处理这个时序。高速调试时,如果不等待写周期就发下一条命令,数据全部写飞。
2.2 NOR Flash:固件镜像和 FPGA 配置的家
NOR Flash在我的方案里定位非常明确:存FPGA比特流和固件备份。工业控制器采用FPGA后,通常有两种启动方式:一是FPGA自己主动从NOR Flash加载配置,这叫主动串行模式;二是由主控处理器把配置数据送进去,叫被动模式。我的方案是"主动串行为主+SPI在线升级为辅",既保证上电快速启动,又让STM32可以通过SPI接口升级FPGA。
选NOR Flash芯片时,有几个参数需要重点看:扇区擦除时间、页编程时间、擦写次数、供电电压范围、工作温度范围。工业级产品建议选-40℃到85℃甚至105℃的规格,商业级Flash在高温环境下容易出现擦写失败或者数据保持时间变短的问题。
另外,NOR Flash的读速度很快,很多需要XIP的场合选NOR而不是NAND。FPGA的配置数据从NOR里读,本质是一个顺序流,时序要求不算高,但对供电纹波和信号完整性有要求,这部分放到电路设计里细讲。
2.3 SD 卡:日志记录与数据导出的移动仓库
SD卡在工业控制器里的角色很特殊:技术上它是个可靠的存储介质,但在实际工程里,它更像"外设"而不是"存储器"。原因很简单:可拆卸、有标准文件系统、适合现场取证。
用SD卡做日志存储,需要注意几个点:
- 板级可以用FATFS或者其他文件系统,日志文件定期轮转,比如按日生成、按大小回卷
- 写入尽量用比较大的块。SD卡对连续大块写入友好,对随机小块写入很吃力,日志缓冲区至少要512字节以上,更建议4KB到32KB
- 掉电保护:SD卡最怕写文件系统元数据时掉电,导致整个目录损坏。我通常会设计"日志文件先写到临时区,写完一个数据块后定期同步目录项",或者干脆用"分区循环写"的方式
选卡也是个学问。工业设备不要用消费级杂牌卡,廉价卡在振动、高温、频繁断电环境下,寿命和稳定性都很差。优先选工业级SLC卡,或者至少是大厂MLC的高耐久型号。
2.4 选型参数对照表
把三种介质放在一张表里,更直观:
| 属性 | EEPROM | NOR Flash | SD卡 |
|---|---|---|---|
| 接口 | I2C/SPI | SPI/QSPI | SDIO/SPI |
| 典型容量 | 2Kb ~ 4Mb | 4MB ~ 128MB | 4GB ~ 128GB |
| 擦写单位 | 字节/页 | 扇区 | 块/页 |
| 擦写寿命 | 100万次以上 | 10万次 | 主控相关 |
| 读取速度 | 慢(I2C) | 快 | 快 |
| 写入速度 | 慢 | 中等 | 快 |
| 温度范围 | 通常 -40~85℃ | 通常 -40~85/105℃ | 商业/工业分档 |
| 掉电保护 | 字节级原子写 | 需注意扇区管理 | 依赖文件系统 |
| 单位成本 | 高(按bit计) | 中(按bit计) | 低(按bit计) |
选型时先问自己三个问题:数据多大?多久改一次?掉电能不能接受丢一点?这三个问题的答案,基本会自然导向正确的介质。
3. STM32+FPGA 双芯架构下的存储分工与数据通路
3.1 为什么工业控制器爱用 STM32+FPGA 组合
STM32+FPGA不是炫技,而是工业控制器现实需求的结果。STM32这类MCU擅长跑协议栈、管理外设和做复杂逻辑判断,但IO数量、并行处理能力和实时性有限;FPGA擅长并行处理,一个周期内能完成高速计数、多路PWM输出、编码器解码、并行ADC采样这些任务。在电机控制、运动控制、视觉检测前处理这类场景,FPGA几乎是不可或缺的。
存储方案里双芯架构带来一个隐蔽的好处:可以把存储任务按"谁更合适"拆开。STM32管理与协议、参数、日志相关的存储,FPGA只管自己的配置启动。两者通过通用接口交互数据,存储层级天然分开,不用互相拖累。
3.2 存储资源分配方案:谁挂 EEPROM、谁挂 Flash
我给出一个经过验证的具体分配方案,供参考:
- EEPROM(比如AT24C256,256Kbit=32KB):挂STM32的I2C2,存放设备参数、校准数据、运行状态字、精简版故障记录
- NOR Flash(比如W25Q128,16MB):挂FPGA配置接口(AS模式)和STM32的SPI(升级时共享),存放FPGA比特流、STM32固件备份、出厂参数备份
- SD卡(比如工业级32GB):挂STM32的SDIO接口(4bit模式),存放日志、报警、采集数据、配方文件、远程升级镜像
为什么EEPROM不挂在FPGA侧?因为FPGA对参数管理没有意义,FPGA只吃配置流。为什么SD卡不挂FPGA侧?因为文件系统本来就跑在STM32上,FPGA不跑OS,访问SD卡没有优势。
3.3 数据访问路径与总线仲裁
正常运行时的访问路径可以这样理解:
- 上电:FPGA主动从NOR Flash加载配置;STM32从内部Flash启动,同时读取EEPROM参数区做校验
- 运行:STM32周期性把缓存数据合并写入SD卡;用户修改参数时,STM32先写EEPROM做快速持久化
- 升级:STM32通过SPI写NOR Flash,用远程镜像覆盖升级区,然后触发FPGA软复位重新加载
这里最关键的坑是:STM32和FPGA都访问NOR Flash时,必须有仲裁机制。我的做法是:Flash的片选信号经过一个总线开关控制,STM32访问Flash时,先把FPGA侧的配置控制信号隔离或拉低,避免两个主机同时驱动SPI总线。
如果Flash始终挂在FPGA的配置引脚上,而STM32又要在线升级,就得考虑FPGA是否正在读配置。调试时可以在Flash的CS脚上加一个缓冲器,用STM32的GPIO控制使能,这个细节能在现场省很多事。
4. 关键电路设计与实操细节
4.1 EEPROM 的 I2C 电路:上拉、地址和 WP 引脚
EEPROM的电路看起来简单,问题都在细节上。以AT24C256为例,I2C接口需要:
- I2C总线必须接上拉电阻,典型4.7kΩ。如果总线上挂的器件多或者走线长,可以降到2.2kΩ。我见过有人忘记加上拉,结果总线全是毛刺,通信随机失败
- 地址引脚A0/A1/A2按需配置,AT24C256的地址是0x50到0x57。设计时最好留出跳线或0欧电阻焊盘,方便多板卡复用或者避开地址冲突
- WP引脚接GPIO。正常工作时拉高,禁止写入;升级时拉低,允许写入。这能防止程序跑飞时意外改写参数,是工业可靠性的重要一环。
供电方面,如果EEPROM是3.3V供电,MCU也是3.3V,直接连没问题。如果MCU的IO是5V,就要加限流电阻或者做电平转换。工业现场5V的MCU并不少见,这个坑我也踩过。
4.2 NOR Flash 与 FPGA 配置电路:模式选择和在线升级
FPGA配置Flash的接线比较讲究。以SPI接口的NOR Flash为例,FPGA的配置引脚要对应Flash的MISO、MOSI、CS、SCLK。如果采用AS模式,FPGA内部会产生配置时钟,Flash的数据在时钟采样点被读取。
实际设计中有几个细节需要关注:
- 配置时钟频率不要超过Flash的最高工作频率,FPGA配置时序的要求要查数据手册确认
- Flash的WP#和HOLD#引脚要处理干净,上拉到VCC或者加RC,防止悬空导致误触发写保护或暂停
- 如果STM32需要在线升级FPGA配置,Flash的CS就不能直接连FPGA的固定引脚,得用IO切换或者总线开关来控制
在线升级"烧到一半掉电"的问题是工业现场的噩梦。推荐做双镜像区:A区和B区各存一份合法比特流,升级时写B区并校验CRC,校验通过后切换启动标志,下次启动FPGA从B区加载。这样即使升级中途掉电,A区依然可用,设备不会变砖。
4.3 SD 卡接口设计:电平、走线和供电
SD卡的接口设计有几个容易翻车的地方:
- 电平:标准SD卡接口是3.3V,主控IO也是3.3V时直接连接。如果主控是5V,必须电平转换
- SDIO走线:4bit数据线加DCLK加CMD,数据线要求尽量等长,注意阻抗和驱动强度,过长的走线会导致高速模式初始化失败
- 上拉电阻:CMD和数据线需要弱上拉,有些MCU内部带了,外部可以加10kΩ到47kΩ,保证卡在初始化阶段稳定
- CD(卡检测)引脚和写保护开关:用GPIO读取,检测SD卡是否插入,避免在无卡时执行文件系统操作导致死机
还有一个很意外的坑:SD卡在大容量写入时,供电电流可以达到100mA以上。如果板子上用LDO直接给SD卡供电,瞬间压降可能拉低整个3.3V电压,需要加足够的滤波电容(10μF钽电容加100nF陶瓷),或者用独立电源单独给卡供电。
4.4 电源与掉电保护:掉电检测和储能电容
工业控制器掉电是常态,存储设计必须正视这件事。我的做法是在电源入口放置掉电检测电路:用电阻分压加比较器或者ADC监控24V母线,当电压低于某个阈值(比如18V)时触发一个PVD/EXTI中断。
掉电中断触发后,MCU的紧急任务只有一件:把RAM里的关键参数写入EEPROM,然后把日志缓存的尾部数据追加到SD卡,如果时间和电源还够。时间窗口大概只有几十到几百毫秒,取决于板子上储能电容的容量。
设计时可以用超级电容(0.5F到1F)在掉电后维持3.3V输出几百毫秒。EEPROM掉电写入的场景下,I2C速度400kHz,写128字节加等待写周期大约8到15ms,完全来得及。关键是初始化时就要规划好"紧急存储区",而不是掉电时再遍历RAM找数据。
5. 固件层的支撑策略:写入、校验和掉电恢复
5.1 写入策略与磨损均衡
硬件只是载体,固件策略才是可靠性的灵魂。EEPROM的磨损均衡,核心做法是"循环缓冲加版本号"。
具体实现思路:把EEPROM某个区域划分为N个槽位,每个槽位存一份参数副本加序列号加CRC。写入时找序列号最大的槽位的下一个槽位写,N个槽位循环使用。读取时遍历所有槽位,取序列号最大且CRC校验通过的那份。这样寿命可以扩大到N倍,而且天然支持回滚——如果最新槽位CRC出错,自动读取前一个。
可以用下面的结构体来组织参数槽:
typedef struct { uint8_t slot_id; // 槽位编号 uint32_t seq; // 写入序列号 uint16_t crc16; // 整槽校验 uint8_t param[64]; // 实际参数数据 } ParamSlot;NOR Flash侧,双镜像方案也算是一种磨损均衡,因为升级率不高,寿命压力不大,但可靠性要求极高。
SD卡侧的磨损主要是文件策略问题:日志按日期分文件、限制单文件大小、定期清理旧数据。FATFS在嵌入式环境里最常见,如果日志量很大,也可以考虑底层的扇区级日志设计,但这个实现复杂度会上一个台阶。
5.2 数据完整性与双备份机制
存储的可靠性有一条铁律:单体数据不能作为唯一真值。参数区必须是双份甚至三份冗余,掉电保护的第一原则是"即使写坏了也不会丢旧数据"。
具体做法:
- 参数区:双份冗余加CRC16校验,启动时对比两份,CRC不一致时以另一份为准,并自动修复损坏的那份
- FPGA配置Flash:双镜像,启动标志区记录当前有效镜像号
- SD卡日志:周期性fsync,确保关键记录落盘,对掉电丢失窗口设置一个容忍上限
我的经验是:校验一定要用CRC32而不是简单的求和。纯校验和在单字节翻转时可能漏检,工业现场电磁干扰很容易造成多位翻转。CRC32虽然计算量大一点,对MCU来说也就几毫秒的事,完全值得。
5.3 标准的掉电保护流程
一个标准掉电存储流程应该是这样的:
- 检测到电源跌落中断
- 关闭非必要外设中断,进入紧急存储模式
- 把参数缓存区的"脏数据"写入EEPROM紧急区
- 把日志缓冲区的尾部数据追加写入SD卡
- 电源完全断开前,所有外设进入低功耗或复位状态
这个过程里还要注意一个细节:掉电检测必须加迟滞,否则电源抖动会造成反复进入中断。比较器可以加迟滞电阻,MCU内部的PVD本身有滞回特性,这个要活用。
6. 现场问题排查:三块存储的常见故障实录
6.1 EEPROM 数据丢失和 I2C 卡死
故障一:参数写进去,重启后全丢。 排查思路:先查写入是否真的成功,读回验证;再查WP引脚是不是被拉高;最后查上拉电阻和总线时序。我遇到最多的情况其实是初始化顺序问题——写EEPROM时I2C外设还没完全就绪,代码把时序带错了。
故障二:写EEPROM死等。 典型原因是硬件上没有正确处理NACK响应,或者I2C总线被某个设备卡死。解决办法是给I2C访问加超时,并允许总线错误恢复,这让现场排查轻松很多。
6.2 NOR Flash 加载失败和升级变砖
故障一:FPGA上电后无法加载。 先量Flash供电和CS信号,看FPGA的状态引脚。再检查配置模式跳线是否和固件一致。用示波器抓配置时钟,确认FPGA确实在发起读取。
故障二:在线升级后FPGA加载失败。 多半是双镜像标志写错或者擦写中断。恢复手段是用独立烧录器重写,或者从备用区恢复到主区。我还遇到过Flash型号差异导致配置时序不匹配的情况,换回原型号就好了。
6.3 SD 卡挂载失败和日志损坏
故障一:初始化失败,提示"SD card not mount"。 先换一张卡测试,排除卡本身问题;再查供电电压和CD引脚检测;然后检查SDIO时钟频率是否过高,有些卡不支持高速模式,降低时钟频率往往能解决。
故障二:写入日志一段时间后文件损坏。 通常是文件系统在掉电时没有同步。可以增加掉电检测后主动同步的流程,或者每次写日志时小批量地同步目录项。如果还不能解决,就考虑预留冗余分区。
故障三:写入速度越来越慢。 可能原因包括用了SPI模式而不是SDIO模式、日志缓冲太小、或者卡本身写速不行。先用速度测试程序测一下卡的裸写速度,再做针对性优化。
6.4 常见故障速查表
最后整理一份速查表,方便现场排查:
| 现象 | 可能原因 | 排查动作 |
|---|---|---|
| EEPROM数据重启丢失 | WP脚被拉高、上拉缺失 | 检查WP控制、读回验证 |
| I2C总线卡死 | 从机无ACK、总线冲突 | 加超时、查上拉电阻 |
| FPGA无法配置 | Flash接线错误、模式引脚错误 | 查配置引脚电平与时钟 |
| 升级后FPGA变砖 | 双镜像标志错误、镜像CRC失败 | 用烧录器重写、检查CRC |
| SD卡挂载失败 | 供电不足、接触不良、时序不对 | 换卡、量电压波形、降时钟 |
| 日志频繁损坏 | 掉电未同步文件元数据 | 增加fsync、加超级电容 |
做控制器这些年,我最大的体会是:存储方案的选择不是"哪个好"的问题,而是"哪个在你的可靠性预算内"的问题。EEPROM虽然寿命久,但你不做磨损均衡照样会挂;NOR Flash虽然稳定,但升级方案不做镜像一样会跪;SD卡容量大,但不管理文件系统掉电照样坏。硬件给了可能性,固件和策略兜底。
最后一个建议:如果你在设计初期就把存储分区做出来,调试阶段能省一半时间。别等样机出来了再补存储逻辑,那样你会像我当年一样,蹲在产线边上改代码改到怀疑人生。