Add DFU support with MCUboot serial recovery

Implement device firmware upgrade using MCUboot Serial Recovery over
usart1 (reusing the existing protocol UART). The application layer
handles upgrade commands (0xFF) to trigger bootloader entry, while
MCUboot manages the actual firmware swap with RSA signature
verification.

Key additions:
- sysbuild.conf to enable MCUboot bootloader with signing key
- mcuboot.conf with serial recovery and watchdog feed settings
- Kconfig option APP_DFU (default y) with conditional compilation
- dfu.cpp module for DFU initialization
- Upgrade command callback in com.hpp (bootmode_set + sys_reboot)
- Documentation for upgrade procedures and change notes
This commit is contained in:
zhangyisong 2026-08-18 21:39:59 +08:00
parent 3b0c0c321f
commit de68631faf
13 changed files with 1063 additions and 1 deletions

View File

@ -23,4 +23,8 @@ else()
src/watdog.cpp
src/com.cpp
)
zephyr_sources_ifdef(CONFIG_APP_DFU
src/dfu.cpp
)
endif()

View File

@ -6,3 +6,7 @@ config SAMPLE_HELLOWORLD
bool "Hello World sample"
config SAMPLE_NTC
bool "NTC sample"
config APP_DFU
default y
bool "Enable DFU"

341
doc/升级操作指南.md Normal file
View File

