對象:STM32U595 + MM8108 USB cdc-ncm,wolfBoot secure boot
兩個重點:(1) 為什麼「Linux VM 編譯、Windows 燒錄」這樣跨系統分工;(2) 為什麼這板子要逐 sector 抹除,
-e all反而會失敗。
為什麼要「VM 編譯、Windows 燒錄」
| 步驟 | 在哪做 | 原因 |
|---|---|---|
| 編譯 4 個 bin | Linux VM(Ubuntu) | MM-IoT-SDK 的 toolchain、Makefile、簽章/config 腳本都假設 Linux 環境(pipenv、protobuf、sass、wolfBoot keygen 等) |
| 燒錄到晶片 | Windows + CubeProgrammer mode=UR |
這板子有 IWDG + 不乾淨 reset,openocd 在 app 跑著時抹寫不穩;mode=UR 在 app 啟動前接管,最可靠(詳見另一份取捨文件) |
所以是:VM 產出 bin → 複製到 Windows → CubeProgrammer 燒。
編譯產出的檔案與燒錄位址
build/ 目錄會產生:
| 檔案 | 燒錄位址 | 說明 |
|---|---|---|
wolfboot.bin |
0x08000000 |
bootloader(約 40KB,前 5 個 sector) |
factory.bin |
0x0800A000 |
MAC + 國別碼(選配,HaLow 需要;sector 5) |
config.bin |
0x0800C000 |
config 第一份 |
config.bin |
0x0800E000 |
config 第二份 |
app_signed.bin |
0x08010000 |
簽章後的 app(約 1.58MB,wolfBoot header 0x400) |
U595 每個 sector = 8KB。位址換算:
0x0800A000= page 5、0x0800C000= page 6、0x0800E000= page 7、0x08010000= page 8。
燒錄前必做的三個檢查(順序很重要)
1. bootloader 金鑰一致性(最關鍵,否則 wolfBoot 會 panic)
要驗證的是:build 出來的 wolfboot.bin 內嵌的信任根公鑰,是否 == committed 的那把 。
不要寫死一串 hex 當比對值(magic number,換金鑰就失準、也看不出來源)。
比對基準要從信任根本身動態萃取 它是 wolfBoot build 時唯一被編進 bootloader 的公鑰來源,且已驗證 == app 簽章用的 private.der,公鑰起頭 04 2d c7 25。
bash
KEYSTORE="$KEYS/xxx.c" # 信任根來源(唯一被編進 bootloader 的公鑰)
# 從公鑰 byte 陣列抽出連續 hex(0x04,0x2d,0xc7,... → 042dc7...)
KEYHEX=$(grep -oE '0x[0-9a-fA-F]{2}' "$KEYSTORE" | sed 's/0x//' | tr 'A-F' 'a-f' | tr -d '\n')
# 取前 8 bytes(16 hex 字元)當指紋;夠唯一、又不必整把比
FINGERPRINT=${KEYHEX:0:16}
echo "信任根指紋(取自 xxx.c): $FINGERPRINT" # 這台應印出 042dc725d046b040
# build 出來的 bootloader 是否內嵌了這個信任根公鑰
if od -An -tx1 build/wolfboot.bin | tr -d ' \n' | grep -q "$FINGERPRINT"; then
echo "[OK] wolfboot.bin 內嵌公鑰 == committed keystore.c(信任根一致)"
else
echo "[FATAL] wolfboot.bin 內嵌公鑰 != keystore.c --- wolfBoot 八成自動產生了隨機金鑰,燒下去會驗章失敗卡 panic"
fi
(VM 上若有 xxd,最後一段也可寫成 xxd -p build/wolfboot.bin | tr -d '\n' | grep -q "$FINGERPRINT",效果相同;本文用 od 是因為某些精簡環境沒裝 xxd。)
- OK = 內嵌的是 committed 信任根公鑰,正確。
- FATAL = wolfBoot 在 build 時自動產生了隨機金鑰覆蓋 keystore(
make clean→keysclean把 committed keystore 刪了之後最容易發生),燒下去會驗章失敗、開機卡 panic。先修金鑰再 build(見交接文件 Stage 1)。
⚠️ 別抓錯檔當比對基準。 repo / 上傳目錄裡可能存在另一把 )------那不是這台的信任根 (很可能是 wolfBoot 範例預設或別的來源)。拿它去比一定 NOT FOUND。信任根只認
實際上編譯到 bootloader的(起頭04 2d c7 25)。註:信任根 xxxx.c 裡除了公鑰陣列,可能還有長度/ID 之類的小常數。若
grep 0xXX把那些也抓進來導致指紋前幾 byte 不對,就先grep -nE 'pubkey|ecc256|0x04, 0x2d|\{' "$KEYSTORE"找出公鑰陣列的變數名,再用awk只取那個{ ... }區塊抽 hex,最準。
2. factory.bin 國別碼正確(曾被污染成日本)
bash
xxd build/factory.bin
# offset 0--5 = MAC(例 00 0a 52 ab cd ef)
# offset 8 = 國別碼:01=US/AU、02=EU、04=JP ← 一定要確認!
曾發生 offset 8 = 04(JP)的污染(前一次中途失敗殘留)。要 US 就確認是 01,不對就重組:
bash
printf '\x00\x0a\x52\xab\xcd\xef\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00' > build/factory.bin
3. 檔案複製到 Windows 沒壞(用 SHA256 對拍)
bash
sha256sum build/wolfboot.bin # Linux
powershell
Get-FileHash "$dir\wolfboot.bin" -Algorithm SHA256 # Windows
兩邊一致才往下。務必用二進位方式複製(共用資料夾直接拖、scp),絕不要經文字編輯器(會改 CRLF/編碼把 bin 弄壞)。
補充:CubeProgrammer 需要副檔名,openocd 不需要
- build 產出的 config 沒有副檔名 → 給 CubeProgrammer 前要 改名成
config.bin(CubeProgrammer 靠副檔名判斷格式)。 - openocd 的
flash write_image ... bin已明示格式,不需要副檔名 ,可直接用build/config。
為什麼要「逐 sector 抹除」,不能 -e all
這是wolfboot + IWDG 組合最反直覺、也最容易卡住的一點。
直接原因:-e all 在這板子會被 IWDG 打斷
-
-e all是抹整片 4MB,耗時較久。 -
只要過程中有任何瞬間 app 跑起來開了 IWDG(即使 Connect mode 顯示 Under Reset,整片抹除拖太久仍可能被打斷),看門狗就重置晶片 → 抹除中斷。
-
你會看到誤導訊息:
Mass erase operation failed. Please verify flash protection或
Existing specified sectors are erased successfully Protected sectors are not erased這兩個訊息都不代表 flash 真的被保護 ------我們讀過 option bytes:RDP=0xAA(Level 0)、WRP 全 unlock(STRT > END 且 UNLOCK=1)、TZEN=0,完全沒有保護。真正原因是抹除被中途重置 / CubeProgrammer 誤報。
深層原因:NOR flash 寫入只能 1→0,沒抹乾淨就寫會對不上
-
flash 抹除 = 把位元全設成
1(0xFF);寫入只能把1變0。 -
若某 sector 沒被抹乾淨 (還殘留
0),你寫新資料時,殘留的0清不掉 → 讀回 = 舊值 AND 新值。 -
實際踩到的證據:
Error: Data mismatch found at address 0x080004CA (byte = 0x81 instead of 0x02) Download verification failed0x81vs0x02正是「sector 0 沒抹乾淨就寫」的典型。而檔案 hash 已驗證一致 → 不是檔案壞,是抹除沒完成。
逐 sector 抹除的好處
- 每塊抹除耗時短,被看門狗打斷的機率大幅降低。
- 只動要寫的區,不碰其他、不增加被打斷的時間窗。
- 可以抹完馬上讀回驗空 (應全
0xFF),確認真的乾淨再寫,避免帶著殘留去寫造成 verify mismatch。
還有一點:每個 -d 寫入本來就會先抹該區
CubeProgrammer 的 -d file addr -v 在寫之前會自動抹除目標 sector 。所以把 5 個區全部 -d 寫一遍,等於覆蓋了所有用到的 flash------很多時候根本不需要 mass-erase。要全清只是為了「連中間沒用到的空白區也清掉」,對開機沒影響。
完整燒錄流程(逐 sector,全程 mode=UR)
powershell
$cli = "C:\Program Files\STMicroelectronics\STM32Cube\STM32CubeProgrammer\bin\STM32_Programmer_CLI.exe"
$dir = "C:\Users\username\Documents\build"
# 0. 確認連得上 + option bytes(RDP 0、WRP unlock、Connect mode: Under Reset)
& $cli -c port=SWD mode=UR -ob displ
# 1. 只抹 bootloader 用到的前幾個 sector(U595 8KB/sector,bootloader≈page 0..4)
& $cli -c port=SWD mode=UR -e [0 4]
# 2. 讀回 0x080004C0 附近確認是 FF(真的抹乾淨)
& $cli -c port=SWD mode=UR -r32 0x080004C0 0x10
# 3. 單獨燒 + 驗 bootloader(先確定這塊乾淨能過,再燒其餘)
& $cli -c port=SWD mode=UR -d "$dir\wolfboot.bin" 0x08000000 -v
# 4. 抹其餘要用到的區,再依序燒
& $cli -c port=SWD mode=UR -e [5 199]
& $cli -c port=SWD mode=UR -d "$dir\factory.bin" 0x0800A000 -v
& $cli -c port=SWD mode=UR -d "$dir\config.bin" 0x0800C000 -v
& $cli -c port=SWD mode=UR -d "$dir\config.bin" 0x0800E000 -v
& $cli -c port=SWD mode=UR -d "$dir\app_signed.bin" 0x08010000 -v -rst
若抹除/verify 還是失敗(IWDG 真的在打斷)
降低 SWD 頻率,慢一點更穩、較不易被中途重置打斷:
powershell
& $cli -c port=SWD mode=UR freq=1000 -e [0 4]
& $cli -c port=SWD mode=UR freq=1000 -d "$dir\wolfboot.bin" 0x08000000 -v
並確認輸出 Connect mode: Under Reset + Reset mode: Hardware reset;若 NRST 接觸不良,UR 名實不符,抹除一樣會被看門狗打斷。
燒完驗證
bash
# 實體拔插 dongle 上電後(Linux)
sudo dmesg -w
# 期待:cdc_ncm ... MAC-Address: 00:0a:52:ab:cd:ef 且 cdc_ncm ... usb0 列舉成功
Windows 端看網卡 MAC:
powershell
Get-NetAdapter | Format-Table Name, InterfaceDescription, MacAddress, Status
找 NCM/USB 那張,MAC 顯示 00-0A-52-AB-CD-EF = factory 生效;若是隨機 locally-administered MAC(如 7E-4F-...)= factory 還沒燒進去。
重點回顧(一句話版)
- VM 編譯 因為 toolchain 是 Linux;Windows 燒錄 因為這板子要
mode=UR避開 IWDG。 - 逐 sector 抹除 因為
-e all太久會被看門狗打斷、且 flash 沒抹乾淨就寫會 1/0 殘留導致 verify mismatch。 Mass erase failed / Protected sectors在這板子不是真的保護(option bytes 已證實沒鎖),而是抹除被中途重置。- 每個
-d -v會自動先抹該區,所以全寫一遍通常不必 mass-erase。 - 凡是 verify 在 bootloader 區(
0x08000xxx)對不上:先懷疑「抹除沒完成」,讀回驗0xFF,必要時降頻freq=1000。
ab:cd:ef是測試 MAC,正式出貨換公司分配的位址,並確認國別碼對應實際出貨地。
附錄:下次若要改用「不含 wolfBoot」的韌體(app 直接開機)
情境:同一塊板子,下次不想跑 wolfBoot secure boot,而是讓 example app 直接開機 。
而 example 的 build 可能只產出
.elf(沒有 wolfBoot 那套 sign / header 注入 /wolfboot_burn.sh流程)。下面說明這時要注意什麼、
.elf怎麼燒、以及怎麼還原回 wolfBoot。
先搞懂「拿掉 wolfBoot」改變了什麼
| 項目 | 有 wolfBoot(現況) | 不含 wolfBoot(直接開機) |
|---|---|---|
| 0x08000000 放什麼 | wolfBoot bootloader | 直接放 app(或仍放 app 在他處,改開機位址) |
| app 連結位址 | 0x08010000 + header(被簽章、前面有 wolfBoot manifest header) |
app 的向量表要落在晶片的開機位址 |
| 簽章 / header | 需要(private.der 簽、dd 注入 config) |
不需要簽章、不需要 header |
| 開機流程 | reset → wolfBoot 驗章 → 跳 app | reset → 直接跑 app 的 reset vector |
| 產出檔 | wolfboot.bin + cdc-ncm_v1_signed.bin |
通常只有 app.elf(或 objcopy 出的 app.bin) |
關鍵:不能拿「wolfBoot 版的簽章 app」直接燒到 0x08000000。 它是連結在 0x08010000 + header 的,前面還有 wolfBoot manifest,向量表不在 0x08000000,直接開機會 hardfault。要用的是為直接開機另外連結 的那份 app(通常就是非 -wolfboot 的 target 產出的 .elf)。
.elf 其實可以直接燒,不一定要轉 .bin
.elf 內含各區段的載入位址(LMA),CubeProgrammer 和 openocd 都讀得懂,不必給位址:
powershell
# CubeProgrammer 直接燒 elf(位址由 elf 自帶;全程 mode=UR)
& $cli -c port=SWD mode=UR -d "C:\...\app.elf" -v -rst
若想要 .bin(例如要對照位址、或走和 wolfBoot 一樣的 -d file addr 流程):
bash
arm-none-eabi-objcopy -O binary app.elf app.bin # 產出從最低 LMA 起的 raw binary
然後依它連結的開機位址燒,例如 -d app.bin 0x08000000。
燒之前一定要先確認:這個 .elf 連結在哪個位址
這是整件事的核心------開機位址、向量表、option byte 三者要對得上。先讀 elf:
bash
arm-none-eabi-readelf -h app.elf | grep Entry # Entry point address(reset 入口)
arm-none-eabi-objdump -h app.elf | grep -E '\.isr_vector|\.text' # 向量表 / code 的 LMA
arm-none-eabi-readelf -l app.elf # program headers 的 PhysAddr(LMA)
看 .isr_vector(或等價的向量表區段)的位址,分兩種情況處理:
情況 A:elf 連結在 0x08000000(最單純,建議走這條)
- app 的向量表就在開機位址,
NSBOOTADD0現況已是0x08000000(option bytes 不用改)。 - 直接燒 elf 到 0x08000000,會覆蓋掉 wolfBoot(wolfBoot 本來就在那)。
- 確認 app 的
SystemInit/ 啟動碼把SCB->VTOR設成0x08000000(多數 ST 啟動碼會依連結位址自動設,通常不用動)。
情況 B:elf 仍連結在 0x08010000(或其他非 0 位址)
- 兩個選擇:
-
改連結位址到 0x08000000,重編,再走情況 A(最乾淨)。
-
不改 app,改開機位址 :把 app 燒到它連結的位址(如 0x08010000),再把 option byte
NSBOOTADD0指過去:powershell# 讓晶片 reset 後直接從 0x08010000 開機(0x08010000 >> 7 = 0x100200) & $cli -c port=SWD mode=UR -ob NSBOOTADD0=0x100200換算:
NSBOOTADD0存的是「位址 >> 7」。0x08000000>>7 = 0x100000、0x08010000>>7 = 0x100200。用這條前先
-ob displ記下原值(現況0x100000),方便還原。- 這條路 0x08000000 那塊 wolfBoot 可留可清(反正 reset 不再從那進入),但建議清掉避免混淆。
-
還有:IWDG 與「直接開機」後的再燒錄
- 直接開機後,這顆 app 若自己啟動 IWDG (很可能,cdc-ncm app 就會),那麼下次要再改 flash 時,一樣會遇到「app+IWDG 在跑、抹寫被打斷」的老問題 → 仍然用
mode=UR(under-reset,app 啟動前接管)。 - 而且拿掉 wolfBoot 後,就沒有「卡在 wolfBoot panic 迴圈、無看門狗」那個 openocd 友善視窗 了。所以再燒錄請直接認定走 CubeProgrammer
mode=UR。
還原回 wolfBoot(保留退路)
把這幾樣留好,隨時能切回 secure boot:
wolfboot.bin(committed 金鑰版,grep 指紋 == keystore.c)cdc-ncm_v1_signed.bin(簽章 app)config.bin、factory.bin- 原始
NSBOOTADD0 = 0x100000(= 0x08000000)
還原步驟:把 NSBOOTADD0 設回 0x100000 → 照本文「完整燒錄流程」逐 sector 重燒 wolfboot.bin@0x08000000 + factory/config + 簽章 app@0x08010000。
檢查清單(改用不含 wolfBoot 前)
- 確認手上的
.elf是直接開機版(非 -wolfboot target),不是 wolfBoot 的簽章 app。 -
readelf -h看 Entry、objdump -h看.isr_vector的 LMA → 決定走情況 A 還是 B。 - 開機位址、向量表、
NSBOOTADD0三者一致;SCB->VTOR對應向量表。 - 用
mode=UR燒(.elf可直接-d app.elf -v -rst)。 - 記下原始
NSBOOTADD0、留好 wolfBoot 那套檔,保留還原退路。 - 燒完
dmesg/Get-NetAdapter確認開機 + 列舉正常。
重點回顧(一句話版)
- VM 編譯 因為 toolchain 是 Linux;Windows 燒錄 因為這板子要
mode=UR避開 IWDG。 - 逐 sector 抹除 因為
-e all太久會被看門狗打斷、且 flash 沒抹乾淨就寫會 1/0 殘留導致 verify mismatch。 Mass erase failed / Protected sectors在這板子不是真的保護(option bytes 已證實沒鎖),而是抹除被中途重置。- 每個
-d -v會自動先抹該區,所以全寫一遍通常不必 mass-erase。 - 凡是 verify 在 bootloader 區(
0x08000xxx)對不上:先懷疑「抹除沒完成」,讀回驗0xFF,必要時降頻freq=1000。
ab:cd:ef是測試 MAC,正式出貨換公司分配的位址,並確認國別碼對應實際出貨地。