第二章 STM32启动流程

STM32启动进入main函数前,需要完成的操作。

STM32在执行进入main函数之前,会对芯片硬件本身进行一个初始化和内存重映射的工作。
参考文章:

https://blog.csdn.net/Teminator_/article/details/142333178

https://blog.csdn.net/a_qwq_a/article/details/134476247?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522580bb93f649aa5dc576a426f9a14db0d%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D\&request_id=580bb93f649aa5dc576a426f9a14db0d\&biz_id=0\&utm_medium=distribute.pc_search_result.none-task-blog-2\~all\~sobaiduend\~default-1-134476247-null-null.142\^v101\^pc_search_result_base7\&utm_term=stm32上电启动流程\&spm=1018.2226.3001.4187

1、STM32的三种启动方式

启动模式只是决定程序烧录的位置,加载程序完成后会有一个重映射的功能,将地址空间重映射到0x0000 0000这个地址位置,产生复位信号时,CPU是从这个位置开始执行的。

1.1 从主Flash启动

STM32内置的Flash启动,一般使用JTAG或者SWD模式下载程序时,程序就存储在0x0800 0000 - 0x0807 FFFF 地址空间中。

这里需要将Flash地址0x0800 0000 映射到0x0000 0000,这样启动以后相当于从0x0800 0000开始的。

1.2 从系统存储器启动

从系统存储器(System Memory)启动,这种模式启动的程序功能是由厂家设置的,一般来说选择这个模式是位了用串口下载程序。程序存储在0x1FFF F000 - 0x1FFF F7FF地址空间中。

启动后,会将0x1FFF F000 地址映射到0x0000 0000,这样启动以后相当于从0x1FFF F000开始。

系统存储器里面存储了STM32自带的Bootloader代码,它提供了UART1接口将用户代码下载到Flash中的功能。

1.3 从嵌入式SRAM启动

片上SRAM启动,从内置SRAM启动,这个空间没有程序储存能力,这个模式一般用于程序调试,程序启动地址0x2000 0000 - 0x3FFF FFFF

2、 STM32启动文件

文件名一般是startup_stm32xxx.s以s结尾的文件中

2.1 栈Stack

Stack_Size EQU 0x00000400 //表示开辟栈的大小为0X00000400(1KB),EQU是伪指令,相当于C中的#define

AREA STACK, NOINIT, READWRITE, ALIGN=3 //开辟一段可读可写数据空间,ARER 伪指令表示下面将开始定义一个代码段或者数据段。此处是定义数据段。ARER 后面的关键字表示这个段的属性。段名为STACK,可以任意命名;NOINIT 表示不初始化;READWRITE 表示可读可写,ALIGN=3,表示按照 8 字节对齐。

Stack_Mem SPACE Stack_Size //SPACE 用于分配大小等于 Stack_Size连续内存空间,单位为字节。

__initial_sp // __initial_sp表示栈顶地址。栈是由高向低生长的。
定义一个名 STACK的内存区域,大小为0x00000400(1KB)

开辟一个大小为0x00000400,名字为STACK的栈空间

2.2 堆Heap

Heap_Size EQU 0x00000200 //开辟堆的大小为 0X00000200(512 字节)。

AREA HEAP, NOINIT, READWRITE, ALIGN=3 //名字为 HEAP,NOINIT 即不初始化,可读可写,8字节对齐。

__heap_base //__heap_base 表示对的起始地址。

Heap_Mem SPACE Heap_Size //SPACE 用于分配大小等于 Heap_Size连续内存空间,单位为字节。

__heap_limit //__heap_limit 表示堆的结束地址。

PRESERVE8 //这个指令通常用于确保指令对齐。ARM指令集要求指令在内存中对齐,以提高执行效率。PRESERVE8可能用于确保在生成的机器代码中,指令的地址是8字节对齐的。

THUMB //这是ARM架构中一种指令集,用于提高代码密度。THUMB指令集的指令长度比标准ARM指令集的指令长度短,这有助于减小程序的大小。
定义一个名HEAP的内存区域,大小为0x00000200(512字节)

