1. 工业控制器存储方案的整体设计思路工业控制器和消费类电子产品最大的区别在于它得在强电磁干扰、宽温、长时间无人值守的环境下稳定跑五年甚至十年以上。这种场景下数据存储方案的设计不能只考虑“能存能读”更要考虑掉电安全、写入寿命、数据分级和现场可维护性。我手上这个项目是一台面向产线设备的工业控制器主控用STM32F407搭配一颗FPGA做高速数据采集和预处理需要存储的数据大致分成三类参数配置、运行日志、采集原始数据。参数配置包括设备ID、校准系数、通信地址、PID参数等特点是数据量极小几百字节到几KB但修改频率低、掉电绝对不能丢这类数据我放在EEPROM里。运行日志是设备运行过程中的状态记录、报警信息、操作记录数据量中等几十MB级别写入频繁但可以容忍一定的历史覆盖这类数据放在NOR Flash里。采集原始数据是FPGA从ADC拿到的波形数据数据量大GB级别、写入带宽要求高这类数据放在SD卡里。这就是“分级存储”的核心逻辑不同特性的数据用不同介质承载各取所长。为什么不用一颗大容量Flash全包了因为工业场景下没有一种存储介质能同时满足“小数据高可靠、中数据高频写、大数据高带宽”这三个矛盾需求。EEPROM的字节级擦写和百万次寿命适合参数NOR Flash的扇区管理和较快读取适合日志SD卡的大容量和文件系统生态适合批量数据。分开存储还有一个隐性好处故障隔离。SD卡坏了不影响参数读取NOR Flash写坏了不影响设备启动这在现场维护时非常关键。STM32和FPGA的分工也要说清楚。STM32负责文件系统管理、通信协议、人机交互和EEPROM/NOR Flash的读写控制FPGA负责高速数据流的缓冲和向SD卡控制器的数据搬运。两者之间通过FSMC或SPI总线交互STM32把SD卡的写入命令和数据通过总线交给FPGAFPGA用硬件状态机实现高速写入避免STM32软件开销成为瓶颈。这个架构在工业控制器里很常见因为FPGA的并行能力天然适合处理连续数据流。2. 三种存储介质的核心细节与选型依据2.1 EEPROM参数存储的可靠基石EEPROM选的是ST的M24C6464Kbit容量I2C接口SOIC-8封装。为什么是它而不是STM32内部的Flash模拟EEPROM内部Flash擦写寿命通常只有1万次左右而且擦除单位是扇区通常1KB或2KB改一个字节要擦整个扇区工业设备参数可能每天改几次几年下来内部Flash就废了。M24C64的字节级擦写寿命是400万次按每天改10次算能用一千多年这才是工业级参数存储该有的寿命。I2C接口的硬件设计有几个坑要注意。上拉电阻不能随便选4.7kΩ在100kHz标准模式下没问题但如果跑400kHz快速模式建议降到2.2kΩ否则上升沿太慢会导致通信失败。我实测过用10kΩ上拉跑400kHz误码率明显上升。另外EEPROM的WP写保护引脚一定要接硬件上通过一个GPIO控制设备正常运行时拉高禁止写入只有需要修改参数时才拉低这样能防止程序跑飞时误写参数。地址规划上我把M24C64的8KB空间分成几个区域0x0000-0x00FF存设备基本信息序列号、型号、生产日期0x0100-0x01FF存校准系数0x0200-0x02FF存通信参数0x0300-0x03FF存PID参数后面留作扩展。每个区域头部加一个2字节的CRC16校验读取时先校验再使用校验失败就加载默认值并记录故障。这个做法在工业现场救过我好几次有一次现场电磁干扰导致EEPROM某几个字节翻转CRC校验直接发现了问题设备自动切到默认参数继续运行没有停机。2.2 NOR Flash日志存储的平衡之选NOR Flash选的是Winbond的W25Q12816MB容量SPI接口。为什么不用NAND FlashNAND便宜、容量大但有坏块管理问题工业控制器如果跑一个完整的坏块管理文件系统如UBIFS软件复杂度会飙升而且NAND的位翻转率比NOR高需要ECC纠错。NOR Flash虽然贵一些、容量小一些但可靠性高、随机读取快、接口简单对于几十MB的日志存储来说完全够用。W25Q128的扇区是4KB块是64KB。日志写入策略我采用的是“扇区轮转磨损均衡”。具体做法是把16MB分成256个64KB的块前4个块作为索引区记录每个日志块的写入状态和序号后面252个块作为数据区循环写入。每次写日志时先读索引区找到当前写入块写满后更新索引并切换到下一个块。这样每个块的擦写次数大致均匀按每天写1MB日志算16MB容量能存16天252个块轮转一圈要252天每个块一年擦写不到2次W25Q128的10万次擦写寿命绰绰有余。SPI接口的时钟频率我设的是STM32 SPI2的42MHzAPB1时钟84MHz二分频实测W25Q128在80MHz下也能工作但工业环境里降频能提高抗干扰余量。SPI的CS、CLK、MOSI、MISO四根线要走等长长度差控制在5mm以内否则高速下时序会出问题。另外MISO线上建议串一个22Ω电阻抑制反射。2.3 SD卡大数据存储的性价比之王SD卡选的是工业级microSD卡8GB容量Class 10速度等级。为什么不用eMMCeMMC焊接在板上可靠性更高但容量固定、更换困难工业控制器有时候需要把数据卡拔出来拿到PC上分析SD卡的可插拔性在这里是优势。当然可插拔也意味着连接器可靠性要过关我选的是带自锁的push-push卡座插拔寿命1万次以上。SD卡的接口模式有两种SPI模式和SDIO模式。SPI模式简单但速度慢理论最大25MHz时钟实际写入速度也就2-3MB/s。SDIO模式用4位数据线STM32F407的SDIO时钟可以到48MHz实际写入速度能到10-15MB/s。对于FPGA采集的原始数据这个速度才够用。所以硬件上我走的是SDIO 4位模式CMD、CLK、D0-D3六根线CLK线上串22Ω电阻数据线上拉10kΩ电阻。SD卡的文件系统用FatFs这是STM32生态里最成熟的方案。但FatFs有个问题频繁小文件写入会导致FAT表频繁更新SD卡内部控制器做垃圾回收时会产生不可预测的延迟。我的做法是日志和原始数据都用大文件追加写每个文件固定64MB写满后关闭再开新文件。这样FAT表更新频率大大降低写入延迟也更稳定。3. 硬件连接与STM32-FPGA协同实操3.1 硬件连接方案与引脚分配先看整体连接。STM32F407通过I2C1连接M24C64PB6是SCLPB7是SDA上拉电阻4.7kΩ到3.3VWP引脚接PC13。W25Q128通过SPI2连接PB13是SCKPB14是MISOPB15是MOSIPB12是CS另外PB12还兼作FPGA的片选信号之一。SD卡通过SDIO连接PC8-PC12是D0-D3和CLKPD2是CMD所有数据线和CMD线上拉10kΩ。FPGA选的是Xilinx Spartan-6 XC6SLX9它和STM32之间通过FSMC总线连接。STM32的FSMC把FPGA映射成一片外部SRAM地址范围0x60000000-0x63FFFFFF。STM32往这个地址写数据FPGA内部的状态机就能收到。具体分工是STM32把SD卡的写入命令、数据长度、数据缓冲区地址通过FSMC寄存器写给FPGAFPGA从自己的Block RAM里取数据通过SDIO控制器写入SD卡。这样STM32只需要把数据搬到FPGA的Block RAM里剩下的高速写入由FPGA硬件完成STM32的CPU占用率从70%降到15%以下。这里有个细节STM32和FPGA的FSMC总线时序要匹配。STM32的FSMC读写的建立时间、保持时间要按FPGA内部逻辑的延迟来调。我实测下来地址建立时间设2个HCLK周期、数据保持时间设4个HCLK周期比较稳。如果设太短FPGA还没准备好数据STM32就读了会读到错误值设太长又浪费带宽。这个参数需要用示波器抓FSMC的NE、NWE、NOE信号来调。3.2 EEPROM读写代码实操I2C读写EEPROM的代码网上很多但工业场景下要加几个保护。首先是超时机制I2C总线如果被从机拉死STM32的硬件I2C会一直等所以每次读写都要加超时计数超时后重新初始化I2C外设。其次是页写边界处理M24C64的页大小是32字节跨页写会回卷覆盖所以写数据时要按页对齐拆分。#define EEPROM_ADDR 0xA0 #define PAGE_SIZE 32 #define I2C_TIMEOUT 10000 uint8_t EEPROM_Write(uint16_t memAddr, uint8_t *data, uint16_t len) { uint16_t i 0; while (i len) { uint16_t pageRemain PAGE_SIZE - (memAddr % PAGE_SIZE); uint16_t writeLen (len - i pageRemain) ? (len - i) : pageRemain; if (HAL_I2C_Mem_Write(hi2c1, EEPROM_ADDR, memAddr, I2C_MEMADD_SIZE_16BIT, data[i], writeLen, I2C_TIMEOUT) ! HAL_OK) { return 1; } HAL_Delay(5); // 等待EEPROM内部写周期完成 memAddr writeLen; i writeLen; } return 0; }注意那个HAL_Delay(5)M24C64的写周期最大5ms写完一页必须等它内部完成才能写下一页否则数据会丢。我见过有人用HAL_Delay(1)结果批量写参数时偶尔丢数据查了半天才发现是写周期没等够。另外读操作不需要延时可以直接连续读。3.3 NOR Flash日志写入实操W25Q128的写入流程是写使能→擦除扇区→写使能→页编程。擦除一个4KB扇区典型时间45ms最大400ms所以擦除操作要放在后台任务里做不能阻塞主循环。我的做法是维护一个空闲扇区池后台任务提前擦好几个扇区备用写日志时直接从池里取取完了再补充。uint8_t NOR_LogWrite(uint8_t *logData, uint16_t len) { static uint32_t currentSector LOG_START_SECTOR; static uint16_t sectorOffset 0; if (sectorOffset len SECTOR_SIZE) { currentSector GetNextFreeSector(); sectorOffset 0; } W25Q_WriteEnable(); W25Q_PageProgram(currentSector * SECTOR_SIZE sectorOffset, logData, len); while (W25Q_IsBusy()); sectorOffset len; UpdateIndex(currentSector, sectorOffset); return 0; }GetNextFreeSector()从空闲池里取扇区如果池空了就触发后台擦除。UpdateIndex()更新索引区的记录索引区也用类似的方式轮转写入避免索引区自己成为磨损瓶颈。3.4 SD卡高速写入与FPGA协同SD卡的高速写入是整个方案里最复杂的部分。STM32的SDIO外设配合DMA能跑到10MB/s以上但FatFs的文件写入API有文件系统开销实际速度会降到5-6MB/s。为了突破这个瓶颈我把原始数据的写入路径改成STM32只负责创建文件和写入文件头实际数据由FPGA通过SDIO控制器直接写入SD卡的连续扇区绕过文件系统。具体做法是STM32先用FatFs创建一个固定大小的文件比如64MB然后通过f_lseek把文件指针移到末尾再通过f_expand预分配簇。接着STM32把文件的起始扇区号通过FSMC告诉FPGAFPGA从ADC拿到数据后直接往这些扇区里写。写满64MB后FPGA发中断给STM32STM32关闭文件并创建新文件。这样写入速度能到12-15MB/s满足大多数工业采集场景。FPGA侧的SDIO控制器用Verilog实现核心是一个状态机IDLE→CMD24写单块→等待响应→发送数据→等待CRC状态→IDLE。CMD24是写单块命令如果要连续写多块用CMD25。状态机的时钟用50MHzSDIO时钟分频到25MHz实测写入稳定。// SD卡写单块状态机核心片段 always (posedge clk) begin case (state) IDLE: if (write_req) state SEND_CMD24; SEND_CMD24: begin cmd_reg {8h51, 32h00000000, 8hFF}; // CMD24 state WAIT_RESP; end WAIT_RESP: if (resp_received) state SEND_DATA; SEND_DATA: begin if (data_cnt 512) state WAIT_CRC; else data_cnt data_cnt 1; end WAIT_CRC: if (crc_ok) state IDLE; endcase end这段代码是简化版实际还要处理超时、错误重试、多块传输的边界。FPGA写SD卡最大的坑是时序SDIO的建立时间和保持时间要按SD卡规范来数据在时钟上升沿采样建立时间最小5ns保持时间最小5ns。我用50MHz时钟周期20ns数据在时钟下降沿变化上升沿采样余量足够。4. 常见问题与排查技巧实录4.1 EEPROM读写失败排查EEPROM最常见的问题是I2C通信失败表现为HAL_I2C_Mem_Read返回HAL_ERROR。排查顺序是先量SCL和SDA的静态电平正常应该是3.3V再量上拉电阻如果阻值太大比如10kΩ高速下波形会变圆然后用示波器抓I2C波形看起始条件、地址、ACK是否正常。我遇到过一种情况EEPROM的A0/A1/A2地址引脚悬空导致地址不确定有时候能通有时候不通。后来把三个引脚都接地问题解决。另一个坑是写保护。M24C64的WP引脚如果悬空内部下拉默认允许写入但如果PCB上WP接到了VCC那就永远写不进去。我有个项目WP引脚误接到了3.3V调试了半天才发现。所以WP一定要用GPIO控制不要直接接固定电平。4.2 NOR Flash数据丢失排查NOR Flash数据丢失通常有两个原因一是擦除不完整就写入二是写使能没发。W25Q128的写操作前必须发0x06写使能命令如果漏了数据写不进去但也不报错读出来还是0xFF。我的做法是在W25Q_PageProgram函数里强制先发写使能并且读状态寄存器确认WEL位为1再写。擦除不完整的问题更隐蔽。W25Q128的扇区擦除命令是0x20块擦除是0xD8整片擦除是0xC7。如果发错命令比如想擦扇区结果发了块擦除会把64KB都擦掉日志就丢了。我在代码里加了命令校验擦除前先读一下目标地址的数据如果已经是0xFF就跳过擦除这样能减少不必要的擦除操作延长寿命。4.3 SD卡写入速度慢排查SD卡写入速度慢的原因很多按影响程度排序第一是SDIO时钟频率如果跑在SPI模式或者SDIO 1位模式速度直接砍半第二是文件系统开销FatFs的f_write每次都要更新FAT表小数据量写入时开销占比很大第三是SD卡本身的性能Class 4的卡写入速度可能只有2MB/sClass 10能到10MB/s以上。我实测过同一张卡在SPI模式和SDIO 4位模式下的速度差异SPI模式写入2.1MB/sSDIO 4位模式写入11.3MB/s差了5倍多。所以硬件设计时一定要走SDIO 4位模式不要图省事用SPI。另外DMA一定要开STM32的SDIO配合DMA能把CPU占用率从80%降到10%以下。还有一个容易被忽略的点SD卡的供电。SD卡写入瞬间电流可能到100mA如果电源走线太细或者去耦电容不够电压会跌落导致写入失败。我在SD卡座旁边放了两个10uF的陶瓷电容和一个100nF的高频电容问题解决。4.4 STM32与FPGA通信异常排查STM32和FPGA通过FSMC通信最常见的问题是时序不匹配。表现是STM32读到的数据偶尔错误或者写入FPGA的数据FPGA收不到。排查方法是先用STM32往FPGA的一个固定寄存器写一个已知值再读回来看是否一致。如果不一致用示波器抓FSMC的NE、NWE、NOE、地址线和数据线看建立时间和保持时间是否满足FPGA的要求。我遇到过一种情况FSMC的地址线和数据线走线太长超过10cm而且没有做等长导致高速下数据错位。后来把FSMC时钟从168MHz降到84MHz问题缓解但根本解决还是要重新布线把地址线和数据线走等长长度差控制在5mm以内。另一个坑是FPGA的IO电平标准。STM32的FSMC是3.3V LVTTLFPGA的IO Bank电压要设成3.3V如果设成2.5V通信会不稳定。这个在FPGA的约束文件里要明确写出来。4.5 常见问题速查表问题现象可能原因排查方法解决方案EEPROM读写失败I2C上拉电阻过大示波器抓波形换成2.2kΩ上拉EEPROM写入无效WP引脚接高量WP引脚电平GPIO控制WPNOR Flash读出全FF写使能未发读状态寄存器WEL位写前强制发0x06NOR Flash数据丢失擦除命令错误检查擦除命令码确认用0x20擦扇区SD卡写入慢SPI模式或1位模式检查SDIO配置改4位模式DMASD卡写入失败供电不足量写入时电压加10uF去耦电容FSMC通信错误时序不匹配示波器抓FSMC信号调建立/保持时间FPGA收不到数据IO电平不匹配量FPGA IO电压约束文件设3.3V5. 存储方案的可靠性与寿命评估工业控制器的存储方案不能只看功能还要算寿命。EEPROM按每天写10次算400万次寿命能用1000年以上完全不用担心。NOR Flash按每天写1MB算16MB容量能存16天252个块轮转每个块一年擦写约1.4次10万次寿命能用70年。SD卡按每天写1GB算8GB容量能存8天但SD卡的擦写寿命通常只有3000-5000次P/E Cycle按每天全盘擦写一次算8年就到寿命了。所以SD卡是寿命短板。我的做法是SD卡只存原始数据不存参数和日志坏了直接换卡不影响设备运行。另外SD卡的文件系统要定期做FSCK检查防止文件系统损坏。STM32上跑一个简化的FSCK每次上电时检查FAT表和目录项发现异常就重建文件系统。还有一个可靠性设计是掉电保护。工业现场突然断电是常态掉电时正在写的EEPROM页可能只写了一半NOR Flash的扇区可能擦了一半SD卡的文件可能没关闭。我的做法是EEPROM用双备份CRC写的时候先写备份区再写主区读的时候如果主区CRC错就用备份区。NOR Flash的日志用追加写每条日志带序号和CRC掉电后扫描日志区找到最后一条完整日志后面的丢弃。SD卡用FatFs的f_sync定期同步掉电最多丢最后几秒的数据。最后说一个经验工业控制器的存储方案一定要做高低温测试。-40℃到85℃循环每个温度点跑24小时读写测试。我遇到过EEPROM在-40℃下写周期变长到10ms的情况如果代码里延时还是5ms就会丢数据。所以延时参数要留足余量或者用状态寄存器轮询代替固定延时。这个方案在产线设备上跑了两年多EEPROM零故障NOR Flash换过一次因为日志写太满没及时清理SD卡换过三次都是容量满了或者卡本身老化。整体可靠性满足工业要求成本也比全用eMMC或工业级SSD低得多。后续如果要扩展可以在FPGA里加一个DDR缓存把SD卡写入的突发性进一步平滑不过那是另一个话题了。