STM32F4单片机bootloader及在线升级IAP基本原理

STM32F407基础总结系列(三)



一 、前言

在线升级是产品开发维护的必要功能之一,对产品的迭代优化、需求定制、稳定性提升以及人力节约上提供了很大帮助。就像我们的手机更新系统一样不断地升级不断地有新的体验。
此部分内容从基于STM32F407进行BootLoader、内存分布、IAP升级原理、升级模式等方面进行介绍,其它类型的单片机可进行参考设置。


二、背景知识补充

先梳理一下STM32启动过程以及变量、代码在内存中分布过程。

2.1 系统启动过程

了解STM32的启动过程可以从启动文件以及上电执行了一些流程开始。
首先是启动文件,启动文件的结构如下图所示:
在这里插入图片描述

这里还有一点也要说一下,如果使用了c++的类定义了对象,构造函数会在标准库__main中执行,也就是在执行主函数main前所有的全局变量构造函数均已初始化完成。
中断函数的地址是由keil编译器固定好的,更改编程ROM初始地址,中断函数地址会相应的在生成的bin文件中被改掉。

2.2 内存映射分布

下面说一下内存映射的过程:
在这里插入图片描述

在这里插入图片描述
单片机有两个引脚会引出来,当外部给不同电平时产生不同的内部地址映射,正常用的就是从主Flash映射,从系统存储器映射常用来ISP串口一键下载,从SRAM映射常用来调试程序,解锁flash等。

在这里插入图片描述
当从0x00000000映射到FLASH上时,0x00000000-0x000fffff就相当于FLASH0x08000000-0x080fffff的别名,操作谁都一样,就像指向指针的指针。
接下来就是代码变量堆栈的分布情况了,这里以从flash启动为例。
正常编写完程序,用keil编译后生成的bin文件或是烧写程序到单片机都是只有FLASH(相当于ROM)里的内容,具体可以通过map文件查看。

Memory Map of the image

  Image Entry point : 0x08000189

  Load Region LR_IROM1 (Base: 0x08000000, Size: 0x000005a8, Max: 0x00080000, ABSOLUTE)

    Execution Region ER_IROM1 (Exec base: 0x08000000, Load base: 0x08000000, Size: 0x00000590, Max: 0x00080000, ABSOLUTE)

    Exec Addr    Load Addr    Size         Type   Attr      Idx    E Section Name        Object

    0x08000000   0x08000000   0x00000188   Data   RO          232    RESET               startup_stm32f40_41xxx.o
    0x08000188   0x08000188   0x00000008   Code   RO          290  * !!!main             c_w.l(__main.o)
    0x08000190   0x08000190   0x00000034   Code   RO          449    !!!scatter          c_w.l(__scatter.o)
    0x080001c4   0x080001c4   0x0000001a   Code   RO          451    !!handler_copy      c_w.l(__scatter_copy.o)
    0x080001de   0x080001de   0x00000002   PAD
    0x080001e0   0x080001e0   0x0000001c   Code   RO          453    !!handler_zi        c_w.l(__scatter_zi.o)
    0x080001fc   0x080001fc   0x00000002   Code   RO          317    .ARM.Collect$$libinit$$00000000  c_w.l(libinit.o)
    0x080001fe   0x080001fe   0x00000004   Code   RO          323    .ARM.Collect$$libinit$$00000001  c_w.l(libinit2.o)
    0x08000202   0x08000202   0x00000000   Code   RO          326    
    0x08000226   0x08000226   0x00000002   PAD
    0x08000228   0x08000228   0x00000010   Code   RO          214    .emb_text           sys.o
    0x08000238   0x08000238   0x0000001c   Code   RO            3    .text               main.o
    0x08000254   0x08000254   0x0000001a   Code   RO          139    .text               stm32f4xx_it.o
    0x0800026e   0x0800026e   0x00000002   PAD
    0x08000270   0x08000270   0x00000210   Code   RO          188    .text               system_stm32f4xx.o
    0x08000480   0x08000480   0x00000040   Code   RO          233    .text               startup_stm32f40_41xxx.o
    0x080004c0   0x080004c0   0x0000002c   Code   RO          259    .text               iap.o
    0x080004ec   0x080004ec   0x00000006   Code   RO          288    .text               c_w.l(heapauxi.o)
    0x080004f2   0x080004f2   0x0000004a   Code   RO          308    .text               c_w.l(sys_stackheap_outer.o)
    0x0800053c   0x0800053c   0x00000012   Code   RO          310    .text               c_w.l(exit.o)
    0x0800054e   0x0800054e   0x00000002   PAD
    0x08000550   0x08000550   0x00000008   Code   RO          318    .text               c_w.l(libspace.o)
    0x08000558   0x08000558   0x0000000c   Code   RO          379    .text               c_w.l(sys_exit.o)
    0x08000564   0x08000564   0x00000002   Code   RO          390    .text               c_w.l(use_no_semi.o)
    0x08000566   0x08000566   0x00000000   Code   RO          392    .text               c_w.l(indicate_semi.o)
    0x08000566   0x08000566   0x0000000a   Code   RO          377    x$fpl$fpinit        fz_wm.l(fpinit.o)
    0x08000570   0x08000570   0x00000020   Data   RO          447    Region$$Table       anon$$obj.o

    Execution Region RW_IRAM1 (Exec base: 0x20000000, Load base: 0x08000590, Size: 0x00000678, Max: 0x00020000, ABSOLUTE)

    Exec Addr    Load Addr    Size         Type   Attr      Idx    E Section Name        Object

    0x20000000   0x08000590   0x00000014   Data   RW          189    .data               system_stm32f4xx.o
    0x20000014   0x080005a4   0x00000004   Data   RW          260    .data               iap.o
    0x20000018        -       0x00000060   Zero   RW          319    .bss                c_w.l(libspace.o)
    0x20000078        -       0x00000200   Zero   RW          231    HEAP                startup_stm32f40_41xxx.o
    0x20000278        -       0x00000400   Zero   RW          230    STACK               startup_stm32f40_41xxx.o

