【STM32】STM32启动流程分析

启动模式:

1. Flash Memory 启动方式(BOOT0 = 0)

启动地址:0x08000000,这是 STM32 内置的 Flash 存储区域,通常用于存储用户程序。
特点:这是 STM32 最常用的启动模式,几乎所有正常运行的应用程序都在这个模式下启动。
当你通过 JTAG 或 SWD 接口下载程序时,程序会被写入 Flash 中,重启后会直接从该地址启动程序。这种方式是常规的开发和部署方式。

2. System Memory 启动方式(BOOT0 = 1,BOOT1 = 0)

启动地址:0x1FFF0000,这是 STM32 的系统存储器地址,该区域预置了厂商提供的 Bootloader 程序。
特点:该模式下,启动的程序是由 STMicroelectronics 提供的内建 Bootloader 程序,这个程序是出厂时预设在芯片内的,并且是不可修改的。Bootloader 允许用户通过串口、USB 或其他接口进行固件更新,这种方式在开发过程中用于加载程序到 Flash 中。
启动过程:
        设置 BOOT0 = 1,BOOT1 = 0,然后复位。
        进入 Bootloader 后,通过串口等方式下载新的程序到 Flash 中。
        下载完成后,需要将 BOOT0 设置为 0 并复位,这样 STM32 才会从 Flash 启动新的应用程序。这种模式虽然有固件更新功能,但需要手动切换 BOOT0 的状态,并且操作繁琐,因此一般不作为常规启动方式,更多用于固件升级和恢复。

3. SRAM 启动方式(BOOT0 = 1,BOOT1 = 1)

启动地址:0x20000000,STM32 的内置 SRAM 地址。
特点:该模式下,程序直接从 SRAM 启动,而 SRAM 本身无法存储长期数据,因此此模式一般用于调试或开发过程中,特别是当开发人员需要快速验证代码修改时。由于 SRAM 的访问速度比 Flash 快,而且无需擦除 Flash,调试时可以直接在内存中运行修改后的程序,避免了 Flash 的写入和擦除过程,提升了调试效率。但该模式无法用于正式的应用程序存储,因为 SRAM 在断电后会丢失数据。

启动文件:

1.启动文件的作用:

在keil的项目中经常会看到.s的文件就是启动文件,启动文件的作用通常包括设置中断向量表、初始化堆栈、清除未初始化的数据、调用系统初始化函数、以及最后跳转到 main() 函数执行应用程序代码。

2.链接脚本文件详解

当我们在keil中写好一个项目编译成功时,会看到如下信息

其中Code (18008 bytes): 这是程序代码区的大小,包含了所有的指令和逻辑。

RO-data (1296 bytes): 这是只读数据区的大小,通常包括常量数据、字符串字面量和某些初始化的静态变量。

RW-data (192 bytes): 这是读写数据区的大小,包含程序中所有被初始化的全局变量和静态变量。ZI-data (1280 bytes): 这是零初始化数据区的大小,存放所有未显式初始化且默认值为零的全局和静态变量。

这些区域的大小一般都是通过一个脚本链接文件设置的,keil的链接文件的后缀是sct,打开一个项目的链接文件如下面所示

