当前位置: 首页 > article >正文

STM32启动文件浅析

目录

  • STM32启动文件简介
    • 启动文件中的一些指令
  • 启动文件代码详解
    • 栈空间的开辟
    • 堆空间的开辟
    • 中断向量表定义(简称:向量表)
    • 复位程序
      • 对于weak的理解
      • 对于_main函数的分析
    • 中断服务程序
    • 用户堆栈初始化
  • 系统启动流程
  • 其他

STM32启动文件简介

STM32启动文件由ST官方提供,在官方的固件包里。启动文件由汇编编写,是系统上电复位后第一个执行的程序。

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

  1. 初始化堆栈指针 SP = _initial_sp
  2. 初始化程序计数器指针 PC = Reset_Handler
  3. 设置堆和栈的大小
  4. 初始化中断向量表
  5. 配置外部SRAM作为数据存储器(可选)
  6. 配置系统时钟,通过调用SystemInit函数(可选)
  7. 调用C库中的 _main 函数初始化用户堆栈,最终调用 main 函数

ARM指针寄存器 —— 堆栈指针寄存器SP、程序计数器PC、连接寄存器LR

堆栈指针R13(SP):每一种异常模式都有其自己独立的R13,它通常指向异常模式所专用的堆栈,也就是说五种异常模式、非异常模式(用户模式和系统模式),都有各自独立的堆栈,用不同的堆栈指针来索引。这样当ARM进入异常模式的时候,程序就可以把一般通用寄存器压入堆栈,返回时再出栈,保证了各种模式下程序的状态的完整性;

连接寄存器R14(LR):每种模式下R14都有自身版组,它有两个特殊功能;

  1. 保存子程序返回地址;使用BL或BLX时,跳转指令自动把返回地址放入R14中;子程序通过把R14复制到PC来实现返回,通常用下列指令之一:

    MOV PC, LR
    BX LR
    

    通常子程序这样写,保证了子程序中还可以调用子程序:

    stmfd sp!, {lr}
    ...
    ldmfd sp!, {pc}
    
  2. 当异常发生时,异常模式的R14用来保存异常返回地址,将R14:如栈可以处理嵌套中断

程序计数器R15(PC):PC是有读写限制的,当没有超过读取限制的时候,读取的值是指令的地址加上8个字节,由于ARM指令总是以字对齐的,故bit[1:0]总是00。当用str或stm存储PC的时候,偏移量有可能是8或12等其它值。

ARM处理器使用流水线来增加处理器指令流的速度,这样可使几个操作同时进行,并使处理与存储器系统之间的操作更加流畅,连续,能提供0.9MIPS/MHZ的指令执行速度。

在这里插入图片描述
在随机存储器区划出一块区域作为堆栈区,数据可以一个个顺序地存入(压入)到这个区域之中,这个过程称为压栈(push )。通常用一个指针(堆栈指针 SP—StackPointer)实现做一次调整,SP总指向最后一个压入堆栈的数据所在的数据单元(栈顶)。从堆栈中读取数据时,按照堆栈 指针指向的堆栈单元读取堆栈数据,这个过程叫做弹出(pop ),每弹出一个数据,SP 即向相反方向做一次调整,如此就实现了后进先出的原则。

堆栈是计算机中广泛应用的技术,基于堆栈具有的数据进出LIFO特性,常应用于:

  1. 保存中断断点;
  2. 保存子程序调用返回点;
  3. 保存CPU现场数据等;
  4. 也用于程序间传递参数;

ARM处理器中通常将寄存器R13作为堆栈指针(SP)。ARM处理器针对不同的模式,共有 6 个堆栈指针(SP),其中用户模式和系统模式共用一个SP,每种异常模式都有各自专用的R13寄存器(SP)。它们通常指向各模式所对应的专用堆栈,也就是ARM处理器允许用户程序有六个不同的堆栈空间。这些堆栈指针分别为R13、R13_svc、R13_abt、R13_und、R13_irq、R13_fiq,如下表堆栈指针寄存器所示。

堆栈指针用户系统管理中止未定义中断快速中断
R13 (SP)R13R13R13_svcR13_abtR13_undR13_irqR13_fiq

为了更准确地描述堆栈,根据“压栈”操作时堆栈指针的增减方向,将堆栈区分为‘递增堆栈’(SP 向大数值方向变化)和‘递减堆栈’(SP 向小数值方向变化);又根据SP 指针指向的存储单元是否含有堆栈数据,又将堆栈区分为‘满堆栈’(SP 指向单元含有堆栈有效数据)和‘空堆栈’(SP 指向单元不含有堆栈有效数据)。

