Qt中的串口编程基础类,实现了同步的读写接口及出错重连机制

2025-05-18   6 次阅读


serialcommunication.h

#ifndef SERIALCOMMUNICATOR_H
#define SERIALCOMMUNICATOR_H

#include <QObject>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QTimer>
#include <QMutex>

class SerialCommunicator : public QObject
{
    Q_OBJECT
public:
    explicit SerialCommunicator(QObject *parent = nullptr);
    ~SerialCommunicator();

    // 配置串口名称
    bool setPortName(const QString &portName);
    QString portName() const;

    // 同步读写接口
    bool writeData(const QByteArray &data, int timeout = 3000);
    QByteArray readData(int timeout = 3000);

    // 打开/关闭串口
    bool open();
    void close();
    bool isOpen() const;

signals:
    // 故障通知信号
    void errorOccurred(const QString &errorMsg);
    void portReconnected();

private slots:
    void handleError(QSerialPort::SerialPortError error);
    void attemptReconnect();

private:
    QSerialPort *m_serialPort;
    QString m_portName;
    bool m_isReconnecting;
    QTimer *m_reconnectTimer;
    QMutex m_mutex;
};

#endif // SERIALCOMMUNICATOR_H

serialcommunication.cpp

#include "serialcommunicator.h"
#include <QDebug>

SerialCommunicator::SerialCommunicator(QObject *parent) : QObject(parent),
    m_serialPort(new QSerialPort(this)),
    m_isReconnecting(false),
    m_reconnectTimer(new QTimer(this))
{
    // 连接错误信号
    connect(m_serialPort, &QSerialPort::errorOccurred, this, &SerialCommunicator::handleError);
    
    // 配置重连定时器
    m_reconnectTimer->setInterval(2000); // 2秒重试间隔
    connect(m_reconnectTimer, &QTimer::timeout, this, &SerialCommunicator::attemptReconnect);
}

SerialCommunicator::~SerialCommunicator()
{
    close();
}

bool SerialCommunicator::setPortName(const QString &portName)
{
    QMutexLocker locker(&m_mutex);
    
    if (m_serialPort->isOpen()) {
        qDebug() << "请先关闭串口再修改端口名称";
        return false;
    }
    
    m_portName = portName;
    return true;
}

QString SerialCommunicator::portName() const
{
    return m_portName;
}

bool SerialCommunicator::writeData(const QByteArray &data, int timeout)
{
    QMutexLocker locker(&m_mutex);
    
    if (!m_serialPort->isOpen()) {
        emit errorOccurred("串口未打开,无法写入数据");
        return false;
    }
    
    qint64 bytesWritten = m_serialPort->write(data);
    if (bytesWritten == -1) {
        emit errorOccurred("写入数据失败: " + m_serialPort->errorString());
        return false;
    } else if (bytesWritten != data.size()) {
        emit errorOccurred("未能写入全部数据");
        return false;
    }
    
    if (m_serialPort->waitForBytesWritten(timeout)) {
        return true;
    } else {
        emit errorOccurred("写入超时: " + m_serialPort->errorString());
        return false;
    }
}

QByteArray SerialCommunicator::readData(int timeout)
{
    QMutexLocker locker(&m_mutex);
    
    if (!m_serialPort->isOpen()) {
        emit errorOccurred("串口未打开,无法读取数据");
        return QByteArray();
    }
    
    if (m_serialPort->waitForReadyRead(timeout)) {
        QByteArray data = m_serialPort->readAll();
        // 处理可能的连续数据
        while (m_serialPort->waitForReadyRead(10))
            data.append(m_serialPort->readAll());
        return data;
    } else {
        emit errorOccurred("读取超时: " + m_serialPort->errorString());
        return QByteArray();
    }
}

bool SerialCommunicator::open()
{
    QMutexLocker locker(&m_mutex);
    
    if (m_serialPort->isOpen())
        return true;
    
    m_serialPort->setPortName(m_portName);
    
    // 使用默认配置,但设置为阻塞模式用于同步操作
    if (m_serialPort->open(QIODevice::ReadWrite)) {
        m_serialPort->setBaudRate(QSerialPort::Baud115200);
        m_serialPort->setDataBits(QSerialPort::Data8);
        m_serialPort->setParity(QSerialPort::NoParity);
        m_serialPort->setStopBits(QSerialPort::OneStop);
        m_serialPort->setFlowControl(QSerialPort::NoFlowControl);
        qDebug() << "串口已成功打开: " << m_portName;
        return true;
    } else {
        emit errorOccurred("无法打开串口: " + m_serialPort->errorString());
        return false;
    }
}

void SerialCommunicator::close()
{
    QMutexLocker locker(&m_mutex);
    
    if (m_serialPort->isOpen()) {
        m_serialPort->close();
        qDebug() << "串口已关闭: " << m_portName;
    }
}

bool SerialCommunicator::isOpen() const
{
    return m_serialPort->isOpen();
}

void SerialCommunicator::handleError(QSerialPort::SerialPortError error)
{
    if (error == QSerialPort::NoError)
        return;
    
    // 忽略一些不重要的错误
    if (error == QSerialPort::ParityError || 
        error == QSerialPort::FramingError ||
        error == QSerialPort::OverrunError) {
        emit errorOccurred("通信错误: " + m_serialPort->errorString());
        return;
    }
    
    // 处理严重错误,尝试重新连接
    if (!m_isReconnecting && m_serialPort->isOpen()) {
        QString errorMsg = "串口故障: " + m_serialPort->errorString();
        emit errorOccurred(errorMsg);
        qDebug() << errorMsg << "- 尝试重新连接";
        
        m_isReconnecting = true;
        close();
        m_reconnectTimer->start();
    }
}

void SerialCommunicator::attemptReconnect()
{
    qDebug() << "尝试重新连接串口: " << m_portName;
    
    if (open()) {
        m_reconnectTimer->stop();
        m_isReconnecting = false;
        emit portReconnected();
        qDebug() << "串口重新连接成功";
    } else {
        qDebug() << "串口重新连接失败,将再次尝试";
    }
}

Q.E.D.

知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议

No PAINS No GAINS.