尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

STM32+FPGA工业控制器三级存储架构:EEPROM、NOR Flash与SD卡分工与掉电保护实践

STM32+FPGA工业控制器三级存储架构:EEPROM、NOR Flash与SD卡分工与掉电保护实践 凌晨两点的调试现场值班同事打来电话网关重启后配置参数全部丢光。那台设备用的是当时最省事的方案一块外部NOR Flash所有数据往里怼。结果呢重启丢配置、日志写坏、Flash磨损到坏块各种问题拖了整整两周。后来整套存储方案推倒重来改成STM32FPGA架构下的三级存储EEPROM放小参数NOR Flash放固件和关键镜像SD卡放大批量日志。这个组合运行到现在一年多再没出过同类问题。这篇是硬件篇第12篇聊的是工业控制器里数据到底怎么存以及我基于STM32FPGA做分级存储方案的完整思路。适合正在做嵌入式/工控项目的工程师尤其是那些手头有个现场设备配置数据老丢、日志越写越乱、掉电后恢复不了的朋友。看完你可以直接照抄这套架构也能避开我踩过的坑。1. 为什么工业控制器需要三级存储而不是“一块Flash装完”1.1 掉电不丢只是起点真正的门槛是寿命、写原子性和速度很多人一开始的思路很简单选一颗容量大点的Flash什么都往里扔不就行了只要容量够不就完事了听起来没错但工业控制器和手机、开发板的差异很大。手机里存储掉电丢数据顶多是重启慢一点工业控制器掉电丢配置轻则校准数据全没重则整个产线停工。真正的门槛有三个写入寿命、写原子性、写速度。寿命上常用NOR Flash的扇区擦除寿命一般标10万次EEPROM则可以到100万次甚至更高。原子性指的是“一次写入要么成功要么不写”。NOR Flash按扇区先擦后写掉电擦到一半数据就处于不确定状态下次上电读回来可能是0xFF、半旧半新或者CRC校验失败。EEPROM按字节写原子性好很多但容量又太小。至于SD卡容量和速度都很理想可它最怕掉电时正好在改FAT表一旦FAT损坏整张卡都难读。1.2 先给数据分类再谈存储器选型我把工业控制器的数据分成四类这也是整套分级方案的底层逻辑。第一类是出厂配置和标定参数。比如设备地址、波特率、PID参数、传感器校准系数。这类数据量很小可能几十到几百字节但改动频率极低可能一年改一次唯一要求是掉电绝对不能丢。就算Flash主控芯片彻底跑飞也不能把出厂校准值弄丢。第二类是运行参数和故障快照。设备运行时需要周期保存当前状态比如最近一次故障时刻的电压、电流、温度。这类数据量中等写频率看工况几分钟到几小时一次要求掉电后至少最近一次快照能恢复。第三类是过程日志和历史数据。控制器每隔几秒或几十毫秒记录一条事件一跑就是几个月。这类数据量和写频率都很高但允许偶发丢失最后一小段比如掉电前的几百毫秒日志没存上可以接受。第四类是固件代码和FPGA bitstream。固件升级时需要暂存新版本FPGA上电时需要加载配置。这类数据容量最大对读取速度要求也最严。这四类数据特性完全不一样。如果全塞一块NOR Flash里容量小的存不下日志写频率高的磨损又太快固件升级时还容易覆盖到配置区。强行堆到一张SD卡里参数老丢且启动太慢。所以分级不是炫技是需求逼出来的。1.3 分级存储的账其实很好算从成本和可靠性角度也算得过来。EEPROM按颗算非常便宜但容量也就几百KB到头当参数区很合适。NOR Flash每MB价格稍贵但它能XIP直接执行挂FPGA侧当启动镜像最理想。SD卡按每MB算最便宜容量能做到几百GB用来扛日志这种“海量但没那么关键”的数据正合适。中间第二类数据稍微灵活可以放EEPROM也可以放NOR取决于现场掉电保护能力。我在最终方案里把“故障快照”放在EEPROM因为掉电瞬间只做这一件事来得及把“周期性运行参数”放NOR后台低速写。这样每种介质的数据量和写频率都踩在最舒服的点位上整体可靠性和寿命都能拉满。2. EEPROM、NOR Flash、SD卡三种介质的定位与硬指标对比2.1 核心参数横向对比选型前先看数据手册上的硬指标。我做了个项目组内常用的对照表这里简化后拿给你参数EEPROMNOR FlashSD卡典型容量16Kb~4Mb4MB~128MB256MB~1TB最小写单位1字节先擦后写扇区4KB/32KB/64KB逻辑块通常512B编程方式页写/字节写Page Program一般256B一页单块写内部FTL负责映射典型擦写寿命100万次10万次按扇区由内部FTL做磨损均衡寿命与容量成正比读速度几百KB/sI2C/SPI单线SPI几MB/sQSPI可达几十MB/sSDIO 4bit能到几十MB/s掉电风险低字节写本身原子性较好扇区擦写中途掉电会遗留不确定状态可能损坏FAT/目录区索引丢失典型用途参数、校准、配置固件、FPGA bitstream、启动镜像日志、历史数据、升级包暂存很多人对EEPROM的100万次寿命没概念。我算过一笔账一台控制器每5分钟保存一次512字节的运行参数到EEPROM一天就是288次按100万次寿命算理论能写9.5年。看起来还不错对吧但如果把保存频率改成每1分钟一次一天1440次理论寿命直接掉到1.9年。所以即便是EEPROM也不能玩命高频写。这也是为什么我把“周期运行参数”放NOR后台慢写而不用EEPROM死扛。2.2 选型顺序从数据量和系统架构反向推我的选型顺序永远是“从SD卡倒着往前推”而不是先看EEPROM。原因很简单SD卡的容量、接口和文件系统决定了主控芯片SDMMC的资源占用和整体系统复杂度这属于最重的决策。先确定日志量级才能确定要不要上大容量SD卡、要不要上FatFS、MCU引脚够不够、DMA通道怎么分配。第二步定NOR Flash容量。NOR主要用来放FPGA bitstream、MCU固件和升级镜像。FPGA的bitstream根据逻辑资源量差别很大比如Spartan-6级别的写出来可能几MB到十几MB加上MCU固件选一颗16MB的SPI NOR很稳。如果你用的是QSPI接口还能顺便享受内存映射模式CPU直接读Flash像读内存一样速度快到不需要搬数据。最后才是EEPROM大小。参数区最好留出两倍空间做双备份比如需要存512字节配置就选1KB以上的EEPROM留出AB区和坏区余量。容量没差多少钱但可靠性差很多。2.3 工业级和消费级千万别混用选型时还有一个容易踩的坑温度范围和供应周期。消费级SD卡或Flash的工作温度一般是0~70℃工业级是-40~85℃甚至105℃。工业控制器装在机柜、户外、甚至振动环境里夏天机柜内温度轻松到60℃以上冬天北方户外可能零下30℃消费级器件在这种环境下的可靠性完全是玄学。还有一点是供应稳定。工业设备出厂后可能要服务十年用一颗随时可能停产更换的消费级主控Flash后期维护成本会非常被动。所以NOR Flash我一般优先选Winbond、Macronix、Micron这类长期供货的工业级型号SD卡则选带工业级标识的microSD虽然贵一点但省下来的维护时间和出差成本远不止这点差价。3. STM32FPGA的数据通道分工读写、总线和仲裁3.1 一条清晰的数据通路分工STM32和FPGA在一个系统里首先要回答到底谁管哪颗存储芯片这里有两种常见接法。第一种是STM32管EEPROM和SD卡FPGA只管NOR Flash。第二种是FPGA统一管理NOR和SDSTM32通过内部总线访问所有存储。我实际采用的是第一种思路的变体EEPROM挂在STM32的I2C总线上SD卡走STM32的SDMMC接口NOR Flash直接挂在FPGA的QSPI控制器下。为什么这么分原因很实际。FPGA上电时首先要加载自己的bitstream而bitstream通常就放在NOR Flash里所以让FPGA直接控制NOR Flash最顺省去STM32先启动再搬数据的环节。EEPROM是纯参数存储STM32处理I2C最方便代码也简单。SD卡走SDMMC才能跑出高速读写不然日志写太慢会影响主流程。STM32和FPGA之间用一组并行数据总线加控制信号通信简单、延迟低。STM32想读NOR某个地址时只要往FPGA的寄存器里写请求FPGA收到后自行完成SPI读操作再把数据通过并行总线回传。这样STM32不需要自己操作NOR时序也避免了两个主控直接抢一条SPI线的混乱。3.2 FPGA内部实现NOR Flash读写的状态机思路FPGA侧做NOR控制器核心是一个SPI Master状态机加上Flash命令序列。工业上最常见的SPI NOR Flash命令集大同小异读ID用0x9F写使能用0x06读数据用0x03页编程用0x02扇区擦除用0x20读状态寄存器用0x05。FPGA内部只要把这些基础命令封装成状态机就能覆盖绝大多数型号。简化状态机的跳转顺序大概是这样的读ID拉低CS → 发9F命令 → 连续读3字节 → 拉高CS 写使能拉低CS → 发06命令 → 拉高CS 扇区擦除写使能 → 拉低CS → 发20命令 3字节扇区地址 → 拉高CS → 轮询状态寄存器直到BUSY位清0 页编程写使能 → 拉低CS → 发02命令 3字节地址 最多256字节数据 → 拉高CS → 轮询BUSY实际写Verilog时需要严格按Datasheet的建立时间和保持时间控CS、SCK、MOSI。这里最容易出问题的是Testbench。很多人写FPGA仿真时只给一个写命令然后直接等BUSY结束结果上板就跑飞。正确的做法是在Testbench里模拟Flash模型比如读ID命令要回一个型号ID扇区擦除要置BUSY状态若干周期再清0页编程要检查地址和写入长度。只有仿真把正常流程和异常流程都覆盖到上板才敢说稳。3.3 片选与总线仲裁两个主控抢Flash有多要命NOR Flash挂在FPGA侧会不会被STM32同时访问会。系统上电后STM32要读固件版本、校准信息FPGA要加载bitstream两边几乎同时想要操作Flash。我第一版测试时没做仲裁结果某次调试中STM32发起读取FPGA正好在擦除一个扇区两边同时把CS拉低Flash内部状态机彻底乱掉连读ID都没响应只能断电重启。从那之后我加了一个“总线占用锁”寄存器结构很简单STM32想访问NOR时先通过并行总线向FPGA发读请求FPGA收到后如果自己当前没在操作Flash就回一个ACK置位STM32得到ACK后才能拉低CS发起SPI命令。FPGA写完或读完再释放这个锁。如果FPGA自己正在烧写bitstreamSTM32就得等待。两个主控之间永远只有一方握有Flash总线控制权。这种仲裁设计有个关键点必须加超时看门狗。如果STM32发出请求后FPGA一直不响应不能无限等死。我在FPGA里加了一个毫秒级计数超时直接释放CS并给STM32报错误。这个做法让系统从“偶尔死锁”变成了“可恢复故障”对现场设备来说区别很大。4. 读写实现与掉电保持细节比选型更决定成败4.1 STM32侧读写EEPROMI2C地址分页页写缓冲千万别跨过EEPROM读写看似简单坑全在细节。比如AT24C32这颗常见的I2C EEPROM硬件地址是0x507位地址具体由A0/A1/A2引脚决定。芯片内部页写缓冲通常只有32字节这正是最容易翻车的地方。很多人写I2C驱动时一次性往EEPROM里写64字节结果发现只写成功前32字节后面的数据跑到了别的地址上。原因就是跨过了页写边界缓冲区回卷覆盖了页内前面的数据。正确做法是按页切分写入伪代码大概是int eeprom_write_bytes(uint16_t dev_addr, uint16_t mem_addr, uint8_t *buf, uint16_t len) { while (len 0) { uint16_t page_remain EEPROM_PAGE_SIZE - (mem_addr % EEPROM_PAGE_SIZE); uint16_t chunk (len page_remain) ? page_remain : len; // 发送起始位设备地址寄存器地址 // 然后连续输出 chunk 个字节 i2c_start(dev_addr); i2c_write(mem_addr 8); i2c_write(mem_addr 0xFF); for (uint16_t i 0; i chunk; i) { i2c_write(buf[i]); } i2c_stop(); delay_ms(5); // 等待内部写周期 mem_addr chunk; buf chunk; len - chunk; } return 0; }每写完一页必须等内部的写周期结束。我踩过的一个类似“delay函数卡死”的问题就是在等待写周期时死等某个标志位结果I2C外设进入了异常状态卡在循环里出不来。后来所有I2C等待都加了超时计数超时后重新初始化外设再也不会死锁。STM32的I2C时钟树也值得说一句。I2C的SCL频率取决于APB1总线时钟和当前分频配置如果你的系统主频和APB1分频没理清楚SCL可能跑到1MHz以上EEPROM直接罢工。所以我一般把SCL目标设在400kHz以内并且用示波器实测过波形再继续往下做。4.2 FPGA侧实现NOR Flash擦写别忘了先擦后写和轮询BUSYNOR Flash的页面编程一次最多256字节而且写之前必须先对所在扇区进行擦除。很多人第一次调NOR时总觉得Flash“写不进去”其实就是没做擦除或者擦除后没等BUSY清0就急着写。FPGA侧处理时序时要注意页编程命令发出后Flash内部写操作需要时间通常几个ms到几十个ms。这期间CS必须保持高电平不能再发任何命令。我们通过轮询读状态寄存器判断第0位是否为0。状态寄存器读到的值中忙标志为1表示还在忙为0才表示空闲。擦除更费时间一个4KB扇区通常需要几十到几百ms。掉电瞬间擦到一半整个扇区数据就不可信了。所以我对NOR Flash的写入策略是只允许后台正常运行时写掉电中断里绝不碰NOR。这样即使掉电也能保证掉电前已完成写入的数据是完整的未完成的顶多留一个旧版本。4.3 SD卡日志文件系统与裸写两种路线怎么选SD卡存日志有两条路线裸写扇区和文件系统。很多人一上来就选FatFS图的是方便拔出SD卡直接能读。但工业场景里日志数据经常是高频长时间写入FAT表非常容易因掉电损坏。我一开始也是FatFS直接上后来连续几台设备出现了“SD卡里日志全没”的问题排查到最后都是掉电时FAT表写一半导致的。所以我的建议是分场景处理。数据量不大一个月不超过几百MB且允许拔卡读取的用FatFS但每次写完必须调f_sync强制落盘日志文件按天新建。数据量极大且更看重可靠性的直接裸写固定扇区自己维护一个环形缓冲区记录“下一个扇区号”每条日志加CRC32。这样即使掉电最多丢最后一条不会整卡报废。顺带一提很多人在SD卡调试时还遇到过“内部寄存器锁死”这种怪问题表现为卡完全没反应、CMD0都不响应。这往往不是卡坏了而是主机上电初始化序列不对这个我在第五章专门展开说。4.4 掉电检测与“抢救窗口”的设计掉电保护是整个存储方案的灵魂。硬件上我用STM32内部的PVD可编程电压检测器或者外部电压监控芯片当电源电压掉到阈值以下时触发中断。这个中断优先级最高掉电瞬间它会打断一切正在执行的任务。在这个中断里只干三件事关掉其它中断、把待存参数从RAM输出到EEPROM、等待写完成并置标志。不能做任何耗时长的NOR擦写也不要去改SD卡。时间预算上做个估算EEPROM写一页32字节大约需要5ms如果待存参数有512字节需要写16页总时间约80ms。为了留足余量我在电源输入端加了470uF的大电容实测掉电后能维持150ms左右足够完成所有关键参数写入。这个“抢救窗口”的价值在哪里它保证了掉电瞬间系统可以完成最后一次“状态保存”。下次上电时程序首先检查保存标志如果标志表明上次保存完整就直接恢复如果不完整则从双备份参数区的另一区恢复。这套机制跑下来在现场几乎没再遇到过参数丢失。4.5 双备份、CRC和磨损均衡参数区的最后一道防线即便有掉电抢救窗口也不能排除EEPROM自身坏块或写入错误所以参数区一定要做双备份。我把EEPROM分成A、B两区每区存一份完整配置开头放标识和CRC32。启动时先看A区校验是否通过不通过就看B区两个都坏才进出厂默认。这样单区损坏不会影响系统可用性。磨损均衡同样重要。EEPROM虽然寿命高但如果某几个固定地址反复写那几个字节先到寿命极限整个芯片就得退役。我在项目中做了一个简单的“槽位轮转”配置区划分多个槽位写新值时不覆盖当前槽而是写到下一个槽然后更新一个“当前槽号”指针。这样写次数被平均摊到了整个参数区。NOR Flash侧如果有频繁更新固件或配置的需求也建议做AB镜像升级先擦B区、写B区、校验B区、再切换启动地址。掉电时最多留下一个未完成的B区A区还能继续干活升级失败也能自动回滚。5. 我踩过的坑从SD卡锁死到“掉电全丢”5.1 掉电瞬间配置全丢不是硬件问题是时序执行顺序问题那个凌晨的电话不是偶然第一次现场测试时我们的设备就有偶发的配置丢失。排查链路是这样的先怀疑EEPROM电路问题示波器抓写时序发现正常再怀疑I2C初始化问题加打印确认没有异常最后才把目光放到掉电时序上。实测发现掉电瞬间PVD中断确实触发了但在中断处理函数里我们居然还想着“顺便”往NOR Flash写一条故障日志。NOR Flash一个扇区擦除要几十毫秒电源只撑了不到30ms结果擦除没完成Flash内部状态机停在半擦状态。下次上电后读该扇区数据全是0xFF配置自然就“丢”了。修复方案就是我在4.4节说的掉电中断里只允许写EEPROMNOR和SD一律不动。掉电前没写完的日志等下次上电后由启动任务补写数据晚一点出现完全可以接受。改完之后我又做了1000次随机断电测试每次都读回CRC校验一遍过。5.2 SD卡内部寄存器锁死与恢复SD卡锁死这个问题我第一次遇到的时候差点以为是卡坏了。现象很典型上电后发送CMD0没有响应卡一直返回0xFF。换了几种初始化代码都不行。排查后发现两个问题。第一SD卡上电后需要至少74个时钟周期的稳定时钟有些卡对首次时钟要求更严格。如果MCU刚初始化完SDMMC外设就立刻发命令没有留足时钟卡可能根本没进入SPI/SD模式。第二CS拉高到把卡识别为新的传输会话之间要留足够时间不然卡的内部状态机还停在上一轮命令的某个状态里。恢复手段其实很简单彻底断电等SD卡供电完全消失再重新上电按标准初始化流程走一遍。如果这一步做了卡还是没反应就用读卡器插到PC上重新做分区和格式化。这和“拿手机根目录授权”那类消费级调试问题完全不同MCU侧没有权限系统卡能不能用完全取决于上电时序和命令序列。后来我在硬件上重点处理了SD卡电源的滤波加了一个RC滤波和上电时序控制确保5V转3.3V后电源稳定上升卡检测脚和写保护脚也加了默认上拉这个问题再没出现过。5.3 NOR Flash“老化了才开始坏”的陷阱另一台设备跑了将近一年开始偶发启动失败查到最后是NOR Flash固定地址反复写擦写寿命耗尽。原因说出来很简单我们把“当前最后一次运行温度”这种半小时更新一次的变量直接放NOR固定地址每次更新前擦除整个4KB扇区等于写一次放大8倍磨损。一年下来这个扇区擦了几万次接近寿命极限数据开始不稳定。这片位置后来改成了EEPROM存储并在代码里把所有高频变值挪出NOR固定区。教训就是用NOR Flash做数据存储CRITICAL的地址一定要做磨损均衡不能图省事固定在一个地址疯狂写。磨损均衡的实现可以很简单就是维护一个写指针每次从上一个位置往后挪凑够一整圈再考虑擦除。这样寿命能提升若干倍代价只是一点点代码复杂度。5.4 验收测试怎么安排才能放心交付存储方案改完不代表就能高枕无忧。我的习惯是出厂前做三轮针对性验收。第一轮是随机断电测试。用继电器控制设备电源随机通断每次上电后自动检查配置区CRC、日志偏移指针是否在合法范围内。这个测试至少跑500到1000次能看到大多数时序类问题。第二轮是高温老化加读写压力。把设备放进85℃恒温箱长时间循环写NOR和SD卡。高温下Flash的擦写电气特性会变差能不能扛住85℃连续写48小时能筛出一大批器件质量问题和布局散热问题。第三轮是破坏性恢复测试。人为把EEPROM A区写坏、破坏SD卡部分扇区、模拟固件升级中断看系统能否通过备份或校验恢复。这轮测试决定设备在现场遇到恶意断电、异常写入时是“掉链子”还是“降级可用”。能跑完这三轮的版本我才会考虑量产。最后再分享一个小技巧。我们在FPGA里做了一块很小的“存储代理”状态机STM32只需要通过寄存器提交“我要读NOR某地址”或“我要擦除某扇区”的请求FPGA自己排优先级和时序。看似多了一次内部握手却把STM32侧的掉电处理、写仲裁都集中到了硬件层。这套分级存储方案后来从网关一直复用到通信测试终端和边缘计算盒子没有再因为存储问题返工。如果你也在做STM32FPGA的工业控制器这套架构可以直接抄。
返回列表