STM32F1 IAP在线升级功能实现(使用串口)及心得

公司产品要求,需要做一个能远程升级程序的功能,找了很多例程,大多都是需要按键来完成操作的,而我需要的是通过串口发送指令来完成,于是东拼西凑最后还是用了四天的时间勉强做出来

整个功能需要的程序是两个部分。一个是IAP程序,一个是APP程序。对于IAP程序和APP原理方面的内容就不再过多赘述。直接从操作开始吧。

IAP程序

写IAP程序之前首先得配置程序的起始地址和大小。这里根据个人情况而定,我这里单片机flash大小是512k,所以IAP程序选择分配的大小是64k。
STM32F1 IAP在线升级功能实现(使用串口)及心得_第1张图片

点击魔术棒,然后在选择target,设置起始地址(start)和大小(size),IAP程序起始地址都是从0x8000000开始的,大小就是0x10000也就是64k啦。
在这里我参考了原子哥的源码和一位大神的分享,原子的资料一搜一大堆,这里就仅贴出大佬的链接
stm32 IAP 程序编写心得
有了前车之鉴,做起来也稍显得心应手,这里的flash操作的函数和IAP功能函数都是拿来主义了,原子IAP例程里拿来就可以用。主要工作还是针对main函数,这里直接贴出源码:

#include "delay.h"
#include "sys.h"
#include "usart.h"
#include "stmflash.h"
#include "iap.h"
 
#define ADDR_CodeWriteFlag  0X08070000		//设置跳转标志位保存地址
#define ADDR_JumpToIAPFlag  0X08070001
int main(void)
{	
	u16 IAPFlagBuf[2];
	u16 RxDataCount=0;				//串口接收到的数据计数
	u16 RxDataLength=0;				//串口接收到的数据长度
	u16 AppCodeLength = 0;		//接收到的app代码长度
	u8  RxCmdFlag = 0;				
	u8  AppRunFlag = 0;				//应用程序运行标志
	u16 JumpToAPPFlag;									//跳转至APP程序标志位
	u16 JumpToIAPFlag;									//跳转回IAP标志位
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
	JumpToAPPFlag = STMFLASH_ReadHalfWord(ADDR_CodeWriteFlag); //读取APP写入标志位,判断是否已有程序
	JumpToIAPFlag = STMFLASH_ReadHalfWord(ADDR_JumpToIAPFlag);
	uart_init(9600);					//串口初始化为9600
	delay_init();	   	 				//延时初始化 
	IAPFlagBuf[0] = 0x11;
	IAPFlagBuf[1] = 0x00;
	
	printf("提示:输入send发送bin文件!\r\n");
	while(1)
	{
		if(JumpToAPPFlag != 0x11)			//判断是否已有APP程序,如果已有,跳转至APP程序运行
		{
			if(JumpToIAPFlag == 0x11)		//判断是否从APP程序跳转回来,如果是擦除已有APP程序
			{			
				JumpToIAPFlag = 0x00;		
			}
			if(USART_RX_CNT)			//如果有数据进来
			{
				if(RxDataCount == USART_RX_CNT)				//串口没有再收到新数据
				{
					RxDataLength = USART_RX_CNT;
					if(RxCmdFlag == 0 && RxDataLength == 4)	//接收到IAP指令
					{
						if(USART_RX_BUF[0] == 's' && USART_RX_BUF[1] == 'e' && USART_RX_BUF[2] == 'n' && USART_RX_BUF[3] == 'd')//判断是否为IAP指令
						{
							RxCmdFlag = 1;							//接收到更新APP代码指令,标志位置1
							RxDataLength = 0;						//清空指令长度,防止影响后面计算APP代码大小
							printf("准备接收app程序,请添加bin文件!\r\n"); //准备好接收bin文件,等待用户添加
						}
						else
						{
							CodeUpdateFlag = 0;
							AppCodeLength = 0;
							printf("指令错误!\r\n");	//未接收到IAP更新指令,其他任何串口发送数据都认为指令错误
						}
					}
					
					else if(RxCmdFlag == 1 && RxDataLength > 10)//接收APP程序
					{
						CodeUpdateFlag = 1;												//代码更新标志位置位,用于应用程序代码接收完成后写FLASH
						RxCmdFlag = 0;
						AppCodeLength = USART_RX_CNT;
						printf("APP程序接收完成!\r\n");
						printf("程序大小:%dBytes\r\n",AppCodeLength);
					}
					
					else
					{
						RxDataLength = 0;
						printf("文件或指令错误!\r\n"); //如果代码大小不足10Bytes,认为没有正确添加bin文件
					}
					RxDataCount = 0;
					USART_RX_CNT = 0;
				}
				else 
				{
					RxDataCount = USART_RX_CNT;
				}
			}
			
			delay_ms(10);											//给以串口中断的时间,判断是否接收完成
			
			if(CodeUpdateFlag)								//代码更新标志位置位
			{
				CodeUpdateFlag = 0;
				if(AppCodeLength)
				{
					printf("程序更新中...\r\n");
					if(((*(vu32*)(0X20001000+4))&0xFF000000)==0x08000000)					//判断代码合法性
					{	
						printf("正在下载程序!\r\n");
						iap_write_appbin(FLASH_APP1_ADDR,USART_RX_BUF,AppCodeLength);	//新代码写入FLASH  
						AppRunFlag = 1;
					}
					else 
					{
						printf("程序更新失败,请检查bin文件是否正确!\r\n");
						printf("跳转到原有应用程序!\r\n");
						iap_load_app(FLASH_APP1_ADDR);								         //执行FLASH APP代码
					}
				}
				else 
				{
					printf("没有程序可以更新!\r\n");
				}								 
			}
			
			if(AppRunFlag)																//App运行标志置位
			{
				printf("开始运行程序!\r\n");
				delay_ms(10);                        
				if(((*(vu32*)(FLASH_APP1_ADDR + 4)) & 0xFF000000) == 0x08000000)		//判断代码合法性
				{	 
					AppRunFlag = 0;
					STMFLASH_Write(ADDR_CodeWriteFlag,IAPFlagBuf,2);		   //写入APP代码标志
					//RCC_DeInit(); //关闭外设
					//__disable_irq();
					iap_load_app(FLASH_APP1_ADDR);								         //执行FLASH APP代码
				}
				else 
				{
					printf("应用程序错误!\r\n");  
				}									   
			}
		}
		else
		{
			printf("已有一个应用程序!\r\n");
			printf("开始运行程序!\r\n");
			delay_ms(10);
			iap_load_app(FLASH_APP1_ADDR);										//执行FLASH APP代码
		}
	}
} 

  1. main函数中,选择一块不使用的flash区域来保存跳转标志位。
    在这里插入图片描述
    我选择的是0x8070000开始的区域,整个flash大小是512k也就是0x8080000,所以最后我又留了64k的大小来存放,所以留给APP程序的就是0x08010000到0x08070000的地址,也是就384k大小空间,记住这个0x08010000到0x08070000,后面APP程序要考。

  2. 每次进入main函数都要先读这个地址的值,若等于0x11则直接跳转到APP,因为在跳转APP之前是要对这个地址进行写0x11的,所以跳转到APP后这个地址是0x11,开机启动会自动进入APP。
    在这里插入图片描述
    到此似乎思路就清晰了,原子通过按键来操作接收,下载。我这里通过指令来接收下载,其他的判断栈顶地址的合法性,看两遍代码也能明白了。

