QSerialPort 是 Qt 提供的一个功能强大、简单易用的串口通信类。通过本文的学习,您应该对 QSerialPort 的基本使用、高级应用技巧及相关注意事项有了全面的理解。在实际项目中,QSerialPort 可以帮助实现与外部设备的串口通信,确保数据的可靠传输和接收。希望本文能帮助您更好地理解和使用 QSerialPort,在各种应用场景中实现高效稳定的串口通信。


【QT开发】串口通信管理QSerialPort类详解及实战应用-LMLPHP
【QT开发】串口通信管理QSerialPort类详解及实战应用-LMLPHP

【QT开发】串口通信管理QSerialPort类详解及实战应用

    • 🌟 概述
    • 📖 QSerialPort 类介绍
      • 常用方法
      • 信号和槽机制
      • 常用枚举类型
    • 💻 示例代码
      • 示例:基本串口通信操作
        • 头文件:mainwindow.h
        • 源文件:mainwindow.cpp
      • 代码解释
      • 方法分析
      • 高级应用技巧
        • 1. 实现串口数据完整性校验
        • 2. 使用线程处理串口通信
        • 3. 使用信号槽机制处理串口事件
        • 4. 动态调整串口参数
    • 🚩 常见问题及解决方法
      • 1. 串口无法打开
      • 2. 数据丢失或乱码
      • 3. 串口通信延迟
    • 📌 总结

🌟 概述

QSerialPort 是 Qt 串口通信模块中的一个类,用于管理和控制串行端口通信。它提供了一种与串行设备(如传感器、终端、工业设备等)进行数据通信的便捷方式。通过 QSerialPort,您可以打开和配置串口、发送和接收数据、处理串口错误等。

QSerialPort 常用于需要与硬件设备进行串口通信的场景,比如串口调试工具,工业自动化系统,嵌入式系统通信等。

📖 QSerialPort 类介绍

在 Qt 官方文档中,QSerialPort 类的定义如下:

class QSerialPort : public QIODevice
{
    Q_OBJECT
    //...
}

QSerialPort 继承自 QIODevice,是一个用于串口通信的类。以下是一些关键特性和功能:

  • 打开和关闭串口:能够打开和关闭指定的串口。
  • 配置串口参数:支持设置串口的波特率、数据位、停止位、校验位和流控制。
  • 读写数据:能够发送和接收数据。
  • 事件处理:通过信号槽机制处理串口的各种事件(如数据可读、错误发生等)。

常用方法

以下是 QSerialPort 类中一些常用的方法及其简要介绍:

  • 构造函数和析构函数:

    • QSerialPort(QObject *parent = nullptr):默认构造函数。
    • QSerialPort(const QString &name, QObject *parent = nullptr):通过串口名称构造。
    • QSerialPort(const QSerialPortInfo &info, QObject *parent = nullptr):通过 QSerialPortInfo 对象构造。
    • ~QSerialPort():析构函数,关闭串口并释放资源。
  • 打开和关闭串口:

    • bool open(QIODevice::OpenMode mode):以指定模式打开串口。
    • void close():关闭串口。
  • 配置串口参数:

    • bool setBaudRate(qint32 baudRate, Directions directions = AllDirections):设置波特率。
    • qint32 baudRate(Directions directions = AllDirections) const:返回当前波特率。
    • bool setDataBits(QSerialPort::DataBits dataBits):设置数据位。
    • QSerialPort::DataBits dataBits() const:返回当前数据位。
    • bool setParity(QSerialPort::Parity parity):设置校验位。
    • QSerialPort::Parity parity() const:返回当前校验位。
    • bool setStopBits(QSerialPort::StopBits stopBits):设置停止位。
    • QSerialPort::StopBits stopBits() const:返回当前停止位。
    • bool setFlowControl(QSerialPort::FlowControl flowControl):设置流控制。
    • QSerialPort::FlowControl flowControl() const:返回当前流控制。
  • 读写数据:

    • qint64 write(const char *data, qint64 maxSize):写入数据到串口。
    • qint64 write(const QByteArray &byteArray):写入数据到串口。
    • QByteArray readAll():读取所有可用数据。
    • qint64 read(char *data, qint64 maxSize):读取数据。
    • qint64 bytesAvailable() const:返回可读的字节数。
    • bool waitForReadyRead(int msecs):等待数据可读。
  • 串口控制:

    • bool setBreakEnabled(bool set):设置或清除串口的断点信号。
    • bool flush():刷新串口缓冲区。

