第四章 启动流程分析:MCU上电复位流程、中断向量表重映射、启动模式选择
大家好,我是老张。这一章咱们聊聊MCU启动流程。说实话,很多工程师写了几年代码,对启动流程还是一知半解。我见过不少同事,Bootloader跑不起来,最后发现是中断向量表没配对。嗯,这章咱们就把这事彻底讲透。
4.1 MCU上电复位流程
MCU上电后,到底发生了什么?我习惯把它分成三个阶段:
- 硬件复位阶段:电源稳定后,复位引脚拉高,内部复位信号释放
- 启动代码阶段:CPU从复位向量取址,执行启动代码
- 用户程序阶段:初始化完成后,跳转到main函数
你想想看,CPU上电第一件事是什么?它得知道自己从哪里开始跑代码。ARM Cortex-M系列的做法是:从0x00000000地址读取栈顶指针,从0x00000004地址读取复位向量。
核心要点:复位向量里存放的,就是启动代码的第一条指令地址。这个地址指向的代码,负责完成最基本的硬件初始化。
我在项目中遇到过一个问题:某次用国产MCU,上电后程序死活跑不起来。查了半天,发现是电源纹波太大,导致复位信号不稳定。后来加了复位芯片才解决。所以啊,硬件设计上,复位电路千万别省。
4.2 中断向量表重映射
中断向量表,说白了就是一张地址表。每个中断源对应一个表项,表项里放着中断服务函数的地址。默认情况下,这张表放在Flash的起始地址。
但Bootloader有个特殊需求:它需要把中断向量表搬到RAM里,或者搬到Flash的其他位置。为什么?因为Bootloader要升级应用程序,升级过程中Flash会被擦写,这时候中断向量表就不能放在原来的位置了。
具体怎么做?以Cortex-M3为例:
// 中断向量表重映射示例
#define SCB_VTOR (*(volatile uint32_t *)0xE000ED08)
// 将中断向量表重映射到RAM起始地址
SCB_VTOR = (uint32_t)&_ram_vector_table;
// 或者重映射到Flash的偏移地址
SCB_VTOR = APP_START_ADDRESS;
我的经验:重映射后,一定要确认新向量表已经正确初始化。我曾经犯过一个错:重映射后忘了把中断服务函数地址填进去,结果一进中断就跑飞。调试了整整一天才找到原因。
这里有个细节要注意:重映射的地址必须对齐到512字节或1024字节,具体看芯片手册。不对齐的话,CPU会忽略低几位地址,导致向量表错位。
4.3 启动模式选择
MCU一般支持三种启动模式:
| 启动模式 | 存储介质 | 典型应用 |
|---|---|---|
| Boot from Flash | 内部Flash | 正常启动,运行应用程序 |
| Boot from RAM | 内部SRAM | 调试模式,快速验证代码 |
| Boot from ROM | 内部Boot ROM | 出厂固件,安全启动 |
启动模式的选择,通常通过芯片的BOOT引脚电平来决定。比如STM32:
- BOOT0=0, BOOT1=x:从Flash启动
- BOOT0=1, BOOT1=0:从系统ROM启动
- BOOT0=1, BOOT1=1:从RAM启动
我个人习惯在产品设计时,把BOOT引脚引出到测试点。这样量产时可以通过治具切换启动模式,方便烧录和测试。
避坑指南:我曾经遇到一个案例,客户把BOOT引脚悬空了。结果芯片上电后随机进入不同启动模式,时好时坏。排查了半个月才发现是这个问题。所以BOOT引脚一定要接固定电平,不能悬空。
4.4 启动流程实战分析
咱们拿一个实际项目来说。假设我们要设计一个Bootloader,它需要从Flash启动,然后跳转到应用程序。完整的启动流程是这样的:
- 上电复位,CPU从0x00000000读取栈顶指针
- CPU从0x00000004读取复位向量,跳转到启动代码
- 启动代码初始化时钟、配置中断向量表
- 检查是否需要进入固件升级模式
- 如果需要升级,执行升级流程;否则跳转到应用程序
跳转应用程序的代码,我一般这么写:
typedef void (*app_func_t)(void);
void jump_to_app(uint32_t app_addr)
{
uint32_t stack_ptr;
app_func_t app_entry;
// 关闭全局中断
__disable_irq();
// 读取应用程序的栈顶指针和入口地址
stack_ptr = *(volatile uint32_t *)app_addr;
app_entry = (app_func_t)(*(volatile uint32_t *)(app_addr + 4));
// 设置主栈指针
__set_MSP(stack_ptr);
// 跳转到应用程序
app_entry();
}
关键点:跳转前一定要关闭所有中断,否则跳转过程中发生中断,CPU会去查旧的向量表,导致程序崩溃。跳转完成后,应用程序会重新初始化中断向量表并开启中断。
嗯,这里还要注意一点:跳转前要把外设恢复到复位状态。比如定时器、DMA这些,如果不复位,应用程序初始化时可能会出问题。我一般会在跳转前调用一个deinit_all_peripherals()函数。
4.5 总结与建议
启动流程这块,说白了就是搞清楚三件事:
- 从哪里开始:复位向量决定入口
- 中断表在哪:重映射保证中断正常
- 启动模式选哪个:BOOT引脚决定存储介质
我个人建议,做Bootloader开发时,先把启动流程画成流程图。每个步骤做什么、有什么依赖关系,一目了然。我带的团队,新人入职第一件事就是画启动流程图,画不清楚不准写代码。
下一章咱们聊Flash驱动与分区管理,这是Bootloader的核心内容。到时候我会分享一些实际项目中的分区策略,敬请期待。