STM32基于BootLoader进行网络远程更新程序

       以前在学习STM32的时候,看到有的开发板用BootLoader下载程序,觉得脱离下载器程序下载进去挺有意思的,于是就自己琢磨着也做一个,采用RL-TCPnet实现网络通信,还涉及到RTX嵌入式操作系统(不用也行的,没多大必要,因为例程自带RTX所以我用了,注意用RTX前要用注册机添加RTX的注册码)。

一、BootLoader的作用

      BootLoader也是一段程序,简单来说,就是运行应用程序之前,先运行BootLoader做一些必要的处理,再跳到应用程序去执行。对于咱今天要做的事情来说,就是通过局域网把程序下载到芯片里,实现远程升级。因为用下载器下载程序同样也是擦除Flash后把程序文件烧写进Flash,而STM32支持程序运行的时候擦写其他地方的Flash区域,所以可以实现下载功能。

 

二、Flash空间划分

     我用的芯片是STM32F4系列的,有1M的Flash大小,准备划分为以下几个区域。

STM32基于BootLoader进行网络远程更新程序_第1张图片

(1)BootLoader区(0x8000000~0x8020000):128KB,放置BootLoader代码。

(2)Download区(0x8020000~0x8080000):384KB,放置下载的文件,因为下载过程中可能中途断电或者出错,如果直接把下载过程中的数据覆盖到APP区会导致应用程序遭到损坏,Download区起到暂存文件的作用。

(3)APP区(0x8080000~0x80E0000):384KB,放置要运行的应用程序,就是真正想跑的代码。

(4)Reserve区(0x80E0000~0x8100000):128KB,预留一段空间供用户保存信息,可以当做EEPROM用,掉电数据不丢失。

区间的划分可以根据实际使用情况划分。

 

三、程序怎样从BootLoader跳转到APP

     说再多不如直接上代码,把APP区的首地址传入就可以实现跳转了:

void(*Boot_Jump2App)();

//传入要跳转到的程序地址进行跳转
void Boot_LoadApp(DWORD dwAddr)
{
	BYTE i;

	//检查栈顶地址是否合法
	if (((*(vu32*)dwAddr) & 0x2FFE0000) == 0x20000000)	
	{
		//设置跳转地址
		Boot_Jump2App = (void(*)())*(vu32*)(dwAddr + 4);		
		//初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
		__set_MSP(*(vu32*)dwAddr);
		//关闭所有中断
		for (i = 0; i < 8; i++)
		{
			NVIC->ICER[i] = 0xFFFFFFFF;	
			NVIC->ICPR[i] = 0xFFFFFFFF;	
		}
		//跳转到APP Code
		Boot_Jump2App();		
		//跳转之前用死循环卡住
		while (1);
	}
}

不知道大家会不会这样想,如果我又想从APP段跳回BootLoader,不想以断电的方式重新运行BootLoader下载程序,怎么做?我的想法是调用STM32的软件复位函数NVIC_SystemReset()。

__set_FAULTMASK(1);
NVIC_SystemReset();

为什么前面加了一句__set_FAULTMASK(1),作用是关闭所有中断,这里有一个要注意的问题:如果只调用NVIC_SystemReset(),从SYSRESETREQ 被置为有效,到复位发生器执行复位命令,往往会有一个延时。在此延时期间,处理器仍然可以响应中断请求。但我们的本意往往是要程序执行到此为止,不要再做任何其它事情了。所以,最好在发出复位请求前,先把FAULTMASK 置位才万无一失。

 

四、BootLoader程序和APP程序的配置

    BootLoader程序有一点要注意,要对应BootLoader区地址进行如下设置。

STM32基于BootLoader进行网络远程更新程序_第2张图片

    APP程序有三点要注意,(1)也要设置对应的开始地址和大小 

STM32基于BootLoader进行网络远程更新程序_第3张图片

(2)APP程序还要在程序一开始设置中断向量的偏移,就是程序一开始调用这样一条语句,0x80000是APP程序的起始偏移,根据实际情况填写。

NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x80000);

(3)要下载的文件不是Keil编译生成的Hex文件,而是bin文件,怎么生成呢?照着下面依葫芦画瓢就可以了。

STM32基于BootLoader进行网络远程更新程序_第4张图片

就是加上这一句 E:/keil/ARM/ARMCC/bin/fromelf.exe  --bin -o Flash/MC-IOV3248.bin Flash/Obj/output.axf

实际路径根据自己的情况修改。

 

四、程序实现流程 

     程序实现的大致流程如下图:

STM32基于BootLoader进行网络远程更新程序_第5张图片

     BootLoader初始化IP地址为192.168.1.41,根据需要设置了两个端口可以分别独立进行通信。第一个端口为5198,为程序下载端口;第二个端口为5199,为Debug端口,用来输出调试信息和Boot Debug模式下使用,当然这个端口也可以用STM32的串口来代替输出调试信息,因为接线繁琐,所以用了网络端口调试。

    程序一开始创建4个任务如下:

int main (void) 
{
	
	bsp_Init();

	/* 创建启动任务 */
 	os_sys_init_user (AppTaskStart,              /* 任务函数 */
	                  4,                         /* 任务优先级 */
	                  &AppTaskStartStk,          /* 任务栈 */
	                  sizeof(AppTaskStartStk));  /* 任务栈大小,单位字节数 */
	while(1);
}

//启动任务,也是最高优先级任务,	创建任务 ,时间基准更新。
__task void AppTaskStart(void)
{
	//这里实现RL-TCPnet的时间基准更新。
	HandleTaskTCPMain = os_tsk_create_user(RL_TCPnet_timer_tick,
											4,
											&RL_TCPnet_timer_tick_stk,
											sizeof(RL_TCPnet_timer_tick_stk));

	//TCP数据收发处理任务
	HandleTaskTCPMain = os_tsk_create_user(AppTaskTCPMain,
											3,
											&AppTaskTCPMainStk,
											sizeof(AppTaskTCPMainStk));

	//用户任务
	HandleTaskUserIF = os_tsk_create_user(AppTaskUserIF,
											2,
											&AppTaskUserIFStk,
											sizeof(AppTaskUserIFStk));
		
	//LED闪烁任务
	HandleTaskTCPMain = os_tsk_create_user(led_bling_task,
											1,
											&led_bling_taskStk,
											sizeof(led_bling_taskStk));
	
    while(1)
	{
		os_dly_wait(1000);
    }
}

