首页 > 其他分享 >STM32 芯片启动过程

STM32 芯片启动过程

时间:2024-09-18 16:51:53浏览次数:3  
标签:__ 初始化 启动 芯片 DCD STM32 地址 Handler main

目录


一、前言

下面主要讲解从上电复位到 main 函数的过程。主要有以下步骤:

  1. 初始化堆栈指针:SP = __initial_spPC = Reset_Handler
  2. 初始化中断向量表
  3. 配置系统时钟
  4. 调用 C 库函数 _main 初始化用户堆栈,然后进入 main 函数

二、STM32 的启动模式

启动模式决定了中断向量表的位置,STM32 有三种启动模式:

  1. 主闪存存储器启动:从 STM32 内置的 Flash 启动( 0 x 08000000 − 0 x 0807 F F F F 0x0800 0000-0x0807 FFFF 0x08000000−0x0807FFFF),一般我们使用 JTAG 或者 SWD 模式下载程序时,就是下载到这个里面,重启后也直接从这启动程序。以 0x08000000 对应的内存为例,则该块内存既可以通过 0x00000000 操作也可以通过 0x08000000 操作,且都是操作的同一块内存。
  2. 系统存储器启动:从系统存储器启动( 0 x 1 F F F F 000 − 0 x 1 F F F F 7 F F 0x1FFFF000-0x1FFF F7FF 0x1FFFF000−0x1FFFF7FF),这种模式启动的程序功能是由厂家设置的。一般来说,我们选用这种启动模式时,是为了从串口下载程序,因为在厂家提供的 ISP 程序中,提供了串口下载程序的固件,可以通过这个 ISP 程序将用户程序下载到系统的 Flash 中。以 0x1FFFFFF0 对应的内存为例,则该块内存既可以通过 0x00000000 操作也可以通过 0x1FFFFFF0 操作,且都是操作的同一块内存。
  3. 片上 SRAM 启动:从内置 SRAM 启动( 0 x 20000000 − 0 x 3 F F F F F F F 0x2000 0000-0x3FFFFFFF 0x20000000−0x3FFFFFFF),既然是 SRAM,自然也就没有程序存储的能力了,这个模式一般用于程序调试。SRAM 只能通过 0x20000000 进行操作,与上述两者不同。从 SRAM 启动时,需要在应用程序初始化代码中重新设置向量表的位置。

我们可以选择设置 BOOT0 和 BOOT1 的引脚电平状态,来选择复位后的启动模式,如下表:

BOOT1BOOT0自举模式自举空间
x0主 Flash选择主 Flash 作为自举空间
01系统存储器选择系统存储器作为自举空间
11嵌入式 SRAM选择嵌入式 SRAM 作为自举空间

启动模式只决定程序烧录的位置,加载完程序之后会有一个重映射(映射到 0x00000000 地址位置);真正产生复位信号的时候,CPU 还是从开始位置执行

值得注意的是 STM32 上电复位以后,代码区都是从 0x00000000 开始的,三种启动模式只是将各自存储空间的地址映射到 0x00000000 中。

三、STM32 启动文件分析

下面针对 startup_stm32f40_41xxx.s 文件进行简单的分析。

1、栈 Stack

Stack_Size      EQU     0x00000400

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

这段代码的含义如下:开辟栈的大小为 0X00000400(1KB),名字为 STACK, NOINIT 即不初始化,可读可写, ALIGN=3 表示 8(2^3)字节对齐。

栈的作用是用于局部变量,函数调用,函数形参等的开销,栈的大小不能超过内部 SRAM 的大小。如果编写的程序比较大,定义的局部变量很多,那么就需要修改栈的大小。

  • EQU:宏定义的伪指令,相当于等于,类似于 C 中的 define。
  • AREA:告诉汇编器汇编一个新的代码段或者数据段。STACK 表示段名,这个可以任意命名;NOINIT 表示不初始化;READWRITE 表示可读可写,ALIGN=3,表示按照 2 3 2^3 23 对齐,即 8 字节对齐。
  • SPACE:用于分配一定大小的内存空间,单位为字节。这里指定大小等于 Stack_Size。
  • 标号 __initial_sp 紧挨着 SPACE 语句放置,表示栈的结束地址,即栈顶地址,栈是由高向低生长的。

2、堆 Heap

Heap_Size       EQU     0x00000200

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

				PRESERVE8
                THUMB

解释如下:开辟堆的大小为 0X00000200(512 字节),名字为 HEAP, NOINIT 即不初始化,可读可写,ALIGN=3 同理表示 8(2^3)字节对齐。__heap_base 表示堆的起始地址,__heap_limit 表示堆的结束地址。堆是由低向高生长的,跟栈的生长方向相反。

  • PRESERVE8:指定当前文件的堆栈按照 8 字节对齐。
  • THUMB:表示后面指令兼容 THUMB 指令。THUBMARM 指令集的一个子集重新编码二形成的一个指令集,其指令长度为 16bit,现在 Cortex-M 系列的都使用 THUMB-2 指令集,THUMB-2 是 32 位的,兼容 16 位和 32 位的指令,是 THUMB 的超集。

堆主要用来动态内存的分配,像 malloc() 函数申请的内存就在堆上面。不过这个在 STM32 里面用的比较少。

3、中断向量表 Vectors

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

这里定义一个数据段,名字为 RESET,可读。并声明 __Vectors__Vectors_End__Vectors_Size 这三个标号具有全局属性,可供外部的文件调用。

  • EXPORT:声明一个标号可被外部的文件使用,使标号具有全局属性。如果是 IAR 编译器,则使用的是 GLOBAL 这个指令。

当内核响应了一个发生的异常后,对应的异常服务例程(ESR)就会执行。为了决定 ESR 的入口地址, 内核使用了向量表查表机制。这里使用一张向量表。向量表其实是一个 WORD(32 位整数)数组,每个下标对应一种异常,该下标元素的值则是该 ESR 的入口地址。向量表在地址空间中的位置是可以设置的,通过 NVIC 中的一个重定位寄存器来指出向量表的地址。在复位后,该寄存器的值为 0。因此,在地址 0 (即 FLASH 地址 0) 处必须包含一张向量表,用于初始时的异常分配。要注意的是这里有个另类:0 号类型并不是什么入口地址,而是给出了复位后 MSP 的初值。下图是 F407 的向量表的一部分,具体参阅想要使用的芯片对应的手册即可。

可以看到和代码中的定义是一一对应的:

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

__Vectors_Size  EQU  __Vectors_End - __Vectors

向量表从 FLASH 的 0 地址开始放置,以 4 个字节为一个单位,地址 0 存放的是栈顶地址, 0X04 存放的是复位程序的地址,以此类推。从代码上看,向量表中存放的都是中断服务函数的函数名,可我们知道 C 语言中的函数名就是一个地址。

  • DCD:分配一个或者多个以字为单位的内存,以四字节对齐,并要求初始化这些内存。在向量表中,DCD 分配了一堆内存,并且以 ESR 的入口地址初始化它们。

3.1 中断响应流程

这里既然提到了中断向量表,就额外补充以下中断响应流程:

  1. 中断信号发送到 NVIC
  2. NVIC 通知 CPU
  3. CPU 根据中断号得到中断服务程序地址(基地址 + 中断编号 * 4B)
  4. 保存现场
  5. 执行中断服务程序
  6. 恢复现场
  7. 继续执行程序

下面以 PendSV_Handler 为例,说明一下:

可以看到 PendSV_Handler 的中断号是 14,也就是 14 ∗ 4 = ( 56 ) 10 = ( 38 ) 2 14*4=(56)_{10}=(38)_2 14∗4=(56)10​=(38)2​。

而中断向量表的基地址为 0x08000000,故其服务函数的地址在 0x08000038,见下 bin 文件(下面会提到):

解析出来是:0x080002D7,而 map 文件中却是:0x080002D6

