STM32_BOOT未引出_串口升级系统Bootloader方案

0 阅读8分钟

STM32 BOOT 未引出场景下的串口升级方案

解决"BOOT0/BOOT1 引脚未引出、但需要通过串口升级固件"这类问题的技术方案。 以 STM32F407 + USART1 + HAL 库为实例,已实测验证可行。


1. 适用场景

  • BOOT0(甚至 BOOT1)引脚未引出到板边,无法通过硬件拨码/跳线进入系统 bootloader
  • 需要现场通过串口(或 USB DFU 等)升级固件;
  • 希望在应用运行中通过一条指令/命令触发升级;
  • 应用里启用了独立看门狗(IWDG),需要兼顾升级过程不被看门狗打断。

方案思路:软件跳转 + 标志 + 复位 + 上电早期跳转,不依赖 BOOT0 引脚。


2. 背景与原理

2.1 什么是系统 bootloader(System Bootloader)

STM32 出厂时在芯片内部固化了一段 ROM 引导程序(System Bootloader),存放在 System Memory(系统存储器) 中。它不占用用户的 Flash,可以擦写整个 Flash(包括用户自己的 bootloader 区)。

2.2 BOOT 引脚与启动模式,以及为什么能软件跳转

复位时,硬件根据 BOOT0 / BOOT1 的组合决定从哪块存储启动:

BOOT1BOOT0启动模式启动空间
x0主 Flash0x08000000(别名到 0x00000000)
01System Memory(系统 bootloader)0x1FFF0000
11内嵌 SRAM0x20000000

注:BOOT0 是物理引脚;BOOT1 的来源因系列而异——STM32F1 是独立引脚(PB2),STM32F4 则由选项字节 nBOOT1 位决定(不再占用引脚)。

BOOT0 / BOOT1 只在复位瞬间被采样。运行中的程序可以直接跳转到 System Memory 的执行入口,等效于复位后从系统 bootloader 启动,从而绕开 BOOT0 引脚。这一点在 AN2606 中明确说明。

2.3 System Memory 地址(关键)

不同系列地址不同,务必以 AN2606 为准。常见速查:

系列System Memory 基址备注
STM32F00x1FFFEC00 / 0x1FFFD800 等分具体型号
STM32F10x1FFFF000F10x
STM32F2 / F40x1FFF0000本文所用
STM32F30x1FFFD800
STM32F70x1FF00000
STM32G0 / G40x1FFF0000
STM32L0 / L10x1FF00000
STM32L40x1FFF0000
STM32H70x1FF09800注意:入口 ≠ 基址 0x1FFF0000

2.4 串口协议要点(AN3155)

  • 数据帧:8 数据位 + 偶校验(even parity) + 1 停止位;
  • 自动波特率检测:bootloader 用 SysTick 测量首个 0x7F 同步字节的帧长来推算波特率;
  • 波特率范围 1200 ~ 115200,115200 是上限(最敏感),9600 更稳定
  • 实际波特率偏差需 < 2.5%;
  • 握手机制:host 发 0x7F → bootloader 回 0x79(ACK);命令字节 + 其按位取反(XOR 校验),错误回 0x1F(NACK)。

3. 方案设计

3.1 为什么不能"直接在运行中跳转"

在运行中的应用里直接跳转会有三个问题:

  1. IWDG 一旦启动,软件无法关闭(只能靠复位停止)。跳转后 bootloader 运行期间没人喂狗,约数秒后(取决于超时配置)芯片被复位回主 Flash,升级被打断。
  2. 跳转前外设状态不干净:DMA、中断、PLL、USART 等都还在运行,bootloader 期望的是复位后的默认状态,脏状态可能导致自动波特率检测异常。
  3. 跳转前时钟、Flash 延迟等配置残留,可能影响 bootloader 内部的 SysTick 波特率测量。

3.2 正确流程:标志 + 复位 + 早期跳转

mermaid diagram

跳转必须发生在 HAL_Init() / SystemClock_Config() / MX_IWDG_Init() / 任何外设初始化之前

3.3 为什么用 RTC 备份寄存器存标志

标志必须在软件复位后依然存在,否则复位后无法得知要进入 bootloader。可选方案对比:

方案跨软复位保持实现难度说明
普通全局变量❌ 复位清零简单不可用
no-init RAM 变量较复杂需在散文件/linker 单独声明 no-init 段,不同工具链(Keil/GCC)配置各异,易出错
RTC 备份寄存器最简单硬件原生支持,无需改 linker
内部 Flash 固定位置复杂需擦写、速度慢、有磨损寿命

选 RTC 备份寄存器(RTC_BKP_DRx)的理由:

  1. 跨软复位保持:备份寄存器位于独立备份域,NVIC_SystemReset() 这类软复位不会清零,正好满足"跨复位传递标志"的需求;
  2. 掉电才丢失、窗口极短:只要 VDD 供电(或 VBAT 接电池)就保持,仅 VDD 掉电且无 VBAT 时才丢失。而本方案流程是"设标志 → 立即复位 → 检测到即清除",窗口极短,不影响正常使用;
  3. 零 linker 改动:读写只需三步(使能 PWR 时钟 → 解除备份域写保护 HAL_PWR_EnableBkUpAccess() → 读写寄存器),不用动散文件/链接脚本,最省事、也不容易出错。

注:STM32F407 有 20 个备份寄存器(RTC_BKP_DR0 ~ RTC_BKP_DR19),任选一个即可(本例用 BKP0R),注意别与工程里已有的 RTC 用途冲突。

