Keil 启动文件详解

文章目录

  • 1. 启动文件详解
  • 1.1 启动文件的主要任务
    • 1.2 启动文件中的汇编指令
    • 1.3 启动代码讲解
      • 1.3.1 栈的初始化
      • 1.3.2 堆的初始化
      • 1.3.3 向量表初始化
      • 1.3.4 复位程序
      • 1.3.5 中断服务程序
      • 1.3.6 用户堆栈初始化
    • 1.4 系统启动流程


1. 启动文件详解

1.1 启动文件的主要任务

    启动文件会完成以下五件事:

  • 初始化堆栈指针 SP=__initial_sp
  • 初始化PC指针 PC=Reset_Handler
  • 初始化中断向量表
  • 配置系统时钟
  • 调用C库函数__main()初始化用户堆栈,从而最终调用main函数去到C世界

1.2 启动文件中的汇编指令

    汇编指令可以通过keil->help->uVision Help->搜索中查找,启动文件中的汇编指令如下:

Keil 启动文件详解_第1张图片

1.3 启动代码讲解

1.3.1 栈的初始化

Keil 启动文件详解_第2张图片

    此处开辟栈的大小为0x00000400(1KB), 名字为STACK, NOINIT即不初始化,READWRITE表示可读可写,ALIGN=3,表示2^3=8字节对齐。栈的作用是用于局部变量,函数调用,函数形参等开销。栈是在SRAM中开辟的,因此栈的大小不能超过SRAM。如果局部变量很多,或者函数调用很深,可能需要增大栈的大小。

    标号__initial_sp紧挨着SPACE语句放置,表示栈的结束地址,即栈顶的指针,栈是由高向低生长的。

1.3.2 堆的初始化

Keil 启动文件详解_第3张图片

    与栈的初始化类似。不同的是__heap_base表示堆的起始地址,__heap_limit表示堆的结束地址,堆是由下向上生长的,和栈的生长方向相反。堆主要用来动态内存的分配,malloc()函数就是从堆里面分配内存,这个在STM32里面用的比较少。

1.3.3 向量表初始化

在这里插入图片描述

    首先定义了一个数据段,名字叫RESET,只读,并声明了__Vectors、Vectors_End和__Vectors_Size这三个标号具有全局属性,可供属性外部的文件调用。当内核响应了一个发生的异常后,对应的异常服务例程就会执行ISR,找到ISR的入口地址,内核使用了“向量表查表机制”。向量表在地址空间中的位置是可以设置的,通过NVIC中的一个重定位寄存器指向向量表的地址。在复位后,该寄存器的值为0。因此,在地址0(在flash地址0)处必须包含一张向量表,用于初始时的异常分配。这个向量表的头是MSP,第二个才是Reset。

Keil 启动文件详解_第4张图片

Keil 启动文件详解_第5张图片

__Vectors为向量表起始地址,__Vectors_End为向量表结束地址,两个相减即算出向量表的大小
在向量表中,DCD分配一个内存,并且以ISR的入口地址初始化他们

1.3.4 复位程序

Keil 启动文件详解_第6张图片

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hETA05F4-1575439728535)(B1AAD5E21B954D348E83C881B999DD57)]

    定义一个名为|.text|的代码段,可读。复位子程序是系统上电后第一个执行的程序,调用SystemInit函数初始化系统时钟,然后调用C库__mian,然后调用main函数去C世界。

    ==SystemInit()==是一个标准的库函数,在system_stm32f7xx.c这个库文件中定义。主要作用是配置系统时钟,这里调用这个函数后,F767的系统时钟被配置成16M。

    __main是一个标准C库函数,主要作用是初始化用户堆栈,最终调用main函数取C世界,这就是为什么我们写的程序都有一个main函数的原因。如果我们在这里不调用__main,那么程序最终就不会调用我们C文件里面的main,但是也可以自己定义main,然后在systeminit之后直接调用。

Keil 启动文件详解_第7张图片

介绍几个常用的指令:

Keil 启动文件详解_第8张图片

1.3.5 中断服务程序

    启动文件中已经写好了所有的中断服务函数,但是只是占个位置,具体的实现需要在C代码中定义相同名字的函数。如果没有定义相关C函数,就会用默认的启动文件中的ISR,进入无限循环。

Keil 启动文件详解_第9张图片

1.3.6 用户堆栈初始化

Keil 启动文件详解_第10张图片
    ALIGN,对指令或者数据存放的地址进行对齐,后面会跟着一个立即数,如果没有的话,默认4字节对齐。

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

1.4 系统启动流程

    1. 系统上电
    1. 从0x00000000处取出MSP
    1. 从0x00000004处取出PC的初始值,这个值就是复位向量Reset_Handler,LSB必须是1
    1. 跳转到复位向量,执行Reset_Handler函数
    1. 执行C库的__main()函数
    1. 跳转到我们定义的main函数

Keil 启动文件详解_第11张图片

注:这和传统的ARM架构不同,传统的ARM架构总是从0地址开始执行第一条指令。他们的0地址总是一条跳转指令。但是在CM3/4中,在0地址处提供的是MSP的初始值,然后紧跟着是向量表中的函数地址,向量表的第一个条目指向复位向量,即Reset_Handler函数。

Keil 启动文件详解_第12张图片

    1. 因为CM3使用的是向下生长的满栈,所以MSP的初始值必须是满堆栈内存的莫地址加1。例如:如果我们的堆栈区域在0x20007c00~0x20007fff之间,那么MSP的初始值就必须是0x20008000。
    1. 向量表跟最在MSP之后。要注意CM3/4是在Thumb态下执行的,所以向量表中的每个数据都必须把LSB置1(也就是奇数)。正是因为这个原因,图14-3中使用0x101来表示0x100地址。
    1. 在进入C世界之间初始化MSP时必须的,因为如果还没初始化好MSP就来了NMI或者Hardfault。MSP初始化好后就已经为他们的服务例程准备好了堆栈。

你可能感兴趣的:(Cortex-M系列内核,启动文件,Keil,MDK,Cortex-M)