Qt QSerialPort串口通信

1. 示例

在这里插入图片描述

2. 轮询电脑串口设备

    // 查询所有串口设备
    QList<QSerialPortInfo> serialList = QSerialPortInfo::availablePorts();

    QStringList serialPortNameList;
    foreach(const QSerialPortInfo &info,QSerialPortInfo::availablePorts())
    {
        serialPortNameList << info.portName();
        qDebug()<<"serialPortName: "<<info.portName();
    }
    // 展示在下拉列表中
    ui->comboBox->addItems(serialPortNameList);

注意,要使用QSerialPort需要在pro中添加 QT += serialport

3. 串口类

	m_serialPort = new QSerialPort(this);
    connect(m_serialPort, &QSerialPort::readyRead, this, &MainWindow::handleReadyRead);
    connect(m_serialPort, &QSerialPort::errorOccurred, this, &MainWindow::handleError);

readRead 事件: 异步接收串口的数据
errorOccurred 事件: 异常

void MainWindow::handleReadyRead()
{
    m_recvData = m_serialPort->readAll();
    writeMsg("异步收到:" + QString::fromUtf8(m_recvData));

    emit serialDataReadFinished();  // 自定义的事件,为了配合通知异步等待完成
}

void MainWindow::handleError(QSerialPort::SerialPortError serialPortError)
{
    if (serialPortError == QSerialPort::ReadError)
    {
        QString error = QString("An I/O error occurred while reading "
                                        "the data from port %1, error: %2")
                            .arg(m_serialPort->portName())
                            .arg(m_serialPort->errorString());
        writeMsg(error);
    }
}

4. 发送串口数据

4.1 发送串口数据后,不等待, 由handleReadyRead异步接收来自串口的数据

void MainWindow::on_sendMsgButton_clicked()
{
    QString sendData = ui->textEdit->toPlainText();
    writeMsg("发送:" + sendData);

    m_serialPort->write(sendData.toUtf8(), sendData.toUtf8().size());
}

4.2 发送串口数据后,要同步等待串口返回数据

void MainWindow::on_sendMsgButton_clicked()
{
    QString sendData = ui->textEdit->toPlainText();
    writeMsg("发送:" + sendData);

    m_serialPort->write(sendData.toUtf8(), sendData.toUtf8().size());

	if(!m_serialPort->waitForReadyRead(5000))
	{
		writeMsg("等待回复超时");
		return;
	}
	
	QByteArray recvData = m_serialPort->readAll();
	writeMsg("同步收到:" + QString::fromUtf8(recvData));
}

但是,如果我们监听了QSerialPortreadRead 事件,这里waitForReadyRead是等不到串口返回的数据的,一直是超时。



有时我们给串口发送消息后需要及时收到串口返回数据,又要在没有发送消息时,采用异步监听的方式实时接收串口上报的数据。
所以:
a. 需要同步等待读取串口数据;
b. 需要监听readRead 事件,实时接收数据



解决方法 一【最初的解决思路,后来发现有问题】:

我们可以自己写一个异步等待的方法,使异步转同步

bool MainWindow::customerWaitForReadyRead(int msec)
{
    QEventLoop eventloop;
    QObject::connect(this, &MainWindow::serialDataReadFinished, &eventloop, &QEventLoop::quit);

    bool isTimeout = false;
    QTimer::singleShot(msec, &eventloop, [&](){
        eventloop.quit();
        isTimeout = true;
    });

    eventloop.exec(QEventLoop::AllEvents);
    return !isTimeout;
}
  • serialDataReadFinished事件是我们自定义的事件,在handleReadyRead槽函数中收到串口数据后,发出此事件通知异步等待结束;
  • QTimer::singleShot计时,超时后,异步等待也结束;
  • 返回值,如果是收到serialDataReadFinished事件结束的异步等待,则是正常结束,返回true;如果是超时结束,则是异常结束,返回false.

