问题现象
用户报告了一个诡异的现象:在 Bootloader 模式下刷固件时:
- 刷
App_B.bin:成功,设备正常进入 App_B - 刷
App_A.bin:上位机显示”升级传输完成”,但设备实际没有进入 App,仍停留在 Bootloader
同时,上位机界面的”当前运行槽位”和”固件”两项都显示为”未知”。
日志回放
以下是一次刷 App_A.bin 失败的真实日志(关键片段):
[18:26:43] 准备升级,模式 本地升级。
[18:26:43] 警告: 未读取到当前运行槽位,将按所选镜像继续升级。
[18:26:43] 所选镜像槽:A。
[18:26:43] 镜像复位向量:0x08008145。
[18:26:43] 固件: App_A.bin
[18:26:43] 固件大小: 26952 bytes (0x00006948)
[18:26:44] 打开串口 COM5,波特率 115200。
[18:26:44] TX 解锁: 0A060030A55A73D5
[18:26:49] 警告: 解锁 未在 5 秒内收到回包,继续尝试后续流程。
[18:26:49] TX 进入 Bootloader: 0A0600310005197D
[18:26:54] 警告: 进入 Bootloader 未在 5 秒内收到回包,继续尝试后续流程。
[18:26:56] 等待 Bootloader YMODEM 握手 ('C')...
[18:26:57] 已收到 Bootloader 握手字符 'C'。
[18:26:57] YMODEM 头包已确认。
...(传输过程正常,100% 完成)...
[18:27:01] 最终空包已确认。
[18:27:01] 升级传输完成,设备应完成校验并自动复位回 App。
[18:27:01] 流程结束。
从日志看,YMODEM 传输 100% 完成,头包、数据包、EOT、最终空包全部确认。但设备没有复位进入 App。
定位分析
分析一:为什么运行槽位显示”未知”
上位机通过 Modbus 读取寄存器 0x005A(REG_DIAG_RUNNING_SLOT)获取当前运行槽位。该寄存器由 App 层的 G780s Modbus 从站提供:
// G780s.c — App 运行态
case REG_DIAG_RUNNING_SLOT: // 0x005A
return Upgrade_GetRunningSlot();
Upgrade_GetRunningSlot() 根据 SCB->VTOR 判断当前运行在 A 槽还是 B 槽。
但当设备进入 Bootloader 后,USART3 不再运行 App 的 Modbus 从站逻辑,而是被 Bootloader 接管用于 YMODEM 收发。此时上位机去读 0x005A 不会得到正常回包,因此显示为”未读取”或”未知”。
这不是 Bug,而是 Bootloader 态的预期行为——设备不在 App 中,自然无法响应 App 层的 Modbus 查询。
分析二:为什么 App_A 传输成功但设备不启动
这是真正的 Bug 所在。问题出在 Bootloader 和上位机对”目标槽位”的理解不一致。
上位机侧:YMODEM 头包中传递的信息是:
- 文件名
- 文件大小
- CRC32
target_fw_version- SHA256
没有”目标槽位 A/B”这个字段。 上位机虽然允许用户选择 App_A.bin 或 App_B.bin,但这个选择只影响”推荐哪个镜像文件”,不会通过 YMODEM 协议传给 Bootloader。
Bootloader 侧:在 BootFlash_BeginImage() 中,BootFlash_SelectDownloadSlot() 自行决定写入目标:
// boot_flash.c
runtime->transfer_slot = BootFlash_SelectDownloadSlot(runtime);
选择策略是:
- 读取
boot_control.confirmed_slot和boot_control.active_slot - 找到当前认为”稳定可运行”的槽位
- 选择另一个槽位作为下载目标
也就是说,Bootloader 的槽位选择与上位机选择了哪个镜像完全无关。
根因链条
假设当前 Bootloader 认为”稳定槽”为 A,则下载目标固定为 B:
上位机选择 App_A.bin → Bootloader 写入 B 槽 → 向量表校验失败 → 停留 Bootloader
上位机选择 App_B.bin → Bootloader 写入 B 槽 → 向量表校验通过 → 进入 App_B
App_A.bin 的复位向量位于 A 槽地址范围(如 0x08008145),但实际被写入了 B 槽。Bootloader 最终校验时,按”B 槽镜像”去校验向量表合法性:
// boot_verify.c
uint8_t Upgrade_IsAppVectorValid(uint32_t app_base_addr)
{
uint32_t app_reset = *(__IO uint32_t *)(app_base_addr + 4);
// 复位向量必须指向 App 区域内
if (app_reset < app_base_addr ||
app_reset >= (app_base_addr + UPGRADE_APP_MAX_SIZE))
return 0u;
return 1u;
}
App_A.bin 的 ResetHandler 地址在 A 槽范围,不在 B 槽范围,因此校验失败,Bootloader 不放行,设备继续停留。
代码对应关系
上位机侧
| 文件 | 关键逻辑 |
|---|---|
LocalUpgradeViewModel.cs | 准备升级、读取运行槽位、输出日志 |
RunningSlotProtocol.cs | 读 0x005A 寄存器,Bootloader 态失败 |
YModemProtocol.cs | 发送 YMODEM 头包/数据包,不传目标槽位 |
设备 App 侧
| 文件 | 关键逻辑 |
|---|---|
G780s.c | App 态提供 0x005A 运行槽位寄存器 |
Upgrade.c | Upgrade_GetRunningSlot() 根据 VTOR 判断 |
Bootloader 侧
| 文件 | 关键逻辑 |
|---|---|
boot_flash.c | BootFlash_SelectDownloadSlot() 决定写入槽位 |
boot_main.c | 升级完成后最终校验和跳转 |
boot_verify.c | CRC32/SHA256/向量表放行校验 |
ymodem.c | 解析 YMODEM 头包,不解析目标槽位 |
最终结论
- “运行槽位显示未知”是 Bootloader 态的预期行为,设备不在 App 中自然无法响应
- 真正的问题是上位机和 Bootloader 对”目标槽位”的理解不一致
- 上位机允许用户选择
App_A.bin/App_B.bin,但这个选择不影响 Bootloader 的写入目标 - Bootloader 自行决定写入哪个槽位,与镜像文件名无关
- 当 Bootloader 决定写入 B 槽时,发送
App_B.bin可以通过校验,发送App_A.bin会因向量表不匹配而失败
处理状态
本次仅完成根因定位与分析,未修改任何代码。后续修复方案待定,可选方向包括:
- 方案 A:在 YMODEM 头包中增加目标槽位字段,让 Bootloader 按上位机指令写入
- 方案 B:在 Bootloader 校验阶段,允许向量表落在任一槽位范围(放宽校验,但风险增加)
- 方案 C:上位机在 Bootloader 模式下先通过其他方式获取 Bootloader 的下载目标槽位,再自动匹配对应镜像文件