STM32F103ZE芯片存储空间的地址映射关系图。
在MDK编译程序设置ROM和RAM地址时候发现:
IROM1为片上程序存储器,即片上集成的Flash存储器,对该处理器Flash大小为512KB,即0x80000 地址区间为0x8000000~0x0807FFFF
IRAM1为片上数据存储器,即片上集成的SRAM存储器,对该处理器RAM大小为64KB,即0x10000 地址区间为0x20000000~0x20010000
这里问题为什么程序启动地址在MDK设置里为0x08000000?
分析根据图1(Cortex-M3 预定义的存储器映射)这个是Cortex-M3核地址的映射分布图,我们看到代码区是0x00000000-0x1FFFFFFF总共对应地址大小为512MB。而对于STM32F103ZE芯片使用到的ROM空间只有512KB。Cortex-M3定下的规矩是从0地址启动,SMT32当然不能破坏ARM定下的“规矩”,所以它做了一个地址重映射的过程。
疑问:明明代码是下载到 0x80000000 往后的存储空间中,为什么说运行又是从 0x00000000地址运行的呢?为什么不是供 0x80000000 开始运行的呢?
有关这个问题,就是我们说的单片机的自举。
正常情况下都是映射到主FLASH上,所以都是从主FLASH上启动的,为了从FLASH启动,我们需要将代码下载到主FLASH上。
什么是重映射?
引用文章,如有侵权请联系作者删除
https://blog.51cto.com/u_14114084/4930970
如果0x00000000-0x001 F FFFF之前是映射在系统存储器或者嵌入式SRAM上的,现在改变BOOT0、BOOT1的电平为0、X。0x00000000-0x001 FFFFF就被重新映射在了主FLASH上,这就是单片机的地址重映射。
重映射就是本来是和张三进行映射的的,现在改为了和李四映射。换句话说重映射就是0x00000000-0x001FFFFF(1MB)本来映射在系统存储器0x1FFF0000-0x1FFF7A0F(30KB)上面,现在映射到了主FLASH0x08000000-0x081 F FFFF(1M8)上面.
选择从主FLASH启动时,显然FLASH会被映射在了两片地址上。
- 原本映射的地址(1MB):0x08000000-0x081 F FFFF,进行ST-LINK下载时使用这个地址
·重映射的地址(1MB):0x00000000-0x001 F FFFF,启动时CPU就是从重映射的地址读取指令 - 这两片地址都是有效的,重映射到FLASH上后,CPU从O地址开始运行时,就从FLASH上读
取指令,当然前提是我们需要将代码下载FLASH中。
这就解释了为什么我们在keil中设置好程序的下载地址为0x800 0000,但是单片机上电是确实从0开始执行。是因为我们在硬件上设置了BOOT0=1,BOOT1=X,从而导致了主FLASH区(也叫主闪存,大小1MB)被映射到了0x0000 0000 - 0x001F FFFF(1MB),故而代码是下载到 0x80000000 往后的存储空间中,却说运行又是从 0x00000000地址运行的。
疑问:下载时,能不能使用 0x0000 0000 地址来下载?
答:这个不行,因为下载时,0x0000 0000 - 0x001F FFFF 还没有被重映射到 flash 上,只能使用 0x0800 0000 来下载。
既然设置到0x0800 0000这么麻烦,为什么不直接使用0x0000 0000?
这是因为STM32不仅可以从内部Flash启动,还可以从系统存储器(可以实现串口ISP,USB DFU等程序下载方式,这个程序是ST固化好的程序代码)和从内部SRAM启动,
我们将内部Flash安排到0x0000 0000显然是不行的。这样会导致系统存储器或者内部SRAM无法重映射到0x0000 0000了。
上面说的是我们用JLink下载器下载代码,但是有时候我们还听说可以用串口来下载程序,这又是怎么回事?
用串口下载程序,也就是我们说的ISP在系统中编程。从系统存储器启动,即STM32的ISP了。此时硬件电路B00T0=1,B00T1=0。由于串口不能直接把程序下载到主FLASH里面,所以需要使用到ST公司内嵌于系统存储区的Bootloader来引导把程序下载到主FLASH里面。JLink能直接把程序下载到内置的FLASH里面,是因为JLink下载器内部有Bootloader来引导把程序下载到FLASH里面。 程序下载完成后还需要配置BOOT引脚为BOOT0=0,BOOT1=X(即从主闪存存储器启动),复位后才能正常启动程序。如果你不修改BOOT引脚的话也就是B00T0=1,B00T1=0,那么0x0000 0000 - 0x001F FFFF是不是被重映射到系统存储器上面,而程序代码在主FLASH里面。你复位后程序肯定不能正常运行,只有在使用串口下载程序后配置BOOT引脚为BOOT0=0,BOOT1=X,复位后才能正常执行代码。你明白了吗?
总结:使用JLink下载代码,JLink下载器内部的Bootloader将程序引导下载到主FLASH里面。使用串口下载代码,由于串口没有Bootloader,就要使用ST官方内置在芯片系统存储区的Bootloader代码,将程序引导下载止主FLASH。又因为程序是从0开始执行的,所以我们复位后运行程序时一定要让BOOT0=0,BOOT1=X,将0x00000000 - 0x001FFFFF是重映射到主FLASH我们代码存在的地方,从0开始执行代码。
下图是使用FlyMcu串口下载程序,这个串口是USB-TTL,下载程序时让BOOT0=0,BOOT1=X即可。不是说在系统中编程需要将B00T0=1,B00T1=0吗?这是因为我们使用的是这个软件,这个软件可以通过DTR和RTS改变BOOT的引脚电平,达到不用修改BOOT引脚就可以下载运行代码,实际上是软件替我们做了改变BOOT引脚的操作,具体介绍可以看上面的说明。
ARM芯片的地址重映射 映射就是一一对应的意思。重映射就是重新分配这种一一对应的关系
普通的单片机把可执行代码和数据存放到存储器中。单片机中的CPU从储器中取指令代码和数据。其中存储器中每个物理存储单元与其地址是一一对应而且是不可变的。如下图,CPU读取0x00000000地址上存储单元的过程
ARM芯片中有些物理存储单元的地址可以根据设置变换。就是说一个物理存储单元现在对应一个地址,经过设置以后,这个存储单元就对应了另外一个地址了(重映射就是重新分配这种一一对应的关系)
如下图
上图表示把0x00000000地址上的存储单元映射到新的地址0x08000000上。CPU存取0x08000000就是存取0x00000000上的物理存储单元。网上说这种重映射设计主要是为了提高应用程序异常响应得速度。因为RAM的存取速度远高于对FLASH的存取速度,当程序发生异常后,这时代码区存在两个异常向量区,提高异常响应速度(待考证)
那么我们看芯片上的BOOT0和BOOT1配置启动方式如下图:
当BOOT1=X,BOOT0=0时(这种情况是嵌入式开发过程中产品最常用的启动方式),则CPU可以访问0x0000000或0x08000000(这两个地址是共同的物理空间)。
验证设置ROM起始地址为0x00000000是否和0x08000000这样:
通过配置编译环境,仿真时看到memory,0x00000000和0x08000000的数据完全一致。
如果设置ROM地址为0x0000000进行编译
通过配置编译环境,仿真时看到memory,0x00000500和0x08000500的数据完全一致。
STM32三种启动模式
下好程序后,重启芯片时,SYSCLK的第4个上升沿,BOOT引脚的值将被锁存,这就是所谓的启动过程。
STM32上电或者复位后,代码区始终从0x00000000开始,其实就是将存储空间的地址映射到0x00000000中。三种启动模式如下:
- 从主闪存存储器启动,将主Flash地址0x08000000映射到0x00000000,这样代码启动之后就相当于从0x08000000开始。主闪存存储器是STM32内置的Flash,作为芯片内置的Flash,是正常的工作模式。一般我们使用JTAG或者SWD模式下载程序时,就是下载到这个里面,重启后也直接从这启动程序。
- 从系统存储器启动。首先控制BOOT0、BOOT1管脚,复位后,STM32与上述两种方式类似,从系统存储器地址0x1FFF F000开始执行代码。系统存储器是芯片内部一块特定的区域,芯片出厂时在这个区域预置了一段Bootloader,就是通常说的ISP程序。这个区域的内容在芯片出厂后没有人能够修改或擦除,即它是一个ROM区。启动的程序功能由厂家设置。系统存储器存储的其实就是STM32自带的bootloader代码。
- 从内置SRAM启动,将SRAM地址0x20000000映射到0x00000000,这样代码启动之后就相当于从0x20000000开始。内置SRAM,也就是STM32的内存,既然是SRAM,自然也就没有程序存储的能力了,这个模式一般用于程序调试。假如我只修改了代码中一个小小的地方,然后就需要重新擦除整个Flash,比较的费时,可以考虑从这个模式启动代码,用于快速的程序调试,等程序调试完成后,在将程序下载到SRAM中。
用户可以通过设置BOOT1和BOOT0引脚的状态,来选择在复位后的启动模式。STM32三种启动模式对应的存储介质均是芯片内置的,如下图:
串口下载程序原理
从系统存储器启动,这种模式启动的程序功能是由厂家设置的。一般来说,这种启动方式用的比较少。系统存储器是芯片内部一块特定的区域,STM32在出厂时,由ST在这个区域内部预置了一段BootLoader,也就是我们常说的ISP程序,这是一块ROM,出厂后无法修改。
一般来说,我们选用这种启动模式时,是为了从串口下载程序,因为在厂家提供的BootLoader中,提供了串口下载程序的固件,可以通过这个BootLoader将程序下载到系统的Flash中。
这个下载方式需要以下步骤:
将BOOT0设置为1,BOOT1设置为0,然后按下复位键,这样才能从系统存储器启动BootLoader;
在BootLoader的帮助下,通过串口下载程序到Flash中;
程序下载完成后,又有需要将BOOT0设置为GND,手动复位,这样,STM32才可以从Flash中启动。
STM32启动过程
此部分引用文章,如有侵权请联系作者删除
https://blog.csdn.net/qq_44971108/article/details/124051734
作为一个单片机相关的开发人员,大家应该都很清楚单片机是从main函数开始执行代码的,但是很显然微控制器无法从硬件上定位main函数的入口地址,因为使用C语言作为开发语言后,变量/函数的地址便由编译器在编译时自行分配,这样一来main函数的入口地址在微控制器的内部存储空间中不再是绝对不变的。那么单片机又是如何找到咱们所写的main函数的呢,大家应该在各种单片机开发过程中都会了解到一个启动文件的东西。没错,单片机正式通过启动文件来找到咱们的main函数的。接下来咱们就来看一下STM32单片机是如何启动的。
官方给的启动流程图如下
上述开机启动流程比较详细,内容较为全面,但部分步骤可以省略(红字可省略标出),因为对于某些初始化,我们可能会在main函数中重新配置。
其中提到了两个指针一个是SP指针一个是PC指针,这两个指针的作用如下:
PC:Program Counter,是通用寄存器,但是有特殊用途,用来指向当前运行指令的下一条指令。
SP:Stack Pointer,堆栈指针,也是通用寄存器,用于入栈和出栈操作。
对于ARM7而言,是三级流水线结构,一条指令为4个字节大小,一条指令包含三个过程:Fetch(取指)、Decode(译指)、Execute(执行)。
CPU运行地址 = 当前PC值 = 当前程序执行位置 + 8;
对于ARM7而言,PC为R15,SP为R13。
PC不是指向正在执行的指令,而是始终指向下一个取指的指令。对于ARM7三级流水先结构和指令的三个过程,所以PC = 当前程序执行位置 + 8。
启动地址
单片机上电之后会默认从地址0x00000000取出栈指针的初始值,作为MSP的值,从地址0x00000004取出程序指针PC的初始值,对应第一条执行的程序。我以我们需要看一下STM32官方对内存地址的管理,STM32将4GB的地址空间进行了如下划分:
其中Block 0 的内容如下,我们从哪里开始执行代码取决于BOOT引脚:
BOOT引脚的电平和启动位置的具体关系如下,一般我们在KEIL软件中设置的下载地址是从0x0800 0000开始,也就是说咱们的代码一般是保存到Flash里的,所以一般也是从主闪存存储器开始执行的:
启动文件分析
无论性能高下,结构简繁,价格贵贱,每一种微控制器(处理器)都必须有启动文件,启动文件的作用便是负责执行微控制器从“复位”到“开始执行main函数”中间这段时间(称为启动过程)所必须进行的工作。最为常见的51,AVR或MSP430等微控制器当然也有对应启动文件,但开发环境往往自动完整地提供了这个启动文件,不需要开发人员再行干预启动过程,只需要从main函数开始进行应用程序的设计即可。
启动文件是使用汇编指令写的,所以在看启动文件之前大家需要先了解一下汇编指令,常用的汇编指令如下:
有了以上汇编指令的基础,那么我们去读启动文件就会方便很多:
堆栈定义
堆栈知识详细请点击这里
1,Stack栈
栈的作用是用于局部变量,函数调用,函数形参等的开销,栈的大小不能超过内部SRAM 的大小。当程序较大时,需要修改栈的大小,不然可能会出现的HardFault的错误。
第35行:表示开辟栈的大小为 0X00002000,EQU是伪指令,相当于C 中的 define。
第37行:开辟一段可读可写数据空间,ARER 伪指令表示下面将开始定义一个代码段或者数据段。此处是定义数据段。ARER 后面的关键字表示这个段的属性。段名为STACK,可以任意命名;NOINIT 表示不初始化;READWRITE 表示可读可写,ALIGN=3,表示按照 8 字节对齐。
第38行:SPACE 用于分配大小等于 Stack_Size连续内存空间,单位为字节。
第39行: __initial_sp表示栈顶地址。栈是由高向低生长的。
2,Heap堆
堆主要用来动态内存的分配,像 malloc()函数申请的内存就在堆中。
开辟堆的大小为 0X00000200(512 字节),名字为 HEAP,NOINIT 即不初始化,可读可写,8字节对齐。__heap_base 表示对的起始地址,__heap_limit 表示堆的结束地址。
向量表
向量表是一个WORD( 32 位整数)数组,每个下标对应一种异常,该下标元素的值则是该 ESR 的入口地址。向量表在地址空间中的位置是可以设置的,通过 NVIC 中的一个重定位寄存器来指出向量表的地址。在复位后,该寄存器的值为 0。因此,在地址 0 (即 FLASH 地址 0)处必须包含一张向量表,用于初始时的异常分配。
值得注意的是这里有个另类: 0 号类型并不是什么入口地址,而是给出了复位后 MSP 的初值,后面会具体讲解。
第57行:定义一块代码段,段名字是RESET,READONLY 表示只读。
第58-60行:使用EXPORT将3个标识符申明为可被外部引用,声明 __Vectors、__Vectors_End 和__Vectors_Size 具有全局属性。
第62行:__Vectors 表示向量表起始地址,DCD 表示分配 1 个 4 字节的空间。每行 DCD 都会生成一个 4 字节的二进制代码,中断向 量表 存放的实际上是中断服务程序的入口地址。当异常(也即是中断事件)发生时,CPU 的中断系统会将相应的入口地址赋值给 PC 程序计数器,之后就开始执行中断服务程序。在60行之后,依次定义了中断服务程序的入口地址。
第140行:__Vectors_End 为向量表结束地址。
第141行:__Vectors_Size则是向量表的大小,向量表的大小是通过__Vectors 和__Vectors_End 相减得到的。
复位程序
复位程序是系统上电后执行的第一个程序,复位程序也是中断程序,只是这个程序比较特殊,因此单独提出来讲解。
第147行:定义了一个服务程序,PROC表示程序的开始。
第148行:使用EXPORT将Reset_Handler申明为可被外部引用,后面WEAK表示弱定义,如果外部文件定义了该标号则首先引用该标 号,如 果外部文件没有声明也不会出错。这里表示复位程序可以由用户在其他文件重新实现,这种写法在HAL库中是很常见的。
第149-150行:表示该标号来自外部文件,SystemInit()是一个库函数,在system_stm32f1xx.c中定义的,__main 是一个标准的 C 库函数,主要作用是初始化用户堆栈,这个是由编译器完成的,该函数最终会调用我们自己写的main函数,从而进入C世界中。
第151行:这是一条汇编指令,表示从存储器中加载SystemInit到一个寄存器R0的地址中。
第152行:汇编指令,表示跳转到寄存器R0的地址,并根据寄存器的 LSE 确定处理器的状态,还要把跳转前的下条指令地址保存到 LR。
第153行:和149行是一个意思,表示从存储器中加载__main到一个寄存器R0的地址中。
第154行:和150稍微不同,这里跳转到至指定寄存器的地址后,不会返回。
第155行:和PROC是对应的,表示程序的结束。
中断服务程序
我们平时要使用哪个中断,就需要编写相应的中断服务程序,只是启动文件把这些函数留出来了,但是内容都是空的,真正的中断复服务程序需要我们在外部的 C 文件里面重新实现,这里只是提前占了一个位置罢了。
这部分没啥好说的,和服务程序类似的,只需要注意‘B .’语句,B表示跳转,这里跳转到一个‘.’,即表示无限循环,如果在调试代码的过程中发现代码卡死到这个B的位置上,那就说名你的代码开启了某个中断,但是你没有对中断进行处理,就会出现这中情况。
堆栈初始化
堆栈初始化是由一个IF条件来实现的,MICROLIB的定义与否决定了堆栈的初始化方式。这个定义是在Options->Target中设置的。
如果没有定义__MICROLIB , 则会使用双段存储器模式,且声明了__user_initial_stackheap 具有全局属性,这需要开发者自己来初始化堆栈。文章来源:https://www.toymoban.com/news/detail-792107.html
总结一下STM32从Flash的启动流程
1,上电之后先去0x0000 0000地址加载SP指针从地址0x0000 0004加载PC指针,如果是BOOT0引脚为低电平,则指向Flash处执行程序
2,从Flash处也就是0x0800 0000加载栈顶指针SP,从0x0800 0004处加载中断向量表的起始地址PC,也就是复位程序的地址
3,执行复位程序,先执行SystemInit()函数初始化系统时钟,然后执行main()函数文章来源地址https://www.toymoban.com/news/detail-792107.html
到了这里,关于为什么STM32设置Flash地址0x08000000而不是0x00000000?STM32的启动过程的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!