原因如下:ARM 指令集有 ARM 指令集和 Thumb 指令集。ARM 指令集位数长;而 Thumb 指令集位数短,故而占用内存比较小,所以编译器大部分时间采用 Thumb 指令集。

  • Thumb 指令集末尾是奇数位 1,像这里的 0x080002D7
  • ARM 指令集末尾是偶数位 0,也就是这里的 0x080002D6

而我们访问 0x080002D7 这个地址的时候,它实际上会跳转到 0x080002D6 这个地址。

4、复位程序 Reset_Handler

				AREA    |.text|, CODE, READONLY

; Reset handler
Reset_Handler    PROC
                 EXPORT  Reset_Handler             [WEAK]
        IMPORT  SystemInit
        IMPORT  __main

                 LDR     R0, =SystemInit
                 BLX     R0
                 LDR     R0, =__main
                 BX      R0
                 ENDP

首先定义一个名称为 .text 的代码段,仅可读。

复位子程序是系统上电后第一个执行的程序,调用 SystemInit 函数初始化系统时钟,然后调用 C 库函数 __main,最终调用 main 函数去到 C 程序中。

  • WEAK:表示弱定义,如果外部文件优先定义了该标号则首先引用该标号,如果外部文件没有声明也不会出错。这里表示复位子程序可以由用户在其他文件重新实现,这里并不是唯一的。
  • IMPORT:表示该标号来自外部文件,跟 C 语言中的 EXTERN 关键字类似。这里表示 SystemInit__main 这两个函数均来自外部的文件。
  • LDR:从存储器中加载字到一个寄存器中
  • BL:跳转到由寄存器/标号给出的地址,并把跳转前的下一条指针地址保存到 LR
  • BLX:跳转到由寄存器给出的地址,并根据寄存器的 LSE 确定处理器的状态,还要把跳转前的下条指令地址保存到 LR
  • BX:跳转到由寄存器/标号给出的地址,不用返回

SystemInit() 是一个标准的库函数,在 system_stm32f4xx.c 这个库文件中定义。主要作用是配置系统时钟。__main 是一个标准的 C 库函数,主要作用是初始化用户堆栈,并在函数的最后调用 main 函数去到 C 程序中。这就是为什么我们写的程序都有一个 main 函数的原因。

5、中断服务函数

在启动文件里面已经帮我们写好所有中断的中断服务函数,跟我们平时写的中断服务函数不一样的就是这些函数都是空的,真正的中断服务程序需要我们在外部的 C 文件里面重新实现,这里只是提前占了一个位置而已。

如果我们在使用某个外设的时候,开启了某个中断,但是又忘记编写配套的中断服务程序或者函数名写错,那当中断来临的时,程序就会跳转到启动文件预先写好的空的中断服务程序中,并且在这个空函数中无线循环,即程序就死在这里。

NMI_Handler     PROC
                EXPORT  NMI_Handler                [WEAK]
                B       .
                ENDP
				...
SysTick_Handler PROC
                EXPORT  SysTick_Handler            [WEAK]
                B       .
                ENDP

Default_Handler PROC

                EXPORT  WWDG_IRQHandler                   [WEAK]    
				...
CRYP_IRQHandler                                                    
HASH_RNG_IRQHandler
FPU_IRQHandler
   
                B       .

                ENDP
  • B:跳转到一个标号。这里跳转到一个‘.’,即表示无线循环

6、用户堆栈初始化

ALIGN

;*******************************************************************************
; User Stack and Heap initialization, 由 C 库函数 __main 来完成
;*******************************************************************************
                 IF      :DEF:__MICROLIB  ; 这个宏在 KEIL 里面开启
                
                 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
  • ALIGN:对指令或者数据存放的地址进行对齐,后面会跟一个立即数。缺省表示 4 字节对齐。
  • IF,ELSE,ENDIF:汇编的条件分支语句,跟 C 语言的 if ,else 类似
  • END:文件结束

