STM32启动代码原理分析
STM32启动代码原理分析(底层技术); 简述 ARM Cortex-M系列MCU的启动代码(使用汇编语言编程则不需要)主要做3件事情: 初始化并正确放置异常/中断矢量表; 分散加载; 初始化C语言运行环境(初始化堆栈以及C Library、浮点等)。 Cortex-M3内核规定,起始地址必须存放堆顶指针,而第二个地址则必须存放复位中断入口矢量地址,这样在Cortex-M3内核复位后,会自动从起始地址的下一个32位空间取出复位中断入口矢量,跳转执行复位中断服务程序。对比ARM7/ARM9内核,Cortex-M3内核则是固定了中断矢量表的位置而起始地址是可变化的。 源码分析 基于STM32F103C6T6的启动文件startup_stm32f103x6.s的简要说明如下: ;******************** (C) COPYRIGHT 2017 STMicroelectronics ******************** ;* File Name : startup_stm32f103x6.s ;* Author : MCD Application Team ;* Description : STM32F103x6 Devices vector table for MDK-ARM toolchain. ;* This module performs: ;* - Set the initial SP ;* - Set the initial PC == Reset_Handler ;* - Set the vector table entries with the exceptions ISR address ;* - Configure the clock system ;* - Branches to __main in the C library (which eventually ;* calls main()). ;* After Reset the Cortex-M3 processor is in Thread mode, ;* priority is Privileged, and the Stack is set to Main. ;****************************************************************************** ;* @attention ;* ;* Copyright (c) 2017 STMicroelectronics. ;* All rights reserved. ;* ;* This software component is licensed by ST under BSD 3-Clause license, ;* the "License"; You may not use this file except in compliance with the ;* License. You may obtain a copy of the License at: ;* opensource.org/licenses/BSD-3-Clause ;* ;****************************************************************************** ; Amount of memory (in bytes) allocated for Stack ; Tailor this value to your application needs ; Stack Configuration ; Stack Size (in Bytes) ; Stack_Size EQU 0x400 ;声明栈的大小为0x400字节 AREA STACK, NOINIT, READWRITE, ALIGN=3 Stack_Mem SPACE Stack_Size ;开辟一段大小为Stack_Size的内存空间作为栈 __initial_sp ;标号__initial_sp,表示栈空间顶地址。 ; Heap Configuration ; Heap Size (in Bytes) ; Heap_Size EQU 0x200 ;声明栈的大小为0x200字节 AREA HEAP, NOINIT, READWRITE, ALIGN=3 __heap_base ;标号__heap_base,表示堆空间起始地址。 Heap_Mem SPACE Heap_Size ;开辟一段大小为Heap_Size的内存空间作为堆。 __heap_limit ;标号__heap_limit,表示堆空间结束地址。 ;-------------------------------------------- ;第一部分: ;启动代码最重要的工作是把异常中断向量表放到正确的Flash地址上 ;把向量表定义为只读数据段,并导出向量表标号(Symbol),让链接器识别此标号并根据分散加载文件正确的放置向量表 ;__Vectors标号需要与分散加载文件合起来看,才会明白其真正的功能 ;-------------------------------------------- PRESERVE8 ;告诉编译器以8字节对齐。 THUMB ;告诉编译器使用THUMB指令集。 ; Vector Table Mapped to Address 0 at Reset AREA RESET, DATA, READONLY ;声明权限为“READONLY”的名称为“RESET”的数据段 ;(假设STM32从FLASH启动,则此中断矢量表起始地址即为0x8000000) EXPORT __Vectors ;将标号__Vectors声明为全局标号,这样外部文档就可以使用这个标号。 EXPORT __Vectors_End EXPORT __Vectors_Size ;标号__Vectors,表示中断矢量表入口地址 ;创建中断矢量表 ;--------------------------------------- ;第二部分: ;__initial_sp ; 1、栈顶指针地址,此语法跟MDK编译器的底层相关,是ARMCC编译器才能识别的语法 ; GCC与IAR的底层编译器ICCARM编译器不能识别; ; 2、__initial_sp 是一个链接器Image Symbol; ; 3、此处__initial_sp相当于是顶地址,或者此处直接把顶地址写到此处也行(如:0x20004000); ; 4、__initial_sp具体是多少,在此种写法下,是由分散加载文件决定的,下文会有详细论述; ; ;Reset_Handler: ; 1、Reset_Handler函数地址,此处相当于把Reset_Handler函数地址赋值给PC,即调用Reset_Handler函数; ; 2、此处也可以是其他函数,只是把复位函数放于此处最符合实际应用场景。 ; 重要关键节点: ; 1、绝大多数cortex-M微控制器(M0、M3、M4都是这样)复位后先进入厂商BOOTROM,此时所有用户行为均无法介入处理器; ; 2、厂商BOOTROM(有些厂商会有其他名称来称呼此功能) 主要负责处理一些芯片最初级初始化、 ; 加密以及一些对MCU的差异化设置等工作: ; 3、BOOTROM顺利完成后,MCu控制权会交给用户,即启动代码; ; 4、启动代码(运行汇编语言则不需要此启动代码),最重要的工作在于设置MSP (主堆栈指针)以及PC(程序计数器)的值; ; 5、Cortex-M微控制器会默认把0x00000000地址里面的值设置为MSP的值,0x00000004地址里面的值设置为PC的值; ; 6、5中的默认地址可以通过修改Cortex-M中的VTOR寄存器来重新映射,比如改到0x20000000地址或其他; ; ;关于Exception(异常) 与Interrupt (中断) 的区别说明 ; 1、Exception(异常)与Interrupt(中断)是不同的,是两个不同的概念,很多人会混淆两者, ; 把他们都按照中断来看待,这是错误的; ; 2、Exception(异常)是向量表的前16个向量,其优先级为负数,高于所有中断,而且不可调整优先级也不可关闭, ; 可以打断正常程序与Interrupt(中断) 的运行: ; 3、从第16个向量以后才是Interrupt(中断),可以设置优先级,不用时可以关闭,但优先级永远低于Exception(异常); ; 4、从这里大家就可以理解Pendsy Handler与svsTick Handler为什么会被用于嵌入式操作系统, ; 因为其优先级高于所有中断,可以确保操作系统拥有高于普通用户程序执行的超级权限, ; SVC_Handler有时也会用于操作系统,原理相同; ; 5、用户应用程序应尽量避免使用Exception(异常); ;--------------------------------------- __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 操作系统会用到的心跳定时器异常向量(没有操作系统时可以用作普通定时器中断) ;--------------------------------------- ;第三部分: ; 1、这里开始是中断向量表; ; 2、各个向量的顺序是芯片设计的时候就定义好的,不能更改; ;--------------------------------------- ; 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_2 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 0 ; Reserved DCD I2C1_EV_IRQHandler ; I2C1 Event DCD I2C1_ER_IRQHandler ; I2C1 Error DCD 0 ; Reserved DCD 0 ; Reserved DCD SPI1_IRQHandler ; SPI1 DCD 0 ; Reserved DCD USART1_IRQHandler ; USART1 DCD USART2_IRQHandler ; USART2 DCD 0 ; Reserved DCD EXTI15_10_IRQHandler ; EXTI Line 15..10 DCD RTC_Alarm_IRQHandler ; RTC Alarm through EXTI Line DCD USBWakeUp_IRQHandler ; USB Wakeup from suspend __Vectors_End __Vectors_Size EQU __Vectors_End - __Vectors ;--------------------------------------- ;第四部分: ;这部分开始可以称作Reset Handler实体,芯片上电后,经过BOOTROM后进入的用户可控最开始处的地方; ; 1、如果想让Mcu正常使用c语言,务必在此处调用 main函数; ; 2、__main() 不是main() 两者有着本质性的区别; ; 3、__main()是c Library中的函数,Kei1开发环境中自带的c Library中的函数; ; 4、main()是被 __main()调用的,__main()工作完成后最后一步就是调用main(); ; 5、__main()被调用之前,可以根据需要插入一个或多个其他功能函数; ;--------------------------------------- AREA |.text|, CODE, READONLY ;定义只读的代码段 ; Reset handler routine ;复位中断服务程序,PROC…ENDP结构表示程序的开始和结束。 Reset_Handler PROC EXPORT Reset_Handler [WEAK] ;声明复位中断矢量Reset_Handler为全局属性 ;这样外部文档就可以调用此复位中断服务。 IMPORT __main ;声明__main标号。 IMPORT SystemInit ;声明SystemInit标号。 LDR R0, =SystemInit ;跳转SystemInit地址执行 BLX R0 LDR R0, =__main ;跳转__main地址执行 BX R0 ENDP ; Dummy Exception Handlers (infinite loops which can be modified) ;--------------------------------------------------------------- ;第五部分: ; 1、[weak]指定了一个这个函数为"弱函数”; ; 2、这些中断服务函教定义成弱函数的意义是,当中断出现时,需要有一个中断服务函数予以响应,但真实的 ; 用户程序往往只会使用一部分中断,甚至不使用中断,所以以下这些函数给出了异常/中断服务函数的 ; 默认实现,很简单,默认实现就是死循环汇编中的"B."语句,相当于while(1);因为不知道用户是否会 ; 用到多少中断,但这些服务函数又很重要,所以就把这些函数都"实现"并声明为弱函数; ; 3、弱函数的意思是如果用户定义了同样名称的另一个函数,那么默认实现的弱函数就会被覆盖,比如 ; HardFault_Handler异常在下面有一个默认的实现,但这种默认的实现不能满足我的需要的时候,我可以 ; 再重新定义一个HardEault Handler函数这个新定义的HardFault_Handler函数会覆盖原有的被声明 ; 为[WEAK]的弱函数; ; 4、有很多种适合使用弱函数的场合,默认的异常/中断服务函数只是一种应用场景; ; 5、在C语言中声明弱函数是在函数后加“__attribute((weak))”; ;---------------------------------------------------------------- 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 I2C1_EV_IRQHandler [WEAK] EXPORT I2C1_ER_IRQHandler [WEAK] EXPORT SPI1_IRQHandler [WEAK] EXPORT USART1_IRQHandler [WEAK] EXPORT USART2_IRQHandler [WEAK] EXPORT EXTI15_10_IRQHandler [WEAK] EXPORT RTC_Alarm_IRQHandler [WEAK] EXPORT USBWakeUp_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 I2C1_EV_IRQHandler I2C1_ER_IRQHandler SPI1_IRQHandler USART1_IRQHandler USART2_IRQHandler EXTI15_10_IRQHandler RTC_Alarm_IRQHandler USBWakeUp_IRQHandler B . ENDP ALIGN ;******************************************************************************* ; User Stack and Heap initialization ;******************************************************************************* ;IF…ELSE…ENDIF结构,判断是否使用DEF:__MICROLIB(此处为不使用)。 ;若使用DEF:__MICROLIB,则将__initial_sp,__heap_base,__heap_limit ;亦即栈顶地址,堆始末地址赋予全局属性,使外部程序可以使用。 IF :DEF:__MICROLIB 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 ;标号__user_initial_stackheap,表示用户堆栈初始化程序入口 ;分别保存栈顶指针和栈大小,堆始地址和堆大小至R0,R1,R2,R3寄存器。 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***** 部分解释 以上便是STM32的启动代码的完整解析,接下来对几个小地方做解释: ...