STM32F103代码远程升级(二)基于串口IAP的简单实现

本文详细介绍了如何使用STM32的IAP功能通过串口进行固件升级,包括Bootloader与APP程序的编写、Flash擦除与重写、中断向量表偏移设置、代码存放地址空间更改、PC指针跳转及串口接收bin文件等关键步骤。
摘要由CSDN通过智能技术生成


本次所采用的编译环境为Keil,本来是想在IAR环境下开发的,但是还是用不太惯它的调试,所以还是换成了Keil。
本次用到的单片机是Stm32F103C8T6。


在知道了IAP编程的原理之后,需要知道具体实现的过程,这里推荐一篇博文
http://www.51hei.com/stm32/4315.html
博文中博主把IAP方案实现的原理以及所需要注意的问题和解决办法说得很通透了,这里我就不再赘述了。我也是通过学习这篇博文实现了第一个基于串口发送bin文件升级单片机代码的程序(通信中没有加任何数据校验)。


一、确定需要解决的问题

这里我就记录一下我的学习过程。
首先确定几个问题:

  1. 实现IAP编程需要着手编写两个程序,一个是Bootloader程序,一个是APP应用程序。
  2. 需要对STM32的Flash进行擦除和写入操作。
  3. 需要根据APP应用程序开始地址设置中断向量表的偏移
  4. 需要改变代码存放的地址空间(因为BootLoader要存放在0x08000000处,用户程序要存放在0x08005000处,而默认的代码存放的地址空间为0x08000000)。
  5. 在下载完更新文件之后需要进行PC指针的强制跳转,跳转时需要做什么
  6. 串口接收的用户代码数据是什么样的代码数据,是一种什么样的文件,该如何得到该格式文件

然后我开始一个一个解决问题。


二、解决问题

1、准备好Bootloder和APP应用两个程序。

首先我在网上找到了很多基于串口的IAP程序,最后参考的是原子旗舰版关于串口IAP实验的例程。以下是该例程中主函数的操作:

