历史上的今天
返回首页

历史上的今天

今天是:2024年09月28日(星期六)

2019年09月28日 | 再造STM32---第十二部分:启动文件详解

2019-09-28 来源:eefocus

       本章参考资料《STM32F4xx 中文参考手册》第十章-中断和事件:表 46.STM32F42xxx 和 STM32F43xxx 的向量表; MDK 中的帮助手册—ARM Development Tools:用来查询 ARM 的汇编指令和编译器相关的指令。


12.1 启动文件简介:

启动文件由汇编编写,是系统上电复位后第一个执行的程序。主要做了以下工作:

              1、 初始化堆栈指针 SP=_initial_sp

              2、 初始化 PC 指针=Reset_Handler

              3、 初始化中断向量表

              4、 配置系统时钟

              5、 调用 C 库函数_main 初始化用户堆栈,从而最终调用 main 函数去到 C 的世界


12.2 查找 ARM 汇编指令:

       在讲解启动代码的时候,会涉及到 ARM 的汇编指令和 Cortex 内核的指令,有关Cortex 内核的指令我们可以参考《CM3 权威指南 CnR2》第四章:指令集。剩下的 ARM 的汇编指令我们可以在 MDK->Help->Uvision Help 中搜索到,以 EQU 为例,检索如下:

       检索出来的结果会有很多,我们只需要看 Assembler User Guide 这部分即可。下面列出了启动文件中使用到的 ARM 汇编指令,该列表的指令全部从 ARM Development Tools这个帮助文档里面检索而来。其中编译器相关的指令 WEAK 和 ALIGN 为了方便也放在同一个表格了。

表格 10 启动文件使用的 ARM 汇编指令汇总

image.png?imageView2/2/w/550

12.3 启动文件代码讲解:

1. Stack—栈:


Stack_Size EQU 0x00000400

AREA STACK, NOINIT, READWRITE, ALIGN=3

Stack_Mem SPACE Stack_Size

__initial_sp

       开辟栈的大小为 0X00000400(1KB),名字为 STACK, NOINIT 即不初始化,可读可写, 8(2^3)字节对齐。

       栈的作用是用于局部变量,函数调用,函数形参等的开销,栈的大小不能超过内部SRAM 的大小。如果编写的程序比较大,定义的局部变量很多,那么就需要修改栈的大小。如果某一天,你写的程序出现了莫名奇怪的错误,并进入了硬 fault 的时候,这时你就要考虑下是不是栈不够大,溢出了。

       EQU:宏定义的伪指令,相当于等于,类似与 C 中的 define。

       AREA:告诉汇编器汇编一个新的代码段或者数据段。 STACK 表示段名,这个可以任意命名; NOINIT 表示不初始化;READWRITE 表示可读可写, ALIGN=3,表示按照 2^3对齐,即 8 字节对齐。

       SPACE:用于分配一定大小的内存空间,单位为字节。这里指定大小等于 Stack_Size。

       标号__initial_sp 紧挨着 SPACE 语句放置,表示栈的结束地址,即栈顶地址,栈是由高向低生长的。

2. Heap 堆:


Heap_Size EQU 0x00000200

AREA HEAP, NOINIT, READWRITE, ALIGN=3

__heap_base

Heap_Mem SPACE Heap_Size

__heap_limit

       开辟堆的大小为 0X00000200(512 字节),名字为 HEAP, NOINIT 即不初始化,可读可写, 8(2^3)字节对齐。 __heap_base 表示对的起始地址, __heap_limit 表示堆的结束地址。堆是由低向高生长的,跟栈的生长方向相反。

       堆主要用来动态内存的分配,像 malloc()函数申请的内存就在堆上面。这个在 STM32里面用的比较少。


PRESERVE8

THUMB

       PRESERVE8: 指定当前文件的堆栈按照 8 字节对齐。

       THUMB: 表示后面指令兼容 THUMB 指令。 THUBM 是 ARM 以前的指令集, 16bit,现在 Cortex-M 系列的都使用 THUMB-2 指令集, THUMB-2 是 32 位的,兼容 16 位和 32 位的指令,是 THUMB 的超级。

3. 向量表:


AREA RESET, DATA, READONLY

EXPORT __Vectors

EXPORT __Vectors_End

