文章目录
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));
}
但是,如果我们监听了QSerialPort
的readRead
事件,这里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: