STM32实现TCP IAP在线更新程序

IAP(In Application Programming)即在应用编程, IAP 是用户自己的程序在运行过程中对User Flash 的部分区域进行烧写。利用TCP实现IAP即可以实现对应用代码的远程更新,可以对后期应用程序的维护和更新节省很大的人力物力。

在进行应用程序更新时,应用程序是不能正常运行的,所以更新时我们需要运行另一段代码。因此我们需要在STM32中写入两段代码:Bootloader 和 APP。Bootloader 主要进行引导进入应用程序和进行应用程序更新,而APP是我们需要执行的应用程序。

Bootloader 是进行引导应用程序的,所以我们必须保证Bootloader 的稳定性,且不能随意更新,要让Bootloader 具有“不死之身”。 系统开机一般首先执行 Bootloader,也许这里你会有疑问,我们这里的Bootloader不过是用来更新APP的,开机直接运行到APP,需要更新时再跳转到Bootloader执行不行吗?我们前面说过Bootloader 具有不死之身,而我们的APP的不过是肉体凡胎。由于反复的更新等操作,都可能随意引入未知的BUG,这些未知的BUG可能随时导致我们的程序崩溃。如果我们的APP崩溃了,而我们的系统开机就执行APP,那么就再也没有办法进入Bootloader了,那么我们的板子也没有办法进行更新了,这时候就只能进行“返厂”重新烧写了。我们每次开机执行Bootloader,在跳转到APP,这样即便APP崩溃,Bootloader还是可以正常运行,我们就可对APP进行更新。本文中我们是用TCP来进行APP的更新,所以Bootloader 中我们必定要包含TCP通信功能。Bootloader所实现的内容如下:(1)开机执行Bootloader;(2)启动后等待建立TCP连接;(3)接收TCP发送的APP,收到“start”后,将APP写入flash并执行;(4)20s未收到数据,则执行APP,避免系统卡在Bootloader。

APP是由Bootloader拉起,在运行过程中我们也需要进行程序更新,所以在APP中我们也需要能够远程再次跳转到Bootloader。所以APP除本身的功能外,还应该具备以下功能:(1)建立TCP连接;(2)收到“boot”后跳转Bootloader执行。

本次实验采用的是正点原子ALIENTEK 战舰 STM32F103 开发板,且代码借鉴了正点原子的例程。本次实验代码仅用于学习交流,请勿用作其他商业用途。本文中仅贴上部分关键代码,源码请下载源码工程

Bootloader的主函数位于TCP IAP\TCP Bootloader\USER\main.c,这里面我们主要是建立了TCP服务端,然后LED0闪烁,如果20s没有收到数据就跳转到flash的APP执行。

int main(void)
{	 
	u32 t;
	delay_init();	    	//延时函数初始化	  
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);		//设置NVIC中断分组2:2位抢占优先级,2位响应优先级
	uart_init(115200);	 	//串口初始化为115200
 	LED_Init();			    //LED端口初始化
	TIM3_Int_Init(1000,719);//定时器3频率为100hz	
 	FSMC_SRAM_Init();		//初始化外部SRAM
	my_mem_init(SRAMIN);	//初始化内部内存池
	my_mem_init(SRAMEX);	//初始化外部内存池    
	lwip_comm_init();//lwip初始化
	tcp_server_test();  	//TCP Server模式
  while(1)
	{	
		lwip_periodic_handle();	//LWIP内核需要定时处理的函数
		lwip_pkt_handle();
		delay_ms(2);
		t++;
		wait_time++;
		if(t==200)
		{
			t=0;
			LED0=!LED0;
		} 
		
		if(wait_time==10000)//20s没有接收到数据
		{
			iap_load_app(FLASH_APP1_ADDR);//执行FLASH APP代码
		}
	}
}

TCP数据的处理位于TCP IAP\TCP Bootloader\LWIP\lwip_app\tcp_server_demo\tcp_server_demo.c. tcp_server_recvbuf用来临时存放我们的接收缓存,tcp_server_appdata用来存放我们接受的应用文件,因为比较大,所以存放在外部SRAM的地址中。我们在malloc中已经占有了部分地址,所以这里从0X68000000+MEM2_MAX_SIZE+MEM2_ALLOC_TABLE_SIZE*2开始。

//TCP Server接收数据缓冲区
u8 tcp_server_recvbuf[TCP_SERVER_RX_BUFSIZE];
u8 tcp_server_appdata[TCP_SERVER_RX_APPSIZE] __attribute__ ((at(0X68000000+MEM2_MAX_SIZE+MEM2_ALLOC_TABLE_SIZE*2)));	

