《嵌入式 - 深入剖析STM32》STM32 启动流程详解(GCC)

news2025/10/27 3:24:13

开发环境:
处理器:STM32F103
GCC:10.3.1

对于我们常用的桌面操作系统而言,我们在开发应用时,并不关心系统的初始化,绝大多数应用程序是在操作系统运行后才开始运行的,操作系统已经提供了一个合适的运行环境,然而对于嵌入式设备而言,在设备上电后,所有的一切都需要由开发者来设置,这里处理器是没有堆栈,没有中断,更没有外围设备,这些工作是需要软件来指定的,而且不同的CPU类型、不同大小的内存和不同种类的外设,其初始化工作都是不同的。本文将以STMF103(基于Cortex-M3)为例进行讲解。

在开始正式讲解之前,你需要了解ARM寄存器、汇编以及反编译相关的知识,这些可以参考笔者博文。

深入理解ARM寄存器

ARM汇编入门

Keil反编译入门(一)
Keil反编译入门(二)

下面我们就来具体看一下用户从Flash启动STM32的过程,主要讲解从上电复位到main函数的过程。主要有以下步骤:

1.初始化栈顶指针sp,进入C程序需要先设置栈地址,因为是通过函数调用进入C程序,需要用到栈空间。
2.设置PC指针
3.将Flash的data段拷贝到RAM中
4.配置系统时钟
5.调用 C 库函数_libc_init_array初始化用户堆栈,然后进入 main 函数。

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

1 STM32的启动模式

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

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

2 ) 系统存储器(System Memory)启动:系统储存器指的是STM32的内置ROM,选择该启动模式后,内置ROM的起始地址将被重映射到0x00000000地址,代码在此处开始运行。ROM中有一段出厂预置的代码,这段代码起到一个桥的作用,允许外部通过UART/CAN或USB等将代码写入STM32的内置Flash中。这段代码也被称为ISP(In System Programing)代码,这种烧录代码的方式也被称为ISP烧录。
一般来说,我们选用这种启动模式时,是为了从串口下载程序,因为在厂家提供的ISP程序中,提供了串口下载程序的固件,可以通过这个ISP程序将用户程序下载到系统的Flash中。
以0x1FFFFFF0对应的内存为例,则该块内存既可以通过0x00000000 操作也可以通过0x1FFFFFF0操作,且都是操作的同一块内存。

3 ) 片上SRAM启动:从内置SRAM启动(0x2000 0000-0x3FFFFFFF),既然是SRAM,自然也就没有程序存储的能力了,这个模式一般用于程序调试。SRAM 只能通过0x20000000进行操作,与上述两者不同。从SRAM 启动时,需要在应用程序初始化代码中重新设置向量表的位置。该方法是在STM32的内置SRAM中启动,选择该启动模式后,内置SRAM的起始地址将被重映射到0x00000000地址,代码在此处开始运行。这种模式由于烧录程序过程中不需要擦写Flash,因此速度较快,适合调试,但是掉电丢失。

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

在这里插入图片描述

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

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

2 STM32的启动文件分析

因为启动过程主要是由汇编完成的,因此STM32的启动的大部分内容都是在启动文件里。笔者的启动文件是startup_stm32f103xe.s。当然还有一个链接文件,链接文件主要制定了入口函数,堆栈大小和数据段的整体布局。值得注意的是,对于MDK,也有相应的内存管理文件,只是MDK去做这个事情了,也就是sct分段加载。

启动文件主要有三个部分:定义各个内存地址,Reset_Handler函数,中断向量表
在这里插入图片描述

第32行:指明CPU的类型,这里的CPU核是Cortex-M。
第33行:表明浮点运算的类型,Cortex-M没有硬件FPU单元,因此这里是软件FPU。
第34行:指定了指令的类型为thumb。
在这里插入图片描述

第41行:data段地址的变量。
第43行:data段的起始地址。
第45行:data段的结束地址。
第47行:bss段的起始地址。
第49行:bss段的结束地址。

