前言

新学qt,急需练手,在b站找了一个适合我这个等级的视频,根据视频一步一个脚印成功完成了串口调试助手,但是我不满足于视频的内容,视频没有写全协议,而且使用的方法是代码布局,我想练手,所以最终决定使用ui布局,修改和完善该代码。

我的学习视频为b站up主折途想要敲代码的:200行C++代码写一个QT串口助手

本人开发使用的环境为Qt 6.7.3 for macOS

代码以上传至Github:qt6 串口调试助手

正文

UI

首先新建项目,完成ui的布局,给每一个组件一个合适的名称(下图为具体ui界面)

串口的各种代码(串口链接,串口配置,刷新可用串口号)

核心代码:分有三个函数,一个为设置ui右边的两个groupBox(串口配置和串口链接)的代码——包括创建串口指针对象和初始化配置设定;一个为传参后配置具体串口设定的代码——包括设定串口的波特率,数据位,停止位,校验位;最后一个为刷新可用串口号,让ui上可以选择可用串口。

主要涉及七个控件——comboBoxPort(串口号),comboBoxBaud(波特率),comboBoxData(数据位),comboBoxStop(停止位),comboBoxCheck(校验位),pushButtonStartUSART(打开串口),pushButtonEndUSART(关闭串口)

以下为具体代码:

void Widget::USARTLinkInit() //串口链接初始化
{
    serialport = new QSerialPort(this);  //创建串口指针,此对象配置好的函数将会用到这个对象

    ui->pushButtonEndUSART->setDisabled(true); //开始时没有打开串口,所以关闭串口按钮不能选择
    ui->comboBoxBaud->setCurrentText("9600");
    ui->comboBoxData->setCurrentText("8");

    connect(ui->pushButtonStartUSART,&QPushButton::clicked,[&](){ //信号槽 匿名函数 打开串口后的设置
        QString port = ui->comboBoxPort->currentText(); //读取目前的各种设置
        QString baud = ui->comboBoxBaud->currentText();
        QString data = ui->comboBoxStop->currentText();
        QString stop = ui->comboBoxStop->currentText();
        QString check = ui->comboBoxCheck->currentText();
        if(port != "") //如果目前串口号非空
        {
            ui->pushButtonStartUSART->setDisabled(true); //连接了就不能再连接串口了
            ui->pushButtonEndUSART->setEnabled(true); //连接了关闭串口就能按了
            ui->pushButtonSend->setEnabled(true); //连接了发送按钮就能按了
            USART(port,baud,data,stop,check);
        }
        else
        {
            QMessageBox::critical(this, QString::fromLocal8Bit("串口打开失败"), QString::fromLocal8Bit("请确认是否存在串口")); //没有串口报错
        }
    });

    connect(ui->pushButtonEndUSART,&QPushButton::click,[&](){ //信号槽 匿名函数 断开串口
        ui->pushButtonStartUSART->setEnabled(true); //断开了就可以做连接串口操作了
        ui->pushButtonEndUSART->setDisabled(true); //断开了了关闭串口就不能按了
        ui->pushButtonSend->setDisabled(true); //断开了发送按钮就不能按了
        serialport->close(); //断开串口
    });
}

