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

QT:串口上位机

创建工程

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

布局UI界面

在这里插入图片描述
设置名称

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

设置数据

设置波特率
在这里插入图片描述
波特率默认9600
在这里插入图片描述

设置数据位
在这里插入图片描述
数据位默认8
在这里插入图片描述

设置停止位
在这里插入图片描述

设置校验位
在这里插入图片描述
调整串口设置、接收设置、发送设置为Group Box

在这里插入图片描述

修改配置

QT += core gui serialport 

代码详解

mianwindow.h

首先在mianwindow.h当中定义一个串口指针

public:
QSerialPort *serialPort;//定义串口指针

并且添加头文件

#include <QMainWindow>
#include <QSerialPort>
#include <QString>
#include <QSerialPortInfo>
#include <QMessageBox>
#include <QTimer>
#include <QPainter>

QMainWindow:是 Qt 中主窗口的基类,提供了主窗口的基本功能,如菜单栏、工具栏等。
QSerialPort:用于串口通信的类,可实现与串口设备的数据交互。
QString:Qt 中用于处理字符串的类,提供了丰富的字符串操作方法。
QSerialPortInfo:用于获取系统中可用串口的信息,如串口名称、描述等。
QMessageBox:用于显示消息框,可用于提示用户信息、警告或错误。
QTimer:用于实现定时器功能,可在指定时间间隔后触发特定操作。
QPainter:用于在窗口或其他绘图设备上进行绘图操作。

private:// 发送、接收字节计数long sendNum, recvNum;QLabel *lblSendNum;QLabel *lblRecvNum;QLabel *lblPortState;void setNumOnLabel(QLabel *lbl, QString strS, long num);// 定时发送-定时器QTimer *timSend;

Ui::MainWindow *ui;:指向由 Qt Designer 生成的用户界面类的指针,用于访问和操作界面元素。
long sendNum, recvNum;:用于记录发送和接收的字节数。
QLabel *lblSendNum;、QLabel *lblRecvNum;、QLabel *lblPortState;:分别指向用于显示发送字节数、接收字节数和串口状态的 QLabel 控件。
void setNumOnLabel(QLabel *lbl, QString strS, long num);:私有成员函数,用于将指定的数字显示在 QLabel 控件上。
QTimer *timSend;:指向 QTimer 对象的指针,用于实现定时发送功能。

private slots:/*手动连接槽函数*/void manual_serialPortReadyRead();/*以下为mainwindow.ui文件中点击“转到槽”自动生成的函数*/void on_openBt_clicked();void on_sendBt_clicked();void on_clearBt_clicked();void on_btnClearSend_clicked();void on_chkTimSend_stateChanged(int arg1);void on_btnSerialCheck_clicked();

void manual_serialPortReadyRead();:手动连接的槽函数,当串口有数据可读时触发。
void on_openBt_clicked();:当 openBt 按钮被点击时触发的槽函数,通常用于打开串口。
void on_sendBt_clicked();:当 sendBt 按钮被点击时触发的槽函数,通常用于发送数据。
void on_clearBt_clicked();:当 clearBt 按钮被点击时触发的槽函数,通常用于清除接收区的数据。
void on_btnClearSend_clicked();:当 btnClearSend 按钮被点击时触发的槽函数,通常用于清除发送区的数据。
void on_chkTimSend_stateChanged(int arg1);:当 chkTimSend 复选框的状态改变时触发的槽函数,用于处理定时发送的开启和关闭。
void on_btnSerialCheck_clicked();:当 btnSerialCheck 按钮被点击时触发的槽函数,通常用于检查系统中可用的串口。

mianwindow.cpp

MainWindow::MainWindow(QWidget *parent)

    serialPort = new QSerialPort(this);connect(serialPort,SIGNAL(readyRead()),this,SLOT(manual_serialPortReadyRead()));

将串口的 readyRead() 信号与自定义的槽函数 manual_serialPortReadyRead() 进行连接。

QMetaObject::Connection QObject::connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type = Qt::AutoConnection);

sender:发送信号的对象,这里是 serialPort,即 QSerialPort 对象。
signal:发送的信号,使用 SIGNAL 宏将信号名称转换为字符串。readyRead() 是 QSerialPort 类的一个信号,当串口接收到新的数据时会自动发出该信号。
receiver:接收信号的对象,这里是 this,即 MainWindow 对象本身。
method:接收信号后要执行的槽函数,使用 SLOT 宏将槽函数名称转换为字符串。manual_serialPortReadyRead() 是在 MainWindow 类中定义的一个私有槽函数,用于处理串口接收到的数据。
type:连接类型,默认为 Qt::AutoConnection,表示根据发送者和接收者所在的线程自动选择合适的连接方式。

    ui->serailCb->clear();//通过QSerialPortInfo查找可用串口foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts()){ui->serailCb->addItem(info.portName());}

ui->serailCb->clear();:
ui 是一个指向 Ui::MainWindow 类对象的指针,Ui::MainWindow 类通常是由 Qt Designer 生成的,用于管理主窗口的用户界面元素。
serailCb 是用户界面中的一个下拉列表控件(可能是 QComboBox 类型)。
clear() 是 QComboBox 类的一个成员函数,用于清除下拉列表中的所有现有选项。这一步是为了确保在添加新的串口选项之前,下拉列表中没有其他无关的选项。
foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts()):
foreach 是 Qt 提供的一个用于遍历容器的宏。在这里,它用于遍历 QSerialPortInfo::availablePorts() 返回的可用串口信息列表。
QSerialPortInfo 是 Qt 中用于获取串口设备信息的类,例如串口名称、描述、制造商等。
availablePorts() 是 QSerialPortInfo 类的一个静态成员函数,它返回一个包含系统中所有可用串口信息的 QList 列表。
const QSerialPortInfo &info 声明了一个常量引用 info,用于在每次迭代中存储当前遍历到的串口信息对象。通过引用的方式,可以避免不必要的对象拷贝,提高效率。
ui->serailCb->addItem(info.portName());:
对于 foreach 循环中的每一个串口信息对象 info,调用 portName() 成员函数获取该串口的名称。
然后使用 ui->serailCb->addItem() 将获取到的串口名称作为一个新的选项添加到下拉列表 serailCb 中。这样,用户就可以在下拉列表中选择系统中可用的串口设备了。

    // 发送、接收计数清零sendNum = 0;recvNum = 0;// 状态栏QStatusBar *sBar = statusBar();// 状态栏的收、发计数标签lblSendNum = new QLabel(this);lblRecvNum = new QLabel(this);lblPortState = new QLabel(this);lblPortState->setText("Connected");//设置串口状态标签为绿色 表示已连接状态lblPortState->setStyleSheet("color:red");