我们只关心TCP数据收发任务AppTaskTCPMain和用户任务AppTaskUserIF。

介绍这两个任务之前,先介绍一下程序里涉及到的几个结构体,首先是tyDownloadHead,就是下载bin文件之前要先发送含有特定格式的下载头,验证成功后才能进行程序下载,否则随便乱发了一些数据过来也当做文件数据的话就太不可靠了。

#define BYTE unsigned char
#define WORD unsigned short
#define DWORD unsigned int

#define DOWNLOAD_HEAD_FLAG			0x55591012

typedef struct
{
	DWORD dwStructFLag;						//识别头 
	DWORD dwStructFLagInverse;              //识别头反码
	BYTE  bFileType;						//文件类型,0:APP程序,1:BootLoader程序
	BYTE  bReserve[3];						//预留,四字节对齐
	DWORD dwFileLen;						//下载文件大小
	DWORD dwFileCRC;						//文件数据校验
	DWORD dwStructCRC;						//结构体校验
}tyDownloadHead;

 还有就是tyBoot结构体,就是控制流程运行的一些变量。

#define BOOT_STATE_DISCONNECT  	        0  //未连接
#define BOOT_STATE_IDL				    1  //空闲
#define BOOT_STATE_DEBUG			    2  //Debug模式
#define BOOT_STATE_DOWNLOADING	        3  //下载模式

typedef struct
{
	BYTE bBootState;          //连接状态
	BYTE bIsNeedSelCmd;
	BYTE bSelCmdNum;
	DWORD dwDownloadLen;
	DWORD dwRecFileLen;
	DWORD dwFileCRC;
}tyBoot;

最后一个是socketNature_t,是对TCP收发数据的控制。

typedef struct{
	U8 *recvBuf;  // 接收数据缓存
	U8 *sendBuf;  // 发送数据缓存
	U16 port;     // 套接字端口
	U8 id;        // 套接字id
	U8 recvFlag;  // 接收到数据的标志:0空闲,1有数据,2正在处理
	U8 recvLen;   // 接收到数据的长度
	U8 sendFlag;  // 待发送标志
	U8 sendLen;   // 要发送的长度
} socketNature_t; // 套接字描述结构

TCP数据收发任务只调用了下面这个函数,完成下载端口和Debug端口的创建,随后不断的侦测是否有数据需要收发,至于RL-TCPnet的具体使用设置网上资料很多,就不解释了。

void TCPnetTest(void)
{
	static U8 socket_pc_recvBuf[256];
	static U8 socket_pc_sendBuf[256];
	static U8 socket_debug_recvBuf[256];
	static U8 socket_debug_sendBuf[256];

	//创建TCP Socket并创建监听
	socket_pc.port = 5198;
	socket_pc.recvBuf = socket_pc_recvBuf;
	socket_pc.sendBuf = socket_pc_sendBuf;
	socket_pc.id = tcp_get_socket(TCP_TYPE_SERVER | TCP_TYPE_KEEP_ALIVE, 0, 10, socket_callback);
	if(socket_pc.id!=0)
	{
		tcp_listen(socket_pc.id,socket_pc.port);
	}
	
	//创建TCP Socket并创建监听
	socket_debug.port = 5199;
	socket_debug.recvBuf = socket_debug_recvBuf;
	socket_debug.sendBuf = socket_debug_sendBuf;
	socket_debug.id = tcp_get_socket(TCP_TYPE_SERVER | TCP_TYPE_KEEP_ALIVE, 0, 10, socket_callback);
	if(socket_debug.id != 0)
	{
		tcp_listen(socket_debug.id,socket_debug.port);
	}

	while (1) 
	{
		main_TcpNet();/* RL-TCPnet处理函数 */
		send_poll(&socket_pc);
		send_poll(&socket_debug);
		os_dly_wait (2);
	}
}

同样这一堆代码我们只关心socket_callback这个回调函数和send_poll这个发送数据函数。

(1)socket_callback函数如下

