stm32-IAP(在线升级程序)

第一章 背景知识

什么是IAP?

IAP的知识网上的各种资料也说的比较明白,在此简单介绍一下。IAP( In Application Programming)即在线应用编程,也就是用户可以使用自己的程序对单片机的User Flash的某一区域(一般为存放自己程序的区域)进行烧写。在真正的工作中产品发布后,可以很方便的使用预留的通信接口(串口、USB、网口、蓝牙等)来完成程序的升级,从而避免了把机器拆开使用下载器烧写程序。
要实现IAP功能一般要设计两部分代码,

  • 一是BootLoader程序,这部分程序存储在FLASH的某一位置,主要用来引导、升级App程序;
  • 二是App程序,这个程序才是实现产品的功能程序。通过BootLoader来完成对App程序的更新升级,这就是IAP功能。

最终要实现的是:

  • 单片机每次上电会先运行Boot程序,检查标志位如果标志位为FLAG_TO_APP则直接跳转到App程序运行,如果标志位为FLAG_TO_BOOT,则运行Boot程序准备升级。
  • App程序在运行时,正常情况下正常完成其功能,当接收到升级的指令后(升级指令一般来自某个外设的中断,如上位机从串口发来升级指令,或者上位机从网络发来升级指令)会在FLASH中的某处空间(参数区)写下升级的标志位FLAG_TO_BOOT,并且加载Boot程序,Boot程序会接受新的程序文件并且存储在相应的FLASH空间里,完成升级后会在标志位的空间写下FLAG_TO_APP,并且运行新的程序。
一、stm32的内存映射

参考博文:STM32 IAP 在线升级详解
操作前我们先来说一下内存映射:
下图在stm32f100芯片手册的29页,我们只截取关键部分
在这里插入图片描述
在这里插入图片描述
注意: 根据启动方式不同,地址空间中从0x0000 0000到0x07FF FFFF这段空间,可以是flash空间或system memory的映射(别名)。

一般而言,flash内存的开始地址是0x0800 0000.
在这里插入图片描述

所以我们需要先查看一下misc.h文件中的中断向量表的初始位置宏定义为 NVIC_VectTab_Flash 0x0800 0000

还要设置编译器keil 中的 options for target 的target选项中的 IROM1地址 为0x0800 0000 大小为 0x20000即128K; IRAM1地址为0x2000 0000 大小为0x2000;
在这里插入图片描述
(提示:这一项IROM1 地址 即为当前程序下载到flash的地址的起始位置)

二、解析STM32的启动方式

具体的启动文件和解读,请见另一篇博文:stm32–启动文件(.s)与启动过程
当前的嵌入式应用程序开发过程里,并且C语言成为了绝大部分场合的最佳选择。如此一来main函数似乎成为了理所当然的起点——因为C程序往往从main函数开始执行。但一个经常会被忽略的问题是:微控制器(单片机)上电后,是如何寻找到并执行main函数的呢?很显然微控制器无法从硬件上定位main函数的入口地址,因为使用C语言作为开发语言后,变量/函数的地址便由编译器在编译时自行分配,这样一来main函数的入口地址在微控制器的内部存储空间中不再是绝对不变的。相信读者都可以回答这个问题,答案也许大同小异,但肯定都有个关键词,叫“启动文件”,用英文单词来描述是“Bootloader”。

无论性能高下,结构简繁,价格贵贱,每一种微控制器(处理器)都必须有启动文件,启动文件的作用便是负责执行微控制器从“复位”到“开始执行main函数”中间这段时间(称为启动过程)所必须进行的工作。最为常见的51,AVR或MSP430等微控制器当然也有对应启动文件,但开发环境往往自动完整地提供了这个启动文件,不需要开发人员再行干预启动过程,只需要从main函数开始进行应用程序的设计即可。

话题转到STM32微控制器,无论是keil uvision4还是IAR EWARM开发环境,ST公司都提供了现成的直接可用的启动文件,程序开发人员可以直接引用启动文件后直接进行C应用程序的开发。这样能大大减小开发人员从其它微控制器平台跳转至STM32平台,也降低了适应STM32微控制器的难度(对于上一代ARM的当家花旦ARM9,启动文件往往是第一道难啃却又无法逾越的坎)。

