STM32启动流程详解(超全,startup_stm32xx.s分析)

单片机上电后执行的第一段代码

1.初始化堆栈指针 SP=_initial_sp

2.初始化 PC 指针=Reset_Handler

3.初始化中断向量表

4.配置系统时钟

5.调用 C 库函数_main 初始化用户堆栈,然后进入 main 函数。

在正式讲解之前,我们需要了解STM32的启动模式。

STM32的启动模式

手册可以在Keil中跳转查看

STM32的三种启动模式

首先要讲一下STM32的启动模式,因为启动模式决定了向量表的位置,STM32有三种启动模式:

1. 主闪存存储器(Main Flash memory)启动

从STM32内置的Flash启动(0x0800 0000-0x0807 FFFF),一般我们使用JTAG或者SWD模式下载程序时,就是下载到这个里面,重启后也直接从这启动程序。以0x08000000 对应的内存为例,则该块内存既可以通过0x00000000 操作也可以通过0x08000000 操作,且都是操作的同一块内存。

2. 系统存储器(System memory)启动

从系统存储器启动(0x1FFFF000 - 0x1FFF F7FF),这种模式启动的程序功能是由厂家设置的。一般来说,我们选用这种启动模式时,是为了从串口下载程序,因为在厂家提供的ISP程序中,提供了串口下载程序的固件,可以通过这个ISP程序将用户程序下载到系统的Flash中。以0x1FFFFFF0对应的内存为例,则该块内存既可以通过0x00000000 操作也可以通过0x1FFFFFF0操作,且都是操作的同一块内存。

3. 片上SRAM(Embedded SRAM)启动

从内置SRAM启动(0x2000 0000-0x3FFFFFFF),既然是SRAM,自然也就没有程序存储的能力了,这个模式一般用于程序调试。SRAM 只能通过0x20000000进行操作,与上述两者不同。从SRAM 启动时,需要在应用程序初始化代码中重新设置向量表的位置。

用户可以通过设置BOOT0和BOOT1的引脚电平状态,来选择复位后的启动模式。

如下图所示:

总结

++启动模式只决定程序烧录的位置++,加载完程序之后会有一个重映射(映射到0x00000000地址位置);真正产生复位信号的时候,CPU还是从开始位置执行。

值得注意的是STM32上电复位以后,代码区都是从0x00000000开始的,三种启动模式只是将各自存储空间的地址映射到0x00000000中。

STM32的启动文件分析

因为单片机上电启动过程主要是由汇编完成的,因此STM32的启动的大部分内容都是在启动文件里。我用CubeMX生成的的启动文件是startup_stm32f103xb.s,不管使用标准库还是使用HAL库,启动文件都是差不多的。

1. Stack栈

栈的作用是用于局部变量,函数调用,函数形参等的开销,栈的大小不能超过内部SRAM 的大小。当程序较大时,需要修改栈的大小,不然可能会出现的HardFault的错误。

第32行:表示开辟栈的大小为 0X400(1KB),EQU是伪指令,相当于C 中的 define。

第34行:开辟一段可读可写数据空间,ARER 伪指令表示下面将开始定义一个代码段或者数据段。此处是定义数据段。ARER 后面的关键字表示这个段的属性。段名为STACK,可以任意命名;NOINIT 表示不初始化;READWRITE 表示可读可写,ALIGN=3,表示按照 8 字节对齐。

第35行:SPACE 用于分配大小等于 Stack_Size连续内存空间,单位为字节。

第37行: __initial_sp表示栈顶地址。栈是由高向低生长的。

2. Heap堆

堆主要用来动态内存的分配,像malloc()函数申请的内存就在堆中。

开辟堆的大小为 0X200(512 字节),名字为 HEAP,NOINIT 即不初始化,可读可写,8字节对齐。__heap_base 表示对的起始地址,__heap_limit 表示堆的结束地址。

3. 向量表

向量表是一个WORD( 32 )数组,每个下标对应一种异常,该下标元素的值则是该 ESR 的入口地址。向量表在地址空间中的位置是可以设置的,通过 NVIC 中的一个重定位寄存器来指出向量表的地址。在复位后,该寄存器的值为 0。因此,在地址 0 (即 FLASH 地址 0)处必须包含一张向量表,用于初始时的异常分配。

值得注意的是这里有个另类: 0 号类型并不是什么入口地址,而是给出了复位后 MSP 的初值,后面会具体讲解。

