STM32_启动流程详解

目录标题

  • 前言
  • 启动流程概述
    • 复位中断函数详解
      • SystemInit函数详解
      • __main函数详解
  • 附录
    • stm32单片机的存储器映像
    • 中断向量表的映射

前言

最近在学习IAP远程OTA升级单片机固件程序,发现自己对单片机的启动流程还不是那么了解,就总结整理一下吧。


启动流程概述

  • 1.内核初始化;
    • 1.内核复位和NVIC寄存器部分清零;
    • 2.内核设置堆栈:内核从向量表0地址读出堆栈地址,并设置主堆栈指针(SP_main)
    • 3.设置PC和LR寄存器
      • a. LR设置未初始复位值0xFFFF FFFF
      • b. 单片机的内部硬件机制自动将PC指针定位到中断向量表的复位中断向量处,把复位中断函数Reset_Handler的地址赋值给PC指针,然后跳转执行Reset_Handler。
  • 2.强制PC指针指向中断向量表的复位中断向量执行复位中断函数;
  • 3.在复位中断函数中调用 SystemInit 函数,初始化时钟配置中断向量表
  • 4.调用 __main 函数完成全局/静态变量的初始化和重定位工作,初始化堆栈和库函数
  • 5.跳转到main函数中执行

复位中断函数详解

上面内核初始化的最后一步,是把复位中断函数Reset_Handler的地址赋值给PC指针,然后跳转执行复位中断处理函数,我们来看一下在复位中断里内核都做了哪些操作。
我们随便打开一个标准库工程的启动文件,都能找到下面这段代码:

; Reset handler                     //程序注释(汇编中;表示注释)
Reset_Handler    PROC               //定义了一个子程序:Reset_Handler
                 EXPORT  Reset_Handler             [WEAK]   //EXPORT  表明此函数可供启动模块调用
     IMPORT  __main               //IMPORT 表明函数定义在外部,链接时需要去寻找
     IMPORT  SystemInit            
                 LDR     R0, =SystemInit    //将SystemInit地址加载到R0寄存器
                 BLX     R0                 //跳转到R0执行SystemInit程序
                 LDR     R0, =__main        //将__main地址加载到R0寄存器
                 BX      R0                 //跳转到R0执行_main程序
                 ENDP                       //表明程序结束

根据代码我们可以看出,复位中断主要是调用了SystemInit__main 这两个函数,下面我们再来详细介绍下这两个函数都做了什么工作。

SystemInit函数详解

利用跳转功能,我们可以看到SystemInit()函数的代码部分:

/**
  * @brief   设置微控制器系统
  *         初始化嵌入式Flash接口、PLL,并且更新SystemCoreClock 变量
  * @note   此功能仅能在复位后使用.
  * @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 |= (uint32_t)0x00000001;

  /* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */
#ifndef STM32F10X_CL
  RCC->CFGR &= (uint32_t)0xF8FF0000;
#else
  RCC->CFGR &= (uint32_t)0xF0FF0000;
#endif /* STM32F10X_CL */   
  
  /* Reset HSEON, CSSON and PLLON bits */
  RCC->CR &= (uint32_t)0xFEF6FFFF;

  /* Reset HSEBYP bit */
  RCC->CR &= (uint32_t)0xFFFBFFFF;

  /* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */
  RCC->CFGR &= (uint32_t)0xFF80FFFF;

#ifdef STM32F10X_CL
  /* Reset PLL2ON and PLL3ON bits */
  RCC->CR &= (uint32_t)0xEBFFFFFF;

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

  /* Reset CFGR2 register */
  RCC->CFGR2 = 0x00000000;
#elif defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)
  /* Disable all interrupts and clear pending bits  */
  RCC->CIR = 0x009F0000;

  /* Reset CFGR2 register */
  RCC->CFGR2 = 0x00000000;      
#else
  /* Disable all interrupts and clear pending bits  */
  RCC->CIR = 0x009F0000;
