升级链路概览

本地升级走的是标准三段式链路:

维护电脑 ──USB转RS485──▶ 现场485总线 ──USART3──▶ STM32

整个过程分为两个阶段:

  1. App 运行态:通过 Modbus RTU 发送维护命令,触发 Bootloader 进入
  2. 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 写入命令 0x0005G780S_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-25664 字符小写十六进制写入状态页供后续复核

数据包格式

支持两种包格式,根据文件大小自动选择:

格式包大小适用场景
SOH(0x01)128 字节小文件或最后一块
STX(0x02)1024 字节常规大文件传输

Bootloader 内部状态机

Bootloader 使用升级状态页持久化当前状态,共 7 个状态:

名称说明
0x0000IDLE空闲,等待 App 请求
0x0001REQUESTEDApp 已请求进入升级
0x0002ERASING正在擦除 App 区
0x0003PROGRAMMING正在写入固件
0x0004VERIFYING正在做最终校验
0x0005DONE升级完成,可跳 App
0x0006FAILED升级失败,停留 Bootloader

状态流转路径:

IDLE → REQUESTED → ERASING → PROGRAMMING → VERIFYING → DONE
                    ↓            ↓            ↓          ↓
                  FAILED  ←──────┴────────────┴──────────┘

每个状态都持久化到 Flash,即使中途掉电,重启后也能从上次状态继续。

错误码体系

Bootloader 定义了 14 种错误码,覆盖从传输到校验的完整链路:

错误码名称说明
0x0000BOOT_ERR_NONE无错误
0x0001BOOT_ERR_BAD_FRAMEYMODEM 会话异常或发送方主动中止
0x0003BOOT_ERR_BAD_LENGTH包长度非法
0x0004BOOT_ERR_BAD_STATE当前状态不允许写包
0x0005BOOT_ERR_BAD_SIZE镜像大小越界
0x0006BOOT_ERR_FLASH_ERASEFlash 擦除失败
0x0007BOOT_ERR_FLASH_PROGRAMFlash 编程失败
0x0008BOOT_ERR_BAD_OFFSET顺序写入偏移异常
0x0009BOOT_ERR_VERIFY通用验证失败兜底
0x000ABOOT_ERR_NOT_COMPLETE收包未完成就试图结束
0x000BBOOT_ERR_PACKET_CRCYMODEM 包 CRC16 错误或会话错误过多
0x000CBOOT_ERR_TIMEOUT_RECOVERY升级长时间卡住后自动恢复
0x000DBOOT_ERR_STATE_CRC升级状态页 CRC16 非法
0x000EBOOT_ERR_VERIFY_CRC32整包 CRC32 校验失败
0x000FBOOT_ERR_VERIFY_SHA256整包 SHA-256 校验失败
0x0010BOOT_ERR_VECTOR_INVALIDApp 向量表非法

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 导出