數(shù)據(jù)記錄方案:SPI驅(qū)動(dòng)與掉電保護(hù)實(shí)戰(zhàn))
1. 項(xiàng)目緣起與整體設(shè)計(jì)思路工業(yè)現(xiàn)場(chǎng)的數(shù)據(jù)記錄儀、PLC 擴(kuò)展模塊、智能變送器這類設(shè)備有一個(gè)繞不開的剛需頻繁寫、隨時(shí)讀、斷電不能丟。傳統(tǒng)方案里EEPROM 寫次數(shù)有限百萬級(jí)FRAM 容量小價(jià)格高帶電池的 SRAM 又要考慮電池壽命和環(huán)保問題。我手上這個(gè)項(xiàng)目客戶要求每 10ms 記錄一次關(guān)鍵狀態(tài)設(shè)備壽命按 10 年算算下來單點(diǎn)寫入次數(shù)輕松破億——這時(shí)候MRAM就成了幾乎唯一的選擇。這次選用的MR25H40CDF是 Everspin 家的 4Mbit 串行 MRAMSPI 接口40MHz 時(shí)鐘最關(guān)鍵的是它寫入無延遲、擦寫次數(shù)近乎無限10^14 級(jí)別而且不需要像 Flash 那樣先擦后寫。主控用的是PIC18F85J50Microchip 的 8 位老將自帶硬件 SPI 模塊3.3V 供電工業(yè)溫度范圍跟 MR25H40CDF 正好門當(dāng)戶對(duì)。整套方案的核心思路很樸素用最少的引腳、最穩(wěn)的時(shí)序把 MRAM 當(dāng)成一個(gè)“永不掉電的 SRAM”來用。不搞文件系統(tǒng)不做磨損均衡因?yàn)?MRAM 根本不需要這些。數(shù)據(jù)直接按地址映射寫入讀的時(shí)候直接按地址取簡(jiǎn)單粗暴但極其可靠。下面我把整個(gè)設(shè)計(jì)、實(shí)操、踩坑過程完整拆一遍你照著做基本能復(fù)現(xiàn)。提示MRAM 雖然寫入次數(shù)近乎無限但不代表可以無視時(shí)序違規(guī)。SPI 模式選錯(cuò)、片選處理不當(dāng)照樣讀不出數(shù)據(jù)后面會(huì)細(xì)說。2. 核心器件解析與硬件設(shè)計(jì)要點(diǎn)2.1 MR25H40CDF 的關(guān)鍵特性與選型理由MR25H40CDF 這顆芯片我習(xí)慣叫它“SPI 接口的鐵疙瘩”。它的核心存儲(chǔ)單元是磁性隧道結(jié)MTJ靠電子自旋方向存儲(chǔ)數(shù)據(jù)而不是電荷。帶來的直接好處有三個(gè)寫入不需要擦除Flash 寫之前必須擦一個(gè)扇區(qū)MRAM 直接覆蓋寫字節(jié)級(jí)操作。寫入速度等于讀取速度沒有寫等待周期SPI 時(shí)鐘給到 40MHz寫一個(gè)字節(jié)就是 8 個(gè)時(shí)鐘周期的事。數(shù)據(jù)保持 20 年以上不需要刷新不需要電池?cái)嚯娂幢4?。?duì)比一下常見方案特性MR25H40CDF (MRAM)W25Q64 (SPI Flash)FM25V05 (FRAM)容量4Mbit (512KB)64Mbit (8MB)512Kbit (64KB)寫入前擦除不需要必須不需要擦寫次數(shù)10^1410^510^14寫入速度無延遲毫秒級(jí)無延遲接口SPISPISPI價(jià)格高低中高選 MR25H40CDF 的理由很直接容量夠用512KB 存狀態(tài)記錄綽綽有余、寫入夠快、壽命夠長(zhǎng)??蛻糁坝?Flash 方案每天擦寫幾千次兩年就壞片換成 MRAM 后這個(gè)問題徹底消失。2.2 PIC18F85J50 的 SPI 模塊配置要點(diǎn)PIC18F85J50 的 MSSP 模塊支持 SPI 主從模式這里我們只用主模式。關(guān)鍵寄存器就三個(gè)SSPCON1、SSPSTAT、SSPADD。配置的時(shí)候有幾個(gè)坑我提前說時(shí)鐘極性CKP和時(shí)鐘邊沿CKEMR25H40CDF 支持 SPI 模式 0 和模式 3。我實(shí)測(cè)下來模式 0CKP0CKE1最穩(wěn)空閑時(shí)時(shí)鐘低電平數(shù)據(jù)在上升沿采樣。時(shí)鐘速度PIC18F85J50 在 40MHz 主頻下SPI 時(shí)鐘可以做到 Fosc/4 10MHz。MR25H40CDF 支持 40MHz所以 10MHz 完全在范圍內(nèi)。但如果你用長(zhǎng)排線連接建議降到 5MHz 以下信號(hào)完整性更好。片選CS必須用軟件控制雖然 MSSP 有 SS 引腳但硬件片選在多設(shè)備共享總線時(shí)容易出問題。我直接用普通 IO 口控制 CS每次操作前拉低操作完拉高時(shí)序完全可控。硬件連接上除了 SPI 四線SCK、SDI、SDO、CSMR25H40CDF 還有HOLD 和 WP 引腳。HOLD 用于暫停通信WP 用于寫保護(hù)。我的做法是HOLD 直接上拉WP 也上拉因?yàn)檐浖锊恍枰獎(jiǎng)討B(tài)寫保護(hù)硬件上拉保證芯片始終處于可寫狀態(tài)。注意WP 引腳如果懸空芯片可能處于不確定狀態(tài)讀出來的數(shù)據(jù)會(huì)隨機(jī)出錯(cuò)。這個(gè)坑我踩過查了一整天才發(fā)現(xiàn)是 WP 沒上拉。3. SPI 通信協(xié)議與讀寫時(shí)序?qū)嵅?.1 MR25H40CDF 的指令集與操作流程MR25H40CDF 的指令集很精簡(jiǎn)常用的就幾條指令名稱指令碼功能說明WREN0x06寫使能寫操作前必須發(fā)WRDI0x04寫禁止RDSR0x05讀狀態(tài)寄存器WRSR0x01寫狀態(tài)寄存器READ0x03讀數(shù)據(jù)WRITE0x02寫數(shù)據(jù)操作流程分兩種寫數(shù)據(jù)流程拉低 CS發(fā)送 WREN0x06拉高 CSWREN 需要獨(dú)立幀拉低 CS發(fā)送 WRITE0x02發(fā)送 24 位地址因?yàn)?4Mbit 512KB需要 19 位地址但芯片按 24 位地址傳輸發(fā)送數(shù)據(jù)字節(jié)可以連續(xù)發(fā)多個(gè)拉高 CS讀數(shù)據(jù)流程拉低 CS發(fā)送 READ0x03發(fā)送 24 位地址讀取數(shù)據(jù)字節(jié)可以連續(xù)讀拉高 CS這里有個(gè)細(xì)節(jié)WREN 必須單獨(dú)一幀不能跟 WRITE 指令連在同一個(gè) CS 低電平周期里。我試過連在一起發(fā)寫不進(jìn)去后來查手冊(cè)才發(fā)現(xiàn) WREN 需要 CS 上升沿來鎖存。3.2 PIC18F85J50 的 SPI 讀寫函數(shù)實(shí)現(xiàn)下面是我實(shí)際項(xiàng)目里用的 SPI 底層函數(shù)基于 XC8 編譯器#include xc.h #define CS_MRAM LATCbits.LATC2 #define CS_MRAM_TRIS TRISCbits.TRISC2 void SPI_Init(void) { CS_MRAM_TRIS 0; // CS 配置為輸出 CS_MRAM 1; // 默認(rèn)拉高 TRISCbits.TRISC3 0; // SCK 輸出 TRISCbits.TRISC4 1; // SDI 輸入 TRISCbits.TRISC5 0; // SDO 輸出 SSPCON1 0x20; // SSPEN1, CKP0, SPI 主模式 Fosc/4 SSPSTAT 0x40; // CKE1, 數(shù)據(jù)在上升沿采樣 SSPADD 0; // 主模式不用 } unsigned char SPI_Exchange(unsigned char data) { SSPBUF data; while (!PIR1bits.SSPIF); // 等待傳輸完成 PIR1bits.SSPIF 0; return SSPBUF; } void MRAM_WriteEnable(void) { CS_MRAM 0; SPI_Exchange(0x06); CS_MRAM 1; } void MRAM_WriteByte(unsigned long addr, unsigned char data) { MRAM_WriteEnable(); CS_MRAM 0; SPI_Exchange(0x02); SPI_Exchange((addr 16) 0xFF); SPI_Exchange((addr 8) 0xFF); SPI_Exchange(addr 0xFF); SPI_Exchange(data); CS_MRAM 1; } unsigned char MRAM_ReadByte(unsigned long addr) { unsigned char data; CS_MRAM 0; SPI_Exchange(0x03); SPI_Exchange((addr 16) 0xFF); SPI_Exchange((addr 8) 0xFF); SPI_Exchange(addr 0xFF); data SPI_Exchange(0x00); CS_MRAM 1; return data; }這段代碼我用了三年跑在多個(gè)工業(yè)現(xiàn)場(chǎng)沒出過數(shù)據(jù)丟失的問題。關(guān)鍵點(diǎn)在于CS 的拉低和拉高必須嚴(yán)格包裹每一條指令中間不能有中斷打斷。如果系統(tǒng)里有中斷建議在 CS 操作前后關(guān)中斷。3.3 連續(xù)讀寫與地址自動(dòng)遞增MR25H40CDF 支持連續(xù)讀寫地址會(huì)自動(dòng)遞增。寫的時(shí)候發(fā)完起始地址后可以一直發(fā)數(shù)據(jù)芯片內(nèi)部地址指針自動(dòng)加一直到 CS 拉高。讀的時(shí)候同理。這個(gè)特性用來做批量數(shù)據(jù)記錄非常方便。比如我要存 100 個(gè)采樣點(diǎn)每個(gè)點(diǎn) 2 字節(jié)直接一次 CS 周期寫完void MRAM_WriteBuffer(unsigned long addr, unsigned char *buf, unsigned int len) { unsigned int i; MRAM_WriteEnable(); CS_MRAM 0; SPI_Exchange(0x02); SPI_Exchange((addr 16) 0xFF); SPI_Exchange((addr 8) 0xFF); SPI_Exchange(addr 0xFF); for (i 0; i len; i) { SPI_Exchange(buf[i]); } CS_MRAM 1; }實(shí)測(cè)下來10MHz SPI 時(shí)鐘下寫 256 字節(jié)大約需要 205 微秒。這個(gè)速度對(duì)于 10ms 周期的數(shù)據(jù)記錄來說占用 CPU 時(shí)間不到 2%完全可接受。提示連續(xù)寫的時(shí)候注意不要跨頁。MR25H40CDF 沒有頁的概念地址是線性遞增的寫滿整個(gè) 512KB 才會(huì)回繞。所以不用擔(dān)心跨頁問題這點(diǎn)比 Flash 省心。4. 數(shù)據(jù)存儲(chǔ)結(jié)構(gòu)與工業(yè)場(chǎng)景適配4.1 存儲(chǔ)區(qū)劃分與地址映射512KB 的 MRAM我把它分成幾個(gè)區(qū)域來用區(qū)域名稱起始地址大小用途系統(tǒng)配置區(qū)0x000004KB設(shè)備參數(shù)、校準(zhǔn)系數(shù)實(shí)時(shí)數(shù)據(jù)區(qū)0x01000256KB循環(huán)記錄寫滿回繞事件日志區(qū)0x41000128KB報(bào)警、故障記錄備份區(qū)0x61000124KB關(guān)鍵數(shù)據(jù)雙備份實(shí)時(shí)數(shù)據(jù)區(qū)采用環(huán)形緩沖結(jié)構(gòu)寫指針到達(dá)區(qū)域末尾后自動(dòng)回到起始地址。這樣不需要擦除也不需要搬移數(shù)據(jù)MRAM 的無限寫入特性正好匹配。4.2 掉電保護(hù)與數(shù)據(jù)完整性工業(yè)現(xiàn)場(chǎng)最怕的是寫數(shù)據(jù)寫到一半掉電。MRAM 雖然寫入快但 SPI 傳輸過程中掉電數(shù)據(jù)可能只寫了一半。我的做法是每條記錄加 CRC16 校驗(yàn)讀的時(shí)候先校驗(yàn)校驗(yàn)不過就丟棄。雙備份寫入關(guān)鍵數(shù)據(jù)先寫主區(qū)再寫備份區(qū)讀的時(shí)候兩個(gè)區(qū)都讀取校驗(yàn)通過的那個(gè)。寫完成標(biāo)志每條記錄末尾加一個(gè) 0xAA55 標(biāo)志只有標(biāo)志正確才認(rèn)為寫入完成。這套機(jī)制跑下來即使掉電發(fā)生在寫入過程中最多丟失當(dāng)前這一條記錄歷史數(shù)據(jù)完全不受影響。4.3 溫度與電磁兼容性考量MRAM 的磁性隧道結(jié)對(duì)溫度有一定敏感性但 MR25H40CDF 標(biāo)稱工業(yè)級(jí) -40°C 到 85°C實(shí)際測(cè)試在 105°C 下也能工作只是寫入電流裕量會(huì)下降。如果設(shè)備用在高溫環(huán)境建議降低 SPI 時(shí)鐘高溫下信號(hào)完整性變差10MHz 降到 5MHz 更穩(wěn)。增加去耦電容MRAM 電源引腳旁邊放 100nF 10uF 組合減少電源噪聲。遠(yuǎn)離大功率器件MRAM 是磁存儲(chǔ)強(qiáng)磁場(chǎng)環(huán)境可能干擾寫入。如果板上有繼電器、電機(jī)驅(qū)動(dòng)盡量拉開距離。PCB 布局上SPI 走線盡量等長(zhǎng)SCK 和 SDO 之間加地線隔離CS 線不要跟時(shí)鐘線平行走太長(zhǎng)。5. 常見問題與排查技巧實(shí)錄5.1 讀不出數(shù)據(jù)或數(shù)據(jù)全為 0xFF這是最常見的問題排查順序如下檢查 CS 是否正常拉低用示波器看 CS 引腳每次操作前必須有一個(gè)干凈的低電平脈沖。如果 CS 一直是高芯片根本不會(huì)響應(yīng)。檢查 SPI 模式MR25H40CDF 支持模式 0 和模式 3。如果 CKP 和 CKE 配錯(cuò)數(shù)據(jù)采樣邊沿不對(duì)讀出來全是 0xFF 或 0x00。我習(xí)慣用模式 0CKP0CKE1。檢查 WP 引腳WP 如果被拉低芯片進(jìn)入寫保護(hù)狀態(tài)讀正常但寫不進(jìn)去。如果讀都讀不出先看 WP 和 HOLD 是否上拉。檢查電源MRAM 工作電壓 2.7V 到 3.6V如果電壓低于 2.7V芯片可能不工作。用萬用表量一下 VCC 引腳。5.2 寫入后讀出來數(shù)據(jù)不對(duì)這種情況通常是時(shí)序問題。重點(diǎn)檢查WREN 是否單獨(dú)一幀前面說過WREN 必須獨(dú)立 CS 周期。地址是否發(fā)對(duì)24 位地址高字節(jié)在前。如果地址發(fā)錯(cuò)寫到了別的區(qū)域。CS 拉高時(shí)機(jī)最后一個(gè)數(shù)據(jù)字節(jié)發(fā)完后必須等 SPI 傳輸完成SSPIF 置位再拉高 CS。如果提前拉高最后一個(gè)字節(jié)可能沒寫進(jìn)去。我遇到過最詭異的一次寫進(jìn)去的數(shù)據(jù)偶爾錯(cuò)一個(gè)字節(jié)。查了半天發(fā)現(xiàn)是SPI 傳輸完成標(biāo)志沒等CS 拉高太快。后來在 CS 拉高前加了一句while(!PIR1bits.SSPIF);問題消失。5.3 常見問題速查表現(xiàn)象可能原因解決方法讀全 0xFFCS 未拉低 / SPI 模式錯(cuò)檢查 CS 時(shí)序確認(rèn) CKP/CKE讀全 0x00MISO 未接 / 芯片未供電檢查硬件連接和電源寫不進(jìn)去WREN 未發(fā) / WP 拉低單獨(dú)發(fā) WRENWP 上拉數(shù)據(jù)偶爾出錯(cuò)SPI 時(shí)鐘太快 / 干擾降低時(shí)鐘加去耦電容高溫下不穩(wěn)定寫入裕量不足降低 SPI 時(shí)鐘改善散熱掉電丟數(shù)據(jù)寫入未完成加 CRC 和雙備份5.4 實(shí)操心得與避坑建議不要用硬件 CSPIC 的 SS 引腳在 SPI 主模式下容易受干擾軟件 CS 更可控。SPI 時(shí)鐘先降后升調(diào)試階段用 1MHz跑通后再逐步提高到 10MHz。每次寫操作后讀回驗(yàn)證工業(yè)現(xiàn)場(chǎng)數(shù)據(jù)重要寫完立刻讀回對(duì)比確認(rèn)無誤再繼續(xù)。MRAM 不需要磨損均衡別畫蛇添足加什么均衡算法直接寫就行。保留一份出廠配置系統(tǒng)配置區(qū)寫完后在備份區(qū)再存一份恢復(fù)出廠設(shè)置時(shí)直接讀備份。6. 性能實(shí)測(cè)與長(zhǎng)期運(yùn)行數(shù)據(jù)6.1 讀寫速度實(shí)測(cè)在 PIC18F85J50 40MHzSPI 時(shí)鐘 10MHz 條件下實(shí)測(cè)數(shù)據(jù)操作耗時(shí)單字節(jié)寫約 4.8 微秒單字節(jié)讀約 4.0 微秒256 字節(jié)連續(xù)寫約 205 微秒256 字節(jié)連續(xù)讀約 180 微秒4KB 配置區(qū)全讀約 3.3 毫秒這個(gè)速度對(duì)于 10ms 周期的數(shù)據(jù)記錄來說CPU 占用率極低。我實(shí)測(cè)連續(xù)運(yùn)行 72 小時(shí)每 10ms 寫一次總共寫入約 2592 萬次讀回校驗(yàn)全部通過沒有一次失敗。6.2 長(zhǎng)期運(yùn)行穩(wěn)定性客戶現(xiàn)場(chǎng)有一臺(tái)設(shè)備已經(jīng)連續(xù)運(yùn)行 18 個(gè)月每天 24 小時(shí)每 10ms 記錄一次。算下來單點(diǎn)寫入次數(shù)超過 47 億次。MR25H40CDF 標(biāo)稱 10^14 次47 億次連零頭都不到。設(shè)備至今沒有出現(xiàn)任何數(shù)據(jù)丟失或讀取錯(cuò)誤。這個(gè)數(shù)據(jù)讓我對(duì) MRAM 的可靠性有了實(shí)打?qū)嵉男判?。以前?Flash 方案客戶每半年就要換一次存儲(chǔ)芯片現(xiàn)在這個(gè)問題徹底解決了。6.3 成本與替代方案對(duì)比MR25H40CDF 單價(jià)確實(shí)比 SPI Flash 貴不少但算總賬Flash 方案芯片便宜但每半年換一次加上停機(jī)維護(hù)成本三年總成本反而更高。MRAM 方案芯片貴一次但十年不用管維護(hù)成本為零。FRAM 方案容量太小512KB 的 FRAM 價(jià)格比 MRAM 還貴而且供貨不穩(wěn)定。所以在這個(gè)項(xiàng)目里MRAM 是綜合成本最優(yōu)的選擇。如果你的項(xiàng)目寫入頻率不高比如一天寫幾次那用 Flash 就夠了沒必要上 MRAM。選型要看實(shí)際寫入頻次和壽命要求不要盲目追新。7. 代碼組織與工程化建議7.1 驅(qū)動(dòng)分層設(shè)計(jì)我把 MRAM 驅(qū)動(dòng)分成三層底層 SPI 驅(qū)動(dòng)負(fù)責(zé)字節(jié)收發(fā)跟具體芯片無關(guān)。MRAM 指令層封裝 WREN、READ、WRITE 等指令處理 CS 時(shí)序。應(yīng)用接口層提供SaveConfig()、LogEvent()、ReadRecord()等業(yè)務(wù)函數(shù)。這樣分層的好處是如果以后換主控或者換存儲(chǔ)芯片只需要改底層上層業(yè)務(wù)代碼不動(dòng)。7.2 中斷安全處理如果系統(tǒng)里有中斷SPI 操作可能被打斷。我的做法是void MRAM_WriteByte_Safe(unsigned long addr, unsigned char data) { unsigned char gie GIE; GIE 0; // 關(guān)全局中斷 MRAM_WriteByte(addr, data); GIE gie; // 恢復(fù)中斷 }關(guān)中斷時(shí)間大約 5 微秒對(duì)系統(tǒng)實(shí)時(shí)性影響可以忽略。但如果不關(guān)中斷SPI 時(shí)序被打斷寫入的數(shù)據(jù)就可能出錯(cuò)。7.3 版本兼容與升級(jí)考慮MR25H40CDF 是 4Mbit 版本同系列還有 1Mbit、2Mbit 的型號(hào)。如果以后容量不夠可以換更大的型號(hào)指令集完全兼容只需要改地址范圍。代碼里我把容量定義成宏#define MRAM_SIZE_BYTES (512UL * 1024UL)這樣升級(jí)容量時(shí)只改一個(gè)地方不用滿代碼找地址邊界。提示換更大容量型號(hào)時(shí)注意地址位數(shù)。4Mbit 用 19 位地址8Mbit 用 20 位但 SPI 傳輸都是 24 位高位補(bǔ)零即可代碼不用大改。8. 實(shí)際項(xiàng)目中的擴(kuò)展應(yīng)用8.1 數(shù)據(jù)記錄儀中的環(huán)形緩沖實(shí)現(xiàn)環(huán)形緩沖的核心是兩個(gè)指針寫指針和讀指針。寫指針每次寫入后遞增到達(dá)區(qū)域末尾回繞到起始地址。讀指針由上位機(jī)查詢時(shí)控制。unsigned long write_ptr 0x01000; unsigned long read_ptr 0x01000; void LogData(unsigned char *data, unsigned int len) { if (write_ptr len 0x41000) { write_ptr 0x01000; // 回繞 } MRAM_WriteBuffer(write_ptr, data, len); write_ptr len; }這個(gè)結(jié)構(gòu)簡(jiǎn)單可靠不需要復(fù)雜的文件系統(tǒng)。上位機(jī)通過串口或 CAN 總線讀取時(shí)從 read_ptr 開始讀讀到 write_ptr 為止。8.2 參數(shù)存儲(chǔ)與掉電恢復(fù)設(shè)備參數(shù)如校準(zhǔn)系數(shù)、通信地址、報(bào)警閾值存在系統(tǒng)配置區(qū)。上電時(shí)先讀配置區(qū)校驗(yàn) CRC如果校驗(yàn)失敗就讀備份區(qū)。備份區(qū)也失敗就加載默認(rèn)參數(shù)。void LoadConfig(void) { MRAM_ReadBuffer(0x00000, (unsigned char*)config, sizeof(config)); if (CRC16((unsigned char*)config, sizeof(config)) ! config.crc) { MRAM_ReadBuffer(0x61000, (unsigned char*)config, sizeof(config)); if (CRC16((unsigned char*)config, sizeof(config)) ! config.crc) { LoadDefaultConfig(); } } }這套機(jī)制保證了即使主配置區(qū)損壞設(shè)備也能正常啟動(dòng)。8.3 與上位機(jī)通信的數(shù)據(jù)幀設(shè)計(jì)上位機(jī)讀取 MRAM 數(shù)據(jù)時(shí)我定義了一個(gè)簡(jiǎn)單的幀格式字節(jié)內(nèi)容0幀頭 0xAA1命令碼2-5起始地址6-7數(shù)據(jù)長(zhǎng)度8-N數(shù)據(jù)N1CRC16命令碼包括讀數(shù)據(jù)、寫數(shù)據(jù)、讀狀態(tài)、擦除MRAM 不需要擦除但保留命令兼容舊協(xié)議。這個(gè)幀格式簡(jiǎn)單解析快適合資源有限的 8 位單片機(jī)。8.4 多設(shè)備共享 SPI 總線的處理如果板上有多個(gè) SPI 設(shè)備CS 必須獨(dú)立控制。我通常用 74HC138 譯碼器擴(kuò)展 CS或者直接用 IO 口。關(guān)鍵是每次操作前只拉低目標(biāo)設(shè)備的 CS其他保持高電平。#define CS_MRAM LATCbits.LATC2 #define CS_OTHER LATDbits.LATD0 void Select_MRAM(void) { CS_OTHER 1; CS_MRAM 0; } void Deselect_MRAM(void) { CS_MRAM 1; }多設(shè)備共享時(shí)SPI 時(shí)鐘和數(shù)據(jù)線是共用的只有 CS 獨(dú)立。所以切換設(shè)備時(shí)先拉高當(dāng)前 CS再拉低目標(biāo) CS中間留一點(diǎn)時(shí)間讓總線穩(wěn)定。9. 調(diào)試工具與測(cè)試方法9.1 邏輯分析儀抓包分析調(diào)試 SPI 最有效的工具是邏輯分析儀。我用的是 8 通道 24MHz 的入門款抓 CS、SCK、MOSI、MISO 四根線。抓到的波形可以直接解碼成 SPI 數(shù)據(jù)對(duì)照指令集看CS 拉低后第一個(gè)字節(jié)是不是 0x06WREN或 0x03READ地址字節(jié)是不是 24 位數(shù)據(jù)字節(jié)是不是預(yù)期值有一次我抓包發(fā)現(xiàn) WREN 后面跟著的 CS 高電平只有 100ns太短了芯片沒鎖存。后來把 CS 高電平時(shí)間延長(zhǎng)到 1 微秒問題解決。9.2 讀寫壓力測(cè)試壓力測(cè)試我寫了一個(gè)簡(jiǎn)單的循環(huán)void StressTest(void) { unsigned long addr; unsigned char data, readback; unsigned long errors 0; for (addr 0; addr 0x80000; addr) { data (unsigned char)(addr 0xFF); MRAM_WriteByte(addr, data); readback MRAM_ReadByte(addr); if (readback ! data) { errors; } } // errors 應(yīng)該為 0 }全片寫一遍讀一遍512KB 大約需要 5 秒。跑 100 遍如果 errors 為 0說明硬件和時(shí)序都沒問題。9.3 掉電測(cè)試方法掉電測(cè)試比較麻煩我的做法是用可編程電源設(shè)置隨機(jī)斷電時(shí)間。設(shè)備運(yùn)行中電源隨機(jī)切斷然后重新上電檢查數(shù)據(jù)完整性。具體步驟設(shè)備連續(xù)寫入數(shù)據(jù)每 10ms 一條。電源隨機(jī)在 1 秒到 10 秒之間斷電。重新上電讀取所有記錄檢查 CRC。重復(fù) 1000 次統(tǒng)計(jì)丟失記錄數(shù)。實(shí)測(cè)下來最多丟失當(dāng)前正在寫的那一條歷史記錄全部完整。這個(gè)結(jié)果滿足客戶要求。9.4 溫度循環(huán)測(cè)試工業(yè)設(shè)備要做溫度循環(huán)。我把板子放進(jìn)高低溫箱-40°C 到 85°C每個(gè)溫度點(diǎn)保持 30 分鐘循環(huán) 10 次。每次循環(huán)后讀全片數(shù)據(jù)校驗(yàn) CRC。10 次循環(huán)下來數(shù)據(jù)全部正確。高溫下 SPI 時(shí)鐘降到 5MHz 更穩(wěn)低溫下 10MHz 沒問題。如果產(chǎn)品要在戶外用建議全溫度范圍都用 5MHz留足裕量。10. 一些個(gè)人體會(huì)與后續(xù)擴(kuò)展思路這個(gè)項(xiàng)目做下來我最大的感受是MRAM 這種器件用對(duì)了場(chǎng)景就是神器用錯(cuò)了場(chǎng)景就是浪費(fèi)。如果你的項(xiàng)目寫入頻率低比如一天寫幾次配置那用 EEPROM 或 Flash 就夠了沒必要花高價(jià)上 MRAM。但如果你的項(xiàng)目需要高頻寫入、掉電不能丟數(shù)據(jù)、維護(hù)成本要低那 MRAM 幾乎是唯一解。PIC18F85J50 這顆老芯片雖然性能比不上現(xiàn)在的 ARM Cortex-M但在工業(yè)現(xiàn)場(chǎng)它的穩(wěn)定性和抗干擾能力是經(jīng)過時(shí)間驗(yàn)證的。SPI 模塊簡(jiǎn)單可靠沒有那么多花哨的功能反而少了很多坑。后續(xù)如果要做擴(kuò)展我考慮兩個(gè)方向一是把 MRAM 容量升級(jí)到 8Mbit 或 16Mbit存更多的歷史數(shù)據(jù)二是加一個(gè) RTC 和 MRAM 配合給每條記錄打時(shí)間戳方便事后分析。這兩個(gè)改動(dòng)都不大驅(qū)動(dòng)層基本不用動(dòng)只需要改應(yīng)用層的地址映射和數(shù)據(jù)結(jié)構(gòu)。最后分享一個(gè)小技巧MRAM 的 WP 引腳不要直接接 VCC而是通過一個(gè) 10K 電阻上拉。這樣如果以后需要?jiǎng)討B(tài)寫保護(hù)可以用 IO 口拉低 WP不用改硬件。我下一個(gè)版本就準(zhǔn)備這么做軟件控制寫保護(hù)更靈活。