void Widget::USART(QString port, QString baud, QString data,QString stop,QString check) //串口 核心代码
{
    QSerialPort::BaudRate Baud; //波特率
    QSerialPort::DataBits Data; //数据位
    QSerialPort::StopBits Stop; //停止位
    QSerialPort::Parity Check; //校验位

    QString BaudArray[] ={"1200", "2400", "4800", "9600", "19200", "38400", "57600", "115200"}; //使用数组方便循环判断当前选择波特率
    QSerialPort::BaudRate baudRates[] = { //和上面数组同样,枚举数组方便赋值
        QSerialPort::Baud1200,
        QSerialPort::Baud2400,
        QSerialPort::Baud4800,
        QSerialPort::Baud9600,
        QSerialPort::Baud19200,
        QSerialPort::Baud38400,
        QSerialPort::Baud57600,
        QSerialPort::Baud115200
    };

    Baud = QSerialPort::Baud9600; //波特率默认为9600
    for(int i = 0; i < 8; ++i) //循环判断传入的参数的波特率,赋值正确的枚举值
    {
        if(baud == BaudArray[i])
        {
            Baud = baudRates[i];
            break;
        }
    }

    QString DataArray[] = {
        "5", "6", "7", "8"
    };

    QSerialPort::DataBits dataBits[] = {
        QSerialPort::Data5,
        QSerialPort::Data6,
        QSerialPort::Data7,
        QSerialPort::Data8
    };
    Data = QSerialPort::Data8; //默认为Data8
    for(int i = 0; i < 4; ++i)
    {
        if(data == DataArray[i])
        {
            Data = dataBits[i];
            break;
        }
    }

    //其他的内容判断没有那么多,只是练手代码,故写为if判断

    if(stop == "1") Stop = QSerialPort::OneStop; //停止位的判断
    else if(stop == "1.5") Stop = QSerialPort::OneAndHalfStop;
    else if(stop == "2") Stop = QSerialPort::TwoStop;

    if(check == QString::fromLocal8Bit("无")) Check = QSerialPort::NoParity; //校验位判断
    else if(check == QString::fromLocal8Bit("奇校验")) Check = QSerialPort::OddParity;
    else if(check == QString::fromLocal8Bit("偶校验")) Check = QSerialPort::EvenParity;

    serialport->setBaudRate(Baud); //设置串口的各种配置
    serialport->setPortName(port);
    serialport->setDataBits(Data);
    serialport->setParity(Check);
    serialport->setStopBits(Stop);

    if(serialport->open(QSerialPort::ReadWrite)) //如果打开了串口的读写模式
    {
        //成功不写内容,接收区另写具体函数
    }
    else //如果没有成功链接串口
    {
        QMessageBox::critical(this, QString::fromLocal8Bit("串口连接失败"), QString::fromLocal8Bit("请确认串口是否正确连接")); //打开失败报错
    }
}

void Widget::RefreshPort()
{
    refreshTimer = new QTimer(this); //创建定时器

    connect(refreshTimer,&QTimer::timeout,this,[&](){
        QVector<QString> availablePorts; //创建字符串数组,用来存储目前可用的串口

        foreach(const QSerialPortInfo info, QSerialPortInfo::availablePorts()) { //读取目前可用的串口并存入字符串数组中
            availablePorts.append(info.portName());
        }

        QVector<QString> currentPorts; //创建字符串数组,用来存储comboBoxPort上有的串口

        for(int i=0;i<ui->comboBoxPort->count();++i)
        {
            currentPorts.append(ui->comboBoxPort->itemText(i)); //获取每个条目的文本
        }

        if (availablePorts != currentPorts) //如果当前的串口号列表与新获取到的串口号列表不同,则更新ComboBoxPort
        {
            ui->comboBoxPort->clear(); //清空现有的串口号
            ui->comboBoxPort->addItems(availablePorts); //更新ComboBoxPort
        }
    });
  refreshTimer->start(1000); //定时器设定时间1000ms刷新
}

接收区数据代码

也就是ui左上角接收区数据的代码,主要涉及两个控件——plainTextEditReceive(显示接收内容——plainTextEdit控件)和pushButtonClearReceive(清除接收去按钮——pushButton控件)。

以下为具体代码:

