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