开辟一个大小为512字节,名字为HEAP的堆空间

2.3 中断向量表

; Vector Table Mapped to Address 0 at Reset

AREA RESET, DATA, READONLY //定义一个名为RESET的内存区域,该区域被声明为数据区域(DATA),并且只能读取(READONLY)。

EXPORT __Vectors //将符号__Vectors导出,使其在其他地方可见,__Vectors是向量表的起始地址。

EXPORT __Vectors_End //将符号__Vectors_End导出,同样使其在其他地方可见,__Vectors_End是向量表的结束地址。

EXPORT __Vectors_Size //将符号__Vectors_Size导出,使其在其他地方可见,__Vectors_Size是向量表的大小。

//DCD 用于创建一个存储地址的向量表,每个地址占用一个双字。

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

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 RTC_Alarm_IRQHandler ; RTC Alarm through EXTI Line

DCD USBWakeUp_IRQHandler ; USB Wakeup from suspend

DCD TIM8_BRK_IRQHandler ; TIM8 Break

DCD TIM8_UP_IRQHandler ; TIM8 Update

DCD TIM8_TRG_COM_IRQHandler ; TIM8 Trigger and Commutation

DCD TIM8_CC_IRQHandler ; TIM8 Capture Compare

DCD ADC3_IRQHandler ; ADC3

DCD FSMC_IRQHandler ; FSMC

DCD SDIO_IRQHandler ; SDIO

DCD TIM5_IRQHandler ; TIM5

DCD SPI3_IRQHandler ; SPI3

DCD UART4_IRQHandler ; UART4

DCD UART5_IRQHandler ; UART5

DCD TIM6_IRQHandler ; TIM6

DCD TIM7_IRQHandler ; TIM7

DCD DMA2_Channel1_IRQHandler ; DMA2 Channel1

DCD DMA2_Channel2_IRQHandler ; DMA2 Channel2

DCD DMA2_Channel3_IRQHandler ; DMA2 Channel3

DCD DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel5

__Vectors_End //__Vectors_End 为向量表结束地址。

__Vectors_Size EQU __Vectors_End - __Vectors //__Vectors_Size则是向量表的大小,向量表的大小是通过__Vectors 和__Vectors_End 相减得到的。

AREA |.text|, CODE, READONLY //使用 AREA 指令,用于定义一个名为 .text 的内存区域。这一行代码指定了一个只读的代码区域,通常用于存储程序的可执行指令。

中断响应流程

1、中断信号发送到NVIC

2、NVIC通知CPU

3、CPU根据中断号得到的中断服务程序地址(基地址+中断编号*4B)

4、保存现场

5、执行中断服务函数

6、恢复现场

7、继续执行程序
例如:PendSV_Handler中断号是14,也就是14*4 = 56 == 0x38

而中断向量表的基地址 0x08000000 故改中断服务函数地址为 0x08000038

Thumb指令集末尾是奇数位1

ARM指令集末尾是偶数0

解析出来 0x080002D7,而map文件中确实:0x080002D6

原因:ARM指令集有ARM指令集和Thumb指令集,ARM指令集位数常;而Thumb指令集位数端,故占用内存较小,所有编译器大部分采用Thumb指令集。

2.4 复位处理程序

; Reset handler

Reset_Handler PROC //定义了一个服务程序,PROC表示程序的开始。

EXPORT Reset_Handler [WEAK] //使用EXPORT将Reset_Handler申明为可被外部引用,后面WEAK表示弱定义,如果外部文件定义了该标号则首先引用该标号,如果外部文件没有声明也不会出错。

IMPORT __main //__main 是一个标准的 C 库函数,主要作用是初始化用户堆栈,这个是由编译器完成的,该函数最终会调用我们自己写的main函数

IMPORT SystemInit //表示该标号来自外部文件,SystemInit()是一个库函数,在system_stm32f1xx.c中定义的

