GD32 startup.s
.Cortex-M3内核规定,起始地址必须存放堆顶指针,而第二个地址则必须存放复位中断入口向量地址,这样在Cortex-M3的的内核复位后,会自动从起始地址的下一个32位空间取出复位中断入口向量,跳转执行复位中断服务程序。对比ARM7 / 一个RM9内核,Cortex-M3内核则是固定了中断向量表的位置而起始地址是可变化的。
<----------------------- - ------------------------------分割线----------------- - ------------------------------------>
我所用到的芯片是GD32,GD32是我国兆易创新公司生产的完全兼容STM32系列的Cortex-M3处理器,具有几大亮点:1,高主频108MHz。性能提升30%以上,可超频到120MHz 2,Flash零等待.STM32的72MHz需要两个等待,其实兆易创新公司本来就是做闪存起家的,具有gFlash专利
3,采用ARM Cortex-M3新内核R2p1 .STM32采用R1p1,带有一些缺陷4,性价比高.GD32比对应的STM32芯片一般便宜20%,某些芯片便宜30%以上
一下是相对于STM32的对比:可以参考文档:GD32与STM32区别点击打开链接
看完这个文档,对于我们新手来说,一定是一头雾水,那么接下来我们来细细研究一下他的启动文件的的的Startup.s。
<------------------------------------------------- -----分割线------------------------------------------- ----------->
一,启动文件的作用(关于启动代码的作用,前面已经提到过了,这里再啰嗦一下)
(1)初始堆栈指针SP;
(2)初始化程序计数器指针PC;
(3)设置堆,栈的大小;
(4)设置异常向量表的入口地址;
(5)配置外部SRAM作为数据存储器(这个由用户配置,一般的开发板可没有外部SRAM);
(6)设置Ç库的分支入口__main最终用来调用主函数);
(7)在3.5版的启动文件还调用了system_stm32f10x.c文件中的SystemIni()函数配置系统时钟。
二,启动代码详解
1,栈 - 栈
Stack_Size EQU 0x400
AREA STACK,NOINIT,READWRITE,ALIGN = 3
Stack_Mem空间Stack_Size
__initial_sp
分配名为STACK,不初始化,可读可写,8(2 ^ 3)字节对齐的1KB空间。
栈:局部变量,函数形参等栈的大小不能超过内部SRAM大小。
AREA:汇编一个新的代码段或者数据段
堆栈段名,任意命名; NOINIT表示不初始化;
READWRITE可读可写;
ALIGN = 3(2 ^ 3 = 8字节对齐)。
__initial_sp紧挨了空间放置,表示栈的结束地址,栈是从高往低生长,结束地址就是栈顶地址。
&Stack_Size EQU 0x400定义了栈的大小,EQU相当于汇编中的宏定义,堆栈备选0x400,即16 ^ 2 * 4 = 1024字节
&Stack_Mem SPACE Stack_Size分配连续的Stack_Size字节的存储单元并初始化为0
&__ initial_sp标号,表示堆栈顶部位置
2,堆
Heap_Size等于0x400
AREA HEAP,NOINIT,READWRITE,ALIGN = 3
__heap_base
Heap_Mem空间Heap_Size
__heap_limit
分配名为HEAP,不初始化,可读可写,8(2 ^ 3)字节对齐的512字节空间.__ heap_base的堆的起始地址,__ heap_limit堆的结束地址。堆由低向生长。动态分配内存用到堆
&Heap_Size EQU 0x400定义了堆的大小,大小和栈相同为1K
&__ heap_base的标号,代表堆栈底部地址__heap_limit标号,代表堆栈限制地址
& PRESERVE8指示编译器8字节对齐.PRESERVE8指令指定当前文件保存堆栈八字节对齐。它设置PRES8编译属性以通知链接器。链接器检查要求堆栈八字节对齐的任何代码是否仅由保持堆栈八字节对齐的代码直接或间接地调用。
&拇指表示后面指令兼容THUMB指令.THUBM是ARM以前的指令集,16位,现在的Cortex-M系列的都使用拇指2指令集,拇指2是32位的,兼容16位和32位的指令,是拇指的超级。
3,定位中断向量表
; 带有例外ISR地址的向量表条目
区域重置,数据,READONLY
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size
__Vectors DCD __initial_sp; 堆栈顶部
DCD Reset_Handler; 矢量编号1,复位处理程序
DCD NMI_Handler; 矢量数字2,NMI处理程序
DCD HardFault_Handler; 矢量编号3,硬故障处理程序
DCD MemManage_Handler; 矢量编号4,MPU故障处理程序
DCD BusFault_Handler; 矢量编号5,总线故障处理程序
DCD UsageFault_Handler; 向量编号6,使用错误处理程序
DCD 0; 保留的
DCD 0; 保留的
DCD 0; 保留的
DCD 0; 保留的
DCD SVC_Handler; 矢量编号11,SVCall处理程序
DCD DebugMon_Handler; 矢量编号12,调试监视器处理程序
DCD 0; 保留的
DCD PendSV_Handler; 矢量编号14,PendSV处理程序
DCD SysTick_Handler; 矢量编号15,SysTick处理程序
; 外部中断
DCD WWDG_IRQHandler; 矢量数16,窗口看门狗
DCD LVD_IRQHandler; 矢量编号17,通过EXTI线路检测的LVD
DCD RTC_IRQHandler; 矢量编号18,RTC通过EXTI线
DCD FMC_IRQHandler; 矢量数字19,FMC
DCD RCC_IRQHandler; 矢量编号20,RCC
DCD EXTI0_1_IRQHandler; 矢量编号21,EXTI线0和EXTI线1
DCD EXTI2_3_IRQHandler; 矢量编号22,EXTI线2和EXTI线3
DCD EXTI4_15_IRQHandler; 矢量编号23,EXTI Line 4到EXTI Line 15
DCD TSI_IRQHandler; 矢量编号24,TSI
DCD DMA1_Channel1_IRQHandler; 向量编号25,DMA1通道1
DCD DMA1_Channel2_3_IRQHandler; 向量编号26,DMA1通道2和DMA1通道3
DCD DMA1_Channel4_5_IRQHandler; 向量编号27,DMA1通道4和DMA1通道5
DCD ADC1_CMP_IRQHandler; 矢量编号28,ADC1和比较器1-2
DCD TIMER1_BRK_UP_TRG_COM_IRQHandler; 向量编号29,TIMER1中断,更新,触发器和换向
DCD TIMER1_CC_IRQHandler; 向量编号30,TIMER1捕获比较
DCD TIMER2_IRQHandler; 矢量编号31,TIMER2
DCD TIMER3_IRQHandler; 矢量数32,TIMER3
DCD TIMER6_DAC_IRQHandler; 矢量编号33,TIMER6和DAC
DCD 0; 保留的
DCD TIMER14_IRQHandler; 矢量编号35,TIMER14
DCD TIMER15_IRQHandler; 矢量编号36,TIMER15
DCD TIMER16_IRQHandler; 矢量编号37,TIMER16
DCD TIMER17_IRQHandler; 矢量编号38,TIMER17
DCD I2C1_EV_IRQHandler; 向量编号39,I2C1事件
DCD I2C2_EV_IRQHandler; 向量编号40,I2C2事件
DCD SPI1_IRQHandler; 矢量编号41,SPI1
DCD SPI2_IRQHandler; 矢量编号42,SPI2
DCD USART1_IRQHandler; 矢量数43,USART1
DCD USART2_IRQHandler; 矢量编号44,USART2
DCD 0; 保留的
DCD CEC_IRQHandler; 矢量编号46,CEC
DCD 0; 保留的
DCD I2C1_ER_IRQHandler; 向量编号48,I2C1错误
DCD 0; 保留的
DCD I2C2_ER_IRQHandler; 向量编号50,I2C2错误
DCD I2C3_EV_IRQHandler; 向量编号51,I2C3事件
DCD I2C3_ER_IRQHandler; 向量编号52,I2C3错误
DCD USB_FS_LP_IRQHandler; 矢量编号53,USB FS LP
DCD USB_FS_HP_IRQHandler; 矢量编号54,USB FS HP
DCD 0; 保留的
DCD 0; 保留的
DCD 0; 保留的
DCD USBWakeUp_IRQHandler; 矢量数字58,USB唤醒
DCD CAN1_TX_IRQHandler; 向量编号59,CAN1 TX
DCD CAN1_RX0_IRQHandler; 向量编号60,CAN1 RX0
DCD CAN1_RX1_IRQHandler; 向量编号61,CAN1 RX1
DCD CAN1_SCE_IRQHandler; 矢量编号62,CAN1 SCE
DCD LCD_IRQHandler; 矢量编号63,LCD
DCD DMA1_Channel6_7_IRQHandler; 向量编号64,DMA1通道6和通道7
DCD 0; 保留的
DCD 0; 保留的
DCD SPI3_IRQHandler; 矢量编号67,SPI3
DCD 0; 保留的
DCD 0; 保留的
DCD 0; 保留的
DCD 0; 保留的
DCD 0; 保留的
DCD 0; 保留的
DCD 0; 保留的
DCD 0; 保留的
DCD 0; 保留的
DCD 0; 保留的
DCD 0; 保留的
DCD 0; 保留的
DCD 0; 保留的
DCD 0; 保留的
DCD 0; 保留的
DCD 0; 保留的
DCD 0; 保留的
DCD 0; 保留的
DCD CAN2_TX_IRQHandler; 矢量编号86,CAN2 TX
DCD CAN2_RX0_IRQHandler; 向量编号87,CAN2 RX0
DCD CAN2_RX1_IRQHandler; 向量编号88,CAN2 RX1
DCD CAN2_SCE_IRQHandler; 矢量编号89,CAN2 SCE
空间0x5A
__Vectors_End
__Vectors_Size EQU __Vectors_End - __Vectors
& 定义一个名为RESET,可读的数据段。并声明__Vectors,__ Vectors_End和__Vectors_Size这三个标号可被外部的文件使用。
&_Vectors为向量表起始地址,__ Vectors_End为向量表结束地址,两个相减即可算出向量表大小。
&向量表从FLASH的0地址开始放置,以4个字节为一个单位,地址0存放的是栈顶地址,0X04存放的是复位程序的地址,以此类推。从代码上看,向量表中存放的都是中断服务函数的函数名,可我们知道ç语言中的函数名就是一个地址。
4,复位程序
AREA | .text |,CODE,READONLY
&定义了一个名为的.text,可读的代码段
; 重置处理程序
Reset_Handler PROC
EXPORT Reset_Handler [弱]
IMPORT __main
IMPORT System_Init
LDR R0,= System_Init
BLX R0
LDR R0,= __ main
BX R0
ENDP
SystemInit()函数初始系统时钟,然后调用C库函数_main。复位中断(复位入口矢量被硬件固定在地址0x0000_0004)的处理函数:复位子程序是系统上电后第一个执行的程序,Reset_Handler,它的作用就是将保存于闪存中的初始化数据复制到SRAM中,调用上面说到的SystemInit来初始化时钟,接着跳转到主执行。
__main()是编译系统提供的一个函数,负责完成库函数的初始化和初始化应用程序执行环境,最后自动跳转到主()
5,终端服务子程序
NMI_Handler PROC
EXPORT NMI_Handler [弱]
B.
ENDP
HardFault_Handler PROC
EXPORT HardFault_Handler [弱]
B.
ENDP
MemManage_Handler PROC
EXPORT MemManage_Handler [WEAK]
B.
ENDP
&启动文件里面已经帮我们写好所有中断的中断服务函数,跟我们平时写的中断服务函数不一样的就是这些函数都是空的,真正的中断复服务程序需要我们在外部的Ç文件里面重新实现,这里只是提前占了一个位置而已。如果我们在使用某个外设的时候,开启了某个中断,但是又忘记编写配套的中断服务程序或者函数名写错,那当中断来临的时,程序就会跳转到启动文件预先写好的空的中断服务程序中,并且在这个空函数中无线循环,即程序就死在这里。
&B:“”跳到一个,表示无限循环.B表示将程序流程转移到另一个程序中
&Default_Handler,这个是作为其他所有中断的默认处理函数,作用就是死循环,所以你假如开启了某个中断,请按照这里面的中断函数名给它写中断处理函数,例如串口中断处理函数名是USART1_IRQHandler ,你开了串口中断,如果不重写USART1_IRQHandler,就默认执行Default_Handler,死循环了。而如果你有重写,那么中断向量表中的处理函数的地址就会更新为你自己写的那个函数的地址了。
如图6所示,用户堆栈初始化
ALIGN
; ******************* *******************************
; 用户堆栈和堆初始化
; ******************* ******************************
如果:DEF:__ MICROLIB
EXPORT __initial_sp
EXPORT __heap_base
EXPORT __heap_limit
其他
导入__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
万一
结束
&ALIGN:对指令或者数据存放的地址进行对齐,后面会跟一个立即数缺省表示4字节对齐。
&判断是否定义了__MICROLIB,如果定义了则赋予标号__initial_sp(栈顶地址),__ heap_base(堆起始地址),__ heap_limit(堆结束地址)全局属性,可供外部文件调用。如果没有定义的__MICROLIB)则使用默认的ç库,然后初始化用户堆栈大小,这部分有ç库函数__main来完成。
&__ user_initial_stackheap,此处是初始化两区的堆栈空间,堆是从低到高的增长,堆栈是由高向低生长的,两个是互相独立的数据段,并且不能交叉使用。
&LDR R0,= HeapMem //保存堆始地址
LDR R1,=(StackMem + Stack)//保存栈的大小
LDR R2,=(HeapMem + Heap)//保存堆的大小
LDR R3,= StackMem //保存栈顶指针
至此,启动文件就完成了执行微控制器从“复位”到“开始执行主函数”中间这段时间(称为启动过程)所必须进行的工作。定义了主要的入口,生命了很多中断
以上仅为个人学习整理资料,由不足的地方还请提出批评