QT:串口上位机

news2025/3/14 0:55:59

创建工程

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

布局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

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

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

相关文章

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

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

【认识OpenThread协议】

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

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

原题描述&#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;安全性高、操…

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

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

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

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

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;引起了广泛关注。本文将从配置性…

【学习笔记】《逆向工程核心原理》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;大量…

算法精讲 | 树(番外):平衡世界的四大守护者:AVL vs 红黑树 vs B树 vs B+树

&#x1f332; 算法精讲 | 树&#xff08;番外&#xff09;&#xff1a;平衡世界的四大守护者&#xff1a;AVL vs 红黑树 vs B树 vs B树 &#x1f4c5; 2025/03/12 || &#x1f31f; 推荐阅读时间 30分钟 &#x1f680; 开篇&#xff1a;数据结构界的四大天王 想象你是一名图书…

第十八:go 并发 goroutine

channel 可以让多个goroutine 之间实现通信 Add方法调用时机&#xff1a;必须在goroutine 启动之前调用Add方法来增加计数器的值。 如果在goroutine已经启动之后再调用Add&#xff0c;可能会导致Wait方法提前返回&#xff0c;因为计数器没有正确反映正在运行的goroutine的数量…

在vs中无法用QtDesigner打开ui文件的解决方法

解决方法 右键ui文件&#xff0c;选择打开方式&#xff0c;弹出如下界面。 点击添加&#xff0c;弹出如下界面 点击程序后边的三个点&#xff0c;去电脑查找designer.exe,我的位置为D:\Qt\Qt5.9.9\5.9.9\msvc2015_64\bin\designer.exe。 名称可以自己起一个名字&#xff0c…

图像识别技术与应用-YOLO

1 YOLO-V1 YOLO-V1它是经典的one-stage方法&#xff0c;You Only Look Once&#xff0c;名字就已经说明了一切&#xff01;把检测问题转化成回归问题&#xff0c;一个CNN就搞定了&#xff01;也可以对视频进行实时检测&#xff0c;应用领域非常广&#xff01; YOLO-V1诞生与2…