U16 socket_callback (U8 soc, U8 evt, U8 *ptr, U16 par)
{
	switch (evt) 
	{
		//远程客户端连接消息
		//1、数组ptr存储远程设备的IP地址,par中存储端口号。
		//2、返回数值1允许连接,返回数值0禁止连接。
		case TCP_EVT_CONREQ:return (1);
		case TCP_EVT_ABORT:break;/* 连接终止 */
		case TCP_EVT_CONNECT:
			if (soc == socket_pc.id)
			{
				tBoot.bBootState = BOOT_STATE_IDL;
			}
		
			if (soc == socket_debug.id)
			{
				tBoot.bBootState = BOOT_STATE_DEBUG;
			}
			
			break;/* Socket远程连接已经建立 */
		case TCP_EVT_CLOSE:break;/* 连接断开 */
		case TCP_EVT_ACK:break;/* 发送的数据收到远程设备应答 */
		case TCP_EVT_DATA:/* 接收到TCP数据帧,ptr指向数据地址,par记录数据长度,单位字节 */
			if (soc == socket_debug.id)
			{
				socket_debug.recvLen = par;
				memcpy(socket_debug.recvBuf, ptr, socket_debug.recvLen);
				socket_debug.recvFlag = 1;

				socket_debug.sendLen = par;
				memcpy(socket_debug.sendBuf, ptr, socket_debug.sendLen);
				socket_debug.sendFlag = 1;
			}
			else if (soc == socket_pc.id)
			{
				switch (tBoot.bBootState)
				{
					case BOOT_STATE_IDL:
						if (DOWNLOAD_HEAD_FLAG == ((tyDownloadHead *)ptr)->dwStructFLag)
						{
							if (sizeof(tyDownloadHead) == par &&
								~DOWNLOAD_HEAD_FLAG == ((tyDownloadHead *)ptr)->dwStructFLagInverse &&
								CalculateCRC((DWORD *)ptr, sizeof(tyDownloadHead) - 4, 0) == ((tyDownloadHead *)ptr)->dwStructCRC)
							{
								tBoot.dwRecFileLen = ((tyDownloadHead *)ptr)->dwFileLen;
								tBoot.dwDownloadLen = 0;
								tBoot.dwFileCRC = ((tyDownloadHead *)ptr)->dwFileCRC;
								tBoot.bBootState = BOOT_STATE_DOWNLOADING;
							}
						}

						break;

					case BOOT_STATE_DOWNLOADING:
						socket_pc.recvLen = par;
						memcpy(socket_pc.recvBuf, ptr, socket_pc.recvLen);
						socket_pc.recvFlag = 1;

						socket_pc.sendLen = socket_pc.recvLen;
						memcpy(socket_pc.sendBuf, ptr, socket_pc.sendLen);
						socket_pc.sendFlag = 1;

						STMFLASH_Write(DOWNLOAD_ADDR + tBoot.dwDownloadLen, (DWORD *)socket_pc.recvBuf, socket_pc.recvLen / 4);
						tBoot.dwDownloadLen += socket_pc.recvLen;

						if (tBoot.dwDownloadLen >= tBoot.dwRecFileLen)
						{
							tBoot.bBootState = BOOT_STATE_IDL;
						}
						break;

					default:
						break;
				}

			}
			break;
	}
	return (0);
}

 这段代码完成对数据的接收,同时对下载头进行校验,校验成功后转到下载模式接收文件。

(2)send_poll函数

void send_poll(socketNature_t *socket){
	switch (tcp_get_state(socket->id))/* 用于网线插拔的处理 */
	{
		case TCP_STATE_FREE:
		case TCP_STATE_CLOSED:
			tcp_listen (socket->id,socket->port);//网络断开边接后,开始监听
			return;
		case TCP_STATE_LISTEN://处于监听时不发送
		default:
			socket->sendFlag=0;
			return;
		case TCP_STATE_CONNECT://处于连接状态
			break;
	}
	if(socket->sendFlag)
	{
		U8 *sendbuf;
		S32 iCount;
		U16 maxlen;
		socket->sendFlag=0;
		iCount = socket->sendLen;
		do{
			main_TcpNet();
			if (tcp_check_send(socket->id) == __TRUE)
			{
				maxlen = tcp_max_dsize (socket->id);
				iCount -= maxlen;

				if(iCount < 0)
				{
					maxlen = iCount + maxlen;/* 这么计算没问题的 */
				}
				sendbuf = tcp_get_buf(maxlen);
				memcpy(sendbuf,socket->sendBuf,maxlen);
				tcp_send (socket->id, sendbuf, maxlen);/* 测试发现只能使用获取的内存 */

			}
		}while(iCount > 0);
	}
}

纯粹就是发送TCP数据而已,当哪个port的sendBuf里有数据,就发出去。

下面贴出所有文件的实现代码:

(1)main.c

#include "includes.h"
#include "includeAll.H"
#include "bsp.h"
#include "bootLoader.h"

__task void AppTaskStart(void);
__task void RL_TCPnet_timer_tick(void);
__task void AppTaskTCPMain(void);
__task void AppTaskUserIF(void);
__task void led_bling_task(void);

/* 任务栈 */
static uint64_t AppTaskStartStk[1024/8];
static uint64_t RL_TCPnet_timer_tick_stk[1028/8];
static uint64_t AppTaskTCPMainStk[2048/8];
static uint64_t AppTaskUserIFStk[2028/8];
static uint64_t led_bling_taskStk[2028/8];

/* 任务句柄 */
OS_TID HandleTaskUserIF = NULL;
OS_TID HandleTaskTCPMain = NULL;

//IP地址等设置
LOCALM ip_config = {
	{ 192, 168, 1, 41 },             // IP address
	{ 192, 168, 1, 1 },               // Default Gateway
	{ 255, 255, 255, 0 },             // Net mask
	{ 194, 25, 2, 129 },              // Primary DNS server
	{ 194, 25, 2, 130 }               // Secondary DNS server
};

int main (void) 
{
	、、NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x80000);
	
	bsp_Init();

	/* 创建启动任务 */
 	os_sys_init_user (AppTaskStart,              /* 任务函数 */
	                  4,                         /* 任务优先级 */
	                  &AppTaskStartStk,          /* 任务栈 */
	                  sizeof(AppTaskStartStk));  /* 任务栈大小,单位字节数 */
	while(1);
}

//启动任务,也是最高优先级任务,	创建任务 ,时间基准更新。
__task void AppTaskStart(void)
{
	//这里实现RL-TCPnet的时间基准更新。
	HandleTaskTCPMain = os_tsk_create_user(RL_TCPnet_timer_tick,
											4,
											&RL_TCPnet_timer_tick_stk,
											sizeof(RL_TCPnet_timer_tick_stk));

	//TCP数据收发处理任务
	HandleTaskTCPMain = os_tsk_create_user(AppTaskTCPMain,
											3,
											&AppTaskTCPMainStk,
											sizeof(AppTaskTCPMainStk));

	//用户任务
	HandleTaskUserIF = os_tsk_create_user(AppTaskUserIF,
											2,
											&AppTaskUserIFStk,
											sizeof(AppTaskUserIFStk));
		
	//LED闪烁任务
	HandleTaskTCPMain = os_tsk_create_user(led_bling_task,
											1,
											&led_bling_taskStk,
											sizeof(led_bling_taskStk));
	
    while(1)
	{
		os_dly_wait(1000);
    }
}

