# 光磁头在线固件升级方案 > 方案: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 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 #include 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 kRxTable[] = { {static_cast(Cmd::kTemp), OnTempQuery}, {static_cast(Cmd::kId), OnGetId}, {static_cast(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 # 复位触发 swap mcumgr --conntype=serial --connstring='dev=/dev/ttyUSBx,baud=115200' \ reset # 确认新固件(可选,test 模式未确认会自动回滚) mcumgr --conntype=serial --connstring='dev=/dev/ttyUSBx,baud=115200' \ image confirm ``` --- ## 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 中重复配置