LDR R0, =SystemInit //表示从存储器中加载SystemInit到一个寄存器R0的地址中。

BLX R0 //表示跳转到寄存器R0的地址,并根据寄存器的 LSE 确定处理器的状态,还要把跳转前的下条指令地址保存到 LR。

LDR R0, =__main //表示从存储器中加载__main到一个寄存器R0的地址中。

BX R0 //跳转到至指定寄存器的地址后,不会返回。

ENDP //PROC是对应的,表示程序的结束。

2.5 中断服务程序

启动文件把这些中断服务函数留出来了,但是内容都是空的,真正的中断复服务程序需要我们在外部的 C 文件里面重新实现,这里只是提前占了一个位置罢了。B表示跳转,这里跳转到一个'.',即表示无线循环。

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

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

Default_Handler PROC

EXPORT WWDG_IRQHandler [WEAK]

EXPORT PVD_IRQHandler [WEAK]

EXPORT TAMPER_IRQHandler [WEAK]

EXPORT RTC_IRQHandler [WEAK]

EXPORT FLASH_IRQHandler [WEAK]

EXPORT RCC_IRQHandler [WEAK]

EXPORT EXTI0_IRQHandler [WEAK]

EXPORT EXTI1_IRQHandler [WEAK]

EXPORT EXTI2_IRQHandler [WEAK]

EXPORT EXTI3_IRQHandler [WEAK]

EXPORT EXTI4_IRQHandler [WEAK]

EXPORT DMA1_Channel1_IRQHandler [WEAK]

EXPORT DMA1_Channel2_IRQHandler [WEAK]

EXPORT DMA1_Channel3_IRQHandler [WEAK]

EXPORT DMA1_Channel4_IRQHandler [WEAK]

EXPORT DMA1_Channel5_IRQHandler [WEAK]

EXPORT DMA1_Channel6_IRQHandler [WEAK]

EXPORT DMA1_Channel7_IRQHandler [WEAK]

EXPORT ADC1_2_IRQHandler [WEAK]

EXPORT USB_HP_CAN1_TX_IRQHandler [WEAK]

EXPORT USB_LP_CAN1_RX0_IRQHandler [WEAK]

EXPORT CAN1_RX1_IRQHandler [WEAK]

EXPORT CAN1_SCE_IRQHandler [WEAK]

EXPORT EXTI9_5_IRQHandler [WEAK]

EXPORT TIM1_BRK_IRQHandler [WEAK]

EXPORT TIM1_UP_IRQHandler [WEAK]

EXPORT TIM1_TRG_COM_IRQHandler [WEAK]

EXPORT TIM1_CC_IRQHandler [WEAK]

EXPORT TIM2_IRQHandler [WEAK]

EXPORT TIM3_IRQHandler [WEAK]

EXPORT TIM4_IRQHandler [WEAK]

EXPORT I2C1_EV_IRQHandler [WEAK]

EXPORT I2C1_ER_IRQHandler [WEAK]

EXPORT I2C2_EV_IRQHandler [WEAK]

EXPORT I2C2_ER_IRQHandler [WEAK]

EXPORT SPI1_IRQHandler [WEAK]

EXPORT SPI2_IRQHandler [WEAK]

EXPORT USART1_IRQHandler [WEAK]

EXPORT USART2_IRQHandler [WEAK]

EXPORT USART3_IRQHandler [WEAK]

EXPORT EXTI15_10_IRQHandler [WEAK]

EXPORT RTC_Alarm_IRQHandler [WEAK]

EXPORT USBWakeUp_IRQHandler [WEAK]

EXPORT TIM8_BRK_IRQHandler [WEAK]

EXPORT TIM8_UP_IRQHandler [WEAK]

EXPORT TIM8_TRG_COM_IRQHandler [WEAK]

EXPORT TIM8_CC_IRQHandler [WEAK]

EXPORT ADC3_IRQHandler [WEAK]

