ARTICLE DETAIL

资讯详情

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

MRAM替代EEPROM:瑞萨RA2L1工业数据存储方案实践

MRAM替代EEPROM:瑞萨RA2L1工业数据存储方案实践 做工业嵌入式设备的这几年一提到“存储和读取数据”很多人第一反应就是 SPI Flash 或者 I2C EEPROM。可一旦场景变成高频写日志、断电要保数据、产品生命周期内要反复擦写这两类器件很容易成为整个系统里最先掉链子的部分。我最近在一个工业数据采集器项目里把这些底层存储整个换成了 Everspin 的 MR25H40CDF 磁阻存储器MRAM主控用的是瑞萨 R7FA2L1AB2DFP搭建了一套适合工业环境的数据读写方案。这篇就完整记录这颗 MRAM 和这颗 MCU 的配合过程为什么选了 MRAM、硬件怎么接、FSP 里怎么配置 RSPI、驱动代码如何组织、以及实测中遇到的那些典型问题和排查方法。如果你也在做嵌入式数据记录、参数存储或者需要频繁写入的缓冲类应用希望这些内容能帮你少走几步弯路。1. 为什么工业数据存储偏偏选了 MRAM1.1 一个简单算账把 EEPROM 的寿命劣势摊开以前很多工业采集板都用串行 EEPROM比如 25LC256、AT24CXXX 之类。EEPROM 不是不能用而是它的擦写次数天花板太低低到什么程度呢常规 SPI EEPROM 的擦写寿命一般是 10 万到 100 万次写入一个字节还要先擦后写耗时大概 3 到 5 毫秒。我当时那个项目现场设备每 2 秒要记录一条 16 字节的工况数据。一天下来就是 43200 次写入一年就是 1576 万次左右。拿 100 万次寿命的 EEPROM 来算两个月多一点就报废了。哪怕把寿命说到 1000 万次一年左右也要提心吊胆。现场反馈回来的故障也确实吻合写失败、数据偶尔丢失、设备重启后日志断档。这还没算 EEPROM 的另一个问题写一页和写一个字节的时间几乎一样但页编程前必须把整个页擦掉。日志类应用往往是随机追加、字节级修改成本非常高。所以那版设计我基本没犹豫直接上了 MRAM。MR25H40CDF 的写耐久性典型值是 10 的 14 次方次换算成刚才那个写入频率设备要跑几十万年才可能摸到寿命边界。对工业设备来说基本等于永久。而且它写数据不需要擦除任意字节直接覆盖这就把“先擦后写”的流程彻底省掉了。1.2 MRAM 的技术底子与 Flash/EEPROM 的差异很多人听到“非易失性存储器”脑子里只有 Flash 和 EEPROM。MRAM 的原理和它们完全不同。EEPROM 和 Flash 靠的是浮栅里的电荷电荷会被慢慢泄漏编程时还要用电荷泵产生高压把电子注入进去擦除时再靠隧穿效应把电子拉出来。这个过程天生慢而且每一次擦写都在氧化层上留下一点损伤所以寿命有限。MRAM 用的是磁隧道结MTJ。存储单元里有两层磁性材料中间夹着一层极薄的绝缘层自由层的磁化方向和固定层的磁化方向平行或反平行时隧道电阻不同对应 0 和 1。写入的时候施加电流产生的磁场或自旋极化电流直接把自由层的磁化方向翻转。没有电荷注入没有擦除操作翻转完成数据就稳定了。理解了这个原理你就能明白为什么 MRAM 在工业场景里这么香写入前不需要擦除所以随机写、字节写都很快不挑地址。写寿命极高不用做磨损均衡。非易失性断电后数据不丢而且不存在电荷泄漏的长期可靠性隐患。读速度接近 SRAMSPI 接口跑到 40MHz 没什么压力。数据保持能力通常在 20 年以上。当然MRAM 也不是没有缺点最直接的就是单位容量价格比 Flash 高而且大容量型号选择少。但在工业数据记录这个场景里容量需求往往只有几十到几百 KBMRAM 的价格劣势并不明显可靠性优势却是实打实的。1.3 MR25H40CDF 规格里最该关心的几个数字MR25H40CDF 是 Everspin MR25H40 系列里的 SPI 接口 MRAM。我选它最看重的几个数字列一下参数数值容量4 Mbit也就是 512 KB组织结构512 K × 8 bit接口SPI支持模式 0 / 模式 3最高时钟40 MHz供电电压3.0 ~ 3.6 V典型 3.3 V写耐久性典型 10 的 14 次方次写周期数据保持 20 年地址字段3 字节有效地址 0x00000 ~ 0x7FFFF这里有一个容易忽略的地方4 Mbit 看起来不大但 512 KB 划分成日志区、参数区、缓冲区已经非常够用。拿每条记录 64 字节来算512 KB 能存 8192 条配合环形覆盖设计等于一个掉电不丢失的小型磁盘。命令集也比较简单核心就 6 条WREN0x06、WRDI0x04、RDSR0x05、WRSR0x01、WRITE0x02、READ0x03。后面驱动代码主要就是围绕这几条命令展开。2. 硬件接线与 SPI 总线搭建把两颗芯片接起来并不复杂2.1 R7FA2L1AB2DFP 的 RSPI 外设与引脚分配R7FA2L1AB2DFP 属于瑞萨 RA2L1 系列Arm Cortex-M23 内核主频 48 MHz有 256 KB Flash 和 32 KB RAM片上带了 RSPI 串行外设接口。对 MRAM 这种标准 SPI 器件来说不需要什么特殊外设RSPI 即可。在 FSP 里RSPI 可以配置成主模式引脚一般使用 SCLK、MOSI、MISO 三根线。片选 CS 我建议不要用 RSPI 的硬件 SS而是单独拿一个普通 GPIO 来控制。原因后面写驱动时会详细说核心点是 MRAM 的写使能和写执行需要精确控制 CS 的下降沿和上升沿用 GPIO 软控时序最直接可读性也最好。我的接线示意如下MR25H40CDF 引脚功能接 R7FA2L1AB2DFPCS片选P301GPIO 输出默认拉高SCLKSPI 时钟RSPI0 SCLKSI数据输入RSPI0 MOSISO数据输出RSPI0 MISOWP写保护接 3.3 V禁用写保护HOLD传输暂停接 3.3 V禁用暂停功能VCC电源3.3 V并联 100 nF 10 uFVSS地GND这里要特别提醒一句接线时先分清 SO 和 SI别接反了。MRAM 的 SO 是输出接 MCU 的 MISOSI 是输入接 MCU 的 MOSI。有些开发板丝印容易让人看反我第一版调试就吃过这个亏全部数据读回都是 0xFF排查半天才发现是 MOSI 和 MISO 交叉了。2.2 CS、WP、HOLD 这些控制脚的接法CS 脚作为片选每次读写事务都要拉低事务结束后拉高。这个脚不能悬空如果 MCU 复位期间 GPIO 是高阻状态外部最好加一个 10 kΩ 上拉电阻到 VCC保证 MRAM 在系统上电瞬间不被误触发。WP 脚叫写保护低电平有效。接低电平会禁止写状态寄存器和数据区具体行为取决于器件配置但最稳妥的做法就是直接接高电平彻底关掉写保护功能。不要让它悬空工业现场电磁干扰多悬空引脚很容易被耦合出错误电平导致写操作莫名失败。HOLD 脚是传输暂停低电平有效。HOLD 拉低时SPI 时钟和 CS 状态会被器件忽略总线上的数据保持外部主机暂时没办法继续通信。这个脚同样不能悬空接高电平即可。VCC 旁边要放 100 nF 的陶瓷电容尽量贴近引脚再加一颗 10 uF 钽电容做低频去耦。工业现场如果电源本身波动大建议在电源入口加 TVS 管和磁珠防止继电器或其他大功率设备启停时把尖峰灌进来。2.3 电路上常见的三个坑第一个坑是电平匹配。RA2L1 的 GPIO 和 MRAM 都是 3.3 V 系统直接连接没有问题。但如果你之前用惯了 5 V 的 AVR 或老式 51千万别顺手把 MRAM 接到 5 V 上MR25H40CDF 的绝对最大额定值不允许。5 V 系统必须加电平转换。第二个坑是 SPI 信号走线太长。工业电路板上如果有电机驱动、继电器、开关电源这些干扰源会通过长走线耦合进 SPI 总线。调通之后可以把时钟跑到 40 MHz但前期调试建议降到 1 MHz 到 10 MHz 之间先把功能跑对再慢慢提速度。40 MHz 下串 33 Ω 电阻可以抑制振铃具体阻值示波器看波形再定。第三个坑是 MISO 引脚误开了内部上拉。RA2L1 的 GPIO 可以配置内部上下拉如果在 FSP 里把 MISO 配成带上拉的输入虽然读取逻辑上问题不大但上拉会抬高信号的低电平上升沿高速 SPI 下容易造成数据采样错误。MISO 建议关闭上下拉。3. FSP 里配完 RSPI剩下就是命令序列和驱动封装3.1 用 e² studio FSP 生成 RSPI 工程瑞萨 RA 系列用 e² studio 开发FSP 是它的软件包配置框架。新建工程时选择 R7FA2L1AB2DFP 这个具体型号然后在 Stacks 页面添加 SPI 驱动也就是 r_spi 模块。配置项里几个关键参数Operation ModeMaster主机模式。Frame FormatSPI。Data Bit Width8 bit。SPI Mode模式 0 或者模式 3MRAM 两种都支持。Bitrate先填 1 MHz跑通后调到 10 MHz 或 20 MHz 都可以。Bit OrderMSB First。片选不用 RSPI 的硬件 SS用 GPIO 自己控制。生成代码后工程里会出现 g_spi0_ctrl 和 g_spi0_cfg。主函数里调用 R_SPI_Open 初始化一次即可。有一点必须强调FSP 的 R_SPI_WriteRead 默认是异步行为代码不能裸调完就立刻拉 CS否则数据可能还没完全从移位寄存器里移出去CS 就拉高了整个命令会丢失。我习惯在配置里开传输完成中断注册回调函数在回调里置一个事件标志主流程等待这个标志后再拉高 CS。如果你喜欢轮询也可以用 R_SPI_EventGet 不断查询事件状态但回调加标志位的方式更清晰多任务下也更容易扩展。3.2 命令序列从 WREN 到最后的 CS 上升沿MR25H40CDF 的读写虽然简单但命令顺序很讲究尤其 CS 边沿的时序。先看写使能 WREN这是所有写操作的前提CS L 发送 0x06WREN CS H上升沿写使能锁存到状态寄存器这一步如果 CS 拉高前数据还没发完写使能就不会生效。所以代码里发送完 0x06 之后必须等 SPI 传输完成事件再拉高 CS。然后是写数据 WRITECS L 发送 0x02WRITE 发送 3 字节地址高位在前 发送数据字节 CS H上升沿执行写入MRAM 的好处是 CS 拉高后写入立即完成不需要像 EEPROM 那样等待内部编程时间。写完可以马上发下一条命令也可以马上读回验证。但注意每次写命令之前都必须重新做一次 WREN因为 WEL 位在一次写操作完成后会自动清掉。很多第一次用 MRAM 的工程师都会在这里翻车写使能只做了一次然后连续写多个地址第二个地址开始全部写不进去。读数据 READ 更简单CS L 发送 0x03READ 发送 3 字节地址 持续发送时钟MISO 上读出数据 CS HREAD 命令下地址可以连续递增保持 CS 为低就可以一直读下去直到地址越过 0x7FFFF 后自动回绕到 0x00000。写数据也一样地址会自动递增所以批量写入时不需要每字节都带地址。3.3 驱动模块设计与完整读写代码驱动逻辑我拆成三个层次底层 SPI 收发、MRAM 命令封装、上层读写接口。底层只关心把数据发出去或者收发同时进行上层接口负责地址合法性判断和分包。先给一个头文件定义命令字和地址范围#ifndef MRAM_H_ #define MRAM_H_ #include stdint.h #define MRAM_CMD_WREN 0x06 #define MRAM_CMD_WRDI 0x04 #define MRAM_CMD_RDSR 0x05 #define MRAM_CMD_WRSR 0x01 #define MRAM_CMD_WRITE 0x02 #define MRAM_CMD_READ 0x03 #define MRAM_MAX_ADDR 0x7FFFFu int mram_init(void); int mram_write_buf(uint32_t addr, const uint8_t *data, uint32_t len); int mram_read_buf(uint32_t addr, uint8_t *data, uint32_t len); #endif再看底层实现。以 FSP 生成的 g_spi0_ctrl 为例我用回调方式等待传输完成#include hal_data.h #include mram.h #include string.h static volatile bool g_mram_spi_done false; void mram_spi_callback(spi_callback_args_t * p_args) { if (SPI_EVENT_TRANSFER_COMPLETE p_args-event) { g_mram_spi_done true; } } static void mram_cs_low(void) { R_IOPORT_PinWrite(g_ioport_ctrl, PIN_MRAM_CS, BSP_IO_LEVEL_LOW); } static void mram_cs_high(void) { R_IOPORT_PinWrite(g_ioport_ctrl, PIN_MRAM_CS, BSP_IO_LEVEL_HIGH); } static void mram_wait_tx_done(void) { while (!g_mram_spi_done) { } g_mram_spi_done false; } static int mram_tx_bytes(const uint8_t *tx, uint16_t len) { fsp_err_t err R_SPI_Write(g_spi0_ctrl, (uint8_t *)tx, len, false); if (FSP_SUCCESS ! err) { return -1; } mram_wait_tx_done(); return 0; } static int mram_txrx_bytes(const uint8_t *tx, uint8_t *rx, uint16_t len) { fsp_err_t err R_SPI_WriteRead(g_spi0_ctrl, (uint8_t *)tx, rx, len, false); if (FSP_SUCCESS ! err) { return -1; } mram_wait_tx_done(); return 0; }这里提醒一下回调事件具体名称取决于 FSP 版本有些版本是 SPI_EVENT_OPERATION_COMPLETE我示例里用的是 SPI_EVENT_TRANSFER_COMPLETE编译报错的话改一下宏名就行。如果工程里开了 DTC传输完成后的处理逻辑会略有不同我建议这个模块保持最简单的中断模式不开 DTC方便排查。写使能和读状态static int mram_write_enable(void) { uint8_t cmd MRAM_CMD_WREN; mram_cs_low(); if (mram_tx_bytes(cmd, 1) ! 0) { mram_cs_high(); return -1; } mram_cs_high(); return 0; } static uint8_t mram_read_status(void) { uint8_t tx[2] { MRAM_CMD_RDSR, 0x00 }; uint8_t rx[2] { 0x00, 0x00 }; mram_cs_low(); if (mram_txrx_bytes(tx, rx, 2) ! 0) { mram_cs_high(); return 0xFF; } mram_cs_high(); return rx[1]; }状态寄存器最低位就是 WEL读取后判断一下能帮我们快速定位“写不进去”的问题。写数据接口。我每次最多写 256 字节超过就分包处理。原因有两个一是避免在栈上开太大的数组二是每次写事务的时间可控不会长时间占用 SPI 总线int mram_write_buf(uint32_t addr, const uint8_t *data, uint32_t len) { static uint8_t tx[256 4]; if (addr MRAM_MAX_ADDR || (addr len - 1) MRAM_MAX_ADDR) { return -1; } uint32_t pos 0; while (pos len) { uint16_t chunk (len - pos 256) ? 256 : (uint16_t)(len - pos); uint32_t cur addr pos; tx[0] MRAM_CMD_WRITE; tx[1] (cur 16) 0xFF; tx[2] (cur 8) 0xFF; tx[3] cur 0xFF; memcpy(tx[4], data[pos], chunk); if (mram_write_enable() ! 0) { return -1; } mram_cs_low(); if (mram_tx_bytes(tx, 4 (uint16_t)chunk) ! 0) { mram_cs_high(); return -1; } mram_cs_high(); pos chunk; } return 0; }读数据接口用全双工 R_SPI_WriteRead 实现。发送命令和地址后后续时钟由 MCU 生成同时 MISO 逐位移出数据收到的前 4 个字节是命令和地址阶段的无效数据从 rx[4] 开始才是真正的数据int mram_read_buf(uint32_t addr, uint8_t *data, uint32_t len) { uint8_t tx[256 4] { 0 }; uint8_t rx[256 4] { 0 }; uint8_t hdr[4]; if (addr MRAM_MAX_ADDR || (addr len - 1) MRAM_MAX_ADDR) { return -1; } hdr[0] MRAM_CMD_READ; uint32_t pos 0; while (pos len) { uint16_t chunk (len - pos 256) ? 256 : (uint16_t)(len - pos); uint32_t cur addr pos; hdr[1] (cur 16) 0xFF; hdr[2] (cur 8) 0xFF; hdr[3] cur 0xFF; memset(tx, 0x00, 4 chunk); memcpy(tx, hdr, 4); mram_cs_low(); if (mram_txrx_bytes(tx, rx, (uint16_t)(4 chunk)) ! 0) { mram_cs_high(); return -1; } mram_cs_high(); memcpy(data[pos], rx[4], chunk); pos chunk; } return 0; }这个驱动读写独立分包每次 CS 重新建立事务所以跨 0x7FFFF 边界的问题也分块堵住了上层不用太担心地址回绕。唯一要注意的是静态局部变量 tx 让驱动不可重入如果项目里有多个任务同时访问 MRAM记得在外面加互斥保护。4. 实测与排错全 0xFF、写不进去、以及滚写可靠性4.1 读回全 0xFFSPI 模式会和信号完整性背锅第一次上板我调用 mram_read_buf 读 MRAM 厂商出厂数据区返回的全是 0xFF。这个现象在 SPI 存储器件调试里太经典了基本可以按下面的顺序排查。先测电源和地。MRAM 供电要是 3.3 VVCC 和 VSS 之间不能有虚焊。然后用示波器看 CS 拉低后SCLK 上是否有时钟输出。如果 CS 一直高MRAM 根本没被选中数据线自然一直高电平读回来全是 0xFF。第二步查 SPI 模式。MR25H40CDF 只支持模式 0 和模式 3如果 FSP 里配成了模式 1 或模式 2器件永远不会给出正确数据。模式 0 和模式 3 的区别在于空闲时钟电平和采样沿示波器上一抓就能看出来。我当时把 RSPI 从模式 0 换成模式 3数据立刻正常了。第三步查 MISO。用示波器看 MISO 引脚在 CS 拉低、发送 READ 命令后MISO 上应该有脉冲信号。如果始终是一条高电平要么芯片虚焊要么 SO 引脚没接到 MCU 的 MISO。再不行就把 SPI 速度降到 1 MHz排除信号振铃。还有一个容易被忽略的点就是读状态寄存器。上电后正常 RDSR 应该返回 0x00。如果 RDSR 返回 0xFF几乎可以断定命令根本没发进去问题出在 CS 时序或 SPI 配置如果 RDSR 返回 0x02 之类说明 WEL 处于置位状态可能是上次写操作后没清这本身不算故障。4.2 写不进去先查 WEL 位再查 CS 边沿全 0xFF 的问题解决后紧接着遇到写不进去调用 write_buf 再读回来数据跟写之前完全一样。这个时候第一件事就是查 WEL。我在 mram_write_enable 后加了一个断言函数读 RDSR判断 bit0 是不是 1。如果置位失败说明 WREN 命令没被正确执行。常见原因有两个第一个是传输没完成就拉高了 CS。代码里用 R_SPI_Write 发送 0x06 后如果没有等待事件标志而立刻 cs_high在 SPII 高速率下0x06 可能只说了一半CS 就上去了。解决方式就是前面驱动里写的发送后必须等 g_mram_spi_done 标志置位再拉高 CS。第二个是 WP 脚被拉低了。WP 拉低会让器件进入写保护状态WREN 命令本身虽然能发但 WEL 可能不会被正确置位或者即使置位了后续 WRITE 也会被拒绝。所以先量一下 WP 引脚电压确认是高电平。如果 WEL 已经是 1但 WRITE 还是写不进去就要查地址对不对。MR25H40CDF 只有 19 位有效地址最高地址是 0x7FFFF如果地址算错写到了非法区域器件不会报错但数据也进不去。我总是先用固定地址比如 0x00000 来测试写一组 0x5A 5A 5A 5A读回比对最大限度减少变量。最后还要注意一个时序细节写命令的数据字节必须在 CS 上升沿之前全部发完。对于 MR25H40CDF写入动作就是 CS 上升沿那个瞬间提交的如果你在发送数据的中途就拉高 CS这次写入会失败或者只写入半个字节。用示波器抓 CS 和 SCLK 的关系确认 CS 拉高前最后一个数据位已经稳定这比盯着代码猜靠谱得多。4.3 百万次滚写与掉电回读测试驱动调通之后真正的考验才开始。我写了一个压力测试固件对固定地址反复写入递增计数每次写完立即读回校验并使用软件 CRC32 做数据完整性判断。日志通过串口输出。测试结果很直观260 万次滚写全部通过没有一次读回错误。这个数字对 EEPROM 来说已经是不可想象的任务对 MRAM 来说只是热身。我也用示波器同时观察了 SPI 波形写操作完成后 CS 上升沿到下一次命令发起之间没有额外延时说明 MRAM 确实不需要内部写等待。掉电测试更有意思。我在主循环里周期性写一条 64 字节记录然后人为随机断电再重新上电扫描 MRAM 里的记录区检查每一条记录的 magic、长度和 CRC。因为 MRAM 的写入是原子提交每次结果要么是完整的旧记录要么是完整的新记录没有出现半条记录混杂的情况。这正是工业掉电场景最需要的特性。高低温方面我用热风枪把板子加热到 105℃ 左右持续运行再放回常温验证数据读写均正常。有条件的话建议做一轮完整的 -40℃ 到 85℃ 温度循环工业级存储器件在这种条件下反而比消费级 Flash 稳定得多。5. 工程化思考掉电保护、容量规划与跨平台迁移5.1 掉电瞬间怎么写不丢MRAM 本身是非易失的但“非易失”不代表“掉电时正在写的操作也一定能完成”。真正的工程问题是当电源开始跌落而 MCU 恰好正在往 MRAM 写数据这个事务到底能不能完整提交。我建议从两头发力。第一硬件上在系统电源端加掉电检测。RA2L1 自带低电压检测模块也可以外接 TPS3839 之类的电压监控芯片当 VCC 低于 3.0 V 时触发中断或复位。第二固件里把关键数据维护成“镜像”。比如环形日志区先写入记录正文和 CRC全部成功后再更新日志头里的写指针。掉电发生在新记录写了一半时上电后头部指针还指向上一条记录新记录虽然空间占用了但不会被当作有效记录也不会破坏旧数据。MRAM 写 512 字节在 20 MHz SPI 下大概是 200 微秒左右加上命令和地址开销也不到 300 微秒。如果 MCU 的掉电检测能在电压跌落到器件最低工作电压之前留出 1 毫秒那么写一两条关键记录完全来得及。真正要避免的是在电源已经不稳的时候还去做耗时的读改写流程所以掉电中断里只做最精简的写操作。5.2 磨损、容量与缓冲区规划MRAM 虽然寿命极高但它也有自己的“写周期”概念。一次完整的 CS 上升沿提交算一个写周期无论写 1 字节还是写 256 字节都是消耗一次。所以真正高效的用法是数据攒够一包再写而不是每来一个字节就发起一次事务。这样做还能减少 SPI 总线占用和调度开销。容量规划方面512 KB 需要想清楚分区。我习惯把 MRAM 分成三个区域区域大小用途参数区4 KB设备配置、校准参数低频写日志索引区4 KB写指针、读指针掉电恢复依据循环日志区剩余空间环形数据记录高频覆盖写参数区低频写入用双缓冲加 CRC 防止更新过程中掉电。日志索引区单独划出来是因为每次写入记录后都要更新指针如果指针和记录混在一起灾难恢复会非常麻烦。主日志区做环形缓冲满了以后覆盖最旧记录这样 MRAM 的写寿命被分散在整个区域而不是集中在一个地址上。如果设备需要保存较大的波形数据或批量事件512 KB 可能不够那就需要考虑把 MRAM 换成功耗更高的 SPI NOR Flash或者加 SD 卡。但请注意这种扩容通常会牺牲随机写入寿命和写速度本质上是用复杂度换容量不是所有场景都划算。5.3 换个 MCU 怎么办MR25H40CDF 是标准 SPI 命令集和主控品牌没有绑定关系。RA2L1 用 FSPSTM32 用 HALNXP 用 SDK底层函数各不一样但上层逻辑完全可以原样搬走。我在驱动设计时就把硬件有关的操作收敛成了几个内部函数mram_cs_low、mram_cs_high、mram_tx_bytes、mram_txrx_bytes、mram_wait_tx_done。换平台时只需要重写这五个函数的实现mram_write_buf 和 mram_read_buf 不需要动。如果换成带硬件 CS 的 MCU我还是坚持用 GPIO 软控 CS因为 MRAM 命令序列对 CS 边沿的要求比较明确软控不受外设自动控制逻辑干扰出问题也好定位。另外提醒一点不同 MCU 的 SPI 时钟极性命名可能不同。ST 的 HAL 里 SPI_MODE0 是 CPOL0、CPHA0SPI_MODE3 是 CPOL1、CPHA1。瑞萨 FSP 里同样用模式 0/模式 3 命名但有些老工程师习惯叫“空闲时钟电平高还是低”这时候直接看寄存器定义最安全。这次把存储介质从 EEPROM 换成 MRAM改动的代码量并不大但带来的是整个系统可靠性的明显提升。如果非要说留下什么经验我会记下三条第一MRAM 不是 Flash别再用擦除的老思路第二CS 边沿的时序比命令字本身更关键第三测试存储器件一定要在真实写频和掉电条件下跑而不是写入一次读一次就算验证完成。后面如果再遇到类似的工业数据记录需求我还是会优先看这类 MRAM 方案。
返回列表