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