本文主要记录华大低功耗单片机 HC32L110 的 汇编启动过程,包括startup_hc32l110启动文件详细注释
目录
- 1.启动文件的作用
- 2.堆栈定义
- 2.1 栈
- 2.2堆
- 3.向量表
- 4.复位程序
- 5.中断服务程序
- 6.堆栈初始化
- 启动过程详解
- 7.1从0地址开始
- 7.2在Reset_Handler中干了啥?
- 8.启动过程总结
- 9. startup.s 文件注释
1.启动文件的作用
启动文件为 startup_hc32l110.s
,启动文件中完成了:
-
堆和栈的初始化
- 包括堆栈的大小,主栈指针 MSP 的初始值
-
向量表定义
- 定义各MSP的初值以及中断服务函数的入口地址
-
中断服务程序
-
设置系统时钟频率 (在复位中断服务程序
Reset_handler
中调用系统时钟频率初始化程序) -
中断寄存器初始化
-
进入C的
main
函数
2.堆栈定义
2.1 栈
栈的作用是用于局部变量,函数调用,函数形参等的开销,栈的大小不能超过内部SRAM 的大小。当程序较大时,需要修改栈的大小,不然可能会出现的HardFault的错误。
EQU
是伪指令,相当于C 中的define
。ARER
伪指令表示下面将开始定义一个代码段或者数据段ARER
后面的关键字表示这个段的属性。段名为STACK
,可以任意命名;NOINIT
表示不初始化;READWRITE
表示可读可写,ALIGN=3
,表示按照 8 字节对齐。SPACE
用于分配大小等于Stack_Size
连续内存空间,单位为字节。__initial_sp
表示栈顶地址。栈是由高向低生长的
; Stack Configuration
; Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>Stack_Size EQU 0x00000100AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp; 定义栈大小为 0x100 256字节
; 定义一个数据段,8字节对齐,名称为 STACK ,数据段不初始化,仅仅保留内存单元,可读可写
; 分配一段大小为 Stack_Size 的连续内存空间,并初始化为0
; 表示栈顶指针地址
2.2堆
堆主要用来动态内存的分配,像 malloc()
函数申请的内存就在堆中。
; Heap Configuration
; Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>Heap_Size EQU 0x00000400AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit; 定义堆大小为 0x400 1024字节
; 定义一个数据段,8字节对齐,名称为 HEAP ,数据段不初始化,仅仅保留内存单元,可读可写
; __heap_base放置在SPACE之前表示堆的起始地址。
; 分配一段大小为 Heap_Size 的连续内存空间,并初始化为0
; __heap_limit放置在SPACE之后表示堆的结束地址。PRESERVE8 ; 指示编译器8字节对齐THUMB ; 指示编译器以后的指令为THUMB指令
3.向量表
向量表本质上是一个U32
的数组,每个元素代表一种异常,中断向量表 存放的实际上是中断服务程序的入口地址。当异常(也即是中断事件)发生时,CPU 的中断系统会将相应的入口地址赋值给 PC 程序计数器,之后就开始执行中断服务程序。 在地址 0 (即 FLASH 地址 0)处必须包含一张向量表,用于初始时的异常分配。
EXPORT
将标识符申明为可被外部引用DCD
表示分配 1 个 4 字节的空间
; Vector Table Mapped to Address 0 at Reset
;中断向量表定义AREA RESET, DATA, READONLY ;定义只读数据段,名字是REST;EXPORT:在程序中声明一个全局的标号__Vectors,;该标号可在其他的文件中引用EXPORT __Vectors ;表示向量表的起始地址EXPORT __Vectors_End ;表示向量表的结束地址EXPORT __Vectors_Size ;表示向量表的大小; 中断向量表
__Vectors DCD __initial_sp ; Top of StackDCD Reset_Handler ; ResetDCD NMI_Handler ; NMIDCD HardFault_Handler ; Hard FaultDCD 0 ; ReservedDCD 0 ; ReservedDCD 0 ; ReservedDCD 0 ; ReservedDCD 0 ; ReservedDCD 0 ; ReservedDCD 0 ; ReservedDCD SVC_Handler ; SVCallDCD 0 ; ReservedDCD 0 ; ReservedDCD PendSV_Handler ; PendSVDCD SysTick_Handler ; SysTick………………省略一部分__Vectors_End__Vectors_Size EQU __Vectors_End - __Vectors
4.复位程序
复位程序是系统上电后执行的第一个程序
; Reset Handler;利用PROC、ENDP这一对伪指令把程序段分为若干过程;使得程序结构明晰
Reset_Handler PROC ;过程开始EXPORT Reset_Handler [WEAK] ;weak 符号表示 该函数可以在外部重写IMPORT SystemInit ; IMPORT表示函数是从外部带入的,代码中的系统初始化程序IMPORT __main;reset NVIC if in rom debugLDR R0, =0x20000000 ; 将ram 地址写入R0寄存器LDR R2, =0x0 ;将 flash 0地址写入R2寄存器MOVS R1, #0 ; for warning, 将立即数0 写入R1寄存器ADD R1, PC,#0 ; for A1609W, 将PC初值+0 写入R1寄存器CMP R1, R0 ; 比较RAM首地址与 flash 首地址的值 BLS RAMCODE ; 若满足小于等于,则跳转; ram code base address. ADD R2, R0,R2
RAMCODE; reset Vector table address.LDR R0, =0xE000ED08 ; ?????????????STR R2, [R0]LDR R0, =SystemInit ;执行初始化,BLX R0LDR R0, =__main ;__main是C库函数 想要使用__main这个C库函数 必须勾选微库选项BX R0ENDP
5.中断服务程序
我们平时要使用哪个中断,就需要编写相应的中断服务程序,只是启动文件把这些函数留出来了,但是内容都是空的,真正的中断复服务程序需要我们在外部的 C 文件里面重新实现,这里只是提前占了一个位置罢了。
B .
表示无线循环,类似于while(1)
6.堆栈初始化
堆栈初始化是由一个IF条件来实现的,MICROLIB
的定义与否决定了堆栈的初始化方式。如果没有定义__MICROLIB
, 则会使用双段存储器模式,
且声明了__user_initial_stackheap
具有全局属性,这需要开发者自己来初始化堆栈。
; User Initial Stack & Heap ;堆和栈的初始化IF :DEF:__MICROLIB ;如果定义了MICORLIB,使用微库EXPORT __initial_sp ; 赋予【栈顶地址】【堆起始地址】【堆结束地址】全局的属性EXPORT __heap_base ; 可在外部使用EXPORT __heap_limitELSE ;如果使用默认的C库IMPORT __use_two_region_memory ;通知编译器要使用的标号在其他文件EXPORT __user_initial_stackheap
__user_initial_stackheapLDR R0, = Heap_Mem ;保存堆始地址LDR R1, =(Stack_Mem + Stack_Size) ;保存栈的大小LDR R2, = (Heap_Mem + Heap_Size) ;保存堆的大小LDR R3, = Stack_Mem ;保存栈顶指针BX LRALIGN ;填充字节使地址对齐ENDIFEND
启动过程详解
7.1从0地址开始
ARM
内核单片机的启动方式都是如下图流程:
通过以上分析可知,flash 的0 地址存储这中断向量表。
- 单片机从 flash 的 0 地址取出数据赋给MSP指针,然后从 0+ 4 地址处取出值赋值给PC
- 此时 SP = 栈顶地址 PC =
Reset_Handler
,程序从复位开始执行
7.2在Reset_Handler中干了啥?
Reset_Handler
仅仅执行了两个函数调用,一个是SystemInit
,另一个__main,
SystemInit
定义在system_hc32xxxx.c
中,主要初始化了系统时钟系统:RCH,RCL,XTH,XTL,PLL,SystemClk,HCLK,PCLK
等等.__main
函数由编译器生成,负责初始化栈、堆等,并在最后跳转到用户自定义的main()
函数,来到C
的世界
8.启动过程总结
- ;先在
RAM
中分配系统使用的栈,RAM
的起始地址为0x2000_0000
- ;然后在
RAM
中分配变量使用的堆 - ;然后在
CODE
区(flash
)分配中断向量表,flash
的起始地址为0x0000_0000
,该中断向量表就从这个起始地址开始分配 - ;分配完成后,再定义和实现相应的中断函数,
- ;所有的中断函数全部带有[
weak
]特性,即弱定义,如果编译器发现在别处文件中定义了同名函数,在链接时用别处的地址进行链接。 - ;中断函数仅仅实现了
Reset_Handler
,其他要么是死循环,要么仅仅定义了函数名称 - ;
HC32
被设置为从内部FLASH启动时(这也是最常见的一种情况),当HC32遇到复位信号后, - ;从
0x0000_0000
处取出栈顶地址存放于MSP
寄存器,从0x0000_0004
处取出复位中断服务入口地址放入PC寄存器, - ;继而执行复位中断服务程序
Reset_Handler
, - ;
Reset_Handler
仅仅执行了两个函数调用,一个是SystemInit
,另一个__main
, - ;
SystemInit
定义在system_hc32xxxx.c
中,主要初始化了HC
的时钟系统:RCH,RCL,XTH,XTL,PLL,SystemClk,HCLK,PCLK等等. - ;
__main
函数由编译器生成,负责初始化栈、堆等,并在最后跳转到用户自定义的main()
函数,来到C的世界
9. startup.s 文件注释
;/******************************************************************************
;* Copyright (C) 2017, Xiaohua Semiconductor Co.,Ltd All rights reserved.
;*
;* This software is owned and published by:
;* Xiaohua Semiconductor Co.,Ltd ("XHSC").
;*
;* BY DOWNLOADING, INSTALLING OR USING THIS SOFTWARE, YOU AGREE TO BE BOUND
;* BY ALL THE TERMS AND CONDITIONS OF THIS AGREEMENT.
;*
;* This software contains source code for use with XHSC
;* components. This software is licensed by XHSC to be adapted only
;* for use in systems utilizing XHSC components. XHSC shall not be
;* responsible for misuse or illegal use of this software for devices not
;* supported herein. XHSC is providing this software "AS IS" and will
;* not be responsible for issues arising from incorrect user implementation
;* of the software.
;*
;* Disclaimer:
;* XHSC MAKES NO WARRANTY, EXPRESS OR IMPLIED, ARISING BY LAW OR OTHERWISE,
;* REGARDING THE SOFTWARE (INCLUDING ANY ACOOMPANYING WRITTEN MATERIALS),
;* ITS PERFORMANCE OR SUITABILITY FOR YOUR INTENDED USE, INCLUDING,
;* WITHOUT LIMITATION, THE IMPLIED WARRANTY OF MERCHANTABILITY, THE IMPLIED
;* WARRANTY OF FITNESS FOR A PARTICULAR PURPOSE OR USE, AND THE IMPLIED
;* WARRANTY OF NONINFRINGEMENT.
;* XHSC SHALL HAVE NO LIABILITY (WHETHER IN CONTRACT, WARRANTY, TORT,
;* NEGLIGENCE OR OTHERWISE) FOR ANY DAMAGES WHATSOEVER (INCLUDING, WITHOUT
;* LIMITATION, DAMAGES FOR LOSS OF BUSINESS PROFITS, BUSINESS INTERRUPTION,
;* LOSS OF BUSINESS INFORMATION, OR OTHER PECUNIARY LOSS) ARISING FROM USE OR
;* INABILITY TO USE THE SOFTWARE, INCLUDING, WITHOUT LIMITATION, ANY DIRECT,
;* INDIRECT, INCIDENTAL, SPECIAL OR CONSEQUENTIAL DAMAGES OR LOSS OF DATA,
;* SAVINGS OR PROFITS,
;* EVEN IF Disclaimer HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
;* YOU ASSUME ALL RESPONSIBILITIES FOR SELECTION OF THE SOFTWARE TO ACHIEVE YOUR
;* INTENDED RESULTS, AND FOR THE INSTALLATION OF, USE OF, AND RESULTS OBTAINED
;* FROM, THE SOFTWARE.
;*
;* This software may be replicated in part or whole for the licensed use,
;* with the restriction that this Disclaimer and Copyright notice must be
;* included with each copy of this software, whether used in part or whole,
;* at all times.
;*/
;/*****************************************************************************/;/*****************************************************************************/
;/* Startup for ARM */
;/* Version V1.2 */
;/* Date 2016-06-02 */
;/* Target-mcu {MCU_SERIES} */
;/*****************************************************************************/; Stack Configuration
; Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>Stack_Size EQU 0x00000100AREA STACK, NOINIT, READWRITE, ALIGN=3
Stack_Mem SPACE Stack_Size
__initial_sp; 定义栈大小为 0x100 256字节
; 定义一个数据段,8字节对齐,名称为 STACK ,数据段不初始化,仅仅保留内存单元,可读可写
; 分配一段大小为 Stack_Size 的连续内存空间,并初始化为0
; 表示栈顶指针地址; Heap Configuration
; Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>Heap_Size EQU 0x00000400AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit; 定义堆大小为 0x400 1024字节
; 定义一个数据段,8字节对齐,名称为 HEAP ,数据段不初始化,仅仅保留内存单元,可读可写
; __heap_base放置在SPACE之前表示堆的起始地址。
; 分配一段大小为 Heap_Size 的连续内存空间,并初始化为0
; __heap_limit放置在SPACE之后表示堆的结束地址。PRESERVE8 ; 指示编译器8字节对齐THUMB ; 指示编译器以后的指令为THUMB指令; Vector Table Mapped to Address 0 at Reset
;中断向量表定义AREA RESET, DATA, READONLY ;定义只读数据段,名字是REST;EXPORT:在程序中声明一个全局的标号__Vectors,;该标号可在其他的文件中引用EXPORT __Vectors ;表示向量表的起始地址EXPORT __Vectors_End ;表示向量表的结束地址EXPORT __Vectors_Size ;表示向量表的大小; 中断向量表
__Vectors DCD __initial_sp ; Top of StackDCD Reset_Handler ; ResetDCD NMI_Handler ; NMIDCD HardFault_Handler ; Hard FaultDCD 0 ; ReservedDCD 0 ; ReservedDCD 0 ; ReservedDCD 0 ; ReservedDCD 0 ; ReservedDCD 0 ; ReservedDCD 0 ; ReservedDCD SVC_Handler ; SVCallDCD 0 ; ReservedDCD 0 ; ReservedDCD PendSV_Handler ; PendSVDCD SysTick_Handler ; SysTickDCD IRQ000_Handler ; DCD IRQ001_Handler ; DCD IRQ002_Handler ; DCD IRQ003_Handler ; DCD IRQ004_Handler ; DCD IRQ005_Handler ; DCD IRQ006_Handler ; DCD IRQ007_Handler ; DCD IRQ008_Handler ; DCD IRQ009_Handler ; DCD IRQ010_Handler ; DCD IRQ011_Handler ; DCD IRQ012_Handler ; DCD IRQ013_Handler ; DCD IRQ014_Handler ; DCD IRQ015_Handler ; DCD IRQ016_Handler ; DCD IRQ017_Handler ; DCD IRQ018_Handler ; DCD IRQ019_Handler ; DCD IRQ020_Handler ; DCD IRQ021_Handler ; DCD IRQ022_Handler ; DCD IRQ023_Handler ; DCD IRQ024_Handler ; DCD IRQ025_Handler ; DCD IRQ026_Handler ; DCD IRQ027_Handler ; DCD IRQ028_Handler ; DCD IRQ029_Handler ; DCD IRQ030_Handler ; DCD IRQ031_Handler ; __Vectors_End__Vectors_Size EQU __Vectors_End - __VectorsAREA |.text|, CODE, READONLY ;代码段定义,只读; Reset Handler;利用PROC、ENDP这一对伪指令把程序段分为若干过程;使得程序结构明晰
Reset_Handler PROC ;过程开始EXPORT Reset_Handler [WEAK] ;weak 符号表示 该函数可以在外部重写IMPORT SystemInit ; IMPORT表示函数是从外部带入的,代码中的系统初始化程序IMPORT __main;reset NVIC if in rom debugLDR R0, =0x20000000LDR R2, =0x0MOVS R1, #0 ; for warning, ADD R1, PC,#0 ; for A1609W, CMP R1, R0BLS RAMCODE; ram code base address. ADD R2, R0,R2
RAMCODE; reset Vector table address.LDR R0, =0xE000ED08 STR R2, [R0]LDR R0, =SystemInitBLX R0LDR R0, =__main ;__main是C库函数 想要使用__main这个C库函数 必须勾选微库选项BX R0ENDP; Dummy Exception Handlers (infinite loops which can be modified); B . 表示原地跳转(即无限循环),等同于while(1);
NMI_Handler PROCEXPORT NMI_Handler [WEAK]B . ENDP
HardFault_Handler\PROCEXPORT HardFault_Handler [WEAK]B .ENDP
SVC_Handler PROCEXPORT SVC_Handler [WEAK]B .ENDP
PendSV_Handler PROCEXPORT PendSV_Handler [WEAK]B .ENDP
SysTick_Handler PROCEXPORT SysTick_Handler [WEAK]B .ENDPDefault_Handler PROCEXPORT IRQ000_Handler [WEAK]EXPORT IRQ001_Handler [WEAK]EXPORT IRQ002_Handler [WEAK]EXPORT IRQ003_Handler [WEAK]EXPORT IRQ004_Handler [WEAK]EXPORT IRQ005_Handler [WEAK]EXPORT IRQ006_Handler [WEAK]EXPORT IRQ007_Handler [WEAK]EXPORT IRQ008_Handler [WEAK]EXPORT IRQ009_Handler [WEAK]EXPORT IRQ010_Handler [WEAK]EXPORT IRQ011_Handler [WEAK]EXPORT IRQ012_Handler [WEAK]EXPORT IRQ013_Handler [WEAK]EXPORT IRQ014_Handler [WEAK]EXPORT IRQ015_Handler [WEAK]EXPORT IRQ016_Handler [WEAK]EXPORT IRQ017_Handler [WEAK]EXPORT IRQ018_Handler [WEAK]EXPORT IRQ019_Handler [WEAK]EXPORT IRQ020_Handler [WEAK]EXPORT IRQ021_Handler [WEAK]EXPORT IRQ022_Handler [WEAK]EXPORT IRQ023_Handler [WEAK]EXPORT IRQ024_Handler [WEAK]EXPORT IRQ025_Handler [WEAK]EXPORT IRQ026_Handler [WEAK]EXPORT IRQ027_Handler [WEAK]EXPORT IRQ028_Handler [WEAK]EXPORT IRQ029_Handler [WEAK]EXPORT IRQ030_Handler [WEAK]EXPORT IRQ031_Handler [WEAK]IRQ000_Handler
IRQ001_Handler
IRQ002_Handler
IRQ003_Handler
IRQ004_Handler
IRQ005_Handler
IRQ006_Handler
IRQ007_Handler
IRQ008_Handler
IRQ009_Handler
IRQ010_Handler
IRQ011_Handler
IRQ012_Handler
IRQ013_Handler
IRQ014_Handler
IRQ015_Handler
IRQ016_Handler
IRQ017_Handler
IRQ018_Handler
IRQ019_Handler
IRQ020_Handler
IRQ021_Handler
IRQ022_Handler
IRQ023_Handler
IRQ024_Handler
IRQ025_Handler
IRQ026_Handler
IRQ027_Handler
IRQ028_Handler
IRQ029_Handler
IRQ030_Handler
IRQ031_HandlerB .ENDPALIGN ;填充字节使地址对齐; User Initial Stack & Heap ;堆和栈的初始化IF :DEF:__MICROLIB ;如果定义了MICORLIB,使用微库EXPORT __initial_sp ; 赋予【栈顶地址】【堆起始地址】【堆结束地址】全局的属性EXPORT __heap_base ; 可在外部使用EXPORT __heap_limitELSE ;如果使用默认的C库IMPORT __use_two_region_memory ;通知编译器要使用的标号在其他文件EXPORT __user_initial_stackheap
__user_initial_stackheapLDR R0, = Heap_Mem ;保存堆始地址LDR R1, =(Stack_Mem + Stack_Size) ;保存栈的大小LDR R2, = (Heap_Mem + Heap_Size) ;保存堆的大小LDR R3, = Stack_Mem ;保存栈顶指针BX LRALIGN ;填充字节使地址对齐ENDIFEND