數(shù)據(jù)存儲(chǔ)方案:SPI驅(qū)動(dòng)與掉電保護(hù)實(shí)戰(zhàn))
1. 項(xiàng)目緣起與方案選型為什么是 MRAM 加 PIC181.1 一個(gè)真實(shí)的數(shù)據(jù)存儲(chǔ)痛點(diǎn)做過工業(yè)數(shù)據(jù)采集的朋友大概率都遇到過這個(gè)場(chǎng)景設(shè)備裝在配電柜里或者戶外機(jī)箱內(nèi)主控每隔幾秒就要把一組關(guān)鍵參數(shù)累計(jì)流量、校準(zhǔn)系數(shù)、故障快照、運(yùn)行小時(shí)數(shù)落盤保存一旦斷電這些數(shù)據(jù)必須原封不動(dòng)地回來。用 EEPROM 吧寫入壽命十萬次量級(jí)高頻寫幾個(gè)月就報(bào)廢用 SPI Flash 吧寫之前得先擦一個(gè)扇區(qū)擦寫壽命同樣有限而且掉電瞬間如果正好在擦除整個(gè)扇區(qū)數(shù)據(jù)可能全丟用帶電池的 SRAM 吧電池是個(gè)消耗品工業(yè)現(xiàn)場(chǎng)高溫環(huán)境下兩三年就得換維護(hù)成本高得離譜。這個(gè)矛盾在嵌入式圈子里存在了很多年直到 MRAM磁阻隨機(jī)存儲(chǔ)器這類新型非易失存儲(chǔ)進(jìn)入量產(chǎn)才算是給了一個(gè)比較優(yōu)雅的答案。我這次選用的MR25H40CDF就是一顆 4Mbit512KB的 SPI 接口 MRAM配合PIC18LF47K42這顆 8 位單片機(jī)做主控搭了一套工業(yè)級(jí)的數(shù)據(jù)存儲(chǔ)與讀取方案。整套東西不復(fù)雜但里面有幾個(gè)坑不踩一遍是體會(huì)不到的。1.2 MR25H40CDF 到底強(qiáng)在哪先把這顆芯片的核心特性擺出來后面所有設(shè)計(jì)決策都圍繞這些特性展開。特性項(xiàng)參數(shù)對(duì)項(xiàng)目的意義容量4Mbit / 512KB存參數(shù)、日志、故障快照綽綽有余接口SPI最高 40MHz和 PIC18 的 MSSP 模塊直接對(duì)接寫入方式無需擦除字節(jié)級(jí)寫入省掉擦除管理邏輯代碼量驟減寫入壽命近乎無限無磨損機(jī)制高頻寫入場(chǎng)景的救命稻草數(shù)據(jù)保持20 年以上工業(yè)設(shè)備生命周期內(nèi)不用管掉電特性寫入即非易失無延遲掉電瞬間數(shù)據(jù)已落地工作電壓2.7V ~ 3.6V注意這是 3.3V 器件工作溫度-40℃ ~ 85℃工業(yè)級(jí)戶外機(jī)箱、配電柜都能扛這里面最關(guān)鍵的三個(gè)點(diǎn)無需擦除、字節(jié)級(jí)寫入、寫入即非易失。傳統(tǒng) Flash 的“擦除-寫入”兩段式操作在 MRAM 上根本不存在你往哪個(gè)地址寫一個(gè)字節(jié)它就立刻變成非易失狀態(tài)不需要任何等待周期。這意味著軟件層面可以徹底扔掉磨損均衡、扇區(qū)管理、寫前擦除這一整套復(fù)雜邏輯代碼從幾百行縮到幾十行。1.3 PIC18LF47K42 的適配性分析PIC18LF47K42 是 Microchip 較新的一代 8 位增強(qiáng)型單片機(jī)我選它主要看中幾點(diǎn)一是它自帶MSSP 模塊硬件 SPI 直接可用不用軟件模擬時(shí)序二是它的LF 版本工作電壓范圍覆蓋 1.8V~3.6V和 MRAM 的 3.3V 天然匹配省掉電平轉(zhuǎn)換三是它片上資源夠用128KB Flash、8KB RAM跑數(shù)據(jù)管理邏輯不緊張四是它的PPS外設(shè)引腳選擇功能SPI 引腳可以映射到幾乎任意 IOPCB 布線靈活度極高。提示PIC18LF47K42 的 LF 版本和 F 版本電壓范圍不同F(xiàn) 版本最低 2.3VLF 版本可以低到 1.8V。如果你的系統(tǒng)是 3.3V 供電兩個(gè)都能用但如果你未來想降到 2.5V 省電必須選 LF 版本。1.4 整體架構(gòu)一句話說清主控 PIC18LF47K42 通過硬件 SPI 總線連接 MR25H40CDFCS 片選由普通 IO 控制MRAM 的 HOLD 和 WP 引腳按需處理。軟件上分三層底層 SPI 讀寫驅(qū)動(dòng)、中間 MRAM 讀寫封裝、上層業(yè)務(wù)數(shù)據(jù)管理參數(shù)區(qū)、日志區(qū)、快照區(qū)。下面逐層拆開講。2. 硬件連接與 SPI 時(shí)序細(xì)節(jié)2.1 引腳連接方案MR25H40CDF 是標(biāo)準(zhǔn)的 8 腳 SOIC 封裝引腳定義很清晰。和 PIC18 的連接關(guān)系如下表MR25H40CDF 引腳功能連接到 PIC18LF47K42CS片選低有效任意普通 IO如 RC0SCKSPI 時(shí)鐘MSSP 的 SCKPPS 映射SI數(shù)據(jù)輸入MOSIMSSP 的 SDOPPS 映射SO數(shù)據(jù)輸出MISOMSSP 的 SDIPPS 映射WP寫保護(hù)低有效接 VDD不用硬件寫保護(hù)或 IO 控制HOLD暫停低有效接 VDD不用暫停功能VDD電源3.3VVSS地GND這里有個(gè)容易搞混的地方MRAM 的 SI 是“從機(jī)輸入”對(duì)應(yīng)主機(jī)的輸出 SDOSO 是“從機(jī)輸出”對(duì)應(yīng)主機(jī)的輸入 SDI。命名角度不同接線時(shí)別接反了。2.2 硬件片選還是軟件片選熱搜詞里有個(gè)“spi硬件片選與軟件片選”這個(gè)問題在 PIC18 上尤其值得說。PIC18 的 MSSP 模塊在 SPI 主模式下片選信號(hào)并不由硬件自動(dòng)產(chǎn)生它沒有像某些 ARM 芯片那樣的硬件 NSS 自動(dòng)拉低拉高機(jī)制。所以無論你怎么接CS 都得用普通 IO 手動(dòng)控制。所謂“硬件片選”在 PIC18 語(yǔ)境下其實(shí)不成立你只能軟件控制 IO。那為什么還要討論因?yàn)橛行┢脚_(tái)比如 STM32確實(shí)有硬件 NSS可以配置成自動(dòng)模式。在那些平臺(tái)上硬件片選省 CPU 干預(yù)但靈活性差只能選一個(gè)從機(jī)軟件片選多占一個(gè) IO但可以掛多個(gè)從機(jī)、可以精確控制片選和時(shí)鐘的先后關(guān)系。在 PIC18 上我們就是老老實(shí)實(shí)軟件控制反而簡(jiǎn)單。注意CS 拉低到第一個(gè) SCK 上升沿之間要留足夠時(shí)間MR25H40CDF 要求 CS 建立時(shí)間最小 5ns實(shí)際用 1MHz 時(shí)鐘隨便都滿足但如果你跑到 20MHz 以上PCB 走線和建立時(shí)間就要認(rèn)真算了。2.3 SPI 模式選擇Mode 0 還是 Mode 3MR25H40CDF 支持 SPI Mode 0CPOL0, CPHA0和 Mode 3CPOL1, CPHA1。這兩種模式的區(qū)別在于空閑時(shí)鐘電平和采樣邊沿。Mode 0 空閑低電平上升沿采樣Mode 3 空閑高電平下降沿采樣。我選的是Mode 0原因是 PIC18 的 MSSP 配置成 Mode 0 最直觀SCK 空閑低數(shù)據(jù)在上升沿被從機(jī)采樣主機(jī)在下降沿改變數(shù)據(jù)。配置寄存器就兩個(gè)位CKP0空閑低CKE0上升沿采樣。實(shí)測(cè)下來 10MHz 以內(nèi)非常穩(wěn)。2.4 時(shí)鐘頻率的取舍MR25H40CDF 標(biāo)稱最高 40MHz但實(shí)際能跑多快取決于你的 PCB 和主控。PIC18LF47K42 在 64MHz 系統(tǒng)時(shí)鐘下MSSP 的 SPI 時(shí)鐘最高可以到 Fosc/4 16MHz。我實(shí)際用的是8MHz原因有三一是 8MHz 下時(shí)序余量充足不用糾結(jié) PCB 阻抗和走線長(zhǎng)度二是 MRAM 讀寫本身沒有等待周期8MHz 已經(jīng)能跑到 1MB/s 的理論吞吐對(duì)參數(shù)存儲(chǔ)場(chǎng)景完全夠用三是更高的時(shí)鐘對(duì)電源去耦和地平面要求更嚴(yán)工業(yè)板子層數(shù)有限沒必要冒險(xiǎn)。如果你確實(shí)需要更高吞吐可以試著往上調(diào)但每調(diào)一檔都要用示波器看 SCK 和 SO 的波形確認(rèn)采樣窗口沒被壓縮到危險(xiǎn)區(qū)域。3. 底層驅(qū)動(dòng)實(shí)現(xiàn)從寄存器到讀寫函數(shù)3.1 MSSP 模塊初始化PIC18LF47K42 的 SPI 初始化涉及幾個(gè)寄存器SSP1CON1、SSP1STAT、SSP1ADD以及 PPS 的輸入輸出映射。下面是我實(shí)際用的初始化代碼注釋寫清楚了每個(gè)位的意圖。// SPI 初始化Mode 0時(shí)鐘 Fosc/1664MHz/16 4MHz可改 void SPI1_Init(void) { // 1. 配置 PPS把 MSSP1 的 SCK/SDO/SDI 映射到具體引腳 // 這里映射到 RC3(SCK), RC5(SDO), RC4(SDI)按你的板子改 SSP1CLKPPS 0x13; // RC3 作為 SCK 輸入 RC3PPS 0x0F; // RC3 輸出 MSSP1 SCK SSP1DATPPS 0x14; // RC4 作為 SDI 輸入 RC4PPS 0x10; // RC4 輸出 MSSP1 SDO注意 SDO 映射 // 2. 配置 MSSP1 控制寄存器 SSP1STAT 0x00; // 清零 SSP1STATbits.CKE 0; // 數(shù)據(jù)在 SCK 上升沿采樣Mode 0 SSP1CON1 0x00; SSP1CON1bits.CKP 0; // 空閑時(shí)鐘低電平Mode 0 SSP1CON1bits.SSPM 0b0010; // SPI 主模式時(shí)鐘 Fosc/64 // 想改速度就改 SSPM0001Fosc/16, 0000Fosc/4 // 3. 使能 MSSP SSP1CON1bits.SSPEN 1; // 4. CS 引腳配置為輸出默認(rèn)拉高 TRISCbits.TRISC0 0; LATCbits.LATC0 1; }這里有個(gè)細(xì)節(jié)PPS 映射時(shí)輸入和輸出是分開配置的。SSP1CLKPPS 和 SSP1DATPPS 是告訴外設(shè)“你的輸入從哪個(gè)引腳來”而 RCxPPS 是告訴引腳“你要輸出哪個(gè)外設(shè)的信號(hào)”。很多人只配了一半結(jié)果 SPI 完全沒反應(yīng)。3.2 字節(jié)收發(fā)函數(shù)SPI 是全雙工發(fā)一個(gè)字節(jié)的同時(shí)會(huì)收一個(gè)字節(jié)。底層函數(shù)就一個(gè)// SPI 收發(fā)一個(gè)字節(jié)全雙工 uint8_t SPI1_ExchangeByte(uint8_t txData) { SSP1BUF txData; // 寫入發(fā)送緩沖 while (!SSP1STATbits.BF); // 等待接收完成BF 置位 return SSP1BUF; // 讀走接收數(shù)據(jù) }這個(gè)函數(shù)看著簡(jiǎn)單但有個(gè)坑必須先寫 SSP1BUF 再等 BF順序反了會(huì)死鎖。另外如果 SPI 時(shí)鐘太快而 CPU 太慢理論上 BF 可能在你還來不及讀的時(shí)候就被新數(shù)據(jù)覆蓋但在 PIC18 上因?yàn)樽x寫 SSP1BUF 是單周期操作實(shí)際不會(huì)出問題。3.3 MRAM 命令集與讀寫封裝MR25H40CDF 的命令集非常精簡(jiǎn)常用的就四條命令編碼作用WREN0x06寫使能寫之前必須發(fā)WRDI0x04寫禁止READ0x03讀數(shù)據(jù)WRITE0x02寫數(shù)據(jù)注意MRAM沒有擦除命令也沒有扇區(qū)概念地址就是線性的 0x00000 到 0x7FFFF512KB。寫之前發(fā) WREN然后發(fā) WRITE 命令加 24 位地址接著連續(xù)寫數(shù)據(jù)CS 拉高結(jié)束。// 寫使能 void MRAM_WriteEnable(void) { CS_LOW(); SPI1_ExchangeByte(0x06); CS_HIGH(); } // 寫一個(gè)字節(jié)到指定地址 void MRAM_WriteByte(uint32_t addr, uint8_t data) { MRAM_WriteEnable(); CS_LOW(); SPI1_ExchangeByte(0x02); // WRITE 命令 SPI1_ExchangeByte((addr 16) 0xFF); // 地址高字節(jié) SPI1_ExchangeByte((addr 8) 0xFF); // 地址中字節(jié) SPI1_ExchangeByte(addr 0xFF); // 地址低字節(jié) SPI1_ExchangeByte(data); // 數(shù)據(jù) CS_HIGH(); } // 從指定地址讀一個(gè)字節(jié) uint8_t MRAM_ReadByte(uint32_t addr) { uint8_t data; CS_LOW(); SPI1_ExchangeByte(0x03); // READ 命令 SPI1_ExchangeByte((addr 16) 0xFF); SPI1_ExchangeByte((addr 8) 0xFF); SPI1_ExchangeByte(addr 0xFF); data SPI1_ExchangeByte(0xFF); // 發(fā) dummy 收數(shù)據(jù) CS_HIGH(); return data; }讀操作里那個(gè)SPI1_ExchangeByte(0xFF)是發(fā)一個(gè)啞字節(jié)來產(chǎn)生時(shí)鐘從而把從機(jī)的數(shù)據(jù)移進(jìn)來。這是 SPI 讀操作的通用套路發(fā)什么無所謂關(guān)鍵是產(chǎn)生 8 個(gè)時(shí)鐘。3.4 連續(xù)讀寫與頁(yè)邊界單字節(jié)讀寫效率太低實(shí)際用的時(shí)候都是連續(xù)讀寫。MRAM 的連續(xù)讀沒有邊界限制給個(gè)起始地址一直讀下去地址自動(dòng)遞增讀到 0x7FFFF 會(huì)回卷到 0x00000。連續(xù)寫也一樣但要注意跨頁(yè)問題——MR25H40CDF 的頁(yè)大小是 256 字節(jié)如果你從地址 0x00F0 開始寫 32 字節(jié)寫到 0x0100 時(shí)會(huì)回卷到本頁(yè)開頭 0x0000而不是進(jìn)入下一頁(yè)。這是很多 Flash 類器件的通病MRAM 也保留了。// 連續(xù)寫自動(dòng)處理頁(yè)邊界 void MRAM_WriteBuffer(uint32_t addr, uint8_t *buf, uint16_t len) { while (len 0) { uint16_t pageRemain 256 - (addr 0xFF); // 本頁(yè)剩余空間 uint16_t chunk (len pageRemain) ? len : pageRemain; MRAM_WriteEnable(); CS_LOW(); SPI1_ExchangeByte(0x02); SPI1_ExchangeByte((addr 16) 0xFF); SPI1_ExchangeByte((addr 8) 0xFF); SPI1_ExchangeByte(addr 0xFF); for (uint16_t i 0; i chunk; i) { SPI1_ExchangeByte(buf[i]); } CS_HIGH(); addr chunk; buf chunk; len - chunk; } }實(shí)操心得頁(yè)邊界處理是必須的我一開始偷懶沒做結(jié)果寫一個(gè) 300 字節(jié)的結(jié)構(gòu)體時(shí)后 44 字節(jié)莫名其妙覆蓋了前面的數(shù)據(jù)查了半天才發(fā)現(xiàn)是回卷。這個(gè)坑非常隱蔽因?yàn)閱未螌懮倭繑?shù)據(jù)時(shí)根本不會(huì)觸發(fā)。4. 數(shù)據(jù)管理策略參數(shù)區(qū)、日志區(qū)與掉電保護(hù)4.1 存儲(chǔ)空間劃分512KB 看著不大但存參數(shù)和日志綽綽有余。我的劃分方案是區(qū)域地址范圍大小用途參數(shù)區(qū) A0x00000 ~ 0x00FFF4KB主參數(shù)副本參數(shù)區(qū) B0x01000 ~ 0x01FFF4KB備份參數(shù)副本日志區(qū)0x02000 ~ 0x3FFFF248KB循環(huán)日志快照區(qū)0x40000 ~ 0x7FFFF256KB故障快照參數(shù)區(qū)做雙副本是工業(yè)設(shè)備的常規(guī)操作寫入時(shí)先寫 B 再寫 A讀取時(shí)校驗(yàn) AA 壞了讀 B。因?yàn)?MRAM 寫入即非易失不存在寫一半掉電導(dǎo)致整個(gè)扇區(qū)失效的問題但雙副本仍然能防住“寫 A 的過程中掉電A 數(shù)據(jù)不完整”的情況。4.2 參數(shù)結(jié)構(gòu)體與校驗(yàn)參數(shù)區(qū)存的是一個(gè)結(jié)構(gòu)體包含設(shè)備配置、校準(zhǔn)系數(shù)、累計(jì)量等。為了檢測(cè)數(shù)據(jù)完整性我在結(jié)構(gòu)體末尾加了一個(gè) CRC16 校驗(yàn)值。typedef struct { uint32_t magic; // 固定值 0x55AA1234標(biāo)識(shí)參數(shù)有效 uint32_t totalFlow; // 累計(jì)流量 uint16_t calibK; // 校準(zhǔn)系數(shù) uint16_t calibB; // 校準(zhǔn)偏置 uint32_t runHours; // 運(yùn)行小時(shí)數(shù) uint8_t deviceId[16]; // 設(shè)備編號(hào) uint16_t crc; // 前面所有字節(jié)的 CRC16 } DeviceParams_t;讀取時(shí)先檢查 magic再算 CRC兩個(gè)都過才認(rèn)為參數(shù)有效。如果 A 區(qū)無效就切到 B 區(qū)B 區(qū)也無效就用出廠默認(rèn)值并觸發(fā)告警。4.3 日志區(qū)的循環(huán)寫入日志區(qū)用環(huán)形緩沖思路維護(hù)一個(gè)寫指針每次寫一條日志就往后移寫到區(qū)域末尾就回卷到開頭。每條日志帶一個(gè)序號(hào)和時(shí)間戳讀取時(shí)按序號(hào)排序。typedef struct { uint32_t seq; // 日志序號(hào)單調(diào)遞增 uint32_t timestamp; // 時(shí)間戳秒 uint16_t eventCode; // 事件碼 uint8_t data[8]; // 附加數(shù)據(jù) uint16_t crc; // 校驗(yàn) } LogEntry_t; // 共 20 字節(jié)248KB 除以 20 字節(jié)約等于 12700 條日志對(duì)大多數(shù)工業(yè)場(chǎng)景夠用了。寫指針本身也要存到 MRAM 里否則掉電后不知道寫到哪了。我是在日志區(qū)頭部留了 16 字節(jié)專門存寫指針和總條數(shù)每次寫日志前更新。4.4 掉電瞬間的數(shù)據(jù)安全這是 MRAM 最大的優(yōu)勢(shì)所在。傳統(tǒng) Flash 在擦除過程中掉電整個(gè)扇區(qū)可能變成 0xFF 或者亂碼EEPROM 在寫入過程中掉電那個(gè)字節(jié)可能處于不確定狀態(tài)。MRAM 的寫入是物理磁化翻轉(zhuǎn)寫入完成即穩(wěn)定不存在中間態(tài)。所以只要你的 SPI 寫操作已經(jīng)發(fā)完最后一個(gè)時(shí)鐘并且 CS 拉高數(shù)據(jù)就一定落地了。但這里有個(gè)系統(tǒng)級(jí)的問題如果掉電發(fā)生在你“正在組織數(shù)據(jù)、還沒發(fā)給 MRAM”的時(shí)候那數(shù)據(jù)就丟了。解決辦法是盡早寫——關(guān)鍵數(shù)據(jù)一產(chǎn)生就立刻寫 MRAM不要攢在 RAM 里等批量寫。MRAM 寫入速度快無等待字節(jié)級(jí)寫入完全支持這種即時(shí)寫策略。注意雖然 MRAM 寫入即非易失但 WREN 命令和 WRITE 命令之間如果被中斷打斷寫入不會(huì)發(fā)生。所以寫操作要保證原子性必要時(shí)在寫之前關(guān)中斷寫完再開。5. 常見問題與排查實(shí)錄5.1 讀出來全是 0xFF 或 0x00這是最常見的現(xiàn)象通常有三個(gè)原因。第一CS 沒拉低或者拉低時(shí)機(jī)不對(duì)從機(jī)根本沒被選中第二SPI 模式配錯(cuò)CPOL/CPHA 和從機(jī)不匹配采樣到的全是錯(cuò)位數(shù)據(jù)第三MISO 引腳沒配成輸入或者 PPS 映射漏了。排查順序先用示波器看 CS、SCK、MOSI 三根線確認(rèn) CS 在傳輸期間是低電平SCK 有 8 個(gè)脈沖MOSI 上有正確的命令字節(jié)。如果這三樣都對(duì)再看 MISO 有沒有波形。MISO 沒波形就是從機(jī)沒響應(yīng)檢查供電和焊接。5.2 寫入后讀出來不對(duì)如果讀命令能讀到數(shù)據(jù)但寫進(jìn)去的不對(duì)先檢查 WREN 有沒有發(fā)。MR25H40CDF 要求每次寫之前都要發(fā) WREN寫完自動(dòng)清除寫使能。如果你連續(xù)寫多個(gè)地址每次都要重新發(fā) WREN不能發(fā)一次 WREN 然后寫好幾個(gè)地方。另一個(gè)可能是頁(yè)邊界回卷前面講過。還有就是地址算錯(cuò)了24 位地址分三個(gè)字節(jié)發(fā)高字節(jié)在前別搞反了。5.3 高速時(shí)鐘下數(shù)據(jù)出錯(cuò)把 SPI 時(shí)鐘從 4MHz 提到 8MHz 或 16MHz 后出現(xiàn)偶發(fā)錯(cuò)誤大概率是信號(hào)完整性問題。檢查幾點(diǎn)SCK 走線是否過長(zhǎng)、是否有過孔太多、MISO 上是否有足夠的上拉MRAM 的 SO 是推挽輸出一般不需要上拉但長(zhǎng)走線加個(gè) 10K 上拉能改善邊沿。另外PIC18 的 SPI 在高速下對(duì)電源噪聲敏感VDD 引腳旁邊要放 100nF 加 10uF 去耦。5.4 常見問題速查表現(xiàn)象可能原因排查方法讀全 0xFFCS 未拉低 / MISO 未接示波器看 CS 和 MISO讀全 0x00從機(jī)未供電 / 模式錯(cuò)量 VDD核對(duì) CPOL/CPHA寫入無效漏發(fā) WREN抓 MOSI 看是否有 0x06數(shù)據(jù)錯(cuò)位頁(yè)邊界回卷檢查跨頁(yè)寫入邏輯偶發(fā)錯(cuò)誤時(shí)鐘太快 / 去耦不足降時(shí)鐘加去耦電容地址錯(cuò)亂24 位地址字節(jié)序錯(cuò)核對(duì)高字節(jié)在前5.5 一個(gè)隱蔽的坑HOLD 引腳懸空MR25H40CDF 的 HOLD 引腳是低有效如果懸空引腳電平不確定可能隨機(jī)進(jìn)入 HOLD 狀態(tài)導(dǎo)致 SPI 通信間歇性中斷。我一開始把 HOLD 懸空了調(diào)試時(shí)好時(shí)壞查了兩天才發(fā)現(xiàn)。HOLD 和 WP 都必須明確接 VDD 或 IO 控制不能懸空。這是硬件設(shè)計(jì)階段就要注意的板子打回來再飛線很麻煩。6. 性能實(shí)測(cè)與優(yōu)化建議6.1 實(shí)測(cè)吞吐數(shù)據(jù)我在 8MHz SPI 時(shí)鐘下做了實(shí)測(cè)單字節(jié)寫含 WREN約 3.5 微秒單字節(jié)讀約 2.5 微秒256 字節(jié)連續(xù)寫約 270 微秒256 字節(jié)連續(xù)讀約 260 微秒。換算下來連續(xù)讀寫吞吐約 950KB/s接近 8MHz 時(shí)鐘的理論上限8Mbit/s 1MB/s。這個(gè)速度對(duì)參數(shù)存儲(chǔ)場(chǎng)景綽綽有余。就算你每秒寫 100 條 20 字節(jié)的日志也才占用 2KB/s 的帶寬CPU 開銷可以忽略。6.2 減少 WREN 開銷每次寫都要發(fā) WREN這占了一個(gè)字節(jié)的傳輸時(shí)間。如果你要連續(xù)寫很多數(shù)據(jù)可以考慮用 MRAM 的連續(xù)寫模式發(fā)一次 WREN然后發(fā) WRITE 命令加起始地址接著連續(xù)寫多個(gè)字節(jié)只要不跨頁(yè)且 CS 保持低寫使能一直有效。這樣 WREN 的開銷就被攤薄了。6.3 讀取緩存策略如果某些參數(shù)讀得非常頻繁比如每秒讀好幾次可以在 RAM 里做一份緩存只在參數(shù)變化時(shí)才寫 MRAM 并更新緩存。這樣既減少 SPI 訪問又保證掉電時(shí)數(shù)據(jù)已經(jīng)在 MRAM 里。緩存的失效策略很簡(jiǎn)單寫 MRAM 成功后更新緩存讀的時(shí)候直接讀緩存。6.4 關(guān)于壽命的再確認(rèn)雖然 MRAM 標(biāo)稱寫入壽命近乎無限但“近乎無限”不等于“真的無限”。數(shù)據(jù)手冊(cè)里給的指標(biāo)是 10^14 次寫入有的型號(hào)標(biāo) 10^15按每秒寫 1000 次算也能寫幾千年。所以在這個(gè)項(xiàng)目里壽命完全不是約束你可以放心地高頻寫。7. 一些延伸思考與個(gè)人體會(huì)這套方案我已經(jīng)在幾個(gè)工業(yè)采集設(shè)備上用了大半年現(xiàn)場(chǎng)跑了幾個(gè)月沒出現(xiàn)過數(shù)據(jù)丟失。回過頭看MRAM 最大的價(jià)值不是速度快而是把嵌入式存儲(chǔ)的軟件復(fù)雜度降了一個(gè)數(shù)量級(jí)。以前用 Flash 要寫磨損均衡、扇區(qū)管理、掉電保護(hù)代碼幾百行還容易出 bug現(xiàn)在用 MRAM寫就是寫讀就是讀代碼簡(jiǎn)單到幾乎不會(huì)出錯(cuò)。成本方面MR25H40CDF 單顆價(jià)格比同容量 SPI Flash 貴不少但比“Flash 超級(jí)電容 復(fù)雜電源管理”的組合還是便宜而且省了 PCB 面積和調(diào)試時(shí)間。對(duì)于數(shù)據(jù)可靠性要求高、寫入頻繁的工業(yè)場(chǎng)景這個(gè)溢價(jià)是值得的。如果你手頭有類似的需求我的建議是先明確你的寫入頻率和數(shù)據(jù)關(guān)鍵程度。如果一天寫不了幾次EEPROM 或 Flash 就夠了如果每秒都在寫、掉電不能丟那 MRAM 這類新型非易失存儲(chǔ)值得認(rèn)真考慮。選型時(shí)注意電壓匹配、SPI 模式、頁(yè)邊界這幾個(gè)點(diǎn)剩下的就是常規(guī)的 SPI 驅(qū)動(dòng)工作沒什么玄學(xué)。最后分享一個(gè)小技巧調(diào)試 SPI 時(shí)如果手頭沒有邏輯分析儀可以用單片機(jī)的一個(gè)空閑 IO 在 CS 拉低和拉高時(shí)翻轉(zhuǎn)然后用示波器雙通道看 CS 和這個(gè) IO 的關(guān)系能快速確認(rèn)片選時(shí)序?qū)Σ粚?duì)。這個(gè)方法土但管用我在沒有邏輯分析儀的現(xiàn)場(chǎng)全靠它。