相对于ARM上一代的主流ARM7/ARM9内核架构,新一代Cortex内核架构的启动方式有了比较大的变化。ARM7/ARM9内核的控制器在复位后,CPU会从存储空间的绝对地址0x000000取出第一条指令执行复位中断服务程序的方式启动,即固定了复位后的起始地址为0x000000(PC = 0x000000)同时中断向量表的位置并不是固定的。
而Cortex-M3内核则正好相反,有3种情况:

  • 1、 通过boot引脚设置可以将中断向量表定位于SRAM区,即起始地址为0x2000000,同时复位后PC指针位于0x2000000处;
  • 2、 通过boot引脚设置可以将中断向量表定位于FLASH区,即起始地址为0x8000000,同时复位后PC指针位于0x8000000处(最常用的方式);
  • 3、 通过boot引脚设置可以将中断向量表定位于内置Bootloader区(system memory:0x1FFF F000—0x1FFF F7FF),本文不对这种情况做论述;
    而Cortex-M3内核规定,起始地址必须存放堆顶指针,而第二个地址则必须存放复位中断入口向量地址,这样在Cortex-M3内核复位后,会自动从起始地址的下一个32位空间取出复位中断入口向量,跳转执行复位中断服务程序。对比ARM7/ARM9内核,Cortex-M3内核则是固定了中断向量表的位置而起始地址是可变化的。
    在这里插入图片描述
三、stm32的启动过程

(1)没有IAP,只有APP时的正常启动流程:
STM32的FLASH地址起始于0x08000000,程序文件就从此地址开始写入。此外STM32内部通过“中断向量表”来响应中断,程序启动后,将首先从“中断向量表”取出复位中断向量执行复位中断程序完成启动,而“中断向量表”的起始地址是0x08000004,当中断来临,STM32的内部硬件机制亦会自动将PC指针定位到“中断向量表”处,并根据中断源取出对应的中断向量执行中断服务程序。
在这里插入图片描述
根据上图分析启动和运行过程,

① STM32在复位后,先从0X08000004地址取出复位中断向量的地址,并跳转到复位中断服务程序,
② 在复位中断服务程序执行完之后,会跳转到的main函数(如使用KEIL MDK调试时一下载进程序,会发现需要运行几次下一步才会跳转到main函数的位置)
③ main函数一般都是超循环体(while(1)死循环),在main函数执行过程中,如果收到中断请求(发生重中断),此时STM32强制将PC指针指回中断向量表处
④ 根据中断源进入相应的中断服务程序,
⑤ 在执行完中断服务程序以后,程序再次返回main函数执行。

(2)加入IAP后的启动流程
在这里插入图片描述

根据上图分析加入IAP后的起动和运行过程

① STM32复位后,还是从0X08000004地址取出复位中断向量的地址,并跳转到复位中断服务程序,在运行完复位中断服务程序之后跳转到IAP的main函数,如将IAP看作是一个APP的话,那么此部分和正常起动是一样的。(此步=执行复位中断服务程序+跳转main,即将正常运行的①和②合并了)。

② 在执行完IAP以后(固件升级或直接跳转),跳转至APP的复位向量表(APP的复位中断向量起始地址为0X08000004+N+M)。

③ 取出APP的复位中断向量的地址,并跳转执行新程序的复位中断服务程序,随后跳转至APP的main函数(此步=执行复位中断服务程序+跳转main)

④ 同样main函数为一个超循环,并且注意到此时STM32的FLASH,在不同位置上,共有两个中断向量表。在main函数执行过程中,如果CPU得到一个中断请求,PC指针仍强制跳转到地址0X08000004中断向量表处,而不是APP程序的中断向量表。

⑤ 程序再根据我们设置的中断向量表偏移量,跳转到对应中断源的APP的中断服务程序中,

⑥ 在执行完中断服务程序后,程序返回main函数继续运行。

四、IAP时flash的分配

IAP时,flash空间如何分布?
例子1:
我们在设计程序时把FLASH分成3部分,第一部分从0x08000000到0x0800FFFF共64K来存放BootLoader程序,第二部分为0x08010000 到0x0802FFFF共128K来存放App程序,第三部分从0x08030000开始到0x803FFFF共64K来存放程序运行的标志位和其他,如下所示:
在这里插入图片描述