在这里插入图片描述

第61-62行:定义新的代码段,并申明为weak函数。
第63行:将Reset_Handler声明为函数。
第67-69行:设置data段、bss段的地址
第71行:复制data段到RAM中

在这里插入图片描述

以上就是将Flash中的data段复制到RAM的整个过程,另外还有bss段的清零工作。

接下来就是进入C空间前做的一些准备。

在这里插入图片描述

第98行:初始化系统时钟。
第100行:初始化lib库
第102行:跳转main函数。

最后就是中断向量表的内容。

在这里插入图片描述

以上函数只是weak函数,并没有函数实体,如果外部有中断触发但是没有进入中断函数,会发现代码卡在了Default_Handler函数里。因为外部没有定义同名函数,则都运行缺省 Default_Handler 函数。

在这里插入图片描述

上述向量表可以在《Reference manual》中找到的,笔者这里只截取了部分。

在这里插入图片描述

startup_stm32f103xe.s文件是系统的启动文件,主要包括堆和栈的初始化配置、中断向量表的配置以及将程序引导到main( )函数等。

startup_stm32f103xe.s主要完成三个工作:栈和堆的初始化、定位中断向量表、调用Reset Handler。

3 STM32的启动流程实例分析

3.1 Bootloader的作用

根据BOOT引脚确定了启动方式后,处理器进行的第二大步就是开始从0x00000000地址处开始执行代码,而该处存放的代码正是Bootloader。

Bootloader,也可以叫启动文件,每一种微控制器(处理器)都必须有启动文件,启动文件的作用便是负责执行微控制器从“复位”到“开始执行main函数”中间这段时间(称为启动过程)所必须进行的工作。最为常见的51,AVR或MSP430等微控制器当然也有对应启动文件,但开发环境往往自动完整地提供了这个启动文件,不需要开发人员再行干预启动过程,只需要从main函数开始进行应用程序的设计即可。同样,STM32微控制器,无论是MDK还是IAR开发环境,ST公司都提供了现成的直接可用的启动文件。
启动文件中首先会定义堆栈,定义中断/异常向量表,而其中只实现了复位的异常处理函数Reset_Handler,该函数其主要功能除了初始化时钟,FPU等,还会执行一个重要功能,那就是内存的搬移、初始化操作。

在这里插入图片描述

我们知道烧录的镜像文件中包含只读代码段.text,已初始化数据段.data和未初始化的或者初始化为0的数据段.bss。代码段由于是只读的,所以是可以一直放在Flash中,CPU通过总线去读取代码执行就行,但是.data段和.bss段由于会涉及读写为了,为了更高的读写效率是要一定搬到RAM中执行的,因此Bootloader会执行很重要的一步,就是会在RAM中初始化.data和.bss段,搬移或清空相应内存区域。
当启动方式选择的是从内置Flash启动的时候,代码依旧是在Flash中执行,而数据则会被拷贝到内部SRAM中,该过程是由Bootloader完成的。Bootloader在完成这些流程之后,就会将代码交给main函数开始执行用户代码。

有了前面的分析,接下来就来具体看看STM32启动流程的具体内容。

3.2 初始化SP、PC、向量表

当系统复位后,处理器首先读取向量表中的前两个字(8 个字节),第一个字存入 MSP,第二个字为复位向量,也就是程序执行的起始地址。

在这里插入图片描述

这里通过J-Flash打开hex文件。

在这里插入图片描述

硬件这时自动从0x0800 0000位置处读取数据赋给栈指针SP,然后自动从0x0800 0004位置处读取数据赋给PC,完成了复位操作,SP= 0x2001 0000,PC = 0x0800 0D95。

初始化SP、PC紧接着就初始化向量表,如果感觉看HEX文件抽象,我们看看反汇编文件吧。

在这里插入图片描述

是不是更容易些,是不是和《Reference manual》中的向量表对应起来了。其实看反汇编文件更好理解STM32的启动流程,只是有些抽象。

3.3 设置系统时钟