#include "led.h"
#include "delay.h"
#include "key.h"
#include "sys.h"
#include "lcd.h"
#include "usart.h"
#include "stmflash.h"
#include "iap.h"
//ALIENTEK战舰STM32开发板实验48
//IAP实验 Bootloader V1.0 代码 
//技术支持:www.openedv.com
//广州市星翼电子科技有限公司 
int main(void)
{		 
	u8 t;
	u8 key;
	u16 oldcount=0;	//老的串口接收数据值
	u16 applenth=0;	//接收到的app代码长度
	u8 clearflag=0;

	uart_init(256000);	//串口初始化为256000
	delay_init();	   	 	//延时初始化 
	LCD_Init();	
	LED_Init();		  		//初始化与LED连接的硬件接口

 	KEY_Init();				//按键初始化

 	POINT_COLOR=RED;//设置字体为红色 
	LCD_ShowString(60,50,200,16,16,"Warship STM32");	
	LCD_ShowString(60,70,200,16,16,"IAP TEST");	
	LCD_ShowString(60,90,200,16,16,"ATOM@ALIENTEK");
	LCD_ShowString(60,110,200,16,16,"2012/9/24");  
	LCD_ShowString(60,130,200,16,16,"WK_UP:Copy APP2FLASH");
	LCD_ShowString(60,150,200,16,16,"KEY1:Erase SRAM APP");
	LCD_ShowString(60,170,200,16,16,"KEY0:Run SRAM APP");
	LCD_ShowString(60,190,200,16,16,"KEY2:Run FLASH APP");
	POINT_COLOR=BLUE;
	//显示提示信息
	POINT_COLOR=BLUE;//设置字体为蓝色	  
	while(1)
	{
	 	if(USART_RX_CNT)
		{
			if(oldcount==USART_RX_CNT)//新周期内,没有收到任何数据,认为本次数据接收完成.
			{
				applenth=USART_RX_CNT;
				oldcount=0;
				USART_RX_CNT=0;
				printf("用户程序接收完成!\r\n");
				printf("代码长度:%dBytes\r\n",applenth);
			}else oldcount=USART_RX_CNT;			
		}
		t++;
		delay_ms(10);
		if(t==30)
		{
			LED0=!LED0;
			t=0;
			if(clearflag)
			{
				clearflag--;
				if(clearflag==0)LCD_Fill(60,210,240,210+16,WHITE);//清除显示
			}
		}	  	 
		key=KEY_Scan(0);
		if(key==KEY_UP)
		{
			if(applenth)
			{
				printf("开始更新固件...\r\n");	
				LCD_ShowString(60,210,200,16,16,"Copying APP2FLASH...");
 				if(((*(vu32*)(0X20001000+4))&0xFF000000)==0x08000000)//判断是否为0X08XXXXXX.
				{	 
					iap_write_appbin(FLASH_APP1_ADDR,USART_RX_BUF,applenth);//更新FLASH代码   
					delay_ms(100);
					LCD_ShowString(60,210,200,16,16,"Copy APP Successed!!");
					printf("固件更新完成!\r\n");	
				}else 
				{
					LCD_ShowString(60,210,200,16,16,"Illegal FLASH APP!  ");	   
					printf("非FLASH应用程序!\r\n");
				}
 			}else 
			{
				printf("没有可以更新的固件!\r\n");
				LCD_ShowString(60,210,200,16,16,"No APP!");
			}
			clearflag=7;//标志更新了显示,并且设置7*300ms后清除显示									 
		}
		if(key==KEY_DOWN)
		{
			if(applenth)
			{																	 
				printf("固件清除完成!\r\n");    
				LCD_ShowString(60,210,200,16,16,"APP Erase Successed!");
				applenth=0;
			}else 
			{
				printf("没有可以清除的固件!\r\n");
				LCD_ShowString(60,210,200,16,16,"No APP!");
			}
			clearflag=7;//标志更新了显示,并且设置7*300ms后清除显示									 
		}
		if(key==KEY_LEFT)
		{
			printf("开始执行FLASH用户代码!!\r\n");
			if(((*(vu32*)(FLASH_APP1_ADDR+4))&0xFF000000)==0x08000000)//判断是否为0X08XXXXXX.
			{	 
				iap_load_app(FLASH_APP1_ADDR);//执行FLASH APP代码
			}else 
			{
				printf("非FLASH应用程序,无法执行!\r\n");
				LCD_ShowString(60,210,200,16,16,"Illegal FLASH APP!");	   
			}									 
			clearflag=7;//标志更新了显示,并且设置7*300ms后清除显示	  
		}
		if(key==KEY_RIGHT)
		{
			printf("开始执行SRAM用户代码!!\r\n");
			if(((*(vu32*)(0X20001000+4))&0xFF000000)==0x20000000)//判断是否为0X20XXXXXX.
			{	 
				iap_load_app(0X20001000);//SRAM地址
			}else 
			{
				printf("非SRAM应用程序,无法执行!\r\n");
				LCD_ShowString(60,210,200,16,16,"Illegal SRAM APP!");	   
			}									 
			clearflag=7;//标志更新了显示,并且设置7*300ms后清除显示	 
		}				   
		 
	}   	   
}

我直接在这个例程上面将代码修改成我需要的Bootloader程序。
1、首先是去掉了程序中所有关于LCD屏幕的操作,将串口改成我需要的串口,同样也是利用按键来触发更新程序的代码。
2、然后再新建了一个stm32F103的工程,写了一个简单的流水灯程序,作为APP应用程序。

2、对flash进行擦除和重写

在原子的例程中就包含了对Flash的擦除和重写,在操作Flash之前一定要记得先释放Flash的操作权限,即解锁。通过阅读原子例程的代码可以清晰的知道,他实现的对Flash的操作主要是调用了stm32固件库中的stm32f10x_flash.c中的三个库函数:

  • void FLASH_Unlock(void);
  • FLASH_Status FLASH_ErasePage(uint32_t Page_Address);
  • FLASH_Status FLASH_ProgramHalfWord(uint32_t Address, uint16_t Data)

所以我们要对Flash进行操作,直接查看 stm32f10x_flash.c中的函数,选择合适的函数进行调用即可。

3、设置APP应用程序的中断向量表偏移

在推荐的博文中把APP应用程序的中断向量表需要设置偏移的原因已经讲的很清楚了,这里就不再赘述了。
首先我将Flash分成了两个部分,从Flash起始地址0x8000000到0x8005000的20KB大小作为Bootloader区域;从0x8005000以后作为APP应用程序区域。
所以我们这里需要设置一下之前写好的APP应用程序的中断向量表。