EXPORT __Vectors_Size

       定义一个数据段,名字为 RESET,可读。并声明 __Vectors、 __Vectors_End 和Vectors_Size 这三个标号具有全局属性,可供外部的文件调用。


       EXPORT: 声明一个标号可被外部的文件使用,使标号具有全局属性。如果是 IAR 编译器,则使用的是 GLOBAL 这个指令。


       当内核响应了一个发生的异常后,对应的异常服务例程(ESR)就会执行。为了决定 ESR的入口地址, 内核使用了―向量表查表机制‖。这里使用一张向量表。向量表其实是一个WORD(32 位整数)数组,每个下标对应一种异常,该下标元素的值则是该 ESR 的入口地址。向量表在地址空间中的位置是可以设置的,通过 NVIC 中的一个重定位寄存器来指出向量表的地址。在复位后,该寄存器的值为 0。因此,在地址 0 (即 FLASH 地址 0) 处必须包含一张向量表,用于初始时的异常分配。要注意的是这里有个另类: 0 号类型并不是什么入口地址,而是给出了复位后 MSP 的初值。

表格 11 F429 向量表

image.png?imageView2/2/w/550

image.png?imageView2/2/w/550

代码 12 向量表

 


__Vectors DCD __initial_sp ;栈顶地址

DCD Reset_Handler ;复位程序地址

DCD NMI_Handler

DCD HardFault_Handler

DCD MemManage_Handler

DCD BusFault_Handler

DCD UsageFault_Handler

DCD 0 ; 0 表示保留

DCD 0

DCD 0

DCD 0

DCD SVC_Handler

DCD DebugMon_Handler

DCD 0

DCD PendSV_Handler

DCD SysTick_Handler

;外部中断开始

DCD WWDG_IRQHandler

DCD PVD_IRQHandler

DCD TAMP_STAMP_IRQHandler

;限于篇幅,中间代码省略

DCD LTDC_IRQHandler

DCD LTDC_ER_IRQHandler

DCD DMA2D_IRQHandler

__Vectors_End

__Vectors_Size EQU __Vectors_End - __Vectors

       __Vectors 为向量表起始地址, __Vectors_End 为向量表结束地址,两个相减即可算出向量表大小。

       向量表从 FLASH 的 0 地址开始放置,以 4 个字节为一个单位,地址 0 存放的是栈顶地址, 0X04 存放的是复位程序的地址,以此类推。从代码上看,向量表中存放的都是中断服务函数的函数名,可我们知道 C 语言中的函数名就是一个地址。

       DCD:分配一个或者多个以字为单位的内存,以四字节对齐,并要求初始化这些内存。在向量表中, DCD 分配了一堆内存,并且以 ESR 的入口地址初始化它们。

4. 复位程序:


AREA |.text|, CODE, READONLY

       定义一个名称为.text 的代码段,可读。


Reset_Handler PROC

EXPORT Reset_Handler [WEAK]

IMPORT SystemInit

IMPORT __main

LDR R0, =SystemInit

BLX R0

LDR R0, =__main

BX R0

ENDP

       复位子程序是系统上电后第一个执行的程序,调用 SystemInit 函数初始化系统时钟,然后调用 C 库函数_mian,最终调用 main 函数去到 C 的世界。

       WEAK:表示弱定义,如果外部文件优先定义了该标号则首先引用该标号,如果外部文件没有声明也不会出错。这里表示复位子程序可以由用户在其他文件重新实现,这里并不是唯一的。

       IMPORT:表示该标号来自外部文件,跟 C 语言中的 EXTERN 关键字类似。这里表示 SystemInit 和__main 这两个函数均来自外部的文件。

       SystemInit()是一个标准的库函数,在 system_stm32f4xx.c 这个库文件总定义。主要作用是配置系统时钟,这里调用这个函数之后, F429 的系统时钟配被配置为 180M。

       __main 是一个标准的 C 库函数,主要作用是初始化用户堆栈,最终调用 main 函数去到 C 的世界。这就是为什么我们写的程序都有一个 main 函数的原因。如果我们在这里不调用__main,那么程序最终就不会调用我们 C 文件里面的 main,如果是调皮的用户就可以修改主函数的名称,然后在这里面 IMPORT 你写的主函数名称即可。


Reset_Handler PROC

EXPORT Reset_Handler [WEAK]

IMPORT SystemInit

IMPORT user_main

LDR R0, =SystemInit

BLX R0

LDR R0, =user_main

BX R0

