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