Loading

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也算做未初始化,用到的堆空间和栈空间也会被算入这里面;

程序的组成

程序的状态分为:

  1. 运行状态:启动时会将Flash中的RW data加载到SRAM中;ZI data时程序运行时产生,成局部变量的栈空间包含在ZI-data区的范围
  2. 存储状态:向Flash中下载程序时,其实仅仅下载的是CODE+RO-data+RW-data的内容

FLash和SRAM的空间分配如下图所示,图来自:ARM Cortex-M底层技术(1)—程序在Flash和SRAM的空间分配

3.png

程序状态与区域 组成
程序执行时的只读区域(RO) Code + RO data
程序执行时的可读写区域(RW) RW data + ZI data
程序存储时占用的ROM区 Code + RO data + RW data

程序下载

程序烧录方式对比

  • ST-Link:通过SWD/JTAG调试接口直接访问芯片内核,功能强大,支持调试和烧录,速度更快,是开发阶段的首选。
  • ISP:通过串口等标准通信接口,利用芯片内部预置的Bootloader进行烧录,无需额外调试器,适合量产现场升级,但不支持调试功能。

启动流程

以STM32F103C8T6为例,启动过程大致分为以下几个过程:

  1. 上电复位,根据BOOT0和BOOT1两个引脚,判断从哪个存储器启动
  2. 定位中断向量表并初始化栈指针
  3. 执行复位中断服务程序 (Reset_Handler)
  4. 进入主程序main()

阶段1 STM32启动模式

STM32启动模式(boot modes)分为[1]

  • 从主闪存(Flash)启动:一般我们使用JTAG或者SWD模式下载程序时,就是下载到这个Flash里面,重启后也直接从这启动程序。C8T6芯片有64KB闪存,其内存地址范围0x0800 0000~0x0800 FFFF。主闪存被映射到启动内存空间(0x0000 0000),但仍可通过其原始内存空间(0x800 0000)访问。换句话说,闪存内容可以从地址 0x0000 00000x800 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)

1.png

SRAM映射:

2.png

阶段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:这是一个标签,代表了向量表的起始地址。
  • DCDData 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:外部中断0
      • USART1_IRQHandler:串口1中断
      • ...:其他具体的外设中断
    • 保留位置:一些位置被标记为 0Reserved,这是为了与Cortex-M内核保持兼容,这些位置未来可能会被使用或必须为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完成硬件初始化后执行的第一段程序代码。它的主要任务有两个:

  1. 调用 SystemInit() 函数初始化芯片关键系统(尤其是时钟)。
  2. 调用 __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() 函数。

参考资料

  1. STM32F103x8数据手册,P15
  2. STM32F103x参考手册,P61
  3. STM32F103x参考手册,P49
  4. STM32F103x8数据手册,P34
  5. STM32F103x参考手册,P204,表63
  6. ARM Cortex-M底层技术(1)—程序在Flash和SRAM的空间分配
  7. ARM Cortex-M底层技术(2)—启动代码详解
posted @ 2025-09-01 16:31  记录学习的Lyx  阅读(59)  评论(0)    收藏  举报