1. 课程导入

各位同学,咱们今天正式开讲。做嵌入式开发这么多年,我见过不少工程师把 Bootloader 当成一个「黑盒子」——会用,但真出了问题就抓瞎。其实这东西没那么玄乎,说白了它就是一段上电后最先跑起来的代码,负责把真正的应用程序请进内存。

我个人习惯把 Bootloader 比作「系统看门大爷」。它不干重活,但所有进出的程序都得经过它点头。你想想看,没有它,你的单片机一上电就直接跑应用代码,那以后想升级固件怎么办?拆机烧录?太原始了。

1.1 什么是 Bootloader

Bootloader 是系统上电后执行的第一段用户程序。它的生命周期很短——完成初始化、校验、跳转后就功成身退,把 CPU 控制权交给应用程序。

核心定义:Bootloader 是一段独立于应用程序的固件,驻留在 Flash 的起始区域,负责引导系统、验证固件完整性,并支持固件升级。

我在项目中遇到过最典型的场景:客户现场 200 多台设备需要升级协议栈。要是没有 Bootloader,你得一台台开壳接烧录器,三天都干不完。有了它,一条串口线就搞定。

特性 Bootloader 应用程序
存储位置 Flash 起始扇区(如 0x08000000) Flash 后续扇区(如 0x08020000)
生命周期 上电后短暂运行,完成后跳转 长期运行,执行业务逻辑
升级频率 几乎不变 按需升级
代码量 通常 2KB ~ 32KB 几十 KB 到几 MB

1.2 IAP 编程原理

IAP,全称 In-Application Programming,中文叫「在应用编程」。它跟 ISP(In-System Programming)最大的区别是:IAP 不需要外部烧录器,应用程序自己就能把自己给换了。

原理其实很简单。单片机内部 Flash 支持按扇区擦除和写入,Bootloader 通过串口、CAN、USB 或网络拿到新固件数据,然后调用 Flash 驱动把数据写到应用程序所在的区域。写完后复位,Bootloader 再校验新固件,没问题就跳转过去。

我的经验:IAP 编程最关键的三个步骤是「擦除 → 写入 → 校验」。擦除必须按扇区对齐,写入要留意地址偏移,校验推荐用 CRC32 或 MD5。我曾经遇到过写入时掉电导致固件半残的情况,从那以后我坚持「先校验再跳转」的原则。

IAP 的典型流程可以概括为:

  1. 接收固件:通过通信接口接收固件数据包
  2. 存储暂存:将数据暂存到 RAM 或临时 Flash 区域
  3. 擦除目标区:按扇区擦除应用程序所在的 Flash 区域
  4. 写入固件:将暂存数据写入目标地址
  5. 完整性校验:计算 CRC 或哈希值,与固件包中的校验值比对
  6. 跳转执行:校验通过后,修改向量表并跳转到应用程序入口

这里有个容易踩的坑——中断向量表重映射。应用程序编译时默认向量表在 0x08000000,但你的应用跑在 0x08020000,如果不重映射,中断一来 CPU 就跑错地方。我刚开始做 IAP 时就吃过这个亏,查了两天才发现是 SCB->VTOR 没设置。

注意:跳转前必须关闭所有外设中断、清除中断挂起位,否则跳转到应用程序后可能触发意外中断导致死机。我习惯在跳转前调用 __disable_irq(),并在应用程序入口处重新初始化中断系统。

1.3 系统启动流程分析

咱们以 STM32 为例,看看上电后到底发生了什么。这个过程我画了张图,你一看就明白。

系统启动流程与 Bootloader 跳转逻辑 系统启动流程与 Bootloader 跳转逻辑 上电复位 从 0x00000000 取 MSP 与复位向量 执行 Bootloader 有升级 请求? 是 接收固件 擦除并写入 Flash 校验固件完整性 否 跳转应用程序 无升级请求时直接跳转 有升级请求时先升级再跳转

这张图把整个流程串起来了。你注意看那个菱形判断——这是 Bootloader 的核心决策点。我一般用三种方式触发升级:检测 GPIO 电平、检查 Flash 中的升级标志位、或者通过通信接口收到特定命令。

具体到代码层面,跳转应用程序的 C 实现大概是这样的:

/* 跳转到应用程序 */
typedef void (*pFunction)(void);

void jump_to_app(uint32_t app_addr)
{
    uint32_t msp_value;
    pFunction app_entry;
    
    /* 1. 关闭全局中断 */
    __disable_irq();
    
    /* 2. 关闭所有外设时钟 */
    RCC->AHBENR = 0;
    RCC->APB1ENR = 0;
    RCC->APB2ENR = 0;
    
    /* 3. 清除所有中断挂起 */
    NVIC->ICER[0] = 0xFFFFFFFF;
    NVIC->ICPR[0] = 0xFFFFFFFF;
    
    /* 4. 从应用向量表取 MSP 和复位向量 */
    msp_value = *(volatile uint32_t*)app_addr;
    app_entry = (pFunction)(*(volatile uint32_t*)(app_addr + 4));
    
    /* 5. 设置主栈指针 */
    __set_MSP(msp_value);
    
    /* 6. 跳转 */
    app_entry();
}

避坑指南:我曾经在跳转前忘了关闭 Systick 中断,结果应用程序里 Systick 一触发就跑回 Bootloader 的中断向量表,直接 HardFault。所以跳转前务必把 Systick 也关了:SysTick->CTRL = 0;

1.4 本章小结

咱们这一章把 Bootloader 的底子打好了。你记住三件事:

  • Bootloader 是看门大爷,不干重活但管着进出
  • IAP 的核心是擦、写、校验,缺一不可
  • 跳转前要清场——关中断、清挂起、重映射向量表

这些概念后面每一章都会用到。尤其是中断向量表重映射和跳转前的清理工作,我建议你现在就动手写个小 Demo 试试,踩过坑才能真正记住。