使用一个STM32芯片,对于内存而言有两个直观的指标就是 RAM 大小,FLASH大小,比如STM32F103系列(其他系列也是如此):
那么着两个大小意味着什么?怎么去理解这两个内存,那就得从什么是Flash,什么是RAM说起。 一、FLASH 和 RAM基本概念先来看一张图:
- O0 w9 e( g) T4 h1 h) Q) W5 f 1.1 FLASH是什么 通过上图我们可以知道,FLASH属于 非易失性存储器: 扩展一点说,FLASH又称为闪存,不仅具备电子可擦除可编程(EEPROM)的性能,还不会断电丢失数据同时可以快速读取数据,U盘和MP3里用的就是这种存储器。在以前的嵌入式芯片中,存储设备一直使用ROM(EPROM),随着技术的进步,现在嵌入式中基本都是FLASH,用作存储Bootloader以及操作系统或者程序代码或者直接当硬盘使用(U盘)。 然后 Flash 主要有两种NOR Flash和NADN Flash。(对于这两者的区别,下面的话供参考,因为这些介绍都是基于早些年的技术了) NOR Flash的读取和我们常见的SDRAM的读取是一样,用户可以直接运行装载在NOR FLASH里面的代码,这样可以减少SRAM的容量从而节约了成本。 NAND Flash没有采取内存的随机读取技术,它的读取是以一次读取一块的形式来进行的,通常是一次读取512个字节,采用这种技术的Flash比较廉价。用户不能直接运行NAND Flash上的代码,因此好多使用NAND Flash的开发板除了使用NAND Flah以外,还作上了一块小的NOR Flash来运行启动代码。 STM32单片机内部的FLASH为 NOR FLASH。Flash 相对容量大,掉电数据不丢失,主要用来存储 代码,以及一些掉电不丢失的用户数据。
RAM 属于易失性存储器: RAM随机存储器(Random Access Memory)表示既可以从中读取数据,也可以写入数据。当机器电源关闭时,存于其中的数据就会丢失。比如电脑的内存条。 RAM有两大类,一种称为静态RAM(Static RAM/SRAM),SRAM速度非常快,是目前读写最快的存储设备了,但是它也非常昂贵,所以只在要求很苛刻的地方使用,譬如CPU的一级缓冲,二级缓冲。另一种称为动态RAM(Dynamic RAM/DRAM),DRAM保留数据的时间很短,速度也比SRAM慢,不过它还是比任何的ROM都要快,但从价格上来说DRAM相比SRAM要便宜很多,计算机内存就是DRAM的。 DRAM分为很多种,常见的主要有FPRAM/FastPage、EDORAM、SDRAM、DDR RAM、RDRAM、SGRAM以及WRAM等,这里介绍其中的一种DDR RAM。 DDR RAM(Date-Rate RAM)也称作DDR SDRAM,这种改进型的RAM和SDRAM是基本一样的,不同之处在于它可以在一个时钟读写两次数据,这样就使得数据传输速度加倍了。这是目前电脑中用得最多的内存,而且它有着成本优势,事实上击败了Intel的另外一种内存标准-Rambus DRAM。在很多高端的显卡上,也配备了高速DDR RAM来提高带宽,这可以大幅度提高3D加速卡的像素渲染能力。 为什么需要RAM,因为相对FlASH而言,RAM的速度快很多,所有数据在FLASH里面读取太慢了,为了加快速度,就把一些需要和CPU交换的数据读到RAM里来执行(注意这里不是全部数据,只是一部分需要的数据,这个在后面介绍STM32的内存管理中会提到)。 STM32单片机内部的 RAM 为 SRAM。RAM相对容量小,速度快,掉电数据丢失,其作用是用来存取各种动态的输入输出数据、中间计算结果以及与外部存储器交换的数据和暂存数据。 # ?7 c9 ~2 G: t: A2 @ 在《ARM Cotrex-M3权威指南》中有关 M3的存储器映射表:
存储器映射 是用 地址来表示 对象,因为Cortex-M3是32位的单片机,因此其PC指针可以指向2^32=4G的地址空间,也就是图中的 0x00000000到0xFFFFFFFF的区间,也就是将程序存储器、数据存储器、寄存器和输入输出端口被组织在同一个4GB的线性地址空间内,数据字节以小端格式存放在存储器中。 . Q, i6 z2 u1 z2 [) [* [6 L: I" Q6 r0 _6 I7 i/ e 2.2 STM32 的存储器映射分析 STM32存储器映射表(选用的是STM32F103VE的,不同的型号Flash 和 SRAM 的地址空间不同,起始地址都是一样的):
那么我们所需要分析的STM32 内存,就是图中 0X0800 0000开始的 Flash 部分 和 0x2000 0000 开始的SRAM部分,这里还要介绍一个和Flash模块相关的部分:
2 Z3 ]- T# d/ r4 i) s# o. g - A+ o% J; ^: ` STM32的Flash,严格说,应该是Flash模块。该Flash模块包括:Flash主存储区(Main memory)、Flash信息区(Informationblock),以及Flash存储接口寄存器区(Flash memory interface)。 主存储器,该部分用来存放代码和数据常数(如加const类型的数据)。对于大容量产品,其被划分为256页,每页2K,小容量和中容量产品则每页只有1K字节。主存储起的起始地址为0X08000000,B0、B1都接GND的时候,就从0X08000000开始运行代码。 信息块,该部分分为2个部分,其中启动程序代码,是用来存储ST自带的启动程序,用于下载,当B0接3.3V,B1接GND时,运行的就这部分代码,用户选择字节,则一般用于配置保护等功能。 闪存储器块,该部分用于控制闪存储器读取等,是整个闪存储器的控制机构。 对于主存储器和信息块的写入有内嵌的闪存编程管理;编程与擦除的高压由内部产生。 在执行闪存写操作时,任何对闪存的读操作都会锁定总线,在写完成后才能正确进行,在进行读取或擦除操作时,不能进行代码或者数据的读取操作。 7 M$ a6 n& K; P2 y: p! r" D STM32 的内存管理起始就是对 0X8000 0000 开始的 Flash 部分 和 0x2000 0000 开始的 SRAM 部分使用管理
参考博文:STM32内存结构介绍 在了解如何使用内存管理之前,先得理解一下 6 个储存数据段 和 3种存储属性区 的概念: & d9 [( g& m% @! G1 `- s* w+ A4 u
.data 数据段,储存已初始化且不为0的全局变量和静态变量(全局静态变量和局部静态变量)。static声明的变量放在data段。数据段属于静态内存分配,所以放在RAM里,准确来说,是在程序运行的时候需要在RAM中运行。
.BSS Block Started by Symbol。储存未初始化的,或初始化为0的全局变量和静态变量。BSS段属于静态内存分配,所以放在RAM里。
.text(CodeSegment/Text Segment) 代码段,储存程序代码。也就是存放CPU执行的机器指令(machineinstructions)。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读(某些架构也允许代码段为可写,即允许修改程序)。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。放在Flash里。 ! ]4 ^: }, O5 c& X* E$ \& j .constdata 储存只读常量。const修饰的常量,不管是在局部还是全局放在Flash 里。所以为了节省 RAM,把常量的字符串,数据等 用const声明 8 P1 C' E2 U, K- G) `+ g# @ heap(堆) 堆是用于存放进程运行中被动态分配的内存段。他的大小并不固定,可动态扩张或者缩减,由程序员使用malloc()和free()函数进行分配和释放。当调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)。放在RAM里其可用大小定义在启动文件startup_stm32fxx.s中。
stack(栈) 栈又称堆栈,是用户存放程序临时创建的局部变量,由系统自动分配和释放。可存放局部变量、函数的参数和返回值(但不包括static声明的变量,static意味着 放在 data 数据段中)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。???由于栈的先进先出(FIFO)特点???上面这句话正确吗?Cortex-M3/M4的堆栈是向下生长,第一个入栈的元素应该是最后一个才能出来??所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。放在RAM里其大小定义在启动文件startup_stm32fxx.s中。 + G K1 p2 N& e, z8 X+ T, E( l 3.2.1 MDK 编译 MDK编译后的结果:
5 L' B! @/ p9 L; K3 P+ W4 o Code:程序代码部分。.text 段放在ROM里面,就是Flash,需要占用flash空间 RO-data(Read Only)只读数据 程序定义的常量,只读数据,字符串常量(const修饰的).constdata 段放在flash里面,需要占用flash空间 RW-data(Read Write)可读可写数据 已经初始化的全局变量和静态变量(就是static修饰的变量);.data 段需要在 RAM里面运行,但是起初需要保存在 Flash里面,程序运行后复制到 RAM里面运行,需要占用Flash空间 ZI-data(Zero Initialize)未初始化的全局变量和静态变量,以及初始化为0的变量;.BSS段ZI的数据全部是0,没必要开始就包含,只要程序运行之前将ZI数据所在的区域(RAM里面)一律清 0,不占用Flash,运行时候占用RAM. heap 和 stack 其实也属于 ZI,只不过他不是程序编译就能确定大小的,必须在运行中才会有大小,而是是变化的 因为RAM掉电丢失,所以 RW-data 数据也得下载到ROM(flash) 中,在运行的时候复制到 RAM中运行,如下图所示: . F) K9 g3 R& P1 `
由上我们得知: 程序占用 Flash = Code + RO data + RW data 程序运行时候占用 RAM = RW data + ZI data。 Code + RO data + RW data 的大小也是生成的 bin 文件的大小
GCC编译结果:
GCC编译, 图中红色的部分是占用 Flash 的大小:Flash = text + data 。蓝色部分是运行时候占用 RAM大小:RAM = data + bss。 7 V& v. Z$ T% Q 我们前面说到的 stack(栈) 和 heap(堆),程序编译完成以后并不能知道运行时候实际占用RAM 大小。但是我们可以知道的是 stack(栈) 和 heap(堆)的起始地址,和能够使用的最大空间,我们先看能够使用的空间大小。
MDK是在 startup_stm32fxxx.s 中定义的: 2 P4 d% ~- J6 o
& B, p) Q. L, v9 ~( U8 @ E4 x 在 startup_stm32fxxx.s 中我们可以看到关于 Stack_Size 和 Heap_Size的定义,图中的定义就是规定本程序中 栈 的大小为 1K, 堆的大小为 0.5K。 startup_stm32fxxx.s文件是系统的启动文件,startup_stm32fxxx.s主要完成三个工作:栈和堆的初始化、定位中断向量表、调用Reset Handler。(关于STM32的启动文件,我们有单独的一篇文章,请查看另一篇博文 STM32的启动过程(startup_xxxx.s文件解析)) 我们在生成的.map文件可以看到 HEAP 和 STACK的起始地址(不懂的可以先看博文下面一节的内容——四、MDK生成的.map文件简析),我们要注意的是:堆使用时候从起始地址开始,往上加栈使用时候从结束地址,就是__initial_sp(栈顶指针的地址)开始,往下减他们的空间大小定义好了,如果入栈元素过大,使得元素减到了堆的地址范围,就是栈溢出,这会导致改变堆中相应地址元素的值。同样的,当申请的动态内存过大,使得堆的变量加到了栈的地址范围,就是堆溢出。 在实际项目中,如果程序复杂,中断嵌套太多,栈需要多设置一点空间如果你使用裸机程序,从来不使用标准库的malloc,heap可以没有,因为永远不会用到,还一直占用着一片RAM区。 所以在我们知道了以上的知识后,我们可以按照自己的工程需求,定义Stack_Size 和 Heap_Size。 0 z& p; s: R- O 如果是在GCC编译器下面,关于 Stack_Size 和 Heap_Size的定义如下图: - g! o* W1 a7 t( |% J3 l; I
- D/ P$ }, j& g7 B! H& [ 为了更深层次的理解上述内容,我们还有必要分析一下MDK生成的 .map 文件, 那么既然要分析,除了我们关注的flash 和 ram部分的内容,其他的地方也稍微做一下笔记: U0 ~! `4 ~9 h* {! ^& @ 主要是不同文件中函数的调用关系:
2 ]* E+ K& z. B) L6 @0 |( n 我们查看一下 clock.c 文件下的 rt_tick_increase函数:
被删除的冗余函数:
删除函数功能在MDK的配置中可以设置,勾选以后删除得多,不勾选删除得少,如下图: " D, Y. e" b) b- x. {
在 Removing Unused input sections from the image 的最后会列出删除的冗余函数的大小,如果在MDK上改变上图所示的配置,下图中的删除总代码可以看到变化: - F2 n& V; r3 e( r6 L0 y( _' a" P* g
4.3.1 Local Symbols 局部标号, 用Static声明的全局变量地址和大小, C文件中函数的地址和用static声明的函数代码大小, 汇编文件中的标号地址(作用域限本文件): - |: Z" Z" b+ U
. k/ ` T$ z- i, r* M; s 我们找个占用内存地址的地方看一下: ; p, p* f% O7 g# T9 n( J; t2 |! H
" j2 ^! R8 L6 @, d. ^6 }" a/ s \ 我们在 startup_stm32f103xg.s ,可以看到RESET函数:我们继续往下看:
上图中的 i.RCC_Delay 下面跟了一个 RCC_Delay,说明这个函数是用static修饰的,我们找到 stm32f1xx_hal_rcc.c下的RCC_Delay 如图:
4 }" o, t7 ^$ N- F 我们接着看到 SRAM区:
2 C& o, s% q% ?: n% R2 b 我们继续看到后面的.bss段,包括了 HEAP 和 STACK区域:
( K. m2 f+ d, C2 r" U/ X' ? 通过上图中我们可以看到 HEAP 的起始地址为 0x20002338, ram从 0x2000 0000开始存放的依次为 .data、.bss、HEAP、STACK。HEAP在 startup_stm32fxxx.s 中定义过大小为 0x200, 所以结束地址为0x20002538, HEAP 是和 STACK连接在一起的,所以STACK的起始地址为 0x20002538,大小 0X400,结束地址为 0x20002938。最后我们可以看到 __initial_sp 指向的是 0x20002938,入栈从高地址开始入栈,地址越来越小。
全局标号, 全局变量的地址和大小, C文件中函数的地址及其代码大小, 汇编文件中的标号地址(作用域全工程):
我们找到Flash地址开始的部分:
在 startup_stm32fxxx.s 能看到对应的部分:
) }$ r7 {4 j$ @0 |. n6 S 看到最后的 RAM区,注意下图中标出的两行的功能: ! `8 X, ?5 E( s" A- h" A
映像文件可以分为加载域(Load Region)和运行域(Execution Region):加载域反映了ARM可执行映像文件各个段存放在存储器中时的位置关系。 : F$ t+ s+ ^" ^8 o
' j$ S, [5 m1 L8 c 其中还能看出来 Flash中存放的都是 code,和 RO_Data: . S' ?/ E( [2 T k# `" B+ a' q
6 u3 c# S% H" m5 g- J 我们看看SRAM部分:
! e# \. Q" n7 {4 x* K: ^ 需要注意一下,我们前面代码he 数据部分都是4字节对齐,PAD一般都是补充2个字节,到了栈部分,需要8字节对齐: / H" i ?2 {# V. q
: d8 Z" E5 I8 C0 M 存储组成大小 这部分的内容就比较直观 }" C0 S3 X" E5 A
/ H& p) l, X/ P, Y 最后就是我们熟悉的部分: ! x3 a' a z7 r7 {1 b7 {
. r' F. Q8 n# _% B2 W
) d3 R# R4 h$ M( e, J p 第一种启动方式是最常用的用户FLASH启动,正常工作就在这种模式下,STM32的FLASH可以擦出10万次,所以不用担心芯片哪天会被擦爆! 第二种启动方式是系统存储器启动方式,即我们常说的串口下载方式(ISP),不建议使用这种,速度比较慢。STM32 中自带的BootLoader就是在这种启动方式中,如果出现程序硬件错误的话可以切换BOOT0/1到该模式下重新烧写Flash即可恢复正常。 第三种启动方式是STM32内嵌的SRAM启动。该模式用于调试。 用jlink在线仿真,则是下载到SRAM中。 以上三种启动方式我们都很熟悉,但是他的究竟是如何实现的呢? 我们先来看看《Cortex-M3权威指南》关于CM3复位后的动作:
/ P" K+ _% p o8 F/ n9 H0 [8 f 当选择相应的启动方式时,对应的存储器空间被映射到启动空间(0x00000000)。 从闪存存储器启动:主闪存存储器被映射到启动空间(0x0000 0000) ,也就是0x08000000被映射到0x00000000。 从内嵌SRAM启动 :SRAM起始地址 0x2000 0000 被映射到0x00000000。 从系统存储器启动:系统存储器被映射到启动空间(0x0000 0000),也就是0x1FFF F000被映射到0x00000000。(为什么是0x1FFF F000 可以查阅上文中的 2.2小节 STM32 的存储器映射分析,STM32互联型产品这个地址不一样,此地址由ST官方写入了一段BootLoader代码,可以通过官方BootLoader升级MCU固件,无法修改)。 , V, n. D( M4 P; E D. \ 通过上面的内容,我们现在知道STM32 从Flash程序启动以后会从 0X08000000 开始运行,那么他这个地址是否可以修改,答案是当然的!但是单独的改他的启动地址,没有任何意义,一般都是需要使用到 Bootloader 才会使得应用程序的地址发生变化。 在 0x1FFF F000 这个地址上官方写入了一段 BootLoader 用户使用,我们也可以自己写一段 BootLoader 程序方便自己使用,因为是自己写的,他还是用户程序,只是我们自己把程序分成了 BootLoader部分和 应用程序部分,大概的意思如下图所示: 8 U5 P4 k4 B) ]/ t
( a- \ B2 H# k2 a; N; o4 s 为什么要使用用户 BootLoader : 在有些项目中,可能因为某些原因需要经常更换 程序,如果每次都是重新烧录,特别的麻烦,那么我们就可以自己设计一个 BootLoader,通过 SD卡进行升级:上电后先运行 BootLoader,BootLoader主要工作是检测是否有SD卡,SD卡中是否有需要的BIn文件, 如果检测到就将其复制到 应用程序区域 使得程序得以更新,更新结束以后跳转到应用程序执行;如果没检测到相应的SD卡,就说明程序不需要更新,也跳转到应用程序执行; 以上主要是说明使用 BootLoader 的思路与适用场合,至于具体的实现其实网上有很多教程,如果有机会我也会补充或者单独写一篇文章总结一下 (说明:下面是零碎的笔记,还没整理完,仅供参考 stm32 FLASH的起始地址是0x08000000,当然也可以自定义起始地址,不过记得在main函数中定义变量后加一句SCB->VTOR=FLASH_BASE | OFFSET;OFFSET是想要偏移的量,可宏定义或直接0xXX。当然也可以调用库函数 NVIC_SetVectorTable()进行偏移,效果一样。IAP升级这样用的多) : Z9 n- S1 [5 b1 C0 X 参考博文:cpu运行时程序是在flash中还是在RAM呢?x86的pc机cpu在运行的时候程序是存储在RAM中的,而单片机等嵌入式系统则是存于flash中 x86cpu和单片机读取程序的具体途径:pc机在运行程序的时候将程序从外存(硬盘)中,调入到RAM中运行,cpu从RAM中读取程序和数据 原因分析 :x86构架的cpu是基于冯.诺依曼体系的,即数据和程序存储在一起,而且pc机的RAM资源相当丰富,从几十M到几百M甚至是几个G,客观上能够承受大量的程序数据。9 B* r# @8 [* r9 G 冯.诺依曼体系与哈佛体系的区别:二者的区别就是程序空间和数据空间是否是一体的。早期的微处理器大多采用冯诺依曼结构,典型代表是Intel公司的X86微处理器。取指令和取操作数都在同一总线上,通过分时复用的方式进行的。缺点是在高速运行时,不能达到同时取指令和取操作数,从而形成了传输过程的瓶颈。3 t$ N# g4 G! E M" N5 N! D1 b (此部分是学习了韦东山老师的视频后来记录的,原视频可以在韦东山老师官网里面找到:百问网) ARM芯片属于精简指令集计算机(RISC:Reduced Instruction Set Computing),它所用的指令比较简单,有如下特点:① 对内存只有读、写指令 ② 对于数据的运算是在CPU内部实现 ③ 使用RISC指令的CPU复杂度小一点,易于设计 . U" q/ ]7 s4 ?; n$ @
5 @+ q: i% @+ }7 p8 A/ D 在ARM架构中,对于乘法运算a = a * b, 在RISC中要使用4条汇编指令:① 读内存a ② 读内存b ③ 计算a*b ④ 把结果写入内存 x86属于复杂指令集计算机(CISC:Complex Instruction Set Computing), 它所用的指令比较复杂,比如某些复杂的指令,它是通过“微程序”来实现的。 / g! m, K7 J/ R. q
比如执行乘法指令时,实际上会去执行一个“微程序”, 一样是去执行这4步操作:① 读内存a ② 读内存b ③ 计算a*b ④ 把结果写入内存
RISC和CISC的区别:
##六、 GCC下生成的.map文件 我们知道了MDK下的.map文件,GCC下的.map文件基本上也可以看懂了,这里添加GCC下.map文件关于RAM的部分: : z: d0 C6 y- J d% _8 E RAM部分从0x2000 0000开始,首先存放的是.data部分: 3 d: v6 e. ]! _. ~8 I
接下来是.bss段: * x# R$ G+ _+ [# Y2 n
如果使用了FreeRTOS,那么在.bbs段后面会有关于FreeRTOS部分的数据:
! i6 t0 E" A9 N$ ` 最后才到了heap和stack: 0 J2 d% S% j; R3 s3 s
如有侵权请联系删除 转载自:矜辰所致2 _! {1 Q2 X! ]: M8 M
|
OpenBLT移植到STM32F405开发板
为什么要先开启STM32外设时钟?
【STM32MP157】从ST官方例程中分析RPMsg-TTY/SDB核间通信的使用方法
【经验分享】STM32实例-RTC实时时钟实验④-获取RTC时间函数与中断服务函数
STM32 以太网 MAC Loopback 的实现
STM32功能安全设计包,助力产品功能安全认证
基于STM32启动过程startup_xxxx.s文件经验分享
HRTIM 指南
ST 微控制器电磁兼容性 (EMC) 设计指南
适用于STM32微控制器的ΣΔ数字接口入门