背景
本篇文章将介绍一下ARM架构的ARM-M系列单片机的flash以及ram,以及程序启动的逻辑。提前申明,本人所写的本文是汲取网上的知识以及自己的理解,如果哪里讲的不对请广大网友指正。本文先以stm32f429ZIT6这个型号的单片机进行介绍。
STM32F429ZIT6微控制器2048KB FLASH,256 KB SRAM,SDRAM ** Mbits。最高180MHz主频
FLASH
先说flash ,它在嵌入式系统中的功能可以和硬盘在PC中的功能相比。它们都是用来存储程序和数据的,好比是ROM。而且可以在掉电的情况下继续保存数据使其不会丢失。Flash memory(闪速存储器)作为一种安全、快速的存储体,具有体积小,容量大,成本低,掉电数据不丢失等一系列优点,已成为嵌入式系统中数据和程序最主要的载体。根据结构的不同可以将其分为NOR Flash和NAND Flash两种。NOR Flash的特点是应用程序可以直接在闪存中运行,不需要再把代码读到系统RAM中运行。NAND Flash不行。而我们单片机基本都是NOR FLASN。而手机我们说的 ** 128应该是NAND FASH。
毫无疑问,stm32F429的flash是NOR Flash。
stm32F429的FLASH内部构成
主存储器块,就是我们说的2MB的那个区域,也就是keil或者其他ide 将我们的程序烧写的地方。存储我们的代码以及初始化好的数据。系统存储器,存储的是一段特殊的程序,叫做bootloader,通过运行此段区域里的程序,可以对Main memory进行重新烧写。此代码是出厂的时候就固化在STM32F4里面了,专门来给主存储器下载代码的。OTP区域 即一次性可编程区域,共528字节,被分成两个部分,前面512字节(32字节为1块,分成16块),可以用来存储一些用户数据(一次性的,写完一次,永远不可以擦除!!),后面16字节,用于锁定对应块。这块区域本人没用过,感兴趣的同学可以自己研究。选项字节 用于配置读写保护、 BOR 级别、软件/硬件看门狗以及器件处于待机或下图为stm32F429的flash构成:注意,地址是从0x8000000开始的,后面会讲程序的启动与FLASH的基本关系。
stm32F429 SRAM
关于他的RAM,其实没有什么好讲的,可以把它比作电脑的内存,但是程序的运行细节和Linux是不一样的,这个后续再说。这里想强调一点是,256KB的RAM,其中 ** K是CCRAM。CCM 只能通过 CPU 访问,CCM是直接挂在D-bus上的,除了CPU(即Cortex-M核)之外,谁都无法访问。此外,由于CCM不属于BusMatrix的一部分,所有也就不能被其他组件访问,例如DMA控制器。对于CCM,CPU能以最大的系统时钟和最小的等待时间从CCM中读取数据或者代码。官方文档说明了使用CCM的一些优势:比如将频繁读取的数据放到CCM,将中断函数放到CCM,这都能加快程序的执行速度。另外关于CCM内存上的使用可以参考以下链接stm32F4上CCM的使用另外三块是地址连续的RAM,大小分别是112K 16K ** K。为什么要分成三份呢?不合为一个整体呢?这个读者感兴趣可以研究下。另外ram中的地址如下图所示,下图为MDK KEIL魔术棒的界面。
对于使用mdk keil的同学,我遇到一个坑希望读到这篇文章的同学可以避免,我在使用rt-thread下stm32f429开发版的时候,发现内存内存size数据明明写的0x30000,但是打开 ** p文件后发现并不是,最大只是128k,少了 ** k,那是因为keil底下的这个√没有选择,
如果不勾选则选择已经写好的默认分散加载文件,勾选了就用魔术棒面板配置好的生成的文件。这样会导致默认使用之前配好的内存大小,这个配好的需要一个后缀名为sct的文件,即分散加载文件(即scatter file 后缀为.scf)是一个文本文件,通过编写一个分散加载文件来指定ARM连接器在生成映像文件时如何分配RO,RW,ZI等数据的存放地址。如果不用SCATTER文件指定,那么ARM连接器会按照默认的方式来生成映像文件,一般情况下我们是不需要使用分散加载文件的。具体使用可以参考以下几个链接。正常使用keil的情况无需关注,默认直接是加载到初始地址。
keil 下的分散加载文件
stm32分散加载文件
stm32单片机程序启动过程以及运行方式
启动过程
首先说一下启动模式:
正常情况下上电会根据boot 引脚设置,以将中断向量表定位于FLASH区(一般代码都会烧写到这里),即起始地址为0x8000000,同时复位后PC指针位于0x8000000处;然后进入以startup_XXX.s 为结尾的后缀的汇编文件中,这个启动文件主要作用就是:
初始化堆栈指针SP;初始化程序计数器指针PC;设置堆、栈的大小设置异常向量表的入口地址设置C库的分支入口__ ** in(最终用来调用 ** in函数)经过上述过程然后会进入 ** in函数里面。这里细节不在详细叙述,感兴趣的同学可以参考以下链接自己研究:说一说stm32启动过程
STM32第二章-启动过程详解
程序运行
这里对于使用mdk keil的同学,我再简单说一下 ** p文件,每次我们编译一个工程,都会生成 ** p文件,并且在keil 中build out的窗口也会显示编译的信息,如下图:
这里我着重介绍一下几个概念:
Code:指代码的大小;Ro-data:指除了内联数据(inline data)之外的常量数据;RW-data:指可读写(RW)、已初始化的变量数据;ZI-data:指未初始化(ZI)的变量数据;Code、Ro-data:位于FLASH中;RW-data、ZI-data:位于RAM中;RW-data已初始化的数据会存储在Flash中,上电会从FLASH搬移至RAM中。
RO Size = Code + RO DataRW Size = RW Data + ZI DataROM Size = Code + RO Data + RW Data关于 ** p文件可以参考下面链接
keil 里面的 ** p文件分析根据上面的概念,所以默认从flash启动,然后将rw-data搬运到ram,然后CPU执行代码还是在flash一条一条读取,只不过RW数据是在RAM。所以程序就是在flash里执行,也就是上文提到的NOR Flash。