☰
工业控制器分级存储方案:STM32+FPGA与EEPROM/NOR Flash/SD卡选型实战
2026/10/1 8:49:22 网站建设 项目流程

工业控制器这类设备有个很现实的问题:它不像消费电子那样可以随便联网、随便扩容,一旦装到现场机柜里,可能三五年都没人碰它。但在这三五年里,它得持续记录运行日志、保存标定参数、缓存采集数据,甚至还要支持现场升级固件。这些数据有的要求掉电不丢、有的要求写入寿命够长、有的要求容量够大能存历史记录——指望一颗芯片全包了,基本不现实。

我在做运动控制器和边缘网关类项目时,反复验证过一套分级存储的组合: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 还是 SPI

EEPROM 的接口选择主要看两点:引脚资源和写入频率。

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 更适合需要 XIP(Execute 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 或 FIFO,STM32 通过 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(如 AT24C02,2KB)用 8 位地址,大容量(如 AT24C256,256KB)用 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 就跳过这条记录。这样即使写入过程中掉电,最多丢失最后一条不完整的日志,不会影响之前的记录。

这个思路和数据库的 WAL(Write-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/s,STM32 的读取速率是 5MB/s,那么 FIFO 至少要能缓冲 1 秒的数据,也就是 10MB。但 FPGA 内部 RAM 有限,通常只有几百 KB 到几 MB。所以要么降低采集速率,要么提高 STM32 读取速率,要么在 FPGA 外部挂 DDR 做缓冲。

6.3 FPGA 与 STM32 的握手协议

FPGA 和 STM32 之间的数据交换需要一套握手协议,确保数据不丢不重。我一般用"中断 + 状态寄存器"的方式:

FPGA 侧维护几个状态寄存器:data_ready(有新数据)、fifo_count(FIFO 中数据量)、overflow(溢出标志)。当 FIFO 中的数据超过阈值时,FPGA 拉高中断线,通知 STM32 来读。

STM32 收到中断后,先读fifo_count确认数据量,然后批量读取,读完清除中断。如果overflow置位,说明 STM32 读取太慢,需要报警或降低采集速率。

这套协议的关键是中断不能太频繁。如果每来一个数据就中断一次,STM32 会被中断淹没。所以要设一个阈值,比如 FIFO 半满时才中断,让 STM32 批量读取。

6.4 FPGA 缓冲与 STM32 落盘的速率匹配

最后要算一笔账:FPGA 采集速率、STM32 读取速率、SD 卡写入速率,三者必须匹配。

假设 FPGA 采集速率是 2MB/s,STM32 通过 FSMC 读取速率是 20MB/s,SD 卡写入速率是 5MB/s。那么瓶颈在 SD 卡,整体速率就是 5MB/s,FPGA 的 2MB/s 完全能消化。

但如果 FPGA 采集速率是 10MB/s,SD 卡写入只有 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 介质选型的决策树

选型时可以按这个顺序决策:

  1. 数据是否需要掉电不丢?否,用 RAM 或 SD 卡缓存即可。
  2. 数据写入频率是否高?高,优先考虑 NOR Flash 或 SD 卡;低,EEPROM 足够。
  3. 数据容量是否大?大,用 SD 卡;小,用 EEPROM 或 NOR Flash。
  4. 是否需要文件系统?需要,用 SD 卡 + FatFS;不需要,用 EEPROM 或 NOR Flash 裸写。
  5. 是否有高速采集?有,加 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 管高速缓冲,三种介质各司其职,这样系统才稳。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询