3.4 完整交互时序

mermaid diagram


4. 核心实现(完整可复制)

4.1 头文件

void jump_to_system_bootloader(void);
void request_system_bootloader(void);
uint8_t bootloader_requested(void);
void clear_bootloader_request(void);

4.2 实现

#include "stm32f4xx_hal.h"

#define BOOTLOADER_MAGIC  0x5AA5C33C   /* 任意唯一魔数 */

/**
 * 跳转到系统 bootloader。注意:此函数不会返回。
 */
void jump_to_system_bootloader(void)
{
    void (*sysmem_boot_jump)(void);
    volatile uint32_t addr = 0x1FFF0000;   /* F407 System Memory 基址 */

    /* 1. 时钟回到复位默认态(HSI) */
    HAL_RCC_DeInit();

    /* 2. 关闭 SysTick */
    SysTick->CTRL = 0;
    SysTick->LOAD = 0;
    SysTick->VAL  = 0;

    /* 3. 关闭全局中断 */
    __disable_irq();

    /* 4. 将 System Memory 重映射到 0x0000 0000,保证向量表正确 */
    __HAL_RCC_SYSCFG_CLK_ENABLE();
    SYSCFG->MEMRMP = 0x01;

    /* 5. 关键顺序:先取跳转地址,再设 MSP(设 MSP 后不能再访问栈) */
    sysmem_boot_jump = (void (*)(void))(*(volatile uint32_t *)(addr + 4));
    __set_MSP(*(volatile uint32_t *)addr);

    /* 6. 跳转 */
    sysmem_boot_jump();

    /* 不应到达 */
    while (1);
}

/**
 * 请求进入系统 bootloader:写备份寄存器标志 + 软件复位。
 * 供运行中的命令(AT 指令、按键、上位机指令等)调用。
 */
void request_system_bootloader(void)
{
    __HAL_RCC_PWR_CLK_ENABLE();
    HAL_PWR_EnableBkUpAccess();
    RTC->BKP0R = BOOTLOADER_MAGIC;
    HAL_NVIC_SystemReset();
}

/**
 * 复位后检查是否有进入 bootloader 的请求(备份寄存器跨软复位保持)。
 */
uint8_t bootloader_requested(void)
{
    __HAL_RCC_PWR_CLK_ENABLE();
    HAL_PWR_EnableBkUpAccess();
    return (RTC->BKP0R == BOOTLOADER_MAGIC);
}

/**
 * 清除请求标志。
 */
void clear_bootloader_request(void)
{
    __HAL_RCC_PWR_CLK_ENABLE();
    HAL_PWR_EnableBkUpAccess();
    RTC->BKP0R = 0;
}

4.3 入口检查 main.c(放在 main() 第一条)

int main(void)
{
    /* 复位后优先检查是否请求进入系统 bootloader */
    if (bootloader_requested())
    {
        clear_bootloader_request();
        jump_to_system_bootloader();   /* 不会返回 */
    }

    /* …… 之后的正常初始化:HAL_Init / SystemClock_Config / MX_IWDG_Init / ... …… */
}

5. 踩坑记录

坑 1:__set_MSP() 的顺序(会导致跳转后 HardFault)

错误写法(先设 MSP、后取跳转地址):

__set_MSP(*(volatile uint32_t *)addr);            /* 先切栈 */
sysmem_boot_jump = ...(addr + 4);                 /* 再读 addr(volatile,从栈读)→ 垃圾值 */

__set_MSP() 会切换栈指针,之后不能再访问任何局部变量addr 是 volatile,编译器每次从栈上重读,切栈后读到的是垃圾,跳到错误地址直接 HardFault。

正确写法:先取跳转地址,最后再设 MSP。

坑 2:IWDG 复位打断升级

跳转后 bootloader 运行期间无人喂狗,IWDG 超时复位回主 Flash。表现为:连接能握手、能读到 Chip ID,但后续命令 timeout/NACK 交替。

解决:用"标志 + 复位 + 早期跳转",让跳转发生在 MX_IWDG_Init() 之前(复位已停掉 IWDG)。

坑 3:自动波特率敏感,GETID 被 NACK

0x7F 同步能过(Activating OK),但 GETID 命令 NACK/timeout 交替。原因:

  • 115200 接近 bootloader 波特率上限,量化误差大;
  • 部分 USB-TTL 适配器(尤其 CH340 类)晶振精度不足。

解决

  • 降波特率到 9600 测试;
  • 换 PL2303GT / FT232 等更可靠的适配器。

坑 4:跳转前状态不干净

在运行中直接跳转(外设/DMA/中断/PLL 未复位)会导致 bootloader 行为异常。ST 官方建议"复位后、初始化前跳转"(即本方案)。


6. 移植到其他 STM32 系列

  1. 改地址:替换 jump_to_system_bootloader() 中的 addr(见 2.3 表,以 AN2606 为准);
  2. 改引脚:确认目标系列系统 bootloader 使用的 USART 引脚(F407 的 USART1 为 PA9/PA10;H7/F7 等需查 AN2606 对应表格);
  3. 改重映射寄存器
    • F4:SYSCFG->MEMRMP
    • F0:SYSCFG->CFGR1
    • 其他系列查对应参考手册的 SYSCFG/内存重映射章节;
  4. 标志存储:若目标芯片无 RTC 备份寄存器,可改用 no-init RAM 变量(需在 linker/散文件里单独声明 no-init 段),或用内部 Flash 固定位置。

7. 参考资料