第三章 Boot Loader
现今,嵌入式系统越来越受到人们的重视。随着系统复杂程度的提高,小型化和网络化也成为嵌入式系统发展的必然趋势。如何利用现成的通信网络,安全、快捷的对各个节点单片机进行在线软件升级(ISP),成为了嵌入式系统发展的一项重要课题,对工业控制、航空航天、通信等领域意义重大。而实现这一功能,需要一段核心代码的支持,这段代码就是BootLoader。
Luminary Micro 公司设计并生产的 Stellaris 系列单片机,基于先进的 ARM Cortex-M3内核,芯片提供的高效的性能、广泛的集成功能,适用于各种关注成本并明确要求具有过程控制以及连接能力的应用方案。不可多得的是,Luminary 官方提供了 BootLoader 的全部源代码,大大减小了开发难度。本章将分析 Stellaris BootLoader 的组成、结构及设计思路,试图掌握其关键技术,以更好地应用 BootLoader。
3.1 简介
BootLoader 是位于 Flash 起始地址处的一小段代码,占据空间默认为 2K。如果没有BootLoader,硬件启动成功后,将直接运行用户应用程序(Application)。反之,BootLoader的启动代码(Start-up Code)将先被执行,进行一系列的初始化操作后,根据预先设定的条件,选择执行用户应用程序(Application)或升级控制程序(Updater)。Updater在升级Flash的过程中,需要与上位机通信,通信的端口可选用UART,SSI,I2C或以太网端口。为了保证数据的无差错传输,BootLoader采用了控制传输的通信协议:UART,SSI,I2C端口均采用了自定义的串行加载协议;而以太网采用了UDP协议。对接收到的格式正确且校验成功的数据包,Updater能够将其解包,并将得到的加载命令转化为对Flash底层寄存器的操作。
由于官方提供了 BootLoader 的全部源代码,用户也可自行修改通信端口、通信协议等相关组件,使其更好地符合用户需求。
BootLoader的源代码由多个文件组成(图3.1):
图3.1 BootLoader文件组成
在图3.1中可以看出主要有6个文件组成部分,同时还有几个头文件。
bl_packet.c 串行数据包收发控制
bl_uart.c UART 端口数据传输
bl_autobaud.h 自动获取波特率
bl_ssi.c SSI 端口数据传输
3.2 原理分析
3.2.1 存储器映射
图3.2 存储器映射
总体来说,Cortex‐M3支持4GB存储空间,如图3.2所示地被划分成若干区域。
从图3.2中可见,不像其它的ARM架构,它们的存储器映射由半导体厂家说了算,Cortex‐M3预先定义好了“粗线条的”存储器映射。通过把片上外设的寄存器映射到外设区,就可以简单地以访问内存的方式来访问这些外设的寄存器,从而控制外设的工作。结果,片上外设可以使用C语言来操作。这种预定义的映射关系,也使得对访问速度可以做高度的优化,而且对于片上系统的设计而言更易集成(还有一个重要的,不用每学一种不同的单片机就要熟悉一种新的存储器映射)。Cortex‐M3的内部拥有一个总线基础设施,专用于优化对这种存储器结构的使用。在此之上,CM3甚至还允许这些区域之间“越权使用”。比如说,数据存储器也可以被放到代码区,而且代码也能够在外部RAM区中执行(但是会变慢不少)。处于最高地址的系统级存储区,是CM3用于藏“私房钱”的——包括中断控制器、MPU以及各种调试组件。所有这些设备均使用固定的地址。通过把基础设施的地址定死,就至少在内核水平上,为应用程序的移植扫清了障碍。
由于Cortex-M3内核具有固定的存储器映射,这使得相同内核的芯片具有了更好的兼容性Cortex-M3的地址空间中,0~0.5G被映射为Flash空间,0.5G~1G被映射为SRAM空间。由于SRAM是易失性存储器,故系统上电时,SRAM中并没有内容,系统必须从Flash开始启动。Flash空间起始地址处必须存放向量表。向量表是异常产生时获取异常处理函数入口的一块连续内存,每一个异常都在向量表固定的偏移地址处(偏移地址以字对齐),通过该偏移地址可以获取异常处理函数的入口指针。向量表中前4个字分别为:栈顶地址、复位处理函数地址、NMIISR地址、硬故障ISR地址。一张向量表至少由这四项组成。在程序代码开始运行后,向量表的基地址也可以改变。通过软件设置NVIC中的向量表偏移寄存器(NVIC_VTABLE,0xE000ED08),可以在任意32字对齐处建立向量表。
3.2.2 硬件启动原理
Stellaris系列单片机硬件启动原理如下:硬件复位时,NVIC_VTABLE复位为0,向量表默认位于Flash空间起始地址处(0x00000000)。内核读取向量表第1个字设置主堆栈(SP_main),读取第2个字设置PC指针,之后跳转到复位处理函数中运行。自此,系统的控制权交由软件接管。
3.2.3 Boot Loader启动原理
BootLoader 启动需先执行以下一系列操作:配置向量表 、初始化存储器、复制 BootLoader 代码到 SRAM 、从 SRAM 中执行代码 。
BootLoader 的作用之一,是提供运行时修改 Flash 的功能。而由于 Stellaris 系列单片机具有单周期的 Flash 读写能力,因此默认的代码段本身就位于 Flash 中。这样,如果内核直接从 Flash 中加载修改其自身的指令,则既容易造成时序上的混乱,又有可能因 Flash 中某些关键指令被自修改而导致整个系统崩溃。
图3.3 BootLoader复制
针对这个问题,Stellaris系列单片机采取的方案为:在SRAM中建立BootLoader的映像,即把BootLoader复制到SRAM中,然后从SRAM中加载指令。如图3.3所示。这样,指令的加载源(SRAM)与修改操作的目标(Flash)相分离,一定程度上保证了软件升级的可靠性和安全性。这样的存储器映射允许修改Flash的全部代码。此外,BootLoader也提供了保护机制,用于保护Flash中的BootLoader代码本身,以及Flash空间顶部的一段存储区(保存即使Flash升级也不需要擦除的代码)。除非相应的配置选项使能,否则,这两段代码不可随意修改。
在Keil版本的Boot Loader中有这样一段代码,用于Boot Loader复制到SRAM中,请看下面一段代码:
EXPORT Reset_Handler
Reset_Handler
;
; Call the C library enty point that handles startup. This will copy
; the .data section initializers from flash to SRAM and zero fill the
; .bss section. It will then call __rt_entry, which will be either the
; C library version or the one supplied here depending on the
; configured startup type.
;
IMPORT __main
;
; Copy the code from flash to RAM
;
mov r0, #0
mov r1, #0x20000000
orr r2, r1, #0x0800
copy_loop
ldr r3, [r0], #4
str r3, [r1], #4
cmp r1, r2
bne copy_loop
b __main
从上面一段代码中可以看出,Boot Loader占用0x800个单元,上面代码的作用是从Flash中把Boot Loader复制到SRAM中,并调到__main函数中去运行,实际上是运行SRAM中的Boot Loader。
3.2.4 运行条件
在SRAM中映像建立完毕并开始执行后,BootLoader将检测某特定引脚(默认的是PB4,也可以自己修改为其它管脚)的极性,该极性可通过硬件修改,以决定运行Application还是Updater(默认情况下,复位时PB4 引脚为低则运行Updater,为高则运行Application),如图 3.4所示。引脚的选择及其极性的意义可在配置文件中修改。
图3.4 运行条件
3.2.5 由 BootLoader 加载 Application
由 BootLoader 加载 Application,与系统复位后直接加载 Application 有所不同。区别在于,在运行BootLoader 的系统中,Application 的向量表并不位于Flash 起始地址处。Application有自己的向量表,且不与 BootLoader 共用向量表。这样做的目的,就是将 BootLoader 本身与 Application 分离开来,使两者具有一定的独立性。这也是由实际应用决定的:BootLoader需在系统投入使用之前编译完成并通过JTAG口或其它方式烧写进单片机,而 Application作为 Flash 的升级程序,则通常在系统运行一段时间后才编写完成。
向量表能够分离,关键在于向量表偏移寄存器(NVIC_VTABLE)的使用。如表 3.1所示。在硬件启动时,该寄存器会被复位为全 0,即向量表位于Flash空间起始地址处;当BootLoader在SRAM中建立映像之后,该寄存器被设置为0x20000000,表示向量表位于SRAM空 间 起 始 地 址 处 ; 如 果 选 择 执 行 Application , 那 么 该 寄 存 器 又 被 设 置 为APP_START_ADDRESS(默认为 0x800,即 2K),表示用户应用程序的向量表位于Flash空间BootLoader以上的某一地址处(BootLoader的代码量不能超过APP_START_ADDRESS)。
表3.1 向量表偏移取值
由BootLoader加载Application的关键代码:
;*****************************************************************************
;
; This function reads the stack pointer from the base address passed in and
; also moves the vector table to point to the vector table provided by the
; application. Once the vector table and stack are configured this function
; reads out the start address for the application and branches to it.
;
;*****************************************************************************
AREA ||.text||, code, readonly, align=2
EXPORT CallApplication
CallApplication
mov r1, #0xE0000000
orr r1, r1, #0x0000ED00
str r0, [r1,#8] ;修改向量表偏移寄存器(NVIC_VTABLE)
ldr sp, [r0], #4
ldr r0, [r0]
bx r0
对于需要由BootLoader 加载的 Application,则在KEIL开发应用程序时Target必须做好相关的设置,因为Boot Loader的区域是0x000~0x800,所以设置如图3.5。
图3.5 on-chip ROM设置
3.3 BootLoader 配置
BootLoader配置是通过一些编译预处理进行的,可以通过注释掉或者去掉相关注释掉的编译预处理进行BootLoader配置,主要在bootloader.h中进行配置。请看bootloader.h中的一段源码:
//*****************************************************************************
//! This defines the crystal frequency used by the microcontroller running the
//! boot loader. If this is unknown at the time of production, then use the
//! AUTOBAUD feature to properly configure the UART.
//*****************************************************************************
#define CRYSTAL_FREQ 6000000
//*****************************************************************************
//! Enable this to call a decryption routine during download of code to the
//! microcontroller.
//*****************************************************************************
#define ENABLE_DECRYPTION
//*****************************************************************************
//! If the boot loader is going to use the UART port for transmitting data,
//! then this define must be turned on.
//*****************************************************************************
#define UART_ENABLE_UPDATE
//*****************************************************************************
//! Enable this to allow automatically detect the baud rate when the crystal
//! value used in the system is unknown. This requires that UART_ENABLE_UPDATE
//! also be defined.
//*****************************************************************************
//#define AUTOBAUD
//*****************************************************************************
//! If the boot loader is going to use the SSI port for transmitting data, then
//! this define must be enabled.
//*****************************************************************************
//#define SSI_ENABLE_UPDATE
//*****************************************************************************
//! This definition controls which GPIO port to use for checking boot
//! mode.
//*****************************************************************************
#define FORCED_UPDATE_PORT (GPIO_PORTB_BASE)
//*****************************************************************************
//! This definition sets the GPIO pin in FORCED_UPDATE_PORT to use for checking
//! boot mode. This is a zero-based number and the valid values are 0-7.
//*****************************************************************************
#define FORCED_UPDATE_PIN (4)
//*****************************************************************************
//! This definition sets the level of the specified GPIO pin that causes the
//! boot loader to enter update mode. If this value is set to 0, it indicates
//! that a low voltage level on the GPIO pin indicates an update request. The
//! only other valid value is 1 indicating that a high voltage level indicates
//! an update request.
//*****************************************************************************
#define FORCED_UPDATE_POLARITY (0)
//*****************************************************************************
//! This sets the start address of the application. It is also used to detect
//! if the application at this address in the flash contains a valid
//! application.
//*****************************************************************************
#define APP_START_ADDRESS (0x0800)
//*****************************************************************************
//! This defines the number of bytes reserved by the boot loader for stack
//! space.
//*****************************************************************************
#define STACK_SIZE (64)
//*****************************************************************************
//! This defines the SSI chip select pin that is being used by the boot loader.
//*****************************************************************************
#define SSI_CS (GPIO_PIN_3)
//*****************************************************************************
//! This defines the SSI clock pin that is being used by the boot loader.
//*****************************************************************************
#define SSI_CLK (GPIO_PIN_2)
//*****************************************************************************
//! This defines the SSI transmit pin that is being used by the boot loader.
//*****************************************************************************
#define SSI_TX (GPIO_PIN_5)
//*****************************************************************************
//! This defines the SSI receive pin that is being used by the boot loader.
//*****************************************************************************
#define SSI_RX (GPIO_PIN_4)
//*****************************************************************************
//! This defines the combination of pins used to implement the SSI port used by
//! the boot loader.
//*****************************************************************************
#define SSI_PINS (SSI_CLK | SSI_TX | SSI_RX | SSI_CS)
//*****************************************************************************
//! This defines the UART receive pin that is being used by the boot loader.
//*****************************************************************************
#define UART_RX (GPIO_PIN_0)
//*****************************************************************************
//! This defines the UART transmit pin that is being used by the boot loader.
//*****************************************************************************
#define UART_TX (GPIO_PIN_1)
相关配置项说明:
CRYSTAL_FREQ 系统的晶振频率
ENABLE_DECRYPTION Application代码加密
UART_ENABLE_UPDATE UART下载程序到Flash
AUTOBAUD 自动获取波特率(一般不用这项)
SSI_ENABLE_UPDATE SSI下载程序到 Flash
FORCED_UPDATE_PORT Updater 关键引脚基地址
FORCED_UPDATE_PIN Updater 关键引脚的管脚位
FORCED_UPDATE_POLARITY 运行Bootloader时管脚极性,默认为“0”,如果要运行用户程序则让对应管脚为高,即“1”。
APP_START_ADDRESS Application 起始地址
STACK_SIZE BootLoader 堆栈深度
SSI_CS、SSI_CLK、SSI_TX、SSI_RX 、SSI_PINS SSI下载程序管脚定义
UART_RX、UART_TX UART下载程序管脚定义
看下面一个配置实例:
//请看bootloader文件夹中的bootloader.h
#define CRYSTAL_FREQ 6000000
#define ENABLE_DECRYPTION
#define UART_ENABLE_UPDATE
//#define AUTOBAUD
//#define SSI_ENABLE_UPDATE
#define FORCED_UPDATE_PORT (GPIO_PORTB_BASE)
#define FORCED_UPDATE_PIN (4)
#define FORCED_UPDATE_POLARITY (0)
#define APP_START_ADDRESS (0x0800)
#define STACK_SIZE (64)
#define SSI_CS (GPIO_PIN_3)
#define SSI_CLK (GPIO_PIN_2)
#define SSI_TX (GPIO_PIN_5)
#define SSI_RX (GPIO_PIN_4)
#define SSI_PINS (SSI_CLK | SSI_TX | SSI_RX | SSI_CS)
#define UART_RX (GPIO_PIN_0)
#define UART_TX (GPIO_PIN_1)