一、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指针,要将其强制转换为具体的指针类型
两种用法(这里使用的第二种):
(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. */