diff --git a/CMakeLists.txt b/CMakeLists.txt index 980e8cc..b76818b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,4 +23,8 @@ else() src/watdog.cpp src/com.cpp ) + + zephyr_sources_ifdef(CONFIG_APP_DFU + src/dfu.cpp + ) endif() diff --git a/Kconfig b/Kconfig index 721f3ef..3ec892a 100644 --- a/Kconfig +++ b/Kconfig @@ -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" diff --git a/doc/升级操作指南.md b/doc/升级操作指南.md new file mode 100644 index 0000000..46b82e0 --- /dev/null +++ b/doc/升级操作指南.md @@ -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('> 1) ^ 0xA001 if crc & 1 else crc >> 1 + cmd += struct.pack(' +``` + +### 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 回滚,设备不会变砖 diff --git a/doc/升级更改说明.md b/doc/升级更改说明.md new file mode 100644 index 0000000..e7bd217 --- /dev/null +++ b/doc/升级更改说明.md @@ -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 + +步骤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 # [新建] 本文档 +``` diff --git a/doc/固件升级方案.md b/doc/固件升级方案.md new file mode 100644 index 0000000..c4cf561 --- /dev/null +++ b/doc/固件升级方案.md @@ -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 + 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 中重复配置 diff --git a/include/com.hpp b/include/com.hpp index 0037b0a..f69af24 100644 --- a/include/com.hpp +++ b/include/com.hpp @@ -10,6 +10,9 @@ #include #include #include +#ifdef CONFIG_APP_DFU +#include +#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 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)); diff --git a/prj.conf b/prj.conf index 9dcc75b..934bb5b 100644 --- a/prj.conf +++ b/prj.conf @@ -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 diff --git a/src/dfu.cpp b/src/dfu.cpp new file mode 100644 index 0000000..001a14f --- /dev/null +++ b/src/dfu.cpp @@ -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 +#include + +static auto DfuInit() -> int { + printk("[dfu] init: upgrade command enabled (0xFF)\n"); + return 0; +} + +SYS_INIT(DfuInit, APPLICATION, 51); diff --git a/src/led.cpp b/src/led.cpp index 2b9180c..fee4edd 100644 --- a/src/led.cpp +++ b/src/led.cpp @@ -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); diff --git a/sysbuild.conf b/sysbuild.conf new file mode 100644 index 0000000..ddfe93d --- /dev/null +++ b/sysbuild.conf @@ -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" diff --git a/sysbuild/mcuboot.conf b/sysbuild/mcuboot.conf new file mode 100644 index 0000000..6571ec8 --- /dev/null +++ b/sysbuild/mcuboot.conf @@ -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 diff --git a/sysbuild/mcuboot.overlay b/sysbuild/mcuboot.overlay new file mode 100644 index 0000000..2f4b06f --- /dev/null +++ b/sysbuild/mcuboot.overlay @@ -0,0 +1,2 @@ +/* MCUboot serial recovery 使用 zephyr,uart-mcumgr (已在板级 dts 指定为 &usart1) + * zephyr,console 保持 &usart3 (调试口),两者不能指向同一设备 */ diff --git a/west.yml b/west.yml index a8bc7a2..7a93025 100644 --- a/west.yml +++ b/west.yml @@ -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