信号和槽机制

以下是 QSerialPort 类中一些常用的信号及其简要介绍:

  • 信号:
    • void readyRead():当有数据可读时发出信号。
    • void bytesWritten(qint64 bytes):当数据写入完成时发出信号。
    • void errorOccurred(QSerialPort::SerialPortError error):当错误发生时发出信号。

常用枚举类型

以下是 QSerialPort 类中一些常用的枚举类型及其简要介绍:

  • QSerialPort::BaudRate:波特率。

    • Baud1200Baud2400Baud4800Baud9600Baud19200Baud38400Baud57600Baud115200UnknownBaud
  • QSerialPort::DataBits:数据位。

    • Data5Data6Data7Data8UnknownDataBits
  • QSerialPort::Parity:校验位。

    • NoParityEvenParityOddParityMarkParitySpaceParityUnknownParity
  • QSerialPort::StopBits:停止位。

    • OneStopOneAndHalfStopTwoStopUnknownStopBits
  • QSerialPort::FlowControl:流控制。

    • NoFlowControlHardwareControlSoftwareControlUnknownFlowControl
  • QSerialPort::SerialPortError:串口错误类型。

    • NoErrorDeviceNotFoundErrorPermissionErrorOpenErrorParityErrorFramingErrorBreakConditionErrorWriteErrorReadErrorResourceErrorUnsupportedOperationErrorUnknownError

💻 示例代码

下面是一个简单的示例,用来演示如何使用 QSerialPort 进行基本的串口通信操作。该示例展示了如何打开和关闭串口、配置串口参数、发送和接收数据以及处理串口错误。

示例:基本串口通信操作

头文件:mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QSerialPort>
#include <QPushButton>
#include <QVBoxLayout>
#include <QTextEdit>
#include <QLabel>
#include <QComboBox>
#include <QLineEdit>

class MainWindow : public QMainWindow
{
    Q_OBJECT

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

private slots:
    void openSerialPort();
    void closeSerialPort();
    void sendData();
    void readData();
    void handleError(QSerialPort::SerialPortError error);

private:
    QSerialPort *serialPort;
    QTextEdit *outputTextEdit;
    QLineEdit *inputLineEdit;
    QPushButton *openButton;
    QPushButton *closeButton;
    QPushButton *sendButton;
    QComboBox *portNameComboBox;
    QComboBox *baudRateComboBox;
};

#endif // MAINWINDOW_H
源文件:mainwindow.cpp
#include "mainwindow.h"
#include <QSerialPortInfo>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent), serialPort(new QSerialPort(this))
{
    setWindowTitle("QSerialPort Example");
    resize(400, 300);

    // 创建UI组件
    portNameComboBox = new QComboBox();
    baudRateComboBox = new QComboBox();
    openButton = new QPushButton("Open Serial Port");
    closeButton = new QPushButton("Close Serial Port");
    sendButton = new QPushButton("Send Data");
    inputLineEdit = new QLineEdit();
    outputTextEdit = new QTextEdit();
    outputTextEdit->setReadOnly(true);

    // 填充端口名称和波特率
    foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts()) {
        portNameComboBox->addItem(info.portName());
    }
    baudRateComboBox->addItem("9600", QSerialPort::Baud9600);
    baudRateComboBox->addItem("19200", QSerialPort::Baud19200);
    baudRateComboBox->addItem("38400", QSerialPort::Baud38400);
    baudRateComboBox->addItem("115200", QSerialPort::Baud115200);

    // 布局管理
    QVBoxLayout *layout = new QVBoxLayout;
    layout->addWidget(new QLabel("Port Name:"));
    layout->addWidget(portNameComboBox);
    layout->addWidget(new QLabel("Baud Rate:"));
    layout->addWidget(baudRateComboBox);
    layout->addWidget(openButton);
    layout->addWidget(closeButton);
    layout->addWidget(new QLabel("Input:"));
    layout->addWidget(inputLineEdit);
    layout->addWidget(sendButton);
    layout->addWidget(new QLabel("Output:"));
    layout->addWidget(outputTextEdit);
    QWidget *centralWidget = new QWidget;
    centralWidget->setLayout(layout);
    setCentralWidget(centralWidget);

    // 连接信号和槽
    connect(openButton, &QPushButton::clicked, this, &MainWindow::openSerialPort);
    connect(closeButton, &QPushButton::clicked, this, &MainWindow::closeSerialPort);
    connect(sendButton, &QPushButton::clicked, this, &MainWindow::sendData);
    connect(serialPort, &QSerialPort::readyRead, this, &MainWindow::readData);
    connect(serialPort, &QSerialPort::errorOccurred, this, &MainWindow::handleError);
}