sendNum 和 recvNum 是在 MainWindow 类中定义的用于记录发送和接收字节数的变量。

statusBar() 是 QMainWindow 类的一个成员函数,用于获取主窗口的状态栏对象。sBar 是一个指向 QStatusBar 对象的指针,通过这个指针可以对状态栏进行操作,比如添加控件、设置文本等。

lblSendNum、lblRecvNum 和 lblPortState 是在 MainWindow 类中定义的指向 QLabel 对象的指针。
new QLabel(this) 动态创建了三个 QLabel 控件,分别用于显示发送字节数、接收字节数和串口连接状态。this 作为参数传递给 QLabel 的构造函数,表示将当前 MainWindow 对象作为这些 QLabel 控件的父对象,这样当 MainWindow 对象被销毁时,这些 QLabel 控件也会被自动销毁,避免内存泄漏。

setText() 是 QLabel 类的一个成员函数,用于设置标签上显示的文本内容。将 lblPortState 标签的文本设置为 “Connected”,表示串口已经成功连接。

    // 设置标签最小大小lblSendNum->setMinimumSize(100, 20);lblRecvNum->setMinimumSize(100, 20);lblPortState->setMinimumSize(550, 20);setNumOnLabel(lblSendNum, "S: ", sendNum);setNumOnLabel(lblRecvNum, "R: ", recvNum);// 从右往左依次添加sBar->addPermanentWidget(lblPortState);sBar->addPermanentWidget(lblSendNum);sBar->addPermanentWidget(lblRecvNum);

lblSendNum、lblRecvNum 和 lblPortState 是之前创建的 QLabel 控件指针,分别用于显示发送字节数、接收字节数和串口连接状态。
setMinimumSize(int width, int height) 是 QLabel 类从 QWidget 继承而来的一个成员函数,用于设置控件的最小宽度和高度。这里将 lblSendNum 和 lblRecvNum 的最小大小设置为宽 100 像素、高 20 像素,将 lblPortState 的最小大小设置为宽 550 像素、高 20 像素。这样做可以确保在界面布局变化时,这些标签不会被压缩到小于指定的大小,保证显示内容的完整性。

setNumOnLabel 是 MainWindow 类中定义的一个私有成员函数,用于将指定的字符串和数字组合后显示在 QLabel 控件上。
对于 lblSendNum,传递的参数 "S: " 作为前缀,sendNum 是之前清零后的发送字节数计数,函数会将它们组合成一个字符串并显示在 lblSendNum 标签上,用于提示用户发送数据的字节数。
同理,对于 lblRecvNum,传递的参数 "R: " 作为前缀,recvNum 是接收字节数计数,函数会将组合后的字符串显示在 lblRecvNum 标签上,用于提示用户接收数据的字节数。
sBar 是之前通过 statusBar() 函数获取的主窗口状态栏指针。
addPermanentWidget(QWidget * widget) 是 QStatusBar 类的一个成员函数,用于将一个 QWidget 类型的控件(这里是 QLabel 控件)永久添加到状态栏中。状态栏中的控件通常按照添加的顺序从左到右排列,但由于这里注释提到 “从右往左依次添加”,实际效果是 lblPortState 在最右边,然后是 lblSendNum,最后是 lblRecvNum 在最左边。这样在状态栏中就可以依次显示串口连接状态、发送字节数和接收字节数,方便用户查看相关信息。

    // 定时发送-定时器timSend = new QTimer;timSend->setInterval(1000);// 设置默认定时时长1000msconnect(timSend, &QTimer::timeout, this, [=](){on_sendBt_clicked();});

timSend 是 MainWindow 类中定义的一个指向 QTimer 对象的指针。
new QTimer 使用 new 运算符在堆上动态创建一个 QTimer 对象,该对象用于实现定时功能。创建后,timSend 指针指向这个新创建的 QTimer 对象。
setInterval(int msec) 是 QTimer 类的一个成员函数,用于设置定时器的时间间隔,单位是毫秒(ms)。
这里将定时器的时间间隔设置为 1000 毫秒,也就是 1 秒。意味着定时器每隔 1 秒就会触发一次超时信号 timeout()。
connect 是 Qt 中用于连接信号和槽的函数,它建立了信号发送者、信号、信号接收者和槽函数之间的关联。
timSend:信号的发送者,即刚刚创建的 QTimer 对象。
&QTimer::timeout:发送的信号,timeout() 是 QTimer 类的一个信号,当定时器超时时会自动发出该信号。
this:信号的接收者,这里是 MainWindow 对象本身。
={ on_sendBt_clicked(); }:一个 Lambda 表达式,作为槽函数。[=] 表示以值捕获的方式捕获 Lambda 表达式所在作用域中的所有变量,这样 Lambda 表达式内部就可以访问这些变量。on_sendBt_clicked() 是 MainWindow 类中定义的一个槽函数,通常用于处理发送按钮被点击时的操作,比如发送数据。当定时器超时发出 timeout() 信号时,这个 Lambda 表达式会被执行,进而调用 on_sendBt_clicked() 函数,实现定时发送数据的功能。

void MainWindow::setNumOnLabel(QLabel *lbl, QString strS, long num)

void MainWindow::setNumOnLabel(QLabel *lbl, QString strS, long num)
{// 标签显示// QString strN;// strN.sprintf("%ld", num);// QString strN = strFormat.arg(num);QString strN = QString::number(num);QString str = strS + strN;lbl->setText(str);
}

函数名称:setNumOnLabel,这是 MainWindow 类的一个成员函数,用于将一个字符串前缀和一个长整型数字组合成一个新的字符串,并将其显示在指定的 QLabel 控件上。
参数:
QLabel *lbl:一个指向 QLabel 控件的指针,该函数会将组合后的字符串显示在这个 QLabel 控件上。
QString strS:一个 QString 类型的字符串,作为组合字符串的前缀。
long num:一个长整型数字,将被转换为字符串并与前缀组合。
返回值:void,表示该函数不返回任何值。