//这里实现RL-TCPnet的时间基准更新。
__task void RL_TCPnet_timer_tick(void)
{
	U32 CpuID[3];
	U8 mac_adr[6] = { 0, 1, 2, 50, 60, 70 };
	extern U8 own_hw_adr[];
	extern U8 lhost_name[];
	extern LOCALM localm[];
		
	//根据CPU唯一ID设置MAC地址
	//CpuID[0]=*(vu32*)(0x1ffff7e8);//获取CPU唯一ID(F1的)
	//CpuID[1]=*(vu32*)(0x1ffff7ec);
	//CpuID[2]=*(vu32*)(0x1ffff7f0);
	CpuID[0]=*(vu32*)(0x1FFF7A10);//获取CPU唯一ID(F4的)
	CpuID[1]=*(vu32*)(0x1FFF7A10 + 4);
	CpuID[2]=*(vu32*)(0x1FFF7A10 + 8);
	mac_adr[0] = (u8)((CpuID[1] & 0x00FF0000)>>16);
	mac_adr[1] = (u8)((CpuID[1] & 0xFF000000)>>24);
	mac_adr[2] = (u8)( CpuID[2] & 0x000000FF);
	mac_adr[3] = (u8)((CpuID[2] & 0x0000FF00)>>8);
	mac_adr[4] = (u8)((CpuID[2] & 0x00FF0000)>>16);
	mac_adr[5] = (u8)((CpuID[2] & 0xFF000000)>>24);
	
	//设置网卡物理地址
	mem_copy (own_hw_adr, (U8 *)mac_adr, 6);

	init_TcpNet ();/* 初始化RL-TCPnet */

	//设置主机的名字
	str_copy (lhost_name,"BootLoader");

	//设置ip地址等
	dhcp_disable();
	mem_copy((U8 *)&localm[NETIF_ETH], (U8 *)&ip_config, sizeof(ip_config));

	os_itv_set (100);

    while(1)
	{
		timer_tick ();/* RL-TCPnet时间基准更新函数 */
		os_itv_wait ();
    }
}

//TCP数据收发处理任务
__task void AppTaskTCPMain(void)
{
	while (1)
	{
		TCPnetTest();
	}
}

//用户任务
__task void AppTaskUserIF(void)
{
	BYTE overtimeCnt = 0;
	
	Boot_Init();

	while(1)
	{
		if (BOOT_STATE_DOWNLOADING == tBoot.bBootState)
		{
			Boot_Download();
			tBoot.bBootState = BOOT_STATE_IDL;
			overtimeCnt = 0;
		}
		else if(BOOT_STATE_DEBUG == tBoot.bBootState)
		{
			Boot_Debug();
			overtimeCnt = 0;
		}

		overtimeCnt++;
		
		if(overtimeCnt > 50 && BOOT_STATE_DISCONNECT == tBoot.bBootState)
		{
			Boot_LoadApp(APP_ADDR);
		}
		
		os_dly_wait(100);
	}
}

//LED指示灯闪烁
__task void led_bling_task(void)
{

	U8 flag = 0;

    while(1)
	{
		BOARD_LED = flag;
		flag = flag ? 0 : 1;
		os_dly_wait(50);
    }
}


(2)app_tcpnet_lib.c

#include "app_tcpnet_lib.h"
#include "includes.h"	
#include "bootLoader.h"

socketNature_t socket_pc;   // 与pc端通讯的套接字描述
socketNature_t socket_debug;  // 与plc通讯的套接字描述


/********************************************************************
 * 描述:套接字发送数据函数。
 * 参数:[*socket]套接字描述
 *     [len]要发送的数据长度
 ********************************************************************/
void socket_send(socketNature_t *socket,U8 len){
	socket->sendLen=len;
	socket->sendFlag=1;
}

void send_poll(socketNature_t *socket);
/*
*********************************************************************************************************
*	函 数 名: tcp_callback
*	功能说明: TCP Socket的回调函数
*	形    参: soc  TCP Socket类型
*             evt  事件类型
*             ptr  事件类型是TCP_EVT_DATA,ptr指向的缓冲区记录着接收到的TCP数据,其余事件记录IP地址
*             par  事件类型是TCP_EVT_DATA,记录接收到的数据个数,其余事件记录端口号
*	返 回 值:
*********************************************************************************************************
*/
//			if (soc == socket_debug.id)
//			{
//					char buf[50];
//					sprintf(buf, "This is BootLoader Code\r\n");
//				
//					socket_debug.sendLen = strlen(buf);
//					memcpy(socket_debug.sendBuf, buf, socket_debug.sendLen);
//					socket_debug.sendFlag = 1;
//			}