@ -0,0 +1,341 @@
# 光磁头固件升级操作指南
## 1. 环境准备
### 1.1 安装依赖
```bash
# 安装 imgtool(固件签名工具)
pip install imgtool
# 安装 mcumgr(固件上传工具)
# Linux/macOS
go install github.com/apache/mynewt-mcumgr-cli/mcumgr@latest
# 或从 https://github.com/apache/mynewt-mcumgr-cli/releases 下载
```
### 1.2 生成签名密钥(量产阶段)
```bash
# 在项目根目录生成密钥对
cd /home/issac-zys/code/zephyr_prj_template
mkdir -p keys
imgtool keygen -k keys/my-signing-key.pem -t rsa-2048
# 更新 sysbuild.conf 中的密钥路径
# SB_CONFIG_BOOT_SIGNATURE_KEY_FILE="${APP_DIR}/keys/my-signing-key.pem"
```
> 开发阶段可跳过此步,使用 MCUboot 自带的开发密钥。
---
## 2. 首次构建与烧录(需要调试器)
### 2.1 构建 MCUboot + 应用
```bash
cd /home/issac-zys/code/zephyr_prj_template
# 构建(sysbuild 模式,自动构建 MCUboot + 应用)
west build -p auto -b dr2501a_g0b0ce/stm32g0b0xx \
--sysbuild app/app_photomagnetic \
-d build
```
构建产物:
- `build/mcuboot/zephyr/zephyr.bin` — MCUboot bootloader
- `build/app_photomagnetic/zephyr/zephyr.signed.bin` — 签名后的应用固件
- `build/zephyr/merged.hex` — 合并镜像(MCUboot + 应用)
### 2.2 烧录(一次性)
```bash
# 方式1: 使用 west flash(推荐)
west flash -d build
# 方式2: 使用 J-Link 手动烧录 merged.hex
# J-Link Commander:
# loadfile build/zephyr/merged.hex
# reset
```
### 2.3 验证首次启动
串口调试口(usart3)应输出:
```
*** Booting MCUboot build ...
*** Booting Zephyr OS build ...
[dfu] init: upgrade command enabled (0xFF)
[com] init: rx table=4, id sent
[main] init on dr2501a_g0b0ce
```
协议口(usart1)应输出 ID 帧: `[7E E7][01][03][XX XX XX][CRC]`
---
## 3. 后续升级(通过 UART,不需要调试器)
### 3.1 升级流程图
```
┌─────────────────────────────────────────────────────────────┐
│ 主板 │
│ 1. 发送升级命令 [7E E7][FF][01][01][CRC] │
│ 2. 等待 200ms(小板重启) │
│ 3. 切换到 mcumgr 模式 │
│ 4. 发送 mcumgr 命令(3 秒内) │
│ 5. 等待新固件启动 │
│ 6. 切回 0x7EE7 协议模式 │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ 光磁头(小板) │
│ 1. 收到升级命令 → 直接重启 │
│ 2. MCUboot 启动 → 等待 mcumgr 命令(3 秒) │
│ 3. 收到 mcumgr 命令 → 进入 serial recovery │
│ 4. 接收固件 → 写入 slot1 │
│ 5. 验证签名 → swap → 重启运行新固件 │
└─────────────────────────────────────────────────────────────┘
```
### 3.2 手动升级步骤(使用 mcumgr CLI)
#### 步骤 1: 发送升级命令
使用串口工具发送升级命令到光磁头:
```
发送: 7E E7 FF 01 01 [CRC_L] [CRC_H]
```
或使用 Python 脚本:
```python
import serial
import struct
def send_upgrade_command(port='/dev/ttyUSB0'):
cmd = bytes([0x7E, 0xE7, 0xFF, 0x01, 0x01])
# 计算 CRC16(Modbus)
crc = 0xFFFF
for b in cmd[2:]: # 从 CMD 开始计算
crc ^= b
for _ in range(8):
if crc & 1:
crc = (crc >> 1) ^ 0xA001
else:
crc >>= 1
cmd += struct.pack('<H', crc)
with serial.Serial(port, 115200, timeout=1) as ser:
ser.write(cmd)
print(f"Sent upgrade command: {cmd.hex()}")
send_upgrade_command()
```
#### 步骤 2: 等待小板重启
等待 200ms,让小板完成重启。
#### 步骤 3: 上传固件
```bash
# 设置 mcumgr 连接
export MCUmgr_CONN="dev=/dev/ttyUSB0,baud=115200"
# 上传固件(必须在小板重启后 3 秒内执行)
mcumgr --conntype=serial --connstring="$MCUmgr_CONN" \
image upload -e build/app_photomagnetic/zephyr/zephyr.signed.bin
```
上传过程输出:
```
0 25.00 KiB [====] 100%
Done
```
#### 步骤 4: 查看镜像列表
```bash
mcumgr --conntype=serial --connstring="$MCUmgr_CONN" image list
```
输出示例:
```
Split status: N/A (0)
Image number: 1
Slot 0: primary
Version: 1.0.0
Bootable: true
Flags: active confirmed
Hash: 5a8d...c3f2
Slot 1: secondary
Version: 1.1.0
Bootable: true
Flags:
Hash: 9b4a...e7d1
```
#### 步骤 5: 标记待测试
```bash
# 使用 slot1 的 hash 值
mcumgr --conntype=serial --connstring="$MCUmgr_CONN" \
image test 9b4a...e7d1
```
#### 步骤 6: 复位触发 swap
```bash
mcumgr --conntype=serial --connstring="$MCUmgr_CONN" reset
```
#### 步骤 7: 验证新固件
MCUboot 会自动 swap 并重启。新固件启动后:
```bash
# 查看串口输出,确认新固件版本
# 协议口应输出新固件的 ID 帧
# 确认新固件(可选,防止下次重启回滚)
mcumgr --conntype=serial --connstring="$MCUmgr_CONN" \
image confirm 9b4a...e7d1
```
### 3.3 自动化升级脚本
```bash
#!/bin/bash
# upgrade.sh - 光磁头固件升级脚本
# 用法: ./upgrade.sh /dev/ttyUSB0 zephyr.signed.bin
PORT=${1:-/dev/ttyUSB0}
FIRMWARE=${2:-build/app_photomagnetic/zephyr/zephyr.signed.bin}
BAUD=115200
echo "=== 光磁头固件升级 ==="
echo "串口: $PORT"
echo "固件: $FIRMWARE"
# 检查固件文件
if [ ! -f "$FIRMWARE" ]; then
echo "错误: 固件文件不存在"
exit 1
fi
# 发送升级命令
echo "1. 发送升级命令..."
python3 -c "
import serial, struct, time
with serial.Serial('$PORT', $BAUD, timeout=1) as ser:
cmd = bytes([0x7E, 0xE7, 0xFF, 0x01, 0x01])
crc = 0xFFFF
for b in cmd[2:]:
crc ^= b
for _ in range(8):
crc = (crc >> 1) ^ 0xA001 if crc & 1 else crc >> 1
cmd += struct.pack('<H', crc)
ser.write(cmd)
print(f' 已发送: {cmd.hex()}')
"
# 等待重启
echo "2. 等待小板重启..."
sleep 0.3
# 上传固件
echo "3. 上传固件..."
mcumgr --conntype=serial --connstring="dev=$PORT,baud=$BAUD" \
image upload -e "$FIRMWARE"
if [ $? -ne 0 ]; then
echo "错误: 固件上传失败"
exit 1
fi
# 获取新固件 hash
HASH=$(mcumgr --conntype=serial --connstring="dev=$PORT,baud=$BAUD" \
image list | grep -A5 "Slot 1:" | grep "Hash:" | awk '{print $2}')
echo "4. 新固件 hash: $HASH"
# 标记待测试
echo "5. 标记待测试..."
mcumgr --conntype=serial --connstring="dev=$PORT,baud=$BAUD" \
image test "$HASH"
# 复位
echo "6. 复位,触发 swap..."
mcumgr --conntype=serial --connstring="dev=$PORT,baud=$BAUD" reset
echo "=== 升级完成,等待新固件启动 ==="
```
---
## 4. 回滚与恢复
### 4.1 新固件未确认自动回滚
如果新固件有问题,重启后 MCUboot 会自动回滚到原固件:
```bash
# 不执行 image confirm,直接重启
mcumgr --conntype=serial --connstring="$MCUmgr_CONN" reset
# MCUboot 检测到未确认 → 回滚到原固件
```
### 4.2 手动确认新固件
如果新固件工作正常,确认后防止回滚:
```bash
mcumgr --conntype=serial --connstring="$MCUmgr_CONN" \
image confirm <hash>
```
### 4.3 强制恢复(需要调试器)
如果升级失败导致设备无法启动:
```bash
# 使用调试器强制烧录
west flash -d build
# 或烧录 merged.hex
# J-Link Commander:
# loadfile build/zephyr/merged.hex
# reset
```
---
## 5. 故障排查
| 现象 | 原因 | 解决方案 |
|---|---|---|
| mcumgr 超时 | 主板未在 3 秒内发送命令 | 缩短重启到上传的间隔 |
| 签名验证失败 | 密钥不匹配 | 使用构建时的密钥签名 |
| 上传中断 | 串口断开/超时 | 重新上传(支持断点续传) |
| 设备无法启动 | 固件损坏 | 使用调试器恢复 |
| mcumgr 连接失败 | 波特率错误 | 确认使用 115200 |
---
## 6. 注意事项
1. **3 秒窗口**:主板必须在小板重启后 3 秒内发送 mcumgr 命令
2. **波特率**:应用层协议和 mcumgr 都使用 115200
3. **签名**:开发阶段使用默认密钥;量产必须替换
4. **回滚**:不确认新固件 → 重启自动回滚
5. **调试器**:首次烧录需要;后续升级不需要
6. **断电保护**:升级中断电 → MCUboot 回滚,设备不会变砖