QString::number() 是 QString 类的一个静态成员函数,用于将各种数值类型(如 int、long、double 等)转换为 QString 类型的字符串。这里将传入的长整型数字 num 转换为对应的字符串,并存储在 strN 变量中。

void MainWindow::on_sendBt_clicked()

/*发送数据*/
void MainWindow::on_sendBt_clicked()
{QByteArray array;//Hex复选框if(ui->chk_send_hex->checkState() == Qt::Checked){//array = QString2Hex(data);  //HEX 16进制array = QByteArray::fromHex(ui->sendEdit->toPlainText().toUtf8()).data();}else{//array = data.toLatin1();    //ASCIIarray = ui->sendEdit->toPlainText().toLocal8Bit().data();}if(ui->chk_send_line->checkState() == Qt::Checked){array.append("\r\n");}// 如发送成功,会返回发送的字节长度。失败,返回-1。int a = serialPort->write(array);// 发送字节计数并显示if(a > 0){// 发送字节计数sendNum += a;// 状态栏显示计数值setNumOnLabel(lblSendNum, "S: ", sendNum);}
}

QByteArray 是 Qt 中用于处理二进制数据的类,这里定义了一个 QByteArray 类型的对象 array,用于存储要发送的数据。
ui->chk_send_hex 是界面上的一个复选框,用于表示是否以十六进制格式发送数据。
如果该复选框被选中(checkState() == Qt::Checked),则将发送编辑框(ui->sendEdit)中的文本内容先转换为 UTF - 8 编码的 QByteArray,再使用 QByteArray::fromHex() 方法将其解析为十六进制数据,存储到 array 中。
如果该复选框未被选中,则将发送编辑框中的文本内容转换为本地编码(toLocal8Bit())的 QByteArray 并存储到 array 中。

ui->chk_send_line 是界面上的另一个复选框,用于表示是否在发送数据末尾添加换行符(\r\n)。
如果该复选框被选中,则使用 append() 方法将换行符添加到 array 末尾。

serialPort 是 MainWindow 类中定义的 QSerialPort 指针,用于串口通信。
write(const QByteArray &data) 是 QSerialPort 类的成员函数,用于向串口发送数据。该函数返回实际发送的字节数,如果发送失败则返回 -1。这里将发送的字节数存储在变量 a 中。

如果发送成功(a > 0),则将实际发送的字节数 a 累加到 sendNum 变量中,sendNum 用于记录总的发送字节数。
调用 setNumOnLabel() 函数将更新后的发送字节数显示在状态栏的 lblSendNum 标签上,标签前缀为 "S: "。

void MainWindow::on_openBt_clicked()

QSerialPort::BaudRate baudRate;
QSerialPort::DataBits dataBits;
QSerialPort::StopBits stopBits;
QSerialPort::Parity checkBits;

定义了四个变量,分别用于存储串口的波特率、数据位、停止位和奇偶校验位的设置。它们的类型是 QSerialPort 类中定义的枚举类型,用于准确表示串口通信的不同参数设置。

if(ui->baundrateCb->currentText()=="1200")baudRate=QSerialPort::Baud1200;
// 其他波特率判断代码...
else if(ui->baundrateCb->currentText()=="115200")baudRate=QSerialPort::Baud115200;

通过检查界面上波特率下拉框(ui->baundrateCb)当前选中的文本内容,来确定对应的波特率枚举值并赋值给 baudRate 变量。根据不同的文本内容,设置相应的波特率枚举值,如 Baud1200、Baud2400 等。

if(ui->databitCb->currentText()=="5")dataBits=QSerialPort::Data5;
// 其他数据位判断代码...
else if(ui->databitCb->currentText()=="8")dataBits=QSerialPort::Data8;

通过检查数据位下拉框(ui->databitCb)的当前文本内容,确定对应的串口数据位枚举值并赋值给 dataBits 变量,如 Data5、Data6 等。

if(ui->stopbitCb->currentText()=="1")stopBits=QSerialPort::OneStop;
// 其他停止位判断代码...
else if(ui->stopbitCb->currentText()=="2")stopBits=QSerialPort::TwoStop;

过检查停止位下拉框(ui->stopbitCb)的当前文本内容,确定对应的串口停止位枚举值并赋值给 stopBits 变量,如 OneStop、OneAndHalfStop 等。

if(ui->checkbitCb->currentText() == "none"){checkBits = QSerialPort::NoParity;
}// 其他奇偶校验位判断代码...
else if(ui->checkbitCb->currentText() == "偶校验"){checkBits = QSerialPort::EvenParity;
}

通过检查奇偶校验位下拉框(ui->checkbitCb)的当前文本内容,确定对应的串口奇偶校验位枚举值并赋值给 checkBits 变量,如 NoParity、OddParity 等。

serialPort->setPortName(ui->serailCb->currentText());
serialPort->setBaudRate(baudRate);
serialPort->setDataBits(dataBits);
serialPort->setStopBits(stopBits);
serialPort->setParity(checkBits);

使用之前获取到的串口参数,设置 serialPort 对象的属性。包括设置串口端口号(从端口号下拉框 ui->serailCb 获取)、波特率、数据位、停止位和奇偶校验位。

if(ui->openBt->text() == "打开串口"){if(serialPort->open(QIODevice::ReadWrite) == true){ui->openBt->setText("关闭串口");ui->serailCb->setEnabled(false);}else{QMessageBox::critical(this, "错误提示", "串口打开失败!!!\r\n该串口可能被占用\r\n请选择正确的串口");}// 状态栏显示端口状态代码...lblPortState->setText(status);lblPortState->setStyleSheet("color:green");
}else{serialPort->close();ui->openBt->setText("打开串口");ui->serailCb->setEnabled(true);// 状态栏显示端口状态代码...lblPortState->setText(status);lblPortState->setStyleSheet("color:red");
}