MainWindow::~MainWindow() = default;

void MainWindow::openSerialPort()
{
    serialPort->setPortName(portNameComboBox->currentText());
    serialPort->setBaudRate(static_cast<QSerialPort::BaudRate>(baudRateComboBox->currentData().toInt()));
    serialPort->setDataBits(QSerialPort::Data8);
    serialPort->setParity(QSerialPort::NoParity);
    serialPort->setStopBits(QSerialPort::OneStop);
    serialPort->setFlowControl(QSerialPort::NoFlowControl);

    if (serialPort->open(QIODevice::ReadWrite)) {
        outputTextEdit->append("Serial port opened.");
    } else {
        outputTextEdit->append("Failed to open serial port: " + serialPort->errorString());
    }
}

void MainWindow::closeSerialPort()
{
    if (serialPort->isOpen())
        serialPort->close();

    outputTextEdit->append("Serial port closed.");
}

void MainWindow::sendData()
{
    if (serialPort->isOpen()) {
        QByteArray data = inputLineEdit->text().toUtf8();
        serialPort->write(data);
        outputTextEdit->append("Data sent: " + data);
    } else {
        outputTextEdit->append("Serial port is not open.");
    }
}

void MainWindow::readData()
{
    QByteArray data = serialPort->readAll();
    outputTextEdit->append("Data received: " + data);
}

void MainWindow::handleError(QSerialPort::SerialPortError error)
{
    if (error == QSerialPort::ResourceError) {
        outputTextEdit->append("Critical Error: " + serialPort->errorString());
        closeSerialPort();
    }
}

代码解释

  • 创建主窗口,并设置其标题和大小:

    MainWindow(QWidget *parent)
        : QMainWindow(parent), serialPort(new QSerialPort(this))
    {
        setWindowTitle("QSerialPort Example");
        resize(400, 300);
    }
    
  • 创建 QSerialPort 对象:

    serialPort = new QSerialPort(this);
    
  • 创建 UI 组件,包括端口名选择框、波特率选择框、按钮、文本编辑区域等:

    portNameComboBox = new QComboBox();
    baudRateComboBox = new QComboBox();
    openButton = new QPushButton("Open Serial Port");
    closeButton = new QPushButton("Close Serial Port");
    sendButton = new QPushButton("Send Data");
    inputLineEdit = new QLineEdit();
    outputTextEdit = new QTextEdit();
    outputTextEdit->setReadOnly(true);
    
  • 填充端口名称和波特率:

    foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts()) {
        portNameComboBox->addItem(info.portName());
    }
    baudRateComboBox->addItem("9600", QSerialPort::Baud9600);
    baudRateComboBox->addItem("19200", QSerialPort::Baud19200);
    baudRateComboBox->addItem("38400", QSerialPort::Baud38400);
    baudRateComboBox->addItem("115200", QSerialPort::Baud115200);
    
  • 布局管理,将 UI 组件添加到布局中并设置为窗口中央控件:

    QVBoxLayout *layout = new QVBoxLayout;
    layout->addWidget(new QLabel("Port Name:"));
    layout->addWidget(portNameComboBox);
    layout->addWidget(new QLabel("Baud Rate:"));
    layout->addWidget(baudRateComboBox);
    layout->addWidget(openButton);
    layout->addWidget(closeButton);
    layout->addWidget(new QLabel("Input:"));
    layout->addWidget(inputLineEdit);
    layout->addWidget(sendButton);
    layout->addWidget(new QLabel("Output:"));
    layout->addWidget(outputTextEdit);
    QWidget *centralWidget = new QWidget;
    centralWidget->setLayout(layout);
    setCentralWidget(centralWidget);
    
  • 连接按钮点击信号和 QSerialPort 信号到槽函数:

    connect(openButton, &QPushButton::clicked, this, &MainWindow::openSerialPort);
    connect(closeButton, &QPushButton::clicked, this, &MainWindow::closeSerialPort);
    connect(sendButton, &QPushButton::clicked, this, &MainWindow::sendData);
    connect(serialPort, &QSerialPort::readyRead, this, &MainWindow::readData);
    connect(serialPort, &QSerialPort::errorOccurred, this, &MainWindow::handleError);
    
  • 实现打开串口的槽函数:

    void MainWindow::openSerialPort()
    {
        serialPort->setPortName(portNameComboBox->currentText());
        serialPort->setBaudRate(static_cast<QSerialPort::BaudRate>(baudRateComboBox->currentData().toInt()));
        serialPort->setDataBits(QSerialPort::Data8);
        serialPort->setParity(QSerialPort::NoParity);
        serialPort->setStopBits(QSerialPort::OneStop);
        serialPort->setFlowControl(QSerialPort::NoFlowControl);
    
        if (serialPort->open(QIODevice::ReadWrite)) {
            outputTextEdit->append("Serial port opened.");
        } else {
            outputTextEdit->append("Failed to open serial port: " + serialPort->errorString());
        }
    }
    
  • 实现关闭串口的槽函数:

    void MainWindow::closeSerialPort()
    {
        if (serialPort->isOpen())
            serialPort->close();
    
        outputTextEdit->append("Serial port closed.");
    }
    
  • 实现发送数据的槽函数:

    void MainWindow::sendData()
    {
        if (serialPort->isOpen()) {
            QByteArray data = inputLineEdit->text().toUtf8();
            serialPort->write(data);
            outputTextEdit->append("Data sent: " + data);
        } else {
            outputTextEdit->append("Serial port is not open.");
        }
    }
    
  • 实现接收数据的槽函数:

    void MainWindow::readData()
    {
        QByteArray data = serialPort->readAll();
        outputTextEdit->append("Data received: " + data);
    }
    
  • 实现处理串口错误的槽函数:

    void MainWindow::handleError(QSerialPort::SerialPortError error)
    {
        if (error == QSerialPort::ResourceError) {
            outputTextEdit->append("Critical Error: " + serialPort->errorString());
            closeSerialPort();
        }
    }
    

