游戏电视苹果数码历史美丽
投稿投诉
美丽时装
彩妆资讯
历史明星
乐活安卓
数码常识
驾车健康
苹果问答
网络发型
电视车载
室内电影
游戏科学
音乐整形

GD32开发实战指南第4章GD32启动流程详解(Keil版)

  开发环境:
  MDK:Keil5。30
  开发板:GD32F207IEVAL
  MCU:GD32F207IK
  对于我们常用的桌面操作系统而言,我们在开发应用时,并不关心系统的初始化,绝大多数应用程序是在操作系统运行后才开始运行的,操作系统已经提供了一个合适的运行环境,然而对于嵌入式设备而言,在设备上电后,所有的一切都需要由开发者来设置,这里处理器是没有堆栈,没有中断,更没有外围设备,这些工作是需要软件来指定的,而且不同的CPU类型、不同大小的内存和不同种类的外设,其初始化工作都是不同的。本文将以GD32F207IK(基于CortexM3)为例进行讲解。
  在开始正式讲解之前,你需要了解ARM寄存器、汇编以及反编译相关的知识,这些可以参考笔者博文。
  深入理解ARM寄存器:https:bruceou。blog。csdn。netarticledetails117866186
  ARM汇编入门:https:bruceou。blog。csdn。netarticledetails117897496
  Keil反编译入门(一):https:bruceou。blog。csdn。netarticledetails118314875
  Keil反编译入门(二):https:bruceou。blog。csdn。netarticledetails118400368
  下面我们就来具体看一下用户从Flash启动GD32的过程,主要讲解从上电复位到main函数的过程。主要有以下步骤:
  1。初始化堆栈指针SPinitialsp,初始化PC指针ResetHandler
  2。初始化中断向量表
  3。配置系统时钟
  4。调用C库函数main初始化用户堆栈,然后进入main函数。
  在开始讲解之前,我们需要了解GD32的启动模式。4。1GD32的启动模式
  首先要讲一下GD32的启动模式,因为启动模式决定了向量表的位置,GD32有三种启动模式:
  1)主闪存存储器(MainFlash)启动:从GD32内置的Flash启动(0x080000000x0807FFFF),一般我们使用JTAG或者SWD模式下载程序时,就是下载到这个里面,重启后也直接从这启动程序。以0x08000000对应的内存为例,则该块内存既可以通过0x00000000操作也可以通过0x08000000操作,且都是操作的同一块内存。
  2)系统存储器(SystemMemory)启动:从系统存储器启动(0x1FFFF0000x1FFFF7FF),这种模式启动的程序功能是由厂家设置的。一般来说,我们选用这种启动模式时,是为了从串口下载程序,因为在厂家提供的ISP程序中,提供了串口下载程序的固件,可以通过这个ISP程序将用户程序下载到系统的Flash中。以0x1FFFFFF0对应的内存为例,则该块内存既可以通过0x00000000操作也可以通过0x1FFFFFF0操作,且都是操作的同一块内存。
  3)片上SRAM启动:从内置SRAM启动(0x200000000x3FFFFFFF),既然是SRAM,自然也就没有程序存储的能力了,这个模式一般用于程序调试。SRAM只能通过0x20000000进行操作,与上述两者不同。从SRAM启动时,需要在应用程序初始化代码中重新设置向量表的位置。
  用户可以通过设置BOOT0和BOOT1的引脚电平状态,来选择复位后的启动模式。如下图所示:
  启动模式只决定程序烧录的位置,加载完程序之后会有一个重映射(映射到0x00000000地址位置);真正产生复位信号的时候,CPU还是从开始位置执行。
  值得注意的是GD32上电复位以后,代码区都是从0x00000000开始的,三种启动模式只是将各自存储空间的地址映射到0x00000000中。
  Bootloader存放在系统(System)存储内,可以在MCU启动后对Flash进行再编程。在GD32F20x系列产品中,Bootloader通过USART0与外界交互。
  GD32F20x芯片支持嵌入式引导程序通过多种接口方式来更新Flash。可以有1或2个USART端口和标准USB端口用于GD32F205xx和GD32F207xx互联型产品。如下表所示。
  产品线
  产品
  支持串行外设
  互联型
  GD32F205xx
  USART0(PA9PA10)
  USART1(PD5PD6)
  USB(PA9PA10PA11PA12)
  GD32F207xx
  USART0(PA9PA10)
  USART1(PD5PD6)
  USB(PA9PA10PA11PA12)
  4。2GD32的启动文件分析
  因为启动过程主要是由汇编完成的,因此GD32的启动的大部分内容都是在启动文件里。笔者的启动文件是startupgd32f20xcl。s。4。2。1堆栈定义
  1。Stack栈
  栈的作用是用于局部变量,函数调用,函数形参等的开销,栈的大小不能超过内部SRAM的大小。当程序较大时,需要修改栈的大小,不然可能会出现的HardFault的错误。
  第43行:表示开辟栈的大小为0X00000400(1KB),EQU是伪指令,相当于C中的define。
  第45行:开辟一段可读可写数据空间,ARER伪指令表示下面将开始定义一个代码段或者数据段。此处是定义数据段。ARER后面的关键字表示这个段的属性。段名为STACK,可以任意命名;NOINIT表示不初始化;READWRITE表示可读可写,ALIGN3,表示按照8字节对齐。
  第46行:SPACE用于分配大小等于StackSize连续内存空间,单位为字节。
  第47行:initialsp表示栈顶地址。栈是由高向低生长的。
  2。Heap堆
  堆主要用来动态内存的分配,像malloc()函数申请的内存就在堆中。
  开辟堆的大小为0X00000200(512字节),名字为HEAP,NOINIT即不初始化,可读可写,8字节对齐。heapbase表示对的起始地址,heaplimit表示堆的结束地址。4。2。2向量表
  向量表是一个WORD(32位整数)数组,每个下标对应一种异常,该下标元素的值则是该ESR的入口地址。向量表在地址空间中的位置是可以设置的,通过NVIC中的一个重定位寄存器来指出向量表的地址。在复位后,该寄存器的值为0。因此,在地址0(即FLASH地址0)处必须包含一张向量表,用于初始时的异常分配。
  值得注意的是这里有个另类:0号类型并不是什么入口地址,而是给出了复位后MSP的初值,后面会具体讲解。
  第66行:定义一块代码段,段名字是RESET,READONLY表示只读。
  第6769行:使用EXPORT将3个标识符申明为可被外部引用,声明Vectors、VectorsEnd和VectorsSize具有全局属性。这几个变量在Keil分散加载时会用到。
  第71行:Vectors表示向量表起始地址,DCD表示分配1个4字节的空间。每行DCD都会生成一个4字节的二进制代码,中断向量表存放的实际上是中断服务程序的入口地址。当异常(也即是中断事件)发生时,CPU的中断系统会将相应的入口地址赋值给PC程序计数器,之后就开始执行中断服务程序。在60行之后,依次定义了中断服务程序的入口地址。
  第179行:VectorsEnd为向量表结束地址。
  第181行:VectorsSize则是向量表的大小,向量表的大小是通过Vectors和VectorsEnd相减得到的。
  上述向量表可以在《GD32F20xUserManualENRev2。4》中找到的,笔者这里只截取了部分。
  笔者只截取了部分。4。2。3复位程序
  复位程序是系统上电后执行的第一个程序,复位程序也是中断程序,只是这个程序比较特殊,因此单独提出来讲解。
  第186行:定义了一个服务程序,PROC表示程序的开始。
  第187行:使用EXPORT将ResetHandler申明为可被外部引用,后面WEAK表示弱定义,如果外部文件定义了该标号则首先引用该标号,如果外部文件没有声明也不会出错。这里表示复位程序可以由用户在其他文件重新实现。
  第188189行:表示该标号来自外部文件,SystemInit()是一个库函数,在systemgd32f10x。c中定义的,main是一个标准的C库函数,主要作用是初始化用户堆栈,这个是由编译器完成的,该函数最终会调用我们自己写的main函数,从而进入C世界中。
  第190行:这是一条汇编指令,表示从存储器中加载SystemInit到一个寄存器R0的地址中。R0R3寄存器通常用于函数入参出参或子程序调用。
  第191行:汇编指令,表示跳转到寄存器R0的地址,并根据寄存器的LSE确定处理器的状态,还要把跳转前的下条指令地址保存到LR。
  第192行:和190行是一个意思,表示从存储器中加载main到一个寄存器R0的地址中。
  第193行:和191稍微不同,这里跳转到至指定寄存器的地址后,不会返回。
  第194行:和PROC是对应的,表示程序的结束。
  值得注意的是,这里的main和C语言中的main()不是一样东西,main是Clib中的函数,也就是在Keil中自带的;而main()函数是C的入口,main()会被main调用。4。2。4中断服务程序
  我们平时要使用哪个中断,就需要编写相应的中断服务程序,只是启动文件把这些函数留出来了,但是内容都是空的,真正的中断复服务程序需要我们在外部的C文件里面重新实现,这里只是提前占了一个位置罢了。
  这部分没啥好说的,和服务程序类似的,只需要注意‘B。’语句,B表示跳转,这里跳转到一个‘。’,即表示无线循环。4。2。5堆栈初始化
  堆栈初始化是由一个IF条件来实现的,MICROLIB的定义与否决定了堆栈的初始化方式。
  这个定义是在OptionsTarget中设置的。
  如果没有定义MICROLIB,则会使用双段存储器模式,且声明了userinitialstackheap具有全局属性,这需要开发者自己来初始化堆栈。
  这部分也没啥讲的,需要注意的是,ALIGN表示对指令或者数据存放的地址进行对齐,缺省表示4字节对齐。4。2。6其他
  第62行:PRESERVE8用于指定当前文件的堆栈按照8字节对齐。
  第63行:THUMB表示后面指令兼容THUMB指令。现在CortexM系列的都使用THUMB2指令集,THUMB2是32位的,兼容16位和32位的指令,是THUMB的超集。4。3GD32的启动流程实例分析
  有了前面的分析,接下来就来具体看看GD32启动流程的具体内容。4。3。1初始化SP、PC、向量表
  当系统复位后,处理器首先读取向量表中的前两个字(8个字节),第一个字存入MSP,第二个字为复位向量,也就是程序执行的起始地址。
  这里通过JFlash打开hex文件。
  硬件这时自动从0x08000000位置处读取数据赋给栈指针SP,然后自动从0x08000004位置处读取数据赋给PC,完成了复位操作,SP0x02002008,PC0x080001BD。
  初始化SP、PC紧接着就初始化向量表,如果感觉看HEX文件抽象,我们看看反汇编文件吧。
  是不是更容易些,是不是和《GD32F20xUserManualENRev2。4》中的向量表对应起来了。其实看反汇编文件更好理解GD32的启动流程,只是有些抽象。
  生成反汇编的方法如下。
  在KEIL的User选项中,如下图添加这两项:
  fromelfbinoutputGD32F207IEVAL。bin。。OutputGD32F207IEVAL。axf
  fromelftextacoutputGD32F207IEVAL。dis。。OutputGD32F207IEVAL。axf
  然后重新编译,即可得到二进制文件GD32F207IEVAL。bin(以后会分析)、反汇编文件GD32F207IEVAL。dis。
  如下图所示:
  4。3。2设置系统时钟
  细心的朋友可能发现,PC0x080001BC的地址是没有对齐的。然后在反汇编文件中却是这样的:
  这里是硬件自动对齐到0x080001BC,并执行SystemInit函数初始化系统时钟。
  当然也可通过硬件调试来确认上面的分析:
  接下来就会进入SystemInit函数中。
  SystemInit函数内容如下:!briefsetupthemicrocontrollersystem,initializethesystemparam〔in〕noneparam〔out〕noneretvalnonevoidSystemInit(void){resettheRCCclockconfigurationtothedefaultresetstateenableIRC8MRCUCTLRCUCTLIRC8MEN;RCUCFG0RCUCFG0SCS;resetHXTALEN,CKMEN,PLLENbitsRCUCTL(RCUCTLHXTALENRCUCTLCKMENRCUCTLPLLEN);resetSCS,AHBPSC,APB1PSC,APB2PSC,ADCPSC,CKOUT0SELbitsRCUCFG0(RCUCFG0SCSRCUCFG0AHBPSCRCUCFG0APB1PSCRCUCFG0APB2PSCRCUCFG0ADCPSCRCUCFG0ADCPSC2RCUCFG0CKOUT0SEL);resetHXTALEN,CKMEN,PLLENbitsRCUCTL(RCUCTLHXTALENRCUCTLCKMENRCUCTLPLLEN);ResetHXTALBPSbitRCUCTL(RCUCTLHXTALBPS);resetPLLSEL,PREDV0LSB,PLLMF,USBFSPSCbitsRCUCFG0(RCUCFG0PLLSELRCUCFG0PREDV0LSBRCUCFG0PLLMFRCUCFG0USBFSPSCRCUCFG0PLLMF4);resetPLL1ENandPLL2ENbitsRCUCTL(RCUCTLPLL1ENRCUCTLPLL2EN);resetCFG1registerRCUCFG10x00000000U;resetINTregisterRCUINT0x00FF0000U;resetCFG2registerRCUCFG20x00000000U;resetPLLTCTLregisterRCUPLLTCTL(RCUPLLTCTLPLLTEN);resetPLLTINTregisterRCUPLLTINT0x00400000U;resetPLLTCFGregisterRCUPLLTCFG0x20003010U;configurethesystemclocksource,PLLmultiplier,AHBAPBxprescalersandflashsettingssystemclockconfig();}
  前面部分是配置时钟的,具体参考手册吧。
  systemclockconfig()函数用于时钟初始化,这里使用的是HXTAL,HXTAL进过通过PLL锁相环后为120MHz。
  systemclockconfig()函数调用的systemclock120mhxtal()函数,其如下:!briefconfigurethesystemclockto120MbyPLLwhichselectsHXTAL(8M)asitsclocksourceparam〔in〕noneparam〔out〕noneretvalnonestaticvoidsystemclock120mhxtal(void){uint32ttimeout0U;uint32tstabflag0U;enableHXTALRCUCTLRCUCTLHXTALEN;waituntilHXTALisstableorthestartuptimeislongerthanHXTALSTARTUPTIMEOUTdo{timeout;stabflag(RCUCTLRCUCTLHXTALSTB);}while((0Ustabflag)(HXTALSTARTUPTIMEOUT!timeout));iffailif(0U(RCUCTLRCUCTLHXTALSTB)){while(1){}}HXTALisstableAHBSYSCLKRCUCFG0RCUAHBCKSYSDIV1;APB2AHB1RCUCFG0RCUAPB2CKAHBDIV1;APB1AHB2RCUCFG0RCUAPB1CKAHBDIV2;CKPLL(CKPREDIV0)10120MHzRCUCFG0(RCUCFG0PLLMFRCUCFG0PLLMF4RCUCFG0PREDV0LSBRCUCFG0PLLSEL);RCUCFG0(RCUPLLSRCHXTALRCUPLLMUL10);CKPREDIV0(CKHXTAL)512512MHzRCUCFG1(RCUCFG1PREDV0SELRCUCFG1PLL1MFRCUCFG1PREDV1RCUCFG1PREDV0);RCUCFG1(RCUPREDV0SRCCKPLL1RCUPLL1MUL12RCUPREDV1DIV5RCUPREDV0DIV5);enablePLL1RCUCTLRCUCTLPLL1EN;waittillPLL1isreadywhile((RCUCTLRCUCTLPLL1STB)0U){}enablePLLRCUCTLRCUCTLPLLEN;waituntilPLLisstablewhile(0U(RCUCTLRCUCTLPLLSTB)){}selectPLLassystemclockRCUCFG0RCUCFG0SCS;RCUCFG0RCUCKSYSSRCPLL;waituntilPLLisselectedassystemclockwhile(0U(RCUCFG0RCUSCSSPLL)){}}4。3。3初始化堆栈并进入main
  执行指令LDRR0,main,然后就跳转到main程序段运行,当然这里指标准库的main函数。
  这中间初始化了栈区。
  这段代码是个循环(BCC0x080001e6),实际运行时候循环了两次。第一次运行的时候,读取加载数据段的函数的地址并跳转到该函数处运行(注意加载已初始化数据段和未初始化数据段用的是同一个函数);第二次运行的时候,读取初始化栈的函数的地址并跳转到该函数处运行。
  最后就进入C文件的main函数中,至此,启动过程到此结束。
  最后,总结下GD32从flash的启动流程。
  MCU上电后从0x08000000处读取栈顶地址并保存,然后从0x08000004读取中断向量表的起始地址,这就是复位程序的入口地址,接着跳转到复位程序入口处,初始向量表,然后设置时钟,设置堆栈,最后跳转到C空间的main函数,即进入用户程序。
  启动流程1(使用标准库,不使用Microlib)如下图:
  启动流程2(使用Microlib)如下图:

