• 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);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    注意,要使用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);
    
    • 1
    • 2
    • 3

    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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    4. 发送串口数据

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

    void MainWindow::on_sendMsgButton_clicked()
    {
        QString sendData = ui->textEdit->toPlainText();
        writeMsg("发送:" + sendData);
    
        m_serialPort->write(sendData.toUtf8(), sendData.toUtf8().size());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    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));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

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



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



    解决方法 一:

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

    bool MainWindow::waiteForReadyRead(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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 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(!waiteForReadyRead(5000))	// 自定义的异步等待函数
        {
            writeMsg("等待回复超时");
        }
        else
        {
            writeMsg("同步收到: " + m_recvData);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    此方法有弊端,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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22




    以上完整代码:QSerialPortTest



    5. 测试

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

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

  • 相关阅读:
    初次使用servlet写HelloWorld
    【计算机网络笔记】网络地址转换(NAT)
    CSS 创建
    【Android笔记22】Android中四大组件之Activity活动栈以及启动模式
    注塑行业MES系统解决方案,打造数字化智能工厂 先达智控
    把Eclipse创建的Web项目(非Maven)导入Idea
    一套简单的机器人短途路径规划算法
    SpringCloud无介绍快使用,nacos配置中心的基本使用(十九)
    SpringMVC
    手写redis分布式锁
  • 原文地址:https://blog.csdn.net/Jay_Xio/article/details/127394729