#endif /* STM32F10X_CL */
    
#if defined (STM32F10X_HD) || (defined STM32F10X_XL) || (defined STM32F10X_HD_VL)
  #ifdef DATA_IN_ExtSRAM
    SystemInit_ExtMemCtl(); 
  #endif /* DATA_IN_ExtSRAM */
#endif 

  /* Configure the System clock frequency, HCLK, PCLK2 and PCLK1 prescalers */
  /* Configure the Flash Latency cycles and enable prefetch buffer */
  SetSysClock();

#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 
}

根据上面的代码,我们可以看出,SystemInit()主要做了两件事:

  • 1、初始化时钟:SYSCLK,HCLK,PCLK2 PCLK1 预分频器等
  • 2、配置中断向量表:中断向量表的定位是在 Flash 还是 SRAM,以及是否需要偏移

注意:

  • 可以通过system_stm32f1xx文件中的宏定义修改系统时钟频率(通过设置锁相环的相关系数),中断向量表的地址(位于SRAM还是Flsah,是否偏移,偏移地址多少等参数)
  • 函数内含VTOR寄存器(即中断向量偏移)设置:SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; 产品IAP由BootLoader跳转到app程序时,需设置中断向量偏移。

__main函数详解

__main 其实不是我们定义的,当编译器编译时,只要遇到这个标号就会定义这个函数,该函数的主要功能是:负责初始化栈、堆,配置系统境,并在最后跳转到用户自定义的main函数,从此来到C的世界。
所以在不同的IDE该函数的名称也有所不同,但所实现的功能大同小异:

  • 完成全局变量/静态变量/常量的初始化和重定位工作
    • a. 跳转进入__scatterload_rt2函数:通过设置四个寄存器来配置待copy内容(静态变量、全局变量、常量)的的加载域和运行域,设置待copy内容的大小,为后续__scatterload_cpy()函数服务。
    • b. 跳转进入__scatterload_cpy函数,完成静态变量、全局变量、常量的从flash到SRAM的重定位。
    • c. 跳转进入__scatterload_zeroinit函数,完成未初始化的全局变量的初始化。
  • 初始化堆栈(这里指程序栈)
    • 跳转进入__user_steup_stackheap函数:调用 __user_libspac__user_libspace 为C库保持了静态数据。这是一个96字节,0初始化的数据块,该块由C库创建。在C库初始化期间可以用来当做临时栈。再调用 __user_initial_stackheap 用户的初始化堆栈函数,实现用户的堆栈的配置,调用 _fp_init 和 __rt_fp_status_addr (C库函数) 两个函数调用实现浮点运算的支持。
    • 如果在”魔法棒“—”Target“中编译勾选了”Use Micro_lib“,程序则采用单区存放堆栈的方式;否则,采用双区存储的方式,分别初始化堆区、栈区。
  • 程序跳转,进入main()函数,执行用户代码

最后,总结下STM32从Flash的启动流程:
1、初始化堆栈指针。 单片机复位后从0x0800 0000处读取栈顶地址并保存。
2、初始化PC指针。 从0x0800 0004读取中断向量表的起始地址(复位中断入口地址),接着跳转到复位程序
3、初始向量表,然后设置时钟,设置堆栈。
4、最后跳转到C空间的main函数,即进入用户程序。
在这里插入图片描述

附录

stm32单片机的存储器映像

类型起始地址存储器用途
ROM0x0800 0000程序存储器Flash存储C语言编译后的程序代码
0x1FFF F000系统存储器存储BootLoader,用于串口下载
0x1FFF F800选项字节存储一些独立于程序代码的配置参数
RAM0x2000 0000运行内存SRAM存储运行过程中的临时变量
0x4000 0000外设寄存器存储各个外设的配置参数
0xE000 0000内核外设寄存器存储内核各个外设的配置参数

中断向量表的映射