Bootloader中的接受处理逻辑:1.每次收到数据我们都将wait_time=0,清除等待时间,将每次接受到的数据都存在了tcp_server_recvbuf,因为tcp接受的数据会分片,然后我们将每次的数据组装放入u8 tcp_server_appdata。2.如果收到"clean",我们就将tcp_server_appdata清空。3.如果收到"start",我们先判读是否有效,然后将其写到falsh,再跳转运行,这时开始执行app。

//lwIP tcp_recv()函数的回调函数
err_t tcp_server_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
{
	err_t ret_err;
	u32 data_len = 0;
	struct pbuf *q;
  	struct tcp_server_struct *es;
	
	LWIP_ASSERT("arg != NULL",arg != NULL);
	es=(struct tcp_server_struct *)arg;
	if(p==NULL) //从客户端接收到空数据
	{
		es->state=ES_TCPSERVER_CLOSING;//需要关闭TCP 连接了
		es->p=p; 
		ret_err=ERR_OK;
	}else if(err!=ERR_OK)	//从客户端接收到一个非空数据,但是由于某种原因err!=ERR_OK
	{
		if(p)pbuf_free(p);	//释放接收pbuf
		ret_err=err;
	}else if(es->state==ES_TCPSERVER_ACCEPTED) 	//处于连接状态
	{
		if(p!=NULL)  //当处于连接状态并且接收到的数据不为空时将其打印出来
		{
			memset(tcp_server_recvbuf,0,TCP_SERVER_RX_BUFSIZE);  //数据接收缓冲区清零
			wait_time=0;//清除等待时间
			for(q=p;q!=NULL;q=q->next)  //遍历完整个pbuf链表
			{
				//判断要拷贝到TCP_SERVER_RX_BUFSIZE中的数据是否大于TCP_SERVER_RX_BUFSIZE的剩余空间,如果大于
				//的话就只拷贝TCP_SERVER_RX_BUFSIZE中剩余长度的数据,否则的话就拷贝所有的数据
				if(q->len > (TCP_SERVER_RX_BUFSIZE-data_len)) memcpy(tcp_server_recvbuf+data_len,q->payload,(TCP_SERVER_RX_BUFSIZE-data_len));//拷贝数据
				else memcpy(tcp_server_recvbuf+data_len,q->payload,q->len);
				data_len += q->len;  	
				if(data_len > TCP_SERVER_RX_BUFSIZE) 
				{
					data_len=TCP_SERVER_RX_BUFSIZE;
					break; //超出TCP客户端接收数组,跳出	
				}
			}
			tcp_server_flag|=1<<6;	//标记接收到数据了
			lwipdev.remoteip[0]=tpcb->remote_ip.addr&0xff; 		//IADDR4
			lwipdev.remoteip[1]=(tpcb->remote_ip.addr>>8)&0xff; //IADDR3
			lwipdev.remoteip[2]=(tpcb->remote_ip.addr>>16)&0xff;//IADDR2
			lwipdev.remoteip[3]=(tpcb->remote_ip.addr>>24)&0xff;//IADDR1 
 			tcp_recved(tpcb,p->tot_len);//用于获取接收数据,通知LWIP可以获取更多数据
			pbuf_free(p);  	//释放内存
			ret_err=ERR_OK;
			if(memcmp(tcp_server_recvbuf,"clean",5)==0)
			{
				printf("清空数据!\r\n");
				memset(tcp_server_appdata,0,TCP_SERVER_RX_APPSIZE);
				appdata_len=0;
			}
			else if(memcmp(tcp_server_recvbuf,"start",5)==0)
			{
				printf("开始更新固件...\r\n");	
				if(((*(vu32*)((0X68000000+MEM2_MAX_SIZE+MEM2_ALLOC_TABLE_SIZE*2)+4))&0xFF000000)==0x08000000)//判断是否为0X08XXXXXX.
				{	 
					iap_write_appbin(FLASH_APP1_ADDR,tcp_server_appdata,appdata_len);//更新FLASH代码   
					printf("固件更新完成!\r\n");	
					printf("开始执行FLASH用户代码!!\r\n");
					if(((*(vu32*)(FLASH_APP1_ADDR+4))&0xFF000000)==0x08000000)//判断是否为0X08XXXXXX.
					{	 
						//lwip_comm_mem_free();
						iap_load_app(FLASH_APP1_ADDR);//执行FLASH APP代码
					}else 
					{
						printf("非FLASH应用程序,无法执行!\r\n");   
					}				
					}else 
					{  
						printf("非FLASH应用程序!\r\n");
					}

			}
			else
			{
				memcpy(tcp_server_appdata+appdata_len,tcp_server_recvbuf,data_len);
				appdata_len=appdata_len+data_len;
				printf("正在接收数据 %d byte...\r\n",appdata_len);
			}
		}
	}else//服务器关闭了
	{
		tcp_recved(tpcb,p->tot_len);//用于获取接收数据,通知LWIP可以获取更多数据
		es->p=NULL;
		pbuf_free(p); //释放内存
		ret_err=ERR_OK;		
	}
	return ret_err;
}

