STM32如何IAP升级用户程序
IAP简介
IAP( In Application Programming)即在应用编程,也就是用户可以使用自己的程序对MCU的中的运行程序进行更新,而无需借助于外部烧写器。ST官网也给出了IAP的示例程序。
注意:在MCU中,有一个特殊区域被称为 System memory。在这块区域中存放了ST公司自己的 bootloader 程序,它是在MCU出厂时,有ST固化到芯片中的,后续不能再更改。其中的 bootloader 程序也可以对MCU进行升级。
设计思想
设计分两部分:
1、Bootloader(或叫IAP程序,以下混用):主要接收待升级的bin文件,擦除旧的用户APP程序,并重新写入升级文件,之后跳转执行新的用户App。
2、用户程序App:用户主代码,实现正常的功能。
Bootloader和App分别是2个独立的完整的STM32工程。
包含Bootloader和用户主App的Flash存储空间分布如下:
Bootloader及App在FLASH分布及跳转流程
STM32内部FLASH的起始地址为0X08000000,Bootloader程序就存放此地址,存放APP程序的首地址设置在紧跟Bootloade之后。当程序开始执行时,首先运行的是Bootloader程序,然后Bootloader收到用户程序bin文件并将其复制到APP区域以更新,之后跳转到APP程序执行。
程序启动后,首先从“中断向量表”取出复位中断向量,执行复位中断程序Reset_Handler,在Reset_Handler最后跳转到用户main()函数。由此可见,在最后一步的设计中需要根据存放APP程序的起始地址以及中断向量表来设置栈顶地址,并获取复位中断地址跳转到Reset_Handler。
在常规工程即只有一个程序的情况下如下所示:
STM32有一个中断向量表,存放在flash起始地址+4个字节处(即0x08000004),Flash起始前4个字节存放的是栈顶地址(MSP)。当发生中断后程序通过查找该表得到相应的中断服务程序入口,然后跳到相应的中断服务程序中执行。上电后从0x08000004处取出复位中断向量的地址,然后跳转到复位中断程序Reset_Handler入口(标号1所示),执行结束后跳转到main函数。在执行main函数的过程中发生中断,则STM32强制将PC指针指回中断向量表处(标号3所示),从中断向量表中找到相应的中断函数入口地址,跳转到相应的中断服务函数,执行完中断服务函数后再返回到main函数中来。
在内置的Flash里面添加一个BootLoader程序,BootLoader程序和user application各有一个中断向量表。假设BootLoader程序占用的空间为N+M字节,则程序的走向应该如下图所示。
上电初始程序依然从0x08000004处取出复位中断向量地址,执行复位中断函数后跳转到IAP的main(标号①所示),在IAP的main函数执行完成后强制跳转到0x08000004+N+M处(标号②所示),最后跳转到新的main函数中来(标号③所示),当发生中断请求后,程序跳转到新的中断向量表中取出新的中断函数入口地址,再跳转到新的中断服务函数中执行(标号④⑤所示),执行完中断函数后再返回到main函数中来(标号⑥所示)。对于步骤④⑤我认为的是,在main函数的执行过程中,如果CPU得到一个中断请求,PC指针本来应该跳转到0x08000004处的中断向量表,由于我们设置了中断向量表偏移量为N+M,因此PC指针被强制跳转到0x08000004+N+M处的中断向量表中得到相应的中断函数地址,再跳转到相应新的中断服务函数,执行结束后返回到main函数中来。
IAP-Bootloader实现
BootLoader流程图大致应该如下:
1、初始化时钟。
2、初始化中断向量表地址。
在BootLoader程序的中断向量表设置中应有这么一句:
NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x0); //设置中断向量表指向(其中NVIC_VectTab_FLASH是个宏定义,的值为0x08000000。)
其中NVIC_VectTab_FLASH是个宏定义,的值为0x08000000。
3、初始化按键。 (使用按键触发方式,上电时如果按键被按下则进行用户程序更新操作)
4、初始化串口。(用于接收用户程序bin文件)
5、检测按键是否被按下,是则执行步骤6,否则执行步骤10。
6、擦除用户程序(擦除0x08003000—0x0807ffff地址空间Flash)。
7、从串口读取新的用户代码数据,把代码写入用户程序空间。
8、检测串口数据接收完毕?是则执行步骤9,否则跳回步骤7。
9、用户程序更新完毕,等待重新上电或硬件复位。
10、跳转到用户程序(强制将PC指针跳转到0x08003000+4处)。
具体可参考官方IAP源码,实际工程实现中需根据STM32型号和用户需求修改。
预先定义:
typedef void (*pFunction)(void); pFunction Jump_To_Application;uint32_t JumpAddress; #define ApplicationAddress 0x08003000 //可自定义用户App程序起始地址
main()函数:
此处省略了“串口接收用户程序bin文件,并烧写进FLASH的用户程序空间”这部分代码。其中,操作FLASH需要用到特定的库函数,比如解锁、上锁FLASH、擦除、烧写等。
代码释义:
1、
if (((*(__IO uint32_t*)ApplicationAddress) & 0x2FFE0000 ) == 0x20000000) //判断栈定地址值 //是否在0x2000 0000 - 0x 2000 2000之间
*(__IO uint32_t*)ApplicationAddress) 即取0x08003000开始的前4个字节的值, 因为我们的应用程序APP中设置把中断向量表放置在0x08003000 开始的位置;而中断向量表里第一个放的就是MSP栈顶地址的值。这句话即判断MSP的地址值是否正确(是否在0x2000 0000 - 0x 2000 2000之间) 来判断是否应用程序已经下载了,因为应用程序的启动文件刚开始就去初始化化栈空间,如果栈顶值对了,说应用程已经下载了,启动文件的初始化也执行了。
2、
JumpAddress = *(__IO uint32_t*) (ApplicationAddress + 4);
ApplicationAddress + 4 即为0x0800 3004,*(__IOuint32_t*) (ApplicationAddress + 4)即获取复位中断向量Reset_Handler的地址值,然后赋值给JumpAddress。
3、
Jump_To_Application = (pFunction) JumpAddress;
此时跳转函数指针Jump_To_Application指向了Reset_Handler程序的地址。
4、
__set_MSP(*(__IO uint32_t*) ApplicationAddress); \\设置用户主函数栈指存储空间为ApplicationAddress处,即用过App空间首地址0x0800 3000
把存储空间ApplicationAddress(即用户App空间首地址0x0800 3000)存放的内容(即栈顶地址)赋值给MSP。
5、
Jump_To_Application(); \\执行复位函数(强制从PC指向的JumpAddress地址执行)
把用户代码的中断复位程序地址赋给PC指针,跳转执行用户函数。
用户App设计
用户程序App关键点在Flash地址的设置和中断偏移量的设置。在用户程序的main()函数里,必须有这句话:
int main(void) { NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x3000); //设置中断向量表指向......}
程序烧写
下载Bootloader和用户App的.hex文件时,要分2次烧写,先烧写Bootloader,再烧写App,两次烧写时在烧写工具软件里要分别设置起始地址和size。
下图的值是示意,必须按代码实际定义来填。