• IAP固件升级进阶(Qt上位机)


    前言

    时隔近一年,再次接触IAP固件升级,这次修改了以前的一些bug,同时新增一些实用性的功能。

    有纰漏请指出,转载请说明。

    学习交流请发邮件 1280253714@qq.com。

    上位机界面

    视频演示

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

    固件升级指令

    重要代码

    1.通过拖拽实现文件读取

    1. // MainWindow类的dragEnterEvent方法
    2. // 当鼠标拖动文件进入MainWindow的边界时,触发此方法
    3. void MainWindow::dragEnterEvent(QDragEnterEvent *event)
    4. {
    5. // 检查拖动的数据是否包含URLs(即文件路径)
    6. if(event->mimeData()->hasUrls())
    7. // 如果包含URLs,则接受拖放操作
    8. event->acceptProposedAction();
    9. else
    10. // 如果不包含URLs,则忽略拖放操作
    11. event->ignore();
    12. }
    13. // MainWindow类的dropEvent方法
    14. // 当鼠标在MainWindow内释放拖动的文件时,触发此方法
    15. void MainWindow::dropEvent(QDropEvent *event)
    16. {
    17. // 获取拖放事件中的MIME数据
    18. const QMimeData *mimeData = event->mimeData();
    19. // 检查MIME数据是否包含URLs
    20. if(mimeData->hasUrls())
    21. {
    22. // 获取URLs列表
    23. QList urlList = mimeData->urls();
    24. // 获取第一个URL的本地文件路径
    25. QString fileName = urlList.at(0).toLocalFile();
    26. // 获取textEdit_fwUpdateFile控件中的文本
    27. QString text = ui->textEdit_fwUpdateFile->toPlainText();
    28. // 检查文件名是否为空,并且textEdit_fwUpdateFile中的文本为空
    29. if((!fileName.isEmpty()) && (text.isEmpty()))
    30. {
    31. // 检查文件扩展名是否为.bin
    32. if (fileName.endsWith(".bin"))
    33. {
    34. // 创建一个QFile对象,用于读取文件
    35. QFile file(fileName);
    36. // 创建一个QFileInfo对象,用于获取文件信息
    37. QFileInfo fileInfo(fileName);
    38. // 初始化固件更新相关变量
    39. fwPackIndex = 0;
    40. // 获取文件大小
    41. fwFileLen = fileInfo.size();
    42. // 计算固件包的数量(根据fwPackLength,但fwPackLength在此代码中未定义)
    43. fwPackNum = fwFileLen/fwPackLength+1;
    44. // 尝试以只读方式打开文件
    45. if(!file.open(QIODevice::ReadOnly))
    46. // 如果文件打开失败,则返回不执行后续操作
    47. return;
    48. // 读取文件全部内容
    49. binRawData = file.readAll();
    50. // 在lineEdit_fwUpdateFile控件中显示文件名
    51. ui->lineEdit_fwUpdateFile->setText(fileName);
    52. // 在textEdit_fwUpdateFile控件中追加文件的十六进制表示
    53. ui->textEdit_fwUpdateFile->append(binRawData.toHex());
    54. // 关闭文件
    55. file.close();
    56. // 启用开始固件更新按钮
    57. ui->pushButton_startFwUpdate->setEnabled(true);
    58. // 禁用停止固件更新按钮
    59. ui->pushButton_stopFwUpdate->setEnabled(false);
    60. // 初始化固件更新状态
    61. fwUpdateState = fwInit;
    62. // 重置固件包索引
    63. fwPackIndex = 0;
    64. // 重置超出标记和索引(这些变量在代码中没有明确的定义和用途)
    65. fwExceedFlag = 0;
    66. fwExceedIndex = 0;
    67. // 重置进度条
    68. ui->progressBar_upgrade->reset();
    69. }
    70. else
    71. {
    72. // 如果文件扩展名不是.bin,则显示警告消息
    73. QMessageBox::warning(this, tr("错误"), tr("无法打开正确的文件!"));
    74. }
    75. }
    76. }
    77. }

    2.串口初始化自动识别并连接带CH340的串口

    1. void MainWindow::serialPortInit()
    2. {
    3. // 获取所有可用的串口信息,并存储在QList
    4. QList comList = QSerialPortInfo::availablePorts();
    5. // 清空comboBox_chooseCom控件中的所有项
    6. ui->comboBox_chooseCom->clear();
    7. // 创建一个QMap,用于存储串口名(portName)到comboBox中索引的映射
    8. QMapint> portNameToIndexMap;
    9. // 遍历所有可用的串口信息
    10. foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts()) {
    11. // 构建一个显示文本,由串口名和描述组成
    12. QString displayText = info.portName() + ": " + info.description();
    13. // 获取当前comboBox的项数,作为新项的索引
    14. int index = ui->comboBox_chooseCom->count();
    15. // 在comboBox_chooseCom控件中添加新的显示文本
    16. ui->comboBox_chooseCom->addItem(displayText);
    17. // 将当前串口名及其索引存入映射中
    18. portNameToIndexMap.insert(info.portName(), index);
    19. // 检查串口的描述或制造商信息是否包含"CH340"(不区分大小写)
    20. if (info.description().contains("CH340", Qt::CaseInsensitive) ||
    21. info.manufacturer().contains("CH340", Qt::CaseInsensitive)) {
    22. // 使用映射来查找该串口名对应的索引
    23. // 由于之前已经将串口名和索引存入映射,这里肯定能找到
    24. if (portNameToIndexMap.contains(info.portName())) {
    25. // 设置comboBox_chooseCom的当前项为找到的包含"CH340"的串口
    26. ui->comboBox_chooseCom->setCurrentIndex(portNameToIndexMap.value(info.portName()));
    27. }
    28. // 找到后退出循环,因为我们只需要设置第一个匹配的串口
    29. break;
    30. }
    31. }
    32. }

    3.IAP相关代码,跟上位机通信的逻辑部分

    1. // IapRcvDataProc 函数用于处理接收到的IAP(In-Application Programming)数据消息
    2. void IapRcvDataProc(MSG_S stMsg)
    3. {
    4. // 定义一个缓冲区用于存储接收到的消息数据
    5. u8 MsgData[100];
    6. // 将接收到的消息数据复制到MsgData缓冲区中
    7. memcpy(MsgData, stMsg.szData, stMsg.u8Len);
    8. u8 cmd = MsgData[__CmdIndex]; // 获取命令码
    9. u8 i = 0; // 循环计数器
    10. // 根据接收到的命令码的第3位(MsgData[2]的高4位)来构造回复命令的第1个字节
    11. u8 replyCmd1 = 0xB0 | (MsgData[2] >> 4);
    12. // 定义一个发送消息的缓冲区
    13. u8 txMsg[20] = {0};
    14. // 根据命令码执行不同的操作
    15. switch(cmd)
    16. {
    17. case 0xF0: // 固件更新请求
    18. // 读取固件更新标志位
    19. SystemFlashRead(__FwUpdateFlagAddr, __FwUpdateFlagSize, &stIap.u32FwFlag);
    20. // 清除IAP结构体stIap的所有数据
    21. memset(&stIap, 0, sizeof(IAP_S));
    22. // 复制固件版本号到IAP结构体的flashData字段
    23. memcpy(&stIap.stFwVer.flashData, &MsgData[6], 3);
    24. // 将固件版本号写入到固件版本地址
    25. SystemFlashWrite(__FwVersionAddr, __FwUpdateFlagSize, &stIap.stFwVer.flashData);
    26. // 构造回复消息
    27. txMsg[0] = replyCmd1;
    28. txMsg[1] = cmd;
    29. txMsg[2] = 1; // 表示成功的标志位
    30. // 发送回复消息
    31. UartLoadTxMsg(1, txMsg, 3);
    32. break;
    33. case 0xF1: // 固件擦除
    34. // 设置固件标志位为无固件
    35. stIap.u32FwFlag = __NO_FW;
    36. // 写入固件更新标志位
    37. SystemFlashWrite(__FwUpdateFlagAddr, __FwUpdateFlagSize, &stIap.u32FwFlag);
    38. // 擦除应用程序空间
    39. EraseFwSpace(__APP_START_ADDR, __APP_SIZE / __FLASH_PAGE_SIZE);
    40. break;
    41. case 0xF2: // 固件数据接收
    42. // 更新帧索引
    43. stIap.u16FwFrameIndex++;
    44. // 设置接收帧的长度
    45. stIap.stRcvFrame.u8Length = MsgData[6];
    46. // 复制接收到的数据到IAP结构体的接收帧缓冲区
    47. memcpy(&stIap.stRcvFrame.u8Data, &MsgData[7], stIap.stRcvFrame.u8Length);
    48. // 逐块(每块4个字节)将数据写入到应用程序起始地址
    49. for(i = 0; i < stIap.stRcvFrame.u8Length; i += 4)
    50. {
    51. FlashWriteWord(__APP_START_ADDR + stIap.u32WriteAddrIndex, *(u32 *)&stIap.stRcvFrame.u8Data[i]);
    52. stIap.u32WriteAddrIndex += 4; // 更新写入地址
    53. }
    54. // 构造回复消息,包含帧编号和成功标志位
    55. txMsg[0] = replyCmd1;
    56. txMsg[1] = cmd;
    57. txMsg[2] = MsgData[4]; // 帧编号的高字节
    58. txMsg[3] = MsgData[5]; // 帧编号的低字节
    59. txMsg[4] = 1; // 成功标志位
    60. // 发送回复消息
    61. UartLoadTxMsg(1, txMsg, 5);
    62. break;
    63. case 0xF3: // 固件更新完成
    64. // 设置固件标志位为已有固件
    65. stIap.u32FwFlag = __HAVE_FW_REPLY;
    66. // 将固件标志位写入到固件更新标志地址
    67. SystemFlashWrite(__FwUpdateFlagAddr, __FwUpdateFlagSize, &stIap.u32FwFlag);
    68. // 跳转到应用程序开始执行(切换到新的固件)
    69. JumpToApplication();
    70. break;
    71. case 0xF4: // 固件版本查询
    72. // 从固件版本地址读取固件版本数据
    73. SystemFlashRead(__FwVersionAddr, __FwUpdateFlagSize, &stIap.stFwVer.flashData);
    74. // 如果读取到的固件版本数据不是0xFFFFFFFF(表示无固件或无效版本)
    75. if (stIap.stFwVer.flashData != 0xFFFFFFFF)
    76. {
    77. // 解析固件版本数据到结构体中的major、minor、patch字段
    78. stIap.stFwVer.major = stIap.stFwVer.flashData & 0xFF; // 低8位为major版本
    79. stIap.stFwVer.minor = (stIap.stFwVer.flashData >> 8) & 0xFF; // 接下来的8位为minor版本
    80. stIap.stFwVer.patch = (stIap.stFwVer.flashData >> 16) & 0xFF; // 再接下来的8位为patch版本
    81. }
    82. else
    83. {
    84. // 如果固件版本数据为0xFFFFFFFF,则清空固件版本结构体
    85. memset(&stIap.stFwVer, 0, sizeof(stIap.stFwVer));
    86. }
    87. // 构造回复消息,包含固件版本号
    88. txMsg[0] = replyCmd1;
    89. txMsg[1] = cmd;
    90. txMsg[2] = stIap.stFwVer.major; // 固件major版本
    91. txMsg[3] = stIap.stFwVer.minor; // 固件minor版本
    92. txMsg[4] = stIap.stFwVer.patch; // 固件patch版本
    93. // 发送固件版本回复消息
    94. UartLoadTxMsg(1, txMsg, 5);
    95. break;
    96. }
    97. }

     4.APP检测将要更新的固件版本是否高于当前固件版本

    1. // 从系统闪存中读取固件版本信息
    2. SystemFlashRead(__FwVersionAddr, __FwUpdateFlagSize, &stApp.stFwVer.flashData);
    3. // 检查读取到的固件版本数据是否不是0xFFFFFFFF(通常表示无效或未设置的值)
    4. if (stApp.stFwVer.flashData != 0xFFFFFFFF)
    5. {
    6. // 如果不是无效值,则解析固件版本数据
    7. // 提取主要版本号(低8位)
    8. stApp.stFwVer.major = stApp.stFwVer.flashData & 0xFF;
    9. // 提取次要版本号(接下来的8位)
    10. stApp.stFwVer.minor = (stApp.stFwVer.flashData >> 8) & 0xFF;
    11. // 提取补丁版本号(再接下来的8位,但注意这里实际上只用了24位来表示版本)
    12. stApp.stFwVer.patch = (stApp.stFwVer.flashData >> 16) & 0xFF;
    13. }
    14. else
    15. {
    16. // 如果固件版本数据是无效值,则清空固件版本结构体
    17. memset(&stApp.stFwVer, 0, sizeof(stApp.stFwVer));
    18. }
    19. // 从接收到的消息数据中复制固件版本数据(MsgData是一个包含固件版本数据的数组)
    20. memcpy(&stRxFwVer.flashData, &MsgData[6], 3);
    21. // 解析接收到的固件版本数据
    22. // 提取主要版本号
    23. stRxFwVer.major = stRxFwVer.flashData & 0xFF;
    24. // 提取次要版本号
    25. stRxFwVer.minor = (stRxFwVer.flashData >> 8) & 0xFF;
    26. // 提取补丁版本号
    27. stRxFwVer.patch = (stRxFwVer.flashData >> 16) & 0xFF;
    28. // 调用函数比较当前固件版本(stApp.stFwVer)和接收到的固件版本(stRxFwVer)
    29. // 如果接收到的版本更高或等于当前版本,则返回1
    30. tmp = isNewVersionHigherOrEqual(&stApp.stFwVer, &stRxFwVer);
    31. // 根据比较结果决定下一步操作
    32. if (tmp == 1)
    33. {
    34. // 如果接收到的版本更高或等于当前版本
    35. // 设置固件标志位为需要固件更新
    36. stApp.u32FwFlag = __HAVE_FW_UPDATE;
    37. // 将固件标志位写入到固件更新标志地址
    38. SystemFlashWrite(__FwUpdateFlagAddr, __FwUpdateFlagSize, &stApp.u32FwFlag);
    39. // 创建一个任务来跳转到IAP模式进行固件更新(假设Task2和JumpToIap是任务相关的函数和参数)
    40. OS_TaskCreat(Task2, JumpToIap, 100);
    41. // 注意:这里可能还需要发送一个确认消息给发送方,但代码中没有体现
    42. }
    43. else
    44. {
    45. // 构造回复消息
    46. txMsg[0] = replyCmd1; // 回复命令标识符
    47. txMsg[1] = cmd; // 原始命令
    48. // 如果接收到的版本不是更高,则在回复消息中添加0表示无需更新
    49. txMsg[2] = 0;
    50. // 发送无需更新的确认消息
    51. UartLoadTxMsg(1, txMsg, 3);
    52. }

     5.上位机对收到命令进行处理

    1. void MainWindow::rcvFwReply(QByteArray *protocalData)
    2. {
    3. // 定义一个字节数组pRxData来存储接收到的数据
    4. uint8_t pRxData[100];
    5. // 定义一个QString对象来存储接收到的固件版本号信息(但在此段代码中未使用)
    6. QString rcvFwVer;
    7. // 将传入的QByteArray中的数据复制到pRxData数组中
    8. memcpy(pRxData, protocalData->data(), protocalData->size());
    9. // 清空并设置文本编辑框的换行模式
    10. ui->textEdit_fwRcvInfo->clear();
    11. ui->textEdit_fwRcvInfo->setWordWrapMode(QTextOption::WordWrap);
    12. // 根据接收到的数据的第四个字节进行不同的处理
    13. switch (pRxData[3])
    14. {
    15. case 0xF0:
    16. if(pRxData[4] == 1)
    17. {
    18. // 重置fwExceedIndex和fwUpdateState变量,并启动相关的定时器
    19. fwExceedIndex = 0;
    20. fwUpdateState = fwTransfer;
    21. fwNumCmd(); // 调用fwNumCmd函数(该函数在此段代码中未给出)
    22. fwUpdateTimer->start(1000); // 定时器每隔1000毫秒(1秒)触发一次
    23. fwUpdateTimeOut->start(2000); // 另一个定时器每隔2000毫秒(2秒)触发一次
    24. // 在文本编辑框中插入“开始升级”文本
    25. ui->textEdit_fwRcvInfo->insertPlainText(QString("开始升级"));
    26. // 这里fwExceedIndex被重复设置为了0,可能是冗余代码
    27. fwExceedIndex = 0;
    28. }
    29. else if (pRxData[4] == 0)
    30. {
    31. // 在文本编辑框中插入“固件已是最新版/对方拒绝升级”文本
    32. ui->textEdit_fwRcvInfo->insertPlainText(QString("固件已是最新版/对方拒绝升级"));
    33. }
    34. break;
    35. case 0xF2:
    36. // 从接收到的数据的第五个和第六个字节中解析出一个uint16_t类型的索引值
    37. uint16_t u16FwPackIndex;
    38. *(uint16_t *)&u16FwPackIndex = *(uint16_t *)&pRxData[4];
    39. // 如果解析出的索引值与当前fwPackIndex相等
    40. if(u16FwPackIndex == fwPackIndex)
    41. {
    42. // 如果接收到的数据的第七个字节是1
    43. if(pRxData[6] == 1)
    44. {
    45. // 重置fwExceedIndex变量
    46. fwExceedIndex = 0;
    47. // 在文本编辑框中插入“第 [索引值] 帧传输成功”的文本
    48. ui->textEdit_fwRcvInfo->insertPlainText(QString("第 ["));
    49. ui->textEdit_fwRcvInfo->insertPlainText(QString::number(fwPackIndex));
    50. ui->textEdit_fwRcvInfo->insertPlainText(QString("] 帧传输成功"));
    51. // 重置fwExceedIndex变量(这里再次被重复设置,可能是冗余代码)
    52. fwExceedIndex = 0;
    53. // 启动相关的定时器
    54. fwUpdateTimer->start(20); // 定时器每隔20毫秒触发一次
    55. fwUpdateTimeOut->start(2000); // 另一个定时器每隔2000毫秒触发一次
    56. // 更新进度条的值
    57. ui->progressBar_upgrade->setValue(static_cast<int>(fwPackIndex * 100.0 / fwPackNum));
    58. }
    59. else if (pRxData[6] == 0)
    60. {
    61. // 在文本编辑框中插入“[索引值] 帧传输失败”的文本
    62. ui->textEdit_fwRcvInfo->insertPlainText(QString("["));
    63. ui->textEdit_fwRcvInfo->insertPlainText(QString::number(fwPackIndex));
    64. ui->textEdit_fwRcvInfo->insertPlainText(QString("] 帧传输失败"));
    65. }
    66. }
    67. break;
    68. case 0xF3:
    69. if(pRxData[4] == 1)
    70. {
    71. // 更新状态为升级完成
    72. fwUpdateState = fwComplete;
    73. // 在文本编辑框中插入“对方回复升级成功”的文本
    74. ui->textEdit_fwRcvInfo->insertPlainText(QString("对方回复升级成功"));
    75. // 重置一些变量
    76. fwExceedIndex = 0;
    77. fwUpdateTimeOut->stop();
    78. }
    79. else if (pRxData[4] == 0)
    80. {
    81. // 在文本编辑框中插入“对方回复升级失败”的文本
    82. ui->textEdit_fwRcvInfo->insertPlainText(QString("对方回复升级失败"));
    83. }
    84. // 无论成功还是失败,都重置UI中的按钮状态并更新fwUpdateState和其他相关变量
    85. ui->pushButton_startFwUpdate->setEnabled(true);
    86. ui->pushButton_stopFwUpdate->setEnabled(false);
    87. fwUpdateState = fwInit;
    88. fwPackIndex = 0;
    89. fwExceedFlag = 0;
    90. fwExceedIndex = 0;
    91. break;
    92. case 0xF4:
    93. // 从接收到的数据的第五、六、七个字节中分别获取固件的主版本号、次版本号和补丁版本号
    94. quint8 data1 = static_cast(pRxData[4]);
    95. quint8 data2 = static_cast(pRxData[5]);
    96. quint8 data3 = static_cast(pRxData[6]);
    97. // 将十六进制转换为十进制
    98. int versionMajor = data1;
    99. int versionMinor = data2;
    100. int versionPatch = data3;
    101. // 组合成版本号字符串,并确保补丁版本号至少为两位数,不足补0
    102. QString versionString = QString("对方固件版本号为:%1.%2.%3").arg(versionMajor).arg(versionMinor).arg(versionPatch, 2, 10, QChar('0')).toUpper();
    103. // 在文本编辑框中插入版本号字符串
    104. ui->textEdit_fwRcvInfo->insertPlainText(versionString);
    105. break;
    106. }
    107. }

  • 相关阅读:
    谷粒商城个人笔记一
    mongodb 日志详情
    初步了解Vite
    【校招VIP】前端项目开发之正则表达
    开源相机管理库Aravis例程学习(二)——连续采集multiple-acquisition-main-thread
    Apipost模拟HTTP客户端
    设计模式之观察者模式
    全国数学竞赛D题思路分享
    R语言提取站点的nc文件时间序列数据
    QT DAY2
  • 原文地址:https://blog.csdn.net/weixin_45817947/article/details/139751495