ARTICLE DETAIL

资讯详情

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

STM32F103RC驱动MR25H40CDF MRAM:SPI时序与工业存储实战

STM32F103RC驱动MR25H40CDF MRAM:SPI时序与工业存储实战 MRAM 这东西我第一次接触的时候心里是犯嘀咕的。EEPROM 我熟Flash 我也熟FRAM 用过几回但 MRAM 到底是个什么脾气跟 STM32F103RC 这种经典到不能再经典的 MCU 搭在一起能不能跑顺说实话当时没底。后来在一个工业数据采集的项目里因为现场掉电频繁、写入次数又高EEPROM 的寿命和写入延迟实在扛不住才硬着头皮把 MR25H40CDF 这颗 4Mbit 的磁阻随机存储器拉上了板子。跑通之后发现这东西在特定场景下确实香但坑也不少尤其是 SPI 时序和写保护那块稍不注意就给你来个写进去读出来全是 0xFF。这篇就把我从选型、硬件设计、SPI 驱动、读写验证到实际踩坑的完整过程摊开讲一遍。不管你是刚接触嵌入式存储的新手还是想给工业项目换一套更耐造的存储方案的老手应该都能从里面捞到点能直接用的东西。核心关键词就几个MR25H40CDF、STM32F103RC、MRAM、SPI、嵌入式存储。下面按我实际做项目的顺序来展开。1. 为什么在 STM32F103RC 上选 MR25H40CDF 而不是 EEPROM 或 Flash1.1 三类非易失存储的脾气对比做嵌入式存储选型绕不开三个老面孔EEPROM、NOR Flash、FRAM再加上这几年逐渐铺开的 MRAM。它们各自的脾气差别很大不是简单一句谁快谁好能概括的。我先把关键维度拉出来对比一下这也是我当初做决策时列的表。特性维度EEPROM如 24C 系列NOR Flash如 W25Q 系列FRAM如 FM25 系列MRAMMR25H40CDF写入寿命约 100 万次约 10 万次扇区级约 10^14 次近乎无限10^15 量级写入速度毫秒级页写 5ms 左右毫秒级扇区擦除更慢纳秒到微秒级纳秒级无写入延迟擦除需求字节可写无需擦除必须先擦除再写无需擦除无需擦除接口I2C 为主SPI/QSPISPI/I2CSPI掉电数据保持好好好好20 年以上单位容量成本中低高偏高写入功耗较高高擦除时低低这张表里最关键的两行是写入寿命和擦除需求。EEPROM 虽然字节可写、用起来最省心但 100 万次的寿命在工业场景里真的不够看——如果你的设备每秒写一次理论上 11 天左右就把一个字节写废了。Flash 更惨10 万次还是扇区级的写之前必须擦擦除又慢又费电。FRAM 寿命和速度都很好但容量做不大、价格也贵。MR25H40CDF 的定位刚好卡在中间4Mbit512KB的容量够存配置、日志、校准参数SPI 接口通用写入无延迟、无需擦除、寿命近乎无限。1.2 MR25H40CDF 的核心参数拆解选型不能只看宣传页得把数据手册里的关键参数抠出来。MR25H40CDF 是 Everspin 家的 4Mbit MRAM我整理了几个必须关注的参数容量4Mbit即 512K × 8 位。地址线 19 位寻址范围 0x00000 到 0x7FFFF。接口标准 SPI支持 Mode 0CPOL0, CPHA0和 Mode 3CPOL1, CPHA1。最高时钟频率 40MHz。供电2.7V 到 3.6V典型 3.3V跟 STM32F103RC 的 IO 电平天然匹配不需要电平转换。指令集跟标准 SPI Flash 类似有 READ0x03、WRITE0x02、WREN0x06、WRDI0x04、RDSR0x05、WRSR0x01等。写保护通过状态寄存器的 BP0/BP1 位和 WP 引脚双重保护。数据保持20 年以上。写入耐久10^15 次。这里有个容易忽略的点MR25H40CDF 的 WRITE 指令是直接写不需要像 Flash 那样先发 WREN 再发擦除指令再写。它的 WREN 只是用来解除状态寄存器的写保护不是每次写数据都必须的——但为了保险我习惯每次写之前都发一次 WREN这个后面细说。1.3 工业场景下 MRAM 真正解决的问题我在项目里遇到的具体问题是这样的设备是户外工业数据采集终端每 200ms 采集一次传感器数据需要把最近 1000 条记录存下来掉电不能丢。用 EEPROM 的话200ms 写一次一天就是 43 万次写入EEPROM 撑不过三天。用 Flash 的话得攒够一个扇区再擦写但掉电时缓存里的数据就丢了而且擦除期间如果掉电整个扇区数据都可能损坏。MRAM 在这里的优势就体现出来了写入是纳秒级的200ms 写一次对它来说跟没写一样寿命完全不是问题不需要擦除也就不存在擦除期间掉电损坏整块数据的风险SPI 接口跟原来的 Flash 电路几乎可以 pin-to-pin 替换硬件改动极小。实测下来连续跑了三个月每天写入量在 40 万次以上读取校验没有出现过一次错误。注意MRAM 虽然写入寿命近乎无限但它的写入是直接覆盖没有擦除步骤所以不存在 Flash 那种写前必须擦的约束。这一点在驱动逻辑上要区别对待不能照搬 Flash 的驱动。2. STM32F103RC 与 MR25H40CDF 的硬件连接设计2.1 SPI 引脚分配与片选策略STM32F103RC 有 3 个 SPI 外设我用的是 SPI1引脚分配如下STM32F103RC 引脚功能连接到 MR25H40CDFPA5SPI1_SCKSCKPA6SPI1_MISOSOPA7SPI1_MOSISIPA4GPIO 输出CS片选3.3V电源VDDGND地VSSPA4 可选GPIOWP写保护PA4 可选GPIOHOLD保持这里有个关键决策片选用硬件 NSS 还是软件 GPIO我强烈建议用软件 GPIO 控制片选。原因有两个一是 STM32 的硬件 NSS 在多主或多从场景下容易出问题二是软件片选可以精确控制 CS 拉低到第一个时钟沿之间的时间这对 MRAM 的时序余量很重要。我用的就是 PA4 作为普通 GPIO 输出初始化时拉高通信前拉低通信后拉高。WP 和 HOLD 引脚如果不用建议直接上拉到 3.3V不要悬空。WP 悬空时如果受到干扰被拉低可能触发写保护HOLD 悬空被拉低会暂停通信。我在第一版板子上就是 WP 悬空结果现场偶尔出现写不进去的情况查了半天才发现是 WP 被干扰了。2.2 电源与去耦的细节MRAM 的写入是纳秒级的电流脉冲对电源的瞬态响应要求比 EEPROM 高。我在 VDD 和 GND 之间放了两个电容一个 0.1μF 的陶瓷电容紧贴芯片引脚负责高频去耦一个 10μF 的钽电容放在附近负责低频储能。这两个电容的位置很关键0.1μF 的那个必须尽量靠近 VDD 引脚走线越短越好否则高频去耦效果大打折扣。另外MR25H40CDF 的供电范围是 2.7V 到 3.6VSTM32F103RC 是 2.0V 到 3.6V两者都用 3.3V 供电没问题。但要注意如果系统里有 5V 器件SPI 线上不能直接接MRAM 的 IO 不耐 5V。我见过有人把 5V 的 SPI 设备跟 MRAM 挂在同一条总线上结果 MRAM 的 IO 被打穿这个坑要避开。2.3 PCB 布局中的 SPI 走线经验SPI 在 40MHz 下虽然不算高速信号但走线不好照样出问题。我的经验是SCK 走线尽量短不要有过孔如果必须过孔控制在两个以内。SCK 和 MOSI 不要长距离平行走线避免串扰。如果实在要平行中间加地线隔离。MISO 是输入线对干扰敏感尽量远离 SCK。CS 走线可以稍长但不要跟 SCK 平行。整条 SPI 总线下面最好有一层完整的地平面。我在第二版板子上把 SCK 走了个直角结果 40MHz 下偶尔读到错误数据改成圆弧走线后问题消失。这个细节在低频时无所谓但 MRAM 支持 40MHz跑高速时就得注意。3. 基于 HAL 库的 SPI 驱动实现3.1 CubeMX 配置要点用 STM32CubeMX 配置 SPI1 的时候有几个参数必须设对ModeFull-Duplex Master。MRAM 是全双工 SPI虽然写的时候 MISO 上的数据没用但读的时候需要 MOSI 发地址所以必须全双工。Data Size8 Bits。Clock Polarity (CPOL)Low 或 High 都行MRAM 支持 Mode 0 和 Mode 3。我选的是 Low对应 Mode 0。Clock Phase (CPHA)1 Edge配合 CPOLLow 就是 Mode 0。NSSDisable用软件片选。Baud Rate Prescaler先设大一点比如 256 分频跑通之后再往上提。STM32F103RC 的 SPI1 挂在 APB2 上72MHz 时钟256 分频后是 281kHz很稳。First BitMSB First。MRAM 是高位先出。配置完之后生成代码HAL 会初始化 SPI1 和 GPIO。但片选引脚 PA4 需要手动配置成 GPIO 输出CubeMX 里把它设成 GPIO_Output初始电平 High。3.2 底层读写函数的封装HAL 库提供了HAL_SPI_Transmit和HAL_SPI_Receive但直接用来操作 MRAM 不够顺手我封装了几个底层函数。先定义片选操作#define MRAM_CS_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET) #define MRAM_CS_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET)然后是写一个字节和读一个字节static void MRAM_WriteByte(uint8_t data) { HAL_SPI_Transmit(hspi1, data, 1, 100); } static uint8_t MRAM_ReadByte(void) { uint8_t data 0; HAL_SPI_Receive(hspi1, data, 1, 100); return data; }这里有个细节HAL_SPI_Transmit和HAL_SPI_Receive在超时参数上我给了 100ms实际 40MHz 下传一个字节也就 0.2μs100ms 是极大的余量主要是防止总线异常时死等。3.3 写使能、写数据、读数据的完整流程MRAM 的写操作流程跟 Flash 不一样不需要擦除但需要先发 WREN 解除写保护。完整流程如下void MRAM_WriteEnable(void) { MRAM_CS_LOW(); MRAM_WriteByte(0x06); // WREN MRAM_CS_HIGH(); } void MRAM_Write(uint32_t addr, uint8_t *buf, uint16_t len) { MRAM_WriteEnable(); MRAM_CS_LOW(); MRAM_WriteByte(0x02); // WRITE MRAM_WriteByte((addr 16) 0xFF); MRAM_WriteByte((addr 8) 0xFF); MRAM_WriteByte(addr 0xFF); for (uint16_t i 0; i len; i) { MRAM_WriteByte(buf[i]); } MRAM_CS_HIGH(); } void MRAM_Read(uint32_t addr, uint8_t *buf, uint16_t len) { MRAM_CS_LOW(); MRAM_WriteByte(0x03); // READ MRAM_WriteByte((addr 16) 0xFF); MRAM_WriteByte((addr 8) 0xFF); MRAM_WriteByte(addr 0xFF); for (uint16_t i 0; i len; i) { buf[i] MRAM_ReadByte(); } MRAM_CS_HIGH(); }注意地址是 24 位的虽然 MR25H40CDF 只有 19 位地址但指令格式是 3 字节地址高位补 0 就行。这里我踩过一个坑一开始只发了 2 字节地址结果读出来的数据全错位。后来翻数据手册才发现MRAM 的地址是 3 字节格式跟某些小容量 Flash 的 2 字节地址不一样。提示每次写操作前都发一次 WREN 是最稳妥的做法。虽然 MRAM 的 WREN 只是解除状态寄存器写保护但如果你之前设置过块保护不发 WREN 就会写失败。我习惯在MRAM_Write里直接调用MRAM_WriteEnable省得忘。3.4 状态寄存器与写保护配置MR25H40CDF 的状态寄存器里BP0 和 BP1 两位控制块保护范围BP1BP0保护范围00无保护01保护高 1/4 地址空间10保护高 1/2 地址空间11保护全部地址空间我一般把 BP0 和 BP1 都设成 0不启用块保护因为项目里需要全片可写。但 WP 引脚我上拉到了 3.3V作为硬件兜底。如果你有部分数据需要保护比如出厂校准参数可以把 BP 位设成保护高 1/4 空间把校准参数放在高地址区。读状态寄存器的代码uint8_t MRAM_ReadStatus(void) { uint8_t status; MRAM_CS_LOW(); MRAM_WriteByte(0x05); // RDSR status MRAM_ReadByte(); MRAM_CS_HIGH(); return status; }写状态寄存器需要先发 WREN再发 WRSR0x01然后写状态值。这个操作要谨慎写错了可能导致整片不可写。4. 读写验证与实测中暴露的问题4.1 基础读写测试从单字节到跨页驱动写完之后第一步是验证基本读写。我写了个测试函数从地址 0x00000 开始写 256 字节递增数据再读回来比对void MRAM_Test(void) { uint8_t wbuf[256], rbuf[256]; for (int i 0; i 256; i) wbuf[i] i; MRAM_Write(0x00000, wbuf, 256); MRAM_Read(0x00000, rbuf, 256); for (int i 0; i 256; i) { if (wbuf[i] ! rbuf[i]) { printf(Mismatch at %d: wrote %02X, read %02X\n, i, wbuf[i], rbuf[i]); } } }第一次跑全对。但我不放心又测了跨页写入从地址 0x000FE 开始写 256 字节跨越了页边界。MRAM 没有页的概念理论上可以连续写实测也确实没问题。这一点比 Flash 省心Flash 跨页写需要分页处理MRAM 不需要。4.2 高速 SPI 下的数据错误排查基础测试通过后我把 SPI 时钟从 281kHz 逐步提到 40MHz。在 20MHz 时一切正常到 40MHz 时开始出现偶发错误大概每读几千次错一次。这个问题排查了我整整一个下午。排查过程是这样的先怀疑是 SPI 配置问题检查了 CPOL、CPHA、数据位序确认无误。用逻辑分析仪抓 SCK 和 MISO 波形发现错误发生时 MISO 上的数据确实错了不是 MCU 读错。怀疑是 MRAM 的时序余量不够查数据手册发现 40MHz 下 SCK 高电平和低电平的最小时间都是 11ns而 STM32F103RC 在 72MHz 下 SPI 分频到 40MHz 时SCK 占空比不是严格的 50%高电平时间可能只有 10ns 左右刚好卡在边界。把 SPI 时钟降到 36MHz72MHz 的 2 分频错误消失。结论是STM32F103RC 的 SPI 在最高速下占空比不够理想跟 MRAM 的时序余量刚好卡边。36MHz 下稳定运行实际读写速度也完全够用。这个坑在数据手册里不会写只有实测才能发现。注意如果你非要用 40MHz可以尝试调整 SPI 的时钟分频组合或者换用支持更高主频的 MCU。但在 STM32F103RC 上36MHz 是我实测最稳的点。4.3 掉电测试与数据完整性验证工业场景最关心的就是掉电数据完整性。我做了两组测试第一组是正常写入后立即断电重新上电读取。连续做了 500 次数据全部正确。MRAM 的写入是纳秒级完成的只要 WRITE 指令的最后一个字节发完数据就已经落盘了不存在写入缓存未刷新的问题。第二组是写入过程中断电也就是在发 WRITE 指令和数据的中间随机断电。这组测试出现了部分数据损坏——这是正常的因为写入过程被打断部分字节写进去了部分没写。但关键是损坏范围仅限于当前写入的地址段不会影响其他地址的数据。这一点比 Flash 好太多Flash 在擦除过程中断电可能导致整个扇区数据丢失。基于这个特性我在应用层做了个简单的双备份机制关键数据存两份分别放在两个地址区读取时比对不一致就用备份恢复。配合 MRAM 的快速写入两份数据的写入间隔极短掉电同时损坏两份的概率极低。5. 工业应用中的实战经验与避坑清单5.1 写入频率与寿命的真实关系很多人看到 MRAM 标称 10^15 次写入寿命就觉得可以随便写。理论上确实可以但实际项目中要考虑的不只是芯片寿命还有系统层面的问题。比如如果你每秒写 1000 次虽然 MRAM 扛得住但 SPI 总线的占用率会很高可能影响其他外设的响应。我在项目里把写入频率控制在 200ms 一次SPI 占用率不到 1%对其他任务没有影响。另外虽然 MRAM 不需要擦除但频繁写入还是会消耗一定的动态功耗。实测下来连续写入时的电流比待机时高 5mA 左右。如果设备是电池供电这个功耗要算进去。5.2 SPI 总线共享时的片选管理工业设备里 SPI 总线上往往挂多个从设备比如 MRAM、Flash、显示屏、传感器等。共享总线时片选管理是重中之重。我的做法是每个从设备有独立的 CS 引脚任何时候只有一个 CS 拉低。在切换从设备之前确保上一个设备的 CS 已经拉高并且总线空闲。如果某个从设备支持 HOLD 功能不用时把 HOLD 拉高。在 SPI 传输函数外层加互斥锁防止多任务同时访问总线。我遇到过一次诡异的问题MRAM 读出来的数据偶尔会变成显示屏的初始化数据。查了半天发现是两个任务的 CS 控制没有互斥一个任务在操作 MRAM 时另一个任务把显示屏的 CS 拉低了导致总线数据被显示屏接收同时 MRAM 的数据也被干扰。加互斥锁之后问题解决。5.3 常见问题速查表现象可能原因排查方向读出来全是 0xFFCS 没拉低、SPI 没初始化、MRAM 未供电用逻辑分析仪抓 CS 和 SCK写进去读出来不对地址格式错误、WREN 没发、写保护生效检查 3 字节地址、状态寄存器 BP 位偶发数据错误SPI 时钟过高、走线干扰、电源不稳降时钟、查走线、加去耦电容完全无响应接线错误、芯片损坏、WP/HOLD 被拉低查接线、测供电、查 WP/HOLD 电平写入后立即读正常过段时间读错误电源跌落、数据保持问题查电源纹波、做掉电测试这张表是我在实际项目中遇到问题后总结的基本上覆盖了 90% 的常见故障。遇到问题按表排查能省不少时间。5.4 从 Flash 迁移到 MRAM 的注意事项如果你的项目原来用的是 SPI Flash想换成 MRAM有几个地方必须改去掉擦除逻辑Flash 写之前必须擦MRAM 不需要。如果保留擦除代码虽然不会出错MRAM 会忽略擦除指令但浪费时间和总线带宽。地址格式确认小容量 Flash 可能是 2 字节地址MRAM 是 3 字节驱动要改。页边界处理Flash 跨页写要分页MRAM 不需要可以简化代码。状态寄存器轮询Flash 写完之后要轮询 WIP 位等待写入完成MRAM 写入是即时的不需要轮询。如果保留轮询逻辑会白白浪费时间。写保护配置Flash 和 MRAM 的 BP 位定义可能不同要重新确认。我迁移的时候把原来的 Flash 驱动整个重写了一遍代码量少了将近三分之一因为省掉了擦除和轮询逻辑。5.5 数据组织与磨损均衡的取舍MRAM 寿命近乎无限理论上不需要磨损均衡。但在实际项目中我还是建议做简单的地址轮转原因不是为了延长芯片寿命而是为了提高掉电安全性。比如日志存储如果每次都从固定地址开始写掉电时正在写的地址段可能损坏如果采用环形缓冲区每次写不同的地址即使某次掉电损坏了当前地址之前的数据仍然完好。我的做法是把 512KB 分成若干块每块存一条记录用环形指针管理。写满一圈后从头覆盖。这样既简单又能保证掉电时最多损坏一条记录。配合前面说的双备份机制数据可靠性很高。6. 性能实测数据与优化空间6.1 读写速度实测在 36MHz SPI 时钟下我实测了 MRAM 的读写速度操作数据量耗时等效速率连续读512KB约 118ms约 4.3MB/s连续写512KB约 120ms约 4.2MB/s单字节读1 字节约 3.5μs-单字节写1 字节约 4.2μs-这个速度在 SPI 存储里算很快了。作为对比同容量 EEPROM 的连续写速度大概在 10KB/s 量级Flash 的扇区擦除就要几十毫秒。MRAM 的优势非常明显。单字节写的 4.2μs 里大部分时间花在发指令和地址上4 字节实际数据写入是纳秒级。所以如果你要写大量数据连续写比单字节写效率高得多。6.2 进一步优化的方向如果觉得 4.3MB/s 还不够快有几个优化方向提高 SPI 时钟如果换用支持更高 SPI 时钟的 MCU比如 STM32H7 系列可以跑到 40MHz 甚至更高。但要注意 MRAM 的 40MHz 上限。使用 DMAHAL 库的阻塞式传输会占用 CPU改用 DMA 可以释放 CPU 去处理其他任务。我在项目后期加了 DMACPU 占用率从 15% 降到了 2% 以下。批量传输MRAM 支持连续读写一次 CS 拉低可以传输任意长度数据不需要每字节都操作 CS。我的驱动里已经是这样实现的。减少 WREN 次数如果连续写多个不连续的地址段可以在第一次写之前发一次 WREN后续写操作不再重复发。但要注意如果中间有读操作WREN 状态可能会被清除需要重新发。DMA 的配置稍微复杂一点需要把 SPI 的 TX 和 RX 都配成 DMA 模式并且处理好传输完成中断。但收益很明显尤其是在高速连续读写场景下。6.3 与其他存储方案的组合使用在实际项目中我没有只用 MRAM 一种存储。我的方案是MRAMMR25H40CDF存关键配置、校准参数、实时日志。要求高可靠性、高写入频率。SPI FlashW25Q128存固件备份、历史数据归档。容量大、成本低写入频率低。STM32F103RC 内部 Flash存程序代码和启动参数。这种组合的邏輯是把不同特性的数据放在最适合的存储介质上。MRAM 负责高频、关键、小量Flash 负责低频、大量、可容忍较慢写入。这样既保证了关键数据的可靠性又控制了整体成本。MRAM 的价格确实比 Flash 高不少4Mbit 的 MR25H40CDF 单价大概是同容量 Flash 的十几倍。所以不要盲目全用 MRAM按需分配才是合理的做法。7. 写在最后的一些个人体会这个项目做完我对 MRAM 的看法从试试看变成了真香。它在工业场景下的优势是实打实的写入快、寿命长、无需擦除、掉电安全。但它也不是万能的价格高、容量有限适合做关键数据的存储不适合做大容量归档。如果你正在做工业嵌入式项目遇到 EEPROM 寿命不够、Flash 擦除太慢的问题MR25H40CDF 配 STM32F103RC 这套组合值得认真考虑。硬件上几乎不用大改软件上把 Flash 驱动改一改就能跑。唯一需要花时间的是 SPI 时序调优和掉电测试这两块做扎实了后面基本不会出问题。最后分享一个小技巧调试 MRAM 的时候一定要用逻辑分析仪抓一次完整的读写波形把 CS、SCK、MOSI、MISO 四根线都接上。很多问题看波形比看代码快得多。我第一次遇到写不进去的问题代码查了两小时没头绪抓了波形一看CS 在数据传输中间抖了一下原因是 GPIO 配置成了开漏输出没加上拉。这种问题波形一看就明白。
返回列表