STM32启动流程
Flash和SRAM空间分配
在keil中编译整个项目会输出以下的信息:
linking...
Program Size: Code=1040 RO-data=252 RW-data=0 ZI-data=1632
".\Objects\Project.axf" - 0 Error(s), 0 Warning(s).
Build Time Elapsed: 00:00:08
参考:https://www.cnblogs.com/39950436-myqq/p/11387179.html
- Code:代码区,指程序中代码即函数体的大小,注意程序中未使用的函数也会算在CODE中,也即会占用FLASH空间,因此不用的函数最好删除掉,以免占用过多FLASH空间
- RO-data:RO就是只读的意思,程序中只读的变量(也就是带Const的)和已初始化的字符串等;
- RW-data:特指已初始化的可读可写全局/静态变量;
- ZI-data:未初始化的可读可写全局/静态变量,注意初始化为0也算做未初始化,用到的堆空间和栈空间也会被算入这里面;
程序的组成
程序的状态分为:
- 运行状态:启动时会将Flash中的RW data加载到SRAM中;ZI data时程序运行时产生,成局部变量的栈空间包含在ZI-data区的范围。
- 存储状态:向Flash中下载程序时,其实仅仅下载的是CODE+RO-data+RW-data的内容
FLash和SRAM的空间分配如下图所示,图来自:ARM Cortex-M底层技术(1)—程序在Flash和SRAM的空间分配