U16 socket_callback (U8 soc, U8 evt, U8 *ptr, U16 par)
{
	switch (evt) 
	{
		//远程客户端连接消息
		//1、数组ptr存储远程设备的IP地址,par中存储端口号。
		//2、返回数值1允许连接,返回数值0禁止连接。
		case TCP_EVT_CONREQ:return (1);
		case TCP_EVT_ABORT:break;/* 连接终止 */
		case TCP_EVT_CONNECT:

			if (soc == socket_pc.id)
			{
				tBoot.bBootState = BOOT_STATE_IDL;
			}
		
			if (soc == socket_debug.id)
			{
				tBoot.bBootState = BOOT_STATE_DEBUG;
			}
			
			break;/* Socket远程连接已经建立 */
		case TCP_EVT_CLOSE:break;/* 连接断开 */
		case TCP_EVT_ACK:break;/* 发送的数据收到远程设备应答 */
		case TCP_EVT_DATA:/* 接收到TCP数据帧,ptr指向数据地址,par记录数据长度,单位字节 */
			if (soc == socket_debug.id)
			{
				socket_debug.recvLen = par;
				memcpy(socket_debug.recvBuf, ptr, socket_debug.recvLen);
				socket_debug.recvFlag = 1;

				socket_debug.sendLen = par;
				memcpy(socket_debug.sendBuf, ptr, socket_debug.sendLen);
				socket_debug.sendFlag = 1;
			}
			else if (soc == socket_pc.id)
			{
				switch (tBoot.bBootState)
				{
					case BOOT_STATE_IDL:
						if (DOWNLOAD_HEAD_FLAG == ((tyDownloadHead *)ptr)->dwStructFLag)
						{
							if (sizeof(tyDownloadHead) == par &&
								~DOWNLOAD_HEAD_FLAG == ((tyDownloadHead *)ptr)->dwStructFLagInverse &&
								CalculateCRC((DWORD *)ptr, sizeof(tyDownloadHead) - 4, 0) == ((tyDownloadHead *)ptr)->dwStructCRC)
							{
								tBoot.dwRecFileLen = ((tyDownloadHead *)ptr)->dwFileLen;
								tBoot.dwDownloadLen = 0;
								tBoot.dwFileCRC = ((tyDownloadHead *)ptr)->dwFileCRC;
								tBoot.bBootState = BOOT_STATE_DOWNLOADING;
							}
						}

						break;

					case BOOT_STATE_DOWNLOADING:
						socket_pc.recvLen = par;
						memcpy(socket_pc.recvBuf, ptr, socket_pc.recvLen);
						socket_pc.recvFlag = 1;

						socket_pc.sendLen = socket_pc.recvLen;
						memcpy(socket_pc.sendBuf, ptr, socket_pc.sendLen);
						socket_pc.sendFlag = 1;

						STMFLASH_Write(DOWNLOAD_ADDR + tBoot.dwDownloadLen, (DWORD *)socket_pc.recvBuf, socket_pc.recvLen / 4);
						tBoot.dwDownloadLen += socket_pc.recvLen;

						if (tBoot.dwDownloadLen >= tBoot.dwRecFileLen)
						{
							tBoot.bBootState = BOOT_STATE_IDL;
						}
						break;

					default:
						break;
				}

			}
			break;
	}
	return (0);
}

void TCPnetTest(void)
{
	static U8 socket_pc_recvBuf[256];
	static U8 socket_pc_sendBuf[256];
	static U8 socket_debug_recvBuf[256];
	static U8 socket_debug_sendBuf[256];

	//创建TCP Socket并创建监听
	socket_pc.port = 5198;
	socket_pc.recvBuf = socket_pc_recvBuf;
	socket_pc.sendBuf = socket_pc_sendBuf;
	socket_pc.id = tcp_get_socket(TCP_TYPE_SERVER | TCP_TYPE_KEEP_ALIVE, 0, 10, socket_callback);
	if(socket_pc.id!=0)
	{
		tcp_listen(socket_pc.id,socket_pc.port);
	}
	
	//创建TCP Socket并创建监听
	socket_debug.port = 5199;
	socket_debug.recvBuf = socket_debug_recvBuf;
	socket_debug.sendBuf = socket_debug_sendBuf;
	socket_debug.id = tcp_get_socket(TCP_TYPE_SERVER | TCP_TYPE_KEEP_ALIVE, 0, 10, socket_callback);
	if(socket_debug.id != 0)
	{
		tcp_listen(socket_debug.id,socket_debug.port);
	}

	while (1) 
	{
		main_TcpNet();/* RL-TCPnet处理函数 */
		send_poll(&socket_pc);
		send_poll(&socket_debug);
		os_dly_wait (2);
	}
}

void send_poll(socketNature_t *socket){
	switch (tcp_get_state(socket->id))/* 用于网线插拔的处理 */
	{
		case TCP_STATE_FREE:
		case TCP_STATE_CLOSED:
			tcp_listen (socket->id,socket->port);//网络断开边接后,开始监听
			return;
		case TCP_STATE_LISTEN://处于监听时不发送
		default:
			socket->sendFlag=0;
			return;
		case TCP_STATE_CONNECT://处于连接状态
			break;
	}
	if(socket->sendFlag)
	{
		U8 *sendbuf;
		S32 iCount;
		U16 maxlen;
		socket->sendFlag=0;
		iCount = socket->sendLen;
		do{
			main_TcpNet();
			if (tcp_check_send(socket->id) == __TRUE)
			{
				maxlen = tcp_max_dsize (socket->id);
				iCount -= maxlen;

				if(iCount < 0)
				{
					maxlen = iCount + maxlen;/* 这么计算没问题的 */
				}
				sendbuf = tcp_get_buf(maxlen);
				memcpy(sendbuf,socket->sendBuf,maxlen);
				tcp_send (socket->id, sendbuf, maxlen);/* 测试发现只能使用获取的内存 */

			}
		}while(iCount > 0);
	}
}

(3)bootloader.h

#ifndef BOOT_LOADER_H_
#define BOOT_LOADER_H_

#include "includes.h"
#include "includeAll.H"
#include "bsp.h"

#define BOOT_ADDR 			0x8000000  			//bootLoader 128K 
#define DOWNLOAD_ADDR 	0x8020000  			//固件		 384K
#define APP_ADDR 				0x8080000				//应用程序区 384K
#define RESERVED_ADDR		0x80E0000				//预留空间	 128K

#define TRUE 1
#define FALSE 0

#define FUN_SUC 0
#define FUN_FAIL 1

#define BYTE unsigned char
#define WORD unsigned short
#define DWORD unsigned int

#define DOWNLOAD_HEAD_FLAG			0x55591012
#define DEBUG_HEAD_FLAG				0x55591013

#define BOOT_STATE_DISCONNECT  	0
#define BOOT_STATE_IDL					1
#define BOOT_STATE_DEBUG				2
#define BOOT_STATE_DOWNLOADING	3

typedef struct
{
	DWORD dwStructFLag;				//识别头 
	DWORD dwStructFLagInverse;		//识别头取反
	DWORD dwStructCRC;				//结构体校验
}tyDebugHead;

typedef struct
{
	DWORD dwStructFLag;								//识别头 
	DWORD dwStructFLagInverse;				//识别头反码
	BYTE  bFileType;									//文件类型,0:APP程序,1:BootLoader程序
	BYTE  bReserve[3];								//预留,四字节对齐
	DWORD dwFileLen;									//下载文件大小
	DWORD dwFileCRC;									//文件数据校验
	DWORD dwStructCRC;								//结构体校验
}tyDownloadHead;

