4、启动流程基础:复位向量、启动模式、BootROM原理

好,咱们今天聊聊嵌入式系统启动的第一步。说白了,就是芯片上电后,到底发生了什么?

我刚开始做嵌入式那会儿,总觉得启动是个黑盒子。按一下复位键,程序就跑起来了。但后来踩过几次坑才发现——不理解启动流程,你连芯片为什么起不来都查不出原因。

4.1 复位向量:芯片的第一口气

芯片上电后,CPU 做的第一件事是什么?

答案是:去一个固定的地址,取第一条指令。

这个固定地址,就叫复位向量(Reset Vector)。

不同的架构,复位向量的位置不一样:

架构 复位向量地址 说明
ARM Cortex-M 0x00000000(向量表基址) 存放栈指针和复位处理函数地址
ARM Cortex-A 0x00000000 或 0xFFFF0000 可配置,由 SCTLR.V 位决定
RISC-V 0x00000000 或 0x80000000 由具体芯片设计决定
x86 0xFFFFFFF0 靠近 4GB 地址空间顶端

嗯,这里要注意:复位向量存的不是代码,而是地址。CPU 拿到这个地址后,再跳过去执行真正的启动代码。

核心要点:复位向量是 CPU 的「出生证明」。它告诉 CPU:「你该从哪里开始活。」

我在项目中遇到过一件事:有次换了一颗国产 MCU,程序死活跑不起来。查了半天,发现是链接脚本里把中断向量表放错了位置。复位向量指向了一个空地址,CPU 直接跑飞了。嗯,从那以后我每次建新工程,第一件事就是确认向量表地址对不对。

4.2 启动模式:芯片的「人格分裂」

同一个芯片,可以从不同的地方启动。这就是启动模式(Boot Mode)。

你想想看,芯片厂商为什么要搞这么多启动模式?

原因很简单:灵活

  • 开发阶段:从 Flash 启动,方便调试
  • 量产阶段:从内部 Flash 启动,成本低
  • 升级阶段:从 UART 或 USB 启动,方便烧录
  • 特殊场景:从 SRAM 启动,追求极速

常见的启动模式有这些:

启动模式 启动介质 典型应用
Flash 启动 内部/外部 NOR Flash 正常运行时
NAND 启动 NAND Flash 大容量存储场景
UART 启动 串口 工厂烧录、调试
USB 启动 USB 接口 批量烧录
SRAM 启动 内部 SRAM 快速启动、调试

启动模式通常由芯片的BOOT 引脚决定。比如 STM32,通过 BOOT0 和 BOOT1 两个引脚的电平组合,就能选择三种启动模式。

个人建议:设计电路板时,一定要把 BOOT 引脚引出到跳线或按键。我见过太多板子,BOOT 引脚直接焊死,结果想升级固件只能吹下来重烧——那叫一个痛苦。

4.3 BootROM:芯片里的「接生婆」

BootROM 是什么?

说白了,就是芯片出厂时固化在 ROM 里的一段小程序。它负责在芯片上电后,完成最基本的初始化工作。

BootROM 通常做这几件事:

  1. 初始化硬件:设置时钟、配置内存控制器、初始化堆栈
  2. 检测启动模式:读取 BOOT 引脚电平,决定从哪启动
  3. 加载启动代码:从启动介质中读取用户程序到 RAM
  4. 跳转到用户程序:把控制权交给用户代码

我举个例子,ARM Cortex-M 的启动流程大致是这样的:

// 伪代码:BootROM 启动流程
void BootROM_Main(void) {
    // 1. 初始化硬件
    Init_SystemClock();      // 设置系统时钟
    Init_StackPointer();     // 从向量表加载栈指针
    
    // 2. 检测启动模式
    uint8_t boot_mode = Read_BootPins();
    
    // 3. 根据模式加载代码
    switch(boot_mode) {
        case BOOT_FROM_FLASH:
            Load_From_Flash(USER_CODE_ADDR, RAM_ADDR);
            break;
        case BOOT_FROM_UART:
            Load_From_UART(RAM_ADDR);
            break;
        case BOOT_FROM_USB:
            Load_From_USB(RAM_ADDR);
            break;
    }
    
    // 4. 跳转到用户程序
    Jump_To_UserCode(RAM_ADDR);
}

注意,BootROM 是只读的。你改不了它。所以 BootROM 里的代码必须足够稳定、足够通用。

我曾经踩过的坑:有次做量产烧录,发现某些芯片通过 UART 启动时总是超时。查了三天,最后发现是 BootROM 里的串口波特率是 115200,而我们的烧录工具默认配的是 9600。BootROM 不会自适应波特率,所以一直握手失败。嗯,从那以后我每次换芯片,第一件事就是仔细看 BootROM 的文档。

4.4 启动流程全景图

把上面三个概念串起来,一个典型的嵌入式系统启动流程是这样的:

  1. 上电复位:CPU 从复位向量地址取第一条指令
  2. 执行 BootROM:完成硬件初始化,检测启动模式
  3. 加载用户程序:从 Flash/UART/USB 等介质读取代码到 RAM
  4. 跳转执行:CPU 跳转到用户程序入口,开始运行
  5. 用户程序初始化:设置中断向量表、初始化外设、进入 main()

整个过程,从复位到 main(),通常只需要几毫秒到几十毫秒。但就是这几毫秒,决定了你的系统能不能正常跑起来。

一句话总结:复位向量是起点,启动模式是路径,BootROM 是向导。三者配合,才能让芯片从「死物」变成「活系统」。

我个人习惯,在设计一个新系统时,会先画一张启动流程图。把复位向量地址、BOOT 引脚配置、BootROM 行为都标清楚。这样后面调试起来,心里有底。

好,这一章就到这里。下一章咱们聊聊中断向量表——它和复位向量是什么关系?为什么说中断向量表是嵌入式系统的「神经中枢」?到时候见。