STM32的啟動文件
STM32作為一款單片機,它的啟動方式很簡單,即當Boot配置了從內部Flash啟動模式之后,一上電程序就會從0x8000000地址處開始執行文件,因此我們在使用Keil設置程序起始地址的時候,需要將這個Flash地址設置成0x8000000,只有將這個地址設置成0x8000000,生成的hex文件才可以被正常燒錄到此地址,單片機上電之后才可以正常啟動。而如果使用J-Flash工具燒寫Hex文件時,這個地址會自動根據Hex文件解析出來。然而如果當你燒寫二進制Bin文件時,還需要手動將單片機的起始地址制定出來,關于Hex文件和Bin文件的異同點,這個又是可以長篇大論一番了,我們下次特別寫文章來講。

圖1 Keil設置起始地址和空間
STM32啟動文件
;********************* (C) COPYRIGHT 2017 STMicroelectronics ********************;* File Name : startup_stm32l151xb.s;* Author : MCD Application Team;* Description : STM32L151XB Devices vector 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 system clock;* - 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.;********************************************************************************;*;* 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;*;*******************************************************************************;* <<< Use Configuration Wizard in Context Menu >>>;; Amount of memory (in bytes) allocated for Stack; Tailor this value to your application needs; <h> Stack Configuration; <o> Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>; </h>Stack_Size EQU 0x00000400AREA STACK, NOINIT, READWRITE, ALIGN=3Stack_Mem SPACE Stack_Size__initial_sp; <h> Heap Configuration; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>; </h>Heap_Size EQU 0x00000200AREA HEAP, NOINIT, READWRITE, ALIGN=3__heap_baseHeap_Mem SPACE Heap_Size__heap_limitPRESERVE8THUMB; Vector Table Mapped to Address 0 at ResetAREA RESET, DATA, READONLYEXPORT __VectorsEXPORT __Vectors_EndEXPORT __Vectors_Size__Vectors DCD __initial_sp ; Top of StackDCD Reset_Handler ; Reset HandlerDCD 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 ; ReservedDCD 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 InterruptsDCD WWDG_IRQHandler ; Window WatchdogDCD PVD_IRQHandler ; PVD through EXTI Line detectDCD TAMPER_STAMP_IRQHandler ; Tamper and Time StampDCD RTC_WKUP_IRQHandler ; RTC WakeupDCD 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_IRQHandler ; ADC1DCD USB_HP_IRQHandler ; USB High PriorityDCD USB_LP_IRQHandler ; USB Low PriorityDCD DAC_IRQHandler ; DACDCD COMP_IRQHandler ; COMP through EXTI LineDCD EXTI9_5_IRQHandler ; EXTI Line 9..5DCD 0 ; ReservedDCD TIM9_IRQHandler ; TIM9DCD TIM10_IRQHandler ; TIM10DCD TIM11_IRQHandler ; TIM11DCD 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 RTC_Alarm_IRQHandler ; RTC Alarm through EXTI LineDCD USB_FS_WKUP_IRQHandler ; USB FS Wakeup from suspendDCD TIM6_IRQHandler ; TIM6DCD TIM7_IRQHandler ; TIM7__Vectors_End__Vectors_Size EQU __Vectors_End - __VectorsAREA |.text|, CODE, READONLY; Reset handler routineReset_Handler PROCEXPORT Reset_Handler [WEAK]IMPORT __mainIMPORT SystemInitLDR R0, =SystemInitBLX R0LDR R0, =__mainBX R0ENDP; Dummy Exception Handlers (infinite loops which can be modified)NMI_Handler PROCEXPORT NMI_Handler [WEAK]B .ENDPHardFault_Handler\PROCEXPORT HardFault_Handler [WEAK]B .ENDPMemManage_Handler\PROCEXPORT MemManage_Handler [WEAK]B .ENDPBusFault_Handler\PROCEXPORT BusFault_Handler [WEAK]B .ENDPUsageFault_Handler\PROCEXPORT UsageFault_Handler [WEAK]B .ENDPSVC_Handler PROCEXPORT SVC_Handler [WEAK]B .ENDPDebugMon_Handler\PROCEXPORT DebugMon_Handler [WEAK]B .ENDPPendSV_Handler PROCEXPORT PendSV_Handler [WEAK]B .ENDPSysTick_Handler PROCEXPORT SysTick_Handler [WEAK]B .ENDPDefault_Handler PROCEXPORT WWDG_IRQHandler [WEAK]EXPORT PVD_IRQHandler [WEAK]EXPORT TAMPER_STAMP_IRQHandler [WEAK]EXPORT RTC_WKUP_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_IRQHandler [WEAK]EXPORT USB_HP_IRQHandler [WEAK]EXPORT USB_LP_IRQHandler [WEAK]EXPORT DAC_IRQHandler [WEAK]EXPORT COMP_IRQHandler [WEAK]EXPORT EXTI9_5_IRQHandler [WEAK]EXPORT TIM9_IRQHandler [WEAK]EXPORT TIM10_IRQHandler [WEAK]EXPORT TIM11_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 USB_FS_WKUP_IRQHandler [WEAK]EXPORT TIM6_IRQHandler [WEAK]EXPORT TIM7_IRQHandler [WEAK]WWDG_IRQHandlerPVD_IRQHandlerTAMPER_STAMP_IRQHandlerRTC_WKUP_IRQHandlerFLASH_IRQHandlerRCC_IRQHandlerEXTI0_IRQHandlerEXTI1_IRQHandlerEXTI2_IRQHandlerEXTI3_IRQHandlerEXTI4_IRQHandlerDMA1_Channel1_IRQHandlerDMA1_Channel2_IRQHandlerDMA1_Channel3_IRQHandlerDMA1_Channel4_IRQHandlerDMA1_Channel5_IRQHandlerDMA1_Channel6_IRQHandlerDMA1_Channel7_IRQHandlerADC1_IRQHandlerUSB_HP_IRQHandlerUSB_LP_IRQHandlerDAC_IRQHandlerCOMP_IRQHandlerEXTI9_5_IRQHandlerTIM9_IRQHandlerTIM10_IRQHandlerTIM11_IRQHandlerTIM2_IRQHandlerTIM3_IRQHandlerTIM4_IRQHandlerI2C1_EV_IRQHandlerI2C1_ER_IRQHandlerI2C2_EV_IRQHandlerI2C2_ER_IRQHandlerSPI1_IRQHandlerSPI2_IRQHandlerUSART1_IRQHandlerUSART2_IRQHandlerUSART3_IRQHandlerEXTI15_10_IRQHandlerRTC_Alarm_IRQHandlerUSB_FS_WKUP_IRQHandlerTIM6_IRQHandlerTIM7_IRQHandlerB .ENDPALIGN;*******************************************************************************; User Stack and Heap initialization;*******************************************************************************IF :DEF:__MICROLIBEXPORT __initial_spEXPORT __heap_baseEXPORT __heap_limitELSEIMPORT __use_two_region_memoryEXPORT __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;************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE*****
首先讓我們來看下STM32啟動文件,當MCU上電復位之后,整個程序會跳轉到以0x8000000為基址,偏移0的地址處,即還是0x8000000。但是STM32的0x8000000地址處存放的并不是整個芯片的第一句指令,而是整個芯片的堆棧初始化程序,如圖2所示。

