[STM32F4]

Cortex M4 启动代码分析(以STM32F413为例)

[复制链接]
4815|17
手机看帖
扫描二维码
随时随地手机跟帖
zhanzr21|  楼主 | 2017-1-18 19:19 | 显示全部楼层 |阅读模式
本帖最后由 zhanzr21 于 2017-1-18 21:47 编辑

一直想研究一下Cortex M系列的启动文件.这次以STM32F413为例,分析一下Cortex M4的启动过程,就是从上电到main函数之间的内容.其他CortexM系列的片也应该大同小异.开发工具使用Keil MDK,其他工具的启动语法上应该有差别,内容基本一致.比如gcc使用的是AT&T格式的汇编语言风格,Keil的ARM CC使用的是Intel格式的汇编语言.这都是形式上的差别,闲话少叙.
首先随便建立一个工程.能跑起来就可以,建议使用CubeMX建立.这个过程直接跳过去.看打开的工程.
keil_startup_proj_file_f4.png

这里我们只对这两个文件感兴趣,主要是那个"startup_stm32f413xx.s",那个C文件只是有个函数被".s"中调用. 以下是".s"文件内容,为了叙述方便,我去掉了比较长段的注释.
Stack_Size                EQU     0x400

                AREA    STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem       SPACE   Stack_Size
__initial_sp


; <h> Heap Configuration
;   <o>  Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>

Heap_Size      EQU     0x200

                AREA    HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem        SPACE   Heap_Size
__heap_limit

                PRESERVE8
                THUMB

; 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
                .......

__Vectors_End

__Vectors_Size  EQU  __Vectors_End - __Vectors

                AREA    |.text|, CODE, READONLY

; Reset handler
Reset_Handler    PROC
                 EXPORT  Reset_Handler             [WEAK]
        IMPORT  SystemInit
        IMPORT  __main

                 LDR     R0, =SystemInit
                 BLX     R0
                 LDR     R0, =__main
                 BX      R0
                 ENDP

; Dummy Exception Handlers (infinite loops which can be modified)

NMI_Handler     PROC
                EXPORT  NMI_Handler                [WEAK]
                B       .
                ENDP
....

Default_Handler PROC

                EXPORT  WWDG_IRQHandler                   [WEAK]
                ......

                B       .

                ENDP

                ALIGN

;*******************************************************************************
; User Stack and Heap initialization
;*******************************************************************************
                 IF      :DEF:__MICROLIB
               
                 EXPORT  __initial_sp
                 EXPORT  __heap_base
                 EXPORT  __heap_limit
               
                 ELSE
               
                 IMPORT  __use_two_region_memory
                 EXPORT  __user_initial_stackheap
                 
__user_initial_stackheap

                 LDR     R0, =  Heap_Mem
                 LDR     R1, =(Stack_Mem + Stack_Size)
                 LDR     R2, = (Heap_Mem +  Heap_Size)
                 LDR     R3, = Stack_Mem
                 BX      LR

                 ALIGN

                 ENDIF

                 END
代码很长,但是有规可循. 现在从上到下来分析一下.
Stack_Size      EQU     0x00000400
Heap_Size       EQU     0x00000200
这两个分别是定义StackHeap大小,比较小的工程保留这样设置够用.如果不够用,则要研究.map文件重新定义了.尤其是heap,有些人认为没有动态分配内存,就把heap_size设定为0,但是用户代码不用不代表调用的函数不用,尤其是<string.h>中的函数,有些是要用heap.接下来就是向量区,按照ARM的规范,这些向量都是从0开始的.但是STM32F400x8000000两个地址映射在一起了.
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
                ....

__Vectors_End
这是ARM公司的说明:
arm_vector_table_illus.png
ST公司在编程参考手册中也有说明:
The Cortex™-M4 with FPU CPU always fetches the reset vector on the ICode bus, which implies to have the boot space available only in the code area (typically, Flash memory). STM32F4xx microcontrollers implement a special mechanism to be able to boot from other memories (like the internal SRAM).
大致是说ST的F4XX用了一种特殊方法让系统可以从不同的位置启动,比如SRAM.STM32F1系列的比较熟的应该有映像. 其实这个特殊方法在ST的Cortex整个系列都是比较类似.就是Boot1,Boot0两个引脚来配置启动选项.
st_boot1_0_conf.png
这样做的实质上就是启动的时候将什么区域映射到0这个位置.为了简洁起见,这里先以常规的配置为例,也就是BOOT0拉低从Flash启动. 这样0x8000000就被映射到0这个地址.上述的向量都在0x8000000开始的地址存储.
接下来就是复位向量的处理函数.
Reset_Handler    PROC
                 EXPORT  Reset_Handler             [WEAK]
        IMPORT  SystemInit
        IMPORT  __main

                 LDR     R0, =SystemInit
                 BLX     R0
                 LDR     R0, =__main
                 BX      R0
                 ENDP
