当前位置:   article > 正文

STM32启动项-startup___startup_config_stack_alignement

__startup_config_stack_alignement

简述:

1、初始化堆栈指针SP=_initial_sp

2、初始化PC指针=Reset_Handler

3、初始化中断向量表

4、配置系统时钟

5、调用C库函数_main初始化用户堆栈,从而最终调用main函数;

一、堆栈设置

1、栈空间设置

一般默认为Stack_Size  EQU 0x00000400(1024Byte);

注:一个函数中定义的所有局部变量,加起来不能大于工程的栈大小,否则程序必然会出现内存溢出,导致复位;一个函数中定义的所有局部变量+全局变量不能大于工程的栈。

2、分配内存空间,初始化内存单元值

 AREA   STACK, NOINIT, READWRITE, ALIGN

伪指令AREA,表示开辟一段内存空间,段名是SUN(叠加);
NOINIT:表示不初始化,此数据段仅仅保留了内存单元,而没有将各初始值写入内存单元,或将各个内存单元值初始化为0;
READWRITE:可读可写
ALIGN=3: 2 的3次方表示以8字节对齐;

Stack_Mem      SPACE            Stack_Size       

__initial_sp:设置栈指针 

3、堆设置

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

与栈类似

4、堆栈对齐

REQUIRE8
指令指定当前文件要求堆栈八字节对齐。 它设置 REQ8 编译属性以通知链接器。


PRESERVE8
指令指定当前文件保持堆栈八字节对齐。 它设置 PRES8 编译属性以通知链接器。

链接器检查要求堆栈八字节对齐的任何代码是否仅由保持堆栈八字节对齐的代码直接或间接地调用。

THUMB表示后面指令兼容THUMB指令。THUBM是ARM以前的指令集,16bit,现在Cortex-M系列的都使用THUMB-2指令集,THUMB-2是32位的,兼容16位和32位的指令,是THUMB的超级。

5、向量表

   AREA             RESET, DATA, READONLY
   EXPORT           __Vectors
   EXPORT           __Vectors_End
   EXPORT           __Vectors_Size

定义一个数据段,名字为RESET,可读。

并声明__Vectors、__Vectors_End和__Vectors_Size这三个标号具有全局属性,可供外部的文件调用。

EXPORT声明一个标号可被外部的文件使用,使标号具有全局属性。如果是IAR编译器,则使用的是GLOBAL这个指令。

当内核响应了一个发生的异常后,对应的异常服务例程(ESR)就会执行。为了决定ESR 的入口地址,内核使用了"向量表查表机制"。这里使用一张向量表。向量表其实是一个WORD(32 位整数)数组,每个下标对应一种异常,该下标元素的值则是该ESR 的入口地址。向量表在地址空间中的位置是可以设置的,通过NVIC 中的一个重定位寄存器来指出向量表的地址。在复位后,该寄存器的值为0。因此,在地址0 (即FLASH 地址0)处必须包含一张向量表,用于初始时的异常分配。要注意的是这里有个另类:0 号类型并不是什么入口地址,而是给出了复位后MSP 的初值。

                      __Vectors
                               DCD              __initial_sp ; Top of Stack
   71 00000004 00000000        DCD              Reset_Handler ; Reset Handler
   72 00000008 00000000        DCD              NMI_Handler ; NMI Handler
   73 0000000C 00000000        DCD              HardFault_Handler ; Hard Fault 
                                                            Handler
   74 00000010 00000000        DCD              MemManage_Handler 
                                                            ; MPU Fault Handler
                                                            
   75 00000014 00000000        DCD              BusFault_Handler 
                                                            ; Bus Fault Handler
                                                            
   76 00000018 00000000        DCD              UsageFault_Handler ; Usage Faul
                                                            t Handler
   77 0000001C 00000000        DCD              0           ; Reserved
   78 00000020 00000000        DCD              0           ; Reserved
   79 00000024 00000000        DCD              0           ; Reserved
   80 00000028 00000000        DCD              0           ; Reserved
   81 0000002C 00000000        DCD              SVC_Handler ; SVCall Handler
   82 00000030 00000000        DCD              DebugMon_Handler ; Debug Monito
                                                            r Handler

... ...中间省略部分代码


  175 0000019C 00000000        DCD              0           ; 103:Reserved
  176 000001A0 00000000        DCD              TLI_IRQHandler ; 104:TLI
  177 000001A4 00000000        DCD              TLI_ER_IRQHandler 
                                                            ; 105:TLI error
  178 000001A8         __Vectors_End

  179 000001A8         
  180 000001A8 000001A8 
                       __Vectors_Size
                               EQU              __Vectors_End - __Vectors
  181 000001A8         
  182 000001A8                 AREA             |.text|, CODE, READONLY

1 __Vectors_Size EQU __Vectors_End - __Vectors

__Vectors为向量表起始地址,__Vectors_End 为向量表结束地址,两个相减即可算出向量表大小。

向量表从FLASH的0地址开始放置,以4个字节为一个单位,地址0存放的是栈顶地址,0X04存放的是复位程序的地址,以此类推。从代码上看,向量表中存放的都是中断服务函数的函数名,C语言中的函数名就是一个地址。

DCD:分配一个或者多个以字为单位的内存,以四字节对齐,并要求初始化这些内存。在向量表中,DCD分配了一堆内存,并且以ESR的入口地址初始化它们。

6、复位程序

184 00000000         ;/* reset Handler */
  185 00000000         Reset_Handler
                               PROC
  186 00000000                 EXPORT           Reset_Handler                  
   [WEAK]
  187 00000000                 IMPORT           __main
  188 00000000                 IMPORT           SystemInit
  189 00000000 4809            LDR              R0, =SystemInit
  190 00000002 4780            BLX              R0
  191 00000004 4809            LDR              R0, =__main
  192 00000006 4700            BX               R0
  193 00000008                 ENDP

