一、STM32 启动模式选择与地址映射
STM32 上电后,通过 BOOT 引脚配置不同的启动方式,决定 MCU 是从 Flash 启动、从 SRAM 启动,还是进入系统 Bootloader。
1.1 启动方式总览
1.2 启动地址别名机制
STM32 将 0x0000 0000 视为启动地址,它是对真实存储区域的映射(alias):
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 文件):
四、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();
for (i = 0; i < __init_array_end - __init_array_start; i++)
__init_array_start();
}
如果是纯 C 项目,可以选择不使用它,而直接在 Reset_Handler 中清理 .bss、复制 .data。
六、main() 函数 — 用户应用程序入口
int main(void)
{
init_gpio();
init_uart();
while (1) {
toggle_led();
delay_ms(500);
}
}
main() 是用户写的程序入口,往往会初始化外设,然后进入主循环。
七、小结:完整启动流程表
————————————————
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/hallo_zz/article/details/147712387
|