于是,发送串口数据后,需要等待结果的,可以写成这样:

void MainWindow::on_sendMsgButton_2_clicked()
{
    QString sendData = ui->textEdit->toPlainText();
    writeMsg("发送:" + sendData);

    m_serialPort->write(sendData.toUtf8(), sendData.toUtf8().size());
    if(!customerWaitForReadyRead(5000))	// 自定义的异步等待函数
    {
        writeMsg("等待回复超时");
    }
    else
    {
        writeMsg("同步收到: " + m_recvData);
    }
}

此方法有弊端,eventloop在等待所有的事件,也就是说,在等待serialDataReadFinished事件的同时,也会等待按钮点击事件,如果在另一个线程再次触发了Button_2的点击事件,这里就会重复进入,导致只会等到第二次Button_2的点击事件的回复,第一次点击的回复永远等不到了。
所以推荐使用解决方法二,因为串口类自带的waitForReadyRead是阻塞等待的, 即使多次触发Button_2的点击事件,也是排队执行的,不会出现方法一的问题



解决方法 二【推荐使用】:

write之前 disconnect 断开 readRead 事件的连接, 使用串口类自带的 waitForReadyRead 等待串口数据,然后读取串口数据。读完后再次使用 connect 连接 readRead 事件。

因为串口类自带的waitForReadyRead是阻塞等待的, 即使多次触发Button_2的点击事件,也是排队执行的,不会出现方法一的问题

void MainWindow::on_sendMsgButton_2_clicked()
{
    QString sendData = ui->textEdit->toPlainText();
    writeMsg("发送:" + sendData);
	
	// 断开readyRead事件
	disconnect(m_serialPort, &QSerialPort::readyRead, this, &MainWindow::handleReadyRead);
	
    m_serialPort->write(sendData.toUtf8(), sendData.toUtf8().size());
    if(!m_serialPort->waitForReadyRead(5000))	// 串口类自带的等待函数
    {
        writeMsg("等待回复超时");
    }
    else
    {
    	m_recvData = m_serialPort->readAll();
        writeMsg("同步收到: " + m_recvData);
    }
    
    // 重新连接readyRead事件
	connect(m_serialPort, &QSerialPort::readyRead, this, &MainWindow::handleReadyRead);
}


5. 完整代码

#include "mainwindow.h"
#include "ui_mainwindow.h"

#include <QDateTime>
#include <QDebug>
#include <QSerialPortInfo>
#include <QThread>
#include <QTimer>


// 关于中文乱码的问题, 不必像下面这样设置,因为这样必须在所有cpp文件中都要添加这几行代码。
// 完美解决的方法是在pro中添加:msvc:QMAKE_CXXFLAGS += /utf-8
// #if defined(_MSC_VER) && (_MSC_VER >= 1600)
// #pragma execution_character_set("utf-8")
// #endif


MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent),
                                          ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    // 查询所有串口设备
    QList<QSerialPortInfo> serialList = QSerialPortInfo::availablePorts();

    QStringList serialPortNameList;
    foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts()) {
        serialPortNameList << info.portName();
        qDebug() << "serialPortName: " << info.portName();
    }
    // 展示在下拉列表中
    ui->comboBox->addItems(serialPortNameList);
    ui->lineEdit->setText("115200");

    m_serialPort = new QSerialPort(this);
    connect(m_serialPort, &QSerialPort::readyRead, this, &MainWindow::handleReadyRead);
    connect(m_serialPort, &QSerialPort::errorOccurred, this, &MainWindow::handleError);
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::writeMsg(QString str)
{
    QString strLog = QString("%1 [%2]  %3").arg(QDateTime::currentDateTime().toString("hh:mm:ss zzz")).arg(quintptr(QThread::currentThreadId())).arg(str);

    QTextCursor tc = ui->textBrowser->textCursor();
    tc.movePosition(QTextCursor::End);
    tc.insertText(strLog + "\n");
    ui->textBrowser->moveCursor(QTextCursor::End);
}