根据打开按钮(ui->openBt)当前的文本内容判断是要打开还是关闭串口:
如果按钮文本是 “打开串口”,则尝试以读写模式(QIODevice::ReadWrite)打开串口。如果打开成功,将按钮文本改为 “关闭串口”,并禁用端口号下拉框(ui->serailCb),同时在状态栏(lblPortState)显示串口已打开的状态信息,且设置状态栏文本颜色为绿色。如果打开失败,弹出一个错误提示框,显示串口打开失败的原因。
如果按钮文本是 “关闭串口”,则关闭串口,将按钮文本改回 “打开串口”,启用端口号下拉框,并在状态栏显示串口已关闭的状态信息,设置状态栏文本颜色为红色。

void MainWindow::on_clearBt_clicked()

/*清空接收*/
void MainWindow::on_clearBt_clicked()
{ui->recvEdit->clear();// 清除发送、接收字节计数sendNum = 0;recvNum = 0;// 状态栏显示计数值setNumOnLabel(lblSendNum, "S: ", sendNum);setNumOnLabel(lblRecvNum, "R: ", recvNum);
}

setNumOnLabel 是 MainWindow 类中定义的一个私有成员函数,用于将指定的前缀字符串和计数值组合成一个新的字符串,并将其显示在指定的 QLabel 控件上。
lblSendNum 和 lblRecvNum 是指向 QLabel 控件的指针,分别用于在状态栏上显示发送和接收字节的计数信息。
"S: " 和 "R: " 是前缀字符串,分别表示 “发送” 和 “接收”。
sendNum 和 recvNum 是当前的发送和接收字节计数

on_clearBt_clicked 函数的主要作用是为用户提供一种清除接收数据和重置计数信息的方式。当用户点击相应的清除按钮时,该函数会清空接收编辑框中的内容,将发送和接收字节的计数归零,并更新状态栏上的计数显示,以便用户重新开始统计和查看数据。

void MainWindow::on_btnClearSend_clicked()

void MainWindow::on_btnClearSend_clicked()
{ui->sendEdit->clear();// 清除发送字节计数sendNum = 0;// 状态栏显示计数值setNumOnLabel(lblSendNum, "S: ", sendNum);
}

void MainWindow::on_chkTimSend_stateChanged(int arg1)

// 定时发送开关 选择复选框
void MainWindow::on_chkTimSend_stateChanged(int arg1)
{// 获取复选框状态,未选为0,选中为2if(arg1 == 0){timSend->stop();// 时间输入框恢复可选ui->txtSendMs->setEnabled(true);}else{// 对输入的值做限幅,小于10ms会弹出对话框提示if(ui->txtSendMs->text().toInt() >= 10){timSend->start(ui->txtSendMs->text().toInt());// 设置定时时长,重新计数// 让时间输入框不可选,避免误操作(输入功能不可用,控件背景为灰色)ui->txtSendMs->setEnabled(false);}else{ui->chkTimSend->setCheckState(Qt::Unchecked);QMessageBox::critical(this, "错误提示", "定时发送的最小间隔为 10ms\r\n请确保输入的值 >=10");}}
}

arg1 == 0 表示复选框处于未选中状态。
timSend 是 QTimer 类型的对象指针,用于实现定时发送功能。timSend->stop() 会停止定时器,即停止定时发送的操作。
ui->txtSendMs 是界面上用于输入定时时间(毫秒)的文本框。setEnabled(true) 方法将该文本框设置为可编辑状态,允许用户修改定时时间。
当 arg1 不等于 0 时,表示复选框被选中。
ui->txtSendMs->text().toInt() 会将文本框中输入的文本转换为整数,代表用户设置的定时时间(毫秒)。
如果该值大于等于 10 毫秒,timSend->start(ui->txtSendMs->text().toInt()) 会启动定时器,并将定时时间设置为用户输入的值,开始重新计数。同时,ui->txtSendMs->setEnabled(false) 会将文本框设置为不可编辑状态,防止用户在定时发送功能开启后误修改定时时间。
如果该值小于 10 毫秒,ui->chkTimSend->setCheckState(Qt::Unchecked) 会将复选框重新设置为未选中状态,QMessageBox::critical 会弹出一个错误提示框,告知用户定时发送的最小间隔为 10 毫秒,并提醒用户确保输入的值大于等于 10。

void MainWindow::manual_serialPortReadyRead()

QByteArray recBuf = serialPort->readAll();
QString str_rev;// 接收字节计数
recvNum += recBuf.size();
// 状态栏显示计数值
setNumOnLabel(lblRecvNum, "R: ", recvNum);

serialPort->readAll() 会读取串口缓冲区中的所有数据,并将其存储在 QByteArray 类型的 recBuf 中。
recvNum 是一个用于记录接收字节总数的变量,recBuf.size() 表示本次接收到的数据字节数,将其累加到 recvNum 中。
setNumOnLabel 函数用于将更新后的接收字节数显示在状态栏的 lblRecvNum 标签上。

if(ui->chk_rev_hex->checkState() == false){if(ui->chk_rev_time->checkState() == Qt::Checked){QDateTime nowtime = QDateTime::currentDateTime();str_rev = "[" + nowtime.toString("yyyy-MM-dd hh:mm:ss") + "] ";str_rev += QString(recBuf).append("\r\n");}else{if(ui->chk_rev_line->checkState() == Qt::Checked){str_rev = QString(recBuf).append("\r\n");}else{str_rev = QString(recBuf);}}
}

ui->chk_rev_hex 是一个复选框,用于控制是否以十六进制显示接收到的数据。如果该复选框未被选中(checkState() == false),则按非十六进制方式处理数据。
若 ui->chk_rev_time 复选框被选中,会获取当前时间并格式化为 yyyy-MM-dd hh:mm:ss 的字符串,添加到 str_rev 中,然后将接收到的数据转换为 QString 类型并添加换行符后追加到 str_rev 中。
若 ui->chk_rev_time 未被选中,再根据 ui->chk_rev_line 复选框的状态决定是否添加换行符。

else{// 16进制显示,并转换为大写QString str1 = recBuf.toHex().toUpper();// 添加空格QString str2;for(int i = 0; i<str1.length (); i+=2){str2 += str1.mid (i,2);str2 += " ";}if(ui->chk_rev_time->checkState() == Qt::Checked){QDateTime nowtime = QDateTime::currentDateTime();str_rev = "[" + nowtime.toString("yyyy-MM-dd hh:mm:ss") + "] ";str_rev += str2.append("\r\n");}else{if(ui->chk_rev_line->checkState() == Qt::Checked)str_rev += str2.append("\r\n");elsestr_rev = str2;}
}