细心的朋友可能发现,PC=0x0800 0D95的地址是没有对齐的。然后在反汇编文件中却是这样的:

在这里插入图片描述

这里是硬件自动对齐到 0x0800 0D95,并执行SystemInit函数初始化系统时钟。

接下来就会进入SystemInit函数中。

在这里插入图片描述

SystemInit函数内容如下:

/**
  * @brief  Setup the microcontroller system
  *         Initialize the Embedded Flash Interface, the PLL and update the 
  *         SystemCoreClock variable.
  * @note   This function should be used only after reset.
  * @param  None
  * @retval None
  */
void SystemInit (void)
{
  /* Reset the RCC clock configuration to the default reset state(for debug purpose) */
  /* Set HSION bit */
  RCC->CR |= 0x00000001U;

  /* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */
#if !defined(STM32F105xC) && !defined(STM32F107xC)
  RCC->CFGR &= 0xF8FF0000U;
#else
  RCC->CFGR &= 0xF0FF0000U;
#endif /* STM32F105xC */   
  
  /* Reset HSEON, CSSON and PLLON bits */
  RCC->CR &= 0xFEF6FFFFU;

  /* Reset HSEBYP bit */
  RCC->CR &= 0xFFFBFFFFU;

  /* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */
  RCC->CFGR &= 0xFF80FFFFU;

#if defined(STM32F105xC) || defined(STM32F107xC)
  /* Reset PLL2ON and PLL3ON bits */
  RCC->CR &= 0xEBFFFFFFU;

  /* Disable all interrupts and clear pending bits  */
  RCC->CIR = 0x00FF0000U;

  /* Reset CFGR2 register */
  RCC->CFGR2 = 0x00000000U;
#elif defined(STM32F100xB) || defined(STM32F100xE)
  /* Disable all interrupts and clear pending bits  */
  RCC->CIR = 0x009F0000U;

  /* Reset CFGR2 register */
  RCC->CFGR2 = 0x00000000U;      
#else
  /* Disable all interrupts and clear pending bits  */
  RCC->CIR = 0x009F0000U;
#endif /* STM32F105xC */
    
#if defined(STM32F100xE) || defined(STM32F101xE) || defined(STM32F101xG) || defined(STM32F103xE) || defined(STM32F103xG)
  #ifdef DATA_IN_ExtSRAM
    SystemInit_ExtMemCtl(); 
  #endif /* DATA_IN_ExtSRAM */
#endif 

#ifdef VECT_TAB_SRAM
  SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM. */
#else
  SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH. */
#endif 
}

前面部分是配置时钟的,具体参考手册吧。

最终PLL的时钟位72MHz。

这里还需要注意以下代码:

#ifdef VECT_TAB_SRAM
  SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM. */
#else
  SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH. */
#endif 

默认是没有开启VECT_TAB_SRAM,则从FLASH中启动,VTOR 寄存器存放的是中断向量表的起始地址,在IAP升级会修改这里的偏移量,后面讲解IAP升级在细讲吧。

3.4 初始化堆栈并进入main

执行指令bl main,然后就跳转到main函数。
当然在此之前会初始化libc。

在这里插入图片描述

至此,启动过程到此结束。
最后,总结下STM32 从flash的启动流程。
MCU上电后从0x0800 0000处读取栈顶地址并保存,然后从0x0800 0004读取中断向量表的起始地址,这就是复位程序的入口地址,接着跳转到复位程序入口处,初始向量表,然后设置时钟,设置堆栈,最后跳转到C空间的main函数,即进入用户程序。

在这里插入图片描述




欢迎访问我的网站

BruceOu的哔哩哔哩
BruceOu的主页
BruceOu的博客
BruceOu的CSDN博客
BruceOu的简书
BruceOu的知乎

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vxe-table 表格尾部小计列项再合计展示