方法分析

  1. 构造函数:

    QSerialPort(QObject *parent = nullptr)
    QSerialPort(const QString &name, QObject *parent = nullptr)
    QSerialPort(const QSerialPortInfo &info, QObject *parent = nullptr)
    

    用于创建一个 QSerialPort 对象,指定串口名称或通过 QSerialPortInfo 对象来创建。

    示例:

    QSerialPort serialPort1;
    QSerialPort serialPort2("COM1");
    QSerialPortInfo portInfo;
    QSerialPort serialPort3(portInfo);
    
  2. bool open(QIODevice::OpenMode mode):以指定模式打开串口,可以是 QIODevice::ReadOnly, QIODevice::WriteOnly, 或 QIODevice::ReadWrite

    bool open(QIODevice::OpenMode mode)
    

    以指定的模式打开串口,返回值表示操作是否成功。

    示例:

    if (serialPort.open(QIODevice::ReadWrite)) {
        qDebug() << "Serial port opened successfully.";
    } else {
        qDebug() << "Failed to open serial port:" << serialPort.errorString();
    }
    
  3. void close():关闭串口。

    void close()
    

    关闭当前打开的串口。

    示例:

    serialPort.close();
    qDebug() << "Serial port closed.";
    
  4. bool setBaudRate(qint32 baudRate, Directions directions = AllDirections):设置串口的波特率。

    bool setBaudRate(qint32 baudRate, Directions directions = AllDirections)
    

    设置串口的波特率,可以指定方向(读、写或两者)。返回值表示操作是否成功。

    示例:

    if (serialPort.setBaudRate(QSerialPort::Baud9600)) {
        qDebug() << "Baud rate set to 9600.";
    } else {
        qDebug() << "Failed to set baud rate:" << serialPort.errorString();
    }
    
  5. qint32 baudRate(Directions directions = AllDirections) const:返回当前波特率。

    qint32 baudRate(Directions directions = AllDirections) const
    

    返回当前设置的波特率。

    示例:

    qint32 currentBaudRate = serialPort.baudRate();
    qDebug() << "Current baud rate:" << currentBaudRate;
    
  6. bool setDataBits(QSerialPort::DataBits dataBits):设置数据位。

    bool setDataBits(QSerialPort::DataBits dataBits)
    

    设置串口的数据位,常用值包括:QSerialPort::Data8QSerialPort::Data7等。返回值表示操作是否成功。

    示例:

    if (serialPort.setDataBits(QSerialPort::Data8)) {
        qDebug() << "Data bits set to 8.";
    } else {
        qDebug() << "Failed to set data bits:" << serialPort.errorString();
    }
    
  7. QSerialPort::DataBits dataBits() const:返回当前数据位。

    QSerialPort::DataBits dataBits() const
    

    返回当前设置的数据位。

    示例:

    QSerialPort::DataBits currentDataBits = serialPort.dataBits();
    qDebug() << "Current data bits:" << currentDataBits;
    
  8. bool setParity(QSerialPort::Parity parity):设置校验位。

    bool setParity(QSerialPort::Parity parity)
    

    设置串口的校验位,包括:无校验(QSerialPort::NoParity)、偶校验(QSerialPort::EvenParity)、奇校验(QSerialPort::OddParity)等。返回值表示操作是否成功。

    示例:

    if (serialPort.setParity(QSerialPort::NoParity)) {
        qDebug() << "No parity set.";
    } else {
        qDebug() << "Failed to set parity:" << serialPort.errorString();
    }
    
  9. QSerialPort::Parity parity() const:返回当前校验位。

    QSerialPort::Parity parity() const
    

    返回当前设置的校验位。

    示例:

    QSerialPort::Parity currentParity = serialPort.parity();
    qDebug() << "Current parity:" << currentParity;
    
  10. bool setStopBits(QSerialPort::StopBits stopBits):设置停止位。

    bool setStopBits(QSerialPort::StopBits stopBits)
    

    设置串口的停止位,包括:一个停止位(QSerialPort::OneStop)和两个停止位(QSerialPort::TwoStop)等。返回值表示操作是否成功。

    示例:

    if (serialPort.setStopBits(QSerialPort::OneStop)) {
        qDebug() << "Stop bits set to one.";
    } else {
        qDebug() << "Failed to set stop bits:" << serialPort.errorString();
    }
    
  11. QSerialPort::StopBits stopBits() const:返回当前停止位。

    QSerialPort::StopBits stopBits() const
    

    返回当前设置的停止位。

    示例:

    QSerialPort::StopBits currentStopBits = serialPort.stopBits();
    qDebug() << "Current stop bits:" << currentStopBits;
    
  12. bool setFlowControl(QSerialPort::FlowControl flowControl):设置流控制。

    bool setFlowControl(QSerialPort::FlowControl flowControl)
    

    设置串口的流控制,包括:无流控制(QSerialPort::NoFlowControl)、硬件流控制(QSerialPort::HardwareControl)、软件流控制(QSerialPort::SoftwareControl)等。返回值表示操作是否成功。

    示例:

    if (serialPort.setFlowControl(QSerialPort::NoFlowControl)) {
        qDebug() << "No flow control set.";
    } else {
        qDebug() << "Failed to set flow control:" << serialPort.errorString();
    }
    
  13. QSerialPort::FlowControl flowControl() const:返回当前流控制。

    QSerialPort::FlowControl flowControl() const
    

    返回当前设置的流控制方式。

    示例:

    QSerialPort::FlowControl currentFlowControl = serialPort.flowControl();
    qDebug() << "Current flow control:" << currentFlowControl;
    
  14. qint64 write(const char *data, qint64 maxSize):写入数据到串口。

    qint64 write(const char *data, qint64 maxSize)
    

    将指定的数据写入串口,返回写入的字节数。如果返回值为-1,则表示写入失败。

    示例:

    const char *data = "Hello";
    qint64 bytesWritten = serialPort.write(data, strlen(data));
    qDebug() << "Bytes written:" << bytesWritten;
    
  15. qint64 write(const QByteArray &byteArray):写入数据到串口。

    qint64 write(const QByteArray &byteArray)
    

    将 QByteArray 类型的数据写入串口,返回写入的字节数。如果返回值为-1,则表示写入失败。

    示例:

    QByteArray data = "Hello";
    qint64 bytesWritten = serialPort.write(data);
    qDebug() << "Bytes written:" << bytesWritten;
    
  16. QByteArray readAll():读取所有可用数据。

    QByteArray readAll()
    

    读取串口中所有可用的数据,并将其作为 QByteArray 返回。

    示例:

    QByteArray data = serialPort.readAll();
    qDebug() << "Data received:" << data;
    
  17. qint64 read(char *data, qint64 maxSize):读取数据。

    qint64 read(char *data, qint64 maxSize)
    

    从串口读取最多 maxSize 字节的数据,存储到 data 缓冲区中,返回读取的字节数。如果返回值为-1,则表示读取失败。

    示例:

    char buffer[100];
    qint64 bytesRead = serialPort.read(buffer, sizeof(buffer));
    qDebug() << "Bytes read:" << bytesRead;
    
  18. qint64 bytesAvailable() const:返回可读的字节数。

    qint64 bytesAvailable() const
    

    该方法返回单个读缓冲区中当前要读取的字节数。它可以用于在读取数据之前确定有多少数据可用。

    示例:

    qint64 availableBytes = serialPort->bytesAvailable();
    qDebug() << "Bytes available to read:" << availableBytes;
    
  19. bool waitForReadyRead(int msecs):等待数据可读。

    bool waitForReadyRead(int msecs)
    

    该方法在指定的毫秒数内等待数据变得可读,如果在超时时间内有数据可读,则返回 true;否则返回 false。它通常与无事件驱动的嵌入式系统中的阻塞 I/O 一起使用。

    示例:

    if (serialPort->waitForReadyRead(5000)) {
        QByteArray data = serialPort->readAll();
        qDebug() << "Data received:" << data;
    } else {
        qDebug() << "Read timed out.";
    }
    
  20. bool setBreakEnabled(bool set):设置或清除串口的断点信号。

    bool setBreakEnabled(bool set)
    

    该方法设置或清除串口的断点信号。如果成功设置或清除断点信号,返回 true;否则,返回 false

    示例:

    if (!serialPort->setBreakEnabled(true)) {
        qDebug() << "Failed to set break:" << serialPort->errorString();
    }
    
  21. bool flush():刷新串口缓冲区。

    bool flush()
    

    该方法刷新串口的输入和输出缓冲区。如果成功,则返回 true;否则,返回 false

    示例:

    if (serialPort->flush()) {
        qDebug() << "Serial port buffer flushed.";
    } else {
        qDebug() << "Failed to flush buffer:" << serialPort->errorString();
    }
    

