分享

STM32开发(4)----系统启动流程

 大傻子的文渊阁 2024-01-04 发布于浙江

前言

本章介绍STM32系统启动流程的相关内容,包括对系统启动方式,启动汇编文件的分析,启动原理的介绍等。

一、系统启动方式

在介绍系统启动流程之前,我们先来了解下启动模式,就像我们对电脑进行刷机一样,我们从硬盘或是U盘去引导系统启动是不同的,STM32有三种启动模式,即从FLASH启动,从SRAM启动,从系统存储启动。
在这里插入图片描述
系统是从 0x0000 0000 和 0x0000 0004 两个的地址获取堆栈指针 SP 和程序计数器指针 PC。而 0x0000 0000 和 0x0000 0004 两个的地址可以被重映射到其他的地址空间,映射的空间地址由BOOT0和BOOT1共同决定。如果内部 FLASH 启动:会将 0x0800 0000 映射到 0x0000 0000,会将 0x0800 0004 映射到 0x0000 0004。如果内部 SRAM 启动:会将 0x0200 0000 映射到 0x0000 0000,会将 0x0200 0004 映射到 0x0000 0004。如果用系统存储器启动:会将 0x01FF FF00 映射到 0x0000 0000,会将 0x01FF FF04 映射到 0x0000 0004

随后从地址 0x0800 0000/ 0x0200 0000 /0x01FF FF00 处取出堆栈指针 MSP 的初始值,从地址 0x0800 0004/ 0x0200 0004 /0x01FF FF04处取出程序计数器指针PC 的初始值。CPU 会从 PC 寄存器指向的地址空间取出的第 1 条指令开始执行程序,就是开始执行复位中断服务程序 Reset_Handler。

其中,我们最常用的启动方式是从FLASH启动。当然选择了启动模式后也要将固件下载到对应的地址空间才能引导成功,
在keil进行烧写固件时可以看到对固件烧写起始地址的设置,如下图所示:
在这里插入图片描述

二、启动汇编文件分析

前面介绍过通过STM32CubeMX生成了一个简单的工程,如下图,生成的项目中是一些官方提供的基本库和文件:

  1. 用户程序:应用程序,中断配置,用户配置文件等
  2. 外设驱动,这STM32CubeMX根据你配置的引脚功能涉及的功能库进行自动添加的
  3. 标准接口文件:ARM Cortex 微控制器软件接口标准文件
  4. 芯片的启动文件

在这里插入图片描述
启动文件主要做了以下工作:

  1. 初始化堆栈指针
  2. 初始化中断向量表
  3. 在Reset_Handler中调用 SystemInit
  4. 调用 C 库中的 _main 函数初始化用户堆栈,最终调用 main 函数进入C程序

下面我们对stm32f10系列芯片的启动文件startup_stm32f10x_ld.s分段进行分析,在分析的过程中我们不对汇编指令进行介绍,因为很少用到,都是简写,也不容易记忆,所以我们仅仅对大致功能做一个说明。

1. 初始化堆栈指针

; <h> Stack Configuration
;   <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>

Stack_Size      EQU     0x00000400

                AREA    STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem       SPACE   Stack_Size
__initial_sp

这段代码的目的是初始化栈空间:开辟一段大小为 0x0000 0400(1KB)的栈空间,其中 ALIGN=3,表示按照 2^3 对齐,即 8 字节对齐,栈是从高到低排布的。

我们知道,栈由编译器自动分配和释放的内存,不需要用户参与管理,主要作用是存放局部变量,函数形参等,比如在程序中定义局部变量较多或者较大,占用占空间较多时,可能会导致栈空间溢出报 HardFault错误。这时,我们就需要修改启动代码中修改栈的大小Stack_Size,但是栈空间Stack_Size的大小不能大于内存SRAM的大小。

; <h> Heap Configuration
;   <o>  Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>

Heap_Size       EQU     0x00000200

                AREA    HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem        SPACE   Heap_Size
__heap_limit

这段代码的目的是初始化堆空间:开辟一段大小为 0x0000 0200(512 字节)的堆空间,其中 ALIGN=3,表示按照 2^3 对齐,即 8 字节对齐,__heap_base 表示堆的起始地址, __heap_limit 表示堆的结束地址,堆是由低到高排布的。

我们知道,堆由程序员自行管理,动态分配和释放,如使用 malloc()、 calloc()和 realloc()等函数申请的内存就在堆上面。如果程序员没有释放,程序结束时可能由操作系统回收。

2. 初始化中断向量表

; Vector Table Mapped to Address 0 at Reset
                AREA    RESET, DATA, READONLY
                EXPORT  __Vectors
                EXPORT  __Vectors_End
                EXPORT  __Vectors_Size

