当前位置: 首页 > news >正文

基于Qt Creator的Serial Port串口调试助手项目(代码开源)

前言:本文为手把手教学的基于 Qt Creator Serial Port 串口调试助手项目教程,项目使用 Qt 版本为 Qt 5.9.0,整体上实现了 Serial Port 串口助手所有的功能。本项目的 Serial Port 串口助手除了设计出常规的 Serial Port 串口打印功能外,还额外模仿了著名软件 VOFA+ 的上位机波形实时输出功能。本项目代码实现偏简单可行,可能存在很多可以优化性能的地方,亦或可能存在功能性上的 BUG ,欢迎各位读者朋友们使用该代码或在此代码框架上进行二次开发。当然,也欢迎各位读者朋友们提出珍贵的改进建议(篇末代码开源)!

软件界面:

所需环境和软件工具:

1. Qt Creator 4.3.0

2. Qt 5.9.0

3. Window10

一、Serial Port串口助手

串口助手(Serial Port Debugger)是一款运行在计算机(PC)上的软件工具,其核心功能是通过计算机的物理串行通信接口(如 COM 口、USB 转串口适配器)或虚拟串口,与外部具备串行通信能力的硬件设备进行双向数据交互。它是嵌入式系统开发、工业控制、仪器仪表调试、物联网设备通信等领域的基础且至关重要的工具。本项目在传统的 Serial Port 助手的功能上还提供了 Plot 绘制的功能,该功能模仿了超级优秀的串口上位机 VOFA+ 进行设计,如下为 VOFA+ 这款非常优秀的串口调试助手界面:

本篇博客的 Serial Port 串口调试助手是模仿 VOFA+ 这款优秀上位机进行制作的,使用的开发软件为 Qt Creator 4.3.0,Qt 的版本为Qt 5.9.0。考虑到需要实现 Plot 波形数据绘制,还引入了 QCustomPlot 这个图形库,利用 QCustomPlot 提供的 API 函数实现 Plot 的绘制。本项目的 Serial Port 串口助手的 Plot 波形绘制功能如下:

二、Serial Port布局 ui 画面

2.1 Serial Port整体布局

一般情况下,为了保证设计的 Serial Port 助手的布局合理性,可能会固定窗口大小,共有 2  种方法;

方案 1:使用代码 this->setFixedSize(this->size());其中这里的this表示所需要固定的窗口。

方案 2:调整 ui 窗口的最大值与最小值,即使得二者相等,如下图所示:

2.2 功能性按键布局

Serial Port 串口助手的功能布局:

1、串口选择:USB_CH340_COM;myComboBox

2、波特率:1200~1382400;QComboBox

3、停止位:1、1.5、2;QComboBox

4、数据位:5、6、7、8;QComboBox

5、奇偶校验:无、奇校验、偶校验;QComboBox

6、串口操作:控制串口的打开;QPushButton

Serial Port 计数的功能布局:

1、发送计数:发送的字节数量;QLineEdit

2、接收计数:接收的字节数量;QLineEdit

3、发送速度:发送的字节速度;QLineEdit

4、接收速度:接收的字节速度;QLineEdit

5、清除接收/统计:清除接收内容与接收计数;QPushButton

6、清除发送:清除发送内容与发送计数;QPushButton

2.3 数据接收与发送布局

Serial Port 串口数据接收:

打开串口助手后,默认串口数据-接收区将会一直接收下位机发送过来的数据。该数据接收区的 Qt 部件为 QPlainTextEdit,读者朋友们可以自行设计其 Size() 大小、Font 字符内容和 Format 格式等

Serial Port 串口数据发送:

打开串口助手后,可以在串口数据发送区输入文本内容,点击发送按钮 PushButton 即可;亦或是使用定时器发送数据内容。补充说明:数据接收与数据发送区都可以使用 16 进制数据进行处理。

Serial Port 的串口数据接收区和发送区都属于 Qt 中的 QPlainTextEdit;

Serial Port 最终布局:

三、Serial Port 串口功能实现

3.1 Serial Port 初始化与接收

Serial Port 初始化需要选择 PC端现在存在的 COM 端口,之后需要设置 Serial Port 波特率、数据的停止位、数据位和奇偶校验。然后,点击串口操作按钮 PushButton  打开串口,作者这里使用了 Qt 槽函数进行连接;

补充说明:Qt 提供了一系列特别方便的 Serial Port 串口助手库函数,我们可以直接使用 mySerialPort->readAll() 和 mySerialPort->writeAll() 针对串口提供的数据进行操作;

// Serial Port串口设置初始化
void MainWindow::on_btnSwitch_clicked()
{QSerialPort::BaudRate baudRate;QSerialPort::DataBits dataBits;QSerialPort::StopBits stopBits;QSerialPort::Parity   checkBits;// 获取串口波特率baudRate = (QSerialPort::BaudRate)ui->cmbBaudRate->currentText().toUInt();// 获取串口数据位dataBits = (QSerialPort::DataBits)ui->cmbData->currentText().toUInt();// 获取串口停止位if(ui->cmbStop->currentText() == "1"){stopBits = QSerialPort::OneStop;}else if(ui->cmbStop->currentText() == "1.5"){stopBits = QSerialPort::OneAndHalfStop;}else if(ui->cmbStop->currentText() == "2"){stopBits = QSerialPort::TwoStop;}else{stopBits = QSerialPort::OneStop;}// 获取串口奇偶校验位if(ui->cmbCheck->currentText() == "无"){checkBits = QSerialPort::NoParity;}else if(ui->cmbCheck->currentText() == "奇校验"){checkBits = QSerialPort::OddParity;}else if(ui->cmbCheck->currentText() == "偶校验"){checkBits = QSerialPort::EvenParity;}else{checkBits = QSerialPort::NoParity;}// 想想用 substr strchr怎么从带有信息的字符串中提前串口号字符串// 初始化串口属性,设置 端口号、波特率、数据位、停止位、奇偶校验位数mySerialPort->setBaudRate(baudRate);mySerialPort->setDataBits(dataBits);mySerialPort->setStopBits(stopBits);mySerialPort->setParity(checkBits);//mySerialPort->setPortName(ui->cmbSerialPort->currentText());// 不匹配带有串口设备信息的文本// 匹配带有串口设备信息的文本QString spTxt = ui->cmbSerialPort->currentText();spTxt = spTxt.section(':', 0, 0);//spTxt.mid(0, spTxt.indexOf(":"));//qDebug() << spTxt;mySerialPort->setPortName(spTxt);// 根据初始化好的串口属性,打开串口// 如果打开成功,反转打开按钮显示和功能。打开失败,无变化,并且弹出错误对话框。if(ui->btnSwitch->text() == "打开串口"){if(mySerialPort->open(QIODevice::ReadWrite) == true){ui->btnSwitch->setText("关闭串口");// 让端口号下拉框不可选,避免误操作(选择功能不可用,控件背景为灰色)ui->cmbSerialPort->setEnabled(false);ui->cmbBaudRate->setEnabled(false);ui->cmbStop->setEnabled(false);ui->cmbData->setEnabled(false);ui->cmbCheck->setEnabled(false);}else{QMessageBox::critical(this, "错误提示", "串口打开失败!!!\r\n\r\n该串口可能被占用,请选择正确的串口\r\n或者波特率过高,超出硬件支持范围");}}else{mySerialPort->close();ui->btnSwitch->setText("打开串口");// 端口号下拉框恢复可选,避免误操作ui->cmbSerialPort->setEnabled(true);ui->cmbBaudRate->setEnabled(true);ui->cmbStop->setEnabled(true);ui->cmbData->setEnabled(true);ui->cmbCheck->setEnabled(true);}
}
// 串口接收显示,槽函数
void MainWindow::serialPortRead_Slot()
{/* 利用QtSerial库接收数据 */QByteArray recBuf = mySerialPort->readAll();Plot_Num = recBuf;// 判断是否为16进制接收,将以后接收的数据全部转换为16进制显示(先前接收的部分在多选框槽函数中进行转换。最好多选框和接收区组成一个自定义控件,方便以后调用)if(ui->chkRec->checkState() == false){// GB2312编码输入QString strb = QString::fromLocal8Bit(recBuf);//QString::fromUtf8(recBuf);//QString::fromLatin1(recBuf);// 在当前位置插入文本,不会发生换行。如果没有移动光标到文件结尾,会导致文件超出当前界面显示范围,界面也不会向下滚动。ui->txtRec->insertPlainText(strb);}else{// 16进制显示,并转换为大写QString str1 = recBuf.toHex().toUpper();//.data();// 添加空格QString str2;for(int i = 0; i<str1.length (); i+=2){str2 += str1.mid (i,2);str2 += " ";}ui->txtRec->insertPlainText(str2);}/* 1.计算接收到的字节数 */RecvNum += recBuf.size();/* 2.格式化并显示总字节数 */ui->RxCount->setText(QString::number(RecvNum));/* 3.计算并显示接收速度 */QString speedText = calculateSpeed(RecvNum);ui->RxSpeed->setText(speedText);/* 4.更新上一次记录(用于下次计算速度)*/lastRecvNum = RecvNum;lastUpdateTime = QDateTime::currentMSecsSinceEpoch();/* 5.移动光标到文本结尾 */ui->txtRec->moveCursor(QTextCursor::End);
}

