文章目录
-
- 1.创建工程
-
- 1.1 建立空白工程
- 1.2 添加控件
- 2. 在主线程实现串口发送
-
- 2.1 在.pro文件和.h文件中引入QSerialPort
- 2.2 查找可用串口
- 2.3 打开&关闭串口
- 2.4 将字节序列转换为对应的16进制字符串
- 2.5 将16进制字符串转换为对应的字节序列
- 2.6 使用串口发送
- 2.7 使用串口接收
- 3. 在子线程实现串口发送与接收
-
- 3.1 建立串口事务处理子线程类:SerialWorker
- 3.2 修改默认工程
- 3.3 增加子线程串口发送、接收槽函数、结果通知信号
- 3.4 在GUI线程中增加数据发送信号、结果接收槽
- 3.5 在GUI线程中添加串口子线程
- 3.6 效果演示
文章示例工程下载地址:
https://download.csdn.net/download/u014779536/13790253
1.创建工程
1.1 建立空白工程
工程名:Qt_MultiThread_SerialPort
1.2 添加控件
添加下图所示的所有控件,并重新命名:
各控件命名如下:
2. 在主线程实现串口发送
实现串口发送我们需要进行以下几个步骤:
- 查找可用串口
- 打开串口
- 通信发送
- 通信接收
- 关闭串口
- 字符串转16字节(发送用)
- 16字节转字符串(显示用)
2.1 在.pro文件和.h文件中引入QSerialPort
QT += serialport
#include <QSerialPort>
#include <QSerialPortInfo>
void InitSerialPortName();
2.2 查找可用串口
我们在程序启动时自动搜索电脑设备,并将可用的串口名称添加到串口选择框。
void MainWindow::InitSerialPortName()
{
// 清空下拉框
ui->box_portName->clear();
//通过QSerialPortInfo查找可用串口
foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
{
QString showName = info.portName()+":"+info.description();
qDebug() << showName.length();
ui->box_portName->addItem(showName);
}
}
2.3 打开&关闭串口
在manwindow.h中声明串口
设置打开按钮关闭槽函数:
void MainWindow::on_btn_openPort_clicked()
{
if(ui->btn_openPort->text()==QString("打开串口"))
{
//设置串口名
QString portName = (ui->box_portName->currentText()).split(":").at(0);
qDebug() << portName;
serial_1.setPortName(portName);
//设置波特率
serial_1.setBaudRate(ui->box_baudrate->currentText().toInt());
//设置停止位
if(ui->box_stopBit->currentText() == "1")
serial_1.setStopBits(QSerialPort::OneStop);
else if(ui->box_stopBit->currentText() == "1.5")
serial_1.setStopBits(QSerialPort::OneAndHalfStop);
else if(ui->box_stopBit->currentText() == "2")
serial_1.setStopBits(QSerialPort::TwoStop);
//设置数据位数
if(ui->box_dataBits->currentText() == "8")
serial_1.setDataBits(QSerialPort::Data8);
else if(ui->box_dataBits->currentText() == "7")
serial_1.setDataBits(QSerialPort::Data7);
else if(ui->box_dataBits->currentText() == "6")
serial_1.setDataBits(QSerialPort::Data6);
else if(ui->box_dataBits->currentText() == "5")
serial_1.setDataBits(QSerialPort::Data5);
//设置奇偶校验
if(ui->box_parityBit->currentText() == "None")
serial_1.setParity(QSerialPort::NoParity);
else if(ui->box_parityBit->currentText() == "Even")
serial_1.setParity(QSerialPort::EvenParity);
else if(ui->box_parityBit->currentText() == "Odd")
serial_1.setParity(QSerialPort::OddParity);
//设置流控制
serial_1.setFlowControl(QSerialPort::NoFlowControl);
//打开串口
if(!serial_1.open(QIODevice::ReadWrite))
{
QMessageBox::about(NULL, "提示", "无法打开串口!");
return;
}
//下拉菜单控件失能
ui->box_portName->setEnabled(false);
ui->box_baudrate->setEnabled(false);
ui->box_dataBits->setEnabled(false);
ui->box_parityBit->setEnabled(false);
ui->box_stopBit->setEnabled(false);
ui->btn_openPort->setText(QString("关闭串口"));
//发送按键使能
ui->btn_send->setEnabled(true);
}
else
{
//关闭串口
serial_1.close();
//下拉菜单控件使能
ui->box_portName->setEnabled(true);
ui->box_baudrate->setEnabled(true);
ui->box_dataBits->setEnabled(true);
ui->box_parityBit->setEnabled(true);
ui->box_stopBit->setEnabled(true);
ui->btn_openPort->setText(QString("打开串口"));
//发送按键失能
ui->btn_send->setEnabled(false);
}
}
2.4 将字节序列转换为对应的16进制字符串
QString MainWindow::ByteArrayToHexString(QByteArray data)
{
QString ret(data.toHex().toUpper());
int len = ret.length()/2;
qDebug()<<len;
for(int i=1;i<len;i++)
{
//qDebug()<<i;
ret.insert(2*i+i-1," ");
}
return ret;
}
2.5 将16进制字符串转换为对应的字节序列
QByteArray MainWindow::HexStringToByteArray(QString HexString)
{
bool ok;
QByteArray ret;
HexString = HexString.trimmed();
HexString = HexString.simplified();
QStringList sl = HexString.split(" ");
foreach (QString s, sl) {
if(!s.isEmpty())
{
char c = s.toInt(&ok,16)&0xFF;
if(ok){
ret.append(c);
}else{
qDebug()<<"非法的16进制字符:"<<s;
QMessageBox::warning(0,tr("错误:"),QString("非法的16进制字符: \"%1\"").arg(s));
}
}
}
//qDebug()<<ret;
return ret;
}
2.6 使用串口发送
void MainWindow::on_btn_send_clicked()
{
//获取界面上的数据并转换成utf8格式的字节流
QByteArray data;
if(ui->check_hexSend->isChecked() == true)
data = HexStringToByteArray(ui->edit_dataSend->toPlainText());
else
data = ui->edit_dataSend->toPlainText().toUtf8();
serial_1.write(data);
}
void MainWindow::on_btn_clearSend_clicked()
{
ui->edit_dataSend->clear();
}
效果演示:
2.7 使用串口接收
串口接收有两种方式:
- 主动查询
- 等待消息接收中断
我们这里介绍第二种。
[signal] void QIODevice::readyRead()
每当有新数据可用于从设备的当前读取通道读取时,都会发出此信号。 仅当有新数据可用时(例如,当网络套接字上有新的网络数据有效负载到达时,或将新的数据块附加到设备上时),它才会再次发出。
readyRead()不会递归地发出; 如果您重新进入事件循环或在连接到readyRead()信号的插槽内调用waitForReadyRead(),则不会重新发出该信号(尽管waitForReadyRead()可能仍返回true)。
对于实现从QIODevice派生的类的开发人员请注意:当新数据到达时,您应该始终发出readyRead()(不要仅仅因为缓冲区中仍有待读取的数据而发出它)。 在其他情况下不要发出readyRead()。
编写接收函数:
void MainWindow::serialPort_readyRead()
{
// 获取当前时间字符串
QDateTime current_date_time =QDateTime::currentDateTime();
QString dateStr =current_date_time.toString("[yyyy-MM-dd hh:mm:ss.zzz]");
//从接收缓冲区中读取数据
QByteArray buffer = serial_1.readAll();
QString bufferStr = ByteArrayToHexString(buffer);
QString displayStr = dateStr+"\n"+bufferStr+"\n";
//从界面中读取以前收到的数据
QString oldString = ui->browser_dataReceive->toPlainText();
oldString = oldString + QString(displayStr);
//清空以前的显示
ui->browser_dataReceive->clear();
//重新显示
ui->browser_dataReceive->append(oldString);
}
在构造函数关联信号与槽:
//连接信号和槽
connect(&serial_1, &QSerialPort::readyRead, this, &MainWindow::serialPort_readyRead);
效果演示:
3. 在子线程实现串口发送与接收
假如我们要对一个串口进行长时间的轮询监控,我们不可能把它放在GUI线程处理,这样会造成界面卡顿,此时,我们就需要在子线程使用串口,具体怎么使用呢?我们将串口指针传入子线程进行处理。
3.1 建立串口事务处理子线程类:SerialWorker
点击 “Add New":
选择”C++ class“:
设置类属性:
3.2 修改默认工程
1. 修改 serialworker.h
#ifndef SERIALWORKER_H
#define SERIALWORKER_H
#include <QObject>
#include <QSerialPort>
class SerialWorker : public QObject
{
Q_OBJECT
public:
explicit SerialWorker(QSerialPort *ser,QObject *parent = nullptr);
signals:
private:
QSerialPort *serial;
};
#endif // SERIALWORKER_H
2. 修改 serialworker.cpp
3.3 增加子线程串口发送、接收槽函数、结果通知信号
.h文件:
#ifndef SERIALWORKER_H
#define SERIALWORKER_H
#include <QObject>
#include <QSerialPort>
class SerialWorker : public QObject
{
Q_OBJECT
public:
explicit SerialWorker(QSerialPort *ser,QObject *parent = nullptr);
signals:
void sendResultToGui(QString result);
public slots:
void doDataSendWork(const QByteArray data);
void doDataReciveWork();
private:
QSerialPort *serial;
};
#endif // SERIALWORKER_H
cpp文件:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6KWaX92P-1608952364444)(https://tangxing-markdown-pic.oss-cn-shenzhen.aliyuncs.com/images/20201225162529.png)]
#include "serialworker.h"
#include <QDebug>
#include <QThread>
SerialWorker::SerialWorker(QSerialPort *ser,QObject *parent)
: QObject(parent),serial(ser)
{
}
void SerialWorker::doDataSendWork(const QByteArray data)
{
qDebug() << "子线程槽函数发送数据:" << data << "线程ID:" << QThread::currentThreadId();
// 发送数据
serial->write(data);
}
void SerialWorker::doDataReciveWork()
{
// 1.收到数据
QByteArray buffer = serial->readAll();
// 2.进行数据处理
QString resultStr = buffer;
qDebug() << "子线程收到数据:" << resultStr << "线程ID:" << QThread::currentThreadId();
// 3.将结果发送到主线程
emit sendResultToGui(resultStr);
}
3.4 在GUI线程中增加数据发送信号、结果接收槽
在mainwindow.h中增加与子线程结果接收槽函数:
signals:
void serialDataSend(const QByteArray data);
public slots:
void handleResults(QString &result);
在mainwindow.cpp 发送数据:
void MainWindow::on_btn_send_clicked()
{
//获取界面上的数据并转换成utf8格式的字节流
QByteArray data;
if(ui->check_hexSend->isChecked() == true)
data = HexStringToByteArray(ui->edit_dataSend->toPlainText());
else
data = ui->edit_dataSend->toPlainText().toUtf8();
// 在主线程发送
//serial_1.write(data);
// 在子线程发送
emit serialDataSend(data);
qDebug() << "主线程发送信号,线程ID:" << QThread::currentThreadId();
}
在mainwindow.cpp 结果结果:
void MainWindow::handleResults(QString &result)
{
qDebug() << "主线程收到结果数据:" << result << "线程ID:" << QThread::currentThreadId();
}
3.5 在GUI线程中添加串口子线程
在mainwindow.h定义子线程:
QThread serialThread_1; // 定义子线程
在mainwindow.h使用串口子线程:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
InitSerialPortName();
// 1.新建串口处理子线程
SerialWorker *serialWorker = new SerialWorker(&serial_1);
serialWorker->moveToThread(&serialThread_1);
// 2.连接信号和槽
connect(&serialThread_1, &QThread::finished,
serialWorker, &QObject::deleteLater); // 线程结束,自动删除对象
connect(this, &MainWindow::serialDataSend,
serialWorker, &SerialWorker::doDataSendWork); // 主线程串口数据发送的信号
connect(&serial_1, &QSerialPort::readyRead,
serialWorker, &SerialWorker::doDataReciveWork); // 主线程通知子线程接收数据的信号
connect(serialWorker, &SerialWorker::sendResultToGui,
this, &MainWindow::handleResults); // 主线程收到数据结果的信号
// 3.开始运行子线程
serialThread_1.start(); // 线程开始运行
// 在主线程接收串口数据
//connect(&serial_1, &QSerialPort::readyRead, this, &MainWindow::serialPort_readyRead);
}
MainWindow::~MainWindow()
{
// 退出串口1子线程
serialThread_1.quit();
serialThread_1.wait();
delete ui;
}
3.6 效果演示
文章示例工程下载地址:
https://download.csdn.net/download/u014779536/13790253