4、启动流程分析:芯片上电启动流程、中断向量表重映射、Bootloader与App的跳转

好,咱们今天聊聊芯片上电后到底发生了什么。很多新手觉得Bootloader就是跑个跳转指令,其实没那么简单。我当年第一次做Bootloader时,就被中断向量表重映射坑过——程序跑飞了三天,最后发现是向量表地址没对齐。嗯,咱们一步步来。

4.1 芯片上电启动流程

芯片一上电,CPU会去一个固定地址取第一条指令。这个地址通常是0x00000000或者0x08000000,具体看芯片。以STM32为例,它从0x08000000开始执行,因为Flash映射到了那里。

启动流程大致分三步:

  1. 硬件初始化:复位后,CPU从0x00000004取出复位向量,也就是Reset_Handler的地址。然后跳过去执行。
  2. 系统初始化:在Reset_Handler里,会调用SystemInit(),配置时钟、PLL、Flash等待周期等。这一步很重要,时钟配错了后面全白搭。
  3. C环境初始化:清零BSS段、拷贝数据段、初始化堆栈指针。最后跳转到main()。

我见过有人把SystemInit()里的时钟配置改错了,结果芯片跑在8MHz内部振荡器上,还以为Bootloader性能差。其实说白了,启动流程就是给C语言搭好舞台。

核心要点:启动文件(startup_xxx.s)里定义了中断向量表,以及Reset_Handler的实现。不要随意修改启动文件,除非你清楚每个寄存器的含义。

4.2 中断向量表重映射

中断向量表,说白了就是一个地址表。每个中断源对应一个入口地址。芯片上电时,默认向量表在Flash起始位置。但Bootloader和App是两个独立的程序,它们各自有自己的中断向量表。

问题来了:当App运行时,如果发生中断,CPU会去Flash起始位置找向量表。但那里是Bootloader的向量表啊!App的中断根本响应不了。

解决办法就是中断向量表重映射。把向量表的基地址改成App所在的地址。以STM32为例,在App的main()最前面加上:

/* 重映射向量表到App起始地址 */
SCB->VTOR = APP_BASE_ADDRESS;

这里APP_BASE_ADDRESS是App在Flash中的偏移地址,比如0x08010000。注意,这个地址必须对齐到512字节边界。我当初就是没对齐,结果中断响应时好时坏,查了两天才发现。

避坑指南:我曾经在STM32F103上做过一个项目,App的向量表地址设成了0x08010004,结果中断一触发就死机。后来查手册才发现,VTOR寄存器的低9位是保留的,必须清零。所以地址要写成0x08010000,而不是0x08010004。

有些芯片支持双向量表,比如NXP的LPC系列。它可以在运行时切换向量表,不需要修改VTOR。但大多数ARM Cortex-M芯片还是用VTOR重映射的方式。

4.3 Bootloader与App的跳转

Bootloader做完升级校验后,就要跳转到App了。跳转不是简单的函数调用,因为Bootloader和App是两个独立的程序,它们的链接地址不同。

正确的跳转姿势是这样的:

/* 定义函数指针类型 */
typedef void (*pFunction)(void);

/* 获取App的复位向量地址 */
uint32_t app_stack = *(volatile uint32_t*)APP_BASE_ADDRESS;
uint32_t app_reset = *(volatile uint32_t*)(APP_BASE_ADDRESS + 4);

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

/* 设置主堆栈指针 */
__set_MSP(app_stack);

/* 跳转到App的复位向量 */
pFunction jump_to_app = (pFunction)app_reset;
jump_to_app();

这里有几个关键点:

  • 先关中断:跳转过程中如果有中断进来,向量表还没切换,程序就乱了。
  • 设置堆栈指针:App有自己的堆栈空间,必须用App的栈顶地址。
  • 跳转到复位向量:不是直接跳main(),而是跳Reset_Handler。这样App会重新初始化硬件和C环境。

你想想看,如果直接跳main(),App里的全局变量初始化代码就没执行,变量值全是乱的。我见过有人这么干,结果App跑起来各种诡异现象。

个人经验:我习惯在跳转前把Bootloader用到的外设都复位一遍。比如关闭定时器、DMA、USART等。否则App初始化时可能会遇到意外状态。尤其是DMA,如果还在传输,App一启动就触发中断,容易死循环。

4.4 跳转后的注意事项

App启动后,Bootloader就彻底退出了。但有些资源需要清理干净:

资源类型 建议操作 原因
外设时钟 关闭Bootloader用到的外设时钟 降低功耗,避免冲突
中断 确保所有中断已关闭 防止跳转瞬间中断触发
堆栈 使用App的堆栈指针 Bootloader的堆栈空间可能不够
全局变量 让App重新初始化 Bootloader的变量值对App无意义

嗯,这里要注意:有些芯片在跳转后,外设寄存器还保留着Bootloader设置的值。App如果没重新配置,可能会出问题。我建议在App的SystemInit()里把所有外设时钟都复位一遍,再重新配置。

最后总结一下:启动流程就是芯片从复位到运行main()的过程;中断向量表重映射是让App能正确响应中断的关键;跳转时要关中断、设栈指针、跳复位向量。这三步做好了,Bootloader和App就能和平共处。

核心口诀:上电先跑启动文件,向量表要重映射,跳转之前关中断,栈指针对齐再跳。