[AT32F403/403A] 以startup_at32f403a_407.s为例详述启动文件,收获不小!

[复制链接]
22|0
dffzh 发表于 2026-2-6 14:31 | 显示全部楼层 |阅读模式
本帖最后由 dffzh 于 2026-2-6 14:50 编辑

#申请原创#

临近放假,偷得半日闲,便来研究一下MCU的启动文件,加上一直在用雅特力MCU芯片AT32F403A,于是乎就以该芯片的启动文件startup_at32f403a_407.s为例与大家分享一下所学内容,供大家指正和参考,其他的启动文件一般大同小异:
3812769858ae28d2d8.png
启动文件的后缀一般是.s格式,即表示汇编语言(assembly language)所写的代码文件,按其命名解释,其作用无非就是“启动”的意思,启动什么呢?当然是启动MCU,即每次MCU芯片上电运行之前第一个需要执行的代码,主要负责初始化MCU硬件,并引导至main主函数,归根结底的说,其核心作用主要包括:
初始化内存区域;
初始化中断向量表;
设置系统时钟;
调用初始化函数;
提供默认中断处理等等。
接下来就逐个部分向大家阐述,并将代码和作用对号入座,一起来学习吧!
1、栈(stack)空间的大小及配置
7140969858b0a83a89.png
该部分的代码主要负责初始化配置栈空间默认值,汇编指令EQU其实就是等于的意思,即默认值为0x00000400字节,即1024字节,即1KB。当然,你可以根据自身需求调整这个值,但首先你得清楚栈上主要存储哪些内容?主要包括:局部变量(函数内部声明的非static的变量)、函数部分参数、返回地址等等。
有时候一些比较诡异的让很多攻城狮头疼的程序bug,比如设备运行一定时间后死机,HardFault  Exception异常等等,其根本原因也许就是栈溢出,即分配的栈空间太少了,因为有时候随着程序运行时间的加长,其所用的栈空间会越来越多:
4067969858b29c4caa.png
说到底,栈的本质是一种后进先出(LIFO)的数据结构。
2、堆(heap)空间的大小及配置
1447169858b5c4491f.png
和栈空间类似,该部分的代码主要负责初始化配置堆空间默认值,即默认值为0x00000200字节,即512字节。当然,你可以根据自身需求调整这个值。堆也是一个重要的内存区域,但堆与栈的最大区别就是:堆需要程序员自己主动管理生命周期。
堆的本质是一块由程序员在程序运行时动态管理的内存池,一般会通过malloc(),calloc(), realloc() 等函数动态申请内存,并通过free()函数释放内存。
至于堆和栈的详细应用,可以参考网上资料学习。
1和2的配置作用其实就是对应的初始化内存区域的一部分。
3、中断向量表地址映射
4649669858b7e685d7.png
这部分应该算是我们的应用程序里会经常用到的,其实主要是列出了所有中断(异常中断,外设中断,外部中断等)的中断向量表的具体名称,有时候我们需要使用中断服务程序(ISR)的时候,不知道MCU的中断向量表名称是什么?这个时候,你就可以来启动文件里面寻找,比如如果你需要用到定时器3中断,就可以找到对应的是TMR3_GLOBAL_IRQHandler,在应用程序里就可以这么实现:
3553469858b88f3f8a.png
有些外设功能的中断向量表是在一起的,比如USBFS_L_CAN1_RX0_IRQHandler:
950869858b9f715fe.png
这个时候,在软硬件设计的时候,如果同时用上这两个功能的时候,就需要考虑怎么设计了,不然就会产生冲突。
另外,中断向量表的前面几个一般都是故障型的异常中断,比如大家比较常见的也是看着就有点头疼的HardFault_Handler硬件故障中断:
879169858bcb9f804.png
不同MCU厂商在中断向量表名称的定义上都是有些差别的,所以要知道这一点还是比较重要的。
4、系统初始化配置
5504769858cf0d105a.png
Reset_Handler主要负责MCU芯片上电后的系统初始化操作,会通过IMPORT汇编指令导入main函数和SystemInit函数,其中的SystemInit函数就是启动微控制器系统的作用:
544169858dac9a070.png
通过汇编语句LDR     R0, =__SystemInit将 SystemInit的地址加载到 R0 寄存器中,再通过BLX      R0执行SystemInit函数。
通过汇编语句LDR     R0, =__main将 main 的地址加载到 R0 寄存器中,再通过BX      R0跳转到main函数。
5、定义弱符号
2175469858dc5133b1.png
9398469858e030b2ea.png
WEAK其实是一个汇编伪指令,其主要作用就是定义弱符号,所谓的弱符号,就是允许链接器在特定情况下进行灵活的符号解析。通俗一点讲,就是如果应用程序里没有实现这些函数,链接器会使用这里默认的实现,如果应用程序自定义重写了弱符号,则链接器会使用应用程序里面的,即所谓的强符号,强符号可以覆盖弱符号。
因为如果不加WEAK,当用户没有实现某个中断处理函数时,链接器会报告未定义符号的错误,比如我把WWDT_IRQHandler屏蔽掉,然后应用程序也没定义,编译则会报错:
4674869858e1bd4ed1.png
6、堆栈空间初始化
9465969858e34b40e8.png
以上汇编代码的主要作用其实就是系统实际初始化已分配好空间大小的堆和栈,将栈和堆指针分配加载到R0,R1,R2,R3等寄存器中,其中:
Stack_Mem: 就是代表栈内存区域的起始地址;
Stack_Size: 就是代表栈的总大小(以字节为单位),即1所述的内容。
Heap_Mem: 就是代表堆内存区域的起始地址;
Heap_Size: 就是代表堆的总大小(以字节为单位),即2所述的内容。
汇编语句BX     LR的作用就是返回到调用处,跳回LR保存的地址,继续从调用点之后执行。
汇编代码的功底有限,就不在这里班门弄斧了!!
7、启动文件的Rebuild
另外,程序Rebuild的时候,启动文件也会被加入build序列:
2315569858e80237d3.png
只是不像源文件使用“compiling”一样,汇编文件会直接跳过“预处理”和“编译”的过程,直接使用“assembling”进入汇编过程,因为一般情况下,从源代码到可执行文件是需要经过预处理Preprocessing、编译Compilation、汇编Assembly和链接Linking等过程。同样的,启动文件经过汇编后也会产生对象文件(.o目标文件)和依赖文件(.d):
2304069858e9dbeb34.png
这里的依赖文件的内容如下所示,主要用于描述目标文件是由哪些源文件产生的:
3633969858ebac542f.png
这里的startup_at32f403a_407.o文件只由startup_at32f403a_407.s文件产生,而对于包含多个文件的源文件而言,则会有更多依赖关系,如下所示的add_di.d应用文件:
1430669858ec44ef62.png
此外,还可以在工程的listings文件夹下面看到一个startup_at32f403a_407.lst文件:
9257269858ecff1ffd.png
3970069858ed4dfe82.png
这个文件我们可能很少关注,因为貌似用不上,其实这个文件是一个汇编列表文件,一般由MCU的编译器或汇编器在编译过程中产生,作用嘛,主要包括三个方面吧:
提供源代码与机器码的对应关系,即显示汇编指令和生成的机器码;
提供内存地址分配信息,即每条指令在内存中的具体位置
提供符号表信息,即变量和函数的地址分配。
在实际的bug排查过程中,我倒是没有用过这个文件,不知道哪位大佬用它来解决过问题?欢迎来贴分享!!
除此之外,在在工程的listings文件夹下面的映像文件map里面也可以看到启动文件的相关信息:
6858269858ede0b98b.png
以上所有便是我对启动文件的相关内容的研究和解读,如有描述和解释不当之处,欢迎大家指正和参考~~我们一起进步!
您需要登录后才可以回帖 登录 | 注册

本版积分规则

393

主题

2702

帖子

26

粉丝
快速回复 在线客服 返回列表 返回顶部
0