高级应用技巧

1. 实现串口数据完整性校验

在串口通信中,使用校验和(checksum)或 CRC(循环冗余校验)来确保数据的完整性是常见做法。以下是一个简单的校验和计算示例:

uint8_t calculateChecksum(const QByteArray &data)
{
    uint8_t checksum = 0;
    for (char byte : data) {
        checksum += static_cast<uint8_t>(byte);
    }
    return checksum;
}

在发送和接收数据时,可以附加校验和,并在接收时进行验证:

void MainWindow::sendDataWithChecksum(const QByteArray &data)
{
    QByteArray packet = data;
    uint8_t checksum = calculateChecksum(data);
    packet.append(checksum);
    serialPort->write(packet);
}

void MainWindow::readData()
{
    QByteArray data = serialPort->readAll();
    if (!data.isEmpty()) {
        uint8_t receivedChecksum = data.at(data.size() - 1);
        QByteArray payload = data.left(data.size() - 1);
        if (calculateChecksum(payload) == receivedChecksum) {
            outputTextEdit->append("Data received (valid): " + payload);
        } else {
            outputTextEdit->append("Data received (invalid checksum).");
        }
    }
}
2. 使用线程处理串口通信

为了避免在主线程中处理串口通信而导致界面卡顿,可以将串口通信放到单独的线程中。使用工作者/线程模式实现:

