一:前言
网络上充斥着各种各样的串口升级方案,基本都是基于Y-Mode协议下载。采用这种升级方案学习还是可以,用在产品上还是有点欠缺。如下载完成后,需要把时间发送到设备就不好搞了。如下这个设备返回的信息,最后一项就是固件更新时间。
固件名称: KEA128
编译时间: 2022/03/02 14:25:18
固件版本: 1.0.3
硬件版本: 1.0.3
产品序列号: 30304536001 A2210000002D
功能名称: App
固件更新时间: 2022/03/02 14:26:46
串口升级演示视频
项目源码下载:
【免费】s9keaz128串口升级方案1:上位机qt5源码2:单片机底层与应用程序3:烧写文档4:原理图资源-CSDN文库
二:升级协议
1:获取固件信息
PC发送:
头部 | 功能 | 数据长度 | 数据 | 校验 | 结束 |
55 AA | 01 | 00 00 | - | 校验和 | 5A |
设备返回:
头部 | 功能 | 数据长度 | 数据 | 校验 | 结束 |
55 AA | 01 | LL HH | “版本” | 校验和 | 5A |
2:运行命令
PC发送:
头部 | 功能 | 数据长度 | 数据 | 校验 | 结束 |
55 AA | 02 | 04 00 | 地址(xx xx xx xx) | 校验和 | 5A |
设备返回:
头部 | 功能 | 数据长度 | 数据 | 校验 | 结束 |
55 AA | 02 | 01 00 | 00-失败 01-成功 | 校验和 | 5A |
3:擦除Flash命令
PC发送:
头部 | 功能 | 数据长度 | 数据 | 校验 | 结束 |
55 AA | 03 | 08 00 | 地址(AA AA AA AA) 长度(LL LL LL LL) | 校验和 | 5A |
设备返回:
头部 | 功能 | 数据长度 | 数据 | 校验 | 结束 |
55 AA | 03 | 01 00 | 00-失败 01-成功 | 校验和 | 5A |
4:写数据命令
PC发送:
头部 | 功能 | 数据长度 | 数据 | 校验 | 结束 |
55 AA | 04 | LL LL | 数据 | 校验和 | 5A |
设备返回:
头部 | 功能 | 数据长度 | 数据 | 校验 | 结束 |
55 AA | 04 | 01 00 | 00-失败 01-成功 | 校验和 | 5A |
5:设置写地址命令
PC发送:
头部 | 功能 | 数据长度 | 数据 | 校验 | 结束 |
55 AA | 05 | 04 00 | 地址(AA AA AA AA) | 校验和 | 5A |
设备返回:
头部 | 功能 | 数据长度 | 数据 | 校验 | 结束 |
55 AA | 05 | 01 00 | 00-失败 01-成功 | 校验和 | 5A |
6:数据校验命令
PC发送:
头部 | 功能 | 数据长度 | 数据 | 校验 | 结束 |
55 AA | 06 | 13 00 | 当前时间如“2024/02/02 10:00:00” | 校验和 | 5A |
设备返回:
头部 | 功能 | 数据长度 | 数据 | 校验 | 结束 |
55 AA | 06 | 01 00 | 00-失败 01-成功 | 校验和 | 5A |
三:数据接收与处理
数据接收采用环形缓冲区,串口中断接收数据后放入缓冲区,主函数对数据监听与处理。大致如下处理流程
1:串口接收数据后放入环形缓冲区
2:从环形缓存区取出一帧数据
3:处理数据
四:关键性软件
void Boot_Executive(void)
{
uint8_t head_data[USER_CMD_STRUCT_LEN];
int offset;
uint8_t CallFucNumber;
uint8_t func;
unsigned int lenght;
uint8_t end;
uint8_t checkSum;
unsigned int queueAvailLen;
start:
//获取环形缓存区数据长度
queueAvailLen = fifo_used(&uart1_fifo);
if(queueAvailLen >= USER_CMD_STRUCT_LEN) //缓冲器中有数据
{
//偷看下数据最小长度
fifo_out_peek(&uart1_fifo,head_data,USER_CMD_STRUCT_LEN);
//查找头部 55 AA
offset = finSerialDatahead(head_data,USER_CMD_STRUCT_LEN);
if(offset>0)
{
fifo_out_clear(&uart1_fifo,offset);//删除前面无效数据
goto start;
}
func = head_data[2];
lenght = (uint8_t)head_data[4];
lenght <<= 8;
lenght |= (uint8_t)head_data[3];
if(lenght>512) //一帧数据最大长度设置
{
fifo_out_clear(&uart1_fifo,2);//去掉头部
goto start;
}
if(queueAvailLen>=(int)(lenght+7))//可能有完整一帧数据
{
//都不数据已无用清除
fifo_out_clear(&uart1_fifo,USER_CMD_STRUCT_LEN-2);
//获取数据部分
fifo_out_peek(&uart1_fifo,uart_frame,lenght+2);
//获取尾部数据5A
end = uart_frame[lenght+1];
//对数据区计算校验
checkSum = uart_checkSum(func,&uart_frame[0],lenght);
//判断尾部与校验和
if(end ==0x5a && checkSum == uart_frame[lenght])
{
CallFucNumber = sizeof(UserUartFunctionCall)/sizeof(UartOutProCessFunc);
if(func<CallFucNumber)
{
if(UserUartFunctionCall[func]!=NULL)
{
g_BootLoaderCmd.autoFlag = 0;
UserUartFunctionCall[func](func,&uart_frame[0],lenght);
}
}
//开始处理数据
fifo_out_clear(&uart1_fifo,lenght+2);
}else
{
fifo_out_clear(&uart1_fifo,lenght+2); //清除头部
//"数据帧异常";
}
}
}
Boot_ExecutiveJump(&g_BootLoaderCmd);
}
1:判断数据是否大于等于最小数据长度7字节。
2:读取7字节,查找头部 55 AA 位置
3:头位置不是0清除前面无效数据返回到第一步。
4:判断帧数据长度;长度大于最大长度代表帧异常,清除头部 55 AA两字节返回第一步。
5:判断缓冲区数据长度是否大于等于数据长度+7,表示是否有完整帧。
6:计算数据校验和,以及对尾部5A判断 异常清除头部 55 AA两字节返回第一步。
7:处理数据数据。
处理功能集合,通过函数指针调用:
typedef void (*UartOutProCessFunc)(uint8_t func,uint8_t *buf,uint32_t len);
UartOutProCessFunc UserUartFunctionCall[]=
{
NULL, //0
get_board_info, //1
run_app_command,//2
erase_flash_command, //3
write_data_command,//4
set_start_address_command,//5
verify_data_command,//6
};
例如运行命令:
void run_app_command(uint8_t func,uint8_t *buf,uint32_t len)
{
uint32_t adrress=0;
uint8_t status;
adrress = CharToIntBigEnd(&buf[0],4);
if((adrress>=APP_START_ADDR) && (adrress<FLASH_END_ADDR))
{
g_BootLoaderCmd.jump_address = adrress;
g_BootLoaderCmd.status = 0x01; //需要跳转
status = 1;
}
else
{
status = 0;
}
uart_WriteFrame(func,&status,1); //返回状态
}
返回数据处理:
uint8_t uart_WriteFrame(uint8_t func,uint8_t *buf,unsigned int lenght)
{
unsigned int i;
if(lenght>MAX_FRAME_DATA_NUMBER) return 1;
uart2_Txbuf[0] = 0x55;
uart2_Txbuf[1] = 0xAA;
uart2_Txbuf[2] = func;
uart2_Txbuf[3] = lenght&0xff;
uart2_Txbuf[4] = (lenght>>8)&0xff;
memcpy(&uart2_Txbuf[5],buf,lenght);
uart2_Txbuf[5+lenght] = uart_checkSum(func,buf,lenght);
uart2_Txbuf[6+lenght] = 0x5a;
for(i=0;i<lenght+7;i++)
UART_PutChar(UART0,uart2_Txbuf[i]);
return 0;
}
不断对上位机发送的命令处理,以及对处理状态的应答。经过一些列处理后完成对单片机Flash的擦除,写,校验。
五:工程