ARTICLE DETAIL

资讯详情

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

STM32F767ZG 与 MR25H40CDF MRAM 工业级 SPI 驱动设计与掉电保护实战

STM32F767ZG 与 MR25H40CDF MRAM 工业级 SPI 驱动设计与掉电保护实战 1. 项目缘起与方案选型1.1 为什么要在工业场景里盯上 MRAM 这颗料做工业嵌入式的人都有一个共同的痛设备装在现场断电是家常便饭可有些数据——比如累计运行时长、标定系数、故障快照、掉电前的最后几个采样点——是绝对不能丢的。传统方案无非三条路EEPROM、NOR Flash、带后备电池的 SRAM。EEPROM 写入慢、擦写寿命有限频繁写参数容易提前报废NOR Flash 有擦除块限制写之前得整块擦掉电窗口期一崩就全没了带电池的 SRAM 倒是快可电池本身就是个定时炸弹工业现场高温高湿三五年后电池漏液腐蚀主板的事我见得太多了。MR25H40CDF 这颗芯片之所以值得单独拿出来聊是因为它用的是MRAM磁性随机存储器工艺。它的存储单元靠磁隧道结的磁化方向来记录 0 和 1不需要电荷保持所以断电后数据天然不丢。更关键的是它的写入机制跟 RAM 一样不需要擦除、没有擦写寿命的概念官方标称读写次数是 10^14 量级按每秒写一万次算够你写三百年。写入速度也是纳秒级SPI 接口跑满 40MHz 时钟毫无压力。我第一次在项目里用它是因为一个电力监测终端需要每 100ms 记录一次电参量快照掉电后要能回溯最近 10 分钟的数据。用 FRAM 成本太高用 Flash 又怕擦写寿命和掉电丢数据最后选了 MR25H40CDF4Mbit 容量512K 字节够存 6000 条 80 字节的记录环形缓冲区一绕完美解决。1.2 STM32F767ZG 作为主控的匹配度分析STM32F767ZG 是 ST 家 F7 系列里的高性能型号Cortex-M7 内核216MHz 主频带双精度浮点单元1MB Flash、512KB SRAM外设资源丰富。选它来配 MR25H40CDF主要看中三点第一SPI 外设足够多且灵活。F767 有 6 个 SPI/I2S 接口其中 SPI1 挂在 APB2 上最高时钟可以到 108MHz分频后给 MRAM 提供 40MHz 的 SCLK 绰绰有余。而且 F767 的 SPI 支持硬件 NSS 和软件 NSS 两种片选模式工业板子上如果片选线走线紧张可以用软件片选省一根线。第二内存够大方便做缓存和校验。512KB SRAM 意味着我可以在内存里开一个 4KB 的写缓存攒够一页再一次性写进 MRAM减少 SPI 事务次数同时留出空间做 CRC 校验和双备份。第三工业温度等级和长期供货。F767ZG 有 -40 到 85 和 -40 到 105 两个等级MR25H40CDF 也是工业级 -40 到 85两者搭配在大多数工业环境里都扛得住。1.3 SPI 接口选型的几个关键考量MR25H40CDF 支持 SPI 模式 0 和模式 3最高时钟 40MHz。这里有个坑很多人以为 SPI 模式随便设只要时钟极性对就行实际上 MRAM 对时序的要求比 Flash 严格。模式 0 是 CPOL0、CPHA0空闲时 SCLK 低电平数据在上升沿采样模式 3 是 CPOL1、CPHA1空闲时 SCLK 高电平数据在下降沿采样。两种模式都能用但如果你板上还有其他 SPI 从设备最好统一模式避免切换时出问题。我一般选模式 0因为大多数逻辑分析仪和示波器默认按模式 0 解码调试起来直观。另外MR25H40CDF 的 HOLD 和 WP 引脚在纯 SPI 模式下可以悬空或上拉但工业环境建议把 WP 上拉到 VCCHOLD 也上拉防止干扰导致误写。注意MR25H40CDF 的 VCC 范围是 2.7V 到 3.6V典型 3.3V。如果你板上是 5V 系统必须加电平转换否则会烧芯片。我见过有人直接 5V 怼上去芯片当场冒烟。2. 硬件设计与信号完整性要点2.1 原理图连接与引脚分配MR25H40CDF 是 8 引脚 SOIC 封装引脚定义如下引脚名称功能连接建议1CS片选低有效接 STM32 的 SPI_NSS 或任意 GPIO2SO数据输出MISO接 STM32 的 SPI_MISO3WP写保护低有效上拉到 VCC或接 GPIO 控制4VSS地接 GND5SI数据输入MOSI接 STM32 的 SPI_MOSI6SCK时钟接 STM32 的 SPI_SCK7HOLD保持低有效上拉到 VCC8VCC电源接 3.3V加 0.1uF 去耦我一般把 CS 接到 STM32 的 SPI1_NSSPA4但如果你用软件片选可以接到任意 GPIO比如 PA8。软件片选的好处是灵活坏处是每次传输前后要手动拉低拉高代码里多两行但时序更可控。2.2 PCB 布局布线的实战经验SPI 虽然号称低速总线但跑到 40MHz 时信号完整性问题就出来了。我踩过的坑包括SCK 走线太长导致时钟边沿变缓MRAM 采样出错MISO 和 MOSI 平行走线太长导致串扰读出来的数据随机翻转。几个硬性建议SCK 走线尽量短最好控制在 5cm 以内并且包地处理。如果板上空间允许SCK 和 GND 走成差分对更好。CS 线不要和 SCK 平行走否则 CS 跳变时会耦合到 SCK 上产生假时钟。去耦电容必须靠近 VCC 引脚0.1uF 陶瓷电容加 1uF 钽电容并联放在芯片背面或同一面 2mm 以内。WP 和 HOLD 不要悬空虽然内部有弱上拉但工业环境干扰大外部再加 10K 上拉更稳。我做过一个对比测试同一块板子SCK 走线 3cm 包地 vs 8cm 不包地前者 40MHz 读写 10 万次零错误后者 20MHz 就开始偶发校验失败。所以别小看这几厘米。2.3 电源与掉电保护设计MRAM 虽然写入不需要擦除但写入过程中如果电源掉电正在写的那一个字节可能处于不确定状态。所以工业设计里我一般会加一个掉电检测电路用 STM32 的 PVD可编程电压检测器监控 VDD当电压降到 2.9V 时触发中断在中断里把关键数据紧急写入 MRAM然后进入停机模式。PVD 的阈值可以设 2.9V因为 MR25H40CDF 的最低工作电压是 2.7V留 0.2V 余量。中断服务程序里只做一件事把内存里的紧急数据通过 SPI 写进 MRAM写完拉高 CS然后关中断。整个过程要在电源跌到 2.7V 之前完成按 40MHz SPI 算写 256 字节大概 60us加上中断响应和函数调用开销200us 内能搞定。电源上的储能电容要够大按 3.3V、100mA 电流、200us 时间算C I * t / ΔV 0.1 * 0.0002 / 0.6 ≈ 33uF实际用 100uF 更保险。3. 软件驱动与核心代码实现3.1 STM32CubeMX 配置 SPI 外设用 CubeMX 配置 SPI1 的步骤在 Pinout 视图里找到 SPI1Mode 选 Full-Duplex MasterHardware NSS Signal 选 Disable用软件片选。Parameter Settings 里Frame Format 选 MotorolaData Size 选 8 BitsFirst Bit 选 MSB First。Clock Polarity 选 LowClock Phase 选 1 Edge这样就是模式 0。Prescaler 选 4F767 的 SPI1 挂在 APB2 上时钟 108MHz分频后 27MHz。如果想跑 40MHzPrescaler 选 2得到 54MHz但 MRAM 最高 40MHz所以选 4 更稳。生成代码后HAL 会自动初始化 SPI1。这里有个细节CubeMX 生成的MX_SPI1_Init里hspi1.Init.BaudRatePrescaler设的是SPI_BAUDRATEPRESCALER_4对应 27MHz。如果你板上还有其他 SPI 设备注意不要冲突。3.2 MRAM 读写命令集与操作时序MR25H40CDF 的命令集很简洁核心就几条命令十六进制功能WREN0x06写使能WRDI0x04写禁止RDSR0x05读状态寄存器WRSR0x01写状态寄存器READ0x03读数据WRITE0x02写数据写数据前必须先发 WREN否则写操作被忽略。读数据不需要 WREN。状态寄存器的 bit0 是 WEL写使能锁存bit1 是 BP0bit2 是 BP1用于块保护。读时序拉低 CS发 0x03发 24 位地址MR25H40CDF 是 4Mbit需要 19 位地址但按 24 位发高 5 位忽略然后连续读数据每来一个时钟出一个字节。地址会自动递增到最高地址后回绕到 0。写时序拉低 CS发 0x06WREN拉高 CS。再拉低 CS发 0x02发 24 位地址然后连续写数据。写完后拉高 CS芯片内部开始写入周期典型 5ms最大 10ms。这期间状态寄存器的 WIP 位为 1可以轮询 RDSR 等待写入完成。注意WREN 和 WRITE 之间必须拉高 CS否则 WREN 不生效。我见过有人连续发 0x06 和 0x02 不拉 CS结果写不进去查了半天。3.3 底层驱动代码实现下面是我在实际项目里用的驱动代码基于 HAL 库去掉了多余封装直接操作寄存器效率更高。#include mram.h #define MRAM_CS_LOW() HAL_GPIO_WritePin(MRAM_CS_GPIO_Port, MRAM_CS_Pin, GPIO_PIN_RESET) #define MRAM_CS_HIGH() HAL_GPIO_WritePin(MRAM_CS_GPIO_Port, MRAM_CS_Pin, GPIO_PIN_SET) static SPI_HandleTypeDef *mram_spi; void MRAM_Init(SPI_HandleTypeDef *hspi) { mram_spi hspi; MRAM_CS_HIGH(); } static void MRAM_WriteEnable(void) { uint8_t cmd 0x06; MRAM_CS_LOW(); HAL_SPI_Transmit(mram_spi, cmd, 1, 100); MRAM_CS_HIGH(); } static uint8_t MRAM_ReadStatus(void) { uint8_t cmd 0x05; uint8_t status; MRAM_CS_LOW(); HAL_SPI_Transmit(mram_spi, cmd, 1, 100); HAL_SPI_Receive(mram_spi, status, 1, 100); MRAM_CS_HIGH(); return status; } static void MRAM_WaitWriteComplete(void) { uint32_t timeout 100000; while ((MRAM_ReadStatus() 0x01) timeout--); } void MRAM_Write(uint32_t addr, uint8_t *data, uint32_t len) { uint8_t cmd[4]; cmd[0] 0x02; cmd[1] (addr 16) 0xFF; cmd[2] (addr 8) 0xFF; cmd[3] addr 0xFF; MRAM_WriteEnable(); MRAM_CS_LOW(); HAL_SPI_Transmit(mram_spi, cmd, 4, 100); HAL_SPI_Transmit(mram_spi, data, len, 1000); MRAM_CS_HIGH(); MRAM_WaitWriteComplete(); } void MRAM_Read(uint32_t addr, uint8_t *data, uint32_t len) { uint8_t cmd[4]; cmd[0] 0x03; cmd[1] (addr 16) 0xFF; cmd[2] (addr 8) 0xFF; cmd[3] addr 0xFF; MRAM_CS_LOW(); HAL_SPI_Transmit(mram_spi, cmd, 4, 100); HAL_SPI_Receive(mram_spi, data, len, 1000); MRAM_CS_HIGH(); }这段代码里MRAM_WaitWriteComplete轮询状态寄存器的 WIP 位超时 10 万次循环按 27MHz SPI 算读一次状态大概 2us10 万次就是 200ms足够覆盖最大写入时间。实际测试中写 256 字节后 WIP 位在 3ms 内就清零了。3.4 数据校验与双备份策略工业数据不能只存一份我一般用双备份加 CRC 校验。具体做法把 512KB 空间分成两个 256KB 的区A 区和 B 区每个区里再分成若干条记录每条记录包含数据、CRC16 校验和、写入序号。写的时候先写 A 区再写 B 区读的时候两个区都读CRC 都对就取序号大的只有一个对就用对的那个两个都不对就报错。CRC16 用查表法实现速度快。下面是一个常用的 CRC16-CCITT 表static const uint16_t crc16_table[256] { 0x0000, 0x1021, 0x2042, 0x3063, /* ... 省略中间项 ... */ }; uint16_t CRC16_Calc(uint8_t *data, uint32_t len) { uint16_t crc 0xFFFF; while (len--) { crc (crc 8) ^ crc16_table[((crc 8) ^ *data) 0xFF]; } return crc; }写入序号用一个 32 位计数器存在 MRAM 的固定地址每次写记录前先读出来加一再写回去。这样即使掉电序号也不会乱。4. 性能实测与优化技巧4.1 读写速度实测数据我在 F767ZG 平台上SPI 时钟 27MHz做了批量读写测试操作数据量耗时等效速率连续读64KB19.2ms3.33MB/s连续写64KB21.5ms2.98MB/s单字节读1B2.1us-单字节写1B3.2ms-单字节写慢是因为每次都要 WREN、WRITE、等待 WIP开销主要在命令和等待上。所以批量写比单字节写效率高几十倍。我的做法是在内存里开一个 4KB 的环形缓冲区攒够 4KB 或超时 100ms 再一次性写入 MRAM。4.2 减少 SPI 事务次数的技巧每次 SPI 传输都有 CS 拉低拉高、命令字节、地址字节的开销。对于小数据量频繁写入的场景可以这样优化合并写操作把多个小记录拼成一个大包一次写入。比如 10 条 32 字节的记录拼成 320 字节一次 WRITE 搞定比 10 次单写快 10 倍以上。用页写模式MR25H40CDF 支持连续写地址自动递增没有页边界限制所以可以一次写任意长度只要不超过芯片容量。读操作加 DMAF767 的 SPI 支持 DMA读大块数据时用 DMA 搬运CPU 可以去干别的。我实测用 DMA 读 64KBCPU 占用从 100% 降到 5%。4.3 掉电保护与数据恢复实战掉电保护的核心是在电源跌到芯片最低工作电压之前把关键数据写完。我的方案是PVD 设 2.9V触发中断。中断里立刻拉低 CS发 WREN发 WRITE把内存里的紧急数据写进 MRAM 的固定地址。写完拉高 CS关全局中断进入死循环等待电源彻底掉电。上电后先读紧急数据区如果 CRC 正确说明掉电保护成功恢复数据如果 CRC 错误说明没写完用备份区数据。实测中电源从 3.3V 跌到 2.7V 的时间取决于板上电容和负载电流。我的板子负载 80mA电容 220uF跌电时间约 8ms足够写 1KB 数据。如果你板上负载更大电容要相应加大。提示PVD 中断里不要调用 HAL 库的阻塞式函数因为 HAL 里可能有超时等待会拖长时间。直接操作寄存器最快。5. 常见问题与排查实录5.1 读写失败问题速查表现象可能原因排查方法解决方案读全 0xFFCS 没拉低、SPI 模式错、芯片没供电示波器看 CS 和 SCK检查片选和模式读全 0x00MISO 没接、芯片损坏万用表测 MISO 通断重新焊接或换芯片写不进去没发 WREN、WP 拉低、写保护位设置读状态寄存器发 WRENWP 上拉数据随机翻转SCK 太快、走线太长、干扰降速到 10MHz 测试优化布线加屏蔽掉电丢数据电容不够、PVD 阈值太低测掉电时间加大电容提高阈值5.2 我踩过的三个坑第一个坑WREN 和 WRITE 之间拉高 CS 的时间太短。有一次我写完 WREN 立刻拉高 CS然后马上拉低发 WRITE结果写不进去。后来看手册WREN 后 CS 高电平要保持至少 10ns实际上我拉高后立刻拉低保持时间可能只有几纳秒。解决办法是在 WREN 后加一个__NOP()或微秒级延时。第二个坑SPI 时钟相位设错。我一开始用模式 0但 CubeMX 里 Clock Phase 选成了 2 Edge结果读出来的数据整体偏移一位。后来改成 1 Edge 就对了。模式 0 对应 CPOL0、CPHA0CubeMX 里就是 Clock Polarity Low、Clock Phase 1 Edge。第三个坑MRAM 写入后立刻读读到旧数据。这是因为写入周期还没完成WIP 位还是 1。我一开始没加MRAM_WaitWriteComplete写完立刻读读出来是旧值。加上等待后问题解决。所以写完必须等 WIP 清零这是硬性要求。5.3 工业现场的干扰对策工业现场有变频器、继电器、接触器电磁干扰很强。我遇到过 MRAM 数据偶尔翻转的情况后来做了以下改进SPI 线加磁珠和 TVS 管抑制高频干扰。MRAM 的 VCC 加 LC 滤波L 用 10uHC 用 10uF 加 0.1uF。软件上每写一条记录都算 CRC读的时候校验错了就重读或从备份恢复。关键数据存三份三取二表决。这些措施加上后连续运行半年零数据丢失。6. 项目扩展与个人体会6.1 从单芯片到多芯片级联如果 4Mbit 不够用可以级联多片 MR25H40CDF。SPI 没有地址线级联靠片选每片一个 CSSTM32 用 GPIO 控制。软件上维护一个地址映射表把逻辑地址映射到物理芯片和片内地址。我做过 4 片级联总共 2MB用 4 个 GPIO 片选读写时先算逻辑地址落在哪片再拉对应 CS。级联的注意点不同芯片的 CS 不能同时拉低否则数据冲突。另外SPI 时钟要按最慢的芯片设如果混用不同型号取最低速。6.2 文件系统与数据管理如果数据量大、需要按文件名管理可以在 MRAM 上跑一个轻量级文件系统比如 LittleFS 或 SPIFFS。这两个都是为嵌入式 Flash 设计的但 MRAM 没有擦除块限制用起来更简单。我移植过 LittleFS 到 MRAM把 block size 设成 512 字节读写性能比 Flash 好很多因为不需要擦除。不过文件系统有开销如果只是存固定格式的记录直接裸写更高效。我的选择是数据量小于 100KB 用裸写加环形缓冲区大于 100KB 用 LittleFS。6.3 个人实操体会这个项目做下来我最大的体会是MRAM 不是万能药但它在特定场景下确实省心。如果你的应用需要频繁写小数据、掉电不能丢、又不想加电池MRAM 是首选。但它的成本比 Flash 高容量比 Flash 小所以不适合存大块日志或固件。另外SPI 虽然简单但高速下的信号完整性不能忽视。我建议第一次画板时SCK 和 CS 之间留够间距最好包地。如果板子已经画好了发现高速下不稳定先降速到 10MHz 测试确认是信号问题还是代码问题再针对性优化。最后分享一个小技巧MRAM 的 WP 引脚可以接 STM32 的 GPIO平时拉高需要写保护时拉低。这样可以在固件升级或调试时临时保护数据比硬件上拉更灵活。我在一个项目里就这么干的升级固件时把 WP 拉低防止误写标定参数升级完再拉高很实用。
返回列表