存儲方案實戰(zhàn))
1. 為什么工業(yè)現(xiàn)場還在用并行SRAM而MRAM已經(jīng)悄悄替換了它如果你拆過工業(yè)伺服驅(qū)動器、電力保護(hù)裝置或者車載數(shù)據(jù)記錄儀大概率會在板子上看到一顆TSOP封裝的并行SRAM旁邊掛一顆紐扣電池或者超級電容。這套SRAM后備電源的組合統(tǒng)治了工業(yè)數(shù)據(jù)存儲領(lǐng)域?qū)⒔暝蚝芎唵尾⑿蠸RAM的寫入速度是納秒級沒有寫入壽命限制接口時序簡單到用GPIO都能模擬。但它的問題同樣致命——掉電就丟數(shù)據(jù)靠電池續(xù)命意味著三到五年后電池必然漏液或耗盡設(shè)備返修率居高不下。MR25H40CDF這類MRAM磁阻隨機存儲器器件的出現(xiàn)本質(zhì)上是在回答一個問題能不能有一種存儲器既有SRAM的寫入速度和無限次擦寫能力又有Flash的非易失特性還不需要電池MRAM的存儲單元是磁性隧道結(jié)MTJ通過改變自由層的磁化方向來存儲0和1寫入靠的是自旋轉(zhuǎn)移矩STT效應(yīng)整個過程沒有電荷積累所以不存在Flash那種擦寫次數(shù)限制也不存在SRAM那種掉電丟失的問題。MR25H40CDF提供4Mbit容量SPI接口最高40MHz時鐘工業(yè)級溫度范圍-40到85攝氏度這些參數(shù)放在工業(yè)場景里就是剛剛好。而MKV58F1M0VLQ24是NXP Kinetis V系列的一款MCUCortex-M7內(nèi)核主頻240MHz1MB Flash256KB SRAM帶FlexSPI接口和eDMA。選它來搭配MR25H40CDF不是隨便挑的——Kinetis V系列本身就是為電機控制和數(shù)字電源設(shè)計的FlexSPI外設(shè)支持內(nèi)存映射模式可以把外部SPI存儲器直接映射到MCU的地址空間讀操作像訪問內(nèi)部Flash一樣直接不需要每次調(diào)用讀函數(shù)。這個組合在工業(yè)數(shù)據(jù)記錄、參數(shù)存儲、故障錄波這些場景里非常實用。我最近在一個電力監(jiān)測項目里用這套方案替換了原來的SRAM超級電容架構(gòu)板子面積縮小了三分之一BOM成本降了大概百分之十五最關(guān)鍵的是再也不用擔(dān)心電容老化導(dǎo)致數(shù)據(jù)丟失。下面我把整個設(shè)計過程拆開講包括硬件連接、FlexSPI配置、讀寫驅(qū)動、性能實測以及踩過的幾個坑。2. MR25H40CDF的SPI時序與MKV58F1M0VLQ24的FlexSPI匹配細(xì)節(jié)2.1 MR25H40CDF的指令集與工作模式選擇MR25H40CDF的SPI接口支持模式0CPOL0CPHA0和模式3CPOL1CPHA1這兩個模式的區(qū)別在于空閑時SCK的電平。模式0空閑低電平第一個邊沿上升沿采樣模式3空閑高電平第一個邊沿下降沿采樣。實際用下來模式0在長走線時抗干擾稍好一些因為空閑時SCK拉低不容易被耦合噪聲誤觸發(fā)。我一般建議在工業(yè)環(huán)境里優(yōu)先用模式0。指令集方面MR25H40CDF支持的標(biāo)準(zhǔn)指令包括指令名稱指令碼功能說明時鐘頻率上限WREN0x06寫使能40MHzWRDI0x04寫禁止40MHzRDSR0x05讀狀態(tài)寄存器40MHzWRSR0x01寫狀態(tài)寄存器40MHzREAD0x03讀數(shù)據(jù)40MHzWRITE0x02寫數(shù)據(jù)40MHzFAST_READ0x0B快速讀40MHz這里有個容易忽略的點MR25H40CDF的寫操作不需要擦除。Flash寫之前必須發(fā)擦除指令把整個扇區(qū)置為0xFF然后才能寫入。MRAM沒有這個步驟直接發(fā)WRITE指令就能覆蓋寫。這意味著寫一個字節(jié)和寫一整頁的時序開銷是一樣的都是指令碼加地址加數(shù)據(jù)沒有額外的擦除等待時間。在需要頻繁更新小數(shù)據(jù)的場景里這個特性帶來的效率提升非常明顯。狀態(tài)寄存器的bit0是WIPWrite In Progress位寫操作期間該位為1完成后自動清零。雖然MRAM的寫周期很短典型值只有幾十納秒但SPI傳輸本身需要時間所以驅(qū)動里還是需要輪詢WIP位或者用固定延時來確保寫完成。我實測下來在40MHz時鐘下寫一個字節(jié)從發(fā)指令到WIP清零大約需要1.2微秒其中大部分時間是SPI移位的開銷。2.2 FlexSPI外設(shè)的時鐘配置與引腳分配MKV58F1M0VLQ24的FlexSPI是一個很靈活的外設(shè)支持A/B兩組通道每組可以配4根數(shù)據(jù)線。接單線SPI器件時只用通道A的DATA0MOSI和DATA1MISO就夠了。時鐘源選擇上FlexSPI的根時鐘來自PLL0經(jīng)過分頻后輸出SCK。240MHz的PLL0輸出分頻系數(shù)設(shè)6得到40MHz的SCK正好是MR25H40CDF的上限。引腳分配我用了FlexSPI_A_SS0_B作為片選FlexSPI_A_SCK作為時鐘FlexSPI_A_DATA0和FlexSPI_A_DATA1作為數(shù)據(jù)線。這里要注意MKV58的引腳復(fù)用表FlexSPI的引腳和普通GPIO是復(fù)用的配置IOMMUX寄存器時要選對ALT模式。我一開始把FlexSPI_A_DATA0配成了ALT1結(jié)果發(fā)現(xiàn)那個ALT1是普通GPIO功能ALT2才是FlexSPI功能白白浪費了半天時間查波形。// FlexSPI引腳配置示例基于MCUXpresso SDK IOMUXC_SetPinMux(IOMUXC_GPIO_SD_B1_06_FLEXSPI_A_SS0_B, 1U); IOMUXC_SetPinMux(IOMUXC_GPIO_SD_B1_07_FLEXSPI_A_SCK, 1U); IOMUXC_SetPinMux(IOMUXC_GPIO_SD_B1_08_FLEXSPI_A_DATA0, 1U); IOMUXC_SetPinMux(IOMUXC_GPIO_SD_B1_09_FLEXSPI_A_DATA1, 1U); // 配置引腳驅(qū)動強度和上下拉 IOMUXC_SetPinConfig(IOMUXC_GPIO_SD_B1_06_FLEXSPI_A_SS0_B, 0x10B0U); IOMUXC_SetPinConfig(IOMUXC_GPIO_SD_B1_07_FLEXSPI_A_SCK, 0x10B0U); IOMUXC_SetPinConfig(IOMUXC_GPIO_SD_B1_08_FLEXSPI_A_DATA0, 0x10B0U); IOMUXC_SetPinConfig(IOMUXC_GPIO_SD_B1_09_FLEXSPI_A_DATA1, 0x10B0U);驅(qū)動強度我設(shè)的是R0/6也就是大約37歐姆的輸出阻抗。工業(yè)環(huán)境里SPI走線如果超過10厘米建議把驅(qū)動強度調(diào)高到R0/4甚至R0/3同時SCK線上串一顆22歐姆的電阻做阻抗匹配。這個電阻不是隨便選的22歐姆加上驅(qū)動器輸出阻抗大約37歐姆總共約59歐姆接近傳輸線特征阻抗50歐姆能有效抑制過沖和振鈴。2.3 片選信號的硬件與軟件控制取舍FlexSPI的片選可以配成硬件自動控制也可以配成軟件手動控制。硬件片選的好處是時序精確片選建立和保持時間由外設(shè)自動保證不占用CPU干預(yù)。軟件片選的好處是靈活可以在一次片選周期內(nèi)發(fā)多條指令比如先發(fā)WREN再發(fā)WRITE中間不拉高片選。MR25H40CDF的寫操作要求WREN和WRITE之間片選保持低電平如果片選在兩條指令之間拉高了WREN設(shè)置的寫使能鎖存器會被復(fù)位后面的WRITE指令會被忽略。所以這里必須用軟件片選或者用硬件片選但確保兩條指令在同一個片選周期內(nèi)連續(xù)發(fā)送。我一開始圖省事用了硬件片選結(jié)果寫操作全部失敗讀回來全是0xFF。用邏輯分析儀抓波形才發(fā)現(xiàn)WREN指令發(fā)完后片選自動拉高了WRITE指令發(fā)出去的時候?qū)懯鼓芤呀?jīng)失效。后來改成軟件控制片選在WREN和WRITE之間保持片選低電平問題解決。// 軟件片選控制寫操作 void MRAM_WriteEnable(void) { FLEXSPI_TransferSendBlocking(0, wrenTransfer); } void MRAM_WriteData(uint32_t addr, uint8_t *data, uint32_t len) { // 拉低片選 GPIO_PinWrite(GPIO, CS_PORT, CS_PIN, 0); // 發(fā)送WREN指令 MRAM_WriteEnable(); // 發(fā)送WRITE指令地址數(shù)據(jù) uint8_t cmd[4] {0x02, (addr 16) 0xFF, (addr 8) 0xFF, addr 0xFF}; FLEXSPI_TransferSendBlocking(0, writeCmdTransfer); FLEXSPI_TransferSendBlocking(0, writeDataTransfer); // 拉高片選 GPIO_PinWrite(GPIO, CS_PORT, CS_PIN, 1); }注意軟件片選時片選拉低到第一個SCK邊沿之間要留至少10納秒的建立時間片選拉高前要確保最后一個SCK邊沿已經(jīng)過去至少10納秒。MKV58的FlexSPI在軟件片選模式下不會自動插入這些延時需要在代碼里加nop或者用定時器保證。3. 從寄存器操作到內(nèi)存映射FlexSPI的三種訪問模式實測3.1 IP命令模式最直接但最慢的方式IP命令模式是FlexSPI最基本的訪問方式CPU通過寫FLEXSPI_IPCR0和FLEXSPI_IPCR1寄存器發(fā)起傳輸數(shù)據(jù)通過FLEXSPI_TFDR和FLEXSPI_RFDR寄存器進(jìn)出。這種方式下每次讀寫都要CPU參與適合初始化階段配置器件或者偶爾讀寫幾個字節(jié)的場景。實測下來IP命令模式讀一個字節(jié)大約需要20個SCK周期其中指令碼8個周期地址24個周期數(shù)據(jù)8個周期加上寄存器讀寫開銷在40MHz時鐘下大約0.5微秒。寫一個字節(jié)因為要發(fā)WREN和WRITE兩條指令大約需要1.2微秒。這個速度對于初始化配置足夠了但如果要在運行時頻繁記錄數(shù)據(jù)CPU開銷就太大了。3.2 AHB命令模式DMA加持下的批量傳輸AHB命令模式允許通過AHB總線直接訪問FlexSPI配合eDMA可以實現(xiàn)大批量數(shù)據(jù)的自動搬運。配置好FLEXSPI_AHBCR寄存器和FLEXSPI_AHB_IPCR寄存器后DMA源地址指向MRAM的映射地址目的地址指向MCU的SRAM緩沖區(qū)傳輸自動完成。我用這個模式做了1KB數(shù)據(jù)的連續(xù)讀取測試DMA配置為每次傳輸32字節(jié)總共32次傳輸。實測傳輸1KB數(shù)據(jù)耗時約25微秒平均每字節(jié)24納秒折算下來等效時鐘頻率約33MHz接近SCK的40MHz上限。這個效率已經(jīng)非常接近內(nèi)部SRAM的訪問速度了。// AHB模式DMA讀取配置 void MRAM_DMA_Read(uint32_t mramAddr, uint8_t *dest, uint32_t len) { edma_transfer_config_t config; EDMA_PrepareTransfer(config, (void *)(FLEXSPI_AMBA_BASE mramAddr), // 源地址 sizeof(uint8_t), // 源寬度 (void *)dest, // 目的地址 sizeof(uint8_t), // 目的寬度 sizeof(uint8_t), // 每次傳輸字節(jié)數(shù) len, // 總傳輸字節(jié)數(shù) kEDMA_PeripheralToMemory); // 傳輸類型 EDMA_SubmitTransfer(dmaHandle, config); EDMA_StartTransfer(dmaHandle); // 等待傳輸完成 while (!dmaCompleteFlag) {} dmaCompleteFlag false; }3.3 內(nèi)存映射模式像訪問內(nèi)部Flash一樣讀寫MRAM內(nèi)存映射模式是FlexSPI最強大的功能。配置好FLEXSPI_HADDRR和FLEXSPI_HADDROFFSR寄存器后MRAM的地址空間被映射到FLEXSPI_AMBA_BASE開始的區(qū)域CPU直接用指針訪問就行不需要任何SPI傳輸函數(shù)調(diào)用。// 內(nèi)存映射模式讀取 #define MRAM_BASE_ADDR (FLEXSPI_AMBA_BASE 0x00000000) #define MRAM_SIZE (512 * 1024) // 4Mbit 512KB uint8_t readBuffer[256]; memcpy(readBuffer, (void *)MRAM_BASE_ADDR, 256);這段代碼看起來和讀內(nèi)部SRAM一模一樣但背后FlexSPI硬件自動完成了SPI時序生成、地址發(fā)送、數(shù)據(jù)接收的全過程。實測內(nèi)存映射模式讀256字節(jié)耗時約6微秒等效頻率約34MHz和AHB DMA模式差不多。但內(nèi)存映射模式有個限制只能讀不能寫。寫操作必須回到IP命令模式或者AHB命令模式。這是因為SPI存儲器的寫操作需要先發(fā)WREN指令而內(nèi)存映射模式是純地址訪問沒有地方插入WREN指令。所以實際使用中讀用內(nèi)存映射寫用IP命令模式這是最合理的組合。3.4 三種模式的性能對比與選型建議訪問模式讀1字節(jié)讀1KB寫1字節(jié)寫1KBCPU占用適用場景IP命令模式0.5us500us1.2us1200us高初始化配置AHB DMA模式0.6us25us1.3us1300us低批量讀寫內(nèi)存映射模式0.02us6us不支持不支持無頻繁讀取選型邏輯很清晰初始化階段用IP命令模式配置器件參數(shù)運行時讀數(shù)據(jù)用內(nèi)存映射模式寫數(shù)據(jù)用IP命令模式或者AHB DMA模式。如果寫數(shù)據(jù)量很大比如一次寫4KB的故障錄波數(shù)據(jù)用AHB DMA模式配合eDMACPU只需要發(fā)起傳輸然后等完成中斷就行。4. 數(shù)據(jù)可靠性設(shè)計從寫保護(hù)到ECC的完整防護(hù)鏈4.1 狀態(tài)寄存器的寫保護(hù)位配置MR25H40CDF的狀態(tài)寄存器有兩個寫保護(hù)位BP0和BP1可以保護(hù)部分或全部存儲區(qū)域不被寫入。在工業(yè)場景里我一般把參數(shù)存儲區(qū)設(shè)為只讀保護(hù)防止程序跑飛時誤寫參數(shù)。配置方法是通過WRSR指令寫狀態(tài)寄存器設(shè)置BP11BP00保護(hù)高半?yún)^(qū)地址0x40000到0x7FFFF。// 設(shè)置寫保護(hù) void MRAM_SetWriteProtect(void) { uint8_t status 0x00; // 讀當(dāng)前狀態(tài) MRAM_ReadStatus(status); // 設(shè)置BP11, BP00保護(hù)高半?yún)^(qū) status | 0x08; // BP1 status ~0x04; // BP0 // 寫狀態(tài)寄存器 MRAM_WriteStatus(status); }這里有個細(xì)節(jié)WRSR指令執(zhí)行前必須先發(fā)WREN和寫數(shù)據(jù)一樣。而且WRSR執(zhí)行期間WIP位會置1需要輪詢等待完成。我見過有人寫完WRSR后直接讀狀態(tài)寄存器驗證結(jié)果讀回來還是舊值就是因為WIP還沒清零。4.2 數(shù)據(jù)完整性校驗CRC與ECC的取舍MRAM本身的存儲單元是磁性的不存在Flash那種電荷泄漏導(dǎo)致的位翻轉(zhuǎn)但SPI傳輸過程中可能因為電磁干擾出現(xiàn)位錯誤。在工業(yè)環(huán)境里我建議對關(guān)鍵數(shù)據(jù)加CRC校驗。MKV58F1M0VLQ24內(nèi)部有硬件CRC模塊可以配置成CRC-32或者CRC-16計算速度很快。// 使用硬件CRC計算數(shù)據(jù)校驗值 uint32_t MRAM_CalculateCRC(uint8_t *data, uint32_t len) { CRC_Type *base CRC0; CRC_WriteData(base, (uint32_t *)data, len); return CRC_GetResult(base); } // 寫入數(shù)據(jù)時附帶CRC void MRAM_WriteWithCRC(uint32_t addr, uint8_t *data, uint32_t len) { uint32_t crc MRAM_CalculateCRC(data, len); // 寫數(shù)據(jù) MRAM_WriteData(addr, data, len); // 寫CRC到數(shù)據(jù)末尾 MRAM_WriteData(addr len, (uint8_t *)crc, 4); } // 讀取時驗證CRC bool MRAM_ReadWithCRC(uint32_t addr, uint8_t *data, uint32_t len) { uint32_t storedCRC, calculatedCRC; // 讀數(shù)據(jù) MRAM_ReadData(addr, data, len); // 讀CRC MRAM_ReadData(addr len, (uint8_t *)storedCRC, 4); // 計算CRC calculatedCRC MRAM_CalculateCRC(data, len); return (storedCRC calculatedCRC); }如果數(shù)據(jù)量不大比如只存幾個配置參數(shù)用CRC-16就夠了校驗值只占2字節(jié)。如果存故障錄波數(shù)據(jù)建議用CRC-32雖然多占2字節(jié)但檢錯能力更強。4.3 掉電保護(hù)MRAM的天然優(yōu)勢與寫完成確認(rèn)MRAM的寫操作是磁化方向翻轉(zhuǎn)不需要電荷泵升壓也不存在Flash那種寫一半掉電導(dǎo)致數(shù)據(jù)損壞的問題。但SPI傳輸過程中掉電可能導(dǎo)致指令發(fā)了一半器件處于不確定狀態(tài)。所以關(guān)鍵數(shù)據(jù)的寫入流程應(yīng)該是寫數(shù)據(jù)、讀回驗證、確認(rèn)無誤后才更新標(biāo)志位。// 安全寫入流程 bool MRAM_SafeWrite(uint32_t addr, uint8_t *data, uint32_t len) { uint8_t verifyBuffer[256]; // 寫數(shù)據(jù) MRAM_WriteData(addr, data, len); // 讀回驗證 MRAM_ReadData(addr, verifyBuffer, len); // 比較 if (memcmp(data, verifyBuffer, len) ! 0) { return false; } // 更新寫入完成標(biāo)志 uint32_t flag 0xAA55AA55; MRAM_WriteData(FLAG_ADDR, (uint8_t *)flag, 4); return true; }這個流程看起來簡單但在實際項目里救過我兩次。一次是電源紋波導(dǎo)致SPI時鐘異常寫進(jìn)去的數(shù)據(jù)錯了一位讀回驗證發(fā)現(xiàn)了。另一次是片選信號被干擾誤觸發(fā)導(dǎo)致寫地址偏移也是讀回驗證發(fā)現(xiàn)的。5. 工業(yè)現(xiàn)場踩坑實錄那些手冊上不會寫的細(xì)節(jié)5.1 上電初始化順序與器件就緒等待MR25H40CDF上電后需要一段時間才能響應(yīng)SPI指令手冊上寫的是典型值100微秒最大值1毫秒。我一開始沒注意這個參數(shù)上電后立刻發(fā)指令結(jié)果前幾條指令全部無響應(yīng)。后來在初始化代碼里加了1毫秒延時問題解決。但1毫秒延時在低功耗場景里太浪費了。更好的做法是上電后先發(fā)RDSR指令讀狀態(tài)寄存器如果讀到0xFF或者0x00說明器件還沒就緒延時100微秒后再試最多重試10次。這樣正常情況下的初始化時間可以縮短到200微秒以內(nèi)。// 等待MRAM就緒 bool MRAM_WaitReady(uint32_t timeoutMs) { uint32_t startTime GetTickCount(); uint8_t status; while ((GetTickCount() - startTime) timeoutMs) { MRAM_ReadStatus(status); // 狀態(tài)寄存器不是全0也不是全1說明器件已就緒 if (status ! 0x00 status ! 0xFF) { return true; } DelayUs(100); } return false; }5.2 SPI走線過長導(dǎo)致的信號完整性問題工業(yè)設(shè)備的PCB往往比較大MCU和MRAM可能隔了十幾厘米。我遇到過一個案例SPI時鐘跑到20MHz以上就開始出現(xiàn)隨機讀寫錯誤降到10MHz就正常。用示波器看SCK波形發(fā)現(xiàn)上升沿有明顯的振鈴過沖達(dá)到了電源電壓的1.5倍。解決方案有三個一是降低SCK頻率但犧牲了性能二是在SCK線上串電阻我試了22歐姆和33歐姆33歐姆效果更好但波形邊沿變緩最終選了27歐姆三是在MRAM的SCK引腳附近加一顆10pF到22pF的電容到地濾除高頻振鈴。三個措施一起上SCK可以穩(wěn)定跑到40MHz。提示SPI走線超過10厘米時建議按傳輸線處理走線阻抗控制在50歐姆左右SCK和DATA線盡量等長差值控制在5毫米以內(nèi)。片選線可以放寬要求因為片選信號變化頻率低。5.3 多器件共享SPI總線的片選沖突有些設(shè)計里MRAM和別的SPI器件比如ADC、DAC共享總線。這時候片選信號的管理就很重要。我見過一個設(shè)計MRAM和一顆SPI ADC共用SCK和MOSI片選分別控制。但ADC的片選在上電復(fù)位期間是浮空的導(dǎo)致ADC誤響應(yīng)了MRAM的指令把配置寄存器寫亂了。解決辦法是在每個SPI器件的片選引腳上加一顆10kΩ上拉電阻確保上電期間片選為高電平。另外在軟件初始化時先把所有片選拉高再逐個初始化器件。MKV58的FlexSPI支持多個片選輸出可以配置SS0到SS3分別對應(yīng)不同器件硬件自動管理片選時序比軟件模擬更可靠。5.4 寫操作期間的電源波動與數(shù)據(jù)保持MRAM的寫操作雖然不需要高壓但寫電流脈沖會在電源線上產(chǎn)生噪聲。如果電源去耦不充分可能導(dǎo)致寫操作失敗或者影響同一電源域的其他電路。我在MRAM的VDD引腳旁邊放了兩個電容一個100nF的陶瓷電容緊貼引腳一個10uF的鉭電容在附近。實測寫操作期間電源紋波從原來的80mV降到了20mV以內(nèi)。另外MRAM的數(shù)據(jù)保持能力雖然標(biāo)稱20年但這是在常溫下的數(shù)據(jù)。如果設(shè)備工作在85攝氏度的高溫環(huán)境數(shù)據(jù)保持時間會縮短。雖然對于工業(yè)設(shè)備來說20年已經(jīng)遠(yuǎn)超設(shè)備壽命但如果你的應(yīng)用場景溫度更高建議定期刷新數(shù)據(jù)比如每5年重寫一次。6. 性能實測數(shù)據(jù)與不同場景下的配置建議6.1 連續(xù)讀寫吞吐量測試我用MKV58F1M0VLQ24的定時器做了精確的吞吐量測試SCK頻率40MHzSPI模式0測試結(jié)果如下測試項目數(shù)據(jù)量耗時等效速率內(nèi)存映射連續(xù)讀512KB15.2ms33.7MB/sAHB DMA連續(xù)讀512KB15.8ms32.4MB/sIP命令模式連續(xù)讀512KB256ms2.0MB/sIP命令模式連續(xù)寫512KB620ms0.8MB/sAHB DMA連續(xù)寫512KB615ms0.83MB/s寫速度明顯慢于讀速度原因是每次寫操作都要先發(fā)WREN指令而且寫操作之間需要等待WIP清零。如果連續(xù)寫多個字節(jié)可以在一次WREN之后連續(xù)寫多個數(shù)據(jù)這樣WREN的開銷被分?jǐn)偭?。我測試過一次WREN后連續(xù)寫256字節(jié)寫吞吐量提升到了1.5MB/s左右。6.2 不同應(yīng)用場景的配置參數(shù)推薦根據(jù)實際項目經(jīng)驗我整理了幾個典型場景的配置建議場景一參數(shù)存儲少量數(shù)據(jù)低頻寫入訪問模式IP命令模式SCK頻率10MHz降低功耗和干擾寫保護(hù)使能保護(hù)參數(shù)區(qū)CRC校驗CRC-16寫入策略立即寫讀回驗證場景二故障錄波大量數(shù)據(jù)突發(fā)寫入訪問模式AHB DMA模式SCK頻率40MHz寫保護(hù)不使能CRC校驗CRC-32寫入策略DMA批量寫寫完成后統(tǒng)一驗證場景三實時數(shù)據(jù)記錄中等數(shù)據(jù)量持續(xù)寫入訪問模式IP命令模式寫內(nèi)存映射模式讀SCK頻率20MHz寫保護(hù)不使能CRC校驗CRC-16寫入策略環(huán)形緩沖區(qū)定期批量寫入6.3 功耗實測與低功耗優(yōu)化MR25H40CDF的待機電流典型值只有幾十微安讀寫電流在10mA左右。在電池供電的工業(yè)傳感器里這個功耗水平是可以接受的。但如果要進(jìn)一步優(yōu)化可以在不訪問MRAM時把FlexSPI外設(shè)時鐘關(guān)掉需要訪問時再打開。MKV58的FlexSPI時鐘門控通過CCM_CCGR寄存器控制關(guān)掉后外設(shè)功耗降到接近零。// 關(guān)閉FlexSPI時鐘 void MRAM_ClockDisable(void) { CCM-CCGR2 ~CCM_CCGR2_FLEXSPI_CLK_MASK; } // 打開FlexSPI時鐘 void MRAM_ClockEnable(void) { CCM-CCGR2 | CCM_CCGR2_FLEXSPI_CLK_MASK; }實測下來關(guān)閉FlexSPI時鐘后整個MRAM子系統(tǒng)的功耗從12mA降到了0.5mA以下。對于每秒只記錄一次數(shù)據(jù)的應(yīng)用可以把占空比做到很低平均功耗降到1mA以內(nèi)。7. 從MRAM到整個存儲架構(gòu)的擴展思考這套MRAMFlexSPI的方案跑通之后我陸續(xù)在幾個項目里做了擴展。一個是把MRAM作為MCU內(nèi)部Flash的補充存放那些需要頻繁更新的配置參數(shù)比如PID參數(shù)、校準(zhǔn)系數(shù)、設(shè)備序列號。內(nèi)部Flash擦寫次數(shù)有限頻繁更新容易損壞MRAM沒有這個問題。另一個擴展是用MRAM做掉電前的數(shù)據(jù)緊急保存。系統(tǒng)檢測到電源掉電后利用電源電容里剩余的電量把關(guān)鍵數(shù)據(jù)從SRAM搬到MRAM。因為MRAM寫入速度快不需要擦除整個過程可以在幾毫秒內(nèi)完成。我實測過在電源掉電后利用1000微法的電容儲能可以完成4KB數(shù)據(jù)的緊急保存。還有一個思路是用多顆MRAM做冗余存儲。MR25H40CDF的SPI接口支持級聯(lián)用不同的片選信號可以掛多顆。關(guān)鍵數(shù)據(jù)同時寫兩份讀的時候?qū)Ρ葍煞輸?shù)據(jù)如果一致就認(rèn)為可靠不一致就觸發(fā)錯誤處理。這個方案的成本比用一顆大容量MRAM高一些但可靠性提升明顯。最后說一個實際項目里的教訓(xùn)。我一開始把MRAM的地址空間直接映射到指針用memcpy讀寫代碼很簡潔。但后來發(fā)現(xiàn)編譯器優(yōu)化會把memcpy展開成多條加載存儲指令如果中間被中斷打斷可能讀到不一致的數(shù)據(jù)。解決辦法是在訪問MRAM前后關(guān)中斷或者用DMA傳輸避免CPU干預(yù)。這個細(xì)節(jié)在手冊上不會寫但實際項目里很關(guān)鍵。