在这里插入图片描述
通过编译的信息可以总结出stm32内存分布的情况,编译烧写目标为flash,上电运行到main函数前完成对ram的分配,ram中全局不为零变量从flash中复制,全局为零以及堆栈大小的信息包含在flash中的code中(具体在哪里暂时没找到,不过启动文件中可以设置堆栈大小的就证明了此部分的信息)在c语言库函数__mian()执行,运行过程中对堆栈进行操作。这里有一点药注意,当你操作msp堆栈指针时会清空局部变量的值。所以在BootLoader中尽量使用全局变量。
在这里插入图片描述
到这里基本启动过程以及内存分布就差不多了,iap操作就是操作这些地址及变量的分布。

三、bootloader

BootLoader的意思就是启动引导,跟电脑启动先加载主板BIOS一样,引导程序从哪一部分映射地址启动。
BootLoader在stm32可分成两类:一类是原厂自带的boot,也叫做自举程序,一般用作ISP串口下载程序;另一类属于用户自定义的,用于制作IAP,跳转不同flash区域的代码。

3.1 原厂BootLoader

在这里插入图片描述
原厂BootLoader在0x1fff 0000 - 0x1fff 77ff的stm32内部存储器内,大小为30K。通过boot引脚设置 boot0= 1;boot1= 0即可进入。
在这里插入图片描述
STM32F407支持串口、can、USB进行自举升级程序。具体升级协议可自行参考stm32F407中文参考手册。原厂bootloader与flash的组合构成一种基本的升级模式,但会覆盖源程序。

3.2 自定义BootLoader

此部分位于用户操作的flash区域,定义为执行用户逻辑程序APP前的一段引导。
在这里插入图片描述
这部分可以引导具体执行从flash中哪个地址开始的程序。但基本流程是烧写两短代码BootLoader段和APP段。

跳转代码很简单,执行一个函数即可,如下:

iapfun jump2app; //定义函数指针
//跳转到应用程序段
//appxaddr:用户代码起始地址.
void iap_load_app(u32 appxaddr)
{
    
    
	if(((*(vu32*)appxaddr)&0x2FFE0000)==0x20000000)	//检查栈顶地址是否合法.
	{
    
     
		jump2app=(iapfun)*(vu32*)(appxaddr+4);		//用户代码区第二个字为程序开始地址(复位地址)		
		MSR_MSP(*(vu32*)appxaddr);					//初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
		jump2app();									//跳转到APP.
	}
}	