首先判断是否定义了 __MICROLIB

  • 如果定义了这个宏则赋予标号 __initial_sp(栈顶地址)、__heap_base(堆起始地址)、__heap_limit(堆结束地址)全局属性,可供外部文件调用。有关这个宏我们在 KEIL 里面配置,具体见下图。然后堆栈的初始化就由 C 库函数 __main 来完成。
  • 如果没有定义 __MICROLIB,则才用双段存储器模式,且声明标号 __user_initial_stackheap 具有全局属性,让用户自己来初始化堆栈。

四、STM32 启动流程分析

经过刚才对启动文件的分析,下面对 STM32 启动流程的分析就要清晰不少了。

1、初始化 SP、PC 及中断向量表

当系统复位后,处理器首先读取向量表中的前两个字(8 个字节),第一个字存入 SP,第二个字存入 PC,也就是
程序执行的起始地址。

下面打开经过编译生成的 bin 文件,看到前 8 个字节的内容如下(小端模式):

Keil 默认生成 hex 文件,要想生成 bin 文件要自己添加命令,设置如下:

D:\Keil_v5\ARM\ARMCC\bin\fromelf.exe --bin --output=Objects/stm32f407.bin Objects/stm32f407.axf

0x200006600x0800020D,下面在 map 文件(存放链接地址)中查找这两个地址存放的是什么数据:

在这里插入图片描述

这正是中断向量表的前两项内容,这也印证了前面所说的内容:

2、设置系统时钟

接下来执行 PC 指向的 Reset_Handler,并调用 SystemInit 初始化系统时钟。

void SystemInit(void)
{
  /* FPU settings ------------------------------------------------------------*/
  #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
    SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2));  /* set CP10 and CP11 Full Access */
  #endif
  /* Reset the RCC clock configuration to the default reset state ------------*/
  /* Set HSION bit */
  RCC->CR |= (uint32_t)0x00000001;

  /* Reset CFGR register */
  RCC->CFGR = 0x00000000;

  /* Reset HSEON, CSSON and PLLON bits */
  RCC->CR &= (uint32_t)0xFEF6FFFF;

  /* Reset PLLCFGR register */
  RCC->PLLCFGR = 0x24003010;

  /* Reset HSEBYP bit */
  RCC->CR &= (uint32_t)0xFFFBFFFF;

  /* Disable all interrupts */
  RCC->CIR = 0x00000000;

#if defined(DATA_IN_ExtSRAM) || defined(DATA_IN_ExtSDRAM)
  SystemInit_ExtMemCtl(); 
#endif /* DATA_IN_ExtSRAM || DATA_IN_ExtSDRAM */
         
  /* Configure the System clock source, PLL Multiplier and Divider factors, 
     AHB/APBx prescalers and Flash settings ----------------------------------*/
  SetSysClock();

  /* Configure the Vector Table location add offset address ------------------*/
#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
}

前面的部分是配置时钟的,具体参考手册即可。需要注意的是最后一段代码:

默认是没有开启 VECT_TAB_SRAM,则从 FLASH 中启动,VTOR 寄存器存放的是中断向量表的起始地址,在 IAP 升级会修改这里的偏移量。

3、初始化堆栈并进入 main

执行完 SystemInit 后又调用了标准库中的 __main 函数:

在这里会初始化堆、栈、RO、RW、ZI 段。最后就进入到 C 文件中的 main 函数中。

有关内存分段的内存可以参考:单片机内存区域划分

五、总结

至此,启动过程圆满结束!

标签:__,初始化,启动,芯片,DCD,STM32,地址,Handler,main
From: https://blog.csdn.net/Teminator_/article/details/142333178

