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_NAMESPACE
class SerialApp : public QWidget {
Q_OBJECT
public:
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_NAMESPACE class SerialApp : public QWidget { Q_OBJECT public: 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_NAMESPACE
class SerialPortApp : public QWidget {
Q_OBJECT
public:
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); // 设置默认波特率为 115200
list.clear();
// 设置数据位
list << "5" << "6" << "7" << "8" << "-1";
ui->dataCb->addItems(list);
ui->dataCb->setCurrentIndex(3); // 设置默认数据位为 8
list.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 0x42
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);
}
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);
}
}