【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
- 打开.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)
相关文章:
【STM32-启动文件 startup_stm32f103xe.s】
STM32-启动文件 startup_stm32f103xe.s ■ STM32-启动文件■ STM32-启动文件主要做了以下工作:■ STM32-启动文件指令■ STM32-启动文件代码详解■ 栈空间的开辟■ 栈空间大小 Stack_Size■ .map 文件的详细介绍■ 打开map文件 ■ 堆空间■ PRESERVE8 和 THUMB 指令…...
51学习记录(一)——51介绍及震动感应灯
文章目录 前言一、STC89C522.内部结构及引脚 二、继电器原理及震动传感器原理三、项目搭建及实现 前言 一个学习嵌入式的小白~ 有问题评论区或私信指出 提示:以下是本篇文章正文内容,下面案例可供参考 一、STC89C52 1.简介 所属系列:51单…...
2024GLEE生活暨教育(上海)博览会,8月20-22日,国家会展中心(上海)
2024GLEE生活暨教育(上海)博览会将于8月20-22日在中国国家会展中心(上海)举行,博览会总面积近万平方米,设有美好生活和教育产品两大主力展区,全面覆盖婴幼儿、学龄前、小学、初中、高中、大学、中年、老年各个年龄段的…...
debug调试高级功能 断点、布局 及Android Studio常用快捷按键使用详情
文章目录 debug断点篇:打临时断点(只用一次):alt断点条件断点:在断点上,点击右键,在Condition那里,设置我们需要的值,循环就会自动停到我们设置的那个值那里依赖断点&…...
51单片机STC89C52RC——6.1 中断系统
一,文字层面理解 反正我看下面的几段文字时脑壳没有正常运转。一个头几个大 中断系统是为使CPU具有对外界紧急事件的实时处理能力而设置的。 当中央处理机CPU正在处理某件事的时候外界发生了紧急事件请求,要求CPU暂停当前的工作,转而去处理这…...
Redis源码学习:高性能Hash表的设计与实现
哈希表(Hash)是Redis数据库的数据类型之一,理解哈希表的实现对于掌握Redis非常重要。这篇文章,从哈希冲突和哈希扩展这两个角度,来一步步讲解Redis哈希表的工作原理。 什么是哈希表? 哈希表是一种通过哈希…...
如何防范常见的数据库安全问题
随着数据量的增加和系统的复杂性提高,数据库可能面临多种安全威胁,包括未授权访问、数据泄露、注入攻击等。 1. 未授权访问 未授权访问是指,未经授权的用户对数据库的内容进行访问。这会导致数据泄露、数据篡改或其他安全事故。 针对未授权访问的防范措施如下。 (1)强化…...
[Day 19] 區塊鏈與人工智能的聯動應用:理論、技術與實踐
區塊鏈的數據透明性 區塊鏈技術作為一種分布式賬本技術,因其去中心化、不可篡改和高度透明的特性,已經在各行各業中得到了廣泛應用。在本文中,我們將深入探討區塊鏈的數據透明性,包括其原理、實現方法及相關代碼示例,…...
【Hadoop学习笔记】认识Hadoop
认识Hadoop 从网上找的课程做的笔记,有些图是自己理解画的,可能不正确,可以作为参考,有疑问的地方请直接指出,共同交流。 Hadoop是由Apache基金会开发的一个分布式系统基础架构,主要解决海量数据的存储和海…...
CISP-PTE综合靶机-WinServer2003
1.收集网站的地址和开放的端口,完成前期信息收集。10分 2.访问站点,找出站点的敏感文件,利用返回数据找到相关敏感信 息,完成网站结构的信息收集。10分 3.利用文件包含漏洞读取敏感文件,找出数据库连接凭证,利用此 凭证连接数据库。10分 4.网站后台提权:找出后台管理员登…...
sklearn之各类朴素贝叶斯原理
sklearn之贝叶斯原理 前言1 高斯朴素贝叶斯1.1 对连续变量的处理1.2 高斯朴素贝叶斯算法原理 2 多项式朴素贝叶斯2.1 二项分布和多项分布2.2 详细原理2.3 如何判断是否符合多项式贝叶斯 3 伯努利朴素贝叶斯4 类别贝叶斯4 补充朴素贝叶斯4.1 核心原理4.2 算法流程 前言 如果想看…...
年薪50w+的项目经理,手把手教你如何复盘
复盘是一种重要的学习和改进工具,对于项目经理来说,能帮助识别项目中的成功与失败,为未来的项目管理提供宝贵经验。 理论部分 定义目标。在开始复盘之前,明确复盘的目标是什么。是为了找出项目中的问题并提出解决方案,…...
Web3新视野:Lumoz节点的潜力与收益解读
摘要:低估值、高回报、无条件退款80%...... Lumoz正通过其 zkVerifier 节点销售活动,引领一场ZK计算革命。 长期以来,加密市场以其独特的波动性和增长潜力,持续吸引着全球投资者的目光。而历史数据表明,市场往往在一年…...
【shell脚本速成】mysql备份脚本
文章目录 案例需求脚本应用场景:解决问题脚本思路实现代码 🌈你好呀!我是 山顶风景独好 🎈欢迎踏入我的博客世界,能与您在此邂逅,真是缘分使然!😊 🌸愿您在此停留的每一刻…...
高考志愿填报,理科生如何分析选专业?
理科生选择专业的范围更大一些,相比文科说理工科的院校也更多,如何选择适合自己的专业,这是一个比较重要的课题,毕竟大学专业直接关系到职业,是一辈子的大事。 那么理科究竟如何选择专业呢?需要从什么地方…...
qt 简单实验 json格式的文件写入配置文件
1.概要 2.代码 //#include "mainwindow.h"#include <QApplication> #include <QFile> #include <QJsonDocument> #include <QJsonObject> //读取json数据的配置文件int main(int argc, char *argv[]) {QApplication a(argc, argv);QString…...
将WIN10的wifi上网分享给以太网接口
目录 打开网络设置设置属性点这里的设置将wlan主机的以太网接口IP设为自动获取 如果连接不成功,拔网线重连一次 打开网络设置 设置属性 点这里的设置 将wlan主机的以太网接口IP设为自动获取 如果连接不成功,拔网线重连一次...
在 iPhone 上恢复已删除联系人的 5 种简便方法
想象一下:您正在 iPhone 上滚动并搜索要拨打的联系人,但却找不到任何结果。然后您想起昨晚您试图删除一个名字相似的联系人,但不知何故删除了错误的联系人。或者您的孩子错误地删除了一些联系人。这些情况足以让您感到迷茫。但别担心…...
小白指南:前端使用javascript如何判断集合是不是空集合?
背景 最近在开发一个Web应用时,我遇到了一个关于集合处理的问题。具体来说,我需要判断一个集合是否为空。集合可以是数组、对象、Map或Set等不同的数据结构。就简单的整理了一下如何在JavaScript中有效地判断一个集合是否为空呢? 解决方案 …...
人力资源招聘社会校企类型招聘系统校园招聘小程序
校企社会人力资源招聘小程序:开启高效招聘新时代 🚀开篇:打破传统,开启招聘新篇章 在快速发展的现代社会,人力资源招聘已经成为企业和学校共同关注的重要议题。为了更高效、便捷地满足双方的招聘需求,一款…...
vscode里如何用git
打开vs终端执行如下: 1 初始化 Git 仓库(如果尚未初始化) git init 2 添加文件到 Git 仓库 git add . 3 使用 git commit 命令来提交你的更改。确保在提交时加上一个有用的消息。 git commit -m "备注信息" 4 …...
Lombok 的 @Data 注解失效,未生成 getter/setter 方法引发的HTTP 406 错误
HTTP 状态码 406 (Not Acceptable) 和 500 (Internal Server Error) 是两类完全不同的错误,它们的含义、原因和解决方法都有显著区别。以下是详细对比: 1. HTTP 406 (Not Acceptable) 含义: 客户端请求的内容类型与服务器支持的内容类型不匹…...
《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)
CSI-2 协议详细解析 (一) 1. CSI-2层定义(CSI-2 Layer Definitions) 分层结构 :CSI-2协议分为6层: 物理层(PHY Layer) : 定义电气特性、时钟机制和传输介质(导线&#…...
使用分级同态加密防御梯度泄漏
抽象 联邦学习 (FL) 支持跨分布式客户端进行协作模型训练,而无需共享原始数据,这使其成为在互联和自动驾驶汽车 (CAV) 等领域保护隐私的机器学习的一种很有前途的方法。然而,最近的研究表明&…...
解锁数据库简洁之道:FastAPI与SQLModel实战指南
在构建现代Web应用程序时,与数据库的交互无疑是核心环节。虽然传统的数据库操作方式(如直接编写SQL语句与psycopg2交互)赋予了我们精细的控制权,但在面对日益复杂的业务逻辑和快速迭代的需求时,这种方式的开发效率和可…...
Python实现prophet 理论及参数优化
文章目录 Prophet理论及模型参数介绍Python代码完整实现prophet 添加外部数据进行模型优化 之前初步学习prophet的时候,写过一篇简单实现,后期随着对该模型的深入研究,本次记录涉及到prophet 的公式以及参数调优,从公式可以更直观…...
Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)
引言:为什么 Eureka 依然是存量系统的核心? 尽管 Nacos 等新注册中心崛起,但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制,是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...
全面解析各类VPN技术:GRE、IPsec、L2TP、SSL与MPLS VPN对比
目录 引言 VPN技术概述 GRE VPN 3.1 GRE封装结构 3.2 GRE的应用场景 GRE over IPsec 4.1 GRE over IPsec封装结构 4.2 为什么使用GRE over IPsec? IPsec VPN 5.1 IPsec传输模式(Transport Mode) 5.2 IPsec隧道模式(Tunne…...
视觉slam十四讲实践部分记录——ch2、ch3
ch2 一、使用g++编译.cpp为可执行文件并运行(P30) g++ helloSLAM.cpp ./a.out运行 二、使用cmake编译 mkdir build cd build cmake .. makeCMakeCache.txt 文件仍然指向旧的目录。这表明在源代码目录中可能还存在旧的 CMakeCache.txt 文件,或者在构建过程中仍然引用了旧的路…...
【Go语言基础【12】】指针:声明、取地址、解引用
文章目录 零、概述:指针 vs. 引用(类比其他语言)一、指针基础概念二、指针声明与初始化三、指针操作符1. &:取地址(拿到内存地址)2. *:解引用(拿到值) 四、空指针&am…...
