升级链路概览
本地升级走的是标准三段式链路:
维护电脑 ──USB转RS485──▶ 现场485总线 ──USART3──▶ STM32
整个过程分为两个阶段:
- App 运行态:通过 Modbus RTU 发送维护命令,触发 Bootloader 进入
- Bootloader 态:通过 YMODEM-C 协议接收固件镜像,校验后写入 Flash
两个阶段使用同一物理串口(USART3),但协议完全不同——App 态跑 Modbus RTU,Bootloader 态跑 YMODEM。
第一阶段:App 触发进入 Bootloader
设备在 App 运行态时,通过 Modbus RTU 从站(地址 0x0A)发送维护命令。
解锁
先向寄存器 0x0030 写入解锁口令 0xA55A,获得 30 秒维护窗口:
0A 06 00 30 A5 5A 73 D5
进入 Bootloader
向寄存器 0x0031 写入命令 0x0005(G780S_CMD_ENTER_BOOT_UPGRADE):
0A 06 00 31 00 05 19 7D
App 收到后执行以下操作:
// 写入升级请求状态
Upgrade_RequestBootMode(UPGRADE_SOURCE_LOCAL);
// 软件复位
NVIC_SystemReset();
复位后 Bootloader 接管 USART3,设备不再响应 Modbus 维护寄存器。
第二阶段:Bootloader 通信参数
| 参数 | 值 |
|---|---|
| 物理串口 | USART3 |
| 485 方向控制 | PA5 |
| 波特率 | 115200, 8N1 |
| 传输协议 | YMODEM-C |
Bootloader 进入升级态后,会周期性发送 C(0x43),表示请求使用 CRC16 的 YMODEM 会话。维护电脑收到 C 后开始发送头包和数据包。
YMODEM 协议适配
工程里的 YMODEM 不是原样照搬 ST IAP 示例,而是做了项目内适配:
- 去掉了
flash_if / menu / common / UartHandle等示例工程依赖 - 改为由 Bootloader 提供串口收发回调和 Flash 写入回调
- 保留 YMODEM 的
SOH / STX / EOT / ACK / NAK / C交互方式 - 保留每包
CRC16-CCITT / XMODEM校验
回调结构体定义:
const YmodemReceiveConfig config = {
.read_byte = BootProtocol_YmodemReadByte, // 逐字节读取
.write_bytes = BootProtocol_YmodemWriteBytes, // RS-485 方向切换 + 发送
.on_start = BootProtocol_YmodemStart, // 头包回调 → 擦除 Flash
.on_data = BootProtocol_YmodemData, // 数据包回调 → 写入 Flash
};
头包(Packet #0)元数据
YMODEM 头包里使用文件名和文件信息字段传递元数据:
文件名:App.bin
文件信息:<image_size> 0x<crc32> <target_fw_version> <sha256_hex>
示例:
123456 0x89ABCDEF 0 e1f6011e54799d1fd4ba165ba0d612c2c2c26b809600266df06d48690f92f5c5
四个字段含义:
| 字段 | 格式 | 说明 |
|---|---|---|
| 镜像字节数 | 十进制 | 用于校验文件完整性 |
| 整包 CRC32 | 十六进制 | 与最终校验值比对 |
| 目标固件版本 | 十六进制 | 本地升级默认填 0 |
| 整包 SHA-256 | 64 字符小写十六进制 | 写入状态页供后续复核 |
数据包格式
支持两种包格式,根据文件大小自动选择:
| 格式 | 包大小 | 适用场景 |
|---|---|---|
| SOH(0x01) | 128 字节 | 小文件或最后一块 |
| STX(0x02) | 1024 字节 | 常规大文件传输 |
Bootloader 内部状态机
Bootloader 使用升级状态页持久化当前状态,共 7 个状态:
| 值 | 名称 | 说明 |
|---|---|---|
0x0000 | IDLE | 空闲,等待 App 请求 |
0x0001 | REQUESTED | App 已请求进入升级 |
0x0002 | ERASING | 正在擦除 App 区 |
0x0003 | PROGRAMMING | 正在写入固件 |
0x0004 | VERIFYING | 正在做最终校验 |
0x0005 | DONE | 升级完成,可跳 App |
0x0006 | FAILED | 升级失败,停留 Bootloader |
状态流转路径:
IDLE → REQUESTED → ERASING → PROGRAMMING → VERIFYING → DONE
↓ ↓ ↓ ↓
FAILED ←──────┴────────────┴──────────┘
每个状态都持久化到 Flash,即使中途掉电,重启后也能从上次状态继续。
错误码体系
Bootloader 定义了 14 种错误码,覆盖从传输到校验的完整链路:
| 错误码 | 名称 | 说明 |
|---|---|---|
0x0000 | BOOT_ERR_NONE | 无错误 |
0x0001 | BOOT_ERR_BAD_FRAME | YMODEM 会话异常或发送方主动中止 |
0x0003 | BOOT_ERR_BAD_LENGTH | 包长度非法 |
0x0004 | BOOT_ERR_BAD_STATE | 当前状态不允许写包 |
0x0005 | BOOT_ERR_BAD_SIZE | 镜像大小越界 |
0x0006 | BOOT_ERR_FLASH_ERASE | Flash 擦除失败 |
0x0007 | BOOT_ERR_FLASH_PROGRAM | Flash 编程失败 |
0x0008 | BOOT_ERR_BAD_OFFSET | 顺序写入偏移异常 |
0x0009 | BOOT_ERR_VERIFY | 通用验证失败兜底 |
0x000A | BOOT_ERR_NOT_COMPLETE | 收包未完成就试图结束 |
0x000B | BOOT_ERR_PACKET_CRC | YMODEM 包 CRC16 错误或会话错误过多 |
0x000C | BOOT_ERR_TIMEOUT_RECOVERY | 升级长时间卡住后自动恢复 |
0x000D | BOOT_ERR_STATE_CRC | 升级状态页 CRC16 非法 |
0x000E | BOOT_ERR_VERIFY_CRC32 | 整包 CRC32 校验失败 |
0x000F | BOOT_ERR_VERIFY_SHA256 | 整包 SHA-256 校验失败 |
0x0010 | BOOT_ERR_VECTOR_INVALID | App 向量表非法 |
Python 升级脚本
PC 侧使用 stm32_local_upgrade.py 脚本完成升级,内部自动处理:
- 计算整包 CRC32 和 SHA-256
- 将元数据写入 YMODEM 头包
- 自动选择 128 或 1024 字节数据包
- 等待 Bootloader 握手
C字符
依赖安装:
python -m pip install pyserial
典型用法:
python stm32_local_upgrade.py run --port COM12 --timeout 5 --verbose
带目标版本号:
python stm32_local_upgrade.py run --port COM12 --target-fw-version 0x010200 --timeout 5
保护策略
升级过程中只擦写 App 区,参数区和诊断区不参与擦除。中途掉电后设备停留在 Bootloader,允许重新完整发送。若连续多次上电仍卡在升级进行中,且当前 App 仍有效,则自动标记为超时失败并回 App。
排障指南
| 现象 | 排查方向 |
|---|---|
PC 一直等不到 C | 检查设备是否真的进入 Bootloader(复位后观察串口) |
头包发出后被双 CA 中止 | 镜像大小是否超出 App 区 |
| 传输完成但设备不跳转 App | 检查向量表是否正确、链接地址是否为 0x08008000 |
| 数据发到末尾失败 | App.bin 是否由正确的 target 导出 |