这个函数翻译成C语言就是两句:
SystemInit();
__main();
SystemInitARM公司的CMSIS规范要求的,所有符合该规范的代码都得用SystemInit来完成初始化.需要注意的是调用SystemInit的时候,stackheap都没有初始化,所以SystemInit函数内尽量不要过多调用别的函数或者动态分配内存,以免造成麻烦.SystemInit"system_stm32f4xx.c"文件里面.来看看这个函数:
void SystemInit(void)
{
      /* FPU settings ------------------------------------------------------------*/
      #if (__FPU_PRESENT == 1) && (__FPU_USED == 1)
        SCB->CPACR |= ((3UL << 10*2)|(3UL << 11*2));  /* set CP10 and CP11 Full Access */
      #endif
      /* Reset the RCC clock configuration to the default reset state ------------*/
      /* Set HSION bit */
      RCC->CR |= (uint32_t)0x00000001;

      /* Reset CFGR register */
      RCC->CFGR = 0x00000000;

      /* Reset HSEON, CSSON and PLLON bits */
      RCC->CR &= (uint32_t)0xFEF6FFFF;

      /* Reset PLLCFGR register */
      RCC->PLLCFGR = 0x24003010;

      /* Reset HSEBYP bit */
      RCC->CR &= (uint32_t)0xFFFBFFFF;

      /* Disable all interrupts */
      RCC->CIR = 0x00000000;

    #if defined (DATA_IN_ExtSRAM) || defined (DATA_IN_ExtSDRAM)
      SystemInit_ExtMemCtl();
    #endif /* DATA_IN_ExtSRAM || DATA_IN_ExtSDRAM */

      /* Configure the Vector Table location add offset address ------------------*/
    #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
}
帖子长度超限制了,余下的在下面继续写.





zhanzr21|  楼主 | 2017-1-18 19:20 | 显示全部楼层
本帖最后由 zhanzr21 于 2017-1-18 21:45 编辑

这个函数主要做的就是初始化时钟,如果定义了外部内存,则需要初始化FSMC或者FMC接口.以便后面的stack,heap初始化能够用上.

除此之外,这个函数的最后面要赋值Vector Table的偏移量.SCB中的VTOR这个变量在M0内核中没有的, M0+的核中可选(厂商决定是否有此功能),而M3,M4,M7的内核中都有这个变量.

如果你的程序从Flash启动,那么VTOR就是0x08000000U,如果是SRAM中启动,就是0x02000000U.这是通常情况.如果你的程序的偏移量不是Flash的起始,比如在Flash的底部有自定义的BootLoader那么需要定义VECT_TAB_OFFSET为你的偏移量.如果是Keil开发,可以这样选择你程序的偏移量.
keil_offset.png
SystemInit之后就是__main.注意__main不是用户的main函数.

__main函数是在系统提供的__rt_entry区,完成C Lib的初始化,bss区,text区,heap区等初始化,这部分用户一般不用关心,提供一个返回堆栈信息的函数即可.在这个".s"文件下面:

    __user_initial_stackheap PROC
    LDR R0, = Heap_Mem
    LDR R1, =(Stack_Mem + Stack_Size)
    LDR R2, = (Heap_Mem + Heap_Size)
    LDR R3, = Stack_Mem
    BX LR
    ENDP

ARM公司推荐使用__user_setup_stackheap来代替__user_initial_stackheap[参考: Legacy support for __user_initial_stackheap()],不过无所谓了,这里两个都可以用.

当然如果使用Microlib那么这个函数也不用提供.是否用MicroLIB在这里设置:
keil_microlib.png
此外如果用户没有提供的ISR,那么都使用默认的ISR:

    NMI_Handler PROC
    EXPORT NMI_Handler [WEAK]
    B .
    ENDP
    .....

    Default_Handler PROC

    EXPORT WWDG_IRQHandler [WEAK]
    ....

    WWDG_IRQHandler
    .....

    B .

    ENDP

    ALIGN

这个B翻译成C语言也就是while(1);注意这些ISR都是[WEAK]属性,WEAK的意思是用户可以提供自己的同名字代码来代替,相当于C++中的默认构造函数,以用户提供的ISR优先.

初始化代码就是这么个结构,写的比较散,如果觉得描述不清楚的可以跟贴讨论.需要注意这是Keil MDK的启动代码,其他GCC,IAR,TASKING的又稍稍不同,但是大同小异,以后有时间也研究一下来发贴.

使用特权