第55行:定义一块代码段,段名字是RESET,READONLY 表示只读。

第56-58行:使用EXPORT将3个标识符申明为可被外部引用,声明 __Vectors、__Vectors_End 和__Vectors_Size 具有全局属性。

第60行:__Vectors 表示向量表起始地址,DCD 表示分配 1 个 4 字节的空间。每行 DCD 都会生成一个 4 字节的二进制代码,中断向量表 存放的实际上是中断服务程序的入口地址。当异常(也即是中断事件)发生时,CPU 的中断系统会将相应的入口地址赋值给 PC 程序计数器,之后就开始执行中断服务程序。在60行之后,依次定义了中断服务程序的入口地址。

第121行:__Vectors_End 为向量表结束地址。

第123行:__Vectors_Size则是向量表的大小,向量表的大小是通过__Vectors 和__Vectors_End 相减得到的。

4. 复位程序

复位程序是系统上电后执行的第一个程序,复位程序也是中断程序,只是这个程序比较特殊,因此单独提出来讲解。

第128行:定义了一个服务程序,PROC表示程序的开始。

第129行:使用EXPORT将Reset_Handler申明为可被外部引用,后面WEAK表示弱定义,如果外部文件定义了该标号则首先引用该标号,如果外部文件没有声明也不会出错。这里表示复位程序可以由用户在其他文件重新实现,这种写法在HAL库中是很常见的。

第130-131行:表示该标号来自外部文件,SystemInit()是一个库函数,在system_stm32f1xx.c中定义的,__main 是一个标准的 C 库函数,主要作用是初始化用户堆栈,这个是由编译器完成的,该函数最终会调用我们自己写的main函数,从而进入C世界中。

第132行:这是一条汇编指令,表示从存储器中加载SystemInit到一个寄存器R0的地址中。

第133行:汇编指令,表示跳转到寄存器R0的地址,并根据寄存器的 LSE 确定处理器的状态,还要把跳转前的下条指令地址保存到 LR。

第134行:和132行是一个意思,表示从存储器中加载__main到一个寄存器R0的地址中。

第135行:和133稍微不同,这里跳转到至指定寄存器的地址后,不会返回。

第136行:和PROC是对应的,表示程序的结束。

5. 中断服务程序

我们平时要使用哪个中断,就需要编写相应的中断服务程序,只是启动文件把这些函数留出来了,但是内容都是空的,真正的中断复服务程序需要我们在外部的 C 文件里面重新实现,这里只是提前占了一个位置罢了。

这部分没啥好说的,和服务程序类似的,只需要注意'B .'语句,B表示跳转,这里跳转到一个'.',即表示无线循环。

6. 堆栈初始化

堆栈初始化是由一个IF条件来实现的,MICROLIB的定义与否决定了堆栈的初始化方式。

这个定义是在Options->Target中设置的

这部分也没啥讲的,需要注意的是,ALIGN表示对指令或者数据存放的地址进行对齐,缺省表示4字节对齐。

相关推荐
无垠的广袤11 小时前
【工业树莓派 CM0 NANO 单板计算机】本地部署 EMQX
linux·python·嵌入式硬件·物联网·树莓派·emqx·工业物联网
雲烟13 小时前
嵌入式设备EMC安规检测参考
网络·单片机·嵌入式硬件
泽虞13 小时前
《STM32单片机开发》p7
笔记·stm32·单片机·嵌入式硬件
田甲13 小时前
【STM32】 数码管驱动
stm32·单片机·嵌入式硬件
up向上up13 小时前
基于51单片机垃圾箱自动分类加料机快递物流分拣器系统设计
单片机·嵌入式硬件·51单片机
纳祥科技1 天前
Switch快充方案,内置GaN,集成了多个独立芯片
单片机
桧***攮1 天前
C在物联网协议中的实现
物联网
单片机日志1 天前
【单片机毕业设计】【mcugc-mcu826】基于单片机的智能风扇系统设计
stm32·单片机·嵌入式硬件·毕业设计·智能家居·课程设计·电子信息
松涛和鸣1 天前
从零开始理解 C 语言函数指针与回调机制
linux·c语言·开发语言·嵌入式硬件·排序算法
小曹要微笑1 天前
STM32F7 时钟树简讲(快速入门)
c语言·stm32·单片机·嵌入式硬件·算法