type
status
slug
date
summary
tags
category
password
icon
前言:
主要是讨论bootloader,stm32启动过程,flash,ram相关的理解学习
前置基础
flash sram启动模式
来自官网的stm32f401re.pdf
我们看看整个F4的地址空间是多大:0x1 0000 0000 也就是bytes 即4GB
可以看到F401re的flash是有0x8 0000 bytes这么大,也就是bytes也即512KB,这就是F401re的flash大小,我们能够写入的代码大小也就是这么多。起始地址是0x800 0000是不是很熟悉,之前在keil中写代码之前,是不是在config里面设置过flash起始地址?并且我们到gcc工具链的时候,测试下载功能的时候是不是指定了下载地址是0x800 0000? yes,这个就是原因。看看下面这个图👇
上图👆说明了设置BOOT0 1为不同电平的时候的启动模式,其中BOOT 0接地就是我们最常用的方式,直接将代码烧录进入了main Flash—0x800 0000(我们的例子)
上面这个代码是我在vscode上的task.json里面配置的下载任务,你看到很明显的0x800 0000就是使用SWD接口下载的地址。
那我们继续:上面当BOOT0 接上高电平,BOOT1 接上低电平,就会从System memory 启动(0x1FFF 0000)。使用STlink/Jlink调试下载就是直接下载在这个位置(ICP下载In-circuit programmer)
我们之前有时候如果正常烧录不进去,比如stlink的端口被占用(SWD模式无法实现),那么就可以采用上面方式,st公司为stm32芯片出产时写了一个系统存储区,这个区里面的代码(bootloader)能够引导把程序下载到flash里面(称为ISP方式(In-System Programming)),并从flash位置继续执行代码。(这里是我猜测的, 看到后面说boot启动后,内存有一个重映射,我又把握不准这个猜测对不对,但是之前自己做实验的时候就是发现了这样烧录后好像它直接就在跑代码了。)
应该是错的,烧录完后要运行的话,还是要把BOOT0拉低,转成main flash启动,就能运行刚烧录的代码了
原话:
The embedded bootloader code is located in system memory. It is programmed by ST during production. For additional information, refer to application note AN2606
下图是使用FlyMcu串口下载程序,这个串口是USB-TTL,下载程序时让BOOT0=0,BOOT1=X即可。不是说在系统中编程需要将B00T0=1,B00T1=0吗?这是因为我们使用的是这个软件,这个软件可以通过DTR和RTS改变BOOT的引脚电平,达到不用修改BOOT引脚就可以下载运行代码,实际上是软件替我们做了改变BOOT引脚的操作
同样的如果是两个BOOT都是接的高电平,就会从SRAM启动(0x2000 0000)(这里埋一个问题:SRAM不是动态内存吗?从SRAM启动有什么用?):
A:多数情况下SRAM 只是在调试时使用,也可以做其他一些用途。如做故障的局部诊断,写一段小程序加载到SRAM 中诊断板上的其他电路,或用此方法读写板上的Flash 或EEPROM 等。还可以通过这种方法解除内部Flash 的读写保护,当然解除读写保护的同时Flash 的内容也被自动清除,以防止恶意的软件拷贝
👆上图注意到最后的一行的,将代码执行(从0x0000 0000)开始的区域根据boot方式的不同,重映射到不同的区域。
还有这个旁注(花了点时间理解): Even when aliased in the boot memory space, the related memory is still accessible at its original memory space.
这是啥意思呢?简单的,就是说你看,我们在0x000 0000 - 0x0007 FFFF 这个地方不是做了一个map的alias吗?那么相当于访问FLASH SRAM Syetem memory的方式就变成(常规下)去访问0x000 0000起始的这段地址了吗?那之前的方式还能访问吗?答案是能的,也就是这句话说的意思,就是它alias的对象,还是可以通过原来的original 的地址来访问。
Depending on the selected boot mode, main Flash memory, system memory or SRAM is accessible as follows:
- Boot from main Flash memory: the main Flash memory is aliased in the boot memory space (0x0000 0000), but still accessible from its original memory space (0x800 0000). In other words, the Flash memory contents can be accessed starting from address 0x0000 0000 or 0x800 0000.
- Boot from system memory: the system memory is aliased in the boot memory space (0x0000 0000), but still accessible from its original memory space (0x1FFF B000 in connectivity line devices, 0x1FFF F000 in other devices).
Boot from the embedded SRAM: SRAM is accessible only at address 0x2000 0000. (我感觉这句话说得不对,SRAM不也是可以用重映射的地址吗?
还有一个坑,现在还填不了:可以看到SRAM的地方,F401RE只用了96KB,注释中写的96KB aliased by bit-banding,这里不知道是什么意思,感觉内存这点也不是很够用呀。
来填了:stm32的确就是只用了96KB,然后说说这个地方的bit-band
stm32F401RE总共有两个地方有bit-band这个东西,一个是SRAM这里,还要一个是在peripheral memory。这里我接下来就是讲解SRAM里面的bit-band,另一个和它是一样的方法。
bit-band只有1M大小,bit-band alias有32M,思路特别简单,就是把这个1M大小的内容,有8Mbit吧,就bit来看就需要占用8M个位置,然后再浪费一点,每个bit会占用4个位置来表示,这种表示方法就会用到32M个标记,每个4标记(地址)对应的只是单纯的一个bit,浪费了31个位和原来相比。
所以通过bit-band 的位置来计算bit-band alias也是很简单,比如在0x2000 0001地址的第2个bit,应该放在bit-band alias的哪里呢?
首先确定alias的基地址,这个是约定的为0x2200 0000,然后因为每一个byte有8位,每位占用4个地址来表示,所以马上又有了一个1*32的偏移,然后因为是要第二个bit(编号由0开始),所以起始还需要偏移一个2*4,所以地址就是0x2200 0028.
这个过程标准化写一个公式出来就是:
唯一需要注意的点就是,这两个bit band和bit band alias是绑定的,但是因为bitband alias有31位没用,所以你修改alias的高31位会忽略,0xFF和0x01是一样的,就是这个道理。
来一个直观的映射图来加深印象:
下面👇这个就是AN2606手册,是stm32的bootloader的权威参考
内存分布与总线
先来一个参考链接:
这个链接挺好的,我根据它做一些笔记:
我们来看看和ARM内核有连接的几个总线:(来自这个RM的37页)
按照类型分为:
由于图片资料是F407的,这里有些地方略微不一致,不过也可以参考得到其意:👇
I-bus:
该总线连接带有FPU的Cortex-M4内核的指令总线到BusMatrix。该总线被内核用于取指令操作。该总线的控制目标是一块包括代码的内存。
This bus connects the Instruction bus of the Cortex®-M4 with FPU core to the BusMatrix.
This bus is used by the core to fetch instructions. The target of this bus is a memory containing code (internal Flash memory/SRAM).
这里没有FSMC部分
D-bus:
该总线连接带有FPU的Cortex-M4内核的数据总线到BusMatrxi。该总线被用于下载代码与调试,该总线的控制目标是一块包括代码(通过DCODE)或者数据的内存
This bus connects the databus of the Cortex®-M4 with FPU to the BusMatrix. This bus is
used by the core for literal load and debug access. The target of this bus is a memory
containing code or data (internal Flash memory/SRAM).
S-bus:
This bus connects the system bus of the Cortex-M4 with FPU core to a BusMatrix. This bus is used to access data located in a peripheral or in SRAM. Instructions may also be fetched on this (less efficient than ICode). The targets of this bus are the internal SRAM, the AHB1 peripherals including the APB peripherals, and the AHB2 peripherals.
这个时候,就会想bus Matrix是什么,怎么这些总线访问的功能区域好像有重叠呢?
总线矩阵为主要部件访问次要部件提供了一种访问机制,即使多个高速外设同时工作,也能实现并发访问和高效操作。主要部件就是图中上面的部分,次要部件就是侧边的部分。
接着我们可以继续看看总线矩阵核心部件里面的两个DMA(在F401中,虽然我给的参考图是F407的,会多一些核心部件)
DMA内存总线:
此总线连接DMA内存总线主接口到总线矩阵。用于DMA执行到内存的传输。其访问目标是数据内存——内部的Flash数据,SRAM(SRAM1,SRAM2) (注:图中可见,DMA2内存访问的范围比DMA1还要广)
This bus connects the DMA memory bus master interface to the BusMatrix. It is used by the
DMA to perform transfer to/from memories. The targets of this bus are data memories:
internal Flash memory, internal SRAM and additionally for S4 the AHB1/AHB2 peripherals
including the APB peripherals.
这里终于反应过来了这个点,你看看图里面是不是有点👆,那些圆点是有含义的,比如看看之前的I-bus,它对应的列S0,两个圆点对应着M0、M2,也就是ICODE和SRAM1,也就是定义的I-bus能够碰到的位置
DMA外设总线:
This bus connects the DMA peripheral masterbus interface to the BusMatrix. This bus is
used by the DMA to access AHB peripherals or to perform memory-to-memory transfers.
The targets of this bus are the AHB and APB peripherals plus data memories: Flash
memory and internal SRAM.
再让我们看看时钟树:(F401RE)
想一个问题,那AHB1 AHB2 和APB1 APB2的关系是什么呢?为什么DMA peripheral 那里,对于DMA2就有两种方式来获取,从AHB1和直接从APB2,所以就感觉到有问题,这两种方式有什么不同?
通过这里这个图我了解了, DMA1其实可以和APB1直接相连,DMA2也有线与APB2直接相连,所以上面的BUSMatrix图里面的DMA才会有画两条不经过matrix的线
接下来我们看看stm32的内存管理
.data
数据段,储存已初始化且不为0的全局变量和静态变量(全局静态变量和局部静态变量)。 static声明的变量放在data段。 数据段属于静态内存分配,所以放在RAM里,准确来说,是在程序运行的时候需要在RAM中运行。
.BSS
Block Started by Symbol。储存未初始化的,或初始化为0的全局变量和静态变量。 BSS段属于静态内存分配,所以放在RAM里。
.text(CodeSegment/Text Segment)
代码段,储存程序代码。也就是存放CPU执行的机器指令(machineinstructions)。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读(某些架构也允许代码段为可写,即允许修改程序)。 在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。 放在Flash里。
.constdata
储存只读常量。const修饰的常量,不管是在局部还是全局 放在Flash 里。 所以为了节省 RAM,把常量的字符串,数据等 用const声明
heap(堆)
堆是用于存放进程运行中被动态分配的内存段。他的大小并不固定,可动态扩张或者缩减,由程序员使用malloc()和free()函数进行分配和释放。当调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)。 放在RAM里 其可用大小定义在启动文件startup_stm32fxx.s中。
stack(栈)
栈又称堆栈,是用户存放程序临时创建的局部变量,由系统自动分配和释放。可存放局部变量、函数的参数和返回值(但不包括static声明的变量,static意味着 放在 data 数据段中)。
MDK的情况
Code:代码
RO-data:.constdata 只读数据,也就是常量
RW-data: 可读可写数据,这里是指非BSS的部分,也就是已经初始化的 全局变量和静态变量,并且它们的初始化非0.这里需要注意的是.data段 是在RAM里面运行的,但是为啥Flash里面需要存储呢?因为起初程序的全局变量如果有初始化,是需要持久化保存的,所以放在Flash里面,只有没有初始化的情况下,或者本身为0,才会放在RAM里面,这是一种优化,也就是说可以不这样做,也把他们放在flash。你又会想,那为什么全局变量一定要占用flash的空间呢?
想想编译原理学的内容,我们是给全局变量建立了一个符号表的(当然,编译过程中也会对局部变量放在符号表里面,只不过最后的可执行文件里面的符号表段没有局部变量),从而在代码里面,全局变量的初始化代码就消失了,转而是那个符号表,那么如果最开始全局变量初始化有了有效值,在后续某个函数中执行到全局变量++的操作,那它的初始值很重要,但是这个初始值又不在代码区里面,那么就必须有一个地方是存放全局变量的初始值的,同理对于static变量也是如此。
ZI-data(Zero Initialize)未初始化的全局变量和静态变量,以及初始化为0的变量
这里需要纠正一个地方,可能会有误解,说BSS段是不占FLASH是不严谨的,严格来说,.bss段里面是由内容,是那些变量符号的占位符,他只是没有存放那些变量的具体值,因为他们要么没有初始值要么初始值为0,那么都会选择用内存中的值进行代替,也就是直接在运行时分配初始值(为0,仅占用RAM里面的值)
所以归纳一下keil的MKD编译后的内存占用:
程序占用 Flash = Code + RO data + RW data
程序运行时候占用 RAM = RW data + ZI data。
GCC的情况
GCC编译, 图中红色的部分是占用 Flash 的大小:Flash = text + data 。 蓝色部分是运行时候占用 RAM大小:RAM = data + bss。
注意这里FLASH里面少了一个表示RO_data的部分,这个部分我猜测就算在text部分了,反正只读数据也不会被加载到RAM的位置,只会被放在和代码区一样,位于FLASH区,程序运行时将地址映射过去。
有了这些前置知识,我们在用手上的stm32F401做实验之前,先用师兄已经做好的stm32H7的板子进行学习,并且也更深的了解一下高级芯片的功能。
- 作者:liamY
- 链接:https://liamy.clovy.top/article/linux/embeded/stm32F401RE
- 声明:本文采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
相关文章