STM32 裸机启动流程详解
一、STM32 启动模式选择与地址映射
STM32 上电后,通过 BOOT 引脚配置不同的启动方式,决定 MCU 是从 Flash 启动、从 SRAM 启动,还是进入系统 Bootloader。
1.1 启动方式总览
启动模式 | BOOT 引脚配置 | 启动地址 | 用途 |
---|---|---|---|
Main Flash | BOOT0 = 0 | 0x0800 0000 | 默认从用户程序启动 |
System Memory | BOOT0 = 1, BOOT1 = 0 | 0x1FFF xxxx | 启动内置 Bootloader,支持串口/I2C |
SRAM | BOOT0 = 1, BOOT1 = 1 | 0x2000 0000 | 用于调试程序,通常用于开发阶段 |
1.2 启动地址别名机制
STM32 将 0x0000 0000
视为启动地址,它是对真实存储区域的映射(alias):
BOOT模式 | 0x0000 0000 映射地址 | 实际跳转 |
---|---|---|
Flash 启动 | 0x0800 0000 | 用户程序 |
System Memory 启动 | 0x1FFF F000 等 | ST Bootloader |
SRAM 启动 | 0x2000 0000 | RAM 中程序 |
1.3 BOOT 引脚配置方式
- 开发板:使用拨码开关或跳线帽配置 BOOT0/BOOT1
- 正式产品:可通过 Option Bytes 写死启动模式,避免引脚漂移
1.4 进入串口烧录模式步骤(以 STM32F1 为例)
- 设置
BOOT0 = 1, BOOT1 = 0
- 上电或复位,MCU 进入 System Memory
- 连接串口,使用 STM32CubeProgrammer 烧录程序
1.5 启动源检测(调试时使用)
if ((SCB->VTOR & 0x2FFE0000) == 0x20000000) {
// SRAM 启动
} else if ((SCB->VTOR & 0x2FFE0000) == 0x1FFF0000) {
// Bootloader 启动
} else {
// Flash 启动
}
二、STM32 裸机启动流程概述
STM32 裸机开发启动流程是指在没有操作系统参与的情况下,从上电复位到执行 main()
函数的全过程。这个过程涉及启动汇编文件、系统初始化、C运行时初始化以及用户主函数的调用。
2.1 流程图
上电/复位
↓
读取向量表地址 (栈顶地址, Reset_Handler)
↓
Reset_Handler
├─> 初始化 .data/.bss 段
├─> 调用 SystemInit()
├─> 调用 __libc_init_array()
└─> main()
三、启动文件 startup_stm32xxx.s
启动文件是一个汇编文件,包含两部分:中断向量表定义和 Reset_Handler 实现。
3.1 中断向量表定义
.section .isr_vector,"a",%progbits
.word _estack // 栈顶地址(链接脚本定义)
.word Reset_Handler // 复位中断向量
.word NMI_Handler
.word HardFault_Handler
...
3.2 Reset_Handler 函数实现
Reset_Handler:
LDR R0, =_sidata // flash 中 .data 初始化数据
LDR R1, =_sdata // RAM 中 .data 起始地址
LDR R2, =_edata // RAM 中 .data 结束地址
Loop_Copy_Data:
CMP R1, R2
ITTT LT
LDRLT R3, [R0], #4
STRLT R3, [R1], #4
BLT Loop_Copy_Data
LDR R1, =_sbss
LDR R2, =_ebss
MOVS R3, #0
Loop_Zero_BSS:
CMP R1, R2
IT LT
STRLT R3, [R1], #4
BLT Loop_Zero_BSS
BL SystemInit // 初始化系统时钟
BL __libc_init_array // C库初始化
BL main // 进入主函数
这些地址符号来自链接脚本(.ld
文件):
符号 | 含义 |
---|---|
_sidata | .data 初始化数据(Flash) |
_sdata | .data 段起始地址(RAM) |
_edata | .data 段结束地址(RAM) |
_sbss | .bss 段起始地址 |
_ebss | .bss 段结束地址 |
_estack | 栈顶地址 |
四、SystemInit()
— 系统时钟初始化
定义在 system_stm32xxx.c
,由 ST 官方提供,常见初始化内容:
- 打开 HSE/HSI、配置 PLL
- 设置 Flash 等待周期
- 切换系统时钟为 PLL
- 设置 AHB/APB 分频器
示例(STM32F1):
void SystemInit(void)
{
RCC->CR |= RCC_CR_HSION; // 开启内部高速时钟
RCC->CFGR = 0x00000000; // 清除分频设置
RCC->CR &= ~(RCC_CR_HSEON | RCC_CR_PLLON);
RCC->PLLCFGR = ... // 配置 PLL 参数
FLASH->ACR |= FLASH_ACR_LATENCY_5WS; // 设置 Flash 延时
RCC->CR |= RCC_CR_PLLON; // 开启 PLL
while ((RCC->CR & RCC_CR_PLLRDY) == 0); // 等待锁定
RCC->CFGR |= RCC_CFGR_SW_PLL; // 切换系统时钟为 PLL
}
五、__libc_init_array()
— 初始化 C/C++ 运行环境
这个函数位于标准 C 库中(如 newlib),主要功能:
- 调用
.preinit_array
函数(用于 C++) - 调用
.init_array
中的构造函数 - 初始化静态变量
void __libc_init_array(void)
{
for (i = 0; i < __preinit_array_end - __preinit_array_start; i++)
__preinit_array_start[i]();
for (i = 0; i < __init_array_end - __init_array_start; i++)
__init_array_start[i]();
}
如果是纯 C 项目,可以选择不使用它,而直接在 Reset_Handler
中清理 .bss、复制 .data。
六、main()
函数 — 用户应用程序入口
int main(void)
{
init_gpio();
init_uart();
while (1) {
toggle_led();
delay_ms(500);
}
}
main()
是用户写的程序入口,往往会初始化外设,然后进入主循环。
七、小结:完整启动流程表
阶段 | 执行内容 |
---|---|
启动模式 | 通过 BOOT 引脚或 option bytes 选择启动源 |
启动汇编 | 设置栈,拷贝 .data,清 .bss |
SystemInit | 配置系统时钟、PLL、Flash |
libc init | C/C++构造函数,全局变量初始化 |
main | 用户应用程序 |