指南)
1. 為什么 MRAM 在嵌入式存儲里越來越受關注搞嵌入式存儲的兄弟應該都有體會選型的時候翻來覆去就那么幾個選項EEPROM 擦寫壽命長但容量小、速度慢NOR Flash 讀取快但寫入前必須擦除而且擦寫次數(shù)有限NAND Flash 容量大便宜但壞塊管理和 ECC 校驗能把人搞瘋FRAM 速度快、壽命幾乎無限但容量小、價格高。每一種都有自己繞不過去的短板。MRAM磁阻隨機存取存儲器這幾年的存在感明顯在上升核心原因就一個它把 SRAM 的讀寫速度、Flash 的非易失性、以及近乎無限的擦寫壽命揉到了一起。MR25H40CDF 就是這類器件里比較有代表性的一顆4Mbit 容量SPI 接口工業(yè)級溫度范圍不需要擦除就能直接寫入寫入次數(shù)沒有實際意義上的壽命限制。對于需要頻繁記錄數(shù)據(jù)、又不想在掉電后丟數(shù)據(jù)的場景這東西確實省心。STM32F722ZE 這邊Cortex-M7 內核跑 216MHz帶 FPU 和 DSP 指令集本身定位就是高性能嵌入式主控。它上面有多個 SPI 外設其中 SPI1 掛在 APB2 上時鐘源頻率高拿來驅動 MR25H40CDF 這種需要高速讀寫的器件很合適。兩者搭配一個負責高速運算和協(xié)議控制一個負責可靠存儲在工業(yè)數(shù)據(jù)記錄、參數(shù)保存、黑匣子類應用里是很實用的組合。這篇文章面向的是有一定 STM32 開發(fā)基礎、正在做數(shù)據(jù)存儲方案選型的嵌入式工程師或者已經(jīng)拿到這兩顆器件、準備把驅動跑起來的開發(fā)者。我會從硬件連接到 SPI 時序配置、從寄存器操作到實際讀寫測試把整個鏈路拆開講清楚包括我在調試過程中踩過的坑和驗證過的參數(shù)。2. 硬件設計與 SPI 接口連接要點2.1 MR25H40CDF 的引腳功能與封裝選擇MR25H40CDF 用的是 8 引腳 DFN 封裝引腳定義和標準 SPI Flash 基本兼容這也是它方便替換的原因之一。具體引腳功能如下引腳編號名稱功能說明1CS片選低電平有效2SO數(shù)據(jù)輸出MISO3WP寫保護低電平有效4VSS地5SI數(shù)據(jù)輸入MOSI6SCK時鐘7HOLD保持低電平有效8VDD電源2.7V~3.6VWP 和 HOLD 這兩個引腳在標準 SPI 模式下需要拉高否則器件會進入寫保護或保持狀態(tài)導致通信異常。我見過不少人調試時發(fā)現(xiàn)讀不到 ID查了半天時序最后發(fā)現(xiàn)是 HOLD 懸空了。DFN 封裝體積小但焊接時要注意底部散熱焊盤的處理如果手工焊接建議用熱風槍配合焊膏避免虛焊。2.2 與 STM32F722ZE 的 SPI 連接方案STM32F722ZE 的 SPI1 我一般這樣分配引腳PA5→ SPI1_SCKPA6→ SPI1_MISOPA7→ SPI1_MOSIPA4→ GPIO 輸出作為軟件片選為什么用軟件片選而不是硬件 NSS因為 MR25H40CDF 的片選需要在每個命令序列前后精確控制硬件 NSS 在多主機或者需要靈活控制片選時序的場景下反而添亂。軟件片選就是把 PA4 配成普通推挽輸出需要通信時拉低通信結束拉高時序完全可控。SPI 模式方面MR25H40CDF 支持 Mode 0CPOL0CPHA0和 Mode 3CPOL1CPHA1。我實測下來 Mode 0 最穩(wěn)STM32 這邊配置 CPOLLow、CPHA1Edge 就行。時鐘頻率方面MR25H40CDF 最高支持 40MHz但實際跑的時候要考慮 PCB 走線和信號完整性。我一般先跑 10MHz 驗證功能穩(wěn)定后再往上提。注意SPI 走線盡量等長SCK 線上如果有過沖可以在源端串一個 22Ω~33Ω 的電阻實測能明顯改善波形。2.3 電源與去耦的細節(jié)處理MR25H40CDF 的供電范圍是 2.7V~3.6V和 STM32F722ZE 的 3.3V 系統(tǒng)可以直接對接不需要電平轉換。但去耦電容不能省我一般在 VDD 引腳旁邊放一個 0.1μF 的陶瓷電容再并一個 1μF 的位置盡量靠近引腳。MRAM 在寫入瞬間的電流波動比 Flash 小但對電源紋波還是敏感的尤其是高速連續(xù)寫入的時候。如果板子上還有其他大功率器件建議給 MRAM 單獨走一路 LDO或者至少在電源入口加一個磁珠隔離。我之前有個項目MRAM 和電機驅動共用 3.3V電機一啟動就讀寫異常后來加了磁珠和額外的去耦電容才解決。3. STM32F722ZE 的 SPI 外設配置與底層驅動3.1 CubeMX 配置 SPI1 的關鍵參數(shù)用 CubeMX 配置 SPI1 的時候幾個關鍵參數(shù)這樣設ModeFull-Duplex MasterData Size8 BitsClock PolarityLowClock Phase1 EdgeNSSSoftwareBaud Rate Prescaler先設 16對應 216MHz/16 ≈ 13.5MHzFirst BitMSB First這里有個細節(jié)STM32F722ZE 的 SPI1 掛在 APB2 上APB2 的時鐘默認是 216MHz。分頻系數(shù)選 16 的話SCK 就是 13.5MHz對于 MR25H40CDF 來說完全在安全范圍內。等驗證通過后可以把分頻改成 8 甚至 4跑到 27MHz 或 54MHz但 54MHz 已經(jīng)超過器件標稱的 40MHz 了不建議長期跑。CubeMX 生成代碼后SPI1 的初始化函數(shù)會自動配好但片選引腳 PA4 需要手動配成 GPIO 輸出初始狀態(tài)拉高。3.2 片選控制的時序要求MR25H40CDF 的片選時序有幾個硬性要求CS 拉低到第一個 SCK 上升沿之間至少要有 5ns 的建立時間最后一個 SCK 下降沿到 CS 拉高之間至少要有 5ns 的保持時間。STM32 的 GPIO 翻轉速度在 100MHz 以上這些時間完全滿足但代碼里要注意不要在 CS 拉低后立刻發(fā)數(shù)據(jù)中間加幾個 NOP 或者用 HAL 庫的延時函數(shù)緩沖一下。我一般這樣寫片選控制#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)在每次 SPI 傳輸前后調用這兩個宏確保片選狀態(tài)和通信序列嚴格對應。3.3 SPI 收發(fā)函數(shù)的封裝HAL 庫提供了HAL_SPI_Transmit和HAL_SPI_Receive但在 MRAM 操作里更常用的是HAL_SPI_TransmitReceive因為很多命令需要同時發(fā)送和接收。我封裝了一個通用的字節(jié)交換函數(shù)uint8_t MRAM_SPI_Transfer(uint8_t txData) { uint8_t rxData; HAL_SPI_TransmitReceive(hspi1, txData, rxData, 1, 100); return rxData; }這個函數(shù)在讀寫寄存器、發(fā)送命令、讀取數(shù)據(jù)時都會用到。超時時間設 100ms 足夠實際傳輸一個字節(jié)在 13.5MHz 下也就不到 1μs。實操心得如果發(fā)現(xiàn)讀回來的數(shù)據(jù)全是 0xFF 或者 0x00先檢查片選是否正常翻轉再用示波器看 SCK 和 MOSI 有沒有波形。很多時候問題出在片選沒拉低或者 SPI 外設根本沒使能。4. MR25H40CDF 的寄存器操作與讀寫命令詳解4.1 狀態(tài)寄存器與寫使能機制MR25H40CDF 內部有幾個關鍵寄存器操作之前需要先了解寄存器命令碼功能狀態(tài)寄存器0x05讀取狀態(tài)包括 WEL、WIP 等位寫使能0x06置位 WEL允許寫入寫禁止0x04清除 WEL禁止寫入讀數(shù)據(jù)0x03從指定地址讀取數(shù)據(jù)寫數(shù)據(jù)0x02向指定地址寫入數(shù)據(jù)讀 ID0x9F讀取器件 ID每次寫入之前必須先發(fā) 0x06 寫使能命令否則寫入操作會被忽略。這一點和 EEPROM 類似但 MRAM 不需要等待擦除寫使能后可以直接寫數(shù)據(jù)。狀態(tài)寄存器的 WIP 位Write In Progress在 MRAM 里其實很少會置位因為寫入速度極快但為了兼容性和保險起見寫完還是可以讀一下狀態(tài)確認。4.2 讀 ID 驗證通信鏈路上電后第一步應該是讀器件 ID確認 SPI 通信正常。MR25H40CDF 的 ID 是 0x9F 命令后返回的字節(jié)序列具體值可以參考數(shù)據(jù)手冊。我一般這樣寫uint8_t MRAM_ReadID(void) { uint8_t id; MRAM_CS_LOW(); MRAM_SPI_Transfer(0x9F); id MRAM_SPI_Transfer(0x00); MRAM_CS_HIGH(); return id; }如果讀回來的 ID 不對先查硬件連接再查 SPI 配置。我遇到過 SPI 模式設成 Mode 2 導致讀 ID 失敗的情況改成 Mode 0 就正常了。4.3 頁寫與連續(xù)讀的操作流程MR25H40CDF 支持頁寫Page Write每頁 256 字節(jié)。寫入時可以跨頁但建議按頁對齊操作避免地址回卷的復雜性。寫數(shù)據(jù)的流程是拉低 CS發(fā)送 0x06 寫使能拉高 CS拉低 CS發(fā)送 0x02 寫命令發(fā)送 24 位地址高字節(jié)在前發(fā)送數(shù)據(jù)字節(jié)拉高 CS讀數(shù)據(jù)更簡單拉低 CS發(fā)送 0x03 讀命令發(fā)送 24 位地址讀取數(shù)據(jù)字節(jié)拉高 CS地址是 24 位的因為 4Mbit 容量對應 512K 字節(jié)需要 19 位地址但器件用 24 位地址格式高 5 位忽略。注意連續(xù)讀的時候地址會自動遞增可以一直讀到 CS 拉高為止。但如果跨過最高地址會回卷到 0x000000這個行為在數(shù)據(jù)手冊里有說明寫代碼時要注意邊界判斷。5. 完整讀寫測試與性能驗證5.1 單字節(jié)讀寫測試先做最基本的單字節(jié)讀寫驗證驅動是否正常void MRAM_Test_SingleByte(void) { uint32_t addr 0x000100; uint8_t writeData 0xA5; uint8_t readData; MRAM_WriteByte(addr, writeData); readData MRAM_ReadByte(addr); if (readData writeData) { printf(Single byte test PASS\r\n); } else { printf(Single byte test FAIL: wrote 0x%02X, read 0x%02X\r\n, writeData, readData); } }這個測試能過說明基本的 SPI 時序、片選控制、命令序列都沒問題。5.2 批量數(shù)據(jù)讀寫與速度測試接下來做批量測試寫 4KB 數(shù)據(jù)再讀回來對比#define TEST_SIZE 4096 uint8_t txBuf[TEST_SIZE]; uint8_t rxBuf[TEST_SIZE]; void MRAM_Test_Bulk(void) { for (int i 0; i TEST_SIZE; i) { txBuf[i] i 0xFF; } uint32_t startTick HAL_GetTick(); MRAM_WriteBuffer(0x000000, txBuf, TEST_SIZE); uint32_t writeTime HAL_GetTick() - startTick; startTick HAL_GetTick(); MRAM_ReadBuffer(0x000000, rxBuf, TEST_SIZE); uint32_t readTime HAL_GetTick() - startTick; if (memcmp(txBuf, rxBuf, TEST_SIZE) 0) { printf(Bulk test PASS, write %lu ms, read %lu ms\r\n, writeTime, readTime); } else { printf(Bulk test FAIL\r\n); } }在 13.5MHz SPI 時鐘下4KB 寫入大概 3~4ms讀取大概 2~3ms。這個速度比 EEPROM 快了一個數(shù)量級比 NOR Flash 也快不少因為不需要擦除等待。5.3 掉電數(shù)據(jù)保持驗證MRAM 的非易失性是其核心賣點驗證方法也簡單寫入數(shù)據(jù)后斷電等幾秒再上電讀取。我實測下來斷電 10 秒后數(shù)據(jù)依然完整。如果需要更嚴格的驗證可以反復斷電上電 100 次檢查數(shù)據(jù)一致性。實操心得掉電測試時建議在電源端加一個大電容模擬緩慢掉電的場景。MRAM 在電壓下降到 2.7V 以下時可能無法正常寫入但已經(jīng)寫入的數(shù)據(jù)不會丟失。6. 常見問題排查與避坑指南6.1 讀不到 ID 或數(shù)據(jù)全為 0xFF這是最常見的問題排查順序如下現(xiàn)象可能原因解決方法讀 ID 返回 0xFFMISO 未連接或片選未拉低檢查 PA6 連接確認 CS 翻轉讀 ID 返回 0x00MOSI 未連接或時鐘未輸出檢查 PA7 和 PA5用示波器看波形數(shù)據(jù)隨機錯誤SPI 模式不匹配改為 Mode 0降低時鐘頻率寫入后讀回不變未發(fā)送寫使能命令每次寫入前發(fā) 0x06我踩過最坑的一次是 HOLD 引腳懸空導致器件間歇性進入保持狀態(tài)讀出來的數(shù)據(jù)時對時錯。后來把 HOLD 和 WP 都上拉到 3.3V問題徹底解決。6.2 高速通信下的數(shù)據(jù)錯誤當 SPI 時鐘超過 20MHz 時如果 PCB 走線較長或者沒有阻抗匹配容易出現(xiàn)數(shù)據(jù)錯誤。解決方法降低 SPI 時鐘到 10MHz 驗證功能在 SCK 和 MOSI 上串 22Ω 電阻縮短走線避免過孔確保地平面完整6.3 寫入速度不如預期如果發(fā)現(xiàn)寫入速度慢檢查以下幾點SPI 分頻系數(shù)是否設得太大是否每次寫入都重新發(fā)送寫使能可以批量寫入時只發(fā)一次是否在寫入后輪詢狀態(tài)寄存器MRAM 不需要可以直接連續(xù)寫MRAM 的寫入沒有擦除等待理論上可以連續(xù)背靠背寫入不需要像 Flash 那樣等待 WIP 位清零。7. 工業(yè)場景下的應用擴展與經(jīng)驗總結7.1 數(shù)據(jù)記錄儀中的環(huán)形緩沖區(qū)設計在工業(yè)數(shù)據(jù)記錄場景里MRAM 很適合做環(huán)形緩沖區(qū)。因為擦寫壽命幾乎無限可以頻繁覆蓋寫入。我一般這樣設計劃分一塊區(qū)域作為緩沖區(qū)比如 64KB維護一個寫指針每次寫完數(shù)據(jù)后指針遞增指針到達末尾后回卷到起始地址讀取時從指針位置往前讀這種設計不需要垃圾回收也不需要磨損均衡因為 MRAM 沒有擦寫壽命限制。7.2 參數(shù)存儲與掉電保護系統(tǒng)參數(shù)需要頻繁修改且掉電不能丟MRAM 是理想選擇。我通常把參數(shù)區(qū)放在 MRAM 的固定地址每次修改后立即寫入不需要像 Flash 那樣先擦除整個扇區(qū)。寫入時加一個 CRC 校驗上電讀取時驗證 CRC如果校驗失敗就加載默認參數(shù)。7.3 與 STM32F722ZE 的 DMA 配合如果需要更高速的數(shù)據(jù)傳輸可以啟用 SPI 的 DMA 通道。STM32F722ZE 的 SPI1 支持 DMA 請求配置好 DMA 后大批量數(shù)據(jù)讀寫可以解放 CPU。我實測用 DMA 讀寫 4KB 數(shù)據(jù)CPU 占用率從 30% 降到 5% 以下。配置 DMA 時注意SPI1_TX 用 DMA2 Stream 3 或 Stream 5SPI1_RX 用 DMA2 Stream 0 或 Stream 2傳輸完成后要清除 DMA 標志位片選控制要在 DMA 傳輸完成后手動拉高7.4 個人經(jīng)驗與后續(xù)擴展方向我在多個工業(yè)項目里用過 MR25H40CDF 搭配 STM32F722ZE 的方案穩(wěn)定性確實好尤其是需要頻繁寫入的場景比 Flash 方案省心太多。唯一需要注意的是成本MRAM 單價比同容量 Flash 高不少所以適合用在對可靠性和寫入壽命要求高的地方而不是無腦替換所有存儲。后續(xù)如果要擴展可以考慮用文件系統(tǒng)如 LittleFS管理 MRAM 空間增加雙備份機制關鍵數(shù)據(jù)存兩份結合 RTC 做帶時間戳的數(shù)據(jù)記錄用 STM32F722ZE 的硬件 CRC 單元校驗數(shù)據(jù)完整性這套方案我目前跑在最長的項目已經(jīng)連續(xù)運行兩年多每天寫入上千次沒有出現(xiàn)過數(shù)據(jù)丟失或器件失效。對于需要高可靠存儲的嵌入式應用來說MR25H40CDF 加 STM32F722ZE 的組合值得認真考慮。