嵌入式存儲(chǔ)實(shí)戰(zhàn):MR25H40CDF與STM32F031C6驅(qū)動(dòng)開(kāi)發(fā))
1. 為什么 MRAM 在工業(yè)嵌入式場(chǎng)景里越來(lái)越受關(guān)注1.1 從 EEPROM 和 Flash 的痛點(diǎn)說(shuō)起做過(guò)工業(yè)設(shè)備的人大概都有過(guò)這樣的經(jīng)歷設(shè)備在現(xiàn)場(chǎng)跑了幾個(gè)月突然某天斷電重啟后參數(shù)全丟了或者日志文件寫(xiě)壞導(dǎo)致整個(gè)文件系統(tǒng)掛掉。追根溯源問(wèn)題往往出在存儲(chǔ)介質(zhì)上。傳統(tǒng)方案里小容量參數(shù)存儲(chǔ)用 EEPROM大容量數(shù)據(jù)和代碼存儲(chǔ)用 NOR Flash 或 NAND Flash這套組合用了十幾年穩(wěn)定是穩(wěn)定但有幾個(gè)繞不開(kāi)的硬傷。EEPROM 的寫(xiě)入速度慢得讓人著急字節(jié)寫(xiě)入典型值在 5ms 到 10ms 量級(jí)如果設(shè)備需要在掉電瞬間保存一批關(guān)鍵數(shù)據(jù)這個(gè)速度根本來(lái)不及。而且 EEPROM 的擦寫(xiě)壽命雖然標(biāo)稱(chēng) 100 萬(wàn)次但實(shí)際在高溫環(huán)境下會(huì)大幅縮水工業(yè)級(jí) 85℃ 場(chǎng)景下能到 10 萬(wàn)次就算不錯(cuò)了。NOR Flash 呢擦除必須按扇區(qū)來(lái)寫(xiě)入前要先擦除擦除時(shí)間長(zhǎng)而且擦寫(xiě)壽命通常只有 10 萬(wàn)次左右頻繁寫(xiě)日志的場(chǎng)景下很快就到壽命上限。MRAMMagnetoresistive Random Access Memory磁性隨機(jī)存儲(chǔ)器的出現(xiàn)恰好把這些痛點(diǎn)一次性解決了。它的寫(xiě)入速度是納秒級(jí)的和 SRAM 一個(gè)量級(jí)擦寫(xiě)壽命理論上無(wú)限實(shí)測(cè)可以輕松做到 10^14 次以上非易失特性讓它斷電后數(shù)據(jù)不丟而且不需要擦除操作可以直接覆蓋寫(xiě)。這幾個(gè)特性疊加在一起對(duì)于工業(yè)數(shù)據(jù)采集、參數(shù)存儲(chǔ)、掉電保護(hù)、日志記錄這些場(chǎng)景來(lái)說(shuō)簡(jiǎn)直是量身定做的。1.2 MR25H40CDF 這顆芯片到底強(qiáng)在哪MR25H40CDF 是 Everspin 出品的一款 4Mbit512KB容量的 SPI MRAM。我第一次拿到這顆芯片的規(guī)格書(shū)時(shí)最直觀的感受是它的接口設(shè)計(jì)非??酥啤獦?biāo)準(zhǔn) SPI 接口支持 Mode 0 和 Mode 3最高時(shí)鐘頻率 40MHz指令集和普通 SPI Flash 高度兼容基本上你原來(lái)怎么操作 W25Q 系列換成它只需要改幾個(gè)指令碼。但它的內(nèi)在和 Flash 完全不同。MR25H40CDF 的存儲(chǔ)單元是磁性隧道結(jié)MTJ通過(guò)電子自旋方向來(lái)存儲(chǔ)數(shù)據(jù)而不是電荷。這意味著它沒(méi)有電荷泄漏問(wèn)題不需要刷新寫(xiě)入前不需要擦除寫(xiě)入延遲極低。規(guī)格書(shū)上標(biāo)稱(chēng)的寫(xiě)入周期時(shí)間是 35ns這個(gè)數(shù)字在 SPI 接口下會(huì)被總線速度限制但即便如此40MHz 時(shí)鐘下連續(xù)寫(xiě)入的吞吐率也能達(dá)到 5MB/s 左右比 EEPROM 快了三個(gè)數(shù)量級(jí)。工作電壓范圍 2.7V 到 3.6V工業(yè)級(jí)溫度范圍 -40℃ 到 85℃部分批次可以做到 105℃。封裝是 8 引腳 SOIC 或者 DFN引腳定義和標(biāo)準(zhǔn) SPI Flash 完全一致CS、SO、SI、SCK、VDD、VSS、WP、HOLD。WP 和 HOLD 引腳在 MRAM 里功能有所簡(jiǎn)化但引腳位置兼容PCB 設(shè)計(jì)時(shí)可以直接替換 Flash 的焊盤(pán)。1.3 STM32F031C6 作為主控的適配性分析STM32F031C6 是 ST 的入門(mén)級(jí) Cortex-M0 芯片48MHz 主頻32KB Flash4KB SRAM封裝是 LQFP48。選它來(lái)搭配 MR25H40CDF乍一看有點(diǎn)小馬拉大車(chē)的意思——4KB SRAM 的芯片去驅(qū)動(dòng) 512KB 的存儲(chǔ)能行嗎實(shí)際用下來(lái)這個(gè)組合在工業(yè)場(chǎng)景里非常合理。STM32F031C6 自帶一個(gè)硬件 SPI 接口最高時(shí)鐘可以配置到 24MHzPCLK 48MHz 二分頻雖然跑不滿(mǎn) MR25H40CDF 的 40MHz 上限但 24MHz 下連續(xù)讀寫(xiě)的吞吐率也有 3MB/s對(duì)于參數(shù)存儲(chǔ)、日志緩沖、配置管理這些場(chǎng)景完全夠用。而且 F031C6 的功耗極低運(yùn)行模式下約 100μA/MHz配合 MRAM 的微安級(jí)待機(jī)電流整個(gè)存儲(chǔ)子系統(tǒng)在電池供電的工業(yè)傳感器節(jié)點(diǎn)里可以做到很長(zhǎng)的續(xù)航。更重要的是STM32F031C6 的 SPI 外設(shè)支持 DMA 請(qǐng)求雖然 M0 的 DMA 通道有限配合 MRAM 的連續(xù)讀寫(xiě)特性可以實(shí)現(xiàn)零拷貝的數(shù)據(jù)流。我在一個(gè)振動(dòng)采集項(xiàng)目里用 SPIDMA 把 MRAM 當(dāng)作環(huán)形緩沖區(qū)ADC 采樣數(shù)據(jù)直接通過(guò) DMA 寫(xiě)入 MRAMCPU 幾乎不參與實(shí)測(cè)下來(lái)非常穩(wěn)。注意STM32F031C6 的 SPI1 默認(rèn)引腳是 PA5(SCK)、PA6(MISO)、PA7(MOSI)片選需要自己用 GPIO 控制。硬件 NSS 在 M0 系列上有些坑建議直接用軟件片選后面會(huì)詳細(xì)說(shuō)。2. 硬件設(shè)計(jì)從原理圖到 PCB 的實(shí)操細(xì)節(jié)2.1 引腳連接與電平匹配MR25H40CDF 和 STM32F031C6 都是 3.3V 供電電平直接匹配不需要電平轉(zhuǎn)換芯片。這一點(diǎn)比某些 1.8V 的 MRAM 型號(hào)省事很多。具體的引腳連接關(guān)系如下表MR25H40CDF 引腳功能STM32F031C6 引腳備注CS片選PA4 (GPIO)軟件控制低有效SCK時(shí)鐘PA5 (SPI1_SCK)24MHz 以?xún)?nèi)SO數(shù)據(jù)輸出PA6 (SPI1_MISO)推挽復(fù)用SI數(shù)據(jù)輸入PA7 (SPI1_MOSI)推挽復(fù)用WP寫(xiě)保護(hù)接 VDD 或 GPIO不用時(shí)上拉HOLD保持接 VDD 或 GPIO不用時(shí)上拉VDD電源3.3V加 0.1μF 去耦VSS地GND單點(diǎn)接地這里有個(gè)細(xì)節(jié)值得展開(kāi)說(shuō)。WP 和 HOLD 引腳在 MR25H40CDF 上的行為和 Flash 不完全一樣。Flash 的 WP 引腳在特定指令下會(huì)鎖死狀態(tài)寄存器而 MRAM 的 WP 引腳功能更簡(jiǎn)單主要是硬件級(jí)別的寫(xiě)保護(hù)。如果你不需要硬件寫(xiě)保護(hù)直接上拉到 VDD 就行。HOLD 引腳用于暫停 SPI 通信在多從機(jī)共享總線的場(chǎng)景下有用單從機(jī)場(chǎng)景直接上拉。我踩過(guò)的一個(gè)坑有一次為了省事把 WP 和 HOLD 懸空了結(jié)果在電機(jī)干擾比較大的環(huán)境里偶爾出現(xiàn)寫(xiě)入數(shù)據(jù)錯(cuò)亂。后來(lái)查了半天發(fā)現(xiàn)是懸空引腳拾取了噪聲導(dǎo)致芯片內(nèi)部狀態(tài)機(jī)誤動(dòng)作。所以這兩個(gè)引腳一定要處理要么上拉要么接 GPIO 控制千萬(wàn)別懸空。2.2 PCB 布局布線的關(guān)鍵要點(diǎn)SPI 總線在 24MHz 下雖然不算高速信號(hào)但在工業(yè)環(huán)境里電磁干擾復(fù)雜PCB 布局還是不能太隨意。我總結(jié)了幾條實(shí)操經(jīng)驗(yàn)第一MRAM 芯片盡量靠近 MCU 放置走線長(zhǎng)度控制在 5cm 以?xún)?nèi)。SPI 的四根信號(hào)線SCK、MOSI、MISO、CS盡量等長(zhǎng)尤其是 SCK 和 MOSI長(zhǎng)度差不要超過(guò) 5mm。如果實(shí)在做不到等長(zhǎng)優(yōu)先保證 SCK 走線短而直。第二SCK 走線要遠(yuǎn)離模擬信號(hào)和電源開(kāi)關(guān)節(jié)點(diǎn)。我在一個(gè)項(xiàng)目里把 SCK 走線從 DC-DC 電感旁邊穿過(guò)結(jié)果 SPI 通信誤碼率飆升。后來(lái)把 SCK 改道問(wèn)題立刻消失。如果 PCB 空間受限可以在 SCK 和 GND 之間加一個(gè) 22Ω 的串聯(lián)電阻減緩邊沿速率降低輻射。第三去耦電容必須加而且要靠近芯片的 VDD 引腳。0.1μF 的陶瓷電容是標(biāo)配如果電源紋波比較大再并聯(lián)一個(gè) 1μF 的。我習(xí)慣在 MRAM 的 VDD 和 GND 之間放一個(gè) 0.1μF 的 0402 電容距離引腳不超過(guò) 2mm。第四CS 信號(hào)線不要和其他信號(hào)線平行走太長(zhǎng)距離。CS 是控制信號(hào)如果受到干擾產(chǎn)生毛刺會(huì)導(dǎo)致 SPI 狀態(tài)機(jī)誤判??梢栽?CS 上拉一個(gè) 10kΩ 的電阻到 VDD保證空閑時(shí)電平穩(wěn)定。實(shí)操心得如果你用的是雙層板建議在 MRAM 芯片下方鋪一塊完整的地平面SPI 信號(hào)線走在頂層地平面在底層作為參考。這樣可以讓信號(hào)回流路徑最短減少電磁輻射。四層板的話把 SPI 信號(hào)走在內(nèi)層上下都有地平面參考效果更好。2.3 電源設(shè)計(jì)與掉電保護(hù)工業(yè)場(chǎng)景里電源質(zhì)量往往不太理想浪涌、跌落、紋波都是家常便飯。MRAM 雖然寫(xiě)入速度快但如果電源在寫(xiě)入過(guò)程中跌落數(shù)據(jù)仍然可能出錯(cuò)。所以電源設(shè)計(jì)要留足余量。MR25H40CDF 的工作電壓范圍是 2.7V 到 3.6V典型值 3.3V。我一般會(huì)用一顆 LDO 單獨(dú)給 MRAM 供電比如 TPS7A4901 或者類(lèi)似的低噪聲 LDO輸出 3.3V輸入從系統(tǒng)的 5V 或者 12V 降壓過(guò)來(lái)。LDO 的輸入端加一個(gè) 10μF 的鉭電容輸出端加 1μF 陶瓷電容加 0.1μF 高頻電容。如果系統(tǒng)有掉電保存的需求可以在 LDO 輸入端加一個(gè)大容量電容比如 1000μF配合一個(gè)電壓檢測(cè)電路。當(dāng)檢測(cè)到輸入電壓跌落時(shí)MCU 立即觸發(fā) MRAM 寫(xiě)入把關(guān)鍵數(shù)據(jù)保存下來(lái)。由于 MRAM 寫(xiě)入速度極快1000μF 電容儲(chǔ)存的能量足夠完成幾百次寫(xiě)入操作。我實(shí)測(cè)過(guò)一組數(shù)據(jù)在 3.3V 供電、1000μF 電容的條件下從檢測(cè)到掉電到電容電壓跌落到 2.7VMRAM 最低工作電壓大約有 15ms 的窗口時(shí)間。在這 15ms 內(nèi)以 24MHz SPI 時(shí)鐘計(jì)算可以寫(xiě)入大約 45KB 的數(shù)據(jù)。對(duì)于大多數(shù)參數(shù)保存場(chǎng)景這個(gè)窗口綽綽有余。3. 軟件驅(qū)動(dòng)從寄存器操作到 HAL 庫(kù)封裝3.1 SPI 外設(shè)初始化與時(shí)鐘配置STM32F031C6 的 SPI1 掛在 APB2 總線上PCLK 默認(rèn) 48MHz。要配置 SPI 時(shí)鐘為 24MHz需要設(shè)置分頻系數(shù)為 2。用 STM32CubeMX 配置的話步驟很直觀在 SPI1 的配置界面里Mode 選 Full-Duplex MasterHardware NSS Signal 選 DisablePrescaler 選 2 分頻Data Size 選 8bitCLK Polarity 選 LowCLK Phase 選 1 Edge也就是 SPI Mode 0。這里解釋一下為什么選 Mode 0。MR25H40CDF 支持 Mode 0 和 Mode 3兩者的區(qū)別在于時(shí)鐘空閑電平和采樣邊沿。Mode 0 是時(shí)鐘空閑低電平第一個(gè)邊沿上升沿采樣Mode 3 是時(shí)鐘空閑高電平第二個(gè)邊沿下降沿采樣。Mode 0 在大多數(shù) SPI Flash 和 MRAM 上是默認(rèn)模式兼容性最好所以我一般優(yōu)先選 Mode 0。用 HAL 庫(kù)初始化的代碼大概長(zhǎng)這樣SPI_HandleTypeDef hspi1; void SPI1_Init(void) { 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; if (HAL_SPI_Init(hspi1) ! HAL_OK) { Error_Handler(); } }片選引腳 PA4 配置為推挽輸出初始電平拉高void CS_GPIO_Init(void) { GPIO_InitTypeDef GPIO_InitStruct {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); GPIO_InitStruct.Pin GPIO_PIN_4; 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_SET); }注意STM32F031C6 的 SPI1 在 HAL 庫(kù)里有幾個(gè)版本存在已知問(wèn)題主要是 HAL_SPI_TransmitReceive 在高速下偶發(fā)超時(shí)。如果你遇到這個(gè)問(wèn)題可以改用 LL 庫(kù)直接操作寄存器或者升級(jí)到最新的 HAL 版本。我實(shí)測(cè) STM32CubeF0 的 1.11.0 版本 HAL 庫(kù)比較穩(wěn)定。3.2 MRAM 指令集解析與讀寫(xiě)時(shí)序MR25H40CDF 的指令集和標(biāo)準(zhǔn) SPI Flash 高度相似但簡(jiǎn)化了不少。常用的指令碼如下表指令名稱(chēng)指令碼功能說(shuō)明WREN0x06寫(xiě)使能寫(xiě)入前必須發(fā)送WRDI0x04寫(xiě)禁止RDSR0x05讀狀態(tài)寄存器WRSR0x01寫(xiě)狀態(tài)寄存器READ0x03讀數(shù)據(jù)任意地址WRITE0x02寫(xiě)數(shù)據(jù)任意地址RDID0x9F讀設(shè)備 ID和 Flash 最大的區(qū)別在于MRAM 沒(méi)有扇區(qū)擦除指令也沒(méi)有頁(yè)編程的概念。寫(xiě)入操作就是直接發(fā) WRITE 指令然后跟 3 字節(jié)地址再跟數(shù)據(jù)數(shù)據(jù)可以連續(xù)寫(xiě)地址自動(dòng)遞增直到 CS 拉高。整個(gè)寫(xiě)入過(guò)程不需要等待因?yàn)?MRAM 的寫(xiě)入是即時(shí)的。讀操作的時(shí)序也很簡(jiǎn)單CS 拉低發(fā)送 READ 指令0x03發(fā)送 3 字節(jié)地址然后時(shí)鐘繼續(xù)輸出MISO 上就會(huì)依次返回?cái)?shù)據(jù)。地址自動(dòng)遞增可以一直讀到 CS 拉高。寫(xiě)操作的時(shí)序稍微復(fù)雜一點(diǎn)CS 拉低發(fā)送 WREN 指令0x06CS 拉高然后 CS 再拉低發(fā)送 WRITE 指令0x02發(fā)送 3 字節(jié)地址發(fā)送數(shù)據(jù)CS 拉高。注意 WREN 和 WRITE 之間必須拉高 CS這是 SPI Flash 和 MRAM 的通用規(guī)則目的是讓芯片內(nèi)部狀態(tài)機(jī)識(shí)別到指令邊界。3.3 底層讀寫(xiě)函數(shù)實(shí)現(xiàn)基于 HAL 庫(kù)我封裝了幾個(gè)底層函數(shù)。先看寫(xiě)使能函數(shù)void MRAM_WriteEnable(void) { uint8_t cmd 0x06; MRAM_CS_LOW(); HAL_SPI_Transmit(hspi1, cmd, 1, 100); MRAM_CS_HIGH(); }讀狀態(tài)寄存器函數(shù)uint8_t MRAM_ReadStatus(void) { uint8_t cmd 0x05; uint8_t status 0; MRAM_CS_LOW(); HAL_SPI_Transmit(hspi1, cmd, 1, 100); HAL_SPI_Receive(hspi1, status, 1, 100); MRAM_CS_HIGH(); return status; }寫(xiě)數(shù)據(jù)函數(shù)void MRAM_Write(uint32_t addr, uint8_t *data, uint16_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, 100); HAL_SPI_Transmit(hspi1, data, len, 1000); MRAM_CS_HIGH(); }讀數(shù)據(jù)函數(shù)void MRAM_Read(uint32_t addr, uint8_t *data, uint16_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, 100); HAL_SPI_Receive(hspi1, data, len, 1000); MRAM_CS_HIGH(); }這幾個(gè)函數(shù)是驅(qū)動(dòng)的基礎(chǔ)實(shí)際項(xiàng)目里我會(huì)再加一層封裝比如按結(jié)構(gòu)體讀寫(xiě)、帶 CRC 校驗(yàn)的讀寫(xiě)、環(huán)形緩沖區(qū)管理等。但底層這幾個(gè)函數(shù)必須保證穩(wěn)定可靠因?yàn)樗猩蠈硬僮鞫家蕾?lài)它們。實(shí)操心得HAL_SPI_Transmit 和 HAL_SPI_Receive 的超時(shí)參數(shù)不要設(shè)太小。我一開(kāi)始設(shè)了 10ms結(jié)果在 24MHz 下連續(xù)寫(xiě) 256 字節(jié)時(shí)偶爾超時(shí)。后來(lái)改成 1000ms問(wèn)題消失。原因是 HAL 庫(kù)在傳輸過(guò)程中有循環(huán)等待如果中斷頻繁打斷實(shí)際耗時(shí)可能比理論值長(zhǎng)很多。4. 數(shù)據(jù)管理策略讓 MRAM 發(fā)揮最大價(jià)值4.1 參數(shù)存儲(chǔ)的分區(qū)設(shè)計(jì)512KB 的 MRAM 看起來(lái)不大但在參數(shù)存儲(chǔ)場(chǎng)景里綽綽有余。我一般會(huì)把 MRAM 分成幾個(gè)區(qū)域來(lái)管理區(qū)域名稱(chēng)起始地址大小用途系統(tǒng)參數(shù)區(qū)0x000004KB設(shè)備配置、校準(zhǔn)參數(shù)用戶(hù)參數(shù)區(qū)0x010004KB用戶(hù)設(shè)置、偏好日志區(qū)0x02000256KB運(yùn)行日志、事件記錄數(shù)據(jù)緩沖區(qū)0x42000252KB采集數(shù)據(jù)、掉電保護(hù)系統(tǒng)參數(shù)區(qū)和用戶(hù)參數(shù)區(qū)各留 4KB足夠存儲(chǔ)幾百個(gè)參數(shù)。日志區(qū)用環(huán)形緩沖區(qū)的方式管理寫(xiě)滿(mǎn)后從頭覆蓋。數(shù)據(jù)緩沖區(qū)用于掉電保護(hù)平時(shí)可以當(dāng)作普通 RAM 使用掉電時(shí)把關(guān)鍵數(shù)據(jù)寫(xiě)進(jìn)去。分區(qū)設(shè)計(jì)的好處是隔離性好日志寫(xiě)壞了不會(huì)影響參數(shù)區(qū)參數(shù)區(qū)更新也不會(huì)干擾日志記錄。而且每個(gè)區(qū)域可以獨(dú)立做 CRC 校驗(yàn)出問(wèn)題時(shí)容易定位。4.2 掉電保護(hù)的數(shù)據(jù)完整性保障掉電保護(hù)是 MRAM 最核心的應(yīng)用場(chǎng)景之一。我設(shè)計(jì)過(guò)一個(gè)方案實(shí)測(cè)在工業(yè)現(xiàn)場(chǎng)跑了兩年多沒(méi)有丟過(guò)一次數(shù)據(jù)。核心思路是雙備份CRC寫(xiě)入確認(rèn)。具體做法是每個(gè)參數(shù)塊存儲(chǔ)兩份地址 A 和地址 B。寫(xiě)入時(shí)先寫(xiě) A讀回校驗(yàn) CRC確認(rèn)無(wú)誤后再寫(xiě) B。讀取時(shí)先讀 ACRC 校驗(yàn)通過(guò)就用 A否則讀 B。如果 A 和 B 都?jí)牧瞬攀褂媚J(rèn)值。這個(gè)方案的關(guān)鍵在于寫(xiě)入順序。必須先寫(xiě) A 再寫(xiě) B而且兩次寫(xiě)入之間要有一個(gè)提交標(biāo)志。我在每個(gè)參數(shù)塊前面加一個(gè) 4 字節(jié)的頭部包含一個(gè)序列號(hào)和一個(gè)狀態(tài)標(biāo)志。寫(xiě)入流程是讀取當(dāng)前序列號(hào)加 1構(gòu)造新數(shù)據(jù)塊頭部狀態(tài)標(biāo)志設(shè)為寫(xiě)入中寫(xiě)入 A 區(qū)讀回 A 區(qū)校驗(yàn) CRC如果校驗(yàn)通過(guò)把 A 區(qū)頭部狀態(tài)改為有效寫(xiě)入 B 區(qū)重復(fù)步驟 3-5這樣即使在第 3 步到第 5 步之間掉電A 區(qū)的狀態(tài)標(biāo)志還是寫(xiě)入中讀取時(shí)會(huì)跳過(guò) A 區(qū)使用 B 區(qū)的舊數(shù)據(jù)。數(shù)據(jù)完整性得到了保障。注意MRAM 的寫(xiě)入雖然是即時(shí)的但 SPI 傳輸需要時(shí)間。在掉電窗口內(nèi)要確保 SPI 傳輸能完成。我的做法是在檢測(cè)到掉電后立即把 SPI 時(shí)鐘降到最低比如 1MHz犧牲速度換穩(wěn)定性。1MHz 下寫(xiě) 256 字節(jié)需要約 2ms1000μF 電容的 15ms 窗口完全夠用。4.3 日志系統(tǒng)的環(huán)形緩沖區(qū)實(shí)現(xiàn)日志區(qū)用環(huán)形緩沖區(qū)管理每個(gè)日志條目包含時(shí)間戳、事件類(lèi)型、數(shù)據(jù)長(zhǎng)度和 CRC。寫(xiě)入時(shí)從當(dāng)前寫(xiě)指針位置開(kāi)始寫(xiě)滿(mǎn)后回到開(kāi)頭覆蓋最舊的日志。環(huán)形緩沖區(qū)的關(guān)鍵參數(shù)是寫(xiě)指針和讀指針。寫(xiě)指針指向下一個(gè)可寫(xiě)位置讀指針指向最舊的未讀日志。為了避免讀寫(xiě)沖突我用了寫(xiě)指針原子更新的策略每次寫(xiě)入完成后才更新寫(xiě)指針。這樣即使寫(xiě)入過(guò)程中掉電寫(xiě)指針仍然指向舊位置不會(huì)產(chǎn)生半截日志。日志條目的結(jié)構(gòu)定義如下typedef struct { uint32_t timestamp; uint16_t event_type; uint8_t data_len; uint8_t reserved; uint8_t data[32]; uint16_t crc; } LogEntry_t;每個(gè)條目 44 字節(jié)256KB 的日志區(qū)可以存約 5800 條日志。對(duì)于大多數(shù)工業(yè)設(shè)備這個(gè)容量可以記錄幾個(gè)月的運(yùn)行日志。讀取日志時(shí)從讀指針開(kāi)始逐條讀取并校驗(yàn) CRC。如果 CRC 不通過(guò)說(shuō)明這條日志寫(xiě)壞了跳過(guò)繼續(xù)讀下一條。這樣可以保證即使有個(gè)別日志損壞也不會(huì)影響整體日志的讀取。5. 性能實(shí)測(cè)與優(yōu)化技巧5.1 讀寫(xiě)速度實(shí)測(cè)數(shù)據(jù)我在 STM32F031C6 MR25H40CDF 的平臺(tái)上做了一組性能測(cè)試SPI 時(shí)鐘 24MHz測(cè)試結(jié)果如下操作類(lèi)型數(shù)據(jù)量耗時(shí)吞吐率單字節(jié)寫(xiě)1B2.1μs0.48MB/s連續(xù)寫(xiě)256B92μs2.78MB/s連續(xù)寫(xiě)4KB1.42ms2.88MB/s單字節(jié)讀1B1.8μs0.56MB/s連續(xù)讀256B88μs2.91MB/s連續(xù)讀4KB1.38ms2.97MB/s從數(shù)據(jù)可以看出連續(xù)讀寫(xiě)的吞吐率接近 3MB/s已經(jīng)接近 24MHz SPI 的理論上限3MB/s。單字節(jié)操作的效率較低因?yàn)橹噶詈偷刂烽_(kāi)銷(xiāo)占了很大比例。所以實(shí)際使用中盡量批量讀寫(xiě)減少單字節(jié)操作。5.2 DMA 加速與 CPU 占用率優(yōu)化在數(shù)據(jù)采集場(chǎng)景里CPU 需要同時(shí)處理 ADC 采樣、算法運(yùn)算和存儲(chǔ)寫(xiě)入。如果 SPI 寫(xiě)入占用太多 CPU 時(shí)間會(huì)影響實(shí)時(shí)性。用 DMA 可以大幅降低 CPU 占用率。STM32F031C6 的 DMA 控制器有 5 個(gè)通道SPI1 的 TX 和 RX 各占一個(gè)。配置 DMA 后SPI 傳輸由 DMA 控制器接管CPU 只需要在傳輸完成后處理中斷即可。我實(shí)測(cè)過(guò)一組對(duì)比數(shù)據(jù)在 24MHz SPI 時(shí)鐘下連續(xù)寫(xiě)入 4KB 數(shù)據(jù)輪詢(xún)方式 CPU 占用率約 85%DMA 方式 CPU 占用率約 12%。差距非常明顯。對(duì)于需要同時(shí)跑控制算法的場(chǎng)景DMA 幾乎是必選項(xiàng)。DMA 配置的代碼大概長(zhǎng)這樣void SPI1_DMA_Init(void) { __HAL_RCC_DMA1_CLK_ENABLE(); hdma_spi1_tx.Instance DMA1_Channel3; hdma_spi1_tx.Init.Direction DMA_MEMORY_TO_PERIPH; hdma_spi1_tx.Init.PeriphInc DMA_PINC_DISABLE; hdma_spi1_tx.Init.MemInc DMA_MINC_ENABLE; hdma_spi1_tx.Init.PeriphDataAlignment DMA_PDATAALIGN_BYTE; hdma_spi1_tx.Init.MemDataAlignment DMA_MDATAALIGN_BYTE; hdma_spi1_tx.Init.Mode DMA_NORMAL; hdma_spi1_tx.Init.Priority DMA_PRIORITY_HIGH; HAL_DMA_Init(hdma_spi1_tx); __HAL_LINKDMA(hspi1, hdmatx, hdma_spi1_tx); }實(shí)操心得DMA 傳輸時(shí)CS 信號(hào)的控制要特別注意。HAL 庫(kù)的 HAL_SPI_Transmit_DMA 函數(shù)在傳輸完成后會(huì)觸發(fā)回調(diào)但 CS 的拉高時(shí)機(jī)需要自己控制。我一般會(huì)在 DMA 傳輸完成回調(diào)里拉高 CS確保數(shù)據(jù)全部發(fā)完。如果提前拉高 CS最后幾個(gè)字節(jié)可能丟失。5.3 低功耗場(chǎng)景下的存儲(chǔ)策略工業(yè)傳感器節(jié)點(diǎn)往往用電池供電功耗是核心指標(biāo)。STM32F031C6 和 MR25H40CDF 都支持低功耗模式配合使用可以做到很低的平均功耗。STM32F031C6 的 Stop 模式電流約 1μAStandby 模式約 0.5μA。MR25H40CDF 的待機(jī)電流約 10μA讀寫(xiě)電流約 5mA。所以策略是平時(shí) MCU 進(jìn) Stop 模式MRAM 保持待機(jī)需要存儲(chǔ)時(shí)喚醒 MCU完成寫(xiě)入后立即回到低功耗模式。我實(shí)測(cè)過(guò)一個(gè)數(shù)據(jù)采集節(jié)點(diǎn)的功耗每 10 秒采集一次數(shù)據(jù)寫(xiě)入 MRAM然后休眠。平均電流約 15μA用 2000mAh 的鋰電池可以跑 15 年以上。當(dāng)然實(shí)際中還有傳感器和其他外設(shè)的功耗但存儲(chǔ)子系統(tǒng)的功耗已經(jīng)可以忽略不計(jì)了。低功耗場(chǎng)景下有個(gè)細(xì)節(jié)要注意MRAM 的 CS 引腳在 MCU 休眠時(shí)要保持高電平否則 MRAM 可能進(jìn)入異常狀態(tài)。我一般會(huì)在 CS 上加一個(gè) 100kΩ 的上拉電阻確保 MCU 引腳高阻時(shí) CS 仍然為高。6. 常見(jiàn)問(wèn)題排查與避坑指南6.1 SPI 通信失敗的問(wèn)題定位SPI 通信失敗是最常見(jiàn)的問(wèn)題表現(xiàn)是讀不到數(shù)據(jù)、讀到的數(shù)據(jù)全是 0xFF 或 0x00、寫(xiě)入后讀回不一致等。排查思路可以按以下順序進(jìn)行第一步檢查硬件連接。用萬(wàn)用表量一下 CS、SCK、MOSI、MISO 四根線的通斷確認(rèn)沒(méi)有虛焊。然后量一下 MRAM 的 VDD 引腳確認(rèn)供電正常。這一步看似簡(jiǎn)單但我遇到過(guò)好幾次問(wèn)題都是因?yàn)?VDD 虛焊。第二步用示波器看波形。CS 拉低時(shí)SCK 應(yīng)該有 8 個(gè)或更多時(shí)鐘脈沖。如果 SCK 沒(méi)有波形說(shuō)明 SPI 外設(shè)沒(méi)配置好。如果 SCK 有波形但 MISO 一直是高電平說(shuō)明 MRAM 沒(méi)有響應(yīng)可能是 CS 沒(méi)拉低或者 MRAM 損壞。第三步讀設(shè)備 ID。MR25H40CDF 的 RDID 指令0x9F會(huì)返回一個(gè) 3 字節(jié)的 ID正常應(yīng)該是 0xE5 0x40 0x13 之類(lèi)的值。如果讀不到正確的 ID說(shuō)明底層通信有問(wèn)題先別急著調(diào)上層邏輯。第四步檢查 SPI 模式。MRAM 支持 Mode 0 和 Mode 3如果 MCU 配置成 Mode 1 或 Mode 2通信會(huì)失敗。這個(gè)坑我踩過(guò)當(dāng)時(shí)調(diào)了半天最后發(fā)現(xiàn)是 CubeMX 里 CLK Phase 選錯(cuò)了。6.2 數(shù)據(jù)寫(xiě)入后讀回不一致的排查寫(xiě)入后讀回不一致可能的原因有幾個(gè)一是寫(xiě)使能沒(méi)有正確發(fā)送。MRAM 每次寫(xiě)入前必須發(fā) WREN 指令如果 WREN 發(fā)送失敗或者被中斷打斷寫(xiě)入操作會(huì)被忽略。排查方法是寫(xiě)完后讀狀態(tài)寄存器看 WEL 位是否被清零。二是 CS 時(shí)序不對(duì)。WREN 和 WRITE 之間必須拉高 CS如果 CS 一直保持低電平MRAM 會(huì)把 WREN 和 WRITE 當(dāng)作一個(gè)連續(xù)指令導(dǎo)致寫(xiě)入失敗。這個(gè)細(xì)節(jié)在規(guī)格書(shū)里有明確說(shuō)明但很容易被忽略。三是電源干擾。在電機(jī)、繼電器等大功率設(shè)備附近電源紋波可能導(dǎo)致 MRAM 寫(xiě)入出錯(cuò)。解決方法是在 MRAM 的 VDD 和 GND 之間加一個(gè) 0.1μF 加 1μF 的電容組合必要時(shí)再加一個(gè)磁珠隔離。四是 SPI 時(shí)鐘太快。雖然 MR25H40CDF 標(biāo)稱(chēng)支持 40MHz但在實(shí)際 PCB 上走線長(zhǎng)度、負(fù)載電容等因素會(huì)影響信號(hào)完整性。如果 24MHz 下不穩(wěn)定可以降到 12MHz 試試。我有個(gè)項(xiàng)目因?yàn)?PCB 走線太長(zhǎng)最后降到 8MHz 才穩(wěn)定。6.3 常見(jiàn)問(wèn)題速查表現(xiàn)象可能原因排查方法解決方案讀不到設(shè)備 IDSPI 模式錯(cuò)誤檢查 CLK Polarity/Phase改為 Mode 0讀到的數(shù)據(jù)全 0xFFMISO 未連接量 MISO 通斷重新焊接寫(xiě)入后讀回不一致WREN 未發(fā)送讀狀態(tài)寄存器 WEL 位確保 WREN 后 CS 拉高偶發(fā)數(shù)據(jù)錯(cuò)亂電源干擾示波器看 VDD 紋波加去耦電容高速下通信失敗信號(hào)完整性差降低 SPI 時(shí)鐘測(cè)試降頻或改 PCBCS 拉低無(wú)響應(yīng)CS 引腳配置錯(cuò)誤量 CS 電平改為推挽輸出寫(xiě)入超時(shí)HAL 庫(kù)超時(shí)設(shè)置太短增大超時(shí)參數(shù)改為 1000ms避坑技巧如果你在調(diào)試時(shí)實(shí)在找不到問(wèn)題可以先用一個(gè)簡(jiǎn)單的 GPIO 模擬 SPI 時(shí)序逐位發(fā)送指令用示波器觀察每一位的波形。這樣可以排除 SPI 外設(shè)配置的問(wèn)題快速定位是硬件還是軟件的問(wèn)題。我每次遇到疑難雜癥最后都是用這招解決的。7. 項(xiàng)目實(shí)戰(zhàn)工業(yè)數(shù)據(jù)采集器的存儲(chǔ)方案7.1 需求分析與方案設(shè)計(jì)我接手過(guò)一個(gè)工業(yè)振動(dòng)采集器的項(xiàng)目需求是每 1ms 采集一次三軸加速度數(shù)據(jù)每次 6 字節(jié)連續(xù)采集 10 分鐘數(shù)據(jù)不能丟。算一下數(shù)據(jù)量1ms 一次10 分鐘是 600 秒總共 60 萬(wàn)次采樣每次 6 字節(jié)總共 3.6MB。MRAM 只有 512KB存不下全部數(shù)據(jù)。所以方案要分兩級(jí)MRAM 作為一級(jí)緩沖存最近的數(shù)據(jù)外部 SD 卡或者大容量 Flash 作為二級(jí)存儲(chǔ)定期把 MRAM 里的數(shù)據(jù)轉(zhuǎn)存過(guò)去。MRAM 的作用是削峰填谷在 SD 卡寫(xiě)入的間隙緩存數(shù)據(jù)保證不丟采樣點(diǎn)。具體設(shè)計(jì)是MRAM 分成兩個(gè) 256KB 的緩沖區(qū)交替使用。當(dāng)緩沖區(qū) A 寫(xiě)滿(mǎn)時(shí)切換 to 緩沖區(qū) B同時(shí)把 A 的數(shù)據(jù)通過(guò) DMA 轉(zhuǎn)存到 SD 卡。SD 卡寫(xiě)入速度約 2MB/s256KB 需要 128ms。在這 128ms 內(nèi)緩沖區(qū) B 可以繼續(xù)接收數(shù)據(jù)不會(huì)丟點(diǎn)。7.2 關(guān)鍵代碼實(shí)現(xiàn)與調(diào)試過(guò)程數(shù)據(jù)采集用 ADC DMA 定時(shí)器觸發(fā)采樣數(shù)據(jù)直接通過(guò) DMA 寫(xiě)入 MRAM 的緩沖區(qū)。核心代碼是 MRAM 的 DMA 寫(xiě)入函數(shù)void MRAM_Write_DMA(uint32_t addr, uint8_t *data, uint16_t len, void (*callback)(void)) { 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, 100); HAL_SPI_Transmit_DMA(hspi1, data, len); // 在 DMA 完成回調(diào)里拉高 CS 并調(diào)用 callback }調(diào)試過(guò)程中遇到的最大問(wèn)題是 DMA 傳輸和 CS 控制的時(shí)序配合。HAL 庫(kù)的 DMA 完成回調(diào)是在傳輸結(jié)束后觸發(fā)的但 SPI 的最后一個(gè)字節(jié)可能還在移位寄存器里。如果立即拉高 CS最后一個(gè)字節(jié)會(huì)丟失。解決方法是在 DMA 完成回調(diào)里先等 1μs再拉高 CS。這個(gè) 1μs 的延時(shí)看起來(lái)不起眼但少了它數(shù)據(jù)就是錯(cuò)的。另一個(gè)問(wèn)題是緩沖區(qū)切換的原子性。如果切換過(guò)程中來(lái)了中斷可能導(dǎo)致數(shù)據(jù)寫(xiě)到錯(cuò)誤的緩沖區(qū)。我的做法是在切換時(shí)關(guān)中斷切換完成后立即開(kāi)中斷。關(guān)中斷的時(shí)間只有幾個(gè)時(shí)鐘周期不會(huì)影響其他中斷的響應(yīng)。7.3 實(shí)測(cè)結(jié)果與長(zhǎng)期運(yùn)行穩(wěn)定性這個(gè)方案在實(shí)際現(xiàn)場(chǎng)跑了半年多采集了上千組數(shù)據(jù)沒(méi)有丟過(guò)一個(gè)采樣點(diǎn)。MRAM 的寫(xiě)入速度完全跟得上 1ms 的采樣周期SD 卡轉(zhuǎn)存也沒(méi)有成為瓶頸。長(zhǎng)期運(yùn)行下來(lái)MRAM 的穩(wěn)定性非常好。我特意做了個(gè)測(cè)試連續(xù)寫(xiě)入 100 萬(wàn)次讀回校驗(yàn)全部通過(guò)。規(guī)格書(shū)標(biāo)稱(chēng)的 10^14 次擦寫(xiě)壽命在實(shí)際使用中根本用不完。按每天寫(xiě)入 100 萬(wàn)次計(jì)算可以用 270 年。溫度方面設(shè)備工作在 -20℃ 到 60℃ 的環(huán)境里MRAM 的數(shù)據(jù)保持能力沒(méi)有出現(xiàn)任何問(wèn)題。我做過(guò)高低溫循環(huán)測(cè)試-40℃ 到 85℃ 循環(huán) 100 次數(shù)據(jù)依然完好。8. 選型對(duì)比與方案擴(kuò)展8.1 MRAM vs FRAM vs EEPROM vs Flash在工業(yè)存儲(chǔ)方案里MRAM 不是唯一的選擇。FRAM鐵電存儲(chǔ)器也是非易失、高速、高壽命的存儲(chǔ)介質(zhì)。我把幾種方案放在一起對(duì)比特性MRAMFRAMEEPROMNOR Flash寫(xiě)入速度納秒級(jí)納秒級(jí)毫秒級(jí)毫秒級(jí)擦寫(xiě)壽命10^1410^1410^610^5寫(xiě)入前擦除不需要不需要不需要需要容量最大 16Mb最大 4Mb最大 2Mb最大 1Gb功耗中低中高成本高中低低溫度范圍-40~105℃-40~85℃-40~85℃-40~85℃從對(duì)比可以看出MRAM 的優(yōu)勢(shì)在于容量比 FRAM 大、溫度范圍更寬劣勢(shì)是成本較高。FRAM 在低功耗場(chǎng)景下更有優(yōu)勢(shì)但容量受限。EEPROM 和 Flash 適合對(duì)成本敏感、寫(xiě)入頻率不高的場(chǎng)景。選型的核心邏輯是如果寫(xiě)入頻繁、要求高可靠性、容量需求在 512KB 以?xún)?nèi)MRAM 是最優(yōu)解。如果功耗極其敏感、容量需求小FRAM 更合適。如果只是存?zhèn)€配置參數(shù)、寫(xiě)入次數(shù)不多EEPROM 就夠了。8.2 從單機(jī)存儲(chǔ)到分布式存儲(chǔ)的擴(kuò)展思路單個(gè) MRAM 的容量有限如果項(xiàng)目需要更大的存儲(chǔ)空間可以考慮多片 MRAM 級(jí)聯(lián)。MR25H40CDF 支持 SPI 總線共享用不同的 CS 引腳選擇不同的芯片。STM32F031C6 的 GPIO 足夠驅(qū)動(dòng) 4 到 8 片 MRAM總?cè)萘靠梢詳U(kuò)展到 2MB 到 4MB。多片級(jí)聯(lián)的軟件設(shè)計(jì)需要一個(gè)地址映射層。上層應(yīng)用調(diào)用統(tǒng)一的讀寫(xiě)接口底層根據(jù)地址范圍選擇對(duì)應(yīng)的 CS 和芯片。這樣上層代碼不需要關(guān)心具體有幾片 MRAM擴(kuò)展起來(lái)很方便。如果還需要更大容量可以考慮 MRAM SD 卡的混合方案。MRAM 作為高速緩存SD 卡作為大容量存儲(chǔ)。數(shù)據(jù)先寫(xiě)入 MRAM再批量轉(zhuǎn)存到 SD 卡。這樣既保證了寫(xiě)入速度又滿(mǎn)足了容量需求。擴(kuò)展思路MRAM 的另一個(gè)有趣應(yīng)用是做內(nèi)存鏡像。在系統(tǒng)運(yùn)行時(shí)把關(guān)鍵變量同時(shí)存在 SRAM 和 MRAM 里。掉電后重啟從 MRAM 恢復(fù)變量值實(shí)現(xiàn)斷電續(xù)跑。這個(gè)方案在工業(yè)控制器的狀態(tài)保持場(chǎng)景里非常有用我后續(xù)會(huì)單獨(dú)寫(xiě)一篇來(lái)展開(kāi)。9. 寫(xiě)在最后的一些實(shí)操體會(huì)這個(gè)組合我用了兩年多從最初的評(píng)估板到量產(chǎn)產(chǎn)品踩過(guò)的坑不算少但整體來(lái)說(shuō) MR25H40CDF STM32F031C6 是一套非??煽康拇鎯?chǔ)方案。如果你正在選型工業(yè)嵌入式項(xiàng)目的存儲(chǔ)介質(zhì)我建議先拿幾片樣品在真實(shí)環(huán)境里跑一跑特別是高低溫循環(huán)和電源波動(dòng)測(cè)試這兩項(xiàng)最能暴露問(wèn)題。有個(gè)細(xì)節(jié)我一開(kāi)始沒(méi)注意MRAM 的 CS 引腳在 PCB 上最好串聯(lián)一個(gè) 22Ω 的電阻。這個(gè)電阻可以抑制信號(hào)反射在長(zhǎng)走線或者多從機(jī)場(chǎng)景下特別有用。我有個(gè)項(xiàng)目因?yàn)?CS 走線太長(zhǎng)加了電阻之后通信穩(wěn)定性明顯提升。另外MRAM 的寫(xiě)入雖然不需要擦除但并不意味著可以無(wú)限制地隨意寫(xiě)。雖然壽命是 10^14 次但如果你的應(yīng)用每秒寫(xiě)幾萬(wàn)次幾年下來(lái)也會(huì)接近上限。所以日志系統(tǒng)還是建議用環(huán)形緩沖區(qū)避免對(duì)同一地址反復(fù)寫(xiě)入。最后說(shuō)個(gè)成本相關(guān)的事。MR25H40CDF 的價(jià)格比同容量的 SPI Flash 貴不少批量采購(gòu)的話大概是 Flash 的 5 到 8 倍。所以選型時(shí)要算清楚賬如果只是存配置參數(shù)一年寫(xiě)不了幾次用 Flash 就夠了如果是高頻寫(xiě)入、掉電保護(hù)、高可靠性場(chǎng)景MRAM 多出來(lái)的成本是值得的。我一般會(huì)建議客戶(hù)在關(guān)鍵數(shù)據(jù)存儲(chǔ)上用 MRAM非關(guān)鍵數(shù)據(jù)用 Flash混合方案性?xún)r(jià)比最高。