1. 项目缘起与方案选型思路
1.1 为什么要在工业场景里折腾 MRAM 这颗料
做过工业数据采集或者电力终端的朋友应该都有体会,最头疼的往往不是算法多复杂,而是数据存不住。设备跑在变电站、工厂产线、户外机柜里,夏天六十多度、冬天零下三四十度,偶尔还来个意外断电,这时候你辛辛苦苦写进 EEPROM 或者 Flash 里的校准参数、累计电量、故障记录,说没就没了。我早年做配电终端的时候就吃过这个亏,一批设备在现场跑了半年,返修回来发现存储区数据大面积损坏,最后查出来是 Flash 在低温下写入时序出了问题。
后来接触到MRAM这类磁性随机存储器,才算真正把这块心病放下。这次要聊的MR25H40CDF就是一颗 4Mbit 的 SPI 接口 MRAM,来自 Everspin 的 MR2xH40 系列。它和传统存储最大的区别在于:写入不依赖电荷,而是靠磁性隧道结的磁化方向翻转。这个物理机制决定了它几个非常硬核的特性——写入速度是纳秒级、擦写寿命近乎无限(官方标称 10^14 次以上)、不需要擦除操作、掉电数据保持二十年以上,而且工作温度覆盖 -40℃ 到 +85℃ 的工业级甚至更宽。
对比一下常见的方案你就明白为什么选它了。EEPROM 写入慢、寿命只有百万次量级,频繁记录的场景根本扛不住;NOR Flash 虽然容量大,但必须整块擦除、写入前要解锁、还有写放大问题;FRAM 性能接近但容量普遍偏小、价格也高。MRAM 基本是这几者的一个平衡点,尤其是字节级随机写入这个能力,对于那种"来一条数据记一条"的工业日志场景简直是量身定做。
1.2 TM4C1294NCZAD 为什么适合当主控
主控这边选的是 TI 的TM4C1294NCZAD,这是 Tiva C 系列里比较能打的一颗。它基于 ARM Cortex-M4F 内核,主频 120MHz,带浮点单元,片上 1MB Flash、256KB SRAM,外设资源相当丰富——8 个 UART、4 个 I2C、4 个 SSI(也就是 SPI)、还有以太网 MAC、USB、CAN 这些工业场景常用的接口。封装是 212 脚的 BGA,引脚多到用不完,做复杂一点的工业网关或者数据记录仪绰绰有余。
选它配 MR25H40CDF 有几个现实理由。第一,SSI 外设成熟稳定,TI 的 TivaWare 库把 SPI 主机的配置封装得很干净,FIFO 深度 16 字节,配合 DMA 可以做到几乎不占 CPU 的连续读写。第二,宽温版本齐全,TM4C1294 有工业级温度范围的型号,和 MRAM 的温区能对上。第三,生态资料多,不管是 TI 官方文档还是社区里的踩坑记录,遇到问题基本都能搜到答案,这对项目落地太重要了。
1.3 整体架构长什么样
整个方案说白了就是一句话:TM4C1294 当 SPI 主机,MR25H40CDF 当从机,通过四线 SPI 完成数据的存储和读取。硬件上连接 CS、SCK、MISO、MOSI 四根线,加上电源和地。软件上分三层:底层是 SPI 驱动,负责字节收发;中间层是 MRAM 驱动,封装读写命令、地址处理、状态查询;上层是应用逻辑,比如参数存储、日志记录、掉电保护。
这个架构的好处是解耦彻底。底层 SPI 驱动换成别的 MCU 也能复用,中间层 MRAM 驱动只要改改 SPI 收发函数就能移植,上层应用完全不关心底下是 MRAM 还是 Flash。我后面会把每一层的实现细节都拆开讲,包括那些文档里不会写、只有实际调过才知道的坑。
提示:MR25H40CDF 是 3.3V 供电的器件,TM4C1294 的 IO 也是 3.3V 电平,两者可以直接对接,不需要电平转换。但如果你的主控是 5V 系统,务必加电平转换芯片,否则会打坏 MRAM。
2. MR25H40CDF 核心细节与硬件设计要点
2.1 芯片内部结构和寻址方式
MR25H40CDF 的容量是 4Mbit,换算成字节就是512KB,地址范围从 0x00000 到 0x7FFFF,一共 19 位地址线。它内部按字节组织,每个字节都可以独立读写,没有扇区、没有页的概念,这一点和 Flash 完全不同。你给它一个地址,它就直接返回那个地址的数据,不需要考虑对齐、不需要考虑擦除边界。
SPI 接口支持模式 0 和模式 3,也就是 CPOL/CPHA 的两种组合。模式 0 是时钟空闲低电平、数据在上升沿采样;模式 3 是时钟空闲高电平、数据在下降沿采样。这两种模式 MRAM 都认,具体用哪个取决于你的主控配置。TM4C1294 的 SSI 外设两种都支持,我一般习惯用模式 0,因为示波器上看波形比较直观,上升沿采样也符合大多数人的直觉。
命令集方面,常用的就几条:WREN(0x06)写使能、WRDI(0x04)写禁止、READ(0x03)读数据、WRITE(0x02)写数据、RDSR(0x05)读状态寄存器、WRSR(0x01)写状态寄存器。注意写操作之前必须先发 WREN,这是硬性要求,忘了这一步数据写不进去,而且芯片不会报错,只会默默忽略你的写命令,这个坑我踩过。
2.2 硬件连接与 PCB 布局注意事项
接线本身很简单,四根信号线加电源地。但 PCB 布局有几个细节必须注意,否则高速通信时容易出问题。
第一,SCK 走线要尽量短且远离干扰源。SPI 时钟频率我一般跑到 10MHz 到 20MHz,MR25H40CDF 最高支持 40MHz,但实际能跑多快取决于你的板子质量。如果 SCK 旁边有大电流的开关电源走线,很容易耦合进噪声,导致数据采样错误。我建议 SCK 和 MOSI 之间留够间距,最好包地处理。
第二,CS 片选信号的处理。CS 是低电平有效,空闲时必须拉高。如果 CS 悬空或者上电时序不对,MRAM 可能误进入某种状态。我习惯在 CS 上拉一个 10K 电阻到 3.3V,确保上电瞬间芯片处于未选中状态。另外,CS 的下降沿到第一个 SCK 边沿之间要留够时间,MR25H40CDF 要求至少 5ns,一般 MCU 都能满足,但如果你的 SPI 配置里 CS 是硬件自动控制的,要确认一下时序参数。
第三,电源去耦。MRAM 在写入瞬间会有电流尖峰,虽然不大,但如果在电源引脚旁边没有合适的去耦电容,可能导致写入失败。我的做法是在 VDD 引脚旁边放一个0.1uF 陶瓷电容紧贴引脚,再在电源入口放一个1uF 到 10uF 的钽电容做储能。这两个电容的位置比容值更重要,0.1uF 那个一定要离引脚越近越好。
第四,MISO 的上拉问题。MISO 是 MRAM 的输出,当 CS 为高时它处于高阻态。如果主控的 MISO 引脚没有内部上拉,悬空状态下可能读到随机数据。TM4C1294 的 SSI 引脚可以配置内部上拉,我一般会打开,或者在板子上加一个 10K 外部上拉。
2.3 关键参数与时序计算
要把 MRAM 用稳,几个时序参数必须搞清楚。下面这张表是我从数据手册里整理出来的核心参数,实际配置 SPI 时对着看就行。
| 参数 | 符号 | 最小值 | 典型值 | 最大值 | 单位 |
|---|---|---|---|---|---|
| 供电电压 | VDD | 2.7 | 3.3 | 3.6 | V |
| 最高时钟频率 | fSCK | - | - | 40 | MHz |
| CS 建立时间 | tCSS | 5 | - | - | ns |
| CS 保持时间 | tCSH | 5 | - | - | ns |
| 数据输出延迟 | tV | - | - | 8 | ns |
| 写入周期时间 | tWC | - | - | 0.5 | ms |
| 数据保持 | - | 20 | - | - | 年 |
| 擦写寿命 | - | 10^14 | - | - | 次 |
这里重点说两个。tV 是数据输出延迟,意思是 SCK 边沿到来后,MISO 上的数据最多 8ns 才稳定。如果你跑 40MHz,一个时钟周期才 25ns,8ns 的延迟占了将近三分之一,留给主控采样窗口就很紧张了。所以实际项目中我一般不会跑满 40MHz,15MHz 到 20MHz 是比较稳妥的选择,既能保证速度,又留足了时序余量。
tWC 是写入周期时间,0.5ms 是最大值。注意 MRAM 的写入是异步的,你发完写命令和地址数据后,芯片内部需要时间完成磁性翻转。在这 0.5ms 内,如果你又发新的写命令,可能会被忽略。所以连续写入时,要么在每次写之后读状态寄存器确认完成,要么在两次写之间插入足够的延时。我实测下来,单字节写入后延时 100us 基本就稳了,但为了保险,批量写入时我会用状态寄存器轮询的方式。
3. 软件驱动实现与实操过程
3.1 TM4C1294 的 SSI 外设初始化
先讲底层 SPI 配置。TM4C1294 的 SSI 外设配置分几步:使能外设时钟、配置 GPIO 复用、设置 SSI 参数、使能 SSI。我用的是 SSI2 模块,对应 PB4 到 PB7 这几个引脚,具体代码逻辑如下。
#include "inc/hw_memmap.h" #include "inc/hw_ssi.h" #include "driverlib/ssi.h" #include "driverlib/gpio.h" #include "driverlib/pin_map.h" #include "driverlib/sysctl.h" void MRAM_SPI_Init(void) { // 使能 SSI2 和 GPIOB 时钟 SysCtlPeripheralEnable(SYSCTL_PERIPH_SSI2); SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB); while(!SysCtlPeripheralReady(SYSCTL_PERIPH_SSI2)); while(!SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOB)); // 配置引脚复用 GPIOPinConfigure(GPIO_PB4_SSI2CLK); GPIOPinConfigure(GPIO_PB5_SSI2FSS); GPIOPinConfigure(GPIO_PB6_SSI2RX); GPIOPinConfigure(GPIO_PB7_SSI2TX); GPIOPinTypeSSI(GPIO_PORTB_BASE, GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7); // 配置 SSI:主机模式、模式0、16位数据、20MHz SSIConfigSetExpClk(SSI2_BASE, SysCtlClockGet(), SSI_FRF_MOTO_MODE_0, SSI_MODE_MASTER, 20000000, 8); // 使能 SSI SSIEnable(SSI2_BASE); // 清空接收 FIFO uint32_t dummy; while(SSIDataGetNonBlocking(SSI2_BASE, &dummy)); }这段代码里有几个点值得展开。SSIConfigSetExpClk 的最后一个参数是数据位宽,我写的是 8,也就是每次收发 8 位。MRAM 是字节寻址的,8 位正好。但 TM4C1294 的 SSI FIFO 是 16 位宽的,如果你发 8 位数据,实际占用 FIFO 的低 8 位,读的时候也要按 8 位读,否则会错位。
时钟频率我设的是 20MHz,前面说过这是稳妥值。SysCtlClockGet() 返回的是系统时钟,TM4C1294 默认 120MHz,SSI 分频后得到 20MHz。如果你改了系统时钟,这个参数要跟着调整。
模式 0 的配置对应 SSI_FRF_MOTO_MODE_0,这是最常用的。如果你用模式 3,改成 SSI_FRF_MOTO_MODE_3 即可,但要注意 MRAM 的时序要求是一样的,模式选择只影响时钟极性和相位。
3.2 MRAM 读写函数的封装
底层 SPI 通了之后,就可以封装 MRAM 的读写函数了。核心是三个操作:写使能、读数据、写数据。先看写使能的实现。
#define MRAM_CMD_WREN 0x06 #define MRAM_CMD_WRDI 0x04 #define MRAM_CMD_READ 0x03 #define MRAM_CMD_WRITE 0x02 #define MRAM_CMD_RDSR 0x05 static void MRAM_CS_Low(void) { GPIOPinWrite(GPIO_PORTB_BASE, GPIO_PIN_5, 0); } static void MRAM_CS_High(void) { GPIOPinWrite(GPIO_PORTB_BASE, GPIO_PIN_5, GPIO_PIN_5); } static uint8_t MRAM_SPI_Transfer(uint8_t data) { uint32_t rxData; SSIDataPut(SSI2_BASE, data); while(SSIBusy(SSI2_BASE)); SSIDataGet(SSI2_BASE, &rxData); return (uint8_t)(rxData & 0xFF); } void MRAM_WriteEnable(void) { MRAM_CS_Low(); MRAM_SPI_Transfer(MRAM_CMD_WREN); MRAM_CS_High(); }这里有个细节:CS 的控制方式。TM4C1294 的 SSI 外设可以硬件自动控制 FSS 引脚,但我在实际项目里更倾向于软件手动控制 CS。原因是硬件 CS 的时序是固定的,有时候在命令和地址之间需要插入额外延时,硬件 CS 做不到。软件控制虽然多几行代码,但灵活得多,尤其是调试阶段,你可以用示波器清楚地看到每一帧的边界。
MRAM_SPI_Transfer 这个函数是核心,它完成一个字节的收发。注意 SSIDataPut 之后要等 SSIBusy 清零,确保数据发完,然后再读接收 FIFO。如果忘了等 Busy,读出来的可能是上一次的残留数据。这个函数在 20MHz 下执行一次大概几微秒,512KB 全读一遍大概一两秒,对于工业应用完全够用。
接下来是读数据函数。MRAM 的读命令格式是:发 0x03,然后发 3 字节地址(实际只用低 19 位),然后连续读数据。地址是高位在前,MSB first。
void MRAM_Read(uint32_t addr, uint8_t *buf, uint32_t len) { uint32_t i; MRAM_CS_Low(); MRAM_SPI_Transfer(MRAM_CMD_READ); MRAM_SPI_Transfer((addr >> 16) & 0xFF); MRAM_SPI_Transfer((addr >> 8) & 0xFF); MRAM_SPI_Transfer(addr & 0xFF); for(i = 0; i < len; i++) { buf[i] = MRAM_SPI_Transfer(0xFF); } MRAM_CS_High(); }读操作不需要写使能,直接发命令就行。注意发地址的时候,虽然 MRAM 只有 19 位地址,但协议要求发 3 个字节也就是 24 位,高 5 位会被忽略。我习惯把地址按 24 位处理,这样代码通用性好,以后换更大容量的 MRAM 也不用改。
写数据函数稍微复杂一点,因为要处理写入周期。
void MRAM_Write(uint32_t addr, const uint8_t *buf, uint32_t len) { uint32_t i; MRAM_WriteEnable(); MRAM_CS_Low(); MRAM_SPI_Transfer(MRAM_CMD_WRITE); MRAM_SPI_Transfer((addr >> 16) & 0xFF); MRAM_SPI_Transfer((addr >> 8) & 0xFF); MRAM_SPI_Transfer(addr & 0xFF); for(i = 0; i < len; i++) { MRAM_SPI_Transfer(buf[i]); } MRAM_CS_High(); // 等待写入完成 MRAM_WaitWriteComplete(); } static void MRAM_WaitWriteComplete(void) { uint8_t status; do { MRAM_CS_Low(); MRAM_SPI_Transfer(MRAM_CMD_RDSR); status = MRAM_SPI_Transfer(0xFF); MRAM_CS_High(); } while(status & 0x01); // WIP 位为1表示正在写入 }MRAM_WaitWriteComplete 这个函数很关键。状态寄存器的 bit0 是 WIP(Write In Progress)位,写入过程中为 1,完成后为 0。轮询这个位比固定延时更可靠,因为写入时间会随温度、电压变化。我实测在常温下,单字节写入大概 50us 到 100us 完成,批量写入时每个字节之间也要等,不能连续猛发。
注意:MRAM 的写入是逐字节进行的,即使你一次发了 100 个字节的数据,芯片内部也是一个字节一个字节地翻转。所以批量写入时,总时间大约是 单字节时间 × 字节数。如果你要写 1KB 数据,按 100us 每字节算,大概要 100ms,这个时间在实时性要求高的场景要提前规划好。
3.3 数据存储结构的设计
驱动通了之后,就要考虑数据怎么组织。工业应用里,MRAM 通常存三类东西:系统参数、运行日志、掉电快照。我一般把 512KB 的空间划分成几个区域,每个区域有固定的起始地址和大小。
| 区域名称 | 起始地址 | 大小 | 用途 |
|---|---|---|---|
| 系统参数区 | 0x00000 | 4KB | 设备ID、校准系数、配置项 |
| 运行日志区 | 0x01000 | 256KB | 循环记录运行数据、故障事件 |
| 掉电快照区 | 0x41000 | 4KB | 掉电瞬间的关键状态 |
| 预留区 | 0x42000 | 248KB | 后续扩展 |
系统参数区用结构体方式存储,每个参数有固定的偏移。我习惯在结构体开头加一个魔数和一个 CRC 校验,上电时先读出来校验,如果 CRC 不对就加载默认值。这样即使 MRAM 因为某种极端原因数据出错,设备也能正常启动。
运行日志区用循环队列的方式管理。维护一个写指针,每次写一条日志就往后移,写到区域末尾就绕回开头。同时维护一个序号,用来判断哪些是最新数据。这种设计的好处是写入均匀,不会因为反复写同一个地址导致局部磨损——虽然 MRAM 寿命近乎无限,但均匀写入总是好习惯。
掉电快照区是配合掉电检测电路用的。当检测到电源电压下降到阈值时,MCU 进入中断,把关键变量快速写入 MRAM。MRAM 的写入速度是纳秒级,比 Flash 快几个数量级,这个场景下优势特别明显。我做过测试,从掉电中断触发到写完 256 字节数据,总共不到 1ms,而同样的数据写 Flash 至少要几十毫秒,根本来不及。
3.4 完整读写流程的实操记录
我把整个流程串一遍,从设备上电到数据读写,每一步都对应实际代码。
第一步,上电初始化。系统启动后,先初始化时钟、GPIO、SSI,然后调用 MRAM_SPI_Init()。初始化完成后,读一次 MRAM 的设备 ID 或者状态寄存器,确认通信正常。我一般会读地址 0x00000 的前几个字节,看看是不是之前写入的魔数。
第二步,参数加载。从系统参数区读取配置结构体,校验 CRC。如果校验通过,把参数加载到 RAM 里的全局变量;如果失败,加载默认参数并写回 MRAM。
第三步,正常运行。主循环里定期采集数据,比如每秒一次,把数据打包成日志条目写入运行日志区。写入时先更新写指针,再写数据,最后更新序号。注意写指针和序号的更新顺序,我习惯先写数据再更新指针,这样即使写入过程中掉电,最多丢一条日志,不会破坏整个队列结构。
第四步,掉电保护。掉电中断触发后,立即把当前的关键状态写入掉电快照区。这里要注意,中断里不要做太复杂的操作,直接调用 MRAM_Write 写一小块数据就行。写完之后可以进入低功耗模式或者等待电源彻底掉下去。
第五步,上电恢复。下次上电时,先读掉电快照区,看看有没有有效的快照数据。如果有,说明上次是异常掉电,可以根据快照恢复状态;如果没有,说明上次是正常关机,按正常流程启动。
这套流程我在好几个项目里用过,稳定性很好。唯一需要注意的是写入频率。虽然 MRAM 寿命长,但也没必要每秒写几百次,根据实际需求定一个合理的记录周期就行。我一般设 1 秒到 10 秒一次,既不会丢关键数据,也不会给 CPU 增加太多负担。
4. 常见问题排查与避坑经验
4.1 通信失败类问题速查
调试 SPI 器件,十有八九的问题都出在通信上。我整理了一张速查表,遇到问题按这个顺序排查,基本能覆盖 90% 的情况。
| 现象 | 可能原因 | 排查方法 | 解决方法 |
|---|---|---|---|
| 读出来全是 0xFF | MISO 没接好或 CS 没拉低 | 示波器看 MISO 波形 | 检查接线,确认 CS 时序 |
| 读出来全是 0x00 | MOSI 没数据或时钟没输出 | 示波器看 SCK 和 MOSI | 检查 SSI 配置和引脚复用 |
| 数据偶尔错位 | 时钟太快或时序余量不足 | 降低时钟频率测试 | 降到 10MHz 或加延时 |
| 写入不成功 | 忘了发 WREN | 读状态寄存器确认 | 写之前必须发 WREN |
| 写入后读出来是旧数据 | 没等写入完成 | 读 WIP 位 | 加 WaitWriteComplete |
| 上电后第一次读失败 | CS 上电时序问题 | 看 CS 上电波形 | 加 CS 上拉电阻 |
"读出来全是 0xFF"是最常见的。0xFF 意味着 MISO 一直是高电平,要么是 MRAM 根本没响应,要么是 CS 没拉低导致 MRAM 没被选中。我遇到过一次,查了半天发现是 CS 引脚配置成了普通 GPIO 输出但初始电平设成了高,而 MRAM 的 CS 是低有效,结果一直没选中。后来改成初始化时先拉低再拉高,确保一个完整的选中周期。
"写入不成功"这个坑我踩过不止一次。MRAM 的写操作必须严格遵循"WREN -> WRITE -> 等待完成"的流程。有一次我为了优化速度,把 WREN 和 WRITE 合并到一个 CS 周期里,结果数据写不进去。后来查手册才发现,WREN 必须在独立的 CS 周期里发送,芯片需要看到 CS 的上升沿才会锁存写使能状态。这个细节文档里写得很清楚,但容易忽略。
4.2 数据可靠性问题与对策
通信通了之后,下一个要关注的是数据可靠性。工业场景对数据完整性要求高,我一般会做几层保护。
第一层是 CRC 校验。每个数据块后面跟一个 CRC16 或者 CRC32,读取时校验。如果 CRC 不对,说明数据损坏,可以用备份数据恢复。MRAM 本身出错概率极低,但加上 CRC 心里踏实,而且能防止软件 bug 导致的数据错乱。
第二层是双备份。关键参数存两份,地址隔开。读取时如果主份 CRC 不对,自动切换到备份。两份都错的情况我还没遇到过,但设计上要留这个余地。
第三层是写入确认。每次写入后立即读回来对比,确认写入成功。这个操作会增加一点时间,但对于关键数据值得。我一般在参数写入时做确认,日志数据为了速度就不做了。
第四层是掉电检测。配合硬件掉电检测电路,在电源跌落时及时保存数据。这里要注意检测阈值的选择,太低可能来不及保存,太高可能误触发。我一般设在 2.9V 左右,留出足够的保存时间。
4.3 性能优化的几个实操技巧
如果项目对读写速度有要求,下面几个技巧可以试试。
用 DMA 搬运数据。TM4C1294 的 SSI 支持 DMA 触发,配置好 DMA 通道后,SPI 收发可以完全不占 CPU。我做过测试,用 DMA 读 512KB 数据,CPU 占用率不到 5%,而轮询方式要占 60% 以上。配置 DMA 稍微麻烦一点,但收益很大,尤其是需要同时处理其他任务的场景。
合并写入操作。前面说过 MRAM 写入是逐字节的,但如果你要写的数据在地址上是连续的,可以一次 CS 周期内写完,减少 CS 翻转开销。我实测连续写 256 字节比逐字节写(每次一个 CS 周期)快大约 30%。
合理设置 SPI 时钟。20MHz 是个平衡点,但如果你的板子质量好、走线短,可以试试 30MHz 甚至 40MHz。我有一块板子跑到 35MHz 很稳,另一块到 25MHz 就开始出错。这个要实际测试,用示波器看波形质量,眼图张开度好的话就可以往上提。
利用 MRAM 的随机写入特性。这是 MRAM 相比 Flash 最大的优势。Flash 写入前要擦除整个扇区,哪怕你只改一个字节。MRAM 不需要,直接改那个字节就行。所以在设计数据结构时,可以大胆使用"就地更新"的方式,不用像 Flash 那样搞复杂的日志结构或者磨损均衡。
4.4 那些文档里不会写的经验
最后分享几个我在实际项目中总结的经验,都是踩过坑才明白的。
关于 CS 的软件控制。前面提过我用软件控制 CS,这里补充一个细节:在 CS 拉低之后、发第一个时钟之前,最好插入一个几微秒的延时。有些批次的 MRAM 对 CS 建立时间比较敏感,虽然手册标称 5ns,但实际留点余量更稳。我一般加 1us 延时,对整体速度影响可以忽略。
关于写入后的读取。MRAM 写入完成后,理论上可以立即读取。但我遇到过一种情况:写入后马上读,读出来是旧数据,等几微秒再读就是新数据。后来分析可能是芯片内部的状态切换需要一点时间。所以我在关键写入后加一个 10us 的小延时,再读确认,就再没出现过这个问题。
关于温度的影响。MRAM 在极端温度下的写入时间会变长。我在 -40℃ 的低温箱里测试过,单字节写入时间从常温的 50us 增加到 150us 左右。所以如果你的设备要在宽温范围工作,WaitWriteComplete 的超时时间要设得足够长,我一般设 10ms 超时,确保任何温度下都能等到写入完成。
关于电源的缓慢上升。有些工业电源上电比较慢,电压从 0 升到 3.3V 可能要几十毫秒。在这期间,MCU 可能已经开始运行,但 MRAM 还没准备好。我的做法是在初始化 MRAM 之前加一个延时,等电源稳定后再通信。具体延时多久取决于你的电源设计,我一般等 100ms,保险一点。
关于 SPI 模式的选择。虽然模式 0 和模式 3 都能用,但我建议固定用一种模式,不要在不同项目里换来换去。因为模式切换容易搞混 CPOL 和 CPHA 的配置,而且示波器调试时习惯了一种波形,换模式后容易看错。我一直用模式 0,所有项目统一,省心。
这套 MRAM 加 TM4C1294 的方案,我从最早的评估板到现在量产项目,前后用了好几年,累计出货几万套,现场返修率极低。存储这块基本没再出过问题,算是把早年 Flash 掉数据的阴影彻底翻篇了。如果你也在做工业数据存储,不妨试试这个组合,前期多花点时间把驱动调稳,后面就是安心用。