模式到調(diào)試器救磚)
手里這片 STM32C542 昨晚差點(diǎn)被我折騰成“磚”。第一版固件燒進(jìn)去之后復(fù)位幾次開(kāi)始出現(xiàn)調(diào)試器無(wú)法連接的提示剛開(kāi)始以為是 SWD 接口虛焊量了一圈波形才發(fā)現(xiàn)問(wèn)題出在 BOOT_SEL 配置上。這個(gè)藏在選項(xiàng)字節(jié)里的位平時(shí)幾乎沒(méi)人碰但一旦改錯(cuò)CPU 上電后的第一條指令從哪里取都可能翻天。這篇是 STM32C542 開(kāi)發(fā)系列的第二篇專(zhuān)門(mén)把 BOOT_SEL 配置這件事從頭到尾捋清楚。我會(huì)先講它在上電復(fù)位瞬間做了什么決策再分別介紹引腳控制和選項(xiàng)字節(jié)控制兩條鏈路最后給出 CubeProgrammer 圖形化配置、HAL 代碼配置以及一次完整的“啟動(dòng)配置改錯(cuò)導(dǎo)致調(diào)試器消失”的排查過(guò)程。適合正在用 STM32C5 系列做開(kāi)發(fā)、或者從老 STM32 遷移到 C5 的開(kāi)發(fā)同學(xué)參考。1. BOOT_SEL 在上電瞬間決定了什么1.1 CPU 的第一條指令從哪里來(lái)Cortex-M33 內(nèi)核的 CPU 在上電或復(fù)位釋放后會(huì)從0x00000000讀取初始棧指針從0x00000004讀取復(fù)位向量也就是第一段要執(zhí)行的代碼地址。問(wèn)題在于0x00000000這個(gè)地址并不是固定的物理 Flash 地址而是一個(gè)可以重映射的別名區(qū)處理器會(huì)根據(jù)啟動(dòng)模式把 Flash 主存儲(chǔ)區(qū)、System Memory內(nèi)置 BootROM或者 SRAM 中的一個(gè)映射到這個(gè)位置。所以BOOT_SEL 這個(gè)名字很容易讓人誤會(huì)它并不是直接選擇“從 Flash 啟動(dòng)”還是“從 SRAM 啟動(dòng)”而是選擇由誰(shuí)來(lái)決策啟動(dòng)源。真正決定啟動(dòng)源的要么是 BOOT0/BOOT1 引腳電平要么是選項(xiàng)字節(jié)里的 nBOOT0/nBOOT1 位。BOOT_SEL 只是前面那道總開(kāi)關(guān)。1.2 BOOT_SEL 在選項(xiàng)字節(jié)中的位置STM32C542 的 BOOT_SEL 位放在 Flash 選項(xiàng)字節(jié)的 User Configuration 區(qū)域和 nBOOT0、nBOOT1、BOR_LEV 這些配置放在一起。通過(guò) STM32CubeProgrammer 打開(kāi) Option Bytes 面板就能直接看到也可以在代碼里通過(guò) HAL 庫(kù)操作 Flash 模塊的選項(xiàng)字節(jié)編程接口來(lái)修改。官方的選項(xiàng)字節(jié)默認(rèn)值通常是“主 Flash 啟動(dòng)”也就是 nBOOT0 位默認(rèn)對(duì)應(yīng) Flash 啟動(dòng)BOOT_SEL 位默認(rèn)走引腳鏈路或者字節(jié)鏈路和型號(hào)以及選項(xiàng)字節(jié)出廠值有關(guān)。這里有一個(gè)很關(guān)鍵的點(diǎn)選項(xiàng)字節(jié)不是改完立刻生效的必須等一次復(fù)位。而且要注意某些復(fù)位方式不會(huì)重新加載選項(xiàng)字節(jié)這也是我把板子折騰到“看起來(lái)像磚”的原因之一。1.3 BOOT_SEL 只有 0 和 1但兩條路徑差別巨大BOOT_SEL 的取值很簡(jiǎn)單BOOT_SEL 值啟動(dòng)源選擇依據(jù)0由 BOOT0/BOOT1 引腳電平?jīng)Q定復(fù)位時(shí)鎖存1由選項(xiàng)字節(jié) nBOOT0/nBOOT1 決定不依賴(lài)外部引腳當(dāng) BOOT_SEL 0 時(shí)所有邏輯都圍繞引腳展開(kāi)當(dāng) BOOT_SEL 1 時(shí)外部引腳的 BOOT0 基本退居二線啟動(dòng)路徑完全由 Flash 里存的位決定。后面我會(huì)詳細(xì)拆這兩條鏈路。2. 兩條控制鏈路的完整拆解2.1 BOOT_SEL 0引腳鏈路適合開(kāi)發(fā)調(diào)試在最經(jīng)典的 STM32 啟動(dòng)模式里BOOT0 引腳和 BOOT1 引腳的組合決定了三檔啟動(dòng)源。BOOT_SEL 0 時(shí)STM32C542 沿用了這個(gè)思路BOOT1 引腳BOOT0 引腳啟動(dòng)源00主 Flash01System MemoryBootLoader11SRAM取決于具體型號(hào)配置注意這里指的是復(fù)位釋放瞬間的電平不是運(yùn)行時(shí)的動(dòng)態(tài)電平。BOOT0/BOOT1 引腳在復(fù)位期間被采樣后結(jié)果會(huì)鎖存到內(nèi)部寄存器后續(xù)引腳電平再變化也不會(huì)改變本次啟動(dòng)的來(lái)源。所以如果板子上 BOOT0 引腳懸空或者被干擾復(fù)位瞬間可能采到不確定值啟動(dòng)源就會(huì)亂跳。實(shí)際項(xiàng)目里我建議 BOOT0 引腳外部加一顆 10k 下拉電阻默認(rèn)低電平從 Flash 啟動(dòng)同時(shí)預(yù)留一個(gè)跳線或者按鍵可以拉高。這樣需要進(jìn)入系統(tǒng) BootLoader 時(shí)上電前把跳線接高復(fù)位一次就能進(jìn)固件升級(jí)模式。開(kāi)發(fā)調(diào)試階段這個(gè)方案比隨時(shí)改選項(xiàng)字節(jié)要靈活得多也安全得多。2.2 BOOT_SEL 1選項(xiàng)字節(jié)鏈路適合量產(chǎn)當(dāng) BOOT_SEL 1 時(shí)BOOT0 引腳的狀態(tài)不再直接參與啟動(dòng)源選擇而是由選項(xiàng)字節(jié)里的 nBOOT0、nBOOT1 位來(lái)決定。注意這些位是負(fù)邏輯名字里帶n也就是說(shuō)位值為 0 時(shí)通常對(duì)應(yīng)“啟用”或“選擇”。量產(chǎn)場(chǎng)景下我最常用的一組配置是配置場(chǎng)景nBOOT0nBOOT1效果從主 Flash 啟動(dòng)01每次上電直接進(jìn)應(yīng)用強(qiáng)制進(jìn)入 BootLoader11方便產(chǎn)線升級(jí)或恢復(fù)之所以在量產(chǎn)時(shí)用 BOOT_SEL 1是因?yàn)楫a(chǎn)品不可能在每個(gè) PCB 上都留一個(gè) BOOT0 跳線也不可能依賴(lài)客戶的現(xiàn)場(chǎng)操作。把啟動(dòng)策略固化到選項(xiàng)字節(jié)里上電行為完全可控。如果產(chǎn)品有遠(yuǎn)程升級(jí)需求應(yīng)用里自己實(shí)現(xiàn)跳轉(zhuǎn)到 BootLoader 的邏輯即可不需要硬件干預(yù)期。2.3 兩條鏈路的選擇建議這里給一個(gè)我總結(jié)的選型表基本覆蓋最常見(jiàn)的項(xiàng)目階段項(xiàng)目階段推薦配置理由原型開(kāi)發(fā)、調(diào)試BOOT_SEL 0BOOT0 引腳跳線控制改引腳就能切啟動(dòng)源不需要頻繁寫(xiě)選項(xiàng)字節(jié)小批量試產(chǎn)BOOT_SEL 0BOOT0 默認(rèn)下拉開(kāi)發(fā)環(huán)境一致排查問(wèn)題方便量產(chǎn)固化BOOT_SEL 1nBOOT0 0不依賴(lài)引腳抗干擾啟動(dòng)路徑固定現(xiàn)場(chǎng)救援、產(chǎn)線升級(jí)BOOT_SEL 1nBOOT0 1或引腳拉高強(qiáng)制進(jìn)入系統(tǒng) BootLoader注意這個(gè)推薦表只針對(duì)大多數(shù)常規(guī)應(yīng)用。如果你的固件里開(kāi)啟了 TrustZone或者使用了 OEM 安全啟動(dòng)啟動(dòng)配置鏈路會(huì)更復(fù)雜BOOT_SEL 會(huì)與安全狀態(tài)綁定配置時(shí)要單獨(dú)評(píng)估。3. 用 STM32CubeProgrammer 改一次 BOOT_SEL3.1 連接之前先做的三件事打開(kāi) STM32CubeProgrammer 之前先把硬件狀態(tài)確認(rèn)好。第一確認(rèn) BOOT0 引腳當(dāng)前電平最好通過(guò)跳線或電阻明確拉低避免懸空第二確認(rèn) ST-Link 的 SWDIO、SWCLK、NRST、GND 四根線都接了特別是 NRST后面要用 Connect Under Reset 模式第三給板上電測(cè)量供電電壓是否穩(wěn)定。如果 ST-Link 連接不上優(yōu)先在 STM32CubeProgrammer 的 Mode 下拉框里選擇 Under Reset并把 Frequency 降到 1MHz 左右。很多情況下 CPU 已經(jīng)跑進(jìn)了一個(gè)異常啟動(dòng)模式正常連接時(shí)內(nèi)核不響應(yīng)調(diào)試請(qǐng)求但硬件復(fù)位信號(hào)可以把內(nèi)核停在復(fù)位狀態(tài)調(diào)試器就能趁機(jī)握手上。3.2 圖形界面下的修改路徑連上芯片后點(diǎn)擊左側(cè)的 Option Bytes 圖標(biāo)進(jìn)入選項(xiàng)字節(jié)面板。在 User Configuration 區(qū)域能看到 BOOT_SEL、nBOOT0、nBOOT1、BOR_LEV 等字段。STM32C542 的 BOOT_SEL 位如果顯示為0當(dāng)前就是引腳模式改成1就是選項(xiàng)字節(jié)模式。修改完目標(biāo)位后點(diǎn)擊右上角的 Apply 按鈕。CubeProgrammer 會(huì)先擦除并編程整個(gè)選項(xiàng)字節(jié)區(qū)域然后自動(dòng)觸發(fā)一次系統(tǒng)復(fù)位。這里要留意彈出來(lái)的信息窗口可能會(huì)提示“Option Bytes successfully programmed”但不代表新的啟動(dòng)配置已經(jīng)完整生效。3.3 驗(yàn)證配置是否真的生效我的習(xí)慣是點(diǎn)完 Apply 之后不會(huì)立刻跑去看應(yīng)用代碼而是先斷開(kāi)調(diào)試器把板上電再重新連接回到 Option Bytes 面板確認(rèn) BOOT_SEL、nBOOT0、nBOOT1 的值。之后再做一次完整的 Power On Reset也就是斷電再上電而不是按一下復(fù)位鍵。因?yàn)橛行┻x項(xiàng)字節(jié)的加載邏輯只在 POR 過(guò)程中完成尤其涉及 Boot 配置時(shí)NRST 復(fù)位不一定能觸發(fā)重新采樣。如果你手邊有示波器可以抓一下 BOOT0 引腳上電瞬間的電平以及 NRST 引腳釋放后到 CPU 開(kāi)始執(zhí)行 Flash 程序的延遲。沒(méi)有示波器也沒(méi)關(guān)系最簡(jiǎn)單的方法是看現(xiàn)象把 BOOT_SEL 配成從 Flash 啟動(dòng)后如果代碼里初始化了一個(gè) UART 或 GPIO查看對(duì)應(yīng)輸出是否出現(xiàn)即可。3.4 修改選項(xiàng)字節(jié)時(shí)最容易出現(xiàn)的警告CubeProgrammer 在編程選項(xiàng)字節(jié)之前會(huì)彈一個(gè)警告大意是“Option bytes programming requires a power-on reset to be effective”。很多人忽略這個(gè)提示點(diǎn)完 Apply 后立刻按復(fù)位鍵發(fā)現(xiàn)配置沒(méi)變就以為是軟件問(wèn)題。其實(shí)不是只是復(fù)位方式不對(duì)直接斷電再上電往往就生效了。另外如果芯片當(dāng)前 RDP讀保護(hù)級(jí)別是 Level 1 或 Level 2選項(xiàng)字節(jié)的修改權(quán)限會(huì)被限制。Level 1 下某些用戶選項(xiàng)字節(jié)仍然可以修改但 Level 2 會(huì)把調(diào)試口和 Boot 相關(guān)配置完全鎖死基本只能通過(guò)全片擦除才能恢復(fù)。所以拿到新板子先看一眼 RDP 級(jí)別別在 RDP Level 2 上做這種實(shí)驗(yàn)。4. 用代碼寫(xiě) BOOT_SELHAL 庫(kù)操作細(xì)節(jié)4.1 為什么需要在代碼里改選項(xiàng)字節(jié)量產(chǎn)階段如果每一片板子都要用 CubeProgrammer 手工點(diǎn)一次 BOOT_SEL效率太低了。正確的做法是讓生產(chǎn)測(cè)試固件或者 BootLoader 在第一次啟動(dòng)時(shí)自動(dòng)寫(xiě)入選項(xiàng)字節(jié)之后正式應(yīng)用啟動(dòng)時(shí) BOOT_SEL 就已經(jīng)是量產(chǎn)配置產(chǎn)線不需要額外操作。還有一類(lèi)場(chǎng)景是遠(yuǎn)程升級(jí)如果當(dāng)前固件運(yùn)行在 Flash 中但你想讓設(shè)備下次重啟進(jìn)入 System BootLoader可以在運(yùn)行期把 nBOOT0 選項(xiàng)位改為對(duì)應(yīng)值然后觸發(fā)軟復(fù)位。設(shè)備重啟后 CPU 直接進(jìn)入 BootROM配合 UART 或 USB 完成固件接收。4.2 選項(xiàng)字節(jié)編程的完整 HAL 流程STM32 各系列操作選項(xiàng)字節(jié)的 HAL 接口大同小異基本流程是解鎖 Flash、解鎖選項(xiàng)字節(jié)、配置編程結(jié)構(gòu)體、執(zhí)行編程、觸發(fā)加載、重新上鎖。static void config_boot_sel(uint8_t boot_sel, uint8_t nboot0) { FLASH_OBProgramInitTypeDef ob {0}; HAL_StatusTypeDef status; // 1. 解鎖 Flash 和 Option Bytes HAL_FLASH_Unlock(); HAL_FLASH_OB_Unlock(); // 2. 聲明本次要修改的是 User 類(lèi)選項(xiàng)字節(jié) ob.OptionType OPTIONBYTE_USER; ob.UserType OB_USER_BOOT_SEL | OB_USER_NBOOT0; ob.UserConfig 0; // 3. 按目標(biāo)值填充 BOOT_SEL 和 nBOOT0 位 if (boot_sel) { ob.UserConfig | OB_BOOT_SEL_1; } else { ob.UserConfig | OB_BOOT_SEL_0; } if (nboot0) { ob.UserConfig | OB_NBOOT0_1; } else { ob.UserConfig | OB_NBOOT0_0; } // 4. 執(zhí)行編程 status HAL_FLASHEx_OBProgram(ob); if (status ! HAL_OK) { // 失敗時(shí)讀取 FLASH_SR 中的錯(cuò)誤標(biāo)志這里做簡(jiǎn)單處理 Error_Handler(); } // 5. 觸發(fā)選項(xiàng)字節(jié)加載等效于一次復(fù)位 HAL_FLASH_OB_Launch(); // 6. 編程完成后重新上鎖 HAL_FLASH_OB_Lock(); HAL_FLASH_Lock(); }需要注意OB_USER_BOOT_SEL、OB_BOOT_SEL_1、OB_NBOOT0_0這些宏定義在具體型號(hào)的 HAL 頭文件里可能名稱(chēng)有微小差別不同系列、不同 SDK 版本會(huì)不一樣。比較穩(wěn)妥的做法是編譯前先在stm32c5xx_hal_flash_ex.h里搜一下BOOT_SEL確認(rèn)當(dāng)前 SDK 支持的宏名再把示例代碼里的宏替換成實(shí)際名稱(chēng)。4.3 為什么必須先解鎖再編程選項(xiàng)字節(jié)區(qū)域?qū)儆谙到y(tǒng)存儲(chǔ)區(qū)的一部分復(fù)位的功耗和時(shí)序要求比普通 Flash 編程更嚴(yán)格所以芯片默認(rèn)對(duì)這塊區(qū)域加了保護(hù)。HAL_FLASH_Unlock()和HAL_FLASH_OB_Unlock()的作用就是往 Flash 控制寄存器里寫(xiě)固定的解鎖序列防止程序誤操作把啟動(dòng)配置改亂。如果代碼意外進(jìn)入 HardFault或者上電后主頻不對(duì)千萬(wàn)不要在中斷服務(wù)函數(shù)里直接寫(xiě)選項(xiàng)字節(jié)。選項(xiàng)字節(jié)編程過(guò)程中發(fā)生異常復(fù)位輕則配置寫(xiě)入不完整重則導(dǎo)致啟動(dòng)路徑不確定。我的做法是把這段配置邏輯放在main()最開(kāi)始并且加一個(gè)判斷條件只在確實(shí)需要修改時(shí)才執(zhí)行避免每次開(kāi)機(jī)都擦寫(xiě)一次。4.4 選項(xiàng)字節(jié)能擦寫(xiě)多少次這個(gè)參數(shù)很少有人注意但量產(chǎn)前必須評(píng)估。選項(xiàng)字節(jié)區(qū)域雖然也是 Flash 存儲(chǔ)但它的擦寫(xiě)壽命和主存儲(chǔ)區(qū)類(lèi)似通常也是幾千到上萬(wàn)次級(jí)別具體數(shù)值要看數(shù)據(jù)手冊(cè)。如果每次開(kāi)機(jī)都執(zhí)行一次選項(xiàng)字節(jié)編程不用多久就可能磨損。所以正確的做法是程序里先讀出當(dāng)前值和目標(biāo)值比較一致就跳過(guò)整個(gè)編程流程不一致才執(zhí)行。uint32_t cur_user READ_BIT(FLASH-OPTR, FLASH_OPTR_BOOT_SEL | FLASH_OPTR_NBOOT0); // 判斷 cur_user 是否等于目標(biāo)值不等再調(diào)用 config_boot_sel這樣既保證了量產(chǎn)時(shí)能自動(dòng)寫(xiě)入又避免了無(wú)謂的擦寫(xiě)損耗。5. 踩坑記錄BOOT_SEL 導(dǎo)致調(diào)試器“消失”的完整排查鏈路5.1 故障現(xiàn)象固件能燒但復(fù)位后就掉線昨晚遇到的情況很典型。第一版固件燒進(jìn)去后跑得好好的但我在固件里加了一段“上電后自動(dòng)把 BOOT_SEL 改成選項(xiàng)字節(jié)模式”的初始化代碼重新燒錄后現(xiàn)象立刻變了STM32CubeProgrammer 能識(shí)別到芯片也能擦除和下載但只要斷開(kāi)連接、按一下板子上的復(fù)位鍵再點(diǎn)連接就報(bào) No STM32 target found。一開(kāi)始我懷疑是 SWD 引腳被程序重新復(fù)用了因?yàn)榇a里確實(shí)有引腳重映射。但把程序擦除之后依然找不到目標(biāo)這就說(shuō)明問(wèn)題不在應(yīng)用代碼而在配置層。5.2 排查過(guò)程從硬件量到軟件配置排查順序是這樣的萬(wàn)用表量板子供電3.3V 正常電流也沒(méi)有明顯異常。示波器抓 SWDIO、SWCLK 引腳。在 ST-Link 發(fā)起連接時(shí)SWDIO 有微弱電平變化說(shuō)明調(diào)試器已經(jīng)在發(fā)請(qǐng)求但芯片沒(méi)有回應(yīng)。按住 BOOT0 跳線拉高后重新上電再用 STM32CubeProgrammer 連接成功。這說(shuō)明芯片本身沒(méi)有鎖死而是啟動(dòng)源被改到了非預(yù)期位置。連接后直接讀取 Option Bytes發(fā)現(xiàn) BOOT_SEL 已經(jīng)是 1nBOOT0 也變成了 1。也就是說(shuō)每次復(fù)位后 CPU 都嘗試進(jìn)入 System BootLoader而我的板子上沒(méi)有接 BootLoader 對(duì)應(yīng)的通信外設(shè)芯片就停在了一個(gè)沒(méi)有響應(yīng)的狀態(tài)。關(guān)鍵問(wèn)題就在這里BOOT_SEL 1 后啟動(dòng)源不再看外部 BOOT0 引腳而是直接看選項(xiàng)字節(jié)。我把 nBOOT0 配成了非 Flash 啟動(dòng)所以復(fù)位后 CPU 根本不會(huì)執(zhí)行 Flash 里的用戶程序SWD 調(diào)試器沒(méi)有應(yīng)用代碼響應(yīng)自然很難連上。5.3 恢復(fù)方法不用拆芯片也能救回來(lái)這次能救回來(lái)靠的是 BOOT0 引腳拉高后進(jìn)入了系統(tǒng) BootLoader然后用 STM32CubeProgrammer 的 UART 或 ST-Link 接口把選項(xiàng)字節(jié)重新改回 Flash 啟動(dòng)。具體步驟是斷電。把 BOOT0 跳線短接到高電平。重新上電此時(shí) BOOT_SEL 雖然是 1但某些系列會(huì)額外判斷 BOOT0 引腳電平作為強(qiáng)制 BootLoader 入口或者選項(xiàng)字節(jié)里本來(lái)就是 BootLoader 模式。用 STM32CubeProgrammer 連接進(jìn)入 Option Bytes 面板把 BOOT_SEL 改回 0nBOOT0 改回 0然后 Apply。斷電去掉 BOOT0 跳線重新上電芯片恢復(fù)正常。如果你的板子沒(méi)有 BOOT0 跳線也可以用 UART BootLoader 方式恢復(fù)。STM32 的 System BootLoader 一般都支持 UARTCubeProgrammer 里選擇 UART 接口配置好串口參數(shù)連接后先執(zhí)行 Full Chip Erase再手動(dòng)調(diào)節(jié) Option Bytes。這個(gè)流程在不同系列上引腳和默認(rèn)波特率有差異但思路完全一致。5.4 如何避免這個(gè)問(wèn)題這次踩坑后我給自己定了幾條規(guī)矩開(kāi)發(fā)階段保持 BOOT_SEL 0外部 BOOT0 下拉并預(yù)留跳線。這樣即使 Flash 里的程序崩潰拉高 BOOT0 上電就能進(jìn) BootLoader恢復(fù)手段永遠(yuǎn)存在。代碼里修改選項(xiàng)字節(jié)時(shí)先讀取當(dāng)前值確認(rèn)不是目標(biāo)值再寫(xiě)不能無(wú)腦每次開(kāi)機(jī)都寫(xiě)。量產(chǎn)固件把 BOOT_SEL 1 寫(xiě)成“首次啟動(dòng)判斷”避免反復(fù)擦寫(xiě)。如果你要用 BOOT_SEL 1至少留一個(gè) UART BootLoader 的硬件通道比如 USART1 的 RX/TX 引腳引出到測(cè)試點(diǎn)防止后面需要現(xiàn)場(chǎng)恢復(fù)。6. 從經(jīng)典 STM32 遷移到 C5 時(shí) BOOT 配置有哪些差異6.1 BOOT_SEL 是新增的總開(kāi)關(guān)用慣 STM32F1、F4 的老工程師會(huì)非常熟悉 BOOT0/BOOT1 引腳直接決定啟動(dòng)模式的做法。到了 STM32C542 這類(lèi) Cortex-M33 系列芯片出廠后默認(rèn)行為雖然還是主 Flash 啟動(dòng)但你的程序或配置工具隨時(shí)可以把 BOOT_SEL 打開(kāi)讓啟動(dòng)路徑從引腳切換到選項(xiàng)字節(jié)。遷移老代碼時(shí)最大的坑就是默認(rèn)假設(shè) BOOT0 引腳始終有效但其實(shí) BOOT_SEL 可能已經(jīng)被改成 1外部引腳怎么拉都沒(méi)用。6.2 選項(xiàng)字節(jié)名字里多了 n 前綴老系列里很多選項(xiàng)字節(jié)叫 BOR_LEV、WDG_SW到了新系列你會(huì)發(fā)現(xiàn) nBOOT0、nBOOT1 這種帶n的命名特別多。n代表負(fù)邏輯即寫(xiě) 0 才是“選中”。我在第一次配置時(shí)就把 nBOOT0 寫(xiě)反了以為寫(xiě) 1 是 Flash 啟動(dòng)結(jié)果 CPU 每次都進(jìn) BootLoader。新上手 C5 時(shí)務(wù)必多看幾眼 CubeProgrammer 面板里的提示和參考手冊(cè)的啟動(dòng)模式真值表別憑老經(jīng)驗(yàn)猜。6.3 啟動(dòng)模式真值表更復(fù)雜經(jīng)典系列啟動(dòng)模式就是三檔Flash、System Memory、SRAM。C5 系列的啟動(dòng)模式表里加入了更細(xì)粒度區(qū)分尤其是 Secure 和 Non-Secure 啟動(dòng)路徑、OEM 區(qū)域等。如果你沒(méi)開(kāi)啟 TrustZone它和老系列看起來(lái)差不多但一旦開(kāi)啟安全特性啟動(dòng)模式數(shù)量會(huì)變多BOOT_SEL 只是最基礎(chǔ)的一層。我的建議是剛開(kāi)始調(diào)試 C5 時(shí)先把 TrustZone 關(guān)閉跑通基本啟動(dòng)流程后再考慮安全相關(guān)配置。6.4 遷移檢查清單檢查項(xiàng)老系列做法C5 系列注意點(diǎn)啟動(dòng)源選擇BOOT0/BOOT1 引腳確認(rèn) BOOT_SEL 當(dāng)前值引腳鏈路可能被切斷選項(xiàng)字節(jié)命名BOOT0、BOOT1nBOOT0、nBOOT1負(fù)邏輯進(jìn)入 BootLoaderBOOT0 拉高復(fù)位BOOT_SEL 0 時(shí)引腳拉高BOOT_SEL 1 時(shí)改選項(xiàng)字節(jié)量產(chǎn)固定啟動(dòng)硬件拉低 BOOT0BOOT_SEL 1 nBOOT0 0 更可靠恢復(fù)手段BOOT0 跳線保留 BOOT0 跳線或 UART BootLoader 通道SWD 調(diào)試異常檢查引腳復(fù)用先查 Option Bytes再查引腳復(fù)用6.5 遷移期的實(shí)用建議從老系列遷移到 C5最好先畫(huà)一塊兼容開(kāi)發(fā)板把 BOOT0 跳線、UART BootLoader 測(cè)試點(diǎn)、SWD 調(diào)試口全部引出。第一版固件不要急著動(dòng) BOOT_SEL保持默認(rèn)值。等 Flash 讀寫(xiě)、時(shí)鐘、外設(shè)都調(diào)通了再單獨(dú)驗(yàn)證 BOOT_SEL 配置功能。換句話說(shuō)不要讓 BOOT_SEL 成為第一個(gè)排查對(duì)象它應(yīng)該是最后一個(gè)改動(dòng)的啟動(dòng)配置。最后再分享一個(gè)小技巧修改選項(xiàng)字節(jié)之前先把當(dāng)前 Option Bytes 面板截圖或者用-Ob命令行參數(shù)導(dǎo)出一份備份文件。STM32CubeProgrammer 可以通過(guò)命令行執(zhí)行STM32_Programmer_CLI -ob read -file backup.ob之類(lèi)的操作把當(dāng)前選項(xiàng)字節(jié)完整保存下來(lái)。一旦配置改崩了直接恢復(fù)備份文件比手工一項(xiàng)項(xiàng)填回去快得多。我現(xiàn)在的習(xí)慣是每次拿到新板子先做一次選項(xiàng)字節(jié)備份再開(kāi)始折騰 BOOT_SEL已經(jīng)幫我省了好幾次救磚的時(shí)間。