这样两两组合共有四种堆栈方式——满递增、空递增、满递减和空递减。
ARM处理器的堆栈操作具有非常大的灵活性,对这四种类型的堆栈都支持。
ARM处理器中的R13被用作SP。当不使用堆栈时,R13 也可以用做通用数据寄存器。

栈的整体作用

  1. 保护现场;
  2. 传递参数;
  3. 临时变量保存在栈中;

深入理解ARM三个寄存器

在这里插入图片描述

PC 代表程序计数器,流水线使用三个阶段,因此指令分为三个阶段执行:

  1. 取指:从存储器装载一条指令;
  2. 译码:识别将要被执行的指令;
  3. 执行:处理指令并将结果写回寄存器;

R15(PC)总是指向 正在取指 的指令:ARM指令是三级流水线,取指、译指、执行是同时进行的,现在PC指向的是正在取指的地址,那么cpu正在译指的指令地址是PC-4(假设在ARM状态 下,一个指令占4个字节),cpu正在执行的指令地址是PC-8,也就是说PC所指向的地址和现在所执行的指令地址相差8。当突然发生中断的时候,保存的是PC的地址这样你就知道了,如果返回的时候返回PC,那么中间就有一个指令没有执行,所以用 SUB pc lr -lrq #4

启动文件中的一些指令

指令名称作用
EQU给数字常量取一个符号名,相当于C语言中的define
AREA汇编一个新的代码段或者数据段
ALIGN编译器对指令或者数据的存放地址进行对齐,一般需要跟一个立即数,缺省表示4字节对齐。要注意的是,这个不是ARM的指令,是编译器的,这里放到一起为了方便
SPACE分配空间
PRESERVE8当前文件堆栈需要按照8字节对齐
THUMB表示后面指令兼容 THUMB 指令。在 ARM 以前的指令集中有 16 位的THUMBM 指令,现在 Cortex-M 系列使用的都是 THUMB-2 指令集,THUMB-2 是 32 位的,兼容 16 位和 32 位的指令,是 THUMB 的超级版
EXPORT声明一个标号具有全局属性,可被外部的文件使用
DCD以字节为单位分配内存,要求 4 字节对齐,并要求初始化这些内存
PROC定义子程序,与 ENDP 成对使用,表示子程序结束
WEAK弱定义,如果外部文件声明了一个标号,则优先使用外部文件定义的标号,如果外部文件没有定义也不会出错。要注意的是,这个不是 ARM 的指令,是编译器的,这里放到一起为了方便
IMPORT声明标号来自外部文件,跟 C 语言中的 extern 关键字类似
LDR从存储器中加载字到一个存储器中
BLX跳转到由寄存器给出的地址,并根据寄存器的 LSE 确定处理器的状态,还要把跳转前的下条指令地址保存到 LR
BX跳转到由寄存器/标号给出的地址,不用返回
B跳转到一个标号
IF,ELSE,ENDIF汇编条件分支语句,跟 C 语言的类似
END到达文件的末尾,文件结束

启动文件代码详解

下面,我们以 STM32F103 的启动代码为例讲解,版本是:STM32Cube_FW_F1_V1.8.0,
启动文件名称是:startup_stm32f103xe.s。把启动代码分成几个功能段进行详细的讲解,详
情如下

栈空间的开辟

Stack_Size		EQU		0x00000400

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

**EQU:**宏定义的伪指令,给数字常量取一个符号名,类似与 C 中的 define。定义栈大小为 0x00000400 字节,即 1024B(1KB),常量的符号是 Stack_Size。

AREA 汇编一个新的代码段或者数据段。段名为 STACK,段名可以任意命名;NOINIT 表示不初始化; READWRITE 表示可读可写;ALIGN=3,表示按照 2^3 对齐,即 8 字节对齐。

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

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

栈主要用于存放局部变量,函数形参等,属于编译器自动分配和释放的内存,栈的大小不能超过内部 SRAM 的大小。如果工程的程序量比较大,定义的局部变量比较多,那么就需要在启动代码中修改栈的大小,即修改 Stack_Size 的值。如果程序出现了莫名其妙的错误,并进入了 HardFault 的时候,你就要考虑下是不是栈空间不够大,溢出了的问题。

堆空间的开辟

Heap_Size		EQU		0x00000200

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

堆空间开辟代码跟栈空间开辟代码是类似的了。这部分代码的意思就是:开辟堆的大小为 0x00000200(512 字节),段名为 HEAP,不初始化,可读可写,(2^3 对齐)8 字节对齐。__heap_base表示堆的起始地址__heap_limit 表示堆的结束地址。堆和栈的生长方向相反的,堆是由低向高生长而栈是从高往低生长

堆主要用于动态内存的分配,像 malloc()、calloc()和 realloc()等函数申请的内存就在堆上面。堆中的内存一般由程序员分配和释放,若程序员不释放,程序结束时可能由操作系统回收。

