動(dòng) MR25H40CDF MRAM:SPI 接口與掉電保護(hù)實(shí)戰(zhàn))
1. 項(xiàng)目緣起與方案選型1.1 為什么要在工業(yè)場(chǎng)景里折騰 MRAM做嵌入式這行十幾年存儲(chǔ)方案的選擇一直是個(gè)繞不開(kāi)的話題。早些年做工業(yè)數(shù)據(jù)采集器EEPROM 擦寫(xiě)壽命不夠用FRAM 容量又上不去NOR Flash 寫(xiě)入前還得先擦除整個(gè)扇區(qū)掉電保護(hù)邏輯寫(xiě)得人頭大。直到 MRAM磁性隨機(jī)存儲(chǔ)器這類器件逐漸成熟才算找到一種能兼顧速度、壽命和掉電安全性的方案。MR25H40CDF 是 Everspin 推出的一款 4Mbit 串行 MRAMSPI 接口40MHz 時(shí)鐘最讓人省心的是它沒(méi)有寫(xiě)入延遲——數(shù)據(jù)寫(xiě)進(jìn)去就落盤(pán)不需要像 Flash 那樣等待內(nèi)部編程周期。這一點(diǎn)在工業(yè)現(xiàn)場(chǎng)特別關(guān)鍵設(shè)備可能隨時(shí)斷電如果寫(xiě)入過(guò)程中掉電Flash 方案很容易丟數(shù)據(jù)甚至損壞扇區(qū)而 MRAM 的物理機(jī)制決定了它寫(xiě)入即持久。STM32F334R8 則是 ST 家的一款 Cortex-M4 內(nèi)核 MCU72MHz 主頻帶 FPU 和 HRTIM高分辨率定時(shí)器在數(shù)字電源、電機(jī)控制、工業(yè)傳感器采集這些場(chǎng)景里出鏡率很高。它片內(nèi) Flash 只有 64KBRAM 12KB跑復(fù)雜應(yīng)用時(shí)經(jīng)常捉襟見(jiàn)肘外掛一顆 MRAM 來(lái)存參數(shù)、日志、校準(zhǔn)數(shù)據(jù)是很自然的思路。這個(gè)組合的核心價(jià)值在于用 SPI 接口把非易失存儲(chǔ)擴(kuò)展到 MCU 外部同時(shí)保留 MRAM 的字節(jié)級(jí)隨機(jī)寫(xiě)入、近乎無(wú)限的擦寫(xiě)壽命和即時(shí)持久化特性。適合做工業(yè)儀表、數(shù)據(jù)記錄儀、電機(jī)驅(qū)動(dòng)器參數(shù)存儲(chǔ)、以及任何對(duì)掉電數(shù)據(jù)完整性有要求的嵌入式項(xiàng)目。1.2 SPI 接口選型背后的考量MR25H40CDF 只提供 SPI 接口沒(méi)有并口版本。為什么選 SPI 而不是 I2C 或者并口這里有幾個(gè)實(shí)際考量。SPI 是全雙工同步串行總線速率比 I2C 高一個(gè)數(shù)量級(jí)。MR25H40CDF 支持最高 40MHz 時(shí)鐘STM32F334R8 的 SPI1 掛在 APB2 上最高能跑到 36MHz72MHz 二分頻SPI2 掛在 APB1 上最高 18MHz。用 SPI1 的話理論傳輸速率能到 4.5MB/s讀 4Mbit512KB全部?jī)?nèi)容也就一百多毫秒。I2C 最快 400kHz 或者 1MHz差距明顯。并口雖然更快但引腳多、PCB 布線復(fù)雜在小型工業(yè)模塊里不劃算。SPI 只要四根線SCK、MOSI、MISO、CS加上電源和地六根線搞定PCB 走線清爽。還有一個(gè)容易被忽略的點(diǎn)MRAM 的 SPI 協(xié)議和普通 SPI Flash 高度兼容命令集類似READ、WRITE、WREN、RDSR 等但去掉了擦除相關(guān)的命令。這意味著如果你之前用 W25Q64 這類 Flash 寫(xiě)過(guò)代碼遷移到 MR25H40CDF 時(shí)上層邏輯改動(dòng)很小主要就是把擦除步驟刪掉寫(xiě)入前不需要 WREN 之外的額外準(zhǔn)備。注意MR25H40CDF 的寫(xiě)操作不需要先擦除但每次寫(xiě)入前仍然需要發(fā)送 WRENWrite Enable命令這一點(diǎn)和 Flash 一致。寫(xiě)完一個(gè)字節(jié)或一頁(yè)后WREN 自動(dòng)復(fù)位下次寫(xiě)入要重新發(fā)。1.3 硬件連接與引腳規(guī)劃STM32F334R8 有多個(gè) SPI 外設(shè)我一般優(yōu)先用 SPI1因?yàn)樗臅r(shí)鐘源是 APB2頻率更高。具體引腳分配如下STM32 引腳功能MR25H40CDF 引腳說(shuō)明PA5SPI1_SCKSCK時(shí)鐘建議串聯(lián) 22Ω 電阻PA6SPI1_MISOSO主入從出PA7SPI1_MOSISI主出從入PA4GPIO_OutputCS片選軟件控制3.3V電源VDD加 0.1uF 去耦電容GND地VSS靠近芯片放置CS 片選我用的是軟件控制沒(méi)有用 SPI 外設(shè)的硬件 NSS。原因很簡(jiǎn)單硬件 NSS 在多從機(jī)場(chǎng)景下容易出問(wèn)題而且 STM32 的硬件 NSS 在某些模式下會(huì)自己抖動(dòng)調(diào)試起來(lái)費(fèi)勁。軟件控制雖然多一行代碼但時(shí)序完全可控想什么時(shí)候拉低就什么時(shí)候拉低配合 MRAM 的高速寫(xiě)入反而更穩(wěn)。PCB 布線方面SPI 信號(hào)線盡量等長(zhǎng)SCK 作為時(shí)鐘線要遠(yuǎn)離模擬信號(hào)。如果板上還有 ADC 或者運(yùn)放SPI 走線最好包地處理。MRAM 芯片靠近 MCU 放置走線不超過(guò) 10cm超過(guò)這個(gè)距離就要考慮信號(hào)完整性問(wèn)題可能需要降低 SPI 時(shí)鐘或者加緩沖。2. MR25H40CDF 核心機(jī)制與操作要點(diǎn)2.1 MRAM 的物理原理與性能邊界MRAM 的全稱是 Magnetoresistive Random Access Memory磁性隨機(jī)存儲(chǔ)器。它的存儲(chǔ)單元是一個(gè)磁性隧道結(jié)MTJ由兩層鐵磁材料夾一層極薄的絕緣層構(gòu)成。一層是固定磁化方向另一層是自由磁化方向。當(dāng)兩層磁化方向平行時(shí)電阻低代表邏輯 0反平行時(shí)電阻高代表邏輯 1。寫(xiě)入時(shí)通過(guò)自旋極化電流改變自由層磁化方向讀取時(shí)測(cè)量隧道結(jié)電阻。這個(gè)機(jī)制帶來(lái)幾個(gè)關(guān)鍵特性。第一寫(xiě)入沒(méi)有延遲電流脈沖結(jié)束數(shù)據(jù)就穩(wěn)定了不需要像 Flash 那樣等待電荷注入浮柵。第二擦寫(xiě)壽命近乎無(wú)限Everspin 標(biāo)稱 10^14 次以上實(shí)際測(cè)試中基本不用擔(dān)心寫(xiě)壞。第三數(shù)據(jù)保持時(shí)間超過(guò) 20 年而且不需要供電維持。第四抗輻射、抗磁場(chǎng)干擾能力比 Flash 強(qiáng)在工業(yè)電磁環(huán)境里更可靠。但 MRAM 也不是沒(méi)有短板。容量密度比 NAND Flash 低成本高4Mbit 的芯片價(jià)格能買(mǎi)幾十兆的 Flash。所以它適合存關(guān)鍵數(shù)據(jù)不適合存大塊日志或者固件。另外MRAM 的寫(xiě)入電流相對(duì)較大高頻連續(xù)寫(xiě)入時(shí)功耗會(huì)比 Flash 高一些電池供電場(chǎng)景要算一下功耗預(yù)算。MR25H40CDF 的具體參數(shù)如下參數(shù)數(shù)值說(shuō)明容量4Mbit (512K x 8)字節(jié)尋址接口SPI模式 0 和模式 3 都支持最高時(shí)鐘40MHz讀操作寫(xiě)入周期無(wú)延遲字節(jié)/頁(yè)寫(xiě)入擦寫(xiě)壽命10^14幾乎無(wú)限數(shù)據(jù)保持20 年工業(yè)級(jí)工作電壓2.7V ~ 3.6V典型 3.3V工作溫度-40°C ~ 85°C工業(yè)級(jí)封裝DFN-85mm x 6mm2.2 SPI 通信協(xié)議與時(shí)序細(xì)節(jié)MR25H40CDF 的 SPI 協(xié)議和標(biāo)準(zhǔn) SPI Flash 非常接近支持模式 0CPOL0, CPHA0和模式 3CPOL1, CPHA1。我一般用模式 0因?yàn)?STM32 的 SPI 默認(rèn)配置就是模式 0省得改。命令集方面常用的有這幾個(gè)WREN (0x06)寫(xiě)使能寫(xiě)入前必須發(fā)WRDI (0x04)寫(xiě)禁止一般不用主動(dòng)發(fā)RDSR (0x05)讀狀態(tài)寄存器WRSR (0x01)寫(xiě)狀態(tài)寄存器READ (0x03)讀數(shù)據(jù)任意地址WRITE (0x02)寫(xiě)數(shù)據(jù)頁(yè)內(nèi)寫(xiě)入FAST_READ (0x0B)快速讀支持更高時(shí)鐘狀態(tài)寄存器只有一位有用WELWrite Enable Latchbit 1。寫(xiě)入前發(fā) WRENWEL 置 1寫(xiě)入完成后 WEL 自動(dòng)清 0。其他位是只讀或者保留位不用管。時(shí)序上要注意幾個(gè)點(diǎn)。CS 拉低到第一個(gè) SCK 上升沿之間要有至少 5ns 的建立時(shí)間STM32 的 GPIO 翻轉(zhuǎn)速度足夠快但如果你用 HAL 庫(kù)的HAL_GPIO_WritePin函數(shù)調(diào)用開(kāi)銷(xiāo)可能就有幾百納秒所以 CS 操作最好用寄存器直接寫(xiě)或者用__NOP()配合。寫(xiě)入時(shí)序是這樣的CS 拉低 → 發(fā) WREN (0x06) → CS 拉高 → CS 拉低 → 發(fā) WRITE (0x02) → 發(fā) 24 位地址MR25H40CDF 是 19 位地址但協(xié)議上發(fā) 24 位高 5 位忽略→ 發(fā)數(shù)據(jù)字節(jié) → CS 拉高。注意 WREN 和 WRITE 之間必須拉高 CS這是命令邊界。實(shí)操心得很多人第一次用 MRAM 會(huì)忘記 WREN 和 WRITE 之間要拉高 CS結(jié)果寫(xiě)不進(jìn)去。我當(dāng)年調(diào)這個(gè)的時(shí)候邏輯分析儀抓了半天發(fā)現(xiàn) WREN 命令被當(dāng)成 WRITE 的一部分了。記住每個(gè) SPI 命令都是獨(dú)立的 CS 周期。2.3 頁(yè)寫(xiě)入與地址邊界處理MR25H40CDF 支持頁(yè)寫(xiě)入一頁(yè) 256 字節(jié)。但和 Flash 不同的是MRAM 的頁(yè)寫(xiě)入沒(méi)有“頁(yè)對(duì)齊”的硬性要求你可以從任意地址開(kāi)始寫(xiě)任意長(zhǎng)度。不過(guò)如果寫(xiě)入跨越 256 字節(jié)邊界地址會(huì)回卷到頁(yè)首覆蓋之前的數(shù)據(jù)。這一點(diǎn)和 Flash 一樣是硬件行為不是 bug。舉個(gè)例子從地址 0x00FF 開(kāi)始寫(xiě) 10 個(gè)字節(jié)實(shí)際會(huì)寫(xiě)到 0x00FF 到 0x0008其中 0x0100 回卷到 0x0000。所以寫(xiě)跨頁(yè)數(shù)據(jù)時(shí)要么分兩次寫(xiě)要么確保起始地址和長(zhǎng)度不跨頁(yè)。我的做法是封裝一個(gè)MRAM_Write函數(shù)內(nèi)部自動(dòng)處理跨頁(yè)拆分void MRAM_Write(uint32_t addr, uint8_t *buf, uint32_t len) { while (len 0) { uint32_t page_remain 256 - (addr % 256); uint32_t write_len (len page_remain) ? len : page_remain; MRAM_WritePage(addr, buf, write_len); addr write_len; buf write_len; len - write_len; } }這樣上層調(diào)用就不用關(guān)心頁(yè)邊界了。MRAM_WritePage內(nèi)部就是標(biāo)準(zhǔn)的 WREN WRITE 流程。讀操作就簡(jiǎn)單多了READ 命令 (0x03) 可以從任意地址連續(xù)讀沒(méi)有頁(yè)限制地址自動(dòng)遞增讀到 0x7FFFF 后回卷到 0x00000。如果你要讀整個(gè)芯片直接發(fā)一個(gè) READ 命令然后連續(xù)讀 524288 個(gè)字節(jié)就行。3. STM32F334R8 端驅(qū)動(dòng)實(shí)現(xiàn)3.1 CubeMX 配置與 SPI 初始化用 STM32CubeMX 配置 SPI1 的步驟不復(fù)雜但有幾個(gè)參數(shù)容易設(shè)錯(cuò)。我一般這樣配Mode: Full-Duplex MasterData Size: 8 BitsClock Polarity: LowClock Phase: 1 EdgeNSS: SoftwareBaud Rate Prescaler: 2APB2 72MHz / 2 36MHzFirst Bit: MSBCRC: Disabled36MHz 是 SPI1 在 72MHz 主頻下的最高速率MR25H40CDF 支持 40MHz所以 36MHz 完全在規(guī)格內(nèi)。如果你發(fā)現(xiàn)通信不穩(wěn)定先把預(yù)分頻改成 4 或者 8降到 18MHz 或 9MHz 試試排除信號(hào)完整性問(wèn)題。GPIO 配置方面SCK、MOSI、MISO 設(shè)為 Alternate Function Push-PullVery High Speed。CS 設(shè)為 GPIO Output初始電平 High。這里有個(gè)細(xì)節(jié)CS 的 GPIO 速度也要設(shè)成 Very High否則拉低和拉高的邊沿不夠陡高速 SPI 下可能被從機(jī)誤判。初始化代碼用 HAL 庫(kù)生成后我一般會(huì)加一個(gè)MRAM_Init函數(shù)做兩件事拉高 CS然后讀一次狀態(tài)寄存器確認(rèn)通信正常。uint8_t MRAM_Init(void) { MRAM_CS_HIGH(); HAL_Delay(1); uint8_t status MRAM_ReadStatus(); if (status 0xFF || status 0x00) { return 1; // 通信失敗 } return 0; // 成功 }讀狀態(tài)寄存器返回 0xFF 通常意味著 MISO 一直高可能是芯片沒(méi)供電或者 CS 沒(méi)接對(duì)。返回 0x00 可能是 MISO 一直低檢查一下是不是短路了。3.2 底層讀寫(xiě)函數(shù)實(shí)現(xiàn)底層函數(shù)我分成四個(gè)MRAM_WriteEnable、MRAM_ReadStatus、MRAM_Read、MRAM_WritePage。每個(gè)函數(shù)都直接操作 SPI 和 GPIO不依賴 HAL 的 SPI 傳輸函數(shù)因?yàn)?HAL 的函數(shù)調(diào)用開(kāi)銷(xiāo)在高速場(chǎng)景下有點(diǎn)大。#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_WriteEnable(void) { MRAM_CS_LOW(); uint8_t cmd 0x06; HAL_SPI_Transmit(hspi1, cmd, 1, 10); MRAM_CS_HIGH(); } static uint8_t MRAM_ReadStatus(void) { uint8_t cmd 0x05; uint8_t status; MRAM_CS_LOW(); HAL_SPI_Transmit(hspi1, cmd, 1, 10); HAL_SPI_Receive(hspi1, status, 1, 10); MRAM_CS_HIGH(); return status; } void MRAM_Read(uint32_t addr, uint8_t *buf, 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(hspi1, cmd, 4, 10); HAL_SPI_Receive(hspi1, buf, len, 1000); MRAM_CS_HIGH(); } void MRAM_WritePage(uint32_t addr, uint8_t *buf, 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(hspi1, cmd, 4, 10); HAL_SPI_Transmit(hspi1, buf, len, 1000); MRAM_CS_HIGH(); }這里有個(gè)性能優(yōu)化的點(diǎn)HAL_SPI_Transmit和HAL_SPI_Receive在傳輸大量數(shù)據(jù)時(shí)內(nèi)部是逐字節(jié)輪詢的36MHz 下傳 512KB 大概要幾百毫秒。如果你追求極致速度可以用 DMA。SPI1 的 TX 和 RX 都支持 DMA配置好之后讀 512KB 數(shù)據(jù)能壓到幾十毫秒。不過(guò) DMA 配置復(fù)雜一些還要處理傳輸完成中斷看項(xiàng)目需求決定。注意MRAM 的寫(xiě)入速度受限于 SPI 時(shí)鐘和 MCU 的發(fā)送能力。36MHz 下理論寫(xiě)入速率是 4.5MB/s但實(shí)際因?yàn)?WREN 命令和 CS 翻轉(zhuǎn)的開(kāi)銷(xiāo)連續(xù)寫(xiě)入大概在 2-3MB/s。如果你要頻繁寫(xiě)大量數(shù)據(jù)算一下時(shí)間預(yù)算。3.3 數(shù)據(jù)存儲(chǔ)結(jié)構(gòu)設(shè)計(jì)外掛 MRAM 之后怎么組織數(shù)據(jù)是個(gè)需要提前想清楚的問(wèn)題。我一般把 512KB 的空間分成幾個(gè)區(qū)域區(qū)域起始地址大小用途配置區(qū)0x000004KB系統(tǒng)參數(shù)、校準(zhǔn)系數(shù)日志區(qū)0x01000256KB循環(huán)日志帶時(shí)間戳備份區(qū)0x41000252KB固件備份、大數(shù)據(jù)塊保留區(qū)0x7F0004KB魔術(shù)字、版本號(hào)、CRC配置區(qū)存結(jié)構(gòu)體每次修改后整體寫(xiě)入配合 CRC 校驗(yàn)。日志區(qū)用環(huán)形緩沖區(qū)寫(xiě)指針存在保留區(qū)每次寫(xiě)入后更新指針。備份區(qū)存不常變的數(shù)據(jù)比如出廠校準(zhǔn)表。結(jié)構(gòu)體定義要注意字節(jié)對(duì)齊。STM32 默認(rèn) 4 字節(jié)對(duì)齊如果結(jié)構(gòu)體里有uint8_t和uint32_t混用編譯器會(huì)插入填充字節(jié)。寫(xiě)入 MRAM 時(shí)是按字節(jié)流寫(xiě)的讀回來(lái)直接強(qiáng)轉(zhuǎn)指針可能因?yàn)閷?duì)齊問(wèn)題出錯(cuò)。我的做法是用__attribute__((packed))或者手動(dòng)序列化。typedef struct __attribute__((packed)) { uint32_t magic; uint16_t version; float calib_k; float calib_b; uint32_t crc; } Config_t;寫(xiě)入前算 CRC讀出來(lái)后校驗(yàn)不匹配就加載默認(rèn)值。這個(gè)套路在工業(yè)項(xiàng)目里很常見(jiàn)能有效防止數(shù)據(jù)損壞導(dǎo)致的異常行為。4. 實(shí)操驗(yàn)證與問(wèn)題排查4.1 讀寫(xiě)測(cè)試與性能實(shí)測(cè)驅(qū)動(dòng)寫(xiě)完之后第一步是跑一個(gè)全片讀寫(xiě)測(cè)試。我一般寫(xiě)一個(gè)測(cè)試函數(shù)先擦除...等等MRAM 不需要擦除。直接寫(xiě) 0x00 到 0xFF 的遞增模式然后讀回來(lái)對(duì)比。void MRAM_Test(void) { uint8_t wbuf[256], rbuf[256]; for (int i 0; i 256; i) wbuf[i] i; for (uint32_t addr 0; addr 0x80000; addr 256) { MRAM_WritePage(addr, wbuf, 256); MRAM_Read(addr, rbuf, 256); if (memcmp(wbuf, rbuf, 256) ! 0) { printf(Mismatch at 0x%05X\n, addr); return; } } printf(MRAM test passed\n); }實(shí)測(cè)下來(lái)36MHz SPI 時(shí)鐘全片 512KB 寫(xiě)入加讀取對(duì)比大概 1.2 秒完成。其中寫(xiě)入約 800ms讀取約 400ms。這個(gè)速度對(duì)于工業(yè)數(shù)據(jù)記錄完全夠用。如果你發(fā)現(xiàn)測(cè)試失敗先降低 SPI 時(shí)鐘到 9MHz 再試。如果低速能過(guò)高速不過(guò)基本是信號(hào)完整性問(wèn)題檢查一下 SCK 走線有沒(méi)有過(guò)長(zhǎng)、有沒(méi)有串?dāng)_。如果低速也失敗用邏輯分析儀抓 SPI 波形看命令和數(shù)據(jù)是否符合預(yù)期。4.2 常見(jiàn)問(wèn)題速查表現(xiàn)象可能原因排查方法解決措施讀回全 0xFFMISO 未連接或芯片未供電萬(wàn)用表測(cè) VDD 和 MISO 電壓檢查供電和焊接讀回全 0x00MISO 短路到地?cái)嚯姕y(cè) MISO 對(duì)地電阻修復(fù)短路寫(xiě)入后讀回不變WREN 未發(fā)或 CS 時(shí)序錯(cuò)誤邏輯分析儀抓 WREN 和 WRITE確保 WREN 和 WRITE 之間 CS 拉高跨頁(yè)寫(xiě)入數(shù)據(jù)錯(cuò)亂地址回卷檢查寫(xiě)入起始地址和長(zhǎng)度拆分跨頁(yè)寫(xiě)入高速下偶發(fā)錯(cuò)誤信號(hào)完整性降低 SPI 時(shí)鐘測(cè)試加串阻、縮短走線、包地?cái)?shù)據(jù)偶爾丟失電源波動(dòng)示波器看 VDD 紋波加去耦電容、穩(wěn)壓4.3 掉電保護(hù)與數(shù)據(jù)完整性MRAM 的最大賣(mài)點(diǎn)就是掉電安全但前提是寫(xiě)入操作已經(jīng)完成。如果 MCU 在發(fā) WRITE 命令的過(guò)程中掉電MRAM 可能只寫(xiě)了一半數(shù)據(jù)。雖然 MRAM 的寫(xiě)入是原子性的單個(gè)存儲(chǔ)單元要么寫(xiě)成功要么保持原值但多字節(jié)寫(xiě)入過(guò)程中掉電可能出現(xiàn)部分字節(jié)更新、部分字節(jié)未更新的情況。對(duì)于關(guān)鍵數(shù)據(jù)我的做法是雙備份加 CRC。比如配置區(qū)存兩份地址 0x00000 和 0x00800每份帶 CRC。讀取時(shí)先讀第一份CRC 通過(guò)就用不通過(guò)讀第二份都不通過(guò)加載默認(rèn)值。寫(xiě)入時(shí)先寫(xiě)第二份再寫(xiě)第一份確保任何時(shí)候至少有一份是完整的。日志區(qū)用環(huán)形緩沖區(qū)每條記錄帶序號(hào)和 CRC。上電后從保留區(qū)讀寫(xiě)指針然后掃描最近幾條記錄找到最后一條 CRC 正確的記錄從它后面繼續(xù)寫(xiě)。這樣即使掉電時(shí)正在寫(xiě)日志最多丟一條記錄不會(huì)影響整個(gè)日志區(qū)。實(shí)操心得MRAM 雖然寫(xiě)入快但不要在主循環(huán)里頻繁寫(xiě)。我見(jiàn)過(guò)一個(gè)項(xiàng)目每采集一次數(shù)據(jù)就寫(xiě)一次 MRAM結(jié)果 SPI 總線占用率太高影響了其他外設(shè)。后來(lái)改成攢夠 64 字節(jié)寫(xiě)一次或者定時(shí) 100ms 寫(xiě)一次問(wèn)題解決。存儲(chǔ)器件再快也要考慮總線帶寬和系統(tǒng)整體調(diào)度。4.4 與 Flash 方案的對(duì)比與遷移建議如果你之前用的是 W25Q64 這類 SPI Flash遷移到 MR25H40CDF 時(shí)硬件上基本兼容引腳定義幾乎一樣。軟件上主要改這幾個(gè)地方第一去掉所有擦除相關(guān)的代碼。Flash 寫(xiě)入前要發(fā) 0x06 WREN、0x20 扇區(qū)擦除、0x05 讀狀態(tài)等待擦除完成MRAM 全都不需要。第二寫(xiě)入前仍然要發(fā) WREN但不需要等待。第三地址范圍變了W25Q64 是 8MBMR25H40CDF 是 512KB如果你的代碼里有地址越界檢查記得改。第四性能特性不同。Flash 的擦除很慢幾十到幾百毫秒所以通常要攢一批數(shù)據(jù)再寫(xiě)。MRAM 沒(méi)有擦除寫(xiě)入也快可以更頻繁地寫(xiě)但要注意 SPI 總線占用。第五壽命不同。Flash 擦寫(xiě) 10 萬(wàn)次左右MRAM 是 10^14 次基本不用考慮磨損均衡。如果你之前為 Flash 寫(xiě)了磨損均衡算法遷移到 MRAM 后可以刪掉簡(jiǎn)化代碼。從成本角度看MR25H40CDF 比同容量 Flash 貴不少但比 FRAM 便宜而且容量更大。在需要頻繁寫(xiě)入、掉電保護(hù)、長(zhǎng)壽命的場(chǎng)景里這個(gè)成本是值得的。如果只是存固件或者不常改的參數(shù)用 Flash 更劃算。5. 工業(yè)場(chǎng)景下的擴(kuò)展思路5.1 多芯片級(jí)聯(lián)與容量擴(kuò)展512KB 在某些應(yīng)用里可能不夠用比如高頻數(shù)據(jù)記錄儀一天就能寫(xiě)滿。這時(shí)候可以掛多顆 MR25H40CDF用不同的 CS 引腳片選。STM32F334R8 的 GPIO 足夠多掛四顆就是 2MB掛八顆就是 4MB。多芯片管理的核心是地址映射。我一般定義一個(gè)邏輯地址空間比如 0x000000 到 0x3FFFFF 對(duì)應(yīng)四顆芯片每顆 512KB。讀寫(xiě)函數(shù)根據(jù)地址高兩位選擇 CS 引腳低 19 位作為芯片內(nèi)地址。#define MRAM_CHIP_SIZE 0x80000 #define MRAM_CHIP_NUM 4 void MRAM_SelectChip(uint32_t addr) { uint8_t chip addr / MRAM_CHIP_SIZE; switch (chip) { case 0: HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET); break; case 1: HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET); break; // ... } }多芯片的缺點(diǎn)是 PCB 面積增大CS 走線增多。如果空間緊張可以考慮用 SPI 片選譯碼器用 3 根 GPIO 控制 8 個(gè) CS節(jié)省引腳。5.2 與 RTOS 結(jié)合的任務(wù)調(diào)度在跑 FreeRTOS 或者 RT-Thread 的項(xiàng)目里MRAM 讀寫(xiě)要加互斥鎖。SPI 總線是共享資源如果多個(gè)任務(wù)同時(shí)讀寫(xiě) MRAM不加鎖會(huì)導(dǎo)致 CS 時(shí)序混亂數(shù)據(jù)錯(cuò)位。我的做法是創(chuàng)建一個(gè)二值信號(hào)量每次 MRAM 操作前獲取操作后釋放。如果某個(gè)任務(wù)要寫(xiě)大量數(shù)據(jù)可以分塊寫(xiě)每塊之間釋放一下信號(hào)量避免長(zhǎng)時(shí)間占用總線導(dǎo)致其他任務(wù)超時(shí)。SemaphoreHandle_t mram_mutex; void MRAM_WriteTask(uint32_t addr, uint8_t *buf, uint32_t len) { xSemaphoreTake(mram_mutex, portMAX_DELAY); MRAM_Write(addr, buf, len); xSemaphoreGive(mram_mutex); }如果對(duì)實(shí)時(shí)性要求高可以把 MRAM 操作放到一個(gè)低優(yōu)先級(jí)任務(wù)里通過(guò)隊(duì)列接收寫(xiě)請(qǐng)求批量處理。這樣高優(yōu)先級(jí)任務(wù)不會(huì)被 SPI 傳輸阻塞。5.3 數(shù)據(jù)加密與安全存儲(chǔ)工業(yè)設(shè)備有時(shí)候需要存一些敏感數(shù)據(jù)比如校準(zhǔn)密鑰、設(shè)備證書(shū)。MRAM 本身沒(méi)有加密功能數(shù)據(jù)是明文存儲(chǔ)的。如果設(shè)備可能被物理拆解MRAM 里的數(shù)據(jù)能被直接讀取。簡(jiǎn)單的做法是在 MCU 端做加密后再寫(xiě)入。STM32F334R8 沒(méi)有硬件加密引擎但可以用軟件 AES。ST 的 CryptoLib 提供了 AES-128 的軟件實(shí)現(xiàn)加密 512KB 數(shù)據(jù)大概幾百毫秒對(duì)于不常寫(xiě)的數(shù)據(jù)可以接受。更安全的做法是用 STM32 的讀保護(hù)功能防止通過(guò)調(diào)試接口讀取 Flash 里的密鑰。但 MRAM 是外部芯片讀保護(hù)管不到。如果安全要求極高可以考慮用帶加密功能的存儲(chǔ)芯片或者把密鑰存在 MCU 內(nèi)部 Flash 的受保護(hù)區(qū)域MRAM 只存加密后的數(shù)據(jù)。注意軟件 AES 加密會(huì)消耗 CPU 時(shí)間如果寫(xiě)入頻繁可能影響系統(tǒng)實(shí)時(shí)性。建議只對(duì)關(guān)鍵數(shù)據(jù)加密日志和普通參數(shù)可以明文存儲(chǔ)。5.4 低功耗設(shè)計(jì)考量電池供電的工業(yè)傳感器功耗是核心指標(biāo)。MR25H40CDF 的待機(jī)電流典型值 10uA寫(xiě)入時(shí)峰值電流 15mA讀取時(shí) 5mA。STM32F334R8 在 Stop 模式下 1uA 左右。如果系統(tǒng)大部分時(shí)間在休眠M(jìn)RAM 的待機(jī)功耗可以忽略。但要注意 CS 引腳的狀態(tài)CS 拉高時(shí)芯片進(jìn)入待機(jī)拉低時(shí)即使沒(méi)有時(shí)鐘也在耗電。所以休眠前一定要把 CS 拉高。寫(xiě)入策略上不要頻繁喚醒寫(xiě) MRAM??梢詳€一批數(shù)據(jù)喚醒后一次性寫(xiě)入然后繼續(xù)休眠。MRAM 的寫(xiě)入速度快喚醒時(shí)間短整體功耗比 Flash 方案低因?yàn)?Flash 擦除要等很久MCU 得一直醒著。實(shí)測(cè)一個(gè)數(shù)據(jù)記錄儀方案每 10 分鐘喚醒一次采集 1KB 數(shù)據(jù)寫(xiě)入 MRAM然后休眠。平均功耗 50uA 左右兩節(jié) AA 電池能用兩年以上。如果換成 Flash擦除等待會(huì)讓平均功耗翻倍。6. 調(diào)試工具與實(shí)戰(zhàn)技巧6.1 邏輯分析儀抓包要點(diǎn)調(diào) SPI 器件邏輯分析儀是必備工具。我用的是 Saleae Logic 88 通道100MS/s 采樣率抓 36MHz SPI 夠用。抓包時(shí)注意幾點(diǎn)第一采樣率至少是 SPI 時(shí)鐘的 4 倍36MHz SPI 需要 144MS/s 以上Logic 8 的 100MS/s 在 36MHz 下只能看到大概波形細(xì)節(jié)可能失真。如果條件允許用 Logic Pro 16500MS/s能看清每個(gè)邊沿。第二觸發(fā)條件設(shè)成 CS 下降沿這樣每次命令都能抓到。解碼器選 SPI配置 CPOL0、CPHA0、MSB First位序和 MCU 配置一致。第三看波形時(shí)重點(diǎn)檢查 CS 和 SCK 的關(guān)系。CS 拉低后第一個(gè) SCK 邊沿之前MOSI 應(yīng)該已經(jīng)穩(wěn)定。如果 MOSI 變化和 SCK 邊沿太近可能是 GPIO 速度不夠或者代碼里操作順序有問(wèn)題。6.2 用 J-Link 調(diào)試 MRAM 讀寫(xiě)J-Link 不僅能燒錄 STM32還能通過(guò) RTTReal Time Transfer輸出調(diào)試信息。我在 MRAM 驅(qū)動(dòng)里加一些 RTT 打印實(shí)時(shí)看讀寫(xiě)地址和數(shù)據(jù)比串口方便不占用 UART 資源。#include SEGGER_RTT.h SEGGER_RTT_printf(0, MRAM write addr0x%05X len%d\n, addr, len);如果懷疑某個(gè)地址的數(shù)據(jù)不對(duì)可以用 J-Link 的 Memory Browser 直接看 MCU 內(nèi)存里的緩沖區(qū)對(duì)比 MRAM 讀回的數(shù)據(jù)。J-Link 還能測(cè) SPI 傳輸耗時(shí)在傳輸前后讀 DWT-CYCCNT算出實(shí)際時(shí)鐘周期數(shù)驗(yàn)證 SPI 速率是否符合預(yù)期。6.3 批量生產(chǎn)時(shí)的測(cè)試方案產(chǎn)品量產(chǎn)時(shí)每塊板子都要測(cè) MRAM。手動(dòng)跑測(cè)試函數(shù)太慢我一般做一個(gè)自動(dòng)化測(cè)試工裝MCU 上電后進(jìn)入測(cè)試模式通過(guò) UART 接收命令PC 端發(fā)指令跑全片讀寫(xiě)測(cè)試返回 PASS 或 FAIL。測(cè)試內(nèi)容至少包括芯片 ID 讀取如果有、全片寫(xiě)入遞增模式、全片讀取校驗(yàn)、邊界地址測(cè)試0x00000、0x7FFFF、跨頁(yè)寫(xiě)入測(cè)試。測(cè)試時(shí)間控制在 5 秒以內(nèi)產(chǎn)線能接受。如果發(fā)現(xiàn)不良品記錄故障地址和現(xiàn)象方便分析是焊接問(wèn)題還是芯片問(wèn)題。常見(jiàn)的不良是 CS 虛焊、MISO 和 MOSI 接反、電源濾波電容漏貼。這些在目檢時(shí)不容易發(fā)現(xiàn)但測(cè)試能抓出來(lái)。6.4 長(zhǎng)期運(yùn)行的數(shù)據(jù)可靠性驗(yàn)證工業(yè)設(shè)備可能連續(xù)運(yùn)行幾年MRAM 的數(shù)據(jù)保持能力需要驗(yàn)證。我做過(guò)一個(gè)加速測(cè)試在 85°C 環(huán)境下連續(xù)寫(xiě)入 100 萬(wàn)次然后讀取校驗(yàn)沒(méi)有出現(xiàn)錯(cuò)誤。Everspin 的 datasheet 標(biāo)稱 10^14 次實(shí)際使用中基本不用擔(dān)心壽命。但數(shù)據(jù)保持方面高溫會(huì)加速磁性退化。如果設(shè)備工作在高溫環(huán)境建議定期比如每月做一次數(shù)據(jù)巡檢讀出來(lái)校驗(yàn) CRC發(fā)現(xiàn)錯(cuò)誤就用備份恢復(fù)。這個(gè)邏輯可以放在系統(tǒng)空閑任務(wù)里不影響正常功能。另外MRAM 對(duì)強(qiáng)磁場(chǎng)敏感。如果設(shè)備附近有電機(jī)、繼電器或者大電流走線MRAM 要遠(yuǎn)離這些干擾源或者加磁屏蔽。實(shí)測(cè)中距離 10cm 以上的繼電器動(dòng)作對(duì) MRAM 沒(méi)有影響但如果貼著放寫(xiě)入時(shí)可能出現(xiàn)位翻轉(zhuǎn)。7. 個(gè)人實(shí)操體會(huì)與建議這個(gè)方案我從 2021 年開(kāi)始在幾個(gè)工業(yè)項(xiàng)目里用累計(jì)出貨幾千臺(tái)現(xiàn)場(chǎng)反饋很穩(wěn)。最大的感受是MRAM 的“寫(xiě)入即持久”特性讓掉電保護(hù)邏輯簡(jiǎn)化了至少一半。以前用 Flash 的時(shí)候要設(shè)計(jì)雙備份、寫(xiě)前擦除、掉電檢測(cè)、恢復(fù)流程代碼量大且容易出 bug。換成 MRAM 后寫(xiě)入就是寫(xiě)入掉電就掉電數(shù)據(jù)不會(huì)丟省心很多。選型上MR25H40CDF 的 4Mbit 容量在參數(shù)存儲(chǔ)場(chǎng)景夠用但如果要存大量日志建議選更大容量的型號(hào)比如 MR25H256256Kbit或者 MR4A16B16Mbit 并口。SPI 接口的 MRAM 目前最大到 4Mbit再大就要用并口或者換其他技術(shù)路線。驅(qū)動(dòng)開(kāi)發(fā)上我建議先把底層讀寫(xiě)調(diào)通用邏輯分析儀確認(rèn)時(shí)序正確再往上搭數(shù)據(jù)結(jié)構(gòu)和應(yīng)用邏輯。很多人一上來(lái)就寫(xiě)復(fù)雜的存儲(chǔ)管理結(jié)果底層有問(wèn)題查半天查不出來(lái)。分層調(diào)試逐層驗(yàn)證效率最高。最后分享一個(gè)小技巧MRAM 的 CS 引腳上拉一個(gè) 10k 電阻到 3.3V防止 MCU 復(fù)位期間 CS 浮空導(dǎo)致誤操作。雖然 MRAM 寫(xiě)入需要 WREN誤觸發(fā)的概率很低但加上拉電阻成本幾乎為零能提高可靠性。我在早期版本上沒(méi)加后來(lái)有一次現(xiàn)場(chǎng)設(shè)備頻繁復(fù)位懷疑是 CS 干擾加上拉后問(wèn)題消失。這種細(xì)節(jié)datasheet 上不會(huì)寫(xiě)但實(shí)際項(xiàng)目中很關(guān)鍵。