class SerialWorker : public QObject
{
    Q_OBJECT

public:
    SerialWorker(const QString &portName, QObject *parent = nullptr)
        : QObject(parent), serialPort(new QSerialPort(portName, this)) {}

public slots:
    void start()
    {
        if (serialPort->open(QIODevice::ReadWrite)) {
            connect(serialPort, &QSerialPort::readyRead, this, &SerialWorker::readData);
            qDebug() << "Serial port opened.";
        } else {
            qDebug() << "Failed to open serial port:" << serialPort->errorString();
        }
    }

    void stop()
    {
        if (serialPort->isOpen()) {
            serialPort->close();
        }
        qDebug() << "Serial port closed.";
    }

    void readData()
    {
        QByteArray data = serialPort->readAll();
        emit dataReceived(data);
    }

    void writeData(const QByteArray &data)
    {
        if (serialPort->isOpen()) {
            serialPort->write(data);
        }
    }

signals:
    void dataReceived(const QByteArray &data);

private:
    QSerialPort *serialPort;
};

在主程序中启动串口通信线程:

SerialWorker *worker = new SerialWorker("COM1");
QThread *thread = new QThread;

worker->moveToThread(thread);

connect(thread, &QThread::started, worker, &SerialWorker::start);
connect(thread, &QThread::finished, worker, &SerialWorker::stop);
connect(worker, &SerialWorker::dataReceived, [](const QByteArray &data) {
    qDebug() << "Data received in main thread:" << data;
});