相关文章

  • 英特尔剥离芯片代工业务 与亚马逊达成重要合作
    KlipC报道:据英特尔首席执行官PatGelsinger周一的一份声明,英特尔官宣剥离芯片代工业务,已与亚马逊达成重要合作,期待扭亏为盈。同时,英特尔周一早些时候还表示,它有资格获得美国政府高达30亿美元的资金,用于为军方制造芯片,有望获得更多政府合同。据KlipC了解,英特尔将把其芯片代工部门剥......
  • SD NAND在STM32平台的存储性能提升
    在当今快速发展的嵌入式系统领域,STM32在嵌入式领域的地位市场非常显著,而MK米客方德的SDNAND闪存技术,以其卓越的耐用性、小巧的体积和高速的数据传输性能,在STM32平台上也展现出良好的适配性。 一、MK米客方德SDNAND技术亮点MK米客方德SDNAND是一种采用NAND闪存技术的贴片式存储......
  • 了解快充协议芯片诱骗取电过程
    快充协议芯片诱骗取电的过程主要涉及充电器与设备之间的通信和电压协商,以确保安全、快速和高效的充电。这个过程依赖于快充协议芯片,如XSP08Q快充诱骗芯片,它们内置通信模块,能够与供电端的充电器进行握手通信,从而申请出需要的电压给设备供电。首先,充电器会对外广播电源信息,当用......
  • 设计方案:372-基于7VX690T的万兆光纤、双FMC扩展的综合计算平台 RISCV 芯片验证平台
    基于7VX690T的万兆光纤、双FMC扩展的综合计算平台RISCV芯片验证平台 一、板卡概述      基于V7的高性能PCIe信号处理板,板卡选用Xilinx 公司Virtex7系列FPGA 7VX690T-2FFG1761C为处理芯片,板卡提供两个标准FMC插槽,适用于高性能采集、回放以及相关处理。通过连接不同的FMC......
  • 制作基于debian的linux live启动u盘(UEFI)
    参考从零开始制作一个linuxiso镜像_详解:把Linux系统做成Livecd从零制作Ubuntu20.04LiveCDLiveCDCustomization(ubuntu官方文档)从零开始制作Ubuntu22.04LiveCDCreateaCustomDebianLiveEnvironment(CDorUSB)方法一:live-build这是debian官方的live镜......
  • 明明没有程序占用端口,但是启动程序却提示端口无法使用,项目也启动失败
    明明没有程序占用端口,但是启动程序却提示端口无法使用,项目也启动失败win10、端口占用、port、netstat、used背景曾在springboot中遇到过,新建springcloud时又遇到这个问题,如果不从根本上解决,就需要改端口,太过麻烦,为免再浪费时间所以这个问题必须要解决了。问题描述正......
  • windows系统Tomcat启动过程中找不到JAVA_HOME nor the JRE_HOME解决方法
    有很多人第一次下载Tomcat时会遇到一个问题,就是信心满满按着教程配置好环境之后,一运行startup.bat会出现一下问题:NeithertheJAVA_HOMEnortheJRE_HOMEenvironmentvariableisdefinedAtleastoneoftheseenvironmentvariableisneededtorunthisprogram;在......
  • 使用STM32实现按键控制LED
    以下是一个使用STM32实现按键控制LED的代码示例,详细说明了每个步骤和代码的功能。#include"stm32f4xx.h"voidGPIO_Configuration(void);voidEXTI_Configuration(void);voidLED_On(void);voidLED_Off(void);intmain(void){GPIO_Configuration();EXTI_C......
  • ActivityManagerService 启动进程(3)
    ActivityManagerService启动进程简述上一节我们介绍了Activity的启动流程,这一节会在上一节的基础上介绍当要启动的Activity所在的进程之前没有启动,这个情况下是怎么样启动一个新的进程,并且继续启动Activity。我们知道Android是基于linux系统开发的,而linux系统启动一个应......
  • STM32智能小车--测距模块(舵机+HC_SR04超声波)
    大家好哇!我是湫湫,一个正在摸索嵌入式的普通大学生。进入正题,最近在做智能小车,想通过文章记录学习过程。本次文章主要使用 stm32F1C8T6实现小车测距。非常感谢大家的阅读,如果有不对的地方欢迎指正。目录一.实物图1.舵机2.HC_SR04:二.基本工作原理1.舵机工作原理2.......