本文仅在ARM Cortex M3/M4芯片上进行过测试
Bootloader用于用户程序的引导,其用途在于软件启动、固件升级等,Bootloader编写的核心内容是向量表的重定位。为了读者能够比较清晰了解Bootloader的机制,小军会说明CMSIS启动文件的机理,为此本文分为以下三个方面:
下述为Adu360(ARM Cortex-M3内核)芯片的启动代码,由于符合CMSIS标准的启动文件大同小异,本文将以此启动文件作为示例,详细代码如下:
Stack_Size EQU 0x00000400 AREA STACK, NOINIT, READWRITE, ALIGN=3 Stack_Mem SPACE Stack_Size __initial_sp Heap_Size EQU 0x00000200 AREA HEAP, NOINIT, READWRITE, ALIGN=3 __heap_base Heap_Mem SPACE Heap_Size __heap_limit PRESERVE8 THUMB ; 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 DCD NMI_Handler ; The NMI handler ;中间省略若干个中断向量 DCD PWM2_Int_Handler ; PWM2 [38] DCD 0 ; [39] __Vectors_End __Vectors_Size EQU __Vectors_End - __Vectors AREA |.text|, CODE, READONLY ; Reset handler Reset_Handler PROC EXPORT Reset_Handler [WEAK] IMPORT SystemInit IMPORT __main LDR R0, =SystemInit BLX R0 LDR R0, =__main BX R0 ENDP ; Dummy Exception Handlers (infinite loops which can be modified) NMI_Handler PROC EXPORT NMI_Handler [WEAK] B . ENDP ;中间省略若干弱引用的中断函数 PWM2_Int_Handler B . ENDP ALIGN IF :DEF:__MICROLIB EXPORT __initial_sp EXPORT __heap_base EXPORT __heap_limit ELSE IMPORT __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 ENDIF END
启动程序还是比较简单的,其主要分为三个部分:
ARM Cortex-M系列芯片堆栈的相关操作为C准备运行时环境,这里不做细说;中断向量表为芯片运行的根基,我们来重点分析一下中断向量表:
; 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函数及__main函数,SystemInit函数为CMSIS定义的芯片初始化函数,用于对芯片的时钟等进行初始化配置,__main函数为C运行时库的入口函数,所以跳转到Reset_handler函数中可以启动应用;
一般,ARM Cortex-M芯片从存储器地址0x00000000处开始执行程序,通过__initial_sp初始化主栈指针,通过执行Reset_handler执行main函数中的程序。
如果我们希望升级系统中的固件或者从SD卡(或网络)中加载程序执行,需要执行如下步骤:
其示例程序为:
/* 在调用此函数之前完成用户应用程序的加载和拷贝 */ void retarget_vector(uint32_t new_addr) { __DMB(); /* 数据存储器屏障 */ SCB->VTOR = new_addr; __DSB(); /* 数据同步屏障,保证在其后中断均为新的地址 */ }
重定向之后,我们还需要跳转到用户程序中运行,其示例程序为:
/* 在调用此函数之前完成重定向操作 */ __asm void jump_to_application(uint32_t addr) { LDR SP, [R0] ; 设置用户程序MSP的值 LDR PC, [R0, #4] ; 运行用户程序的Reset_handler }
在Bootloader的编写中还需要注意以下几点:
使用bin格式,因为hex文件附带地址信息,并不是我们需要的纯二进制文件;bin格式为按存储顺序的二进制文件
有可能存储设备和ARM芯片的大小端不一致,导致加载的程序不符合ARM芯片的大小端字序,从而导致程序无法运行;
使用VTOR时需要将中断向量表的大小拓展到最近的2的幂次方,且新向量的基址必须要对齐到这个数值。
这里的固件升级对固件编译情况是有要求的,比如说想要把固件放到0x4000-0x20000的位置,固件在编译时需要设置其ROM的启动地址为0x4000,大小为0x1C000,bootloader复制代码到0x4000-0x20000,并且中断向量表重定向后固件才可以正常运行。
Bootloader的实现过程可以总结如下(详细见《ARM Cortex-M3 与 Cortex-M4 权威指南》):
此部分详细见《ARM Cortex-M3 与 Cortex-M4 权威指南》