本文启动文件位STM32G030的启动文件(。s为结尾的文件),其他型号单片机大同小异,可以直接参考。 我们先来看下启动文件的,开头说明;;FileName:startupstm32g030xx。s;Author:MCDApplicationTDescription:STM32G030xxdevicesvectortableforMDKARMtoolchain。;Thismoduleperforms:;SettheinitialSP;SettheinitialPCResetHSetthevectortableentrieswiththeexceptionsISRBranchestomainintheClibrary(callsmain())。;AfterResettheCortexM0processorisinThreadmode,;priorityisPrivileged,andtheStackissettoMain。;UseConfigurationWizardinContextM;;Copyright(c)2019STMicroelectronics。Allrightsreserved。;;ThissoftwarecomponentislicensedbySTunderApacheLicense,Version2。0,;theLYLicense。YoumayobtainacopyoftheLicenseat:;opensource。orglicensesApache2。0;;;Amountofmemory(inbytes)allocatedforSThStackCoStackSize(inBytes)0x00xFFFFFFFF:8;h1、说明 说明里除了版权的声明外主要说明了启动文件的主要功能: 1)设置堆栈指针SPinitialsp。 2)设置PC指针ResetHandler。 3)设置中断向量表。 4)配置系统时钟。 5)配置外部SRAMSDRAM用于程序变量等数据存储(这是可选的)。 6)跳转到C库中的main,最终会调用用户程序的main()函数。 CortexM内核处理器复位或者上电后,处于线程模式,指令权限为最高级别的特权级别,堆栈设置为使用主堆栈MSP。2、启动流程 单片机在复位或者重新上电之后,CPU首先将0X08000000位置存放的堆栈栈顶地址存放到SP中(MSP),当然这个的前提是我们的程序存储到了flash里。之后将0X08000004位置存放的向量地址放入PC程序计数器中。 这时候CPU从PC寄存器指向的地址取出指令并执行,这个执行的程序是复位中断的服务程序ResetHandler。 复位中断服务程序中调用了SystemInit()函数,这个函数的作用是配置系统时钟、配置FMC总线上的外部SRAMSDRAM。调用完SystemInit()函数之后,跳转到了C库中的main函数。这个时候任务就交给了C库中的main函数,main函数对用户的程序进行初始化操作,然后main函数会调用我们自己写的main函数执行程序。3、程序分析StackSizeEQU0x400AREASTACK,NOINIT,READWRITE,ALIGN3StackMemSPACEStackSizeinitialsp 1)这里EQU是个伪指令,和我们C中的define比较像,编译器编译不会生成二进制代码。0X400表示栈的大小。 2)AREASTACK,NOINIT,READWRITE,ALIGN3这句话表示,下面开始定义一个代码段或者数据段。此处是定义数据段。AREA后面的关键字表示这个段的属性。 STACK:这个是代表这个数据段的名字,当然我们可以取任意名字。 NOINIT:表示此数据段不需要填入初始数据。 READWRITE:表示此段可读可写。 ALIGN3:表示首地址按照2的3次方对齐,即按照8字节对齐(地址对8求余数等于0)。 4)SPACE这行指令告诉编译器给STACK(前面命名的名称)段分配0x00000400字节的连续内存空间。 5)initials表示了栈顶地址。initialsp只是一个标号,标号主要用于表示一片内存空间的某个位置,等价于C语言中的地址概念。地址仅仅表示存储空间的一个位置,从C语言的角度来看,变量的地址,数组的地址或是函数的入口地址在本质上并无区别。HeapSizeEQU0x200AREAHEAP,NOINIT,READWRITE,ALIGN3heapbaseHeapMemSPACEHeapSizeheaplimit 6)这部分代码实现开辟堆(heap)空间,主要用于动态内存分配,也就是说用malloc,calloc,realloc等函数分配的变量空间是在堆上。这里和上面的类似,首先分配一片连续的内存空间这里的名字叫HEAP,即分配堆的空间,大小是0X200。heapbase表示堆的开始地址。heaplimit表示堆的结束地址(只是标号)。PRESERVE8THUMB;VectorTableMappedtoAddress0atResetAREARESET,DATA,READONLYEXPORTVectorsEXPORTVectorsEndEXPORTVectorsSize 7)PRESERVE8指定当前文件保持堆栈八字节对齐。 8)THUMB表示后面的指令是THUMB指令集,我们的内核使用的THUMB指令集。 9)AREA定义一块代码段,只读,段名字是RESET。 10)EXPORT语句将3个标号申明为可被外部引用,主要提供给链接器用于连接库文件或其他文件。VectorsDCDTopofStackDCDResetHResetHandlerDCDNMIHNMIHandlerDCDHardFaultHHardFaultHandler此处省略若干代码DCD0;ReservedDCDRTCTAMPIRQHRTCthroughEXTILineDCDFLASHIRQHFLASHDCDRCCIRQHRCCDCDEXTI01IRQHEXTILine0and1DCDEXTI23IRQHEXTILine2and3DCDEXTI415IRQHEXTILine4to15此处省略若干代码DCDI2C1IRQHI2C1DCDI2C2IRQHI2C2DCDSPI1IRQHSPI1DCDSPI2IRQHSPI2DCDUSART1IRQHUSART1此处省略若干代码 11)我们可以看到这里就是我们的中断向量表了,DCD表示分配1个4字节的空间。每行DCD都会生成一个4字节的二进制代码。中断向量表存放的实际上是中断服务程序的入口地址。当异常(也即是中断事件)发生时,CPU的中断系统会将相应的入口地址赋值给PC程序计数器,之后就开始执行中断服务程序。这里地址定义到了代码断的最前面。具体的物理地址由链接器的配置参数(IROM1的地址)决定。我们的程序在Flash运行,中断向量表的起始地址是0x08000000。 VectorsSizeEQUVectorsEndVectorsAREA。text,CODE,READONLY;ResethandlerroutineResetHandlerPROCEXPORTResetHandler〔WEAK〕IMPORTmainIMPORTSystemInitLDRR0,SystemInitBLXR0LDRR0,mainBXR0ENDP 12)AREA定义一块代码段,只读,段名字是。text。READONLY表示只读。 13)利用PROC、ENDP这一对伪指令把程序段分为若干个过程,使程序的结构加清晰。 14)WEAK声明其他的同名标号优先于该标号被引用,就是说如果外面声明了的话会调用外面的。这个声明很重要,它让我们可以在C文件中任意地方放置中断服务程序,只要保证C函数的名字和向量表中的名字一致即可。 15)IMPORT:伪指令用于通知编译器要使用的标号在其他的源文件中定义。但要在当前源文件中引用,而且无论当前源文件是否引用该标号,该标号均会被加入到当前源文件的符号表中。 16)SystemInit函数,主要实现RCC相关寄存器复位和中断向量表位置设置。 17)main标号表示CC标准实时库函数里的一个初始化子程序main的入口地址。该程序的一个主要作用是初始化堆栈(跳转userinitialstackheap标号进行初始化堆栈的,下面会讲到这个标号),并初始化映像文件,最后跳转到C程序中的main函数。这就解释了为何所有的C程序必须有一个main函数作为程序的起点。因为这是由CC标准实时库所规,并且不能更改。NMIHandlerPROCEXPORTNMIHandler〔WEAK〕省略若干EXPORTTIM14IRQHandler〔WEAK〕EXPORTTIM16IRQHandler〔WEAK〕EXPORTTIM17IRQHandler〔WEAK〕EXPORTI2C1IRQHandler〔WEAK〕EXPORTI2C2IRQHandler〔WEAK〕EXPORTSPI1IRQHandler〔WEAK〕EXPORTSPI2IRQHandler〔WEAK〕EXPORTUSART1IRQHandler〔WEAK〕EXPORTUSART2IRQHandler〔WEAK〕 18)死循环,用户可以在此实现自己的中断服务程序。不过很少在这里实现中断服务程序,一般多是在其它的C文件里面重新写一个同样名字的中断服务程序,因为这里是WEEK弱定义的。如果没有在其它文件中写中断服务器程序,且使能了此中断,进入到这里后,会让程序卡在这个地方。IF:DEF:MICROLIBEXPORTinitialspEXPORTheapbaseEXPORTheaplimitELSEIMPORTusetworegionmemoryEXPORTuserinitialstackheapuserinitialstackheapLDRR0,HeapMemLDRR1,(StackMemStackSize)LDRR2,(HeapMemHeapSize)LDRR3,StackMemBXLRALIGNENDIFEND 19)简单的汇编语言实现IF。ELSE语句。如果定义了MICROLIB,那么程序是不会执行ELSE分支的代码。MICROLIB在MDK的TargetOption里面设置。userinitialstackheap由main函数进行调用。