void Widget::ReceiveInit(void) //接收区初始化
{
    ui->plainTextEditReceive->setReadOnly(true); //接收区只读
    connect(ui->pushButtonClearReceive,&QPushButton::clicked,[&](){ //信号槽 匿名函数 当按下清空接收区按钮清空接收区内容
        ui->plainTextEditReceive->clear();
    });

    connect(serialport,&QSerialPort::readyRead,[&](){ //信号槽 匿名函数 如果收到了数据就读取并显示
        auto data = serialport->readAll();
        if(ui->comboBoxReceiveMode->currentText() == "HEX") //如果选择的接收模式为HEX模式
        {
            QString hex = data.toHex(' '); //data转换为HEX格式并以空格分隔
            ui->plainTextEditReceive->appendPlainText(hex); //显示到接收区
        }
        else if(ui->comboBoxReceiveMode->currentText() == "文本") //如果选择的接收模式为文本模式
        {
            if(ui->comboBoxReceiveCodec->currentText()=="UTF-8") //字节流转换为UTF-8编码
            {
                QString text = QString::fromUtf8(data);
                ui->plainTextEditReceive->appendPlainText(text);
            }
            else if(ui->comboBoxReceiveCodec->currentText()=="ASCII") //字节流转换为ASCII编码
            {
                QString text = QString::fromLatin1(data);
                ui->plainTextEditReceive->appendPlainText(text);
            }
            else if(ui->comboBoxReceiveCodec->currentText()=="GBK") //字节流转换为GBK编码
            {
                QString text = QString::fromLocal8Bit(data);
                ui->plainTextEditReceive->appendPlainText(text);
            }
        }
    });
}

发送区数据代码

也就是ui左下角发送区数据的代码,主要涉及五个控件——plainTextEditSend(显示发送内容),checkBoxSendOnTime(定时发送/ms),spinBoxSendOnTime(设定定时时间),pushButtonSend(发送),pushButtonClearSend(清空发送区)。

这部分写的比较乱,分了三个函数,在命名上有点问题。个人比较懒,就不做更改了。

以下为具体代码:

void Widget::SendInit() //发送区初始化
{
    connect(ui->pushButtonClearSend,&QPushButton::clicked,[&](){ //信号槽 匿名函数 当按下清空发送区按钮清空发送区内容
        ui->plainTextEditSend->clear();
    });

    ui->pushButtonSend->setDisabled(true); //先将发送按钮设置为不可点击,当连接上串口后才能发送

    connect(ui->pushButtonSend,&QPushButton::clicked,[&](){
        QString data = ui->plainTextEditSend->toPlainText(); //从文本框获得内容存入data中
        if(ui->comboBoxSendMode->currentText() == "HEX") //如果为HEX发送模式
        {
            QByteArray arr; //创建一个二进制数组
            for(int i=0;i<data.size();i++) //遍历读取到的data,
            {
                if(data[i] == ' ') continue; //如果读到了空格就跳过
                else
                {
                    int num = data.mid(i,2).toUInt(nullptr,16); //将data从i取两位,转换为16进制数并给num赋值
                    i++; //因为是两位数,所以i要加两次
                    arr.append(num); //将算好的num以字节存入
                }
                serialport->write(arr); //串口写入arr二进制数组
            }
        }
        else //如果为文本发送模式
        {
            if(ui->comboBoxSendCodec->currentText()=="UTF-8") //选择了UTF-8编码
            {
                serialport->write(data.toUtf8());
            }
            else if(ui->comboBoxSendCodec->currentText()=="ASCII") //选择了ASCII编码
            {
                serialport->write(data.toLatin1());
            }
            else if(ui->comboBoxSendCodec->currentText()=="GBK") //选择了GBK编码
            {
                serialport->write(data.toLocal8Bit());
            }
        }
    });
}

void Widget::SendSetInit() //发送区配置初始化
{
    ui->comboBoxSendCodec->setDisabled(true); //初始打开时默认为HEX,所以编码模式默认选不了

    connect(ui->comboBoxSendMode,&QComboBox::currentTextChanged,[&](){ //信号槽 匿名函数 如果目前的text被改变了,就做判断
        if(ui->comboBoxSendMode->currentText() == "HEX")
        {
            ui->comboBoxSendCodec->setDisabled(true); //是HEX就没有编码模式
        }
        else
        {
            ui->comboBoxSendCodec->setEnabled(true); //是文本就有编码模式
        }
    });
}

