先上图:

该图是加上在0x08000000位置启动的整体情况

一.芯片的启动模式 

BOOT有三种模式,分别代表了启动模式,也就是启动位置,我们通常是模式1也就是主闪存启动(flash)

ps:不同的启动位置都有自己的中断向量表(都需要初始化pc和sp指针)

二.开始上电在(举例子在0x08000000位置启动的整体情况)

1.芯片第一条指令肯定从0x00000000位置读取(为什么,因为这是ARM规定的,ST买了ARM的内核,但没权限改ARM内核代码(因为加密混淆了),所以想从0800000开始的话,就只能通过内核以外的手段实现),但是根据BOOT模式的不同,该地址保存的地址也不同,比如BOOT0设置为0,从flash开始执行,那么就会把0x08000000位置的地址映射0x00000000,将其中包含的地址放入SP指针中(这一波是硬件自动完成)下面是中断向量表

问题1:为什么说开始是从 0x00000000开始,但是中断向量表的位置在0x0800000呢?

回答:这就是映射请求,无论启动位置在哪里,都是从0x00000000启动,只是会更具启动位置不同,将对应位置的地址映射过来,这里看似执行0x00位置,实际上是运行0x08位置内容

三.详细拆解每一步(包括汇编文件)

1. 每一步都是干什么的?

        首先上面截图的sp初始和中断服务函数,在这个位置(DCD就是定义一个常量)都是放置一个四个字节的地址,程序开始执行到这里就是根据放置地址继续跳转到程序函数执行。举例子sp指针是栈,他的最终指向都是在SRam空间内(0x20开头空间)这一点可以通过下面截图看出来(这个.map文件)。开始的初始化sp指针是开辟一块栈空间,这个空间供所有栈使用(main函数等等),后面的中断服务函数包括调用SystemInit用来初始化时钟(设置HSL,锁相环PLL),跳转__main它会初始化全局变量(复制 .data 段到 SRAM,清零 .bss 段,开辟出堆栈(在最初的空间中再开辟)),然后跳转到用户写的 main 函数。

注解:

 1.什么是.map文件?

        这是单纯由连接器生成的文件,包含了所有代码存在内存的位置,方便调试查看。

2.前面的data,bss和heap是什么?

        可以看这篇文章的第十三节

初版BL程序一些细节整理(碎碎念)-CSDN博客

3.怎么初始化sp指针,你怎么知道sp指针的位置在哪?

        最终确定存放在哪sp指针的具体位置由连接器决定,连接器需要查看分散加载文件.sct👇

我们可以看到文件中规定了(+RW + ZI)的大小,实际上就是先规定了data和bss段的大小

再假设

      我们知道data,bss和堆栈的大小分别是多少,也知道他们是按照这个顺序再0x20地址开始排序,我们就可以算出栈顶的位置是多。这就是连接器的计算过程

我们再看看,这些参数都在启动文件的哪个位置👇图,个图里面只有41行的__initial_sp会被执行

这个图的意思:包括了栈的大小0x00000400(也就是1KB),也说分配空间的格式

AREA    STACK, NOINIT, READWRITE, ALIGN=3,定义了栈空间的各种参数类型

Stack_Mem       SPACE   Stack_Size ,表示从Stack_Mem(位置由连接器分配)也是个地址常量位置开始分配空间

连接器根据data,bss和堆,算出Stack_Mem 的位置,然后根据栈大小得出sp指针位置,然后再这里给41行的__initial_sp赋值,(41行的__initial_sp)就是我们前面看到👇

里面指存放的四字节地址(0x20开头地址)。

4.中断服务函数是怎么实现的,里面都有什么呢?

上面这个图,(173行)就是中断服务函数的地址

Reset_Handler 是一个函数标签(label),它标识了一个名为 Reset_Handler 的函数入口。

PROC:表示该标签是一个过程(procedure)的开始。通常,这个标签后面的代码是一个函数体。在 ARM 汇编中,PROC 用来定义一个过程(即函数)

        172行为单纯的注释行为

  • EXPORT Reset_Handler:这行指令将 Reset_Handler 函数标记为 导出符号,也就是将它公开给其他模块使用。

  • [WEAK]:这个标志意味着 Reset_Handler 是一个 弱符号(weak symbol)。弱符号允许该符号在链接时被替代(即如果有其他强符号(strong symbol)定义了 Reset_Handler,那么它会被使用,而不是使用这个弱符号)。这在嵌入式系统中非常常见,因为如果在用户代码中有自己的 Reset_Handler,那么这个默认的 Reset_Handler 就会被覆盖。

  • IMPORT SystemInit:这表示 SystemInit 函数是从外部模块导入的,即它在其他地方定义。SystemInit 通常是一个用于初始化系统时钟、外设等硬件的函数,通常由启动文件提供,或者由硬件库提供。

  • IMPORT __main:类似地,__main 函数是从其他地方导入的,它通常是程序的主要入口函数。在嵌入式系统中,__main 负责执行系统初始化后进入主程序的执行,通常是用户编写的程序代码。

下面解答各个汇编是什么意思

这句PROC:表示“这是一个过程(函数)”,用于配合 ENDP 成对使用,是一种语法结构提示,但本身不产生代码。意思类似c语言中,我们定义了一个叫Reset handler的函数。

这句的EXPORT只是声明“本文件里定义了Reset_Handler”并且“这个符号可被别的文件访问”,类似C语言中的在头文件声明了函数定义。

IMPORT就类似C语言的extern,从其他文件引入函数或者变量。

178:将SystemInit加载到R0寄存器

179:跳转到R0寄存器中的地址,并且保存返回值

180: 同上意思

181:跳转到R0寄存器中的地址,不保存返回值

182:ENDP 用于标识一个过程的结束。在 ARM 汇编中,PROCENDP 成对使用,定义了一个过程(或函数)的开始和结束。

5.__main函数和main函数有什么不同,里面是什么呢?

里面的__scatterload是用于初始化.data段和.bss段

__rt_entry负责初始化堆栈(main)以及引用C库函数等,最后跳转到用户的main函数

6.最后BL程序的启动方式我们可以确定,分为两种

 1.开始直接赋值sp和pc指针,就是这种常规上电一点

2.开始只赋值pc指针,然后在中断服务函数中根据计算得出sp指针的位置,然后在__main函数前赋值给sp指针即可

7.启动文件要根据编译器的不同查看不同的资料

8.关于中断向量表的理解

中断向量表本质就是个数值,他由__Vectors  开头以__Vectors_End结束,通过DCD连接器把中断向量函数地址储存到对应的位置,__Vectors的开头地址是由于连接器查看SCB->VTOR寄存器来定义的,所以__Vectors这个名字也是可以修改的,但是需要同时修改启动文件和连接器脚本。
最后处理器通过这个地址查找中断源对应的处理程序,这就是调用中断函数的细节过程。

修改SCB->VTOR的位置在system_stm32f4xx.c文件里面

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