0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

STM32 启动流程的详细讲解

ouxiaolong 来源:嵌入式大杂烩 作者:嵌入式大杂烩 2022-03-06 15:26 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

开发环境:

处理器STM32F103

MDK:5.30

STM32CubeMX:6.0.1

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

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

深入理解ARM寄存器:https://bruceou.blog.csdn.net/article/details/117866186

ARM汇编入门:https://bruceou.blog.csdn.net/article/details/117897496

Keil反编译入门(一):https://bruceou.blog.csdn.net/article/details/118314875

Keil反编译入门(二):https://bruceou.blog.csdn.net/article/details/118400368

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

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

2.初始化中断向量表

3.配置系统时钟

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

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

1 STM32的启动模式

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

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

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

poYBAGGm-NuAD4bcAAB4W9j4wms335.png

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

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

2 STM32的启动文件分析

因为启动过程主要是由汇编完成的,因此STM32的启动的大部分内容都是在启动文件里。笔者的启动文件是startup_stm32f103xe.s,不管使用标准库还是使用HAL库,启动文件都是差不多的。

2.1堆栈定义

1.Stack栈

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

pYYBAGGm-NyAO9D2AAB5BNYbnlQ498.png

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

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

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

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

2.Heap堆

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

pYYBAGIkYG2AKWA_AAB_1MjIEWw739.png

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

2.2向量表

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

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

pYYBAGIkYIWAalemAAEzAzwzIsM222.png

……

poYBAGGm-OGAYKu-AACYtV0J3wM865.png

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

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

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

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

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

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

poYBAGIkYJqAea1JAAXNebKDzzY290.png

2.3复位程序

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

pYYBAGGm-OSAPptXAACAL8cSWmk466.png

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

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

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

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

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

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

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

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

2.4中断服务程序

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

pYYBAGIkYMeAUS-_AADPgbuSoi0636.png

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

2.5堆栈初始化

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

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

poYBAGIkYNuAYXyDAAGMgB83ta4038.png

如果没有定义__MICROLIB,则会使用双段存储器模式,且声明了__user_initial_stackheap 具有全局属性,这需要开发者自己来初始化堆栈。

pYYBAGIkYOqALSSLAAEVrV020ZY840.png

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

2.6其他

poYBAGGm-OqAGc6jAAASCQvb2jQ569.png

第50行:PRESERVE8用于指定当前文件的堆栈按照 8字节对齐。

第51行:THUMB表示后面指令兼容 THUMB指令。现在 Cortex-M系列的都使用 THUMB-2指令集,THUMB-2是 32位的,兼容 16位和 32位的指令,是 THUMB的超集。

3 STM32的启动流程实例分析

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

3.1初始化SP、PC、向量表

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

pYYBAGGm-OuAQF1vAAByWiO3WGs335.jpg

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

pYYBAGIkYSWAP1kUAAK0V6w8j5Y782.png

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

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

poYBAGIkYV-ACQ07AAI1iaHeQ28403.png

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

3.2设置系统时钟

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

pYYBAGIkYXGAF4_rAAHcuom8E2I251.png

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

当然也可通过硬件调试来确认上面的分析:

pYYBAGIkYYCAPxBeAALPXJh0jEs254.png

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

poYBAGGm-POAQf9bAADXhfB5j74822.png

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 
}

前面部分是配置时钟的,具体参考手册吧,这里需要注意以下代码:

#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.3初始化堆栈并进入main

执行指令LDR R0, =__main,然后就跳转到__main程序段运行,当然这里指标准库的__main函数。

pYYBAGIkYcWABomfAAFzw58xdfQ653.png

这中间初始化了栈区。

poYBAGIkYcqAcd5VAAHfb4agIdk973.png

这段代码是个循环(BCC 0x08000192),实际运行时候循环了两次。第一次运行的时候,读取“加载数据段的函数”的地址并跳转到该函数处运行(注意加载已初始化数据段和未初始化数据段用的是同一个函数);第二次运行的时候,读取“初始化栈的函数”的地址并跳转到该函数处运行。

最后就进入C文件的main函数中,至此,启动过程到此结束。

最后,总结下STM32从flash的启动流程。

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

pYYBAGIkYduAHd6EAAIBGqGQGI0878.png

审核编辑:符乾江

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • STM32
    +关注

    关注

    2314

    文章

    11214

    浏览量

    375396
  • 启动流程
    +关注

    关注

    0

    文章

    14

    浏览量

    6675
