ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

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

STM32+FPGA分级存储:EEPROM、NOR Flash与SD卡的工业控制器数据方案 搞嵌入式这么多年我最怕听到的一句话是客户在现场打电话过来你们那个控制器怎么重启了一下配方全丢了 这种问题比程序 bug 还难解释因为你没法跟一个在产线上急得冒火的操作工讲掉电时序和 Flash 磨损。工业控制器数据存储这件事看着简单——不就是挂个存储芯片嘛但真要把它做扎实里面门道不少。今天借着STM32FPGA 分级存储这个架构把我在这类项目里关于 EEPROM、NOR Flash、SD 卡三种介质的分工、选型、电路设计、软件落盘流程和踩坑记录完整梳理一遍。这套东西适用于绝大多数带触摸屏或者上位机的工业控制器项目像灌装设备、变频电源、伺服驱动器、边缘网关这类场景基本都能直接抄作业。先说清楚这个方案针对的是 STM32 做逻辑控制和界面交互、FPGA 做实时采集和高速数据搬运的典型异构架构。为什么非得分级因为工业控制器里要存的数据根本不是同一种脾气有的比金子还金贵有的比流水还着急有的就是纯粹的量大管饱。把这三种数据塞进同一种介质里要么浪费钱要么关键时刻掉链子。1. 为什么工业控制器必须做分级存储1.1 工业现场的数据其实天生就是分层的我习惯把工业控制器里需要保存的数据分成三类分类的依据不是容量大小而是三个维度丢失代价、写入频次、时效要求。第一类是配置参数类。配方参数、PID 系数、报警阈值、通讯地址、校准系数这一类数据的典型特征是单条记录很小几十到几百字节修改频率极低可能一天改一次甚至一个礼拜都不动一次但丢失代价极高。一份配方丢了整条产线可能就得停下来重新调参数这个损失不是一块钱两块钱的事。第二类是运行记录类。设备开机时间、关机时间、报警事件、操作日志、当前累计运行时长还有固件升级包。这类数据比参数大不少单条几十到几 KB写入频率中等丢了有点可惜但不算致命。第三类是工艺数据类。比如温度曲线、压力波形、振动采样、产量统计这类数据一旦开始记录就是持续高频追加一天下来几十 MB 是常态。数据本身的价值密度低但客户要的是你能给我导出曲线所以容量一定要够而且最好能被上位机直接以文件形式读取。如果你用一张表来看这三类数据和存储介质的对应关系思路会非常清楚数据类别典型大小修改频率丢失代价最适合的介质配置参数字节~KB极低极高EEPROM运行日志/升级包KB~MB中等中NOR Flash工艺曲线/历史报表MB~GB高/持续低SD 卡1.2 单片存储介质撑不起整个控制器很多人第一版设计喜欢一卡走天下直接上一张 SD 卡参数也存卡里日志也存卡里曲线也存卡里。这种方案在实验室里跑得好好的一到现场就问题频出。先说参数存 SD 卡的痛点。SD 卡读写依赖文件系统FATFS 在掉电瞬间可能正处于写文件的过程文件分配表损坏的概率虽然不高但在工业现场虽然不高就意味着一定会发生。更重要的是SD 卡的启动依赖初始化时序程序开机要等卡 ready万一客户插了一张慢卡或者接触不良的卡整个控制器启动都得卡住。最后你会发现为了存那 200 字节的配方你引入了一整条文件系统依赖链不值。EEPROM 单独也扛不住。KByte 级别的容量根本装不了日志和曲线而且它的写入速度慢页写一次要大几个毫秒拿它记日志就像拿计算器记账记着记着就疯了。NOR Flash 容量和速度都行但它按扇区擦除的特性决定了它不适合做高频小块写入更不适合做 GB 级的大容量存储单位成本比 SD 卡贵得多。所以分级存储不是炫技是三种介质各自的能力边界逼出来的最优解。EEPROM 管住不能丢的NOR Flash 管住经常翻的SD 卡管住量大随便造的。这样任何一块出问题都不会影响另外两块。1.3 STM32FPGA 架构给存储带来的新问题如果只是单 MCU 方案分级存储就是纯粹软件层面的活——分配好地址、选好介质就行。但加了 FPGA 之后事情变得稍微有点复杂。FPGA 这边经常在干两件事高速 ADC 采样比如 10Msps 以上的信号采集和多端口数据流搬运。这两个任务会产生海量实时数据STM32 根本来不及挨个处理。所以就出现了一个问题FPGA 采集的数据要持久化到底是 FPGA 自己直接写存储介质还是先把数据交给 STM32 再写答案取决于数据的实时性要求。如果数据是用来做实时波形观看的那就往 DDR 里放不落盘如果数据是事后分析的那就从 DDR 搬到 SD 卡这时候走 FPGA 直接做 SPI Master 控制 SD 卡还是通过 STM32 转发得看总线带宽和软件开销的权衡。这部分我在第四章详细展开这里先记住一个结论FPGA 管数据搬运STM32 管存储策略让擅长的人干擅长的事。2. 架构设计与选型思路2.1 整个系统的存储拓扑我这套方案的最终拓扑大概是这样的EEPROM 挂在 STM32 的 I2C1 上总线地址 0xA08 位地址模式容量 32KBAT24C256存放配方/标定参数/系统配置。NOR Flash 挂在 STM32 的 QSPI 接口上8MB 容量W25Q64JV分成三个区Bootloader 区、App 区、参数和日志备份区。为什么不用 SPI 而用 QSPI因为 App 要做 XIP就地执行QSPI 的读取速度比标准 SPI 快一个量级而且 STM32 的 QSPI 外设可以直接内存映射访问 Flash代码执行效率高不少。SD 卡走 SDIO 4bit 模式class 10 工业级 SLC 卡FAT32 文件系统存日志文件和工艺曲线数据。这里有个设计细节为什么运行日志不直接存 SD 卡而要在 NOR Flash 里留一块因为 SD 卡有拔卡场景——客户可能要拿卡去电脑上读数据此时卡不在设备里但设备还在运行日志不能断。所以 NOR Flash 上的日志区是 SD 卡的兜底缓冲SD 卡在的时候定时搬过去不在的时候就攒在 Flash 里。2.2 FPGA 在这个架构里管哪些数据通路FPGA 管的是和 SD 卡之间的数据搬运。具体来说FPGA 内部维护一个 FIFO/双口 RAM采集到的高速数据先入 FIFO由 FPGA 侧的状态机把 FIFO 数据按块搬到 SD 卡控制器。STM32 这边通过双口 RAM 的一小段寄存器区域和 FPGA 通信告诉 FPGA现在可以开始写卡了区块写完了没有等信息。STM32 和 FPGA 的通信可以是并行总线也可以是 SPI/UART 加握手信号看具体的引脚资源。这样设计的最大好处是STM32 不需要被高速数据流中断淹没。否则一个 10Msps 采样、每 0.1ms 就产生一批数据STM32 靠中断处理根本来不及CPU 全耗在搬运上了界面刷新都成问题。2.3 FPGA 侧要不要自己写存储控制器我经常被问到FPGA 侧为什么不用 Verilog 写一个 I2C EEPROM 控制器/SPI Flash 控制器 这个问题要分场景回答。如果你是一颗带有 FPGA 的纯逻辑控制器没有 ARM 核也不外接 MCU那你必须自己写 Verilog 控制器去访问存储芯片。某些工业板卡就是这么干的比如网卡、采集卡。这种情况下Verilog 里实现 SPI Master 访问 W25Q系列 Flash、实现 I2C Master 访问 AT24C 系列 EEPROM属于必修课我早期也这么干过。但我们现在这个方案里有 STM32它本来就有 I2C、SPI、QSPI、SDIO 的外设和对应的 HAL/标准库驱动还有成熟的文件系统中间件。非要让 FPGA 再写一套存储控制器纯属重复造轮子还增加 FPGA 内部资源和时序调试成本。我的原则是FPGA 的存储控制器只在STM32 够得着的场景里让 STM32 做FPGA 只做 STM32 做不了的实时高速搬运。如果采集数据量大到必须 FPGA 直写存储介质那通常优先选 DDR 缓冲而不是直接挂 SD 卡因为 SD 卡的写速度瓶颈就在那里。3. 硬件电路设计与选型的实操细节3.1 EEPROM 的选型与电路设计EEPROM 选型我一般锁定 ATMEL/Microchip 的 AT24C 系列容量看工程规模一个 30 条配方、每条 256 字节的控制器配 32KBAT24C256就够用还能留出冗余页。如果只是存校准参数AT24C08/16 就绰绰有余。电路设计上有三个容易被新手忽略的细节SCL/SDA 上拉电阻。I2C 是开漏总线必须加上拉。阻值选 2.2k 到 4.7k 之间总线电容大就选小一点总线速度快也选小一点。STM32 的 I2C 速率一般是 100kHz/400kHz4.7k 起步问题不大。电平匹配上要注意如果 STM32 是 3.3V 供电EEPROM 也 3.3V上拉到 3.3V 即可。WP 写保护引脚。AT24C 系列的 WP 引脚拉高会禁止写入这个引脚建议在 PCB 上直接引出到 0 欧电阻或跳线而不是焊死给固定电平——产线调试阶段可能要临时允许写入生产后焊上跳线锁定保护。低成本方案就是 WP 接 GND永远允许写但真到了现场误刷参数的时候就哭吧。A0/A1/A2 地址线。如果总线上只挂一片 EEPROM这三个脚直接接 GND 就行从 0xA0 开始寻址。如果在一片故障后想兼容第二片做替换冗余可以考虑占用一个地址位但我通常不建议把事情搞复杂。此外EEPROM 写入有延时页写 5ms 左右这个时序要在驱动层处理正确——具体说写完一页后不能立即发下一条写命令要等待内部写周期完成否则数据会丢。STM32 HAL 库的 HAL_I2C_Mem_Write 是阻塞等待完成的省事但要注意它在高速通信时也会拖慢总线效率所以批量参数下发建议用页写模式而不是逐字节写。3.2 NOR Flash 电路与分区策略NOR Flash 选 W25Q64JV 还是 W25Q128JV主要看 App 固件大小和日志缓冲区的需求。一个带 Qt 界面或者 LittlevGL 的固件随便就上 MB加上日志缓冲8MB 起步不亏16MB 更从容。电路设计看着就是个 SPI Flash但有几个值得记笔记的点VCC 去耦。Flash 在擦写瞬间电流会突然增大VCC 引脚要放一个 100nF 陶瓷电容最好再加一个 10uF 钽电容兜底防止电压跌落导致写失效。片选引脚默认状态。STM32 上电复位期间 GPIO 引脚电平不确定如果正好把 Flash 的 CS 拉低选中Flash 可能进入未知状态。所以在 CS 引脚上加一个 10k 上拉到 VCC确保复位期间 Flash 不响应。时钟频率。W25Q64JV 的 DTR 模式最高能跑 133MHz但 STM32 的 QSPI 配置需要综合考虑布线长度和信号完整性我刚上手时直接拉到 80MHz后来发现跑高速时读数据偶尔出错降到 40MHz 后稳如老狗。对 20MHz 以内的系统时钟通常不用太担心。更关键是分区。一片 8MB Flash我建议至少分四个区分区地址范围用途Bootloader0x000000 - 0x00FFFF64KB固件升级引导一般占不到 32KB留一点余量App 区0x010000 - 0x01FFFF主程序编译后用脚本生成 bin烧录到指定偏移参数备份区0x020000 - 0x03FFFF关键参数的镜像备份配合 EEPROM 做多重保险日志区0x040000 - 0x7FFFFF掉电保护日志和升级包暂存区分区之间的地址要按扇区对齐W25Q 的扇区是 4KB否则擦除操作会波及相邻分区的数据。这个问题我以前在项目里踩过一次升级完 App 后日志全变了排查半天发现是 bin 烧录时地址没对齐。3.3 SD 卡接口SPI 还是 SDIOSD 卡接口有两种选择SPI 模式最多用到 4 根线CS、SCK、MOSI、MISO和 SDIO 模式最多用到 6 根线CLK、CMD、DAT0-DAT3。SPI 模式的优势是引脚少、初始化逻辑简单、很多 MCU 可以直接复用现成的 SPI 外设。缺点是速度上不去——SPI 模式下 SD 卡的读写性能大约只能发挥到 400kbps 到 2Mbps对写日志和曲线数据来说勉强够用但如果工艺数据量很大比如每秒钟要落几十 KB就不行了。SDIO 4bit 模式走的是专用外设带宽高STM32F4 系列 SDIO 最高 48MHz 时钟4bit 模式理论吞吐约 24MB/s但布线要求更高DAT0-DAT3 之间要保持等长SDIO 时钟频率高于 25MHz 时对信号完整性有要求EMI 也更明显。工业环境里还要考虑 SD 卡插座和主控之间的走线长度越长越容易出问题。我的取舍逻辑是如果 SD 卡只是存日志、曲线等低频数据SPI 模式完全够如果要做高速数据采集落盘必须上 SDIO 4bit。另外工业级场景强烈建议用工业级 SD 卡SLC 颗粒消费级卡在高温、频繁写入的环境下寿命差一个数量级情绪价值拉满但可靠性差。还有插座的机械设计SD 卡是现场最容易被人插拔的器件SD 卡座的卡扣强度很关键座子最好选带写入保护开关检测和卡检测信号的型号把这两个信号接到 STM32 的 GPIO让软件能感知卡的状态变化。我见过不少设备卡没插好导致数据写一半全靠这个检测信号提前告警。4. 软件落盘流程与关键实现4.1 参数存储的原子性与掉电安全EEPROM 里存参数看起来是最简单的活把结构体 memcpy 进去就完事了。但实际工业场景掉电往往发生在刚写了一半的瞬间。比如参数结构体有 100 字节EEPROM 一页写 64 字节你第一次写完 64 字节断电了那这 100 字节就残缺了。等下次开机读出前 64 字节新参数、后 36 字节旧参数整个参数就是撕裂的。解决这个问题的最常用方案是版本号 双缓冲在 EEPROM 中开两个参数区Bank A 和 Bank B每个 Bank 里存一个结构体结构体头部放一个 magic number比如 0xAA55和递增的版本号。写入时先写 Bank B确认成功后更新 Bank A读出时先比较两边的版本号与 CRC版本新且校验通过的那个为有效参数。如果发现两个 Bank 都损坏这种情况极少但是程序设计要考虑就用出厂默认参数启动并且在界面上提示用户参数异常。我还会在参数区的末尾放一个 CRC32 校验值防止 EEPROM 自身数据翻转导致读到脏数据。CRC32 适用于 100~200 字节的参数块性能开销极低。写 EEPROM 时有一个更稳妥的做法把重要参数的最近有效值同时镜像到 NOR Flash 的参数备份区。为什么因为 EEPROM 写入期间掉电可能损坏而 NOR Flash 的扇区擦写同样存在掉电风险但两者不同时时序失效的概率更低互为备份。镜像策略推荐异步执行EEPROM 每次参数变更都立即写入NOR Flash 的备份在 EEPROM 写成功、且系统空闲时才执行避免两个介质同时进入写状态。4.2 STM32 侧稳定落盘的代码流程以 STM32F4 为例掉电保护参数存储的主流程大概是这样的收到保存参数指令可以是上位机命令也可能是 UI 上的确认按钮把参数打包成结构体计算 CRC32先写 NOR Flash 的参数备份区备份最新副本写 EEPROM Bank B新版本号1读取 EEPROM 回验 CRC确认写入成功更新 EEPROM Bank A 的版本状态应答上位机保存成功。关键点是第 3 步和第 4 步的顺序不能反过来因为先写 Flash 备份再写 EEPROM即使 EEPROM 那步出问题Flash 里至少还留了一份完整的新参数反过来就只有旧参数了。硬件上要配合掉电检测。STM32 的 PVD可编程电压检测器可以在电压跌到设定阈值时触发中断这个中断是优先级比较高的——在中断里做的事情很简单置标志位告诉主循环快把当前运行参数缓存到 RAM 里。等主循环检测到标志位后快速调用 EEPROM 写接口依靠主电源输出端的电容储能完成最后的落盘。要注意电容容量规格一般几毫秒的写入窗口需要法拉电容或大容量铝电解电容把写入循环控制在最快的路径里。4.3 FPGA 和 STM32 共享存储数据的通信协议FPGA 侧的高速采集数据要落盘的时候通常走 DMA 到 DDR再由应用层从 DDR 读出来写入 SD 卡。这里有一个通信协议问题两个处理器怎么知道当前数据在哪里、有多少。我的做法是在双口 RAM 里划定一个 32 字节的控制块结构大概是偏移 0命令字0x00 空闲 / 0x01 FPGA 请求落盘 / 0x02 STM32 已接收 / 0x03 FPGA 已释放偏移 4数据在 DDR 中的起始地址偏移 8数据长度偏移 12校验和FPGA 计算STM32 校验FPGA 写满一段缓冲区后填好控制块触发 STM32 的外部中断STM32 在中断里读取控制块、把 DDR 里的数据通过 SDIO 写进 SD 卡写完回了 ACKFPGA 才知道这块 DDR 可以复用。整个过程像一个简单的 mail box 协议比用共享内存裸读裸写可靠得多。4.4 SD 卡文件系统的使用要点SD 卡上我推荐用 FatFs 文件系统原因是它在嵌入式领域的受众最大、文档最全、移植最简单。但 FatFs 用不好也会埋雷。一定要调f_sync。调用f_write之后数据未必立刻落盘因为 FatFs 内部有缓存。工业现场最常见的数据丢失就是程序写完文件关了 IO掉电之后打开文件发现只有一半。f_sync的作用是把缓存强制刷到物理介质上代价是每次写日志后多了一次写操作速度略降但可靠性大幅提升。我一般每写满一条日志就 sync 一次数据量大的话每 N 条 sync 一次根据丢数据的损失权衡。避免频繁的打开-写入-关闭操作。FatFs 的f_open是有开销的尤其是反复创建文件、删除文件会产生碎片。日志建议设计成固定大小的环形文件——比如一个 64MB 的log1.log/log2.log轮流写写满一个往下一个写而不是每天新建一个日期命名的文件。这样文件系统碎片少、卡寿命也更长。处理 SD 卡未就绪/被拔出。FatFs 挂载失败时要给出状态反馈比如界面显示SD 卡未就绪写文件时如果返回 FR_NOT_READY程序要自动回退到 NOR Flash 的日志缓冲区不能一直循环重试导致系统卡死。5. 我在这个方案里踩过的坑5.1 EEPROM 上电误写问题有一次设备在断电重启后生产参数莫名其妙被清零。排查了三天最后发现是 STM32 复位瞬间 GPIO 电平不确定I2C 的 SCL 信号被拉出了几个毛刺脉冲EEPROM 把这几个毛刺当成了起始位然后跟着写入了一段 0x00把配置区覆盖了。解决办法有两条一是 EEPROM 的 WP 引脚默认拉高单片机正常工作时才拉低允许写二是在 I2C 总线上加 10k 串联电阻减小毛刺幅度。后来我的固件升级流程里也加了类似保护写 EEPROM 之前先读一下 WP 的电平确认是允许写状态才执行一步到位防止各种奇怪的误触发。5.2 NOR Flash 擦写磨损的教训W25Q64 的擦写寿命是 10 万次左右。如果拿它当一个普通变量存储来高频记录运行状态比如每秒写一次运行标志一天就是 86400 次擦写四天不到就把一个扇区写报废了。真实案例是某设备运行三个月后日志区不可写固件升级反复失败。要解决这个问题一是磨损均衡——把日志区划成 16 个扇区循环写不要死磕一个扇区二是事件聚合——同类事件在 RAM 里缓冲攒够一批比如 1KB再一次性写入 Flash而不是每条事件都直接擦写扇区。这两个策略组合能把 Flash 寿命延长一两个数量级。5.3 SD 卡半文件才是真噩梦SD 卡写入中途掉电文件系统里会出现一个半文件——日志文件有半截文件系统表显示长度不对打开后尾部是一堆乱码。FatFs 本身对这个问题的处理比较弱它能保证的是下次挂载不崩不保证已写的文件内容完整。我后来引入了一个文件封口机制每条日志记录自带一个 9 字节的头部固定魔数 块号 长度读取日志时逐条校验发现魔数不对就停止读取。这样即使文件被切断前面的有效记录依然可以完整解析后面的部分静默丢弃。工业日志分析需要的是有多少可靠数据而不是文件是不是长得完整。5.4 常见问题速查表故障现象可能原因排查方向/解决思路设备重启后参数为出厂默认EEPROM 写入未完成/被误写检查 WP 引脚、上电时序、写入后回读校验固件升级后 App 起不来烧录地址不对/分区未对齐确认 bin 偏移、扇区擦除范围、Bootloader 跳转地址日志文件只有半截SD 卡中途掉电/未 f_sync日志加魔数分块每块独立有效SD 卡写满后系统卡死文件系统没处理写满配置环形文件、删除旧文件前先检查剩余空间Flash 写一段时间报错扇区磨损/坏块磨损均衡、调低写入频次、换 SLC 工业级颗粒高速采集频繁丢数据FPGA 和 STM32 通信协议效率低控制块中断避免轮询和重复拷贝最后分享一点我个人的习惯这个分级存储方案不是一次成型的东西它是在现场被教育了好几轮之后才打磨成现在这样的。每次开始一个新项目我都会先问自己三个问题再动手画原理图这块数据丢了客户会不会砸机器这块数据写入频次上限是多少这块数据在设备运行期间需不需要被外部工具直接访问把这三个问题想清楚该用哪种介质基本就水落石出了。另外一个小技巧所有存储介质的关键寄存器、分区表、版本号定义我习惯在工程里单独放一个storage_layout.h头文件把地址、魔数、大小、版本全用宏定义写清楚并且在代码注释里写上修改此文件前先联系硬件确认分区。这个文件是固件和硬件工程师之间的契约比任何文档都靠谱因为它直接参与编译——改错了就编不过。好了这套方案里的坑和办法都在上面了希望对正在折腾 STM32FPGA 存储方案的朋友有点用。
返回列表