首先介绍一下STM32的三种启动模式
当STM32芯片上电或复位时,代码始终都是从0x00000000开始的,也就是说三种启动模式其实都是将三块存储分区映射到0x00000000处。
-
主闪存存储器(Main Flash memory)启动
STM32从内置的Flash启动(0x0800 0000-0x0807 FFFF),在使用JTAG或者SWD模式下载程序时,就是下载到这个里面,重启后也直接从这启动程序。 -
系统存储器(System memory)启动
STM32从系统存储器启动(0x1FFFF000 - 0x1FFF F7FF),这种模式启动的程序功能是由厂家设置的。一般来说,我们选用这种启动模式时,是为了通过串口烧录程序。 -
片上SRAM(Embedded SRAM)启动
从内置SRAM启动(0x2000 0000-0x3FFFFFFF),既然是SRAM,自然也就没有程序存储的能力了,这个模式一般用于程序调试。SRAM 只能从0x20000000进行操作,与上述两者不同。从SRAM 启动时,需要在应用程序初始化代码中重新设置向量表的位置。

分析STM32上电后第一个执行的程序代码startup文件
下面以STM32F407的启动文件为例
1.栈的设置
Stack_Size EQU 0x400
AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp
这段主要进行了Stack栈的设置。栈是编译器自动分配和释放的一块连续内存,NOINIT (不初始化),READWRITE(可读可写),ALIGN=3(8 字节对齐)。
SPACE表示申请Stack_Size大小(0x400 1kb)的内存。
__initial_sp表示栈顶地址。特别声明一点,栈的顺序是由高到低的。
2.堆的设置
Heap_Size EQU 0x200
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
PRESERVE8
THUMB
和栈相反,堆是程序主动申请释放的一块内存,一般不连续,NOINIT (不初始化),READWRITE(可读可写),ALIGN=3(8 字节对齐)。
SPACE Heap_Size申请了一块Heap_Size大小(0x200 512b)的内存。
__heap_limit表示堆的结束地址。
PRESERVE8表示当前文件的堆栈以8字节对齐。
THUMB表示支持THUMB指令集,当前Cortex-M系列支持THUMB-2指令集,THUMB-2是THUMB的超集同时支持16位和32位指令。
指令集就是CPU能直接读懂的代码,直观来讲就是像1110 00 0 0100 0001 0000 000000000001 这样由01组成的指令。
3.向量表
; 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 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 DMA1_Stream7_IRQHandler ; DMA1 Stream7
DCD FMC_IRQHandler ; FMC
DCD SDIO_IRQHandler ; SDIO
DCD TIM5_IRQHandler ; TIM5
DCD SPI3_IRQHandler ; SPI3
DCD UART4_IRQHandler ; UART4
DCD UART5_IRQHandler ; UART5
DCD TIM6_DAC_IRQHandler ; TIM6 and DAC1&2 underrun errors
DCD TIM7_IRQHandler ; TIM7
DCD DMA2_Stream0_IRQHandler ; DMA2 Stream 0
DCD DMA2_Stream1_IRQHandler ; DMA2 Stream 1
DCD DMA2_Stream2_IRQHandler ; DMA2 Stream 2
DCD DMA2_Stream3_IRQHandler ; DMA2 Stream 3
DCD DMA2_Stream4_IRQHandler ; DMA2 Stream 4
DCD ETH_IRQHandler ; Ethernet
DCD ETH_WKUP_IRQHandler ; Ethernet Wakeup through EXTI line
DCD CAN2_TX_IRQHandler ; CAN2 TX
DCD CAN2_RX0_IRQHandler ; CAN2 RX0
DCD CAN2_RX1_IRQHandler ; CAN2 RX1
DCD CAN2_SCE_IRQHandler ; CAN2 SCE
DCD OTG_FS_IRQHandler ; USB OTG FS
DCD DMA2_Stream5_IRQHandler ; DMA2 Stream 5
DCD DMA2_Stream6_IRQHandler ; DMA2 Stream 6
DCD DMA2_Stream7_IRQHandler ; DMA2 Stream 7
DCD USART6_IRQHandler ; USART6
DCD I2C3_EV_IRQHandler ; I2C3 event
DCD I2C3_ER_IRQHandler ; I2C3 error
DCD OTG_HS_EP1_OUT_IRQHandler ; USB OTG HS End Point 1 Out
DCD OTG_HS_EP1_IN_IRQHandler ; USB OTG HS End Point 1 In
DCD OTG_HS_WKUP_IRQHandler ; USB OTG HS Wakeup through EXTI
DCD OTG_HS_IRQHandler ; USB OTG HS
DCD DCMI_IRQHandler ; DCMI
DCD 0 ; Reserved
DCD HASH_RNG_IRQHandler ; Hash and Rng
DCD FPU_IRQHandler ; FPU
__Vectors_End
__Vectors_Size EQU __Vectors_End - __Vectors
AREA |.text|, CODE, READONLY
向量表是一个WORD(32bit)数组,数组的每个位置都对应一种异常(中断也属于异常的一种)程序,对应的值则是其ESR(Exception Syndrome Register异常综合寄存器 在Cortex-M中并不存在 对应应该是状态寄存器(xxx_SR))的入口地址,这一整张的向量表的地址是可以更改的,在NVIC中由重定位寄存器来指出向量表的地址,在复位后,寄存器的值置0,所有向量表的地址位置应该是0。
DCD后面的0是复位后MSP(Main Stack Pointer主堆栈指针寄存器)的值。这篇说的各种寄存器会在下一篇文章中进行统一总结和归纳,觉得烧脑的同学可以详细看看下一篇文章。
AREA RESET, DATA, READONLY 表示申请一块READONLY (只读)的代码段。
EXPORT 表示__Vectors __Vectors_End __Vectors_Size 均可被外部引用并有全局属性。
__Vectors 表示向量表的起始地址。
__Vectors_End 表示向量表的结束地址。
__Vectors_Size 表示向量表的大小。
DCD 表示分配4字节的空间,每执行一次DCD就会生成一个特定的4字节的二进制代码。
4.复位程序
复位程序就是板子在复位后执行的第一个程序,它也属于中断程序的一种。
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT SystemInit
IMPORT __main
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
Reset_Handler表示声明一个程序,PROC 表示程序的开始。
EXPORT表示程序可以被外部引用,[WEAK] 表示弱定义。
IMPORT 引入了两段函数SystemInit __main ,SystemInit 在system_stm32f4xx中进行了定义,__main 则是对初始化用户堆栈程序,最终会调用用户的main函数。
LDR R0, =SystemInit 表示将SystemInit 载入寄存器R0地址中。
BLX R0 表示跳转到寄存器R0,根据寄存器的LSE确定状态,并将跳转前的下条指令地址保存到LR。
LDR R0, =__main 同理,不再过多赘述
BX R0 只跳转不返回。
ENDP 表示程序的结束。
5.中断服务程序
这部分就没什么好说的了,内容较为统一,都是允许被外部引用,B . 表示无限循环。
ALIGN表示对指令或者数据存放的地址进行对齐,缺省表示4字节对齐。
6.堆栈的初始化
IF :DEF:__MICROLIB
EXPORT __initial_sp
EXPORT __heap_base
EXPORT __heap_limit
ELSE
IMPORT __use_two_region_memory
EXPORT __user_initial_stackheap
显而易见,影响堆栈初始化的唯一判断条件就是MICROLIB的开启与否。
结尾
至此,你已经对STM32启动流程有一定认识了,下一篇文章我们会详细介绍STM32当中的寄存器。