对于很多人来说,BootLoader并不是一个陌生的词,甚至会经常用到它。因为在很多情况下我们都需要BootLoader程序,比如我们需要对系统在线升级时就需要它,还有当我们需要在外部存储器中运行程序时也需要用到它。在这里我们就来设计一个应用于STM32系列MCU的BootLOader程序。
既然我们想要实现一个面向STM32的BootLOader程序,那么首先我们必须来了解一下BootLOader程序的基本原理。
顾名思义,BootLOader程序肯定是要实现系统的引导,这是BootLOader程序的基本功能。对于STM32系列MCU来说,系统启动后都会从内部Flash存储器的起始地址开始执行程序。然后进入应用程序并按既定的顺序执行下去,这时BootLoader与应用程序是一体的,具体如图所示:
但有些时候,我们希望应用程序并不是直接运行,如我们希望对系统实现IAP的时候;或者我们希望应用程序并不在我们的内部Flash中运行;又或者应用程序虽然在内部Flash运行,但我们希望应用程序从我们指定的内部Flash地址运行等等。在这些时候我们就需要一个单独的BootLoader程序。系统首先启动BootLoader程序,系统准备就绪后进入到应用程序执行,具体如图所示:
在上图中,我们实际上将应用程序存储在内部Flash的指定位置,这样做当然是为了实现我们某种需求,如系统IAP。当然我们也可以让应用程序存储于外部Flash中,然后BootLoader程序跳转到外部Flash去执行应用程序,不过前提是外部Flash内购执行程序。具体过程如下:
当然,这只是一种示例,对于不同的存储器我们只需要修改地址就可以了。至于在BootLoader程序中要实现的功能就看使用情况了。原则上我们可以添加我们任意想要的功能,如硬件检测、系统升级等等。
我们的目标是实现一个面向STM32的BootLoader程序。那么接下来我们就设计如何实现一个面向STM32的BootLoader程序。
我们以STM32F407IGT6为目标MCU,这款MCU具有1M的Flash和192K的SRAM。我们将Flash划分为2个部分,一个是启动程序区(0x0800 0000 - 0x0800 3FFF )大小为16K Bytes,剩下的为应用程序区(0x0800 4000 - 0x080F FFFF)。具体分配如下图:
我们让BootLoader程序占用16K的存储空间。但它是可以操作整个Flash存储空间的。
我们来考虑一下BootLoader程序的结构问题。我们设计BootLoader程序的主要目的就是为了对应用程序进行升级。那么在BootLoader程序中主要需要实现哪些功能呢?有几个方面是必须要包括的,一是基本的配置,如时钟等,我们在主程序中实现;二是对Flash的操作,我们升级应用程序肯定会对Flash进行查处和写入操作;三是跳转控制程序,我们最终是需要去执行应用程序的,跳转功能必不可少。当然根据不同的需求可能会有其它的需要。具体如下图所示:
上图中,我们除了签署的三项基本实现外,还添加了IAP文件的获取功能。这部分功能也是需要的,但在不同的模式下可能会有较大区别。因为获取文件的方式可以是各类通讯如以太网口、串口等。也可以是各类存储器,如SD卡、U盘等。所以这一功能虽然必不可少但实现方式则非常灵活,在后续实现中,我们具体问题具体分析。
我们已经确定了Flash的划分,也基本明白了BootLoader的基本工作流程。在接下来我就来讨论一下究竟怎么实现一个BootLoader程序。
我们知道芯片上电时先运行BootLoader程序,然后跳转到应用程序区执行应用程序。所以我们在编写BootLoader程序时我们首先判断系统是否有IAP的需求,如果有IAP请求则进入IAP模式,完成后再跳转到应用程序执行,如果没有IAP请求则直接跳转到应用程序执行。具体流程如下:
关于IAP的处理在不同的情况下会有不同的处理方式,在这里我们主要看一看跳转控制程序。首先定义应用程序的首地址并声明一个函数指针类型。具体如下:
#define ApplicationAddress 0x08004000 //应用程序首地址定义
typedef void (*pFunction)(void); //定义跳转函数指针类型
可能有人要问问什么定义这样一个函数指针类型,因为我们最终是跳转到Reset_Handler函数,所以必须要一个可以指向这个函数的函数指针。接下来我们就可以实现跳转程序了。
/*跳转到应用程序处理函数*/
static void JumpToApplication(void)
{
uint32_t StackAddr; //应用程序栈地址
uint32_t ResetVector; //应用程序中断向量表的地址
pFunction JumpToApp; //定义跳转函数指针
__set_PRIMASK(1); //关闭全局中断
StackAddr = *(__IO uint32_t*)ApplicationAddress; //0x08004000;
ResetVector = *(__IO uint32_t*)(ApplicationAddress + 4); //0x08004004;
if((StackAddr&0x2FFC0000)==0x20000000) //检查栈顶地址是否合法.
{
__set_MSP(StackAddr); //初始化应用程序栈指针
JumpToApp = (pFunction)ResetVector;
JumpToApp();
}
}
实现了BootLoader的编码后,要想正确的跳转到App运行,我们还需要对App作相应的修改。重要的修改有2处。如果应用程序是裸机程序则在配置时钟前我们需要打开全局中端。
/*开启全局中断,在BootLoader中关闭的*/
__set_PRIMASK(0);
同时,还需要修改中端向量表的偏移量地址。对于我们所使用的STM32F07可直接在system_stm32f4xx.c文件中修改就可以了。
#define VECT_TAB_OFFSET 0x4000 /*!< Vector Table base offset field.
实现了上述修改并不能达到我们想要的目的,我们还需要在开发环境中做必要的修改。以我们使用的IAR EWARM V8.4为例。修改icf文件中对中断向量表和Flash存储区域的设定。具体如下图:
完成上述配置后我们下载应用程序和BootLoader程序就可以实现正确的跳转了。
本篇中,我们只是实现了一个简单的BootLoader程序。下载到目标MCU后实现了跳转,应用程序也正常运行,说明我们的设计是正确的,后续可在次基础上添加各种功能实现相应的IAP应用。
需要注意的是在BootLoader程序中我们关闭了全局中断,在应用程序初始化系统时钟之前一定要记得打开全局中断,否则SystemTick不能工作会产生硬件故障(hardfault)。不过如果App是运行在RTOS上,则打开中断可能会出错,这一点需要注意。