Keil5中STM32程序启动流程详解

Keil5中STM32程序起动过程分析

  • 1.起动过程分析配置
  • 2.起动过程第一步
  • 3. 执行复位中断函数

  • 1.起动过程分析配置

    按keil5新建stm32工程创建stm32工程(芯片选择STM32F103C8)后,按keil5使用simulator调试配置完成调试配置。然后点击魔术棒图标,在Options for Target 'Target 1’对话框的Debug标签页,取消勾选Run to Main(),点击OK,关闭对话框。
    取消RUN-TO-MAIN
    重新编译后,进入调试,程序跳入起动文件。如此,可以调试从程序开始执行到跳入我们自己定义的main函数的起动过程。
    程序跳入起动文件

    2.起动过程第一步

    编译后可执行程序储存在ROM中,可执行程序存储位置的初始地址可在Options for Target 'Target 1’对话框的Target标签页中设置,默认为0x08000000。程序执行时,首先从这个初始地址取出第一条数据(32位)存入sp(堆栈寄存器),接着取出第二条数据(32位)存入pc(指令计数器)。由于此时pc为可执行程序的第二条数据(32位),从而程序跳转到该第二条数据对应的地址。
    设置rom初始地址
    .map文件中记录了程序代码中各段经编译器编译后在生成的可执行程序中的存储位置,从而,可以查看.map文件读出可执行程序中各存储位置对应的内容。在主界面的左侧试图中切换到Project选项卡,双击Target 1节点,可在右侧文件视图中打开stm32_demo3.map的.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函数。
    _main函数执行
    跳入__rt_entry函数后,调用__user_setup_stackheap,__user_setup_stackheap中调用了__user_initial_stackheap函数,该函数在startup_stm32f10x_md.s文件中定义。然后调用__rt_lib_init函数之后跳入__rt_entry_main函数,在该函数中调用我们自己写的main函数。
    跳转rt_entry
    个人水平有限,如有错误,请不吝指出!!!

    作者:翻滚的雪球

    物联沃分享整理
    物联沃-IOTWORD物联网 » Keil5中STM32程序启动流程详解

    发表回复