EXPORT FSMC_IRQHandler [WEAK]

EXPORT SDIO_IRQHandler [WEAK]

EXPORT TIM5_IRQHandler [WEAK]

EXPORT SPI3_IRQHandler [WEAK]

EXPORT UART4_IRQHandler [WEAK]

EXPORT UART5_IRQHandler [WEAK]

EXPORT TIM6_IRQHandler [WEAK]

EXPORT TIM7_IRQHandler [WEAK]

EXPORT DMA2_Channel1_IRQHandler [WEAK]

EXPORT DMA2_Channel2_IRQHandler [WEAK]

EXPORT DMA2_Channel3_IRQHandler [WEAK]

EXPORT DMA2_Channel4_5_IRQHandler [WEAK]

WWDG_IRQHandler

PVD_IRQHandler

TAMPER_IRQHandler

RTC_IRQHandler

FLASH_IRQHandler

RCC_IRQHandler

EXTI0_IRQHandler

EXTI1_IRQHandler

EXTI2_IRQHandler

EXTI3_IRQHandler

EXTI4_IRQHandler

DMA1_Channel1_IRQHandler

DMA1_Channel2_IRQHandler

DMA1_Channel3_IRQHandler

DMA1_Channel4_IRQHandler

DMA1_Channel5_IRQHandler

DMA1_Channel6_IRQHandler

DMA1_Channel7_IRQHandler

ADC1_2_IRQHandler

USB_HP_CAN1_TX_IRQHandler

USB_LP_CAN1_RX0_IRQHandler

CAN1_RX1_IRQHandler

CAN1_SCE_IRQHandler

EXTI9_5_IRQHandler

TIM1_BRK_IRQHandler

TIM1_UP_IRQHandler

TIM1_TRG_COM_IRQHandler

TIM1_CC_IRQHandler

TIM2_IRQHandler

TIM3_IRQHandler

TIM4_IRQHandler

I2C1_EV_IRQHandler

I2C1_ER_IRQHandler

I2C2_EV_IRQHandler

I2C2_ER_IRQHandler

SPI1_IRQHandler

SPI2_IRQHandler

USART1_IRQHandler

USART2_IRQHandler

USART3_IRQHandler

EXTI15_10_IRQHandler

RTC_Alarm_IRQHandler

USBWakeUp_IRQHandler

TIM8_BRK_IRQHandler

TIM8_UP_IRQHandler

TIM8_TRG_COM_IRQHandler

TIM8_CC_IRQHandler

ADC3_IRQHandler

FSMC_IRQHandler

SDIO_IRQHandler

TIM5_IRQHandler

SPI3_IRQHandler

UART4_IRQHandler

UART5_IRQHandler

TIM6_IRQHandler

TIM7_IRQHandler

DMA2_Channel1_IRQHandler

DMA2_Channel2_IRQHandler

DMA2_Channel3_IRQHandler

DMA2_Channel4_5_IRQHandler

B .

ENDP

ALIGN

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

用STM32CubeMX生成的工程,在xxx_it.c文件中,对一些中断函数做了重定义,在这里对其进行定了,在汇编里面就不起作用了。

void NMI_Handler(void)

{

}

void HardFault_Handler(void)

{

/* Go to infinite loop when Hard Fault exception occurs */

while (1)

{

}

}

void MemManage_Handler(void)

{

/* Go to infinite loop when Memory Manage exception occurs */

while (1)

{

}

}

void BusFault_Handler(void)

{

/* Go to infinite loop when Bus Fault exception occurs */

while (1)

{

}

}

void UsageFault_Handler(void)

{

/* Go to infinite loop when Usage Fault exception occurs */

while (1)

{

}

}

void SVC_Handler(void)

{

}

void DebugMon_Handler(void)

{

}

void PendSV_Handler(void)

{

}

void SysTick_Handler(void)

{

HAL_IncTick();

}

2.6 用户堆和栈的初始化

;*******************************************************************************

; User Stack and Heap initialization