void Widget::SendOnTime() //定时发送
{
    ui->spinBoxSendOnTime->setMaximum(60000); //最大值设置为60000毫秒
    ui->spinBoxSendOnTime->setValue(1000);    //初始化值设置为1000 毫秒

    sendTimer = new QTimer(this);

    connect(ui->checkBoxSendOnTime,&QCheckBox::stateChanged,this,[&](int state){
        if(state == Qt::Checked)
        {
            int interval = ui->spinBoxSendOnTime->value();

            sendTimer->start(interval);
        }
        else
        {
            sendTimer->stop();
        }
    });

    connect(sendTimer,&QTimer::timeout,this,[&](){
        if(ui->pushButtonSend->isEnabled())
        {
            QString data = ui->plainTextEditSend->toPlainText();
            if(!data.isEmpty())
            {
                if(ui->comboBoxSendMode->currentText() == "HEX") //如果为HEX发送模式
                {
                    QByteArray arr; //创建一个二进制数组
                    for(int i=0;i<data.size();i++) //遍历读取到的data,
                    {
                        if(data[i] == ' ') continue; //如果读到了空格就跳过
                        else
                        {
                            int num = data.mid(i,2).toUInt(nullptr,16); //将data从i取两位,转换为16进制数并给num赋值
                            i++; //因为是两位数,所以i要加两次
                            arr.append(num); //将算好的num以字节存入
                        }
                        serialport->write(arr); //串口写入arr二进制数组
                    }
                }
                else //如果为文本发送模式
                {
                    if(ui->comboBoxSendCodec->currentText()=="UTF-8") //选择了GBK编码
                    {
                        serialport->write(data.toUtf8());
                    }
                    else if(ui->comboBoxSendCodec->currentText()=="ASCII")
                    {
                        serialport->write(data.toLatin1());
                    }
                    else if(ui->comboBoxSendCodec->currentText()=="GBK")
                    {
                        serialport->write(data.toLocal8Bit());
                    }
                }
            }
        }
    });
}

接收区配置+发送区配置代码

个人给groupBox的命名确实有点乱,就是ui右边的接收区配置,发送区配置的代码,内容为控制ui选择不同模式来选择不同编码模式。

主要涉及四个控件——comboBoxReceiveMode(接收模式),comboBoxReceiveCodec(接收编码模式),comboBoxSendMode(发送模式),comboBoxSendCodec(发送编码模式)。

以下为具体代码:

void Widget::SendSetInit() //发送区配置初始化
{
    ui->comboBoxSendCodec->setDisabled(true); //初始打开时默认为HEX,所以编码模式默认选不了

    connect(ui->comboBoxSendMode,&QComboBox::currentTextChanged,[&](){ //信号槽 匿名函数 如果目前的text被改变了,就做判断
        if(ui->comboBoxSendMode->currentText() == "HEX")
        {
            ui->comboBoxSendCodec->setDisabled(true); //是HEX就没有编码模式
        }
        else
        {
            ui->comboBoxSendCodec->setEnabled(true); //是文本就有编码模式
        }

    });
}

void Widget::ReceiveSetInit()
{
    ui->comboBoxReceiveCodec->setDisabled(true); //初始打开时默认为HEX,所以编码模式默认选不了

    connect(ui->comboBoxReceiveMode,&QComboBox::currentTextChanged,[&](){ //信号槽 匿名函数 如果目前的text被改变了,就做判断
        if(ui->comboBoxReceiveMode->currentText() == "HEX")
        {
            ui->comboBoxReceiveCodec->setDisabled(true); //是HEX就没有编码模式
        }
        else
        {
            ui->comboBoxReceiveCodec->setEnabled(true); //是文本就有编码模式
        }
    });
}

刷新时间代码

ui底部有个label标签,用来显示当前时间。

主要涉及一个控件——labelTime(时间)。

以下为具体代码:

void Widget::UpdateTime() //刷新label时间
{
    time = new QTimer(this);
    connect(time,&QTimer::timeout,this,[&](){
        QDateTime currentTime = QDateTime::currentDateTime();
        QString timeString = currentTime.toString("yyyy-MM-dd HH:mm:ss");
        timeString = "Time: "+timeString;
        ui->labelTime->setText(timeString);
    });
    time->start(1000);
}