注意:Bootloader程序的中断向量表不需要做任何设置,只需要对APP应用程序的STM32工程进行设置。

设置中断向量表偏移可以直接修改库文件中对中断向量表的操作,但是一般情况下我们不要随意修改库文件,所以我们只需要在APP应用程序的代码中的主函数开头添加一句话即可:

SCB->VTOR = FLASH_BASE | 0x5000;

因为我定义的APP用户程序开始地址是0x8005000,所以中断向量表偏移0x5000就可以了。

4、改变APP用户程序的代码存放地址空间

在Keil编译环境下改变代码存放的地址空间操作如下图:
Keil配置起始地址

在IAR环境下则是修改stm32f10x_flash.icf文件中的参数。一般该文件在工程的目录文件夹下,不在IAR的工程显示目录下。将该文件拖到IAR中修改两个参数即可,如下图。
IAR配置起始地址

5、在BootLoader程序中将PC指针跳转到用户代码处,如下操作即可:
typedef        void (*pFunction)(void);     
pFunction       Jump_To_Application;
uint32_t       JumpAddress;
#define       ApplicationAddress       0x08005000

//检测栈顶的地址,判断用户代码的堆栈地址是否落在0x2000000~0x2001ffff区间
if (((*(__IO uint32_t*)ApplicationAddress) & 0x2FFE0000 ) == 0x20000000)
{
	/* 锁定 Flash */
	FLASH_Lock(); 
	
	/* 跳转至用户程序 */  //ApplicationAddress + 4  对应的是app中断向量表的第二项,复位地址
	JumpAddress = *(__IO uint32_t*) (ApplicationAddress + 4);
 
	Jump_To_Application = (pFunction) JumpAddress;  //把地址强转为函数指针
	
	//设置主函数栈指针   将用户代码的栈顶地址设为栈顶指针
	__set_MSP(*(__IO uint32_t*) ApplicationAddress);
	
	//调用函数,实际失去app复位地址去执行复位操作---设置程序指针为复位地址
	Jump_To_Application();
}

原子例程中关于代码跳转的部分原理跟这个也差不多,就是封装成了自己的函数,看着要复杂一些。

6、通过串口接收文件

这里我们需要通过串口接收更新代码的文件,然后将文件内容写入指定的Flash地址中,那么这里接收的数据最好是可以直接写入Flash中去的。
而我们常用的烧写代码的文件有**.hex文件和.bin文件,hex文件中不仅包含了代码数据,还包含了代码的位置信息,所以若是我们采用hex文件则需要对接收到的数据进行处理,去掉里面的位置信息,然后再写于相应的地址空间里,这样操作就显得麻烦了许多。而bin文件里的数据全部都是代码数据,也就是我们可以直接读取bin文件中的数据然后直接写入Flash中。
所以,这里我选择的是使用bin文件,即将APP用户程序的可执行文件转换成bin文件之后,再运行bootloader程序,通过按键触发更新操作,然后通过串口工具发送文件给串口,串口接收到该文件之后将其写入指定的Flash地址中,然后再跳转到该起始地址开始运行程序,即通过串口实现了流水灯程序的写入。
如此,现在想要更新该程序,比如想把流水灯效果改为呼吸灯,那么我们只需要先编写好呼吸灯的程序并生成bin文件,然后通过串口上传该文件即可更新程序,达到不用通过烧写器烧写程序即可改变运行的程序。
Keil环境下直接通过配置即可生成bin文件,首先我们得先找到Keil安装目录下自带的fromelf.exe所在的目录,然后将生成的
.axf**文件转换为bin文件即可。具体配置如下图:

F:\Keil_v5\ARM\ARMCC\bin\fromelf.exe --bin -o …\OBJ\firstApp.bin …\OBJ\测试.axf

keil生成Bin文件


参考链接

IAR环境下STM32+IAP方案的实现
【HAL库每天一例】第062例: IAP-串口IAP
【正点原子探索者STM32F407开发板例程连载+教学】第55章 串口IAP实验
【ALIENTEK 战舰STM32开发板例程系列连载+教学】第五十三章 串口IAP实验

评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

微芯供氧

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值