BOOT启动方式主要有三种,主闪存存储器启动、系统存储器启动、内置SRAM启动,BOOT1和BOOT0在芯片复位时的电平状态决定了芯片复位后从哪个区域开始执行程序。

注:启动模式只决定程序烧录的位置,加载完程序之后会有一个重映射(映射到0x00000000地址位置)。所以说STM32上电复位以后,代码区都是从0x00000000开始的,三种启动模式只是将各自存储空间的地址映射到0x00000000中。

对应的BOOT引脚状态及启动地址如下图:

BOOT1BOOT0启动模式启动地址说明
X0主闪存存储器Flash0x0800 0000中断向量表定位于FLASH区,主闪存被选为启动区域,最常用,用户代码。同时复位后PC指针位于0x2000000处
01系统存储器0x1FFF F000系统存储器被选为启动区域,程序功能由厂家设置。中断向量表定位于内置Bootloader区,此时可通过串口下载程序的二进制文件到flash区
11内置SRAM0x2000 0000内置SRAM被选为启动区域,中断向量表定位于SRAM区,同时复位后PC指针位于0x2000000处

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

记录一次API报文替换点滴

1. 需求 各位盆友在日常开发中,有没有遇到上游接口突然不合作了,临时需要切换其他接口的情况?这不巧了,博主团队近期遇到了,又尴尬又忐忑。 尴尬的是临时通知不合作了,事前没有任何提醒; 忐忑…

位图、布隆过滤器、海量数据处理

文章目录 位图布隆过滤器海量数据处理 正文开始前给大家推荐个网站,前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。 位图 概念:所谓位图,就是用每一…

【Spring】08 BeanNameAware 接口

文章目录 1. 简介2. 作用3. 使用3.1 创建并实现接口3.2 配置 Bean 信息3.3 创建启动类3.4 启动 4. 应用场景总结 Spring 框架为开发者提供了丰富的扩展点,其中之一就是 Bean 生命周期中的回调接口。本文将聚焦于其中的一个接口 BeanNameAware,介绍它的作…

深度学习中的预测图片中的矩形框、标签、置信度分别是什么意思。

