开发环境:
处理器: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;
#elseRCC->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_ExtSRAMSystemInit_ExtMemCtl(); #endif /* DATA_IN_ExtSRAM */
#endif#ifdef VECT_TAB_SRAMSCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM. */
#elseSCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH. */
#endif
}

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

最终PLL的时钟位72MHz。

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

#ifdef VECT_TAB_SRAMSCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM. */
#elseSCB->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的知乎

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

  1. 【正点原子Linux连载】第三十二章 U-Boot启动流程详解 -摘自【正点原子】I.MX6U嵌入式Linux驱动开发指南V1.0

    1)实验平台:正点原子阿尔法Linux开发板 2)平台购买地址:https://item.taobao.com/item.htm?id=603672744434 2)全套实验源码+手册+视频下载地址: ...

  2. U-Boot启动流程详解

    参考:U-Boot顶层目录链接脚本文件(u-boot.lds)介绍 作者:一只青木呀 发布时间: 2020-10-23 13:52:23 网址:https://blog.csdn.net/weixin ...

  3. 【Autosar 启动流程详解】

    Autosar 启动流程详解 1. vLinkGen_Template.lsl 2. BrsHwStartup.c 3.BrsMainStartup.c 4.BrsMain.c 链接文件: 1. vL ...

  4. golang程序启动流程详解

    golang程序启动流程详解 环境 go1.16.5 linux/amd64 用例 package mainimport "fmt"func main() {fmt.Println ...

  5. android zygote启动流程,Android zygote启动流程详解

    对zygote的理解 在Android系统中,zygote是一个native进程,是所有应用进程的父进程.而zygote则是Linux系统用户空间的第一个进程--init进程,通过fork的方式创建并 ...

  6. 【线上沙龙直播报名】App 启动流程详解及其优化

    点击上方"公众号"可以订阅哦 [美团点评技术沙龙Online]是美团点评技术团队推出的线上分享课程,每月2-3期,采用目前最火热的线上直播形式,邀请美团点评技术专家,面向互联网技术 ...

  7. Springboot启动流程详解

    SpringMVC请求流程详解 SpringMVC框架是一个基于请求驱动的Web框架,并且使用了'前端控制器'模型来进行设计,再根据'请求映射规则'分发给相应的页面控制器进行处理. (一)整体流程 每 ...

  8. Android App启动流程详解

    前言:在之前的文章中已经写了apk的打包流程.安装流程,今天就是梳理一下apk系列的最后的流程--app启动流程.经过今天的梳理以后咱们就可以对apk包是怎么编译生成的.apk是怎么被安装到安卓手机的 ...

  9. STM32上电启动代码详解(转自安富莱电子)

    很多单片机开发工程师做的工作主要是应用功能开发,可能对于启动流程不是特别清楚,刚开始做嵌入式开发的时候本人也是如此,不过通过参与项目开发,对于追求性能或者特定功能的实现,需要对启动代码做一定的了解,才 ...

最新文章

  1. AI:Algorithmia《2021 enterprise trends in machine learning 2021年机器学习的企业趋势》翻译与解读
  2. haproxy 作为反向代理被攻击
  3. java使用varargs,Java 实例 – Varargs 可变参数使用 - Java 基础教程
  4. 使用H5实现机器人脸
  5. Tableau研学小课堂(part4)--表
  6. C和指针之函数之把数字字符串转为整数并且返回这个数字(ascii_to_integer)
  7. jgroups传输消息_使用JGroups进行ElasticMQ消息复制
  8. Magento教程 17:Magento功能导览(1) 会员功能
  9. Windows10上安装Kali并设置apt源
  10. 利用display属性写的遮罩层
  11. 设计模式全解析 23种
  12. numpy文件读写的三对函数
  13. matlab系统辨识工具箱的使用
  14. (爆笑)国产电视剧的电脑高手
  15. fsf大流行政治天网抗议监视
  16. 第十四届蓝桥杯第一期模拟赛 python
  17. 好书收藏:读书知多少
  18. Couldn‘t terminate the existing process for com.hopechart.gallery
  19. HTML学习笔记(二)--基础
  20. Java拼图游戏总结,Java拼图游戏课程设计报告

热门文章

  1. 通信中的星座图的理解
  2. 100种思维模型之混沌与秩序思维模型-027
  3. 同一个SQL引发多个ORA-7445错误
  4. UML图学习笔记(二)状态图
  5. tplink软件升级有用吗_TP-LINK路由器软件升级图解教程
  6. 网页样式——各种炫酷效果及实现代码
  7. poj1007 DNA Sorting
  8. 印花弹性白胶浆外观品质控制方法
  9. 基于java的考研自习室音视频通话APP设计
  10. 如何用revit打不开服务器文件,双击RVT文件无法在正确版本的Revit中打开项目