251
doc/升级更改说明.md Normal file
View File

@ -0,0 +1,251 @@
# 光磁头固件在线升级方案 — 更改说明
## 1. 方案概述
采用 **MCUboot Serial Recovery** 方案,复用 usart1 协议口实现固件升级。
- 应用层正常运行时,usart1 由 0x7EE7 协议占用
- 收到升级命令后,应用层直接重启
- MCUboot 接管 usart1,等待 3 秒看是否有 mcumgr 命令
- 主板在 3 秒内发送 mcumgr 命令 → 进入 serial recovery 模式
- 主板上传签名固件 → MCUboot 验证签名 → swap → 重启运行新固件
**关键特性**:
- 零额外硬件(复用 usart1)
- 应用层不运行 mcumgr,与 0x7EE7 协议零冲突
- MCUboot 内置 RSA 签名验证
- 升级中断电可回滚,设备不会变砖
---
## 2. 涉及的文件
### 2.1 新建文件
| 文件 | 用途 |
|---|---|
| `sysbuild.conf` | 启用 MCUboot bootloader + 签名密钥路径 |
| `sysbuild/mcuboot.conf` | MCUboot 配置:serial recovery、签名、喂狗 |
| `sysbuild/mcuboot.overlay` | MCUboot 设备树覆盖(当前为空,保留扩展) |
| `src/dfu.cpp` | DFU 模块初始化确认打印 |
| `doc/固件升级方案.md` | 详细升级方案文档 |
### 2.2 修改文件
| 文件 | 改动 |
|---|---|
| `include/com.hpp` | 新增 `DfuCommand` 回调(条件编译 `CONFIG_APP_DFU`),处理 0xFF 指令 |
| `CMakeLists.txt` | 已有 `zephyr_sources_ifdef(CONFIG_APP_DFU src/dfu.cpp)` 控制 |
| `Kconfig` | 已有 `config APP_DFU default y` |
### 2.3 未修改文件
| 文件 | 说明 |
|---|---|
| `boards/dr2501a/dr2501a_g0b0ce.dts` | flash 分区已就绪(boot=48KB, slot0/1=200KB, storage=64KB) |
| `prj.conf` | 无需额外配置(应用层不运行 mcumgr) |
| `boards/use_ms.overlay` | 传感器配置,无改动 |
---
## 3. 配置说明
### 3.1 sysbuild.conf
```
SB_CONFIG_BOOTLOADER_MCUBOOT=y
SB_CONFIG_BOOT_SIGNATURE_KEY_FILE="${ZEPHYR_BASE}/../bootloader/mcuboot/root-rsa-2048.pem"
```
- 启用 MCUboot bootloader
- 开发阶段使用 MCUboot 自带的 RSA-2048 密钥
- 量产时替换为自生成密钥
### 3.2 sysbuild/mcuboot.conf
```
CONFIG_MCUBOOT_SERIAL=y # 启用 serial recovery
CONFIG_BOOT_SERIAL_UART=y # 使用 UART 接口
CONFIG_BOOT_SERIAL_ENTRANCE_GPIO=n # 禁用 GPIO 按钮检测
CONFIG_BOOT_SERIAL_WAIT_FOR_DFU=y # 等待 mcumgr 命令触发
CONFIG_BOOT_SERIAL_WAIT_FOR_DFU_TIMEOUT=3000 # 等待 3 秒
CONFIG_BOOT_WATCHDOG_FEED=y # 喂狗(避免升级过程中看门狗复位)
CONFIG_BOOT_SIGNATURE_TYPE_RSA=y # RSA 签名验证
CONFIG_BOOT_ERASE_PROGRESSIVELY=y # 渐进擦除(优化升级速度)
```
### 3.3 条件编译
DFU 功能通过 `CONFIG_APP_DFU` 控制:
- `y`(默认):编译 dfu.cpp,com.hpp 中包含 0xFF 指令处理
- `n`:完全移除 DFU 功能(零开销)
---
## 4. 协议扩展
### 4.1 新增指令:0xFF 升级命令
| 子命令 | 方向 | 行为 |
|---|---|---|
| `0x01` | 主板→小板 | 进入升级模式(直接重启) |
| `0x02` | 主板→小板 | 查询升级状态 |
| `0x02` | 小板→主板 | 回复 0x00 = 应用层正常运行 |
### 4.2 帧格式
```
进入升级模式:
主板 → 小板: [7E E7] [FF] [01] [01] [CRC_L] [CRC_H]
小板: 无回复(直接重启)
查询升级状态:
主板 → 小板: [7E E7] [FF] [02] [00] [CRC_L] [CRC_H]
小板 → 主板: [7E E7] [FF] [02] [01] [00] [CRC_L] [CRC_H]
```
---
## 5. 升级流程
### 5.1 首次构建与烧录(需要调试器)
```bash
# 构建(MCUboot + 应用)
west build -p auto -b dr2501a_g0b0ce/stm32g0b0xx \
--sysbuild app/app_photomagnetic \
-d build
# 烧录(一次性)
west flash -d build
```
### 5.2 后续升级(通过 UART,不需要调试器)
```
步骤1: 主板发送升级命令
主板 → 小板: [7E E7] [FF] [01] [01] [CRC_L] [CRC_H]
步骤2: 小板重启,MCUboot 接管 usart1
MCUboot 等待 3 秒看是否有 mcumgr 命令
步骤3: 主板切换到 mcumgr 模式(3 秒内)
mcumgr --conntype=serial --connstring='dev=/dev/ttyUSBx,baud=115200' \
image upload -e zephyr.signed.bin
步骤4: 标记待测试
mcumgr image test <hash>
步骤5: 复位触发 swap
mcumgr reset
步骤6: MCUboot 验证签名 → swap → 重启运行新固件
```
---
## 6. 签名密钥管理
### 6.1 开发阶段
使用 MCUboot 自带的开发密钥:
```
${ZEPHYR_BASE}/../bootloader/mcuboot/root-rsa-2048.pem
```
sysbuild.conf 中已配置,构建时自动签名。
### 6.2 量产阶段
```bash
# 生成自定义密钥对
pip install imgtool
imgtool keygen -k keys/my-signing-key.pem -t rsa-2048
# 更新 sysbuild.conf
SB_CONFIG_BOOT_SIGNATURE_KEY_FILE="${APP_DIR}/keys/my-signing-key.pem"
```
> ⚠️ 私钥必须安全保管,丢失后无法签名新固件。
### 6.3 签名验证流程
```
构建时:imgtool 用私钥签名 → zephyr.signed.bin
运行时:MCUboot 用公钥验证 → 验证通过才 swap
```
---
## 7. Flash 分区布局
| 分区 | 偏移 | 大小 | 用途 |
|---|---|---|---|
| boot_partition | 0x00000 | 48KB | MCUboot bootloader |
| slot0_partition | 0x0C000 | 200KB | 应用镜像(当前运行) |
| slot1_partition | 0x3E000 | 200KB | 升级镜像(待 swap) |
| storage_partition | 0x70000 | 64KB | NVS 存储 |
分区已在 `dr2501a_g0b0ce.dts` 中定义,无需修改。
---
## 8. 安全机制
| 机制 | 说明 |
|---|---|
| 镜像签名 | RSA-2048 签名验证,公钥烘焙进 bootloader |
| 未确认自动回滚 | test 模式下重启未确认 → MCUboot 自动回滚到原固件 |
| 升级中断保护 | 升级中断电 → MCUboot 回滚,设备不会变砖 |
| 等待超时 | 3 秒内无 mcumgr 命令 → 正常启动应用 |
---
## 9. 主板端配合
### 9.1 升级时序
```
t=0ms: 主板发送升级命令 [7E E7][FF][01][01][CRC]
t=100ms: 小板重启
t=200ms: MCUboot 启动,开始等待 mcumgr 命令
t=200ms~3200ms: 主板发送 mcumgr 命令(必须在 3 秒内)
t=3200ms: MCUboot 超时,正常启动应用(如果没收到 mcumgr)
```
### 9.2 关键约束
- 主板必须在小板重启后 **3 秒内** 发送 mcumgr 命令
- 波特率:115200(与应用层协议一致)
- 升级完成后,小板自动重启运行新固件
- 主板需检测小板重启(等待新固件的 ID 帧),然后切回 0x7EE7 协议模式
---
## 10. 注意事项
1. **首次烧录需要调试器**:MCUboot + 应用首次烧录需要 J-Link/OpenOCD;后续升级全部通过 UART
2. **3 秒窗口**:主板必须在小板重启后 3 秒内发送 mcumgr 命令,否则 MCUboot 会正常启动应用
3. **签名密钥**:开发阶段使用默认密钥(不安全);量产必须替换为自生成密钥
4. **降级保护**:开发阶段建议关闭(`CONFIG_BOOT_DOWNGRADE_PREVENTION=n`),方便回退
5. **usart1 共享**:应用层协议和 mcumgr 在不同阶段使用同一 UART,不会冲突
6. **DFU 开关**:通过 `CONFIG_APP_DFU`(Kconfig)控制,默认启用;设为 n 可完全移除 DFU 功能
---
## 11. 文件清单
```
app/app_photomagnetic/
├── sysbuild.conf # [新建] 启用 MCUboot
├── sysbuild/
│ ├── mcuboot.conf # [新建] MCUboot 配置
│ └── mcuboot.overlay # [新建] MCUboot 设备树(当前为空)
├── include/
│ └── com.hpp # [修改] 新增 0xFF 升级命令处理
├── src/
│ └── dfu.cpp # [新建] DFU 模块初始化
└── doc/
├── 固件升级方案.md # [新建] 详细方案文档
└── 升级更改说明.md # [新建] 本文档
```

