1. Qt 串口通信流程解析
1.1 串行通信和并行通信对比
- 并行通信适合距离较短的通信,且信号容易受干扰,成本高
- 串口通讯-设备(蓝牙, wifi, gprs, gps)
1.2 Qt 串口通信具体流程
- 1. 创建 QSerialPort 对象
- 2. 配置属性(波特率, 数据位, 停止, 校验位)
- 3. 打开设备
- 4. 发送数据到串口 write
- 5. 在槽函数中读取数据(当串口有数据可读的时候会发送 readyRead 信号)
1.2.1 serialapp.pro
QT += core gui serialport
1.2.2 serialapp.h
#ifndef SERIALAPP_H
#define SERIALAPP_H#include <QWidget>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QIODevice>
#include <QByteArray>
#include <QString>
#include <QDebug>QT_BEGIN_NAMESPACE
namespace Ui { class SerialApp; }
QT_END_NAMESPACEclass SerialApp : public QWidget {Q_OBJECTpublic:SerialApp(QWidget *parent = nullptr);~SerialApp();private slots:void on_openBt_clicked();void on_sendBt_clicked();void read_data();private:Ui::SerialApp *ui;// 1、创建 QSerialPort 对象QSerialPort mSerial;
};
#endif // SERIALAPP_H
1.2.3 serialapp.cpp
#include "serialapp.h"
#include "ui_serialapp.h"SerialApp::SerialApp(QWidget *parent) : QWidget(parent), ui(new Ui::SerialApp) {ui->setupUi(this);// 2、配置设备,波特率,数据位,停止位,校验位mSerial.setPortName("COM1"); // 选择对应的端口号mSerial.setBaudRate(QSerialPort::Baud115200);mSerial.setDataBits(QSerialPort::Data8);mSerial.setStopBits(QSerialPort::OneStop);mSerial.setParity(QSerialPort::NoParity);connect(&mSerial, &QSerialPort::readyRead, this, &SerialApp::read_data);
}SerialApp::~SerialApp() {delete ui;
}// 3、打开设备 (读写)
void SerialApp::on_openBt_clicked() {if (mSerial.open(QIODevice::ReadWrite)) {qDebug() << "open success!";} else {qDebug() << "open failed!";}
}// 4、发送数据到串口 write
void SerialApp::on_sendBt_clicked() {QString data = ui->textEdit->toPlainText();mSerial.write(data.toUtf8());
}// 5、读取串口数据 read
void SerialApp::read_data() {QByteArray array = mSerial.readAll();ui->textBrowser->append(QString(array));
}
1.2.4 serialapp.ui
2. Qt 虚拟串口调试
2.1 VSPD 创建虚拟串口
-
VSPD (Virtual Serial Port Driver) 是一个虚拟串口驱动程序
- 它可以模拟多个串口设备,使得应用程序可以通过虚拟串口与物理串口设备进行通信
- 使用 VSPD 可以方便地进行串口调试、数据采集、数据转发等操作
- VSPD 还支持多种协议,例如模拟 GPS 设备、模拟调制解调器、与虚拟机通信等
-
VSPD虚拟串口软件安装及使用
2.2 SecureCRT 连接虚拟串口
- SecureCRT 是一款安全的终端模拟器,常用于远程访问服务器和网络设备
- 它可以让用户通过 SSH、Telnet、Rlogin 或者串口等协议连接到远程设备,并在本地进行命令行操作
- SecureCRT 还提供了多重会话管理、脚本编写、自动登录、加密通信等多种功能
- SecureCRT安装教程
2.3 Qt 虚拟串口实现
-
serialapp.h
#ifndef SERIALAPP_H #define SERIALAPP_H#include <QWidget> #include <QSerialPort> #include <QSerialPortInfo> #include <QIODevice> #include <QByteArray> #include <QString> #include <QDebug> #include <QList>QT_BEGIN_NAMESPACE namespace Ui { class SerialApp; } QT_END_NAMESPACEclass SerialApp : public QWidget {Q_OBJECTpublic:SerialApp(QWidget *parent = nullptr);~SerialApp();private slots:void on_openBt_clicked();void on_sendBt_clicked();void read_data();private:Ui::SerialApp *ui;// 创建 QSerialPort 对象QSerialPort mSerial; }; #endif // SERIALAPP_H
-
serialapp.cpp
#include "serialapp.h" #include "ui_serialapp.h"SerialApp::SerialApp(QWidget *parent) : QWidget(parent), ui(new Ui::SerialApp) {ui->setupUi(this);// 获取当前设备上的所有串口QList<QSerialPortInfo> list = QSerialPortInfo::availablePorts();for (int i = 0; i < list.size(); i++) {ui->comboBox->addItem(list.at(i).portName());}// 配置设备,波特率,数据位,停止位,校验位//mSerial.setPortName("COM1");mSerial.setBaudRate(QSerialPort::Baud115200);mSerial.setDataBits(QSerialPort::Data8);mSerial.setStopBits(QSerialPort::OneStop);mSerial.setParity(QSerialPort::NoParity);connect(&mSerial, &QSerialPort::readyRead, this, &SerialApp::read_data); }SerialApp::~SerialApp() {delete ui; }// 打开设备 (读写) void SerialApp::on_openBt_clicked() {if (mSerial.isOpen()) {mSerial.close();}mSerial.setPortName(ui->comboBox->currentText()); // 设置端口if (mSerial.open(QIODevice::ReadWrite)) {qDebug() << "open success!";} else {qDebug() << "open failed!";} }// 发送数据到串口 write void SerialApp::on_sendBt_clicked() {QString data = ui->textEdit->toPlainText();mSerial.write(data.toUtf8()); }// 读取串口数据 read void SerialApp::read_data() {QByteArray array = mSerial.readAll();ui->textBrowser->append(QString(array)); }
-
serialapp.ui
2.4 Qt 与 SecureCRT 建立虚拟串口连接
3. Qt 编写串口调试工具
3.1 serialportapp.h
#ifndef SERIALPORTAPP_H
#define SERIALPORTAPP_H#include <QWidget>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QList>
#include <QStringList>
#include <QString>
#include <QIODevice>
#include <QDebug>
#include <QByteArray>
#include <QMessageBox>
#include <QTimerEvent>
#include <QFileDialog>
#include <QFile>QT_BEGIN_NAMESPACE
namespace Ui { class SerialPortApp; }
QT_END_NAMESPACEclass SerialPortApp : public QWidget {Q_OBJECTpublic:SerialPortApp(QWidget *parent = nullptr);~SerialPortApp();void timerEvent(QTimerEvent *event);private slots:void on_openBt_clicked();void on_closeBt_clicked();void on_sendBt_clicked();void on_autoCheckBox_clicked(bool checked);void on_clearSendSizeBt_clicked();void on_sendHexCb_clicked(bool checked);void on_recvHexCb_clicked(bool checked);void read_data();void on_clearRecvSizeBt_clicked();void on_selectfileBt_clicked();void on_sendfileBt_clicked();void send_file_text(quint64 size);private:Ui::SerialPortApp *ui;QSerialPort mSerial;int timerid;qint32 sendsize;qint32 recvsize;QFile file; // 发送文件qint32 sendfilesize;
};
#endif // SERIALPORTAPP_H
3.2 serialportapp.cpp
#include "serialportapp.h"
#include "ui_serialportapp.h"SerialPortApp::SerialPortApp(QWidget *parent) : QWidget(parent), ui(new Ui::SerialPortApp) {ui->setupUi(this);// 遍历获取当前设备上的所有串口QList<QSerialPortInfo> infos = QSerialPortInfo::availablePorts();for (int i = 0; i < infos.size(); i++) {ui->comCb->addItem(infos.at(i).portName());}// 设置波特率显示QStringList list;list << "1200" << "2400" << "4800" << "9600" << "19200" << "38400" << "57600" << "115200";ui->rateCb->addItems(list);ui->rateCb->setCurrentIndex(7); // 设置默认波特率为 115200list.clear();// 设置数据位list << "5" << "6" << "7" << "8" << "-1";ui->dataCb->addItems(list);ui->dataCb->setCurrentIndex(3); // 设置默认数据位为 8list.clear();// 设置停止位list << "1" << "3" << "2" << "-1";ui->stopCb->addItems(list);list.clear();// 设置校验位list << "None" << "NULL" << "Even" << "Odd" << "Space" << "Mark";ui->priCb->addItems(list);list.clear();// 把关闭按钮设置失效ui->closeBt->setEnabled(false);// 当串口有数据可读时会发送 readyRead 信号connect(&mSerial, &QSerialPort::readyRead, this, &SerialPortApp::read_data);// 初始化发送、接收的字节数记录sendsize = recvsize = 0;
}SerialPortApp::~SerialPortApp() {delete ui;
}// 打开
void SerialPortApp::on_openBt_clicked() {// 配置端口,波特率,数据位,停止位,校验位mSerial.setPortName(ui->comCb->currentText());mSerial.setBaudRate(ui->rateCb->currentText().toInt());mSerial.setDataBits((QSerialPort::DataBits)ui->dataCb->currentText().toInt());mSerial.setStopBits((QSerialPort::StopBits)ui->stopCb->currentText().toInt());mSerial.setParity((QSerialPort::Parity)ui->priCb->currentText().toInt());// 打开设备if (mSerial.open(QIODevice::ReadWrite)) {ui->closeBt->setEnabled(true);ui->openBt->setEnabled(false);}
}// 关闭
void SerialPortApp::on_closeBt_clicked() {// 关闭设备mSerial.close();ui->closeBt->setEnabled(false);ui->openBt->setEnabled(true);
}// 手动发送数据
void SerialPortApp::on_sendBt_clicked() {QString data = ui->sendText->toPlainText();if (ui->sendHexCb->isChecked()) {// 转十六进制:data = 4142 --> 0x41 0x42QByteArray array;if (data.size() % 2 != 0) {data.insert(0, '0');}for (int i = 0; i < data.size() / 2; i++) {QString t = data.mid(2*i, 2);bool ok = false;int ihex = t.toInt(&ok, 16);array.append(ihex);}int size = mSerial.write(array); // 发送数据sendsize += size; // 累计发送的字节数} else {int size = mSerial.write(data.toUtf8()); // 发送数据sendsize += size;}// 设置显示已发送的字节数ui->sendsizelabel->setText(QString::number(sendsize));
}// 定时自动发送数据
void SerialPortApp::on_autoCheckBox_clicked(bool checked) {if (checked) {// 获取定时发送周期int ms = ui->autotimeEdit->text().toInt();if (ms < 100) {QMessageBox::warning(this, "time hint", "time should > 100ms");ui->autoCheckBox->setChecked(false);return;}// 启动定时器事件timerid = this->startTimer(ms);} else {// 关闭定时器事件this->killTimer(timerid);}
}// 定时器事件
void SerialPortApp::timerEvent(QTimerEvent *event) {on_sendBt_clicked();
}// 清空已发送的字节数
void SerialPortApp::on_clearSendSizeBt_clicked() {sendsize = 0;ui->sendText->clear();ui->sendsizelabel->setText("0");
}// 发送端:十六进制和十进制转换
void SerialPortApp::on_sendHexCb_clicked(bool checked) {if (checked) { // 十进制 --> 十六进制QString data = ui->sendText->toPlainText();QByteArray array = data.toUtf8().toHex();ui->sendText->setText(QString(array));} else { // 十六进制 --> 十进制QString data = ui->sendText->toPlainText();QByteArray array;if (data.size() % 2 != 0) {data.insert(0, '0');}for (int i = 0; i < data.size() / 2; i++) {QString t = data.mid(2*i, 2);bool ok = false;int ihex = t.toInt(&ok, 16);array.append(ihex);}ui->sendText->setText(QString(array));}
}// 接收端:十六进制和十进制转换
void SerialPortApp::on_recvHexCb_clicked(bool checked) {if (checked) { // 十进制 --> 十六进制QString data = ui->recvText->toPlainText();QByteArray array = data.toUtf8().toHex();ui->recvText->setText(QString(array));} else { // 十六进制 --> 十进制QString data = ui->recvText->toPlainText();QByteArray array;if (data.size() % 2 != 0) {data.insert(0, '0');}for (int i = 0; i < data.size() / 2; i++) {QString t = data.mid(2*i, 2);bool ok = false;int ihex = t.toInt(&ok, 16);array.append(ihex);}ui->recvText->setText(QString(array));}
}// 接收串口数据
void SerialPortApp::read_data() {// 读到的数据是一个个字节QByteArray array = mSerial.readAll();recvsize += array.size(); // 显示已接收到的字节数if (ui->recvHexCb->isChecked()) {ui->recvText->append(array.toHex());} else {ui->recvText->append(array);}// 设置显示已接收到的字节数ui->recvsizelabel->setText(QString::number(recvsize));
}// 清空已接收的字节数
void SerialPortApp::on_clearRecvSizeBt_clicked() {recvsize = 0;ui->recvText->clear();ui->recvsizelabel->setText("0");
}// 选择要发送的文件
void SerialPortApp::on_selectfileBt_clicked() {QString path = QFileDialog::getOpenFileName(this);ui->filepathEdit->setText(path);
}// 发送文件
void SerialPortApp::on_sendfileBt_clicked() {// 当数据发送完毕后会发出一个信号 &QSerialPort::bytesWritten// 每当有效载荷的数据写入到设备当前的写入通道时,就会发出这个信号connect(&mSerial, &QSerialPort::bytesWritten, this, &SerialPortApp::send_file_text);// 打开文件file.setFileName(ui->filepathEdit->text());if (!file.open(QIODevice::ReadOnly)) {return;}// 获取文件大小int filesize = file.size();ui->progressBar->setMaximum(filesize);// 设置进度条显示QByteArray array = file.read(128); // 每次读取 128 字节内容sendfilesize = mSerial.write(array);ui->progressBar->setValue(sendfilesize);
}// 循环(每 128 字节)发送文件
void SerialPortApp::send_file_text(quint64 size) {// 设置进度条显示QByteArray array = file.read(128);quint64 mSize = mSerial.write(array);sendfilesize += mSize;ui->progressBar->setValue(sendfilesize);// 判断文件是否发送完毕if (sendfilesize == ui->progressBar->maximum()) {file.close();disconnect(&mSerial, &QSerialPort::bytesWritten, this, &SerialPortApp::send_file_text);}
}