__Vectors       DCD     __initial_sp               ; Top of Stack
                DCD     Reset_Handler              ; Reset Handler
                DCD     NMI_Handler                ; NMI Handler
                DCD     HardFault_Handler          ; Hard Fault Handler
                ......略
                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     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_Size  EQU  __Vectors_End - __Vectors

                AREA    |.text|, CODE, READONLY

这段代码的目的是初始化中断向量表: __Vectors 为向量表起始地址, __Vectors_End 为向量表结束地址,__Vectors_Size 为向量表大小。

__Vectors       DCD     __initial_sp               ; Top of Stack
                DCD     Reset_Handler              ; Reset Handler

中断向量表被放置在代码段的最前面。起始地址是: 0x0800 0000。如上以DCD开头的为中断向量表中的内容,后面跟着的是中断服务函数的函数名,DCD 以四字节对齐分配内存,所以 Reset_Handler 中断函数入口地址为 0x0800 0004。

3. 在Reset_Handler中调用 SystemInit

; Reset handler routine
Reset_Handler    PROC
                 EXPORT  Reset_Handler             [WEAK]
     IMPORT  __main
     IMPORT  SystemInit
                 LDR     R0, =SystemInit
                 BLX     R0
                 LDR     R0, =__main
                 BX      R0
                 ENDP

; Dummy Exception Handlers (infinite loops which can be modified)

这段代码的目的是定义复位子程序:从中断向量表中我们可以看到,Reset_Handler 是初始化栈空间后的第一个子程序,也是复位后第一个要执行的子程序,因此非常重要。子程序中首先调用SystemInit 函数对系统进行初始化的配置,然后在调用 C 库函数__main,这里最终会调用到 main 函数,至此,将进入我们用c语言写的程序代码中执行。

NMI_Handler     PROC
                EXPORT  NMI_Handler                [WEAK]
                B       .
                ENDP
HardFault_Handler                PROC
                EXPORT  HardFault_Handler          [WEAK]
                B       .
                ENDP
MemManage_Handler                PROC
                EXPORT  MemManage_Handler          [WEAK]
                B       .
                ENDP
BusFault_Handler                PROC
                EXPORT  BusFault_Handler           [WEAK]
                B       .
                ENDP
UsageFault_Handler                PROC
                EXPORT  UsageFault_Handler         [WEAK]
                B       .
                ENDP
SVC_Handler     PROC
                EXPORT  SVC_Handler                [WEAK]
                B       .
                ENDP
DebugMon_Handler                PROC
                EXPORT  DebugMon_Handler           [WEAK]
                B       .
                ENDP
PendSV_Handler  PROC
                EXPORT  PendSV_Handler             [WEAK]
                B       .
                ENDP
SysTick_Handler PROC
                EXPORT  SysTick_Handler            [WEAK]
                B       .
                ENDP
 ......

这段代码的目的是声明各个中断服务子程序,只有函数名,具体的实现会在stm32f10x_it.c中,如下图所示
在这里插入图片描述

4.进入main函数前,初始化用户堆栈

 ;*******************************************************************************
; User Stack and Heap initialization
;*******************************************************************************
                 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是一个高度优化的库,适用于用C语言编写的基于ARM的嵌入式应用程序。与ARM编译器工具链中包含的标准C库相比,MicroLib提供了许多嵌入式系统所需的代码是其显著优势。
MicroLib与标准C库的主要区别在于:

  1. MicroLib专为深度嵌入式应用程序设计。
  2. MicroLib经过优化,比使用ARM标准库使用更少的代码和数据内存。
  3. MicroLib被设计为在没有操作系统的情况下工作,但这并不妨碍它与任何OS或RTOS(如Keil RTX)一起使用。
  4. MicroLib不包含文件I/O或宽字符支持。
  5. 由于MicroLib已被优化以最小化代码大小,一些函数的执行速度将比ARM编译工具中的标准C库例程慢。

要在嵌入式应用程序中使用MicroLib,请选中keil5中的MicroLib复选框并编译应用程序。keil5将您的程序与MicroLib连接起来,快速轻松地缩小程序大小。
在这里插入图片描述

总结

本章内容介绍了STM32三种常用的启动方式和系统启动引导的原理,然后介绍了启动文件的启动流程, 启动文件起到的作用:初始化堆栈指针,初始化中断向量表 ,在Reset_Handler中调用 SystemInit ,调用 C 库中的 _main 函数初始化用户堆栈,最终调用 main 函数进入C程序。

至此,STM32的开发前的一些储备知识到此结束,下面我们通过STM32CubeMX配置功能引脚,进行开发逐一介绍。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多