STM32的BootLoader升级从串⼝升级固件
①Jump_To_Application  = (pFunction)(*(vu32*) (IAPSTART + 4));
__MSR_MSP(*(vu32*) IAPSTART);
Jump_To_Application();
跟踪__MSR_MSP(⼀般这个函数都在库⽂件⾥有,跟踪不到就⽤搜索)到汇编函数为
__MSR_MSP
MSR MSP, r0 ; set Main Stack value
BX r14
②//跳转到应⽤程序
//appxaddr:⽤户代码起始地址.
void iap_load_app(u32 appxaddr)
{
if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000)//检查栈顶地址是否合法.
{
jump2app=(iapfun)*(vu32*)(appxaddr+4);//⽤户代码区第⼆个字为程序开始地址(复位地址)
MSR_MSP(*(vu32*)appxaddr);//初始化APP堆栈指针(⽤户代码区的第⼀个字⽤于存放栈顶地址)
jump2app(); //跳转到APP.
}
}
跟踪MSR_MSP到函数为
//设置栈顶地址
//addr:栈顶地址
__asm void MSR_MSP(u32 addr)
{
MSR MSP, r0 //set Main Stack value
BX r14
}
③  //判断⽤户是否已经下载程序,因为正常情况下此地址是栈地址。
//若没有这⼀句的话,即使没有下载程序也会进⼊⽽导致跑飞。
if (((*(__IO uint32_t*)ApplicationAddress) & 0x2FFE0000 ) == 0x20000000)
{
SerialPutString("Execute user Program\r\n\n");
//跳转⾄⽤户代码
JumpAddress = *(__IO uint32_t*) (ApplicationAddress + 4);
Jump_To_Application = (pFunction) JumpAddress;
//初始化⽤户程序的堆栈指针
__set_MSP(*(__IO uint32_t*) ApplicationAddress);
Jump_To_Application();
mike隋}
跟踪__set_MSP到函数为
__ASM void __set_MSP(uint32_t mainStackPointer)
{
msr msp, r0
bx lr
}
溺的拼音
总结以上发现都是操作ARM的R0跟R14(LR)寄存器。
还有⼀种不太⼀样的,就是stm32F4的库函数中的跳转,如下所⽰
④ //测试⽤户app地址是不是在APPLICATION_ADDRESS位置。检测栈顶的地址,来检验app是否下载成功
if (((*(__IO uint32_t*)APPLICATION_ADDRESS) & 0x2FFE0000 ) == 0x20000000)
{
//APPLICATION_ADDRESS + 4对应的是app中断向量表的第⼆项,复位地址
JumpAddress = *(__IO uint32_t*) (APPLICATION_ADDRESS + 4);
//把地址强转为函数指针
Jump_To_Application = (pFunction) JumpAddress;
//设置主函数栈指针
__set_MSP(*(__IO uint32_t*) APPLICATION_ADDRESS);
//调⽤函数,实际失去app复位地址去执⾏复位操作
Jump_To_Application();
}
跟踪__set_MSP到函数为
static __INLINE void __set_MSP(uint32_t topOfMainStack)
{
register uint32_t __regMainStackPointer    __ASM("msp");av美女排行榜
__regMainStackPointer = topOfMainStack;
}
对于M4的这个库函数我也不太懂,感觉最终的操作应该跟其他的⼀样吧
⼆,关于跳转部分的代码的理解(转)
这⾥重点说⼀下⼏句经典且⾮常重要的代码:
第⼀句: if (((*(__IO uint32_t*)ApplicationAddress) & 0x2FFE0000 ) == 0x20000000)  //判断栈定地址值是否在0x2000 0000 - 0x 2000 2000之间
怎么理解呢? (1),在程序⾥#define ApplicationAddress    0x8003000 ,*(__IO uint32_t*)ApplicationAddress)  即取0x8003000开始到
0x8003003 的4个字节的值, 因为我们的应⽤程序APP中设置把中断向量表放置在0x08003000 开始的位置;⽽中断向量表⾥第⼀个放的就是栈顶地址的值
也就是说,这句话即通过判断栈顶地址值是否正确(是否在0x2000 0000 - 0x 2000 2000之间)来判断是否应⽤程序已经下载了,因为应⽤程序的启动⽂件刚开始就去初始化化栈空间,如果栈顶值对了,说应⽤程已经下载了启动⽂件的初始化也执⾏了;
第⼆句:    JumpAddress = *(__IO uint32_t*) (ApplicationAddress + 4);  [  common.c⽂件第18⾏定义了:  pFunction
Jump_To_Application;]
ApplicationAddress + 4  即为0x0800 3004 ,⾥⾯放的是中断向量表的第⼆项“复位地址”  JumpAddress = *(__IO uint32_t*) (ApplicationAddress + 4); 之后此时JumpAddress
第三句:Jump_To_Application = (pFunction) JumpAddress;
startup_stm32f10x_md_lv. ⽂件中别名typedef  void (*pFunction)(void);    这个看上去有点奇怪;正常第⼀个整型变量  typedef  int  a;  就是给整型定义⼀个别名 a
void (*pFunction)(void);  是声明⼀个函数指针,加上⼀个typedef 之后  pFunction只不过是类型 void (*)(void) 的⼀个别名;例如:
所以,Jump_To_Application = (pFunction) JumpAddress ;  此时Jump_To_Application 指向了复位函数所在的地址;
第四 、五句: __set_MSP(*(__IO uint32_t*) ApplicationAddress);      \\设置主函数栈指针
Jump_To_Application();                        \\执⾏复位函数Jump_To_Application()是把⽤户代码的复位地址付给PC
指针,我看到Jump_To_Application()这句代码debug 的时候对应的汇编代码是从SD 卡升级固件
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. 分⼏步实现BootLoader6种人不能爬泰山
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      跳转到新程序流程
2. 初识BootLoader可能有的同学听说过BootLoader,有的同学没有听说过,这个都很正常。关于BootLoader的概念⼤家可以上⽹查⼀下,有⽐较详细的说明,我在这⾥说说我⾃⼰⽐较⽚⾯的理解,并且是针对CortexM3说明的,实现平台为STM32F103VET6。
2.1百度百科的BootLoader这⾥借⽤⼀下百度百科对BootLoader的解释。在嵌⼊式操作系统中,BootLoader是在操作系统内核运⾏之前运⾏。可以初始化硬件设备、建⽴内存空间映射图,从⽽将系统的软硬件环境带到⼀个合适状态,以便为最终调⽤操作系统内核准备好正确的环境。在嵌⼊式系统中,通常并没有像BIOS那样的固件程序(注,有的嵌⼊式CPU也会内嵌⼀段短⼩的启动程序),因此整个系统的加载启动任务就完全由BootLoader来完成。在⼀个基于ARM7TDMIcore的嵌⼊式系统中,系统在上电或复位时通常都从地
址0x00000000处开始执⾏,⽽在这个地址处安排的通常就是系统的BootLoader程序。
2.2BootLoader的简单理解BootLoader 就是单⽚机启动时候运⾏的⼀段⼩程序,这段程序负责单⽚机固件更新,也就是单⽚机选择性的⾃⼰给⾃⼰下程序。可以更新也可以不更新,更新的话,BootLoader 更新完程序后,跳转到新程序运⾏;不更新的话,BootLoader 直接跳转到原来的程序去运⾏。
需要注意的是:BootLoader下载新程序后并不擦除⾃⼰(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
__initial_spTop就是栈指针,Reset_Handler是复位向量。这⾥只显⽰了16个向量,CortexM3单⽚机的话总共有256个向量,也就是从栈指针的地址开始有1KB的区域属于中断向量表。
单⽚机启动默认先运⾏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)