历史上的今天
返回首页

历史上的今天

今天是:2024年12月26日(星期四)

正在发生

2019年12月26日 | 关于ARM CM3的启动文件分析

2019-12-26 来源:eefocus

下面以ARM Cortex_M3裸核的启动代码为例,做一下简单的分析。首先,在启动文件中完成了三项工作:

1、堆栈以及堆的初始化

2、定位中断向量表

3、调用Reset Handler。


在介绍之前,我们先了解一下ARM芯片启动文件中涉及到的一些汇编指令的用法。

 

补充一下,其中DCD相当于C语言当中的&,定义地址。

 

1、堆栈以及堆的初始化

1.1 堆栈的初始化

Startup_xxx.s中的堆栈初始化代码

  Stack_Size  EQU  0x00000400,这个语句相当于Stack_Size这个标号(标号:链接器的术语,下文中提到的所有“标号”,指的都是指的链接器中的标号)等于0x00000400相当于C语言中的#define  Stack_Size  0x00000400 ,也就是说此语句只是一个声明,并未分配地址。


  AREA    STACK, NOINIT, READWRITE, ALIGN=3,此语句定义了一个叫STACK的代码段,并指明8字节对齐(ALIGN = 3)。其中NOINIT表示未初始化,READWRITE表示可读可写,ALIGN = 3,即表示2^3 = 8,八字节对齐。


  Stack_Mem    SPACE   Stack_Size,为Stack_Mem分配Stack_Size大小的一块内存区域,注意这里分配的是RAM,即分配了大小为1KB的内存空间(0x00000400 = 1024)。


  __initial_sp ,紧跟着栈分配内存后,所以其为栈顶(满递减栈)。此标号有一层隐含的意思就是在M3中堆栈是满递减堆栈,因为它指定了堆栈指针位于堆栈的高地址(在Stack_Mem之后),具体如下图所示。

堆栈指针sp位置

  上图来自Cortex_M3的一个工程的xxx.map文件。可以看出栈的起始地址为0x20000c68,大小为1024字节(即0x00000400 = Stack_Size)。而堆栈指针的位置在0x20001068,其等于栈的起始地址0x2000c68+0x00000400,说明本系列的Cortex_M3微控制器的堆栈为满递减堆栈。


  所以__initial_sp为1KB空间栈的栈顶,栈主要用于局部变量和形参的调用过程的临时存储,属于编译器自动分配和释放的内存,所以这里需要注意如果你的函数所占的内存过大,那么这个空间应调整其大小但一定要小于内部SRAM的大小。堆是程序员空间是程序员进行分配和释放的,如果程序中未释放最后由系统回收。

 

1.2 堆的初始化

 

Startup_xxx.s中的堆初始化代码

堆的初始化过程与堆栈的初始化相同。

 

2、中断向量表的初始化

 

中断向量表的初始化代码(部分)

PRESERVE8指定了以下的代码为8字节对齐,这是keil编译器的一个编程要求,对齐情况如下图所示:

xxx.list文件中的8字节对齐示意图

  THUMB指定了接下来的代码为THUMB指令集。

  AREA    RESET, DATA, READONLY,此语句声明RESET数据段。

  EXPORT  __Vectors,导出向量表标号,EXPORT作用类似于C语言中的extern。之后的代码就是为向量表分配存储区域。中断向量表从FLASH的0x00000000地址开始放置,以4个字节为一个单位,地址0存放的是栈顶指针(sp)的地址,0x00000004存放的是复位程序的地址,往后以此类推,这里我们只设置了一个Reset_Handler向量。从代码上看,向量表中存放的都是中断服务函数的函数名,可我们知道C语言中的函数名就是一个地址。(由此我们知道,中断函数的函数名都已经知道了,我们在写对应的中断服务程序时,从对应的地址取服务例程的入口地址并跳入执行)。但是此处有一个要注意的,就是0号地址不是什么入口地址,而是给出的复位后的MSP的初值。

 

3、调用Reset Handler

调用Reset Handler的代码

  此段代码只完成了一个功能,引导程序进入__main。__main的具体行为在后面做具体描述。


  PROC与ENDP组合在汇编中定义了一段子函数。


用户堆栈的初始化

 

