本文主要记录华大低功耗单片机 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 0x00000100
AREA 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 0x00000400
AREA 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 Stack
DCD Reset_Handler ; Reset
DCD NMI_Handler ; NMI
DCD HardFault_Handler ; Hard Fault
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SVC_Handler ; SVCall
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD PendSV_Handler ; PendSV
DCD 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 debug
LDR 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 R0
LDR R0, =__main ;__main是C库函数 想要使用__main这个C库函数 必须勾选微库选项
BX R0
ENDP
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_limit
ELSE
;如果使用默认的C库
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
启动过程详解
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 0x00000100
AREA 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 0x00000400
AREA 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 Stack
DCD Reset_Handler ; Reset
DCD NMI_Handler ; NMI
DCD HardFault_Handler ; Hard Fault
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SVC_Handler ; SVCall
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD PendSV_Handler ; PendSV
DCD SysTick_Handler ; SysTick
DCD 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 - __Vectors
AREA |.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 debug
LDR R0, =0x20000000
LDR R2, =0x0
MOVS R1, #0 ; for warning,
ADD R1, PC,#0 ; for A1609W,
CMP R1, R0
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 R0
LDR R0, =__main ;__main是C库函数 想要使用__main这个C库函数 必须勾选微库选项
BX R0
ENDP
; Dummy Exception Handlers (infinite loops which can be modified)
; B . 表示原地跳转(即无限循环),等同于while(1);
NMI_Handler PROC
EXPORT NMI_Handler [WEAK]
B .
ENDP
HardFault_Handler\
PROC
EXPORT HardFault_Handler [WEAK]
B .
ENDP
SVC_Handler PROC
EXPORT SVC_Handler [WEAK]
B .
ENDP
PendSV_Handler PROC
EXPORT PendSV_Handler [WEAK]
B .
ENDP
SysTick_Handler PROC
EXPORT SysTick_Handler [WEAK]
B .
ENDP
Default_Handler PROC
EXPORT 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_Handler
B .
ENDP
ALIGN ;填充字节使地址对齐
; User Initial Stack & Heap ;堆和栈的初始化
IF :DEF:__MICROLIB ;如果定义了MICORLIB,使用微库
EXPORT __initial_sp ; 赋予【栈顶地址】【堆起始地址】【堆结束地址】全局的属性
EXPORT __heap_base ; 可在外部使用
EXPORT __heap_limit
ELSE
;如果使用默认的C库
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