APP的主函数位于\USER\main.c,主要是建立了TCP服务端,以及闪灯。

int main(void)
{	 
	u32 t;
	delay_init();	    	//延时函数初始化	  
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);		//设置NVIC中断分组2:2位抢占优先级,2位响应优先级
	uart_init(115200);	 	//串口初始化为115200
 	LED_Init();			    //LED端口初始化
	TIM3_Int_Init(1000,719);//定时器3频率为100hz	
 	FSMC_SRAM_Init();		//初始化外部SRAM
	my_mem_init(SRAMIN);	//初始化内部内存池
	my_mem_init(SRAMEX);	//初始化外部内存池    
	lwip_comm_init();//lwip初始化
	tcp_server_test();  	//TCP Server模式
  while(1)
	{	
		lwip_periodic_handle();	//LWIP内核需要定时处理的函数
		lwip_pkt_handle();
		delay_ms(2);
		t++;
		if(t==200)
		{
			t=0;
			LED1=!LED1;
		} 
		
	}
}

APP的接收处理函数比较简单,只要一收到"boot",则跳转到Bootloader代码段执行。

//lwIP tcp_recv()函数的回调函数
err_t tcp_server_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err)
{
	err_t ret_err;
	u32 data_len = 0;
	struct pbuf *q;
  	struct tcp_server_struct *es;
	
	LWIP_ASSERT("arg != NULL",arg != NULL);
	es=(struct tcp_server_struct *)arg;
	if(p==NULL) //从客户端接收到空数据
	{
		es->state=ES_TCPSERVER_CLOSING;//需要关闭TCP 连接了
		es->p=p; 
		ret_err=ERR_OK;
	}else if(err!=ERR_OK)	//从客户端接收到一个非空数据,但是由于某种原因err!=ERR_OK
	{
		if(p)pbuf_free(p);	//释放接收pbuf
		ret_err=err;
	}else if(es->state==ES_TCPSERVER_ACCEPTED) 	//处于连接状态
	{
		if(p!=NULL)  //当处于连接状态并且接收到的数据不为空时将其打印出来
		{
			memset(tcp_server_recvbuf,0,TCP_SERVER_RX_BUFSIZE);  //数据接收缓冲区清零
			for(q=p;q!=NULL;q=q->next)  //遍历完整个pbuf链表
			{
				//判断要拷贝到TCP_SERVER_RX_BUFSIZE中的数据是否大于TCP_SERVER_RX_BUFSIZE的剩余空间,如果大于
				//的话就只拷贝TCP_SERVER_RX_BUFSIZE中剩余长度的数据,否则的话就拷贝所有的数据
				if(q->len > (TCP_SERVER_RX_BUFSIZE-data_len)) memcpy(tcp_server_recvbuf+data_len,q->payload,(TCP_SERVER_RX_BUFSIZE-data_len));//拷贝数据
				else memcpy(tcp_server_recvbuf+data_len,q->payload,q->len);
				data_len += q->len;  	
				if(data_len > TCP_SERVER_RX_BUFSIZE) 
				{
					data_len=TCP_SERVER_RX_BUFSIZE;
					break; //超出TCP客户端接收数组,跳出	
				}
			}
			tcp_server_flag|=1<<6;	//标记接收到数据了
			lwipdev.remoteip[0]=tpcb->remote_ip.addr&0xff; 		//IADDR4
			lwipdev.remoteip[1]=(tpcb->remote_ip.addr>>8)&0xff; //IADDR3
			lwipdev.remoteip[2]=(tpcb->remote_ip.addr>>16)&0xff;//IADDR2
			lwipdev.remoteip[3]=(tpcb->remote_ip.addr>>24)&0xff;//IADDR1 
 			tcp_recved(tpcb,p->tot_len);//用于获取接收数据,通知LWIP可以获取更多数据
			pbuf_free(p);  	//释放内存
			ret_err=ERR_OK;
			if(memcmp(tcp_server_recvbuf,"boot",4)==0)
			{
				printf("进入Bootloader...\r\n");
				iap_load_app(0x8000000);
			}
			
		}
	}else//服务器关闭了
	{
		tcp_recved(tpcb,p->tot_len);//用于获取接收数据,通知LWIP可以获取更多数据
		es->p=NULL;
		pbuf_free(p); //释放内存
		ret_err=ERR_OK;		
	}
	return ret_err;
}