具体的堆栈以及堆的初始化行为


  这一部分也就是把初始化的堆栈地址赋值给单片机的对应寄存器以方便C程序进行分配释放使用。

 

4、其他代码

有一些芯片厂商对芯片的加密的加密级别的代码也会放在这里,芯片上电后会自动读取这一地址的值以确定芯片的加密方式。

 

5、ARM芯片的启动过程详解

  接下来介绍__main函数的具体实现过程。

  首先在介绍__main函数之前,我们先了解一些关于ARM芯片在启动过程中的基本知识。

“ARM程序”是指在ARM系统中正在执行的程序,而非保存在ROM中的.bin(.axf,.hex)映像(image)文件。


一个ARM程序包含3部分:RO,RW和ZI

         RO 就是只读数据,是程序中指令和常量;

         RW是可读写的数据,程序中已初始化变量;

         ZI 是程序中未初始化的变量和初始化为0的变量。

简单理解就是:

         RO就是readonly,RW就是read/write,ZI就是zero initial。

 

ARM芯片的启动过程详解


注意,以上的过程并非绝对的,不同的ARM架构或者是不同的代码以上的执行过程是不同的。


复位处理程序是在汇编器中编写的短模块,系统一启动就立即执行。复位处理程序最少要为应用程序的运行模式初始化堆栈指针。对于具有本地内存系统(如缓存、TCM、MMU和MPU)的处理器,某些配置必须在初始化过程的这一阶段完成。复位处理程序在执行之后,通常跳到__main以开始C库初始化序列。


 __main中的__scatterload负责设置内存,而__rt_entry负责设置运行时的环境。__scatterload中负责把RO/RW(非零)输出段从装载域地址复制到运行域地址(执行代码和数据复制、解压缩),并完成ZI段运行域数据的0初始化工作。然后跳到__rt_entry设置堆栈和堆、初始化库函数和静态数据。然后,__rt_entry跳转到应用程序的入口main()。主应用程序结束执行后,__rt_entry将库关闭,然后把控制权交换给调试器。函数标签main()具有特殊含义。Main()函数的存在强制链接器链接到__main和__rt_entry中的代码。如果没有标记为main()的函数,则没有链接到初始化序列,因而部分标准C库功能得不到支持。

 

6、结合代码来看芯片启动过程

         上电后硬件设置sp、pc,刚上电复位后,硬件会自动根据向量表地址找到向量表。

 

在离开复位状态后, CM3 做的第一件事就是读取下列两个 32 位整数的值:


1、从地址 0x0000 0000 处取出 MSP 的初始值。

2、从地址 0x0000 0004 处取出 PC 的初始值,这个值是复位向量, LSB 必须是 1。 然后从这个值所对应的地址处取指。


硬件自动从0x0000 0000位置处读取数据赋给栈指针sp,然后从0x0000 0004位置处读取数据赋给pc指针,完成复位,结果为:

SP = 0x2000 1068

PC = 0x0000 011D

 

这与传统的 ARM 架构不同——其实也和绝大多数的其它单片机不同。传统的 ARM 架构总是从 0 地址开始执行第一条指令。它们的 0 地址处总是一条跳转指令。在 CM3 中,在 0 地址处提供 MSP 的初始值,然后紧跟着就是向量表。向量表中的数值是 32 位的地址,而不是跳转指令。向量表的第一个条目指向复位后应执行的第一条指令,就是我们上面分析的Reset_Handler这个函数。

进入__main

  LDR   R0, =__main

  BX   R0

 

执行上两条指令,跳转到__main程序段运行,__main的地址是0x0000 0080,上一步指令pc = 0x0000 011D的地址没有对齐,硬件自动对齐到0x0000 011C,执行__main。

 

pc指针通过立即数寻址,跳转到0x0000 0081处执行,同上这里也会自动对齐到0x0000 0080处。

 

在__scatterload函数中又会进入__scatterload_copy,在__scatterload_copy中进行代码搬运,主要是加载已经初始化的数据段和未初始化的数据段,同时还会初始化栈空间,即ZI段清零(其中搬运次数由代码中声明的变量类型和变量多少来决定)。

 