若 ui->chk_rev_hex 复选框被选中,则按十六进制方式处理数据。
recBuf.toHex().toUpper() 将接收到的数据转换为十六进制字符串并转换为大写。
通过循环在每两个十六进制字符之间添加一个空格。
同样根据 ui->chk_rev_time 和 ui->chk_rev_line 复选框的状态决定是否添加时间戳和换行符。

ui->recvEdit->insertPlainText(str_rev);
ui->recvEdit->moveCursor(QTextCursor::End);

ui->recvEdit 是接收编辑框,insertPlainText(str_rev) 将处理后的字符串 str_rev 插入到编辑框中。
moveCursor(QTextCursor::End) 将编辑框的光标移动到文本末尾,确保新接收到的数据能及时显示在界面上,避免界面不滚动的问题。

mainwindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>
#include <QSerialPort>
#include <QString>
#include <QSerialPortInfo>
#include <QMessageBox>
#include <QTimer>
#include <QPainter>QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACEclass MainWindow : public QMainWindow
{Q_OBJECTpublic:MainWindow(QWidget *parent = nullptr);~MainWindow();QSerialPort *serialPort;//定义串口指针private slots:/*手动连接槽函数*/void manual_serialPortReadyRead();/*以下为mainwindow.ui文件中点击“转到槽”自动生成的函数*/void on_openBt_clicked();void on_sendBt_clicked();void on_clearBt_clicked();void on_btnClearSend_clicked();void on_chkTimSend_stateChanged(int arg1);void on_btnSerialCheck_clicked();private:Ui::MainWindow *ui;// 发送、接收字节计数long sendNum, recvNum;QLabel *lblSendNum;QLabel *lblRecvNum;QLabel *lblPortState;void setNumOnLabel(QLabel *lbl, QString strS, long num);// 定时发送-定时器QTimer *timSend;//QTimer *timCheckPort;
};
#endif // MAINWINDOW_H

