STM32上电启动过程分析

发布者:悠闲自在最新更新时间:2024-09-18 来源: elecfans关键字:STM32  上电  启动过程 手机看文章 扫描二维码
随时随地手机看文章

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

3eeadefbd5727ebac2698ff0e366307f_682808186242d17c8acecaa25ed193a1.png

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


        2.初始化 PC 指针=Reset_Handler


        3.初始化中断向量表


        4.配置系统时钟


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


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


STM32的启动模式


b97aee13cac8d31232ebc4b2cb1ac1c9_6fa567d12d29abc20fde8e4e3f4e3e95.png

        手册可以在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的引脚电平状态,来选择复位后的启动模式。


        如下图所示:

64da3d8a6f0bdfd9a6b3d8c71ff3651f_3f3e084ad01019c0ec7e07d56a2a587d.png

585102ec3f9dbc2ae36cc0b0b4024231_57b2d691a86c2042426590b966d9373c.png

总结 

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


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


STM32的启动文件分析

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


1. Stack栈

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


0b10e37cd1b051e0a781bf3419d05574_4d5fcad6fb4efdf6585c68fa06c37c2e.png

第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()函数申请的内存就在堆中。


297d7b5240baf17a9585828dd14a3af0_394739628171dc456e47ecdc3ca19ad4.png

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

21e75c007735f7fd3eebd0f54b73128b_d65ba589f877ae3e731e6a9f61088cab.png

3. 向量表

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


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

4f6146f50318405b368be9020e188acd_39cfafcce499725038e112a70508236d.png

d2f86f22592406cb722927f569fa072e_139dccd937771dc75685d8ec6a40adab.png

第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. 复位程序

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

257ccee78531d8b6d2f941e0600d696f_a479e74c210d35eb452f9789d91b5579.png

第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表示跳转,这里跳转到一个‘.’,即表示无线循环。

c6f1c67f76a94e83a258fa4508c14d5e.png

b4836b2c21d3483281fe227506248674.png

 6. 堆栈初始化

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


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

3339e34492734b5b8a2561f2cd27d61f.png

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

e7c11f8a3d0b40019860c8d4cca34d1e.png

关键字:STM32  上电  启动过程 引用地址:STM32上电启动过程分析

上一篇:应用笔记|STM32U575/585 MCU 硬件开发入门
下一篇:STM32微控制器的技术特点和性能指标

推荐阅读最新更新时间:2024-11-12 10:32

