尧图精选

工业控制器数据存储分级方案:EEPROM、NOR Flash与SD卡

🕒 发布时间:2026/10/1 7:19:18 📁 来源:尧图网络
一台工业控制器在现场运行最怕的不是某个传感器读数异常而是断电重启之后参数配置全部归零、运行日志乱成一团。我做过好几个基于STM32FPGA架构的控制器和边缘网关项目早期一直有种错觉数据存储嘛无非就是选一颗好一点的存储芯片把该写的东西写进去。直到有一套设备在现场因为日志区写入频繁三个月就把NOR Flash的一个扇区写穿了还有一次工人拔SD卡的时候正好在写文件插回来FAT表直接损坏。这些事让我彻底想明白一个道理——工业控制器的数据存储必须分级而且每一级存储的选择和策略都要在画原理图之前就定清楚。这套方案里我最终采用的是EEPROM、NOR Flash、SD卡三级存储架构。EEPROM存参数配置NOR Flash存运行日志SD卡存采集数据和大文件。三者各司其职配合STM32和FPGA的分工协作才能把数据怎么存这个问题真正解决。这篇文章我会把存储选型逻辑、硬件连接、读写时序、掉电保护、磨损均衡这些细节从头到尾梳理一遍适合正在做工业控制器、数据采集终端或者边缘网关的硬件工程师参考也适合刚入门想搞懂存储系统设计的同学。1. 工业控制器的数据不能一锅炖——先分清哪些数据需要落地1.1 先列一张数据清单再谈存储选型在设计存储方案之前我习惯先把系统里所有需要非易失保存的数据列出来而不是直接打开选型手册找芯片。很多新手一上来就纠结用SPI Flash还是用SD卡但如果不清楚要存的数据长什么样选型就是拍脑袋。拿一套典型的工业控制器来说至少要面对这几类数据设备身份信息序列号、硬件版本、生产日期出厂时写入一次之后基本不再变化量非常小几十字节就够。标定参数与校准系数ADC的零点偏移、温漂系数、传感器标定值这类数据偶尔会因为重新标定而更新典型量级是几百字节。运行参数与配置PID控制参数、通信地址、波特率、报警阈值、用户自定义配置现场调试阶段会频繁改动量级同样在几百字节到几KB。系统运行日志上电记录、故障告警、操作记录、状态变更事件数据持续累积随时间增长少则几KB多则几十MB。采集数据与波形记录如果控制器带高速ADC采样功能比如振动监测、电能质量分析FPGA侧持续产生大量数据需要记录波形或者统计特征数据量从几十MB到数GB都有可能。固件升级包现场升级时临时存放的新固件通常几MB到几十MB升级完成校验通过后可以删除。把这六类数据摆在一起你会发现它们的共性其实很少。有的要频繁改有的几乎不碰有的几个字节有的几个GB有的丢了能忍有的丢一次就是事故。用一块芯片通吃所有需求要么容量浪费要么寿命耗尽要么掉电保护做不过来。1.2 按变化频率数据量可靠性三个维度给数据分桶我对数据分桶的标准有三个维度变化频率、数据量、可靠性要求。三个维度组合起来正好对应三种不同的存储介质。数据类型典型容量变化频率掉电要求可靠性要求序列号/校准系数几十~几百字节极低必须保留极高运行参数/配置几百字节~几KB低必须保留高系统日志几KB~几十MB中尽量保留中高高速采集数据几十MB~GB高尽量保留中固件升级包几MB~几十MB极低尽量保留高写坏则无法升级变化频率直接决定了介质的擦写寿命是否够用。EEPROM擦写寿命在100万次左右NOR Flash普遍是10万次SD卡内部的NAND Flash也是10万次量级。如果有一个参数每次上电都要记录频率很高你把它放在NOR Flash里按10万次寿命算设备只要上电3万次就可能出问题——实际使用中很多控制器一天开关机十几次。数据量则决定了介质容量和文件系统的复杂度。可靠性要求决定了你要不要做双份备份、CRC校验、掉电保护这些策略。打个比方数据存储就像收拾实验室工位。重要的、经常摸的小工具放在手边的抽屉里EEPROM日常要用的文档放文件柜中层NOR Flash海量的历史记录和测试数据直接归档到仓库档案架上SD卡。不分级什么东西都往同一个抽屉里塞既放不下也找不着。数据分级之后每级存储的任务就清晰了EEPROM负责必须存、很小、常改的参数NOR Flash负责持续写、中等量、要可靠的日志SD卡负责大量、按文件管理、可容忍偶发丢失的采集数据。2. 硬件分工怎么定STM32管参数、FPGA管高速数据流的存储协作2.1 为什么是双芯架构而不是单芯片一把梭工业控制器选STM32FPGA的组合核心原因在于控制面任务和数据面任务互相撕扯。STM32擅长系统管理、通信协议、参数处理这些逻辑复杂的事情FPGA擅长并行采样、高速数据搬移、实时信号处理。存储任务也一样分裂低速小数据量的存储管理适合STM32高速大流量的数据搬运和缓冲必须交给FPGA。如果只用一颗STM32高速ADC采样过程会频繁打断CPU。要知道一个5MSPS、16位的ADC一秒钟就是10MB数据STM32要处理数据还要存卡中断压力大到没法做其他任何事情。如果只用FPGA文件系统、协议解析、参数管理这些逻辑实现起来代码量巨大调试难度也高。双芯架构等于把管数据的人和搬数据的人分开各干各的强项。在存储领域的分工是这样的STM32负责管理低速存储介质EEPROM通过I2C接口、NOR Flash通过SPI接口这两种介质的访问速率都在几十MHz以下STM32完全吃得消。参数读写、日志管理、文件系统逻辑都由STM32完成。FPGA负责高速数据的缓冲和前置处理ADC数据先进入FPGA的FIFO或DDR缓存按批次吐给STM32或者由FPGA直接控制DDR做多通道数据汇聚。FPGA不直接管文件系统它管的是把原始数据按批次稳妥地送到下一个存储环节。STM32和FPGA之间通过自定义协议通信可以是一对SPI接口、UART、或者并行FIFO具体看数据量和实时性要求。数据量不大用UART或SPI就够动不动几MB的突发数据建议上并行FIFO或者DDR共享总线。2.2 STM32存储域慢数据的归宿很清晰STM32这边的存储任务相对集中主要是三类EEPROM的参数读写、NOR Flash的日志记录、SD卡的文件系统管理。为什么把这三块都放在STM32这边因为它们的共同特点是操作节奏慢每次操作间隔在几十毫秒到几百毫秒量级STM32的阻塞式访问完全能接受代码也好写、好调试。EEPROM用I2C接口硬件电路简单两根线加上拉电阻就完事。参数变更时上位机下发命令给STM32STM32更新内存副本然后按页拆分写入EEPROM。NOR Flash用SPI接口日志事件发生时STM32把事件结构体写入当前日志块顺便更新块头信息。SD卡用SDIO接口在数据归档或者文件导出时才会访问。这个域的调试经验是STM32代码里最好做一个统一的存储抽象层所有读写EEPROM、NOR Flash、SD卡的函数都走同一套接口比如store_param_write()、log_event_write()。这样即使后面换了Flash型号或者改了介质分配应用层代码不用大改。2.3 FPGA存储域快数据需要缓冲和搬运的中转FPGA侧的存储核心是数据中转。高速ADC的数据是连续不断的你不能让STM32随叫随到也不能让SD卡跟上ADC的实时速度。所以FPGA要做的是在数据源头和存储介质之间加一个大缓冲。最简单的路径是ADC数据流入FPGA内部FIFOFIFO快满时触发一次搬运把数据写到外部DDR缓存。DDR里攒够一批比如512KB或者1MBFPGA拉高数据就绪信号STM32闻到信号后用DMA把这一批数据搬走再写入SD卡。为什么FPGA要先过一遍DDR因为FPGA内部RAM资源有限一个中等规模的FPGA内部块RAM也就几百KB撑不住连续高速采样的缓冲需求。外挂一颗DDR3或者DDR3L成本不高但缓冲能力完全不一样。如果项目涉及多通道ADC同步采样DDR缓存几乎是必需品。多路数据同时进来FPGA内部需要一个多端口仲裁逻辑来管理DDR读写这正好是FPGA擅长的事情。STM32只负责在合适的时候取走已经就绪的数据块完全不会因为数据流量大而被拖死。热词里提到的基于FPGA的多端口DDR读写程序说的就是这类场景的开发。3. EEPROM里存参数I2C读写看似简单页写限制和掉电写坏才是真坑3.1 器件选型与I2C总线设计要点EEPROM这一级我的选型逻辑是容量根据参数总量来决定2Kbit到64Kbit基本覆盖绝大多数场景。型号上最常用的是AT24C02/04/08系列Microchip的AT24系列、ST的M24系列都可靠而且引脚兼容供应链有保障。I2C总线设计有几个细节值得注意上拉电阻典型值是2.2kΩ到4.7kΩ取决于总线上挂了多少设备和走线长度。电阻太小灌电流大电阻太大上升沿变缓影响通信稳定性。器件地址EEPROM的A0/A1/A2引脚决定I2C地址多颗EEPROM或者和其他I2C设备共总线时要注意区分。通信速率EEPROM支持100kHz和400kHz模式控制器里通常选400kHz但要留意总线上最慢设备的限制。STM32和EEPROM的接口有两种做法硬件I2C外设和GPIO模拟I2C。我的经验是如果EEPROM只在设备启动、参数变更时才读写GPIO模拟更加简单可控。硬件I2C外设在某些STM32系列上遇到总线错误后状态机会卡住需要重新初始化才能恢复排查起来比较烦。模拟I2C时序完全由自己掌握出了问题也能直观定位。3.2 分页写入和边界跨越问题EEPROM写入有页写限制这是最容易踩的坑。以AT24C02为例一个页是8字节一次页写最多只能写一个页。如果写入长度跨越页边界芯片会自动回卷到本页开头覆盖数据结果就是你看到的前8字节对、后面的字节全乱。处理办法很简单把要写入的数据按页边界对齐逐页拆分写入。我后来一直沿用的代码逻辑是这样的#define EEPROM_PAGE_SIZE 8 // AT24C02 为8字节AT24C64为32字节 #define EEPROM_WRITE_DELAY_MS 5 // 内部写周期时间 uint8_t eeprom_write_bytes(uint16_t addr, uint8_t *buf, uint16_t len) { while (len 0) { uint16_t page_remaining EEPROM_PAGE_SIZE - (addr % EEPROM_PAGE_SIZE); uint16_t chunk (len page_remaining) ? len : page_remaining; eeprom_page_write(addr, buf, chunk); delay_ms(EEPROM_WRITE_DELAY_MS); // 等待写周期完成 addr chunk; buf chunk; len - chunk; } return 0; }读操作没有页限制可以连续读多个字节但要注意I2C发送读命令时的地址格式连续读模式下每收到一个字节后要回复ACK读完最后一个字节要回NACK再发STOP否则从机不知道什么时候结束。3.3 写入掉电保护与磨损均衡EEPROM的擦写寿命是100万次听起来很多但工业现场的设备往往7x24小时运行某些计数器、上电次数、运行时间这类参数会频繁更新。一旦写入过程中掉电EEPROM的存储单元可能处于未定义状态参数就会损坏。我常用的保护策略是双份存储标志位参数区分为A区和B区两份镜像存储。每个参数带CRC校验CRC值随数据一起存储。写入时先把目标的标志字节改成无效写完数据和CRC后再把标志改成有效。上电读取时先检查A区如果标志“有效”且CRC正确就用A区否则检查B区两个区都损坏才恢复出厂值。磨损均衡方面如果某个参数确实高频更新可以把地址分散到多个单元轮换。比如上电次数计数器在EEPROM里划出32个地址每次写入地址加1写满32个地址后再回到第一个地址。这样同样数量的写入次数被摊到32个单元上单点寿命压力小了很多。4. NOR Flash里存日志擦除周期、磨损均衡和掉电中断的应对思路4.1 为什么选NOR Flash而不是NAND工业控制器里的运行日志、事件记录我优先选NOR Flash而不是NAND。原因有几个随机字节读取NOR Flash支持按字节随机读取甚至可以XIP就地执行做代码存储也没问题。擦除粒度小NOR Flash的最小擦除单位是扇区通常是4KB比NAND的最小擦除块通常128KB以上小得多日志这类小块数据存储起来更方便。可靠性好NOR Flash的坏块管理简单很多型号完全不需要处理坏块问题NAND就必须要维护坏块表和ECC。工业级NAND Flash虽然也有但管理复杂度显著上升对日志这种场景来说性价比不高。缺点是容量和写入速度不如NAND但对几MB到十几MB的日志来说完全够用。选型上常见的是W25Q16/32/64/128、GD25Q系列、MX25L系列SOIC-8或WSON-8封装电路设计简单。4.2 SPI时序和先擦后写的底层逻辑NOR Flash通过SPI接口访问连线就是CS/CLK/MOSI/MISO四根线。电源去耦电容不可省WP引脚写保护和HOLD引脚要注意处理有些设计直接悬空会出诡异问题最好明确上拉或者由GPIO控制。写入流程要记住一个核心规律Flash编程只能把1写成0要把0恢复成1只能靠擦除。所以修改一个字节这个概念在NOR Flash里是不存在的你只能先擦除整个扇区再把需要保留的数据连同修改后的数据一起写回去这个操作叫读-改-写。一次完整的写入流程包括写使能WREN命令0x06页编程命令0x02每次最多写入256字节轮询状态寄存器的WIP位读命令0x05等待内部编程完成如果目标区域已有数据需要先执行扇区擦除命令0x20或块擦除命令0xD8SPI时钟速率方面W25Q系列支持到104MHz甚至更高但在STM32上我一般配置到几十MHz就够了因为日志写入的频率不高瓶颈不会在SPI时钟上。4.3 环形日志与磨损均衡策略日志是持续累积的不能写满了再停机处理。我常用的方案是环形日志固定划分一段空间比如4MB的NOR Flash分成128个32KB的日志块。维护两个指针当前写指针和日志起始指针。写满一圈后覆盖最旧的日志块。日志块头存放序号、时间戳、数据长度、CRC校验值。设备启动时扫描日志块头按照序号重建日志索引。磨损均衡在环形日志里天然实现了——所有日志块轮流被写入不会出现某一个扇区被频繁擦除而其他扇区闲着的情况。虽然NOR Flash有10万次擦写寿命128个块轮流用实际寿命大大延长。如果日志写入频率很高还可以在块头增加一个擦写次数统计每次擦除时递增方便做寿命预警。4.4 擦除断电后的恢复方案NOR Flash最怕擦除过程中掉电。擦除操作是要把整个扇区所有字节都写成0xFF如果中途断电扇区内容是没有定义的——可能是半擦除状态也可能是随机数据。下一次上电读日志就很有可能读到乱码甚至因为块头信息损坏导致整个日志区不可用。A.双标志位方案每个日志块头部放两个标志BLOCK_DIRTY和BLOCK_IN_USE。写入日志前先把目标块标记为DIRTY写完日志数据并校验通过后再标记为IN_USE。上电扫描时如果发现有块处于DIRTY状态说明上次写入未完成直接擦除重写或者丢弃该块。B.双缓冲交替方案把日志区分为两个大区交替写入。A区写完后写B区B区写完后回头擦除A区重新写。这样不管什么时候掉电总有一个区保存着完整的日志。代价是存储利用率减半但可靠性大幅提升。结合STM32的PVD可编程电压检测功能可以在检测到电源跌落时进入紧急处理流程停止日志写入、把缓存刷到NOR Flash、更新标志位。虽然掉电瞬间的操作窗口只有几毫秒到十几毫秒但足够做最关键的保护动作。5. SD卡里存采集数据文件系统、掉电保护与FPGA高速吞吐路径5.1 SDIO和SPI模式速度、引脚与代码量的权衡SD卡在嵌入式里有两种接法SDIO模式和SPI模式。SDIO模式4位数据线并行传输理论上速度能到几十MB/s适合持续大流量写入STM32自带SDIO外设配合DMA效率很高。SPI模式引脚少只有CS/CLK/MOSI/MISO四根但速度上限大概在25MHz时钟实际吞吐量受到协议开销影响会更低适合只存配置文件、不涉及大流量数据的场景。在STM32FPGA这套架构里如果采样数据要落到SD卡我建议直接上SDIO模式。工业控制器的PCB上多几根线不是问题数据吞吐能力却差了一个数量级。另外要注意SD卡初始化有固定时序上电后要给至少74个时钟周期的空闲脉冲然后发CMD0进入初始化流程。很多新手遇到的SD卡识别不了问题多半是初始化时序没按规范走。5.2 FatFS在嵌入式里的配置细节文件系统方面FatFS是嵌入式的事实标准配置好之后可以像操作普通文件一样管理SD卡数据。我一般这样配置打开长文件名支持FF_USE_LFN2带动态内存分配方便生成带时间戳的文件名。设置FF_FS_RPATH1支持相对路径。每个挂载的卷分配一个工作区f_mount之后才能访问。打开文件写入后一定要及时调用f_sync()或者f_close()否则数据只存在FATFS的缓存里掉电就丢了。还有一个容易被忽略的点SD卡写入速度不是恒定的旧卡、碎片化的卡写起来会明显变慢。如果系统对写入实时性有要求应该采用先缓存再批量落盘的策略——FPGA先把数据攒到DDR里攒到一定程度比如512KB再通知STM32一次性写入SD卡。这样减少了文件系统的寻道和碎片开销吞吐量明显更稳定。5.3 掉电损坏和拔卡问题的防护SD卡直接掉电很容易损坏FAT表。现场调试这种问题遇到过太多次工人直接拔卡拷数据卡再插回去就挂载失败。从硬件到软件要做几道防护卡检测引脚SD卡座的CD引脚接STM32的外部中断检测到卡拔出就立刻停止写入任务把已经缓存的数据先落盘。文件轮换策略日志类文件不要一个文件写几个月单个文件过大一旦损坏损失惨重。建议按时间戳分文件比如每小时或每天生成一个新文件。双文件区轮换如果数据非常重要可以设计两个文件A文件写完后写B文件B文件写完后回头覆盖A文件。启动时校验哪个完整就用哪个。定期卸载和挂载STM32检测到文件系统异常时可以提示用户重新格式化或者自动从备份区恢复出厂配置。5.4 FPGA侧的高速数据写入路径FPGA侧处理高速采集数据并写入SD卡的路径是我在做这套方案时花时间最多的地方。以5MSPS、16位ADC为例一秒钟产生10MB数据。这个速率虽然SD卡理论能扛住但文件系统开销、SD卡磨损均衡、写放大等都会拖慢实际写入速度。更麻烦的是STM32不能卡死在写卡上不管其他任务。所以数据路径需要拆成三段ADC原始数据进入FPGA内部FIFOFIFO快满时由FPGA内的控制逻辑把数据搬运到外部DDR缓存。DDR缓存攒够一个批次后FPGA拉高数据就绪中断信号。这个批次大小根据项目需要调整一般256KB到1MB。STM32收到中断后通过DMA把这一批数据搬到自己的内存再调用FatFS写入SD卡文件。在这个流程里FPGA不碰文件系统它只负责把数据从ADC挪到DDR、按批次通知STM32。文件系统的复杂度全部由STM32承担。为什么这样设计因为文件系统逻辑一旦出问题在FPGA里调试极其痛苦而STM32上跑FatFS是成熟方案有什么问题直接仿真器单步就能定位。如果数据量真的大到STM32搬运不过来再考虑让FPGA直接通过SDIO写裸扇区或者干脆上eMMC但那是更大系统才需要的方案。6. 分级存储映射表一份可以直接拿去用的数据落盘策略6.1 分级映射总表与数据流向把前面三个存储层级的内容整合成一张总表。这张表我在原理图设计时就会直接打印出来放在工作台上随时对照。数据类别存储介质接口更新频率容量规划可靠性措施序列号EEPROMI2C极低64BCRC校验标定参数EEPROMI2C低256B双份镜像CRC运行参数EEPROMI2C低512B双份镜像CRC运行日志NOR FlashSPI中4MB~16MB环形覆盖DIRTY标志故障录波NOR FlashSPI中预留扇区级保护波形/采样数据SD卡SDIO高依卡容量文件定期f_sync固件升级包SD卡SDIO极低依卡容量升级完成后校验数据流向可以分成几条清晰的链路参数修改链路上位机下发配置 → STM32更新内存副本 → 按页拆分写入EEPROM镜像区 → 标志位翻转。日志记录链路系统事件产生 → STM32打包事件结构体 → 写入NOR Flash当前日志块 → 更新块头序号。采样数据链路ADC连续采样 → FPGA内部FIFO → 搬移至DDR缓存 → 批次就绪中断 → STM32 DMA搬运 → FatFS追加写入SD卡文件。6.2 数据迁移与状态降级逻辑分级存储还有一个好处数据可以在层级之间流动。NOR Flash里的老旧日志达到一定容量后可以归档到SD卡释放NOR Flash空间给新日志。固件升级包优先放SD卡校验通过后再把固件内容拷到NOR Flash如果NOR容量够且支持XIP最后跳转执行。SD卡没有插入或者挂载失败时采集数据降级为只缓存不落盘或者把数据量较小的统计特征写入NOR FlashSD卡恢复后再把缺失的记录补写过去。这个降级和恢复逻辑在做工业控制器时非常实用。现场把SD卡拔走拷数据是常态系统不能因为卡不在就罢工。我一般用一个简单的状态机管理存储子系统状态A默认只有EEPROM和NOR Flash可用系统正常运行采集数据只缓存或丢弃。状态B增强检测到SD卡并挂载成功进入正常记录模式数据全部落盘。状态C异常SD卡写入失败自动降级为状态A并在NOR Flash日志里记录一次降级事件。7. 写在最后几处改了三版才算稳定的存储细节7.1 一例EEPROM页写跨越引发的数据错乱之前调试一套控制器时配置参数一共80字节我在写EEPROM时图省事直接一次性发80字节给AT24C02。结果读回来发现前8个字节正确后面的全部错乱。查了半天才发现AT24C02的页大小是8字节我一次跨了10个页的边界芯片自动回卷覆盖了本页开头的数据。从那以后我把按页拆分写入写成了所有EEPROM驱动的标配不管是8字节一页还是32字节一页统一走拆分逻辑。这个教训也说明存储芯片的数据手册里那些不起眼的Note往往才是真正会咬人的地方。7.2 NOR Flash擦除掉电后的恢复实测有一轮老化测试里我在设备运行过程中反复插拔电源重启后发现日志区全是0xFF但系统反复启动失败连正常的事件记录都写不进去。定位到最后发现是疑似某次掉电正好落在扇区擦除的窗口中间块头标志变成了未定义状态上电扫描时读到了错误信息把后续的写入逻辑全带偏了。加了DIRTY/IN_USE双标志和启动扫描后这个问题彻底消失了。这个测试让我意识到电源跌落瞬间的处理才是存储可靠性的最后一道关卡。STM32的PVD中断要尽早配置检测到电压跌落先停中断、刷缓存、标记存储状态哪怕只有几毫秒也能避免很多疑难问题。7.3 SD卡热拔插的现场教训某项目里工人习惯不等系统停止写入就直接拔SD卡拷数据结果卡装回去后FatFS挂载失败现场的采集数据全没了。我当时的处理是硬件上增加卡检测引脚软件上在FatFS配置里加了个拔卡锁检测到CD信号变低就立刻停止所有文件操作。同时把单文件长时间写入改成了按小时轮换的小文件即使某个文件损坏损失也就一个小时的数据。这个设计后来又经历了一次现场验证才让我确信文件系统的稳定性问题七成要靠使用习惯和冗余策略来兜底而不是指望卡自己不出错。做了几轮改版之后我的体会是存储方案不是选一颗芯片那么简单而是要把数据分好类再设计出多套互相补充的落盘路径。EEPROM、NOR Flash、SD卡这三者单独看都是老技术但在工业控制器里稳定才是第一位的这套三级架构看似传统却已经在好几个项目里扛住了现场的各种折腾。后面如果碰到新的存储坑我再来单独开一篇聊。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →