【STM32-启动文件 startup_stm32f103xe.s】

STM32-启动文件 startup_stm32f103xe.s

  • [■ STM32-启动文件](#■ STM32-启动文件)
  • [■ STM32-启动文件主要做了以下工作:](#■ STM32-启动文件主要做了以下工作:)
  • [■ STM32-启动文件指令](#■ STM32-启动文件指令)
  • [■ STM32-启动文件代码详解](#■ STM32-启动文件代码详解)
    • [■ 栈空间的开辟](#■ 栈空间的开辟)
    • [■ 栈空间大小 Stack_Size](#■ 栈空间大小 Stack_Size)
    • [■ .map 文件的详细介绍](#■ .map 文件的详细介绍)
      • [■ 打开map文件](#■ 打开map文件)
    • [■ 堆空间](#■ 堆空间)
    • [■ PRESERVE8 和 THUMB 指令](#■ PRESERVE8 和 THUMB 指令)
  • [■ 中断向量表定义(简称:向量表)???????](#■ 中断向量表定义(简称:向量表)???????)
  • [■ 复位程序](#■ 复位程序)
  • [■ weak](#■ weak)
  • [■ _main 函数的分析](#■ _main 函数的分析)
    • [■ __scatterload()函数](#■ __scatterload()函数)
    • [■ __rt_entry()函数](#■ __rt_entry()函数)
  • [■ 中断服务程序](#■ 中断服务程序)
  • [■ ALIGN指令](#■ ALIGN指令)
  • [■ 用户堆栈初始化](#■ 用户堆栈初始化)
  • [■ Use MicroLIB](#■ Use MicroLIB)
  • [■ 系统启动流程](#■ 系统启动流程)
    • [■ 代码下载到内部 FLASH 的情况举例,即代码从地址 0x0800 0000 开始被执行分析](#■ 代码下载到内部 FLASH 的情况举例,即代码从地址 0x0800 0000 开始被执行分析)
      • [■ 1.CM3 内核做的第一件事就是读取下列两个 32 位整数的值:](#■ 1.CM3 内核做的第一件事就是读取下列两个 32 位整数的值:)
      • [■ 2.MSP 和 PC](#■ 2.MSP 和 PC)
      • [■ 3.代码从地址 0x0800 0000 开始被执行,讲解一下系统启动,初始化堆栈、 MSP 和 PC 后的内存情况。](#■ 3.代码从地址 0x0800 0000 开始被执行,讲解一下系统启动,初始化堆栈、 MSP 和 PC 后的内存情况。)

■ STM32-启动文件

STM32 启动文件由 ST 官方提供

启动文件由汇编编写,是系统上电复位后第一个执行的程序。

■ STM32-启动文件主要做了以下工作:

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

2、初始化程序计数器指针 PC = Reset_Handler

3、设置堆和栈的大小

4、初始化中断向量表

5、配置外部 SRAM 作为数据存储器(可选)

6、配置系统时钟,通过调用 SystemInit 函数(可选)

7、调用 C 库中的 _main 函数初始化用户堆栈,最终调用 main 函数

■ STM32-启动文件指令

关于其他更多的 ARM 汇编,我们可以通过 MDK 的索引搜索工具中搜索找到。打开索引搜索工具的方法:

MDK->Help->uVision Help,

■ STM32-启动文件代码详解

例如:startup_stm32f103xe.s 把启动代码分成几个功能段进行详细的讲解

■ 栈空间的开辟

栈是从高往低生长,所以每使用一个栈空间地址,栈顶地址__initial_sp 就减一。

33 行 EQU:宏定义的伪指令, 给数字常量取一个符号名, 类似与 C 中的 define。

定义栈大小为 0x00000400 字节,即 1024B(1KB),常量的符号是 Stack_Size。

35 行 AREA 汇编一个新的代码段或者数据段。

段名为 STACK, 段名可以任意命名;NOINIT 表示不初始化; READWRITE 表示可读可写; ALIGN=3,表示按照 2^3 对齐,即 8 字节对齐。

36 行 SPACE 分配内存指令, 分配大小为 Stack_Size 字节连续的存储单元给栈空间。

37 行__initial_sp 紧挨着 SPACE 放置,表示栈的结束地址,栈是从高往低生长,所以结束地址就是栈顶地址

栈主要用于存放局部变量,函数形参等, 属于编译器自动分配和释放的内存, 栈的大小不能超过内部 SRAM 的大小。如果工程的程序量比较大,定义的局部变量比较多,那么就需要在启动代码中修改栈的大小,即修改 Stack_Size 的值。

如果程序出现了莫名其妙的错误,并进入了 HardFault 的时候,你就要考虑下是不是栈空间不够大,溢出了的问题。

■ 栈空间大小 Stack_Size

  1. 打开.map文件 搜索__initial_sp

    栈顶地址 __initial_sp 的地址是 0x20000538
    栈低地址 STACK 0x20000138
    Stack_Size = 栈顶地址-栈低地址 Stack_Size 的大小是 0x00000400

■ .map 文件的详细介绍

■ 打开map文件

■ 堆空间

这部分代码的意思就是:开辟堆的大小为 0x00000200(512 字节),

段名为 HEAP ,NOINIT不初始化,READWRITE可读可写, ALIGN=3,表示按照 2^3 对齐,即 8 字节对齐。

__heap_base表示堆的起始地址,

__heap_limit 表示堆的结束地址。

堆和栈的生长方向相反的,堆是由低向高生长,而栈是从高往低生长。

注意点:

由于不需要使用 C 库的 malloc 和 free 等函数,也就用不到堆空间,因此我们可以设置 Heap_Size 的大小为 0,以节省内存空间

■ PRESERVE8 和 THUMB 指令

PRESERVE8: 指示编译器按照 8 字节对齐。

THUMB: 指示编译器之后的指令为 THUMB 指令。

■ 中断向量表定义(简称:向量表)???

定义一个数据段,

RESET, READONLY 表示只读。

EXPORT 表示声明一个标号具有全局属性,可被外部的文件使用。

这里是声明了__Vectors、 __Vectors_End 和 __Vectors_Size 三个标号具有全局性,可被外部的文件使用。

__Vectors 为向量表起始地址,

__Vectors_End 为向量表结束地址,

__Vectors_Size 为向量表大小, __Vectors_Size = __Vectors_End - __Vectors。

向量表其实是一个 WORD(32 位整数)数组,每个下标对应一种异常,该下标元素的值则是该 ESR 的入口地址。
向量表在地址空间中的位置是可以设置的,通过 NVIC 中的一个重定位寄存器来指出向量表的地址。

在复位后,该寄存器的值为 0。

因此,在地址 0 (即 FLASH 地址 0) 处必须包含一张向量表,用于初始时的异常分配。如下:

要注意的是这里有个另类: 地址 0x0000 0000 并不是什么入口地址,而是给出了复位后 MSP 的初值。

向量表格中灰色部分是系统内核异常

表格中位置 0 到 59 是外部中断

DCD: 分配一个或者多个以字为单位的内存,以四字节对齐,并要求初始化这些内存。

???????????

■ 复位程序

定义一个段命为.text, 只读的代码段, 在 CODE 区。

利用 PROC、 ENDP 这一对伪指令把程序段分为若干个过程,使程序的结构加清晰。

关键词 描述
145 行 子程序开始
146 行 声明复位中断向量 Reset_Handler 为全局属性,这样外部文件就可以调用此复位中断服务。 WEAK:表示弱定义,如果外部文件优先定义了该标号则首先引用外部定义的标号,如果外部文件没有声明也不会出错。 这里表示复位子程序可以由用户在其他文件重新实现,这里并不是唯一的。
147 行和 148 行 IMPORT 表示该标号来自外部文件。 这里表示 SystemInit 和__main 这两个函数均来自外部的文件。
149 行 LDR 表示从存储器中加载字到一个存储器中。 SystemInit 是一个标准的库函数,在 system_stm32f1xx.c 文件中定义,主要作用是配置系统时钟、还有就是初始化 FSMC/FMC总线上外挂的 SRAM(可选),前面说配置外部 SRAM 作为数据存储器(可选)就是这个。
150 行 BLX 表示跳转到由寄存器给出的地址,并根据寄存器的 LSE 确定处理器的状态,还要把跳转前的下条指令地址保存到 LR。
151 行 把__main 的地址给 R0。 __main 是一个标准的 C 库函数,主要作用是初始化用户堆栈和变量等,最终调用 main 函数去到 C 的世界。 这就是为什么我们写的程序都有一个 main 函数的原因,如果不调用__main,那么程序最终就不会调用我们 C 文件里面的main,也就无法正常运行。
152 行 BX 表示跳转到由寄存器/标号给出的地址,不用返回。 这里表示切换到__main地址,最终调用 main 函数,不返回,进入 C 的世界。
153 行 ENDP 表示子程序结束。

LDR、 BLX、 BX 是内核的指令,可在《CM3 权威指南 CnR2》第四章-指令集里面查询到。

■ weak

weak 顾名思义是"弱"的意思,

在汇编中, 在函数名称后面加[WEAK]来表示

在 C语言中,在函数名称前面加上__weak 修饰符来表示, 这样的函数我们称为"弱函数"。

■ _main 函数的分析

_main 和 main 是两个完全不同的函数。

_main 代码是编译器自动创建的,因此无法找到_main 代码。

当编译器发现定义了 main 函数,那么就会自动创建_main。

程序经过汇编启动代码,执行到__main()后,可以看出有两个大的函数:

__scatterload():负责把 RW/RO 输出段从装载域地址复制到运行域地址,并完成了 ZI运行域的初始化工作。

__rt_entry():负责初始化堆栈,完成库函数的初始化,最后自动跳转向 main()函数。

■ __scatterload()函数

■ __rt_entry()函数

■ 中断服务程序

这些中断服务函数都被[WEAK]声明为弱定义函数

中断函数分为系统异常中断和外部中断,外部中断根据不同芯片有所变化。

B 指令是跳转到一个标号,这里跳转到一个'.',表示无限循环。

中断发生时,程序就会跳转到启动文件预先写好的弱定义的中断服务程序中,并且在 B 指令作用下跳转到一个'.'中,无限循环。

■ ALIGN指令

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

要注意的是,这个不是 ARM 的指令,是编译器的。

■ 用户堆栈初始化

== IF, ELSE, ENDIF 是汇编的条件分支语句。==

关键词 描述
331 行 判断是否定义了__MICROLIB。 关于__MICROLIB 这个宏定义,我们是在 KEIL 里面配置。 勾选了 Use MicroLIB 就代表定义了__MICROLIB 这个宏。
333 行到 335 行 如果定义__MICROLIB, 声明__initial_sp、 __heap_base 和__heap_limit 这三个标号具有全局属性,可被外部的文件使用。 __initial_sp 表示栈顶地址, __heap_base 表示堆起始地址, __heap_limit 表示堆结束地址。
337 行 没有定义__MICROLIB,实际的情况就是我们没有定义__MICROLIB,所以使用默认的 C 库运行。 堆栈的初始化由 C 库函数__main 来完成。
339 行 IMPORT 声明__use_two_region_memory 标号来自外部文件。
340 行 EXPORT 声明__user_initial_stackheap 具有全局属性,可被外部的文件使用。
342 行 标号__user_initial_stackheap,表示用户堆栈初始化程序入口。 接下来进行堆栈空间初始化,堆是从低到高生长,栈是从高到低生长,是两个互相独立的数据段,并且不能交叉使用。
344 行 保存堆起始地址。
345 行 保存栈大小。
346 行 保存堆大小。
347 行 保存栈顶指针。
348 行 跳转到 LR 标号给出的地址,不用返回。
354 行 END 表示到达文件的末尾,文件结束。

■ Use MicroLIB

MicroLIB 是 MDK 自带的微库,是缺省 C 库的备选库, MicroLIB 进行了高度优化使得

其代码变得很小,功能比缺省 C 库少。

MicroLIB 是没有源码的,只有库。

关于 MicroLIB 更多知识可以看官方介绍 http://www.keil.com/arm/microlib.asp

■ 系统启动流程

Cortex-M3 内核复位后的起始地址和中断向量表的位置可以被重映射。充映射的方法是通过启动模式的

选择, 有以下 3 种情况:

1、 通过 boot 引脚设置可以将中断向量表定位于 SRAM 区,即起始地址为 0x2000000,同时复位后 PC 指针位于 0x2000000 处;

2、 通过 boot 引脚设置可以将中断向量表定位于 FLASH 区,即起始地址为 0x8000000,同时复位后 PC 指针位于 0x8000000 处;

3、 通过 boot 引脚设置可以将中断向量表定位于内置 Bootloader 区,本文不对这种情况做论述。

Cortex-M3 内核规定,**起始地址必须存放堆顶指针,而第二个地址则必须存放复位中断入口向量地址,**这样在 Cortex-M3 内核复位后,会自动从起始地址的下一个 32 位空间取出复位中断入口向量,跳转执行复位中断服务程序。

Cortex-M3 权威指南(中文)

■ 代码下载到内部 FLASH 的情况举例,即代码从地址 0x0800 0000 开始被执行分析

复位方式有三种:上电复位,硬件复位和软件复位当产生复位,并且离开 复位状态后,

■ 1.CM3 内核做的第一件事就是读取下列两个 32 位整数的值:

(1)从地址 0x0800 0000 处取出堆栈指针 MSP 的初始值,该值就是栈顶地址。

(2)从地址 0x0800 0004 处取出程序计数器指针 PC 的初始值,该值指向复位后执行的第一条指令。 下面用示意图表示

■ 2.MSP 和 PC

例如:

CM3内核是小端模式,所以倒着读。

0x08000000 的值是 0x20000788 堆栈指针 SP = 0x20000788

0x08000004 的值是 0x080001CD 程序计数器指针 PC = 0x080001CD

■ 3.代码从地址 0x0800 0000 开始被执行,讲解一下系统启动,初始化堆栈、 MSP 和 PC 后的内存情况。

ARM 规定: PC最低两位并不表示真实地址,最低位 LSB 用于表示是 ARM 指令( 0)还是 Thumb 指令( 1)

相关推荐
yutian06063 小时前
Keil MDK下载程序后MCU自动重启设置
单片机·嵌入式硬件·keil
析木不会编程6 小时前
【小白51单片机专用教程】protues仿真独立按键控制LED
单片机·嵌入式硬件·51单片机
枯无穷肉9 小时前
stm32制作CAN适配器4--WinUsb的使用
stm32·单片机·嵌入式硬件
不过四级不改名67710 小时前
基于HAL库的stm32的can收发实验
stm32·单片机·嵌入式硬件
嵌入式科普10 小时前
十一、从0开始卷出一个新项目之瑞萨RA6M5串口DTC接收不定长
c语言·stm32·cubeide·e2studio·ra6m5·dma接收不定长
嵌入式大圣10 小时前
单片机UDP数据透传
单片机·嵌入式硬件·udp
云山工作室11 小时前
基于单片机的视力保护及身姿矫正器设计(论文+源码)
stm32·单片机·嵌入式硬件·毕业设计·毕设
嵌入式-老费11 小时前
基于海思soc的智能产品开发(mcu读保护的设置)
单片机·嵌入式硬件
qq_3975623112 小时前
MPU6050 , 设置内部低通滤波器,对于输出数据的影响。(简单实验)
单片机
liyinuo201712 小时前
嵌入式(单片机方向)面试题总结
嵌入式硬件·设计模式·面试·设计规范