总源码(.h)

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QDebug>

#include <QSerialPortInfo>
#include <QSerialPort>
#include <QMessageBox>
#include <QTimer>
#include <QDateTime>

QT_BEGIN_NAMESPACE
namespace Ui {
class Widget;
}
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

    void ReceiveInit(void); //接收区初始化
    void SendInit(void); //发送区初始化
    void USART(QString port, QString baud, QString data,QString stop,QString check); //串口初始化 重点
    void SendSetInit(void); //发送区配置初始化
    void ReceiveSetInit(void); //接收区配置初始化
    void USARTLinkInit(void); //串口链接初始化
    void RefreshPort(void); //刷新串口号
    void SendOnTime(void); //定时发送
    void UpdateTime(void); //刷新时间

private:
    Ui::Widget *ui;

    QSerialPort* serialport; //串口指针
    QTimer* refreshTimer;
    QTimer* sendTimer;
    QTimer* time;
};
#endif // WIDGET_H

总源码(.cpp)

#include "widget.h"
#include "ui_widget.h"

void Widget::ReceiveInit(void) //接收区初始化
{
    ui->plainTextEditReceive->setReadOnly(true); //接收区只读
    connect(ui->pushButtonClearReceive,&QPushButton::clicked,[&](){ //信号槽 匿名函数 当按下清空接收区按钮清空接收区内容
        ui->plainTextEditReceive->clear();
    });

    connect(serialport,&QSerialPort::readyRead,[&](){ //信号槽 匿名函数 如果收到了数据就读取并显示
        auto data = serialport->readAll();
        if(ui->comboBoxReceiveMode->currentText() == "HEX") //如果选择的接收模式为HEX模式
        {
            QString hex = data.toHex(' '); //data转换为HEX格式并以空格分隔
            ui->plainTextEditReceive->appendPlainText(hex); //显示到接收区
        }
        else if(ui->comboBoxReceiveMode->currentText() == "文本") //如果选择的接收模式为文本模式
        {
            if(ui->comboBoxReceiveCodec->currentText()=="UTF-8") //字节流转换为UTF-8编码
            {
                QString text = QString::fromUtf8(data);
                ui->plainTextEditReceive->appendPlainText(text);
            }
            else if(ui->comboBoxReceiveCodec->currentText()=="ASCII") //字节流转换为ASCII编码
            {
                QString text = QString::fromLatin1(data);
                ui->plainTextEditReceive->appendPlainText(text);
            }
            else if(ui->comboBoxReceiveCodec->currentText()=="GBK") //字节流转换为GBK编码
            {
                QString text = QString::fromLocal8Bit(data);
                ui->plainTextEditReceive->appendPlainText(text);
            }
        }
    });
}

void Widget::SendInit() //发送区初始化
{
    connect(ui->pushButtonClearSend,&QPushButton::clicked,[&](){ //信号槽 匿名函数 当按下清空发送区按钮清空发送区内容
        ui->plainTextEditSend->clear();
    });

    ui->pushButtonSend->setDisabled(true); //先将发送按钮设置为不可点击,当连接上串口后才能发送

    connect(ui->pushButtonSend,&QPushButton::clicked,[&](){
        QString data = ui->plainTextEditSend->toPlainText(); //从文本框获得内容存入data中
        if(ui->comboBoxSendMode->currentText() == "HEX") //如果为HEX发送模式
        {
            QByteArray arr; //创建一个二进制数组
            for(int i=0;i<data.size();i++) //遍历读取到的data,
            {
                if(data[i] == ' ') continue; //如果读到了空格就跳过
                else
                {
                    int num = data.mid(i,2).toUInt(nullptr,16); //将data从i取两位,转换为16进制数并给num赋值
                    i++; //因为是两位数,所以i要加两次
                    arr.append(num); //将算好的num以字节存入
                }
                serialport->write(arr); //串口写入arr二进制数组
            }
        }
        else //如果为文本发送模式
        {
            if(ui->comboBoxSendCodec->currentText()=="UTF-8") //选择了UTF-8编码
            {
                serialport->write(data.toUtf8());
            }
            else if(ui->comboBoxSendCodec->currentText()=="ASCII") //选择了ASCII编码
            {
                serialport->write(data.toLatin1());
            }
            else if(ui->comboBoxSendCodec->currentText()=="GBK") //选择了GBK编码
            {
                serialport->write(data.toLocal8Bit());
            }
        }
    });
}