然后会跳转到__rt_entry函数执行,__rt_entry是使用ARM C库的程序的起点。将所有分散加载区重新定位到其执行地址后,会将控制权传递给__rt_entry。如下图,在__rt_entry中主要实现如下几个功能:

  1、  设置用户的堆和堆栈

  2、  调用__rt_lib_init以初始化C库

  3、  调用main()

  4、  调用__rt_lib_shutdown以关闭C库

  5、  退出

 

__rt_lib_init函数是库函数初始化函数,它与__rt_lib_shutdown配合使用。并且这个函数紧靠__rt_stackheap_init()后面调用,即紧跟堆和堆栈初始化后面调用,并且传递一个要用作堆的初始内存块。此函数是标准ARM库初始化函数,不能重新实现此函数。


注意:最后两步是在程序退出main()函数的时候才会执行,而我们嵌入式程序一般都是死循环,所以基本上不会执行这两个过程。还有以上过程是针对使用标准C Library而言的,不包括使用MDK提供的microlib库的情况。

  

在__rt_entry_main中,用户程序就开始正式执行了(进入C的世界)。在此之前初始化 MSP 是必需的,因为可能第 1 条指令还没来得及执行,就发生了 NMI 或是其它 fault。 MSP 初始化好后就已经为它们的服务例程准备好了堆栈。这也就是__main中做的事情。

 

7、最后关于microlib库

Microlib 是缺省C库的备选库。它旨在与需要装入到极少量内存中的深层嵌入式应用程序配合使用。这些应用程序不在操作系统中运行,因此microlib 进行了高度优化以使代码变得很小,当然它的功能相比缺省C库少,并且根本不具备某些ISO C特性。某些库函数的运行速度也比较慢,比如memcpy()。


Microlib与缺省C库之间的主要差异是:

  Microlib不符合ISO C 库标准。不支持,某些ISO特性,并且其他特性具有的功能也比较少;

  Microlib不符合IEEE754 二进制浮点算法标准;

  Microlib进行了高度优化以使代码变得很小;

  无法对区域设置进行配置。缺省C区域设置是唯一可用的区域设置;

  不能将main()声明为使用参数,并且不能返回内容;

  不支持stdio,但未缓冲的stdin、stdout和stderr除外;

  Microlib对C99函数提供有限的支持;

  Microlib不支持操作系统函数;

  Microlib不支持与位置无关的代码;

  Microlib不提供互斥锁来防止非线程安全的代码;

  Microlib不支持宽字符或多字节字符串;

  与stdlib不同,microlib不支持可选的单或双区内存模型。Microlib只提供双区内存模型,即单独的堆栈和堆区。

 

8、关于生成的xxx.map文件

想要更好的了解启动代码的运行机制,我们就有必要了解一下由Keil的链接器“armlink”生成的描述文件,即xxx.map文件。

 

目标文件的组成

上图即是armlink的链接器为测试代码生成的xxx.map文件中的一部分,其描述了镜像文件的组成信息,其中可以明显看到其由两部分构成:


User Code生成的目标文件

C Library生成的目标文件


可见我们在上文中所描述的启动过程中看到的__main、__rt_entry、__scartterload以及__rt_lib_init等,就是C library中的代码。


所以,我们每次烧录的可执行的ARM的bin文件中不仅有开发者编写的代码,还有C Library的代码。

上图为存放在RAM中的RW段。

 

以上就是CM3芯片的基本启动过程

推荐阅读

史海拾趣

Global Navigation Systems公司的发展小趣事
是的,许多现代远程无线防盗报警系统都支持多防区同时报警功能。这意味着系统可以同时监控多个区域或设备,并在任何一个区域或设备发生异常情况时触发报警。这种功能可以大大提高系统的安全性和可靠性,为用户提供更加全面的保护。
辰颐电子公司的发展小趣事

辰颐电子公司成立于XXXX年,由一群热衷于电子技术的年轻人共同创立。他们看到了电子行业巨大的市场潜力和技术革新的重要性,决定投身于这一领域。初创时期,公司面临着资金短缺、技术瓶颈和市场竞争等多重挑战。然而,他们凭借对技术的执着追求和对市场的敏锐洞察,成功研发出了一款具有创新性的电子产品,并迅速在市场上打开了局面。