评论回复
zhanzr21|  楼主 | 2017-1-18 19:21 | 显示全部楼层
帖子居然超过长度限制, 分成两部分才可以.

使用特权

评论回复
皈依| | 2017-1-18 21:24 | 显示全部楼层
厉害了我的哥~这个值得好好看看

使用特权

评论回复
wind~风| | 2017-1-19 13:43 | 显示全部楼层
分析得很透彻,,估计很多人都不懂这个启动过程

使用特权

评论回复
犹豫的大三| | 2017-1-19 15:35 | 显示全部楼层
这个讲的真实仔细

使用特权

评论回复
huangcunxiake| | 2017-1-19 17:10 | 显示全部楼层
启动代码那种东西算是汇编程序还是其他的,好多指令在汇编里都没见过。

使用特权

评论回复
zhanzr21|  楼主 | 2017-1-19 17:59 | 显示全部楼层
是汇编语言, 一来汇编语言分几个流派,还有这个.s文件用了很多伪指令, 所以看起来很多不是ARM或者Thumb的指令

使用特权

评论回复
moyanming2013| | 2017-1-19 18:48 | 显示全部楼层
学习了,那boot选项的向量表和程序里面配置的SCB->VTOR向量表什么关系呢?

使用特权

评论回复
zhanzr21|  楼主 | 2017-1-19 21:10 | 显示全部楼层
moyanming2013 发表于 2017-1-19 18:48
学习了,那boot选项的向量表和程序里面配置的SCB->VTOR向量表什么关系呢?

BOOT选项是ST公司自己增加的,目的是在启动的那一刻0跟哪个地址映射在一起,
VTOR这个寄存器复位后为0, 所以启动后根据用户程序的需求,需要赋值, 对于ST的代码, Flash启动则赋值为FLASH_BASE+ VECT_OFF,  SRAM启动则为SRAM_BASE + VECT_OFF
如果你的程序从底部运行,那么VECT_OFF保留为默认值(0)即可, 如果你的程序有偏移量,那么VECT_OFF需要定义为偏移量
这是ARM公司关于CortexM0+的VTOR说明,你可以参考一下
http://infocenter.arm.com/help/i ... 0321a/BIHHDGBC.html

使用特权

评论回复
xmshao| | 2017-1-19 22:59 | 显示全部楼层
谢谢!

使用特权

评论回复
狼烟客| | 2017-1-20 17:06 | 显示全部楼层
写的不错,很详细,赞一个

使用特权

评论回复
我听闻| | 2017-2-4 14:20 | 显示全部楼层
顶帖。

使用特权

评论回复
还是看不穿| | 2017-7-18 10:35 | 显示全部楼层
写的真好。楼主我 想问问    SystemInit之后就是__main.注意__main不是用户的main函数.

__main函数是在系统提供的__rt_entry区,完成C Lib的初始化,bss区,text区,heap区等初始化,这部分用户一般不用关心,提供一个返回堆栈信息的函数即可.在这个".s"文件下面:


那是在哪儿被调用的或者是什么地方执行完了才执行main()函数的呀?

使用特权

评论回复
zhanzr21|  楼主 | 2017-7-18 12:57 | 显示全部楼层
还是看不穿 发表于 2017-7-18 10:35
写的真好。楼主我 想问问    SystemInit之后就是__main.注意__main不是用户的main函数.

__main函数是在系 ...

你是问到点子上了, __main之后main之前属于开发工具,也就是Keil的发挥的地方,这部分代码随着工具的版本不同,而不同.
比如某版本是这样的:
  IMPORT __rt_entry
  EXPORT __main
  ENTRY
__main
  B  __rt_entry
  END
__rt_entry之中又会调用一些其他内部函数. 主要是堆栈,clib的初始化之类.


这部分的代码工具厂商是不希望你知道的.

参考:
http://www.keil.com/support/man/docs/armlib/armlib_chr1358938922456.htm
https://community.arm.com/processors/b/blog/posts/decoding-the-startup-file-for-arm-cortex-m4

使用特权

评论回复
feelhyq| | 2017-7-18 14:53 | 显示全部楼层
666666666666666

使用特权

评论回复
稳稳の幸福| | 2017-7-18 20:48 | 显示全部楼层
向量函数的名字都提前在那定义好了

使用特权

评论回复
还是看不穿| | 2017-7-19 07:52 | 显示全部楼层
zhanzr21 发表于 2017-7-18 12:57
你是问到点子上了, __main之后main之前属于开发工具,也就是Keil的发挥的地方,这部分代码随着工具的版本不 ...

哇英文的,那我要好好看看了,谢谢楼主

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:每天都進步

91

主题

1005

帖子

34

粉丝