收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    在 i.MX8M Plus EVK 上刷新和调试 Cortex-M7 固件详细流程是什么?

    Cortex-A53: 直接刷写调试 M7 固件,而不依赖 A53 内核的详细流程是什么? M7 固件通常如何加载到 OCRAM/TCM 中,是否可以完全独立运行? Debugging Tools
    发表于 05-18 06:08

    “中国造”STM32启动规模量产,意法半导体打造MCU产业本地化新样本

    电子发烧友网报道(文/梁浩斌)终于,嵌入式开发的硬通货STM32在中国本土启动规模量产了。近日 意法半导体 宣布,完全中国造的STM32通用MCU已开启交付,首批由 华虹宏力 代工的STM32
    的头像 发表于 03-24 11:27 7664次阅读
    “中国造”<b class='flag-5'>STM32</b><b class='flag-5'>启动</b>规模量产,意法半导体打造MCU产业本地化新样本

    RGB时序灯条的工作原理讲解

    图文配合讲解了RGB时序灯条的应用场景、什么是RGB时序灯条、信号格式与传输规则、灯珠芯片的工作流程、颜色与动态效果控制方式等
    发表于 02-06 11:36 0次下载

    3562 单板机 Linux 系统固化完全指南:从 SD 卡启动到 eMMC 永久部署

    本文为创龙科技 3562 单板机系统固化操作指南,核心涵盖 SD 启动卡、升级卡制作,以及 eMMC 芯片系统固化与启动验证。文档详细说明专用工具使用、镜像烧录参数配置、多方式启动测试
    的头像 发表于 02-04 10:34 2737次阅读
    3562 单板机 Linux 系统固化完全指南:从 SD 卡<b class='flag-5'>启动</b>到 eMMC 永久部署

    初识ros2 功能包建立与可执行文件的配置

    本文介绍了ROS2程序的两种启动方式:直接运行可执行文件(ros2 run)和使用启动文件(ros2 launch)。重点讲解了Python软件包的结构,包括package.xml、setup.py
    的头像 发表于 01-22 13:41 576次阅读

    如何在Zynq UltraScale+ MPSoC平台上通过JTAG启动嵌入式Linux镜像

    流程教程)。本文则进一步讲解如何在 Zynq UltraScale+ MPSoC 平台上通过 JTAG 逐步启动 Linux,并提供了完整的过程与关键命令。只要按步骤操作,即使是复杂的 Linux 镜像也能成功通过 JTAG
    的头像 发表于 01-13 11:45 5333次阅读

    3D打印材料选择完全指南:从原型到终端件的选材流程

    本文系统讲解如何为项目选择最佳3D打印材料,涵盖从使用目的、性能指标到打印工艺的完整流程,并提供典型场景推荐与验证方法,助你高效决策。
    的头像 发表于 12-15 16:25 777次阅读
    3D打印材料选择完全指南:从原型到终端件的选材<b class='flag-5'>流程</b>

    d1哪吒开发板的启动流程分析

    的可以研究很深的开发板。本文主要从研究D1启动流程的角度出发,探索一下D1的裸机开发实践。对于研究D1的底层裸机开发,首先需要知道可以玩那些东西,也可以对RISCV相关的软件生态有比较透彻的理解,本文会
    发表于 10-29 06:44

    【今晚7点半】正点原子 x STM32:智能加速边缘AI应用开发!今晚正点原子B站直播间等你

    STM32团队专业讲解STM32N6与STM32MP2核心架构,AI计算能力详解以及典型应用案例分析。涵盖智能家居以及机器人等多个领域。 正点原子团队创新分享基于
    发表于 09-25 14:14

    STM32C011开发(3)----Flash操作

    STM32C011 系列微控制器内置 Flash 存储器,支持程序存储与数据保存,具备页面擦除、双字写入、读写保护等功能。本文将简要介绍 STM32C011 的 Flash 结构与特性,并通过实际代码示例,讲解 Flash 的擦
    的头像 发表于 09-18 16:48 5435次阅读
    <b class='flag-5'>STM32</b>C011开发(3)----Flash操作

    求助,关于K230启动流程疑问求解

    , GNU ld (GNU Binutils) 2.35) #1 SMP Sat Jun 21 23:51:35 CST 2025 [ 0.000000] earlycon: sbi0 上面是启动流程
    发表于 07-11 06:42

    一文看懂芯片的设计流程

    差异。接下来,我们就以数字芯片为例,详细看看芯片到底是如何设计出来的。芯片设计的主要流程芯片的设计,总体分为规格定义、系统设计、前端设计(Front-EndDesi
    的头像 发表于 07-03 11:37 3253次阅读
    一文看懂芯片的设计<b class='flag-5'>流程</b>

    ElfBoard技术贴|【RK3588】ELF 2开发板开机自启动详解

    在嵌入式系统开发中,合理管理开机自启动项目能够优化系统启动流程,确保关键服务和应用按时加载运行。本文将详细介绍在ELF2开发板Linux5.10.209系统下基于SystemVinit
    的头像 发表于 06-27 16:20 2307次阅读
    ElfBoard技术贴|【RK3588】ELF 2开发板开机自<b class='flag-5'>启动</b>详解

    使用USB转TTL串口板和ST-LINK调试下载器给STM32单片机下载程序

    本文详细介绍如何使用ST-LINK/V2和USB转TTL串口板为STM32单片机下载程序,包括STM32启动模式、JTAG与SWD接口对比、驱动安装及官方软件
    的头像 发表于 06-20 17:26 3420次阅读
    使用USB转TTL串口板和ST-LINK调试下载器给<b class='flag-5'>STM32</b>单片机下载程序

    飞凌嵌入式ElfBoard ELF 1板卡-uboot启动流程分析之uboot启动阶段

    u-boot的relocation重定位操作。四、清除BSS段。五、调用board_init_r函数,执行后续的板级初始化操作,包括各种外设接口、中断、环境变量等。最后调用run_main_loop()函数,进入倒计时等待,等待超时后执行bootcmd启动内核。具体流程
    发表于 05-30 09:24