STM32 IAP学习
一、 IAP介绍
IAP(in application programming)即在应用中编程。即用户可以在程序运行的过程中对user flash部分的区域进行烧写,主要用于产品发布后,固件程序进行更新升级。因此设计固件程序时需要编写两个项目代码,第一个项目称为BootLoader程序,主要通过外设通信(UART、USB、ETH等)来接收程序或数据,这段程序通过JLINK或者ISP烧入;第二个称为APP程序。这段程序根据地址的不同,可以放在SRAM段和FLASH段。若放在FLASH段,一般从最低地址区开始存放BootLoader,接着是APP 程序。
二、STM32程序运行流程分析
1、STM32F4 正常运行流程图分析
STM32F4的内部FLASH地址起始于0x08000000,在一般的下载程序中,程序即从此处开始写入。STM32F4是基于cortex-m4内核的,其内部通过一个“中断向量表”来响应中断。程序复位后,首先从中断向量表起始地址0x08000004开始,然后跳转到复位中断程序入口(即箭头①),复位中断服务程序执行完后,跳转到main函数(即箭头②),接着程序在main中执行死循环程序,当收到中断请求(产生中断事件),此时STM32F4强制将PC指针指回中断向量表处(即箭头③),然后根据中断源进入相应的中断服务程序中(即箭头④),执行完中断服务程序后,程序再次返回main函数(即箭头⑤)。
2、STM32F4 IAP运行流程图分析
STM32F4复位后,还是从0x08000004取出复位中断向量的地址,并跳转到复位中断服务程序,在运行完复位中断服务程序之后跳转到IAP的main函数(即箭头①),在执行完IAP程序以后(即箭头②)(即新的APP代码写入到FLASH,新的程序复位中断向量起始地址为0x08000004+N+M),跳转至新写入程序的复位向量表,取出新程序的复位中断向量的地址,并跳转执行新程序的复位中断服务程序,随后跳转至新程序的main函数(即箭头③)。
此时FLASH上面有两个中断向量表,问题随之而来,当CPU得到一个中断请求时,会响应哪一个中断向量表。由上图可知,PC指针依然强制跳转至0x08000004(即箭头④),但程序则是根据APP中设定的中断向量表偏移量,跳转到对应的中断源服务程序中(即箭头⑤),执行完中断服务程序后,程序再次返回main函数(即箭头⑥)。
由上图还可知道新程序的起始地址必须从IAP程序之后一段偏移量开始,APP程序起始地址设置方法在Keil中设置如下:
默认条件下IROM1的起始地址(Start)一般都是0x08000000,大小(Size)为芯片FLASH的大小,此处我选用的是F407VETx,在Utilities中选的是512K,因此Size的值为0x80000,即程序的存储区为0x08000000开始的512K空间。在IAP程序中Start设置为0x08000000,Size设置为0x10000,即BootLoader程序的空间设置为64K字节,可根据情况增加,但偏移量必须是0x200的倍数。APP程序Start设置为0x08010000,Size设置为0x70000,即APP程序的空间设置为448K字节,0x10000 + 0x70000 = 0x80000即芯片FLASH的大小。
3、STM32F4 中断向量表的偏移量设置
由于系统启动的时候,会首先调用SystemInit函数初始化时钟系统和中断向量表,而且在流程图中,APP程序的中断向量进行了偏移,因此需要在程序中设置中断向量的偏移
/* Configure the System clock source, PLL Multiplier and Divider factors,
AHB/APBx prescalers and Flash settings ----------------------------------*/
SetSysClock();
/* Configure the Vector Table location add offset address ------------------*/
#ifdef VECT_TAB_SRAM
SCB-》VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM */
#else
SCB-》VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH */
#endif
这里默认是未定义VECT_TAB_SRAM,因此需要设置SCB-》VTOR的值,如下:
SCB-》VTOR = FLASH_BASE | VECT_TAB_OFFSET;
其中VECT_TAB_OFFSET = 0x10000,即偏移量,根据实际情况设置。为了方便修改,以及不改动系统文件,将其放在main函数最开始处即可,但是最好直接在system_STMFxxx.c中修改#define VECT_TAB_OFFSET 0x0 。
4、keil生成bin文件设置方法
由于在程序更新时,用.bin文件更加方便,设置如下图:
利用keil自带的格式化工具,在画横线的选项处填入相应的fromelf.exe目录,后面设置一样即可
D:Keil_v5ARMARMCCbinfromelf.exe --bin -o “$L@L.bin” “#L”
三、IAP程序分析
1、官方例程源码分析
#define APPLICATION_ADDRESS (uint32_t)0x08004000
uint32_t JumpAddress;
typedef void (*pFunction)(void);
pFunction Jump_To_Application;
/* Test if user code is programmed starting from address “APPLICATION_ADDRESS” */
if (((*(__IO uint32_t*)APPLICATION_ADDRESS) & 0x2FFE0000 ) == 0x20000000) //@1
{
/* Jump to user application */
JumpAddress = *(__IO uint32_t*) (APPLICATION_ADDRESS + 4); //@2
Jump_To_Application = (pFunction) JumpAddress; //@3
/* Initialize user application‘s Stack Pointer */
__set_MSP(*(__IO uint32_t*) APPLICATION_ADDRESS); //@4
Jump_To_Application(); //@5
}
@1:此处判断栈顶地址值是否在0x2000 0000-0x2001 0000之间 ,在程序里:#define ApplicationAddress 0x8004000 ,__(__IO uint32_t)APPLICATION_ADDRESS) 即取0x8004000开始到0x8004004 的4个字节的值, 注意是值,不是地址,因为在APP程序中设置把中断向量表放置在0x08004000开始的位置。
@2:APPLICATION_ADDRESS + 4为0x8004004,APP程序的复位中断向量的即在此,即将复位中断向量的值赋值给JumpAddress
@3: 由前面的定义pFunction,是声明一个函数指针,加上typedef之后,pFunction成为类型void()void,因此Jump_To_Application = (pFunction) JumpAddress后,Jump_To_Application指向复位中断向量所在的地址。
@4: 设置主函数栈指针
@5: 执行复位函数,APP程序开始运行
2、正点原子部分程序分析
if(((*(vu32*)(FLASH_APP1_ADDR+4))&0xFF000000)==0x08000000)//判断是否为0X08XXXXXX.
{
iap_load_app(FLASH_APP1_ADDR);//执行FLASH APP代码
}
else
{
printf(“非FLASH应用程序,无法执行!rn”);
}
if(((*(vu32*)(0X20001000+4))&0xFF000000)==0x20000000)//判断是否为0X20XXXXXX.
{
iap_load_app(0X20001000);//SRAM地址
}
else
{
printf(“非SRAM应用程序,无法执行!rn”);
}
这两段程序区别在于一个是FLASH,一个是SRAM。这里只单独分析FLASH的,SRAM原理是一样的。为什么单独拿这两段程序出来,因为刚开始一直不懂这些地址取值的原因(包括官方源码里面的判断)。后来查看了IAP和APP生成的hex文件和bin文件如下图:
APP-bin文件:
APP-hex文件:
首先分析正点原子的代码
if(((*(vu32*)(FLASH_APP1_ADDR+4))&0xFF000000)==0x08000000)//判断是否为0X08XXXXXX.
这里判断FLASH_APP1_ADDR+4,由于FLASH_APP1_ADDR为0x08010000,偏移四个后取值,在hex文件找到地址0x08010004对应的值为0x08010289,结合运行流程图,这个就是APP复位中断向量的值,这里0x20000698即是栈顶指针的值。bin文件所给的数据是一样的,只是给的是相对地址,而hex文件给的是绝对地址(FLASH实际地址)。所以无论是官方例程源码还是正点原子的程序里面均有下面一句判断:
if (((*(__IO uint32_t*)APPLICATION_ADDRESS) & 0x2FFE0000 ) == 0x20000000)
即检查位于AATION_ADDRESS顶指针是否正确。查看第二节中的运行流程图,可知在中断向量表里第一个放的是栈顶地址的值。也就是这句话,通过判断顶地址值是否正确(是否在0x20000000-0x20010000之间) 来判断APP程序是否已经正确加载,因为APP程序的启动文件刚开始就去初始化化栈空间,若栈顶值对了,说应用程已经下载了启动文件的初始化也执行了。 如果没有这一句话,即使没有下载程序也会进入而导致程序跑飞。