ENDP

       这个时候你在 C 文件里面写的主函数名称就不是 main 了,而是 user_main 了。

       LDR、 BLX、 BX 是 CM4 内核的指令,可在《CM3 权威指南 CnR2》第四章-指令集里面查询到,具体作用见下表:

image.png?imageView2/2/w/550

5. 中断服务程序:


       在启动文件里面已经帮我们写好所有中断的中断服务函数,跟我们平时写的中断服务函数不一样的就是这些函数都是空的,真正的中断复服务程序需要我们在外部的 C 文件里面重新实现,这里只是提前占了一个位置而已。

       如果我们在使用某个外设的时候,开启了某个中断,但是又忘记编写配套的中断服务程序或者函数名写错,那当中断来临的时,程序就会跳转到启动文件预先写好的空的中断服务程序中,并且在这个空函数中无线循环,即程序就死在这里。


NMI_Handler PROC ;系统异常

EXPORT NMI_Handler [WEAK]

B .

ENDP

;限于篇幅,中间代码省略

SysTick_Handler PROC

EXPORT SysTick_Handler [WEAK]

B .

ENDP

Default_Handler PROC ;外部中断

EXPORT WWDG_IRQHandler [WEAK]

EXPORT PVD_IRQHandler [WEAK]

EXPORT TAMP_STAMP_IRQHandler [WEAK]

;限于篇幅,中间代码省略

LTDC_IRQHandler

LTDC_ER_IRQHandler

DMA2D_IRQHandler

B .

ENDP

B:跳转到一个标号。这里跳转到一个‘.’,即表示无线循环。

6. 用户堆栈初始化:


ALIGN

       ALIGN:对指令或者数据存放的地址进行对齐,后面会跟一个立即数。缺省表示 4 字节对齐。


;用户栈和堆初始化

IF :DEF:__MICROLIB

EXPORT __initial_sp

EXPORT __heap_base

EXPORT __heap_limit

ELSE

IMPORT __use_two_region_memory

EXPORT __user_initial_stackheap

__user_initial_stackheap

LDR R0, = Heap_Mem

LDR R1, =(Stack_Mem + Stack_Size)

LDR R2, = (Heap_Mem + Heap_Size)

LDR R3, = Stack_Mem

BX LR

ALIGN

ENDIF

END

       判断是否定义了__MICROLIB ,如果定义了则赋予标号__initial_sp(栈顶地址)、__heap_base(堆起始地址)、 __heap_limit(堆结束地址)全局属性,可供外部文件调用。如果没有定义(实际的情况就是我们没定义__MICROLIB)则使用默认的 C 库,然后初始化用户堆栈大小,这部分有 C 库函数__main 来完成,当初始化完堆栈之后,就调用 main函数去到 C 的世界。

       IF,ELSE,ENDIF:汇编的条件分支语句,跟 C 语言的 if ,else 类似

       END:文件结束


12.4 系统启动流程:

       下面这段话引用自《CM3 权威指南 CnR2》 3.8—复位序列, CM4 的复位序列跟 CM3 一样。 

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

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

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


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

       因为 CM3 使用的是向下生长的满栈,所以 MSP 的初始值必须是堆栈内存的末地址加1。举例 来说,如果我们的堆栈区域在 0x20007C00-0x20007FFF 之间,那么 MSP 的初始值就必须是 0x20008000。

       向量表跟随在 MSP 的初始值之后——也就是第 2 个表目。要注意因为 CM3 是在Thumb 态下执行,所以向量表中的每个数值都必须把 LSB 置 1(也就是奇数)。正是因为这个原因, 图 12-3 中使用 0x101 来表达地址 0x100。当 0x100 处的指令得到执行后,就正式开始了程序的执行(即去到 C 的世界) 。在此之前初始化 MSP 是必需的,因为可能第 1条指令还没来得及执行,就发生了 NMI 或是其它 fault。 MSP 初始化好后就已经为它们的服务例程准备好了堆栈。

       现在,程序就进入了我们熟悉的 C 世界,现在我们也应该明白 main 并不是系统执行的第一个程序了。


12.5 总结:

1、启动文件的讲解 — 开始

1-注释的讲解

2-程序的讲解

3-如何查找资料( ARM的汇编指令)


2、启动文件的作用


1-初始化堆栈指针SP

2-初始化PC指针,指向复位程序

3-初始化中断向量表

