2. 嵌入式系统启动流程:从复位到用户Bootloader
大家好,我是你们的讲师。今天我们来聊聊嵌入式系统上电后,芯片内部到底发生了什么。很多初学者觉得启动流程很神秘,其实说白了,就是芯片从“睡醒”到“干活”的几步固定动作。
我个人习惯把启动流程拆成四个阶段:CPU复位向量 → ROM代码与Flash映射 → 第一阶段启动(ROM Bootloader) → 第二阶段启动(用户Bootloader)。下面我们一个一个来看。
2.1 CPU复位向量:芯片的第一口“奶”
芯片一上电,CPU会立刻跑到一个固定的地址去取指令。这个地址,就是复位向量(Reset Vector)。
为什么是固定的?因为CPU设计时就定死了。比如ARM Cortex-M系列,复位向量通常在0x00000000或0x08000000。CPU不看别的,上来就从这个地址读数据。
核心要点:复位向量里存的不是代码,而是栈指针(MSP)和复位中断服务函数地址。CPU先拿栈指针,再跳转到复位函数执行。
我在项目中遇到过一个问题:客户说芯片上电后不跑,查了半天,发现是复位向量表被意外擦除了。嗯,从那以后我每次烧录完都会先读一遍向量表确认。
2.2 ROM代码与Flash映射:谁在哪个地址
芯片内部通常有两块存储:ROM(只读存储器)和Flash(可擦写存储器)。它们怎么映射到CPU的地址空间,决定了启动顺序。
你想想看,ROM是出厂固化的,掉电不丢,但改不了。Flash是用户程序存放的地方。芯片厂商会把一段ROM Bootloader固化在ROM里,这段代码负责最基础的初始化。
| 存储区域 | 特点 | 启动阶段 |
|---|---|---|
| ROM(BootROM) | 只读、出厂固化、不可修改 | 第一阶段启动 |
| Flash(主存储) | 可擦写、存放用户程序 | 第二阶段启动 |
| SRAM | 易失性、运行时代码/数据 | 运行时使用 |
映射关系呢?通常芯片上电后,Flash会被映射到地址0x00000000,这样CPU就能从Flash里取复位向量。但有些芯片会先映射ROM,等ROM代码执行完再切换。
避坑指南:我曾经调试一块STM32F4的板子,发现上电后总是进HardFault。后来发现是Flash映射配置错了,芯片实际从ROM启动,但ROM里没有用户代码。解决办法是检查BOOT0/BOOT1引脚电平。
2.3 第一阶段启动:ROM Bootloader
ROM Bootloader是芯片出厂自带的“最小系统”。它干的事不多,但很关键:
- 初始化CPU时钟、堆栈、基本外设(如UART、USB)
- 检查启动条件(比如检测BOOT引脚、校验Flash头部)
- 从指定介质加载用户Bootloader或应用程序
说白了,ROM Bootloader就是个搬运工。它自己不跑用户逻辑,只负责把Flash里的代码搬到RAM里,或者直接跳转到Flash执行。
我记得有一次做车规级项目,芯片的ROM Bootloader支持从CAN总线升级固件。当时我写了个上位机,通过CAN发送固件包,ROM Bootloader接收后写入Flash。嗯,这个功能在产线烧录时特别有用。
注意:ROM Bootloader通常不支持断点续传和校验重试。如果传输过程中断电,芯片可能变砖。所以量产时一定要有备份Bootloader或恢复机制。
2.4 第二阶段启动:用户Bootloader
用户Bootloader是开发者自己写的,放在Flash里。它比ROM Bootloader灵活得多,可以支持:
- 多种升级协议(UART、SPI、I2C、以太网、USB)
- 固件加密、签名校验
- 双备份启动(A/B分区)
- 自定义跳转逻辑
用户Bootloader的启动流程大致如下:
- 初始化硬件(时钟、外设)
- 检查是否有升级请求(比如检测按键、串口数据)
- 如果有,进入升级模式;如果没有,跳转到应用程序
- 跳转前要关闭中断、重置外设、设置向量表偏移
下面是一个典型的跳转代码示例:
/* 用户Bootloader跳转到应用程序 */
void jump_to_app(uint32_t app_addr)
{
uint32_t msp = *(uint32_t *)app_addr; // 取栈指针
uint32_t reset_handler = *(uint32_t *)(app_addr + 4); // 取复位函数地址
// 关闭全局中断
__disable_irq();
// 重置所有外设(重要!)
HAL_RCC_DeInit();
HAL_DeInit();
// 设置主栈指针
__set_MSP(msp);
// 跳转
void (*app_entry)(void) = (void (*)(void))reset_handler;
app_entry();
}
关键点:跳转前一定要关闭中断并重置外设。否则应用程序初始化时可能因为外设状态不对而出错。我曾经没关SysTick中断,跳转后直接进HardFault,查了两天才发现。
2.5 整体启动流程图
下面我用一张SVG图把整个流程串起来,方便你理解。
从图中可以看到,整个流程是串行的:上电 → 读向量 → ROM Bootloader → 用户Bootloader → 应用程序。任何一步出问题,系统都起不来。
2.6 常见问题与避坑
- 复位向量表被破坏:烧录时一定要保护向量表区域,或者使用双备份。
- 跳转前未关闭中断:会导致应用程序初始化时触发意外中断。
- Flash映射配置错误:检查BOOT引脚电平,确保芯片从正确地址启动。
- ROM Bootloader不支持校验:量产时建议在用户Bootloader里增加CRC校验。
我的经验:做Bootloader开发时,我习惯在Flash末尾留一个状态标志位。每次启动先读这个标志,判断是正常启动还是升级后首次启动。这样能避免重复升级或误跳转。
好了,这一章的内容就到这里。启动流程是Bootloader的基础,理解了它,后面讲烧录协议和分区管理时你会觉得轻松很多。