最近项目没那么紧,因此利用QT进行IAP上位机的制作,IAP其实主要就是将bin文件或者hex文件的数据通过串口发给下位机的updataInfo区域即可,发送完成后,下位机进行系统复位,通过bootloader将刚才发送的数据搬运到正常工作的Info区域,同时擦除刚才的updataInfo即可,这样系统就可以进行升级,IAP主要的优势就是不进行拆机就可以升级,你想想如果一百台机器让你拆机进行升级,多痛苦啊!其实固件更新的原理也是一样,只不过这个更新文件(特制bin文件)是通过WIFI,将更新数据打包分帧进行下发,写入到updataInfo,好了扯远了,知道了原理,那编写的上位机其实就是将bin文件或者hex文件读取回来,然后打包发送,如果,你不管下位机是否接收到数据,你可以酷酷发,虽然也可以成功,但是遇到意外,就要拆机重新烧程序了,因此,保守的方法还是应答式,上位机发送一帧数据,下位机就回复上位机是否接受到,如果下位机接收到了回复正确的信息了,那我们再继续发送,循环往复直到最后一帧数据。
帧头 | 指令1 | 指令2 | 数据长度 | 数据域 | 校验和 | |
PC请求 | 0xF1 | 0xAA | 0x01 | 0xFF(1025) | ||
下位机应答 | 0xF2 | 0xAA | 0x01 | 0x01 | 帧号 |
对于一些简单的串口设置的代码就不写了,主要提供一下一些重要的相关代码;
首先打开文件的同时获取下位机的版本号;
void MainWindow::on_OpenFileBt_clicked()
{
fileName = QFileDialog::getOpenFileName(this,"Open File",QDir::currentPath(), "Hex文件 (*.hex);;Binary文件 (*.bin);;所有文件(*.*)");
ui->filePathLineEdit->setText (fileName);
if(fileName.isEmpty())
{
QMessageBox::information(this,"Error Message", "Please Select a Text File");
return;
}
QFileInfo *pcsfileInfo = new QFileInfo(fileName);
binSize = static_cast<int>(pcsfileInfo->size());
delete pcsfileInfo;
ui->SendEdit->clear();
ui->SendEdit->setText("F1 88 00 01 00 89");//获取下位机的版本号
SendInformationFunction();
}
然后就可以点发送按钮了,这里面的函数较多,且听我一一道来,在说明之前,先说一下我们命名文件版本格式:项目+名称+版本号(版本号格式0.0.0.0);eg:HC2024_Project_1.0.0.0.hex
void MainWindow::on_SendHexFileBt_clicked()
{
//获取此次IAP的版本号
QFileInfo fileInfo(fileName);
qDebug()<<"fileName:"<<fileName;
QString IAPVersion = fileInfo.fileName();
QString BinVersion;
QString BinVersionNum;
QRegularExpression re("([A-Za-z0-9_]+)_(\\d+\\.\\d+\\.\\d+\\.\\d+)");
QRegularExpressionMatch IAPmatch = re.match(IAPVersion);
if (IAPmatch.hasMatch()) {
BinVersion = IAPmatch.captured(0); // Whole match 不含checksum的名称
BinVersionNum = IAPmatch.captured(2); // Capturing group 2 - version number
qDebug() << "BinVersion: " << BinVersion;
qDebug() << "BinversionNum: " << BinVersionNum;
}
//获取MCU版本号
qDebug() << "MCUVersion: " << MCUVersion;
QRegularExpressionMatch MCUmatch = re.match(MCUVersion);
QString MCUVersionNum = MCUmatch.captured(2);
qDebug() << "MCUVersionNum: " << MCUVersionNum;
//比较两个版本是否一样
bool CanUpgrade = canUpgrade(MCUVersionNum,BinVersionNum);
if(CanUpgrade == true){
UpgrateStatus = 1;
ui->statusBar->showMessage(tr("正在更新"));
qDebug() << "CanSendUpdata" ;
emit CanSendUpdata();
}else {
QMessageBox::information(this,"提示","请使用合适、规范的文件名及Hex文件进行升级");
return;
}
}
判断完版本号(此函数自己写,每个人的版本号编码不一样),如果需要更新,就可以发送更新内容了,所以emit CanSendUpdata();连接一下槽函数SendUpDataFunction();
void MainWindow::SendUpDataFunction(void){
qDebug() << "SendUpDataFunction" ;
QFile* file = new QFile;
fileData.clear();
file->setFileName(fileName);
//获取是什么类型文件bin还是hex
QFileInfo fileInfo(fileName);
QString DocumentType = fileInfo.suffix();
QTextStream in(file);
if(file->open(QIODevice::ReadOnly) == true){
qDebug() << "File opened successfully";
if(DocumentType.toLower() == "hex"){
while (!in.atEnd()) {
QString line = in.readLine();
// 假设hex文件的每一行格式为:HHAAAATTHHHH
// 其中HH为地址,TT为类型,HHH为数据
QString DeleteLine = line.mid(7, 2);
if(DeleteLine == "00"){
QString data = line.mid(9, line.length() - 11); // 去除地址后的数据
fileData.append(QByteArray::fromHex(data.toLatin1())); // 将hex字符串转换为字节数组
}else{}
}
}else if((DocumentType.toLower() == "bin")){
fileData = file->readAll();
}else {}
// QFile outFile("C:\\Users\\hanqi.zhang\\Desktop\\outputFile.bin");
// if(outFile.open(QIODevice::WriteOnly)) {
// outFile.write(fileData);
// outFile.close();
// }
qDebug() << "fileDataSize: "<<fileData.size();
}
file->close();
if(fileData.size()%1024 == 0)
{
m_totalSize = fileData.size()/1024;
}
else {
m_totalSize = fileData.size()/1024+1;
}
m_bytesSent = 0;
sendNextChunk();
}
发送数据的函数,根据自己的协议自行修改
void MainWindow::sendNextChunk() {
if (m_bytesSent < m_totalSize) {
qDebug()<<"m_totalSize"<< m_totalSize;
s_buffer = fileData.mid(m_bytesSent*1024,1024);
s_buffer.resize(1024);//目的是给最后一帧会补零
// m_buffer.prepend("\x01");
s_buffer.prepend(static_cast<char>(m_bytesSent+1));
qDebug()<<"帧号"<<QByteArray::number(m_bytesSent + 1);
s_buffer.prepend('\xFF');
s_buffer.prepend('\x01');
s_buffer.prepend('\xAA');
s_buffer.prepend('\xF1');
m_checksum = 0;
qDebug()<<"s_buffer.size():"<<s_buffer.size();
for (int j = 1; j < s_buffer.size(); j++) {
m_checksum += s_buffer.at(j);
}
// 将总和添加到QByteArray的末尾
s_buffer.append(m_checksum);
serial->write(s_buffer);
s_buffer.fill(0);//清空buf,防止最后一帧数据不对
qDebug()<<"开始发送数据";
} else {
emit IAPfinish();
}
}
此时发完等待下位机回应
connect(serial,&QSerialPort::readyRead, //接收数据的连接 这边用定时器的原因是防止接收的数据断断续续
this,[=](){
qDebug()<<"timer start";
timer->start(100);//100ms
Receivetext.append(serial->readAll()); // 从串口读取数据
});
后面就是关定时器,分析Receivetext中的数据了,如果是你想要的数据,就继续发下一帧,用sendNextChunk()函数;如果不是则报错即可。这里需要说一下,为什么要开100ms去接收数据,因为本人在采集数据时会发现数据会断断续续的进来,如果简单一点就用我这种方法,如果想用高级一点的,就用队列buf,实时处理数据即可;用timer的方法简单粗暴,节约时间,哈哈哈,另外一种方法可以请读者自行思考。还有下位机的相关代码程序这边直接用我的项目进行举例,如何将数据写入flash中的app2区域。
// 返回 1成功,0失败
uint8_t UpData_IAP_Write(uint16_t Kilo, const uint8_t *pBuf, uint16_t Length)
{
uint8_t ret = 1;
uint32_t addr;
rt_base_t level = rt_hw_interrupt_disable();
rt_enter_critical();
Flash__Unlock();
识别 Kilo
if (0 == Kilo) // 不应该出现0
{
ret = 0;
}
else if (1 == Kilo) // 第1K
{
if (0 != ret)
{
if (0 != memcmp(pBuf, PROJECR_NAME, 6)) // 项目错误
{
//ret = 0;
}
}
//
if (0 != ret)
{
Get Info
if (strlen((char *)pBuf) < 16)
{
strcpy(Update_Info.Project_String, (char *)pBuf);
}
Length
Update_Info.Length = *(pBuf + 0x20) * 0x1000000
+ *(pBuf + 0x21) * 0x10000
+ *(pBuf + 0x22) * 0x100
+ *(pBuf + 0x23);
Checksum
Update_Info.Checksum = *(pBuf + 0x24) * 0x1000000
+ *(pBuf + 0x25) * 0x10000
+ *(pBuf + 0x26) * 0x100
+ *(pBuf + 0x27);
Kilos
if (Update_Info.Length % 1024 == 0)
{
Update_Info.Kilos = Update_Info.Length / 1024;
}
else
{
Update_Info.Kilos = Update_Info.Length / 1024 + 1;
}
Pages
if (Update_Info.Kilos % 8 == 0)
{
Update_Info.Pages = Update_Info.Kilos / 8;
}
else
{
Update_Info.Pages = Update_Info.Kilos/ 8 + 1;
}
擦 App1 全部
for (uint32_t i=0; i<Update_Info.Pages; i++)
{
addr = FLASH_APP1_ADDR_START + (FLASH_PAGE_SIZE * i);
Flash__ErasePage(addr);
}
}
}
else if (Kilo <= Update_Info.Kilos) // 范围合适
{
}
else // 超范围
{
ret = 0;
}
写入1K数据
if (0 != ret)
{
addr = FLASH_APP1_ADDR_START + (1024 * (Kilo - 1));
Flash__WriteArray(addr, pBuf, 1024);
if (Kilo == Update_Info.Kilos) // 最后一页
{
// 做标记
Flash__ErasePage(FLASH_MARK_ADDR);
Flash__WriteWord(FLASH_MARK_ADDR, FLASH_MARK_WORD);
}
}
Flash__Lock();
rt_exit_critical();
rt_hw_interrupt_enable(level);
if (Kilo == Update_Info.Kilos) // 最后一页
{
// 重启前喂狗
#ifdef WDG_EN
WDG_Feed();
#endif
// 重启
NVIC_SystemReset();
}
return(ret);
}
我是一个小白,上面的内容肯定有不周到的地方,如有问题请邮箱zhanghanqi@njfu.edu.cn联系