4-配置系统时钟

5-调用C库函数_main,最终进入C的世界

3、汇编程序如何注释


1-汇编注释用“;”

2-C语言注释用“//”戒者“/**/”

4、启动文件详解


① -Stack—栈

用于局部变量、函数调用、函数形参的开销

EQU: 宏定义的伪指令,相当于等于,类似与 C 中的 define

AREA:告诉汇编器汇编一个新的代码段戒者数据段

SPACE : 用于分配一定大小的内存空间,单位为字节

标号__initial_sp 紧挨着 SPACE 语句放置,表示栈的结束地址,即栈顶地址,栈是由高向低生长的。


② -Heap—堆

堆用于动态内存的分配, malloc函数

PRESERVE8: 指定当前文件的堆栈按照 8 字节对齐

THUMB: 表示后面指令为 THUMB 指令。 THUBM 是ARM 以前的指令集, 16bit,现在 Cortex-M 系列的都使用THUMB-2 指令集, THUMB-2 是 32 位的,兼容 16 位和 32位的指令,是 THUMB 的超级。

EXPORT: 声明一个标号具有全局属性,可被外部的文件使用。 如果是 IAR 编译器,则使用的是 GLOBAL 这个指令。

DCD: 分配一个戒者多个以字为单位的内存,以四字节对齐,并要求初始化这些内存。在向量表中, DCD 分配了一堆内存,并且以 ESR 的入口地址初始化它们。

③ -向量表

1-向量表实际上是一个32位的整型数组,一个元素对应一个异常( ESR),数组元素存的就是ESR的入口地址。

2-向量表在复位后从FLASH的0地址开始,具体的初始化值请查询参考手册的中断章节。

从代码上看,向量表中存放的都是中断服务函数的函数名,可我们知道 C语言中的函数名就是一个地址。

④ -复位程序

1-复位程序是上电后单片机执行的第一个程序

2-调用SystemInit函数配置系统时钟;调用C库函数_main,并最终进入C的世界

WEAK: 表示弱定义,如果外部文件优先定义了该标号则首先引用该标号,如果外部文件没有声明也不会出错。这里表示复位子程序可以由用户在其他文件重新实现,这里并不是唯一的。

IMPORT: 表示该标号来自外部文件,跟 C 语言中的EXTERN 关键字类似。这里表示 SystemInit 和__main 这两个函数均来自外部的文件。

Cortex内核的指令

⑤ -中断服务程序

1-启动文件为我们写好了全部的中断服务程序,函数的名称必须与向量表里面初始化的名称一样。

2-这些程序都是空的,需要我们在C文件里面重新实现。如果我们写的中断服务程序的函数名写错了,程序也不会报错,而是会进入一个死循环。

⑥ -用户堆栈初始化

由标准的C库函数_main来完成。

IF,ELSE,ENDIF: 汇编的条件分支语句,跟 C 语言的if ,else 类似。

END: 文件结束。

ALIGN: 对指令戒者数据存放的地址进行对齐,后面会跟一个立即数。缺省表示 4 字节对齐。


启动文件里面涉及到的ARM指令

推荐阅读

史海拾趣

Eska公司的发展小趣事

Eska公司注重技术传承与创新发展。作为奥地利唯一一家仍在为手套制造业培训学徒和技术工人的企业,Eska坚持手套传统工艺和技术的秉承。同时,公司也积极引入新技术和新材料,不断提升产品的性能和质量。这种技术传承与创新相结合的发展模式,使得Eska在保持传统优势的同时,也能不断适应市场的变化和发展。

Hi-G Relays公司的发展小趣事

随着全球对环保意识的提高,Eska公司积极响应并融入环保理念。公司采用再生纸作为原材料,制造灰板纸,有效减少了资源浪费和环境污染。同时,Eska的造纸过程也注重节能减排,每生产一顿纸板所需的水量非常少。此外,公司还拥有自己的加热和发电站,所产出的电力和蒸汽供应自给自足,进一步减少了对环境的破坏。

CUI Devices公司的发展小趣事

CUI Devices与富昌电子之间的合作关系可以追溯到CUI Inc的时代。多年来,双方建立了深厚的合作基础。在CUI Devices从CUI Inc剥离后,富昌电子继续支持其发展,成为其重要的合作伙伴之一。双方共同致力于推动电子元件行业的发展,为客户提供更优质的产品和服务。这种稳固的合作关系为CUI Devices在市场竞争中提供了有力的支持。

