时隔近一年,再次接触IAP固件升级,这次修改了以前的一些bug,同时新增一些实用性的功能。
有纰漏请指出,转载请说明。
学习交流请发邮件 1280253714@qq.com。


当Up对iap固件升级的机制有了更深的理解后_哔哩哔哩_bilibili

- // MainWindow类的dragEnterEvent方法
- // 当鼠标拖动文件进入MainWindow的边界时,触发此方法
- void MainWindow::dragEnterEvent(QDragEnterEvent *event)
- {
- // 检查拖动的数据是否包含URLs(即文件路径)
- if(event->mimeData()->hasUrls())
- // 如果包含URLs,则接受拖放操作
- event->acceptProposedAction();
- else
- // 如果不包含URLs,则忽略拖放操作
- event->ignore();
- }
-
- // MainWindow类的dropEvent方法
- // 当鼠标在MainWindow内释放拖动的文件时,触发此方法
- void MainWindow::dropEvent(QDropEvent *event)
- {
- // 获取拖放事件中的MIME数据
- const QMimeData *mimeData = event->mimeData();
- // 检查MIME数据是否包含URLs
- if(mimeData->hasUrls())
- {
- // 获取URLs列表
- QList
urlList = mimeData->urls(); - // 获取第一个URL的本地文件路径
- QString fileName = urlList.at(0).toLocalFile();
-
- // 获取textEdit_fwUpdateFile控件中的文本
- QString text = ui->textEdit_fwUpdateFile->toPlainText();
-
- // 检查文件名是否为空,并且textEdit_fwUpdateFile中的文本为空
- if((!fileName.isEmpty()) && (text.isEmpty()))
- {
- // 检查文件扩展名是否为.bin
- if (fileName.endsWith(".bin"))
- {
- // 创建一个QFile对象,用于读取文件
- QFile file(fileName);
- // 创建一个QFileInfo对象,用于获取文件信息
- QFileInfo fileInfo(fileName);
-
- // 初始化固件更新相关变量
- fwPackIndex = 0;
- // 获取文件大小
- fwFileLen = fileInfo.size();
- // 计算固件包的数量(根据fwPackLength,但fwPackLength在此代码中未定义)
- fwPackNum = fwFileLen/fwPackLength+1;
-
- // 尝试以只读方式打开文件
- if(!file.open(QIODevice::ReadOnly))
- // 如果文件打开失败,则返回不执行后续操作
- return;
-
- // 读取文件全部内容
- binRawData = file.readAll();
-
- // 在lineEdit_fwUpdateFile控件中显示文件名
- ui->lineEdit_fwUpdateFile->setText(fileName);
- // 在textEdit_fwUpdateFile控件中追加文件的十六进制表示
- ui->textEdit_fwUpdateFile->append(binRawData.toHex());
-
- // 关闭文件
- file.close();
-
- // 启用开始固件更新按钮
- ui->pushButton_startFwUpdate->setEnabled(true);
- // 禁用停止固件更新按钮
- ui->pushButton_stopFwUpdate->setEnabled(false);
-
- // 初始化固件更新状态
- fwUpdateState = fwInit;
- // 重置固件包索引
- fwPackIndex = 0;
- // 重置超出标记和索引(这些变量在代码中没有明确的定义和用途)
- fwExceedFlag = 0;
- fwExceedIndex = 0;
-
- // 重置进度条
- ui->progressBar_upgrade->reset();
- }
- else
- {
- // 如果文件扩展名不是.bin,则显示警告消息
- QMessageBox::warning(this, tr("错误"), tr("无法打开正确的文件!"));
- }
- }
- }
- }
- void MainWindow::serialPortInit()
- {
- // 获取所有可用的串口信息,并存储在QList
中 - QList
comList = QSerialPortInfo::availablePorts(); -
- // 清空comboBox_chooseCom控件中的所有项
- ui->comboBox_chooseCom->clear();
-
- // 创建一个QMap,用于存储串口名(portName)到comboBox中索引的映射
- QMap
int> portNameToIndexMap; -
- // 遍历所有可用的串口信息
- foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts()) {
- // 构建一个显示文本,由串口名和描述组成
- QString displayText = info.portName() + ": " + info.description();
-
- // 获取当前comboBox的项数,作为新项的索引
- int index = ui->comboBox_chooseCom->count();
-
- // 在comboBox_chooseCom控件中添加新的显示文本
- ui->comboBox_chooseCom->addItem(displayText);
-
- // 将当前串口名及其索引存入映射中
- portNameToIndexMap.insert(info.portName(), index);
-
- // 检查串口的描述或制造商信息是否包含"CH340"(不区分大小写)
- if (info.description().contains("CH340", Qt::CaseInsensitive) ||
- info.manufacturer().contains("CH340", Qt::CaseInsensitive)) {
- // 使用映射来查找该串口名对应的索引
- // 由于之前已经将串口名和索引存入映射,这里肯定能找到
- if (portNameToIndexMap.contains(info.portName())) {
- // 设置comboBox_chooseCom的当前项为找到的包含"CH340"的串口
- ui->comboBox_chooseCom->setCurrentIndex(portNameToIndexMap.value(info.portName()));
- }
- // 找到后退出循环,因为我们只需要设置第一个匹配的串口
- break;
- }
- }
- }
- // IapRcvDataProc 函数用于处理接收到的IAP(In-Application Programming)数据消息
- void IapRcvDataProc(MSG_S stMsg)
- {
- // 定义一个缓冲区用于存储接收到的消息数据
- u8 MsgData[100];
- // 将接收到的消息数据复制到MsgData缓冲区中
- memcpy(MsgData, stMsg.szData, stMsg.u8Len);
- u8 cmd = MsgData[__CmdIndex]; // 获取命令码
-
- u8 i = 0; // 循环计数器
-
- // 根据接收到的命令码的第3位(MsgData[2]的高4位)来构造回复命令的第1个字节
- u8 replyCmd1 = 0xB0 | (MsgData[2] >> 4);
-
- // 定义一个发送消息的缓冲区
- u8 txMsg[20] = {0};
-
- // 根据命令码执行不同的操作
- switch(cmd)
- {
- case 0xF0: // 固件更新请求
- // 读取固件更新标志位
- SystemFlashRead(__FwUpdateFlagAddr, __FwUpdateFlagSize, &stIap.u32FwFlag);
- // 清除IAP结构体stIap的所有数据
- memset(&stIap, 0, sizeof(IAP_S));
- // 复制固件版本号到IAP结构体的flashData字段
- memcpy(&stIap.stFwVer.flashData, &MsgData[6], 3);
- // 将固件版本号写入到固件版本地址
- SystemFlashWrite(__FwVersionAddr, __FwUpdateFlagSize, &stIap.stFwVer.flashData);
- // 构造回复消息
- txMsg[0] = replyCmd1;
- txMsg[1] = cmd;
- txMsg[2] = 1; // 表示成功的标志位
- // 发送回复消息
- UartLoadTxMsg(1, txMsg, 3);
- break;
-
- case 0xF1: // 固件擦除
- // 设置固件标志位为无固件
- stIap.u32FwFlag = __NO_FW;
- // 写入固件更新标志位
- SystemFlashWrite(__FwUpdateFlagAddr, __FwUpdateFlagSize, &stIap.u32FwFlag);
- // 擦除应用程序空间
- EraseFwSpace(__APP_START_ADDR, __APP_SIZE / __FLASH_PAGE_SIZE);
- break;
-
- case 0xF2: // 固件数据接收
- // 更新帧索引
- stIap.u16FwFrameIndex++;
- // 设置接收帧的长度
- stIap.stRcvFrame.u8Length = MsgData[6];
- // 复制接收到的数据到IAP结构体的接收帧缓冲区
- memcpy(&stIap.stRcvFrame.u8Data, &MsgData[7], stIap.stRcvFrame.u8Length);
- // 逐块(每块4个字节)将数据写入到应用程序起始地址
- for(i = 0; i < stIap.stRcvFrame.u8Length; i += 4)
- {
- FlashWriteWord(__APP_START_ADDR + stIap.u32WriteAddrIndex, *(u32 *)&stIap.stRcvFrame.u8Data[i]);
- stIap.u32WriteAddrIndex += 4; // 更新写入地址
- }
- // 构造回复消息,包含帧编号和成功标志位
- txMsg[0] = replyCmd1;
- txMsg[1] = cmd;
- txMsg[2] = MsgData[4]; // 帧编号的高字节
- txMsg[3] = MsgData[5]; // 帧编号的低字节
- txMsg[4] = 1; // 成功标志位
- // 发送回复消息
- UartLoadTxMsg(1, txMsg, 5);
- break;
-
- case 0xF3: // 固件更新完成
- // 设置固件标志位为已有固件
- stIap.u32FwFlag = __HAVE_FW_REPLY;
- // 将固件标志位写入到固件更新标志地址
- SystemFlashWrite(__FwUpdateFlagAddr, __FwUpdateFlagSize, &stIap.u32FwFlag);
- // 跳转到应用程序开始执行(切换到新的固件)
- JumpToApplication();
- break;
-
- case 0xF4: // 固件版本查询
- // 从固件版本地址读取固件版本数据
- SystemFlashRead(__FwVersionAddr, __FwUpdateFlagSize, &stIap.stFwVer.flashData);
- // 如果读取到的固件版本数据不是0xFFFFFFFF(表示无固件或无效版本)
- if (stIap.stFwVer.flashData != 0xFFFFFFFF)
- {
- // 解析固件版本数据到结构体中的major、minor、patch字段
- stIap.stFwVer.major = stIap.stFwVer.flashData & 0xFF; // 低8位为major版本
- stIap.stFwVer.minor = (stIap.stFwVer.flashData >> 8) & 0xFF; // 接下来的8位为minor版本
- stIap.stFwVer.patch = (stIap.stFwVer.flashData >> 16) & 0xFF; // 再接下来的8位为patch版本
- }
- else
- {
- // 如果固件版本数据为0xFFFFFFFF,则清空固件版本结构体
- memset(&stIap.stFwVer, 0, sizeof(stIap.stFwVer));
- }
- // 构造回复消息,包含固件版本号
- txMsg[0] = replyCmd1;
- txMsg[1] = cmd;
- txMsg[2] = stIap.stFwVer.major; // 固件major版本
- txMsg[3] = stIap.stFwVer.minor; // 固件minor版本
- txMsg[4] = stIap.stFwVer.patch; // 固件patch版本
- // 发送固件版本回复消息
- UartLoadTxMsg(1, txMsg, 5);
- break;
- }
- }
- // 从系统闪存中读取固件版本信息
- SystemFlashRead(__FwVersionAddr, __FwUpdateFlagSize, &stApp.stFwVer.flashData);
-
- // 检查读取到的固件版本数据是否不是0xFFFFFFFF(通常表示无效或未设置的值)
- if (stApp.stFwVer.flashData != 0xFFFFFFFF)
- {
- // 如果不是无效值,则解析固件版本数据
- // 提取主要版本号(低8位)
- stApp.stFwVer.major = stApp.stFwVer.flashData & 0xFF;
- // 提取次要版本号(接下来的8位)
- stApp.stFwVer.minor = (stApp.stFwVer.flashData >> 8) & 0xFF;
- // 提取补丁版本号(再接下来的8位,但注意这里实际上只用了24位来表示版本)
- stApp.stFwVer.patch = (stApp.stFwVer.flashData >> 16) & 0xFF;
- }
- else
- {
- // 如果固件版本数据是无效值,则清空固件版本结构体
- memset(&stApp.stFwVer, 0, sizeof(stApp.stFwVer));
- }
-
- // 从接收到的消息数据中复制固件版本数据(MsgData是一个包含固件版本数据的数组)
- memcpy(&stRxFwVer.flashData, &MsgData[6], 3);
-
- // 解析接收到的固件版本数据
- // 提取主要版本号
- stRxFwVer.major = stRxFwVer.flashData & 0xFF;
- // 提取次要版本号
- stRxFwVer.minor = (stRxFwVer.flashData >> 8) & 0xFF;
- // 提取补丁版本号
- stRxFwVer.patch = (stRxFwVer.flashData >> 16) & 0xFF;
-
- // 调用函数比较当前固件版本(stApp.stFwVer)和接收到的固件版本(stRxFwVer)
- // 如果接收到的版本更高或等于当前版本,则返回1
- tmp = isNewVersionHigherOrEqual(&stApp.stFwVer, &stRxFwVer);
-
- // 根据比较结果决定下一步操作
- if (tmp == 1)
- {
- // 如果接收到的版本更高或等于当前版本
- // 设置固件标志位为需要固件更新
- stApp.u32FwFlag = __HAVE_FW_UPDATE;
- // 将固件标志位写入到固件更新标志地址
- SystemFlashWrite(__FwUpdateFlagAddr, __FwUpdateFlagSize, &stApp.u32FwFlag);
- // 创建一个任务来跳转到IAP模式进行固件更新(假设Task2和JumpToIap是任务相关的函数和参数)
- OS_TaskCreat(Task2, JumpToIap, 100);
- // 注意:这里可能还需要发送一个确认消息给发送方,但代码中没有体现
- }
- else
- {
- // 构造回复消息
- txMsg[0] = replyCmd1; // 回复命令标识符
- txMsg[1] = cmd; // 原始命令
- // 如果接收到的版本不是更高,则在回复消息中添加0表示无需更新
- txMsg[2] = 0;
- // 发送无需更新的确认消息
- UartLoadTxMsg(1, txMsg, 3);
- }
- void MainWindow::rcvFwReply(QByteArray *protocalData)
- {
- // 定义一个字节数组pRxData来存储接收到的数据
- uint8_t pRxData[100];
- // 定义一个QString对象来存储接收到的固件版本号信息(但在此段代码中未使用)
- QString rcvFwVer;
-
- // 将传入的QByteArray中的数据复制到pRxData数组中
- memcpy(pRxData, protocalData->data(), protocalData->size());
-
- // 清空并设置文本编辑框的换行模式
- ui->textEdit_fwRcvInfo->clear();
- ui->textEdit_fwRcvInfo->setWordWrapMode(QTextOption::WordWrap);
-
- // 根据接收到的数据的第四个字节进行不同的处理
- switch (pRxData[3])
- {
- case 0xF0:
- if(pRxData[4] == 1)
- {
- // 重置fwExceedIndex和fwUpdateState变量,并启动相关的定时器
- fwExceedIndex = 0;
- fwUpdateState = fwTransfer;
- fwNumCmd(); // 调用fwNumCmd函数(该函数在此段代码中未给出)
- fwUpdateTimer->start(1000); // 定时器每隔1000毫秒(1秒)触发一次
- fwUpdateTimeOut->start(2000); // 另一个定时器每隔2000毫秒(2秒)触发一次
- // 在文本编辑框中插入“开始升级”文本
- ui->textEdit_fwRcvInfo->insertPlainText(QString("开始升级"));
- // 这里fwExceedIndex被重复设置为了0,可能是冗余代码
- fwExceedIndex = 0;
- }
- else if (pRxData[4] == 0)
- {
- // 在文本编辑框中插入“固件已是最新版/对方拒绝升级”文本
- ui->textEdit_fwRcvInfo->insertPlainText(QString("固件已是最新版/对方拒绝升级"));
- }
- break;
-
- case 0xF2:
- // 从接收到的数据的第五个和第六个字节中解析出一个uint16_t类型的索引值
- uint16_t u16FwPackIndex;
- *(uint16_t *)&u16FwPackIndex = *(uint16_t *)&pRxData[4];
-
- // 如果解析出的索引值与当前fwPackIndex相等
- if(u16FwPackIndex == fwPackIndex)
- {
- // 如果接收到的数据的第七个字节是1
- if(pRxData[6] == 1)
- {
- // 重置fwExceedIndex变量
- fwExceedIndex = 0;
-
- // 在文本编辑框中插入“第 [索引值] 帧传输成功”的文本
- ui->textEdit_fwRcvInfo->insertPlainText(QString("第 ["));
- ui->textEdit_fwRcvInfo->insertPlainText(QString::number(fwPackIndex));
- ui->textEdit_fwRcvInfo->insertPlainText(QString("] 帧传输成功"));
-
- // 重置fwExceedIndex变量(这里再次被重复设置,可能是冗余代码)
- fwExceedIndex = 0;
-
- // 启动相关的定时器
- fwUpdateTimer->start(20); // 定时器每隔20毫秒触发一次
- fwUpdateTimeOut->start(2000); // 另一个定时器每隔2000毫秒触发一次
-
- // 更新进度条的值
- ui->progressBar_upgrade->setValue(static_cast<int>(fwPackIndex * 100.0 / fwPackNum));
- }
- else if (pRxData[6] == 0)
- {
- // 在文本编辑框中插入“[索引值] 帧传输失败”的文本
- ui->textEdit_fwRcvInfo->insertPlainText(QString("["));
- ui->textEdit_fwRcvInfo->insertPlainText(QString::number(fwPackIndex));
- ui->textEdit_fwRcvInfo->insertPlainText(QString("] 帧传输失败"));
- }
- }
- break;
-
- case 0xF3:
- if(pRxData[4] == 1)
- {
- // 更新状态为升级完成
- fwUpdateState = fwComplete;
- // 在文本编辑框中插入“对方回复升级成功”的文本
- ui->textEdit_fwRcvInfo->insertPlainText(QString("对方回复升级成功"));
- // 重置一些变量
- fwExceedIndex = 0;
- fwUpdateTimeOut->stop();
- }
- else if (pRxData[4] == 0)
- {
- // 在文本编辑框中插入“对方回复升级失败”的文本
- ui->textEdit_fwRcvInfo->insertPlainText(QString("对方回复升级失败"));
- }
- // 无论成功还是失败,都重置UI中的按钮状态并更新fwUpdateState和其他相关变量
- ui->pushButton_startFwUpdate->setEnabled(true);
- ui->pushButton_stopFwUpdate->setEnabled(false);
- fwUpdateState = fwInit;
- fwPackIndex = 0;
- fwExceedFlag = 0;
- fwExceedIndex = 0;
- break;
-
- case 0xF4:
- // 从接收到的数据的第五、六、七个字节中分别获取固件的主版本号、次版本号和补丁版本号
- quint8 data1 = static_cast
(pRxData[4]); - quint8 data2 = static_cast
(pRxData[5]); - quint8 data3 = static_cast
(pRxData[6]); -
- // 将十六进制转换为十进制
- int versionMajor = data1;
- int versionMinor = data2;
- int versionPatch = data3;
-
- // 组合成版本号字符串,并确保补丁版本号至少为两位数,不足补0
- QString versionString = QString("对方固件版本号为:%1.%2.%3").arg(versionMajor).arg(versionMinor).arg(versionPatch, 2, 10, QChar('0')).toUpper();
-
- // 在文本编辑框中插入版本号字符串
- ui->textEdit_fwRcvInfo->insertPlainText(versionString);
- break;
- }
- }