公式:店员奖金item1item3item5item7 this.dyjj全局变量 this.tableDat 全局数组 1.求出尾部小计这一行的数据 columns.map((column, columnIndex) > { var aa XEUtils.sum(data, column.property) this.tableDat.push(aa) if (columnIndex 0) { return ‘小计…

USB TO I2C/SPI(上海同旺电子)调试器调试ADT7420--step3

所需设备: 1、USB TO I2C/SPI(上海同旺电子)专业版 或 升级版 2、ADT7420 0.25C精度、16位数字I2C温度传感器; USB TO I2C/SPI(上海同旺电子)专业版 或 升级版,既支持I2C的标准指令,又支持I2C的复合指令,市面上大部分I2C调试器…

【JSP】Page指令和九大内置对象

JSPJSP中的Page指令关于Page指令常用的属性1. <%page session"true|false" %>2. <%page contentType"text/json" pageEncoding"UTF-8" %>3. <%page import"java.util.Collection,java.sql.Connection"%>4. <%pag…

ImmunoChemistry艾美捷绿色活/死染色解决方案

ImmunoChemistry艾美捷绿色活/死染色是一种活细胞不渗透、绿色荧光发射DNA染料&#xff0c;用于生存能力、细胞凋亡和坏死研究以及固定细胞核染色。本产品与坏死或渗透化细胞的dsDNA/细胞核结合&#xff0c;可与活细胞染料结合使用&#xff0c;用于活/死鉴别。使用流式细胞仪或…

Redis基础命令(String类型)Value为JSON

目录 String类型&#xff08;存储的值为JSON形式&#xff09; 问题&#xff1a; 解决办法&#xff1a; 示例&#xff1a; 实际操作&#xff1a; 总结&#xff1a; String类型&#xff08;存储的值为JSON形式&#xff09; 问题&#xff1a; Redis没有类似MySql中的表的概…

iText7高级教程之html2pdf——5.自定义标签和CSS应用

在本章中&#xff0c;我们将更改pdfHTML插件的两个最重要的内部机制。 我们将覆盖将HTML标签与iText对象匹配的默认功能&#xff0c;更具体地说是DefaultTagWorkerFactory机制&#xff0c;以及我们将覆盖将CSS样式与iText样式相匹配的默认功能&#xff0c;更具体地说是Default…

【Python百日进阶-WEB开发-冲進Flask】Day183 - Flask数据库ORM基础、增加

文章目录一、day03项目环境和结构搭建1.1 flask-script1.1.1 flask-script是干什么的&#xff1f;1.1.2 flask-script安装1.1.3 flask-script的使用1.1.3.1 创建Manager实例1.1.3.2 初始化实例出错与解决1.1.4 终端启动1.1.4.1 查看runserver参数1.1.5 自定义添加manager命令1.…

ARM-A架构入门基础(二)异常处理

14天学习训练营导师课程&#xff1a;周贺贺《ARMv8/ARMv9架构-快速入门》 1. 异常处理种类 1.1 中断 在ARM中&#xff0c;FIQ的优先级要高于IRQ&#xff0c;在SOC内部会有一个中断控制器负责中断优先级调度&#xff0c;然后发送中断信号给处理器。中断属于异步模式的异常。 …

Python定时打开世界杯直播,还有小姐姐语音提醒哦~不错过每一场世界杯比赛

前言 卡塔尔世界杯今晚0点就要开幕了&#xff0c;为了防止大家沉迷工作&#xff0c;忘记看球&#xff0c;小编用50行Python代码写了一个定时提醒你看球的小程序&#xff0c;还有小姐姐语音提醒哟~&#x1f387; &#xff08;文末送读者福利&#xff09; 1、代码说明 获取上…

Redmine 插件 实现富文本编辑,可插入表格和

基本介绍 Redmine 插件将“所见即所得"的UEditor富文本编辑器完美移植到了Redmine。UEditor具有界面美观、功能丰富、注重用户体验的特点&#xff0c;使用方法比CKeditor更为简便。 该插件在redmine现有的文本编辑模式上添加WYSIWYG所见即所得编辑功能。 Redmine UEditor主…

功能测试如何进阶自动化测试?5个步骤带你成功进阶...

手动测试人员应该权衡测试自动化相对于手动测试的好处&#xff0c;并且即可开始行动。下面我介绍一下从手动测试到自动化测试转换的5步指南。 步骤1: 查找合适的自动化测试用例 测试自动化在重复测试中发挥着极其重要的作用。可以在下表中找到最适合自动化的测试类型列表。 测…

【学习笔记20】JavaScript数据类型之间的区别

一、数据类型 基本数据类型复杂数据类型 (function; object; array)二、存储的区别 基本数据类型: 存储在栈内存中, 变量内部就是实际的值引用数据类型: 变量存储在栈内存中, 变量内部存储的是指向堆内存的地址(对象实际的值, 存储在堆内存中)三、赋值的区别 1. 基本数据类型: …

Linux零基础从入门到精通,必学的55个指令合集【下篇】

Linux学习笔记 资料下载&#xff1a; 链接: https://pan.baidu.com/s/1UvwkJaEJO7W3sU5qkCgKzA?pwdfe2f提取码: fe2f 本篇文章主要适用0基础的读者&#xff0c;内容会比较通俗易懂&#xff0c;也会有详细的图解教程&#xff0c;以及运行后的返回结果。我本人在系统性的学习…

Rsync已过时?替代文件同步方式了解一下

随着企业结构分散化的不断扩大&#xff0c;企业内部和企业间的信息互动更加频繁。越来越多的企业要求内部各种业务数据在服务器、数据中心甚至云上能够有实时的同步留存。所以&#xff0c;企业需要文件同步软件&#xff0c;通过在两个或更多设备之间同步数据并自动更新更改来确…

零时科技 || 分布式资本创始人4200万美金资产被盗分析及追踪工作

事件背景 2022年11月23日&#xff0c;分布式资本创始人沈波发推文称&#xff0c;价值4200万美元的个人钱包资产被盗&#xff0c;其中包含 3800 万枚 USDC和1606 枚 ETH&#xff0c;在纽约时间 11 月 10 日凌晨被盗。被盗资产为个人资金&#xff0c;与分布式相关基金无关。目前…

【学习笔记16】JavaScript函数封装习题

笔记初发 1、书写一个函数, 求任意两个数字的和, 把结果以弹窗的形式展示 书写一个函数需要参数吗?            -->需要需要几个参数?         —>两个函数要做什么?         -->求和然后弹窗的形式展示    -->alert(弹窗展示的值…

Area of a circle

In geometry, the area enclosed by a circle of radius r is πr2. Here the Greek letter π represents the constant ratio of the circumference of any circle to its diameter, approximately equal to 3.14159. One method of deriving this formula, which originate…

【矩阵论】4. 矩阵运算——张量积

4.2 张量积 4.2.1 定义 设A(aij)mn,B(bij)pq,则称如下分块矩阵(a11Ba12B⋯a1nBa21Ba22B⋯a2nB⋮⋮⋱⋮an1Ban2⋯ann)为A与B的张量积记作A⊗B(aijB)mpnq\begin{aligned} &设A(a_{ij})_{m\times n},B(b_{ij})_{p\times q},则称如下分块矩阵\left( \begin{matrix} a_{11}B&am…

【Dense Res2net:两个非局部注意模型:IVIF】

Res2Fusion: Infrared and Visible Image Fusion Based on Dense Res2net and Double Nonlocal Attention Models &#xff08;Res2Fusion: 基于密集Res2net和双非局部注意模型的红外和可见光图像融合&#xff09; 红外和可见光图像融合旨在生成具有出色场景表示和更好视觉感…

Zabbix最新6.2安装及使用!

zabbix官网 Zabbix 是由 Alexei Vladishev 创建&#xff0c;目前是由 Zabbix SIA 在持续开发和提供支持。 Zabbix 是一款能够监控众多网络参数和服务器的健康度和完整性的软件。Zabbix 使用灵活的通知机制&#xff0c;允许用户为几乎任何事件配置基于邮件的警报。这样可以快速…