02 Bootloader 升级与写入笔记

项目定位

本页用于集中整理 Bootloader、APP 分区、bin 写入、跳转、备份回滚和后续 OTA 扩展。当前资料来源包括三份工程:

工程原始来源Wiki 原始资料目录作用
GD BootloaderD:\Users\Desktop\GD_BT\GD_BLraw/projects/gd32-bootloader-gd-blGD32F470 Bootloader,负责启动、升级判断、APP 备份、跳转
GD APPD:\Users\Desktop\GD_BT\GD_APPraw/projects/gd32-bootloader-gd-appGD32F470 APP 侧升级触发,写下载区并复位进入 BL
ARM BootloaderD:\Users\Desktop\Low_enegy-bootloader\ARM_blraw/projects/stm32-arm-bootloader自编 STM32G431 Bootloader,重点验证串口写入、256 字节包、跳转和后续 OTA 思路

当前优先目标:把“bin 如何写入 Flash”整理清楚。OTA、SD 卡、外部 Flash、CAN、BLE/WiFi 等方式先作为扩展方案记录,后续逐个补实现。

当前 Flash 分区

GD32F470 方案

来自 GD_BL/APP/Bootloader/bootloader_port_config.hGD_APP/APP/Bootloader/bootloader.c

区域地址大小用途
Bootloader0x0800000048 KB上电入口,升级管理与跳转
参数区0x0800C0004 KBBootParam、备份参数、升级日志、用户配置、校准数据
APP 主区0x0800D00076 KB当前运行应用
APP 备份区0x08020000128 KB保存上一版 APP,用于升级失败回滚
下载区0x0807300052 KBAPP 侧先写入新固件,复位后由 BL 搬运/校验
Flash 页4 KB-擦除单位

STM32G431 方案

来自 ARM_bl/MyApp/bootloader.hbootloader.cbootloader_save.c

区域地址用途
Bootloader0x08000000上电入口
APP0x08010000应用程序向量表和代码区
接收缓冲BL_RX_BUF_SIZE = 1024UART DMA 接收缓冲
固件包DOWNLOAD_LEN = 256当前串口写入的固定包长
Flash 基址0x08000000计算页号

已有写入方式一:STM32 直接串口写 Flash

位置:raw/projects/stm32-arm-bootloader/MyApp/bootloader.c

流程:

ERASE
  -> 擦除 APP 区非空页
WRITE
  -> s_bl_write_enable = 1
  -> HAL_FLASH_Unlock()
PC 发送固件二进制数据
  -> HAL_UARTEx_RxEventCallback 收到一段写一段
  -> 按 8 字节 doubleword 写 Flash
  -> 如果 Size < 256,认为是最后一包并停止写入
JUMP
  -> 检查 APP MSP
  -> 清外设/中断/SysTick
  -> SCB->VTOR = APP_JUMP_ADDR
  -> __set_MSP(app_msp)
  -> __enable_irq()
  -> 跳转 APP Reset_Handler

特点:

  • 协议简单,适合最小验证。
  • 不需要提前声明总大小。
  • 最后一包依赖 Size < 256 判断结束。
  • 风险是串口 IDLE 分包不一定等于 PC 端发送包边界,后续需要加帧头、长度、序号、CRC 或使用 ringbuffer 方案。

已有写入方式二:STM32 WRITE:<size> + 256 字节包

位置:raw/projects/stm32-arm-bootloader/MyApp/bootloader_save.c

流程:

WRITE:<firmware_size>
  -> 记录 g_bl_total_size
  -> g_bl_written_size = 0
  -> g_bl_write_addr = 0x08010000
  -> 进入 BL_WRITE_READY / BL_WRITE_WRITING
PC 按 256 字节发送 bin
  -> UART DMA 收到数据后写入 ringbuffer
  -> bootloader_proc 周期检查 ringbuffer.itemCount
  -> 满 256 字节就读取 256 字节
  -> bootloader_write_256bytes(data)
  -> 内部按 64 字节块 / 8 字节 doubleword 写 Flash
  -> 不足 8 字节尾包补 0xFF 对齐
写入达到总大小
  -> HAL_FLASH_Lock()
  -> BL_WRITE_DONE

特点:

  • 这是当前更适合扩展的写入框架。
  • WRITE:<size> 明确总长度,避免用串口分包大小判断结束。
  • 256 字节是传输层包长,Flash 实际仍按 8 字节 doubleword 写入。
  • 后续可以在 256 字节包上增加 seqcrc16/crc32、ACK/NACK 和重传。

已有写入方式三:GD APP 写下载区,复位交给 BL 升级

位置:raw/projects/gd32-bootloader-gd-app/APP/Bootloader/bootloader.c

函数:bootloader_write_and_reset(const uint8_t *bin, uint32_t bin_len, uint32_t app_crc32, uint32_t app_version)

流程:

