數(shù)據(jù)存儲方案:SPI驅(qū)動與可靠性設(shè)計)
1. 為什么MRAM在工業(yè)嵌入式場景里越來越受關(guān)注做工業(yè)控制和嵌入式數(shù)據(jù)采集的朋友最近幾年應(yīng)該都有一個共同的感受傳統(tǒng)EEPROM和NOR Flash在頻繁寫入場景下越來越不夠用了。EEPROM的擦寫壽命通常在100萬次左右NOR Flash大概10萬次而工業(yè)現(xiàn)場很多設(shè)備要求每秒甚至每毫秒記錄一次關(guān)鍵狀態(tài)一年下來寫入次數(shù)輕松突破千萬級別。這時候MR25H40CDF這顆MRAM磁性隨機存儲器就進入了視野。MR25H40CDF是Everspin推出的一款4Mbit SPI接口MRAM采用8引腳DFN或SOIC封裝工作電壓3.3V支持最高40MHz的SPI時鐘。它最大的特點就是近乎無限的擦寫壽命官方標(biāo)稱10^14次以上和無延遲寫入——不需要像Flash那樣先擦后寫也不需要等待內(nèi)部寫周期完成。這兩點對于工業(yè)數(shù)據(jù)記錄來說價值非常大。而PIC18F4550是一顆經(jīng)典的Microchip 8位單片機帶全速USB 2.0接口40引腳PDIP封裝擁有32KB Flash程序存儲器、2KB SRAM、256字節(jié)EEPROM內(nèi)置MSSP模塊可以配置為SPI主模式。它雖然不是新片子但在工業(yè)儀表、數(shù)據(jù)采集模塊、USB通信設(shè)備中存量巨大很多老項目還在持續(xù)維護和升級。把MR25H40CDF掛到PIC18F4550的SPI總線上做工業(yè)現(xiàn)場的數(shù)據(jù)存儲和讀取是一個非常務(wù)實的組合。這篇文章主要面向正在做工業(yè)嵌入式數(shù)據(jù)記錄、或者手頭有PIC18F4550老平臺需要擴展非易失存儲的工程師。我會從硬件連接、SPI驅(qū)動、讀寫時序、數(shù)據(jù)管理策略幾個層面把整個方案拆開講清楚包括我在實際項目中踩過的坑和驗證過的參數(shù)配置。2. 方案整體設(shè)計與關(guān)鍵選型考量2.1 為什么選MRAM而不是FRAM或NVSRAM在工業(yè)數(shù)據(jù)存儲這個領(lǐng)域可選的非易失方案其實不少EEPROM、NOR Flash、FRAM鐵電存儲器、NVSRAM帶電池的SRAM、MRAM。我當(dāng)初做選型的時候把這幾種都過了一遍最終選MR25H40CDF主要基于以下幾個維度的權(quán)衡。FRAM的寫入壽命也很高10^12到10^14次寫入速度也快但FRAM的容量通常偏小常見的是256Kbit到2Mbit而且價格按位算比MRAM貴不少。NVSRAM雖然寫入無限快但內(nèi)部靠電池維持?jǐn)?shù)據(jù)工業(yè)現(xiàn)場高溫環(huán)境下電池壽命是個隱患而且封裝里帶電池的器件在振動場景下可靠性存疑。NOR Flash容量大、便宜但擦寫壽命和寫入速度是硬傷做頻繁數(shù)據(jù)記錄需要配合磨損均衡算法軟件復(fù)雜度上去了。MR25H40CDF的4Mbit容量512KB對于工業(yè)數(shù)據(jù)記錄來說剛好夠用——可以存幾萬條帶時間戳的狀態(tài)記錄或者做環(huán)形緩沖區(qū)存最近的歷史數(shù)據(jù)。SPI接口簡單PIC18F4550的MSSP模塊直接支持不需要額外的地址總線。最關(guān)鍵的是寫入不需要等待SPI發(fā)送完數(shù)據(jù)數(shù)據(jù)就已經(jīng)在MRAM單元里了沒有內(nèi)部寫周期這對實時性要求高的場景非常友好。2.2 PIC18F4550的SPI資源分配PIC18F4550的MSSP模塊可以工作在SPI主模式或從模式。我們這里用主模式時鐘由單片機產(chǎn)生。引腳分配如下PIC18F4550引腳功能連接到MR25H40CDFRC3 (SCK)SPI時鐘SCKRC5 (SDO)主出從入SIRC4 (SDI)主入從出SORA5 (SS)片選軟件控制CSVDD/VSS電源VDD/VSS這里有個細(xì)節(jié)需要注意PIC18F4550的MSSP模塊在SPI主模式下SS引腳RA5可以被配置為普通IO片選由軟件控制。我建議一定要用軟件片選不要用硬件片選自動控制。原因后面講時序的時候會詳細(xì)說。另外PIC18F4550的SPI時鐘極性CKP和時鐘邊沿CKE需要根據(jù)MR25H40CDF的時序要求來配置。MR25H40CDF支持SPI模式0CPOL0CPHA0和模式3CPOL1CPHA1。我一般用模式0因為大多數(shù)邏輯分析儀和調(diào)試工具默認(rèn)就是模式0抓波形方便。2.3 電源與去耦設(shè)計MR25H40CDF的工作電壓是2.7V到3.6V典型3.3V。PIC18F4550可以工作在5V或3.3V。如果PIC18F4550用5V供電SPI信號電平就是5V直接連到MRAM的3.3V引腳會超過絕對最大額定值。這時候需要電平轉(zhuǎn)換可以用74LVC245或者簡單的電阻分壓但電阻分壓在高頻下波形會變差。我的建議是整個系統(tǒng)統(tǒng)一用3.3V供電PIC18F4550在3.3V下可以跑到20MHz需要配置PLLSPI時鐘也能滿足MRAM的40MHz上限。這樣省掉電平轉(zhuǎn)換硬件簡單可靠性高。去耦電容方面MR25H40CDF的VDD引腳旁邊必須放一個0.1uF的陶瓷電容越近越好。如果SPI走線超過10cm建議再并一個1uF的電容。工業(yè)現(xiàn)場電磁干擾大電源上的毛刺可能導(dǎo)致MRAM寫入錯誤這個電容不能省。3. MR25H40CDF的SPI驅(qū)動實現(xiàn)細(xì)節(jié)3.1 指令集與操作碼解析MR25H40CDF的指令集和標(biāo)準(zhǔn)SPI Flash很像但有幾個關(guān)鍵區(qū)別。先看操作碼表指令名稱操作碼說明WREN0x06寫使能WRDI0x04寫禁止RDSR0x05讀狀態(tài)寄存器WRSR0x01寫狀態(tài)寄存器READ0x03讀數(shù)據(jù)WRITE0x02寫數(shù)據(jù)SLEEP0xB9進入低功耗模式WAKE0xAB喚醒和Flash最大的區(qū)別是MRAM的WRITE指令不需要先擦除。Flash寫之前必須發(fā)WREN然后發(fā)扇區(qū)擦除指令等待擦除完成再發(fā)WREN再發(fā)WRITE。MRAM只需要發(fā)WREN然后直接發(fā)WRITE指令加地址和數(shù)據(jù)就行。整個流程簡化了很多寫入延遲也從毫秒級降到微秒級。狀態(tài)寄存器的bit0是WEL寫使能鎖存bit1是BP0bit2是BP1bit7是SRWD。上電后WEL是0任何寫操作之前必須先發(fā)WREN把WEL置1。寫操作完成后WEL自動清零。這個機制和Flash一樣是為了防止誤寫入。3.2 PIC18F4550的SPI初始化代碼下面是我在實際項目中用的SPI初始化函數(shù)基于XC8編譯器void SPI_Init(void) { // 配置SPI引腳 TRISCbits.TRISC3 0; // SCK輸出 TRISCbits.TRISC5 0; // SDO輸出 TRISCbits.TRISC4 1; // SDI輸入 TRISAbits.TRISA5 0; // CS輸出 // 片選默認(rèn)拉高 CS_MRAM 1; // MSSP配置SPI主模式時鐘Fosc/16 // SSPCON1: WCOL0, SSPOV0, SSPEN1, CKP0, SSPM0000 SSPCON1 0x20; // 使能SPI模式0時鐘Fosc/16 // SSPSTAT: SMP0(中間采樣), CKE0(空閑到激活時鐘) SSPSTAT 0x00; // 時鐘速度計算Fosc48MHz時SPI時鐘3MHz // 如果需要更快可以改SSPM為0001(Fosc/4)12MHz }這里解釋一下時鐘配置。PIC18F4550的SPI時鐘由SSPCON1的SSPM位決定SSPM0000Fosc/4SSPM0001Fosc/16SSPM0010Fosc/64SSPM0011TMR2溢出如果系統(tǒng)時鐘是48MHzUSB應(yīng)用常用Fosc/16就是3MHzFosc/4就是12MHz。MR25H40CDF支持40MHz所以12MHz完全沒問題。但實際用的時候要考慮PCB走線質(zhì)量和信號完整性我一般先用3MHz調(diào)通再逐步提高到12MHz。3.3 字節(jié)讀寫函數(shù)實現(xiàn)SPI發(fā)送和接收在PIC18F4550上是同一個寄存器SSPBUF。發(fā)送時寫SSPBUF接收時讀SSPBUF。注意讀之前要等BFBuffer Full標(biāo)志置位否則讀到的可能是舊數(shù)據(jù)。uint8_t SPI_Transfer(uint8_t data) { SSPBUF data; // 寫入發(fā)送數(shù)據(jù) while(!PIR1bits.SSPIF); // 等待傳輸完成 PIR1bits.SSPIF 0; // 清除中斷標(biāo)志 return SSPBUF; // 返回接收數(shù)據(jù) } void MRAM_WriteEnable(void) { CS_MRAM 0; SPI_Transfer(0x06); // WREN指令 CS_MRAM 1; } void MRAM_Write(uint32_t addr, uint8_t *buf, uint16_t len) { MRAM_WriteEnable(); CS_MRAM 0; SPI_Transfer(0x02); // WRITE指令 SPI_Transfer((addr 16) 0xFF); // 地址高8位 SPI_Transfer((addr 8) 0xFF); // 地址中8位 SPI_Transfer(addr 0xFF); // 地址低8位 for(uint16_t i 0; i len; i) { SPI_Transfer(buf[i]); } CS_MRAM 1; } void MRAM_Read(uint32_t addr, uint8_t *buf, uint16_t len) { CS_MRAM 0; SPI_Transfer(0x03); // READ指令 SPI_Transfer((addr 16) 0xFF); SPI_Transfer((addr 8) 0xFF); SPI_Transfer(addr 0xFF); for(uint16_t i 0; i len; i) { buf[i] SPI_Transfer(0x00); // 發(fā)送啞數(shù)據(jù)接收返回 } CS_MRAM 1; }注意MRAM_Read里發(fā)送的是0x00這是啞數(shù)據(jù)dummy byte因為SPI是全雙工讀的時候也要有時鐘主機發(fā)送什么無所謂從機不關(guān)心。但有些SPI從機要求啞數(shù)據(jù)是特定值MR25H40CDF不挑0x00或0xFF都行。3.4 片選時序的關(guān)鍵細(xì)節(jié)前面提到一定要用軟件片選原因在這里。MR25H40CDF的CS引腳有兩個作用一是選中芯片二是界定指令邊界。每個SPI事務(wù)必須以CS拉低開始以CS拉高結(jié)束。如果在事務(wù)中間CS抖動了一下MRAM會認(rèn)為指令結(jié)束后續(xù)數(shù)據(jù)就丟了。PIC18F4550的硬件SS功能在SPI主模式下如果SSPCON1的SSPM0000且SSEN使能SS引腳會被自動控制。但硬件SS的時序和MRAM要求的CS時序不一定完全匹配而且硬件SS在多從機場景下容易出問題。所以我的做法是把SS功能關(guān)掉RA5配置為普通IO手動控制CS。手動控制CS的時候要注意CS拉低之后要等至少10ns再發(fā)第一個時鐘MRAM的tCSS參數(shù)。CS拉高之前要確保最后一個時鐘已經(jīng)完成tCSH至少10ns。在3MHz SPI時鐘下一個時鐘周期333ns這些納秒級的延時自然滿足。但如果SPI跑到40MHz周期25ns就需要檢查一下代碼里CS操作的順序確保沒有額外的指令延遲導(dǎo)致時序違規(guī)。4. 工業(yè)數(shù)據(jù)存儲策略與實操流程4.1 數(shù)據(jù)幀格式設(shè)計工業(yè)現(xiàn)場的數(shù)據(jù)記錄不能只存原始字節(jié)要有結(jié)構(gòu)。我一般設(shè)計一個固定長度的數(shù)據(jù)幀包含時間戳、設(shè)備ID、數(shù)據(jù)類型、數(shù)據(jù)載荷和校驗。下面是我在一個電機監(jiān)控項目里用的幀格式字段長度說明幀頭2字節(jié)0xAA55用于同步時間戳4字節(jié)秒級Unix時間戳設(shè)備ID1字節(jié)區(qū)分不同設(shè)備數(shù)據(jù)類型1字節(jié)0x01溫度0x02電流0x03轉(zhuǎn)速數(shù)據(jù)載荷8字節(jié)浮點數(shù)或整型CRC162字節(jié)校驗幀頭到載荷幀尾1字節(jié)0x0D一幀總共19字節(jié)。512KB的MRAM可以存大約27000幀。如果每秒記錄一幀可以存7.5小時。如果每分鐘記錄一幀可以存18天。實際項目中我會根據(jù)需求調(diào)整記錄頻率或者用環(huán)形緩沖區(qū)覆蓋最舊的數(shù)據(jù)。4.2 環(huán)形緩沖區(qū)實現(xiàn)環(huán)形緩沖區(qū)是工業(yè)數(shù)據(jù)記錄的經(jīng)典方案。MRAM的地址空間從0x00000到0x7FFFF512KB。我把它分成兩部分前4KB存元數(shù)據(jù)寫指針、讀指針、總記錄數(shù)、設(shè)備配置后面508KB存數(shù)據(jù)幀。元數(shù)據(jù)區(qū)的結(jié)構(gòu)typedef struct { uint32_t write_ptr; // 下一個寫入位置相對于數(shù)據(jù)區(qū)起始 uint32_t read_ptr; // 下一個讀取位置 uint32_t total_count; // 總寫入幀數(shù) uint32_t magic; // 0x4D52414D (MRAM) uint8_t reserved[240]; // 預(yù)留 } MRAM_Meta_t;每次寫入一幀之前先讀元數(shù)據(jù)計算寫入地址寫數(shù)據(jù)幀然后更新write_ptr和total_count再把元數(shù)據(jù)寫回。這里有個關(guān)鍵點元數(shù)據(jù)的更新必須原子化。如果寫完數(shù)據(jù)幀但元數(shù)據(jù)沒更新下次上電后write_ptr還是舊的新數(shù)據(jù)會被覆蓋。我的做法是元數(shù)據(jù)存兩份地址0x0000和0x0800各一份每份帶CRC。寫入時先寫數(shù)據(jù)幀再寫第一份元數(shù)據(jù)再寫第二份元數(shù)據(jù)。上電初始化時讀兩份元數(shù)據(jù)比較CRC和write_ptr取有效的那份。如果兩份都無效說明MRAM是全新的或者數(shù)據(jù)損壞就初始化元數(shù)據(jù)。4.3 寫入速度實測與優(yōu)化我用邏輯分析儀抓過實際波形在SPI時鐘3MHz下寫一幀19字節(jié)的時間構(gòu)成如下CS拉低到第一個時鐘約1us指令地址4字節(jié) × 8bit / 3MHz 10.7us數(shù)據(jù)載荷19字節(jié) × 8bit / 3MHz 50.7usCS拉高到下一個事務(wù)約2us元數(shù)據(jù)更新讀寫約200us總計約265us。如果SPI時鐘提到12MHz數(shù)據(jù)部分可以縮短到12.7us但元數(shù)據(jù)更新的時間主要花在SPI事務(wù)的固定開銷上提升有限。所以瓶頸不在SPI時鐘而在元數(shù)據(jù)管理策略。優(yōu)化方法不要每寫一幀就更新元數(shù)據(jù)??梢栽赟RAM里緩存write_ptr每寫10幀或者每1秒更新一次元數(shù)據(jù)。這樣元數(shù)據(jù)更新頻率降低10倍整體寫入速度提升明顯。但代價是掉電時可能丟失最近幾幀的元數(shù)據(jù)不過數(shù)據(jù)幀本身已經(jīng)寫進MRAM了只是write_ptr沒更新下次上電后新數(shù)據(jù)會被覆蓋。如果對數(shù)據(jù)完整性要求極高可以每幀都更新元數(shù)據(jù)犧牲速度換可靠性。4.4 讀取流程與數(shù)據(jù)導(dǎo)出讀取數(shù)據(jù)有兩種模式實時讀取和歷史導(dǎo)出。實時讀取是設(shè)備正常運行時上位機通過USB或串口發(fā)送指令單片機從MRAM里讀最新幾幀返回。歷史導(dǎo)出是設(shè)備停機維護時把MRAM里的數(shù)據(jù)全部讀出來上傳到PC。實時讀取的實現(xiàn)比較簡單根據(jù)read_ptr計算地址讀一幀CRC校驗通過USB CDC發(fā)送。PIC18F4550的USB模塊可以配置成CDC類在PC上顯示為虛擬串口波特率隨便設(shè)USB CDC不受波特率限制。歷史導(dǎo)出的數(shù)據(jù)量比較大508KB全部讀出來在3MHz SPI下需要約1.4秒508K × 8 / 3M。如果通過USB全速12Mbps傳輸理論上一秒多能傳完。實際測試中加上協(xié)議開銷和PC端處理大概3到5秒。這個速度對于維護場景完全可以接受。導(dǎo)出的時候要注意讀取過程中設(shè)備不能寫入否則read_ptr和write_ptr會沖突。我的做法是進入導(dǎo)出模式前先停止數(shù)據(jù)記錄任務(wù)把write_ptr和read_ptr快照到SRAM然后按快照讀取。導(dǎo)出完成后恢復(fù)記錄任務(wù)。5. 常見問題與排查技巧實錄5.1 寫入后讀出來全是0xFF或0x00這是新手最常遇到的問題。原因通常有三個一是WREN指令沒發(fā)或者發(fā)完WREN后CS拉高了WEL位被清除二是地址發(fā)錯了寫到了未使用的區(qū)域三是SPI模式配置不對數(shù)據(jù)采樣邊沿錯了。排查步驟先用邏輯分析儀抓SPI波形看WREN指令0x06后面CS是否保持低電平直到WRITE指令發(fā)完。MRAM要求WREN和WRITE可以在同一個CS低電平周期內(nèi)完成也可以分開。我一般分開做WREN一個CS周期WRITE另一個CS周期。但要注意WREN之后WEL位保持直到下一個WRITE完成或者WRDI。如果波形沒問題讀一下狀態(tài)寄存器RDSR0x05看bit0WEL是否為1。如果WEL是0說明WREN沒生效。檢查CS時序WREN指令發(fā)送時CS必須拉低發(fā)送完可以拉高WEL會保持。5.2 SPI通信不穩(wěn)定偶爾讀到錯誤數(shù)據(jù)工業(yè)現(xiàn)場電磁干擾大SPI走線長的時候容易出現(xiàn)位錯誤。我遇到過的情況是MRAM和單片機在同一塊板上但SPI走線經(jīng)過了電機驅(qū)動區(qū)域電機啟動時SPI數(shù)據(jù)偶爾出錯。解決方法一是降低SPI時鐘從12MHz降到3MHz給信號建立更多時間二是在SCK和MOSI上串22歐姆電阻抑制反射三是在MRAM的VDD和VSS之間加0.1uF和1uF電容靠近芯片引腳四是軟件上加CRC校驗發(fā)現(xiàn)錯誤就重讀。CRC校驗我用的是一幀一校驗讀出來先算CRC16和幀尾的CRC比較不匹配就丟棄或者重讀。重讀最多3次3次都失敗就記錄錯誤日志。實際跑下來加了CRC之后數(shù)據(jù)可靠性提升非常明顯誤碼率從萬分之一降到百萬分之一以下。5.3 掉電后數(shù)據(jù)丟失或元數(shù)據(jù)損壞MRAM本身是非易失的掉電數(shù)據(jù)不會丟。但元數(shù)據(jù)更新過程中掉電可能導(dǎo)致write_ptr和實際數(shù)據(jù)不一致。比如數(shù)據(jù)幀寫完了元數(shù)據(jù)還沒更新掉電后write_ptr指向舊位置下次上電后新數(shù)據(jù)覆蓋了剛寫的那幀。解決方案是雙份元數(shù)據(jù)加CRC。寫入順序?qū)憯?shù)據(jù)幀 → 寫元數(shù)據(jù)A → 寫元數(shù)據(jù)B。上電時讀A和B如果A的CRC有效且write_ptr B的write_ptr用A否則用B。如果兩個都無效用默認(rèn)值初始化。還有一種情況是掉電時正在寫元數(shù)據(jù)導(dǎo)致元數(shù)據(jù)A寫了一半。這時候A的CRC肯定無效B還是舊的但有效用B恢復(fù)。最壞情況是A和B都寫了一半但概率極低因為元數(shù)據(jù)只有幾十字節(jié)寫入時間幾十微秒掉電恰好落在兩個元數(shù)據(jù)寫入之間的概率很小。5.4 常見問題速查表現(xiàn)象可能原因排查方法解決措施讀出來全0xFFCS時序錯誤邏輯分析儀抓CS和SCK確保CS在事務(wù)期間保持低寫入不生效WREN未執(zhí)行讀狀態(tài)寄存器bit0寫前先發(fā)WREN數(shù)據(jù)偶爾錯誤SPI時鐘太快降低時鐘測試降到3MHz或加CRC元數(shù)據(jù)損壞掉電時寫入讀雙份元數(shù)據(jù)雙份CRC恢復(fù)邏輯通信完全無響應(yīng)電源或接線測VDD和CS電壓檢查3.3V和地連接讀取速度慢SPI時鐘低測SCK頻率提高SSPM配置5.5 實操心得先調(diào)通再優(yōu)化我剛開始做這個方案的時候想一步到位把SPI配到12MHz結(jié)果波形慘不忍睹數(shù)據(jù)錯誤率很高。后來退回到3MHz先把讀寫邏輯調(diào)通確認(rèn)數(shù)據(jù)正確再逐步提高時鐘。每提高一檔就用邏輯分析儀看波形用CRC統(tǒng)計誤碼率。最后在12MHz下穩(wěn)定運行但3MHz到12MHz之間試了四五次才找到穩(wěn)定的配置。另一個心得是MRAM的寫入不需要等待但SPI事務(wù)之間的間隔不能太短。我試過連續(xù)寫CS拉高后立刻拉低發(fā)下一個指令結(jié)果偶爾出錯。后來在CS拉高后加了一個NOP延時約1us就穩(wěn)定了。查手冊發(fā)現(xiàn)MRAM的tSHSLCS高電平最短時間是10ns理論上不需要延時但實際PCB上有寄生電容CS上升沿需要時間加個小延時更保險。6. 性能優(yōu)化與擴展思路6.1 SPI時鐘提升的邊界條件MR25H40CDF標(biāo)稱最高40MHz SPI時鐘但實際能跑多快取決于PCB走線、電源質(zhì)量和單片機SPI輸出能力。PIC18F4550在48MHz系統(tǒng)時鐘下SPI最高是Fosc/412MHz。如果想更快需要換更高級的單片機比如PIC32系列或者STM32SPI可以跑到40MHz以上。在12MHz下我實測寫一幀19字節(jié)含元數(shù)據(jù)更新約120us讀一幀約60us。如果去掉元數(shù)據(jù)更新寫一幀只要約30us。這個速度對于大多數(shù)工業(yè)數(shù)據(jù)記錄場景已經(jīng)足夠了。如果要求更高可以考慮用DMA或者雙緩沖但PIC18F4550沒有DMA只能靠優(yōu)化代碼。6.2 數(shù)據(jù)壓縮與存儲效率508KB的數(shù)據(jù)區(qū)如果每幀19字節(jié)能存27000多幀。如果數(shù)據(jù)有規(guī)律可以做壓縮。比如溫度數(shù)據(jù)變化緩慢可以只存變化量delta編碼每幀從8字節(jié)載荷壓縮到2字節(jié)。這樣一幀變成13字節(jié)存儲量提升30%以上。但壓縮會增加CPU負(fù)擔(dān)和代碼復(fù)雜度。PIC18F4550的指令周期是48MHz/412MIPS做簡單的delta編碼沒問題做復(fù)雜的壓縮算法就吃力了。我的建議是如果數(shù)據(jù)變化確實緩慢用簡單的差分編碼如果數(shù)據(jù)隨機性大不要壓縮直接存原始數(shù)據(jù)可靠性優(yōu)先。6.3 多設(shè)備SPI總線擴展一個PIC18F4550的SPI總線可以掛多個MRAM芯片用不同的CS引腳選擇。比如掛4片MR25H40CDF總?cè)萘?MB。CS引腳可以用RA5、RB0、RB1、RB2軟件控制。但要注意SPI總線的負(fù)載效應(yīng)每增加一個從機SCK和MOSI上的電容就增加高速下波形會變差。如果掛4片以上建議加SPI緩沖器或者用多路復(fù)用器。另一個擴展方向是用MRAM做程序存儲器的擴展。PIC18F4550只有32KB Flash如果程序超過32KB可以把不常用的函數(shù)或數(shù)據(jù)表放到MRAM里運行時按需讀取。但MRAM是SPI接口不能直接執(zhí)行代碼只能做數(shù)據(jù)存儲。如果需要執(zhí)行代碼得用并行接口的MRAM或者帶XIP功能的Flash。6.4 低功耗設(shè)計考慮工業(yè)現(xiàn)場有些設(shè)備是電池供電的低功耗很重要。MR25H40CDF支持SLEEP模式0xB9指令進入后電流降到幾微安。PIC18F4550也有多種低功耗模式比如Sleep模式電流可以降到0.1uA以下。我的做法是數(shù)據(jù)記錄間隔期間單片機進入SleepMRAM進入SLEEP。定時器或者外部中斷喚醒后先發(fā)WAKE指令0xAB喚醒MRAM等待tWAK約100us再開始SPI通信。這樣平均功耗可以做到毫安級以下用一節(jié)18650電池能撐幾個月。但要注意MRAM從SLEEP喚醒后需要重新發(fā)WREN才能寫入。而且SLEEP期間CS必須保持高電平否則MRAM可能被意外喚醒。這些細(xì)節(jié)在低功耗調(diào)試的時候很容易忽略我當(dāng)初就因為CS在Sleep期間被拉低導(dǎo)致MRAM一直處于喚醒狀態(tài)功耗降不下來。7. 寫在最后的一些個人體會這個方案我從2019年第一次用在電機監(jiān)控項目上到現(xiàn)在陸續(xù)改了三四版中間踩過的坑基本都寫在上面了。MR25H40CDF這顆芯片給我的感覺是貴有貴的道理。單片價格比同容量的NOR Flash貴好幾倍但在頻繁寫入、高可靠性要求的場景下省下來的軟件復(fù)雜度和維護成本遠(yuǎn)超硬件差價。PIC18F4550雖然老但生態(tài)成熟XC8編譯器穩(wěn)定USB功能開箱即用配合MRAM做數(shù)據(jù)記錄模塊整體BOM成本可控開發(fā)周期短。如果你手頭有PIC18F4550的存量項目需要擴展存儲或者正在選型工業(yè)數(shù)據(jù)記錄方案這個組合值得認(rèn)真考慮。最后分享一個小技巧MRAM的WRITE指令支持跨頁寫入不像Flash那樣有頁邊界限制。你可以一次寫任意長度只要不超過芯片容量。但實際用的時候我建議單次寫入不要超過256字節(jié)因為PIC18F4550的SRAM只有2KB緩沖區(qū)太大容易和其他任務(wù)沖突。256字節(jié)的緩沖區(qū)加上元數(shù)據(jù)和棧空間SRAM占用在可控范圍內(nèi)。