005 - STM32学习笔记 - 启动代码
常用汇编指令
指令名称 | 作用 |
---|---|
EQU | 给数字常量取一个符号名,相当于C语言中的#define ; |
AREA | 汇编一个新的代码段或者数据段; |
SPACE | 分配内存空间; |
PRESERVE8 | 当前文件栈需要按照8字节对齐; |
EXPORT | 声明一个具有全局属性的标号,可被外部文件访问; |
DCD | 以字为单位分配内存,要求按照4字节对齐,并初始化这些内存; |
PROC | 定义子程序,与ENDP成对使用,ENDP表示子程序结束; |
WEAK | 若定义,如果外部声明了该指令修饰的标号,则优先使用外部声明的标号,如果外部没有声明,也不会出错。 |
IMPORT | 声明标号来自外部文件,与C语言中的extern 关键字类似 |
B | 跳转指令,即跳转到一个标号; |
ALIGN | 编译器对指令或者数据的存放地址进行对齐,一般需要跟一个立即数,默认表示按照4字节对齐; |
END | 达到文件的末尾,表示文件结束; |
IF,ENSE,ENDIF | 汇编条件分支语句,与C语言中的if...else... 类似 |
; | 注释符,与C语言中// 类似 |
启动代码
看一下startup_stm32f4429_439xx.s文件前面的copyright内容
;******************** (C) COPYRIGHT 2016 STMicroelectronics ********************
;* File Name : startup_stm32f429_439xx.s
;* Author : MCD Application Team
;* @version : V1.8.0
;* @date : 09-November-2016
;* Description : STM32F429xx/439xx devices vector table for MDK-ARM toolchain.
;* This module performs:
;* - Set the initial SP 初始化指针sp
;* - Set the initial PC == Reset_Handler 初始化指针pc
;* - Set the vector table entries with the exceptions ISR address 初始化向量终端表
;* - Configure the system clock and the external SRAM/SDRAM mounted 配置系统时钟
;* on STM324x9I-EVAL boards to be used as data memory
;* (optional, to be enabled by user)
;* - Branches to __main in the C library (which eventually 调用C库
;* calls main()).
;* After Reset the CortexM4 processor is in Thread mode,
;* priority is Privileged, and the Stack is set to Main.
STM32启动代码由汇编编写,会在系统上电后执行,可以认为启动代码就是整个系统启动的引导程序,功能主要分如下几个:
- 初始化栈指针
SP = _initial_sp
; - 初始化PC指针 =
Rest_Handler
; - 初始化中断向量表;
- 配置系统时钟;
- 调用C库函数
_main
初始化用户栈,从而真正转到C的main
函数中。
1、栈
Stack_Size EQU 0x00000400 ;给0x00000400这个立即数一个别名Stack_Size,从名字上可以看出来这是给栈分配空间为1KB,如果不够的话可以修改这里
AREA STACK, NOINIT, READWRITE, ALIGN=3 ;栈名称为STACK,NOINIT为不初始化,READWRIT:可读写,ALIGN=3,表示已2^3(8)字节对齐
Stack_Mem SPACE Stack_Size ;指定了内存大小等于Stack_Size
__initial_sp ;标号__initial_sp紧挨着SPACE放置,表示栈的结束地址,及栈顶地址
打开map文件可以看到关于栈的相关信息
2、堆
Heap_Size EQU 0x00000200 ;给0x00000200这个立即数一个别名Heap_Size,从名字上可以看出来这是给栈分配空间为512B,如果不够的话可以修改这里
AREA HEAP, NOINIT, READWRITE, ALIGN=3 ;堆名称为HEAP,NOINIT为不初始化,READWRIT:可读写,ALIGN=3,表示已2^3(8)字节对齐
__heap_base ;堆的起始地址
Heap_Mem SPACE Heap_Size ;指定对的内存大小等于Heap_Size
__heap_limit ;堆的结束地址
PRESERVE8 ;指定当前文件的堆按照8字节对齐
THUMB ;后面指令兼容THUMB指令
3、向量表
; 重映射向量表
AREA RESET, DATA, READONLY ;定义数据段,名称为RESET,只读模式,
EXPORT __Vectors ;声明可被外部访问的标号
EXPORT __Vectors_End
EXPORT __Vectors_Size
;这里我自己理解就类似于一个数组,__Vectors是数组的起始地址,__Vectors_End是数组结束地址,__Vectors_Size在最后做了计算,是起始地址与结束地址的差值
;向量表中记录了系统所有中断向量地址,当内核响应了发生的异常(中断或事件)后,对应的异常服务例程就会执行,所以这里的向量表就是为异常服务机制提供入口地址
__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD MemManage_Handler ; MPU Fault Handler
DCD BusFault_Handler ; Bus Fault Handler
DCD UsageFault_Handler ; Usage Fault Handler
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SVC_Handler ; SVCall Handler
DCD DebugMon_Handler ; Debug Monitor Handler
DCD 0 ; Reserved
DCD PendSV_Handler ; PendSV Handler
DCD SysTick_Handler ; SysTick Handler
; External Interrupts
DCD WWDG_IRQHandler ; Window WatchDog
DCD PVD_IRQHandler ; PVD through EXTI Line detection
DCD TAMP_STAMP_IRQHandler ; Tamper and TimeStamps through the EXTI line
DCD RTC_WKUP_IRQHandler ; RTC Wakeup through the EXTI line
DCD FLASH_IRQHandler ; FLASH
DCD RCC_IRQHandler ; RCC
DCD EXTI0_IRQHandler ; EXTI Line0
DCD EXTI1_IRQHandler ; EXTI Line1
;由于篇幅原因,中间就删掉了
DCD SPI4_IRQHandler ; SPI4
DCD SPI5_IRQHandler ; SPI5
DCD SPI6_IRQHandler ; SPI6
DCD SAI1_IRQHandler ; SAI1
DCD LTDC_IRQHandler ; LTDC
DCD LTDC_ER_IRQHandler ; LTDC error
DCD DMA2D_IRQHandler ; DMA2D
__Vectors_End ;这里表示向量表的结束地址
__Vectors_Size EQU __Vectors_End - __Vectors ;计算向量表的容量并赋值给__Vectors_Size
4、复位程序
AREA |.text|, CODE, READONLY ;定义了名称为.text的代码段,只读
; Reset handler定义名为Reset_Handler的子程序
Reset_Handler PROC
;导入Reset_Handler子程序,这里用WEAK修饰表示弱定义,优先定义外部定义的程序,如果没有,也不会报错,
EXPORT Reset_Handler [WEAK]
IMPORT SystemInit ;表示SystemInit函数来自外部
IMPORT __main ;表示__main函数来自外部
LDR R0, =SystemInit ;将SystemInit加载到R0寄存器中
BLX R0 ;跳转到R0给出的地址
LDR R0, =__main ;将__main加载到R0寄存器中
BX R0 ;跳转到R0给出的地址
ENDP ;程序结束
SystemInit
是标准库函数,在system_stm32f4xx.c中定义,主要是用来配置系统时钟,我现在用的F429的开发板,在调用SystemInit
后,会将系统时钟配置到180MHz。
__main
是个标准C库函数,但是这里要主义的是,这个main不是我们在C语言中的main函数,__main
作用是初始化用户栈,完成后在最后调用main转到C语言中。
5、中断服务程序
; Dummy Exception Handlers (infinite loops which can be modified)
NMI_Handler PROC ;定义了NMI_Handler中断子程序
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]
EXPORT RTC_WKUP_IRQHandler [WEAK]
EXPORT FLASH_IRQHandler [WEAK]
EXPORT RCC_IRQHandler [WEAK]
EXPORT EXTI0_IRQHandler [WEAK]
;篇幅省略
EXPORT LTDC_ER_IRQHandler [WEAK]
EXPORT DMA2D_IRQHandler [WEAK]
WWDG_IRQHandler
PVD_IRQHandler
TAMP_STAMP_IRQHandler
;篇幅省略
LTDC_IRQHandler
LTDC_ER_IRQHandler
DMA2D_IRQHandler
B . ;跳转到.,在这里.表示无限循环
ENDP
这里需要注意的是,使用中断的时候,应为是弱定义的中断服务子程序,因此会优先调用外部定义的服务程序,当我们在外部没有编写配套的中断服务子程序或者编写的服务子程序与启动文件中的程序名不一致时,系统会自动跳转到启动文件中默认的中断服务子程序中,B .
然后就在此处无限循环,程序就卡死到这里了
6、用户堆与栈的初始化
ALIGN ;用来指定符号的对齐方式,默认按照4字节对齐
;*******************************************************************************
; User Stack and Heap initialization
;*******************************************************************************
IF :DEF:__MICROLIB ;这里会判断是否定义了__MICROLIB,即表示是否使用MicroLib库,可以看仙女棒中是否勾选
EXPORT __initial_sp ;如果使用MicroLib库,则给__initial_sp、__heap_base、__heap_limit给全局属性,可以让外部文件调用
EXPORT __heap_base
EXPORT __heap_limit
ELSE
;如果没有使用MicroLib库,则使用双段存储器模式,并且声明__user_initial_stackheap全局属性,由用户自行初始化堆栈
IMPORT __use_two_region_memory
EXPORT __user_initial_stackheap
;以下就是没有使用MicroLib库,由用户自行初始化堆栈的程序
__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
本节内容中需要主要,后续使用的中断的时候,一定要检查中断服务子程序是否写错或者未定义,否则很容易出现中断服务程序无限循环的情况。