例子2:
参考:如何使用STM32F4的BootLoader和APP程序
怎么使用stm32写IAP的bootloader和APP
在这里插入图片描述

  • 假设我用的是stm32f103c8t6,它的flash的大小是64k,所以把它分成如上所示:
    0x08000000 —0x0800 33FF分配给bootloader使用,大小是13k
    0x0800 3400----0x080097FF分配给第一个APP的使用,大小是25k
    0x08009800----0x0800 FBFF分配给第二个APP(或者缓存远程下载的新的代码文件)的使用,大小是25k
    0x0800FC00----0x0800 FFFF 分配给user_flag和其它标志使用,大小是1k

  • 在keil中设置rom的大小(因为bootloader和每个app都是单独的工程,所以要分别设置其rom的大小)
    A、bootloader中rom大小的设置
    在这里插入图片描述
    B、APP1中rom大小的设置
    在这里插入图片描述
    C、APP2(缓冲区)中rom大小的设置
    在这里插入图片描述

第二章 具体步骤

参考:STM32具备升级功能的bootloader及APP/IAP的实现
附件:示例工程链接
https://github.com/zengzhaorong/stm32_IAP-demo

一、整体规划

整体上,在flash上烧写2个程序,bootloader和APP。
bootloader程序位于0x80000000处,即默认的程序启动地址;
APP程序则位于bootloader程序的往后某地址,空间大小需自行定义。

flash空间规划
STM32F103RCT6的flash大小为256K。如我flash空间分配如下:
在这里插入图片描述
在这里插入图片描述

二、bootloader

bootloader的主要功能:校验数据、启动APP、升级APP。
bootloader的工作流程如下:

1、基础功能初始化(时钟、外设等);
主要进行一些BSP板级初始化:(仅供参考,因工程而异)

/******************************************************************/
/**
 * BSP初始化函数\n
 *
 */
/******************************************************************/
void BSP_Init(void)
{	
	/* MCU Configuration--------------------------------------------------------*/
	
	/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
	HAL_Init();	
	/* Configure the system clock */
	SystemClock_Config();	
	/* Initialize all configured peripherals */	
	/* Initialize GPIO */
	MX_GPIO_Init();	
	/* Initialize usart */
	MX_UART_Init(); 
	/* Initialize timer */
	Timer_ParamInit();
	MX_TIM3_Init();
	MX_TIM4_Init(); 
}

2、数据校验(参数区信息、APP程序的检验)
此步骤为后续启动或升级过程读取一些基础参数(存于参数区),以及校验数据的准确性等。

流程:先读取参数,判断下一步是直接启动APP还是留在bootloader等待升级。若是启动APP则校验APP程序数据是否正常。

  • 读取参数区的参数:如我将升级相关的参数写在此分区(目前仅是启动标志,具体可自行定义),根据此标志来判断下一步该如何走。
/* flash参数区信息结构 */
struct param_info
{
	UINT16		usStartFlag;	// 启动标志 0x0A-跳至APP 0x0B-等待升级 0x0F-已强制启动过
}
  • 校验数据:如我将校验APP程序区的数据是否正常,采用CRC校验。

3、跳转APP或升级APP。

3.1 如何跳转至APP呢?
跳转函数:

/*****************************************************************/
/**
 * 加载APP \n
 * 
 */
/******************************************************************/
void loadAPP(INT32U unLoadAddr)
{
	void (*fnJump2APP)(void);
	INT32U	unJumpAddr;
	
	if(((*(__IO INT32U *)unLoadAddr) & 0x2FFE0000) == 0x20000000)	/* 检查栈顶地址是否合法 */
	{
		printf("%s: ----------------------> run APP addr: 0x%x\r\n", __FUNCTION__, unLoadAddr); 
		/* 用户代码区第5~8字节为程序开始地址(复位地址) */
		unJumpAddr = *(__IO INT32U *)(unLoadAddr + 4);
		fnJump2APP = (void (*)(void))unJumpAddr;
		/* 初始化APP堆栈指针(用户代码区的前4个字节用于存放栈顶地址) */
		__set_MSP(*(__IO INT32U *)unLoadAddr);
		fnJump2APP();
	}
	else
	{
		printf("ERROR: %s: Stack top address is not valid! Can not run func!\r\n", __FUNCTION__);
		while(1);
	}
}