把新疆旅游向全国广而告之石榴云新疆日报记者贾春霞自治区旅游发展大会提出,加大开放力度,激发消费潜力。积极引客入疆,有序恢复跨省团队旅游,大力推动援疆省市游客送疆,鼓励新疆人游新疆,活跃旅游消费市……精致,是女人的尊严唐瑛作为20世纪二三十年代旧上海的一颗闪耀的明星,她与陆小曼齐名,素有南唐北陆之名。她活的自在潇洒,在那个美女云集的花花城市,她从来都是最受瞩目的那个,纵然岁月的轻霜爬上脸颊,……4000万年薪买不来快乐!跟腱断裂母亲去世!NBA状元郎想自北京时间9月23日,约翰沃尔在球员论坛上发布了一篇叫做I’MSTILLHERE(我仍然在这里)的文章。沃尔写道:在受到的三年时间里,我从站在世界之巅,到失去了几乎所有我在乎的东……乱世枭雄张作霖(73)勇击土匪升哨长釜山战场放光芒老土匪钱汝秉,得知五龙背山下有军队的弹药库,那要夺过来,够他们绺子用几年呀!因此到了晚上,就带人来抢。等到弹药库边上了,一瞅周围静悄悄的,只有几个巡逻的哨兵。前面不远是兵……2022下半年买手机眼光要放长远,一步到位选这三款,轻松用五2022下半年买手机眼光要放长远,一步到位选这三款,轻松用五年第一款:华为Mate504999起华为Mate50是这次Mate50系列性价比最高的一款手机,搭载骁龙8Ge……一张1947年的沂南县妇女干部合影2014年,我和刘维常编纂《沂蒙红嫂志》一书时,发现了一张沂南县妇女干部的老照片。由于相似的工作性质,看到这张老照片时,我俩还是比较激动的,激动之余,便有了进一步厘清这些人物的……辛者库籍你以为我很卑微,实际上恐怕普通人还攀不上在很多的清宫剧当中,都有这么一句台词,叫做罚入辛者库服苦役,让很多的人觉得辛者库就是个干粗活尤其是洗衣服受罚的地方,如果被罚进了辛者库,肯定就是犯错了,尤其是雍正年间成书的《清……少年正义行遐延秋水长日暗频风雨,夜深喚晨光。怀宁二少年,觉醒树理想。西行取真经,东返当主将。南下拯劳众,北上斗凶殃。献身抛六欲,矢志排九难。……蔡文姬曹操一生中的挚爱曹操负了天下人,却独不负一人,那便是蔡文姬。曹操年少时拜蔡邕为师,结识了师妹蔡文姬,此时蔡文姬青春靓丽,才华出众,让曹操一见倾心。然而曹操是宦官的后代,与蔡文姬身份悬殊,……特斯拉12月购现车享4000元补贴,集中精力供国内市场比亚迪作者韩哲熙;编辑张麟36氪ToB产业组会为大家汇总每日新能源领域的主要新闻,12月2日新能源行业动态日报如下:新能源应用风向特斯拉:12月购现车享4000元补贴,集……曹操为何那么佩服孙权?曹丕一直不懂,直到他称帝后才亲眼看到公元212年,曹操为报赤壁战败之仇,亲率大军40万与孙权对峙于安徽省濡须口。可是曹操看到孙权兵船上刀枪耀眼,防守森严,士气旺盛,找不出一点儿破绽,于是发出了他那句著名的感慨:生……西汉的豪强西汉初年,因为战争产生了许多军事贵族,他们掌握了执政权。新兴贵族,世袭爵位有食邑,在官僚体系中占据要职。刘邦重建封国体系,并约定非刘姓不得封王。军事贵族与刘氏封王相……
答应了就去办!咱重情重义的山东人又一次被全网怒赞一个发生在素不相识的陌生人之间的温暖故事,登上了微博热搜榜,被全网怒赞山东人太实在了在山东这叫事儿吗?答应了就去办,给警察叔叔和青岛人点赞。一件小事儿,毫无保留地展现出了山东人……1945年毛主席为何坚持要罗荣桓去东北?事实证明他的能力无人一、他躺在担架上去了东北从1943年初开始,罗荣桓的身体就很不好,去年12月18日,山东军区部队讨伐降日的汉奸孙焕彩部,战斗打得不太顺利,罗荣桓闻讯后亲自赶到前线召集指挥……重磅!全国政协委员上交所总经理蔡建春最新发声,三份提案信息量点蓝字关注,不迷路建立信息互通机制,严防上市公司财务造假;推动央企更好利用资本市场做强做优做大;推动指数基金纳入个人养老金配置范围全国政协委员、上交所总经理蔡建春两会提案紧扣资……补310话股市沙伊言和,硅谷破产,印花税有消息了,周末满热闹这个周末满热闹的,10号的评论留到周末来写,上周大盘走势确实有点恼火。沙伊在北京言和,也算一个伟大突破,外国网友评价论亮了。硅谷银行破产,这个觉得有点阴谋论了,这个……读史忆人典故制度成熟需要时间政党协商是我国新型政党制度的重要内容。图为2021年2月,习近平、汪洋、韩正等在北京人民大会堂同党外人士欢聚一堂,共迎佳节。一套制度的成熟,究竟是一蹴而就的突变结果,还是……汪精卫被刺后,150瓶强心针未救回刺客,余生被一颗子弹折磨九1934年10月的一天,在南京秦淮区望鹤楼2号,华克之拿着一块木牌挂在大门外,牌子上写着几个字晨光通讯社。接着,华克之回到屋内,把门窗关严,转身对屋里的几个人说:师父如今……石门白云镇中心幼儿园聚焦前书写分享促提升科教新报新湖南客户端讯(通讯员陈思洁覃业彦)请你们猜猜,这是哪个小朋友的名字?看着图片上孩子画的一个圆鼓鼓的梨、一个可爱的笑脸和一只憨憨的鸭子,老师们有点懵,你一言我一语地开始……曼瑜天雅日常生活为什么有人长相一般,却流露一种高级感?第一点:看气质。人的气质由内而外散发出来,是人拥有独特风格的内在体现,气质好是通往高级感的必要条件。气质好的人,即使就穿了一件普通的衣服,也能营造出恬静淡然又温文尔雅的气质,显……两个儿子被冤杀?老同学进京向毛主席喊冤,伟人做法让人佩服1955年5月,一位老人来到中南海门前,在被门口警卫阻拦后,拿出了一封信,落款赫然便是毛主席的名字。警卫急忙向正在办公的毛主席汇报了此事,当听到老人的名字后,他的脸上露出了回忆……皇帝派70岁大臣平叛,大臣喊拿锄头的人一律无罪!叛乱迅速平定汉宣帝继位初期,渤海郡连年大旱,灼热的阳光将大地都晒出了裂缝,田地里的禾苗已经成了枯黄的枝叶,百姓的脸色也渐渐如禾苗一般变得枯黄,在以庄稼为本的农业社会,失去了这些赖以生存的禾……1946年,北京白云观住持,为何被36位道士,绑在树上活活烧谈及道教史上烧死住持的孤例,说是耸人听闻倒不如说这是一桩冤有头,债有主的案子。对于住持被道士们合伙烧死这件事单从传闻听上去是有些罔顾人伦,但细究其中缘由却不由得令人感叹唏……想必大家的头发已经很长了,但是大家想过没有正月为什么不能剃头如果说正月理发死舅舅,那么正月植发是不是可以帮助舅舅延年益寿?关于正月不剃头这个习俗,坊间传言很多,其中影响最大的当属清朝剃发令引发的思旧说。话说顺治四年正月,满清……
友情链接:易事利快生活快传网聚热点七猫云快好知快百科中准网快好找文好找中准网快软网