APP 收到或准备好新 bin
  -> 读取 BootParam
  -> 计算当前 APP CRC,保存为备份 CRC
  -> 擦除下载区 0x08073000
  -> 将 bin 写入下载区
  -> 设置 updateFlag = 0x5A
  -> 设置 updateStatus = 0x01
  -> 写入 appSize / appCRC32 / appVersion
  -> 保存 BootParam
  -> 软件复位
Bootloader 启动
  -> 读取参数区
  -> 检查更新标志
  -> 校验下载区
  -> 备份当前 APP
  -> 将下载区搬运到 APP 主区
  -> 跳转 APP 或失败回滚

特点:

  • APP 负责“接收新固件”和“写下载区”。
  • Bootloader 负责“最终替换 APP”和“回滚保护”。
  • 这比直接在 Bootloader 中边收边写 APP 更安全,适合 OTA。
  • 当前 updateMode = 0x01,可以后续扩展为 UART、SD、CAN、BLE/WiFi、外部 Flash 等模式。

GD Bootloader 关键机制

位置:raw/projects/gd32-bootloader-gd-bl/APP/Bootloader/bootloader.c

核心点:

  • 启动时读取 BOOT_CONFIG_ADDR = 0x0800C000 的参数区。
  • BootParamBootParam_Reserved 记录当前 APP 与备份 APP 状态。
  • APP 版本不一致时执行 Backup_App()
  • Backup_App() 对 APP 主区计算 CRC32,再擦除备份区并复制 APP。
  • 跳转前检查 APP 栈地址和入口地址,再设置 SCB->VTOR__set_MSP() 并调用 Reset_Handler。
  • 启动失败时累加 bootFailCount,可以作为后续回滚条件。

写入方式扩展清单

后续所有写入方式最终都应收敛到同一个内部接口:

source -> receive buffer -> packet verify -> write download area -> verify full image -> set BootParam -> reset -> BL upgrade

建议优先级:

方式适用场景建议落点状态
UART 256B 包有线调试、最小闭环STM32 bootloader_save.c,GD 可复用协议已有雏形
UART 帧协议可靠串口升级在 256B 包外加帧头、长度、序号、CRC、ACK/NACK待补充
YMODEM/XMODEM通用串口工具传 binBootloader 或 APP 接收端待补充
SD 卡 / FatFS现场插卡升级GD 工程已有 FatFS/SD 组件,可从 /firmware/app.bin 读取待补充
外部 SPI Flash大固件缓存、断点续传APP 先写外部 Flash,BL 再搬运到内部 Flash待补充
CAN 升级多节点车控/工业设备按节点 ID 分发,包长适配 CAN/CAN-FD待补充
BLE/WiFi OTA无线升级APP 下载 bin 到下载区或外部 Flash,BL 只做校验与替换待补充
双 APP A/B高可靠升级固定 A/B 两个应用槽,BootParam 记录 active slot待补充

统一 256 字节包建议

当前 STM32 已经用 256 字节包作为写入单元。建议后续统一成下面的逻辑,而不是直接裸发 256 字节:

PacketHeader
  magic      2B  固定头,例如 0xA55A
  version    1B  协议版本
  type       1B  DATA / START / END / ABORT
  seq        4B  包序号
  offset     4B  写入偏移
  length     2B  数据长度,最大 256
  crc16      2B  当前包校验
payload      0~256B

接收端策略:

  • START:声明固件总大小、整包 CRC32、目标地址、版本号。
  • DATA:按 seq/offset 写入下载区,不直接覆盖正在运行的 APP 主区。
  • END:计算整包 CRC32,通过后设置 BootParam.updateFlag
  • ABORT:退出写入状态,擦除未完成下载区或标记无效。
  • 任意包错误:返回 NACK,PC 端重发当前包。

OTA 扩展方向

OTA 不建议让 Bootloader 直接处理复杂网络协议。推荐分层:

APP 网络层
  -> 下载 app.bin
  -> 校验长度和 CRC
  -> 写入下载区或外部 Flash
  -> 写 BootParam(updateFlag/updateMode/appSize/appCRC32/appVersion)
  -> 软件复位
Bootloader
  -> 只做可信的 Flash 搬运、CRC 校验、备份和回滚

原因:

  • Bootloader 越小越安全,网络协议、TLS、断点续传放在 APP 更容易维护。
  • Bootloader 不依赖复杂外设栈,减少升级失败概率。
  • APP 可以支持多种来源,Bootloader 只关心“下载区是否有一个完整可信的 bin”。

待补充任务

  • 给 STM32 256B 写入协议补 START/DATA/END/ACK/NACK 帧格式。
  • 给 GD 下载区写入补一页“APP 下载 bin 后如何设置 BootParam”。
  • 补充 SD 卡读取 /firmware/app.bin 写下载区流程。
  • 补充 OTA 方案:APP 下载、断点续传、整包 CRC、失败回滚。
  • 统一 BootParam 字段说明:updateFlagupdateModeupdateStatusappCRC32backupCRC32
  • 给跳转流程补检查清单:MSP、Reset_Handler、VTOR、NVIC、SysTick、DMA、__enable_irq()