一:前言
网络上充斥着各种各样的串口升级方案,基本都是基于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 |
三:数据接收与处理
上位机采用事件驱动方式,防止界面卡顿提高升级效率。本方案使用QT5进行串口编程,实现单片机升级功能。本方案使用串口类 QSerialPort
手动拖出如下界面:
1:扫描串口实现
void MainWindow::on_ScanDevicePButton_clicked()
{
QString UartPortName = ui->portBox->currentText();
ui->portBox->clear();
foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
{
// 自动读取串口号添加到端口portBox中
QSerialPort serial;
serial.setPort(info);
if(serial.open(QIODevice::ReadWrite))
{
ui->portBox->addItem(info.portName());
serial.close();
}
}
ui->portBox->setCurrentText(UartPortName);
}
2:串口写数据
void MainWindow::SerialPortWriteFrame(uint8_t func,QByteArray &data)
{
QByteArray uartBuf;
int checkSum=func;
int lenght = data.size(); //
uartBuf.resize(5); //预留5
uartBuf[0] = 0x55;
uartBuf[1] = 0xAA;
uartBuf[2] = func;
uartBuf[3] = lenght&0xff;
uartBuf[4] = (lenght>>8)&0xff;
uartBuf.append(data);
for(int i=0;i<data.size();i++)
{
checkSum += data.at(i);
}
uartBuf.append(checkSum&0xff);
uartBuf.append(0x5a);
if(SerialPort.isOpen())
{
SerialPort.write(uartBuf);
}
}
3:下载状态机
1:用户触发按键开始数据发送;设置重试次数,并开启重试定时器。
2:当串有数据返回,判断是的是正响应,如果正响应继续发送数据。
3:超时定时器超时响应,对重试计算器减一,并对上次数据重新发送。
4:在发送数据这做一个判断分支就能实现发送流程。
1>数据发送流程
void MainWindow::UpdateStep(BT_program_st *program)
{
QByteArray frameData;
frameData.clear();
switch(program->current_Step)
{
case REQ_STATUS://获取板子信息
m_timer->start(500);
if(program->retry_flag == false)
{
program->retry_Num = 2; //重试5次
program->response = RESP_STATUS;//希望返回准备命令
}
SerialPortWriteFrame(1,frameData);
break;
case REQ_JUMPBOOT://进入boot
{
char temp;
m_timer->start(500);
if(program->retry_flag == false)
{
program->retry_Num = 2; //重试2次
program->response = RESP_JUMPBOOT;//希望返回设置成功命令
}
if(program->jumpBootNumber>0)program->jumpBootNumber--;
temp = (softVersionInfo.satrtAdr>>24)&0xff;
frameData.append(temp);
temp = (softVersionInfo.satrtAdr>>16)&0xff;
frameData.append(temp);
temp = (softVersionInfo.satrtAdr>>8)&0xff;
frameData.append(temp);
temp = (softVersionInfo.satrtAdr)&0xff;
frameData.append(temp);
SerialPortWriteFrame(2,frameData);
}
break;
case REQ_ERASE_SRECTOR://擦除扇区
{
char temp;
m_timer->start(1500);
if(program->retry_flag == false)
{
program->retry_Num = 2; //重试3次
program->response = RESP_ERASE_CMD;//希望返回设置成功命令
}
program->blockIndex = 0;
program->sendIndexCnt = 0;
temp = (program->address>>24)&0xff;
frameData.append(temp);
temp = (program->address>>16)&0xff;
frameData.append(temp);
temp = (program->address>>8)&0xff;
frameData.append(temp);
temp = (program->address)&0xff;
frameData.append(temp);
temp = (program->lenght>>24)&0xff;
frameData.append(temp);
temp = (program->lenght>>16)&0xff;
frameData.append(temp);
temp = (program->lenght>>8)&0xff;
frameData.append(temp);
temp = (program->lenght)&0xff;
frameData.append(temp);
SerialPortWriteFrame(3,frameData);
}
break;
case REQ_WRITE_DATA://发送数据
{
m_timer->start(700);
if(program->retry_flag == false)
{
program->retry_Num = 3; //重试3次
program->response = RESP_WRITE_DATA;//希望返回设置成功命令
}
if(program->blockIndex<FirmwarebyteArray.size())
{
program->blockLenght = FirmwarebyteArray.size()-program->blockIndex;
if(program->blockLenght>512) program->blockLenght = 512;
char * pData = FirmwarebyteArray.data();
frameData.append((char *)(&pData[program->blockIndex]),program->blockLenght);
}
program->sendIndexCnt++;
SerialPortWriteFrame(4,frameData);
}
break;
case REQ_START_ADDRESS://设置写地址
{
char temp;
m_timer->start(700);
if(program->retry_flag == false)
{
program->retry_Num = 3; //重试3次
program->response = RESP_START_ADDRESS;//希望返回设
//成功命令
}
temp = (program->address>>24)&0xff;
frameData.append(temp);
temp = (program->address>>16)&0xff;
frameData.append(temp);
temp = (program->address>>8)&0xff;
frameData.append(temp);
temp = (program->address)&0xff;
frameData.append(temp);
SerialPortWriteFrame(5,frameData);
}
break;
case REQ_VERIFY:
{
char updateTime[20]={};
QDateTime current_date_time =QDateTime::currentDateTime();
QString current_date =current_date_time.toString("yyyy/MM/dd hh:mm:ss");
const char *ptime = current_date.toStdString().c_str();
memcpy(updateTime,ptime,19);
updateTime[19] =0;
m_timer->start(700);
if(program->retry_flag == false)
{
program->retry_Num = 3; //重试3次
program->response = RESP_VERIFY;//希望返回准备命令
}
frameData.append((const char *)updateTime,20);
SerialPortWriteFrame(REQ_VERIFY,frameData);
}
break;
case REQ_RUN_APP:
{
char temp;
m_timer->start(500);
if(program->retry_flag == false)
{
program->retry_Num = 2; //重试2次
program->response = RESP_JUMPBOOT;//希望返回设置成功命令
}
if(program->jumpBootNumber>0)program->jumpBootNumber--;
temp = (softVersionInfo.satrtAdr>>24)&0xff;
frameData.append(temp);
temp = (softVersionInfo.satrtAdr>>16)&0xff;
frameData.append(temp);
temp = (softVersionInfo.satrtAdr>>8)&0xff;
frameData.append(temp);
temp = (softVersionInfo.satrtAdr)&0xff;
frameData.append(temp);
SerialPortWriteFrame(2,frameData);
}
break;
case REQ_FINISH:
if(IsSerialPortOpen) closeSerialPort();
ui->textEdit->append("程序更新完成(*^_^*) \r\n");
program->response = RESP_NO;//密钥核对状态
program->blockIndex = 0;
program->sendByteNumber = 0;
break;
case REQ_ERROR:
ui->textEdit->append("执行失败");
if(IsSerialPortOpen) closeSerialPort();
break;
}
}
2>串口数据接收流程
void MainWindow::serial_Read()
{
//从缓冲区中读取数据
QByteArray buffer = SerialPort.readAll();
if(!buffer.isEmpty())//如果非空说明有数据接收
{
m_UartDataBuf += buffer;
SerialDataAnalysis(m_UartDataBuf);
}
}
//串口数据帧格式解析
void MainWindow::SerialDataAnalysis(QByteArray &buffer)
{
int offset,Byteslenght;
while(1)
{
offset = FindSerialDataHead(buffer); //先清空头部多余的数据
if(offset)buffer.remove(0,offset);
Byteslenght = buffer.size();
if(Byteslenght >= 7)
{
unsigned char func;
unsigned int lenght;
unsigned char end;
func = buffer[2];
lenght = (unsigned char)buffer[4];
lenght <<= 8; //低在前
lenght |= (unsigned char)buffer[3];
if(lenght>1024)
{
buffer.remove(0,2);
qDebug()<<"数据长度异常";
continue;
}
if(Byteslenght>=(int)(lenght+7))
{
end = buffer[lenght+6];
if(end ==0x5a)
{
QByteArray text = buffer.left(lenght+7);
buffer.remove(0,lenght+7);
SerialFrameAnalysis(text); //数据响应处理
}else
{
buffer.remove(0,lenght+7);
qDebug()<<"数据帧异常";
}
}
else
{
break;
}
}
else
{
break;
}
}
}
// 用户数据解析
void MainWindow::SerialFrameAnalysis(QByteArray &buffer)
{
char response = buffer.at(2);
if(m_program_Data.response == response)
{
m_program_Data.retry_flag = false; //不需要重试了
m_timer->stop();
//获取单片机运行状态 0->单片机在底层 1->单片机在应用层
if(m_program_Data.current_Step ==REQ_USER_INFO)
{
FW_INFO fwInfo;
char *pchar= buffer.data();
memcpy(&fwInfo,&pchar[5],sizeof(FW_INFO));
FirmwareInfoDisplay(fwInfo);
closeSerialPort();
//qDebug()<<"REQ_USER_INFO";
}
else if(m_program_Data.current_Step == REQ_STATUS)//
{
FW_INFO fwInfo;
char *pchar= buffer.data();
memcpy(&fwInfo,&pchar[5],sizeof(FW_INFO));
FirmwareInfoDisplay(fwInfo);
if(memcmp(fwInfo.Functions,"bootloader",10)==0)
{
if(memcmp(fwInfo.FirmwareName,firmwareInfo.FirmwareName,strlen(firmwareInfo.FirmwareName)))
{
//固件名字问题
ui->textEdit->append("警告! 固件与产品不匹配");
m_program_Data.current_Step = REQ_ERROR;//烧写错误
}
else
{
m_program_Data.current_Step = REQ_ERASE_SRECTOR;//扇区擦除
}
UpdateStep(&m_program_Data);
}
if(memcmp(fwInfo.Functions,"App",4)==0)
{
qDebug()<<"APP";
if(m_program_Data.jumpBootNumber>0)
{
m_program_Data.current_Step = REQ_JUMPBOOT;//进入底层
}
else
{
m_program_Data.current_Step = REQ_ERROR;
}
UpdateStep(&m_program_Data);
}
}
else if(m_program_Data.current_Step == REQ_JUMPBOOT)
{
char status = buffer.at(5);
if(status)
{
m_program_Data.current_Step = REQ_STATUS;//扇区擦除
}
else
{
m_program_Data.current_Step = REQ_ERROR;
ui->textEdit->append("错误! 无法进入底层 ");
}
UpdateStep(&m_program_Data);
}
else if(m_program_Data.current_Step == REQ_ERASE_SRECTOR)
{
char status = buffer.at(5);
if(status)
{
m_program_Data.current_Step = REQ_START_ADDRESS;//扇区擦除
}
else
{
ui->textEdit->append("错误! flash 擦除出错 ");
m_program_Data.current_Step = REQ_ERROR;//失败
}
UpdateStep(&m_program_Data);
}
else if(m_program_Data.current_Step ==REQ_START_ADDRESS)
{
char status = buffer.at(5);
if(status)
{
m_program_Data.current_Step = REQ_WRITE_DATA;//扇区擦除
}
else
{
ui->textEdit->append("错误! 设置写地址失败 ");
m_program_Data.current_Step = REQ_ERROR;//失败
}
UpdateStep(&m_program_Data);
}
else if(m_program_Data.current_Step ==REQ_WRITE_DATA)//写数据
{
char status = buffer.at(5);
if(status)
{
QTextCursor cursor = ui->textEdit->textCursor();
cursor.movePosition(QTextCursor::End);
ui->textEdit->setTextCursor(cursor);
ui->textEdit->insertPlainText("#");
m_program_Data.blockIndex += m_program_Data.blockLenght;
m_program_Data.blockLenght = 0;
if(m_program_Data.blockIndex>=FirmwarebyteArray.size())
{
m_program_Data.current_Step = REQ_VERIFY;//校验数据
}
else
m_program_Data.current_Step = REQ_WRITE_DATA;//继续写数据
}
else
{
ui->textEdit->append("错误! 写FLASH失败 ");
m_program_Data.current_Step = REQ_ERROR;//失败
}
UpdateStep(&m_program_Data);
}
else if(m_program_Data.current_Step == REQ_VERIFY )//
{
char status = buffer.at(5);
if(status)
{
m_program_Data.current_Step = REQ_RUN_APP;//校验数据
ui->textEdit->append("校验 正确");
}
else
{
m_program_Data.current_Step = REQ_ERROR;//继续写数据
ui->textEdit->append("错误! 校验失败 ");
}
UpdateStep(&m_program_Data);
}
else if(m_program_Data.current_Step == REQ_RUN_APP )
{
char status = buffer.at(5);
if(status)
{
m_program_Data.current_Step = REQ_FINISH;//校验数据
ui->textEdit->append("运行 正确 ");
}
else
{
m_program_Data.current_Step = REQ_ERROR;//继续写数据
ui->textEdit->append("运行 失败 ");
}
UpdateStep(&m_program_Data);
}
else if(m_program_Data.current_Step == REQ_ERROR )//REQ_SECURITY_SEED
{
}
else if(m_program_Data.current_Step ==REQ_FINISH)//判断密码是否正确
{
//烧写完成
m_program_Data.current_Step = REQ_STATUS;
m_program_Data.response = RESP_NO;
}
}
}
3>定时器超时处理流程
void MainWindow::UpdateOverTime()
{
if(m_timer->isActive())//如果定时器已经开启就关闭
{
m_program_Data.retry_flag = true;
this->m_timer->stop();
if(m_program_Data.retry_Num>0)
{
m_program_Data.retry_Num--;
}
else
{
m_program_Data.current_Step = REQ_ERROR;
m_program_Data.response = RESP_NO;
}
UpdateStep(&m_program_Data);
}
}