圖2 0x8000000偏移0地址處的堆棧初始化程序指針
由于STM32的地址空間都是4字節對齊的,因此這個棧頂指針的存放空間為4字節,所以STM32復位之后跳轉的地址應該是0x8000000基址偏移4個字節,即0x8000004。如同3所示。

圖3 STM32復位跳轉地址
圖3中的程序非常淺顯易懂,第136和137行,即將程序跳轉到SystemInit處,這是個C語言函數,定義在“system_stm32l1xx.c”文件里,它的目的就是對中斷向量表起始地址進行指定,也就是圖2中的“__Vector”處。當然CM3內核和CM0內核關于SCB(系統控制塊)的定義有些許差別,CM0不在本文討論中,但是CM3和CM4的中斷向量表映射機制還是很相似的。

圖4 SystemInit函數映射中斷向量表
圖4中我們可以看到,SCB中關于Vector的地址是通過符號FLASH_BASE和VECT_TAB_OFFSET計算出來的,我們可以找到關于它們的定義,如圖5所示。

圖5 FLASH_BASE和VECT_TAB_OFFSET的定義
通過圖5中的計算,正好可以得出整個中斷向量表被映射到了0x8000000地址處。
STM32的FLASH分配
前面的大段文章內容中,頻繁提及了一個關鍵的數值,即0x8000000,那么這個0x8000000到底是怎么來的呢?這個數值并不是平白無故拍腦袋想出來的。之前我們就說過,ARM體系的存儲器結構是其一大特色,而這個0x8000000正是整個STM32內置FLASH的起始地址。我們隨便打開一份STM32的數據手冊,在存儲器章節里面就可以看到STM32全部的存儲器定義。如圖6所示。

圖6 STM32內部FLASH的起始地址
STM32的Bootloader思路
拋開所有的Bootloader高級功能來說,我們設計STM32 Bootloader的主要目的有兩個,第一個為方便程序燒寫和更新,第二個目前是從Bootloader程序中跳轉(引導)用戶的應用程序。這兩個目的中,對于Bootloader來說程序跳轉尤其重要,因為程序跳轉成不成功將會嚴重影響整個用戶程序的運行狀態。因而,怎么跳,何時跳,跳到哪里,則是下篇文章的著重討論部分。
前面一個FLASH燒寫,可以根據自己的特殊要求來定制,只要嚴格安裝HEX文件指定的地址和數據的關系,一般不會出錯。
本文分析了STM32啟動時比較重要的一些定義和函數跳轉,下篇將會開始著手設計一個STM32 Bootloader。