| 程序状态与区域 | 组成 |
|---|---|
| 程序执行时的只读区域(RO) | Code + RO data |
| 程序执行时的可读写区域(RW) | RW data + ZI data |
| 程序存储时占用的ROM区 | Code + RO data + RW data |
程序下载
程序烧录方式对比
- ST-Link:通过SWD/JTAG调试接口直接访问芯片内核,功能强大,支持调试和烧录,速度更快,是开发阶段的首选。
- ISP:通过串口等标准通信接口,利用芯片内部预置的Bootloader进行烧录,无需额外调试器,适合量产或现场升级,但不支持调试功能。
启动流程
以STM32F103C8T6为例,启动过程大致分为以下几个过程:
- 上电复位,根据BOOT0和BOOT1两个引脚,判断从哪个存储器启动
- 定位中断向量表并初始化栈指针
- 执行复位中断服务程序 (Reset_Handler)
- 进入主程序
main()
阶段1 STM32启动模式
STM32启动模式(boot modes)分为[1]:
-
从主闪存(Flash)启动:一般我们使用JTAG或者SWD模式下载程序时,就是下载到这个Flash里面,重启后也直接从这启动程序。C8T6芯片有64KB闪存,其内存地址范围
0x0800 0000~0x0800 FFFF。主闪存被映射到启动内存空间(0x0000 0000),但仍可通过其原始内存空间(0x800 0000)访问。换句话说,闪存内容可以从地址0x0000 0000或0x800 0000开始访问[2]。闪存用于存储数据和程序。根据ARM Cortex-M架构典型设计,0x0800 0000是STM32等芯片主闪存的实际物理地址,而0x0000 0000是启动别名地址。
-
从系统存储器system memory启动:系统存储器被映射到启动内存空间(0x0000 0000),但仍可通过其原始内存空间访问(互联型设备为 0x1FFF B000,其他设备为 0x1FFF F000)。引导加载程序(bootloader)位于系统存储器中,它通过USART1将程序写入Flash中。这段程序是芯片制造商在出厂时就固化在芯片内部的,用户无法修改或擦除。它的主要职责就是与外部主机(如电脑)通信,接收新的应用程序数据,并将其烧写到MCU的Flash存储器中。
-
从片上SRAM启动:20KB SRAM,其内存地址范围
0x2000 0000~0x2000 4FFF。静态随机存取存储器SRAM的作用:- 存放全局变量和静态变量:程序中已初始化(
.data段)和未初始化(.bss段)的变量都存放在这里。 - 栈(Stack):用于存放局部变量、函数调用时的返回地址、参数等。由编译器自动管理。
- 堆(Heap):用于动态内存分配(如
malloc()、free()函数操作的内存空间)。 - 运行时数据:程序运行过程中产生的各种临时数据。
- 存放全局变量和静态变量:程序中已初始化(
程序存储器、数据存储器、寄存器和I/O端口统一映射在同一个高达4GB的线性地址空间内[3]。至于在生产芯片时,在工艺和技术上具体是怎么实现这种映射的,我们无需关心。CPU存储器映射图[4]如下所示:(注:图示的Flash为128KB)

SRAM映射:

阶段2 定位中断向量表并初始化栈指针
向量表(Vector Table)是存储在Flash起始地址(通常是0x0000 0000)的第一个数组,这个表的每一个条目(4字节)都是一个中断服务函数 (ISR) 的入口地址。下表给出示例,详细的内容参考[5]
| 序号 | 地址偏移量 | 向量类型 | 说明 |
|---|---|---|---|
| 0 | 0x0000 |
初始SP值 | 主栈指针(MSP)的初始值 |
| 1 | 0x0004 |
Reset | 复位中断服务程序(程序入口) |
| 2 | 0x0008 |
NMI | 不可屏蔽中断 |
| 3 | 0x000C |
HardFault | 硬件错误中断 |
| 4 | 0x0010 |
MemManage | 存储器管理错误 |
| ... | ... | ... | ... |
| 15 | 0x003C |
SysTick | 系统定时器中断 |
| 16 | 0x0040 |
WWDG | 窗口看门狗中断 |
| 17 | 0x0044 |
PVD | 通过外部中断线(EXTI)检测的电源电压检测(PVD)中断 |
| ... | ... | ... | ... |
| n | ... | IRQ n | 具体的外设中断(如USART1, TIM2等) |
向量表定义在启动文件startup_stmf10x_xx.s中,以startup_stmf10x_md.s为例,以下是向量表的汇编代码:
; Vector Table Mapped to Address 0 at Reset
AREA RESET, DATA, READONLY
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size
AREA RESET, DATA, READONLY:AREA指令用于定义一个代码或数据段。RESET是这个段的名字,这是一个特殊的段名,链接器会识别它并将其放置在输出文件的起始位置(通常是地址0x0800 0000,即Flash的起始地址)。DATA表明这个段包含数据。READONLY表明该段是只读的。
EXPORT ...:EXPORT(等同于GLOBAL)指令将符号__Vectors、__Vectors_End和__Vectors_Size声明为全局符号。这意味着链接器和其他源文件(如C文件)可以访问这些符号,从而知道向量表在内存中的位置和大小。
__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD MemManage_Handler ; MPU Fault Handler
DCD BusFault_Handler ; Bus Fault Handler
DCD UsageFault_Handler ; Usage Fault Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SVC_Handler ; SVCall Handler
DCD DebugMon_Handler ; Debug Monitor Handler
DCD 0 ; Reserved
DCD PendSV_Handler ; PendSV Handler
DCD SysTick_Handler ; SysTick Handler
; External Interrupts
DCD WWDG_IRQHandler ; Window Watchdog
DCD PVD_IRQHandler ; PVD through EXTI Line detect
DCD TAMPER_IRQHandler ; Tamper
DCD RTC_IRQHandler ; RTC
DCD FLASH_IRQHandler ; Flash
DCD RCC_IRQHandler ; RCC
DCD EXTI0_IRQHandler ; EXTI Line 0
DCD EXTI1_IRQHandler ; EXTI Line 1
DCD EXTI2_IRQHandler ; EXTI Line 2
DCD EXTI3_IRQHandler ; EXTI Line 3
DCD EXTI4_IRQHandler ; EXTI Line 4
DCD DMA1_Channel1_IRQHandler ; DMA1 Channel 1
DCD DMA1_Channel2_IRQHandler ; DMA1 Channel 2
DCD DMA1_Channel3_IRQHandler ; DMA1 Channel 3
DCD DMA1_Channel4_IRQHandler ; DMA1 Channel 4
DCD DMA1_Channel5_IRQHandler ; DMA1 Channel 5
DCD DMA1_Channel6_IRQHandler ; DMA1 Channel 6
DCD DMA1_Channel7_IRQHandler ; DMA1 Channel 7
DCD ADC1_2_IRQHandler ; ADC1_2
DCD USB_HP_CAN1_TX_IRQHandler ; USB High Priority or CAN1 TX
DCD USB_LP_CAN1_RX0_IRQHandler ; USB Low Priority or CAN1 RX0
DCD CAN1_RX1_IRQHandler ; CAN1 RX1
DCD CAN1_SCE_IRQHandler ; CAN1 SCE
DCD EXTI9_5_IRQHandler ; EXTI Line 9..5
DCD TIM1_BRK_IRQHandler ; TIM1 Break
DCD TIM1_UP_IRQHandler ; TIM1 Update
DCD TIM1_TRG_COM_IRQHandler ; TIM1 Trigger and Commutation
DCD TIM1_CC_IRQHandler ; TIM1 Capture Compare
DCD TIM2_IRQHandler ; TIM2
DCD TIM3_IRQHandler ; TIM3
DCD TIM4_IRQHandler ; TIM4
DCD I2C1_EV_IRQHandler ; I2C1 Event
DCD I2C1_ER_IRQHandler ; I2C1 Error
DCD I2C2_EV_IRQHandler ; I2C2 Event
DCD I2C2_ER_IRQHandler ; I2C2 Error
DCD SPI1_IRQHandler ; SPI1
DCD SPI2_IRQHandler ; SPI2
DCD USART1_IRQHandler ; USART1
DCD USART2_IRQHandler ; USART2
DCD USART3_IRQHandler ; USART3
DCD EXTI15_10_IRQHandler ; EXTI Line 15..10
DCD RTCAlarm_IRQHandler ; RTC Alarm through EXTI Line
DCD USBWakeUp_IRQHandler ; USB Wakeup from suspend
__Vectors_End
__Vectors:这是一个标签,代表了向量表的起始地址。DCD:Data Constant Define 指令,用于分配一个32位(4字节)的字内存,并用指定的值初始化它。- 向量表的内容顺序是由ARM Cortex-M内核架构严格规定的:
- 第一个元素(0号):
__initial_sp。这是初始主栈指针(MSP) 的值。CPU复位后做的第一件事就是从这里加载值到SP寄存器。 - 第二个元素(1号):
Reset_Handler。这是复位中断服务程序的地址。CPU做的第二件事就是跳转到这个地址执行程序。这就是你的程序的真正起点。 - 后续元素(2号及以后):这些是各种异常和中断的处理函数地址。例如:
NMI_Handler:不可屏蔽中断HardFault_Handler:硬件错误中断SysTick_Handler:系统定时器中断EXTI0_IRQHandler:外部中断0USART1_IRQHandler:串口1中断...:其他具体的外设中断
- 保留位置:一些位置被标记为
0或Reserved,这是为了与Cortex-M内核保持兼容,这些位置未来可能会被使用或必须为0。
- 第一个元素(0号):
__Vectors_End:这是一个标签,代表了向量表的结束地址。
__Vectors_Size EQU __Vectors_End - __Vectors
AREA |.text|, CODE, READONLY
这行代码结束了之前的 RESET 数据段,并开始一个新的段。|.text| 是段名,通常用于存放程序代码,CODE 表明这是一个代码段,READONLY 表明该段是只读的。
在这行代码之后,通常会跟着定义所有中断服务程序(例如 Reset_Handler, NMI_Handler 等),它们通常是一个无限循环或者跳转到真正的C处理函数。
阶段3 执行复位中断服务程序
复位中断服务程序汇编代码:
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
Reset_Handler 是中断向量表中的第二个条目,是CPU完成硬件初始化后执行的第一段程序代码。它的主要任务有两个:
- 调用
SystemInit()函数初始化芯片关键系统(尤其是时钟)。 - 调用
__main函数初始化C语言运行环境。注意:这个__main()不是你的 C 语言里的main()!它是 C/C++ 标准库的一部分(有时也叫__aeabi_start)。__main()负责初始化 C 运行环境,具体包括:- 复制数据段 (.data):将存储在 Flash 中的初始值(如已初始化的全局变量)复制到 SRAM 中。更多有关SRAM和Flash的空间分配可以参考这篇博客:ARM Cortex-M底层技术(1)—程序在Flash和SRAM的空间分配
- 清零 BSS 段 (.bss):将未初始化的全局变量所在的内存区域清零。
- 调用标准库初始化函数(如果使用了标准库,如
stdio)。 - 并最终跳转到用户的
main()函数。
SystemInit
可以在system_stm32f10x.c中看见SystemInit函数,主要是初始化时钟,片上Flash等。其中的SetSysClock函数可以对系统时钟System Clock、AHB总线所需时钟HCLK、APB外设所需时钟PCLK1、PCLK2进行配置。有关STM32修改时钟频率的内容,参考另一篇博客:STM32时钟配置
/**
* @brief Setup the microcontroller system
* Initialize the Embedded Flash Interface, the PLL and update the
* SystemCoreClock variable.
* @note This function should be used only after reset.
* @param None
* @retval None
*/
void SystemInit (void)
{
/* Reset the RCC clock configuration to the default reset state(for debug purpose) */
/* Set HSION bit */
RCC->CR |= (uint32_t)0x00000001;
/* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */
#ifndef STM32F10X_CL
RCC->CFGR &= (uint32_t)0xF8FF0000;
#else
RCC->CFGR &= (uint32_t)0xF0FF0000;
#endif /* STM32F10X_CL */
/* Reset HSEON, CSSON and PLLON bits */
RCC->CR &= (uint32_t)0xFEF6FFFF;
/* Reset HSEBYP bit */
RCC->CR &= (uint32_t)0xFFFBFFFF;
/* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */
RCC->CFGR &= (uint32_t)0xFF80FFFF;
#ifdef STM32F10X_CL
/* Reset PLL2ON and PLL3ON bits */
RCC->CR &= (uint32_t)0xEBFFFFFF;
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x00FF0000;
/* Reset CFGR2 register */
RCC->CFGR2 = 0x00000000;
#elif defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x009F0000;
/* Reset CFGR2 register */
RCC->CFGR2 = 0x00000000;
#else
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x009F0000;
#endif /* STM32F10X_CL */
#if defined (STM32F10X_HD) || (defined STM32F10X_XL) || (defined STM32F10X_HD_VL)
#ifdef DATA_IN_ExtSRAM
SystemInit_ExtMemCtl();
#endif /* DATA_IN_ExtSRAM */
#endif
/* Configure the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers */
/* Configure the Flash Latency cycles and enable prefetch buffer */
SetSysClock();
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM. */
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH. */
#endif
}
阶段4 进入主程序main()
此时,所有硬件和软件环境都已准备就绪,程序开始执行用户编写的 main() 函数。
参考资料
- STM32F103x8数据手册,P15
- STM32F103x参考手册,P61
- STM32F103x参考手册,P49
- STM32F103x8数据手册,P34
- STM32F103x参考手册,P204,表63
- ARM Cortex-M底层技术(1)—程序在Flash和SRAM的空间分配
- ARM Cortex-M底层技术(2)—启动代码详解

浙公网安备 33010602011771号