关于跳转部分的代码的理解(转)
这里重点说一下几句经典且非常重要的代码:
第一句: if ((((__IO uint32_t)ApplicationAddress) & 0x2FFE0000 ) == 0x20000000) //判断栈定地址值是否在0x2000 0000 - 0x 2000 2000之间
怎么理解呢? (1)在程序里#define ApplicationAddress 0x8003000 ,那么*(__IO uint32_t*)ApplicationAddress) 即取0x8003000开始到0x8003003 的4个字节的值,(2) 因为我们的应用程序APP中设置把 中断向量表 放置在0x08003000 开始的位置;而中断向量表里第一个放的就是栈顶地址的值。
也就是说,这句话即通过判断栈顶地址值是否正确(是否在0x2000 0000 - 0x 2000 2000之间,因为栈顶地址就是整个地址空间中SRAM所在的位置) 来判断是否应用程序已经下载了,因为应用程序的启动文件刚开始就去初始化栈空间,如果栈顶值对了,说应用程已经下载了并且启动文件的初始化也执行了;
第二句: 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;
此时Jump_To_Application指向了复位函数所在的地址。
( startup_stm32f10x_md_lv. 文件中别名 typedef void (pFunction)(void);
void (pFunction)(void); 是声明一个函数指针,加上一个typedef 之后 pFunction只不过是类型 void ()(void) 的一个别名)
第四 、五句: __set_MSP(
(__IO uint32_t*) ApplicationAddress); \设置主函数栈指针
Jump_To_Application(); \执行复位函数

3.2 升级APP
升级,即是将新的程序数据替换旧的程序数据,因此,只需在程序数据所在区域擦除旧数据再写上新数据即可。

具体而言,无非就是一包一包地接收数据,然后一包一包地写入flash。
最好分为两个阶段,防止升级文件传输不完整或损坏,导致变砖:

  • 文件下发阶段:接收到的升级包不要直接写入flash的APP区域,先存到另外的flash空间或外部存储区(如SD卡);
  • 更新阶段:等全部接收完成,再一次性将外部存储空间的数据写入flash的APP区域。

其中,通信是通过串口,自己定义协议传送数据,将程序数据拆成N个包,一包一包地传输,例如设定3条协议,分别是:开始升级、发送升级数据、升级完成确认(自定义啦)。
在这里插入图片描述
程序数据位置CPU内部flash区,因此需要以flash的读写擦等函数操作为基础。

/****************************************************************
* Func 	:
* Desc	:	读取CPU内部flash
* Input	:
* Output:
* Return:
*****************************************************************/
INT32 cpuflash_read(UINT32 unStartAddr, UINT8 *pData, UINT16 usSize)
{
 
	if(pData == NULL)
		return -1;
 
	memcpy(pData, (INT8U *)unStartAddr, usSize);
 
	return 0;
}
 
/****************************************************************
* Func 	:
* Desc	:	写入CPU内部flash (要先erase才能写)
* Input	:
* Output:
* Return:
*****************************************************************/
INT32 cpuflash_write(UINT32 unStartAddr, UINT8 *pData, UINT16 usSize)
{
	INT32 	i = 0;
    INT32 	nRet = 0;
    UINT16 	usTemp1 = 0;
    UINT16 	usTemp2 = 0;
    UINT16 	usTempALL = 0;
 
	
    if(usSize%2 != 0)
    {
        usSize += 1;
    }
 
	HAL_FLASH_Unlock();		// unlock
	
    for(i=0; i<usSize/2; i++)
    {
		usTemp1 = *pData;
		usTemp2 = *(pData+1);
		usTempALL = ((usTemp1&0X00FF) | ((usTemp2<<8)&0XFF00));
		//usTemp = ((*pData>>8)&0X00FF) | (*(pData+1)&0XFF00);
		//usTemp = *(INT16U *)pData;/*这个会导致硬件崩溃*/
		nRet = HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, unStartAddr, usTempALL);
		if(nRet != HAL_OK)
		{
			HAL_FLASH_Lock();		// lock
			printf("ERROR: %s: program[%d %d] failed-code[%d]\n", __FUNCTION__, usTemp1, usTemp2, nRet);
			return -1;
		}
	
		unStartAddr += 2;
		pData += 2;
	}
 
    HAL_FLASH_Lock();		// lock
 
	return 0;
}
 