;*******************************************************************************

IF :DEF:__MICROLIB //如果 __MICROLIB 被定义,那么执行条件为真的代码块。否则,执行 ELSE 后面的代码块。

EXPORT __initial_sp // 栈顶

EXPORT __heap_base // 堆起始地址

EXPORT __heap_limit //栈结束地址,用于将这些符号导出,使它们在其他地方可见。

ELSE

IMPORT __use_two_region_memory //导入 __use_two_region_memory 符号.

EXPORT __user_initial_stackheap //导出 __user_initial_stackheap.

__user_initial_stackheap //用于初始化堆栈和堆的起始和结束位置.

LDR R0, = Heap_Mem //R0 寄存器加载了 Heap_Mem 的地址。

LDR R1, =(Stack_Mem + Stack_Size) //R1 寄存器加载了 (Stack_Mem + Stack_Size) 的地址,即栈的结束位置。

LDR R2, = (Heap_Mem + Heap_Size) //R2 寄存器加载了 (Heap_Mem + Heap_Size) 的地址,即堆的结束位置。

LDR R3, = Stack_Mem //R3 寄存器加载了 Stack_Mem 的地址,即栈的起始位置。

BX LR //BX LR 用于返回。

ALIGN //用于确保接下来的指令对齐。

ENDIF //表示条件编译块的结束。

END //表示文件结束。

在keil配置中,勾选Use MicroLIB,IF判断成功,使用这个功能能够有效的减小C库生成的代码,但它执行速度没有标准C库快。

如果没有勾选Use MicroLIB,则采用双段存储器模式,且声明标号__user_initial_stackheap具有全属性,让用户自己来初始化堆栈。

3、STM32启动流程分析

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

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

在.map文件中找到这两个地址

__initial_sp 0x20000660 Data 0 startup_stm32f707xe.o(STACK)

Reset_Handler 0x080002D0 Thumb Code 8 startup_stm32f707xe.o(.text)

3.2 设置系统时钟

Reset_Handler PROC

EXPORT Reset_Handler [WEAK]

IMPORT __main // 导入main

IMPORT SystemInit // 导入systemInit

LDR R0, =SystemInit // 讲SystemInit的地址给到R0寄存器

BLX R0 // 调用R0地址中的函数(调用SystemInit函数)

LDR R0, =__main //

BX R0 // 调用main函数

ENDP

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

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

接下来时钟初始化完成后,就调用__main,在指向main函数之前还会初始化堆、栈等,最后才进入C文件指向main函数。

相关推荐
智商偏低2 小时前
单片机之helloworld
单片机·嵌入式硬件
青牛科技-Allen3 小时前
GC3910S:一款高性能双通道直流电机驱动芯片
stm32·单片机·嵌入式硬件·机器人·医疗器械·水泵、
森焱森5 小时前
无人机三轴稳定控制(2)____根据目标俯仰角,实现俯仰稳定化控制,计算出升降舵输出
c语言·单片机·算法·架构·无人机
白鱼不小白5 小时前
stm32 USART串口协议与外设(程序)——江协教程踩坑经验分享
stm32·单片机·嵌入式硬件
S,D6 小时前
MCU引脚的漏电流、灌电流、拉电流区别是什么
驱动开发·stm32·单片机·嵌入式硬件·mcu·物联网·硬件工程
芯岭技术9 小时前
PY32F002A单片机 低成本控制器解决方案,提供多种封装
单片机·嵌入式硬件
youmdt9 小时前
Arduino IDE ESP8266连接0.96寸SSD1306 IIC单色屏显示北京时间
单片机·嵌入式硬件
嘿·嘘10 小时前
第七章 STM32内部FLASH读写
stm32·单片机·嵌入式硬件
Meraki.Zhang10 小时前
【STM32实践篇】:I2C驱动编写
stm32·单片机·iic·驱动·i2c
几个几个n12 小时前
STM32-第二节-GPIO输入(按键,传感器)
单片机·嵌入式硬件