APP程序

接下来就是APP程序的操作部分了。
要从IAP跳转到APP,APP的程序也需要修改。首先就是程序的起始地址和大小。
STM32F1 IAP在线升级功能实现(使用串口)及心得_第2张图片
前面的IAP从0x8000000到0x8010000,所以我们的APP程序只能从0x8010000开始,而0x8070000到0x8080000又要保存跳转标志位,那么APP的地址就只能是0x8010000到0x8070000了,既然这样,那就拉满吧,所以大小设置我选择0x60000。
然后就是输出bin文件了,要用串口传输不能用hex文件,所以在USER界面
选择如图所示操作,方框里填的是E:\keil5\ARM\ARMCC\bin\fromelf.exe --bin -o …\output\Project.bin …\output\Project.axf 注意有空格,当然这个也得根据自己的文件位置更改。
STM32F1 IAP在线升级功能实现(使用串口)及心得_第3张图片
下一步就是更改偏移地址了,这里只需要在主函数里加
SCB->VTOR=FLASH_BASE|0x10000; 即可,我这里偏移0x10000
STM32F1 IAP在线升级功能实现(使用串口)及心得_第4张图片
到此就能编译生成bin文件,通过IAP程序,使用串口来完成升级了。
但是后续要升级怎么办,我们还得从APP跳转到IAP来。所以在APP程序中还需要接收指令来跳转到IAP去。
STM32F1 IAP在线升级功能实现(使用串口)及心得_第5张图片
这里Receive_Data_Point3是接收到的字节长度,我设置的跳转指令是APPTOIAP!共9位。
然后就是跳转前还需将存放标志位得地址写入0x00,前面IAP中会对该地址得值进行判断是否是0x11,是的话就又跳回来了。然后有大佬踩坑后,得知跳转到IAP直接用NVIC_SystemReset(); 即可。
在这里插入图片描述
到此就基本完成了。
第一次写博客,也是为了记录和学习。语言不通顺还请多谅解,如有错误的地方也请多加指正。同时有疑问的同学也可以评论留言,欢迎讨论交流。

可能出现的问题解决方法:
在后续的测试中发现,串口接收bin文件时还没接收完就进入到了写入flash的动作,导致有很大的概率程序升级失败。分析了半天原因可能是,接收缓存在10ms内没收到数据就默认接收完毕进入写入跳转了,我使用的9600波特率,在将下图中的延时增加到100ms后,没有再出现问题了。
STM32F1 IAP在线升级功能实现(使用串口)及心得_第6张图片

你可能感兴趣的:(STM32F1,stm32,单片机,arm)