完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
微控制器(单片机)上电后,是如何寻找到并执行主函数的呢?很显然微控制器无法从硬件上定位主函数的入口地址,因为使用Ç语言作为开发语言后,变量/函数的地址便由编译器在编译时自行分配,这样一来主函数的入口地址在微控制器的内部存储空间中不再是绝对不变的。相信读者都可以回答这个问题,答案也许大同小异,但肯定都有个关键词,叫“启动文件”,用英文单词来描述是“引导程序”。启动文件的作用便是负责执行微控制器从“复位”到“开始执行主函数”中间这段时间(称为启动过程)所必须进行的工作
.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,复位程序 ; 重置处理程序 &定义了一个名为的.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 //保存栈顶指针 至此,启动文件就完成了执行微控制器从“复位”到“开始执行主函数”中间这段时间(称为启动过程)所必须进行的工作。定义了主要的入口,生命了很多中断 以上仅为个人学习整理资料,由不足的地方还请提出批评 |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1535 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1484 浏览 1 评论
905 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
651 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1530 浏览 2 评论
1841浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
590浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
491浏览 3评论
492浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
472浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-3 00:23 , Processed in 0.626604 second(s), Total 47, Slave 40 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号