typedef struct
{
	BYTE bBootState;  //连接状态 
	BYTE bIsNeedSelCmd;
	BYTE bSelCmdNum;
	DWORD dwDownloadLen;
	DWORD dwRecFileLen;
	DWORD dwFileCRC;
}tyBoot;

extern tyBoot tBoot;

void Boot_Init(void);  //初始化 
extern BYTE Boot_Debug(void);  //进入Debug模式
extern BYTE Boot_Download(void);  //进入下载模式
extern void Boot_LoadApp(DWORD dwAddr);  //跳转到APP
extern DWORD CalculateCRC(DWORD *pdwDataBuff, DWORD dwByteLen, DWORD dwOrigXorValue);  //计算CRC

#endif

(4)bootloader.c

#include "bootLoader.h"

tyBoot tBoot;

void Boot_Init(void)
{
	tBoot.bIsNeedSelCmd = FALSE;
	tBoot.bSelCmdNum = 0xFF;
	tBoot.dwDownloadLen = 0;
	tBoot.dwRecFileLen = 0;
	tBoot.bBootState = BOOT_STATE_DISCONNECT;
}

void Debug_Print(char *str)
{
	strcpy(socket_debug.sendBuf, str);
	socket_debug.sendLen = strlen(socket_debug.sendBuf);
	socket_debug.sendFlag = 1;
	while (1 == socket_debug.sendFlag)
	{
		os_dly_wait(2);
	}
}

BYTE Boot_Download(void)
{
	char bBuf[50] = { 0 };
	char overtimeCnt = 0;

	Debug_Print("\r\nDownload Start ...... ");

	
	
	while (tBoot.dwDownloadLen <  tBoot.dwRecFileLen)
	{
		os_dly_wait(200);
		sprintf(bBuf, "\r\nDownloading Download/Total %d/%d ", tBoot.dwDownloadLen, tBoot.dwRecFileLen);
		Debug_Print(bBuf);
		
		overtimeCnt++;
		
		if(overtimeCnt > 50)  //下载超时
		{
			Debug_Print("\r\nDownload Overtime");
			return FUN_FAIL;
		}
	}

	Debug_Print("\r\nDownload Success, Checking File CRC ...... ");
	if(CalculateCRC((DWORD *)DOWNLOAD_ADDR, tBoot.dwRecFileLen, 0) != tBoot.dwFileCRC)
	{
		Debug_Print("\r\nFile CRC Error");
		return FUN_FAIL;
	}
	
	Debug_Print("\r\nFile CRC Pass");
	
	sprintf(bBuf, "\r\nPrograming to App Area 0x%8X ...... ", APP_ADDR);
	Debug_Print(bBuf);

	//从Download区搬移程序到App区
	STMFLASH_Write(APP_ADDR, (u32 *)DOWNLOAD_ADDR, (APP_ADDR - DOWNLOAD_ADDR) / 4);
	
	Debug_Print("\r\nProgram Success");
	//break;

	Debug_Print("\r\nJump to APP Code ");
	Boot_LoadApp(APP_ADDR);

	return FUN_SUC;
}

BYTE Boot_Debug(void)
{
	char bBuf[50] = { 0 };
	char i;

	//打印地址列表
	Debug_Print("\r\n******* BootLoader *******"
					"\r\nBOOT_ADDR    : 0x8000000 - 0x8020000"
					"\r\nDOWNLOAD_ADDR: 0x8020000 - 0x8080000"
					"\r\nAPP_ADDR     : 0x8080000 - 0x80E0000"
					"\r\nRESERVED_ADDR: 0x80E0000 - 0x8100000");

	//等待激活BootLoader命令
//	for (i = 3; i > 0; i--)
//	{
//		sprintf(bBuf, "\r\nPress any key to stop: %d", i);
//		Debug_Print(bBuf);
//		os_dly_wait(1000);

//		if (socket_debug.recvFlag)
//		{
//			socket_debug.recvFlag = 0;
//			tBoot.bIsNeedSelCmd = TRUE;
//			break;
//		}
//	}
	
	tBoot.bIsNeedSelCmd = TRUE;
	
	//跳转到App执行或者选择BootLoader命令
	if (TRUE != tBoot.bIsNeedSelCmd)
	{
		Debug_Print("\r\nJump to APP Code ");
		Boot_LoadApp(APP_ADDR);
	}
	else
	{
		Debug_Print("\r\n1 -> Download App\r\n2 -> Run App");

		//等待用户输入有效的序号
		while ((1 != tBoot.bSelCmdNum) && (2 != tBoot.bSelCmdNum))
		{
			os_dly_wait(100);
			if (socket_debug.recvFlag)
			{
				socket_debug.recvFlag = 0;
				tBoot.bSelCmdNum = *socket_debug.recvBuf;
			}
		}
		
		//根据选择的序号处理命令
		switch (tBoot.bSelCmdNum)
		{
			case 1:
				tBoot.bBootState = BOOT_STATE_IDL;
				break;

			case 2:
				Debug_Print("\r\nJump to APP Code ");
				Boot_LoadApp(APP_ADDR);
				break;

			default:
				break;
		}
	}

	return FUN_SUC;
}

void(*Boot_Jump2App)();

void Boot_LoadApp(DWORD dwAddr)
{
	BYTE i;

	//检查栈顶地址是否合法
	if (((*(vu32*)dwAddr) & 0x2FFE0000) == 0x20000000)	
	{
		//设置跳转地址
		Boot_Jump2App = (void(*)())*(vu32*)(dwAddr + 4);		
		//初始化APP堆栈指针(用户代码区的第一个字用于存放栈顶地址)
		__set_MSP(*(vu32*)dwAddr);
		//关闭所有中断
		for (i = 0; i < 8; i++)
		{
			NVIC->ICER[i] = 0xFFFFFFFF;	
			NVIC->ICPR[i] = 0xFFFFFFFF;	
		}
		//跳转到APP Code
		Boot_Jump2App();		
		//跳转之前用死循环卡住
		while (1);
	}
}