void MainWindow::on_connectButton_clicked()
{
    if (ui->connectButton->text() == "连接") {
        QString portName = ui->comboBox->currentText();
        int baudRate = ui->lineEdit->text().toInt();

        m_serialPort->setPortName(portName);
        if (!m_serialPort->open(QIODevice::ReadWrite)) {
            writeMsg(QString("串口[%1]打开失败,%2").arg(portName).arg(m_serialPort->errorString()));
            return;
        }

        m_serialPort->setBaudRate(baudRate, QSerialPort::AllDirections);
        m_serialPort->setDataBits(QSerialPort::Data8);            // 数据位为8位
        m_serialPort->setFlowControl(QSerialPort::NoFlowControl); // 无流控制
        m_serialPort->setParity(QSerialPort::NoParity);           // 无校验位
        m_serialPort->setStopBits(QSerialPort::OneStop);          // 一位停止位

        writeMsg("打开串口成功");
        ui->connectButton->setText("断开连接");
    } else {
        m_serialPort->close();
        writeMsg("断开串口成功");
        ui->connectButton->setText("连接");
    }
}

void MainWindow::handleReadyRead()
{
    m_recvData = m_serialPort->readAll();
    writeMsg("异步收到:" + QString::fromUtf8(m_recvData));
}

void MainWindow::handleError(QSerialPort::SerialPortError serialPortError)
{
    if (serialPortError == QSerialPort::ReadError) {
        QString error = QString("An I/O error occurred while reading "
                                "the data from port %1, error: %2")
                            .arg(m_serialPort->portName())
                            .arg(m_serialPort->errorString());
        writeMsg(error);
    }
}


// 异步。 发送后不等待回复
void MainWindow::on_sendMsgButton_clicked()
{
    QString sendData = ui->textEdit->toPlainText();
    writeMsg("发送:" + sendData);

    m_serialPort->write(sendData.toUtf8(), sendData.toUtf8().size());
}

// 同步。发送后等待回复
void MainWindow::on_sendMsgButton_2_clicked()
{
    QString sendData = ui->textEdit->toPlainText();
    writeMsg("发送:" + sendData);

    // 断开readyRead事件
    disconnect(m_serialPort, &QSerialPort::readyRead, this, &MainWindow::handleReadyRead);

    m_serialPort->write(sendData.toUtf8(), sendData.toUtf8().size());
    if (!m_serialPort->waitForReadyRead(5000)) {
        writeMsg("等待回复超时");
    } else {
        writeMsg("同步收到: " + m_recvData);
    }

    // 重新连接readyRead事件
    connect(m_serialPort, &QSerialPort::readyRead, this, &MainWindow::handleReadyRead);
}




以上完整代码下载地址:QSerialPortTest



6. 测试

下载一个虚拟串口工具 Virtual Serial Port,创建一个虚拟串口对,例如我创建的串口对是 COM4 和 COM5:
在这里插入图片描述
创建串口对后, 在系统设备管理器中,能看到:

在这里插入图片描述
编译测试程序后, 打开两个进程,一个选COM4,一个选COM5:
在这里插入图片描述