LR_IROM1 0x08000000 0x00010000  {    ; load region size_region

#LR_IROM1:这是一个加载区域的名称,通常表示程序代码存储的 Flash 区域。
#0x08000000:这是该区域的起始地址,通常对应于 Flash 存储的起始位置。
#0x00010000:这是该区域的大小,表示 64 KB 的 Flash 存储空间。

这个区域主要用于指定程序代码(Code)、只读数据(RO-data)等的存放位置。它指定了程序代码和常量数据将从哪里加载到内存中的位置(因为程序运行时代码和常量不会被加载到内存中)

ER_IROM1 0x08000000 0x00010000  {  ; load address = execution address
   *.o (RESET, +First)
   *(InRoot$$Sections)
   .ANY (+RO)
}

#ER_IROM1:这是程序的执行区域,通常用于存放程序的实际代码和数据。加载地址和执行地址通常相同(在这个例子中为 0x08000000)。
#*.o (RESET, +First):指示链接器将启动代码(如 reset 启动向量)放置到该区域的最前面。
#*(InRoot$$Sections):表示将一些初始的、特定的段(sections)放到这个区域,可能是由启动文件或其他配置文件定义的段。
#.ANY (+RO):将所有类型为只读数据(如常量、字面量等)的段放到此区域。

ER_IROM1 是指存放程序代码和初始化的只读数据的区域。

RW_IRAM1 0x20000000 0x00005000  {  ; RW data
   .ANY (+RW +ZI)
}

#RW_IRAM1:这是一个 RAM 区域,用于存放程序中的可读写数据(RW-data)和零初始化数据(ZI-data)。
#0x20000000:这是 RAM 区域的起始地址(通常是 SRAM 或其他类型的 RAM)。
#0x00005000:这是该区域的大小,表示 20 KB 的 RAM 空间。

 RW_IRAM1 (RAM 区域)表示将所有可读写数据(RW)以及零初始化数据(ZI)放到这个区域。RW 数据通常包括全局变量、静态变量、堆栈等;ZI 数据是那些未初始化且默认值为零的全局/静态变量。

程序在最后一个链接阶段时会根据该脚本文件指导链接器如何将程序的不同部分(如代码、数据、堆栈、BSS 等)放置到特定的内存区域中。

3.启动文件详解:

需要提前说明的是单片机在上电前,flash区域已经存放了上述各个数据段,由上文知道flash的启动地址为0x08000000,

向量表的第一个位置(0x00000000)通常存储的是初始栈指针(__initial_sp),这个值指向系统的栈空间起始位置(通常是 SRAM 的末尾)。
向量表的第二个位置(0x00000004)存储的是复位向量,即复位时要跳转到的程序地址。该地址通常指向复位处理程序(Reset_Handler)的入口。

当单片机上电并复位时,硬件会根据预定义的流程,自动将程序计数器(PC)指向 0x00000000(栈指针的初始地址)。然后,微控制器会读取 0x00000004 位置的数据,这是复位向量的地址,它会指向复位处理程序(如 Reset_Handler)的入口。此时,CPU 会跳转到 Reset_Handler,并开始执行复位过程,初始化硬件和系统设置。最后跳转到 main() 函数执行应用程序代码。这些都是由中断向量表设置的,中断向量表又定义在启动文件中。

打开项目的启动文件如下由汇编语言撰写,汇编语法不详细阐述,只讲启动文件中每部分的作用

栈定义:

Stack_Size      EQU     0x00000400          ; 定义栈大小为 1024 字节

AREA    STACK, NOINIT, READWRITE, ALIGN=3  ; 定义一个区域,名为 STACK,未初始化、读写、对齐 8 字节
Stack_Mem       SPACE   Stack_Size         ; 分配 Stack_Size 字节的空间,用于栈内存

堆定义:

Heap_Size       EQU     0x00000C00   #堆的大小被设置为 0xC00 字节(即 3,072 字节,或者 3KB)。

                AREA    HEAP, NOINIT, READWRITE, ALIGN=3  #这是定义一个名为 HEAP 的内存区域
__heap_base
Heap_Mem        SPACE   Heap_Size   #分配 Heap_Size 字节的空间,用于堆内存
__heap_limit

中断向量表相关的定义:

__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     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

__initial_sp 是程序的初始栈指针。此行将栈指针的初始值放入向量表的第一个位置

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。当系统复位时,处理器会自动调用 Reset_Handler,然后执行以下步骤:

        1.调用 SystemInit 函数初始化硬件环境(如时钟、外设等)。
        2.跳转到 __main 函数,开始执行主程序。

Reset_Handler 是系统启动时的第一个代码,它负责确保硬件正确初始化后,程序能够顺利进入主逻辑部分。

异常处理程序定义:

NMI_Handler     PROC
                EXPORT  NMI_Handler                [WEAK]
                B       .
                ENDP
HardFault_Handler\
                PROC
                EXPORT  HardFault_Handler          [WEAK]
                B       .
                ENDP
MemManage_Handler\
........................
SysTick_Handler PROC
                EXPORT  SysTick_Handler            [WEAK]
                B       .
                ENDP

对于每个异常处理程序,当前的实现只是简单地跳转到当前地址,进入一个无限循环,表示在这些异常发生时程序不会执行任何具体的错误处理。通常,在实际的嵌入式系统中,这些异常处理程序会被开发人员填充以处理特定的错误或执行一些恢复操作。

弱符号的中断处理程序:

Default_Handler PROC

                EXPORT  WWDG_IRQHandler            [WEAK]
                EXPORT  PVD_IRQHandler             [WEAK]
                 .....................
RTCAlarm_IRQHandler
USBWakeUp_IRQHandler

                B       

                ENDP

                ALIGN

后面带有弱符号[WEAK]的中断服务函数意味着如果没有定义其他强符号的处理程序,链接器会使用默认的处理程序(通常是一个空的处理程序,可能会进入无限循环,等待恢复或其他动作)。这样做的好处是,程序可以灵活地提供自己的中断处理程序,而不必修改库中的中断处理程序实现。

没带弱符号则不可以自定义!!!!

初始化堆栈:

     IF      :DEF:__MICROLIB           
                
                 EXPORT  __initial_sp
                 EXPORT  __heap_base
                 EXPORT  __heap_limit
                
                 ELSE
                
                 IMPORT  __use_two_region_memory
                 EXPORT  __user_initial_stackheap
                 
__user_initial_stackheap

                 LDR     R0, =  Heap_Mem
                 LDR     R1, =(Stack_Mem + Stack_Size)
                 LDR     R2, = (Heap_Mem +  Heap_Size)
                 LDR     R3, = Stack_Mem
                 BX      LR

                 ALIGN

                 ENDIF

                 END

IF :DEF:__MICROLIB 指令的作用是判断 __MICROLIB 是否已经在编译过程中定义(通过编译器的宏定义)。

当 __MICROLIB 没有被定义时,栈内存和堆内存会被隔开(因为栈向下增长,堆向上增长防止重合出现程序崩溃)

自定义的初始化函数 __user_initial_stackheap,该函数负责设置堆栈和堆的内存区域。

注:本文从底层分析了STM32的启动流程,撰写不易,UU们点点关注,点点赞!!!

作者:@启智森

物联沃分享整理
物联沃-IOTWORD物联网 » 【STM32】STM32启动流程分析

发表回复