Qt 串口编程-从入门到实战

1. Qt 串口通信流程解析

1.1 串行通信和并行通信对比

  • 并行通信适合距离较短的通信,且信号容易受干扰,成本高
  • 串口通讯-设备(蓝牙, wifi, gprs, gps)

Qt 串口编程-从入门到实战_第1张图片

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 
#include 
#include 
#include 
#include 
#include 
#include 

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

Qt 串口编程-从入门到实战_第2张图片

2. Qt 虚拟串口调试

2.1 VSPD 创建虚拟串口

  • VSPD (Virtual Serial Port Driver) 是一个虚拟串口驱动程序

    • 它可以模拟多个串口设备,使得应用程序可以通过虚拟串口与物理串口设备进行通信
    • 使用 VSPD 可以方便地进行串口调试、数据采集、数据转发等操作
    • VSPD 还支持多种协议,例如模拟 GPS 设备、模拟调制解调器、与虚拟机通信等
  • VSPD虚拟串口软件安装及使用

Qt 串口编程-从入门到实战_第3张图片

2.2 SecureCRT 连接虚拟串口

  • SecureCRT 是一款安全的终端模拟器,常用于远程访问服务器和网络设备
    • 它可以让用户通过 SSH、Telnet、Rlogin 或者串口等协议连接到远程设备,并在本地进行命令行操作
    • SecureCRT 还提供了多重会话管理、脚本编写、自动登录、加密通信等多种功能
  • SecureCRT安装教程

Qt 串口编程-从入门到实战_第4张图片

2.3 Qt 虚拟串口实现

  • serialapp.h

    #ifndef SERIALAPP_H
    #define SERIALAPP_H
    
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    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

Qt 串口编程-从入门到实战_第5张图片

2.4 Qt 与 SecureCRT 建立虚拟串口连接

Qt 串口编程-从入门到实战_第6张图片

3. Qt 编写串口调试工具

Qt 串口编程-从入门到实战_第7张图片

3.1 serialportapp.h

#ifndef SERIALPORTAPP_H
#define SERIALPORTAPP_H

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

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);
    }
}

3.3 serialportapp.ui

Qt 串口编程-从入门到实战_第8张图片

你可能感兴趣的:(qt,开发语言,串口编程,VSPD,虚拟串口,secureCRT,c++)