> For the complete documentation index, see [llms.txt](https://oceanaparts.gitbook.io/halnote/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://oceanaparts.gitbook.io/halnote/qi-dong-fang-shi-yu-liu-cheng.md).

# 启动方式与流程

<https://www.bilibili.com/video/BV18X4y1M763?p=18\\&vd\\_source=3d652d6ce183145af7fbfe477961404f> <https://mp.weixin.qq.com/s?\\_\\_biz=Mzg3MjU4NzI2NA==\\&mid=2247493448\\&idx=1\\&sn=e66913fdda73d160469e7bba27af251e\\&source=41#wechat\\_redirect> <https://www.bilibili.com/video/BV1yW411Y7Gw?p=22\\&vd\\_source=3d652d6ce183145af7fbfe477961404f> <https://blog.csdn.net/weixin\\_39951419/article/details/111371619> <https://blog.csdn.net/qq\\_25814297/article/details/105377539> <https://blog.csdn.net/ZDQ1431/article/details/106374285/>

[堆栈指针\_百度百科 (baidu.com)](https://baike.baidu.com/item/%E5%A0%86%E6%A0%88%E6%8C%87%E9%92%88/8770086)

[堆栈3种作用,设置堆栈指针有什么用-天道酬勤-花开半夏 (zhangshilong.cn)](https://www.zhangshilong.cn/work/141461.html)

[堆栈指针是什么\_有什么作用 - 电子常识 - 电子发烧友网 (elecfans.com)](https://www.elecfans.com/dianzichangshi/20171110577625.html)

[堆栈指针sp的作用是什么 堆栈指针在什么情况下需要修改 - 与非网 (eefocus.com)](https://www.eefocus.com/e/508871.html)

[堆栈指针（sp）的作用是什么？在程序设计时，为什么还要对 sp重新赋值？\_百度知道 (baidu.com)](https://zhidao.baidu.com/question/599058097.html)

[(63条消息) 关于arm中ZI-data段和RW-data段，以及堆栈起始地址的理解\_加油2019的博客-CSDN博客\_zi-data](https://blog.csdn.net/qq_40036519/article/details/88377453)

[怎么理解stm32栈指针= ram基地址+RW-data+ZI-data？ (amobbs.com 阿莫电子论坛 - 东莞阿莫电子网站)](https://www.amobbs.com/thread-5741006-1-1.html)

[(23条消息) STM32启动流程详解\_stm32启动过程\_Dokin丶的博客-CSDN博客](https://blog.csdn.net/qq_27575841/article/details/104360578)

## 六、启动文件分析

根据MAP文件注释，我们知道了程序主要做一下几件事:

1. 初始化堆栈指针
2. 设置PC指针的值
3. 设置中断向量表
4. &#x20;配置系统时钟
5. 调用C库函数\_\_main初始化堆栈的工作，最终会跳转到我们自己编写的main

   [STM32第二章-启动过程详解 (qq.com)](https://mp.weixin.qq.com/s?__biz=Mzg3MjU4NzI2NA==\&mid=2247493448\&idx=1\&sn=e66913fdda73d160469e7bba27af251e\&source=41#wechat_redirecthttps://www.bilibili.com/video/BV1yW411Y7Gw?p=22\&vd_source=3d652d6ce183145af7fbfe477961404fhttps://blog.csdn.net/weixin_39951419/article/details/111371619https://blog.csdn.net/qq_25814297/article/details/105377539https://blog.csdn.net/ZDQ1431/article/details/106374285/)

### 6.1    初始化堆栈指针

堆栈指针总是指向栈顶位置，以STM32F103ZET6为例，该程序RW-data+ZI-data的大小为1928字节=0X788字节，**这个大小为RAM的大小，并且超过了堆区+栈区的大小，因为RAM中不仅包含了堆栈**

堆区+栈区的大小已经被包含在ZI-data中了

<img src="https://s2.loli.net/2023/02/23/lDJjfQosK9ehtTS.png" alt="" height="60%" width="60%">

打开调试器，选择Banked选项栏，可以发现MSP的地址正是

**SARM的起始地址+0x788=0x20000000+0x788=0x20000788** 该地址存放在0X08000000即FLASH用户代码区的起始地址上

实际上msp的值也可以通过map文件查找，在map中搜索\_\_initial\_sp即可找到。

**该值指向了栈顶，因为 RW Data + ZI Data正是程序运行前RAM使用的空间大小**

<img src="https://s2.loli.net/2023/02/23/uYifeQ5
VJ4OIbzq.png" alt="" height="30%" width="30%">

用官方提供的启动文件时，我们查看MAP文件，我们可以查看堆栈大小以及起始地址和堆栈指针地址

打开map文件，到Local Symbols段的末尾，有如下信息，说明了msp指针指向地址0x20000788，堆HEAP区的大小为512字节，起始地址（堆向上增长，因此是最低地址）为0x20000188，栈STACK 区的大小为1024字节，起始地址（栈向下增长，因此是最高地址）为0x20000388

<img src="https://s2.loli.net/2023/02/24/mzF48gG
iD1rTpOE.png" alt="" height="100%" width="100%">

#### 6.1.1启动文件里面是怎么进行堆栈初始化以及SP指针设置的

[(23条消息) STM32的启动堆栈初始化\_怎样将数据写入堆栈起始地址 stm32\_qq\_23899395的博客-CSDN博客](https://blog.csdn.net/qq_23899395/article/details/90344731)

以下的代码只是相当于c语言中的Define声明了一下变量而已，等待被调用，当然它已经分配开辟了堆栈空间，以栈为例，SPACE 这行指令告诉汇编器给 STACK 段分配 0x00000800 字节的连续内存空间。但是对于SP指针并没有进行一个实质上的设置， \_\_initial\_sp紧接着 SPACE 语句放置，表示了栈顶地址。\_\_initial\_sp 只是一个标号，相当于一个地址。

<img src="https://s2.loli.net/2023/03/03/C165XJilMzUrBAx.png" alt="" height="60%" width="60%">

在上面的代码设置了\_\_initial\_sp后，通过中断向量表的O位写入值，完成第一次的SP指针初始化

通过编译后会计算出，\_\_initial\_sp值，这个值可以在map文件中查看

<img src="https://s2.loli.net/2023/03/03/Wy2lPBVmIxnzUZY.png" alt="" height="60%" width="60%">

而在\_\_main函数中会进行堆栈的真正初始化，前面的space只是分配了空间，在bin中生成了对应的数据段，真正操作RAM初始化堆栈是在这，该函数在中断向量的1位，复位中断服务函数中调用。

\_\_main 标号表示 C/C++标准实时库函数里的-个初始化子程序 \_\_main 的入口地址。该程序(在复位中断函数调用)的一个**主要作用是初始化堆栈**(跳转\_user\_initial\_stackheap 标号进行初始化堆栈指针的，下面会讲到这个标号)，并初始化映像文件，最后跳转到 C 程序中的 main函数。这就解释了为何所有的 C 程序必须有一个 main 函数作为程序的起点。因为这是由 C/C++标准实时库所规，并且不能更改。

以韦东山了欧式的bootloader教程程序为例，完整的启动文件只有以下这些，直接通过LDR往SP寄存器写入值完成初始化，但有一问题是没有开辟堆栈空间，不过他的程序确实也没有用到堆和栈。

<img src="https://s2.loli.net/2023/03/03/BgNaLtXY5mbw9rM.png" alt="" height="60%" width="60%">

### 6.2    设置PC指针的值

*PC*(Program Counter,*PC*)用来存放当前欲执行指令的地址，即PC指针指向哪里，程序就运行到哪里。实际上启动文件并没有直接设置PC指针的值，根据参考手册的描述，STM32启动后触发复位中断，而该中断地址上存储着Reset\_Handler函数

因此可以说STM32会从0X0800 0004这个地址取出存放的向量地址，装入PC程序计数器，使PC程序计数器指向存放的地址，也就是指向复位中断服务程序。

以上过程由硬件完成，即CPU内部的时序逻辑电路完成。

该函数定义如下：

<img src="https://s2.loli.net/2023/02/23/Oc1F65dCAvqyUsg.png" alt="" height="60%" width="60%">

PORT：标明全局属性，可被外部调用

IMPORT：申明来自外部文件，类extern，等待被调用

PROC：定义子程序（函数定义开始）

ENDP：表示子程序结束（函数定义结束）

WEAK：弱定义，允许在其他地方重新定义该函数

LDR RO 与BLX RO就是在**调用函数**

> 因此如果外部没有：SystemInit函数则会报错！！，因此如果用不到可以选择将该段代码屏蔽或编写一个空的函数，hal库版本在systeam\_stm32f1xx.c文件中定义

### 6.3设置中断向量表

在启动文件中，有以下程序，正是在设置中断向量表，编译完成之后，中断向量表实际上就是存放在Code区（也就是STM32内部的Flash区）从0x00000000地址开始的一个[数组](https://so.csdn.net/so/search?q=%E6%95%B0%E7%BB%84\&spm=1001.2101.3001.7020)，数组的成员为4个字节。也就是说设置中断向量表实际就是在这些地址上存入对应的函数地址。

&#x20; 中断向量相关的可以查看m3权威指南，所有与中断相关的都是内核级别的

**1、当中断来临的时候，首先取向量，每个中断的中断向量不一样，也就是查询的地址不同，根据里面的地址找到中断服务函数(以上过程由硬件完成，即CPU内部的时序逻辑电路完成)，赋值给PC指针，执行中断服务，从而实现整个中断的响应过程。**

**也就是说以发生第0个中断为例，该中断会调用\_\_initial\_sp，执行程序响应中断。**

**2、在启动文件里面已经写好了中断服务函数，只是这些中断服务函数为空，这样在启动文件执行的时候，内核和每个外设的中断服务函数的地址都是已经确定好的，而且带\[weak]弱定义，那么我们就需要在C文件里面重新实现这个中断服务函数，所以才会出现在编写串口中断或外部中断或定时器中断之类的时候需要去编写一个对应的服务函数。**

**3、STM32根据内核和外设中断**[**优先级**](https://so.csdn.net/so/search?q=%E4%BC%98%E5%85%88%E7%BA%A7\&spm=1001.2101.3001.7020)**，同一标号，标号越小，优先级越大。然后把内核和外设的中断服务函数的地址放在这个数组里面，数组的下标跟中断的优先级对应，我们也把这个中断的编号叫做中断向量。**

<img src="https://s2.loli.net/2023/02/23/VB1hczYmyjn5rwe.png" alt="" height="60%" width="60%">

上表对应下表

![](https://s2.loli.net/2022/03/22/lzWX2Tebd6ZgqQ3.png)

> **以 Reset\_Handler为例，我们打开一个工程（不同工程可能地址不同），根据如下图的map文件可知,Reset\_Handler函数地址在0X080001cd，因此在以0X08000000（即用户代码区的起始地址）上+0x04=0x08000004的地址上存放着数据0X080001cd，而该函数对应着1号中断，在复位时会被触发，因此在触发中断时，会跳往0X080001cd地址执行该程序。而0X08000000中储存着MSP堆栈指针的初始值，该值正如前面说计算的是0x20000788**
>
> **值得注意的是，以上地址都是都是编译后在用户主闪存区中的地址，实际上根据启动模式的不同，会将不同的空间映射到启动空间，而启动空间是0x00000000，比如从主闪存器区启动，那么主闪存器会被映射到启动空间，那么以上的地址的起始地址都是从0x00000000开始计算， 如MSP指针储存在0x00000000，值为0x00000788，复位中断向量地址为0x00000040，储存着函数地址0x000001cd，**

<img src="https://s2.loli.net/2023/02/24/PclqGeN9QbZtj6V.png" alt="" height="60%" width="60%">

#### 关于中断向量表的位置的补充说明

中断向量表定位在代码段的最前面。具体的物理地址由连接器的配置参数(IROM1 的地址 )决定。如果程序在 Flash 运行，又以默认设置运行（不修改魔术棒的IROM1参数以及sct文件），则中断向量表的起始地址是 0x08000000，如果设置在其他位置，需要设置内核的寄存器SCB->VTOR，告诉内核向量表的位置

#### 复位中断函数做了什么事？

复位中断服务程序会调用 SystemInit0函数(C 语言的)来配置系统时钟、配置 FSMC 总线上的外部SRAM（如果有的话），然后跳转到 C 库中\_\_ main 函数。由 C 库中的 \_\_main 函数完成用户程序的初始化工作(比如 : 变量赋初值等)，最后由 \_\_main 函数调用用户写的 main函数开始执行 C 程序

#### STM32启动过程（内部FLASH启动为例）

<img src="https://s2.loli.net/2023/02/24/2ZUfvJRT49hX3WE.png" alt="" height="60%" width="60%">

#### 关于代码到底在哪执行的补充说明？

许多芯片都需要将代码复制到RAM区执行（比如WINDOWS平台），而STM32使用了XIP（eXecute In Place 就地执行）技术，即芯片内执行，是指CPU直接从存储器中读取程序代码执行，而不用再读到内存中。应用程序可以直接在flash闪存内运行，这个FLASH需要是NOR-FLASH,不能是NAND-FASLH,不必再把代码读到系统RAM中。好处即是程序代码无需占用内存，减少内存的要求。[XIP技术总结 - 知乎 (zhihu.com)](https://zhuanlan.zhihu.com/p/368276428)
