ARM Cortex系列的MCU在复位、开机的时候, 都会从 0x00000000的内存地址上去获取向量表,特权等级的软件也可以通过写入VTOR寄存器来修改向量表存储地址, 这个值是相当对于Flash_Base地址的偏移量, 这样就可以让程序从不同的存储位置去获取向量表,注意:
VTOR寄存器存储 向量表的基地址相对于0x000000地址偏移值。
完整的向量表形式类似下图:
首先0x00的地址存储的是栈指针,即内核用于设置MSP寄存器的初始值, 而0x04的地址存储的是所有异常向量的起始地址, 也就是内核用于设置PC寄存器的初始值。这两个内存地址和大小在stm32芯片中, 可以通过MDK->TARGET
中配置。 注意: MSP寄存器最先被设置是很有必要的, 这是因为MCU在复位的时候,在很短的时间内很有可能产生NMI和HardFault的异常, 而在异常处理前,将处理器压栈便需要先进行栈存储的操作。
在使用MDK编写stm32的程序的时候,我们的设置一般类似于下图, 配置了只读内存空间的起始地址和大小,以及可读写内存空间的起始地址和大小, 一般这个配置都是根据MDK-->Device
中选择的文件来的, 每个型号的这个地址不一定相同, 所以型号要选对, 才能够确保程序被正确加载和启动。 注意: 可以设置mcu内置的内存空间, 也可以配置mcu外接的内存空间。
这个配置最终会输出到sct文件中, 通过sct文件将会指定只读内存空间的地址和大小(从0x800000开始),然后指定可读写内存空间的地址和大小(从0x20000000开始)。
分散加载文件是一个文本文件,通过编写一个分散加载文件来指定ARM连接器在生成映像文件时如何分配RO,RW,ZI等数据的存放地址。
其中,RESET,+First
表示会启动代码里的创建的RESET数据段放置在内存最前面, 分散加载文件中的ER_IROM会将RO数据存储于0x8000000这个区域, RW_IRAM1会将 RW+ZI数据存储于0x20000000这个区域。
而这其中和汇编代码的关系就在于, 启动代码会去开辟RW内存区域作为栈, 开辟RI内存区域存放向量表, 详情参考最下方的启动程序。
这块知识内容, 可以参考《 CM3启动分析及文件分散加载》。
既然arm都规定了上电后, MCU从0x0000000地址开始取值执行, 为何stm32在烧写的时候, 配置RO内存的位置是从0x08000000的地址开始呢? 这个问题是因为ST公司通过了地址映射来设计了三种启动模式。如下图所示:
该部分的内容主要参考st官方提供的汇编代码, 主要做了以下工作。
要注意: 向量表的第一个地址存储的值会被拿来用于后续初始化栈指针MSP, 向量表的第二个地址存储的值 会被拿来用于初始化PC寄存器的初始值。
# 栈和堆的空间会通过向量表的第一个字存储的栈地址开辟使用。
Stack_Size EQU 0x00000400 #定义宏的值,也就是开辟空间的大小。
AREA STACK, NOINIT, READWRITE, ALIGN=3 #在SRAM中, 开辟一个空间标号为STACK,可读写,并且不进行初始化
Stack_Mem SPACE Stack_Size
__initial_sp # 该标号表示开辟空间之后的内存位置, 也就是栈顶地址。
Heap_Size EQU 0x00000200
AREA HEAP, NOINIT, READWRITE, ALIGN=3 #在SRAM中, 分配完栈之后, 再次开辟一个HEAP的空间
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit # 该标号表示开辟空间之后的内存位置, 也就是栈顶地址。
PRESERVE8
THUMB
; Vector Table Mapped to Address 0 at Reset
AREA RESET, DATA, READONLY #开辟只读数据段来存储数据并命名为RESET, 该数据段将会被按照MDK中地址的配置写入到MCU中。
EXPORT __Vectors
EXPORT __Vectors_End #将标号声明为全局属性,并且该标号可以被C语言所使用。
EXPORT __Vectors_Size
__Vectors DCD __initial_sp ; Top of Stack #分配一个字的内存来存储栈顶地址。。
DCD Reset_Handler ; Reset Handler #分配一个字的内存来存储Reset_Handler标号的地址。
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SVC_Handler ; SVCall Handler
DCD 0 ; Reserved
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 RTC_IRQHandler ; RTC through EXTI Line
DCD FLASH_IRQHandler ; FLASH
DCD RCC_IRQHandler ; RCC
DCD EXTI0_1_IRQHandler ; EXTI Line 0 and 1
DCD EXTI2_3_IRQHandler ; EXTI Line 2 and 3
DCD EXTI4_15_IRQHandler ; EXTI Line 4 to 15
DCD 0 ; Reserved
DCD DMA1_Channel1_IRQHandler ; DMA1 Channel 1
DCD DMA1_Channel2_3_IRQHandler ; DMA1 Channel 2 and Channel 3
DCD DMA1_Channel4_5_6_7_IRQHandler ; DMA1 Channel 4, Channel 5, Channel 6 and Channel 7
DCD ADC1_COMP_IRQHandler ; ADC1, COMP1 and COMP2
DCD LPTIM1_IRQHandler ; LPTIM1
DCD USART4_5_IRQHandler ; USART4 and USART5
DCD TIM2_IRQHandler ; TIM2
DCD TIM3_IRQHandler ; TIM3
DCD TIM6_IRQHandler ; TIM6
DCD TIM7_IRQHandler ; TIM7
DCD 0 ; Reserved
DCD TIM21_IRQHandler ; TIM21
DCD I2C3_IRQHandler ; I2C3
DCD TIM22_IRQHandler ; TIM22
DCD I2C1_IRQHandler ; I2C1
DCD I2C2_IRQHandler ; I2C2
DCD SPI1_IRQHandler ; SPI1
DCD SPI2_IRQHandler ; SPI2
DCD USART1_IRQHandler ; USART1
DCD USART2_IRQHandler ; USART2
DCD LPUART1_IRQHandler ; LPUART1
DCD 0 ; Reserved
DCD 0 ; Reserved
__Vectors_End
__Vectors_Size EQU __Vectors_End - __Vectors
AREA |.text|, CODE, READONLY #开辟只读的程序段
; Reset handler routine
Reset_Handler PROC # 编写Reset_Handler函数
EXPORT Reset_Handler [WEAK] #声明为全局标号, 并且可以被C语言所使用, 弱定义表示该标号可以被外部文件所取代。
IMPORT __main #引入外部的标号, 类似C语言的Extern, 引入C语言中标号。
IMPORT SystemInit # 引入外部的标号, 类似C语言的Extern, 引入C语言中标号。
LDR R0, =SystemInit #将函数的入口地址加载到R0寄存器
BLX R0 #跳转到寄存器
LDR R0, =__main #跳转到_main函数中。
BX R0
ENDP #定义一个执行过程, 由于main函数一般会写一个循环, 则下面的不执行。
; Dummy Exception Handlers (infinite loops which can be modified)
NMI_Handler PROC #编写NMI_Handler函数
EXPORT NMI_Handler [WEAK] #此处的弱定义,表示会被我们在C语言写的函数所取代, 但是, 如果我们把中断函数名字写错了, 就会默认跳转到这个地方, 一致执行此处的死循环。
B . # 执行死循环。
ENDP
HardFault_Handler\
PROC
EXPORT HardFault_Handler [WEAK]
B .
ENDP
SVC_Handler PROC
EXPORT SVC_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 RTC_IRQHandler [WEAK]
EXPORT FLASH_IRQHandler [WEAK]
EXPORT RCC_IRQHandler [WEAK]
EXPORT EXTI0_1_IRQHandler [WEAK]
EXPORT EXTI2_3_IRQHandler [WEAK]
EXPORT EXTI4_15_IRQHandler [WEAK]
EXPORT DMA1_Channel1_IRQHandler [WEAK]
EXPORT DMA1_Channel2_3_IRQHandler [WEAK]
EXPORT DMA1_Channel4_5_6_7_IRQHandler [WEAK]
EXPORT ADC1_COMP_IRQHandler [WEAK]
EXPORT LPTIM1_IRQHandler [WEAK]
EXPORT USART4_5_IRQHandler [WEAK]
EXPORT TIM2_IRQHandler [WEAK]
EXPORT TIM3_IRQHandler [WEAK]
EXPORT TIM6_IRQHandler [WEAK]
EXPORT TIM7_IRQHandler [WEAK]
EXPORT TIM21_IRQHandler [WEAK]
EXPORT TIM22_IRQHandler [WEAK]
EXPORT I2C1_IRQHandler [WEAK]
EXPORT I2C2_IRQHandler [WEAK]
EXPORT I2C3_IRQHandler [WEAK]
EXPORT SPI1_IRQHandler [WEAK]
EXPORT SPI2_IRQHandler [WEAK]
EXPORT USART1_IRQHandler [WEAK]
EXPORT USART2_IRQHandler [WEAK]
EXPORT LPUART1_IRQHandler [WEAK]
WWDG_IRQHandler
PVD_IRQHandler
RTC_IRQHandler
FLASH_IRQHandler
RCC_IRQHandler
EXTI0_1_IRQHandler
EXTI2_3_IRQHandler
EXTI4_15_IRQHandler
DMA1_Channel1_IRQHandler
DMA1_Channel2_3_IRQHandler
DMA1_Channel4_5_6_7_IRQHandler
ADC1_COMP_IRQHandler
LPTIM1_IRQHandler
USART4_5_IRQHandler
TIM2_IRQHandler
TIM3_IRQHandler
TIM6_IRQHandler
TIM7_IRQHandler
TIM21_IRQHandler
TIM22_IRQHandler
I2C1_IRQHandler
I2C2_IRQHandler
I2C3_IRQHandler
SPI1_IRQHandler
SPI2_IRQHandler
USART1_IRQHandler
USART2_IRQHandler
LPUART1_IRQHandler
B .
ENDP
ALIGN
;*******************************************************************************
; User Stack and Heap initialization
;*******************************************************************************
IF :DEF:__MICROLIB
EXPORT __initial_sp #将初始化栈顶地址声明为全局标号, 这几个标号最终会在_main函数中被调用。
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
;************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE*****
map文件
。注意,弱定义的函数,用户可再次在C语言中修改
, 然后, 执行system_init初始化函数, 然后进入__main()库函数中, 通过获得的栈堆的空间地址和空间大小初始化堆栈, 最终跳转到用户的main()函数中。