总的来说是两个操作,一个是将复位函数地址赋值给函数指针,设置堆栈栈顶的地址,第二个是执行函数指针指向的复位函数的地址。
flash源码文件的正确执行就是要正确设置PC和堆栈指针。PC指针指向当前运行的函数,如复位函数;主堆栈指针MSP与函数的嵌套有关,没有这个函数之间的嵌套跳转无法实现。
具体使用可以这样,根据不同标志位跳转不同flash区域的代码,起到了一个引导加载不同程序的作用。

		switch (Flag_Table.ull_updaterun_flag)
				{
    
    
					case 0x00000011://跳转APP1
								{
    
    
									if(((*(vu32*)(STM32_FLASH_APP1_BASE+4))&0xFF000000)==0x08000000)//判断是否为Flash区域
									{
    
    	
											if(Flag_Table.ull_vetofflen!=0x20000)
											{
    
    
												Flag_Table.ull_vetofflen =0x20000;//写入APP1的向量表偏移长度
												WriteFlagTable(&Flag_Table);//将标志位的表写入	
											}												
										iap_load_app(STM32_FLASH_APP1_BASE);//程序跳转
									}
							
								}
						break;
					case 0x00000022://跳转APP2
								{
    
    
									if(((*(vu32*)(STM32_FLASH_APP2_BASE+4))&0xFF000000)==0x08000000)//判断是否为Flash区域
									{
    
    	 
											if(Flag_Table.ull_vetofflen!=0x40000)
											{
    
    
												Flag_Table.ull_vetofflen =0x40000;//写入APP1的向量表偏移长度
												WriteFlagTable(&Flag_Table);//将标志位的表写入	
											}								
										iap_load_app(STM32_FLASH_APP2_BASE);//程序跳转
									}		
									
								}						
						break;
					default://默认在当前BootLoader中						
						break;
				}

四、iap升级

单片机程序的烧录除了使用烧写器下载(ICP)和原厂BootLoader通过串口等外设烧录(ISP)外便是在源程序正常运行的过程中进行修改内部FLASH进行升级操作(IAP),如果对接IAP的外设是无线网模块则可实现远程空中升级(OTA)。

IAP升级意味着需要在程序运行过程中进行升级跳转,无需操作boot引脚,实现更加自由灵活的升级方式。

4.1 基本原理

IAP升级的基本原理就是流程图的后半段,通过BootLoader确定是否升级,升级的话去读写flash将代码写进入,不升级就跳转运行正常逻辑代码。
在这里插入图片描述

4.2 升级模式

升级模式大致分为以下几种更多的均在此基础上进行的演变。

4.2.1 原厂bootloader+app

在这里插入图片描述
此部分常用的名字叫做ISP自举下载。常规项操作,使用不方便需要操作外部boot的电平高低,基本不推荐。

4.2.2 自定义bootloader+app

在这里插入图片描述
单区模式,用自定义的BootLoader代替原厂的,操作方便,通过正常逻辑代码中的外设进行指令级别的跳转无需配置boot引脚电平。缺点是无备份功能,在大容量flash单片机中有些空间未被使用,不用来做程序旧版本保存太浪费。

4.2.3 自定义bootloader+app1+app2

在这里插入图片描述
双区模式,代码备份功能具备,当然对应的对于小容量产品就显的比较占用空间。双区模式又根据APP区的用法分为两种:一种是APP1只作为运行区域的代码,APP2只做备份区。另外一种是,APP1和APP2均可作为运行区域的代码区,随意切换,APP1运行时APP2区便作为备份区。
第一种比较好实现,缺点就是每次升级都需要做一次代码的对调。第二种比较难以实现,因为flash中中断向量表地址偏移是固定的以及中断向量地址也是固定的,每一次编译后地址不会更改。简单点说就是同一份代码更改向量表偏移地址,生成的flash烧写文件BIN文件不一样,因此每次升级需要在单片机中更改源码文件。

五、iap升级实例

这里以我测试用的双区模式为例,使用一个标志位进行APP1和APP2区域的切换。只讲基本过程,具体的串口升级协议没有添加,大家仅供参考。

5.1 flash分区

在这里插入图片描述
首先需要根据flash的情况进行分区确定每个扇区的功能,这次采用双BootLoader的设置,使用BootLoader1跳过sector1的分区,直接引导至BootLoader2,这样做的好处是把标志位放在了一个较小的分区,尽量减少空间浪费,正常Flag标志也存不了几个量。
BootLoader2占空扇区2-4,大小96k。sector5-7存储APP1,sector8-10存储APP2.最后sector11预留使用。
标志位区内容
在这里插入图片描述
目前标志位存两个变量,一个是用于跳转的flag当值为0x11时BootLoader2引导跳转至APP1区,当值为0x22时跳转至APP2区,其它值不跳转,,vetOffLen存储向量表偏移值,APP1对应0x20000,APP2对应0x40000。