Aydin Corp公司的发展小趣事

在国内市场取得一定成绩后,Aydin Corp开始积极拓展国际市场。通过与全球知名企业的合作,公司成功将产品打入多个国家和地区的市场。同时,Aydin Corp还积极参加国际电子展会和交流活动,与业界同行建立了广泛的合作关系。这些举措不仅提升了公司的国际知名度,也为公司的持续发展注入了新的动力。

Cadeka公司的发展小趣事

Cadeka公司始终将品质和创新作为企业发展的核心。公司建立了严格的质量管理体系,确保每一件产品都符合高标准的质量要求。同时,公司还不断投入研发资金,推动技术创新和产品升级。通过品质和创新并重的发展战略,Cadeka公司赢得了客户的信赖和市场的认可。

Herth+Buss Fahrzeugteile GmbH & Co KG公司的发展小趣事
电路结构简单,易于安装和维护。
Brand-Rex公司的发展小趣事

Brand-Rex的综合布线产品在国家重大项目上得到了广泛应用。例如,昌北国际机场作为中国干线机场,其总建筑面积达到96616平方米。该项目于2010年9月中标,2011年5月22日竣工并投入使用。整个系统均采用了Brand-Rex的CAT6PLUS全系列铜缆和光纤产品,充分展示了Brand-Rex在大型项目中的技术实力和产品可靠性。此外,Brand-Rex还参与了其他多个重要项目的布线工作,为国家基础设施的建设做出了积极贡献。

问答坊 | AI 解惑

手机电视渐行渐近,我们该选谁?

本帖最后由 jameswangsynnex 于 2015-3-3 20:01 编辑 最近的手机电视标准之争,似乎搞得人有些疲惫。 究竟消费者想要尝个鲜,该如何做出权衡?  “我国正就广播方式手机电视制定国家标准,还没有最后确定。现在已有一个行业标准,是由广电 ...…

查看全部问答>

学历、性别、经验对电子行业薪酬的影响

学历与薪酬收入   根据2007年度中国电子行业薪酬和职业发展调查。   调查显示,中国电子工程师的薪酬奖金收入与学历成正比例关系。拥有博士学位的电子工程师的平均年薪酬最高约为人民币131396元(月薪人民币10950元),平均年奖金收入为人民币 ...…

查看全部问答>

无线电爱好者

关与无线电通信…

查看全部问答>

有谁用过proteus仿真KS0108的呢?

为什么我读LCD状态老是忙呢?一直是黑屏,有谁仿真过,遇到过这种问题吗?怎么解决的?…

查看全部问答>

50分求.net如何将csv文件直接读入dataSet(ce下没有odbc),实现立刻结贴

wince下没有odbc包,以下代码无法运行     Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load         Dim strConn As String    & ...…

查看全部问答>

无线功率放大

加强功率型 +20dBm 加强功率型,发射功率最高+20dBm,室内通信60-150米,室外通信200-400米 可以很方便的和你的单片机进行接口,注意此模块工作电压为1.8-3.6V,如果你使用的单片机为5v系统,请考虑V系统和3V系统的电平兼容问题! 业界体积最小 ...…

查看全部问答>

急!!!!!!!!能提供一款超声波的收发的芯片或是电路

急!!!!!!!!能提供一款超声波的收发的芯片或是电路…

查看全部问答>

关于keil的问题求助前辈

我在用KEIL编程的时候,发现mian.c函数的前面有三个点, 后面查资料说是没有参与编译。 如图 下载 (9.33 KB) 2010-10-1 20:44 请问下KEIL是在哪里设置函数是否参与编译? 谢谢。…

查看全部问答>

Stellaris M3 选型表 ,含芯片系列(Tempest,Sandstorm..)

附件是ti stellaris m3 芯片的选型表,里面有各种芯片是对应哪个系列的对应关系。很多客户可能不知道一个型号究竟是那个系列的芯片,用这个选型表最容易查出来。…

查看全部问答>

Q:如何根据输出电压来改变PWM占空比

检测输出电压来判断是增加还是减小PWM的占空比(控制开关管),请问这个增加多少和减少多少,应该怎么处理呢,能否直接加1或者减1 啊。。。。…

查看全部问答>