当前的嵌入式应用程序开发过程里,并且C语言成为了绝大部分场合的最佳选择。如此一来main函数似乎成为了理所当然的起点——因为C程序往往从main函数开始执行。但一个经常会被忽略的问题是:微控制器(单片机)上电后,是如何寻找到并执行main函数的呢?很显然微控制器无法从硬件上定位main函数的入口地址,因为使用C语言作为开发语言后,变量/函数的地址便由编译器在编译时自行分配,这样一来main函数的入口地址在微控制器的内部存储空间中不再是绝对不变的。相信读者都可以回答这个问题,答案也许大同小异,但肯定都有个关键词,叫“启动文件”,用英文单词来描述是“Bootloader”。
无论性能高下,结构简繁,价格贵贱,每一种微控制器(处理器)都必须有启动文件,启动文件的作用便是负责执行微控制器从“复位”到“开始执行main函数”中间这段时间(称为启动过程)所必须进行的工作。最为常见的51,AVR或MSP430等微控制器当然也有对应启动文件,但开发环境往往自动完整地提供了这个启动文件,不需要开发人员再行干预启动过程,只需要从main函数开始进行应用程序的设计即可。但接触到嵌入内核比如Linux系统移植过程“bootloader”却是很重要也是必不可少的一个环节。事实上,每一种微控制器,无论性能高下,结构简繁,价格贵贱都是必须有启动文件才能正常工作的,它的作用同“bootloader”类似。启动文件完成了微控制器从“复位”到“开始执行main函数”中间这段时间的必要启动配置。

话题转到STM32微控制器,无论是keil
uvision4还是IAR EWARM开发环境,ST公司都提供了现成的直接可用的启动文件,程序开发人员可以直接引用启动文件后直接进行C应用程序的开发。这样能大大减小开发人员从其它微控制器平台跳转至STM32平台,也降低了适应STM32微控制器的难度(对于上一代ARM的当家花旦ARM9,启动文件往往是第一道难啃却又无法逾越的坎)。

Stm32系统复位后,sysclk第4个上升沿,BOOT引脚被,通过设置BOOT1和BOOT0引脚状态选择启动模式。
启动后,CPU从地址0x0000 0000获取堆栈顶的地址。从存储器的0x0000 0004指示的地址开始执行代码。
虽然不同的启动模式,但Stm32具有存储器映像的功能,因此代码区始终从0x0000 0000通过Icode、Dcode总线访问。数据区(SRAM)始终从0x2000 0000开始。

启动模式:

  1. Flash启动。
    Flash地址0x0800 0000被映射到0x0000 0000空间,但仍然能够在原有的地址0x0800 0000访问它。

  2. 系统存储器启动。
    系统存储器地址0x1fff f000被映射到0x0000 0000空间,但仍然能够在原有的地址0x1fff f000访问它。

  3. 从内置的SRAM启动。
    只能在SRAM地址0x2000 0000开始的地址访问。

启动时钟
复位时内部8MHzRC振荡器作为默认的CPU时钟。不管Stm32外部有没有接晶振。
可以配置选择外部有失效监控的4~16MHz时钟。检测到失效时,会自动转为内部时默认的时钟。如果设置了中断,那么还能检测到这个中断。
多个分频器配置AHB的频率、调整APB(APB2)和低速APB(APB1)区域。AHB和调整的APB最高频率都是72MHz,低速APB最高频率为36MHz。
内嵌的程序用于通过USART1串行接口对闪存存储器进行重新编程。

在STM32中,如果是在MDK下创建一个工程,一般都有提示是否加入Star up Code文件,这个就是启动文件,这里有个误区,一般对于初学者来看,很容易误以为STM32F10x.s这个启动文件是STM32所有类型芯片的通用启动文件,因此也自然不会去理会它的作用,事实上,这个启动文件只是针对部分STM32系列,如果仔细看过它的启动代码就会发现里面很多中断函数定义是没有的,甚至有些和STM32F10x_it.c里的函数是有出路的,如果刚好用到了默认的这个中断服务子函数的话,程序一旦运行到了中断是找不到入口地址的,这样就会莫名其妙地不知问题所在。STM32F10x.s是MDK提供的启动代码,从其里面的内容看来,它只定义了3个串口,4个定时器。实际上STM32的系列产品有5个串口的型号,也只有有2个串口的型号,定时器也是,做多的有8个定时器。比如,如果你用的STM32F103ZET6,而启动文件用的是STM32F10x.s的话,你可以正常使用串口1~3的中断,而串口4和5的中断,则无法正常使用。所以STM32F10x.s并不能适用所有的STM32型号,对于不同型号的STM32,正确做法是选择不同的启动文件。ST公司提供了3个启动文件:startup_stm32f10x_ld.s
/startup_stm32f10x_md.s/startup_stm32f10x_hd.s 分别适用于小容量/中容量/大容量的STM32芯片,具体判断方法如下:

小容量:FLASH≤32K
中容量:64K≤FLASH≤128K
大容量:256K≤FLASH

在启动代码中,补充几点:

启动代码中的两条语句解释:

一、PROC 为子程序开始,ENDP 为子程序结束

二、[weak] 的意思是该函数优先级比较弱,如果其它地方定义了一个同名函数,那么此处的这个函数就被取代了。语法格式为 EXPORT 标号 {[WEAK]} 。EXPORT 可用GLOBAL代替。

对于_main函数的理解:

事实上,_main 和main是两个完全不同的函数!_main代码是编译器自动创建的,因此无法找到_main代码。MDK文档中有一句说明:it is automatically craated by the linker when it sees a definition of main() .大体意思可以理解为:当编译器发现定义了main函数,那么就会自动创建_main.

_main 和main的关系

_main 主要做两件事:其一,C所需的资源;其二,调用main函数。这就不难理解为什么在启动代码调用的是_main ,最后却能转到main函数中去执行的原因了。

AREA指令的理解

AREA指令是一个伪指令,用于段定义。ARM汇编程序由段组成,段是相对独立的指令或数据单位,每个段由AREA伪指令定义,并定义段的属性。

AREA参数说明:

  • STACK——AREA指令的一个参数,定义段名称
  • NOINIT——AREA指令的一个参数,指定本数据段仅仅保留了内在单元,而将句初始值写入内存单元,此时内存单元值初始化为0

  • READWRITE——指定本段为可读可写,数据段默认为READWRITE.READWRITE(读写)、READONLY(只读)

  • ALIGN——也是一个伪指令,指定对齐方式。ALIGN n 指令的对齐值有两种选择:n或者2^n
    例子:开辟一个堆栈段,段名为STACK,定义为可读可写,将内存单元初始化为0,对齐方式为8字节对齐。

AREA STACK,NOINIT,READWRITE,ALIGN=3

总结