STM32启动文件代码解析
目錄
- 啟動流程
- 代碼詳解
- 啟動文件使用的 ARM 匯編指令匯總
- 關于與啟動文件有關的一些問題思考
下面是F1固件庫V3.5.0的啟動文件startup_stm32f10x_hd.s,以此為例做解析,其余的型號的啟動文件都差不多,主要區別在于因片上外設不同,支持的中斷就不一樣,所以在啟動文件的中斷向量表不同型號會有差別。
啟動流程
啟動文件由匯編編寫,是系統上電復位后第一個執行的程序。主要做了以下工作:
1、初始化堆棧指針 SP=_initial_sp
2、初始化 PC指針=Reset_Handler
3、初始化中斷向量表
4、配置系統時鐘
5、調用 C庫函數_main 初始化用戶堆棧,從而最終調用 main 函數去到 C的世界
代碼詳解
下面是用于KEIL中啟動文件,加入了較詳細的注釋:
; 分配給堆棧的內存量(以字節為單位) ; 根據應用程序的需要定制此值 ; <h> Stack Configuration 棧配置 ; <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8> ; </h> Stack_Size EQU 0x00000400 ;類似于:#define Stack_Size 0x00000400AREA STACK, NOINIT, READWRITE, ALIGN=3 ;偽指令AREA,表示名字為 STACK,NOINIT 即不初始化,可讀可寫,8(2^3)字節對齊。 Stack_Mem SPACE Stack_Size ;開辟一段大小為Stack_Size的內存空間作為棧,Stack_Size已經定義為了0x00000400(1KB) __initial_sp ;標號__initial_sp緊挨著SPACE語句放置,表示棧的結束地址,即棧頂地址,棧是由高向低生長的。; <h> Heap Configuration 堆配置 ; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8> ; </h>Heap_Size EQU 0x00000200 ;類似于:#define Heap_Size 0x00000200AREA HEAP, NOINIT, READWRITE, ALIGN=3 ;偽指令AREA,表示名字為 HEAP,NOINIT即不初始化,可讀可寫,8(2^3)字節對齊 __heap_base ; __heap_base 表示對的起始地址 Heap_Mem SPACE Heap_Size ;開辟堆的大小為 0X00000200(512字節) __heap_limit ; __heap_limit 表示堆的結束地址。堆是由低向高生長的,跟棧的生長方向相反PRESERVE8 ; 指定當前文件的堆棧按照 8字節對齊THUMB ; 表示后面指令兼容 THUMB 指令,THUBM 是 ARM 以前的指令集, 16bit,現在 Cortex-M 系列的都使用 THUMB-2 指令集, THUMB-2 是 32 位的,兼容 16 位和 32 位的指令,是 THUMB 的超集。; Vector Table Mapped to Address 0 at ResetAREA RESET, DATA, READONLY ;定義一個數據段,名字為 RESET,可讀。EXPORT __Vectors ;聲明 __Vectors、__Vectors_End 和__Vectors_Size這三個標號具有全局屬性,可供外部的文件調用。EXPORT __Vectors_End ;EXPORT :聲明一個標號可被外部的文件使用,使標號具有全局屬性。EXPORT __Vectors_Size;__Vectors 為向量表起始地址 DCD: 以字為單位分配內存,要求 4字節對齊,并要求初始化這些內存 __Vectors DCD __initial_sp ; Top of Stack 棧頂地址MSPDCD Reset_Handler ; Reset Handler 復位程序地址DCD NMI_Handler ; NMI HandlerDCD HardFault_Handler ; Hard Fault HandlerDCD MemManage_Handler ; MPU Fault HandlerDCD BusFault_Handler ; Bus Fault HandlerDCD UsageFault_Handler ; Usage Fault HandlerDCD 0 ; Reserved 0表示保留DCD 0 ; ReservedDCD 0 ; ReservedDCD 0 ; ReservedDCD SVC_Handler ; SVCall HandlerDCD DebugMon_Handler ; Debug Monitor HandlerDCD 0 ; ReservedDCD PendSV_Handler ; PendSV HandlerDCD SysTick_Handler ; SysTick Handler; External Interrupts 以下是外部中斷DCD WWDG_IRQHandler ; Window WatchdogDCD PVD_IRQHandler ; PVD through EXTI Line detectDCD TAMPER_IRQHandler ; TamperDCD RTC_IRQHandler ; RTCDCD FLASH_IRQHandler ; FlashDCD RCC_IRQHandler ; RCCDCD EXTI0_IRQHandler ; EXTI Line 0DCD EXTI1_IRQHandler ; EXTI Line 1DCD EXTI2_IRQHandler ; EXTI Line 2DCD EXTI3_IRQHandler ; EXTI Line 3DCD EXTI4_IRQHandler ; EXTI Line 4DCD DMA1_Channel1_IRQHandler ; DMA1 Channel 1DCD DMA1_Channel2_IRQHandler ; DMA1 Channel 2DCD DMA1_Channel3_IRQHandler ; DMA1 Channel 3DCD DMA1_Channel4_IRQHandler ; DMA1 Channel 4DCD DMA1_Channel5_IRQHandler ; DMA1 Channel 5DCD DMA1_Channel6_IRQHandler ; DMA1 Channel 6DCD DMA1_Channel7_IRQHandler ; DMA1 Channel 7DCD ADC1_2_IRQHandler ; ADC1 & ADC2DCD USB_HP_CAN1_TX_IRQHandler ; USB High Priority or CAN1 TXDCD USB_LP_CAN1_RX0_IRQHandler ; USB Low Priority or CAN1 RX0DCD CAN1_RX1_IRQHandler ; CAN1 RX1DCD CAN1_SCE_IRQHandler ; CAN1 SCEDCD EXTI9_5_IRQHandler ; EXTI Line 9..5DCD TIM1_BRK_IRQHandler ; TIM1 BreakDCD TIM1_UP_IRQHandler ; TIM1 UpdateDCD TIM1_TRG_COM_IRQHandler ; TIM1 Trigger and CommutationDCD TIM1_CC_IRQHandler ; TIM1 Capture CompareDCD TIM2_IRQHandler ; TIM2DCD TIM3_IRQHandler ; TIM3DCD TIM4_IRQHandler ; TIM4DCD I2C1_EV_IRQHandler ; I2C1 EventDCD I2C1_ER_IRQHandler ; I2C1 ErrorDCD I2C2_EV_IRQHandler ; I2C2 EventDCD I2C2_ER_IRQHandler ; I2C2 ErrorDCD SPI1_IRQHandler ; SPI1DCD SPI2_IRQHandler ; SPI2DCD USART1_IRQHandler ; USART1DCD USART2_IRQHandler ; USART2DCD USART3_IRQHandler ; USART3DCD EXTI15_10_IRQHandler ; EXTI Line 15..10DCD RTCAlarm_IRQHandler ; RTC Alarm through EXTI LineDCD USBWakeUp_IRQHandler ; USB Wakeup from suspendDCD TIM8_BRK_IRQHandler ; TIM8 BreakDCD TIM8_UP_IRQHandler ; TIM8 UpdateDCD TIM8_TRG_COM_IRQHandler ; TIM8 Trigger and CommutationDCD TIM8_CC_IRQHandler ; TIM8 Capture CompareDCD ADC3_IRQHandler ; ADC3DCD FSMC_IRQHandler ; FSMCDCD SDIO_IRQHandler ; SDIODCD TIM5_IRQHandler ; TIM5DCD SPI3_IRQHandler ; SPI3DCD UART4_IRQHandler ; UART4DCD UART5_IRQHandler ; UART5DCD TIM6_IRQHandler ; TIM6DCD TIM7_IRQHandler ; TIM7DCD DMA2_Channel1_IRQHandler ; DMA2 Channel1DCD DMA2_Channel2_IRQHandler ; DMA2 Channel2DCD DMA2_Channel3_IRQHandler ; DMA2 Channel3DCD DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel5 __Vectors_End ; __Vectors_End 為向量表結束地址__Vectors_Size EQU __Vectors_End - __Vectors ;__Vectors 為向量表起始地址, __Vectors_End 為向量表結束地址,兩個相減即可算出向量表大小。AREA |.text|, CODE, READONLY ;定義一個名稱為.text 的代碼段,可讀; Reset handler復位程序 Reset_Handler PROC ;PROC 定義子程序,與 ENDP 成對使用,表示子程序結束EXPORT Reset_Handler [WEAK] ;WEAK表示弱定義IMPORT __main ;IMPORT類似于extern,表示__main和SystemInit都來自外部文件,__main 是一個標準的 C 庫函數,主要作用是初始化用戶堆棧,并在函數的最后調用main 函數去到 C 的世界IMPORT SystemInit ;SystemInit()是一個標準的庫函數,在system_stm32f10x.c這個庫文件中定義。主要作用是配置系統時鐘LDR R0, =SystemInitBLX R0 LDR R0, =__mainBX R0ENDP; Dummy Exception Handlers (infinite loops which can be modified無限循環可以被修改)NMI_Handler PROCEXPORT NMI_Handler [WEAK]B . ;跳轉到一個標號。這里跳轉到一個‘.’,即表示無線循環ENDP HardFault_Handler\PROCEXPORT HardFault_Handler [WEAK]B .ENDP MemManage_Handler\PROCEXPORT MemManage_Handler [WEAK]B .ENDP BusFault_Handler\PROCEXPORT BusFault_Handler [WEAK]B .ENDP UsageFault_Handler\PROCEXPORT UsageFault_Handler [WEAK]B .ENDP SVC_Handler PROCEXPORT SVC_Handler [WEAK]B .ENDP DebugMon_Handler\PROCEXPORT DebugMon_Handler [WEAK]B .ENDP PendSV_Handler PROCEXPORT PendSV_Handler [WEAK]B .ENDP SysTick_Handler PROCEXPORT SysTick_Handler [WEAK]B .ENDPDefault_Handler PROCEXPORT 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 RTCAlarm_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 RTCAlarm_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_IRQHandlerB .ENDPALIGN;******************************************************************************* ; User Stack and Heap initialization 用戶堆棧初始化 ;*******************************************************************************IF :DEF:__MICROLIB ;判斷是否定義了__MICROLIBEXPORT __initial_sp;如果定義了這個宏則賦予標號__initial_sp(棧頂地址)、__heap_base(堆起始地址)、__heap_limit(堆結束地址)全局屬性,可供外部文件調用。EXPORT __heap_baseEXPORT __heap_limitELSE ;如果沒有定義__MICROLIB,則插入標號__use_two_region_memory,這個函數需要用戶自己實現IMPORT __use_two_region_memoryEXPORT __user_initial_stackheap ;聲明標號__user_initial_stackheap 具有全局屬性,可供外部文件調用,下面實現這個標號的內容。__user_initial_stackheapLDR R0, = Heap_MemLDR R1, =(Stack_Mem + Stack_Size)LDR R2, = (Heap_Mem + Heap_Size)LDR R3, = Stack_MemBX LRALIGNENDIFEND ;文件結束啟動文件使用的 ARM 匯編指令匯總
【EQU】 給數字常量取一個符號名,相當于 C語言中的 define
【AREA】 匯編一個新的代碼段或者數據段
【SPACE】 分配內存空間
【PRESERVE8 】當前文件堆棧需按照 8字節對齊
【EXPORT】聲明一個標號具有全局屬性,可被外部的文件使用
【DCD 】以字為單位分配內存,要求 4字節對齊,并要求初始化這些內存
【PROC 】定義子程序,與 ENDP 成對使用,表示子程序結束
【WEAK 】弱定義,如果外部文件聲明了一個標號,則優先使用外部文件定義的標號,如果外部文件沒有定義也不出錯。要注意的是:這個不是ARM的指令,是編譯器的,這里放在一起只是為了方便。
【IMPORT】聲明標號來自外部文件,跟 C 語言中的 EXTERN關鍵字類似
【B】 跳轉到一個標號
【ALIGN】 編譯器對指令或者數據的存放地址進行對齊,一般需要跟一個立即數,缺省表示 4字節對齊。要注意的是:這個不是 ARM的指令,是編譯器的,這里放在一起只是為了方便。
【END】 到達文件的末尾,文件結束
【IF,ELSE,ENDIF】匯編的條件分支語句,跟 C 語言的 if ,else 類似
LDR、BLX、BX 是 CM4 內核的指令,可在《CM3 權威指南 CnR2》第四章-指令集里面查詢到
【LDR】 從存儲器中加載字到一個寄存器中
【BL】 跳轉到由寄存器/標號給出的地址,并把跳轉前的下條指令地址保存到 LR
【BLX】 跳轉到由寄存器給出的地址,并根據寄存器的 LSE 確定處理器的狀態,還要把跳轉前的下條指令地址保存到 LR
【BX】 跳轉到由寄存器/標號給出的地址,不用返回
關于與啟動文件有關的一些問題思考
在啟動文件里面已經幫我們寫好所有中斷的中斷服務函數,跟我們平時寫的中斷服務函數不一樣的就是這些函數都是空的,真正的中斷復服務程序需要我們在外部的 C 文件里面重新實現,這里只是提前占了一個位置而已。
如果我們在使用某個外設的時候,開啟了某個中斷,但是又忘記編寫配套的中斷服務程序或者函數名寫錯,那當中斷來臨的時,程序就會跳轉到啟動文件預先寫好的空的中斷服務程序中,并且在這個空函數中無線循環,即程序就死在這里。
B:跳轉到一個標號。這里跳轉到一個‘.’,即表示無限循環
如果你在調試程序時發現死在了啟動文件中的B .這個位置,大概率是你使能了某個中斷但沒有編寫中斷函數。
棧的作用是用于局部變量,函數調用,函數形參等的開銷,棧的大小不能超過內部SRAM 的大小。如果編寫的程序比較大,定義的局部變量很多,那么就需要修改棧的大小。如果某一天,你寫的程序出現了莫名奇怪的錯誤,并進入了硬 fault 的時候,這時你就要考慮下是不是棧不夠大,溢出了。
修改啟動文件中的Stack_Size EQU 0x00000400可以修改棧大小
修改啟動文件中的Heap_Size EQU 0x00000200可以修改堆大小
在3.5版F1標準庫中的啟動文件還調用了在system_stm32f10x.c文件中的
SystemInit()函數配置系統時鐘,在舊版本的工程中要用戶進入main函數自己調用SystemInit()函數。
無論性能高下,結構簡繁,價格貴賤,每一種微控制器(處理器)都必須有啟動文件,啟動文件的作用便是負責執行微控制器從“復位”到“開始執行main函數”中間這段時間(稱為啟動過程)所必須進行的工作。最為常見的51,AVR或MSP430等微控制器當然也有對應的啟動文件,但開發環境往往自動完整地提供了這個啟動文件,不需要開發人員再行干預啟動過程,只需要從main函數開始進行應用程序的設計即可。
Cortex-M3內核規定,起始地址必須存放堆頂指針,而第二個地址則必須存放復位中斷入口向量地址,這樣在Cortex-M3內核復位后,會自動從起始地址的下一個32位空間取出復位中斷入口向量,跳轉執行復位中斷服務程序。
Cortex-M3內核可通過boot引腳設置中斷向量表的位置(即可以設置從不同位置啟動),有3種情況:
1、 通過boot引腳設置可以將中斷向量表定位于SRAM區,即起始地址為0x2000000,同時復位后PC指針位于0x2000000處;
2、 通過boot引腳設置可以將中斷向量表定位于FLASH區,即起始地址為0x8000000,同時復位后PC指針位于0x8000000處;
3、 通過boot引腳設置可以將中斷向量表定位于內置Bootloader區
參考鳴謝
https://www.cnblogs.com/wenshinlee/p/8859227.html
https://www.cnblogs.com/amanlikethis/p/3719529.html
總結
以上是生活随笔為你收集整理的STM32启动文件代码解析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: OBD技术速成——J1850协议解析软件
- 下一篇: STM32F1如何切换到不同的型号