thread->start();

// 在需要时停止和清理线程
thread->quit();
thread->wait();
delete worker;
delete thread;

通过将串口通信移到单独的线程中,可以避免主线程的卡顿,确保界面响应流畅。

3. 使用信号槽机制处理串口事件

充分利用 QSerialPort 的信号槽机制,可以处理串口的各种事件(如数据可读、数据写入完成、错误发生等),从而实现异步通信。

// 主窗口类中的信号和槽连接
connect(serialPort, &QSerialPort::readyRead, this, &MainWindow::readData);
connect(serialPort, &QSerialPort::bytesWritten, this, &MainWindow::bytesWrittenHandler);
connect(serialPort, &QSerialPort::errorOccurred, this, &MainWindow::handleError);

// 槽函数
void MainWindow::bytesWrittenHandler(qint64 bytes)
{
    qDebug() << bytes << "bytes written.";
}

void MainWindow::handleError(QSerialPort::SerialPortError error)
{
    if (error == QSerialPort::ResourceError) {
        qDebug() << "Critical Error:" << serialPort->errorString();
        closeSerialPort();
    }
}
4. 动态调整串口参数

在有些应用场景中,需要根据外部因素动态调整串口参数(如波特率、数据位等)。可以使用 QSerialPort 提供的相关方法来实现。

void MainWindow::adjustSerialPortParameters(qint32 baudRate, QSerialPort::DataBits dataBits)
{
    if (serialPort->isOpen()) {
        serialPort->setBaudRate(baudRate);
        serialPort->setDataBits(dataBits);
        qDebug() << "Serial port parameters adjusted:" <<
                    "BaudRate:" << baudRate <<
                    "DataBits:" << dataBits;
    }
}

通过提供一个接口,允许用户或程序动态调整串口参数,以满足不同的通信需求。

🚩 常见问题及解决方法

1. 串口无法打开

在使用 QSerialPort 打开串口时,可能会遇到各种错误,其中一些常见原因包括:

  • 串口被占用:检查串口是否被其他程序占用。
  • 权限问题:在 Linux 系统上,确保当前用户有权访问串口设备(通常需要属于 dialout 组)。

解决方法:

if (!serialPort->open(QIODevice::ReadWrite)) {
    qDebug() << "Failed to open serial port:" << serialPort->errorString();
}

2. 数据丢失或乱码

数据丢失或乱码是串口通信中的常见问题,通常是由于串口参数(波特率、数据位、校验位、停止位)设置不匹配导致的。

解决方法:

  • 确保通信双方的串口参数一致。
  • 检查串口缓冲区是否溢出,尝试增大缓冲区大小或更频繁地读取数据。
  • 使用适当的校验和/CRC 校验确保数据完整性。

3. 串口通信延迟

在某些情况下,串口通信可能会出现延迟,导致数据传输不及时。这可能是由多种因素引起的,包括硬件性能限制、串口配置、线程调度等。

解决方法:

  • 尽量使用更高的波特率,以提高数据传输速度。
  • 将串口通信移到单独的线程中,避免主线程卡顿影响串口通信。
  • 优化数据传输和处理逻辑,减少不必要的延迟。

📌 总结

QSerialPort 是 Qt 提供的一个功能强大、简单易用的串口通信类。通过本文的学习,您应该对 QSerialPort 的基本使用、高级应用技巧及相关注意事项有了全面的理解。在实际项目中,QSerialPort 可以帮助实现与外部设备的串口通信,确保数据的可靠传输和接收。希望本文能帮助您更好地理解和使用 QSerialPort,在各种应用场景中实现高效稳定的串口通信。

07-21 01:01