DWORD CalculateCRC(DWORD *pdwDataBuff, DWORD dwByteLen, DWORD dwOrigXorValue)
{
	DWORD i;
	DWORD dwXorResult;

	dwXorResult = dwOrigXorValue;

	for (i = 0; i < (dwByteLen / sizeof(DWORD)); i++)
	{
		dwXorResult ^= pdwDataBuff[i];
	}

	return dwXorResult;
}

五、客户端实现

    江湖有句传言“人生苦短,我用Python”,基于Python的高效率开发,所以我用了Python来实现TCP客户端,废话少说,直接上代码:

from socket import *
import os
import ctypes

class DownloadTool():
    def __init__(self):
        pass

    def Connect(self):
        self.tcpHost = 'localhost'
        self.tcpPort = 5198
        self.tcpRecBufSize = 2048
        self.tcpAddr = ("192.168.1.41", self.tcpPort)
        self.tcpSock = socket(AF_INET, SOCK_STREAM)
        self.tcpSock.connect(self.tcpAddr)

    def CalculateFileSizeAndCRC(self, filePath):
        file = open(filePath, "rb")
        size = os.path.getsize(filePath)
        print("Bin Size = %d" % (size))

        buf = file.read(4)
        crc = 0

        while len(buf) > 0:
            crc = crc ^ (int.from_bytes(buf, byteorder='little', signed=False))
            buf = file.read(4)

        file.close()

        return size, crc

    def DownloadFile(self):
        #传输下载头
        fileLen, fileCRC = self.CalculateFileSizeAndCRC("MC-IOV3248.bin")

        headInfo = []
        headInfo.append(ctypes.c_uint32(0x55591012))
        headInfo.append(ctypes.c_uint32(~0x55591012))
        headInfo.append(ctypes.c_uint32(0x00000000))
        headInfo.append(ctypes.c_uint32(fileLen))
        headInfo.append(ctypes.c_uint32(fileCRC))
        headInfo.append(ctypes.c_uint32(headInfo[0].value ^ headInfo[1].value ^ headInfo[2].value ^ headInfo[3].value ^ headInfo[4].value))

        DownloadHead = bytes()
        for item in headInfo:
            DownloadHead = DownloadHead + bytes(item)

        self.tcpSock.send(DownloadHead)

        //传输bin文件
        file = open("MC-IOV3248.bin", "rb")
        downloadLen = 0

        while True:
            buf = file.read(120)

            if len(buf) <= 0:
                break;

            self.tcpSock.send(buf)
            rec = self.tcpSock.recv(self.tcpRecBufSize)

            if buf != rec:
                print("check err")
                break

            downloadLen = downloadLen + len(buf)
            print("download/Total %d/%d" % (downloadLen, fileLen))

        self.tcpSock.close()

if __name__ == '__main__':
    demo = DownloadTool()
    demo.Connect()
    demo.DownloadFile()

这样就完成了基本的BootLoader下载功能了。

 

六、实验演示

    用网络调试助手连接Debug端口,选择01号下载命令后启动Python程序对下载端口进行程序下载。

STM32基于BootLoader进行网络远程更新程序_第6张图片

 网络调试助手输出信息如下:


[2019-10-27 21:04:44.420]# RECV ASCII>

******* BootLoader *******
BOOT_ADDR    : 0x8000000 - 0x8020000
DOWNLOAD_ADDR: 0x8020000 - 0x8080000
APP_ADDR     : 0x8080000 - 0x80E0000
RESERVED_ADDR: 0x80E0000 - 0x8100000

[2019-10-27 21:04:44.422]# RECV ASCII>

1 -> Download App
2 -> Run App

[2019-10-27 21:04:46.869]# SEND HEX>
01 

[2019-10-27 21:04:46.872]# RECV ASCII>


[2019-10-27 21:04:51.095]# RECV ASCII>

Download Start ...... 

[2019-10-27 21:04:51.296]# RECV ASCII>

Downloading Download/Total 5280/29532 

[2019-10-27 21:04:51.500]# RECV ASCII>

Downloading Download/Total 9480/29532 

[2019-10-27 21:04:51.701]# RECV ASCII>

Downloading Download/Total 13680/29532 

[2019-10-27 21:04:51.905]# RECV ASCII>

Downloading Download/Total 18720/29532 

[2019-10-27 21:04:52.105]# RECV ASCII>

Downloading Download/Total 23640/29532 

[2019-10-27 21:04:52.306]# RECV ASCII>

Downloading Download/Total 28920/29532 

[2019-10-27 21:04:52.508]# RECV ASCII>

Downloading Download/Total 29532/29532 

[2019-10-27 21:04:52.512]# RECV ASCII>

Download Success, Checking File CRC ...... 

[2019-10-27 21:04:52.516]# RECV ASCII>

File CRC Pass
Programing to App Area 0x 8080000 ...... 

[2019-10-27 21:04:55.216]# RECV ASCII>

Program Success

[2019-10-27 21:04:55.220]# RECV ASCII>

Jump to APP Code 

Python端输出信息如下 


