以STM32为例简述 IAP升级

一、APP跳转函数的分析

typedef  void (*iapfun)(void);				//定义一个函数类型的参数.     用于将PC指针指向新的APP程序的起始地址
typedef unsigned           int uint32_t;
#define     __IO    volatile

iapfun jump2app; 

void iap_load_app(u32 appxaddr)
{
	if(((*(__IO uint32_t*)appxaddr)&0x2FFE0000)==0x20000000)	//检查栈顶地址是否合法.(注:正常来说对应4K的RAM地址范围为 0x2000 0000 -- 0x2000 0FFF,所以检索相与值为0x2FFF EFFF)
	{ 
		RCC_DeInit();
		__disable_irq();	//禁止中断
		Delayms(5);
		jump2app=(iapfun)*(__IO uint32_t*)(appxaddr+4);		//用户代码区第二个字为程序开始地址(复位地址)		
		__set_MSP(*(__IO uint32_t*)appxaddr);					//初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
		jump2app();									//跳转到APP.
	}
}

1.函数类型的指针

void*

void*是怎样的存在? 指针是一个特殊的变量,它里面存储的数值被解释成为内存里的一个地址,所以不管你存储的是int指针、float指针、long指针,对于存储指针的内存来说都是分配同样大小的内存的,这也为使用void指针可以存储任意类型的指针打下了基础,但是注意在使用void指针,要将其强制转换为具体的指针类型

具体解释:void*是怎样的存在? - 知乎

两种用法(这里使用的第二种):

(1)跳转函数

typedef void (*IapFun)(void);  //定义函数指针
void func(void);               //定义函数
IapFun fun  = func;            //为函数指针对象赋值
fun();                         //这里的fun()其实就相当于跳转到了func()里

(2)跳转至指定地址执行

typedef void (* IapFun)(void);          //定义函数指针
IapFun jump2app;                         //定义函数指针对象
jump2app=(IapFun) * (__IO uint32_t*)(appxaddr+4);	 //为函数指针对象赋值 appxaddr为函数指针地址,例如0x08000000
jump2app();                              //调用函数且jump2app函数的起始地址在上面赋值,可实现PC指针的跳转;

原函数:

void iapfun (void);	

对应定义函数指针的类型:

typedef  void (*iapfun)(void);	//iapfun类型是void ( * )void

/*注:对比看一下
typedef unsigned int  UINT32;        // UINT32 类型是unsigned int UINT32
 UINT32 sum;                         // 定义一个变量:unsigned int sum

 typedef  int  arr[3];               // arr 类型是 int[3];(存放int型数据的数组)
 arr a;                              // 定义一个数组:int a[3];
*/

这里理解为iapfun表示一个指向函数的指针类型的名字,该指针类型为“指向返回void类型并且无参数的函数的指针”,可以使用函数名对函数指针进行初始化(定义函数指针对象),也可以说是定义一种这样一种类型的函数名的函数指针。(参考:IAP跳转APP段代码理解 - 简书

采用这个类型生成指向函数的指针(也可看做对应类型的函数的函数名):

iapfun jump2app; 

跳转地址赋值函数类型指针(APP函数复位运行地址):

jump2app=(iapfun)*(__IO uint32_t*)(appxaddr+4);	

/*

(__IO uint32_t*)(appxaddr+4)-->将复位地址值强制转化为32位的指针变量

*(__IO uint32_t*)(appxaddr+4)-->取出复位地址保存的数据=中断向量的中断服务程序入口地址

(iapfun)*(__IO uint32_t*)(appxaddr+4)-->将中断服务程序入口地址再转化成定义的函数指针类型

jump2app=(iapfun)*(__IO uint32_t*)(appxaddr+4);	-->中断服务程序入口地址(iapfun类型的函数指针)赋值jump2app(定义的iapfun类型的函数指针其名为jump2app)
*/

(注: 从指针的层次上理解函数——函数的函数名实际上就是一个指针,函数名指向该函数的代码在内存中的首地址。)

如上图,jump2app这个函数首地址将指向APP的复位中断服务程序地址,实现IAP跳转至APP。

2.((*(__IO uint32_t*)appxaddr)&0x2FFE0000)==0x20000000//顶栈合法判断

以128 Kbytes的SRAM为例,地址范围是 0x2000 0000 --0x2001 FFFF;(1024*128-1=1FFFF)

堆栈指针(SP) 必须在 0x2000 0000 -- 0x2001 FFFF 这块区域,所以在校验顶栈地址是否合法时就校验其地址是否超过合法地址即可,所以 SP & 0x2FFE 0000 == 0x2000 0000时说明SP在合法范围内。

假如是 64 Kbytes 的 RAM,地址Region为 0x2000 0000 -- 0x2000 FFFF,那么此时应该这样写 SP & 0x2FFF 0000 == 0x2000 0000。当然,写成 SP & 0x2FFE 0000 也能执行,但是会带来隐患BUG。

二、中断向量的偏移(重映射)

1.M0内核

    u16 i_app;
	FLASH_Unlock();
	for(i_app = 0; i_app < 48; i_app++)
	{
		*((uint32_t*)(0x20000000 + (i_app << 2)))=*(__IO uint32_t*)(APP_ADDR + (i_app<<2));
	}
    //APP_ADDR为APP的起始地址,将对应的中断向量表以及SP搬运到RAM中,下面启用SRAM的启动方式达到向量偏移的效果
	FLASH_Lock();
    /* Enable the SYSCFG peripheral clock*/ 
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE); 

	/* Remap SRAM at 0x00000000 */
	SYSCFG_MemoryRemapConfig(SYSCFG_MemoryRemap_SRAM);
    //采用SRAM启动方式