void Widget::SendSetInit() //发送区配置初始化
{
    ui->comboBoxSendCodec->setDisabled(true); //初始打开时默认为HEX,所以编码模式默认选不了

    connect(ui->comboBoxSendMode,&QComboBox::currentTextChanged,[&](){ //信号槽 匿名函数 如果目前的text被改变了,就做判断
        if(ui->comboBoxSendMode->currentText() == "HEX")
        {
            ui->comboBoxSendCodec->setDisabled(true); //是HEX就没有编码模式
        }
        else
        {
            ui->comboBoxSendCodec->setEnabled(true); //是文本就有编码模式
        }

    });
}

void Widget::ReceiveSetInit()
{
    ui->comboBoxReceiveCodec->setDisabled(true); //初始打开时默认为HEX,所以编码模式默认选不了

    connect(ui->comboBoxReceiveMode,&QComboBox::currentTextChanged,[&](){ //信号槽 匿名函数 如果目前的text被改变了,就做判断
        if(ui->comboBoxReceiveMode->currentText() == "HEX")
        {
            ui->comboBoxReceiveCodec->setDisabled(true); //是HEX就没有编码模式
        }
        else
        {
            ui->comboBoxReceiveCodec->setEnabled(true); //是文本就有编码模式
        }
    });
}

void Widget::USARTLinkInit() //串口链接初始化
{
    serialport = new QSerialPort(this);  //创建串口指针,此对象配置好的函数将会用到这个对象

    ui->pushButtonEndUSART->setDisabled(true); //开始时没有打开串口,所以关闭串口按钮不能选择
    ui->comboBoxBaud->setCurrentText("9600");
    ui->comboBoxData->setCurrentText("8");

    connect(ui->pushButtonStartUSART,&QPushButton::clicked,[&](){ //信号槽 匿名函数 打开串口后的设置
        QString port = ui->comboBoxPort->currentText(); //读取目前的各种设置
        QString baud = ui->comboBoxBaud->currentText();
        QString data = ui->comboBoxStop->currentText();
        QString stop = ui->comboBoxStop->currentText();
        QString check = ui->comboBoxCheck->currentText();
        if(port != "") //如果目前串口号非空
        {
            ui->pushButtonStartUSART->setDisabled(true); //连接了就不能再连接串口了
            ui->pushButtonEndUSART->setEnabled(true); //连接了关闭串口就能按了
            ui->pushButtonSend->setEnabled(true); //连接了发送按钮就能按了
            USART(port,baud,data,stop,check);
        }
        else
        {
            QMessageBox::critical(this, QString::fromLocal8Bit("串口打开失败"), QString::fromLocal8Bit("请确认是否存在串口")); //没有串口报错
        }
    });

    connect(ui->pushButtonEndUSART,&QPushButton::clicked,[&](){ //信号槽 匿名函数 断开串口
        ui->pushButtonStartUSART->setEnabled(true); //断开了就可以做连接串口操作了
        ui->pushButtonEndUSART->setDisabled(true); //断开了了关闭串口就不能按了
        ui->pushButtonSend->setDisabled(true); //断开了发送按钮就不能按了
        serialport->close(); //断开串口
    });
}

