4. 常见Bootloader方案对比

中断,是嵌入式系统的灵魂。Bootloader 的灵魂,就是确保中断能跳对地方。

我见过太多项目,Bootloader 跑得挺好,一跳应用就死机。查到最后,全是向量表没配好。说白了,CPU 一发生中断,它得知道去哪找处理函数。这个「地址表」,就是向量表。

4.1 向量表是什么?为什么需要重映射?

ARM Cortex-M 系列上电后,默认从 0x00000000 取栈顶指针,从 0x00000004 取复位向量。这个位置,就是向量表的基地址。

但你的应用固件可能烧在 0x08010000(内部 Flash 偏移处)。中断来了,CPU 还是跑去 0x00000000 找表——那里只有 Bootloader 的向量,应用的中断处理函数根本不在那儿。结果就是:跑飞。

核心结论:应用固件必须把向量表基地址(VTOR)指向自己的起始地址,否则所有中断都会跑错地方。

4.2 向量表重映射的两种主流方案

我做过几十个 Bootloader 项目,总结下来就两条路:

方案 原理 适用场景 我踩过的坑
VTOR 寄存器重映射 修改 SCB->VTOR,指向应用起始地址 Cortex-M3/M4/M7/M33 等 忘记对齐 512 字节,直接 HardFault
Bootloader 中转子程序 在 Bootloader 向量表中保留中断入口,再跳转到应用 不支持 VTOR 的老内核(如 Cortex-M0) 中断延迟增加,调试时容易晕

我个人习惯:能用 VTOR 就别用中转。简单、直接、不容易出幺蛾子。

4.3 VTOR 重映射实战(以 STM32F4 为例)

假设你的应用固件起始地址是 0x08010000。Bootloader 跳转前,应用自己必须做这件事:

/* 应用启动代码(最早执行的几行) */
#define APPLICATION_ADDRESS  0x08010000
#define VECTOR_TABLE_ALIGN   512  /* 必须 512 字节对齐 */

void early_init(void)
{
    /* 1. 关闭全局中断,防止重映射过程中断嵌套 */
    __disable_irq();

    /* 2. 设置 VTOR 寄存器 */
    SCB->VTOR = APPLICATION_ADDRESS;

    /* 3. 读取新向量表中的栈顶指针,更新 MSP */
    __set_MSP(*(volatile uint32_t *)APPLICATION_ADDRESS);

    /* 4. 重新使能中断 */
    __enable_irq();
}

警告:VTOR 寄存器的值必须 512 字节对齐。这是 Cortex-M 的硬件要求。我曾经因为偏移设成 0x08010010,结果中断全部错乱,查了整整两天。

4.4 向量表对齐检查清单

每次做 Bootloader 跳转,我都会对着这张表过一遍:

  • 应用起始地址:必须是 512 的整数倍(0x08010000、0x08020000 等)
  • VTOR 写入时机:必须在任何中断使能之前完成
  • 栈顶指针:应用向量表前 4 字节必须是有效的栈顶地址
  • 中断屏蔽:重映射期间关中断,防止 VTOR 被意外访问
  • 双 Bank 启动:如果芯片支持双 Bank,注意 Bank 切换后 VTOR 也要跟着变

小技巧:我习惯在应用固件的链接脚本里加一个断言,检查向量表对齐。比如 GNU LD 的 ASSERT(ALIGN(512), "Vector table not 512-byte aligned");。编译期就能发现问题,不用等到跑起来。

4.5 向量表重映射流程图

下面这张图,是我给团队培训时必画的。它把整个流程串起来了:

向量表重映射完整流程 向量表重映射流程(VTOR 方案) 上电复位 Bootloader 启动 跳转到应用入口 关全局中断 设置 SCB->VTOR 更新 MSP 栈指针 开全局中断 ⚠ 关键检查点 • VTOR 必须 512 对齐 • 关中断期间不能有 任何中断请求 • MSP 值必须有效 💡 常见错误 • 忘记关中断 → 死机 • VTOR 写错地址 → HardFault • MSP 未更新 → 栈溢出

4.6 避坑指南:我曾经踩过的三个坑

做 Bootloader 这么多年,向量表这块我交过不少学费。挑三个最典型的说说:

  1. 坑一:VTOR 写对了,但中断还是跑飞

    我曾经在 STM32L4 上遇到这个问题。查到最后发现:应用固件里使能了中断,但中断处理函数地址没编译进向量表。说白了,中断服务函数写了,但没在启动文件里声明。编译器优化时直接把它优化掉了。

    解决办法:检查启动文件的 .isr_vector 段,确保所有用到的中断都有入口。

  2. 坑二:双 Bank 切换后 VTOR 失效

    有一次做 OTA 升级,芯片支持双 Bank。Bootloader 切到 Bank2 后,应用死活不响应中断。后来发现:Bank 切换后,Flash 的地址映射变了,但 VTOR 还是指向 Bank1 的地址。

    解决办法:应用启动时重新读取当前 Bank 的基地址,动态计算 VTOR。

  3. 坑三:中断优先级分组不一致

    这个坑比较隐蔽。Bootloader 设置了中断优先级分组为 4(4位抢占优先级),但应用里用的是分组 3(3位抢占 + 1位子优先级)。跳转后,中断优先级乱套了。

    解决办法:Bootloader 跳转前,把优先级分组恢复成默认值(或者与应用约定一致)。

我的经验法则:向量表重映射,不是「写了 VTOR 就完事」。它是一个完整的上下文切换过程——关中断、设 VTOR、更新 MSP、恢复中断上下文。少一步,系统就不稳定。

4.7 总结:向量表重映射的核心要点

  • VTOR 是 Cortex-M 的标配,能用就用,别搞复杂的中转子程序
  • 512 字节对齐是硬约束,编译期就要检查
  • 关中断 → 设 VTOR → 更新 MSP → 开中断,顺序不能乱
  • 双 Bank、优先级分组这些细节,往往才是真正坑人的地方