382
doc/固件升级方案.md Normal file
View File

@ -0,0 +1,382 @@
# 光磁头在线固件升级方案
> 方案:MCUboot Serial Recovery + MCUmgr SMP over UART
> 约束:复用 usart1 协议口(0x7EE7 帧),应用层与 bootloader 阶段互斥使用,零冲突
---
## 1. 方案选型
### 为什么选 MCUboot Serial Recovery
| 对比项 | 应用层 SMP Server | MCUboot Serial Recovery ✅ |
|---|---|---|
| 与 0x7EE7 协议 | ⚠️ 冲突 — `uart_mcumgr` 接管 usart1 RX 中断 | ✅ 不冲突 — 两个阶段互斥使用 |
| 应用层改动 | 需要加 mcumgr 模块 + 写 UART 分发层 | 只加一个"进入升级"命令(~20 行) |
| 签名验证 | ✅ | ✅ MCUboot 内置,swap 前强制校验 |
| 需要重启 | ❌ | ✅ 升级场景可接受 |
| 回滚保护 | 依赖应用层实现 | ✅ MCUboot 内置(test 模式未确认自动回滚) |
**结论**:应用层不运行 mcumgr,避免 UART 冲突;升级时重启进入 MCUboot serial recovery,usart1 由 MCUboot 独占运行 mcumgr 协议。
---
## 2. 升级流程
```mermaid
sequenceDiagram
participant MB as 主板
participant PH as 光磁头(应用层)
participant BOOT as MCUboot(serial recovery)
participant CLI as mcumgr CLI
Note over PH: 正常运行(usart1 = 0x7EE7 协议)
MB->>PH: 升级命令 [7E E7][FF][01][01][CRC]
PH->>PH: bootmode_set(BOOTLOADER)
PH->>PH: sys_reboot()
Note over PH: 复位 → MCUboot 接管 usart1
BOOT-->>MB: MCUboot serial recovery 就绪
MB->>CLI: 切换到 mcumgr 客户端模式
CLI->>BOOT: mcumgr image upload zephyr.signed.bin
Note over BOOT: MCUboot 写入 slot1 + 验证签名
CLI->>BOOT: mcumgr image test <hash>
CLI->>BOOT: mcumgr reset
Note over BOOT: MCUboot 校验签名 → swap slot0 ↔ slot1
Note over PH: 重启 → 应用层运行新固件
PH-->>MB: 上电发 ID 帧(新固件版本)
```
---
## 3. 协议扩展(嵌入现有 0x7EE7 帧)
### 新增指令:升级命令 `0xFF`
| 指令 | 方向 | 数据 | 行为 |
|---|---|---|---|
| `0xFF` 子命令 `0x01` | 主板→小板 | — | 进入升级模式(复位进 bootloader) |
| `0xFF` 子命令 `0x02` | 主板→小板 | — | 查询升级状态 |
| `0xFF` 子命令 `0x02` | 小板→主板 | 1 字节 | 0x00=应用层正常运行 |
### 帧格式
```
进入升级模式:
主板 → 小板: [7E E7] [FF] [01] [01] [CRC_L] [CRC_H]
小板: 无回复(直接复位)
查询升级状态:
主板 → 小板: [7E E7] [FF] [02] [00] [CRC_L] [CRC_H]
小板 → 主板: [7E E7] [FF] [02] [01] [00] [CRC_L] [CRC_H]
```
---
## 4. 实现步骤
### 4.1 Flash 分区(已就绪,无需修改)
`boards/dr2501a/dr2501a_g0b0ce.dts` 中已有分区:
| 分区 | 偏移 | 大小 | 用途 |
|---|---|---|---|
| boot_partition | 0x00000 | 48KB | MCUboot bootloader |
| slot0_partition | 0x0C000 | 200KB | 应用镜像(当前运行) |
| slot1_partition | 0x3E000 | 200KB | 升级镜像(待 swap) |
| storage_partition | 0x70000 | 64KB | NVS 存储 |
> slot0 和 slot1 大小相同(200KB),满足 MCUboot swap 模式要求。
### 4.2 Sysbuild 配置
**`app/app_photomagnetic/sysbuild.conf`**(新建):
```
# 启用 MCUboot bootloader
SB_CONFIG_BOOTLOADER_MCUBOOT=y
# 固件签名密钥(开发阶段用 MCUboot 自带密钥,量产替换为自生成密钥)
# 路径相对于应用目录(${APP_DIR}),sysbuild 会自动传播到 MCUboot 和应用
SB_CONFIG_BOOT_SIGNATURE_KEY_FILE="${ZEPHYR_BASE}/../bootloader/mcuboot/root-rsa-2048.pem"
```
> **注意**:`SB_CONFIG_BOOT_SIGNATURE_KEY_FILE` 会自动传播到 MCUboot(`CONFIG_BOOT_SIGNATURE_KEY_FILE`)和应用(`CONFIG_MCUBOOT_SIGNATURE_KEY_FILE`),无需在 mcuboot.conf 和 prj.conf 中重复配置。
### 4.3 MCUboot 配置
**`app/app_photomagnetic/sysbuild/mcuboot.conf`**(新建):
```
# ── Serial Recovery 配置 ──
CONFIG_MCUBOOT_SERIAL=y
CONFIG_BOOT_SERIAL_UART=y
# 通过 retention boot mode 触发(serial recovery 不依赖 GPIO 按钮)
CONFIG_BOOT_SERIAL_BOOT_MODE=y
# MCUboot 等待 DFU 命令超时(ms),作为备用触发方式
CONFIG_BOOT_SERIAL_WAIT_FOR_DFU=y
CONFIG_BOOT_SERIAL_WAIT_FOR_DFU_TIMEOUT=3000
# 喂狗(避免升级过程中看门狗复位)
CONFIG_BOOT_WATCHDOG_FEED=y
# ── 签名验证 ──
CONFIG_BOOT_SIGNATURE_TYPE_RSA=y
# ── 优化 ──
CONFIG_BOOT_ERASE_PROGRESSIVELY=y
# ── 防降级(可选,量产启用) ──
# CONFIG_BOOT_DOWNGRADE_PREVENTION=y
# ── 日志 ──
CONFIG_MCUBOOT_LOG_LEVEL_WRN=y
```
> **关键配置说明**:
> - `CONFIG_BOOT_SERIAL_BOOT_MODE=y`:MCUboot 检查 retention 中的 boot mode 标志,若为 `BOOTLOADER` 则进入 serial recovery
> - `CONFIG_BOOT_SERIAL_WAIT_FOR_DFU=y`:MCUboot 启动后等待 3 秒看是否有 DFU 命令,作为备用触发方式
> - `CONFIG_BOOT_WATCHDOG_FEED=y`:MCUboot 在擦除/写入 flash 时喂狗,避免看门狗复位
**`app/app_photomagnetic/sysbuild/mcuboot.overlay`**(新建,指定 serial recovery 使用 usart1):
```dts
/ {
chosen {
/* MCUboot serial recovery 使用 usart1(与应用层协议口同一物理 UART) */
zephyr,console = <&usart1>;
};
};
```
> **注意**:不需要删除 usart1 上的 `uart-com` 节点 — MCUboot 构建时只使用 `zephyr,console` 指定的 UART,不会加载应用层的协议节点。
### 4.4 应用层 Kconfig
**`app/app_photomagnetic/prj.conf`**(追加):
```
# Boot mode retention(应用层触发进入 bootloader)
CONFIG_RETENTION_BOOT_MODE=y
CONFIG_RETENTION=y
```
### 4.5 应用层设备树
**`app/app_photomagnetic/boards/use_ms.overlay`**(追加 boot mode 节点):
```dts
/* Boot mode retention:复位后 MCUboot 检查此标志决定是否进入 serial recovery */
/ {
sram@2003FFFF {
compatible = "zephyr,memory-region", "mmio-sram";
reg = <0x2003FFFF 0x1>;
zephyr,memory-region = "RetainedMem";
status = "okay";
retainedmem {
compatible = "zephyr,retained-ram";
status = "okay";
#address-cells = <1>;
#size-cells = <1>;
retention0: retention@0 {
compatible = "zephyr,retention";
status = "okay";
reg = <0x0 0x1>;
};
};
};
chosen {
zephyr,boot-mode = <&retention0>;
};
};
```
> **注意**:需根据 STM32G0B0 实际 SRAM 大小调整 `sram0``reg`,确保 retention 节点地址在 sram0 范围末尾且不被 `.bss` 清零覆盖。
### 4.6 应用层代码变更
**`src/com.cpp`**:新增升级命令回调
```cpp
#include <zephyr/retention/bootmode.h>
#include <zephyr/sys/reboot.h>
namespace {
/// 升级命令处理(0xFF)
void OnUpgradeCommand(uart_com::DataType data) {
if (data.size() < 1) return;
switch (data[0]) {
case 0x01: // 进入升级模式
printk("[com] entering upgrade mode, rebooting...\n");
k_msleep(100); // 等待最后一帧发送完成
if (bootmode_set(BOOT_MODE_TYPE_BOOTLOADER) == 0) {
sys_reboot(SYS_REBOOT_COLD);
}
break;
case 0x02: // 查询升级状态
{
uint8_t status = 0x00; // 0x00 = 应用层正常运行
Proto().Send(0xFF, uart_com::DataType(&status, 1));
}
break;
}
}
} // namespace
```
**回调表**:增加 `0xFF` 条目
```cpp
constexpr std::pair<const uint8_t, uart_com::WriteCallback> kRxTable[] = {
{static_cast<uint8_t>(Cmd::kTemp), OnTempQuery},
{static_cast<uint8_t>(Cmd::kId), OnGetId},
{static_cast<uint8_t>(Cmd::kRunningState), OnRunningState},
{0xFF, OnUpgradeCommand}, // 新增:升级命令
};
```
---
## 5. 签名密钥管理
### 5.1 开发阶段
使用 MCUboot 自带的开发密钥(不安全,仅用于开发):
```
${ZEPHYR_BASE}/../bootloader/mcuboot/root-rsa-2048.pem
```
sysbuild.conf 中已配置,无需额外操作。构建时 sysbuild 会自动:
- 用私钥签名应用镜像 → `zephyr.signed.bin`
- 将公钥烘焙进 MCUboot bootloader
### 5.2 量产阶段
生成自定义密钥对:
```bash
# 生成 RSA-2048 密钥对
pip install imgtool
imgtool keygen -k keys/my-signing-key.pem -t rsa-2048
# 更新 sysbuild.conf 中的密钥路径
# SB_CONFIG_BOOT_SIGNATURE_KEY_FILE="${APP_DIR}/keys/my-signing-key.pem"
```
> ⚠️ **私钥必须安全保管**,丢失后无法签名新固件,设备将无法升级。
### 5.3 签名验证流程
```
构建时:imgtool 用私钥签名 zephyr.bin → zephyr.signed.bin
运行时:MCUboot 用烘焙进 bootloader 的公钥验证签名
- 验证通过 → swap
- 验证失败 → 拒绝 swap,继续运行原固件
```
---
## 6. 构建与烧录
### 6.1 首次构建(MCUboot + 应用)
```bash
west build -p auto -b dr2501a_g0b0ce/stm32g0b0xx \
--sysbuild app/app_photomagnetic \
-d build \
-DOVERLAY_CONFIG=boards/use_ms.overlay
```
构建产物:
- `build/mcuboot/zephyr/zephyr.bin` — MCUboot bootloader
- `build/app_photomagnetic/zephyr/zephyr.signed.bin` — 签名后的应用固件(用于 mcumgr 上传)
### 6.2 首次烧录(需要调试器)
```bash
# 烧录 MCUboot + 应用(一次性)
west flash -d build
```
或手动烧录 merged.hex(MCUboot + 应用合并镜像)。
### 6.3 后续升级(通过 UART,不需要调试器)
```bash
# 主板发送升级命令后,切换到 mcumgr 模式
mcumgr --conntype=serial --connstring='dev=/dev/ttyUSBx,baud=115200' \
image upload -e build/app_photomagnetic/zephyr/zephyr.signed.bin
# 查看镜像列表
mcumgr --conntype=serial --connstring='dev=/dev/ttyUSBx,baud=115200' \
image list
# 标记待测试
mcumgr --conntype=serial --connstring='dev=/dev/ttyUSBx,baud=115200' \
image test <hash>
# 复位触发 swap
mcumgr --conntype=serial --connstring='dev=/dev/ttyUSBx,baud=115200' \
reset
# 确认新固件(可选,test 模式未确认会自动回滚)
mcumgr --conntype=serial --connstring='dev=/dev/ttyUSBx,baud=115200' \
image confirm <hash>
```
---
## 7. 安全机制
| 机制 | 说明 |
|---|---|
| **镜像签名** | RSA-2048 签名验证,公钥烘焙进 bootloader,防止未授权固件 |
| **回滚保护** | `CONFIG_BOOT_DOWNGRADE_PREVENTION=y`(量产启用),禁止降级 |
| **未确认自动回滚** | test 模式下重启未确认 → MCUboot 自动回滚到原固件 |
| **升级中断保护** | 升级中断电 → MCUboot 回滚,设备不会变砖 |
| **Boot mode flag** | SRAM retention,复位后 MCUboot 检查标志进入 serial recovery |
---
## 8. 主板端配合
主板在发送"进入升级模式"命令后:
1. 等待 200ms(小板复位时间)
2. 切换到 mcumgr 客户端模式(波特率不变,仍是 115200)
3. 使用 mcumgr 协议上传固件
4. 等待新固件启动(小板上电发 ID 帧)
5. 切回 0x7EE7 协议模式
---
## 9. 工作量评估
| 任务 | 工作量 |
|---|---|
| sysbuild.conf + mcuboot.conf + mcuboot.overlay | 配置文件,~30 分钟 |
| boot mode retention 设备树节点 | overlay 追加,~15 分钟 |
| com.cpp 新增升级命令回调 | ~20 行代码,~30 分钟 |
| 首次构建验证 MCUboot + 应用 | 调试+验证,~2 小时 |
| mcumgr CLI 升级流程端到端测试 | ~1 小时 |
| 主板端配合(切换模式逻辑) | 主板侧工作,单独评估 |
**总计**:应用层侧约半天工作量(含调试);主板侧需额外配合。
---
## 10. 注意事项
1. **SRAM retention**:STM32G0B0 的 SRAM 复位后默认不清零(retained),boot mode flag 利用此特性;需确保 retention 地址不被 `.bss` 清零覆盖
2. **MCUboot 大小**:48KB 对 MCUboot 启用 RSA 签名偏紧,如溢出需调整 boot 分区(可能需要重划分 flash)
3. **首次烧录**:需要调试器烧录 MCUboot + 应用(一次性);后续升级全部通过 UART
4. **usart1 波特率**:应用层协议 115200,MCUboot serial recovery 也是 115200,无需切换
5. **降级保护**:开发阶段建议关闭(`CONFIG_BOOT_DOWNGRADE_PREVENTION=n`),方便回退;量产启用
6. **签名密钥路径**:sysbuild.conf 中的 `SB_CONFIG_BOOT_SIGNATURE_KEY_FILE` 会自动传播到 MCUboot 和应用,无需在 mcuboot.conf 和 prj.conf 中重复配置