void Widget::RefreshPort()
{
    refreshTimer = new QTimer(this); //创建定时器

    connect(refreshTimer,&QTimer::timeout,this,[&](){
        QVector<QString> availablePorts; //创建字符串数组,用来存储目前可用的串口

        foreach(const QSerialPortInfo info, QSerialPortInfo::availablePorts()) { //读取目前可用的串口并存入字符串数组中
            availablePorts.append(info.portName());
        }

        QVector<QString> currentPorts; //创建字符串数组,用来存储comboBoxPort上有的串口

        for(int i=0;i<ui->comboBoxPort->count();++i)
        {
            currentPorts.append(ui->comboBoxPort->itemText(i)); //获取每个条目的文本
        }

        if (availablePorts != currentPorts) //如果当前的串口号列表与新获取到的串口号列表不同,则更新ComboBoxPort
        {
            ui->comboBoxPort->clear(); //清空现有的串口号
            ui->comboBoxPort->addItems(availablePorts); //更新ComboBoxPort
        }
    });
    refreshTimer->start(1000); //定时器设定时间1000ms刷新
}

void Widget::SendOnTime() //定时发送
{
    ui->spinBoxSendOnTime->setMaximum(60000); //最大值设置为60000毫秒
    ui->spinBoxSendOnTime->setValue(1000);    //初始化值设置为1000 毫秒

    sendTimer = new QTimer(this);

    connect(ui->checkBoxSendOnTime,&QCheckBox::stateChanged,this,[&](int state){
        if(state == Qt::Checked)
        {
            int interval = ui->spinBoxSendOnTime->value();

            sendTimer->start(interval);
        }
        else
        {
            sendTimer->stop();
        }
    });

    connect(sendTimer,&QTimer::timeout,this,[&](){
        if(ui->pushButtonSend->isEnabled())
        {
            QString data = ui->plainTextEditSend->toPlainText();
            if(!data.isEmpty())
            {
                if(ui->comboBoxSendMode->currentText() == "HEX") //如果为HEX发送模式
                {
                    QByteArray arr; //创建一个二进制数组
                    for(int i=0;i<data.size();i++) //遍历读取到的data,
                    {
                        if(data[i] == ' ') continue; //如果读到了空格就跳过
                        else
                        {
                            int num = data.mid(i,2).toUInt(nullptr,16); //将data从i取两位,转换为16进制数并给num赋值
                            i++; //因为是两位数,所以i要加两次
                            arr.append(num); //将算好的num以字节存入
                        }
                        serialport->write(arr); //串口写入arr二进制数组
                    }
                }
                else //如果为文本发送模式
                {
                    if(ui->comboBoxSendCodec->currentText()=="UTF-8") //选择了GBK编码
                    {
                        serialport->write(data.toUtf8());
                    }
                    else if(ui->comboBoxSendCodec->currentText()=="ASCII")
                    {
                        serialport->write(data.toLatin1());
                    }
                    else if(ui->comboBoxSendCodec->currentText()=="GBK")
                    {
                        serialport->write(data.toLocal8Bit());
                    }
                }
            }
        }
    });

}

void Widget::UpdateTime() //刷新label时间
{
    time = new QTimer(this);
    connect(time,&QTimer::timeout,this,[&](){
        QDateTime currentTime = QDateTime::currentDateTime();
        QString timeString = currentTime.toString("yyyy-MM-dd HH:mm:ss");
        timeString = "Time: "+timeString;
        ui->labelTime->setText(timeString);
    });
    time->start(1000);
}

