從零開始在家開發 IoT: VM編譯到Windows燒錄與逐sector抹除原因

對象: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 cleankeysclean 把 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 抹除 = 把位元全設成 10xFF);寫入只能把 10

  • 若某 sector 沒被抹乾淨 (還殘留 0),你寫新資料時,殘留的 0 清不掉 → 讀回 = 舊值 AND 新值。

  • 實際踩到的證據:

    复制代码
    Error: Data mismatch found at address 0x080004CA (byte = 0x81 instead of 0x02)
    Download verification failed

    0x81 vs 0x02 正是「sector 0 沒抹乾淨就寫」的典型。而檔案 hash 已驗證一致 → 不是檔案壞,是抹除沒完成

逐 sector 抹除的好處

  1. 每塊抹除耗時短,被看門狗打斷的機率大幅降低。
  2. 只動要寫的區,不碰其他、不增加被打斷的時間窗。
  3. 可以抹完馬上讀回驗空 (應全 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 位址)

  • 兩個選擇:
    1. 改連結位址到 0x08000000,重編,再走情況 A(最乾淨)。

    2. 不改 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 = 0x1000000x08010000>>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:

  1. wolfboot.bin(committed 金鑰版,grep 指紋 == keystore.c)
  2. cdc-ncm_v1_signed.bin(簽章 app)
  3. config.binfactory.bin
  4. 原始 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,正式出貨換公司分配的位址,並確認國別碼對應實際出貨地。

相关推荐
XINVRY-FPGA2 小时前
XC7Z035-2FFG900I Xilinx/AMD Zynq-7000 SoC FPGA
人工智能·嵌入式硬件·计算机视觉·fpga开发·硬件工程·dsp开发·fpga
fffzd2 小时前
STM32:串口--DMA
stm32·单片机·嵌入式硬件·串口·dma·回调函数
踏着七彩祥云的小丑2 小时前
嵌入式测试学习第 28 天:网络调试助手使用、TCP服务端客户端实操
单片机·嵌入式硬件·学习
不脱发的程序猿15 小时前
AI Coding时上下文不够用咋办?
单片机·嵌入式硬件·嵌入式
leoFY12316 小时前
SGM3209(圣邦微 高压负压电荷泵)(与TP7660可只修改4脚,7脚即可替换)
单片机·嵌入式硬件
zlinear数据采集卡17 小时前
基准电压电路深度解析:从理论参数到ZLinear采集卡的精准参考实战
c语言·单片机·嵌入式硬件·fpga开发·自动化
下午写HelloWorld17 小时前
GD32F4系列微控制器上电启动流程
单片机·嵌入式硬件
daad77717 小时前
记录一次ardupilot_sitl调试longitude的输入数据流
单片机·嵌入式硬件
搁浅小泽17 小时前
电子负载的作用
单片机·嵌入式硬件