目 录
1. 前言
2. 初识BootLoader
2.1 百度百科的BootLoader
2.2 BootLoader的简单理解
2.3 BootLoader的做用
3. BootLoader预备知识
3.1 复位序列
3.1.1 栈指针
3.1.2 复位向量
3.2 重定位中断向量表
3.2.1 STM32的中断向量表
3.2.2 设置中断向量表偏移
3.3 分散加载文件相关
3.3.1 C语言的函数地址
3.3.2 BootLoader占用的ROM
3.3.3 修改ROM起始地址
3.4 hex文件和bin文件
3.4.1 hex文件
3.4.2 bin文件
3.5 Bin文件生成
4. 分几步实现BootLoader
4.1 跑FAT文件系统
4.2 读写Flash程序
4.2.1 Flash写入步骤
4.2.2 读写Flash调用的库函数
4.2.3 实现Flash读写
4.3 跳转到新程序运行
4.3.1 跳转到复位向量
4.3.2 App开始运行
5. Bootloader具体流程
5.1 主函数流程
5.2 BootLoader流程
5.3 跳转到新程序流程
附录A主函数
附录B更新说明
参考文献小程序
1. 前言自从几个月前接触到有Bootloader这回事,就有一种强烈的冲动,想写一个BootLoader出来。很快在飞思卡尔的Cortex-M4单片机上实现,已是好几个月前的事情了。而后关于BootLoader的事搁在一边很久了,此次弄个STM32的BootLoader出来,Cortex-M3的,顺便发表下博客,跟你们分享一下。
。。。
又过了大半年了吧,慢慢对BootLoader的认识也有点长进啦。特别是跟网友讨论后发现BootLoader的实现仍是须要靠BootLoader程序和App程序的配合才能正常使用。在这里特别感谢网友cary_yingj ,对本BootLoader的研究后发现App程序须要重定位中断向量表,才能正常工做。
在其余网友的反馈下,本人准备再将次文档完善,把不够详细的地方写得再详细,而且力求通俗易懂一点。但愿对学习BootLoader的同窗们有所帮助。
2. 初识BootLoader可能有的同窗据说过BootLoader,有的同窗没有据说过,这个都很正常。关于BootLoader的概念你们能够上网查一下,有比较详细的说明,我在这里说说我本身比较片面的理解,而且是针对CortexM3说明的,实现平台为STM32F103VET6。
2.1百度百科的BootLoader这里借用一下百度百科对BootLoader的解释。在嵌入式操做系统中,BootLoader是在操做系统内核运行以前运行。能够初始化硬件设备、创建内存空间映射图,从而将系统的软硬件环境带到一个合适状态,以便为最终调用操做系统内核准备好正确的环境。在嵌入式系统中,一般并无像BIOS那样的固件程序(注,有的嵌入式CPU也会内嵌一段短小的启动程序),所以整个系统的加载启动任务就彻底由BootLoader来完成。在一个基于ARM7TDMIcore的嵌入式系统中,系统在上电或复位时一般都从地址0x00000000处开始执行,而在这个地址处安排的一般就是系统的BootLoader程序。
2.2BootLoader的简单理解BootLoader就是单片机启动时候运行的一段小程序,这段程序负责单片机固件更新,也就是单片机选择性的本身给本身下程序。能够更新也能够不更新,更新的话,BootLoader更新完程序后,跳转到新程序运行;不更新的话,BootLoader直接跳转到原来的程序去运行。缓存
2.3BootLoader的做用BootLoader使单片机能本身给本身下载程序,因此在程序升级方面很是有做用。好比咱们的BootLoader是经过GSM更新程序的,咱们在升级单片机程序的时候,只要把新程序经过GSM发送给单片机,单片机本身实现程序更新,而后跳转到新程序执行,这样就省去咱们不少升级的功夫啦。
能够想象一下若是把单片机安装在很是高的地方,或者危险的工业现场,或者封装得很难拆下来,咱们很难直接给单片机下载程序,那么BootLoader的做用就体现出来了。简单的说,有了BootLoader,咱们更新程序的话是省心又省力。
想一想是否是很高级,还带点小兴奋哈哈。不用急,下面咱们会继续介绍,让你们都能本身实现BootLoader。至因而经过什么方式升级,这个你们自由发挥,相信会设计出丰富多彩的BootLoader升级方式呢。
3.BootLoader预备知识咱们这里是为ARM的Cortex-M3单片机写的BootLoader,须要了解一下M3内核的架构,而且要了解M3单片机是怎么启动的等等。这个方面的知识,能够参考《Cortex-M3权威指南》,这里的话我只是为了实现BootLoader简单介绍一下,你们有什么不清楚的请参考权威指南。而且这里是以STM32为例说明问题的,使用的开发环境是RVMDK(Keil)。
3.1复位序列这里参考的是《Cortex-M3权威指南》的3.8节,复位序列。
M3单片机复位后,从0x00000000取栈指针(SP),从0x00000004取复位向量(PC),有了栈指针和复位向量后,单片机就按照正常流程运行了,在BootLoader里面,咱们更新完程序后须要作的步骤之一就是设置栈指针,跳转到复位向量。
3.1.1栈指针栈是一种数据结构,后进先出LIFO。借用百度百科的解释:栈由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操做方式相似于数据结构中的栈。它使用的是一级缓存,他们一般都是被调用时处于存储空间中,调用完毕当即释放。
3.1.2复位向量复位向量是一个函数地址,在CortexM3单片机里是复位函数的地址。也就是单片机启动后第一个执行的函数。
3.2重定位中断向量表这里参考《Cortex-M3权威指南》的7.3节,向量表。
BootLoader是一个完整的程序,下载的新程序(如下称为App)也是一个完整的程序。都包含中断向量表,因此的话,咱们是有两个中断向量表,相信由于有两个向量表,你们都知道咱们应该须要对这两个向量表作点什么吧。
3.2.1STM32的中断向量表咱们只看前16个向量,由于其他的向量属于外设使用,与CortexM3内核无关。
__Vectors DCD __initial_spTop ;Top of Stack
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
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD 0 ; Reserved
DCD SVC_Handler ; SVCall Handler
DCD DebugMon_Handler ; Debug Monitor Handler
DCD 0 ; Reserved
DCD PendSV_Handler ; PendSV Handler
DCD SysTick_Handler ; SysTick Handler数据结构
单片机启动默认先运行BootLoader,因此默认的中断向量表位置是BootLoader的中断向量表。为了App能够正常运行,下载完App后,咱们还须要把中断向量表从新定位到App程序那里。根据《CortexM3权威指南》,介绍一下怎样重定位中断向量表。
3.2.2设置中断向量表偏移Cortex-M3单片机有一个管理中断向量表的寄存器,叫作向量表偏移量寄存器(VTOR)(地址:0xE000_ED08)。具体能够看看截图:架构
STM332程序的起始地址通常在0x08000000。因此BootLoader程序是在0x08000000,不是在0x00000000是由于STM32的重映射技术(不符合Cortex-M3的设计,有点搞另类的感受)。因此BootLoader的中断向量表在0x08000000那里。若是咱们的App程序起始地址在0x08070000,而且App的中断向量表在起始地址,那么BootLoader程序下载App后,为了App程序能正确运行,开始App程序的运行后第一步,就要把中断向量表重定位到0x08070000那里。
具体实现下面会再介绍,接下来介绍分散加载文件相关内容。
3.3分散加载文件相关这一节涉及的内容主要属于分散加载文件,你们具体上网了解,这里只是介绍了可以实现BootLoader的一小部分。
3.3.1C语言的函数地址咱们知道C语言的函数名就是函数的地址,而且STM32单片机ROM的起始地址是在0x08000000,那么使用编译器编译程序的话(这里使用的是RVMDK),函数的地址默认都在以0x08000000为首的一段ROM里面了。好比咱们一个函数Delay(),它的地址能够是0x08000167(CortexM3中函数的地址0bit位通常是1),也就是Delay函数的代码在0x08000167,C语言函数调用Delay时,就是执行0x08000167的代码。
3.3.2BootLoader占用的ROM咱们须要注意的问题是,若是不修改程序默认的起始地址的话,那么BootLoader和新App程序的起始地址都是0x08000000,也就是他们重叠了(代码重叠),这样的话确定相互之间有影响,程序是不能正常工做的。
这里的解决方法是,BootLoader程序依然占用0x08000000为首的那段ROM,由于STM32的默认就是从0x08000000运行程序的。保持BootLoader程序先能正确运行。而后App使用除BootLoader占用ROM之外的空间。这里须要知道BootLoader到底占用了多少ROM,很简单,查看MAP文件就好了。这里以个人BootLoader的MAP文件为例说明一下,看截图:
Memory Map of the image
Image Entry point :0x08000131
Load Region LR_IROM1 (Base: 0x08000000,Size: 0x00006da4, Max: 0x00080000, ABSOLUTE)
Execution Region ER_IROM1 (Base: 0x08000000, Size: 0x00006d54, Max:0x00080000, ABSOLUTE)
主要是这句话“Base:0x08000000, Size: 0x00006da4, Max: 0x00080000”,这句话说明了个人BootLoader程序是从0x08000000开始,占用了0x00006DA4大小。只要咱们的App不要和BootLoader程序占用的空间冲突就能够了。个人App程序的起始地址选择为0x08070000,不与BootLoader程序冲突。具体怎么修改ROM起始地址,下面介绍。
3.3.3修改ROM起始地址编译新程序的时候,咱们要修改程序的起始地址,个人修改方法以下(开发环境是RVMDK):打开TargetOption...,切换到Target选项卡,以下函数
修改IROM1的起始地址和长度:
好比,为了避免产生地址冲突,我将起始地址0x08000000修改为0x0807000,将ROM长度0x80000修改为0x10000。以下图所示(左图为修改前、右图为修改后):工具
注意:BootLoader程序是不须要修改的,只是App须要修改(App就是使用BootLoader下载的程序)。
3.4hex文件和bin文件3.4.1hex文件平时咱们用j-Link或者串口下载程序的话,都是打开hex文件下载的,由于hex文件包含地址信息,下载程序的时候知道程序下载到ROM的哪一个区域。从另外一个角度上说,也就是hex文件是不能直接写进ROM的,一边写须要一边转换(解码出地址信息,将对应内容写入ROM)。
3.4.2bin文件bin文件的话,很好理解,是直接的可执行代码。也就是bin文件的内容跟下载ROM里面的内容是同样的。bin文件是没有包含地址信息的,因此在下载以前要知道bin文件是要下载到ROM的那个区域。
咱们的BootLoader下载的是bin文件,直接写进STM32的Flash里面,地址信息的话就是上一节的IROM,0x08070000,从0x08070000开始连续写入,中间不间断。
3.5Bin文件生成默认状况下编译后生成的是hex文件,不过很轻松能够生成bin文件。介绍具体怎么生成bin文件,工具的话是使用fromelf.exe(目录通常是在Keil安装目录里面,本人的fromelf.exe目录是在C:\Keil\ARM\ARMCC\bin),咱们是使用fromelf工具将axf文件转换为bin文件。
熟悉命令行的同窗可能会选择直接敲命令,不过这里介绍使用RVMDK提供的用户命令(编译时能够自动生成bin,省去每次生成bin文件都要敲命令的过程)。
打开TargetOption...,切换到User选项卡,以下学习
主要是在运行用户命令,Run#1ui
具体命令是(记得在Run#1前打勾,才会在编译后执行用户命令生成bin文件):spa
命令能够分为五部分,简化后是fromelf --bin -o xxx.bin xxx.axf,须要注意的是命令的五个部分之间要有空格。还须要说明的是路径问题,这里的路径都是相对.uvproj文件的,下面是个人目录(注意MY_STM32.uvproj文件和Output文件夹)。
个人bin文件和axf文件都在Output文件夹里面,而且路径是相对MY_STM32.uvproj的,Output文件夹里的bin文件(MY_STM32.bin)相对于MY_STM32.uvproj应该写成“.\Output\MY_STM32.bin”。
l 第一部分
这部分是fromelf.exe文件的路径,根据本身的安装目录而变。我这里由于Keil是安装在C盘的,因此个人路径以下所示。
参考命令:C:\Keil\ARM\ARMCC\bin\fromelf.exe
l 第二部分
这部分是固定的,--bin表示生成bin文件。
参考命令:--bin
l 第三部分
这部分也是固定的,-o表示输出。
参考命令:-o
l 第四部分
这部分是生成文件的目录和文件名,我是输出在Output文件夹的,也就是bin文件在Output文件夹里面。
参考命令:.\Output\MY_STM32.bin
l 第五部分
这部分是axf文件的目录和文件名,咱们的bin文件是根据axf文件生成的,也就是说axf文件至关于输入,bin文件至关于输出。个人axf文件也在Output文件夹的。
参考命令:.\Output\MY_STM32.axf
介绍了这些基本知识后,咱们能够来实现BootLoader了。
4. 分几步实现BootLoader有了前面的基础知识后,应该是比较容易理解BootLoader须要怎么实现了。这一章,咱们分几个步骤,一步一步实现BootLoader。
4.1跑FAT文件系统咱们的BootLoader是从SD卡更新程序的,把在电脑上编译后的App程序,也就是bin文件,复制到SD卡中,而后让单片机读取相应的bin文件,就能够实现程序的更新。须要注意的是,App程序须要修改ROM的起始地址,再编译,而且要生成bin文件才支持正常下载。
我跑的文件系统是FATFS_R0.07c,很经典的一个版本。若是你们对文件系统方面不了解的话,请本身网上查找教程,或者说不少同窗对这一步应该已经很熟悉啦。
只要单片机上实现读取bin文件,结合Flash写入程序,就能够实现程序更新。下面介绍读写Flash。
4.2读写Flash程序要实现BootLoader,还有一个前提是能够写入Flash了。若是是STM32单片机的话是很容易实现的,由于咱们有官方库。本人使用的是3.0.0版本,参考官方例程,很容易实现Flash的读写,这里一样是为了实现BootLoader简单介绍一下。
4.2.1Flash写入步骤l 解锁Flash
l 擦除Flash
l 写入Flash
l 验证读写是否正确
4.2.2读写Flash调用的库函数l voidFLASH_Unlock(void) Flash解锁
l FLASH_Status FLASH_ErasePage(uint32_tPage_Address) Flash擦除
l FLASH_Status FLASH_ProgramWord(uint32_t Address, uint32_tData) Flash写入
4.2.3实现Flash读写稍微封装一下STM32的官方库函数,就能实现Flash的读写,并验证读写是否正确,具体我实现的接口函数为如下截图,你们能够参考一下:操作系统
来到这里,咱们能够实如今bin文件写入Flash了,写入完后,就要跳转到App程序执行了,接下来继续介绍。
4.3跳转到新程序运行
l 跳转到复位向量
l 重定位中断向量表
l 设置栈指针
4.3.1跳转到复位向量BootLoader程序须要作的是跳转到复位向量,具体实现能够参考如下代码。
( (void (*)()) (Reset))(); //跳转到复位向量
注意( (void (*)()) (Reset) )();是一去就不返回的,执行完这条语句,单片机就直接跳转到App程序运行的,因此BootLoader程序下载完App后,作一些简单的处理(根据本身的应用,也能够不作任何处理),就用这条语句跳转到App执行。
4.3.2App开始运行BootLoader跳转到App后,App须要作的是先设置栈指针,而后重定位中断向量表地址,具体能够参考如下代码。
__set_MSP( Msp); //设置栈指针
NVIC_SetVectorTable( base, offset); //重定位中断向量表
其中Msp是栈指针,也就是中断向量表第一个字的内容,咱们这里的内容是*((uint32_t)(0x08070000) )。
base是中断向量表的基地址,通常状况下就是ROM的起始地址,这里是0x08070000。
至此,BootLoader实现步骤完了,相信熟悉了这几个步骤后,你们能够本身给本身的单片机写个BootLoader。顺便说一下,Cortex-M4的BootLoader跟Cortex-M3几乎是同样的。我在STM32上的实现彻底是参考本身上次在飞思卡尔Cortex-M4上的实现。下面说一下个人主函数吧,咱们再看看具体的BootLoader流程,再熟悉一下BootLoader。
5.Bootloader具体流程
5.1主函数流程先看截图。
主函数的流程以下所示:
l 时钟初始化
l LED初始化(可有可无)
l 调试接口初始化(可有可无)
l Flash初始化(解锁Flash)
l FAT初始化(挂载文件系统)
l 咱们的BootLoader(重点,下面展开继续介绍)
l 主循环(实际不会运行到这里)
而后在具体讲解BootLoader_FromSDCard函数,这就是咱们的重点,传说中STM32的BootLoader从SD卡更新固件。
5.2BootLoader流程
具体流程以下所示:
l 打开bin文件,检查文件打开是否正确
l 设置Flash下载起始地址(App程序起始地址)
l 读取bin文件,检查读取是否正确
l 获取栈指针SP和复位向量PC
l 进入循环(这里是第5步),条件为若是读取bin文件字节数不为零
l 将读取到的bin写入Flash,并判写入状态
l 调整Flash地址,根据写入字节调整
l 继续读取bin文件,检查读取是否正确,回到5继续循环
来到这里已是退出循环了,也就是说咱们已经将bin写入Flash完成了,准备跳转到新程序运行
5.3跳转到新程序流程其实上面已经讲过了,这里继续啰嗦,截图:
l 重定位中断向量
l 设置栈指针
l 跳转到复位向量(开始运行App程序)
说明一下,在这里重定位中断向量实际上是多余的,App程序执行初始化后,又回到STM32初始状态,因此在App程序中须要执行重定位中断向量表操做,具体同以上操做相同。
啰嗦了又一遍,BootLoader彻底结束,感谢你们都支持啦~
#include "main.h"
int main(void)
{
SystemInit(); //配置系统时钟为72M
LED_GPIO_Config(); //初始化LED端口
Debug_TraceIOEnable(); //使能调试printf的IO口
Flash_Init(); //初始化Flash
FAT_Init(); //初始化文件系统
BootLoader_FromSDCard(); //Bootloader从SD卡更新固件
while(1)
{
LED_StatShow( FuncErr); //LED显示Bootloader状态 }