国产超低功耗华大单片启动文件详解及SystemInit函数分析
启动代码文件名一般可命名为如startup_hc32xxxx.s。启动代码作用一般是:
1) 堆和栈的初始化
包括堆栈的大小,MSP(main stack pointer)值等。MSP的初始值在复位阶段取自存储区的第一个字(即0地址处的值)。
栈Stack: 由编译器自动分配和释放,存放函数的参数值、局部变量的值等,其操作方式类似于数据结构中的栈。向低地址扩展。
堆Heap: 一般由程序员分配和释放,若程序员不释放,程序结束时可能由操作系统回收。分配方式类似于数据结构中的链表。向高地址扩展。
2)向量表定义
定义了各MSP初值,以及各个中断服务程序(ISR)的入口地址。
3)中断服务程序
CPU根据中断号从向量表中获取入口地址后,跳转至对应的ISR
4)设置系统时钟频率
可在复位中断服务程序Reset_handler中调用系统时钟频率初始化程序
5) 中断寄存器的初始化
6)进入C应用程序
在复位中断服务程序即Reset_handler中实现进入C程序。如:
LDR R0 , =_main ;使用“=”表示LDR目前是伪指令。这里是把_main的地址给R0。
BX R0 ; BX是ARM指令集和THUMB指令集之间程序的跳转。
以下以华大单片机HC32L136K8TA的启动文件为例,对1) — 6)进行详细分析。
;先在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的世界。
HC32L136K8TA芯片的中断向量表如下图所示:
; Stack Configuration; Stack Size (in Bytes) <0x0-0xFFFFFFFF:8>Stack_Size EQU 0x00000200 ;定义堆栈大小AREA STACK, NOINIT, READWRITE, ALIGN=3 ;定义一个数据段 按8字节对齐 ;AREA 伪指令用于定义一个代码段或数据段 ;NOINIT:指定此数据段仅仅保留了内存单元, ;而没有将各初始值写入内存单元, ;或者将各个内存单元值初始化为0Stack_Mem SPACE Stack_Size ;保留Stack_Size大小的堆栈空间分配连续 ;Stack_Size字节的存储单元并初始化为 0 __initial_sp ;标号,代表堆栈顶部地址,后面有用; Heap Configuration; Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>Heap_Size EQU 0x00000000 ;定义堆空间大小AREA HEAP, NOINIT, READWRITE, ALIGN=3 ;定义一个数据段,8字节对齐__heap_base ;标号,代表堆末底部地址,后面有用Heap_Mem SPACE Heap_Size ;保留Heap_Size的堆空间 __heap_limit ;标号,代表堆界限地址,后面有用 ;PRESERVE8 指令指定当前文件保持堆栈八字节对齐。 ;它设置 PRES8 编译属性以通知链接器。 ;链接器检查要求堆栈八字节对齐的任何代码是否仅由 ;保持堆栈八字节对齐的代码直接或间接地调用。PRESERVE8 ;指示编译器8字节对齐THUMB ;指示编译器以后的指令为THUMB指令; Vector Table Mapped to Address 0 at Reset ;中断向量表定义AREA RESET, DATA, READONLY;定义只读数据段,其实放在CODE区,位于0地址EXPORT __Vectors ;EXPORT:在程序中声明一个全局的标号__Vectors, ;该标号可在其他的文件中引用EXPORT __Vectors_EndEXPORT __Vectors_Size__Vectors DCD __initial_sp ;Top of Stack;给__initial_sp分配4字节32位的地址0x0DCD Reset_Handler ;Reset ; 给标号Reset Handler分配地址为0x00000004 DCD NMI_Handler ; NMI; 给标号NMI Handler分配地址0x00000008DCD HardFault_Handler ; Hard FaultDCD 0 ; Reserved ; 这种形式就是保留地址,不给任何标号分配DCD 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 PORTA_IRQHandlerDCD PORTB_IRQHandlerDCD PORTC_IRQHandlerDCD PORTD_IRQHandlerDCD DMAC_IRQHandlerDCD TIM3_IRQHandlerDCD UART0_IRQHandlerDCD UART1_IRQHandlerDCD LPUART0_IRQHandlerDCD LPUART1_IRQHandlerDCD SPI0_IRQHandlerDCD SPI1_IRQHandlerDCD I2C0_IRQHandlerDCD I2C1_IRQHandlerDCD TIM0_IRQHandlerDCD TIM1_IRQHandlerDCD TIM2_IRQHandlerDCD LPTIM_IRQHandlerDCD TIM4_IRQHandlerDCD TIM5_IRQHandlerDCD TIM6_IRQHandlerDCD PCA_IRQHandlerDCD WDT_IRQHandlerDCD RTC_IRQHandlerDCD ADC_IRQHandlerDCD PCNT_IRQHandlerDCD VC0_IRQHandlerDCD VC1_IRQHandlerDCD LVD_IRQHandlerDCD LCD_IRQHandlerDCD EF_RAM_IRQHandlerDCD CLKTRIM_IRQHandler __Vectors_End__Vectors_Size EQU __Vectors_End - __VectorsAREA |.text|, CODE, READONLY ;代码段定义; Reset Handler ;利用PROC、ENDP这一对伪指令把程序段分为若干 ;个过程,使程序的结构加清晰Reset_Handler PROC ;过程的开始EXPORT Reset_Handler [WEAK] ;[WEAK] 弱定义,意思是如果在别处也定义该标号 ;(函数),在链接时用别处的地址。 如果没有其它地方 ;定义,编译器也不报错,以此处地址进行链接。 ;EXPORT提示编译器该标号可以为外部文件引用。IMPORT SystemInit ;代码中的系统初始化程序IMPORT __main ;通知编译器要使用的标号在其他文件;reset NVIC if in rom debug LDR 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,R2RAMCODE; reset Vector table address.LDR R0, =0xE000ED08STR R2, [R0]LDR R0, =SystemInit BLX R0 ;BX是ARM指令集和THUMB指令集之间程序的跳转 LDR R0, =__main ;使用“=”表示LDR目前是伪指令不是标准指令。 ;这里是把__main的地址给RO。BX R0 ;BX是ARM指令集和THUMB指令集之间程序的跳转ENDP ;过程的结束; Dummy Exception Handlers (infinite loops which can be modified)NMI_Handler PROCEXPORT NMI_Handler [WEAK]B .ENDPHardFault_Handler\PROCEXPORT HardFault_Handler [WEAK]B . ;原地跳转(即无限循环),等同于while(1);ENDPSVC_Handler PROCEXPORT SVC_Handler [WEAK]B .ENDPPendSV_Handler PROCEXPORT PendSV_Handler [WEAK]B .ENDPSysTick_Handler PROCEXPORT SysTick_Handler [WEAK]B .ENDPDefault_Handler PROCEXPORT PORTA_IRQHandler [WEAK]EXPORT PORTB_IRQHandler [WEAK]EXPORT PORTC_IRQHandler [WEAK]EXPORT PORTD_IRQHandler [WEAK]EXPORT DMAC_IRQHandler [WEAK]EXPORT TIM3_IRQHandler [WEAK]EXPORT UART0_IRQHandler [WEAK]EXPORT UART1_IRQHandler [WEAK]EXPORT LPUART0_IRQHandler [WEAK]EXPORT LPUART1_IRQHandler [WEAK]EXPORT SPI0_IRQHandler [WEAK]EXPORT SPI1_IRQHandler [WEAK]EXPORT I2C0_IRQHandler [WEAK]EXPORT I2C1_IRQHandler [WEAK]EXPORT TIM0_IRQHandler [WEAK]EXPORT TIM1_IRQHandler [WEAK]EXPORT TIM2_IRQHandler [WEAK]EXPORT LPTIM_IRQHandler [WEAK]EXPORT TIM4_IRQHandler [WEAK]EXPORT TIM5_IRQHandler [WEAK]EXPORT TIM6_IRQHandler [WEAK]EXPORT PCA_IRQHandler [WEAK]EXPORT WDT_IRQHandler [WEAK]EXPORT RTC_IRQHandler [WEAK]EXPORT ADC_IRQHandler [WEAK]EXPORT PCNT_IRQHandler [WEAK]EXPORT VC0_IRQHandler [WEAK]EXPORT VC1_IRQHandler [WEAK]EXPORT LVD_IRQHandler [WEAK]EXPORT LCD_IRQHandler [WEAK]EXPORT EF_RAM_IRQHandler [WEAK]EXPORT CLKTRIM_IRQHandler [WEAK]PORTA_IRQHandlerPORTB_IRQHandlerPORTC_IRQHandlerPORTD_IRQHandlerDMAC_IRQHandlerTIM3_IRQHandlerUART0_IRQHandlerUART1_IRQHandlerLPUART0_IRQHandlerLPUART1_IRQHandlerSPI0_IRQHandlerSPI1_IRQHandlerI2C0_IRQHandlerI2C1_IRQHandlerTIM0_IRQHandlerTIM1_IRQHandlerTIM2_IRQHandlerLPTIM_IRQHandlerTIM4_IRQHandlerTIM5_IRQHandlerTIM6_IRQHandlerPCA_IRQHandlerWDT_IRQHandlerRTC_IRQHandlerADC_IRQHandlerPCNT_IRQHandlerVC0_IRQHandlerVC1_IRQHandlerLVD_IRQHandlerLCD_IRQHandlerEF_RAM_IRQHandlerCLKTRIM_IRQHandler B .ENDPALIGN ;填充字节使地址对齐; User Initial Stack & Heap ;堆和栈的初始化IF :DEF:__MICROLIB ;“DEF”的用法:DEF:X就是说X定义了则为真,否则为 ;如果定义了MICORLIB,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 LRALIGN ;填充字节使地址对齐ENDIFEND
编辑:admin 最后修改时间:2020-09-07