STM32配置外设寄存器,不论怎么写都写不进去或全是0
之前在调试STM32定时器时,由于不满意STM32 HAL库,于是便自己写了配置代码。 但是运行时,不论怎么调试,都发现定时器的每个寄存器都为0,也不清楚究竟有没有写进去。 将数据、地址等等都打印出来,都没有问题。 后来仔细检查后才发现是定时器的时钟没有打开。 如果一个外设的时钟没有打开,那么此外设是不会运行的,那么对其执行的访问都是无效的。 于是就记住了这个问题所在。 后来在调试其它外设时偶尔会出现问题,但是出现问题的一瞬间就想到是否是时钟没有打开。 如果每次遇到这个问题,都非常快的就知道了解决方案。 所以在配置外设时,发现寄存器写不进去或者写了之后全为0,那么应该要想到是否是外设时钟没有打开的原
[单片机]
STM32IO及定时器映射到地址
意义: 有时候我们在操作多个STM32 IO 时,硬件设计未必有规律,比如输出引脚是:PB3,PC4,PC5,PD0,但是操作这些引脚具有共性,或者说我们想用 for(it i = 0; i 4; i++) 像操作数组一样操作这些引脚,程序将变得非常简洁,这时候把 IO 映射到地址就可以实现该目的。 方法: 1.//位带操作,实现51类似的GPIO控制功能 //具体实现思想,参考《CM3权威指南》第五章(87~92页),M4同M3类似,只是寄存器地址变了 //IO口操作宏定义 #define BITBAND(addr, bitnum) ((addr & 0xF0000000)+0x2000000+((addr &0xFFFF
[单片机]
STM32 USART3可以接收无法发送问题(Tx一直为高电平)
首先这个问题耗费了我比较多的时间来进行调试,比较郁闷; 1. 同时使用相同的函数进行了USART2和USART3的初始化配置, USART2工作正常, 中断服务程序的结构也是相同的, 收发都是正常的,没有发问题; 2. 使用USART3调试LCD时, 发现无法进行通信, 表现是Rx可以正常进行接收, Tx发送数据时一直为高电平, 使用示波器捕获不到任何波形; 3. 关于USART3的寄存器, GPIO的寄存器, USART3/GPIO/AFIO等时钟已经进行了配置, NVIC也进行了配置; 查看与USART2的差异,发现完全相同,没有差别, 但是无法发送. 4. 单独写了一个文件进行串口测试, 查询发送OK!
[单片机]
STM32之中断与事件一个使用GPIO作为外部中断的示例
1.GPIO 的正确设置 GPIO_InitTypeDef GPIO_InitStructure; /* Enable GPIOD clock */ RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE); /* Configure PD.03, PC.04, as input floating */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 | GPIO_Pin_4 ; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOD, &GPIO_InitStru
[单片机]
一种STM32微控制器处理电机控制的设计和实现
变频器是利用电力半导体器件的通断作用将工频电源变换为另一频率的电能控制装置,能实现对交流异步电机的软起动、变频调速、提高运转精度、改变功率因数、过流/过压/过载保护等功能。变频器集成了高压大功率晶体管技术和电子控制技术,得到广泛应用。变频器的作用是改变交流电机供电的频率和幅值,因而改变其运动磁场的周期,达到平滑控制电动机转速的目的。变频器的出现,使得复杂的调速控制简单化,用变频器+交流鼠笼式感应电动机组合替代了大部分原先只能用直流电机完成的工作,缩小了体积,降低了维修率,使传动技术发展到新阶段。本文将探讨基于ARM的标准微控制器如何在一个被DSP和FPGA长期垄断的市场上打破复杂的控制模式,我们将以意法半导体的基于Cortex-M
[单片机]
一种<font color='red'>STM32</font>微控制器处理电机控制的设计和实现
stm32专题二十七:MPU6050 驱动程序
提供了一个简单的mpu6050的驱动: mpu6050.h #ifndef __MPU6050_H #define __MPU6050_H #include stdint.h #include i2c.h #include usart.h #include stm32f1xx_hal.h /* MPU6050 */ #define DELAY_MS 10 // 初始化延时 #define DEVICE_ADDR (0XD0) // 8位设备地址 #define PWR_MGMT_1 (0X6B) // 电源管理1 #define MPU6050_
[单片机]
<font color='red'>stm32</font>专题二十七:MPU6050 驱动程序
STM32单片机的PSAM卡驱动模块设计
引言 刷卡消费随着人们生活水平的提高已经成为常用的支付方式之一。为了保证刷卡消费的安全性,将PSAM卡内嵌于各种终端刷卡设备中。PSAM(Purchase SecureAccess Module,销售点终端安全存取模块),由IC卡发行主管部门或者应用主管机构发行,是可以用于对IC卡进行脱机消费交易认证的安全认证卡,主要应用于商用POS、网点终端、直连终端等设备上,支持多级发卡机制,适用于多应用的环境,符合识别卡、带触点的集成电路卡标准、ISO/IEC 7816—1/2/3/4以及《中国人民银行PSAM卡规范》。 1 PSAM卡简介 PSAM卡是接触式CPU卡的一种。CPU卡也称智能卡,卡内集成电路带有微处理CPU,存储
[单片机]
<font color='red'>STM32</font>单片机的PSAM卡驱动模块设计
基于STM32的LED和KEY
#include stm32f10x.h /*********************************************************************** ************************************************************************/ GPIO_InitTypeDef GPIO_InitStructure; /*********************************************************************** ****************************************
[单片机]
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

随便看看

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved