历史上的今天
返回首页

历史上的今天

今天是:2026年01月06日(星期二)

正在发生

2023年01月06日 | 详解STM32启动文件

2023-01-06 来源:zhihu

本文对STM32启动文件startup_stm32f10x_hd.s的代码进行讲解,此文件的代码在任何一个STM32F10x工程中都可以找到。可以点击文末阅读原文直接下载此文件,提取码stm3。


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


Stack——栈

Stack_Size EQU 0x00000400

AREA STACK, NOINIT, READWRITE, ALIGN=

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 语句放置,表示栈的结束地址,即栈顶地址,栈是由高向低生长的。

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 的超集。

向量表

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 的初值。下图是F103的向量表。


__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 TAMPER_IRQHandler

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

DCD DMA2_Channel2_IRQHandler

DCD DMA2_Channel3_IRQHandler

DCD DMA2_Channel4_5_IRQHandler

__Vectors_End

__Vectors_Size EQU __Vectors_End - __Vectors

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

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

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

复位程序

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_stm32f10x.c 这个库文件中定义。主要作用是配置系统时钟,这里调用这个函数之后,单片机的系统时钟配被配置为 72M。__main 是一个标准的 C 库函数,主要作用是初始化用户堆栈,并在函数的最后调用main 函数去到 C 的世界。这就是为什么我们写的程序都有一个 main 函数的原因。

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


中断服务程序

在启动文件里面已经帮我们写好所有中断的中断服务函数,跟我们平时写的中断服务函数不一样的就是这些函数都是空的,真正的中断服务程序需要我们在外部的 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:跳转到一个标号。这里跳转到一个‘.’,即表示无线循环

用户堆栈初始化

ALIGN

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

;用户栈和堆初始化,由 C 库函数_main 来完成

IF :DEF:__MICROLIB ;这个宏在 KEIL 里面开启

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(堆结束地址)全局属性,可供外部文件调用。有关这个宏我们在 KEIL 里面配置,具体见图 15-2。然后堆栈的初始化就由 C 库函数_main 来完成。


如果没有定义__MICROLIB,则才用双段存储器模式,且声明标号__user_initial_stackheap 具有全局属性,让用户自己来初始化堆栈。

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

END:文件结束


推荐阅读

史海拾趣

DMS Electronic Components, Inc公司的发展小趣事

DMS非常重视人才的培养和引进。公司建立了完善的人才选拔和激励机制,吸引了一批高素质的专业人才加入。这些人才在技术研发、市场营销、生产管理等方面发挥了重要作用,为公司的快速发展提供了有力保障。同时,DMS还注重员工的培训和发展,为员工提供了广阔的职业发展空间。

EKIT公司的发展小趣事

2023年10月,华为坤灵(HUAWEI eKit)在香港成功举办了分销新品发布会。针对香港市场的特点,HUAWEI eKit展示了面向SOHO办公、酒店餐饮、商业地产、零售商超等场景的新品,并通过整合政策、产品、解决方案、服务和数字工具平台等措施,助力香港区域分销伙伴不断开拓中小企业市场。通过与联强国际(香港)有限公司(SYNNEX)的紧密合作,HUAWEI eKit成功吸引了超过100名香港分销商参与此次发布会,进一步巩固了其在香港市场的地位。

DURATOOL公司的发展小趣事

在电子行业,产品质量是企业生存和发展的基石。DURATOOL公司深知这一点,因此一直将品质管理作为企业发展的核心。公司建立了严格的质量管理体系,从原材料采购到产品生产、检验、包装等各个环节都进行严格把关。此外,DURATOOL公司还积极引入国际先进的质量管理理念和方法,不断提升产品质量水平。这些努力使得DURATOOL公司的产品在市场上享有良好的口碑和信誉,赢得了广大用户的信任和支持。

DURABLE公司的发展小趣事

在电子材料领域取得初步成功后,DURABLE公司意识到市场拓展的重要性。公司开始积极拓展海外市场,与全球知名电子产品制造商建立合作关系。通过深入了解不同地区的市场需求和消费者偏好,DURABLE不断调整产品策略,推出符合当地市场需求的产品。同时,公司还加强了与渠道合作伙伴的合作,通过完善的销售网络和售后服务体系,提升了产品的市场占有率和客户满意度。

Boundary Devices公司的发展小趣事

近年来,电子行业面临着诸多变革和挑战,如物联网的快速发展、人工智能的崛起等。面对这些变革,Boundary Devices积极调整战略,加强在物联网和人工智能领域的研发投入,推出了一系列符合市场趋势的新产品。同时,公司还加强与高校和研究机构的合作,共同推动电子行业的创新与发展。

Aeroflex Metelics / Hi-Rel Components公司的发展小趣事

为了进一步扩大市场份额,Boundary Devices积极实施国际化战略。公司通过与全球各地的合作伙伴建立合作关系,将产品推向国际市场。同时,公司还积极参加国际电子展会和技术交流活动,与全球同行进行深入的交流与合作,不断提升公司的国际影响力。

问答坊 | AI 解惑

Lattice设计软件中文教程集

全面详细的lattice开发软件ispLEVER的开发教程,既适合入门者应用,也适合高级应用. 包括软件开发流程介绍(入门级包括原理图设计输入),MAP,PAR属性参数设计详细介绍,适合高级应用者.…

查看全部问答>

宽带放大器 很经典

本帖最后由 paulhyde 于 2014-9-15 03:28 编辑 威国赛准备的  …

查看全部问答>

热电偶相关知识

两种不同成份的导体(称为热电偶丝材或热电极)两端接合成回路,当接合点的温度不同时,在回路中就会产生电动势,这种现象称为热电效应,而这种电动势称为热电势。热电偶就是利用这种原理进行温度测量的,其中,直接用作测量介质温度的一端叫做工作 ...…

查看全部问答>

红外摄像应用领域大幅拓宽

红外摄像机是由摄像机、防护罩、红外灯、供电散热单元等综合为一体的摄像设备,主要是通过发出红外线经物体反射回来后采集成像。红外线是一种光波,其波长在780nm~1000μm之间,位于无线电波与可见光之间,市场上的红外摄像机所采用的红外光源波长 ...…

查看全部问答>

电池充电时间计算

我有一个1A/7.2V的锂电池,用12V的电压串一个200欧姆的电阻给其充电,请问这个电池须要多长时间才能充满。…

查看全部问答>

pxa270 在wince5.0下的应用程序直接访问物理地址的问题

小弟想在wince5.0下直接用应用程序访问物理地址,直接用VirtualAlloc函数分配800 0000对应的虚拟地址,同时也在应用程序下把gpio78配置成了ncs2,但是不管怎样,总是读不到数据,且连ncs2都打不到波形啊!不知道有没有谁做过类似的改装,给点建议吧 ...…

查看全部问答>

想买个ARM板,请大家帮忙

想买个ARM9的板子用来做嵌入式ARM-linux学习,但是不知道哪个厂家的好。 QQ2440,mini2440,还有扬创、优龙的,这些质量都差不多吗? 价格如何?…

查看全部问答>

请问如何在windows mobile 6中清除ms 蓝牙协议栈

各位:    我在开发windows mobiled手机项目的时候,需要用到第三方的蓝牙协议栈。但必须先清除微软自带的蓝牙协议栈。在windows mobile6的帮助文档中,说得很清楚,修改 Platfom/FILES/ oem.cpm.csv文件,添加: CE_MODULES_BTD,IGNOR ...…

查看全部问答>

用GPRS模块如何实现PING的功能!

我有一GPRS模块,请问如何实现ICMP协议的PING功能。…

查看全部问答>

收到上海ibm issc 的offer,正在考虑。谁了解这个公司阿?

收到上海ibm issc 的offer,正在考虑。谁了解这个公司阿? 发展空间 待遇等等。…

查看全部问答>