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
12 KiB
光磁头在线固件升级方案
方案: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. 升级流程
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 recoveryCONFIG_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):
/ {
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 节点):
/* 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:新增升级命令回调
#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 条目
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 量产阶段
生成自定义密钥对:
# 生成 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 + 应用)
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 bootloaderbuild/app_photomagnetic/zephyr/zephyr.signed.bin— 签名后的应用固件(用于 mcumgr 上传)
6.2 首次烧录(需要调试器)
# 烧录 MCUboot + 应用(一次性)
west flash -d build
或手动烧录 merged.hex(MCUboot + 应用合并镜像)。
6.3 后续升级(通过 UART,不需要调试器)
# 主板发送升级命令后,切换到 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. 主板端配合
主板在发送"进入升级模式"命令后:
- 等待 200ms(小板复位时间)
- 切换到 mcumgr 客户端模式(波特率不变,仍是 115200)
- 使用 mcumgr 协议上传固件
- 等待新固件启动(小板上电发 ID 帧)
- 切回 0x7EE7 协议模式
9. 工作量评估
| 任务 | 工作量 |
|---|---|
| sysbuild.conf + mcuboot.conf + mcuboot.overlay | 配置文件,~30 分钟 |
| boot mode retention 设备树节点 | overlay 追加,~15 分钟 |
| com.cpp 新增升级命令回调 | ~20 行代码,~30 分钟 |
| 首次构建验证 MCUboot + 应用 | 调试+验证,~2 小时 |
| mcumgr CLI 升级流程端到端测试 | ~1 小时 |
| 主板端配合(切换模式逻辑) | 主板侧工作,单独评估 |
总计:应用层侧约半天工作量(含调试);主板侧需额外配合。
10. 注意事项
- SRAM retention:STM32G0B0 的 SRAM 复位后默认不清零(retained),boot mode flag 利用此特性;需确保 retention 地址不被
.bss清零覆盖 - MCUboot 大小:48KB 对 MCUboot 启用 RSA 签名偏紧,如溢出需调整 boot 分区(可能需要重划分 flash)
- 首次烧录:需要调试器烧录 MCUboot + 应用(一次性);后续升级全部通过 UART
- usart1 波特率:应用层协议 115200,MCUboot serial recovery 也是 115200,无需切换
- 降级保护:开发阶段建议关闭(
CONFIG_BOOT_DOWNGRADE_PREVENTION=n),方便回退;量产启用 - 签名密钥路径:sysbuild.conf 中的
SB_CONFIG_BOOT_SIGNATURE_KEY_FILE会自动传播到 MCUboot 和应用,无需在 mcuboot.conf 和 prj.conf 中重复配置