3.2 Serial Port 串口数据发送

Serial Port 串口助手通常拥有定时发送功能,例如:1000ms 进行一次数据发送。当然,还拥有 16 进制数转换的功能进行 Data 数据接收和发送。作者这边提供详细的代码,读者朋友可以直接参考作者提供的开源代码。

// 串口发送数据
void MainWindow::on_btnSend_clicked()
{QByteArray sendData;// 判断是否为16进制发送,将发送区全部的asc2转换为16进制字符串显示,发送的时候转换为16进制发送if(ui->chkSend->checkState() == false){// 字符串形式发送,GB2312编码用以兼容大多数单片机sendData = ui->txtSend->toPlainText().toLocal8Bit();// GB2312编码输出}else{// 16进制发送,不要用.data(),.data()返回的是字符数组,0x00在ASC2中的意义为NUL,也就是'\0'结束符,所以遇到0x00就会终止sendData = QByteArray::fromHex(ui->txtSend->toPlainText().toLocal8Bit());// GB2312编码输出}// 记录发送前的时间戳qint64 sendStartTime = QDateTime::currentMSecsSinceEpoch();// 发送数据int bytesSent = mySerialPort->write(sendData);// 发送字节计数并显示if(bytesSent > 0) {// 更新总发送字节数SendNum += bytesSent;ui->TxCount->setText(QString::number(SendNum));// 记录发送结束时间qint64 sendEndTime = QDateTime::currentMSecsSinceEpoch();qint64 sendDuration = sendEndTime - sendStartTime;// 计算并显示发送速度calculateSendSpeed(bytesSent, sendDuration);}
}// 16进制发送触发按键
void MainWindow::on_chkSend_stateChanged(int arg1)
{// 获取文本字符串QString txtBuf = ui->txtSend->toPlainText();// 获取多选框状态,未选为0,选中为2// 为0时,多选框未被勾选,将先前的发送区的16进制字符串转换为asc2字符串if(arg1 == 0){//QByteArray str1 = QByteArray::fromHex(txtBuf.toUtf8());//仅能处理Unicode编码,因为QString就是Unicode//QString str1 = QString::fromLocal8Bit(txtBuf.toUtf8());//仅能处理GB2312编码,Unicode的数据无论如何都会乱码//把gb2312编码转换成unicodeQString str1 = QTextCodec::codecForName("GB2312")->toUnicode(QByteArray::fromHex(txtBuf.toLocal8Bit()));// 文本控件清屏,显示新文本ui->txtSend->clear();ui->txtSend->insertPlainText(str1);// 移动光标到文本结尾ui->txtSend->moveCursor(QTextCursor::End);}else{// 多选框被勾选,将先前的发送区的asc2字符串转换为16进制字符串//QByteArray str1 = txtBuf.toUtf8().toHex().toUpper();// Unicode编码输出QString str1 = txtBuf.toLocal8Bit().toHex().toUpper();// GB2312编码输出// 添加空格QString str2;for(int i = 0; i<str1.length (); i+=2){str2 += str1.mid (i,2);str2 += " ";}// 文本控件清屏,显示新文本ui->txtSend->clear();ui->txtSend->insertPlainText(str2);// 移动光标到文本结尾ui->txtSend->moveCursor(QTextCursor::End);}
}

3.3 Serial Port 计数功能

Serial Port 的发送速度:

// 计算发送速度(字节/秒)
void MainWindow::calculateSendSpeed(qint64 bytesSent, qint64 durationMs)
{// 处理零时间间隔(理论不可能,但安全处理)if (durationMs == 0) durationMs = 1;// 计算瞬时速度(字节/秒)double instantSpeed = (bytesSent * 1000.0) / durationMs;// 应用指数平滑滤波(减少数值跳动)const double smoothingFactor = 0.3;smoothedSendSpeed = smoothingFactor * instantSpeed + (1 - smoothingFactor) * smoothedSendSpeed;// 单位转换与显示QString speedText;if (smoothedSendSpeed >= 1024 * 1024 * 1024) { // GB/sspeedText = QString("%1 GB/s").arg(smoothedSendSpeed / (1024 * 1024 * 1024), 0, 'f', 2);} else if (smoothedSendSpeed >= 1024 * 1024) { // MB/sspeedText = QString("%1 MB/s").arg(smoothedSendSpeed / (1024 * 1024), 0, 'f', 2);} else if (smoothedSendSpeed >= 1024) { // KB/sspeedText = QString("%1 KB/s").arg(smoothedSendSpeed / 1024, 0, 'f', 2);} else { // B/sspeedText = QString("%1 B/s").arg(smoothedSendSpeed, 0, 'f', 2);}// 更新UI显示ui->TxSpeed->setText(speedText);// 更新最后发送状态lastSendTime = QDateTime::currentMSecsSinceEpoch();lastSendNum = SendNum;
}

Serial Port 的接收速度:

