以前在学习STM32的时候,看到有的开发板用BootLoader下载程序,觉得脱离下载器程序下载进去挺有意思的,于是就自己琢磨着也做一个,采用RL-TCPnet实现网络通信,还涉及到RTX嵌入式操作系统(不用也行的,没多大必要,因为例程自带RTX所以我用了,注意用RTX前要用注册机添加RTX的注册码)。
一、BootLoader的作用
BootLoader也是一段程序,简单来说,就是运行应用程序之前,先运行BootLoader做一些必要的处理,再跳到应用程序去执行。对于咱今天要做的事情来说,就是通过局域网把程序下载到芯片里,实现远程升级。因为用下载器下载程序同样也是擦除Flash后把程序文件烧写进Flash,而STM32支持程序运行的时候擦写其他地方的Flash区域,所以可以实现下载功能。
二、Flash空间划分
我用的芯片是STM32F4系列的,有1M的Flash大小,准备划分为以下几个区域。
(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区地址进行如下设置。
APP程序有三点要注意,(1)也要设置对应的开始地址和大小
(2)APP程序还要在程序一开始设置中断向量的偏移,就是程序一开始调用这样一条语句,0x80000是APP程序的起始偏移,根据实际情况填写。
NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x80000);
(3)要下载的文件不是Keil编译生成的Hex文件,而是bin文件,怎么生成呢?照着下面依葫芦画瓢就可以了。
就是加上这一句 E:/keil/ARM/ARMCC/bin/fromelf.exe --bin -o Flash/MC-IOV3248.bin Flash/Obj/output.axf
实际路径根据自己的情况修改。
四、程序实现流程
程序实现的大致流程如下图:
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程序对下载端口进行程序下载。
网络调试助手输出信息如下:
[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