这五个故事展示了CUI Devices在电子行业中的发展历程和取得的成就。从起源与转型到应对全球健康危机的积极举措,再到在压电警报器市场的卓越表现以及电机品牌的崛起与拓展,每一个故事都见证了公司的成长和进步。同时,与富昌电子的稳固合作也为公司的未来发展奠定了坚实的基础。

Hirose公司的发展小趣事

CUI Devices与富昌电子之间的合作关系可以追溯到CUI Inc的时代。多年来,双方建立了深厚的合作基础。在CUI Devices从CUI Inc剥离后,富昌电子继续支持其发展,成为其重要的合作伙伴之一。双方共同致力于推动电子元件行业的发展,为客户提供更优质的产品和服务。这种稳固的合作关系为CUI Devices在市场竞争中提供了有力的支持。

这五个故事展示了CUI Devices在电子行业中的发展历程和取得的成就。从起源与转型到应对全球健康危机的积极举措,再到在压电警报器市场的卓越表现以及电机品牌的崛起与拓展,每一个故事都见证了公司的成长和进步。同时,与富昌电子的稳固合作也为公司的未来发展奠定了坚实的基础。

辰颐电子公司的发展小趣事

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

Amphenol Thermometrics公司的发展小趣事

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

问答坊 | AI 解惑

【Jack——PIC 单片机操作系统的优点与试用】

在单片机的开发中,软件的过程式开发(跟自顶向下的过程式语言有直接关系)是长久以来的软件开发方法,但不能否认的是,存在数十种为人所知和不知的 RTOS,甚至是开发人员自己动手构造一个简单的 RTOS 以提高工作效率和改善软件构建的复杂性,这也 ...…

查看全部问答>

【藏书阁】基本电路理论实验指导

目录: 第一章:绪论 第二章:电路实验 第三章:电路的计算机辅助分析 第四章:电工测量与常用电工仪表的基本知识 第五章:常用仪器设备使用的基本知识 附录一:电路分析参考程序 附录二:计算机上机操作说明 详细信息: 书名:基本电路理 ...…

查看全部问答>

quartus2波形仿真问题

用quartus2进行波形仿真时,打不开仿真后的波形文件,是什么原因? 程序编译综合都没问题,加载网表也没出错。仿真提示也是成功的,但是在information里可以看到说波形文件里有错导致打不开仿真后的波形图。波形文件怎么会有错呢?…

查看全部问答>

关于Verilog的output,应该是reg型,还是wire型?

module d_ff (         clk,         rst_n,         datain,         dataout     );     ...…

查看全部问答>

CAB安装包修改注册表问题

各位大虾,       小弟需要一个将一个.dll文件打包安装到设备,同时修改注册表.我的.inf文件已经写好了,能正确生成.cab文件, 现在就是不知道怎么才能修改注册表,那位兄弟能指点以下?谢了。…

查看全部问答>

请问ARM9和ARM7比起来有那些缺点

我听说ARM9的功耗大,这是真的吗…

查看全部问答>

www.kingofcoder.com 100MB 免費空間 + 100MB mysql 空間

www.kingofcoder.com 100MB 免費空間 + 100MB mysql 空間 大家快點來呀 JSP, PHP, oracle空間, 很快就會開通, 大家快點登記吧 http://www.kingofcoder.com匯集大量各種編程語言文章、提供免費asp、php、jsp空間、免費mysql、oracle數據庫空間 ...…

查看全部问答>

MSK4203 PWM 电机控制器

MSK 4203PWM电机控制器/放大器 MSK 4203PWM电机控制器/放大器的特点: ★75V,10A的输出能力 ★自带高、低边驱动电路 ★内含死区保护和短路保护 ★与内部电路绝缘的外壳可直接安装散热器 ★四象限工作模式,力矩控制 ★符合MIL-PRF-38534的H级产品 ...…

查看全部问答>

TI LM3S M3的供电设计概述

TI LM3S M3的供电共有4组:3.3V数字,用于I/O供电和内置LDO供电,一般标号为VDD,需要外置供电提供;1.8V,一般标号为VDDC,为内核和逻辑供电,一般由内置LDO提供,直接连接到LDO管脚。查看器件的Errata 表,有的M3的LDO有问题,不能稳定供电 ...…

查看全部问答>