mainwindow.cpp

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "QSerialPortInfo"
#include <QSerialPort>
#include <QMessageBox>
#include <QDateTime>MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);QStringList serialNamePort;serialPort = new QSerialPort(this);//当串口接收到新的数据时,QSerialPort 对象会发出 readyRead() 信号,MainWindow 对象会接收到该信号并调用 manual_serialPortReadyRead() 槽函数来处理接收到的数据。connect(serialPort,SIGNAL(readyRead()),this,SLOT(manual_serialPortReadyRead()));/*手动连接槽函数*/ui->serailCb->clear();//清除下拉列表中的所有现有选项//通过QSerialPortInfo查找可用串口//foreach 用于遍历 QSerialPortInfo foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts()){//将遍历到info的名称添加到serailCbui->serailCb->addItem(info.portName());}// 发送、接收计数清零//sendNum 和 recvNum 是在 MainWindow 类中定义的用于记录发送和接收字节数的变量。sendNum = 0;recvNum = 0;// 状态栏QStatusBar *sBar = statusBar();// 状态栏的收、发计数标签lblSendNum = new QLabel(this);lblRecvNum = new QLabel(this);lblPortState = new QLabel(this);lblPortState->setText("Connected");//设置串口状态标签为红色 表示未连接状态lblPortState->setStyleSheet("color:red");// 设置标签最小大小lblSendNum->setMinimumSize(100, 20);lblRecvNum->setMinimumSize(100, 20);lblPortState->setMinimumSize(550, 20);setNumOnLabel(lblSendNum, "S: ", sendNum);setNumOnLabel(lblRecvNum, "R: ", recvNum);// 从右往左依次添加sBar->addPermanentWidget(lblPortState);sBar->addPermanentWidget(lblSendNum);sBar->addPermanentWidget(lblRecvNum);// 定时发送-定时器timSend = new QTimer;//设置定时器的时间间隔,单位是毫秒(ms)。timSend->setInterval(1000);// 设置默认定时时长1000msconnect(timSend, &QTimer::timeout, this, [=](){on_sendBt_clicked();});
}MainWindow::~MainWindow()
{delete ui;
}//检测通讯端口槽函数
void MainWindow::on_btnSerialCheck_clicked()
{ui->serailCb->clear();//通过QSerialPortInfo查找可用串口foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts()){ui->serailCb->addItem(info.portName());}
}/*手动实现接收数据函数*/
void MainWindow::manual_serialPortReadyRead()
{QByteArray recBuf = serialPort->readAll();;QString str_rev;// 打印接收到的原始数据qDebug() << "Received raw data:" << recBuf;// 接收字节计数recvNum += recBuf.size();// 状态栏显示计数值setNumOnLabel(lblRecvNum, "R: ", recvNum);if(ui->chk_rev_hex->checkState() == false){if(ui->chk_rev_time->checkState() == Qt::Checked){QDateTime nowtime = QDateTime::currentDateTime();str_rev = "[" + nowtime.toString("yyyy-MM-dd hh:mm:ss") + "] ";str_rev += QString(recBuf).append("\r\n");}else{// 在当前位置插入文本,不会发生换行。如果没有移动光标到文件结尾,会导致文件超出当前界面显示范围,界面也不会向下滚动。//ui->recvEdit->appendPlainText(buf);if(ui->chk_rev_line->checkState() == Qt::Checked){str_rev = QString(recBuf).append("\r\n");}else{str_rev = QString(recBuf);}}}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 += " ";}if(ui->chk_rev_time->checkState() == Qt::Checked){QDateTime nowtime = QDateTime::currentDateTime();str_rev = "[" + nowtime.toString("yyyy-MM-dd hh:mm:ss") + "] ";str_rev += str2.append("\r\n");}else{if(ui->chk_rev_line->checkState() == Qt::Checked)str_rev += str2.append("\r\n");elsestr_rev = str2;}}ui->recvEdit->insertPlainText(str_rev);ui->recvEdit->moveCursor(QTextCursor::End);// 打印处理后的数据qDebug() << "Processed data:" << str_rev;
}/*打开串口*/
void MainWindow::on_openBt_clicked()
{/*串口初始化*/QSerialPort::BaudRate baudRate;QSerialPort::DataBits dataBits;QSerialPort::StopBits stopBits;QSerialPort::Parity checkBits;// 获取串口波特率// baudRate = ui->baundrateCb->currentText().toInt();直接字符串转换为 int 的方法if(ui->baundrateCb->currentText()=="1200")baudRate=QSerialPort::Baud1200;else if(ui->baundrateCb->currentText()=="2400")baudRate=QSerialPort::Baud2400;else if(ui->baundrateCb->currentText()=="4800")baudRate=QSerialPort::Baud4800;else if(ui->baundrateCb->currentText()=="9600")baudRate=QSerialPort::Baud9600;else if(ui->baundrateCb->currentText()=="19200")baudRate=QSerialPort::Baud19200;else if(ui->baundrateCb->currentText()=="38400")baudRate=QSerialPort::Baud38400;else if(ui->baundrateCb->currentText()=="57600")baudRate=QSerialPort::Baud57600;else if(ui->baundrateCb->currentText()=="115200")baudRate=QSerialPort::Baud115200;// 获取串口数据位if(ui->databitCb->currentText()=="5")dataBits=QSerialPort::Data5;else if(ui->databitCb->currentText()=="6")dataBits=QSerialPort::Data6;else if(ui->databitCb->currentText()=="7")dataBits=QSerialPort::Data7;else if(ui->databitCb->currentText()=="8")dataBits=QSerialPort::Data8;// 获取串口停止位if(ui->stopbitCb->currentText()=="1")stopBits=QSerialPort::OneStop;else if(ui->stopbitCb->currentText()=="1.5")stopBits=QSerialPort::OneAndHalfStop;else if(ui->stopbitCb->currentText()=="2")stopBits=QSerialPort::TwoStop;// 获取串口奇偶校验位if(ui->checkbitCb->currentText() == "none"){checkBits = QSerialPort::NoParity;}else if(ui->checkbitCb->currentText() == "奇校验"){checkBits = QSerialPort::OddParity;}else if(ui->checkbitCb->currentText() == "偶校验"){checkBits = QSerialPort::EvenParity;}else{}// 初始化串口属性,设置 端口号、波特率、数据位、停止位、奇偶校验位数serialPort->setPortName(ui->serailCb->currentText());serialPort->setBaudRate(baudRate);serialPort->setDataBits(dataBits);serialPort->setStopBits(stopBits);serialPort->setParity(checkBits);// 根据初始化好的串口属性,打开串口// 如果打开成功,反转打开按钮显示和功能。打开失败,无变化,并且弹出错误对话框。if(ui->openBt->text() == "打开串口"){if(serialPort->open(QIODevice::ReadWrite) == true){//QMessageBox::ui->openBt->setText("关闭串口");// 让端口号下拉框不可选,避免误操作(选择功能不可用,控件背景为灰色)ui->serailCb->setEnabled(false);}else{QMessageBox::critical(this, "错误提示", "串口打开失败!!!\r\n该串口可能被占用\r\n请选择正确的串口");}//statusBar 状态栏显示端口状态QString sm = "%1 OPENED, %2, 8, NONE, 1";QString status = sm.arg(serialPort->portName()).arg(serialPort->baudRate());lblPortState->setText(status);lblPortState->setStyleSheet("color:green");}else{serialPort->close();ui->openBt->setText("打开串口");// 端口号下拉框恢复可选,避免误操作ui->serailCb->setEnabled(true);//statusBar 状态栏显示端口状态QString sm = "%1 CLOSED";QString status = sm.arg(serialPort->portName());lblPortState->setText(status);lblPortState->setStyleSheet("color:red");}}/*发送数据*/
void MainWindow::on_sendBt_clicked()
{QByteArray array;//Hex复选框if(ui->chk_send_hex->checkState() == Qt::Checked){//array = QString2Hex(data);  //HEX 16进制array = QByteArray::fromHex(ui->sendEdit->toPlainText().toUtf8()).data();}else{//array = data.toLatin1();    //ASCIIarray = ui->sendEdit->toPlainText().toLocal8Bit().data();}if(ui->chk_send_line->checkState() == Qt::Checked){array.append("\r\n");}// 打印需要发送的数据qDebug() << "Data to be sent:" << array;// 如发送成功,会返回发送的字节长度。失败,返回-1。int a = serialPort->write(array);// 发送字节计数并显示if(a > 0){// 发送字节计数sendNum += a;// 状态栏显示计数值setNumOnLabel(lblSendNum, "S: ", sendNum);}
}
// 状态栏标签显示计数值
void MainWindow::setNumOnLabel(QLabel *lbl, QString strS, long num)
{// 标签显示// QString strN;// strN.sprintf("%ld", num);// QString strN = strFormat.arg(num);QString strN = QString::number(num);QString str = strS + strN;lbl->setText(str);
}
/*清空接收*/
void MainWindow::on_clearBt_clicked()
{ui->recvEdit->clear();// 清除发送、接收字节计数sendNum = 0;recvNum = 0;// 状态栏显示计数值setNumOnLabel(lblSendNum, "S: ", sendNum);setNumOnLabel(lblRecvNum, "R: ", recvNum);
}void MainWindow::on_btnClearSend_clicked()
{ui->sendEdit->clear();// 清除发送字节计数sendNum = 0;// 状态栏显示计数值setNumOnLabel(lblSendNum, "S: ", sendNum);
}
// 定时发送开关 选择复选框
void MainWindow::on_chkTimSend_stateChanged(int arg1)
{// 获取复选框状态,未选为0,选中为2if(arg1 == 0){timSend->stop();// 时间输入框恢复可选ui->txtSendMs->setEnabled(true);}else{// 对输入的值做限幅,小于10ms会弹出对话框提示if(ui->txtSendMs->text().toInt() >= 10){timSend->start(ui->txtSendMs->text().toInt());// 设置定时时长,重新计数// 让时间输入框不可选,避免误操作(输入功能不可用,控件背景为灰色)ui->txtSendMs->setEnabled(false);}else{ui->chkTimSend->setCheckState(Qt::Unchecked);QMessageBox::critical(this, "错误提示", "定时发送的最小间隔为 10ms\r\n请确保输入的值 >=10");}}
}

参考

https://blog.csdn.net/weixin_44788542/article/details/130508621

相关文章:

QT:串口上位机

创建工程 布局UI界面 设置名称 设置数据 设置波特率 波特率默认9600 设置数据位 数据位默认8 设置停止位 设置校验位 调整串口设置、接收设置、发送设置为Group Box 修改配置 QT core gui serialport 代码详解 mianwindow.h 首先在mianwindow.h当中定义一个串口指…...

C++跨平台开发环境搭建全指南:工具链选型与性能优化实战

C跨平台开发环境搭建全指南&#xff1a;工具链选型与性能优化实战 目录 开发环境搭建工具链选型性能优化实战常见问题排查 开发环境搭建 操作系统环境准备 Windows# 安装Visual Studio Build Tools choco install visualstudio2022buildtools choco install cmake --instal…...

数据批处理(队列方式)

数据批处理&#xff08;队列方式&#xff09; public class DataProcessor {private static final int THREAD_COUNT 4;private static final int QUEUE_SIZE 10;private LinkedBlockingQueue<Data> queue new LinkedBlockingQueue<>(QUEUE_SIZE);public DataP…...

win32汇编环境,网络编程入门之二

;运行效果 ;win32汇编环境,网络编程入门之二 ;本教程在前一教程的基础上&#xff0c;研究一下如何得到服务器的返回的信息 ;正常的逻辑是连接上了&#xff0c;然后我发送什么&#xff0c;它返回什么&#xff0c;但是这有一个很尴尬的问题。 ;就是如何表现出来。因为网络可能有延…...

MATLAB—从入门到精通的第二天

在第一天的学习中&#xff0c;我们掌握了 MATLAB 的安装配置、基础语法、变量管理和运算符的使用。本文将深入讲解 控制结构&#xff08;嵌套 if、switch&#xff09;、循环类型 和 向量操作&#xff0c;帮助读者进一步掌握 MATLAB 的核心编程技能。 1. 条件语句进阶 1.1 嵌套…...

【认识OpenThread协议】

OpenThread 是一种基于 IPv6 、IEEE 802.15.4 标准的低功耗无线 Mesh 网络协议&#xff0c;主要用于智能家居、物联网设备等场景。它的设计目标是实现设备之间的高效通信、低功耗运行和高可靠性。 OpenThread官方文档 ① 特性 低功耗: 适合电池供电的设备。 Mesh 网络: 支持多…...

驱动开发系列46 - Linux 显卡KMD驱动代码分析(七)- 显存管理

目录 一:概述 二:应用程序和UMD调用栈 三:KMD 显存分配和和映射过程 一:概述 显存管理是图形驱动程序中至关重要的一部分,涉及到从用户空间(UMD,User Mode Driver)到内核空间(KMD,Kernel Mode Driver)的显存分配和管理。本文将首先梳理从一个 OpenGL 应…...

MATLAB代码开发实战:从入门到高效应用

一、MATLAB生态系统的核心优势 &#xff08;扩展原有内容&#xff0c;增加行业数据&#xff09; MATLAB在全球工程领域的市场占有率已达67%&#xff08;2024年IEEE统计&#xff09;&#xff0c;其核心优势体现在&#xff1a; 矩阵运算速度比传统编程快3-5倍包含22个专业工具箱…...

为什么 NFS 不适合作为 TDengine 的数据存储

NFS NFS 是一种分布式文件系统&#xff0c;允许多台计算机通过网络共享文件。其具有以下优点&#xff1a; 共享存储: 多个数据库实例可以共享同一个 NFS 目录&#xff0c;适合分布式数据库或集群环境。灵活性: 数据存储可以集中管理&#xff0c;便于备份和迁移。成本低: 利用…...

办公常用自动化工具

自动化办公工具说明文档 代码全部在底部。 文件批量重命名工具 (file_renamer.py) 功能概述 file_renamer.py 是一个用于批量重命名文件的工具&#xff0c;可以根据自定义规则为文件重命名&#xff0c;支持按日期、序号、原文件名等格式进行命名。 主要功能 支持按文件类…...

字节跳动 —— 建筑物组合(滑动窗口+溢出问题)

原题描述&#xff1a; 题目精炼&#xff1a; 给定N个建筑物的位置和一个距离D&#xff0c;选取3个建筑物作为埋伏点&#xff0c;找出所有可能的建筑物组合&#xff0c;使得每组中的建筑物之间的最大距离不超过D。最后&#xff0c;输出不同埋伏方案的数量并对99997867取模。 识…...

开源数字人模型Heygem

一、Heygem是什么 Heygem 是硅基智能推出的开源数字人模型&#xff0c;专为 Windows 系统设计。基于先进的AI技术&#xff0c;仅需1秒视频或1张照片&#xff0c;能在30秒内完成数字人形象和声音克隆&#xff0c;在60秒内合成4K超高清视频。Heygem支持多语言输出、多表情动作&a…...

Linux远程工具SecureCRT下载安装和使用

SecureCRT下载安装和使用 SecureCRT是一款功能强大的终端仿真软件&#xff0c;它支持SSH、Telnet等多种协议&#xff0c;可以连接和管理基于Unix和Windows的远程主机和网络设备。SecureCRT提供了语法高亮、多标签页管理、会话管理、脚本编辑等便捷功能&#xff0c;安全性高、操…...

从前端视角理解消息队列:核心问题与实战指南

消息队列&#xff08;Message Queue&#xff09;是现代分布式系统的核心组件之一&#xff0c;它在前后端协作、系统解耦、流量削峰等场景中发挥着重要作用。本文从前端开发者视角出发&#xff0c;解析消息队列的关键问题&#xff0c;并结合实际场景给出解决方案。 一、为什么要…...

Android 线程池实战指南:高效管理多线程任务

在 Android 开发中&#xff0c;线程池的使用非常重要&#xff0c;尤其是在需要处理大量异步任务时。线程池可以有效地管理线程资源&#xff0c;避免频繁创建和销毁线程带来的性能开销。以下是线程池的使用方法和最佳实践。 1. 线程池的基本使用 &#xff08;1&#xff09;创建线…...

CentOS7下安装MongoDB

步骤 1&#xff1a;创建 MongoDB Yum 仓库文件 你需要创建一个 MongoDB 的 Yum 仓库配置文件&#xff0c;以便从官方源下载 MongoDB。打开终端并使用以下命令创建并编辑该文件&#xff1a; sudo vi /etc/yum.repos.d/mongodb-org-7.0.repo 在打开的文件中&#xff0c;输入以下…...

江科大51单片机笔记【15】直流电机驱动(PWM)

写在前言 此为博主自学江科大51单片机&#xff08;B站&#xff09;的笔记&#xff0c;方便后续重温知识 在后面的章节中&#xff0c;为了防止篇幅过长和易于查找&#xff0c;我把一个小节分成两部分来发&#xff0c;上章节主要是关于本节课的硬件介绍、电路图、原理图等理论…...

【网络协议详解】——QOS技术(学习笔记)

目录 QoS简介 QoS产生的背景 QoS服务模型 基于DiffServ模型的QoS组成 MQC简介 MQC三要素 MQC配置流程 优先级映射配置(DiffServ域模式) 优先级映射概述 优先级映射原理描述 优先级映射 PHB行为 流量监管、流量整形和接口限速简介 流量监管 流量整形 接口限速…...

【工具使用】IDEA 社区版如何创建 Spring Boot 项目(详细教程)

IDEA 社区版如何创建 Spring Boot 项目&#xff08;详细教程&#xff09; Spring Boot 以其简洁、高效的特性&#xff0c;成为 Java 开发的主流框架之一。虽然 IntelliJ IDEA 专业版提供了Spring Boot 项目向导&#xff0c;但 社区版&#xff08;Community Edition&#xff09…...

基于Prometheus+Grafana的Deepseek性能监控实战

文章目录 1. 为什么需要专门的大模型监控?2. 技术栈组成2.1 vLLM(推理引擎层)2.2 Prometheus(监控采集层)2.3 Grafana(数据可视化平台)3. 监控系统架构4. 实施步骤4.1 启动DeepSeek-R1模型4.2 部署 Prometheus4.2.1 拉取镜像4.2.2 编写配置文件4.2.3 启动容器4.3 部署 G…...

Spring学习笔记:工厂模式与反射机制实现解耦

1.什么是Spring? spring是一个开源轻量级的java开发应用框架&#xff0c;可以简化企业级应用开发 轻量级 1.轻量级(对于运行环境没有额外要求) 2.代码移植性高(不需要实现额外接口) JavaEE的解决方案 Spring更像是一种解决方案&#xff0c;对于控制层&#xff0c;它有Spring…...

pytest数据库测试文章推荐

参考链接&#xff1a; 第一部分&#xff1a;http://alextechrants.blogspot.fi/2013/08/unit-testing-sqlalchemy-apps.html第二部分&#xff1a;http://alextechrants.blogspot.fi/2014/01/unit-testing-sqlalchemy-apps-part-2.html...

vue3 二次封装uni-ui中的组件,并且组件中有 v-model 的解决方法

在使用uniappvue3开发中&#xff0c; 使用了uni-ui的组件&#xff0c;但是我们也需要自定义组件&#xff0c;比如我要自定一个picker 的组件&#xff0c; 是在 uni-data-picker 组件的基础上进行封装的 父组件中的代码 <classesselect :selectclass"selectclass"…...

探索高性能AI识别和边缘计算 | NVIDIA Jetson Orin Nano 8GB 开发套件的全面测评

随着边缘计算和人工智能技术的迅速发展&#xff0c;性能强大的嵌入式AI开发板成为开发者和企业关注的焦点。NVIDIA近期推出的Jetson Orin Nano 8GB开发套件&#xff0c;凭借其40 TOPS算力、高效的Ampere架构GPU以及出色的边缘AI能力&#xff0c;引起了广泛关注。本文将从配置性…...

Prompt 工程

一、提示原則 import openai import os import openai from dotenv import load_dotenv, find_dotenv from openai import OpenAI def get_openai_key():_ load_dotenv(find_dotenv())return os.environ[OPENAI_API_KEY]client OpenAI(api_keyget_openai_key(), # This is …...

【学习笔记】《逆向工程核心原理》03.abex‘crackme-2、函数的调用约定、视频讲座-Tut.ReverseMe1

文章目录 abexcrackme-21. Visual Basic文件的特征1.1. VB专用引擎1.2. 本地代码与伪代码1.3. 事件处理程序1.4. 未文档化的结构体 2. 开始调试2.1. 间接调用2.2. RT_MainStruct结构体2.3. ThunRTMain()函数 3. 分析crackme3.1. 检索字符串3.2. 查找字符串地址3.3. 生成Serial的…...

React基础之项目实战

规范的项目结构 安装scss npm install sass -D 安装Ant Design组件库 内置了一些常用的组件 npm install antd --save 路由基础配置 npm i react-router-dom 路由基本入口 import Layout from "../page/Layout"; import Login from "../page/Login"; impor…...

SAP-ABAP:SAP数据库视图的创建图文详解

在SAP ABAP中&#xff0c;数据库视图&#xff08;Database View&#xff09;是通过ABAP字典&#xff08;ABAP Dictionary&#xff09;创建的。数据库视图是基于一个或多个数据库表的虚拟表&#xff0c;它允许你定义一种逻辑视图来访问数据。以下是创建数据库视图的步骤&#xf…...

基于深度学习的肺炎X光影像自动诊断系统实现,真实操作案例分享,值得学习!

医疗影像智能化的技术演进 医学影像分析正经历从人工判读到AI辅助诊断的革命性转变。传统放射科医师分析胸部X光片需要8-12年专业训练&#xff0c;而基于深度学习的智能系统可在秒级完成检测。本文将以肺炎X光检测为切入点&#xff0c;详解从数据预处理到模型部署的全流程实现。…...

Unity Shader学习总结

1.帧缓冲区和颜色缓冲区区别 用于存储每帧每个像素颜色信息的缓冲区 帧缓冲区包括&#xff1a;颜色缓冲区 深度缓冲区 模板缓冲区 自定义缓冲区 2.ImageEffectShader是什么 后处理用的shader模版 3.computerShader 独立于渲染管线之外&#xff0c;在显卡上运行&#xff0c;大量…...