forked from EmbeddedTeam/app_photomagnetic
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
342 lines
8.8 KiB
Markdown
342 lines
8.8 KiB
Markdown
# 光磁头固件升级操作指南
|
|
|
|
## 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 回滚,设备不会变砖
|