STM32单片机之分析启动文件小论(I)
STM32单片机之分析启动文件小论(I)
上电时
我们的STM32一经上电,他会跟CPU上电的行为一致,也就是跳转到一个固定的地址。我们的STM32也是一样,对于不同的boot选择,他会跳转到不同的地址。这就跟启动模式的三种方式很有关系:
BOOT0 | BOOT1 | 启动模式 | 0x00000000的映射地址 | 0x00000004的映射地址 |
---|---|---|---|---|
0 | x | 内部FLASH | 0x08000000 | 0x08000004 |
1 | 1 | 内部SRAM | 0x20000000 | 0x20000004 |
1 | 0 | 系统存储器 | 0x1FFFF000 | 0x1FFFF004 |
也就是说,选择不同的boot引脚本质上就是在选择不一样的跳转地址,无论如何,我们要做的,就是在选定我们的特定的跳转地址后,在这些地址上排列我们之后的指令。
启动文件分析
这一场串的文件很大,也是一份arm汇编文件,为此,为了理解这份文件,我们来看看一些简单的汇编标识符:
指令名称 | 作用 |
---|---|
EQU | 相当于#define给常量取名称 |
AREA | 汇编一段全新的段 |
ALIGN | 编译器对指令或者数据的存放地址进行对齐。一般需要跟一个立即数,缺省表示四字节对齐。不过要注意的是:这个并不是arm汇编指令,而是编译器的内置指令 |
SPACE | 分配内存空间 |
PRESERVE8 | 当前文件堆栈需要按照八字节对齐 |
THUMB | 表示向后兼容THUMB指令(详细可以查询ARM状态和THUMB状态) |
EXPORT | 声明导出这个符号给外边文件用 |
IMPORT | 声明这个符号来源于外面 |
DCD | 以字节为单位分配内存要求四字节对齐并且要求初始化这些内存 |
PROC | 子程序的开始,要求以ENDP成对出现 |
WEAK | 这表示若定义如果外部文件声明的一个符号,则优先使用外部文件定义的符号。如果外部文件没有定义这个符号也不会出错。要注意的是这也不是arm的汇编指令而是编译器的内置指令 |
LDR | 从存储器加载一个字 |
BLX | 跳转到寄存器给出的地址,并且根据寄存器的LSE确定处理器的状态,还要把跳转前的下条指令地址保存到LR(实际上就是call) |
BX | 跳转到由寄存器或者标号给出的地址不用返回 |
B | 跳转到一个标号 |
IF ELSE ENDIF | 汇编条件分支语句跟C语言类似 |
END | 文件结束 |
那我们现在尝试着手分析这个文件
文件名称:startup_stm32f103xe.s
Stack_Size EQU 0x00000400 # 声明一个栈大小的宏
AREA STACK, NOINIT, READWRITE, ALIGN=3 # STACK栈区域,不初始化,可读可写,对齐3字节
Stack_Mem SPACE Stack_Size # 表达栈的大小是1024B,也就是1KB大小
__initial_sp
; <h> Heap Configuration
; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
Heap_Size EQU 0x00000200 # 堆大小为512B
AREA HEAP, NOINIT, READWRITE, ALIGN=3 # HEAP堆区域,不初始化,可读可写,对齐3字节
__heap_base # HEAP起始地址
Heap_Mem SPACE Heap_Size # HEAP区域
__heap_limit # HEAP终止地址
PRESERVE8 # 这两条看上面的说明:当前文件堆栈需要按照八字节对齐
THUMB # THUMB指令
; 上面的区域划分了区域,下面开始排布中断向量表,他在0地址处(数据段)
; Vector Table Mapped to Address 0 at Reset
AREA RESET, DATA, READONLY # 中断向量表安排在0地址处
EXPORT __Vectors # 下面的三个符号将会可见(被外面的使用)
EXPORT __Vectors_End
EXPORT __Vectors_Size
# 具体含义如何不是这里的侧重点
__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
; ... 省略了大量的中断向量
DCD DMA2_Channel4_5_IRQHandler ; DMA2 Channel4 & Channel5
__Vectors_End
__Vectors_Size EQU __Vectors_End - __Vectors # 推导出我们的向量表的大小
AREA |.text|, CODE, READONLY # 只读代码段,放在.text段上
; Reset handler # 我们很关心这个
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
# __main函数,也是标准的C库函数,负责初始化堆栈(这里要求要连接到C库)
IMPORT __main
# SystemInit()是一个库函数,在system_stm32f1xx.c中定义的
IMPORT SystemInit # SystemInit贴在了下面
# 先调用SystemInit
LDR R0, =SystemInit
BLX R0
# 再调用__main函数初始化
LDR R0, =__main
BX R0
ENDP
; 省略了大量的handlers的弱定义
; ....
; ....
; 结束
;*******************************************************************************
; User Stack and Heap initialization
;*******************************************************************************
# 有没有定义__MICROLIB?我们默认人没有定义,这样的化就是使用C库函数___main帮助我们完成初始化,
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
;************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE*****
**
* @brief Setup the microcontroller system
* Initialize the Embedded Flash Interface, the PLL and update the
* SystemCoreClock variable.
* @note This function should be used only after reset.
* @param None
* @retval None
*/
# 简单来讲就是初始化Flash接口和初始化系统的时钟
void SystemInit (void)
{
/* Reset the RCC clock configuration to the default reset state(for debug purpose) */
/* Set HSION bit */
RCC->CR |= 0x00000001U;
/* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */
#if !defined(STM32F105xC) && !defined(STM32F107xC)
RCC->CFGR &= 0xF8FF0000U;
#else
RCC->CFGR &= 0xF0FF0000U;
#endif /* STM32F105xC */
/* Reset HSEON, CSSON and PLLON bits */
RCC->CR &= 0xFEF6FFFFU;
/* Reset HSEBYP bit */
RCC->CR &= 0xFFFBFFFFU;
/* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */
RCC->CFGR &= 0xFF80FFFFU;
#if defined(STM32F105xC) || defined(STM32F107xC)
/* Reset PLL2ON and PLL3ON bits */
RCC->CR &= 0xEBFFFFFFU;
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x00FF0000U;
/* Reset CFGR2 register */
RCC->CFGR2 = 0x00000000U;
#elif defined(STM32F100xB) || defined(STM32F100xE)
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x009F0000U;
/* Reset CFGR2 register */
RCC->CFGR2 = 0x00000000U;
#else
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x009F0000U;
#endif /* STM32F105xC */
#if defined(STM32F100xE) || defined(STM32F101xE) || defined(STM32F101xG) || defined(STM32F103xE) || defined(STM32F103xG)
#ifdef DATA_IN_ExtSRAM
SystemInit_ExtMemCtl();
#endif /* DATA_IN_ExtSRAM */
#endif
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM. */
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH. */
#endif
}
看完了:我们捋一下如何启动的:
启动流程分析
上电:单片机根据选择的BOOT引脚跳转到相应的地址,开始取出堆栈指针的初始值(栈顶的初始值)和PC计数器的值(下一条指令在哪里)
这个PC值就是中断向量表的起始地址,也是复位程序的入口地址,接着跳转到复位程序入口处,初始向量表,然后设置时钟(SystemInit),设置堆栈(__main函数),最后跳转到C空间的main函数,即进入用户程序
实际上就是这样的简单。
全体目光向我看齐,我宣布个事!是我Charliechen写的这篇文章!(?)