// 计算接收速度(字节/秒)
QString MainWindow::calculateSpeed(qint64 currentNum)
{qint64 currentTime = QDateTime::currentMSecsSinceEpoch();qint64 deltaTime = currentTime - lastUpdateTime;if (deltaTime == 0 || lastRecvNum == 0) {// 首次计算或时间未变化时返回0lastRecvNum = currentNum;lastUpdateTime = currentTime;return "0 B/s";}// 计算每秒接收字节数double deltaBytes = currentNum - lastRecvNum;double speed = deltaBytes / deltaTime * 1000; // 转换为秒// 速度单位转换if (speed >= 1024 * 1024 * 1024) { // GB/sreturn QString("%1 GB/s").arg(speed / (1024 * 1024 * 1024), 0, 'f', 2);} else if (speed >= 1024 * 1024) { // MB/sreturn QString("%1 MB/s").arg(speed / (1024 * 1024), 0, 'f', 2);} else if (speed >= 1024) { // KB/sreturn QString("%1 KB/s").arg(speed / 1024, 0, 'f', 2);} else { // B/sreturn QString("%1 B/s").arg(speed, 0, 'f', 2);}
}

四、Waveform 波形功能实现

4.1 引入 QCustomPlot

QCustomPlot 是一个超强超小巧的 Qt 绘图类,非常漂亮,非常易用,只需要加入一个 qcustomplot.h 和 qcustomplot.cpp 文件即可使用,远比 qwt 方便和漂亮,可以自己使用两个源文件也可以自己编译成库文件,非常方便。

官方网站:http://www.qcustomplot.com/

进入 QCustomPlot 下载页,下载最新的完整包(包含:源码、文档、示例)!
将下载好的安装包进行解压缩,里面包含文档、示例、更改日志、GPL 授权、以及最重要的两个文件 qcustomplot.h 与 qcustomplot.cpp。

在 Examples 中我们会看到一些自带的示例,可以运行看一下效果。
如果在自己的项目中使用,需要进行以下配置:
首先,在 pro 中需要添加(由于 QCustomPlot 中存在导出功能,使用了 printsupport 模块):

QT += printsupport

然后,将 qcustomplot.h 与 qcustomplot.cpp 拷贝到工程目录下,右键  -> 添加现有文件…,将这两个文件添加至工程。在调用 qcustomplot 的地方,需要引入,才可成功创建一个 qcustomplot 对象:

#include "qcustomplot.h"
/* 创建qcustomplot */
QCustomPlot *pCustomPlot = new QCustomPlot(this);

4.2 Plot ui 格局设计

Plot 波形绘制的区域使用了 QCustomPlot 库中的 QCustomPlot 的类,曲线/散点设置则是一系列 Qt 自带的类进行设计的。最终的 Plot 绘制的情况如下图:

4.3 Plot data 数据筛选

作者设计的 Plot 绘制波形的数据筛选使用了如下格式,且最高支持8个波形的同时绘制:

printf("simples:%f, %f\n", sin(t1), sin(t2)); 

Plot data 数据的筛选部分代码如下:

// 数据清洗与转换
QString str = QString::fromUtf8(Plot_Num).remove(" ").replace(":", ",").replace(":",  ",").replace(",", ",");// 拆分字符串
QStringList parts = str.split(",",  QString::SkipEmptyParts );// 提取数字
QVector<double> numbers;
for (const QString& part : parts) {bool ok;double num = part.toDouble(&ok);if (ok) numbers.append(num);
}

巧妙的使用了 Qt 和 C++ 库函数进行操作,替换原数据文本中不符合要求的内容,在 QVector 容器的基础上进行有效数据的提取!

4.4 Plot 波形图绘制

作者使用的方式是定时器每 10ms 将串口接收到的数据进去筛选提取,并利用 QCustomPlot 库进行 Plot 的绘制(由于很多情况下,串口接收速率是比 Plot 绘制提取的速率快的,这将不可避免地导致数据缺失,读者朋友可以根据直接实际情况使用一个环形缓存区进行缓存处理) 

Plot 波形绘图定时器设计:

// 创建定时器,用于定时生成曲线坐标点数据
timer = new QTimer(this);
timer->setInterval(10);
connect(timer,SIGNAL(timeout()),this,SLOT(TimeData_Update()));
timer->start(10);
// 定时器溢出处理槽函数。用来生成曲线的坐标数据。
void plot::TimeData_Update(void)
{// 数据清洗与转换QString str = QString::fromUtf8(Plot_Num).remove(" ").replace(":", ",").replace(":",  ",").replace(",", ",");// 拆分字符串QStringList parts = str.split(",",  QString::SkipEmptyParts );// 提取数字QVector<double> numbers;for (const QString& part : parts) {bool ok;double num = part.toDouble(&ok);if (ok) numbers.append(num);}// 计算需要绘制几个波形,移动x轴的数据int n = numbers.size();cnt++;// 给曲线添加数据for(int i=0; i<n; i++){pTxtValueCurve[i]->setText(QString::number(numbers[i],'g',8));// 显示曲线当前值pCurve[i]->addData(cnt, numbers[i]);}// 设置x坐标轴显示范围,使其自适应缩放x轴,x轴最大显示pointCountX个点。与chkTrackX复选框有关if(ui->chkTrackX->checkState()){//customPlot->xAxis->setRange((pCurve[0]->dataCount()>pointCountX)?(pCurve[0]->dataCount()-pointCountX):0, pCurve[0]->dataCount());setAutoTrackX(pPlot1);}// 设置y坐标轴显示范围,使其自适应曲线缩放if(ui->chkAdjustY->checkState()){setAutoTrackY(pPlot1);}// 更新绘图,这种方式在高填充下太浪费资源。有另一种方式rpQueuedReplot,可避免重复绘图。// 最好的方法还是将数据填充、和更新绘图分隔开。将更新绘图单独用定时器更新。例程数据量较少没用单独定时器更新,实际工程中建议大家加上。pPlot1->replot(QCustomPlot::rpQueuedReplot);
}

五、代码解析与各项功能

5.1 mainwindow.cpp

本代码为 iKun Serial Port Debugger 串口调试助手的主界面代码功能,作者在上方拆解了部分 Serial Port 功能代码的实现原理与代码。本项目更多的细节点读者朋友可以参考下发的代码内容,其中包括 SendSpeed 和 RecvSpeed 速度的实时更新等。一个优秀的 APP 项目肯定是需要不断打磨的,后期作者会把本项目已知的不足与打算改进的地方就行更新! 

/******************************************************************
* @projectName   iKun Serial Port Debugger
* @brief         mainwindow.cpp
* @author        混分巨兽龙某某
* @email         1178305328@qq.com
*******************************************************************/
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "plot.h"MainWindow::MainWindow(QWidget *parent) :QMainWindow(parent),ui(new Ui::MainWindow)
{ui->setupUi(this);setWindowTitle("iKun Serial Port Debugger");        /* 修改APP标题 */// 状态栏QStatusBar *sBar = statusBar();/* 实例化两个按钮对象,并设置其显示文本为窗口皮肤1和窗口皮肤2 */pushButton1 = new QPushButton("作者信息", this);/* 设定两个QPushButton对象的位置 */sBar->addPermanentWidget(pushButton1);// 状态栏添加超链接QLabel *lblLinkBlog = new QLabel(this);lblLinkBlog->setOpenExternalLinks(true);lblLinkBlog->setText("<style> a {text-decoration: none} </style> <a href=\"https://blog.csdn.net/black_sneak?type=blog\">博客");// 无下划线QLabel *lblLinkSource = new QLabel(this);lblLinkSource->setOpenExternalLinks(true);lblLinkSource->setText("<style> a {text-decoration: none} </style> <a href=\"https://blog.csdn.net/black_sneak/article/details/151232098\">源码下载");// 无下划线lblLinkBlog->setMinimumSize(40, 20);lblLinkSource->setMinimumSize(60, 20);// 从左往右依次添加sBar->addWidget(lblLinkBlog);sBar->addWidget(lblLinkSource);// 定时发送-定时器timSend = new QTimer;timSend->setInterval(1000);// 设置默认定时时长1000msconnect(timSend, &QTimer::timeout, this, [=](){on_btnSend_clicked();});// 初始化清零定时器(每500ms检查一次)speedClearTimer = new QTimer(this);connect(speedClearTimer, &QTimer::timeout, this, &MainWindow::checkSpeedClear);speedClearTimer->start(500);// 新建一串口对象mySerialPort = new QSerialPort(this);// 串口接收,信号槽关联connect(mySerialPort, SIGNAL(readyRead()), this, SLOT(serialPortRead_Slot()));// 信号槽连接,打开作者信息的按钮connect(pushButton1, SIGNAL(clicked()), this, SLOT(pushButton1_Clicked()));
}MainWindow::~MainWindow()
{delete ui;
}// 作者信息的信号槽函数
void MainWindow::pushButton1_Clicked()
{QMessageBox::information(NULL, "作者信息","作者:混粉巨兽龙某某\n联系方式:1178305328\n版本信息:V1.0");
}// 调用Plot界面操作
void MainWindow::on_pushButton_clicked()
{plot *ConfigWindow = new plot;ConfigWindow->show();
}// Serial Port串口设置初始化
void MainWindow::on_btnSwitch_clicked()
{QSerialPort::BaudRate baudRate;QSerialPort::DataBits dataBits;QSerialPort::StopBits stopBits;QSerialPort::Parity   checkBits;// 获取串口波特率baudRate = (QSerialPort::BaudRate)ui->cmbBaudRate->currentText().toUInt();// 获取串口数据位dataBits = (QSerialPort::DataBits)ui->cmbData->currentText().toUInt();// 获取串口停止位if(ui->cmbStop->currentText() == "1"){stopBits = QSerialPort::OneStop;}else if(ui->cmbStop->currentText() == "1.5"){stopBits = QSerialPort::OneAndHalfStop;}else if(ui->cmbStop->currentText() == "2"){stopBits = QSerialPort::TwoStop;}else{stopBits = QSerialPort::OneStop;}// 获取串口奇偶校验位if(ui->cmbCheck->currentText() == "无"){checkBits = QSerialPort::NoParity;}else if(ui->cmbCheck->currentText() == "奇校验"){checkBits = QSerialPort::OddParity;}else if(ui->cmbCheck->currentText() == "偶校验"){checkBits = QSerialPort::EvenParity;}else{checkBits = QSerialPort::NoParity;}// 想想用 substr strchr怎么从带有信息的字符串中提前串口号字符串// 初始化串口属性,设置 端口号、波特率、数据位、停止位、奇偶校验位数mySerialPort->setBaudRate(baudRate);mySerialPort->setDataBits(dataBits);mySerialPort->setStopBits(stopBits);mySerialPort->setParity(checkBits);//mySerialPort->setPortName(ui->cmbSerialPort->currentText());// 不匹配带有串口设备信息的文本// 匹配带有串口设备信息的文本QString spTxt = ui->cmbSerialPort->currentText();spTxt = spTxt.section(':', 0, 0);//spTxt.mid(0, spTxt.indexOf(":"));//qDebug() << spTxt;mySerialPort->setPortName(spTxt);// 根据初始化好的串口属性,打开串口// 如果打开成功,反转打开按钮显示和功能。打开失败,无变化,并且弹出错误对话框。if(ui->btnSwitch->text() == "打开串口"){if(mySerialPort->open(QIODevice::ReadWrite) == true){ui->btnSwitch->setText("关闭串口");// 让端口号下拉框不可选,避免误操作(选择功能不可用,控件背景为灰色)ui->cmbSerialPort->setEnabled(false);ui->cmbBaudRate->setEnabled(false);ui->cmbStop->setEnabled(false);ui->cmbData->setEnabled(false);ui->cmbCheck->setEnabled(false);}else{QMessageBox::critical(this, "错误提示", "串口打开失败!!!\r\n\r\n该串口可能被占用,请选择正确的串口\r\n或者波特率过高,超出硬件支持范围");}}else{mySerialPort->close();ui->btnSwitch->setText("打开串口");// 端口号下拉框恢复可选,避免误操作ui->cmbSerialPort->setEnabled(true);ui->cmbBaudRate->setEnabled(true);ui->cmbStop->setEnabled(true);ui->cmbData->setEnabled(true);ui->cmbCheck->setEnabled(true);}
}// 串口接收显示,槽函数
void MainWindow::serialPortRead_Slot()
{/* 利用QtSerial库接收数据 */QByteArray recBuf = mySerialPort->readAll();Plot_Num = recBuf;// 判断是否为16进制接收,将以后接收的数据全部转换为16进制显示(先前接收的部分在多选框槽函数中进行转换。最好多选框和接收区组成一个自定义控件,方便以后调用)if(ui->chkRec->checkState() == false){// GB2312编码输入QString strb = QString::fromLocal8Bit(recBuf);//QString::fromUtf8(recBuf);//QString::fromLatin1(recBuf);// 在当前位置插入文本,不会发生换行。如果没有移动光标到文件结尾,会导致文件超出当前界面显示范围,界面也不会向下滚动。ui->txtRec->insertPlainText(strb);}else{// 16进制显示,并转换为大写QString str1 = recBuf.toHex().toUpper();//.data();// 添加空格QString str2;for(int i = 0; i<str1.length (); i+=2){str2 += str1.mid (i,2);str2 += " ";}ui->txtRec->insertPlainText(str2);}/* 1.计算接收到的字节数 */RecvNum += recBuf.size();/* 2.格式化并显示总字节数 */ui->RxCount->setText(QString::number(RecvNum));/* 3.计算并显示接收速度 */QString speedText = calculateSpeed(RecvNum);ui->RxSpeed->setText(speedText);/* 4.更新上一次记录(用于下次计算速度)*/lastRecvNum = RecvNum;lastUpdateTime = QDateTime::currentMSecsSinceEpoch();/* 5.移动光标到文本结尾 */ui->txtRec->moveCursor(QTextCursor::End);
}// 计算接收速度(字节/秒)
QString MainWindow::calculateSpeed(qint64 currentNum)
{qint64 currentTime = QDateTime::currentMSecsSinceEpoch();qint64 deltaTime = currentTime - lastUpdateTime;if (deltaTime == 0 || lastRecvNum == 0) {// 首次计算或时间未变化时返回0lastRecvNum = currentNum;lastUpdateTime = currentTime;return "0 B/s";}// 计算每秒接收字节数double deltaBytes = currentNum - lastRecvNum;double speed = deltaBytes / deltaTime * 1000; // 转换为秒// 速度单位转换if (speed >= 1024 * 1024 * 1024) { // GB/sreturn QString("%1 GB/s").arg(speed / (1024 * 1024 * 1024), 0, 'f', 2);} else if (speed >= 1024 * 1024) { // MB/sreturn QString("%1 MB/s").arg(speed / (1024 * 1024), 0, 'f', 2);} else if (speed >= 1024) { // KB/sreturn QString("%1 KB/s").arg(speed / 1024, 0, 'f', 2);} else { // B/sreturn QString("%1 B/s").arg(speed, 0, 'f', 2);}
}// 计算发送速度(字节/秒)
void MainWindow::calculateSendSpeed(qint64 bytesSent, qint64 durationMs)
{// 处理零时间间隔(理论不可能,但安全处理)if (durationMs == 0) durationMs = 1;// 计算瞬时速度(字节/秒)double instantSpeed = (bytesSent * 1000.0) / durationMs;// 应用指数平滑滤波(减少数值跳动)const double smoothingFactor = 0.3;smoothedSendSpeed = smoothingFactor * instantSpeed + (1 - smoothingFactor) * smoothedSendSpeed;// 单位转换与显示QString speedText;if (smoothedSendSpeed >= 1024 * 1024 * 1024) { // GB/sspeedText = QString("%1 GB/s").arg(smoothedSendSpeed / (1024 * 1024 * 1024), 0, 'f', 2);} else if (smoothedSendSpeed >= 1024 * 1024) { // MB/sspeedText = QString("%1 MB/s").arg(smoothedSendSpeed / (1024 * 1024), 0, 'f', 2);} else if (smoothedSendSpeed >= 1024) { // KB/sspeedText = QString("%1 KB/s").arg(smoothedSendSpeed / 1024, 0, 'f', 2);} else { // B/sspeedText = QString("%1 B/s").arg(smoothedSendSpeed, 0, 'f', 2);}// 更新UI显示ui->TxSpeed->setText(speedText);// 更新最后发送状态lastSendTime = QDateTime::currentMSecsSinceEpoch();lastSendNum = SendNum;
}// 新增:速度清零检查函数
void MainWindow::checkSpeedClear()
{qint64 currentTime = QDateTime::currentMSecsSinceEpoch();// 超过1秒无数据时清零显示if (currentTime - lastUpdateTime > 1000) {ui->RxSpeed->setText("0 B/s");ui->TxSpeed->setText("0 B/s");}
}// 串口发送数据
void MainWindow::on_btnSend_clicked()
{QByteArray sendData;// 判断是否为16进制发送,将发送区全部的asc2转换为16进制字符串显示,发送的时候转换为16进制发送if(ui->chkSend->checkState() == false){// 字符串形式发送,GB2312编码用以兼容大多数单片机sendData = ui->txtSend->toPlainText().toLocal8Bit();// GB2312编码输出}else{// 16进制发送,不要用.data(),.data()返回的是字符数组,0x00在ASC2中的意义为NUL,也就是'\0'结束符,所以遇到0x00就会终止sendData = QByteArray::fromHex(ui->txtSend->toPlainText().toLocal8Bit());// GB2312编码输出}// 记录发送前的时间戳qint64 sendStartTime = QDateTime::currentMSecsSinceEpoch();// 发送数据int bytesSent = mySerialPort->write(sendData);// 发送字节计数并显示if(bytesSent > 0) {// 更新总发送字节数SendNum += bytesSent;ui->TxCount->setText(QString::number(SendNum));// 记录发送结束时间qint64 sendEndTime = QDateTime::currentMSecsSinceEpoch();qint64 sendDuration = sendEndTime - sendStartTime;// 计算并显示发送速度calculateSendSpeed(bytesSent, sendDuration);}
}// 16进制发送触发按键
void MainWindow::on_chkSend_stateChanged(int arg1)
{// 获取文本字符串QString txtBuf = ui->txtSend->toPlainText();// 获取多选框状态,未选为0,选中为2// 为0时,多选框未被勾选,将先前的发送区的16进制字符串转换为asc2字符串if(arg1 == 0){//QByteArray str1 = QByteArray::fromHex(txtBuf.toUtf8());//仅能处理Unicode编码,因为QString就是Unicode//QString str1 = QString::fromLocal8Bit(txtBuf.toUtf8());//仅能处理GB2312编码,Unicode的数据无论如何都会乱码//把gb2312编码转换成unicodeQString str1 = QTextCodec::codecForName("GB2312")->toUnicode(QByteArray::fromHex(txtBuf.toLocal8Bit()));// 文本控件清屏,显示新文本ui->txtSend->clear();ui->txtSend->insertPlainText(str1);// 移动光标到文本结尾ui->txtSend->moveCursor(QTextCursor::End);}else{// 多选框被勾选,将先前的发送区的asc2字符串转换为16进制字符串//QByteArray str1 = txtBuf.toUtf8().toHex().toUpper();// Unicode编码输出QString str1 = txtBuf.toLocal8Bit().toHex().toUpper();// GB2312编码输出// 添加空格QString str2;for(int i = 0; i<str1.length (); i+=2){str2 += str1.mid (i,2);str2 += " ";}// 文本控件清屏,显示新文本ui->txtSend->clear();ui->txtSend->insertPlainText(str2);// 移动光标到文本结尾ui->txtSend->moveCursor(QTextCursor::End);}
}// 定时器发送触发按键操作
void MainWindow::on_chkTimSend_stateChanged(int arg1)
{// 获取复选框状态,未选为0,选中为2if(arg1 == 0){timSend->stop();// 时间输入框恢复可选ui->txtSendMs->setEnabled(true);}else{// 对输入的值做限幅,小于20ms会弹出对话框提示if(ui->txtSendMs->text().toInt() >= 20){timSend->start(ui->txtSendMs->text().toInt());// 设置定时时长,重新计数// 让时间输入框不可选,避免误操作(输入功能不可用,控件背景为灰色)ui->txtSendMs->setEnabled(false);}else{ui->chkTimSend->setCheckState(Qt::Unchecked);QMessageBox::critical(this, "错误提示", "定时发送的最小间隔为 20ms\r\n请确保输入的值 >=20");}}
}//
void MainWindow::on_btnClearRec_clicked()
{ui->txtRec->clear();// 清除接收字节计数RecvNum = 0;ui->RxCount->setText(QString::number(RecvNum));
}void MainWindow::on_btnClearSend_clicked()
{ui->txtSend->clear();// 清除发送字节计数SendNum = 0;ui->TxCount->setText(QString::number(SendNum));
}// 16进制的串口数据接收
void MainWindow::on_chkRec_stateChanged(int arg1)
{// 获取文本字符串QString txtBuf = ui->txtRec->toPlainText();// 获取多选框状态,未选为0,选中为2// 为0时,多选框未被勾选,接收区先前接收的16进制数据转换为asc2字符串格式if(arg1 == 0){//QString str1 = QByteArray::fromHex(txtBuf.toUtf8());//QString str1 = QByteArray::fromHex(txtBuf.toLocal8Bit());//把gb2312编码转换成unicodeQString str1 = QTextCodec::codecForName("GB2312")->toUnicode(QByteArray::fromHex(txtBuf.toLocal8Bit()));// 文本控件清屏,显示新文本ui->txtRec->clear();ui->txtRec->insertPlainText(str1);// 移动光标到文本结尾ui->txtRec->moveCursor(QTextCursor::End);}else{// 不为0时,多选框被勾选,接收区先前接收asc2字符串转换为16进制显示//QString str1 = txtBuf.toUtf8().toHex().toUpper();// Unicode编码输出QString str1 = txtBuf.toLocal8Bit().toHex().toUpper();// GB2312编码输出// 添加空格QByteArray str2;for(int i = 0; i<str1.length (); i+=2){str2 += str1.mid (i,2);str2 += " ";}// 文本控件清屏,显示新文本ui->txtRec->clear();ui->txtRec->insertPlainText(str2);// 移动光标到文本结尾ui->txtRec->moveCursor(QTextCursor::End);}
}

5.2 mycombobox.cpp

/******************************************************************
* @projectName   Serial Port Scanning Function
* @brief         mycombobox.cpp
* @author        混分巨兽龙某某
* @email         1178305328@qq.com
*******************************************************************/
#include "mycombobox.h"myComboBox::myComboBox(QWidget *parent) : QComboBox(parent)
{// 扫描可用串口scanActivePort();
}// 扫描可用串口
void myComboBox::scanActivePort()
{// 先清空列表项,防止多次刷新后重叠clear();// 串口端口号列表QStringList serialPortName;// 自动扫描当前可用串口,返回值追加到字符数组中foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts()){// 携带有串口设备信息的文本QString serialPortInfo = info.portName() + ": " + info.description();               // 串口设备信息,芯片/驱动名称//QString serialPortInfo = info.portName() + ": " + info.manufacturer();            // 串口设备制造商//QString serialPortInfo = info.portName() + ": " + info.serialNumber();            // 串口设备的序列号//QString serialPortInfo = info.portName() + ": " + info.systemLocation();          // 串口设备的系统位置serialPortName << serialPortInfo;}/* 可用串口号,显示到串口选择下拉框中 */this->addItems(serialPortName);
}// 重写鼠标点击事件
void myComboBox::mousePressEvent(QMouseEvent *event)
{if(event->button() == Qt::LeftButton){// 扫描可用串口scanActivePort();// 弹出下拉框showPopup();}
}

5.3 plot.cpp

引入 QCustomPlot 之后实现的 Plot 绘制动态波形是比较简单的,核心的部分的代码为串口接收数据的提取,这部分代码作者在上述已经拆解说明了。其余部分代码功能的时候,读者朋友们可以借鉴一下作者提供的开源代码。当然,Plot 波形绘制过程中的 x 和 y轴自适应也挺重要的,读者朋友们也可以学习一下!

/******************************************************************
* @projectName   Serial Port Plot Function
* @brief         plot.cpp
* @author        混分巨兽龙某某
* @email         1178305328@qq.com
*******************************************************************/
#include "plot.h"
#include "ui_plot.h"
#include <stdio.h>
#include "mainwindow.h"QByteArray Plot_Num;plot::plot(QWidget *parent) :QWidget(parent),ui(new Ui::plot)
{ui->setupUi(this);setWindowTitle("iKun Plot Debugger");// 给widget绘图控件,设置个别名,方便书写pPlot1 = ui->winPlot;// 初始化图表1QPlot_init(pPlot1);// 绘图图表的设置控件初始化,主要用于关联控件的信号槽QPlot_widget_init();// 创建定时器,用于定时生成曲线坐标点数据timer = new QTimer(this);timer->setInterval(10);connect(timer,SIGNAL(timeout()),this,SLOT(TimeData_Update()));timer->start(10);// 关联控件初始化ui->txtPointOriginX->setEnabled(false);// 图表重绘后,刷新原点坐标和范围connect(pPlot1,SIGNAL(afterReplot()),this,SLOT(repPlotCoordinate()));
}plot::~plot()
{delete ui;
}// 定时器溢出处理槽函数。用来生成曲线的坐标数据。
void plot::TimeData_Update(void)
{// 数据清洗与转换QString str = QString::fromUtf8(Plot_Num).remove(" ").replace(":", ",").replace(":",  ",").replace(",", ",");// 拆分字符串QStringList parts = str.split(",",  QString::SkipEmptyParts );// 提取数字QVector<double> numbers;for (const QString& part : parts) {bool ok;double num = part.toDouble(&ok);if (ok) numbers.append(num);}// 计算需要绘制几个波形,移动x轴的数据int n = numbers.size();cnt++;// 给曲线添加数据for(int i=0; i<n; i++){pTxtValueCurve[i]->setText(QString::number(numbers[i],'g',8));// 显示曲线当前值pCurve[i]->addData(cnt, numbers[i]);}// 设置x坐标轴显示范围,使其自适应缩放x轴,x轴最大显示pointCountX个点。与chkTrackX复选框有关if(ui->chkTrackX->checkState()){//customPlot->xAxis->setRange((pCurve[0]->dataCount()>pointCountX)?(pCurve[0]->dataCount()-pointCountX):0, pCurve[0]->dataCount());setAutoTrackX(pPlot1);}// 设置y坐标轴显示范围,使其自适应曲线缩放if(ui->chkAdjustY->checkState()){setAutoTrackY(pPlot1);}// 更新绘图,这种方式在高填充下太浪费资源。有另一种方式rpQueuedReplot,可避免重复绘图。// 最好的方法还是将数据填充、和更新绘图分隔开。将更新绘图单独用定时器更新。例程数据量较少没用单独定时器更新,实际工程中建议大家加上。pPlot1->replot(QCustomPlot::rpQueuedReplot);
}// 绘图图表初始化
void plot::QPlot_init(QCustomPlot *customPlot)
{// 添加曲线名称QStringList lineNames;//设置图例的文本lineNames << "波形1" << "波形2" << "波形3" << "波形4" << "波形5" << "波形6" << "波形7" << "波形8";// 曲线初始颜色QColor initColor[8] = {QColor(0,146,152), QColor(162,0,124), QColor(241,175,0), QColor(27,79,147), QColor(229,70,70),\QColor(0,140,94), QColor(178,0,31), QColor(91,189,43)};//QColor(255,255,255)};//白色// 图表添加20条曲线,并设置初始颜色,和图例名称for(int i=0; i<8; i++){pCurve[i] = customPlot->addGraph();pCurve[i]->setPen(QPen(QColor(initColor[i])));pCurve[i]->setName(lineNames.at(i));}// 设置背景颜色customPlot->setBackground(QColor(255,255,255));// 设置背景选择框颜色ui->btnColourBack->setStyleSheet(QString("border:0px solid;background-color: %1;").arg(QColor(255,255,255).name()));// 曲线选择框颜色,与曲线同步颜色。这样写太复杂了,用控件指针数组在下面写过了,记得要在addGraph()之后才有效。//ui->btnColourCurve1->setStyleSheet("border:0px solid;background-color:rgb(0,146,152)");//ui->btnColourCurve1->setStyleSheet(QString("border:0px solid;background-color: %1;").arg(initColor[0].name()));//ui->btnColourCurve20->setStyleSheet(QString("border:0px solid;background-color: %1;").arg(pCurve[]->pen().color().name()));// 设置坐标轴名称customPlot->xAxis->setLabel("X");customPlot->yAxis->setLabel("Y");// 设置x,y坐标轴显示范围pointCountX = ui->txtPointCountX->text().toUInt();pointCountY = ui->txtPointCountY->text().toUInt();customPlot->xAxis->setRange(0,pointCountX);customPlot->yAxis->setRange(pointCountY/2*-1,pointCountY/2);//customPlot->axisRect()->setupFullAxesBox();//四边安装轴并显示//customPlot->xAxis->ticker()->setTickOrigin(1);//改变刻度原点为1//customPlot->xAxis->setNumberFormat("gbc");//g灵活的格式,b漂亮的指数形式,c乘号改成×//customPlot->xAxis->setNumberPrecision(1);//精度1customPlot->xAxis->ticker()->setTickCount(ui->txtMainScaleNumX->text().toUInt());//11个主刻度customPlot->yAxis->ticker()->setTickCount(ui->txtMainScaleNumY->text().toUInt());//11个主刻度customPlot->xAxis->ticker()->setTickStepStrategy(QCPAxisTicker::tssReadability);//可读性优于设置customPlot->yAxis->ticker()->setTickStepStrategy(QCPAxisTicker::tssReadability);//可读性优于设置// 显示图表的图例customPlot->legend->setVisible(true);// 设置波形曲线的复选框字体颜色//ui->chkVisibleCurve1->setStyleSheet("QCheckBox{color:rgb(255,0,0)}");//设定前景颜色,就是字体颜色// 允许用户用鼠标拖动轴范围,以鼠标为中心滚轮缩放,点击选择图形:customPlot->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom | QCP::iSelectPlottables);// 设置鼠标滚轮的缩放倍率,如果不设置默认为0.85,大于1反方向缩放//customPlot->axisRect()->setRangeZoomFactor(0.5);// 设置鼠标滚轮缩放的轴方向,仅设置垂直轴。垂直轴和水平轴全选使用:Qt::Vertical | Qt::HorizontalcustomPlot->axisRect()->setRangeZoom(Qt::Vertical);
}// 绘图图表的设置控件初始化,主要用于关联控件的信号槽
void plot::QPlot_widget_init(void)
{// 获取控件指针数组,方便设置时编码书写pChkVisibleCurve[0] = ui->chkVisibleCurve1; pBtnColourCurve[0] = ui->btnColourCurve1; pTxtValueCurve[0] = ui->txtValueCurve1; pRdoBoldCurve[0] = ui->rdoBoldCurve1;pChkVisibleCurve[1] = ui->chkVisibleCurve2; pBtnColourCurve[1] = ui->btnColourCurve2; pTxtValueCurve[1] = ui->txtValueCurve2; pRdoBoldCurve[1] = ui->rdoBoldCurve2;pChkVisibleCurve[2] = ui->chkVisibleCurve3; pBtnColourCurve[2] = ui->btnColourCurve3; pTxtValueCurve[2] = ui->txtValueCurve3; pRdoBoldCurve[2] = ui->rdoBoldCurve3;pChkVisibleCurve[3] = ui->chkVisibleCurve4; pBtnColourCurve[3] = ui->btnColourCurve4; pTxtValueCurve[3] = ui->txtValueCurve4; pRdoBoldCurve[3] = ui->rdoBoldCurve4;pChkVisibleCurve[4] = ui->chkVisibleCurve5; pBtnColourCurve[4] = ui->btnColourCurve5; pTxtValueCurve[4] = ui->txtValueCurve5; pRdoBoldCurve[4] = ui->rdoBoldCurve5;pChkVisibleCurve[5] = ui->chkVisibleCurve6; pBtnColourCurve[5] = ui->btnColourCurve6; pTxtValueCurve[5] = ui->txtValueCurve6; pRdoBoldCurve[5] = ui->rdoBoldCurve6;pChkVisibleCurve[6] = ui->chkVisibleCurve7; pBtnColourCurve[6] = ui->btnColourCurve7; pTxtValueCurve[6] = ui->txtValueCurve7; pRdoBoldCurve[6] = ui->rdoBoldCurve7;pChkVisibleCurve[7] = ui->chkVisibleCurve8; pBtnColourCurve[7] = ui->btnColourCurve8; pTxtValueCurve[7] = ui->txtValueCurve8; pRdoBoldCurve[7] = ui->rdoBoldCurve8;pCmbScatterStyle[0] = ui->cmbScatterStyle1;pCmbScatterStyle[1] = ui->cmbScatterStyle2;pCmbScatterStyle[2] = ui->cmbScatterStyle3;pCmbScatterStyle[3] = ui->cmbScatterStyle4;pCmbScatterStyle[4] = ui->cmbScatterStyle5;pCmbScatterStyle[5] = ui->cmbScatterStyle6;pCmbScatterStyle[6] = ui->cmbScatterStyle7;pCmbScatterStyle[7] = ui->cmbScatterStyle8;// 设置颜色选择框的初始背景颜色,与曲线同步颜色for(int i=0; i<8; i++){pBtnColourCurve[i]->setStyleSheet(QString("border:0px solid;background-color: %1;").arg(QColor(pCurve[i]->pen().color()).name()));}// 可见性选择框关联for(int i=0; i<8; i++){connect(pChkVisibleCurve[i], &QCheckBox::clicked, [=](){curveSetVisible(pPlot1, pCurve[i], pChkVisibleCurve[i]->checkState());});}// 颜色选择框关联for(int i=0; i<8; i++){connect(pBtnColourCurve[i], &QPushButton::clicked, [=](){curveSetColor(pPlot1, pCurve[i], pBtnColourCurve[i]);});}// 加粗显示多选框关联。尽量别用,会导致CPU使用率升高for(int i=0; i<8; i++){connect(pRdoBoldCurve[i], &QRadioButton::clicked, [=](){curveSetBold(pPlot1, pCurve[i], pRdoBoldCurve[i]->isChecked());});}// 散点样式选择关联for(int i=0; i<8; i++){connect(pCmbScatterStyle[i], &QComboBox::currentTextChanged, [=](){curveSetScatterStyle(pPlot1, pCurve[i], pCmbScatterStyle[i]->currentIndex()+1);});}//QIcon ssCircleIcon (":/pic/ssCircle.png");//ui->cmbScatterStyle1->addItem(ssCircleIcon,"空心圆");for(int i=0; i<8; i++){pCmbScatterStyle[i]->setIconSize(QSize(25,17)); // 设置图片显示像素大小,不然会默认大小显示会模糊}}/* 功能:隐藏/显示曲线n* QCustomPlot *pPlot:父控件,绘图图表* QCPGraph *pCurve:图表的曲线* int arg1:曲线的可见性,>0可见,0不可见* */
void plot::curveSetVisible(QCustomPlot *pPlot, QCPGraph *pCurve, int arg1)
{if(arg1){pCurve->setVisible(true);}else{pCurve->setVisible(false);}pPlot->replot(QCustomPlot::rpQueuedReplot);
}/* 功能:弹出颜色对话框,设置曲线n的颜色* QCustomPlot *pPlot:父控件,绘图图表* QCPGraph *pCurve:图表的曲线* QPushButton *btn:曲线颜色选择框的按键,与曲线的颜色同步* */
void plot::curveSetColor(QCustomPlot *pPlot, QCPGraph *pCurve, QPushButton *btn)
{// 获取当前颜色QColor bgColor(0,0,0);//bgColor = btn->palette().color(QPalette::Background);// 由pushButton的背景色获得颜色bgColor = pCurve->pen().color();// 由curve曲线获得颜色// 以当前颜色打开调色板,父对象,标题,颜色对话框设置项(显示Alpha透明度通道)//QColor color = QColorDialog::getColor(bgColor);QColor color = QColorDialog::getColor(bgColor, this,tr("颜色对话框"),QColorDialog::ShowAlphaChannel);// 判断返回的颜色是否合法。若点击x关闭颜色对话框,会返回QColor(Invalid)无效值,直接使用会导致变为黑色。if(color.isValid()){// 设置选择框颜色btn->setStyleSheet(QString("border:0px solid;background-color: %1;").arg(color.name()));// 设置曲线颜色QPen pen = pCurve->pen();pen.setBrush(color);pCurve->setPen(pen);}// 更新绘图pPlot->replot(QCustomPlot::rpQueuedReplot);
}/* 功能:加粗显示曲线n* QCustomPlot *pPlot:父控件,绘图图表* QCPGraph *pCurve:图表的曲线* int arg1:曲线的粗细,>0粗,0细* */
void plot::curveSetBold(QCustomPlot *pPlot, QCPGraph *pCurve, int arg1)
{// 预先读取曲线的颜色QPen pen = pCurve->pen();//pen.setBrush(pCurve->pen().color());// 由curve曲线获得颜色if(arg1){pen.setWidth(3);pCurve->setPen(pen);}else{pen.setWidth(1);pCurve->setPen(pen);}pPlot->replot(QCustomPlot::rpQueuedReplot);
}/* 功能:选择曲线样式(线,点,积)* QCustomPlot *pPlot:父控件,绘图图表* QCPGraph *pCurve:图表的曲线* int arg1:曲线样式(线,点,积)* */
void plot::curveSetLineStyle(QCustomPlot *pPlot, QCPGraph *pCurve, int arg1)
{// 设置曲线样式//customPlot->graph(19)->setLineStyle(QCPGraph::lsLine); // 数据点通过直线连接//customPlot->graph(19)->setLineStyle((QCPGraph::LineStyle)i);//设置线性//pCurve->setLineStyle(QCPGraph::LineStyle(arg1));pCurve->setLineStyle((QCPGraph::LineStyle)arg1);pPlot->replot(QCustomPlot::rpQueuedReplot);
}/* 功能:选择散点样式(空心圆、实心圆、正三角、倒三角)* QCustomPlot *pPlot:父控件,绘图图表* QCPGraph *pCurve:图表的曲线* int arg1:散点样式(空心圆、实心圆、正三角、倒三角)* */
void plot::curveSetScatterStyle(QCustomPlot *pPlot, QCPGraph *pCurve, int arg1)
{// 设置散点样式//customPlot->graph(19)->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssCircle, 5)); // 空心圆//pCurve->setScatterStyle(QCPScatterStyle::ScatterShape(arg1)); // 散点样式//pCurve->setScatterStyle((QCPScatterStyle::ScatterShape)arg1); // 散点样式if(arg1 <= 10){pCurve->setScatterStyle(QCPScatterStyle((QCPScatterStyle::ScatterShape)arg1, 5)); // 散点样式}else{ // 后面的散点图形略复杂,太小会看不清pCurve->setScatterStyle(QCPScatterStyle((QCPScatterStyle::ScatterShape)arg1, 8)); // 散点样式}pPlot->replot(QCustomPlot::rpQueuedReplot);
}// 图例显示与否
void plot::on_chkShowLegend_stateChanged(int arg1)
{if(arg1){// 显示图表的图例pPlot1->legend->setVisible(true);}else{// 不显示图表的图例pPlot1->legend->setVisible(false);}pPlot1->replot(QCustomPlot::rpQueuedReplot);
}// 绘图演示-曲线
void plot::on_chkDrawDemo_stateChanged(int arg1)
{if(arg1){timer->start(10);}else{timer->stop();}
}// 设置曲线x轴自动跟随
void plot::setAutoTrackX(QCustomPlot *pPlot)
{pointCountX = ui->txtPointCountX->text().toUInt();if(pCurve[0]->dataCount() < pointCountX){pPlot->xAxis->setRange(0,pointCountX);}else{pPlot->xAxis->setRange((pCurve[0]->dataCount()>pointCountX)?(pCurve[0]->dataCount()-pointCountX):0, pCurve[0]->dataCount());}
}// 设置曲线x轴手动设置范围(依照右下角输入框)
void plot::setManualSettingX(QCustomPlot *pPlot)
{pointOriginX = ui->txtPointOriginX->text().toInt();pointCountX = ui->txtPointCountX->text().toUInt();pPlot->xAxis->setRange(pointOriginX, pointOriginX+pointCountX);
}// 设置Y轴自适应
void plot::setAutoTrackY(QCustomPlot *pPlot)
{pPlot->graph(0)->rescaleValueAxis();// y轴自适应,可放大可缩小for(int i=0; i<8; i++){pPlot->graph(i)->rescaleValueAxis(true);// y轴自适应,只能放大}
}// 重新设置X轴显示的点数
void plot::on_txtPointCountX_returnPressed()
{if(ui->chkTrackX->checkState()){setAutoTrackX(pPlot1);}else{setManualSettingX(pPlot1);}pPlot1->replot(QCustomPlot::rpQueuedReplot);
}void plot::on_txtPointCountY_returnPressed()
{pointCountY = ui->txtPointCountY->text().toUInt();pPlot1->yAxis->setRange(pointCountY/2*-1,pointCountY/2);ui->txtPointOriginY->setText(QString::number(pointCountY/2*-1));pPlot1->replot(QCustomPlot::rpQueuedReplot);
}void plot::on_btnColourBack_clicked()
{// 获取当前颜色QColor bgColor(0,0,0);bgColor = ui->btnColourBack->palette().color(QPalette::Background);// 由pushButton的背景色获得颜色// 以当前颜色打开调色板,父对象,标题,颜色对话框设置项(显示Alpha透明度通道)//QColor color = QColorDialog::getColor(bgColor);QColor color = QColorDialog::getColor(bgColor, this,tr("颜色对话框"),QColorDialog::ShowAlphaChannel);// 判断返回的颜色是否合法。若点击x关闭颜色对话框,会返回QColor(Invalid)无效值,直接使用会导致变为黑色。if(color.isValid()){// 设置背景颜色pPlot1->setBackground(color);// 设置背景选择框颜色ui->btnColourBack->setStyleSheet(QString("border:0px solid;background-color: %1;").arg(color.name()));}// 更新绘图pPlot1->replot(QCustomPlot::rpQueuedReplot);
}void plot::on_txtPointOriginX_returnPressed()
{setManualSettingX(pPlot1);pPlot1->replot(QCustomPlot::rpQueuedReplot);
}void plot::on_chkTrackX_stateChanged(int arg1)
{if(arg1){ui->txtPointOriginX->setEnabled(false);setAutoTrackX(pPlot1);pPlot1->replot(QCustomPlot::rpQueuedReplot);}else{ui->txtPointOriginX->setEnabled(true);}
}void plot::on_chkAdjustY_stateChanged(int arg1)
{if(arg1){ui->txtPointOriginY->setEnabled(false);ui->txtPointCountY->setEnabled(false);setAutoTrackY(pPlot1);pPlot1->replot(QCustomPlot::rpQueuedReplot);}else{ui->txtPointOriginY->setEnabled(true);ui->txtPointCountY->setEnabled(true);}
}void plot::on_txtPointOriginY_returnPressed()
{pointOriginY = ui->txtPointOriginY->text().toInt();pointCountY = ui->txtPointCountY->text().toUInt();pPlot1->yAxis->setRange(pointOriginY, pointOriginY+pointCountY);qDebug() << pointOriginY << pointCountY;pPlot1->replot(QCustomPlot::rpQueuedReplot);
}// 每次图表重绘后,都会更新当前显示的原点坐标与范围。与上次不同时才会更新显示,解决有曲线数据时无法输入y的参数的问题
void plot::repPlotCoordinate()
{static int xOrigin, yOrigin, yCount;static int xOriginLast, yOriginLast, yCountLast;xOrigin = pPlot1->xAxis->range().lower;yOrigin = pPlot1->yAxis->range().lower;yCount = pPlot1->yAxis->range().size();// 与上次不同时才会更新显示,解决有曲线数据时无法输入y的参数的问题if(xOriginLast != xOrigin){ui->txtPointOriginX->setText(QString::number(xOrigin));}if(yOriginLast != yOrigin){ui->txtPointOriginY->setText(QString::number(yOrigin));}if(yCountLast != yCount){ui->txtPointCountY->setText(QString::number(yCount));}// 记录历史值xOriginLast = xOrigin;yOriginLast = yOrigin;yCountLast = yCount;
}// 清空绘图
void plot::on_btnClearGraphs_clicked()
{//pPlot1->clearGraphs(); // 清除图表的所有数据和设置,需要重新设置才能重新绘图//pPlot1->clearPlottables(); // 清除图表中所有曲线,需要重新添加曲线才能绘图for(int i=0; i<8; i++){pPlot1->graph(i)->data().data()->clear(); // 仅仅清除曲线的数据}cnt = 0;pPlot1->replot(QCustomPlot::rpQueuedReplot);
}// 设置X轴主刻度个数
void plot::on_txtMainScaleNumX_returnPressed()
{pPlot1->xAxis->ticker()->setTickCount(ui->txtMainScaleNumX->text().toUInt());pPlot1->replot(QCustomPlot::rpQueuedReplot);
}// 设置Y轴主刻度个数
void plot::on_txtMainScaleNumY_returnPressed()
{pPlot1->yAxis->ticker()->setTickCount(ui->txtMainScaleNumY->text().toUInt());pPlot1->replot(QCustomPlot::rpQueuedReplot);
}

5.4 APP 功能使用

1、Serial Port 数据接收:

2、Serial Port 数据发送(含定时功能):

3、Plot 波形绘制:

六、代码开源

代码地址: 基于QtCreator的SerialPort串口调试助手项目代码资源-CSDN下载

如果积分不够的朋友,点波关注评论区留下邮箱,作者无偿提供源码和后续问题解答。求求啦关注一波吧 !!!


文章转载自:

http://9rn4p1ZH.qcwck.cn
http://V0B1QQNM.qcwck.cn
http://5lGR5KWL.qcwck.cn
http://CsFFEFWt.qcwck.cn
http://r5tlXBTK.qcwck.cn
http://aHq08PK0.qcwck.cn
http://5MjmIEIB.qcwck.cn
http://wMIFArgb.qcwck.cn
http://252WWCRV.qcwck.cn
http://MQrVo2mO.qcwck.cn
http://S4VmrxKu.qcwck.cn
http://ksx7HRC9.qcwck.cn
http://21uukq7Y.qcwck.cn
http://VDo8UzcA.qcwck.cn
http://IPdXsCO4.qcwck.cn
http://pc1GYpgC.qcwck.cn
http://XhsgxleT.qcwck.cn
http://IQXoEdec.qcwck.cn
http://ItwUcrPE.qcwck.cn
http://kOGGhlE3.qcwck.cn
http://NYxOhyMb.qcwck.cn
http://W2Y43ZSg.qcwck.cn
http://WcaDx0iw.qcwck.cn
http://PpAuGQ5x.qcwck.cn
http://1NKta7Ug.qcwck.cn
http://KG7N2rWF.qcwck.cn
http://H7WsX7vR.qcwck.cn
http://qbdYDTQK.qcwck.cn
http://U2HWs2uN.qcwck.cn
http://J60ya27y.qcwck.cn
http://www.dtcms.com/a/382715.html

相关文章:

  • Node.js 编码规范
  • Spring Boot 调度任务在分布式环境下的坑:任务重复执行与一致性保证
  • 【数据结构】 ArrayList深入解析
  • 4. 数系
  • 08 函数式编程
  • 安卓 Google Maps 的使用和开发步骤
  • 深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)第十三章知识点问答(15题)
  • 深入理解 Spring @Async 注解:原理、实现与实践
  • 【Qt开发】显示类控件(三)-> QProgressBar
  • 《Linux——gflags》
  • leetcode35.搜索插入位置
  • Java调用UniHttp接口请求失败?一次开源的深入实践-百度SN签名认证场景下参数乱序问题的三种解决策略
  • MongoDB 监控
  • 【Linux】system V共享内存
  • --- 统一请求入口 Gateway ---
  • 豆包Seedream 4.0多图融合实力派:田园犬+三花猫多场景创作,AI绘画新时代来了!
  • 贪心算法应用:数据包调度问题详解
  • html基本知识
  • 视觉SLAM第10讲:后端2(滑动窗口与位子图优化)
  • Modbus协议原理与Go语言实现详解
  • 模型部署|将自己训练的yolov8模型在rk3568上部署
  • Vue中的slot标签——插槽
  • k8s集群—node节点的删除与添加
  • k8s的dashboard
  • k8s-容器探针和生命周期回调学习
  • 跟上大数据时代步伐:食物营养数据可视化分析系统技术前沿解析
  • 大数据毕业设计选题推荐-基于大数据的结核病数据可视化分析系统-Hadoop-Spark-数据可视化-BigData
  • 全网首发! Nvidia Jetson Thor 128GB DK 刷机与测评(三)常用功能测评 DeepAnything 系列
  • Python快速入门专业版(二十六):Python函数基础:定义、调用与返回值(Hello函数案例)
  • 【系列文章】Linux中的并发与竞争[03]-自旋锁