做工业控制器的这些年,我最怕听到的现场反馈不是“代码有bug”,而是“设备断电重启之后,参数全乱了,日志也没了”。参数、日志、固件、标定值,这些数据对系统的意义完全不一样,如果都塞进同一块Flash里硬扛,要么容量不够,要么寿命提前耗尽。这篇是硬件篇的第12篇,想认真聊聊我一直在用的一套方案:STM32+FPGA分级存储,把EEPROM、NOR Flash、SD卡各归其位,让数据按“身价”进不同赛道。这篇文章适合正在做仪器仪表、运动控制、边缘网关的同学参考,也适合刚从单片机裸机过渡到带数据落盘需求的新手。
1. 为什么工业控制器要做分级存储,而不是一块大Flash打天下
1.1 先看清:控制器里到底躺着哪几类数据
工业控制器的数据远没有想象中那么单一。我习惯在项目设计初期先做一张“数据资产清单”,把系统里所有需要掉电保存的内容列出来,再按写入频率、数据量、重要性去分类。实际项目中通常会分出这么几类:
- 设备身份与出厂信息:设备ID、硬件版本、生产日期、序列号,写入次数极少,但丢了就很麻烦,属于“一锤定音”型数据。
- 标定参数与校准表:传感器零点、满偏、温补系数、PID参数,可能一个月改一次,或者半年校准一次,量不大,但每次改完都希望立刻生效。
- 运行状态与计数器:累计运行时间、报警次数、总里程/总产量,这类数据是边跑边写,写入频率从小时级到分钟级不等。
- 运行日志与事件记录:故障码、故障时间戳、操作记录,数据量会随时间增长,需要定期清理,但现场分析时很依赖它。
- 波形快照与批量数据:模拟量采样波形、加速度曲线、工艺曲线,这些是FPGA或高速ADC采出来的“大数据”,一次事件可能产生几百KB甚至几MB。
- 固件与应用代码:Bootloader、App、FPGA bitstream,更新频率低,但在OTA升级时占据大块存储空间。
看到这个清单就明白了,不同数据的“脾气”完全不同,有的又小又金贵,有的又大又粗糙。硬要用一块存储介质去兼容所有属性,总有一头要妥协。
1.2 “一种介质打天下”的三个翻车现场
我见过不少项目一开始图省事,所有数据全放NOR Flash里,或者干脆一块大容量SD卡搞定,结果在工业现场反复出问题。说几个真实的翻车案例。
第一个案例,运行日志直接往EEPROM里塞。EEPROM容量小,大家基本不会拿它存日志,但有人拿它当“频繁计数器”用,比如每跑一段距离就写一次累计里程。EEPROM虽然能抗10万次到100万次擦写,但工业设备一天写几百次的话,几个月就把寿命吃干净。等我们看到现场设备计数不准、参数莫名回到出厂值的时候,就是EEPROM某一块已经写穿了的典型表现。
第二个案例,配置参数只放在NOR Flash里,且频繁更新。NOR Flash写入前必须先擦除,而擦除以扇区为单位,4KB的扇区一旦过于频繁地写完再擦,很快会出现坏块。更麻烦的是,NOR Flash里还同时存着固件,如果因为配置读写把扇区搞坏了,OTA升级时固件没地方落,整机就瘫了。
第三个案例,SD卡被当成U盘一样裸写,完全不考虑掉电。工业现场断电不是新闻,SD卡内部的FAT表如果写在掉电瞬间,下一次上电大概率文件系统损坏,表现为日志文件读不出来,或者整个卡变成“只读”。用户说“SD卡内部寄存器锁死”其实有一半就是文件系统或者控制器状态锅。
1.3 分级存储的决策模型:把数据按“流量、频率、身价”分流
踩过坑之后,我现在设计存储方案只用一个模型:看数据的写入频率、数据量、可靠性要求三个维度,然后分赛道安排介质。
- 高频率、小数据量、不能丢的,进EEPROM。EEPROM支持字节级擦写,读改写简单,哪怕一天写几十次也能用上好几年。
- 中频率、中等数据量、需要随机读取快的,进NOR Flash。固件、配置快照、算法表放这里,读写逻辑简单,掉电安全性比SD卡强。
- 低频次、大体量、允许上文件系统管理的,进SD卡。日志、波形、历史记录,容量不够就换大卡,成本还便宜。
简单说,就是让亿级的EEPROM、十万级的NOR Flash、万级块管理的SD卡各干各最擅长的活。分级不是“硬件堆料”,而是让数据保存这件事在寿命、成本和可靠之间取得平衡。
2. 三种存储介质拆解:EEPROM、NOR Flash、SD卡到底适合干什么
2.1 EEPROM:小而稳的“参数保险箱”
EEPROM的原理是基于浮栅晶体管,每个字节都可以独立擦写,不用先擦整块再写。和Flash最大的区别在于写粒度:NOR Flash要按扇区或块擦除,EEPROM按字节就能改,这让它在存放“改完马上要掉电保存”的参数时非常舒服。
选型上工业设备常用AT24C02到AT24C256这系列,接口是I2C,7位从机地址由A0/A1/A2引脚决定,常见地址是0xA0写、0xA1读。AT24C256容量32KB,支持64字节页写;容量小一些的AT24C16则只支持16字节页写。页写是EEPROM的灵魂功能,因为单字节写虽然灵活,但I2C每传一个字节都要停一下,效率太低。批量写参数时,拼满一页再写,能省下大量总线时间。
EEPROM的寿命标称通常10万到100万次,工业上按最保守的10万次算,如果每天写10次,能用27年,完全足够。参数区的最佳实践是:以“槽位+版本号+CRC”方式组织,不要每次改动都覆盖同一个地址,而是写新槽位,下次读最新版本。这样就算写入中途掉电,旧数据也还在,最多是丢失本次更新。
2.2 NOR Flash:能原地执行、按扇区擦的“固件仓库”
NOR Flash的读取延迟低,支持XIP(片上执行),很多处理器可以直接在NOR Flash里跑代码。它按扇区擦除,常见的W25Q64、W25Q128,扇区大小4KB,块大小64KB,擦除后数据全变0xFF,编程时只能把位从1改成0,所以写之前必须先擦。
SPI NOR Flash的命令集非常固定,核心要掌握这几个命令:0x06写使能、0x03读数据、0x02页编程、0x20扇区擦除、0x52/0xD8块擦除、0x05读状态寄存器。写流程永远是“写使能→扇区擦除(如果原来是脏区)→页编程→读状态等忙”→回读校验”。页编程一般支持256字节,跨页地址会被硬件绕回,所以编程数据控制在页内是最稳妥的。
NOR Flash的寿命通常标称1万到10万次擦写,注意是每个扇区独立计数的。所以哪怕整颗芯片标称10万次,如果你长期只擦写一个扇区,而其他扇区没动,这个扇区照样提前报废。这就是为什么配置数据放NOR Flash时也要做扇区轮换,不能每次都写固定地址。
2.3 SD卡:大容量担当,但也是最需要“伺候”的存储
SD卡内部自带控制器,负责坏块管理和映射,这让我们用起来很方便,但也很容易让人大意。SD卡有SDIO和SPI两种访问方式,工业控制器普遍用SPI模式,简单、引脚少,代价是速度低一些,但对于日志型数据完全够用。
SD卡分成SDSC、SDHC、SDXC三种,容量对应2GB以下、2GB到32GB、64GB以上,文件系统一般用FAT16/FAT32。工业场景我最推荐32GB以下的SDHC卡配FAT32,兼容性好,簇大小4KB到32KB按需选。真不要为了堆参数去选512GB大卡,一是SDXC对控制器的协议要求更高,二是FAT32原生不认64GB以上空间,强行管理反而引入风险。
SD卡的掉电安全是重灾区。卡内部有缓存,写命令发出去不代表数据真的落盘了,命令响应之后还有内部“忙”状态。文件系统的FAT表更新也不是一次原子操作,如果恰好在更新FAT表时掉电,目录项和文件簇链可能全损坏。所以掉电保护不只是“硬件加电容”,更要在软件上严格执行“写文件→f_sync→再更新目录”。
2.4 三介质一张表定死分工
| 介质 | 典型容量 | 接口 | 写粒度 | 标称寿命 | 掉电风险 | 最适合存什么 |
|---|---|---|---|---|---|---|
| EEPROM | KB级 | I2C | 字节级 | 10万~100万次 | 低 | 设备身份、标定参数、计数 |
| NOR Flash | 1MB~256MB | SPI/XIP | 扇区擦除+页编程 | 1万~10万次/扇区 | 中低 | 固件、配置快照、算法表 |
| SD卡 | 数百MB~数十GB | SDIO/SPI | 512B扇区+FAT管理 | 受文件系统与坏块影响 | 中高 | 日志、波形、历史数据 |
每次项目评审时我都会甩出这张表,让软件同事明白为什么不能把数据库思想直接搬到单片机里:数据库看重的事务和原子性,在这里要靠外加的设计去保证。
3. 硬件架构:STM32和FPGA这场戏怎么分工
3.1 系统架构:谁管小存储,谁跑大流水
STM32和FPGA的组合在工业控制器里很常见,FPGA负责高速采集、编码器计数、PWM波形产生,STM32负责通信协议、人机交互、控制算法和存储管理。存储挂在谁下面,本质上取决于谁更了解数据的“生命周期”。
我推荐的分工是:EEPROM和NOR Flash挂在STM32的I2C、SPI接口上,SD卡也由STM32接管,通过SDIO或SPI访问。STM32作为主CPU,启动时第一个要干的事就是读配置,配置的读者、修改者、校验者都是它,挂它下面最顺。FPGA不跑操作系统,也不擅长处理FAT文件系统,让它直接去管理SD卡文件,成本大且收益低。
那FPGA在存储这件事上就完全不参与吗?也不是。FPGA的作用是把数据“撮合”到存储链路上。比如高速ADC连续采了1MB波形,如果让STM32用IO口去搬运,CPU占用率会飙到没法干活。这时候FPGA先把数据压进内部RAM或者双口RAM,攒够一帧再通知STM32过来批量搬走;更彻底的方案是FPGA把数据通过SPI直接写进SD卡的固定扇区,STM32只负责事后补文件系统信息。
3.2 电路设计里容易翻车的几个细节
硬件实现时早期看着没啥问题,量产后才暴露出一堆雷。先说I2C总线,EEPROM挂在I2C上,SCL和SDA必须接上拉电阻,常见取4.7kΩ,总线速度快或者距离长时改用2.2kΩ。但要注意总线上挂多颗芯片时上拉不宜太小,否则灌电流超标。EEPROM的写保护WP引脚,我习惯引到MCU的GPIO上,平时拉高防误写,在需要写入参数时拉低,能挡住不少莫名其妙的数据被改。
SPI NOR Flash这边,最容易翻车的是WP#和HOLD#这两个引脚悬空。很多廉价板子只把CS、CLK、MOSI、MISO连好,WP#和HOLD#什么都不接,结果一上电Flash可能进入保护状态,写操作全部失败,时好时坏的非常难查。正确做法是WP#、HOLD#都应上拉至VCC,确保不误触发写保护和暂停传输。
SD卡电路更要小心。SDIO模式需要CMD、DAT0-DAT3共5根信号线,规范建议每根线上拉10kΩ到100kΩ;如果用SPI模式,CS、MOSI、MISO、SCK四个信号同样建议上拉,SD卡在初始化前若检测不到上拉会认为总线未准备好。SD卡插座的CD卡检测引脚千万别省,量产时工人忘记插到底、接触不良,有CD引脚能辅助判断卡是否存在。
3.3 电源、复位与掉电检测的配合
存储器的供电我建议走独立的100nF+10μF去耦电容,离芯片引脚越近越好。STM32、FPGA、存储都工作在3.3V时,注意3.3V的带载能力,某些大容量SD卡在写操作瞬间电流能冲到100mA以上,电源纹波一抖,数据就写错。
掉电保护是存储系统的生命线。STM32有PVD可编程电压检测器,可以在电源降到阈值以下时立刻触发中断。我的习惯是设置PVD中断,中断里做三件事:封死写操作防止数据写到一半;把当前关键状态缓存刷到EEPROM或NOR Flash;如果文件系统已挂载,立刻执行f_sync。但要记住,掉电中断到真正没电只有毫秒级时间,能否写完整取决于板子上有没有后备电容和大电容储能。粗略估算:假设系统掉电后还需要2ms做保存,电流约100mA,允许压降0.5V,则电容C=I×Δt/ΔV≈100mA×2ms/0.5V=400μF,多加几个470μF就能撑住。
4. 驱动层实现:EEPROM、NOR Flash、SD卡读写细节
4.1 在STM32上把I2C EEPROM跑稳
用STM32的硬件I2C跑EEPROM,代码看似简单,坑全藏在细节里。以AT24C256为例,地址是两字节,写操作要先发高字节地址再发低字节地址。页写注意不能越过页边界,64字节页写如果从地址0x3F开始连续写10个字节,写到0x40之后硬件会绕回同一页的开头,把前面的数据覆盖掉。所以写数据前必须做跨页拆分。
我把I2C EEPROM封装成三个接口:读若干字节、写若干字节(内部自动处理跨页)、写后回读校验。写完成后必须等待内部写周期,最简单的是延时5ms,更稳妥的办法是发一个“零长度写”,通过ACK来查询是否写完,但要注意ACK只在写周期结束后才恢复。下面的代码用HAL库实现了一个跨页写函数:
void EEPROM_WriteBytes(uint16_t addr, uint8_t *buf, uint16_t len) { while (len > 0) { uint16_t page_left = EEPROM_PAGE_SIZE - (addr % EEPROM_PAGE_SIZE); uint16_t chunk = (len < page_left) ? len : page_left; uint8_t hdr[2]; hdr[0] = (uint8_t)(addr >> 8); hdr[1] = (uint8_t)(addr & 0xFF); HAL_I2C_Mem_Write(&hi2c1, EEPROM_W_ADDR, addr, I2C_MEMADD_SIZE_16BIT, buf, chunk, 100); while (HAL_I2C_GetState(&hi2c1) != HAL_I2C_STATE_READY); HAL_Delay(10); addr += chunk; buf += chunk; len -= chunk; } }I2C总线还有一个经典问题:如果器件在通信过程中被复位,或者SDA被拉低后主机没察觉,总线会进入“锁死”状态。处理办法是在每次通信失败后尝试发送9个SCL时钟脉冲,把SDA从低电平拉出来,再发STOP条件复位总线状态。这做法看起来很土,但在现场救了我很多次。
4.2 NOR Flash:扇区擦除、写保护和状态等待的坑
NOR Flash的操作离不开“擦除-编程”这对组合。写数据前必须先保证目标扇区是0xFF状态,否则只能把1改成0,不能把0改成1。实际项目中我会维护一个“扇区使用表”,记录每个扇区放了什么、是否脏区,写之前判断是否需要擦除,避免每次写都执行一次耗时擦除。
SPI时序用STM32硬件SPI很顺,但要注意两个细节。第一,页编程最多256字节,跨页写必须拆成多次,代码里最好对每次写长度做256对齐。第二,写完要读状态寄存器0x05的bit0,这个位叫WIP(Write In Progress),必须等它变0才算写完。很多人只调用传输函数就算完,结果马上回读时数据还没落盘。
FLASH_Status SPI_Flash_PageProgram(uint32_t addr, uint8_t *buf, uint16_t len) { SPI_Flash_SendCmd(0x06); // 写使能 SPI_Flash_CS_Low(); SPI_Flash_SendByte(0x02); // Page Program SPI_Flash_SendByte((addr >> 16) & 0xFF); SPI_Flash_SendByte((addr >> 8) & 0xFF); SPI_Flash_SendByte(addr & 0xFF); for (uint16_t i = 0; i < len; i++) { SPI_Flash_SendByte(buf[i]); } SPI_Flash_CS_High(); return SPI_Flash_WaitBusy(); // 读状态寄存器等WIP清零 }工业OTA场景里,固件区建议做双Bank:Bank A运行App,Bank B存新固件,校验通过后再切换启动地址,回退时切换到旧Bank。只要切换动作本身带版本号和CRC,就算升级过程中掉电,也最多是“停在旧版本”,不会变成砖头。
4.3 SD卡:上文件系统之前,先把这些事想清楚
SD卡驱动相对成熟,除非你是做超高速采集,否则强烈建议直接上FatFs文件系统。裸块写虽然简单,但没有文件系统就没法用PC直接分析现场拷出来的日志,工程师追问题会非常痛苦。
FatFs移植时要特别注意三个配置:_FS_FAT32要支持FAT32;_USE_LFN打开长文件名;_FS_READONLY设为0(如果你要写日志)。挂载流程是f_mount→f_open→f_write→f_sync→f_close,其中f_sync是命脉,它把文件系统的缓存强制刷到物理介质。我见过太多“日志消失”的现场,都是只调f_close没调f_sync,或者压根没按周期做同步,掉电期间FAT表更新丢失。
写策略上,工业日志建议按时间分目录或分文件,比如/log/20250606_08.log,每1小时或4MB切一个新文件。好处有两个:一是文件碎片可控,不会因为一个无限大的日志文件磨损严重;二是现场拷日志时不用整个文件搬走,按时间段挑即可。每次写入可以先攒到128字节或256字节的RAM缓冲,凑够一整块再写SD卡,减少写频繁度。
4.4 FPGA想直写SD卡?先把链路从前到后想明白
有些高速采集场景,比如相控阵相位控制、高速图像采集,采样速率几十兆字节每秒,STM32的SDIO DMA都未必扛得住。这时候FPGA直写SD卡是有意义的。
但这里必须先泼一盆冷水:SD卡初始化时序非常繁琐,CMD0、CMD8、ACMD41、CMD2、CMD3、CMD7,每个命令都有响应和超时,CRC校验也是绕不过去的一道坎。FPGA直写有两种理性路线。第一是FPGA只做SPI主机,裸写SD卡固定地址块,一次性写入,不维护文件系统,事后由STM32扫描这些块补建文件目录;第二是买带持续写入能力的数据采集卡专用芯片,或者让FPGA先把数据导入DDR3,再由STM32以DMA方式整段搬进FATFS文件,这种“SPI搬运行”模式最稳。
建议想在FPGA里写SD卡驱动的人,先在仿真层面把SD卡模型跑通。用Verilog写testbench时,至少要模拟卡的CMD响应时序,把CMD8返回的操作条件、ACMD41的忙等待都仿真到位,这样上板时才不会对着示波器猜协议。FPGA在存储系统里的合理定位是“高速搬运工”和“数据变形器”,别硬把文件系统的大旗扛在FPGA肩膀上。
5. 分级存储的落地架构:一套可以直接抄的配置
5.1 存储规划表:哪类数据放哪、多大、多久写一次
我列一个工业运动控制器的典型存储规划,可以直接参考着改。
| 介质 | 存储区 | 内容 | 更新频率 | 容量规划 |
|---|---|---|---|---|
| EEPROM | 首部区 | 设备ID、硬件版本、出厂日期 | 一次性 | 64B |
| EEPROM | 标定区 | 零点、满偏、温补系数 | 月级 | 256B |
| EEPROM | 滚动计数区 | 累计运行时间、报警次数 | 小时级 | 64B×8槽 |
| NOR Flash | Boot扇区 | 引导程序 | 极少 | 64KB |
| NOR Flash | App A/B区 | 主固件双备份 | OTA时 | 2×1MB |
| NOR Flash | 参数快照区 | 最新完整参数备份 | 每次参数保存 | 64KB×4槽 |
| SD卡 | /log | 运行日志 | 秒级~分级 | 剩余空间 |
| SD卡 | /wave | 波形事件文件 | 触发时 | 按事件大小 |
这个表的好处是每个存储区都有明确的“主人”和“生命周期”,软件同事不用每次开发新功能都来问“这个数据存哪儿”。
5.2 掉电不丢数据的三板斧:备份、校验、回读
“掉电不丢”从来不是某一个功能,而是一条完整的动作链。我归纳成三板斧。
第一板斧是双备份。EEPROM里的关键参数,至少要留两个槽位,一个主槽一个备槽。每次保存时先写备槽,回读校验通过后,再更新主槽。如果写入备槽时掉电,重启后主槽还是旧的,重启后用版本号比较,自动把旧数据恢复到备槽,系统永远有一份已知可用的参数。
第二板斧是校验。每个存储帧都带Magic、Version、Length、Data、CRC32五段,读出来先校验CRC再使用,CRC不过就用备份。工业现场数据坏一个字节是常态,不加校验的存储设计等于让系统靠运气运行。
第三板斧是掉电检测联动。STM32配置PVD电压阈值在3.0V左右(3.3V电源场景),触发中断后立即执行紧急保存流程。我还会在板子上加一个掉电检测电路,在VCC跌到3.0V附近时把GPIO设为中断触发,这比PVD多一层保护。保存动作要快、要小,只固化最重要的几百字节参数,不要在掉电时还去做文件系统操作。
5.3 磨损均衡不是Flash的专利,EEPROM和SD卡也要做
很多人知道NOR Flash做磨损均衡是必要的,但最容易忽略的还是EEPROM。EEPROM虽然寿命标称很高,但工业设备7×24小时运行,一个计数器每10分钟写一次,一天144次,一年5万多次。如果你只有一个固定槽位,两年就把10万次寿命耗掉。解决办法是搞N个槽位轮询写,比如8个槽位每8次才覆写同一个槽,寿命直接乘8,设备生命周期内基本无忧。
SD卡的磨损均衡由内部控制器做了,但文件系统层面的寿命问题还是要我们管。核心思路是:避免一个日志文件无限长大。因为FAT表的簇链更新和文件末尾分配随时发生,长时间不对文件做整理,日志文件会碎成几百片。分时段切文件、定期做文件碎片整理(关停机时执行整目录拷贝重建),能大幅减少SD卡的读写压力。
NOR Flash的磨损均衡没那么玄,就一句话:配置区做多扇区轮换。每次保存写到“下一扇区”,扇区索引循环递增,同时每个扇区里放序列号,重启时读序列号最高的作为最新配置。这套逻辑几十行代码就能实现,换来的是配置区寿命接近整颗芯片上限。
6. 实测排错:现场最常踩的五个坑
6.1 EEPROM读出来全是0xFF,到底谁的问题
现象是参数保存后重启又变回出厂值,或者读出来全是0xFF。排查顺序我一般这样走:先用逻辑分析仪确认I2C启动条件、地址、寄存器地址是否发对,地址是0xA0还是0xA1很容易搞混,因为很多头文件里写的是8位地址而HAL库要求7位地址。接着看WP引脚,如果WP被电平钳在高位,写操作是被硬件屏蔽的,数据根本进不去。最后查写等待时间,AT24C256写周期典型5ms,如果你写完之后立刻读,读出来当然还是旧值或者0xFF。这三个点按顺序排查,基本10分钟内定位。
6.2 NOR Flash擦除正常,写进数据却对不上
有人发现擦除后全片0xFF,但写入某段数据后回读,前面几个字节是对的,后面大量数据全是0x00或者错位。这种情况十有八九是页编程跨页了。W25Q64页编程最多256字节,如果从地址0x0100开始写300字节,硬件会在地址到达页边界时自动回卷到该页起始,把前几个字节再覆盖一遍。解决办法是写数据前做256字节对齐拆分,或者用驱动库里的连续跨页写接口。还有个原因是SPI时钟频率过高,某些Flash在100MHz下布线不好会丢数据,工业上稳妥点用25MHz或33MHz跑SPI,几KB的数据便宜一点无所谓。
6.3 SD卡初始化失败,疑似“内部寄存器锁死”
现场反馈“SD卡一开始还能识别,掉电几次后初始化死活过不去”,这种问题多数不是卡永久损坏,而是初始化时序出了岔子。SD卡初始化时主机要先发至少74个时钟脉冲让卡同步,然后CMD0进入SPI模式,接着CMD8读OCR、ACMD41判断卡是否忙。如果ACMD41一直返回0x01,说明卡还在上电初始化,这时候要么是供电电压掉到临界点,要么是卡被插拔太快导致内部状态机处于未知状态。
处理办法是先给卡完全断电(切断VDD,不是只拉低CS),等2秒再重新上电,再做一次74个时钟的“热身”;初始化过程中时钟频率要低,建议几百kHz,等卡吐出有效响应后再切到高速。电路上把CD检测引脚接好,能在软件里区分“卡不存在”和“卡初始化失败”,排查时少走弯路。别动不动就说卡寄存器锁死,大部分时候是主机太急、供电太虚。
6.4 掉电瞬间丢数据,查了三天发现是时序问题
设备正常运行时一切正常,但只要一断电,最近一次写入的日志或者参数总会丢。最先怀疑PVD没配好,阈值设太低,等电压跌到2.0V以下才触发中断,Flash已经写不进去。正确做法是看数据手册,把PVD阈值设在3.0V上下,留出足够余量。第二个嫌疑是f_sync位置不对,文件系统里f_write只是把数据放进应用层缓冲,必须调用f_sync才会把缓冲刷到物理层。第三个嫌疑是板子上根本没留能量,掉电中断进来之后MCU还在跑大循环,2ms内根本没执行到保存代码。把保存任务做成独立高优先级功能块,其他任务在掉电标志置起后全部让路。
6.5 排查清单速查表
| 问题现象 | 可能原因 | 快速验证手段 | 解决方向 |
|---|---|---|---|
| EEPROM数据丢失/0xFF | 地址错/WP拉高/写周期没等 | 逻辑分析仪抓I2C波形 | 核对地址、WP引脚下拉、延长写后延时 |
| NOR Flash写入错乱 | 跨页写/速度过高/未擦除 | 读回比对前32字节 | 页编程对齐、降SPI时钟、先擦后写 |
| SD卡时好时坏 | 上电时序/供电不足/未上拉 | 低速时钟裸卡初始化 | 74时钟热身、降初始化时钟、补上拉 |
| 掉电丢最后一条数据 | PVD晚触发/f_sync缺失 | 示波器看掉电波形 | 提前阈值、强制f_sync、加后备电容 |
| 某扇区提前写穿 | 缺磨损均衡 | 统计扇区写计数 | 多槽位轮换写 |
最后再分享一个我个人的习惯。这套分级存储方案说白了并不“性感”,全是些老掉牙的介质和接口,但工业现场要的就是这种确定性和可替换性。我会在项目文档最前面放一张“存储身份表”,把每个存储区说什么、多大、多久写一次、备份条数、校验方式全部写死,然后让硬件、软件、测试都对着这张表干活。只要这张表不出错,存储这块的返修率能压到极低。后期想扩展也简单,比如NOR Flash升容量、SD卡换更大容量,只改驱动参数,上层逻辑完全不用动。