复位子程序是系统上电后第一个执行的程序,调用SystemInit函数初始化系统时钟,然后调用C库函数_mian,最终调用main函数。

WEAK:表示弱定义,如果外部文件优先定义了该标号则首先引用该标号,如果外部文件没有声明也不会出错。这里表示复位子程序可以由用户在其他文件重新实现,这里并不是唯一的。

IMPORT:表示该标号来自外部文件,跟C语言中的EXTERN关键字类似。这里表示SystemInit和__main这两个函数均来自外部的文件。

SystemInit()是一个标准的库函数,主要作用是配置系统时钟。

__main是一个标准的C库函数,主要作用是初始化用户堆栈,最终调用main函数。这就是为什么我们写的程序都有一个main函数的原因。如果我们在这里不调用__main,那么程序最终就不会调用我们C文件里面的main,如果是调皮的用户就可以修改主函数的名称,然后在这里面IMPORT你写的主函数名称即可。

7、中断

在启动文件里面已经帮我们写好所有中断的中断服务函数,跟我们平时写的中断服务函数不一样的就是这些函数都是空的,真正的中断复服务程序需要我们在外部的C文件里面重新实现。

如果我们在使用某个外设的时候,开启了某个中断,但是又忘记编写配套的中断服务程序或者函数名写错,那当中断来临的时,程序就会跳转到启动文件预先写好的空的中断服务程序中,并且在这个空函数中无线循环,即程序就死在这里。

 195 00000008         ;/* dummy Exception Handlers */
  196 00000008         NMI_Handler
                               PROC
  197 00000008                 EXPORT           NMI_Handler                    

... ...中间省略部分代码           
     [WEAK]
  238 00000018 E7FE            B                .
  239 0000001A                 ENDP
  240 0000001A         
  241 0000001A         Default_Handler
                               PROC

  402 0000001A         UART7_IRQHandler
  403 0000001A         TLI_IRQHandler
  404 0000001A         TLI_ER_IRQHandler
  405 0000001A         
  406 0000001A E7FE            B                .
  407 0000001C                 ENDP
 

8、堆栈初始化

  ALIGN:对指令或者数据存放的地址进行对齐,后面会跟一个立即数。缺省表示4字节对齐。

用户堆和栈初始化:
  411 0000001C         ; user Initial Stack & Heap
  
  413 0000001C                 IF               :DEF:__MICROLIB
  420 0000001C         
  421 0000001C                 IMPORT           __use_two_region_memory
  422 0000001C                 EXPORT           __user_initial_stackheap
  423 0000001C         
  424 0000001C         __user_initial_stackheap
                               PROC
  425 0000001C 4804            LDR              R0, =  Heap_Mem
  426 0000001E 4905            LDR              R1, =(Stack_Mem + Stack_Size)
  427 00000020 4A05            LDR              R2, = (Heap_Mem +  Heap_Size)
  428 00000022 4B06            LDR              R3, = Stack_Mem

ARM Macro Assembler    Page 12 


  429 00000024 4770            BX               LR
  430 00000026                 ENDP
  431 00000026         
  432 00000026 00 00           ALIGN
  433 00000028         
  434 00000028                 ENDIF
  435 00000028         
  436 00000028                 END
 

IF               :DEF:__MICROLIB   判断是否定义了__MICROLIB ,如果定义了则赋予标号__initial_sp(栈顶地址)、__heap_base(堆起始地址)、__heap_limit(堆结束地址)全局属性,可供外部文件调用。如果没有定义(实际的情况就是我们没定义__MICROLIB)则使用默认的C库,然后初始化用户堆栈大小,这部分有C库函数__main来完成,当初始化完堆栈之后,就调用main函数去到C的世界。

IF,ELSE,ENDIF:汇编的条件分支语句,跟C语言的if ,else类似

END:文件结束

9、系统启动

在离开复位状态后, CM3 做的第一件事就是读取下列两个 32 位整数的值:

1、从地址 0x0000,0000 处取出 MSP 的初始值。

2、从地址 0x0000,0004 处取出 PC 的初始值——这个值是复位向量, LSB 必须是 1。 然后从这个值所对应的地址处取指。

图 142 复位序列

请注意,这与传统的 ARM 架构不同——其实也和绝大多数的其它单片机不同。传统的 ARM 架构总是从 0 地址开始执行第一条指令。它们的 0 地址处总是一条跳转指令。在 CM3 中,在 0 地址处提供 MSP 的初始值,然后紧跟着就是向量表。向量表中的数值是 32 位的地址,而不是跳转指令。向量表的第一个条目指向复位后应执行的第一条指令,就是我们刚刚分析的Reset_Handler这个函数。

因为 CM3 使用的是向下生长的满栈,所以 MSP 的初始值必须是堆栈内存的末地址加 1。举例来说,如果我们的堆栈区域在 0x20007C00-0x20007FFF 之间,那么 MSP 的初始值就必须是 0x20008000。

向量表跟随在 MSP 的初始值之后——也就是第 2 个表目。要注意因为 CM3 是在 Thumb 态下执行,所以向量表中的每个数值都必须把 LSB 置 1(也就是奇数)。正是因为这个原因,图 143中使用0x101 来表达地址 0x100。当 0x100 处的指令得到执行后,就正式开始了程序的执行(即去到C的世界)。在此之前初始化 MSP 是必需的,因为可能第 1 条指令还没来得及执行,就发生了 NMI 或是其它 fault。 MSP 初始化好后就已经为它们的服务例程准备好了堆栈。

附录、汇编指令

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Gausst松鼠会/article/detail/75883
推荐阅读
相关标签
  

闽ICP备14008679号