/****************************************************************
* Func 	:
* Desc	:	擦除CPU内部flash(整页)
* Input	:
* Output:
* Return:
*****************************************************************/
INT32 cpuflash_erase(UINT32 unStartAddr, UINT32 unEndAddr)
{
	FLASH_EraseInitTypeDef	stEraseInit;
	UINT32		ucPageErr = 0;
    UINT32  	unTempAddr = 0;
	INT32		nRet = 0;
 
 
	HAL_FLASH_Unlock();		// unlock
 
	for(unTempAddr=unStartAddr; unTempAddr<=unEndAddr; unTempAddr+=FLASH_PAGE_SIZE)
	{
		stEraseInit.TypeErase = FLASH_TYPEERASE_PAGES;
		stEraseInit.PageAddress = unTempAddr;
		stEraseInit.NbPages = 1;
		nRet = HAL_FLASHEx_Erase(&stEraseInit, &ucPageErr);
		if(nRet != HAL_OK)
		{
			HAL_FLASH_Lock();
			return -1;
		}
		GPIO_feedDog();
	}
 
    HAL_FLASH_Lock();		// lock
 
	return 0;
}

顺便说下,STM32内部flash库的保护问题,若不加保护,则内部程序可轻易被J-Flash等工具读出。因此,常用的措施是:对内部flash添加读写保护机制。锁定与解除函数如下:

/****************************************************************
* Func 	:
* Desc	:	使能读保护函数
* Input	:
* Output:
* Return:
*****************************************************************/
void cpuflash_enableReadProtect(void)
{
  FLASH_OBProgramInitTypeDef OBInit;
  
  __HAL_FLASH_PREFETCH_BUFFER_DISABLE();
  
  HAL_FLASHEx_OBGetConfig(&OBInit);
  if(OBInit.RDPLevel == OB_RDP_LEVEL_0)
  {
  	printf("%s: ------------ set ----------\n", __FUNCTION__);
    OBInit.OptionType = OPTIONBYTE_RDP;
    OBInit.RDPLevel = OB_RDP_LEVEL_1;
    HAL_FLASH_Unlock();
    HAL_FLASH_OB_Unlock();
    HAL_FLASHEx_OBProgram(&OBInit);
    HAL_FLASH_OB_Lock();
    HAL_FLASH_Lock();
	//HAL_FLASH_OB_Launch();
  }
  __HAL_FLASH_PREFETCH_BUFFER_ENABLE();
 
}
 
/****************************************************************
* Func 	:
* Desc	:	失能读保护函数
* Input	:
* Output:
* Return:
*****************************************************************/
void cpuflash_disableReadProtect(void)
{
  FLASH_OBProgramInitTypeDef OBInit;
  
  __HAL_FLASH_PREFETCH_BUFFER_DISABLE();
  
  HAL_FLASHEx_OBGetConfig(&OBInit);
  if(OBInit.RDPLevel == OB_RDP_LEVEL_1)
  {
	  printf("%s: ------------ set ----------\n", __FUNCTION__);
    OBInit.OptionType = OPTIONBYTE_RDP;
    OBInit.RDPLevel = OB_RDP_LEVEL_0;
    HAL_FLASH_Unlock();
    HAL_FLASH_OB_Unlock();
    HAL_FLASHEx_OBProgram(&OBInit);
    HAL_FLASH_OB_Lock();
    HAL_FLASH_Lock();
	//HAL_FLASH_OB_Launch();
  }
  __HAL_FLASH_PREFETCH_BUFFER_ENABLE();
 
}
三、APP程序的编写

APP的主要功能: 1、除升级功能外的所有应用功能, 2、跳转至bootloader准备升级。

  • 中断向量表重映射:由于APP程序的起始地址的变化,所以导致我们的中断向量表也整体偏移了,所以需要在app程序起始添加一行代码,否则程序异常。
    例如偏移0x10000(根据实际使用做相应改动)
    NVIC_SetVectorTable(NVIC_VectTab_FLASH,0x1000);
    位于APP程序的main函数第一句
    在这里插入图片描述
  • 跳转至bootloader,有两种方式:
    1、用类似bootloader的跳转函数,
    2、直接重启reboot(软重启)。
void SoftReset(void)
{
    __set_FAULTMASK(1);      // ¹Ø±ÕËùÓÐÖжË
    NVIC_SystemReset();      // ¸´Î»
}	