问题描述:深度学习中的预测图片中的矩形框、标签、置信度分别是什么意思。 问题解答: 目标框(Bounding Box): 描述目标位置的矩形边界框。 类别标签: 表示模型认为目标属于哪个类别(例如&#…

opencv 十六 python下各种连通域处理方法(按面积阈值筛选连通域、按面积排序筛选连通域、连通域分割等方法)

本博文基于python-opencv实现了按照面积阈值筛选连通域、按照面积排序筛选topK连通域、 连通域细化(连通域骨架提取)、连通域分割(基于分水岭算法使连通域在细小处断开)、按照面积排序赛选topK轮廓等常见的连通域处理代码。并将代码封装为shapeUtils类,在自己的python代码…

[Verilog] 设计方法和设计流程

主页: 元存储博客 文章目录 1. 设计方法2. 设计流程 3 Vivado软件设计流程总结 1. 设计方法 Verilog 的设计多采用自上而下的设计方法(top-down)。设计流程是指从一个项目开始从项目需求分析,架构设计,功能验证&#…

openEuler商业化进展可观:累计装机量超610万套,市场持续扩容

12月15日至16日,以“崛起数字时代,引领数智未来”为主题的操作系统大会&openEuler Summit 2023在北京国家会议中心举办。大会旨在汇聚全球产业界创新力量,构筑坚实的基础软件根基,推动基础软件技术持续创新&#xff0c…

Redis设计与实现之整数集合

目录 一、内存映射数据结构 二、整数集合 1、整数集合的应用 2、数据结构和主要操作 3、intset运行实例 创建新intset 添加新元素到 intset 添加新元素到 intset(不需要升级) 添加新元素到 intset (需要升级) 4、升级 升级实例 5、关于升级 …

帆软FCRP模拟题

制作步骤可见此博主:https://blog.csdn.net/Ipkiss_Yongheng/article/details/125594366 完成文件下载:【免费】帆软FCRP官网模拟题代码资源-CSDN文库

大创项目推荐 垃圾邮件(短信)分类算法实现 机器学习 深度学习

文章目录 0 前言2 垃圾短信/邮件 分类算法 原理2.1 常用的分类器 - 贝叶斯分类器 3 数据集介绍4 数据预处理5 特征提取6 训练分类器7 综合测试结果8 其他模型方法9 最后 0 前言 🔥 优质竞赛项目系列,今天要分享的是 🚩 垃圾邮件(短信)分类算…

5个创建在线帮助文档的好方法!

在线帮助文档是企业为用户提供支持服务的重要工具,它能够帮助用户更好地了解和使用产品,提高用户体验。然而,创建一份优秀的在线帮助文档需要掌握一定的技巧和方法。接下来就介绍一下创建在线帮助文档的5个好方法,帮助企业更好地为…

day05-报表技术-图形报表

1、图表报表简介 ​ 在大数据时代,人们需要对大量的数据进行分析,帮助用户或公司领导更直观的察觉差异,做出判断,减少时间成本,而在web项目中除了表格显示数据外,还可以通过图表来表现数据,这种…

一维数组的定义

什么是数组? (1)数组是具有一定顺序关系的若干变量的集合,组成数组的各个变量统称为数组的元素 (2)数组中的各元素的数据类型要求相同,用数组名和下标确定,数组可以是一维的&#…

【java】数组遍历的方式:

文章目录 1、for循环遍历:2、forEach循环(增强for循环):3、while循环 或者 do while循环:4、利用Arrays工具类当中的toString():5、流式遍历:6、使用Arrays.asList()和forEach()方法进行遍历: 1、for循环遍…

java-IO流

File类 引入 【1】文件,目录: 文件: 内存中存放的数据在计算机关机后就会消失。要长久保存数据,就要使用硬盘、光盘、U 盘等设备。为了便于数据的管理和检索,引入了“文件”的概念。一篇文章、一段视频、一个可执…

博途WinCC专业版C/S架构入门指南

WinCC Professional V16 支持客户机/服务器架构,但目前只支持单个服务器或单对冗余服务器/多个客户机的模式,还不能支持像WinCC V7.5 SP1中的多个服务器/多个客户机的分布式架构。 博途工控人平时在哪里技术交流博途工控人社群 博途工控人平时在哪里技…

C语言 (指针)输入三个整数,由小到大输出

目录 1问题&#xff1a; 2代码&#xff1a; 3运行结果&#xff1a; 4总结&#xff1a; 1问题&#xff1a; 输入3个整数a,b,c,要求按由小到大的顺序将他们输出。用函数和指针实现》 2代码&#xff1a; #include<stdio.h> int main() {void exchange(int *q1,int *q2…

Cython(将Python编译为so)

环境 先配一下环境&#xff0c;我使用的是python3.8.5 pip install Cython 编译过程 我们准备一个要编译的文件 test.py def xor(input_string): output_string "" for char in input_string: output_string chr(ord(char) ^ 0x66) return output_string …

eNSP小实验---(简单混合)

实验目的&#xff1a;实现vlan10 vlan20 172网段用户互访 1.拓扑图 2.配置 PC1 其它同理 SW4 <Huawei> <Huawei>u t m Info: Current terminal monitor is off. <Huawei>sys <Huawei>sys Enter system view, return user view with CtrlZ. [Hua…

Dockerfile:创建镜像,创建自定义的镜像。

Docker的创建镜像的方式&#xff1a; 基于已有镜像进行创建。 根据官方提供的镜像源&#xff0c;创建镜像&#xff0c;然后拉起容器。是一个白板&#xff0c;只能提供基础的功能&#xff0c;扩展性的功能还是需要自己定义&#xff08;进入容器进行操作&#xff09; 基于模板进…