|
以Keil MDK工具的实现为例,如果时间充裕还讲讲GCC的实现.
1.Cortex M系列内核的启动过程以及向量存储
Cortex M都在0x00000000的地址启动,第一个word存放MSP的初始值,第二个word开始存放向量,根据内核不同向量数目不同.
对于除了Cortex M0的内核,向量可以有偏移量,为写bootloader与多应用提供了方便.VTOR在SCB中.
2.具体到STM32系列.
首先STM32一般有三种启动方式, 这是硬件在启动时将0x00000000映射到哪个地址所决定.
BOOT1 BOOT0 不同配置所对应的启动地址:
1.系统memory即ST官方的bootloader, 固化在flash中
2.SRAM,方便快速调试,另SRAM运行比Flash运行快,因为没有等待延迟
3.Flash启动,对于STM32来讲一般在0x8000000
3.以最常见的Flash启动为例看启动的最初位置
此时硬件连接上,BOOT1可以不管(如果有引出可以自由做其他IO使用)
BOOT0拉低,直接接地或者10K电阻拉低都是常见的做法.
启动文件(MDK ARM版本)示例节选:
; Vector Table Mapped to Address 0 at Reset
AREA RESET, DATA, READONLY
EXPORT __Vectors
EXPORT __Vectors_End
EXPORT __Vectors_Size
__Vectors DCD __initial_sp ; Top of Stack
DCD Reset_Handler ; Reset Handler
.....
__initial_sp即是初始的MSP值,指向stack顶部往上一个word.对于PSP,如果需要使用必须初始化,因为没有初始值.
Reset_Handler以及下面的一大排都是向量.这一块地址对于有VTOR的内核来讲可以放在存储器的任意位置(有对齐要求),对于无VTOR内核来讲必须位于Flash的初始位置(对于STM32来讲就是0x8000000.
如果写bootloader要跳转,注意不是跳转到__initial_sp这个位置,而是Reset_Handler这个位置也就是__initial_sp+4,前提当然是要将跳转目的的向量与向量偏移设置好.
4.发生中断时的情形
根据发生的中断号,硬件自动生成向量所在的地址进行跳转.对于前面的几个中断,不可软件屏蔽:
DCD Reset_Handler ; Reset Handler
DCD NMI_Handler ; NMI Handler
DCD HardFault_Handler ; Hard Fault Handler
DCD MemManage_Handler ; MPU Fault Handler
DCD BusFault_Handler ; Bus Fault Handler
DCD UsageFault_Handler ; Usage Fault Handler
.....
如果用户没有提供Handler,则使用启动文件中的默认Handler.如用户有提供Handler,则自动使用用户的Handler.类似于C++中的覆盖现象.注意WEAK关键字.
Default_Handler PROC
EXPORT WWDG_IRQHandler [WEAK]
......................
B .
ENDP
ALIGN
此默认Handler类似于C语言中的:
while(1)
{
;
}
5.复位向量
; Reset handler
Reset_Handler PROC
EXPORT Reset_Handler [WEAK]
IMPORT SystemInit
IMPORT __main
LDR R0, =SystemInit
BLX R0
LDR R0, =__main
BX R0
ENDP
首先要调用SystemInit,此函数配置默认的时钟,存储器(如果有需要配置的存储器,如外接SRAM,SDRAM,如果这些存储器不是初期运行所必需的存储器则可以在进入用户程序之后再初始化),还有向量偏移(如果有VTOR且用户要求向量偏移).
代码节选:
#if defined (DATA_IN_ExtSRAM) || defined (DATA_IN_ExtSDRAM)
SystemInit_ExtMemCtl();
#endif /* DATA_IN_ExtSRAM || DATA_IN_ExtSDRAM */
/* Configure the Vector Table location add offset address ------------------*/
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */
#endif
注意SystemInit这个函数名为CMSIS标准所规定,用户可以不使用此名称的函数来完成上述功能,也可以玩全不调用任何函数直接下一步,那么违反了CMSIS标准,如无特殊理由不建议违反CMSIS标准.
紧接着调用__main,注意这个并非用户的main函数,__main中进行了一些操作之后才会调用用户的main函数.此处为ARM Keil MDK的行为,对于其他编译器如GCC,此处名称不同,__main不是CMSIS规定的函数名字.
6.从__main到main
从__main到main主要是stack, heap初始化,以及其他的一些初始化,可以称作C Runtime初始化.如果程序有用到C++,还包括C++环境的初始化.此处省去C++内容以做简化,因为C++目前看来使用还不普遍.
对于C Lib的使用,可以有多种选择,对于ARM Keil MDK,可以选择Microlib或者Standard Lib. 对于GCC系列工具可选Nanolib或者Standard Lib(大多是newlib实现).不管是microlib还是nanolib,目的只有一个:就是省去一些嵌入式环境少用到的功能以求镜像尺寸的减小.此处以MDK的microlib与standard lib对比为例,看省去了哪些功能:
列表:Microlib与Standard Lib对比省去的功能列表.
根据笔者的测试Dhrystone中,standardlib版本的性能大大超出microlib版本的性能,而对于Coremark,两个版本的性能类似.说明Coremark测试对于CPU/编译器的评估更加独立,不依赖运行时条件.
做完Runtime的初始化之后,即跳入用户的main函数,开始用户代码的运行过程.
7.写bootloader的注意要点(MDK版本例子)
使用官方的Bootloader虽然方便,但是有以下理由促使程序员还要编写自己的bootloader:
使用官方bootloader需要更改boot0的状态,最终应用中做到这一点相当麻烦
使用官方bootloader需要实现官方的通信协议,虽然不是很复杂,但是没有更改的余地,导致无法加密无法压缩
开始设计有bootloader的应用之后需要做:
1.划分bootloader与Application的分野,此处以bootloader位于下方,只有一份用户App为例,当然实际情况取决于程序员对自己应用需求的分析
2.指定跳转bootloader跳转Application的条件,一般是Backup Region或者Flash或者外部引脚或者外部EEPROM做记号进行跳转,使得上电后默认跳入Application,只要在需要的时候才停留在bootloader中等待通信内容来升级
3.分别开发bootloader与Application.注意指定各自的Flash范围,最后生成hex以观察其占据的Flash位置,最后发布时进行合并.
4.通信协议设计,是否需要加密等等.
5.Bootloader的界面,一般情况无交互界面但是有基础的按键LED进行交互,有条件的可以设计图形化界面以方便操作人员.