Bin Size = 29532
download/Total 120/29532
download/Total 240/29532
download/Total 360/29532
download/Total 480/29532
download/Total 600/29532
download/Total 720/29532
download/Total 840/29532
download/Total 960/29532
download/Total 1080/29532
download/Total 1200/29532
download/Total 1320/29532
download/Total 1440/29532
download/Total 1560/29532
download/Total 1680/29532
download/Total 1800/29532
download/Total 1920/29532
download/Total 2040/29532
download/Total 2160/29532
download/Total 2280/29532
download/Total 2400/29532
download/Total 2520/29532
download/Total 2640/29532
download/Total 2760/29532
download/Total 2880/29532
download/Total 3000/29532
download/Total 3120/29532
download/Total 3240/29532
download/Total 3360/29532
download/Total 3480/29532
download/Total 3600/29532
download/Total 3720/29532
download/Total 3840/29532
download/Total 3960/29532
download/Total 4080/29532
download/Total 4200/29532
download/Total 4320/29532
download/Total 4440/29532
download/Total 4560/29532
download/Total 4680/29532
download/Total 4800/29532
download/Total 4920/29532
download/Total 5040/29532
download/Total 5160/29532
download/Total 5280/29532
download/Total 5400/29532
download/Total 5520/29532
download/Total 5640/29532
download/Total 5760/29532
download/Total 5880/29532
download/Total 6000/29532
download/Total 6120/29532
download/Total 6240/29532
download/Total 6360/29532
download/Total 6480/29532
download/Total 6600/29532
download/Total 6720/29532
download/Total 6840/29532
download/Total 6960/29532
download/Total 7080/29532
download/Total 7200/29532
download/Total 7320/29532
download/Total 7440/29532
download/Total 7560/29532
download/Total 7680/29532
download/Total 7800/29532
download/Total 7920/29532
download/Total 8040/29532
download/Total 8160/29532
download/Total 8280/29532
download/Total 8400/29532
download/Total 8520/29532
download/Total 8640/29532
download/Total 8760/29532
download/Total 8880/29532
download/Total 9000/29532
download/Total 9120/29532
download/Total 9240/29532
download/Total 9360/29532
download/Total 9480/29532
download/Total 9600/29532
download/Total 9720/29532
download/Total 9840/29532
download/Total 9960/29532
download/Total 10080/29532
download/Total 10200/29532
download/Total 10320/29532
download/Total 10440/29532
download/Total 10560/29532
download/Total 10680/29532
download/Total 10800/29532
download/Total 10920/29532
download/Total 11040/29532
download/Total 11160/29532
download/Total 11280/29532
download/Total 11400/29532
download/Total 11520/29532
download/Total 11640/29532
download/Total 11760/29532
download/Total 11880/29532
download/Total 12000/29532
download/Total 12120/29532
download/Total 12240/29532
download/Total 12360/29532
download/Total 12480/29532
download/Total 12600/29532
download/Total 12720/29532
download/Total 12840/29532
download/Total 12960/29532
download/Total 13080/29532
download/Total 13200/29532
download/Total 13320/29532
download/Total 13440/29532
download/Total 13560/29532
download/Total 13680/29532
download/Total 13800/29532
download/Total 13920/29532
download/Total 14040/29532
download/Total 14160/29532
download/Total 14280/29532
download/Total 14400/29532
download/Total 14520/29532
download/Total 14640/29532
download/Total 14760/29532
download/Total 14880/29532
download/Total 15000/29532
download/Total 15120/29532
download/Total 15240/29532
download/Total 15360/29532
download/Total 15480/29532
download/Total 15600/29532
download/Total 15720/29532
download/Total 15840/29532
download/Total 15960/29532
download/Total 16080/29532
download/Total 16200/29532
download/Total 16320/29532
download/Total 16440/29532
download/Total 16560/29532
download/Total 16680/29532
download/Total 16800/29532
download/Total 16920/29532
download/Total 17040/29532
download/Total 17160/29532
download/Total 17280/29532
download/Total 17400/29532
download/Total 17520/29532
download/Total 17640/29532
download/Total 17760/29532
download/Total 17880/29532
download/Total 18000/29532
download/Total 18120/29532
download/Total 18240/29532
download/Total 18360/29532
download/Total 18480/29532
download/Total 18600/29532
download/Total 18720/29532
download/Total 18840/29532
download/Total 18960/29532
download/Total 19080/29532
download/Total 19200/29532
download/Total 19320/29532
download/Total 19440/29532
download/Total 19560/29532
download/Total 19680/29532
download/Total 19800/29532
download/Total 19920/29532
download/Total 20040/29532
download/Total 20160/29532
download/Total 20280/29532
download/Total 20400/29532
download/Total 20520/29532
download/Total 20640/29532
download/Total 20760/29532
download/Total 20880/29532
download/Total 21000/29532
download/Total 21120/29532
download/Total 21240/29532
download/Total 21360/29532
download/Total 21480/29532
download/Total 21600/29532
download/Total 21720/29532
download/Total 21840/29532
download/Total 21960/29532
download/Total 22080/29532
download/Total 22200/29532
download/Total 22320/29532
download/Total 22440/29532
download/Total 22560/29532
download/Total 22680/29532
download/Total 22800/29532
download/Total 22920/29532
download/Total 23040/29532
download/Total 23160/29532
download/Total 23280/29532
download/Total 23400/29532
download/Total 23520/29532
download/Total 23640/29532
download/Total 23760/29532
download/Total 23880/29532
download/Total 24000/29532
download/Total 24120/29532
download/Total 24240/29532
download/Total 24360/29532
download/Total 24480/29532
download/Total 24600/29532
download/Total 24720/29532
download/Total 24840/29532
download/Total 24960/29532
download/Total 25080/29532
download/Total 25200/29532
download/Total 25320/29532
download/Total 25440/29532
download/Total 25560/29532
download/Total 25680/29532
download/Total 25800/29532
download/Total 25920/29532
download/Total 26040/29532
download/Total 26160/29532
download/Total 26280/29532
download/Total 26400/29532
download/Total 26520/29532
download/Total 26640/29532
download/Total 26760/29532
download/Total 26880/29532
download/Total 27000/29532
download/Total 27120/29532
download/Total 27240/29532
download/Total 27360/29532
download/Total 27480/29532
download/Total 27600/29532
download/Total 27720/29532
download/Total 27840/29532
download/Total 27960/29532
download/Total 28080/29532
download/Total 28200/29532
download/Total 28320/29532
download/Total 28440/29532
download/Total 28560/29532
download/Total 28680/29532
download/Total 28800/29532
download/Total 28920/29532
download/Total 29040/29532
download/Total 29160/29532
download/Total 29280/29532
download/Total 29400/29532
download/Total 29520/29532
download/Total 29532/29532
end

Process finished with exit code 0

 

你可能感兴趣的:(STM32)