數(shù)據(jù)記錄新方案:MRAM替換并行SRAM與PIC18F47Q10實(shí)戰(zhàn))
1. 為什么工業(yè)現(xiàn)場(chǎng)還在用并行SRAM而MRAM已經(jīng)悄悄替換了它做工業(yè)控制和嵌入式數(shù)據(jù)采集的人大概率都遇到過(guò)這個(gè)場(chǎng)景設(shè)備跑在現(xiàn)場(chǎng)每隔幾毫秒要記錄一次關(guān)鍵狀態(tài)比如電機(jī)轉(zhuǎn)速、閥門(mén)開(kāi)度、累計(jì)運(yùn)行時(shí)長(zhǎng)。這些數(shù)據(jù)不能丟掉電之后必須還在。傳統(tǒng)方案無(wú)非幾種——EEPROM寫(xiě)入太慢擦寫(xiě)壽命也就百萬(wàn)次級(jí)別高頻寫(xiě)入很快就報(bào)廢SRAM速度快但掉電即失必須配電池或者超級(jí)電容電池又要考慮溫度范圍和更換周期NOR Flash寫(xiě)入前得擦除整個(gè)扇區(qū)寫(xiě)入延遲動(dòng)輒幾毫秒到幾十毫秒而且擦寫(xiě)次數(shù)只有十萬(wàn)次量級(jí)。MRAM磁阻隨機(jī)存儲(chǔ)器的出現(xiàn)正好卡在這個(gè)縫隙里。它的寫(xiě)入原理是靠磁隧道結(jié)的磁化方向變化來(lái)存儲(chǔ)數(shù)據(jù)不需要電荷保持所以掉電不丟寫(xiě)入速度接近SRAM沒(méi)有擦除動(dòng)作字節(jié)級(jí)隨機(jī)寫(xiě)入擦寫(xiě)壽命理論上無(wú)限次至少也是10的14次方以上。MR25H40CDF就是Everspin出的一顆4Mbit SPI接口MRAM工業(yè)級(jí)溫度范圍SOIC-8封裝引腳和常見(jiàn)的SPI Flash、EEPROM高度兼容。我這次選它搭配PIC18F47Q10做數(shù)據(jù)記錄模塊核心訴求就三條第一寫(xiě)入頻率高每秒幾百次不能卡第二掉電不能丟數(shù)據(jù)不能靠電池第三接口要簡(jiǎn)單SPI四根線搞定PCB不用大改。PIC18F47Q10是Microchip新出的8位機(jī)帶SPI外設(shè)Q10系列還集成了很多獨(dú)立于內(nèi)核的外設(shè)比如DMA、CLC、CIP用來(lái)做數(shù)據(jù)搬運(yùn)和接口時(shí)序控制非常省心。這篇文章我會(huì)把整個(gè)鏈路拆開(kāi)講MR25H40CDF的SPI時(shí)序到底有什么坑PIC18F47Q10的SPI模塊怎么配置才能跑出穩(wěn)定時(shí)序數(shù)據(jù)寫(xiě)入和讀取的完整代碼邏輯以及我在實(shí)測(cè)中遇到的幾個(gè)典型問(wèn)題。如果你手頭正好有類(lèi)似需求或者單純想搞清楚MRAM和普通SPI Flash在操作上到底差在哪這篇應(yīng)該能幫你省下不少調(diào)試時(shí)間。2. MR25H40CDF的SPI協(xié)議細(xì)節(jié)和普通Flash完全不是一回事2.1 命令集精簡(jiǎn)到只有幾條但每條都有講究MR25H40CDF的SPI命令集比NOR Flash簡(jiǎn)單得多。它沒(méi)有扇區(qū)擦除、塊擦除這些概念因?yàn)镸RAM不需要擦除。核心命令就這幾條命令名稱(chēng)命令碼作用注意事項(xiàng)WREN0x06寫(xiě)使能每次寫(xiě)操作前必須發(fā)WRDI0x04寫(xiě)禁止寫(xiě)完后自動(dòng)復(fù)位一般不用手動(dòng)發(fā)RDSR0x05讀狀態(tài)寄存器檢查WEL位和寫(xiě)保護(hù)狀態(tài)WRSR0x01寫(xiě)狀態(tài)寄存器配置塊保護(hù)READ0x03讀數(shù)據(jù)支持任意地址連續(xù)讀WRITE0x02寫(xiě)數(shù)據(jù)支持任意地址連續(xù)寫(xiě)無(wú)頁(yè)邊界限制這里最關(guān)鍵的一點(diǎn)MR25H40CDF的WRITE命令沒(méi)有頁(yè)邊界限制。普通SPI Flash比如W25Q64一次寫(xiě)操作不能跨頁(yè)256字節(jié)頁(yè)邊界到了必須重新發(fā)命令。MRAM沒(méi)有這個(gè)限制你可以從地址0x0000一直寫(xiě)到0x7FFFF中間不用停。這個(gè)特性在連續(xù)記錄場(chǎng)景下非常舒服不用在代碼里做頁(yè)對(duì)齊判斷。但注意WREN命令必須在每次WRITE之前單獨(dú)發(fā)送而且WREN之后如果SPI片選拉高寫(xiě)使能狀態(tài)會(huì)保持但一旦執(zhí)行完WRITEWEL位自動(dòng)清零。所以你不能發(fā)一次WREN然后連續(xù)寫(xiě)多個(gè)不連續(xù)地址每次WRITE之前都要重新發(fā)WREN。2.2 SPI模式選擇Mode 0還是Mode 3MR25H40CDF支持SPI Mode 0CPOL0CPHA0和Mode 3CPOL1CPHA1。這兩個(gè)模式的區(qū)別在于時(shí)鐘空閑電平和數(shù)據(jù)采樣邊沿。Mode 0時(shí)鐘空閑低電平數(shù)據(jù)在上升沿采樣下降沿變化。 Mode 3時(shí)鐘空閑高電平數(shù)據(jù)在上升沿采樣下降沿變化。實(shí)際用下來(lái)Mode 0和Mode 3在功能上都能正常工作但如果你SPI總線上還掛了其他設(shè)備最好統(tǒng)一模式。我這次總線上只有MRAM一顆從設(shè)備選了Mode 0因?yàn)镻IC18F47Q10的SPI默認(rèn)配置就是Mode 0省得改。有個(gè)細(xì)節(jié)要注意MRAM的SPI時(shí)鐘最高可以跑到40MHz但PIC18F47Q10在64MHz系統(tǒng)時(shí)鐘下SPI分頻后最高也就16MHzFosc/4。這個(gè)速度對(duì)MRAM來(lái)說(shuō)完全夠用16MHz下連續(xù)讀的吞吐率大概2MB/s寫(xiě)的話因?yàn)橐l(fā)WREN和等待實(shí)際有效寫(xiě)入速率大概幾百KB/s對(duì)于數(shù)據(jù)記錄場(chǎng)景綽綽有余。2.3 狀態(tài)寄存器的WEL位和寫(xiě)保護(hù)邏輯RDSR命令讀回來(lái)的狀態(tài)寄存器bit1是WELWrite Enable Latchbit0是WIPWrite In Progress。MRAM的寫(xiě)入速度極快WIP位基本上發(fā)完WRITE命令之后幾個(gè)時(shí)鐘周期就清零了不像Flash要等幾毫秒。但WEL位的檢查不能省。我遇到過(guò)一種情況SPI總線受到干擾WREN命令的位被翻轉(zhuǎn)了結(jié)果WRITE命令發(fā)出去但數(shù)據(jù)沒(méi)寫(xiě)進(jìn)去。后來(lái)在每次WRITE之前加了一步RDSR檢查WEL位確認(rèn)是1再發(fā)WRITE問(wèn)題就沒(méi)再出現(xiàn)過(guò)。狀態(tài)寄存器還有BP0、BP1兩位做塊保護(hù)可以保護(hù)部分地址區(qū)域不被寫(xiě)入。如果你的應(yīng)用里有 bootloader 區(qū)域或者校準(zhǔn)參數(shù)區(qū)域不希望被誤寫(xiě)可以用WRSR命令把對(duì)應(yīng)塊保護(hù)起來(lái)。但注意塊保護(hù)一旦設(shè)置要解除必須發(fā)WRSR而WRSR本身又需要WEL1所以解除保護(hù)的流程是WREN - WRSR寫(xiě)0到BP位- 完成。3. PIC18F47Q10的SPI外設(shè)配置別被寄存器名字騙了3.1 SPI1模塊的時(shí)鐘源和分頻計(jì)算PIC18F47Q10有兩個(gè)SPI模塊SPI1和SPI2。SPI1掛在PPS外設(shè)引腳選擇上可以映射到任意數(shù)字引腳靈活性很高。我這次用SPI1映射到RC3SCK、RC4SDI、RC5SDO片選CS用RA5普通GPIO控制。SPI時(shí)鐘的計(jì)算公式Fspi Fosc / (4 * (SPI1CON1bits.CKP 1) * (2^SPI1CON1bits.SSP1ADD))實(shí)際配置時(shí)我系統(tǒng)時(shí)鐘跑64MHzSPI1CON1bits.SSP1ADD設(shè)為0CKP0Mode 0得到Fspi 64MHz / 4 16MHz。這里有個(gè)坑PIC18F47Q10的SPI模塊在高速下對(duì)引腳驅(qū)動(dòng)能力有要求。我一開(kāi)始用默認(rèn)的引腳配置16MHz下波形上升沿有點(diǎn)塌讀回來(lái)的數(shù)據(jù)偶爾出錯(cuò)。后來(lái)把SCK和SDO引腳的Slew Rate控制打開(kāi)并且把驅(qū)動(dòng)電流設(shè)到最大檔波形才干凈。3.2 PPS映射的步驟和容易漏掉的寄存器PPS映射是PIC18F47Q10比老PIC18F系列方便的地方但步驟不能少// 解鎖PPS PPSLOCK 0x55; PPSLOCK 0xAA; PPSLOCKbits.PPSLOCKED 0; // 映射SCK到RC3 RC3PPS 0x0F; // 0x0F是SPI1 SCK的輸出代碼 // 映射SDO到RC5 RC5PPS 0x10; // 0x10是SPI1 SDO的輸出代碼 // 映射SDI到RC4 SPI1SDIPPS 0x13; // 0x13是RC4的輸入代碼 // 鎖定PPS PPSLOCK 0x55; PPSLOCK 0xAA; PPSLOCKbits.PPSLOCKED 1;注意SPI1SDIPPS是輸入映射寫(xiě)法和其他輸出映射不一樣它是把某個(gè)引腳映射到SPI1的SDI輸入。這個(gè)寄存器的名字容易搞混我一開(kāi)始寫(xiě)成RC4PPS結(jié)果SDI一直讀不到數(shù)據(jù)。3.3 片選信號(hào)的處理硬件片選還是軟件片選PIC18F47Q10的SPI模塊支持硬件片選SS1但我不建議用。原因有兩個(gè)第一硬件片選在SPI使能之后如果SS1引腳為高模塊會(huì)自動(dòng)進(jìn)入從模式容易誤觸發(fā)第二MRAM的WREN和WRITE之間需要保持片選低電平硬件片選的時(shí)序控制不如軟件靈活。我的做法是用RA5做普通GPIO手動(dòng)控制#define MRAM_CS_LAT LATA5 #define MRAM_CS_TRIS TRISA5 // 初始化 MRAM_CS_TRIS 0; // 輸出 MRAM_CS_LAT 1; // 默認(rèn)拉高 // 片選拉低 MRAM_CS_LAT 0; // ... SPI操作 ... // 片選拉高 MRAM_CS_LAT 1;片選拉低和第一個(gè)SCK邊沿之間要留至少20ns的建立時(shí)間。16MHz下每個(gè)時(shí)鐘周期62.5ns所以直接在片選拉低后插入一個(gè)NOP就夠了。但如果你SPI跑得更快比如32MHz就要考慮加一點(diǎn)延時(shí)或者用CLC模塊做硬件延時(shí)。4. 數(shù)據(jù)寫(xiě)入與讀取的完整實(shí)現(xiàn)從單字節(jié)到批量記錄4.1 單字節(jié)寫(xiě)入的完整時(shí)序?qū)懸粋€(gè)字節(jié)到MRAM的完整流程片選拉低發(fā)送WREN0x06片選拉高片選拉低發(fā)送WRITE0x02發(fā)送24位地址MR25H40CDF是4Mbit需要19位地址但按24位發(fā)送高5位忽略發(fā)送數(shù)據(jù)字節(jié)片選拉高注意第3步和第4步之間片選必須拉高這是WREN命令生效的條件。我試過(guò)WREN之后不拉高片選直接發(fā)WRITE數(shù)據(jù)寫(xiě)不進(jìn)去。代碼實(shí)現(xiàn)void MRAM_WriteByte(uint32_t addr, uint8_t data) { MRAM_CS_LAT 0; SPI1_ExchangeByte(0x06); // WREN MRAM_CS_LAT 1; MRAM_CS_LAT 0; SPI1_ExchangeByte(0x02); // WRITE SPI1_ExchangeByte((addr 16) 0xFF); SPI1_ExchangeByte((addr 8) 0xFF); SPI1_ExchangeByte(addr 0xFF); SPI1_ExchangeByte(data); MRAM_CS_LAT 1; }SPI1_ExchangeByte函數(shù)就是往SPI1BUF寫(xiě)數(shù)據(jù)然后等SPI1IF標(biāo)志置位讀SPI1BUF返回接收到的字節(jié)。4.2 批量寫(xiě)入時(shí)如何避免頻繁WREN如果你要連續(xù)寫(xiě)多個(gè)字節(jié)比如一次記錄16字節(jié)的數(shù)據(jù)包有兩種做法方案A每個(gè)字節(jié)都發(fā)WREN WRITE。這樣最保險(xiǎn)但效率低每次寫(xiě)都要額外的WREN開(kāi)銷(xiāo)。方案B發(fā)一次WREN然后發(fā)WRITE命令連續(xù)發(fā)送多個(gè)數(shù)據(jù)字節(jié)最后拉高片選。MRAM支持在WRITE命令之后連續(xù)寫(xiě)入地址自動(dòng)遞增沒(méi)有頁(yè)邊界限制。我實(shí)測(cè)方案B完全可行而且效率高很多。寫(xiě)16字節(jié)的數(shù)據(jù)包方案A需要16次WRENWRITE方案B只需要1次WREN1次WRITE16個(gè)數(shù)據(jù)字節(jié)。在16MHz SPI下方案A大概耗時(shí)200us方案B只要30us左右。但方案B有一個(gè)風(fēng)險(xiǎn)如果SPI傳輸過(guò)程中受到干擾片選意外拉高后面的數(shù)據(jù)就丟了。所以在高干擾環(huán)境下我建議還是用方案A或者至少加CRC校驗(yàn)。4.3 讀取操作的地址自動(dòng)遞增特性MRAM的READ命令0x03支持連續(xù)讀取地址自動(dòng)遞增可以一直讀到0x7FFFF然后回繞到0x0000。這個(gè)特性用來(lái)做數(shù)據(jù)回放非常方便。void MRAM_ReadBuffer(uint32_t addr, uint8_t *buf, uint16_t len) { MRAM_CS_LAT 0; SPI1_ExchangeByte(0x03); // READ SPI1_ExchangeByte((addr 16) 0xFF); SPI1_ExchangeByte((addr 8) 0xFF); SPI1_ExchangeByte(addr 0xFF); for (uint16_t i 0; i len; i) { buf[i] SPI1_ExchangeByte(0x00); } MRAM_CS_LAT 1; }注意讀取的時(shí)候發(fā)送的是0x00因?yàn)镾PI是全雙工你發(fā)什么字節(jié)出去不重要重要的是從設(shè)備返回的數(shù)據(jù)。4.4 數(shù)據(jù)記錄模塊的環(huán)形緩沖區(qū)設(shè)計(jì)我的應(yīng)用場(chǎng)景是每秒記錄100次設(shè)備狀態(tài)每次16字節(jié)。MRAM總?cè)萘?12KB我劃了前256KB做環(huán)形緩沖區(qū)后256KB做參數(shù)存儲(chǔ)和日志。環(huán)形緩沖區(qū)的寫(xiě)指針存在MRAM的固定地址每次寫(xiě)入前先讀指針寫(xiě)完數(shù)據(jù)后更新指針。這里有個(gè)關(guān)鍵點(diǎn)指針更新必須和數(shù)據(jù)寫(xiě)入在同一個(gè)WRITE命令序列里完成否則掉電時(shí)可能出現(xiàn)數(shù)據(jù)寫(xiě)了但指針沒(méi)更新或者指針更新了但數(shù)據(jù)沒(méi)寫(xiě)。我的做法是把指針和數(shù)據(jù)打包成一個(gè)結(jié)構(gòu)體一次寫(xiě)入typedef struct { uint32_t write_ptr; uint8_t data[16]; uint16_t crc; } Record_t;每次記錄時(shí)先讀回當(dāng)前write_ptr構(gòu)造Record_t計(jì)算CRC然后從write_ptr對(duì)應(yīng)的地址開(kāi)始連續(xù)寫(xiě)入sizeof(Record_t)個(gè)字節(jié)。因?yàn)镸RAM沒(méi)有頁(yè)邊界限制這一整塊可以一次WRITE命令寫(xiě)完保證原子性。5. 實(shí)測(cè)中遇到的三個(gè)典型問(wèn)題與排查過(guò)程5.1 問(wèn)題一連續(xù)寫(xiě)入時(shí)偶發(fā)數(shù)據(jù)錯(cuò)位現(xiàn)象批量寫(xiě)入1000次偶爾有幾次讀回來(lái)的數(shù)據(jù)整體偏移一個(gè)字節(jié)。排查過(guò)程先用邏輯分析儀抓SPI波形發(fā)現(xiàn)錯(cuò)位的那幾次WRITE命令之后的第一個(gè)數(shù)據(jù)字節(jié)和地址的最后一個(gè)字節(jié)之間多了一個(gè)時(shí)鐘周期。仔細(xì)看波形是片選拉低之后SCK的第一個(gè)邊沿來(lái)得太早MRAM還沒(méi)準(zhǔn)備好。根因PIC18F47Q10的SPI模塊在片選拉低后如果SPI1BUF里還有之前的數(shù)據(jù)第一個(gè)SCK邊沿可能會(huì)提前。解決辦法是在片選拉低之后、寫(xiě)SPI1BUF之前先清空SPI1BUF或者插入一個(gè)短延時(shí)。我最后的做法是在MRAM_CS_LAT 0之后加了一個(gè)__delay_us(1)問(wèn)題消失。1us在16MHz下是16個(gè)時(shí)鐘周期對(duì)整體性能影響可以忽略。5.2 問(wèn)題二掉電測(cè)試時(shí)最后幾條記錄丟失現(xiàn)象正常運(yùn)行時(shí)數(shù)據(jù)都對(duì)但突然斷電再上電最后幾條記錄讀出來(lái)是0xFF。根因分析MRAM本身寫(xiě)入是非易失的寫(xiě)入完成就保存了。問(wèn)題出在寫(xiě)指針的更新上。我原來(lái)的代碼是先寫(xiě)數(shù)據(jù)再更新指針兩個(gè)操作分開(kāi)。掉電發(fā)生在寫(xiě)數(shù)據(jù)和更新指針之間時(shí)數(shù)據(jù)寫(xiě)進(jìn)去了但指針沒(méi)更新下次上電后新數(shù)據(jù)會(huì)覆蓋掉剛寫(xiě)的那條。解決辦法把數(shù)據(jù)和指針打包成一個(gè)結(jié)構(gòu)體一次WRITE寫(xiě)完。這樣要么全寫(xiě)進(jìn)去要么全沒(méi)寫(xiě)不會(huì)出現(xiàn)中間狀態(tài)。改完之后掉電測(cè)試跑了200次沒(méi)有再丟數(shù)據(jù)。5.3 問(wèn)題三SPI時(shí)鐘跑高之后讀數(shù)據(jù)出錯(cuò)現(xiàn)象SPI時(shí)鐘從8MHz提到16MHz后讀回來(lái)的數(shù)據(jù)偶爾有bit翻轉(zhuǎn)。排查用示波器看SCK和SDI波形發(fā)現(xiàn)16MHz下SDI的上升沿有點(diǎn)慢在SCK采樣邊沿處還沒(méi)達(dá)到VIH。根因PIC18F47Q10的SDI引腳輸入沒(méi)有施密特觸發(fā)器而且默認(rèn)驅(qū)動(dòng)能力較弱。MRAM的SDO輸出驅(qū)動(dòng)能力也有限兩者之間的PCB走線如果比較長(zhǎng)分布電容會(huì)導(dǎo)致邊沿變緩。解決辦法第一縮短SPI走線MRAM盡量靠近MCU第二在SDI線上加一個(gè)4.7kΩ上拉電阻到3.3V加速上升沿第三把SPI時(shí)鐘降到12MHz留出余量。三招一起上問(wèn)題徹底解決。注意SPI時(shí)鐘不是越高越好尤其是工業(yè)現(xiàn)場(chǎng)有干擾的情況下留20%的余量比跑滿(mǎn)速更可靠。6. 工業(yè)場(chǎng)景下的可靠性加固與經(jīng)驗(yàn)總結(jié)6.1 電源監(jiān)控與掉電保護(hù)MRAM雖然寫(xiě)入快但也不是瞬間完成。16MHz SPI下寫(xiě)一個(gè)32字節(jié)的結(jié)構(gòu)體大概需要20us。如果電源掉電時(shí)間小于20us寫(xiě)入可能不完整。我的做法是用PIC18F47Q10的HLVD高/低電壓檢測(cè)模塊設(shè)置檢測(cè)閾值為3.0V。當(dāng)電源電壓降到3.0V時(shí)HLVD觸發(fā)中斷在中斷里立即停止所有寫(xiě)入操作把關(guān)鍵狀態(tài)保存到MRAM的保留區(qū)域。從3.0V降到MRAM最低工作電壓2.7V大概有幾百微秒的窗口足夠完成一次緊急寫(xiě)入。6.2 CRC校驗(yàn)的選型和計(jì)算開(kāi)銷(xiāo)數(shù)據(jù)記錄場(chǎng)景下CRC是必須的。我選的是CRC-16/CCITT多項(xiàng)式0x1021初始值0xFFFF。PIC18F47Q10沒(méi)有硬件CRC模塊用軟件查表法計(jì)算16字節(jié)數(shù)據(jù)大概耗時(shí)8us可以接受。如果你對(duì)速度要求更高可以用PIC18F47Q10的CLC模塊配合DMA做硬件CRC但配置比較復(fù)雜我這次沒(méi)上。6.3 MRAM的壽命和磨損均衡MRAM的擦寫(xiě)壽命是10的14次方以上按每秒寫(xiě)100次算一年也就3.15×10的9次方次遠(yuǎn)遠(yuǎn)沒(méi)到壽命上限。所以MRAM不需要磨損均衡環(huán)形緩沖區(qū)直接循環(huán)覆蓋就行。但有一點(diǎn)要注意MRAM的寫(xiě)入次數(shù)雖然無(wú)限但每次寫(xiě)入的功耗比讀取高。如果設(shè)備是電池供電要控制寫(xiě)入頻率或者用批量寫(xiě)入減少WREN命令的次數(shù)。6.4 和EEPROM、FRAM的對(duì)比選型建議特性MR25H40CDF (MRAM)典型EEPROM典型FRAM接口SPII2C/SPII2C/SPI寫(xiě)入速度極快無(wú)擦除慢5ms/字節(jié)快無(wú)擦除擦寫(xiě)壽命10^1410^610^12掉電保持是是是容量4Mbit通?!?Mbit通?!?Mbit成本中高低中工業(yè)溫度-40~85°C-40~85°C-40~85°C選型邏輯很簡(jiǎn)單寫(xiě)入頻率高、壽命要求高、容量需求大選MRAM成本敏感、寫(xiě)入頻率低選EEPROM介于兩者之間FRAM是折中方案。6.5 我在這個(gè)項(xiàng)目里踩過(guò)的幾個(gè)小坑第一個(gè)坑MRAM的SOIC-8封裝和W25Q64的SOIC-8封裝引腳定義不一樣。W25Q64的pin1是CSpin2是DOpin3是WPpin4是GNDMR25H40CDF的pin1是CSpin2是SOpin3是WPpin4是GND。看起來(lái)一樣但pin5、pin6、pin7、pin8的定義有差異。我一開(kāi)始直接拿W25Q64的PCB改結(jié)果pin7和pin8接反了MRAM一直不工作。后來(lái)查數(shù)據(jù)手冊(cè)才發(fā)現(xiàn)MR25H40CDF的pin7是NCpin8是VCC而W25Q64的pin7是HOLDpin8是VCC。雖然pin8都是VCC但pin7的處理不一樣MRAM的pin7必須懸空不能接地。第二個(gè)坑PIC18F47Q10的SPI1模塊在配置為Master模式時(shí)如果SS1引腳被配置為輸入且為低電平模塊會(huì)自動(dòng)切換到Slave模式。我一開(kāi)始把SS1引腳也映射到了RA5結(jié)果片選拉低的時(shí)候SPI模塊直接罷工。后來(lái)把SS1功能關(guān)掉RA5純做GPIO問(wèn)題解決。第三個(gè)坑MRAM的WREN命令之后如果片選拉高再拉低WEL位會(huì)保持。但如果中間插入了其他SPI設(shè)備的操作WEL位可能會(huì)被意外清除。我的SPI總線上只有MRAM所以沒(méi)這個(gè)問(wèn)題但如果你的總線上有多個(gè)從設(shè)備每次WRITE之前都要重新發(fā)WREN不能假設(shè)WEL位還在。6.6 給后續(xù)開(kāi)發(fā)者的幾條實(shí)用建議如果你準(zhǔn)備用MR25H40CDF做數(shù)據(jù)記錄我的建議是第一SPI時(shí)鐘先跑8MHz調(diào)通之后再往上提不要一上來(lái)就16MHz第二片選控制用GPIO不要用硬件SS第三數(shù)據(jù)和指針打包寫(xiě)入保證原子性第四加CRC校驗(yàn)工業(yè)現(xiàn)場(chǎng)沒(méi)有CRC的數(shù)據(jù)不可信第五電源監(jiān)控一定要做HLVD模塊用起來(lái)成本幾乎為零但能救命。這個(gè)方案我已經(jīng)在三個(gè)現(xiàn)場(chǎng)設(shè)備上跑了半年多每天寫(xiě)入量大概800萬(wàn)次目前沒(méi)有出現(xiàn)過(guò)數(shù)據(jù)丟失或MRAM失效的情況。MRAM的價(jià)格雖然比EEPROM貴但省掉了電池、超級(jí)電容和復(fù)雜的磨損均衡算法整體BOM成本和維護(hù)成本反而更低。如果你也在糾結(jié)數(shù)據(jù)存儲(chǔ)方案不妨試試MRAM這條路。