5.2 升级方案设计

升级方案如下图所示
在这里插入图片描述

5.3 BootLoader设计

然后是BootLoader设计,BootLoader1的功能就是引导跳转至BootLoader2区,跳过flag区。
在这里插入图片描述
源码如下:

int main(void)
{
    
    

	while(1)
	{
    
    
			if(((*(vu32*)(0x08008000+4))&0xFF000000)==0x08000000)//判断是否为0X08XXXXXX.
			{
    
    	 
				iap_load_app(0x08008000);//执行FLASH APP代码
			}
	}  
}

BootLoader2的方案也很简单根据跳转标志跳转至APP1还是APP2,否则停留在当前BootLoader2中,同时进行将相对应的的中断向量表偏移长度写进flag区中。
在这里插入图片描述

源码:

#include <string.h>
#include "stm32_flash.h"  
#include "App_Init.h"

int main(void)
{
    
    
  GPIO_InitTypeDef GPIO_InitStructure;
	Sysint();//系统初始化
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOF, ENABLE);
  /* GPIO初始化 */
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
  GPIO_Init(GPIOF, &GPIO_InitStructure);
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
  GPIO_Init(GPIOF, &GPIO_InitStructure);
	
  RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE);
  /* GPIO初始化 */
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
  GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
  GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
  GPIO_Init(GPIOE, &GPIO_InitStructure);
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
  GPIO_Init(GPIOE, &GPIO_InitStructure);	
  GPIO_SetBits(GPIOF, GPIO_Pin_9);
  GPIO_ResetBits(GPIOF, GPIO_Pin_10);	
	
	while(1)
	{
    
    
		delay_ms(100);		
		ReadFlagTable(&Flag_Table);//将标志位的表读	
	
		if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_3)==0)
		{
    
    
				Flag_Table.ull_updaterun_flag =0x00000011;//写入APP1的向量表偏移长度		
		}
		else if(GPIO_ReadInputDataBit(GPIOE,GPIO_Pin_4)==0)
		{
    
    
				Flag_Table.ull_updaterun_flag =0x00000022;//写入APP1的向量表偏移长度		
		}		
		switch (Flag_Table.ull_updaterun_flag)
				{
    
    
					case 0x00000011://跳转APP1
								{
    
    
									if(((*(vu32*)(STM32_FLASH_APP1_BASE+4))&0xFF000000)==0x08000000)//判断是否为Flash区域
									{
    
    	
											if(Flag_Table.ull_vetofflen!=0x20000)
											{
    
    
												Flag_Table.ull_vetofflen =0x20000;//写入APP1的向量表偏移长度
												WriteFlagTable(&Flag_Table);//将标志位的表写入	
											}												
										iap_load_app(STM32_FLASH_APP1_BASE);//程序跳转
									}
							
								}
						break;
					case 0x00000022://跳转APP2
								{
    
    
									if(((*(vu32*)(STM32_FLASH_APP2_BASE+4))&0xFF000000)==0x08000000)//判断是否为Flash区域
									{
    
    	 
											if(Flag_Table.ull_vetofflen!=0x40000)
											{
    
    
												Flag_Table.ull_vetofflen =0x40000;//写入APP1的向量表偏移长度
												WriteFlagTable(&Flag_Table);//将标志位的表写入	
											}								
										iap_load_app(STM32_FLASH_APP2_BASE);//程序跳转
									}		
									
								}						
						break;
					default://默认在当前BootLoader中
								{
    
    

									GPIO_ToggleBits(GPIOF, GPIO_Pin_9);
									GPIO_ToggleBits(GPIOF, GPIO_Pin_10);									
								}
						
						break;
				}
	}

}

这个增加了一个按键选择功能,帮助测试APP1和APP2区代码的跳转。

5.4 APP中升级处理