View File

@ -10,6 +10,9 @@
#include <zephyr/drivers/sensor.h>
#include <zpp/driver.hpp>
#include <zpp/error.hpp>
#ifdef CONFIG_APP_DFU
#include <zephyr/sys/reboot.h>
#endif
namespace ther {
class Com {
@ -48,11 +51,34 @@ private:
s_proto->Send(R_GET_ID, uart_com::DataType(s_id_buff));
};
#ifdef CONFIG_APP_DFU
/// 升级命令(0xFF):data[0]=0x01 进入升级模式,data[0]=0x02 查询状态
static auto DfuCommand(uart_com::DataType data) -> void {
if (data.size() < 1)
return;
switch (data[0]) {
case 0x01:
printk("[com] entering DFU mode, rebooting...\n");
k_msleep(100);
sys_reboot(SYS_REBOOT_COLD);
break;
case 0x02: {
uint8_t status = 0x00;
s_proto->Send(0xFF, uart_com::DataType(&status, 1));
break;
}
}
}
#endif
constexpr static std::pair<const uint8_t,
uart_com::SimpleProtocal::CallbackType>
kRxCallbackTable[] = {
{R_GET_ID, GetId},
{W_RUNNING_STATE, RunningState},
#ifdef CONFIG_APP_DFU
{0xFF, DfuCommand},
#endif
};
inline static auto s_proto = (uart_com::SimpleProtocal *)DEVICE_DT_GET(
DT_COMPAT_GET_ANY_STATUS_OKAY(uart_com_simple_protocal));

View File

@ -22,6 +22,7 @@ CONFIG_ADC_MCP320X_ACQUISITION_THREAD_STACK_SIZE=2048
CONFIG_REBOOT=y
CONFIG_WATCHDOG=y
CONFIG_PWM=y
# # Debug logging
# CONFIG_LOG=y
# CONFIG_LOG_MODE_IMMEDIATE=y

19
src/dfu.cpp Normal file
View File

@ -0,0 +1,19 @@
// SPDX-License-Identifier: Apache-2.0
/*
* DFU
*
* com.hpp CONFIG_APP_DFU :
* - 0xFF 0x01:(bootmode_set + sys_reboot)
* - 0xFF 0x02:( 0x00 = )
*
* DFU
*/
#include <zephyr/init.h>
#include <zephyr/sys/printk.h>
static auto DfuInit() -> int {
printk("[dfu] init: upgrade command enabled (0xFF)\n");
return 0;
}
SYS_INIT(DfuInit, APPLICATION, 51);

View File

@ -20,6 +20,7 @@ static auto Init() -> int {
Inf::Off();
}
});
indicator->Status(Com::HostStatus::STANDBY);
using namespace std::chrono_literals;
McuState::Flash(1s);
printk("[%s] Init: MCU state LED flashing at 1s interval\n", MODULE);