### 回答1: Qt是一款跨平台的开发框架,其中的SerialPort类可以用来与串行设备进行通信。CDC(Communication Device Class)是一种通信设备类别,是一种通用的串行设备协议。 QtSerialPort类提供了一套方便易用的接口,可以用来访问串行端口进行读写操作。它提供了许多函数和信号槽,可以实现的功能包括打开和关闭端口、设置波特率、数据位、校验位等通信参数、读取和写入数据等。通过这些函数,我们可以轻松地进行串行通信。 对于CDC设备,我们可以使用QtSerialPort类实现与之通信。CDC设备一般都支持标准的串行通信协议,因此可以使用QtSerialPort类提供的功能进行数据的读写。我们只需要打开串行端口,设置好通信参数,然后通过SerialPort类的写入函数发送数据,通过读取函数读取设备返回的数据即可。 为了使用SerialPort类来与CDC设备进行通信,我们需要先包含相应的头文件,并链接SerialPort库。然后我们可以在代码中实例化一个SerialPort对象,并设置好通信参数。通过调用对象的打开函数,我们就可以将该串口打开。接下来我们就可以调用SerialPort对象的写入函数发送数据,或者通过读取函数读取设备返回的数据。 总之,QtSerialPort类提供了一套方便易用的接口,可以用来与串行设备进行通信。而CDC是一种通信设备类别,通过使用QtSerialPort类,我们可以很方便地实现与CDC设备的通信。 ### 回答2: SerialPort 类是 Qt 提供的用于处理串口通信的类,CDC (Communication Device Class) 是一种标准的 USB 设备类别之一,用于处理通信设备的驱动程序和通信协议。 QtSerialPort 类支持与 CDC 设备进行通信。它通过提供一组方法和信号来实现与串口的交互。QtSerialPort 类可以完成打开、关闭、读取、写入等串口操作。通过使用该类,我们可以通过串口与 CDC 设备进行数据的发送和接收。 在使用 SerialPort 类与 CDC 设备通信时,我们首先需要创建一个 SerialPort 实例,并指定串口的属性,如波特率、数据位、校验位和停止位。然后,我们可以打开串口并通过 write() 方法向 CDC 设备发送数据,或通过 readAll() 方法从 CDC 设备读取数据。 除了基本的串口操作外,SerialPort 类还提供了一些与 CDC 设备通信相关的特殊功能,如设置流控制协议、设置数据流控制模式等。这些功能可以根据具体的应用需求进行使用。 总之,QtSerialPort 类提供了一种方便和灵活的方式来与 CDC 设备进行通信。通过使用该类,我们可以很容易地实现串口与 CDC 设备之间的数据交换,满足各种通信需求。 ### 回答3: Qt SerialPort 类提供了一种简便的方式来与串口进行通信,并且支持 CDC(Communications Device Class)。 CDC 是一种用于串行通信设备的通用设备类。它定义了一组通信设备的标准模型和规范,旨在在各种操作系统上实现串口设备的兼容性。 在 Qt 中,我们可以使用 SerialPort 类来实现与 CDC 兼容的串口设备的通信。这个类提供了一组方法来设置和控制串口的属性,如波特率、数据位、停止位和校验位等。我们可以使用这些方法来配置和初始化串口设备。 我们可以使用 SerialPort 的 `open` 方法打开一个串口设备,并且可以通过 `read` 和 `write` 方法来读取和写入数据。read 方法用于从串口设备中读取数据,write 方法用于向串口设备中写入数据。 使用 Qt SerialPort 类与 CDC 兼容的串口设备进行通信的示例代码如下: ``` c++ #include <QtSerialPort/QSerialPort> #include <QtSerialPort/QSerialPortInfo> int main() { QSerialPort serial; serial.setPortName("COM1"); serial.setBaudRate(QSerialPort::Baud115200); serial.setDataBits(QSerialPort::Data8); serial.setStopBits(QSerialPort::OneStop); serial.setParity(QSerialPort::NoParity); if (serial.open(QIODevice::ReadWrite)) { // 读取数据 QByteArray data = serial.readAll(); // 处理数据 // 写入数据 QString message = "Hello, World!"; serial.write(message.toUtf8()); } serial.close(); return 0; } ``` 上述示例代码中,我们创建了一个 SerialPort 对象,并设置了串口设备的属性。然后,我们打开了串口设备,并使用 read 方法读取设备上的数据,并使用 write 方法向设备写入数据。最后,我们关闭了串口设备。 通过 Qt SerialPort 类以及与 CDC 兼容的串口设备,我们可以实现方便且灵活的串口通信功能。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值