QTDemo:串口调试工具

项目简介

本项目通过QT框架设计一款可以在Windows、Linux等平台的跨平台串口助手,串口功能能够满足基本的调试需求。

本项目采用的版本为:QT5.14 + visual studio 2022 进行开发。

项目源码:https://github.com/say-Hai/MyCOMDemo

项目页面:

image-20241220120411588

一、创建开发环境

打开vs新建工程,选择创建Qt Widgets Application项目,选择保存路径后,配置QT的SerialPort模块。

image-20241217214638298

二、配置ui界面

打开工程的ui文件,设置本项目的ui页面(可直接从本项目的ui文件中copy到自己的项目中;但是注意:需要暂时把comboBoxNo_2降级成普通QComboBox

image-20241218103406284

image-20241218102410011

三、编写串口扫描代码

通过QSerialPortInfo::availablePorts生成可用串口列表,(目前暂定在MyCOM.h的构造函数中编写串口列表函数)

MyCOM::MyCOM(QWidget* parent): QMainWindow(parent)
{ui.setupUi(this);//创建串口列表QStringList comPort;foreach(const QSerialPortInfo & info, QSerialPortInfo::availablePorts()){comPort << info.portName();}ui.comboBoxNo_2->addItems(comPort);
}

image-20241218103454075

四、“打开串口”按钮设计

vs中无法使用Qt Creator的“转到槽”功能,因此需要开发者自己绑定槽函数;具体操作步骤为:https://www.cnblogs.com/ybqjymy/p/17999513

注:解决vs + qt 导致的乱码问题:出现中文的文件首行加上#pragma execution_character_set("utf-8")

当我们绑定好槽函数on_pushButtonOpen_clicked(),接下来就是实现串口打开逻辑:以下为具体代码

//Map定义代码查看源文件
void MyCOM::on_pushButtonOpen_clicked()
{QSerialPort::BaudRate CombaudRate;QSerialPort::DataBits ComdataBits;QSerialPort::StopBits ComstopBits;QSerialPort::Parity   ComParity;QString selectedBaudRate = ui.comboBoxComBaud_2->currentText();std::cout << selectedBaudRate.toStdString() << "\n";if (baudRateMap.contains(selectedBaudRate)) {CombaudRate = baudRateMap[selectedBaudRate];}else {// 如果用户选择了一个未知的波特率,可以设置默认值或提示错误CombaudRate = QSerialPort::Baud9600; // 默认值qWarning("Invalid baud rate selected. Defaulting to 9600.");}
//具体代码查看源文件// 根据用户选择设置数据位// 根据用户选择设置停止位// 根据用户选择设置校验方式//初始化串口MyCom.setBaudRate(CombaudRate);MyCom.setDataBits(ComdataBits);MyCom.setStopBits(ComstopBits);MyCom.setParity(ComParity);MyCom.setPortName(spTxt);//打开串口if (ui.pushButtonOpen_2->text() == "打开串口"){bool ComFlag;ComFlag = MyCom.open(QIODevice::ReadWrite);if (ComFlag == true)//串口打开成功{//串口下拉框设置为不可选ui.comboBoxCheck_2->setEnabled(false);//具体代码查看源文件//使能相应按钮等ui.pushButtonSend_2->setEnabled(true);//具体代码查看源文件ui.pushButtonOpen_2->setText(" 关闭串口 ");}else{QMessageBox::critical(this, "错误提示", "串口打开失败,该端口可能被占用或不存在!rnLinux系统可能为当前用户无串口访问权限!");}}else{MyCom.close();ui.pushButtonOpen_2->setText(" 打开串口 ");//具体代码查看源文件//使相应的按钮不可用ui.pushButtonSend_2->setEnabled(false);具体代码查看源文件}
}

五、串口数据发送与接收

通过信号槽机制,在发送区发送数据,通过&QIODevice::readyRead信号来通知接收区函数&MyCOM::MyComRevSlot打印串口发送的数据

代码逻辑:

  • 信号槽逻辑:当串口有数据可以读取时,自动响应MyComRevSlot函数。

    connect(&MyCom, &QIODevice::readyRead, this, &MyCOM::MyComRevSlot);
    
  • 发送区代码逻辑:通过第四步中的“转到槽”机制,在发送按钮上绑定槽函数on_pushButtonSend_clicked(),再槽函数中接收发送区字符并通过MyCom.write(comSendData)发送到串口。

    • 其中16进制发送需要将字符串格式化成16进制 QByteArray::fromHex(SendTemp.toUtf8()).data();
    //精简版,少了一些单选框的逻辑判断
    void MyCOM::on_pushButtonSend_clicked()
    {QByteArray comSendData;QString SendTemp;int temp;//读取发送窗口数据SendTemp = ui.TextSend_2->toPlainText();//判断发送格式,并格式化数据if (ui.checkBoxSendHex_2->checkState() != false)//16进制发送{comSendData = QByteArray::fromHex(SendTemp.toUtf8()).data();//获取字符串}temp = MyCom.write(comSendData);
    }
    
  • 接收区代码逻辑:通过信号槽机制来调用MyComRevSlot函数,利用MyCom.readAll()读取串口的数据,最后显示到文本框内。

    //精简版
    void MyCOM::MyComRevSlot()
    {QByteArray MyComRevBUff;//接收数据缓存QString StrTemp, StrTimeDate, StrTemp1;//读取串口接收到的数据,并格式化数据MyComRevBUff = MyCom.readAll();StrTemp = QString::fromLocal8Bit(MyComRevBUff);curDateTime = QDateTime::currentDateTime();StrTimeDate = curDateTime.toString("[yyyy-MM-dd hh:mm:ss.zzz]");StrTemp = MyComRevBUff.toHex().toUpper();//转换为16进制数,并大写for (int i = 0; i < StrTemp.length(); i += 2)//整理字符串,即添加空格{StrTemp1 += StrTemp.mid(i, 2);StrTemp1 += " ";}//添加时间头StrTemp1.prepend(StrTimeDate);StrTemp1.append("\r\n");//后面添加换行ui.TextRev_2->insertPlainText(StrTemp1);//显示数据ui.TextRev_2->moveCursor(QTextCursor::End);//光标移动到文本末尾
    }
    

image-20241218164558507

六、周期循环发送指令

通过定时器,实现周期性指令发送功能

  • 创建定时器 QTimer* PriecSendTimer;

  • 在构造函数中注册定时器超时connect函数,调用on_pushButtonSend_clicked()

    	connect(PriecSendTimer, &QTimer::timeout, this, [=]() {on_pushButtonSend_clicked(); });
    
  • 通过信号槽机制,绑定选择框状态变化信号处理函数

    image-20241219135218164

  • 编写选择框变化处理函数

    void MyCOM::on_checkBoxPeriodicSend_stateChanged(int arg1)
    {if (arg1 == false){PriecSendTimer->stop();ui.lineEditTime->setEnabled(true);}else{PriecSendTimer->start(ui.lineEditTime->text().toInt());ui.lineEditTime->setEnabled(false);}
    }
    

image-20241219134212145

七、接收流量统计及状态栏设计

通过设计状态栏来实时展示QLabel的相关数据

  • 自定义变量

    	//添加自定义变量long ComSendSum, ComRevSum;//发送和接收流量统计变量QLabel* qlbSendSum, * qlbRevSum;//发送接收流量label对象QLabel* myLink, * MySource;
    
  • 变量绑定状态栏

    //创建底部状态栏及其相关部件
    QStatusBar* STABar = statusBar();qlbSendSum = new QLabel(this);
    qlbRevSum = new QLabel(this);
    myLink = new QLabel(this);
    MySource = new QLabel(this);
    myLink->setMinimumSize(90, 20);// 设置标签最小大小
    MySource->setMinimumSize(90, 20);
    qlbSendSum->setMinimumSize(100, 20);
    qlbRevSum->setMinimumSize(100, 20);
    ComSendSum = 0;
    ComRevSum = 0;setNumOnLabel(qlbSendSum, "Tx: ", ComSendSum);
    setNumOnLabel(qlbRevSum, "Rx: ", ComRevSum);STABar->addPermanentWidget(qlbSendSum);// 从右往左依次添加
    STABar->addPermanentWidget(qlbRevSum);
    STABar->addWidget(myLink);// 从左往右依次添加
    STABar->addWidget(MySource);myLink->setOpenExternalLinks(true);//状态栏显示官网、源码链接
    myLink->setText("<style> a {text-decoration: none} </style> <a href=\"http://8.134.156.7/\">--个人博客--");
    MySource->setOpenExternalLinks(true);
    MySource->setText("<style> a {text-decoration: none} </style> <a href=\"https://github.com/say-Hai/MyCOMDemo\">--源代码--");
    
  • 自定义函数来更改自定义变量

    void MyCOM::setNumOnLabel(QLabel* lbl, QString strS, long num)
    {QString strN = QString("%1").arg(num);QString str = strS + strN;lbl->setText(str);
    }
    
  • 在发送/接收函数中调用自定义函数

    //发送
    temp = MyCom.write(comSendData);
    ComSendSum++;
    setNumOnLabel(qlbSendSum, "Tx: ", ComSendSum);//接收
    MyComRevBUff = MyCom.readAll();
    StrTemp = QString::fromLocal8Bit(MyComRevBUff);
    ComRevSum++;
    setNumOnLabel(qlbRevSum, "Rx: ", ComRevSum);
    

八、数据区清空功能

void MyCOM::on_pushButtonClearRev_clicked()
{ui.TextRev_2->clear();ComSendSum = 0;ComRevSum = 0;setNumOnLabel(qlbSendSum, "Tx: ", ComSendSum);setNumOnLabel(qlbRevSum, "Rx: ", ComRevSum);
}void MyCOM::on_pushButtonClearSend_clicked()
{ui.TextSend_2->clear();ComSendSum = 0;ComRevSum = 0;setNumOnLabel(qlbSendSum, "Tx: ", ComSendSum);setNumOnLabel(qlbRevSum, "Rx: ", ComRevSum);
}

九、文件保存与读取功能

通过文件的读取快速实现对串口发送数据,通过写入文件的方式保存串口的输出。

  • 读取文件:通过QFile aFile(aFileName);QByteArray text = aFile.readAll();来获取文本数据,并写入到文本框中。

    //首先创建on_pushButtonRdFile_clicked信号槽机制打开文件夹选择文件路径
    void MyCOM::on_pushButtonRdFile_clicked()
    {QString curPath = QDir::currentPath();QString dlgTitle = "打开一个文件"; //对话框标题QString filter = "文本文件(*.txt);;所有文件(*.*)"; //文件过滤器QString aFileName = QFileDialog::getOpenFileName(this, dlgTitle, curPath, filter);if (aFileName.isEmpty())return;openTextByIODevice(aFileName);
    }
    //通过openTextByIODevice来读取文件
    bool MyCOM::openTextByIODevice(const QString& aFileName)
    {QFile aFile(aFileName);if (!aFile.exists()) //文件不存在return false;if (!aFile.open(QIODevice::ReadOnly | QIODevice::Text))return false;QByteArray text = aFile.readAll();QString strText = byteArrayToUnicode(text);//编码格式转换,防止GBK中文乱码ui.TextSend_2->setPlainText(strText);aFile.close();return  true;
    }
    //其中防止编码格式问题,通过byteArrayToUnicode进行编码格式转换
    QString MyCOM::byteArrayToUnicode(const QByteArray& array)
    {QTextCodec::ConverterState state;// 先尝试使用utf-8的方式把QByteArray转换成QStringQString text = QTextCodec::codecForName("UTF-8")->toUnicode(array.constData(), array.size(), &state);// 如果转换时无效字符数量大于0,说明编码格式不对if (state.invalidChars > 0){// 再尝试使用GBK的方式进行转换,一般就能转换正确(当然也可能是其它格式,但比较少见了)text = QTextCodec::codecForName("GBK")->toUnicode(array);}return text;
    }
    
  • 写入文件:选择文件路径->调用aFile.write(strBytes, strBytes.length()); 写入文件

    void MyCOM::on_pushButtonSaveRev_clicked()
    {QString curFile = QDir::currentPath();QString dlgTitle = " 另存为一个文件 "; //对话框标题QString filter = " 文本文件(*.txt);;所有文件(*.*);;h文件(*.h);;c++文件(*.cpp) "; //文件过滤器QString aFileName = QFileDialog::getSaveFileName(this, dlgTitle, curFile, filter);if (aFileName.isEmpty())return;saveTextByIODevice(aFileName);
    }
    bool MyCOM::saveTextByIODevice(const QString& aFileName) {QFile aFile(aFileName);if (!aFile.open(QIODevice::WriteOnly | QIODevice::Text))return false;QString str = ui.TextRev_2->toPlainText();//整个内容作为字符串QByteArray  strBytes = str.toUtf8();//转换为字节数组aFile.write(strBytes, strBytes.length());  //写入文件aFile.close();return true;
    }
    

十、多行发送功能

通过信号槽机制和定时器功能,实现对多行数据选择的循环发送

具体逻辑:根据选择框的状态确定定时器状态->通过定时器超时函数唤醒发送事件->在发送事件中确定此次需要发送的行数据->调用对应发送按钮函数

  • 通过选择框的状态变化来打开/关闭定时器发送

    void MyCOM::on_checkBoxMuti_stateChanged(int arg)
    {if (!arg){PriecSendTimer->stop();//关闭定时器ui.lineEditTime->setEnabled(true);//使能对话框编辑}else{LastSend = 0;//从第一行开始发送ui.checkBoxPeriodicSend->setChecked(false);PriecSendTimer->start(ui.lineEditTime->text().toInt());ui.lineEditTime->setEnabled(false);//关闭对话框编辑}
    }
    
  • 重构定时器超时响应函数,适配多行重复发送功能

    connect(PriecSendTimer, &QTimer::timeout, this, [=]() {Pre_on_pushButtonSend_clicked(); });void MyCOM::Pre_on_pushButtonSend_clicked()
    {if (ui.checkBoxPeriodicMutiSend_2->isChecked() == true){while (LastSend < 10){if (checkBoxes[LastSend]->isChecked()){//发送对应行的数据on_pushButtonMuti_clicked(++LastSend);break;}LastSend++;}if (LastSend == 10){LastSend = 0;}}else{//普通发送on_pushButtonSend_clicked();}
    }
    
  • 通过行索引触发对应的点击事件

    void MyCOM::on_pushButtonMuti_clicked(int lineEditIndex)
    {QString Strtemp;switch (lineEditIndex) {case 1:Strtemp = ui.lineEditMuti1_2->text();break;case 2:Strtemp = ui.lineEditMuti2_2->text();break;//...后面对应的操作default:return;  // 默认情况下不做任何操作}ui.TextSend_2->clear();ui.TextSend_2->insertPlainText(Strtemp);ui.TextSend_2->moveCursor(QTextCursor::End);MyCOM::on_pushButtonSend_clicked();
    }
    

    十一:自动刷新串口下拉框

    实现方法:新建一个类继承QComboBox类,重写鼠标点击事件使其调用扫描端口函数

  • 新建mycombobox类,继承QComBox

    #include <QComboBox>
    #include <QMouseEvent>
    #include <QSerialPort>
    #include <QSerialPortInfo>class mycombobox : public QComboBox
    {Q_OBJECT
    public:explicit mycombobox(QWidget* parent = nullptr);void mousePressEvent(QMouseEvent* event) override;
    signals:
    private:void scanActivatePort();
    };
  • 重写扫描函数和鼠标点击函数

    mycombobox::mycombobox(QWidget* parent) : QComboBox(parent)
    {scanActivatePort();
    }void mycombobox::mousePressEvent(QMouseEvent* event)
    {if (event->button() == Qt::LeftButton){scanActivatePort();showPopup();}
    }void mycombobox::scanActivatePort()
    {clear();//创建串口列表QStringList comPort;foreach(const QSerialPortInfo & info, QSerialPortInfo::availablePorts()){QString serialPortInfo = info.portName() + ": " + info.description();// 串口设备信息,芯片/驱动名称comPort << serialPortInfo;}this->addItems(comPort);
    }
    
  • 最后将comboBoxNo_2组件提升为mycombobox

image-20241220115619125

到此整个软件设计完毕

END:信号槽绑定图

image-20241220120509952

参考文献:

[1] https://rymcu.com/portfolio/40

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/64739.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于SpringBoot和OAuth2,实现通过Github授权登录应用

基于SpringBoot和OAuth2&#xff0c;实现通过Github授权登录应用 文章目录 基于SpringBoot和OAuth2&#xff0c;实现通过Github授权登录应用0. 引言1. 创建Github应用2. 创建SpringBoot测试项目2.1 初始化项目2.2 设置配置文件信息2.3 创建Controller层2.4 创建Html页面 3. 启动…

CMS漏洞靶场攻略

DeDeCMS 环境搭建 傻瓜式安装 漏洞一&#xff1a;通过文件管理器上传WebShel 步骤⼀:访问目标靶场其思路为 dedecms 后台可以直接上传任意文件&#xff0c;可以通过⽂件管理器上传php文件获取webshell 登陆网站后台 步骤二&#xff1a;登陆到后台点击 【核心】 --》 【文件式…

0xc0000020错误代码怎么处理,Windows11、10坏图像错误0xc0000020的修复办法

“0xc0000020”是一种 Windows 应用程序错误代码&#xff0c;通常表明某些文件缺失或损坏。这可能是由于系统文件损坏、应用程序安装或卸载问题、恶意软件感染、有问题的 Windows 更新等原因导致的。 比如&#xff0c;当运行软件时&#xff0c;可能会出现类似“C:\xx\xxx.dll …

LabVIEW 中 NI Vision 模块的IMAQ Create VI

IMAQ Create VI 是 LabVIEW 中 NI Vision 模块&#xff08;NI Vision Development Module&#xff09;的一个常用 VI&#xff0c;用于创建一个图像变量。该图像变量可以存储和操作图像数据&#xff0c;是图像处理任务的基础。 ​ 通过以上操作&#xff0c;IMAQ Create VI 是构建…

HTML5 标签输入框(Tag Input)详解

HTML5 标签输入框&#xff08;Tag Input&#xff09;详解 标签输入框&#xff08;Tag Input&#xff09;是一种用户界面元素&#xff0c;允许用户输入多个标签或关键词&#xff0c;通常用于表单、搜索框或内容分类等场景。以下是实现标签输入框的详细讲解。 1. 任务概述 标…

使用位操作符实现加减乘除!

欢迎拜访&#xff1a;雾里看山-CSDN博客 本篇主题&#xff1a;使用位操作符实现加减乘除 发布时间&#xff1a;2025.1.1 隶属专栏&#xff1a;C语言 目录 位操作实现加法运算&#xff08;&#xff09;原理代码示例 位操作实现减法运算&#xff08;-&#xff09;原理代码示例 位…

[Spring] Spring AOP

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏: &#x1f9ca; Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 &#x1f355; Collection与…

Java-数据结构-时间和空间复杂度

一、什么是时间和空间复杂度&#xff1f; &#x1f4da; 那么在了解时间复杂度和空间复杂度之前&#xff0c;我们先要知道为何有这两者的概念&#xff1a; 首先我们要先了解"算法"&#xff0c;在之前我们学习过关于"一维前缀和与差分"&#xff0c;"…

Cesium 实战 27 - 三维视频融合(视频投影)

Cesium 实战 27 - 三维视频融合(视频投影) 核心代码完整代码在线示例在 Cesium 中有几种展示视频的方式,比如墙体使用视频材质,还有地面多边形使用视频材质,都可以实现视频功能。 但是随着摄像头和无人机的流行,需要视频和场景深度融合,简单的实现方式则不能满足需求。…

U盘格式化工具合集:6个免费的U盘格式化工具

在日常使用中&#xff0c;U盘可能会因为文件系统不兼容、数据损坏或使用需求发生改变而需要进行格式化。一个合适的格式化工具不仅可以清理存储空间&#xff0c;还能解决部分存储问题。本文为大家精选了6款免费的U盘格式化工具&#xff0c;并详细介绍它们的功能、使用方法、优缺…

如何使用AI工具cursor(内置ChatGPT 4o+claude-3.5)

⚠️温馨提示&#xff1a; 禁止商业用途&#xff0c;请支持正版&#xff0c;充值使用&#xff0c;尊重知识产权&#xff01; 免责声明&#xff1a; 1、本教程仅用于学习和研究使用&#xff0c;不得用于商业或非法行为。 2、请遵守Cursor的服务条款以及相关法律法规。 3、本…

Spring Boot的开发工具(DevTools)模块中的热更新特性导致的问题

问题&#xff1a; java.lang.ClassCastException: class cn.best.scholarflow.framework.system.domain.entity.SysUser cannot be cast to class cn.best.scholarflow.framework.system.domain.entity.SysUser (cn.best.scholarflow.framework.system.domain.…

异常与中断(上)

文章目录 一、异常与中断的概念引入与处理流程1.1 生活中的中断1.2 母亲如何处理中断1.3 ARM系统中异常与中断处理流程 二、ARM架构中异常与中断的处理2.1 处理流程2.2 cortex M3/M42.2.1 M3/M4的向量表2.2.2 M3/M4的异常/中断处理流程 2.3 cortex A72.3.1 A7的向量表2.3.2 A7的…

Zabbix 监控平台 添加监控目标主机

Zabbix监控平台是一个企业级开源解决方案&#xff0c;用于分布式系统监视和网络监视。它由Zabbix Server和可选组件Zabbix Agent组成&#xff0c;通过C/S模式&#xff08;客户端-服务器模型&#xff09;采集数据&#xff0c;并通过B/S模式&#xff08;浏览器-服务器模型&#x…

游戏关卡设计的常用模式

游戏关卡分为很多种&#xff0c;但常用的有固定套路&#xff0c;分为若干种类型。 关卡是主角与怪物、敌方战斗的场所&#xff0c;包括装饰物、通道。 单人游戏的关卡较小&#xff0c;偏线性&#xff1b; 联机/MMO的关卡较大&#xff0c;通道多&#xff0c;自由度高&#xf…

【容器化技术 Docker 与微服务部署】详解

容器化技术 Docker 与微服务部署 一、容器化技术概述 &#xff08;一&#xff09;概念 容器化技术是一种操作系统级别的虚拟化方法&#xff0c;它允许将应用程序及其依赖项&#xff08;如运行时环境、系统工具、库等&#xff09;打包成一个独立的、可移植的单元&#xff0c;这…

QT集成IntelRealSense双目摄像头3,3D显示

前两篇文章&#xff0c;介绍了如何继承intel realsense相机和opengl。 这里介绍如何给深度数据和色彩数据一块显示到opengl里面。 首先&#xff0c;需要了解深度数据和彩色数据是如何存储的。先说彩色数据。彩色图像一般都是RGB&#xff0c;也就是每个像素有三个字节&#xf…

Postman[4] 环境设置

作用&#xff1a;不同的环境可以定义不同的参数&#xff0c;在运行请求时可以根据自己的需求选择需要的环境 1.创建Environment 步骤&#xff1a; Environment-> ->命名->添加环境变量 2.使用Environment 步骤&#xff1a;Collection- >右上角选择需要的环境

SpringBoot_第二天

SpringBoot_第二天 学习目标 Mybatis整合&数据访问 使用SpringBoot开发企业项目时&#xff0c;持久层数据访问是前端页面数据展示的基础&#xff0c;SpringBoot支持市面上常见的关系库产品(Oracle,Mysql,SqlServer,DB2等)对应的相关持久层框架&#xff0c;当然除了对于关系…

SparseViT:基于稀疏编码Transformer的非语义中心、参数高效的图像篡改定位

摘要 https://arxiv.org/pdf/2412.14598 非语义特征或语义无关特征&#xff0c;与图像上下文无关但对图像篡改敏感&#xff0c;被认为是图像篡改定位&#xff08;IML&#xff09;的重要证据。由于无法获得人工标签&#xff0c;现有工作依赖于手工方法提取非语义特征。手工非语…