网络配置LWIP\lwip_app\lwip_comm\lwip_comm.c中的lwip_comm_default_ip_set中

//lwip 默认IP设置
//lwipx:lwip控制结构体指针
void lwip_comm_default_ip_set(__lwip_dev *lwipx)
{
	//默认远端IP为:192.168.1.100
	lwipx->remoteip[0]=192;	
	lwipx->remoteip[1]=168;
	lwipx->remoteip[2]=1;
	lwipx->remoteip[3]=104;
	//MAC地址设置(高三字节固定为:2.0.0,低三字节用STM32唯一ID)
	lwipx->mac[0]=dm9000cfg.mac_addr[0];
	lwipx->mac[1]=dm9000cfg.mac_addr[1];
	lwipx->mac[2]=dm9000cfg.mac_addr[2];
	lwipx->mac[3]=dm9000cfg.mac_addr[3];
	lwipx->mac[4]=dm9000cfg.mac_addr[4];
	lwipx->mac[5]=dm9000cfg.mac_addr[5]; 
	//默认本地IP为:192.168.1.30
	lwipx->ip[0]=192;	
	lwipx->ip[1]=168;
	lwipx->ip[2]=1;
	lwipx->ip[3]=88;
	//默认子网掩码:255.255.255.0
	lwipx->netmask[0]=255;	
	lwipx->netmask[1]=255;
	lwipx->netmask[2]=255;
	lwipx->netmask[3]=0;
	//默认网关:192.168.1.1
	lwipx->gateway[0]=192;	
	lwipx->gateway[1]=168;
	lwipx->gateway[2]=1;
	lwipx->gateway[3]=1;	
	lwipx->dhcpstatus=0;//没有DHCP	
} 

因为Bootloader和APP运行于不同的地址段中,所以我们需要设置程序的起始地址,点击 Options for Target>Target 选项卡,下图是Bootloader,默认的条件下,图中 IROM1 的起始地址(Start)一般为 0X08000000,大小(Size)为 0X80000,即从 0X08000000 开始的 512K 空间为我们的程序存储(因为我们的 STM32F103ZET6 的 FLASH大小是 512K)。
在这里插入图片描述
APP需要进行地址偏移,设置起始地址(Start)为 0X08010000,即偏移量为 0X10000(64K字节),因而,留给 APP 用的 FLASH 空间(Size)只有 0X80000-0X10000=0X70000(448K 字节)大小了。在这里插入图片描述
MDK 默认生成的文件是.hex 文件,我们用作 IAP文件是.bin 文件,这样可以方便进行 IAP 升级。这里我们通过 MDK 自带的格式转换工具 fromelf.exe,来实现.axf 文件到.bin 文件的转换。该工具在 MDK 的安装目录\ARM\BIN40 文件夹里面。在 MDK 点击 Options for Target>User 选项卡,在 After Build/Rebuild 栏,
勾选 Run #1,并写入: D:\Keil\MDK525\ARM\ARMCC\bin\fromelf.exe --bin -o …\OBJ\APP1.bin …\OBJ\APP1.axf。这样便后就能在\OBJ目录生成bin文件。

在这里插入图片描述
最后我们给如何实现远程更新呢?我们的实验比较粗糙,我们直接使用网络调试助手实现:
1.在开发板中下载Bootloader程序,接入网络,使电脑和开发板在同一局域网并且能相互ping通。
2.在电脑上打开网络调试助手,选择TC Pclient、板子的ip和端口号,建立连接。
3.在网络调试助手启用文件数据源,选择APP的bin文件,发送完毕后再发送"start"即可执行。
4.如果已经在APP,可在建立连接后发送“boot”,进入Bootloader。

本文仅供学习交流,请勿用于商业用途。

  • 5
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值