MCU上电启动流程

BROM(一级引导加载程序)

基本概念

MCU的BROM(Boot ROM,启动只读存储器)代码,是指芯片制造过程中被烧录的,固化在MCU芯片内部的ROM中(独立于Flash)的一段代码,无法被用户更改

MCU最先运行的就是这段代码,它负责设备上电后最底层的初始化工作,判断启动模式、并加载后续的引导程序Bootloader

BROM作用

1. 外设初始配置:BROM通常会做一些最基本的硬件初始化,比如时钟设置、存储器控制器配置等。

2. 启动引导(Boot strapping):MCU上电或复位时,BROM会被第一个执行,判断用来决定MCU的启动方式(比如从外部Flash、内部ROM、或者通过串口、USB等下载程序)。

判断条件:通常是检测Strapping pin管脚,决定启动模式

Strapping pin:用于在上电或复位时配置芯片启动行为或工作模式的引脚

例如:

ESP32的Strapping pin引脚是GPIO0、GPIO2

STM32的相关引脚就是BOOT0、BOOT1

通过检测这两个引脚的电平,来判断如何启动

3. 安全机制:BROM可以集成安全启动(Secure Boot)、加密认证等逻辑,确保只有经过认证的固件(bootloader等)能够运行。

BROM典型执行流程