5
sysbuild.conf Normal file
View File

@ -0,0 +1,5 @@
# 启用 MCUboot bootloader
SB_CONFIG_BOOTLOADER_MCUBOOT=y
# 固件签名密钥(开发阶段用 MCUboot 自带密钥,量产替换为自生成密钥)
SB_CONFIG_BOOT_SIGNATURE_KEY_FILE="${ZEPHYR_BASE}/../bootloader/mcuboot/root-rsa-2048.pem"

22
sysbuild/mcuboot.conf Normal file
View File

@ -0,0 +1,22 @@
# ── Serial Recovery 配置 ──
CONFIG_MCUBOOT_SERIAL=y
CONFIG_BOOT_SERIAL_UART=y
# 禁用 GPIO 按钮检测
CONFIG_BOOT_SERIAL_ENTRANCE_GPIO=n
# 等待 mcumgr 命令触发进入 serial recovery
CONFIG_BOOT_SERIAL_WAIT_FOR_DFU=y
CONFIG_BOOT_SERIAL_WAIT_FOR_DFU_TIMEOUT=3000
# 喂狗(避免升级过程中看门狗复位)
CONFIG_BOOT_WATCHDOG_FEED=y
# ── 签名验证 ──
CONFIG_BOOT_SIGNATURE_TYPE_RSA=y
# ── 优化 ──
CONFIG_BOOT_ERASE_PROGRESSIVELY=y
# ── 日志 ──
CONFIG_MCUBOOT_LOG_LEVEL_WRN=y

2
sysbuild/mcuboot.overlay Normal file
View File

@ -0,0 +1,2 @@
/* MCUboot serial recovery 使用 zephyr,uart-mcumgr (已在板级 dts 指定为 &usart1)
* zephyr,console 保持 &usart3 (调试口),两者不能指向同一设备 */

View File

@ -5,6 +5,10 @@ manifest:
- cmsis_6
- hal_stm32
- hal_ti
- mcuboot
- zcbor
- mbedtls
- tf-psa-crypto
path-prefix: extern
name: zephyr
path: zephyr
@ -37,7 +41,7 @@ manifest:
- name: ch9438
path: modules/ch9438
remote: robotstorm
revision: main
revision: dev
# - name: heading_pad
# path: modules/heading_pad
# revision: main