2. STM32存储器映射与启动模式

在Bootloader升级体系中,理解STM32的存储器映射与启动模式是设计一切升级逻辑的物理基础。STM32的Cortex-M内核采用统一的4GB线性地址空间,从0x00000000到0xFFFFFFFF,所有Flash、SRAM、外设寄存器乃至系统存储器都被映射到这一段连续地址中。上电复位后,内核从地址0x00000000处取出主栈指针(MSP)初值,再从0x00000004处取出复位向量并跳转执行。因此,“0x00000000处映射了哪块物理存储器”直接决定了芯片从哪里开始运行程序,这正是启动模式选择的核心。

三类启动区域的物理映射

STM32F1/F4等主流系列通常提供三个可选的启动区域,它们通过BOOT引脚在上电时被重映射到0x00000000起始的别名区:

  • 主Flash存储器:起始地址0x08000000,容量依型号而定(如STM32F103C8T6为64KB,STM32F407ZGT6为1MB)。这是用户应用程序的常规存放位置,也是Bootloader与App共同驻留的区域。
  • 系统存储器(System Memory):起始地址0x1FFF0000附近,是ST出厂时固化的Boot ROM,内含UART、USB DFU、CAN等系统Bootloader。它不可擦写,用于“救砖”或首次烧录。
  • 内嵌SRAM:起始地址0x20000000,容量通常为20KB~192KB。将SRAM作为启动区主要用于调试场景,掉电即失,不能作为量产启动方式。

需要特别注意的是,0x00000000只是一个“别名窗口”,真正的物理存储体地址并未改变。当BOOT配置选择主Flash启动时,访问0x00000000等价于访问0x08000000;选择系统存储器启动时,0x00000000等价于0x1FFF0000。这一重映射由芯片内部的启动逻辑在复位释放瞬间完成,软件无法在运行中动态切换。

STM32 启动区域映射与 BOOT 引脚选择 物理存储体 主 Flash 0x0800 0000 起 系统存储器 0x1FFF 0000 起 内嵌 SRAM 0x2000 0000 起 BOOT 引脚配置 BOOT0 = 0 BOOT1 = X(无关) BOOT0 = 1 BOOT1 = 0 BOOT0 = 1 BOOT1 = 1 0x0000 0000 别名窗口 映射至主 Flash MSP←0x0800 0000 映射至系统存储器 MSP←0x1FFF 0000 映射至内嵌 SRAM MSP←0x2000 0000 向量表重定位关键点: • 复位后 SCB->VTOR 默认指向 0x0000 0000 别名区,Bootloader 跳转 App 前必须重设 VTOR。 • 若 App 位于 0x0800 8000,则 VTOR 应写为 0x0800 8000,且该地址须 128 字节对齐(低 7 位为 0)。 • 跳转前需关闭全局中断、清除挂起标志,并重设 MSP 为 App 向量表首字。

BOOT引脚配置与启动模式选择

BOOT0和BOOT1(部分型号为BOOT1/PB2复用)在复位释放的瞬间被采样,采样结果决定别名窗口指向哪个物理区域。以STM32F103为例,其对应关系为:BOOT0=0时无论BOOT1为何值均从主Flash启动;BOOT0=1且BOOT1=0时从系统存储器启动;BOOT0=1且BOOT1=1时从内嵌SRAM启动。在硬件设计上,BOOT0通常通过10kΩ电阻下拉到GND,并预留跳线或测试点,以便在需要时拉高进入系统Bootloader。

对于Bootloader升级体系而言,量产设备应始终将BOOT0固定为低电平,从主Flash启动。Bootloader作为Flash中的第一段程序,负责判断是否需要升级:若收到升级指令,则通过UART/CAN等接口接收新固件并写入App区;若无需升级,则直接跳转到App区执行。系统存储器启动模式则作为“最后防线”,当Flash中的Bootloader和App均被破坏时,可通过拉高BOOT0并复位,利用ST出厂Bootloader重新烧录。

向量表重定位方法

Cortex-M内核的向量表默认位于0x00000000,包含MSP初值和复位、NMI、HardFault等异常入口。当Bootloader跳转到位于0x08008000的App时,若不复位向量表,App中的中断仍会跳回Bootloader的向量表,导致中断响应错误。因此必须在App启动前完成向量表重定位。

重定位的核心是修改SCB->VTOR(Vector Table Offset Register)。该寄存器位于系统控制块中,地址为0xE000ED08,低7位保留(要求向量表128字节对齐),其余位存放向量表相对于0x00000000的偏移。对于起始地址0x08008000的App,VTOR应写为0x08008000。典型跳转代码如下:

typedef void (*pFunction)(void);
pFunction Jump_To_App;
uint32_t AppAddr = 0x08008000;

/* 1. 检查App栈顶是否合法(位于SRAM范围内) */
if (((*(__IO uint32_t*)AppAddr) & 0x2FFE0000) == 0x20000000) {
    /* 2. 关闭全局中断,清除挂起标志 */
    __disable_irq();
    for (int i = 0; i < 8; i++) {
        NVIC->ICER[i] = 0xFFFFFFFF;
        NVIC->ICPR[i] = 0xFFFFFFFF;
    }
    /* 3. 重定位向量表 */
    SCB->VTOR = AppAddr;
    /* 4. 重设主栈指针 */
    __set_MSP(*(__IO uint32_t*)AppAddr);
    /* 5. 取复位向量并跳转 */
    Jump_To_App = (pFunction)(*(__IO uint32_t*)(AppAddr + 4));
    Jump_To_App();
}

这段代码中有几个关键判断:首先检查App首字是否落在SRAM地址范围(0x20000000起),用于确认App确实已被烧录且栈顶合法;其次在跳转前关闭所有中断并清除挂起标志,避免Bootloader的残留中断在App中触发;最后先设MSP再取复位向量,顺序不可颠倒。App工程本身也需要在system_stm32f1xx.c或启动文件中将VECT_TAB_OFFSET设置为0x8000,使链接器生成的向量表基址与VTOR一致。

在IAP升级场景中,Bootloader与App的Flash分区需提前规划。例如STM32F103C8T6共64KB Flash,可划分为:Bootloader 0x08000000~0x08007FFF(32KB),App 0x08008000~0x0800FFFF(32KB)。若App超过32KB,则需压缩Bootloader或选用更大容量型号。分区边界必须与VTOR设置、链接脚本中的ROM起始地址三者严格一致,否则跳转后必然出现HardFault。

综上,存储器映射决定了“从哪里取向量”,BOOT引脚决定了“上电时映射哪块存储体”,VTOR重定位决定了“运行中中断向量指向哪里”。三者共同构成Bootloader跳转App的底层约束,任何一处配置不一致都会导致升级后设备无法正常启动。