ARTICLE DETAIL

资讯详情

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

工业控制器存储方案:STM32+FPGA双芯下的EEPROM、NOR Flash与SD卡分级设计

工业控制器存储方案:STM32+FPGA双芯下的EEPROM、NOR Flash与SD卡分级设计 一台工业控制器通常不是单纯一块主控芯片而是“一个会思考的大脑加一双快手”的组合。STM32 负责协议解析、参数管理、业务逻辑FPGA 负责高速采集、时序控制、多路接口扩展。两者协同工作时最容易被忽视也最容易出问题的就是数据到底往哪存、怎么存、掉电后还在不在。EEPROM、NOR Flash、SD 卡三种介质放在一起用不是冗余而是一种按数据特征做的分级存储设计。这篇文章就是把我在实际硬件项目里的分工思路、电路接法、软件流程和踩坑记录完整摊开讲适合正在做 STM32FPGA 双芯系统、或者准备把工业控制器存储方案从“能存”升级到“存得住、存得对”的工程师参考。1. 先搞清楚一台工业控制器里到底要存哪些数据1.1 从应用去看数据的不同“寿命”我见过不少方案一开始只想着“找个 Flash 把数据存下来”结果后面翻车翻得很惨。工业控制器的数据不是一锅粥按生命周期和访问特征可以分为三类。第一类是配置参数和标定值比如 PID 参数、通讯波特率、设备地址、校准偏移量、用户设置的上限下限。这类数据的特点是单条很小改得不频繁但每次改动都必须掉电后不丢失。一年可能只改几十次一次写几十字节但绝对不能因为多写几次把存储介质写坏也不能掉电写一半丢数据。第二类是程序和启动镜像比如 STM32 的固件、FPGA 的配置 bit 流、引导程序、设备出厂字符信息。这类数据体量大可能是几百 KB 到几十 MB更新频率取决于产品迭代节奏正常运行期间完全不改写只有在升级时才有写入动作。这类数据对完整性要求极高半字节错了系统就起不来。第三类是运行日志和采集数据比如温度曲线、报警记录、电机启停历史、网络状态、生产计数。单个数据可能几十 KB 到几 MB持续累加动不动就是几百 MB 甚至几个 G。这类数据丢了不值得大惊小怪但不能一个文件写到死也不能因为整张卡坏了导致整个设备罢工。把这三类数据往一种介质里面塞是我见过最多也最坑的设计。曾经有一个项目为了省一颗料把参数和日志都放同一个 SPI Flash结果日志频繁擦写很快把参数区所在的块磨损了设备运行两个月后所有标定参数全部变成 0xFF产线直接停了。从那之后我对分级存储的立场就非常坚定不同寿命的数据一定住不同规格的房子。1.2 FPGA 和 STM32 在存储链路里各是什么角色双芯系统的分工决定了数据流向。FPGA 在工业控制器里往往承担实时采集和预处理比如多路 ADC 同步采样、编码器计数、高速比较输出。它脑子里存的是当前这一毫秒的数据不具备“把数据组织成文件长期保存”的能力也不适合运行文件系统逻辑。STM32 恰好相反它有丰富的存储外设、成熟的软件生态和文件系统支持适合做数据的统一调度入口。我的做法是数据只管流向 STM32FPGA 不直接碰 EEPROM 和 SD 卡。FPGA 采集的数据经过内部 FIFO 缓存通过 SPI 或者并行总线交给 STM32由 STM32 对这些数据做判断哪些是需要在掉电后立即恢复的标定值哪些是必须落盘的日志哪些只是运行时临时量。这个判断就是分级存储的源头。也有例外情况。如果 FPGA 负责的采集任务对时间戳有严苛要求并且不希望 STM32 介入实时链路我会在 FPGA 侧加一个独立的小容量 SPI NOR Flash 做采集数据的临时缓冲等 STM32 空闲时再搬走。但这种用法只适合“临时缓冲”长期大容量存储仍然统归 STM32 管理否则两台设备同时写一颗 Flash版本管理会变成噩梦。2. 三种介质为什么非分不可2.1 EEPROM只有参数配得上它EEPROM 最典型的规格是容量几十 KB 以内I2C 接口擦写次数号称 100 万次。它和 NOR Flash 的本质区别在于字节级操作能力——NOR Flash 想改一个字节得先把整个扇区擦掉再重写EEPROM 却可以直接改写任意字节。对参数这种“改动频率不高、每次只动几个字节”的数据这是最匹配的特性。工业上常用的型号就是 Atmel/Microchip 的 AT24C128、AT24C256以及 Onsemi 的 CAT24 系列封装小、便宜、供货稳定。I2C 地址由 A0/A1/A2 引脚决定一片板上最多挂 8 个。我把参数存储选择 EEPROM 还有个现实原因NOR Flash 的擦除寿命一般是 10 万次对频繁改写参数的应用是悬在头上的刀而 EEPROM 的 100 万次余量大得多。虽然现在也有号称高寿命的铁电存储器 FRAM但价格和供货普遍不如 EEPROM 普及工业项目里我不太愿意为“理论上更耐用”承担供应链风险。要注意的是 EEPROM 不是没有坑。它的写周期是毫秒级的AT24C256 典型写时间是 5ms这意味着写完一个字节或者一页之后必须等待不能连续发写命令。另外一个容易忽略的点是 EEPROM 的页写能力一页通常是 32 或 64 字节如果一次跨页写一个大结构体要特别小心页边界否则数据会回绕写到页首我在下一节会详细展开。2.2 NOR Flash程序和启动配置的根据地NOR Flash 的特点是随机读取快、支持 XIP 执行可以直接映射到地址空间跑代码所以 STM32 外部启动、FPGA 配置存储这类场景都天然属于它。工业上最常见的型号是 Winbond W25Q 系列从 W25Q162MB到 W25Q25632MBSPI/QSPI 接口。价格便宜、厂商标定成熟、坏块管理虽然不如 NAND 但相对可控。程序镜像和 FPGA bit 流为什么要用 NOR Flash 而不是 SD 卡原因是启动顺序和可靠性。STM32 上电后要第一时间从 Flash 取指令FPGA 配置文件需要在配置阶段就从 Flash 读取这两件事都要求存储介质“通电即能读、读取过程不依赖文件系统、不被其他设备占用”。SD 卡挂载需要初始化、需要文件系统解析、还可能需要电平转换启动阶段根本等不起。而 NOR Flash 上电就是裸读一个 SPI 接口就能把整块内容读出来。我给 NOR Flash 的定位还有一层做关键业务数据备份。日志写在 SD 卡上可能因为断电损坏文件系统但设备出厂参数、最近的运行状态快照这种事关产品生死的数据我用 NOR 专门划一个扇区做周期备份即使 SD 卡拔了也不影响设备基本运行。这个习惯让我避免过至少一次大麻烦。2.3 SD 卡大容量日志与历史数据的归宿SD 卡本质上是 NAND Flash但它自带主控、自带坏块管理、自带文件系统接口对 MCU 来说就像一块可以随意读写的移动硬盘。容量从 128MB 到 128GB 跨度极大成本低、即插即用。工业控制器里的趋势记录、几百天的历史采样、诊断数据包这些动辄几十 MB 起步的数据只有 SD 卡接得住。我用 SD 卡之前犹豫过一个问题NAND Flash 是出了名的怕掉电写一半掉电可能丢一个块SD 卡主控虽然做了 FTL闪存转换层管理但同样不能完全抵抗掉电。工业环境里断电可不是稀奇事。后来想通了日志数据的价值是“有最好没有也不至于翻车”它不像设备参数那样丢了会导致安全事故所以可以放心把日志和采集数据放在 SD 卡上但必须做好掉电后的文件恢复策略。这个策略我会在第五部分详聊。SD 卡的接口方式有两种SPI 模式和 SDIO 模式。SPI 模式接线少、实现简单速度上限约 10MbpsSDIO 4bit 模式吞吐高典型能达到几十 MBps。工业控制器日志每秒可能只有几十 KBSPI 模式其实完全够用但 SDIO 在初始化时对卡兼容性更敏感我通常更倾向 SDIO 加一个软件回退到 SPI 模式的设计保证不同批次卡都能认。2.4 一张表看清三种介质的选型逻辑对比维度EEPROMNOR FlashSD 卡典型容量2KB~512KB1MB~64MB128MB~64GB接口I2C / SPISPI / QSPISPI / SDIO写单位字节页写优化扇区擦除页编程页/块由主控管理擦写寿命约 100 万次/字节约 10 万次/块约 1 万次/块视卡品质掉电保护字节写入原子性较好页编程有损坏风险需处理文件系统损坏风险大典型用途参数、标定、掉电保持固件、配置、启动镜像日志、历史数据、文件交换这张表的核心结论是介质没有好坏只有适合和不适合。把日志写到 EEPROM 是荒谬的把参数存在 SD 卡是危险的把全部数据硬塞进 NOR Flash 是寿命不够的。各自干各自擅长的活就是分级存储的全部意义。3. 硬件链路怎么打通接口、时序和电路细节3.1 数据怎么从 FPGA 走到存储介质上先画一条清晰的数据路径硬件设计才有据可依。以我最近做的一块边缘网关底板为例FPGA 采集 32 路模拟量ADC 采样率 100kSPS每个采样点 16bit一批数据约 8KB。FPGA 内部用双口 RAM 做缓冲当攒够一帧后通过 SPI 中断通知 STM32 来取。STM32 拿到数据后先做有效性判断抽特征、打时间戳再决定去向。参数类数据由 STM32 直接操作 EEPROM走 I2C 总线。程序和 bit 流在出厂时通过烧录器写入 NOR Flash运行升级时由 STM32 的 bootloader 写入另一个分区。日志数据由 STM32 的日志任务打包通过 SDIO 写入 SD 卡上的 FATFS 文件。关键原则是 FPGA 与存储之间不直接高速通信。FPGA 采集速度虽高但把数据逐字节推送给 NOR Flash 或者 SD 卡都不是它擅长的而且异步时钟一多跨时钟域的数据一致性很难保证。所有存储写操作必须由 STM32 串行化处理哪怕速率低一点也要保证没有两个主机同时在动同一颗介质。3.2 接口选型I2C、SPI/QSPI、SDIO 各自的接线套路EEPROM 用 I2C 时SDA 和 SCL 都必须要上拉电阻。上拉电阻值的选择不是随便抓一个 4.7k 完事。总线长度在 10cm 以内用 4.7k 没问题如果为了布局把 EEPROM 放得远一些、走线超过 10cm建议换 2.2k否则总线沿变缓高速通信时可能出无规律的 ACK 失败。ST 的很多 MCU 内部是有上拉的也请不要依赖尤其是电机、变频器附近的板子干扰大内部上拉太弱容易把电平拉坏。NOR Flash 接 STM32 时我用 QSPI 模式而不是普通 SPI因为固件镜像动辄几 MB普通 SPI 8MHz 读一遍要几秒钟手册下载模式下等太久。QFNU 封装或者 SOP8 封装都很常见接线要注意 WP 和 HOLD 两个引脚WP写保护引脚低电平有效要直接拉高或接 GPIO 控制不能悬空HOLD 引脚同样不能悬空必须拉高否则在总线上出现噪声时 Flash 可能莫名其妙进入保持状态读出来的数据全错。我在原理图上都会在这几个脚加上拉并标注“NO-NC”。SD 卡座的设计很容易踩坑。卡座有两种自弹式和推推式工业环境建议用带锁卡座加卡检测引脚。卡检测引脚要在原理图上接上拉并引到 STM32 的 EXTI 中断不接卡检测就插拔 SD 卡时文件系统会直接崩。SDIO 模式的数据线 DAT0-DAT3 要加上拉但上拉值常用 10k 到 47k 之间不能太小否则在高速模式下影响信号完整性。CMD 线必须上拉CLK 线尽量短串联 22Ω 左右电阻做阻抗匹配这个细节能让高速模式稳定很多。3.3 电路设计里容易翻车的几个细节第一个是电源去耦。EEPROM、NOR Flash 这类器件的电源引脚必须有 0.1uF 陶瓷电容且尽量靠近引脚。别以为小器件就不需要擦写瞬间电流尖峰很大供电不足会导致写入失败。SD 卡更夸张上电瞬间电流可能到几百毫安如果直接用 LDO 输出供电又不加大电容初始化时经常出现卡死在 ACMD41 的情况。我一般在 SD 卡电源脚放 10uF0.1uF 双电容必要时用单独的 LDO 给 VDD 供电。第二个是掉电检测电路。这是分级存储能不能真正保住数据的关键。我用一个简单的电阻分压加比较器将电源电压与基准电压比较当电压跌落到 4.5V 以下时触发 STM32 的外部中断。这个中断要最高优先级抢占进入后迅速把当前参数写入 EEPROM然后立刻把日志文件 flush 关闭。同时整个板子要有一组储能电容能让系统在掉电检测触发后坚持 50ms 以上我用的是 470uF 电解并 100uF 钽电容的方案实测从电源跌落信号触发到 EEPROM 完成一页写入足够。第三个是 FPC 或者插接件上的信号完整性。SD 卡卡座如果通过软排线引出不要把 CLK、CMD 和地线走成平行长线。有一次我把卡座放在扩展板上用一段 8cm 的杜邦线连接结果 SDIO 模式死活初始化不过降到 SPI 模式偶尔能过最后把线束改成双绞且缩短到 3cm问题彻底消失。高速信号不能在一坨松散线上运行这条经验很枯燥但很真实。4. 存储软件栈从寄存器操作到业务接口4.1 驱动层不能只调寄存器还得管时序和超时底层驱动是存储方案最需要死磕的部分。很多人拿厂商例程直接抄读没问题一写就卡死。EEPROM 的驱动核心是等待写完成I2C 上送完地址和数据的最后一个 stop 条件之后设备会进入内部写周期此时总线上的器件不响应 ACK。正确做法是发送 1 字节后轮询直到收到 ACK 为止。这个等待要有超时我设 100ms超过就上报告警而不是死等。之前我用过一个实时操作系统平台把 I2C 等待写完成的任务优先级设低了结果 EEPROM 写周期期间被其他任务抢占主控在总线上干等整个系统时钟一团糟。NOR Flash 驱动更要命因为有状态寄存器。W25Q 系列读状态寄存器的指令是 0x05bit0 是 BUSY 位。擦除和编程命令发出后要不断轮询这个位直到清零。问题在于擦除一个扇区最长可能到几百毫秒如果此时 STM32 有中断频繁进来轮询间隙太长没关系但如果你用了 DMA 传输状态和 Flash 状态交叉判断容易把 Flash 的“忙状态”误认为“传输完成”。我的习惯是封装一个阻塞式函数专门干擦写期间关闭无关高优先级中断保证状态机不被扰乱。SD 卡驱动是三层里最复杂的一层。裸操作要做 CMD0 进 SPI 模式CMD8 查电压支持ACMD41 反复握手初始化读 CID、CSD 等。初始化时序有严格参数ACMD41 试多少次、超时多久、CRC 要不要校验不同卡行为有差异。我强烈建议直接用 STM32 官方 SDMMC 库或者成熟的 FatFS 库不要从寄存器自己造轮子。底层驱动唯一要自己写的是超时机制任何命令超过 1 秒没有响应强制复位 SDIO 控制器并重新初始化而不是无限等。4.2 管理层的分区、校验和掉电保护如果说驱动层是“能读写”管理层就是“读写不出事”。NOR Flash 必须做分区管理我通常把 8MB 的 Flash 分成四个区Boot 区放引导程序和升级校验代码A 区放正在运行的固件镜像B 区放待升级镜像C 区放出厂参数备份。升级时先写 B写完整校验通过后再由 bootloader 交换 A/B 指针保证任何时刻都有一个可以启动的镜像。如果直接在 A 区原地写新固件写到一半断电设备就变砖了。每个分区要带校验和版本号。我用的简单方案是分区头部放 4 字节魔数、4 字节版本号、4 字节数据长度、2 字节 CRC16后面跟数据正文。写入完先读回计算 CRC 和魔数全部匹配才把分区标记为 valid。这套保险在产线烧录时救了我很多次——烧录器接触不良导致的半写事故bootloader 能直接识别无效并跳回另一分区。EEPROM 层要做磨损均衡和掉电写入保护。磨损均衡的实现思路不复杂把所有参数结构体复制成多份分别放在 EEPROM 不同地址槽位每次写入轮换使用下一个槽位。比如一份参数占 128 字节EEPROM 里预留 16 个槽位共 2KB每次修改写新槽始终带一个递增的写入序号。上电时扫描所有槽位取序号最大且校验通过的一份作为有效数据。这样即使同一个参数一年写几千次还远远到不了寿命极限。SD 卡层要做的掉电保护更复杂。文件系统最怕写一半掉电。我在应用层做两件事第一不频繁打开关闭文件日志任务 30 秒批量写一次写完调用 f_sync 而不是 f_close减少文件系统元数据更新频率第二每 24 小时强制轮换一个新文件文件名带日期避免单个文件无限增大导致 FAT 表爆掉。至于掉电导致的簇链损坏FatFS 可以在挂载时用 f_mount 带强制格式化的参数重新初始化工业日志丢了就丢了设备不能因为日志卡死。4.3 给业务层留好用的接口存储层再可靠业务工程师用得别扭也是白搭。我的习惯是给上层提供几个简单函数不需要他们了解底层是 EEPROM 还是 SD 卡。参数接口类似Param_Read(uint16_t id, void *buf, uint16_t len)和Param_Write(uint16_t id, const void *buf, uint16_t len)。内部自动查表把 id 映射到槽位、做磨损均衡、加 CRC。日志接口是Log_Append(uint8_t level, const char *fmt, ...)底层自动把不同级别的日志分流到不同文件。固件升级接口是OTA_Start()返回当前是否可升级写入时自动切换目标分区升级完自动验证。抽象层最大的价值是让换存储介质时不影响业务代码。有一回量产时供应商说 EEPROM 缺货要换另一型号容量一样但页大小不同。由于上层全部走抽象接口底层适配器改了两行就完成切换这是我一直坚持加这层的原因。没有这层每次物料变更都是一场涉及全项目的牵一发动全身。5. 实际案例一块边缘网关基板的分级存储落地5.1 EEPROM 磨损均衡别把同一个地址写到天荒地老用一块具体的板子演示完整设计。边缘网关需要保存设备地址、通信参数、模拟量校准系数和累计运行小时数。这些参数合计约 64 字节我选用 AT24C256容量 256Kbit足够做大量槽位冗余。先定义结构体typedef struct { uint32_t magic; // 固定魔数 0xA5A5A5A5 uint32_t seq; // 写入序号每次1 uint16_t crc; // 参数区 CRC16 uint16_t dev_id; // 设备地址 uint32_t baud; // 波特率 float kp[4]; // 校准系数 uint32_t runtime_s; // 累计运行秒数 } param_block_t; // 总长 44 字节加上对齐后按 48 字节管理AT24C256 的页大小是 64 字节44 字节结构体正好装进一页里不会出现跨页问题。我在 EEPROM 里划分 256 个槽位每个槽位 128 字节对齐从地址 0x0000 到 0x7FFF 共 32KB 用于参数区另一半留作扩展或设备信息。每次写入时读当前最新槽位的 seq加 1 后写入下一个槽位。搜索最新有效槽位的方法是上电时从槽位 0 开始扫描找 magic 匹配且 crc 正确且 seq 最大的那一个。磨损寿命计算很直接如果设备每 10 分钟修改一次参数比如累计运行时间半小时上报一次一天就是 144 次一年约 5 万次。EEPROM 标称 100 万次擦写除以 5 万理论上够用 20 年但为了保险我设置了一个阈值当当前槽位序号接近 25 万时整套参数整体搬迁到 EEPROM 另一半空区重新开始计数。这样即使一天写 500 次也能撑好几年。如果你觉得槽位方式太费空间也有另一种做法把参数分散到多个独立区域每个区域 8 字节用倒数计数法实现“只在第 N 次写时需要擦除”。但我测试下来结构体整体槽位法在代码可读性和可靠性上更优工业设备不缺那几 KB 空间。5.2 NOR Flash 双备份启动和固件升级我在这块网关板子上的 STM32 侧用了 W25Q128容量 16MB。分区示意如下起始地址大小用途0x0000001MB引导程序 Bootloader含升级程序0x1000006MBA 区固件运行镜像0x7000006MBB 区固件待升级镜像0xD000001MBC 区出厂参数与坏块隔离0xE00000剩余日志暂存扩展区正常上电流程是这样的STM32 的内部 Flash 放了一个非常小的启动头它只做一件事——检查外部 NOR Flash 的 A/B 分区有效标志。A 区有效就跳 AA 无效就跳 B两个都无效就进入串口下载模式等待恢复。固件升级流程在应用层实现收到升级包后先写入 B 区每写 1KB 算一次 CRC写完整个镜像后读回比较全部通过才把 B 区标志置为 valid并让 A 区标为 invalid。下次启动时 bootloader 看到 A 无效 B 有效就运行新固件。新固件运行成功并上报心跳后应用再反过来把新固件复制到 A 区恢复双备份状态。这个方案最重要的是“先写备份再切换标志”这个顺序顺序反了就会变砖。有一次我图省事直接往 A 区写入新固件写到一半一个看门狗复位了板子再也启动不起来。最后只能拿烧录器重新救回来。从那以后双区加标志切换成了我的默认规则任何固件升级都必须经过“写备份区→校验→切标志→启动新固件→回灌原区”五步。5.3 SD 卡日志容量计算与 FATFS 长时写入配置网关的数据采集任务是每 5 秒记录一条实时数据每条约 1KB。算下来一天 17280 秒除以 5约 3456 条共 3.4MB。用一张 8GB 工业 SD 卡8GB 除以 3.4MB 每天大约可以记录 2400 天完全够用。但日志文件不能无限写。我的做法是每天的 0 点新建一个文件命名为LOG_20250601.BIN这种格式跨天时前一天文件保持不打开状态。FatFS 配置需要打开FF_FS_EXFAT支持大容量卡和FF_USE_MKFS格式化选项。长时写入时有个关键参数FF_FS_TINY如果内存紧张可以打开但它会让文件系统在读写时频繁分配缓冲区日志写入性能下降。我直接关了它给 FatFS 单独分配 4KB 的缓冲区挂载后性能稳得多。写日志代码要注意一个细节每次写完后调用f_sync但不要调用f_close。f_close会释放文件对象和缓冲区下次写还要重新f_open频繁开关文件不仅慢还不断更新目录区增加了掉电损坏文件系统的概率。f_sync只把脏缓存写回介质文件句柄还留着既快又安全。还有一个大坑是 SD 卡的 SPI 模式与 SDIO 模式的写性能差异。SPI 模式写 1MB 可能要 2-3 秒SDIO 模式可能只要 100ms 级别。我的板子用了 SDIO 4bit 模式但代码里做了降级SDIO 初始化失败自动切到 SPI 模式这样至少保证日志能力不缺失。切换后写性能降级但不会完全没法用。这个兼容逻辑写起来不难实测对杂牌卡特别友好。6. 实测现场三次把设备调坏的调试经历6.1 EEPROM 写入 5ms 的等待与 I2C 总线死锁第一次做 EEPROM 驱动时我犯了一个新手错误写完一页数据后立刻去读结果读回来全是 0xFF。查了好久才发现我忽略了页写周期。AT24C256 写完一页后需要 5ms 内部写时间在这个时间内器件不响应任何命令读了自然是空。最坑的是示波器看起来 I2C 波形完全正常因为主控确实发了读命令只是从机没有拉低 ACK逻辑分析仪上就是不显眼的一小段高电平。正确的实现是写完命令后延迟 5ms 再操作或者用轮询 ACK 的方式等从机从忙状态恢复。我后来全部改成轮询 ACK配合超时保护比固定延时稳定得多因为不同供应商的芯片写周期其实有差异固定延时只能按最差情况算浪费了时间。另一个现场问题是 I2C 总线死锁。表现是设备运行几天后所有 EEPROM 操作全部 ACK 失败复位 STM32 也不行只能断电重启才恢复。排查半天发现是多主模式下一个中途打断的写操作让从机进入了内部写周期主控在此时又发了一个 start 条件从机没响应导致 SDA 被拉死。解决方案是操作前先发一个 stop 条件释放总线然后延时再开始新传输。这个细节随后被我写进了所有 I2C 操作的公共函数里。6.2 NOR Flash 擦除时间不满足和 WIP 轮询陷阱第二次事故出在 NOR Flash 的擦除操作上。我天真地以为发出扇区擦除命令后等个 10ms 就完成了然后立刻往这个扇区写数据结果写入到一半Flash 返回的数据全是乱的。后来查手册才知道W25Q 系列扇区擦除典型时间是 45ms最大可达 400ms而且实际时间和温度、电压、芯片批次都有关。不轮询状态寄存器就等于在擦除还没完成时往火山口里倒水。正确的做法是发完擦除命令后循环读状态寄存器的 BUSY 位直到清零。我加的轮询超时是 1 秒实测正常芯片大部分在 40-50ms 内完成。有一次换了一批芯片擦除时间涨到 200ms轮询逻辑没有超时程序卡死在里面看起来像死机后来才知道这批次芯片工艺参数有差异。所以轮询加超时再加错误上报这三个要素缺一不可。WIP 轮询还有个陷阱是中断干扰。如果擦除期间进入了一个长时间中断主循环轮询的节奏被打乱BUSY 位清零后又被外部干扰导致 Flash 状态机紊乱。我后来把 Flash 操作集中到一个独占任务里并在擦写期间用临界区保护把中断尽可能压到最短这个问题再没出现过。6.3 SD 卡 SPI 模式初始化不过的时光第三起事故最有戏剧性。板子返回产线后有三分之一的 SD 卡在 SPI 模式下初始化失败表现为卡一直不返回正确的0x01应答。我用示波器抓 CMD0波形完全符合规范但就是握手失败。排查到深夜才发现问题出在初始化前没有拉高 CS、主机在发送 CMD0 之前必须给卡足够的上电时间。SD 卡规范要求上电后至少 1ms 的稳定时间并且要先送出至少 74 个时钟周期的序列让卡内部的电平稳定。很多示例代码直接发 CMD0但工业环境电源纹波大卡的启动时间比实验室慢得多。解决方法是初始化前死等 100ms再发 80 个空时钟周期之后再发 CMD0 和 ACMD41。加了这段之后所有批次卡都能稳定初始化。还有一次 SD 卡问题不是初始化而是 4bit 模式下数据偶发错误。现象是日志文件总是有随机乱码块但卡本身在其他设备上能正常读写。排查之后发现是卡座上的 DAT1-DAT3 引脚用了杜邦线连接线间串扰导致高速模式下信号眼图关闭。换成 PCB 直连并缩短走线后问题消失。这也验证了我前面强调的SDIO 是高速接口硬件走线不能糊弄。7. 常见问题速查与我的固定习惯7.1 一张表查问题问题现象排查方向解决手段EEPROM 读回全 FF是否处于写周期轮询 ACK 或延时 5ms 再读I2C 总线死锁多主操作冲突启动前先发 stop加超时复位NOR 擦除后写入乱码擦除未完成就写轮询 BUSY带 1s 超时NOR 读数据时好时坏WP/HOLD 悬空受干扰上拉拉死确认 CS 时序SD 卡初始化失败上电时序不足延时 100ms发 80 个时钟SDIO 读取随机乱码走线过长或干扰缩短走线改 SPI 调试对比掉电后参数丢掉电检测太慢加掉电中断储能电容掉电后日志文件损坏文件系统频繁更新批量写f_sync按天分文件这些都是真实项目里碰过的每个条目背后都是一次几小时的排查。把表收藏好再工作能省很多时间。7.2 我在新项目里的固定习惯写了好几年的存储方案我现在的新项目基本都沿用一套固定习惯。第一原理图阶段就确定每种数据用什么介质并且把表画在原理图首页提醒自己和同事不要混用接口。第二驱动层全部封装业务层永远不要直接操作寄存器避免物料变更时改到吐。第三EEPROM 和 NOR Flash 都预留至少 30% 容量冗余别把分区算得刚刚好产品迭代是你无法预知的。第四每批次量产前做一次存储压力测试写满、擦除、掉电、再上电循环至少 200 次。这个测试能提前暴露 98% 的存储批次问题。我遇到过一批 EEPROM 在高温下写周期从 5ms 变成 20ms如果没有压力测试直接出货那批设备会在用户现场陆续出现参数丢失的投诉。最后聊一个个人偏好。我始终会在板子上预留一个 4Pin 的烧录测试点把 EEPROM 的 SDA/SCL、NOR Flash 的 CS/CLK 引出来。调试时用逻辑分析仪夹上去看真实时序比任何仿真都管用。很多所谓的神秘问题其实都是示波器一量就暴露的低级错误。硬件调试没有捷径把信号拉出来看就是最快的路。
返回列表