ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

工业控制器分级存储方案:STM32+FPGA与EEPROM/NOR Flash/SD卡选型实战

工业控制器分级存储方案:STM32+FPGA与EEPROM/NOR Flash/SD卡选型实战 工业控制器这类设备有个很现实的问题它不像消费电子那样可以随便联网、随便扩容一旦装到现场机柜里可能三五年都没人碰它。但在这三五年里它得持续记录运行日志、保存标定参数、缓存采集数据甚至还要支持现场升级固件。这些数据有的要求掉电不丢、有的要求写入寿命够长、有的要求容量够大能存历史记录——指望一颗芯片全包了基本不现实。我在做运动控制器和边缘网关类项目时反复验证过一套分级存储的组合STM32 负责逻辑控制和文件系统管理FPGA 负责高速数据流的缓冲与搬运存储介质按参数区—日志区—历史数据区三层拆开分别落到 EEPROM、NOR Flash 和 SD 卡上。这套方案的好处是每一层只干自己最擅长的事成本可控可靠性也能算得清楚。下面我把这套分级存储的选型逻辑、硬件连接、驱动实现和踩过的坑完整拆一遍适合正在做工业控制器、数据采集终端或者边缘网关的硬件和嵌入式软件工程师参考。1. 为什么工业控制器不能只用一种存储介质1.1 三类数据的写入特征完全不同先别急着选芯片得先把数据分类。工业控制器里的数据粗分三类它们的写入频率、容量需求和寿命要求差异极大。第一类是标定参数和配置信息比如传感器的零点偏移、PID 参数、通信地址、设备序列号。这类数据的特点是写入次数极少出厂标定一次现场偶尔改一次但要求掉电绝对不能丢而且上电后要能立刻读到。容量需求通常只有几百字节到几 KB。第二类是运行日志和故障记录比如每次报警的时间戳、故障码、关键变量快照。这类数据写入频率中等可能一天几十条到几百条要求掉电不丢但允许有一定的写入延迟。容量需求在几十 KB 到几 MB 之间。第三类是历史数据和采集缓存比如高速 ADC 采样的原始波形、连续运行曲线。这类数据写入频率极高可能每秒几 KB 到几 MB容量需求动辄几百 MB 甚至上 GB但允许在掉电时丢失最后几秒的数据。把这三类数据往同一颗芯片上塞要么容量不够要么寿命不够要么成本爆炸。所以分级存储不是为了复杂而复杂而是被数据特征逼出来的必然选择。1.2 三种介质的物理特性决定了它们的分工理解了数据分类再来看介质特性分工就很清楚了。EEPROM的擦写寿命通常在 100 万次以上字节级可寻址写入不需要整块擦除读写的时序也简单。缺点是容量小、单价高、写入速度慢毫秒级。它天生就是存参数的料。NOR Flash的擦写寿命在 10 万次左右按扇区擦除通常 4KB 一个扇区支持随机读取读取速度快。缺点是写入前必须擦除而且擦除次数有限。它适合存日志和固件因为日志可以按扇区轮转写入把擦除次数摊开。SD 卡的容量大、单位成本低但它的写入寿命和可靠性远不如前两者而且存在掉电写入损坏的风险。它适合存历史数据这种丢了也能接受的大容量内容但必须配合文件系统和掉电保护机制。这里有个容易被忽略的点NOR Flash 的擦除次数是按扇区算的不是按整颗芯片算的。如果你把日志反复写在同一扇区那个扇区很快就会坏掉。所以日志存储必须做扇区轮转wear leveling 的简化版这是后面驱动实现里的重点。1.3 分级存储的架构划分基于上面的分析我把架构划成三层层级介质存储内容容量范围接口寿命要求参数层EEPROM标定参数、配置、序列号2KB~64KBI2C/SPI100 万次擦写日志层NOR Flash运行日志、故障记录、固件备份2MB~16MBSPI/QSPI10 万次擦写数据层SD 卡历史数据、采集缓存、导出文件4GB~32GBSDIO/SPI按需更换STM32 的角色是总调度它负责参数读写、日志管理、文件系统操作以及和上位机的通信。FPGA 的角色是数据搬运工它负责高速数据流的采集、缓冲和批量写入避免 STM32 被高频中断拖垮。这个分工的关键在于FPGA 不直接管理文件系统它只做数据缓冲和搬运把整理好的数据块交给 STM32 去落盘。这样既发挥了 FPGA 的并行处理能力又避免了在 FPGA 里实现复杂文件系统的麻烦。2. 硬件连接与接口选型的实际考量2.1 EEPROM 用 I2C 还是 SPIEEPROM 的接口选择主要看两点引脚资源和写入频率。I2C 接口的 EEPROM比如 AT24C 系列只需要两根线SCL、SDA可以挂多个器件适合引脚紧张的场景。但 I2C 的速率通常只有 100kHz~400kHz写入一页通常 8~32 字节需要等待 5ms 左右的内部写周期。如果你需要频繁写参数I2C 会成为瓶颈。SPI 接口的 EEPROM比如 25LC 系列速率可以到几 MHz 甚至十几 MHz写入速度明显更快但要多占两根线CS、CLK加上 MOSI、MISO总共四根线。我的经验是参数写入频率低于每分钟一次的场景用 I2C 就够了省引脚如果需要频繁更新参数比如在线整定 PID 并实时保存上 SPI。在工业控制器里参数通常是改一次存一次I2C 完全够用。这里有个硬件设计上的坑I2C 的 SCL 和 SDA 必须接上拉电阻典型值 4.7kΩ。如果总线上挂了多个器件上拉电阻要适当减小比如 2.2kΩ否则上升沿太慢会导致通信失败。我见过好几次EEPROM 读不出来的问题最后查出来都是上拉电阻没接或者阻值太大。2.2 NOR Flash 的 SPI 与 QSPI 之争NOR Flash 现在主流是 SPI 和 QSPI 两种接口。SPI 是单线传输QSPI 是四线并行传输理论速率差四倍。对于日志存储这种写入不频繁、读取偶尔的场景SPI 接口的 NOR Flash比如 W25Q 系列完全够用速率 50MHz 左右写入几 KB 的日志块也就几百微秒。QSPI 更适合需要 XIPExecute In Place就地执行的场景比如把程序直接放在外部 Flash 里运行。但要注意STM32 的 QSPI 外设不是所有型号都有。比如 STM32F103 系列就没有 QSPI只能用 SPI。如果你选的是 F4 或 H7 系列才有 QSPI。选型时一定要先确认 MCU 的外设资源。另外NOR Flash 的供电电压也要注意。现在很多 W25Q 系列是 3.3V 供电但也有一些是 1.8V 的。如果 MCU 是 3.3V 系统选 1.8V 的 Flash 就需要加电平转换增加成本和复杂度。我一般直接选 3.3V 的型号省事。2.3 SD 卡的 SDIO 与 SPI 模式SD 卡有两种访问模式SDIO 和 SPI。SDIO 是原生模式4 位数据线并行传输速率可以到几十 MB/s适合高速数据记录。但 SDIO 的引脚多CLK、CMD、DAT0~DAT3而且对时序要求高PCB 布线要等长。SPI 模式只用四根线CS、CLK、MOSI、MISO布线简单但速率低通常几 MB/s而且不是所有 SD 卡都完美兼容 SPI 模式。我的建议是如果数据记录速率低于 1MB/s用 SPI 模式就够了布线简单、调试容易如果要做高速采集比如音频、振动信号连续记录必须上 SDIO。在工业控制器里历史数据通常是秒级或分钟级记录SPI 模式完全够用。SD 卡还有个硬件设计要点DAT 线和 CMD 线需要上拉电阻典型 10kΩ~50kΩ否则卡可能无法识别。另外SD 卡的供电要稳定最好单独加一颗 LDO避免和 MCU 共用电源导致写入时电压跌落。2.4 FPGA 与 STM32 之间的数据通道FPGA 和 STM32 之间的通信接口常见的有三种SPI、FSMC/FMC 并行总线、以及自定义的并行接口。SPI 最简单但速率受限适合低速数据交换。FSMC/FMC 是 STM32 的外部存储器接口可以映射成类似 SRAM 的访问方式速率高适合大批量数据传输。自定义并行接口最灵活但需要自己写时序。我在项目里用的是FSMC 映射成 16 位并行接口FPGA 侧实现一个简单的双口 RAM 或 FIFOSTM32 通过 FSMC 地址读写数据。这样 STM32 访问 FPGA 缓冲就像访问外部 RAM 一样简单速率也能到几十 MB/s。这里的关键是地址映射要清晰。我一般把 FPGA 内部划分成几个区域状态寄存器区、命令区、数据 FIFO 区、DMA 描述符区。STM32 通过不同的地址偏移访问不同区域逻辑清晰调试也方便。3. EEPROM 参数存储的驱动实现与寿命管理3.1 参数区的数据结构设计EEPROM 存参数不能随便往地址 0 开始写。我一般会设计一个简单的参数表结构typedef struct { uint16_t magic; // 魔数用于判断参数区是否已初始化 uint16_t version; // 参数版本号用于固件升级时兼容旧参数 uint32_t crc32; // 整个参数区的 CRC 校验 uint8_t data[PARAM_SIZE]; // 实际参数数据 } param_block_t;magic字段用来判断 EEPROM 是否是第一次使用未初始化时通常是 0xFF。version字段在固件升级时特别有用——如果新固件增加了参数项可以根据版本号决定是否用默认值填充。crc32用来校验参数完整性防止掉电写入导致的数据损坏。参数区通常做双备份在 EEPROM 里划两块区域交替写入。写入时先写备份区校验通过后再更新主区。这样即使写入过程中掉电至少有一份完整的数据可用。3.2 I2C 读写 EEPROM 的时序细节I2C 读写 EEPROM 有几个容易踩的坑我逐个说。页写入边界EEPROM 通常按页组织比如 8 字节一页或 32 字节一页。如果你一次写入跨越了页边界地址会回卷到页首导致数据写错位置。所以写入时必须按页对齐或者分多次写。写周期等待EEPROM 每次写入后需要 5ms 左右的内部写周期这期间它不会响应 I2C 通信。如果你连续写必须等待写周期完成。判断方法是应答轮询ACK polling发送起始条件后发送设备地址如果 EEPROM 返回 ACK说明写周期结束如果返回 NACK说明还在忙。// 应答轮询等待 EEPROM 写周期完成 void eeprom_wait_ready(uint8_t dev_addr) { while (1) { i2c_start(); if (i2c_send_byte(dev_addr 1) 0) { // 收到 ACK i2c_stop(); break; } i2c_stop(); delay_us(100); } }地址长度小容量 EEPROM如 AT24C022KB用 8 位地址大容量如 AT24C256256KB用 16 位地址。驱动代码要根据型号适配否则读写地址会错位。3.3 参数写入的寿命优化策略EEPROM 虽然寿命长100 万次但如果某个参数被频繁修改也会提前耗尽。比如 PID 参数如果每次调整都立即保存一天调几十次几年下来也可能接近寿命上限。我的做法是加一层 RAM 缓存 延迟写入参数修改先写到 RAM标记为脏然后每隔一段时间比如 10 秒或者收到保存命令时才真正写入 EEPROM。这样既减少了写入次数又保证了掉电前有足够时间落盘配合掉电检测中断可以在掉电瞬间强制写入。另外不要把频繁变化的数据放 EEPROM。比如运行计时器、累计产量这种每秒都在变的数据应该放 RAM定期比如每小时同步到 NOR Flash 或 SD 卡。EEPROM 只存不常变但必须可靠的参数。4. NOR Flash 日志存储的扇区轮转与掉电保护4.1 日志存储的环形缓冲区设计NOR Flash 存日志核心思路是环形缓冲区 扇区轮转。把 Flash 划分成 N 个扇区比如 4KB 一个日志按顺序写入写满一个扇区就擦除下一个扇区继续写形成一个环。每个扇区头部放一个元数据头typedef struct { uint32_t seq; // 扇区序号用于判断写入顺序 uint32_t timestamp; // 扇区首次写入的时间戳 uint16_t count; // 本扇区已写入的日志条数 uint16_t crc; // 头部 CRC } sector_header_t;写入日志时先读当前扇区的头部找到写入位置追加日志记录然后更新头部。当扇区写满时擦除下一个扇区更新序号继续写。这里的关键是擦除操作要提前做。不能等到要写了才擦除因为擦除一个扇区需要几十到几百毫秒这期间如果有日志要写就会丢。我的做法是当前扇区写到 80% 时后台就开始擦除下一个扇区这样切换时无需等待。4.2 掉电保护日志记录的原子性NOR Flash 的写入是按字节或页进行的但擦除是按扇区。如果写入过程中掉电可能导致日志记录不完整。为了保证原子性我给每条日志加一个有效标记typedef struct { uint16_t valid; // 有效标记写入完成后置为 0x5A5A uint16_t len; // 日志长度 uint32_t timestamp; // 时间戳 uint8_t payload[]; // 日志内容 } log_entry_t;写入顺序是先写len、timestamp、payload最后写valid。读取时先检查valid如果不是 0x5A5A 就跳过这条记录。这样即使写入过程中掉电最多丢失最后一条不完整的日志不会影响之前的记录。这个思路和数据库的 WALWrite-Ahead Logging是一个道理先写数据最后写提交标记。4.3 扇区轮转中的磨损均衡如果日志写入频率不均匀某些扇区可能被反复擦写而其他扇区很少用到。为了延长整体寿命需要做磨损均衡。最简单的做法是顺序轮转不管扇区写了多少都按顺序一个一个用用完一圈再从头开始。这样每个扇区的擦除次数基本一致。进阶做法是动态磨损均衡记录每个扇区的擦除次数优先使用擦除次数少的扇区。但这需要额外的计数存储实现复杂度高。对于工业控制器这种日志量不大的场景顺序轮转就够了。我算过一笔账假设 NOR Flash 有 512 个扇区每个扇区擦写寿命 10 万次顺序轮转下总写入寿命是 512 × 10 万 5120 万次扇区写入。如果每天写 1000 条日志每条日志平均 100 字节一个 4KB 扇区能存 40 条那么每天擦除 25 个扇区一年擦除约 9000 次。512 个扇区轮转一圈需要 512 / 25 ≈ 20 天一年轮转约 18 圈每圈每个扇区擦除一次所以每个扇区一年擦除约 18 次。10 万次寿命可以用 5000 多年。当然这是理想情况实际中还要考虑擦除放大和坏块但至少说明顺序轮转的寿命是绰绰有余的。4.4 SPI NOR Flash 的驱动要点SPI NOR Flash 的驱动有几个关键点读 ID 确认型号上电后先读 JEDEC ID命令 0x9F确认 Flash 型号和容量。不同型号的扇区大小、页大小可能不同驱动要能适配。写使能每次写或擦除前必须发送写使能命令0x06否则操作会被忽略。这是新手最容易忘的一步。等待忙状态写或擦除后要读状态寄存器命令 0x05的 BUSY 位等待操作完成。擦除一个 4KB 扇区通常需要 50~200ms这期间不能发其他命令。地址模式容量大于 16MB 的 Flash 需要 4 字节地址模式命令 0xB7 进入否则地址会溢出。小容量用 3 字节地址即可。// SPI NOR Flash 写使能 等待忙 void nor_flash_write_enable(void) { spi_cs_low(); spi_send_byte(0x06); spi_cs_high(); } void nor_flash_wait_busy(void) { uint8_t status; do { spi_cs_low(); spi_send_byte(0x05); status spi_recv_byte(); spi_cs_high(); } while (status 0x01); // BUSY 位 }5. SD 卡文件系统与高速数据落盘5.1 FatFS 的移植与配置SD 卡存历史数据通常要配文件系统方便上位机直接读取。嵌入式领域最常用的是 FatFS它轻量、开源、移植简单。移植 FatFS 主要做三件事实现磁盘 I/O 层disk_read、disk_write、disk_ioctl、配置ffconf.h、提供实时时钟用于文件时间戳。disk_ioctl里有个CTRL_SYNC命令用于确保写入的数据真正落到卡上。在掉电敏感的场景每次写完关键数据后要调用f_sync它会触发CTRL_SYNC强制刷新缓存。ffconf.h里几个关键配置_FS_TINY设为 1 可以节省 RAM但会降低多文件操作性能。_USE_FASTSEEK设为 1 可以加速大文件定位。_FS_REENTRANT如果多任务访问 SD 卡要设为 1 并实现互斥锁。5.2 SD 卡写入的性能瓶颈与优化SD 卡的写入性能有个特点小数据块随机写入极慢大数据块顺序写入很快。这是因为 SD 卡内部有 Flash 管理单元小写入会触发读-改-写操作而大块顺序写入可以充分利用内部缓存。所以优化策略是攒够一定数据再写。比如采集数据先写到 RAM 缓冲区攒到 4KB 或 8KB 再一次性写入 SD 卡。这样写入次数减少性能提升明显。但攒数据也有风险如果掉电缓冲区里的数据就丢了。所以要在写入性能和数据安全之间权衡。我的做法是普通历史数据攒 8KB 写一次关键数据比如故障前后的波形立即写入并f_sync。另外SD 卡的写入速度会随着使用时间下降因为内部垃圾回收所以设计时要留足余量。如果实测写入速度是 2MB/s设计时按 1MB/s 算避免卡变慢后丢数据。5.3 掉电时 SD 卡的数据完整性SD 卡最怕的是写入过程中掉电可能导致文件系统损坏甚至整张卡无法识别。防护措施有三层第一层是硬件掉电检测。用 ADC 监测电源电压当电压降到阈值比如 4.5V时触发中断立即停止写入并f_sync。第二层是文件系统冗余。FatFS 支持多份 FAT 表备份可以在ffconf.h里配置。另外定期把关键文件复制一份防止单点损坏。第三层是日志式写入。对于特别关键的数据不要直接覆盖旧文件而是写入新文件写完后再删除旧文件。这样即使新文件写坏了旧文件还在。我踩过最惨的一次坑是设备在现场运行了半年某天突然断电SD 卡上的历史数据文件全部变成 0 字节。后来查出来是文件系统的 FAT 表在掉电时损坏了。从那以后我在所有项目里都加了掉电检测和f_sync再也没出过类似问题。6. FPGA 侧的数据缓冲与搬运设计6.1 FPGA 在存储链路中的角色定位FPGA 在这套方案里不是存储控制器而是数据预处理和缓冲单元。它的核心任务有三个第一高速采集。比如 ADC 以 10MSPS 采样STM32 根本来不及处理FPGA 可以先接收、打包、缓存。第二数据打包。FPGA 把原始数据按固定格式打包成帧加上时间戳和校验减轻 STM32 的处理负担。第三流量整形。FPGA 用 FIFO 缓冲数据STM32 按自己的节奏读取避免高速数据冲垮 MCU。这个分工的关键是FPGA 不做文件系统不做复杂协议只做搬运和缓冲。这样 FPGA 逻辑简单资源占用少可靠性高。6.2 双口 RAM 与 FIFO 的选型FPGA 内部缓冲有两种常用结构双口 RAM 和 FIFO。双口 RAM适合随机访问场景比如 STM32 要读取特定地址的数据。它的优点是地址可控缺点是需要自己管理读写指针。FIFO适合顺序流场景比如采集数据按顺序写入、按顺序读出。它的优点是逻辑简单缺点是只能顺序访问。我的做法是两者结合用 FIFO 做采集数据的缓冲用双口 RAM 做命令和状态寄存器的映射。STM32 通过 FSMC 访问双口 RAM 读写命令通过 FIFO 读取采集数据。FIFO 的深度要算清楚。假设采集速率是 10MB/sSTM32 的读取速率是 5MB/s那么 FIFO 至少要能缓冲 1 秒的数据也就是 10MB。但 FPGA 内部 RAM 有限通常只有几百 KB 到几 MB。所以要么降低采集速率要么提高 STM32 读取速率要么在 FPGA 外部挂 DDR 做缓冲。6.3 FPGA 与 STM32 的握手协议FPGA 和 STM32 之间的数据交换需要一套握手协议确保数据不丢不重。我一般用中断 状态寄存器的方式FPGA 侧维护几个状态寄存器data_ready有新数据、fifo_countFIFO 中数据量、overflow溢出标志。当 FIFO 中的数据超过阈值时FPGA 拉高中断线通知 STM32 来读。STM32 收到中断后先读fifo_count确认数据量然后批量读取读完清除中断。如果overflow置位说明 STM32 读取太慢需要报警或降低采集速率。这套协议的关键是中断不能太频繁。如果每来一个数据就中断一次STM32 会被中断淹没。所以要设一个阈值比如 FIFO 半满时才中断让 STM32 批量读取。6.4 FPGA 缓冲与 STM32 落盘的速率匹配最后要算一笔账FPGA 采集速率、STM32 读取速率、SD 卡写入速率三者必须匹配。假设 FPGA 采集速率是 2MB/sSTM32 通过 FSMC 读取速率是 20MB/sSD 卡写入速率是 5MB/s。那么瓶颈在 SD 卡整体速率就是 5MB/sFPGA 的 2MB/s 完全能消化。但如果 FPGA 采集速率是 10MB/sSD 卡写入只有 5MB/s就会积压。这时候要么降低采集速率要么在 STM32 侧加更大的 RAM 缓冲要么换更快的存储介质。我的经验是设计时按最坏情况算留 50% 余量。比如实测 SD 卡写入 5MB/s设计时按 2.5MB/s 算这样即使卡老化变慢也不会丢数据。7. 分级存储的实测数据与踩坑记录7.1 三种介质的实测性能对比我在实际项目里测过这三种介质的性能数据如下介质写入速度读取速度擦除时间写入寿命实测掉电表现EEPROM (I2C 400kHz)约 1KB/s约 40KB/s无需擦除100 万次写入中掉电可能丢当前页NOR Flash (SPI 50MHz)约 500KB/s约 5MB/s4KB 扇区约 80ms10 万次配合有效标记可保证原子性SD 卡 (SPI 20MHz)约 2MB/s约 4MB/s内部管理有限需 f_sync 掉电检测从数据可以看出EEPROM 慢但可靠NOR Flash 均衡SD 卡快但需要保护。这也印证了分级存储的必要性。7.2 踩过的坑NOR Flash 扇区未擦除就写入有一次调试日志存储发现写入的数据读出来全是 0xFF。查了半天最后发现是写入前没有擦除扇区。NOR Flash 只能把 1 写成 0不能把 0 写成 1所以写入前必须先擦除擦除后全变 1。这个坑很典型新手容易忘。我的建议是在驱动层封装一个nor_flash_write函数内部自动判断是否需要擦除。如果目标地址不是扇区起始或者扇区未擦除就先擦除再写。这样上层调用就不用关心擦除细节了。7.3 踩过的坑SD 卡热插拔导致文件系统损坏工业现场有时候需要更换 SD 卡如果不断电直接拔卡文件系统很容易损坏。我遇到过好几次卡拔下来插电脑上读不出来的情况。解决方案有两个一是硬件上加卡检测引脚检测到拔卡时立即停止写入并卸载文件系统二是软件上定期f_sync减少未落盘的数据量。两者结合基本能避免热插拔损坏。另外SD 卡座要选带自锁的避免振动导致接触不良。工业现场的振动比实验室大得多这个细节不能忽略。7.4 踩过的坑FPGA FIFO 溢出导致数据丢失有一次做高速采集发现偶尔会丢一段数据。查了很久最后发现是FPGA FIFO 溢出了。原因是 STM32 在处理其他任务时没有及时读取 FIFO导致 FIFO 写满后新数据覆盖旧数据。解决办法是加溢出检测和流控。FPGA 侧检测到 FIFO 快满时拉高一个almost_full信号STM32 收到后优先处理数据读取。如果真的溢出了记录溢出次数在上位机报警。这个坑的教训是FPGA 和 STM32 之间的速率匹配不能想当然必须实测。设计时留的余量在实际工况下可能不够。8. 固件升级与存储方案的配合8.1 利用 NOR Flash 做固件备份NOR Flash 除了存日志还可以划一块区域做固件备份。STM32 的固件升级流程通常是新固件先写到 NOR Flash 的备份区校验通过后再由 Bootloader 搬运到内部 Flash。这样做的好处是升级过程中掉电不会变砖。因为内部 Flash 的旧固件还在只有新固件完整写入 NOR Flash 并校验通过后才会触发搬运。如果搬运过程中掉电Bootloader 下次上电会检测到搬运未完成重新搬运。固件备份区的大小要根据 MCU 的 Flash 容量来定。比如 STM32F407 有 1MB Flash备份区至少要 1MB。如果 NOR Flash 是 8MB日志区可以分 4MB备份区 2MB还剩 2MB 做其他用途。8.2 SD 卡升级包的校验与回滚如果通过 SD 卡升级流程类似升级包放在 SD 卡根目录Bootloader 读取升级包校验 CRC 和版本号然后写入 NOR Flash 备份区再搬运到内部 Flash。这里的关键是版本号比较。如果 SD 卡里的固件版本比当前版本低应该拒绝升级防止误操作降级。版本号可以存在 EEPROM 的参数区里升级前先比较。回滚机制也很重要。如果新固件启动后连续几次看门狗复位Bootloader 应该自动回滚到旧固件。这需要在 NOR Flash 里保留一份旧固件或者至少保留旧固件的备份。8.3 升级过程中的掉电恢复升级过程中掉电是最危险的情况。我的做法是分阶段标记在 NOR Flash 里维护一个升级状态字记录当前阶段IDLE、WRITING、VERIFYING、MOVING、DONE。每次进入新阶段前更新状态字。上电时 Bootloader 先读状态字如果是IDLE或DONE正常启动。如果是WRITING或VERIFYING说明升级包不完整丢弃备份区正常启动旧固件。如果是MOVING说明搬运过程中掉电重新搬运。这套机制能保证任何阶段掉电都不会变砖最多是升级失败回退到旧固件。9. 分级存储方案的扩展与选型建议9.1 什么场景适合这套方案这套 STM32 FPGA 三级存储的方案适合以下场景工业控制器、运动控制器、PLC 类设备需要保存参数、日志和历史数据。数据采集终端有高速采集需求同时要保存配置和记录。边缘网关需要本地缓存数据定期上传或导出。如果只是简单的数据记录没有高速采集需求可以去掉 FPGA直接用 STM32 EEPROM SD 卡成本更低。9.2 介质选型的决策树选型时可以按这个顺序决策数据是否需要掉电不丢否用 RAM 或 SD 卡缓存即可。数据写入频率是否高高优先考虑 NOR Flash 或 SD 卡低EEPROM 足够。数据容量是否大大用 SD 卡小用 EEPROM 或 NOR Flash。是否需要文件系统需要用 SD 卡 FatFS不需要用 EEPROM 或 NOR Flash 裸写。是否有高速采集有加 FPGA 做缓冲没有STM32 直接处理。9.3 成本与可靠性的平衡最后说成本。EEPROM 便宜但容量小NOR Flash 中等SD 卡容量大但可靠性差。实际选型时不要一味追求高可靠也不要一味追求低成本要根据数据的重要程度分级。我的原则是参数用最可靠的介质日志用均衡的介质历史数据用最便宜的介质。这样整体成本和可靠性都能接受。另外工业现场的环境因素也要考虑温度范围、振动、湿度、电磁干扰。SD 卡在高温下容易失效NOR Flash 和 EEPROM 的工业级型号能到 -40°C~85°C。如果现场环境恶劣SD 卡可能不是好选择要考虑用 eMMC 或者工业级 Flash。我在实际项目里还遇到过一个情况客户要求数据保存 10 年但 SD 卡的寿命和可靠性达不到。最后方案改成了 NOR Flash 定期导出虽然容量小但可靠性满足要求。所以选型时一定要先问清楚数据的保存期限和可靠性要求再决定介质。这套分级存储方案我用了好几个项目整体稳定性不错。最关键的经验是不要试图用一种介质解决所有问题也不要让 FPGA 做它不擅长的事。STM32 管逻辑和文件系统FPGA 管高速缓冲三种介质各司其职这样系统才稳。
返回列表