PRESERVE8
THUMB

PRESERVE8:指示编译器按照 8 字节对齐。
THUMB:指示编译器之后的指令为 THUMB 指令。

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

中断向量表定义代码

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

定义一个数据段,名字为RESET, READONLY表示只读EXPORT表示声明一个标号具有全局属性,可被外部的文件使用。这里是声明了__Vectors、__Vectors_End 和 __Vectors_Size 三个标号具有全局性,可被外部的文件使用

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

复位程序

位置优先级优先级类型名称说明地址
---保留0x0000_0000
-3固定Reset复位0x0000_0004
-2固定NMI不可屏蔽中断;RCC时钟安全系统(CSS)联接到NMI向量0x0000_0008
-1固定硬件失效(HardFault)所有类型的失效0x0000_000C
0可设置存储管理(MemManage)存储器管理0x0000_0010
1可设置总线错误(BusFault)预取值失败,存储器访问失败0x0000_0014
2可设置错误应用(UsageFault)未定义的指令或非法状态0x0000_0018
---保留0x0000_001C ~ 0x0000_002B
3可设置SVCall通过SWI指令的系统服务调用0x0000_002C
4可设置调式监控(DebugMonitor)调试监控器0x0000_0030
---保留0x0000_0034
5可设置PendSV可挂起的系统服务0x0000_0038
6可设置SysTick系统嘀嗒定时器0x0000_003C
07可设置WWDG窗口定时器中断0x0000_0040
18可设置PVD连到EXTI的电源电压检测(PVD)中断0x0000_0044
29可设置TAMPER侵入检测中断0x0000_0048
310可设置RTC实时时钟(RTC)全局中断0x0000_004C
411可设置FLASH内存全局中断0x0000_0050

中间部分省略,详情请参考《STM32中文参考手册》第九章 中断和事件 中断和异常向量

5663可设置DMA2通道1DMA2通道1全局中断0x0000_0120
5764可设置DMA2通道2DMA2通道2全局中断0x0000_0124
5865可设置DMA2通道3DMA2通道3全局中断0x0000_0128
5966可设置DMA2通道4_5DMA2通道4和DMA2通道5全局中断0x0000_012C

举个例子,如果发生了异常 SVCall,则 NVIC 会计算出偏移移量是 11x4=0x2C,然后从那里取出服务例程的入口地址并跳入。要注意的是这里有个另类:地址 0x0000 0000 并不是什么入口地址,而是给出了复位后 MSP 的初值。更详细的向量表,可以参考《STM32中文参考手册》第九章-中断和事件-中断和异常向量

F103 的向量表格中灰色部分是系统内核异常。表格中位置 0 到 59 是外部中断,CM3内核的芯片最大支持 240 个外部中断,具体使用多少个由芯片厂家设计决定。如这个表格中的 103 芯片只是使用了 60 个。这里说的外部中断是相对内核而言

__Vectors		DCD		__initial_sp		;	Top of Stack	(栈顶地址)
				DCD		Reset_Handler		;	Reset Handler	(复位程序地址)
				DCD		NMI_Handler			;	NMI Handler
				DCD		MemManage_Handler	;	H

对于weak的理解

对于_main函数的分析

中断服务程序

用户堆栈初始化

系统启动流程

其他


http://www.kler.cn/news/359213.html

相关文章:

  • 使用JVM分析服务性能问题
  • AI Infra 如何打造?云轴科技ZStack在中国CID大会上主题演讲
  • uni-app 开发微信小程序,实现图片预览和保存
  • 光伏工程造价单自动生成
  • 写了十几年程序,今天才第一天知道什么是屎山代码
  • 基于SSM的网上拍卖平台
  • 我谈傅里叶变换幅值谱的显示
  • 子比美化 – WP添加网站翻译功能 | 实现国际化多语言[js翻译]
  • 蔡顺华演讲《演说艺术的理论与实践》新书首发式在贵阳成功举行
  • 第八课 Vue中的v-bind指令
  • git init更改本地默认分支
  • 富格林:防备出金失败安全指南
  • 《深度学习》模型的部署、web框架 服务端及客户端案例
  • SD-WAN可以搭建在任何网络上,通过中央控制器管理企业所有用户的终端路由器,实现集中配置和监控。
  • 【学术会议征稿】第五届材料化学与复合材料国际学术会议(MCCM 2024)
  • 基于Multisim模拟乒乓球游戏竞赛电路(含仿真和报告)
  • 大数据-180 Elasticsearch - 原理剖析 索引写入与近实时搜索
  • 全方面熟悉Maven项目管理工具(二)坐标、pom.xml文件的解读!
  • VSCODE使用记录
  • 根据日志优化微调