stm32单片机启动文件讲解(STM32单片机启动代码你不会不知道吧)

想要深入学习STM32单片机,就必须要去研究STM32单片机的启动代码,否则你就无法从整体框架上去了解它,所以STM32启动代码早晚都是要研究学习的,避不掉的坑。启动代码里主要是由汇编和伪指令构成的,下面我们从头到尾来理一遍这些神秘的代码究竟是什么含义。


stm32单片机启动文件讲解(STM32单片机启动代码你不会不知道吧)(1)

图一 申请栈

图一这段代码开辟了一个大小是0x00001000的栈,大小可以根据实际情况去调整,栈主要用于保存函数内局域变量和内核寄存器,在调用函数时就会将一些数据保存在栈内,占用一定的栈空间,在调用函数结束返回到上一层函数时就会归还调用函数所占用的栈空间,所以栈的大小决定了你能申请的局域变量的大小,如果函数内定义了较多局域变量、大数组或多层函数嵌套的情况时建议将栈调大,否则运行时可能会由于栈溢出而导致的HardFault_Handler异常。__initial_sp就是这个栈指向地址的一个标号,后面会用到。


stm32单片机启动文件讲解(STM32单片机启动代码你不会不知道吧)(2)

图二 申请堆

图二这段代码开辟了一个大小是0x00000000的堆,即没有分配堆大小,一般未涉及操作系统时,不需要使用堆。如果用到堆也可以直接申请全局变量效果等效于此,所以为了灵活使用,如果程序里用到动态内存分配,直接在使用全局变量定义一个数组效果一样。

需要强调的是ALIGN=3表示8字节对齐,所以自己用全局变量创建数组用作堆时也要保证8字节对齐,可以使用__align(8)来前缀修饰,8字节对齐是为了保证必须使用8字节对齐的函数能正常运行,例如像printf("%.3f",test)这样的函数输出浮点型变量值时,就要保证定义test是8字对齐的。编程时只要保证堆的起始地址是8字节对齐,编译器会自动保证后面在堆里申请的变量也是8字节对齐的。


stm32单片机启动文件讲解(STM32单片机启动代码你不会不知道吧)(3)

省略中间部分......,可自行参考启动代码

stm32单片机启动文件讲解(STM32单片机启动代码你不会不知道吧)(4)

图三 中断向量表

图三这段代码是定义中断向量表,针对STM32单片机中断向量一般表默认是从单片机保存代码的开始位置即0x8000000地址(也可以从映射启动地址,这里不做拓展)。不要纠结这个先后顺序为什么要这样,这是芯片生产厂规定好的向量表第一位就是__initial_sp即栈指针放在0x8000000地址处占4个字节。第二个就是复位中断函数的入口地址放在0x8000004地址处占4个字节。说白了DCD就是申请一个占4字节的坑,每个坑放一个指针,__initial_sp用于放栈指针,其他坑分别放指向一个中断服务函数的入口指针。这样一旦触发中断,硬件上就会来到对应中断的坑,然后从坑里找到对应的中断服务函数的入口地址,跳转到对应中断服务函数运行。


stm32单片机启动文件讲解(STM32单片机启动代码你不会不知道吧)(5)

图四 复位中断服务函数

图四这段代码比较重要,这就是复位中断的服务函数,Reset_Handler标号正好对应上面说的中断向量表里的那个标号一致,编译器就会把这段汇编程序的入口地址编译放到中断向量表里复位中断那个坑里。一旦触发复位,硬件就会立马在中断向量表里从找到复位中断这个坑,从坑里放的指针地址就能跳转到这里来运行。可以看出单片机在复位后,并不是直接从main函数开始运行,而是最先进入复位中断。从复位中断里跳转到__main函数运行,注意__main函数不等于main函数。__main函数是由编译器提供的一个库函数,进入这个函数首先会初始化一些全局变量,堆栈里的数据,比如你定义一个全局变量int x=10;在函数里用到x这个变量时,程序怎么知道他初始值是多少,这个初始10就是在__main函数里进行赋值的,当一切准备工作都做好了__main函数会调用main函数,至此进入到你熟悉的世界里来了,多么美好!


stm32单片机启动文件讲解(STM32单片机启动代码你不会不知道吧)(6)

图五 堆栈初始化

图五为用户堆和栈的初始化

IF :DEF:__MICROLIB ;类似if语句,:DEF:X 就是说X定义了则为真,否则为假。

EXPORT __initial_sp ;栈顶地址。

EXPORT __heap_base ;堆起始地址。

EXPORT __heap_limit ;堆末端地址。

ELSE ;如果没定义__MICROLIB,则使用默认的C运行时库

IMPORT __use_two_region_memory ;通知编译器要使用的标号在其他源文件定义了__use_two_region_memory。

EXPORT __user_initial_stackheap ;声明全局标号__user_initial_stackheap,这样外程序也可调用此标号 ;则进行堆栈和堆的赋值,在__main函数执行过程中调用;如果使用默认的C库,程序启动过程中就不会执行该标号下的代码。

__user_initial_stackheap;表示用户堆栈初始化程序入口,在__main函数执行过程中调用。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


下面对其他一些关键词定义做一个大概介绍:

1.[WEAK]:此修饰符修饰的函数为若函数,意为可以其他源文件中重新定义一个同名函数,最终编译器编译的时候,会选择用户定义的函数,如果用户没有重新定义这个函数,那么编译器就会执行WEAK声明的函数。所以我们可以在别的地方定义一个相同名字的函数,而不必也尽量不要修改之前的函数,比如这里就可以看出来启动代码里已经有了USART1_IRQHandler函数,你在自己写的代码里再定义一个USART1_IRQHandler函数,编译也不会报错,而是自动使用你自己编写的函数。

2.IMPORT:表明要调用的函数、变量或其他标号为其他源文件定义,类似C里的函数声明。

3.EXPORT:表明该函数、变量或其他标号可以被其他源文件使用,类似于C中的extern功能。

4.AREA:伪指令,用于定义代码段或数据段,后跟属性标号。其中常用的有“ READONLY”表示该段为只读属性,联系到STM32的内部存储介质,可知具有该属性的段一般保存于FLASH区,而“READWRITE”表示该段为可读写属性,可知可读写段一般保存于SRAM区。


,

免责声明:本文仅代表文章作者的个人观点,与本站无关。其原创性、真实性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容文字的真实性、完整性和原创性本站不作任何保证或承诺,请读者仅作参考,并自行核实相关内容。文章投诉邮箱:anhduc.ph@yahoo.com

    分享
    投诉
    首页