void Widget::USART(QString port, QString baud, QString data,QString stop,QString check) //串口 核心代码
{
    QSerialPort::BaudRate Baud; //波特率
    QSerialPort::DataBits Data; //数据位
    QSerialPort::StopBits Stop; //停止位
    QSerialPort::Parity Check; //校验位

    QString BaudArray[] ={"1200", "2400", "4800", "9600", "19200", "38400", "57600", "115200"}; //使用数组方便循环判断当前选择波特率
    QSerialPort::BaudRate baudRates[] = { //和上面数组同样,枚举数组方便赋值
        QSerialPort::Baud1200,
        QSerialPort::Baud2400,
        QSerialPort::Baud4800,
        QSerialPort::Baud9600,
        QSerialPort::Baud19200,
        QSerialPort::Baud38400,
        QSerialPort::Baud57600,
        QSerialPort::Baud115200
    };

    Baud = QSerialPort::Baud9600; //波特率默认为9600
    for(int i = 0; i < 8; ++i) //循环判断传入的参数的波特率,赋值正确的枚举值
    {
        if(baud == BaudArray[i])
        {
            Baud = baudRates[i];
            break;
        }
    }

    QString DataArray[] = {
        "5", "6", "7", "8"
    };

    QSerialPort::DataBits dataBits[] = {
        QSerialPort::Data5,
        QSerialPort::Data6,
        QSerialPort::Data7,
        QSerialPort::Data8
    };
    Data = QSerialPort::Data8; //默认为Data8
    for(int i = 0; i < 4; ++i)
    {
        if(data == DataArray[i])
        {
            Data = dataBits[i];
            break;
        }
    }

    //其他的内容判断没有那么多,只是练手代码,故写为if判断

    if(stop == "1") Stop = QSerialPort::OneStop; //停止位的判断
    else if(stop == "1.5") Stop = QSerialPort::OneAndHalfStop;
    else if(stop == "2") Stop = QSerialPort::TwoStop;

    if(check == QString::fromLocal8Bit("无")) Check = QSerialPort::NoParity; //校验位判断
    else if(check == QString::fromLocal8Bit("奇校验")) Check = QSerialPort::OddParity;
    else if(check == QString::fromLocal8Bit("偶校验")) Check = QSerialPort::EvenParity;

    serialport->setBaudRate(Baud); //设置串口的各种配置
    serialport->setPortName(port);
    serialport->setDataBits(Data);
    serialport->setParity(Check);
    serialport->setStopBits(Stop);

    if(serialport->open(QSerialPort::ReadWrite)) //如果打开了串口的读写模式
    {
        //成功不写内容,接收区另写具体函数
    }
    else //如果没有成功链接串口
    {
        QMessageBox::critical(this, QString::fromLocal8Bit("串口连接失败"), QString::fromLocal8Bit("请确认串口是否正确连接")); //打开失败报错
    }
}

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    USARTLinkInit();
    SendInit();
    SendSetInit();
    ReceiveInit();
    ReceiveSetInit();
    SendOnTime();
    UpdateTime();
    RefreshPort();
}

Widget::~Widget()
{
    delete ui;
}

总结

个人认为虽然比起成熟串口助手来说还是缺少了些内容,但基本内容写的还是比较完善的。此代码有偷懒部分——即同种操作直接copy一份,没有设定成函数统一调用。同时存在一些问题,比如命名不规范,不清晰。个人学艺不精请谅解。由于mac没有虚拟串口软件,所以不能开启通道测试此代码内容(使用虚拟串口软件开一个通道,一端连接我写的串口助手,另一边连接一个市面上开发好的串口助手软件就可以做测试了),目前买了个esp32,等到时候做测试。所以目前可能存在bug。

调试完了,发现两个恶性bug修改了过来,一个是在开始就读取了发送区数据,应该是按下发送按钮后读取,导致我发不出去且崩溃。第二个恶性bug是我把刷新串口的函数的启动定时器写在了槽函数里面,导致刷新不出来。

目前使用没有问题,但是缺少一些判断,我用ai总结了下:

USARTLinkInit

  • 添加更多的错误处理,特别是在串口配置和打开操作时,确保能准确反馈错误信息给用户,并且合理设置相关按钮状态

SendInit

  • 对HEX模式和文本模式下的数据处理添加更多的错误检查和异常处理

ReceiveInit

  • 同样添加对串口是否打开的判断以及更好的错误处理,避免在串口未正确打开时出现异常:

SendOnTime

  • 完善定时器启动、停止及超时处理逻辑,增加更多的错误提示和状态判断:

RefreshPort

  • 添加一些错误处理,例如在获取可用串口信息出现问题时进行提示:

USART

  • 优化配置参数获取及串口打开操作的错误处理,使其更健壮: