ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

STM32F100ZE驱动SPI MRAM MR25H40CDF实现工业掉电保存与日志存储

STM32F100ZE驱动SPI MRAM MR25H40CDF实现工业掉电保存与日志存储 1. 方案选型为什么把 MR25H40CDF 接到 STM32F100ZE1.1 工业现场真正要解决什么一开始我们被项目拉着走现场几台设备反复出现参数掉电丢失跑一段时间后日志记录不完整。排查下来问题不在CPU而在存储介质本身。原来的板子上用的串行EEPROM容量小胜在简单但掉电瞬间电压骤降写周期还没完成关键数据直接作废。后来换过NOR Flash又暴露出另一层麻烦擦除时间太长。故障发生时系统忙着保存现场Flash却还在慢慢地擦扇区等擦完故障现场早就没了。工业嵌入式里很多需求其实非常朴素需要足够容量存放运行日志写入要快掉电要能扛住生命周期要长。很多工程师第一反应就是NOR Flash毕竟便宜、容量大、资料多但它的“先擦后写”模型决定了它不适合高频改写场景。无论做电力仪表、电机驱动器还是光伏控制器每次上电都要记录每次报警都要抓现场写入频率远高于普通消费固件的“升一次级、写一次配置”的节奏。1.2 为什么不是 EEPROM、不是 NOR Flash而是 MRAM把三个方案摆在一起差距就很明显了。EEPROM按字节读写理论上不用擦除听起来适合存参数但它的写次数普遍在十万次级别单字节写还要毫秒级时间容量也很少超过1Mbit。NOR Flash容量大但必须先擦后写扇区擦除要几十到几百毫秒寿命同样是十万次量级这恰好和工业日志场景拧着来。越是需要抓故障现场的时候系统越处于高负载、低电压状态NOR Flash越不配合。MRAM是磁阻随机存储器通过磁性状态保存数据刷新速度和RAM接近同时又非易失。SPI接口的MR25H40CDF容量4Mbit也就是512KB支持按字节直接写入不需要先擦除。它的写耐久性标称到10^12次量级假设每秒写一次也可以用几百年正常工作根本不用操心写寿命。MRAM也不依赖电荷保持温度老化带来的数据丢失风险比Flash小得多。它最合适的定位不是替代大容量Flash而是做事件日志、掉电参数、状态标记这类“高频率小数据量”的存储接口。对比项串行EEPROMNOR FlashSPI MRAMMR25H40CDF写入粒度字节页写/块擦除字节直接写写前擦除不需要必须有不需要典型写耐久10^5~10^6次10^4~10^5次10^12次量级写入速度毫秒级秒级含擦除SPI命令级微秒内完成容量覆盖通常1Mb以下大容量主流中等容量适合状态类数据掉电数据保持有但受温度影响明显有磁存储抗温度老化能力较强1.3 STM32F100ZE 在这个场景里承担什么STM32F100ZE 是Cortex-M3内核的Value Line产品主频24MHz最高带512KB Flash和48KB SRAM144引脚外设不算激进在工控、电力仪表、电机保护器里很常见。它上面有多路SPI、UART、ADC足够把控制、采集、通信全部压在单颗芯片里。对我来说用一颗中低规格但抗造的单片机带SPI MRAM比在高端处理器上绕一圈再外挂存储要划算得多。这里有个架构师视角MRAM不是“更大的EEPROM”的简单替换而是把实时状态和日志从易失RAM、慢速Flash里分离出来的一块独立存储区。我在设计里把它规划成“配置块 环形日志块”两个区域各自按固定格式组织不跑文件系统。512KB容量、按字节随时改写、掉电不丢这三个特性放到一起最适合用一份带CRC的裸存储协议来管理而不是硬塞一个文件系统进去加了一堆不必要开销还容易把MRAM的快速写优势吃掉。2. 硬件连接和驱动准备先解决底层问题2.1 MR25H40CDF 引脚不复杂但细节多MR25H40CDF 是8引脚封装一个标准SPI从设备CS、SCK、SI、SO、WP、HOLD另外三只脚是电源和地。WP是写保护脚低电平时会禁止部分写操作HOLD用来暂停通讯。这两个脚绝对不能悬空。我见过一块新板子HOLD悬空环境里一有电磁干扰SPI数据就偶发错乱查了很久才找到是悬空引脚在“飘”。可靠做法是WP和HOLD分别通过10k电阻上拉到VCC正常工作时一直处于允许写、允许通讯的状态。CS必须由MCU的GPIO单独控制不要偷懒直接接到SPI外设的硬件NSS默认脚上。除非你把软件NSS管理彻底关闭否则硬件NSS在总线冲突时可能自己拉低CS时序根本不可控。电源方面MR25H40CDF工作电压2.7V到3.6V和STM32F100ZE的3.3V系统直接对接。VCC旁边放一颗100nF再并一颗1uF到10uF电容尽量贴近芯片电源脚。工业电源纹波大存储芯片电源不干净读写错误率会明显上升。2.2 STM32F100ZE 侧引脚怎么安排我用的SPI1引脚映射为PA4做CS、PA5做SCK、PA6做MISO、PA7做MOSI。SCK和MOSI配成复用推挽输出MISO做浮空输入或带上拉输入CS做通用推挽输出。这个映射是F1系列最常见的网上案例多调试信号也好找。STM32F100ZE是144脚封装选哪组SPI都行但我还是建议固定到这组省得后续接手的人到处找文档。/* 系统时钟24MHzAPB2时钟24MHz */ RCC-APB2ENR | RCC_APB2ENR_GPIOAEN | RCC_APB2ENR_SPI1EN; /* 只配置PA4~PA7不影响其余引脚 */ GPIOA-CRL ~0xFFFF0000; /* PA4通用推挽输出50MHz, PA5复用推挽50MHz, PA6浮空输入, PA7复用推挽50MHz */ GPIOA-CRL | 0x0000B4B3;SPI初始化时关键点有五个主机模式、软件NSS管理、关闭自动片选、MSB在前、SPI模式0。因为F100的SPI时钟只能偶数分频系统24MHz下配置成2分频就是12MHz离MR25H40CDF的速率上限还有很大余量。如果板子走线较长或者现场干扰强不如直接降到6MHz稳定优先级更高。SPI1-CR1 0; SPI1-CR1 | SPI_CR1_MSTR; /* 主机模式 */ SPI1-CR1 | SPI_CR1_SSM | SPI_CR1_SSI; /* 软件NSSCS由GPIO控制 */ SPI1-CR1 | (0x01 3); /* BR0012分频得到12MHz */ SPI1-CR1 | SPI_CR1_SPE; /* 最后开启SPI */CS的操作建议直接用BSRR/BRR寄存器这样速度最快也不容易受上下文开关影响。static inline void mram_cs_low(void) { GPIOA-BRR (1u 4); } static inline void mram_cs_high(void) { GPIOA-BSRR (1u 4); }2.3 SPI 命令集和状态寄存器的坑MR25H40CDF的命令集很像SPI NOR Flash读数据0x03、写数据0x02、写使能0x06、写禁用0x04、读状态寄存器0x05、写状态寄存器0x01、读ID 0x9F。但关键区别在于写数据之前不需要做任何擦除操作。另一个容易忽略的点是写使能不是“写一次永远有效”而是执行完一次写操作后自动失效所以每次写之前都必须重新发0x06。状态寄存器里最常用的是WEL位和WPEN位。WEL是写使能锁存位执行WREN后置1写完一次或执行WRDI后自动清0。WPEN配合WP引脚决定写保护策略默认状态下不需要动它。驱动的结构我习惯做成“一条写命令 写使能 具体写操作”下次就算换Flash、换EEPROM上层调用完全不用改。SPI模式也值得强调。MR25H40CDF支持mode 0和mode 3工程上默认用mode 0最好记忆SCK空闲为低第一个时钟沿采样数据。如果CPOL/CPHA配错数据会变成一个字节的错位而且这种错位看着像随机错误示波器不抓协议的话很难定位。出现“读ID是对的读写内容偶尔错”的诡异现象时先回去确认时钟极性和相位。3. 读写实现让 4Mbit 存储区真正跑起来3.1 最底层的 SPI 收发函数F100的SPI是全双工发送一个字节的同一时刻必然也接收到一个字节。读MRAM时只要主机持续发哑字节MISO上就能连续推出数据。单字节交换函数是整个驱动的基础static uint8_t mram_spi_exchange(uint8_t tx) { while (!(SPI1-SR SPI_SR_TXE)) { } SPI1-DR tx; while (!(SPI1-SR SPI_SR_RXNE)) { } return (uint8_t)SPI1-DR; }这段代码的陷阱在第二个等待。如果漏掉RXNE等待直接读DR连续读的时候第二帧数据会被第一帧覆盖读回来的缓冲整个错位。虽然单字节操作问题不大但做批量读时必现。3.2 初始化、读芯片 ID驱动写完之后不要急着去做日志存储。先把“读ID”当作正常通讯的握手信号。CS拉低发送0x9F再连续读三个字节作为设备ID根据厂商ID和器件ID判断芯片是否正确响应。C代码里我只判断非法值不硬编码完整ID这样量产阶段换批次、换封装都不会因为ID校验太严格导致整个板子起不来。uint8_t mram_init(void) { uint8_t id[3]; mram_cs_low(); mram_spi_exchange(0x9F); id[0] mram_spi_exchange(0x00); id[1] mram_spi_exchange(0x00); id[2] mram_spi_exchange(0x00); mram_cs_high(); if (id[0] 0x00 || id[0] 0xFF) { return 1; /* 接线、电源或SPI配置有问题 */ } return 0; }读ID全FF是老问题。遇到之后不要急着换芯片先拿示波器看CS、SCK、MOSI、MISO四根线。CS要在整个传输期间稳定为低SCK要有完整时钟MOSI上能看到0x9F字节。只要波形对了九成问题是软件配置剩下才是焊接。3.3 字节级写入不需要先擦除MRAM单字节写用完整流程做CS拉低发WRENCS拉高再次CS拉低发写命令0x02跟上24位地址和8位数据CS拉高。MRAM不需要等待内部擦除循环命令结束数据已经写入。这个“无擦除”的特性是整个方案的核心价值。uint8_t mram_write_byte(uint32_t addr, uint8_t val) { uint8_t sr; /* 1. 写使能 */ mram_cs_low(); mram_spi_exchange(0x06); mram_cs_high(); /* 2. 写命令 */ mram_cs_low(); mram_spi_exchange(0x02); mram_spi_exchange((addr 16) 0xFF); mram_spi_exchange((addr 8) 0xFF); mram_spi_exchange((addr 0) 0xFF); mram_spi_exchange(val); mram_cs_high(); /* 3. 可选读状态寄存器确认WEL被清0 */ mram_cs_low(); mram_spi_exchange(0x05); sr mram_spi_exchange(0x00); mram_cs_high(); return (sr 0x02) ? 1 : 0; /* WEL仍为1说明写操作没正常完成 */ }地址为什么发3个字节因为MR25H40CDF的命令格式固定为24位地址即使容量只有512KB地址都用3字节承载。我见过有人按16位地址发结果数据读出来整体错位最后发现是地址少发了一字节。写入速度方面12MHz下每字节命令约40位整体不到5us这种速度对于“掉电瞬间写一条故障记录”绰绰有余。3.4 批量读写和环形日志的思路业务层不能只有单字节接口还需要批量操作。批量写入有两种做法一种是循环调用单字节写简单但每次都要发WREN另一种是先发WREN然后CS拉低后发WRITE命令地址后面连续跟多个数据字节一个命令写一整块。后者更适合日志场景效率高很多。但要注意连续写入不应跨行或页边界所以我要求所有业务存储块都按64字节对齐存放这样既规避跨页问题也让代码不用处理页边界拆分这种脏活。批量读函数更直接不需要WRENCS拉低后发READ命令和地址然后连续收len个字节void mram_read_burst(uint32_t addr, uint8_t *buf, uint32_t len) { uint32_t i; mram_cs_low(); mram_spi_exchange(0x03); mram_spi_exchange((addr 16) 0xFF); mram_spi_exchange((addr 8) 0xFF); mram_spi_exchange((addr 0) 0xFF); for (i 0; i len; i) { buf[i] mram_spi_exchange(0x00); } mram_cs_high(); }做环形日志时我建议用固定大小的记录结构体字段包括序号、时间戳、事件类型、当前数值、CRC。日志区头部保存最新写入位置。每写完一条记录再更新一次头部。由于MRAM支持字节覆盖改写更新头部指针就像改一个RAM变量一样快不需要先擦掉整个扇区再写这是Flash方案完全做不到的。3.5 掉电现场数据的工程化存储我用单条记录16字节结构日志区规划成512条环形记录。每条记录固定offset头部用4字节保存最新记录序号。上电时读头部序号下一条序号直接加1然后顺序写入。不需要扫描全片找空位复杂度低不少。防坏数据用两层保险。第一每条记录带上CRC读取不匹配就判定记录无效回退到前一条有效记录。第二头部起始放magic number如果读出来不对说明上次写入被意外打断系统宁可冷启动也不用半截数据。有人觉得MRAM写入时间短不太需要校验但工业现场电磁干扰复杂任何存储器件都有可能被干扰加CRC和magic不是画蛇添足而是对故障现场负责。4. 常见问题与排查心得4.1 读 ID 全 0xFF先用示波器抓时序全FF是最高频现象。原因通常是CS没拉低就开始了传输、SPI的CPOL/CPHA配置错、MISO虚焊或者电源没到位。排查顺序上先看CS波形是否在传输期间稳定为低再看SCK有没有完整时钟然后看MOSI上是否出现了正确命令字节。如果CS和SCK都正常最后再怀疑MISO把读回来的数据和对端驱动时序对齐就很容易看出问题。还有一点容易被忽略STM32F100的GPIO复用配置。如果PA6没有配置成输入模式而是默认的浮空或模拟模式SPI读数据就会失败。GPIO复用配置错误在代码里看不出来因为寄存器写对了、SPI也开了唯独引脚方向不对。4.2 每次写都失败查 WEL 和写保护如果读功能正常写进0xAA读回来还是全FF优先查三件事写使能有没有发WP引脚是不是被拉低状态寄存器里的写保护位有没有被意外置位。我一块实验板上就出现过WP引脚焊盘接地的情况单字节写命令返回正常数据却始终不更新。MRAM的WPEN位默认是0一般不用再写状态寄存器但万一被置1写命令就受WP引脚控制必须用WRDI和WRSR配合解除。处理这类问题先读状态寄存器对照数据手册看bit0是WPEN、bit1是WEL。如果WEL始终为0说明WREN没有真正生效大概率是发送序列被CS时序破坏如果WEL是1但写后仍在1说明写操作没有完成要做状态机梳理。4.3 连续读偶发错位连续读命令里如果主机发完地址立刻开始读偶尔第一字节会错位。原因是地址和第一个数据字节之间没有给从机足够的建立时间SCK越接近上限越明显。解决办法有两个要么在地址后面先读一个哑字节丢弃再开始保存有效数据要么把SCK从12MHz降到6MHz。批量读临近结束时也要小心CS拉高前要多读一个哑字节把最后一个有效数据从接收移位寄存器里“挤”出来否则最后一个字节可能被截断。现象优先排查常见根因读ID全F/全0示波器抓CS、SCK、MISO接线、SPI模式错误写不进、读回F查WEL、WP引脚、状态寄存器写使能缺失、WP被拉低连续读第一字节错位地址后补哑读、降频SPI时钟过高、建立时间不足偶发数据翻转检查电源纹波、走线、HOLD悬空电气噪声、引脚未接上拉4.4 现场干扰与多任务访问现场环境里驱动逻辑全对但偶尔还是读出坏数据这时候要怀疑并发访问。比如后台任务正在写日志前台中断同时读配置SPI外设的DR寄存器就会互相打架。F100的SPI没有硬件FIFO两个任务不能同时操作同一个从设备。裸机阶段我用全局互斥标志凡是在写操作期间读操作必须等待如果上RTOS直接给读写函数加一个互斥信号量一劳永逸。MRAM写速度快但SPI总线总体带宽有限。频繁小包读写会拖慢主线程尤其环形日志要合并成批量写。我实际测试过每记录一次设备状态就单独调用一次单字节写CPU占用明显上涨改成攒满一页再批量写负载下降立竿见影。4.5 寿命估算与其他方案取舍10^12次写入意味着即便每秒写一次也能坚持超过三万年这条完全不用焦虑。需要琢磨的不是生命周期而是局部热度。虽然MRAM没有Flash那么明显的写疲劳但固定地址频繁写会在局部产生更高温度从产品设计角度还是尽量让日志区域环形轮转避免每次写同一个点。成本取舍也要清醒。MRAM比EEPROM贵容量不如NOR它适合做“重要数据的高速写入接口”不适合替代大容量Flash。如果产品还要存固件升级包、录音波形、图片那就老老实实再加一颗NOR/NAND。最终项目里我是这么分工的SPI NOR存固件备份MRAM存运行参数各干各的才把每颗芯片的价值用到位。5. 从驱动到产品掉电保存和系统集成的细节5.1 掉电保存不能只靠存储芯片存储芯片只是掉电保存链条里的一环真正落地还需要系统配合。我的做法是通过电阻分压检测主电源或者用单片机内置的电压比较器监测掉电阈值。一旦触发掉电中断系统不再接受新的业务任务立刻把关键数据整理成固定格式批量写入MRAM再写一个完成标记。整个窗口通常只有几毫秒。这里的顺序很重要。如果数据量超过64字节要分成两次写先写数据主体最后再更新完成标记。掉电恢复后系统看到“完成标记有效”才使用这份数据否则即使主体是完整的也一律按旧数据或默认值处理。这个“先数据后标记”的提交顺序比单纯依赖CRC更鲁棒因为CRC计算也可能在掉电中途被打断。5.2 把存储接口抽象成业务层别让业务裸连地址我在驱动上层加了一层薄薄的存储接口读参数、写参数、追加事件记录、读最近事件。业务代码只调用这四个函数不直接访问MRAM地址。存储布局、记录格式、CRC算法这些细节全部收敛在存储模块内部。这样做的好处是后续如果从MRAM换到FRAM甚至换成内置Flash模拟存储业务层完全不用动。裸地址访问初期很爽但项目一旦进入联调阶段就变成灾难。日志模块改个长度参数模块的地址就得跟着调两个人同时改代码地址冲突又排查半天。抽象层确实多写一点代码但能把“哪里存储、存到哪个区域”这类事情一次性管理起来后期维护成本降得明显。5.3 和主循环、RTOS配合时的最后心得用裸机时我会把MRAM读写限制在主循环里中断只负责把关键时刻数据塞进共享缓冲区再置一个标志位等主循环处理。中断里直接调SPI读写函数不是不行但要特别小心重入问题而且SPI等待循环在中断里会浪费大量CPU时间。上了RTOS之后给存储模块单独建一个任务其他任务通过队列投递“我要写一条日志”的请求。这样所有MRAM访问只有一个上下文天然避免竞争。如果日志量特别大还可以把数据先搬到DMA通道由DMA完成中断统一拉高CS。实测下来DMA方式比手动循环省掉将近一半的CPU等待时间让24MHz的F100能把精力留给业务逻辑。这套方案做完后我在现场盯着设备反复断电、重启、模拟故障日志再也没有丢过。MRAM最让我放心的一点就是它看起来像RAM那么简单却能在断电后留住现场。做工业产品要的就是这种“简单但管用”。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进