【STM32-启动文件 startup_stm32f103xe.s】

STM32-启动文件 startup_stm32f103xe.s

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/734837.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

用Java获取键盘输入数的个十百位数

这段Java代码是一个简单的程序,用于接收用户输入的一个三位数,并将其分解为个位、十位和百位数字,然后分别打印出来。下面是代码的详细解释: 导入所需类库: import java.util.Scanner;:导入Scanner类,用于从…

使用Gradle查看Android项目中库的依赖关系

| | -- com.android.support:support-compat:25.3.1 | | | — com.android.support:support-annotations:25.3.1 | | -- com.android.support:support-media-compat:25.3.1 | | | -- com.android.support:support-annotations:25.3.1 | | | — com.android.support:support…

Windows10中端口被占用处理方法

前言 在Windows 10中,查看端口被占用情况的方法主要依赖于命令行工具netstat。以下是详细步骤,以及必要的解释和归纳: 打开命令提示符 方法1:使用快捷键Win R,打开“运行”对话框,输入cmd,然…

深入讲解C++基础知识(一)

目录 一、基本内置类型1. 类型的作用2. 分类3. 整型3.1 内存描述及查询3.2 布尔类型 —— bool3.3 字符类型 —— char3.4 其他整型 4. 有符号类型和无符号类型5. 浮点型6. 如何选择类型7. 类型转换7.1 自动类型转换7.2 强制类型转换7.3 类型转换总结 8. 类型溢出8.1 注意事项 …

5.XSS-反射型(post)利用:获取cookie

原理: 文件路径:\pikachu\pkxss\xcookie\post.html 将post.html文件,复制到皮卡丘的根路径下或者根下随意路径即可,并编辑文件 需要修改以下两个地址,第一个地址是将原界面的样子链接过来,让用户认为是原…

数据结构十三:2 - 3树和红黑树

一开始就接触这五点,会让人云里雾里,不利于了解这个数据结构。因为这种先给定义在推导的方式并不适合学习。它没有介绍红黑树的来源,而只是给你生硬的定义。 而学习红黑树的最好学习资料就是大名鼎鼎的《算法4》,如下&#xff1a…

微型操作系统内核源码详解系列五(四):cm3下svc启动任务

系列一:微型操作系统内核源码详解系列一:rtos内核源码概论篇(以freertos为例)-CSDN博客 系列二:微型操作系统内核源码详解系列二:数据结构和对象篇(以freertos为例)-CSDN博客 系列…

LabVIEW电控旋翼测控系统

开发基于LabVIEW开发的电控旋翼测控系统,通过高效监控和控制提升旋翼系统的性能和安全性。系统集成了多种硬件设备,采用模块化设计,实现复杂的控制和数据处理功能,适用于现代航空航天领域。 项目背景 传统旋翼系统依赖机械和液压…

算法04 模拟算法之一维数组相关内容详解【C++实现】

大家好,我是bigbigli,模拟算法我们将分为几个章节来讲,今天我们只看一维数组相关的题目 目录 模拟的概念 训练:开关灯 解析 参考代码 训练:数组变化 解析 参考代码 训练:折叠游戏 解析 参考代码 …

vscode+picgo+gitee实现Markdown图床

vscode中编辑Markdown文件,复制的图片默认是保存在本地的。当文档上传csdn时,会提示图片无法识别 可以在gitee上创建图床仓库,使用picgo工具上传图片,在Markdown中插入gitee链接的方式来解决该问题。 一、 安装picgo工具 1.1 v…

IOS开发学习日记(十六)

目录 App间的唤起和通信 App跳转 通过Scheme唤起其他App Universal Link 组件化 App间的唤起和通信 App跳转 使用URL Scheme支持App启动、跳转及参数传递 分享 / 登陆 / 拉起App Store等 设置URL Type 在UIApplication中处理参数和业务逻辑 -(BOOL)application:(UIApp…

强化安全新篇章:韶关石油化工可燃气体报警器年检解析

韶关,这座位于广东省北部的城市,近年来在石油化工行业取得了显著的发展。 随着一批批大型石化企业的进驻和投产,韶关不仅成为了区域性的石化产业基地,也为地方经济带来了强劲的增长动力。 然而,随着石化产业的快速发…

Selenium WebDriver - 网络元素

本文翻译整理自:https://www.selenium.dev/documentation/webdriver/elements/ 文章目录 一、文件上传二、定位策略1、传统定位器2、创建定位器3、类名4、CSS选择器5、id6、NAME7、链接文本8、部分链接文本9、标签名称10、xpath11、相对定位器它是如何工作的可用相对…

2024最新版Python 3.12.4安装使用指南

2024最新版Python 3.12.4安装使用指南 2024最新版Python 3.12.4安装使用指南0. Python的受欢迎程度1. 安装最新版Python 3.12.42. 验证Python 3.12.4版本3. 验证Python功能4. 使用IDLE交互式开发模式5. 安装Python扩展库相关阅读: By Jackson 2024最新版Python 3.12…

【每日刷题】Day73

【每日刷题】Day73 🥕个人主页:开敲🍉 🔥所属专栏:每日刷题🍍 🌼文章目录🌼 1. 2583. 二叉树中的第 K 大层和 - 力扣(LeetCode) 2. 1325. 删除给定值的叶子…

为 Android 应用打造精良的 Chrome OS 使用体验

override fun onKeyUp(code: Int, ev: KeyEvent?): Boolean { return when (code) { KeyEvent.KEYCODE_J -> { // Do something here true } else -> super.onKeyUp(code, ev) // 重要!! } } 注意我们标出 “重要” 的那一行代码。这行代…

同步FIFO

描述 根据题目提供的双口RAM代码和接口描述,实现同步FIFO,要求FIFO位宽和深度参数化可配置。 电路的接口如下图所示。 端口说明如下表。 双口RAM端口说明: 端口名 I/O 描述 wclk input 写数据时钟 wenc input 写使能 waddr input…

分布式架构的优势与实现

目录 前言1. 什么是分布式架构1.1 分布式架构的定义1.2 分布式架构的基本原理 2. 分布式架构的优势2.1 可扩展性2.2 容错性和高可用性2.3 性能优化2.4 灵活性和可维护性 3. 分布式架构的实现方法3.1 服务拆分3.1.1 功能拆分3.1.2 垂直拆分3.1.3 水平拆分 3.2 数据分布与存储3.2…

RabbitMQ消息队列 安装及基本介绍

一.MQ介绍 Message Queue (MQ)是一种跨进程的通信机制,用于在系统之间进行传递消息。MQ作为消息中间件,可以进行异步处理请求,从而减少请求响应时间和解耦 1.1 应用场景 1.1.1 系统之间通过MQ进行消息通信&#xff0…