讲APP中升级处理前,先将一下APP区代码的结构,如下所示,不同分区中每一份代码都要包含三部分,第一部分中断向量表,表中存储了各个中断函数的地址;第二部分为中断函数;第三部分为主逻辑代码,可理解为main函数。正常中断的执行过程是:发生中断,单片机查询中断向量表,通过中断向量表找到中断函数的地址,执行中断函数后跳回源main函数中。每一APP代码只能使用自己中断向量表以及中断函数,因此有一个向量表偏移长度,让单片机能够通过相对于0X0800 0000的偏移找到对应APP代码的中断函数。
在这里插入图片描述
偏移设置原位置在启动文件中对应的系统初始化void SystemInit(void)中,因此在每一份程序的main函数要要增加一行代码设置向量表偏移,如下所示在flash基地址0x0800 0000 基础上偏移0x20000:

SCB->VTOR = FLASH_BASE | Flag_Table.ull_vetofflen; /* Vector Table Relocation in 

示例如下:

int main(void)
{
    
    
	ReadFlagTable(&Flag_Table);//将标志位的表读	
	SCB->VTOR = FLASH_BASE | Flag_Table.ull_vetofflen; /* Vector Table Relocation in Internal FLASH */		
	Sysint();//系统初始化
	while(1)
	{
    
    
	}

APP中升级方案如下:

在这里插入图片描述
前半部分为正常逻辑流程,后半部分为升级流程,当单片机收到升级指令后,将升级文件写到另外一个APP区,最后根据发送的校验信息完成最终flag标志位的写入然后进行复位操作。

测试代码如下:
单片机中将bin形式的升级文件接收到数组auch_UartRxBuf[]中,接收完成将标志位uch_updateflag置位,主循环中判断成功后,将数组auch_UartRxBuf[]写入另外一个APP对应的flash区域内。写入成功后将运行标志ull_updaterun_flag以及偏移长度ull_vetofflen写入flag分区中。然后关中断重启即可。

