欲练此功必先自宫之STM32汇编启动,放慢是为了更好的前行

2020-08-15 08:49发布

今天来探讨一下武功秘籍的修炼,据说秘籍第一页就写道:欲练此功,必先自宫。意思就是说明一个道理,任何人要想成仙成佛,练就一身超神的本领,那么是要付出的代价并做好准备工作的。

而今天我们的要探讨的主角STM32处理器也不例外,别看它运行起来玉树临风,似乎无所不能,启动初期也要先苦练一番本领才行。那么这一部分都做了哪些工作呢?

STM32这款处理器使用的是的ARM的Cortex-M3内核,Cortex-M3内核有个特点就是在地址0x00000000存放的是全局的栈地址。紧接着从地址0x00000004执行(因为STM32是32位的处理器,一条指令正好对应4个字节),也就是从这个地址取出中断向量表的地址,跳转执行中断处理函数。

555

M3内核复位后程序执行过程

在前面文章中我们有介绍通过BOOT0和BOOT1引脚高低电平的不同组合,对应者三种不同的启动方式,同样也就对应着三种地址的映射: Flash system memory就是将0x00000000映射到了0x08000000;System memory就是将0x00000000映射到了0x1FFF0000;SRAM就是将0x00000000映射到了0x20000000。选择了哪种启动方式就会把0地址映射到对应物理地址。同样该区的物理地址最前面存放的就是栈地址和中断向量表。

      DATA
__vector_table
        DCD     sfe(CSTACK)
        DCD     Reset_Handler             ; Reset Handler

        DCD     NMI_Handler               ; NMI Handler
        DCD     HardFault_Handler         ; Hard Fault Handler
        DCD     0                         ; Reserved
        DCD     0                         ; Reserved
        DCD     0                         ; Reserved
        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     RTC_IRQHandler                 ; RTC through EXTI Line
                DCD     FLASH_IRQHandler               ; FLASH
                DCD     RCC_CRS_IRQHandler             ; RCC_CRS
                DCD     EXTI0_1_IRQHandler             ; EXTI Line 0 and 1
                DCD     EXTI2_3_IRQHandler             ; EXTI Line 2 and 3
                DCD     EXTI4_15_IRQHandler            ; EXTI Line 4 to 15
                DCD     TSC_IRQHandler                 ; TSC
                DCD     DMA1_Channel1_IRQHandler       ; DMA1 Channel 1
                DCD     DMA1_Channel2_3_IRQHandler     ; DMA1 Channel 2 and Channel 3
                DCD     DMA1_Channel4_5_6_7_IRQHandler ; DMA1 Channel 4, Channel 5, Channel 6 and Channel 7
                DCD     ADC1_COMP_IRQHandler           ; ADC1, COMP1 and COMP2 
                DCD     LPTIM1_IRQHandler              ; LPTIM1
                DCD     0                              ; Reserved
                DCD     TIM2_IRQHandler                ; TIM2
                DCD     0                              ; Reserved
                DCD     TIM6_DAC_IRQHandler            ; TIM6 and DAC
                DCD     0                              ; Reserved
                DCD     0                              ; Reserved
                DCD     TIM21_IRQHandler                ; TIM21
                DCD     0                              ; Reserved
                DCD     TIM22_IRQHandler               ; TIM22
                DCD     I2C1_IRQHandler                ; I2C1
                DCD     I2C2_IRQHandler                ; I2C2
                DCD     SPI1_IRQHandler                ; SPI1
                DCD     SPI2_IRQHandler                ; SPI2
                DCD     USART1_IRQHandler              ; USART1
                DCD     USART2_IRQHandler              ; USART2
                DCD     RNG_LPUART1_IRQHandler         ; RNG and LPUART1
                DCD     LCD_IRQHandler                 ; LCD
                DCD     USB_IRQHandler                 ; USB

而中断处理函数的第一个就是复位中断处理函数Reset_Handler:

    THUMB
        PUBWEAK Reset_Handler
        SECTION .text:CODE:NOROOT:REORDER(2)
Reset_Handler
        LDR     R0, =SystemInit
        BLX     R0
        LDR     R0, =__iar_program_start
        BX      R0
        
        PUBWEAK NMI_Handler
        SECTION .text:CODE:NOROOT:REORDER(1)

我们看下默认的复位中断处理函数里边都做了什么事情,首先将SystemInit函数的首地址存放到R0寄存器里边,然后跳转到R0寄存器的地址,即去执行IAR本身库文件中的SystemInit函数进行相关初始化。然后将__iar_program_start函数的首地址存放到R0寄存器里边,然后跳转到R0寄存器的地址,即去执行__iar_program_start函数。

那么__iar_program_start这个函数在哪里呢?没错,你在整个汇编文件中都没有找到!这个文件在IAR的安装目录里边,具体的路径是C:\Program Files (x86)\IAR Systems\Embedded Workbench 7.2\arm\src\lib\thumb,我是将IAR装在C:\Program Files (x86)目录下的,大家根据自己情况查找。在thumb目录下有个cstartup_M.s文件,文件代码如下:


        MODULE  ?cstartup


        PUBLIC  __iar_program_start
        EXTERN  __cmain
        EXTERN  __vector_table
        EXTWEAK __iar_init_core
        EXTWEAK __iar_init_vfp

        SECTION .text:CODE:REORDER(1)
        THUMB
__iar_program_start:
        FUNCALL __iar_program_start, __iar_init_core
        BL      __iar_init_core
        FUNCALL __iar_program_start, __iar_init_vfp
        BL      __iar_init_vfp

        FUNCALL __iar_program_start, __cmain
        BL      __cmain

        REQUIRE __vector_table


        END

__iar_program_start中分别调用了__iar_init_core,__iar_init_vfp和__cmain三个函数。前面两个函数式弱函数,在工程中没有定义,大家可以根据实际情况重写这两个函数。最后一个函数__cmain也在同目录下的cmain.s中定义:

        THUMB
__cmain:
?main:

; Initialize segments.
; __segment_init and __low_level_init are assumed to use the same
; instruction set and to be reachable by BL from the ICODE segment
; (it is safest to link them in segment ICODE).

          FUNCALL __cmain, __low_level_init
        bl      __low_level_init
        cmp     r0,#0
        beq     ?l1
          FUNCALL __cmain, __iar_data_init3
        bl      __iar_data_init3

?l1:
        REQUIRE ?l3

        SECTION .text:CODE:NOROOT(2)

        PUBLIC  _main
        PUBLIC _call_main
        THUMB

__iar_init$done:                 ; Copy initialization is done

?l3:
_call_main:
        MOVS    r0,#0             ;  No parameters

          FUNCALL __cmain, __iar_argc_argv
        BL      __iar_argc_argv   ; Maybe setup command line

          FUNCALL __cmain, main
        BL      main
_main:
          FUNCALL __cmain, exit
        BL      exit

        END

其实最后就是进行了一个底层的初始化,然后就永久的跳转到main.c文件中的main函数了。至此,汇编部分代码结束,开始执行大家在main中编写的代码。