为ARM Cortex-M系列芯片编写Bootloader
https://zhuanlan.zhihu.com/p/25356501
为ARM Cortex-M系列芯片编写Bootloader
6 个月前
为ARM Cortex-M系列芯片编写Bootloader
本文仅在ARM Cortex M3/M4芯片上进行过测试
1.引言
Bootloader用于用户程序的引导,其用途在于软件启动、固件升级等,Bootloader编写的核心内容是向量表的重定位。为了读者能够比较清晰了解Bootloader的机制,小军会说明CMSIS启动文件的机理,为此本文分为以下三个方面:
- CMSIS启动文件简单分析
- Bootlader的机理及实现
- 向量表重定向的应用
2.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准备运行时环境,这里不做细说;中断向量表为芯片运行的根基,我们来重点分析一下中断向量表:
- 中断向量表的起始为主栈指针(MSP)的初始值,用于在C运行时入口函数初始化堆栈之前建立基本的C运行时环境;
紧接着是向量表的第一个中断向量复位向量,其不仅仅作为系统的一个中断存在,还是芯片执行程序的入口函数,其具体代码如下:
; Reset handler
Reset_Handler PROCEXPORT 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函数中可以启动应用;
- 后面则是各种弱引用的中断向量,可以被C代码中同名的强引用定义取代;
一般,ARM Cortex-M芯片从存储器地址0x00000000处开始执行程序,通过__initial_sp初始化主栈指针,通过执行Reset_handler执行main函数中的程序。
2.Bootloader机理及其实现
如果我们希望升级系统中的固件或者从SD卡(或网络)中加载程序执行,需要执行如下步骤:
- 启动Bootloader
- 执行Bootloader中的任务加载用户程序
- 切换到用户程序中执行 这时,我们就需要用到ARM Cortex-M内核的向量表重定向机制,其提供了一个中断向量表偏移寄存器(SCB->VTOR),改寄存器中的值为向量表的起始地址。
其示例程序为:
/* 在调用此函数之前完成用户应用程序的加载和拷贝 */
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的编写中还需要注意以下几点:
- 固件采用hex格式还是bin格式
使用bin格式,因为hex文件附带地址信息,并不是我们需要的纯二进制文件;bin格式为按存储顺序的二进制文件
- 注意ARM芯片的大小端字序
有可能存储设备和ARM芯片的大小端不一致,导致加载的程序不符合ARM芯片的大小端字序,从而导致程序无法运行;
- VTOR的地址有条件限制
使用VTOR时需要将中断向量表的大小拓展到最近的2的幂次方,且新向量的基址必须要对齐到这个数值。
- 什么样的固件才能升级
这里的固件升级对固件编译情况是有要求的,比如说想要把固件放到0x4000-0x20000的位置,固件在编译时需要设置其ROM的启动地址为0x4000,大小为0x1C000,bootloader复制代码到0x4000-0x20000,并且中断向量表重定向后固件才可以正常运行。
- 跳转用户程序之前需要清理BootLoader使用过的资源
为了防止对用户程序产生副作用,Bootloader程序需要关闭所有使用过的芯片资源,最好关掉中断(感谢@望海楼提醒。 )
Bootloader的实现过程可以总结如下(详细见《ARM Cortex-M3 与 Cortex-M4 权威指南》):利用启动ROM中的向量表启动Bootloader
- 启动Bootloader中的任务,完成用户程序的加载
- 设置VTOR指向用户程序存储器中的向量表
- 跳转到用户程序存储器的向量表中的中断向量
3.向量表重定向的应用
- Bootloader的编写
- 应用程序加载到RAM中执行,其类似于Bootloader
- 动态修改向量表,有些情况下ROM中可能会有一个中断的多个处理示例,可能在应用的不同阶段在它们之间切换,这种情况下,可以将向量表从程序存储中复制到RAM中,设置VTOR指向RAM中的向量表,RAM中的内容可以任意时间修改,因此可以在应用的不同阶段使用不同的中断向量。
此部分详细见《ARM Cortex-M3 与 Cortex-M4 权威指南》
还没有评论,来说两句吧...