关于APP与IAP互跳之间的中断处理问题
跳转时中断问题还是一个比较棘手的问题。。经常跳转之后无法进入中断,自己理解大概是,跳转时只是强制改变了PC指正的位置,但是里面的中断寄存器什么的都没有变,这样中断存在,但是中断函数什么的都没有了,造成程序死掉。。我在写的过程中也遇到了问题,第一次从iap跳到app正常,但是从app跳回iap的时候由于残留的中断太多,在iap中程序死了。
处理方式(具体可参考文章http://dzdesigned80.blog.163.com/blog/static/203259238201272425313152/):
1、把app中的跳转命令换成了系统复位NVIC_SystemReset();(不同的固件库可能函数名不同)
2、跳转之前复位或者关闭所有打开的中断
3、跳转后在初始化时加入RCC_DeInit();,,NVIC_DeInit ();等让中断恢复默认值。

第三章 如何生成、下载、保存和更新app的固件(IDE界面操作)

参考博文:stm32实现iap远程固件更新
虽然本文标题是实现远程固件更新,但是具体远程方案本文不做详细说明,重点在于介绍mcu接收到新的固件后怎么保存更新,以及更新失败回滚等。
下面简单说明一下远程的事情。
stm32的通信方式有串口,spi,iic,以及sdio等。也就是说我们的固件可以通过这些方式传输到mcu,不过普遍常用的是串口或者用sdio(外接sd卡)这两种方式。简单点还是再加一个串口网络模块,然后把固件存到服务器,经由串口网络模块透传到mcu。比如用http协议把固件发送下来。远程下载就这么简单一说。

接下来重点分析更新的事情。

固件生成

远程更新使用的固件和我们平时烧录程序用的固件格式有点区别,我们需要用二进制格式(.bin)文件。生成方式以mdk为例介绍一下,只需要添加一条命令行。
在这里插入图片描述

在mdk工程配置选项选择User,这个页面是让我们添加自定义命令行的,我们要添加的命令添加到第三个选项,即在编译完成后执行。
下面是命令内容,需要注意的是 bin前面两个-,app1.bin就是生成的固件,名字可以自定义,**.axf是你工程实际的.axf文件,路径要正确。不知道你的axf在那在output页面查看。

fromelf.exe --bin -o ../app1.bin ./**.axf

现在我们就可以生成bin文件了,但是还差一点步骤。
一般使用下mcu启动后会自动把0x0800 0000映射到地址0x0000 0000,然后取指令执行。但是现在我们程序可以理解成分成了两部分。
在这里插入图片描述
可以看到加入iap升级功能后我们app的起始地址变了,所以对应工程也要做这部分修改
在这里插入图片描述

如图,我这里把地址偏移了0x20000,同时在Linker中把“Use Memory Layout from Target Dialog”勾选,让我们的修改生效。
如此设置以后就一切ok了
在这里插入图片描述

关于固件有一点需要注意,因为起始地址修改了,所以导致我们的中断向量表也整体偏移了,所以需要在app程序起始添加一行代码,本文是偏移0x20000,根据实际使用做相应改动

NVIC_SetVectorTable(NVIC_VectTab_FLASH,0x2000);
固件保存

第一步:把固件下载到板子的缓存区,缓存区可以是SD卡或者flash的某块专用区域(该区域不是bootloader和APP的区域)

第二步:下到板子后,我们需要把固件保存到内置flash(APP区域)对应的地址。
本步骤可分为三个部分:

  • 写入之前,还应该加一些必要的文件完整性检查,比如使用校验等方式。
  • 写入flash
  • 然后在flash特定区域(参数区)立一个flag,通知BootLoader程序更新固件。

如何读写flash第二章的代码:

cpuflash_write{
...
}

本文上面设置的偏移是0x2 0000,所以此处写入flash的地址也必须是0x802 0000(0x800 0000 + 0x2 0000)

固件更新

现在万事具备了,接下来就是更新的事情了,简单说一下更新的思路。
上电启动后运行BootLoader程序,在bootloader中检查是否是否需要更新,不需要的话就引导之前的app程序运行,需要更新就引导新的app程序。
引导步骤大体就是重置栈顶指针,强制跳转app的reset复位中断。
代码见第二章的

void loadAPP(INT32U unLoadAddr)
{
...
}
  • 43
    点赞
  • 213
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值