由于M0内核不支持SCB向量表重定义,在官方指导手册中提供了将向量表复制到SARM,然后从SARM启动程序的方案。(部分M0内核芯片单独添加了SCB->VTOR寄存器,此种情况可以直接使用此寄存器进行地址的便宜,此寄存器是基于m0内核设置的因此地址固定为0xE000ED08)

对应APP的编译文件从APP_ADDR处开始,因此第一段函数作用是将其SP与中断向量表复制到相应的RAM的位置,大小计算方式在下方介绍。

对应每个中断向量的地址占有4个字节,从.s启动文件中找到_Vectors开始,第一个为sp(堆栈指针),其他都为中断服务程序的入口地址,所以就有:

__Vectors_End(所在行)-__Vectors(所在行)-3(中间空的三行)=中断向量表以及SP的占用字节数。如下图:

 119-68-3=48,所需要在SARM中预留的大小为48*4=192=0xC0。

因此在设置RAM的空间时如下设置:

 且开头搬运偏移地址的大小也写为48.

 参考链接如下:STM32F0系列MCU中断向量表的重映射 - STM32/8

2.M0内核以外的芯片

① 直接操作寄存器

在APP程序的main函数的开头设置中断向量表偏移

SCB->VTOR = FLASH_BASE | 0x10000;

其中0x10000是偏移量。。也就是前面的IAP程序所占用的空间大小,要是你的main函数中有SystemInit();的话要在SystemInit();之后添加。

因为SystemInit();中有中断向量表的偏移操作

在void SystemInit (void)系统初始化函数中有初始化中断向量表的语句

#ifdef VECT_TAB_SRAM

SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* 使用内部SRAM启动设置这一句. */

#else

SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* 使用内部FLASH启动设置这句*/

#endif

可以直接修改VECT_TAB_OFFSE的值,这个值代表偏移量。不建议这么改,不建议修改库文件,应为后面其他程序用的话经常忘了这里动过中断向量表,导致中断不能正常运行(我就因为这个浪费了快一天时间,串口就是进不了中断)

其中

#define FLASH_BASE ((uint32_t)0x08000000) /*!<FLASH base address in the alias region */

#define SRAM_BASE ((uint32_t)0x20000000) /*!< SRAM baseaddress in the alias region */

对应keil设置中的(这是一般程序默认的,IAP升级中APP程序的这个地方还得根据中断偏移量改)

② 使用库函数设置偏移量

在库文件中有专门的一个函数

在APP程序初始化时调用函数NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x10000);

其中/* Vector Table Base----------------------------------*/

#define NVIC_VectTab_RAM ((u32)0x20000000)

#define NVIC_VectTab_FLASH ((u32)0x08000000)


NVIC_SetVectorTable(u32NVIC_VectTab, u32 Offset)函数如下:

/***********************************************************************

Function Name : NVIC_SetVectorTable

* Description : Sets the vector table location andOffset.

* Input : - NVIC_VectTab: specifies if thevector table is in RAM or

* FLASH memory.

**********************************************************************/

void NVIC_SetVectorTable (u32NVIC_VectTab, u32 Offset)

{

/* Check the parameters */

assert_param(IS_NVIC_VECTTAB(NVIC_VectTab));

assert_param(IS_NVIC_OFFSET(Offset));

SCB->VTOR = NVIC_VectTab | (Offset & (u32)0x1FFFFF80);

}

③修改库文件(不建议使用)

直接修改固件库里面的数值。在void SystemInit(void)下的

/* Configure the Vector Table location add offsetaddress ------------------*/

#ifdefVECT_TAB_SRAM

SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET;/* Internal SRAM */

#else

SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET;/* Internal FLASH */

#endif

修改

#define VECT_TAB_OFFSET 0x00 /*!< Vector Table base offset field.

This valuemust be a multiple of 0x200. */

为以下:

#define VECT_TAB_OFFSET 0x10000 /*!< Vector Table base offsetfield.

This valuemust be a multiple of 0x200. */

 转自链接:STM32中断向量偏移_zhuyong006的博客-CSDN博客_stm32中断向量偏移

  • 4
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
STM32F103RET6是一款基于ARM Cortex-M3内核的微控制器,而IAP(In-Application Programming)升级是一种在应用程序中进行固件升级的技术。通过IAP升级,可以在不需要外部编程器的情况下,通过应用程序自身来更新微控制器的固件。 在STM32F103RET6上进行IAP升级,可以按照以下步骤进行: 1. 准备固件:首先,需要准备好新的固件文件,该文件包含了要升级到的新版本固件的代码和数据。 2. 编写应用程序:在原有的应用程序中,需要编写一段代码来实现IAP升级功能。这段代码通常包括以下几个步骤: - 初始化IAP功能:配置相关的引脚和外设,使其能够支持IAP升级。 - 检查是否需要升级:通过读取某个标志位或者与服务器通信等方式,判断是否需要进行固件升级。 - 下载新固件:如果需要升级,从外部存储介质(如SD卡、串口等)中读取新固件文件,并将其存储到微控制器的内存中。 - 执行固件升级:将新固件从内存中写入到微控制器的Flash存储器中,覆盖原有的固件。 - 完成升级:重启微控制器,使其加载新固件并开始运行。 3. 测试和验证:在完成应用程序的编写后,需要进行测试和验证,确保IAP升级功能正常工作,并且新固件能够正确地被加载和执行。 需要注意的是,IAP升级过程中需要谨慎处理,以避免升级失败导致微控制器无法正常工作。在实际应用中,还可以考虑添加一些安全机制,如校验固件的完整性和合法性,以提高系统的安全性。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值