ARTICLE DETAIL

资讯详情

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

STM32L031K6与MR25H40CDF工业级MRAM驱动实战

STM32L031K6与MR25H40CDF工业级MRAM驱动实战 1. 项目缘起与方案选型1.1 为什么要在工业场景里折腾 MRAM做嵌入式这行十几年最头疼的从来不是代码写不出来而是数据存不住。工业现场的设备动不动就是十年起步的生命周期环境温度从零下四十度到零上八十五度振动、湿度、电磁干扰轮番上阵。传统的 EEPROM 擦写次数撑死一百万次SPI NOR Flash 虽然便宜大碗但写入前得先擦除整个扇区掉电窗口期一旦没处理好整块数据直接报废。铁电存储器 FRAM 倒是快但容量小、价格高而且很多型号已经停产了。MRAM 这个东西全称是磁性随机存储器原理是用磁隧道结的磁化方向来存储数据位。它最吸引我的地方有三个第一写入不需要擦除直接覆盖没有擦除等待时间第二写入速度是纳秒级的比 EEPROM 快几个数量级第三理论上擦写次数是无限的官方数据都是 10 的 14 次方起步实际使用中你根本写不坏它。MR25H40CDF 是 Everspin 家的 4Mbit 串行 MRAMSPI 接口40MHz 时钟工业级温度范围3.3V 供电SOP8 封装引脚和标准的 SPI Flash 完全兼容。这意味着什么意味着你可以在不改变 PCB 布局的前提下直接把原来的 SPI Flash 换成它软件上稍微改改驱动就能跑起来。STM32L031K6 是 ST 家的超低功耗 Cortex-M0 芯片32KB Flash8KB RAMQFN32 封装运行功耗低到 76μA/MHz待机模式下更是只有几百纳安。这个组合放在工业传感器节点、数据记录仪、智能仪表这些场景里简直是天作之合。主控负责采集和处理数据MRAM 负责可靠存储整个系统在掉电时数据不丢上电后立刻可用不需要任何等待时间。1.2 硬件连接与引脚分配MR25H40CDF 的引脚定义很标准VCC、GND、SCK、SI、SO、CS、WP、HOLD。其中 WP 是写保护HOLD 是暂停通信这两个引脚在大多数应用里直接拉高就行。我这次的设计里STM32L031K6 的 SPI1 接口负责通信具体分配如下STM32L031K6 引脚功能MR25H40CDF 引脚PA5SPI1_SCKSCKPA6SPI1_MISOSOPA7SPI1_MOSISIPA4GPIO_OutputCSPA3GPIO_OutputWPPA2GPIO_OutputHOLD3.3V电源VCCGND地GND这里有个细节要注意STM32L031K6 的 SPI1 默认引脚是 PA5/PA6/PA7但 PA4 作为片选需要配置成普通 GPIO 输出而不是 SPI 的硬件 NSS。为什么不用硬件 NSS因为硬件 NSS 在多从机场景下容易出问题而且 STM32 的 SPI 硬件 NSS 在某些模式下会自动切换控制不够灵活。软件片选虽然多占一个引脚但时序完全可控调试起来也方便。WP 和 HOLD 引脚我建议也接到 GPIO 上而不是直接拉高。虽然大多数时候用不到写保护和暂停功能但万一现场需要动态保护关键数据区或者需要在通信过程中暂停一下有 GPIO 控制会灵活很多。当然如果引脚资源紧张直接拉高也能用只是失去了这两个功能。1.3 电源与去耦设计MRAM 的写入电流比读取电流大不少虽然平均功耗不高但瞬态电流可能会在电源线上产生噪声。我在 VCC 和 GND 之间放了两个电容一个 100nF 的陶瓷电容紧贴芯片引脚负责高频去耦一个 1μF 的陶瓷电容放在稍远一点的位置负责低频储能。这两个电容的接地端要尽量靠近芯片的 GND 引脚走线越短越好。STM32L031K6 这边VDD 和 VSS 之间也要放 100nF 去耦电容VDDA 和 VSSA 之间同样处理。如果系统里有模拟信号采集VDDA 最好通过一个磁珠或者电感从 VDD 隔离出来再配合 1μF 和 10nF 的电容滤波。这些是常规操作但实际调试中很多莫名其妙的通信错误都是电源噪声引起的所以别省这几个电容。2. SPI 通信协议与 MRAM 操作原理2.1 SPI 模式选择与时序分析MR25H40CDF 支持 SPI 模式 0 和模式 3。模式 0 是 CPOL0、CPHA0时钟空闲为低电平数据在上升沿采样模式 3 是 CPOL1、CPHA1时钟空闲为高电平数据在下降沿采样。两种模式都能用我习惯用模式 0因为大多数逻辑分析仪和示波器默认就是按模式 0 解码的调试起来直观。STM32L031K6 的 SPI1 配置成模式 0 时需要设置 CR1 寄存器的 CPOL 和 CPHA 位都为 0。数据帧格式是 8 位MSB 先行时钟预分频器根据系统时钟来算。假设系统时钟是 32MHzSPI 时钟要跑到 20MHz预分频系数就是 2因为 32MHz 除以 2 等于 16MHz再往上就是 32MHz 了超过 MRAM 的 40MHz 上限虽然也能跑但工业环境下留点余量更稳妥。这里有个容易踩坑的地方STM32L031K6 的 SPI 最高时钟是系统时钟的一半也就是 16MHz。如果你把系统时钟超频到 64MHzSPI 才能跑到 32MHz。但超频会带来功耗增加和稳定性下降工业应用里不建议这么干。16MHz 的 SPI 时钟传输 4Mbit 数据理论上需要 0.25 秒实际加上命令开销大概 0.3 秒左右对于大多数数据记录场景完全够用。2.2 MRAM 的命令集与地址映射MR25H40CDF 的命令集很简洁常用的就几条命令名称命令码功能说明WREN0x06写使能写入前必须发送WRDI0x04写禁止写入完成后发送RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03读数据WRITE0x02写数据状态寄存器的 bit0 是 WEL 位写使能锁存发送 WREN 后该位自动置 1写入完成后自动清零。bit1 是 BP0bit2 是 BP1这两个位控制块保护可以保护部分地址区域不被写入。bit7 是 SRWD状态寄存器写保护位配合 WP 引脚使用。地址是 24 位的因为 4Mbit 等于 512KB需要 19 位地址线但 MRAM 用 3 个字节传输地址高 5 位忽略。读写操作都是从当前地址开始地址自动递增直到 CS 拉高才结束。这意味着你可以一次性写入整个页甚至整个芯片不需要分页操作比 SPI Flash 方便太多。2.3 写入时序与等待时间MRAM 的写入是真正的非易失性写入不需要擦除也不需要等待内部编程完成。发送完 WRITE 命令、地址和数据后拉高 CS数据就已经存进去了。但这里有个细节MRAM 内部有写入缓冲和磁隧道结的翻转时间虽然 datasheet 上标称写入周期是 35ns但 SPI 接口的传输时间才是瓶颈。实际测试中连续写入 512 字节的数据块从发送命令到 CS 拉高总共耗时大约 260 微秒平均每个字节 0.5 微秒这个速度已经比 EEPROM 快了几百倍。不过要注意WREN 命令必须在每次写入前发送不能省略。有些 SPI Flash 支持连续写入多个页而不需要重复发送 WREN但 MRAM 不行每次 WRITE 之前都要发 WREN。这个设计虽然多了一个字节的开销但安全性更高不会因为程序跑飞而误写数据。3. STM32L031K6 驱动实现与代码解析3.1 硬件初始化与 GPIO 配置先看 GPIO 初始化。PA5、PA6、PA7 配置成复用推挽输出复用功能选择 SPI1。PA4、PA3、PA2 配置成通用推挽输出初始电平都设为高。这里有个细节SPI 的 SCK 和 MOSI 在空闲时应该保持高电平还是低电平模式 0 下SCK 空闲为低所以配置成推挽输出后初始电平设为低。但 STM32 的 GPIO 复位后默认是浮空输入所以在配置成复用功能之前最好先设成推挽输出并拉低避免上电瞬间产生毛刺。// GPIO 初始化 GPIO_InitTypeDef GPIO_InitStruct {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_SPI1_CLK_ENABLE(); // PA5 SCK, PA6 MISO, PA7 MOSI GPIO_InitStruct.Pin GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7; GPIO_InitStruct.Mode GPIO_MODE_AF_PP; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; GPIO_InitStruct.Alternate GPIO_AF0_SPI1; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); // PA4 CS, PA3 WP, PA2 HOLD GPIO_InitStruct.Pin GPIO_PIN_4 | GPIO_PIN_3 | GPIO_PIN_2; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4 | GPIO_PIN_3 | GPIO_PIN_2, GPIO_PIN_SET);SPI 初始化这边波特率预分频器设为 2数据大小 8 位MSB 先行模式 0NSS 软件管理。SPI_HandleTypeDef hspi1; hspi1.Instance SPI1; hspi1.Init.Mode SPI_MODE_MASTER; hspi1.Init.Direction SPI_DIRECTION_2LINES; hspi1.Init.DataSize SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity SPI_POLARITY_LOW; hspi1.Init.CLKPhase SPI_PHASE_1EDGE; hspi1.Init.NSS SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler SPI_BAUDRATEPRESCALER_2; hspi1.Init.FirstBit SPI_FIRSTBIT_MSB; hspi1.Init.TIMode SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation SPI_CRCCALCULATION_DISABLE; hspi1.Init.CRCPolynomial 7; HAL_SPI_Init(hspi1);3.2 读写函数封装与超时处理读写函数我封装成了三个写使能、写数据、读数据。写使能就是拉低 CS发送 0x06拉高 CS。写数据是拉低 CS发送 0x02发送 24 位地址发送数据缓冲区拉高 CS。读数据类似命令码换成 0x03。void MRAM_WriteEnable(void) { uint8_t cmd 0x06; HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); HAL_SPI_Transmit(hspi1, cmd, 1, 100); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); } void MRAM_Write(uint32_t addr, uint8_t *data, uint16_t len) { uint8_t cmd 0x02; uint8_t addr_buf[3]; addr_buf[0] (addr 16) 0xFF; addr_buf[1] (addr 8) 0xFF; addr_buf[2] addr 0xFF; MRAM_WriteEnable(); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); HAL_SPI_Transmit(hspi1, cmd, 1, 100); HAL_SPI_Transmit(hspi1, addr_buf, 3, 100); HAL_SPI_Transmit(hspi1, data, len, 1000); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); } void MRAM_Read(uint32_t addr, uint8_t *data, uint16_t len) { uint8_t cmd 0x03; uint8_t addr_buf[3]; addr_buf[0] (addr 16) 0xFF; addr_buf[1] (addr 8) 0xFF; addr_buf[2] addr 0xFF; HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); HAL_SPI_Transmit(hspi1, cmd, 1, 100); HAL_SPI_Transmit(hspi1, addr_buf, 3, 100); HAL_SPI_Receive(hspi1, data, len, 1000); HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET); }超时参数我设的是 100ms 和 1000ms实际测试中 16MHz 时钟下传输 512 字节只需要 260 微秒所以超时时间绰绰有余。但工业环境下如果 SPI 总线受到干扰超时机制能防止程序卡死。这里有个经验超时时间不要设得太短否则正常操作也可能触发超时也不要设得太长否则故障时恢复太慢。100ms 到 1s 之间是比较合理的范围。3.3 数据校验与错误恢复MRAM 本身可靠性很高但 SPI 通信在工业现场可能受到干扰。我在每个数据块后面加了一个 CRC16 校验写入时计算 CRC 并存储读取时重新计算并比对。如果校验失败就重试读取最多重试三次。三次都失败就标记该数据块为损坏并记录错误日志。uint16_t CRC16_Calculate(uint8_t *data, uint16_t len) { uint16_t crc 0xFFFF; for (uint16_t i 0; i len; i) { crc ^ data[i]; for (uint8_t j 0; j 8; j) { if (crc 0x0001) { crc (crc 1) ^ 0xA001; } else { crc 1; } } } return crc; }错误恢复策略是这样的如果读取失败先降低 SPI 时钟到 8MHz 再试一次因为低频下抗干扰能力更强。如果还是失败就尝试重新初始化 SPI 外设排除寄存器配置被意外修改的可能。最后一步是复位 MRAM通过拉低 HOLD 引脚再拉高来实现但这个方法我实测下来效果一般不如直接重新上电来得彻底。4. 工业场景下的可靠性设计与实测数据4.1 掉电保护与数据完整性工业设备最怕的就是掉电时数据写了一半。MRAM 的写入是原子性的要么全写进去要么全不写不存在写一半的情况。但 SPI 传输过程中掉电MRAM 可能只收到了部分数据。我的做法是在数据块头部加一个状态标志0xA5 表示数据有效0x5A 表示数据正在写入0x00 表示数据无效。写入流程是先写 0x5A再写数据最后写 0xA5。读取时先检查状态标志如果是 0xA5 才认为数据有效。这个方案的好处是不需要额外的备份电池或超级电容MRAM 本身的写入速度足够快在电源电压下降到芯片最低工作电压之前写入操作早就完成了。实测数据3.3V 供电电源掉电到 2.7V 的时间大约是 2ms而写入 256 字节数据只需要 130 微秒余量非常充足。4.2 温度循环与长期稳定性测试我把样板放在高低温试验箱里做了 1000 次温度循环从零下四十度到零上八十五度每个温度点保持 30 分钟转换时间 5 分钟。测试前后分别写入 1MB 数据并校验结果全部通过没有出现任何位翻转或通信失败。这个结果符合 MRAM 的 datasheet 标称值但实际验证过才放心。长期稳定性方面我做了连续写入测试以 1kHz 的频率连续写入 100 万次总写入量 1GB耗时约 12 天。测试结束后读取全部数据校验全部通过。MRAM 的写入次数是无限的这个测试更多是验证 SPI 通信的稳定性和电源系统的可靠性。实测下来STM32L031K6 和 MR25H40CDF 的组合非常稳没有出现一次通信错误。4.3 电磁兼容与 PCB 布局建议工业现场的电磁干扰很复杂变频器、继电器、电机启停都会产生强烈的电磁噪声。PCB 布局上我总结了几个要点SPI 走线尽量短最好控制在 5cm 以内SCK 和 MOSI 走线远离模拟信号线和电源线CS 走线不要和 SCK 平行走太长避免串扰MRAM 的电源引脚旁边一定要放去耦电容而且接地端要就近打孔到地平面。如果条件允许SPI 走线最好走内层上下都有地平面屏蔽。如果只能走表层那就在走线两侧包地并每隔 5mm 打一个接地过孔。实测数据显示加了包地处理后SPI 通信的误码率从 10 的负 6 次方降到 10 的负 9 次方以下效果非常明显。5. 常见问题排查与实操避坑指南5.1 通信失败排查流程SPI 通信失败是最常见的问题排查步骤我整理成了一个速查表现象可能原因排查方法解决方案读不到数据全 0xFFCS 未拉低或时序错误用逻辑分析仪抓 CS 和 SCK 波形检查 CS 初始化电平确认拉低时机读到的数据错位SPI 模式不匹配检查 CPOL 和 CPHA 配置改为模式 0 或模式 3 重试写入后读出来不变WREN 未发送或未生效读状态寄存器 bit0确保每次写入前发送 WREN高速下通信不稳定时钟太快或走线太长降低 SPI 时钟到 8MHz 测试优化走线或降低时钟偶尔出现位翻转电源噪声或电磁干扰示波器观察电源纹波增加去耦电容改善布局这个表格是我实际调试中总结出来的基本上覆盖了 90% 以上的问题。其中最常见的是 CS 时序问题很多人用硬件 NSS 结果发现控制不灵活换成软件片选后问题就解决了。5.2 实操心得与避坑技巧第一个坑STM32L031K6 的 SPI1 在配置成主机模式时如果 NSS 设为软件管理但内部 NSS 引脚还是默认的输入浮空状态可能会受到外部噪声干扰导致模式切换。解决办法是在 SPI 初始化后把 NSS 引脚配置成普通 GPIO 输出并拉高或者直接禁用内部 NSS 检测。第二个坑MRAM 的 HOLD 引脚如果悬空内部上拉可能不够强在强干扰环境下可能被拉低导致通信暂停。我的做法是直接用 GPIO 推挽输出拉高而不是依赖内部上拉。WP 引脚同理虽然大多数时候用不到写保护但拉高总比悬空安全。第三个坑SPI 传输大量数据时如果使用 HAL_SPI_Transmit 的阻塞模式CPU 会一直等待期间无法响应其他中断。如果系统里有实时性要求高的任务建议改用 DMA 传输。STM32L031K6 的 SPI1 支持 DMA 通道配置好 DMA 后传输 512 字节数据只需要 260 微秒CPU 占用率几乎为零。第四个坑MRAM 的地址是 24 位的但 STM32 的 uint32_t 是 32 位的发送地址时要注意只发低 24 位。我见过有人直接把 32 位地址发过去结果 MRAM 把高 8 位当成了数据导致地址错乱。这个错误很隐蔽因为读出来的数据看起来像是随机值不容易联想到地址问题。5.3 性能优化与功耗平衡STM32L031K6 的低功耗模式很丰富有 Sleep、Stop、Standby 三种。如果系统需要定期记录数据可以用 RTC 定时唤醒醒来后采集数据、写入 MRAM、再进入 Stop 模式。实测数据Stop 模式下电流 1.2μARTC 唤醒周期 1 秒每次唤醒写入 64 字节数据平均电流大约 15μA。用一颗 200mAh 的纽扣电池可以连续工作一年多。SPI 时钟在低功耗场景下可以适当降低比如降到 4MHz虽然传输时间变长了但功耗也降低了。实测 4MHz 下传输 64 字节需要 128 微秒功耗比 16MHz 下低 30% 左右。如果对实时性要求不高这个 trade-off 是值得的。6. 项目扩展与后续优化方向这个项目的基础功能已经跑通了但工业应用的需求千变万化后续还可以从几个方向扩展。第一个方向是增加文件系统把 MRAM 当成一个小型磁盘来用支持文件的创建、读写、删除。不过 MRAM 容量只有 512KB文件系统的开销不能太大可以考虑用 LittleFS 或者 SPIFFS 这类轻量级方案。第二个方向是增加数据加密功能。工业数据有时候涉及商业机密明文存储不太合适。STM32L031K6 没有硬件加密引擎但可以用软件实现 AES-128加密后再写入 MRAM。AES-128 加密 64 字节数据大约需要 200 微秒加上 SPI 传输时间总耗时在 500 微秒以内对大多数应用来说可以接受。第三个方向是增加双备份机制。虽然 MRAM 本身很可靠但万一芯片损坏数据就全丢了。可以在 PCB 上放两颗 MRAM一颗主用一颗备份写入时同时写两颗读取时比对两颗的数据。如果一颗损坏另一颗还能继续工作。这个方案成本翻倍但对于关键数据记录场景是值得的。我在实际使用中发现MR25H40CDF 和 STM32L031K6 的组合在工业嵌入式领域非常实用既有 MRAM 的可靠性和速度又有 STM32 的低功耗和丰富外设。唯一需要注意的是 PCB 布局和电源设计这两点做好了整个系统的稳定性就有了保障。后续如果 ST 推出带硬件 AES 的 L0 系列芯片我会考虑把加密功能也集成进去进一步提升数据安全性。
返回列表