Keil5中STM32程序启动流程详解
Keil5中STM32程序起动过程分析
1.起动过程分析配置
按keil5新建stm32工程创建stm32工程(芯片选择STM32F103C8)后,按keil5使用simulator调试配置完成调试配置。然后点击魔术棒图标,在Options for Target 'Target 1’对话框的Debug标签页,取消勾选Run to Main(),点击OK,关闭对话框。
重新编译后,进入调试,程序跳入起动文件。如此,可以调试从程序开始执行到跳入我们自己定义的main函数的起动过程。
2.起动过程第一步
编译后可执行程序储存在ROM中,可执行程序存储位置的初始地址可在Options for Target 'Target 1’对话框的Target标签页中设置,默认为0x08000000。程序执行时,首先从这个初始地址取出第一条数据(32位)存入sp(堆栈寄存器),接着取出第二条数据(32位)存入pc(指令计数器)。由于此时pc为可执行程序的第二条数据(32位),从而程序跳转到该第二条数据对应的地址。
.map文件中记录了程序代码中各段经编译器编译后在生成的可执行程序中的存储位置,从而,可以查看.map文件读出可执行程序中各存储位置对应的内容。在主界面的左侧试图中切换到Project选项卡,双击Target 1节点,可在右侧文件视图中打开stm32_demo3.map的.map文件。
在map文件中找到编译后可执行程序的起始地址0x08000000,其存储内容为236字节的RESET段,该段属于startup_stm32f10x_md.o对象,我们查看对应的源文件startup_stm32f10x_md.s找到对应的RESET段:
; 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 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 TIM4_IRQHandler ; TIM4
DCD I2C1_EV_IRQHandler ; I2C1 Event
DCD I2C1_ER_IRQHandler ; I2C1 Error
DCD I2C2_EV_IRQHandler ; I2C2 Event
DCD I2C2_ER_IRQHandler ; I2C2 Error
DCD SPI1_IRQHandler ; SPI1
DCD SPI2_IRQHandler ; SPI2
DCD USART1_IRQHandler ; USART1
DCD USART2_IRQHandler ; USART2
DCD USART3_IRQHandler ; USART3
DCD EXTI15_10_IRQHandler ; EXTI Line 15..10
DCD RTCAlarm_IRQHandler ; RTC Alarm through EXTI Line
DCD USBWakeUp_IRQHandler ; USB Wakeup from suspend
__Vectors_End
__Vectors_Size EQU __Vectors_End - __Vectors
该段调用了59次DCD指令,每次分配一个字32位,即4个字节,一共236个字节,与.map文件一致,其第一条数据为 __initial_sp,是栈顶指针地址,其第二条数据为 Reset_Handler,是复位中断函数地址。因此,程序执行时,pc的第一个数据就是复位中断函数地址,从而程序执行的第一步就是跳入复位中断函数。
3. 执行复位中断函数
复位中断函数如下所示,其依次调用了外部函数SystemInit和__main,注意这里的__main函数不是我们写的那个main函数。
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT __main
IMPORT SystemInit
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
SystemInit在system_stm32f10x.c文件中,如下所示,在该函数中,进行时钟设置和中断向量表偏移设置。
/**
* @brief Setup the microcontroller system
* Initialize the Embedded Flash Interface, the PLL and update the
* SystemCoreClock variable.
* @note This function should be used only after reset.
* @param None
* @retval None
*/
void SystemInit (void)
{
/* Reset the RCC clock configuration to the default reset state(for debug purpose) */
/* Set HSION bit */
RCC->CR |= (uint32_t)0x00000001;
/* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */
#ifndef STM32F10X_CL
RCC->CFGR &= (uint32_t)0xF8FF0000;
#else
RCC->CFGR &= (uint32_t)0xF0FF0000;
#endif /* STM32F10X_CL */
/* Reset HSEON, CSSON and PLLON bits */
RCC->CR &= (uint32_t)0xFEF6FFFF;
/* Reset HSEBYP bit */
RCC->CR &= (uint32_t)0xFFFBFFFF;
/* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */
RCC->CFGR &= (uint32_t)0xFF80FFFF;
#ifdef STM32F10X_CL
/* Reset PLL2ON and PLL3ON bits */
RCC->CR &= (uint32_t)0xEBFFFFFF;
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x00FF0000;
/* Reset CFGR2 register */
RCC->CFGR2 = 0x00000000;
#elif defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x009F0000;
/* Reset CFGR2 register */
RCC->CFGR2 = 0x00000000;
#else
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x009F0000;
#endif /* STM32F10X_CL */
#if defined (STM32F10X_HD) || (defined STM32F10X_XL) || (defined STM32F10X_HD_VL)
#ifdef DATA_IN_ExtSRAM
SystemInit_ExtMemCtl();
#endif /* DATA_IN_ExtSRAM */
#endif
/* Configure the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers */
/* Configure the Flash Latency cycles and enable prefetch buffer */
SetSysClock();
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM. */
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH. */
#endif
}
__main函数为编译器库函数,其地址可在.map文件查到为0x080000ec,总共8字节大小。
!!!main 0x080000ec Section 8 __main.o(!!!main)
查看汇编窗口,对应跳转__main函数的编译后的代码如下,其中pc=0x0800018C条指令对应跳转指令。由于指令预取,读取PC时,会返回当前指令地址+4的值,所以读取的pc=0x08000190,所以[pc,#36]对应0x08000190+36=0x080001B4地址对应的内容。
由于stm32为小端模式,0x080001B4-0x080001B6的4字节地址对应的内容为0x080000ED,其对其的地址为:0x080000EC,和.map文件一样,最后一位1标志指令类型。
跳转到0x080000EC,首先为寄存器r10、r11赋值,r10中的地址与r11中的地址相差32字节,共8个字,其中4个字为一组,第一组四个字分别为Rom中的非零数据存储基址,Ram中的加载基址,数据长度和加载函数地址,第二组为Rom中初始为零的数据的存储基址,Ram中的加载基址,数据长度和加载函数地址。程序比较r10和r11中地址是否相等,不相等就从r10位置取出一组数据执行从Rom将数据加载到ram,同时r10中地址增加4个字,共16字节,相等就跳转__rt_entry函数。根据r10、r11数据可知,第一次比较r10和r11,两者不相等,执行非零数据从Rom加载到Ram程序,第二次比较r10和r11,两者不相等,执行初始为零的数据从Rom加载到Ram程序,这样就完成了将数据从Rom加载到Ram,然后r10就和r11相等了,跳转到__rt_entry函数。
跳入__rt_entry函数后,调用__user_setup_stackheap,__user_setup_stackheap中调用了__user_initial_stackheap函数,该函数在startup_stm32f10x_md.s文件中定义。然后调用__rt_lib_init函数之后跳入__rt_entry_main函数,在该函数中调用我们自己写的main函数。
个人水平有限,如有错误,请不吝指出!!!
作者:翻滚的雪球