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

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);// 设置默认定时时长1000ms
    connect(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();    //ASCII
        array = 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,选中为2
    if(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");
        else
            str_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_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    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的名称添加到serailCb
        ui->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);// 设置默认定时时长1000ms
    connect(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");
            else
                str_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();    //ASCII
        array = 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,选中为2
    if(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

相关文章:

  • 电脑神器,轻松超越系统自带!
  • 【免费】2006-2020年各省单位GDP能耗增速数据
  • 每日学习之一万个为什么
  • MySQL的 where 1=1会不会影响性能?
  • Stable Diffusion/DALL-E 3图像生成优化策略
  • Linux:自动化构建-make/Makefile
  • 软件开发项目有哪些风险
  • Redis Sentinel (哨兵模式)深度解析:构建高可用分布式缓存系统的核心机制
  • 【大模型学习】第十七章 预训练技术综述
  • [翱捷]功能机 Wifi
  • Pygame实现射击鸭子游戏3-2
  • 根据 GPU 型号安装指定 CUDA 版本的详细步骤(附有CUDA版本对应torch版本的表格)
  • 策略模式和责任链模式的区别
  • C语言刷题第五章(下)
  • 道路运输安全员考试备考:循序渐进,稳步提升
  • 手机遥控开关技术解析与应用指南
  • 【随手笔记】中移4G记录(ML307R)
  • AutoGen学习笔记系列(十二)Advanced - Memory
  • openai-cua-sample-app - 使用计算机的 Agent示例应用
  • 我与DeepSeek读《大型网站技术架构》(11)- 海量分布式存储系统Doris的高可用架构设计分析
  • 习近平出席中国-拉美和加勒比国家共同体论坛第四届部长级会议开幕式并发表重要讲话
  • 中美大幅下调超100%关税,印巴四日“战争”复盘|907编辑部
  • 淡马锡辟谣:淡马锡和太白投资未在中国销售任何投资产品或金融工具
  • 侧记|青年为何来沪创新创业?从这一天寻找答案
  • 匈牙利外长称匈方已驱逐两名乌克兰外交官
  • 英国和美国就关税贸易协议条款达成一致