int main(void)
{
    
    	
	ReadFlagTable(&Flag_Table);//将标志位的表读	
	SCB->VTOR = FLASH_BASE | Flag_Table.ull_vetofflen; /* Vector Table Relocation in Internal FLASH */		
	Sysint();//系统初始化
	for(int i=0;i<5;i++)
	{
    
    
		delay_ms(500);
		GPIO_ToggleBits(GPIOF, GPIO_Pin_9);	
	}		
	while(1)
	{
    
    
		delay_ms(1000);	
		GPIO_ToggleBits(GPIOF, GPIO_Pin_9);				
		if(uch_updateflag==1)
		{
    
    
			uch_updateflag=0;			
			if(Flag_Table.ull_updaterun_flag==0x00000011)
			{
    
    
				ull_vetselect = 0x40000;//更换BIN文件中的偏移				
				if(WriteAppBinToFlash2(STM32_FLASH_APP2_BASE,auch_UartRxBuf,uch_updataLen))
				{
    
    
					Flag_Table.ull_updaterun_flag=0x00000022;
					Flag_Table.ull_vetofflen = 0x40000;
					WriteFlagTable(&Flag_Table);//将标志位的表写入	
					INTX_DISABLE();//close interupt
					NVIC_SystemReset();//重启								
				}	
			}
			else if(Flag_Table.ull_updaterun_flag==0x00000022)
			{
    
    
				ull_vetselect = 0x20000;//更换BIN文件中的偏移
				if(WriteAppBinToFlash2(STM32_FLASH_APP1_BASE,auch_UartRxBuf,uch_updataLen))
				{
    
    		
					Flag_Table.ull_updaterun_flag=0x00000011;
					Flag_Table.ull_vetofflen = 0x20000;					
					WriteFlagTable(&Flag_Table);//将标志位的表写入	
					INTX_DISABLE();//close interupt					
					NVIC_SystemReset();//重启									
				}	
	
			}
		}
}

因为不同烧写地址偏移对应的bin文件不同(最大不同是向量表地址都是固定的),所以在升级文件写入flash时要更改源码文件。
更改程序烧写首地址如图所示。
在这里插入图片描述

相同源码更改烧写地址后bin文件的不同。

在这里插入图片描述
通过对比发现固定不同的是偏移,图中左边偏移为0x40000右边为0x20000,如果想要将不同的bin文件从APP1移动到APP2所在的flash区域,只需要将bin文件中的偏移更改掉即可。更改方式如下:

//将升级代码写入临时存储区 
u32 WriteAppBinToFlash2(u32 appxaddr,u8 *appbuf,u32 appsize)
{
    
    
	u32 t;
	u32 iapbuf[UpdatePacketMaxLen/4];//缓存
	u16 i=0;
	u32 temp;
	u32 fwaddr=appxaddr;//当前写入的地址
	u8 *dfu=appbuf;
	memset(&iapbuf,0xff,sizeof(iapbuf));
	for(t=0;t<appsize;t+=4)
	{
    
    						   
		temp=(u32)dfu[3]<<24;   
		temp|=(u32)dfu[2]<<16;    
		temp|=(u32)dfu[1]<<8;
		temp|=(u32)dfu[0];	  
		dfu+=4;//偏移4个字节
		if((((temp)&0xFFFF0000)==(FLASH_BASE | Flag_Table.ull_vetofflen))&&(((temp)&0x0000FFFF)!=0X00000000))
		{
    
    
			temp = ((temp)&0x0000FFFF)|(FLASH_BASE|ull_vetselect);		
		}
		iapbuf[i++]=temp;	    
		if(i==UpdatePacketMaxLen/4)
		{
    
    
			i=0; 
			if(STMFLASH_Write(fwaddr,iapbuf,UpdatePacketMaxLen/4))
		  	fwaddr+=UpdatePacketMaxLen;//偏移  UpdatePacketMaxLen=N*4
			else
				return FALSE;
		}
	} 
	if(i)
	{
    
    
		if(!STMFLASH_Write(fwaddr,iapbuf,i))//将最后的一些内容字节写进去. 
			return FALSE;
	}
  return fwaddr;
}

在读取每个4字节数据时判断偏移量是否为当前APP区域的偏移,等于就改写偏移。

		if((((temp)&0xFFFF0000)==(FLASH_BASE | Flag_Table.ull_vetofflen))&&(((temp)&0x0000FFFF)!=0X00000000))
		{
    
    
			temp = ((temp)&0x0000FFFF)|(FLASH_BASE|ull_vetselect);		
		}

最后补充一下boot表以及bin文件生成的知识点。
bin文件的生成需要设置一下
在这里插入图片描述
红色圈中文件需要对应你们工程目录中的.axf文件。

正常情况下BootLoader1、BootLoader2、APP1、APP2四份代码需要更改烧写首地址用keil进行四次烧写。为了方便我们可以在APP1中将BootLoader1、flag、BootLoader2以boot数组表的形式存在。如下

const u32 bootflag[]  __attribute__((at(STM32_FLASH_UPDATERUN_FLAG_BASE))) ={
    
    
	0x00000011,//升级运行标志  
	0x00008000 //变量表偏移长度	
};
const u8 bootloader1[]  __attribute__((at(STM32_FLASH_BASE))) ={
    
    
0x78,0x06,0x00,0x20,0x81,0x04,0x00,0x08,0x55,0x02,0x00,0x08,0x57,0x02,0x00,0x08,
0x5B,0x02,0x00,0x08,0x5F,0x02,0x00,0x08,0x63,0x02,0x00,0x08,0x00,0x00,0x00,0x00,
                               。。。
0x00,0x7A,0x03,0x0A,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x02,0x03,0x04,
0x06,0x07,0x08,0x09,0x00,0x00,0x00,0x00
};

这样每次烧写,APP1会烧写在对应的偏移区域,BootLoader1、BootLoader2、flag会烧写在指定地址内。
boot表数组值从bin文件中提取,可以使用UltraEdit进行打开复制提取。
在这里插入图片描述

在这里插入图片描述
最后进行复制粘贴到keil代码里的数组中(轻松加愉快!)

5.5 测试

为方便测试此次升级直接将bin文件通过单片机串口发送至数组中,发送16进制的 7d 7d 7b 7b当做校验。
使用vofa+进行了串口测试,也可以使用其它的,
在这里插入图片描述

测试资源文件可参考下载使用
示例工程代码


六、总结

总体上内容看起来很多其实原理非常简单,基本上就是flash的读写,BootLoader方案设计好基本都能实现。
本文仅从原理及基本实现上进行了讲解,实际使用可做具体优化:BootLoader函数指针数组化,升级串口增加通信协议,使用.s19文件或hex文件代替bin文件进行升级等。
加油!遇到问题迎难而上,每次的积累都是一次进步!

猜你喜欢

转载自blog.csdn.net/weixin_43058521/article/details/125355343