ESP32

  1. 上电或复位,MCU执行BROM代码

    • 芯片上电或复位后,MCU自动跳转(程序计数器PC指向)到BROM(Boot ROM)起始地址,执行固化在ROM中的引导代码。
  2. 最小硬件初始化

    • 配置最基本的时钟源和供电电路,初始化必要的内置外设(如UART)。
    • 保证能读取启动配置(Strapping)并支持后续数据通信。
  3. 读取Strapping引脚状态(启动配置)

    • 检查关键Strapping引脚(如GPIO0、GPIO2等)状态,判断当前芯片应进入哪种启动模式。

    • 常见启动模式有:

      • SPI Flash启动(正常工作模式)
      • UART下载(烧录)模式(通常GPIO0拉低)
      • 其他特殊模式(如SD卡启动,具体依芯片型号而定)
  4. 启动模式分支执行

    • 选择为SPI Flash启动(正常启动):

      • 根据Flash引脚配置,初始化SPI接口。
      • 从SPI Flash的固定地址读取Bootloader头部信息,校验格式与有效性。
      • 若启用安全启动,则校验Bootloader签名(可选)。
      • 校验通过后,将Bootloader从SPI Flash加载到RAM,并跳转到Bootloader入口地址
      • Bootloader继续运行,进一步初始化、读取分区表,找到并校验用户App(主程序)的位置,最后跳转到用户App入口地址,开始用户应用的执行。
    • 选择为UART下载模式

      • 初始化UART,发送握手字符,如不断发送‘C’(XMODEM协议的握手信号。

      BROM使用串口文件传输协议 (如UART的XMODEM1KXMODEM协议 ),用于和上位机(如电脑上的烧录工具)进行数据收发。

      • 等待PC端工具(如esptool.py)连接,与其交互,使用XMODEM1K协议完成烧录(App、Bootloader、分区表等)等命令。
      • 按照协议完成Flash操作,烧录结束后可复位重启进入正常启动。
    • 其他模式:

      • 若支持SD卡、HSPI等启动方式,按需求初始化相关硬件并加载固件。
  5. 异常与回退处理(如有)

    • 如果Flash启动失败、固件校验出错,或用户请求安全擦除等,BROM可回退至UART下载模式,允许用户重新烧录修复。

STM32

  1. 上电或复位,MCU执行BROM代码

    • MCU上电或复位后,MCU自动跳转到BROM(System Memory)起始地址,执行固化在ROM中的引导代码。
  2. 最小硬件初始化

    • 设置堆栈指针。
    • 初始化系统时钟为内部默认振荡器(如HSI)。
    • 关闭大部分外设,仅为后续判断和通信保留必要硬件支持。
  3. 读取启动配置(BOOT引脚)

    • 读取BOOT0和BOOT1引脚状态。
    • 根据引脚状态决定启动模式:
      • 主Flash启动(通常进入用户应用或用户Bootloader)
      • System Memory 启动(进入BROM内置系统Bootloader,支持烧录)
      • SRAM启动(调试/测试模式,部分型号支持)
  4. 启动模式分支执行

    • 主Flash启动(正常启动)

      • 跳转到主Flash首地址(通常是0x08000000),执行用户自定义Bootloader(如果Bootloader被省略,直接执行用户App)。
      • 若存在Bootloader,Bootloader负责进一步初始化和启动用户App(主程序)。
    • System Memory 启动(系统Bootloader/烧录模式)

      • 初始化串口/I2C/USB等通信外设(具体支持接口依型号而异)。
      • 等待上位机(如STM32CubeProgrammer、Flash Loader Demo等)发送烧录/下载命令。
      • 根据接收到的命令,实现固件烧录、擦除、校验等。
    • SRAM启动

      (部分型号支持):

      • 跳转到SRAM首地址,通常用于调试或特殊应用。
  5. 错误/异常处理

    • 若启动模式配置错误、Flash无效或校验失败,部分型号会停留在BROM,等待外部干预。

BootLoader(二级引导加载程序)

基本概念

Bootloader(引导加载程序)是指在MCU启动过程中,在BROM之后、用户应用(App)之前执行的一段程序,它通常存储在MCU的Flash中,由开发者或芯片厂家提供,可升级。

当一级引导程序(BROM)校验并加载完二级引导程序后,它会从二进制镜像的头部找到二级引导程序(bootloader)的入口点,并跳转过去运行

Bootloader是MCU系统启动的中间桥梁,连接BROM用户App,承担安全、升级、初始化等关键任务。

BootLoader作用

  1. 系统初始化

    • 完成比BROM更复杂的硬件初始化(如外设、分区表、时钟、存储等),为后续应用运行做准备。
  2. 固件升级/下载

    • 支持通过UART、USB、CAN、网口、OTA、SD卡等多种方式下载/升级主固件,实现远程或本地维护。
  3. 固件校验与安全启动

    • 校验主固件完整性(如CRC、Hash、签名等),防止固件损坏或恶意替换,支持加密和安全启动。
  4. 多分区/多固件管理

    • 通过分区表管理多套固件,实现A/B升级、回滚、出厂恢复等功能。
  5. 异常处理与恢复

    • 检测主固件异常(如校验失败、启动失败等),可自动回滚、恢复或进入维护模式。
  6. 引导用户应用

    • 校验通过后,将控制权跳转(设置PC)到主应用(App)入口地址,正式启动用户业务。

BootLoader典型流程

1. Bootloader自身启动(由BROM/硬件跳转)

  • 主要内容

    • 上电或复位后,BROM执行完毕后,跳转到Bootloader执行,接管系统控制权。
    • Bootloader通常位于Flash的固定地址(如ESP32的0x1000,STM32的0x08000000)。

2. 初始化时钟、存储、外设等

  • 主要内容

    • 配置系统时钟到正常运行频率(如PLL倍频,切换到外部晶振)。
    • 初始化存储器(如外部SPI Flash、片上Flash、SRAM等)。
    • 配置必要的外设(如UART、CAN、USB、WiFi、以太网等),为后续升级、通信等做准备。
    • 初始化看门狗、GPIO、调试接口等系统资源。

3. 读取并解析分区表或固件头

  • 主要内容

    • 从Flash的指定位置读取分区表或固件头部信息。
    • 解析出每个分区(如App、OTA、参数区、数据区、NVS等)的起始地址、大小和用途,这样BootLoader才能正确找到并加载要启动的用户App。
    • 检查分区表或固件头的有效性(如magic number、校验和等)。
    • 支持多固件、多数据分区,为OTA升级和数据管理打基础。

4. 判断是否进入固件升级/下载模式(如按键、命令、定时、固件异常等)

  • 主要内容

    • 检查是否有升级请求(如检测升级按键、串口命令、网络指令、定时器触发、上次异常标志等)。
    • 判断是否进入固件下载、烧录或维护模式。
    • 支持多种升级方式(如本地串口、USB、SD卡、远程OTA等)。
    • 若进入升级模式,初始化对应通信外设,准备接收新固件。

5. 如果需要升级,等待或接收外部新固件,烧录到指定分区,并校验完整性。校验完成后进行复位重启

  • 主要内容

    • 通过通信接口接收新固件(如串口、WiFi、以太网、USB等)。

    • 按分区表将固件数据写入指定分区(如app1、upgrade等区域)。

    • 对接收到的固件数据进行校验(如长度、CRC、签名等)。

    • 烧录完成后再次校验固件完整性,确保升级过程正确无误。

    • 切换分区标志/准备启动新固件,复位重启

6. 选择合适的主固件(App)分区,校验签名/Hash等

  • 主要内容

    • 根据分区表和系统当前状态(如成功升级、上次启动成功分区、回滚标志等)选择要启动的主固件分区。
    • 读取目标固件头部信息,校验固件签名、Hash、CRC等,确保固件安全和完整。
    • 支持多分区(如A/B分区结构)、升级回滚等机制,提升系统可靠性。

7. 校验通过后,跳转到主固件入口(用户App)

  • 主要内容

    • 设置主固件的入口地址(如中断向量表、Reset_Handler等)。
    • 关闭或重置Bootloader期间用到的外设,释放系统资源。
    • 跳转到主固件入口,正式启动用户应用(App)。
  • 若失败,则根据策略回滚、恢复、报警或停留在Bootloader

    • 主要内容:
      • 如果固件校验失败或主固件启动异常,根据回滚策略自动切换到上一个可用固件(回滚)。
      • 若回滚也失败,则进入恢复模式、报警(如LED闪烁、蜂鸣器等),或停留在Bootloader等待用户操作。
      • 通过多样化的异常处理,防止异常固件导致系统“变砖”,提升产品的可维护性和安全性。

BROM和BootLoader区别

存放位置区别

BROM:存放在MCU内部的一块ROM区域

  • 这是芯片出厂时烧录固化,用户无法更改或擦写

  • 它和MCU的内核、外设等一样,是芯片设计时就“刻”进硅片里的,通常在芯片内部的一个固定物理地址空间(如0x00000000或芯片手册指定的ROM区)。

Bootloader:一般存放在Flash当中

  • 具体的存放地址取决于芯片架构和启动配置:
    • 常见MCU(如STM32):Bootloader通常放在用户Flash的起始地址(如0x08000000),也可以自定义位置,只要BROM跳转时能找到。
    • ESP32/W800等带外部Flash的芯片:Bootloader通常放在外部SPI Flash(如ESP32的0x1000,W800的0x2000)。
  • Bootloader是可以被擦写、升级、替换的,一般由开发者或厂家烧录进去。

以下是常见MCU的存储区域划分参考即可:

ESP32

image-20250527143830914

STM32:

image-20250527143929732

下载/烧录的区别

BROM的下载烧录:主要就是我们平时将代码写好后,使用烧录工具烧录芯片,就是BROM的下载模式,写入固件(分区表、Bootloader、App)到MCU的flash

表现为:

  • 你通常需要按下“BOOT”键或短接某些引脚(如ESP32的EN+IO0),
  • MCU进入“下载/烧录模式”,
  • 烧录工具就能识别并开始烧写——这整个过程都是由BROM实现的。

Bootloader的下载/升级:Bootloader的下载/升级功能主要用于产品应用阶段的FOTA/OTA(Firmware Over The Air)升级、本地U盘/SD卡升级等“固件维护”场景。

它是设备已经有Bootloader和App之后,系统自己实现的升级方案(比如远程下载新固件、热切换等),而不是空芯片下载烧录

小结

  • 开发阶段用烧录工具刷写固件,就是用的BROM的下载/烧录功能
  • Bootloader的下载/升级一般是产品运行起来后,做OTA升级、本地维护时才用到。

分区表机制

基本概念

分区表就是把MCU上的Flash(闪存)分块管理的一种机制。可以把Flash(芯片里的存储空间)看作一大块硬盘。上面要存很多不同内容:程序代码、配置信息、升级数据、用户数据等。如果大家都“乱放”,就容易互相覆盖、数据混乱。

所以就有了分区表的概念,将每个内容的存储地址固定下来,可以看作存储空间的使用说明书

分区表中的每个条目都包括以下几个部分:

  • Name(名字):比如app、nvs、bootloader等,代表分区用途。
  • Offset(起始地址):从Flash哪个地址开始。
  • Size(大小):这块分区有多大。
  • Flag:目前没用,通常填0。

所有这些信息都写在一张表里,叫“分区表”,芯片启动时会加载它。

W800 分区表的默认偏移地址(默认存放)为 0xE000,分区表的大小为 0x1000(一个sector,4k)。

这个默认偏移地址就是在Flash基地址下进行一个偏移的操作,如基地址为0x08000000,实际地址就是0x08000000+0xE0000x0800E000

注意:

分区表的 Offset 和 size 需要 4K 对齐。请确保在配置分区表时,每个分区的 Offset 和 size 都是 4K 的整数倍。

存放分区表

  1. 固件烧录/生产阶段
  • 首次烧录时,和BootloaderApp等一起,通过烧录工具(如esptool.py、STM32CubeProgrammer、厂家自带烧录脚本等)将分区表写入Flash的指定位置(分区表规定的Flash的位置)。

这通常是出厂生产或初次开发板刷机时的标准步骤。

一般烧录命令会自动包含分区表(如ESP32的--partition-table参数,W800的wm.py flash脚本)。

  1. 固件升级/OTA阶段(很少见)
  • 某些平台允许通过OTA升级时连同新固件一起下发新的分区表(比如分区方案有变更、增加新分区等)。

  • 这种情况较少见,且有兼容性和安全风险——因为分区表变更可能导致旧数据丢失或启动失败,所以一般需要特殊处理和校验。

  1. 用户自定义分区表时
  • 如果你选择自定义分区表(如ESP-IDF/W800的custom partition table),则会在编译时生成自定义的partition_table.img,烧录时也会被刷写到Flash指定区域。

使用分区表

一般由我们的Bootloader读取我们烧录进去的分区表:

  1. Bootloader启动后,会从Flash指定的分区表位置(如W800的0xE000,ESP32的0x8000)读取分区表内容。

  2. 读取后,Bootloader解析分区表,获取各分区(如app、ota、nvs、user等)的起始地址、大小、类型等信息。

  3. Bootloader接下来就会根据这些信息,决定如何加载主固件、如何支持固件升级、数据存储等功能。

以下基于WinnerMicro W80X系列的分区表

分区表机制 — WinnerMicro 在线文档

使用分区表时,包含几种不同的分区表方案:三种预置的分区表和一种用户自定义分区表:

  1. Normal app, no OTA(普通应用程序,没有OTA)

  2. Large app, no OTA(大型应用程序,没有OTA)

  3. Normal app, with OTA(普通应用程序,带有OTA)

  4. Custom partition table(用户自定义分区表)

内置分区表样式:

  1. Normal app, no OTA(partition_table_normal.csv)

image-20250526100316210

  1. Large app, no OTA(partition_table_large.csv)

image-20250526100450525

  1. Normal app, with OTA(partition_table_with_ota.csv)

image-20250526100513925

这三种分区表中,Normal 的分区不带app_otauser 分区,SDK 默认使用 Large 这个分区表,可以从 Kconfig 配置中指定其他分区表。下面是对出现的名词的解释:

  • ft(factory):芯片的出厂固件,启动时将默认加载这个文件。
  • bootloader:存放 bootloader 的启动程序,完成OTA升级文件的解压和固件更新。
  • partition_table:存放分区表信息本身,包括各个分区的名称,地址,大小和FLAG。
  • app:存放应用程序,也就是我们的代码
  • app_ota:存放 OTA 升级包的
  • user:用户自定义分区,可选
  • nvs:非易失存储分区,用于 NVS 模块的 key-value 数据存放,常用来存配置、参数等,断电也不会丢,该分区需要一块做垃圾回收,所以 NVS 的大小需要大于等于2*0x1000。

注意:

ft分区,是芯片出厂固件,用户不能修改其中的内容。

ft,bootloader,partition_table这3个分区的配置 不能修改 。app,app_ota, NVS 这3个分区 不能修改名称 ,可以修改地址和大小。

用户自定义分区表

如果想要根据自己的程序使用灵活性更高的分区表,那么请在 menuconfigPartition Table 菜单中选择 Custom partition table 选项,启用自定义的分区表功能。之后在工程根目录下添加一个 partition_table_custom.csv 文件即可,在编译时构建系统会自动识别并进行处理。

  1. Custom partition table(partition_table_custom.csv)

image-20250526100524119

  • 字段之间的空格会被忽略,任何以 # 开头的行(注释)也会被忽略。

  • TXT 文件中的每个非注释行均为一个分区定义。

  • user 是一个用户自己配置的分区的示例,我们在使用时只需要合理的设置 offset 和 size 字段的内容即可。

  • ft, bootloader, partition_table 这3个分区不能改,app,nvs 这2个分区名称不能改,地址和大小可以修改。

烧录与擦除

一般使用脚本烧录

烧录(写入固件)时,分区表决定了每个固件/数据要写到Flash的哪个位置,烧录工具会自动参考分区表把内容写到正确地址。


为什么要烧录这么多分区内容?

芯片不是只需要运行你写的主程序,还需要其他很多内容才能安全、可靠地工作,比如:

  • bootloader:负责上电启动,决定加载哪个主程序,有的还负责升级。
  • app:你的主应用
  • nvs:用来存放参数、配置信息
  • app_ota/user区:升级固件、用户自定义数据
  • ft:出厂固件,有时用于回滚或工厂测试
  • 分区表本身:让芯片知道这些“房间”的分布

所以烧录过程就是把这些功能模块分门别类地装到芯片的不同地方,让它们各司其职。

芯片类似的分区表(stm32、esp32)

esp32分区表

image-20250527135057557

image-20250527135105250

MCU启动流程

这是以WinnerMicro的W80X系列MCU进行总结的启动流程,与ESP32类似,但是与STM32有好像不同

执行流程

  1. 上电或复位

    • MCU加电或复位后,自动进入启动流程。
  2. 执行BROM(一级引导加载程序)

    • 芯片首先运行内部固化的BROM代码,完成最基础的初始化和启动模式判断,然后决定后续加载哪个程序(如Bootloader或用户App)。
  3. 执行Bootloader(二级引导加载程序)

    • 若启动模式为正常启动,BROM会将控制权交给Bootloader。Bootloader负责更完整的初始化、分区表解析、固件校验、升级检测等,并选择合适的主固件(App)进行启动。
  4. 启动用户App(主程序)

    • 校验无误后,Bootloader跳转到用户App入口,开始执行用户主程序,系统正式进入应用阶段。

注:

  • BROM和Bootloader的具体功能与实现细节见上面内容。
  • 分区表、固件升级、异常处理等机制均在Bootloader阶段完成。

启动流程图

简略流程:BROM->Bootloader->app

image-20250527161320381

详细流程

mcu_start