A 引入
软件功能:
- 自动扫描电脑连接的串口
- 接收消息和发送消息,实现不同编码的通信,比如hex,utf8,gbk
- 能够设置串口的一些配置,比如波特率,停止位等
所需环境和软件工具:
- Qt Creator 4.12.2
- Qt 5.12.9
- 现成的能用串口助手,我用的是江科大的串口助手
- 虚拟串口工具(用于创建一对虚拟串口),我用的是vpsd
- 电脑为win11 64bit
最终界面:
B 具体实现
B.1界面UI布局
在“mainwindow.ui”中布局如下,因为界面比较简单就不写布局的代码了

然后可以在波特率,停止位等下拉框中填入常用的量,后面代码中会使用
B.2实现打开软件时自动读取可用串口
从Qt 5.1版本开始,Qt就有了自己的串口通讯类,之前版本需要使用第三方的串口通信类才行。
要想使用串口通信类,需要在 .pro 文件中添加 QT += serialport

同时要添加头文件:
#include “QSerialPort”
#include “QSerialPortInfo”
显示串口COM的下拉框是一个QcomboBox控件,名称为ui->com_combox我们需要往这个控件中传入电脑连接的所有串口号的列表,所以先通过串口类中的QSerialPortInfo::availablePorts() 可以获得一个 QList ,List中的每一项 QSerialPortInfo 代表一个串口实例,该类中保存了系统中已有串口的端口名称、系统位置、描述和供应商等信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
|
my_serialPort = new QSerialPort;
QStringList my_serialPortName = getPortNameList();
ui->com_combox->clear();
ui->com_combox->addItems(my_serialPortName);
|
下面实现一个打开软件时获取串口的函数getPortNameList()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| #include <QSerialPort> #include <QSerialPortInfo>
QStringList MainWindow::getPortNameList() { QStringList my_serialPortName; auto ports = QSerialPortInfo::availablePorts(); if(ports.isEmpty()) { qDebug()<<"没有串口连接"; } else { for(const QSerialPortInfo &info : ports) { my_serialPortName << info.portName(); } } return my_serialPortName; }
|
B.2实现点击下拉框之后自动更新串口列表
因为QT并没提供有关点击下拉框之后的信号量,所以我们这里需要重写Combobox的showPopup()函数,所以我们可以创建一个自己的Combobox类,然后在ui界面对com_combox进行提升
先创建一个mycombobox.cpp 和 mycombobox.h文件,并重写showPopup()函数,在这个函数中重写获取一次串口号列表,然后写到这个下拉框之中,这样就可以实现用户点击下拉框之后,就执行这些操作,注意QComboBox::showPopup();//转给父类调用不要忘记,否则下拉框点击后可能不会展开
mycombobox.cpp1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| #include "mycombobox.h" #include "mainwindow.h" #include <QDebug> #include <QComboBox>
mycomboBox::mycomboBox(QWidget *parent) : QComboBox(parent) {
}
void mycomboBox::showPopup() { qDebug()<<"qqq";
QString current_text = this->currentText(); QStringList my_serialPortName; my_serialPortName.clear(); QComboBox::clear();
auto ports = QSerialPortInfo::availablePorts(); if(ports.isEmpty()) { qDebug()<<"没有串口连接"; } else { for(const QSerialPortInfo &info : ports) { my_serialPortName << info.portName(); } }
QComboBox::addItems(my_serialPortName); setCurrentText(current_text);
QComboBox::showPopup(); }
|
mycombobox.h1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| #ifndef MYCOMBOBOX_H #define MYCOMBOBOX_H
#include <QWidget> #include <QComboBox> #include <QSerialPort> #include <QSerialPortInfo>
class mycomboBox : public QComboBox { Q_OBJECT public: explicit mycomboBox(QWidget *parent = nullptr);
void showPopup();
signals:
};
#endif
|
B.3实现”打开串口”
当用户点击”打开串口”按钮后,函数中需要判断串口号,停止位,波特率等设置是否正确,然后调用下面相关函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
bool QIODevice::isOpen() const
bool QSerialPort::clear(QSerialPort::Directions directions = AllDirections)
[override virtual] void QSerialPort::close()
void QSerialPort::setPortName(const QString &name)
bool QSerialPort::setBaudRate(qint32 baudRate, QSerialPort::Directions directions = AllDirections)
bool QSerialPort::setDataBits(QSerialPort::DataBits dataBits)
bool QSerialPort::setFlowControl(QSerialPort::FlowControl flowControl)
bool QSerialPort::setParity(QSerialPort::Parity parity)
bool QSerialPort::setStopBits(QSerialPort::StopBits stopBits)
|
主函数中监听按钮是否点击,然后调用open_SerialCom(my_serialPortName);
1 2 3 4 5 6
|
connect(ui->open_radiobut, &QRadioButton::clicked,[=](){ open_SerialCom(my_serialPortName); });
|
下面是对 void open_SerialCom(QStringList my_serialPortName) 的实现,其中如果遇到错误会调用err_Open_Serial()函数发出提示框给用户,并且退出函数。
在open_SerialCom()函数中会判断所有下拉框,比如ui->btl_combox,ui->stop_combox等的值,需要确保下拉框设置的值与函数中使用的值一一对应,否则可能会发生错误
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
|
void MainWindow::open_SerialCom(QStringList my_serialPortName) { auto ports = QSerialPortInfo::availablePorts(); if (ui->open_radiobut->text()=="打开串口") { if(ui->com_combox->count() == 0||ports.isEmpty()||my_serialPort->isOpen()) { err_Open_Serial(); return; } if(my_serialPort->isOpen()) { my_serialPort->clear(); my_serialPort->close(); }
my_serialPort->setPortName(my_serialPortName[ui->com_combox->currentIndex()]);
if(!my_serialPort->open(QIODevice::ReadWrite)) { err_Open_Serial(); return; }
my_serialPort->setBaudRate(ui->btl_combox->currentText().toInt(),QSerialPort::AllDirections);
switch (ui->data_combox->currentIndex()) { case 0 : my_serialPort->setDataBits(QSerialPort::Data8); break; case 1 : my_serialPort->setDataBits(QSerialPort::Data7); break; case 2 : my_serialPort->setDataBits(QSerialPort::Data6); break; case 3 : my_serialPort->setDataBits(QSerialPort::Data5); break; default: qDebug()<<"数据位错误"; }
switch (ui->stop_combox->currentIndex()) { case 0 : my_serialPort->setStopBits(QSerialPort::OneStop);break; case 1 : my_serialPort->setStopBits(QSerialPort::OneAndHalfStop);break; case 2 : my_serialPort->setStopBits(QSerialPort::TwoStop);break; default: qDebug()<<"停止位错误"; }
switch (ui->jiaoyan_combox->currentIndex()) { case 0 : my_serialPort->setParity(QSerialPort::NoParity);break; case 1 : my_serialPort->setParity(QSerialPort::OddParity);break; case 2 : my_serialPort->setParity(QSerialPort::EvenParity);break; default: qDebug()<<"校验位错误"; }
my_serialPort->setFlowControl(QSerialPort::NoFlowControl);
connect(my_serialPort,SIGNAL(readyRead()),this,SLOT(receiveInfo()));
my_setEnabled(false); ui->open_radiobut->setText("关闭串口"); } else { my_setEnabled(true); my_serialPort->close(); ui->open_radiobut->setText("打开串口"); } }
void MainWindow::err_Open_Serial() { QMessageBox::critical(this, "警告", "串口打开失败,请检查串口"); ui->open_radiobut->setChecked(false); my_setEnabled(true); }
|
B.4实现接收和发送数据
当串口打开之后,如果接受到了数据,那么&QSerialPort::readyRead会发出信号,我们需要在主函数监听这个两个信号
1 2 3 4 5 6 7 8 9 10 11
|
connect(ui->send_but,&QPushButton::clicked,[=](){ send_data(); });
connect(my_serialPort,&QSerialPort::readyRead,[=]() { receive_data(); });
|
收到的数据可以通过串口对象的my_serialPort->readAll()方法进行读取,然后我们需要判断接收的格式,进行相应的格式转换,然后将处理好的数据发送到接受数据文本框中 ui->re_text->append()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
|
void MainWindow::receive_data() { QByteArray info = my_serialPort->readAll();
QString strReceiveData = ""; if(ui->hex_re_cheak->isChecked()) { QByteArray hexData = info.toHex(); strReceiveData = hexData.toUpper();
qDebug()<<"接收到串口数据: "<<strReceiveData;
for(int i=0; i<strReceiveData.size(); i+=2+1) strReceiveData.insert(i, QLatin1String(" ")); strReceiveData.remove(0, 1);
qDebug()<<"处理后的串口数据: "<<strReceiveData;
ui->re_text->append(strReceiveData); } else { strReceiveData = info;
if (ui->gbk_re->isChecked()) { QTextCodec *tc = QTextCodec::codecForName("GBK"); QString tmpQStr = tc->toUnicode(info); ui->re_text->append(tmpQStr); } else { QString tmpQStr = QString::fromUtf8(info); ui->re_text->append(tmpQStr); } } }
|
发送数据需要调用my_serialPort->write()函数,同样要根据按钮进行格式转换,然后再进行发送,注意在发送之前检查串口的打开情况,其中用到了两个函数,isHexString():判断是否是hex数据,和convertStringToHex()将Qstring转换成hex数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
|
void MainWindow::send_data() { if (!my_serialPort->isOpen()) { err_Open_Serial(); return; }
QString my_strSendData = ui->send_text->toPlainText();
if(ui->hex_send_cheak->isChecked()) { my_strSendData.remove(" ");
QByteArray sendBuf;
if (!isHexString(my_strSendData)) { QMessageBox::critical(this, "警告", "输入内容非16进制"); return; }
convertStringToHex(my_strSendData, sendBuf);
my_serialPort->write(sendBuf);
} else { if(ui->gbk_send->isChecked()) { my_serialPort->write(my_strSendData.toLocal8Bit()); } else my_serialPort->write(my_strSendData.toUtf8());
} my_serialPort->write("\r\n");
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| bool MainWindow::isHexString(const QString &str) { QRegularExpression re("^[0-9a-fA-F ]+$"); return re.match(str).hasMatch(); }
void MainWindow::convertStringToHex(const QString &str, QByteArray &sendBuf) { sendBuf.clear(); QString hexStr = str.simplified().replace(" ", "");
for (int i = 0; i < hexStr.length(); i += 2) { bool ok; char byte = hexStr.mid(i, 2).toUShort(&ok, 16); if (ok) { sendBuf.append(byte); } else { sendBuf.clear(); break; } } }
|
当这几个基本功能完成之后,就可以利用vpsd(虚拟串口创建工具),测试一下以上功能
先创建一对虚拟串口com1 com2 ,然后利用两个串口助手分别连接1,2,然后相互发送东西

测试发送接收消息

测试在使用其它串口助手打开串口COM1的情况下,用自己写的串口调试助手打开串口COM1

B.5实现其它基础功能及 优化
下面实现“清除文本框”,以及优化当未打开串口时禁用发送按钮,打开串口后禁用部分下拉框,hex模式中禁止gbk选项,直接点击窗口关闭按钮后,主动关闭串口等,部分代码添加在打开串口和关闭串口的代码中,下面主要是函数实现
这是主函数中的监听函数,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
|
ui->send_but->setEnabled(false);
connect(ui->clear_re_but,&QPushButton::clicked,[=]{ ui->re_text->clear(); }); connect(ui->clear_send_but,&QPushButton::clicked,[=]{ ui->send_text->clear(); });
connect(ui->hex_re_cheak, &QPushButton::clicked, [=]() { toggleHexSettings(); }); connect(ui->hex_send_cheak, &QPushButton::clicked, [=]() { toggleHexSettings(); });
|
具体实现如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
|
void MainWindow::my_setEnabled(bool flag) { ui->com_combox->setEnabled(flag); ui->btl_combox->setEnabled(flag); ui->stop_combox->setEnabled(flag); ui->data_combox->setEnabled(flag); ui->jiaoyan_combox->setEnabled(flag); ui->send_but->setEnabled(!flag); }
void MainWindow::toggleHexSettings() { if (ui->hex_re_cheak->isChecked() || ui->hex_send_cheak->isChecked()) { ui->gbk_re->setEnabled(false); ui->gbk_send->setEnabled(false); } else { ui->gbk_re->setEnabled(true); ui->gbk_send->setEnabled(true); } }
void MainWindow::closeEvent(QCloseEvent *event) { if (my_serialPort->isOpen()) { my_serialPort->close(); }
event->accept(); }
|
到此一个简易的串口调试助手大功告成~,具体源码可以参考后面的工程源码,如果有问题和建议可以提出
C有关软件打包的总结
如果没有exe文件图标需求:
当使用release编译之后,使用QT自带的命令框的指令windeployqt打包(具体步骤可以自行查找资料),然后删除不用的dll文件,减少软件体积,然后可以选择直接压缩,或则使用enigmavb软件封包,或HM NIS Edit软件创建安装向导
如果有exe文件需求:
在工程文件源代码中添加一个图片文件(注意不是添加资源文件),要求必须为.ico格式(可以在网上在线转换格式,不要直接改后缀名)

然后在工程的.pro文件中最后添加一句:RC_ICONS = mouse.ico 注意,后面是自己的图标文件名字,最后再进行编译运行,可以发现自己的debug文件中的exe文件的图标已经改成你的图标了


然后同第一个步骤一样就可以打包成自己的exe可执行文件了!
D结尾
参考文献
- QT官网文档
文件地址
- github仓库
留言
有问题请指出,你可以选择以下方式:
- 在下方评论区留言
- 邮箱留言