先級與引腳復(fù)用引發(fā)的詭異問題)
1. 一個“工作日加班到深夜”的經(jīng)典現(xiàn)場從通信失敗到懷疑人生先說結(jié)論這起調(diào)試事故的最終根源不是線接錯了不是代碼邏輯錯了不是時鐘配置錯了而是一個極其隱蔽、極其容易忽略的外部中斷優(yōu)先級與SPI外設(shè)中斷之間的沖突疊加一個引腳復(fù)用狀態(tài)被意外改寫的連鎖反應(yīng)。聽起來有點繞但整個過程特別有代表性。如果你也用過STM32G0系列或者你在用任何新平臺接一顆不熟悉的從機(jī)芯片——尤其是像MT6835這種資料少、時序文檔寫得不全的芯片——這篇復(fù)盤值得你花十分鐘看完因為“見鬼”的背后往往不是鬼而是我們自己對硬件平臺的某幾個特性還沒有建立起肌肉記憶。我先交代一下項目背景這是一塊電池管理系統(tǒng)BMS里的子板主控是STM32G031K8T6從機(jī)是一顆MT6835——一顆用于無線充電接收端功率調(diào)節(jié)的芯片對外接口是SPI從模式。MT6835需要主機(jī)先通過SPI寫入配置寄存器然后周期性讀取狀態(tài)寄存器來判斷充電狀態(tài)。整個系統(tǒng)的設(shè)計目標(biāo)很樸素上電后主控通過SPI把初始化參數(shù)刷進(jìn)去然后每100ms輪詢一次。硬件設(shè)計階段一切順利板子打樣回來電源正常晶振起振SWD能連上GPIO輸出正常點亮了兩個LED。然后當(dāng)我第一次調(diào)用HAL_SPI_TransmitReceive()準(zhǔn)備和MT6835做握手通信時問題來了。通信結(jié)果完全不可控第一次上電偶爾能成功讀到幾個字節(jié)但下一次上電讀取的全是0xFF用示波器抓SCK和MOSI波形看起來是有輸出的但MISO上要么一直拉高要么偶爾有邊上跳的毛刺完全不像一顆正常工作的SPI從機(jī)。這種“波形有、數(shù)據(jù)沒”的狀態(tài)是SPI調(diào)試?yán)镒钊菀鬃屓俗タ竦摹2ㄐ斡姓f明主控在發(fā)數(shù)據(jù)沒說明從機(jī)沒正確響應(yīng)。那么問題就分成了兩個方向要么是MT6835壓根沒有進(jìn)入SPI模式要么是主控發(fā)的東西從機(jī)根本聽不懂。我最初也是沿著這兩個方向排查的結(jié)果在排查過程中發(fā)現(xiàn)MT6835的SPI引腳和這顆芯片另一個復(fù)用功能——一個和充電狀態(tài)相關(guān)的使能引腳——在初始化時序上存在關(guān)聯(lián)。這個關(guān)聯(lián)關(guān)系在芯片的參考手冊里寫得極其隱晦只在寄存器的某個注釋里提了一句這也是為什么我一開始完全沒往這個方向想。2. 為什么SPI“波形正?!眳s讀不到數(shù)據(jù)從機(jī)的上電時序比你想的更敏感2.1 先扒一下中斷發(fā)生后的實際執(zhí)行路徑問題定位的第一步是我在SPI中斷處理函數(shù)里加了調(diào)試計數(shù)器。之前我用的是HAL庫的標(biāo)準(zhǔn)回調(diào)在HAL_SPI_TxRxCpltCallback()里置標(biāo)志位但這個標(biāo)志位在主循環(huán)里判斷一直為真——也就是說中斷其實觸發(fā)過。真正讓我發(fā)現(xiàn)問題的是我單獨把SCK引腳切換成普通GPIO然后用軟件模擬SPI時序去讀MT6835的狀態(tài)寄存器居然能讀到數(shù)據(jù)。這就非?!耙姽怼绷擞布PI發(fā)出來的波形看起來完全正確但芯片不響應(yīng)軟件模擬SPI卻能正常讀寫。兩個方案的差別在哪首先是時鐘極性CPOL和時鐘相位CPHA。我配置的是模式0即CPOL0、CPHA0也就是空閑時SCK為低電平數(shù)據(jù)在第一個邊沿采樣。MT6835的時序圖雖然畫得潦草但模式0應(yīng)該沒問題。其次是時鐘頻率硬件SPI的預(yù)分頻我設(shè)的是16分頻在主頻64MHz下是4MHz的SCK軟件模擬SPI則慢得多大約500kHz。從機(jī)跟不上這么快的SCK是完全可能的。但這里有一個很奇怪的矛盾從機(jī)跟不上頻率的表現(xiàn)通常是MISO上還能看到部分?jǐn)?shù)據(jù)只是校驗不通過而我的情況是MISO幾乎沒有反應(yīng)。所以我當(dāng)時的直覺是——MT6835根本沒有把SPI當(dāng)作它的通信方式它可能認(rèn)為上電后收到的第一個字節(jié)是一個控制命令而這個命令的某些位被我的硬件SPI波形破壞了。2.2 關(guān)鍵發(fā)現(xiàn)硬件SPI傳輸結(jié)束后引腳被拉成了不可預(yù)期的高電平我把注意力集中到片選信號CS上。我的硬件設(shè)計里CS用的是普通GPIO控制沒有接外部上拉或下拉。在SPI空閑狀態(tài)CS應(yīng)該保持高電平在傳輸期間拉低。問題來了硬件SPI在傳輸完成后如果我沒有顯式把CS拉高它的電平會停在某個不確定的狀態(tài)。而MT6835的規(guī)格說明里明確要求CS的下降沿必須發(fā)生在SCK處于空閑狀態(tài)之后如果CS的電平變化和SCK翻轉(zhuǎn)之間沒有足夠的建立時間芯片內(nèi)部的SPI狀態(tài)機(jī)會進(jìn)入一個錯誤分支。我用示波器同時抓CS和SCK發(fā)現(xiàn)了一個細(xì)思極恐的現(xiàn)象CS下降沿穩(wěn)定出現(xiàn)在SCK第一個上升沿之前約200ns這個時序看起來是符合要求的但CS上升沿卻和SCK的最后一個邊沿幾乎同時發(fā)生。這意味著從機(jī)可能還沒把最后一個字節(jié)采樣完整主機(jī)就已經(jīng)宣布傳輸結(jié)束并釋放了CS。從機(jī)的響應(yīng)字節(jié)自然就丟了。那么如何保證CS在SCK時鐘停止后保持足夠長的拉低時間我最初的思路是傳輸結(jié)束后調(diào)用HAL_Delay(1)再拉高CS。但進(jìn)一步分析后我意識到問題的本質(zhì)不在CS拉高的時機(jī)而在于硬件SPI外設(shè)在連續(xù)傳輸字節(jié)之間會持續(xù)輸出SCK時鐘——也就是SPI主模式的“連續(xù)傳輸”特性。如果我在發(fā)送完配置字節(jié)后沒有調(diào)用__HAL_SPI_DISABLE()來徹底關(guān)閉外設(shè)SCK時鐘并不會因為數(shù)據(jù)FIFO空了就立刻停而是會一直跑直到外設(shè)被禁用。MT6835在這種連續(xù)的SCK下會把后續(xù)幾個無意義的字節(jié)當(dāng)成新指令解析最終導(dǎo)致芯片狀態(tài)機(jī)錯亂。注意這是STM32G0系列一個非常經(jīng)典的行為差異。和F1系列不同G0系列的SPI外設(shè)即使只有TX數(shù)據(jù)寄存器被寫入一個字節(jié)它也會產(chǎn)生一個完整的8位SCK時鐘。但如果你連續(xù)調(diào)用多次HAL_SPI_Transmit()中間沒有延時外設(shè)不會自動停止SCK時鐘會持續(xù)不斷這會讓很多對時序敏感的從機(jī)無法正確判斷一幀數(shù)據(jù)的邊界。修正方式很簡單每次傳輸一個字節(jié)之后顯式調(diào)用__HAL_SPI_DISABLE()然后再拉高CS保證SCK完全停止之后CS才變化。修改之后硬件SPI讀取MT6835的狀態(tài)寄存器第一次讀到了0x5A——這是芯片手冊里定義的握手響應(yīng)字節(jié)。當(dāng)時我以為問題解決了結(jié)果這只是個開始。3. 中斷優(yōu)先級設(shè)置不合理才是真正讓MT6835“罷工”的元兇3.1 中斷風(fēng)暴如何影響SPI時序繼續(xù)往下排查我用硬件SPI正常讀取MT6835幾次之后發(fā)現(xiàn)一個規(guī)律如果系統(tǒng)在短時間內(nèi)頻繁進(jìn)行SPI通信比如連續(xù)讀三次以上芯片就會再次變得無法訪問讀回來的全是0xFF。但如果斷電重啟又能恢復(fù)且通信正常。這個“間斷性失靈”讓我完全排除了從機(jī)的物理損壞和主控引腳配置錯誤把目光投向了中斷系統(tǒng)。因為如果只是SPI本身的時序問題應(yīng)該第一次或第二次訪問就會失敗而不是在連續(xù)讀取三次之后才出現(xiàn)問題。我打開了調(diào)試器的中斷監(jiān)控窗口觀察一段時間內(nèi)發(fā)生的中斷分布發(fā)現(xiàn)了異常板子上有一個定時器中斷每1ms觸發(fā)一次優(yōu)先級設(shè)置為1SysTick中斷優(yōu)先級設(shè)置為15而SPI中斷優(yōu)先級我一開始設(shè)置成5。表面上看定時器中斷優(yōu)先級比SPI更高看起來沒什么問題。但問題在于定時器中斷的服務(wù)函數(shù)非常長里面包含了一段用于電池電壓采集的濾波計算耗時大約70μs。在SPI傳輸過程中如果定時器中斷正好觸發(fā)SPI的中斷處理就會被延遲70μs。對于普通的SPI通信70μs的延遲看起來無關(guān)緊要因為數(shù)據(jù)已經(jīng)進(jìn)入硬件FIFO了。但MT6835作為一種時序敏感的從機(jī)它對片選釋放后的響應(yīng)時間有要求。如果主機(jī)在CS拉低之后遲遲不啟動SCK時鐘或者SCK時鐘啟動后因為中斷延遲而產(chǎn)生了間隙從機(jī)內(nèi)部狀態(tài)機(jī)就會判定這次通信異常從而放棄響應(yīng)。3.2 排查過程為什么前兩次能成功第三次就失敗我用了非常笨但非常有效的排查方法在每次SPI傳輸前后反轉(zhuǎn)一個GPIO用示波器觀察這個GPIO和SCK的時序關(guān)系。結(jié)果發(fā)現(xiàn)在成功讀取兩次之后第三次讀取前定時器中斷恰好搶占了CPU導(dǎo)致CS已經(jīng)拉低但SCK一直沒有啟動直到70μs之后才開始。MT6835的手冊里有一句容易被忽略的話從機(jī)在CS拉低后如果超過20μs沒有接收到SCK時鐘就默認(rèn)復(fù)位內(nèi)部通信狀態(tài)機(jī)。也就是說這個芯片對“片選有效后到首幀SCK時鐘到來”的時間有一個隱形的超時限制。我之前完全沒有注意到這個細(xì)節(jié)。于是問題的結(jié)論就很明確了不是SPI配置錯了而是SPI中斷響應(yīng)被高優(yōu)先級中斷阻塞導(dǎo)致CS與SCK之間出現(xiàn)了超過從機(jī)容忍極限的空窗期。解決方法是調(diào)整中斷優(yōu)先級把SPI中斷優(yōu)先級提升到0最高或者至少高于這個1ms定時器中斷。這樣即使定時器中斷正在執(zhí)行SPI傳輸也能搶占并立即啟動SCK時鐘保證CS有效后足夠快地開始通信。3.3 一種更穩(wěn)妥的做法用阻塞式傳輸替代中斷式傳輸?shù)菃慰刻岣邇?yōu)先級并不夠。因為在某些場景下如果定時器中斷已經(jīng)關(guān)閉了全局中斷比如在臨界區(qū)里那即使優(yōu)先級再高也無法搶占。為了保證萬無一失我后來決定對MT6835的讀寫操作改為阻塞式SPI傳輸也就是調(diào)用HAL_SPI_TransmitReceive()后一直等待傳輸完成不在傳輸過程中被任何其他中斷打斷。這種做法的代價是CPU會在此期間被占用但對于BMS主板來說SPI通信只發(fā)生在狀態(tài)讀取時占用時間在200μs以內(nèi)完全可以接受。修改后的整體時序如下表階段內(nèi)容耗時CS拉低從機(jī)片選有效0發(fā)送命令字節(jié)寫入寄存器地址約10μs4MHz SCK發(fā)送寄存器值配置參數(shù)約10μsCS拉高結(jié)束一次通信10μs后響應(yīng)等待從機(jī)內(nèi)部處理50μsCS拉低讀取狀態(tài)寄存器0接收數(shù)據(jù)讀取1字節(jié)約10μsCS拉高完成0這樣一個完整的讀寫流程總耗時不到150μs而且全程沒有中斷競爭問題。實際測試下來連續(xù)讀取上千次都沒有再出現(xiàn)0xFF的情況。注意阻塞式傳輸并非在所有情況下都優(yōu)于中斷式。如果你的系統(tǒng)里有非常緊急且頻繁的實時任務(wù)比如電機(jī)控制環(huán)路那阻塞式SPI會嚴(yán)重影響控制周期。但就本項目的場景而言BMS的電壓采集周期是1ms150μs的阻塞完全在預(yù)算之內(nèi)用阻塞換通信穩(wěn)定性是劃算的。4. GPIO復(fù)用狀態(tài)被意外改寫一個看似無關(guān)的初始化函數(shù)差點毀掉整個通信4.1 復(fù)現(xiàn)一個隱蔽的Bug引腳模式被手動設(shè)置為輸入解決了中斷時序問題后我以為事情到此結(jié)束。但就在我準(zhǔn)備提交代碼評審的時候測試同事反饋了一個新情況板子在某些冷啟動條件下比如斷電后立即上電SPI通信有大約10%的概率會失敗。這個概率在室溫下還算穩(wěn)定但電池打火瞬間會導(dǎo)致通信失敗的幾率顯著上升。這個現(xiàn)象不能用中斷優(yōu)先級或者CS時序來解釋了因為這兩種問題導(dǎo)致的失敗應(yīng)該是隨機(jī)且無序的而現(xiàn)在的失敗集中在特定上電條件下。我重新審視了工程中所有在main()里被調(diào)用的初始化函數(shù)終于在一個用于讀取芯片ID的Read_MT6835_ID()函數(shù)里發(fā)現(xiàn)了異常。這個函數(shù)本來只是用于讀取芯片的ID寄存器但是為了兼容某個早期版本的芯片我在里面加了一段代碼在讀取ID之前先對SPI的MISO引腳進(jìn)行了一個額外的GPIO模式配置確保它不處于復(fù)用模式。這個配置的意圖是防止MISO在上電時被錯誤地驅(qū)動為輸出狀態(tài)。但問題在于我在這段代碼里使用的是HAL_GPIO_Init()而且傳入的參數(shù)結(jié)構(gòu)體犯了兩個錯誤首先是MISO引腳的實際GPIO端口和編號寫錯了其次是引腳模式設(shè)置成了GPIO_MODE_INPUT而非GPIO_MODE_AF_INPUT。4.2 這個錯誤為什么只在上電瞬間觸發(fā)如果這個配置錯誤是固定的那么應(yīng)該每次上電都會失敗而不是只有10%的概率。要理解這一點需要知道HAL_GPIO_Init()函數(shù)的一個特性它每次調(diào)用時會先把GPIO寄存器中的MODER位清空再寫入新的模式。而MISO引腳的復(fù)用功能是由AFR寄存器決定的MODER位清空意味著引腳從復(fù)用模式切換回普通輸入模式。我在讀取ID之前執(zhí)行了這段錯誤的初始化等于人為地把MISO引腳從SPI復(fù)用模式切成了普通輸入模式。在這個瞬間如果你用示波器看MISO它不再輸出從機(jī)的數(shù)據(jù)而是呈現(xiàn)高阻狀態(tài)。之后我調(diào)用的HAL_SPI_TransmitReceive()就會收到隨機(jī)電平可能是1也可能是0因此讀取結(jié)果完全隨機(jī)。那為什么只有10%的概率失敗因為Read_MT6835_ID()函數(shù)只有在主循環(huán)里滿足某個條件才會被調(diào)用而這個條件恰好是“上電后第200ms內(nèi)的一個標(biāo)志位”。如果MCU啟動夠快在200ms內(nèi)已經(jīng)把這個函數(shù)執(zhí)行完了那么MISO引腳就會被錯誤配置成輸入狀態(tài)但緊接著下一次真正的SPI讀取會再次初始化MISO為復(fù)用模式問題就隱藏起來了。如果MCU啟動偏慢這個函數(shù)延遲執(zhí)行而期間恰好有一次真實的SPI通信那么這次通信就會被破壞。這個Bug的本質(zhì)是在初始化代碼里“多管閑事”用錯誤的參數(shù)重新初始化了一個已經(jīng)被正確配置過的引腳。修復(fù)方法是刪除這段多余的操作完全交給CubeMX生成的MX_GPIO_Init()來管理引腳的復(fù)用設(shè)置。4.3 教訓(xùn)初始化代碼切忌“畫蛇添足”這里我要奉勸各位凡是用CubeMX配置好的引腳復(fù)用絕對不要在應(yīng)用層代碼里再去手動調(diào)用HAL_GPIO_Init()去修改它的模式除非你有非常明確的需求而且你完全清楚這個修改會影響哪些外設(shè)。我這次踩坑就是因為當(dāng)時為了兼容一顆舊芯片的ID讀取邏輯自作主張加了一段初始化結(jié)果引入了新問題。檢查這類問題有一個高效手段用邏輯分析儀抓取所有與MT6835連接的引腳電平變化同時打開調(diào)試器查看GPIO的MODER寄存器和AFR寄存器值。如果發(fā)現(xiàn)MISO引腳的MODER變成了00輸入模式而AFR寄存器又恰好沒有正確配置為SPI的AF值那基本可以斷定有代碼在某個時刻重新初始化了這個引腳。根據(jù)這個線索很快就能通過代碼斷點定位到具體是哪個函數(shù)做的壞事。5. 從“能通就行”到“每幀都穩(wěn)定”關(guān)于代碼層面的幾個細(xì)節(jié)優(yōu)化5.1 每一幀數(shù)據(jù)都加上起止標(biāo)記而不是盲讀SPI通信最忌諱的就是“讀回來是什么就是什么”尤其是在沒有硬件校驗位的從機(jī)芯片上。MT6835的狀態(tài)寄存器里高位字節(jié)固定為0x5A作為起始標(biāo)記我之前都是直接判斷接收緩沖區(qū)是否等于這個值。但后來發(fā)現(xiàn)在某些錯誤的通信幀中這個標(biāo)記也可能因為偶然的電平組合而出現(xiàn)。后來我改成了雙字節(jié)標(biāo)記機(jī)制在真正需要讀取的狀態(tài)寄存器地址之前先發(fā)送一個固定的0xAA、0x33前綴字節(jié)對MT6835只有在收到這個前綴后才會把后續(xù)的數(shù)據(jù)當(dāng)作有效命令。這個機(jī)制類似軟件握手代價是多發(fā)送兩個字節(jié)但帶來了非常高的幀可靠性。5.2 用超時保護(hù)替代死等使用阻塞式SPI時最危險的場景是如果MT6835因為某種原因死機(jī)不再響應(yīng)SCK時鐘而主控又調(diào)用HAL_SPI_TransmitReceive()等待傳輸完成那么主機(jī)會一直卡在這個函數(shù)里整個系統(tǒng)陷入假死。HAL庫的HAL_SPI_TransmitReceive()默認(rèn)是無限期等待的所以我在外面包了一層超時保護(hù)uint8_t spi_read_byte_with_timeout(uint8_t reg_addr, uint8_t *data) { uint8_t tx_buf[3] {PREFIX_1, PREFIX_2, reg_addr}; uint8_t rx_buf[3] {0}; uint32_t tickstart HAL_GetTick(); if (HAL_SPI_TransmitReceive(hspi1, tx_buf, rx_buf, 3, 10) ! HAL_OK) { return SPI_TIMEOUT; } if ((HAL_GetTick() - tickstart) 10) { return SPI_TIMEOUT; } *data rx_buf[2]; return SPI_OK; }這里HAL_SPI_TransmitReceive()的最后一個參數(shù)10指的是超時時間單位是毫秒。如果MT6835不響應(yīng)這個函數(shù)會在10ms后返回超時錯誤系統(tǒng)就不會卡死。這個優(yōu)化雖然簡單但在實際產(chǎn)品中非常關(guān)鍵——通信失敗時至少系統(tǒng)還能通過看門狗復(fù)位而不是永遠(yuǎn)停在那。5.3 動態(tài)調(diào)整SCK頻率不推薦但值得了解MT6835支持的SPI時鐘范圍理論上從100kHz一直到8MHz都沒問題。但在實際測試中我發(fā)現(xiàn)當(dāng)SCK超過6MHz時MISO信號的上升沿會出現(xiàn)明顯的振鈴導(dǎo)致采樣結(jié)果偶發(fā)錯誤。解決方法是把SCK頻率固定在4MHz既快又穩(wěn)定。如果你一定要在運(yùn)行時動態(tài)切換SCK頻率比如為了兼容不同從機(jī)需要小心處理__HAL_SPI_SET_BAUDRATEPRESCALER()只能在SPI外設(shè)禁用狀態(tài)下調(diào)用否則會產(chǎn)生異常波形。切換頻率后最好也重新設(shè)置一次CPOL和CPHA因為某些STM32型號在重新設(shè)置分頻系數(shù)后會將時鐘極性寄存器清除。6. 關(guān)鍵的判斷方法如何快速區(qū)分“主控問題”還是“從機(jī)問題”調(diào)試遇到這種“時好時壞”的通信問題最高效的路徑不是瞎猜而是快速二分定位。我先給出一個排查順序表這是我在整個調(diào)試過程中沉淀下來的現(xiàn)象優(yōu)先排查方向典型的坑完全讀不到任何數(shù)據(jù)引腳復(fù)用配置引腳被初始化成普通GPIO第一次讀正常后續(xù)失敗中斷優(yōu)先級 / 阻塞時間高優(yōu)先級中斷長時間占用CPU頻率高時失敗頻率低時成功SCK頻率過高波形振鈴或從機(jī)無法跟上CS有效后等待時間過長代碼中插入了延時從機(jī)有CS到SCK的超時限制斷電重啟后恢復(fù)可能是初始化順序問題某段代碼重新初始化了SPI引腳這張表的含義是大多數(shù)SPI通信問題本質(zhì)上是主控外設(shè)的配置與時序管理不當(dāng)而不是從機(jī)損壞。經(jīng)驗不足的人容易反復(fù)懷疑從機(jī)芯片有問題甚至懷疑買到假芯片但實際上只要主控側(cè)把SCK停止、等待時間、中斷響應(yīng)這三件事處理好絕大多數(shù)問題都會消失。從定位方法上說我強(qiáng)烈建議大家在調(diào)試SPI時準(zhǔn)備一個雙通道示波器。一個通道接SCK另一個通道接CS或MISO。不要只盯著MISO看而是重點觀察CS與SCK在傳輸結(jié)束時的先后順序是否嚴(yán)格符合要求。很多隱蔽問題單看數(shù)據(jù)是看不出來的必須結(jié)合時序才能確認(rèn)。7. 最后一點碎碎念調(diào)試時最該感謝的是“示波器”和“邏輯分析儀”整場折騰下來硬件SPI通信本身并不復(fù)雜真正復(fù)雜的是那些“看起來無關(guān)”的因素中斷優(yōu)先級、引腳復(fù)用狀態(tài)、CS的有效保持時間、片選釋放時SCK是否完全停止。這些參數(shù)每一個單看都不起眼但串聯(lián)起來就會變成一個讓人頭發(fā)掉光的“靈異事件”。順便分享一個工具心得調(diào)試時邏輯分析儀比示波器更好用來抓波形蓋帽——便宜、通道多、能長時間記錄但分析信號質(zhì)量比如上升沿過緩、振鈴時還是得上示波器。兩者配合基本可以做到“通信不正常先看波形波形正常再看寄存器寄存器正常再看中斷”的逐步定位。另外奉勸各位在初始化任何外設(shè)之前先認(rèn)真讀一遍從機(jī)芯片的手冊里關(guān)于“時序要求”的章節(jié)尤其是CS、SCK、數(shù)據(jù)建立時間這三個要素。很多國產(chǎn)芯片的手冊寫得不那么細(xì)致但關(guān)鍵參數(shù)都會藏在某個不起眼的表格里。我這次就是前期沒仔細(xì)讀MT6835的時序要求才走了這么多彎路。如果你也在調(diào)STM32G0系列的SPI通信而且手頭恰好遇到“時好時壞”“第一次成功后續(xù)失敗”“斷電后恢復(fù)”這類詭異現(xiàn)象不妨先按照上面這幾條逐一檢查CS釋放前SCK是否完全停止、中斷優(yōu)先級是否合理、MISO引腳是否被意外重新初始化。這三招夠你在大多數(shù)情況下“驅(qū)鬼”。