解析STM32的启动流程,包括地址映射、中断向量表、堆栈区、静态区这些部分,还有APP的构成、中断向量表跳转偏移,变量的生命周期、作用域和存储位置,以及晶振和锁相环在掉电复位后的重整流程。
首先,STM32启动流程。我记得启动过程一般分为几个阶段:上电复位、从启动地址加载SP和PC、初始化系统时钟、设置中断向量表,然后进入main函数。
地址映射方面,STM32的Flash起始地址是0x08000000,而RAM的起始地址是0x20000000。启动的时候,处理器会从Flash的起始地址开始执行,但可能会有不同的启动模式,比如从系统存储器启动或者内置SRAM启动,不过通常都是从主Flash启动。那启动文件(比如startup_stm32fxxx.s)里应该设置了堆栈和中断向量表吧?
中断向量表应该是一系列函数指针,每个中断服务例程的地址。当中断发生时,处理器会根据中断号在向量表中找到对应的地址并跳转。但是当程序在Flash中运行时,中断向量表的地址应该是固定在0x08000000吗?如果程序有偏移,比如在Bootloader的情况下,可能需要重新定位向量表,这时候是不是要用到SCB->VTOR寄存器来设置新的偏移地址?
堆栈区的设置,启动文件中会为堆和栈分配空间。栈顶指针(SP)是在启动时从向量表的第一个条目加载的,对吧?然后是堆的空间,由__heap_base和__heap_end来定义?这部分在链接脚本里应该有所体现,比如在.ld文件中定义RAM的分配。
静态区的话,存放全局变量和静态变量,这部分变量在程序开始运行前就被初始化了。初始值可能存储在Flash中,启动时通过复制到RAM的.data段,还有.bss段会被清零。这些初始化操作是在SystemInit函数之后,进入main之前完成的,通常是由启动文件中的Reset_Handler处理的。
关于APP的构成,一个应用程序通常包括代码(.text)、已初始化的数据(.data)、未初始化的数据(.bss)、堆栈等。如果使用Bootloader,APP的起始地址可能会有偏移,这时候中断向量表也需要相应调整,比如设置VTOR寄存器为APP的起始地址。例如,如果APP的起始地址是0x08008000,那么VTOR应该设置为这个地址,这样中断发生时,处理器会从该地址开始查找向量表。
变量的生命周期和作用域,全局变量在整个程序运行期间存在,存储在静态区(.data或.bss)。局部变量在函数调用时创建,在栈上分配,函数返回时释放。静态局部变量在第一次调用时初始化,生命周期持续到程序结束,存储在.data或.bss。动态分配的内存在堆里,由malloc/free管理。
对于晶振和锁相环的复位流程。STM32上电复位后,默认使用内部RC振荡器(HSI)作为时钟源。初始化时,通常会先使能外部晶振(HSE),等待其稳定,然后配置PLL将HSE的频率倍频到系统所需的时钟,比如72MHz。然后切换系统时钟源到PLL。如果掉电复位,这些配置会丢失,需要重新初始化。代码里应该是在SystemInit函数中配置时钟,比如在stm32f10x.h中的SystemInit函数,或者通过HAL库的时钟配置函数。
中断向量表的跳转偏移,例如在Bootloader跳转到APP时,需要设置VTOR到APP的向量表地址,这样中断才能正确触发APP的中断处理函数。比如在APP的main函数开始处,设置SCB->VTOR = FLASH_BASE | 0x8000; 如果APP的起始地址是0x08008000的话。
举个例子,启动文件中的Reset_Handler可能会做以下事情:
1. 设置堆栈指针。
2. 调用SystemInit函数配置时钟。
3. 复制.data段到RAM,清零.bss段。
4. 跳转到main函数。
总结起来,启动流程的关键步骤包括硬件初始化、时钟配置、内存区域初始化(data和bss),然后跳转到应用程序。中断向量表的位置由VTOR控制,特别是在有Bootloader的情况下需要调整。而晶振和PLL的配置是在SystemInit中完成的,掉电后需要重新配置。
一、地址映射与启动模式
-
内存布局:
-
Flash:起始地址
0x08000000
,存储代码、常量及中断向量表。 -
RAM:起始地址
0x20000000
,存放堆栈、全局/静态变量等。
-
-
启动模式(通过BOOT引脚选择):
-
主Flash:常规模式,从
0x08000000
启动。 -
系统存储器:用于串口下载(如内置Bootloader)。
-
内置SRAM:调试用途,程序不持久。
-
二、启动流程核心步骤
-
复位序列:
-
CPU从
0x00000000
(映射到Flash起始地址)加载 栈顶指针(SP) 和 复位向量(Reset_Handler)。 -
执行
Reset_Handler
,初始化系统。
-
-
关键代码(汇编启动文件):
Reset_Handler:
ldr sp, =_estack ; 设置栈指针
bl SystemInit ; 初始化时钟
bl __main ; 进入C库初始化(数据复制、BSS清零)
注:_estack 在链接脚本中定义,指向RAM末尾。
三、中断向量表与重定向
1. 向量表的结构
typedef void (*isr_func)(void);
__attribute__((section(".isr_vector"))) isr_func vector_table[] = {
(isr_func)_estack, // 栈顶
Reset_Handler, // 复位处理
/* 其他中断向量... */
};
2. 重定向向量表(比如Bootloader)
// APP代码中设置VTOR偏移
SCB->VTOR = FLASH_BASE | 0x8000; // 假设APP起始于0x08008000
四、内存区域初始化(Reset_Handler细节)
-
数据段搬运:
-
.data
段(已初始化全局变量)从Flash复制到RAM。 -
.bss
段(未初始化数据)清零。
-
-
C库函数
__main
:-
自动处理数据初始化,最终调用
main()
。
-
五、变量存储与生命周期
变量类型 | 存储位置 | 生命周期 | 作用域 |
---|---|---|---|
全局变量 | .data/.bss | 程序运行全程 | 文件/全局 |
静态局部变量 | .data/.bss | 程序运行全程 | 函数内 |
局部变量 | 栈 | 函数调用期间 | 函数内 |
动态分配变量 | 堆 | 由malloc/free控制 | 指针引用范围内 |
六、时钟初始化(SystemInit详解)
void SystemInit(void) {
// 1. 启用HSE(外部晶振)
RCC->CR |= RCC_CR_HSEON;
while (!(RCC->CR & RCC_CR_HSERDY));
// 2. 配置PLL(假设目标72MHz)
RCC->CFGR |= RCC_CFGR_PLLMUL9 | RCC_CFGR_PLLSRC_HSE;
RCC->CR |= RCC_CR_PLLON;
while (!(RCC->CR & RCC_CR_PLLRDY));
// 3. 切换系统时钟到PLL
RCC->CFGR |= RCC_CFGR_SW_PLL;
while ((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL);
}
注:掉电复位后需重新执行此流程,默认回退到HSI(8MHz)。
七、链接脚本(.ld文件)关键配置
MEMORY {
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 128K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 20K
}
SECTIONS {
.isr_vector : { *(.isr_vector) } >FLASH
.text : { *(.text) } >FLASH
.data : { /* ... */ } >RAM AT>FLASH ; 数据段在FLASH中初始化
.bss : { /* ... */ } >RAM ; BSS段清零
_estack = ORIGIN(RAM) + LENGTH(RAM); ; 栈顶定义
}
八、Bootloader与APP交互实例
Bootloader跳转代码:
typedef void (*app_entry)(void);
void jump_to_app(uint32_t app_addr) {
app_entry entry = (app_entry)(*(volatile uint32_t*)(app_addr + 4));
__disable_irq();
SCB->VTOR = app_addr; // 重定向向量表
__set_MSP(*(volatile uint32_t*)app_addr); // 重置栈指针
entry(); // 跳转至APP的Reset_Handler
}
APP的链接脚本调整:
ORIGIN(FLASH) = 0x08008000; // APP偏移32KB
九、常见问题与调试
-
时钟配置失败:检查晶振是否起振,PLL参数是否超频。
-
HardFault:栈溢出或非法内存访问(检查链接脚本分配)。
-
中断不触发:确认VTOR是否正确设置,向量表对齐。