过程
由于是初学,仅仅对串口编程有个了解,大概的功能是通过两个按钮实现串口数据的接收和暂停,其他的功能暂不深入研究。
通过串口调试助手发现,该串口的属性设置如左所示,接收的数据转为字符串后显示格式如右所示。这里是打算将右边的温度显示在一个LCD控件中,效果如下:
设计的思路是,新建一个串口类继承于QObject,然后在该类中实现串口的开、关、以及数据接收及处理功能,作为线程。如下所示:
templateThread.h (需要在pro 文件中添加serialport模块,跟在qt += serialport)
#ifndef TEMPLATETHREAD_H
#define TEMPLATETHREAD_H
#include <QObject>
#include <QSerialPort>
//#include <QSerialPortInfo>
class templateThread : public QObject
{
Q_OBJECT
public:
explicit templateThread(QObject *parent = nullptr);
//打开串口函数
void openPort();
//关闭串口函数
//温度显示函数
void templateDisplay();
private:
QSerialPort comPort; //设置串口对象
// QSerialPortInfo portInfo; //定义串口信息对象
QByteArray all; //串口接收数据的字符数组
signals:
void getTemplate(QString tplt);
};
#endif // TEMPLATETHREAD_H
templateThread.cpp
#include "templatethread.h"
#include <QDebug>
#include <QThread>
templateThread::templateThread(QObject *parent)
: QObject{parent}
{
//连接串口发出的已读信号和数据处理函数
//串口中有数据可读时,便会发送readyread信号,这时连接一个接收函数即可
connect(&comPort, &QSerialPort::readyRead, this, &templateThread::templateDisplay);
}
//数据处理
void templateThread::templateDisplay()
{
//定义暂存数组
//readall读取串口数据
QByteArray temp = comPort.readAll();
QString str(temp);
//将其拼接至已接收的数据中
all.append(temp);
qDebug() << QThread::currentThread();
qDebug() << str.toLocal8Bit().data();
//对已接受的数据进行截取操作
if(all.size() >= 34)
{
//取前34个字符
QString st(all.left(34));
//获取完毕,移除不需要的字符
all.remove(0, 34);
//获取该字符中的温度数据并发射
emit getTemplate(st.mid(8, 5));
}
}
void templateThread::openPort()
{
if(comPort.isOpen())
{
qDebug() << "串口已打开!";
}
else
{
//设置串口通信参数
comPort.setPortName("COM3");
comPort.setBaudRate(4800);
comPort.setDataBits(QSerialPort::Data8);
comPort.setStopBits(QSerialPort::OneStop);
comPort.setParity(QSerialPort::NoParity);
//打开串口
if(comPort.open(QIODeviceBase::ReadOnly))
qDebug() << "串口打开成功!";
}
}
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QThread>
#include "templatethread.h"
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private slots:
void on_ButtonStrat_clicked();
void on_ButtonStop_clicked();
//读取温度函数
void readTemplate(QString temp);
//窗口关闭函数
void dealClose();
private:
Ui::Widget *ui;
templateThread *tplt; //定义温度线程对象
QThread *thread; //定义子线程对象
QString str; //接收串口数据的字符串
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QSerialPortInfo>
#include <QDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//给线程对象分配空间
tplt = new templateThread;
//给子线程对象肥胖空间,并设置父对象为Widget
thread = new QThread(this);
//将温度显示置于子线程上
tplt->moveToThread(thread);
tplt->templateDisplay();
qDebug() << "主线程 = " << QThread::currentThread();
qDebug() << "thread = " << thread->currentThread();
qDebug() << "tport = " << tplt->thread();
//通过信号和槽,打开子线程中的串口
connect(ui->ButtonStrat, &QPushButton::clicked, tplt, &templateThread::openPort);
//使用信号和槽连接子线程处理函数和主线程读取数据函数
connect(tplt, &templateThread::getTemplate, this, &Widget::readTemplate);
//关闭窗口,清理内存
connect(this, &Widget::destroyed, this, &Widget::dealClose);
}
Widget::~Widget()
{
delete ui;
}
//打开串口
void Widget::on_ButtonStrat_clicked()
{
if(thread->isRunning() == false)
{
//开启子线程
thread->start();
}
}
//读取数据
void Widget::readTemplate(QString temp)
{
//获取子线程返回的温度字符串
str = temp;
double templateDiaplay = str.toDouble();
//显示该字符串于lcd控件
ui->lcdNumber->display(templateDiaplay);
}
//停止读取
void Widget::on_ButtonStop_clicked()
{
if(thread->isRunning() == true)
{
//停止收集数据并初始化lcd控件
ui->lcdNumber->display(0);
thread->quit();
thread->wait();
}
}
//关闭窗口
void Widget::dealClose()
{
//关闭窗口,清理子线程和分配的内存
thread->quit();
thread->wait();
delete tplt;
}
虽然,温度是可以正常提取了,但是会提示以下警告:
Object: Cannot create children for a parent that is in a different thread.
(Parent is QSerialPort(0x2ba0d11c9f0), parent’s thread is QThread(0x2ba0d226c20), current thread is QThread(0x2ba0d121050)
大概的意思就是,在当前线程0x2ba0d121050中不能为属于线程0x2ba0d226c20的串口对象创建新成员。
刚开始看到程序正常运行就没多管了,后来发现,这样不就违背了多线程的初衷吗?所以便继续追查下去,接着发现在widge的构造函数中
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//给线程对象分配空间
tplt = new templateThread;
//给子线程对象肥胖空间,并设置父对象为Widget
thread = new QThread(this);
//将温度显示置于子线程上
tplt->moveToThread(thread);
tplt->templateDisplay();
我为templateThread分配了一个空间,这时便会自动调用该线程的构造函数,即使后面把该类使用movetoThread丢到了创建的新线程中。那也只是该类中的方法属于新线程,该类的对象和构造函数依旧是在子线程运行的,而且在templateThread的构造函数中,我还建立了一个信号和槽连接。
代码执行流程大概如下所示:
main{ //进程
...
widget w; //实例化对象
widget{ //构造函数,主线程
...
templateThread t = new templateThread
templateThread{ //构造函数,与widget属于一个线程
这时使用movetoThread(); //创建了与widget不同的线程
...
}
...
}
...
templateThread{ //创建的新子线程
//这里只能运行一些非构造函数的方法。补充:必须使用信号和槽,不然该函数还是会在上面的主线程中
openPort()
templateDisplay();
}
}
所以便设想,可能是在运行子线程之前创造的连接使串口对象comPort提前存在了。于是便将该构造函数清空,将该连接移动至openPort(),中,发现还是不行。但是构造函数的坑应该算是解决了。
后面通过调试发现,只要是主线程所创建的tplt对象的成员,主线程一但通过该对象调用,都会自动跳转到上一段代码的widget主线程中,大概就是在分配空间的时候,已经将该对象的各种成员装配到了主线程的内存中了。
因此,我们必须间接调用主线程创建的对象中的成员。那么只能通过信号和槽的方式了。并且要将串口对象改成指针,只要在槽函数中new,分配空间,那么不就可以跳到子线程了吗?
经过大改,这个问题终于解决了,代码如下:
thread.h
#ifndef TEMPLATEPORT_H
#define TEMPLATEPORT_H
#include <QObject>
#include <QSerialPort>
class templatePort : public QObject
{
Q_OBJECT
public:
explicit templatePort(QObject *parent = nullptr);
~ templatePort();
signals:
void sendTemplate(QString temp); //发送温度值给父线程
public slots:
//温度计串口
void receivePortName(QString name); //接收串口名
void openPort(); //打开串口
void closePort(); //关闭串口
void dealData(); //处理数据
void serialPortInit(); //初始化串口
//可在后续添加一系列串口,当前类可以当作一个串口类,这样可以避免创建多个串口类
private:
QSerialPort* tPort; //创建串口对象
QString portName; //创建串口名
QByteArray all; //接收串口读取的数据
};
#endif // TEMPLATEPORT_H
thread.cpp
#include "templateport.h"
#include <QDebug>
#include <QThread>
templatePort::templatePort(QObject *parent)
: QObject{parent}
{
}
//传递系统支持的串口名
void templatePort::receivePortName(QString name)
{
portName = name;
qDebug() << "接收串口名函数所处线程号 = " << QThread::currentThread();
qDebug() << "串口名 = " << portName;
}
//串口初始化
void templatePort::serialPortInit()
{
tPort = new QSerialPort;
//设置串口通信参数
qDebug() << "串口名已设置";
tPort->setPortName(portName);
tPort->setBaudRate(4800);
tPort->setDataBits(QSerialPort::Data8);
tPort->setStopBits(QSerialPort::OneStop);
tPort->setParity(QSerialPort::NoParity);
//连接串口发出的已读信号和数据处理函数
connect(tPort, &QSerialPort::readyRead, this, &templatePort::dealData);
qDebug() << "温度初始化函数所处线程号 = " << QThread::currentThread();
}
//打开串口
void templatePort::openPort()
{
if(tPort->isOpen())
{
qDebug() << "串口打开失败!";
}
else
{
//打开串口
tPort->open(QIODeviceBase::ReadOnly);
qDebug() << "串口打开函数所处线程号 = " << QThread::currentThread();
qDebug() << "串口已打开!";
}
}
//关闭串口
void templatePort::closePort()
{
if(tPort->isOpen())
{
tPort->close();
qDebug() << "串口关闭函数所处线程号 = " << QThread::currentThread();
qDebug() << "串口已关闭!";
}
else
{
// qDebug() << "串口已关闭!";
}
}
//处理数据
void templatePort::dealData()
{
//定义暂存数组
QByteArray temp = tPort->readAll();
qDebug() << "temp: " <<temp;
//将其拼接至已接收的数据中
all.append(temp.toHex());
qDebug() << "all: " <<all;
//对已接受的数据进行截取操作
if(all.size() >= 34)
{
//取前34个字符
QString str(all.left(34));
//获取完毕,移除不需要的字符
all.remove(0, 34);
//获取该字符中的温度数据并发射
emit sendTemplate(str.mid(8, 5));
qDebug() << "温度发送函数所处线程号 = " << QThread::currentThread();
qDebug() << "温度已发送!";
}
}
templatePort::~templatePort()
{
delete tPort; //清理内存
qDebug() << "串口类对象已退出";
}
widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QThread>
#include <QSerialPortInfo>
#include "templateport.h"
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
public slots:
void sendPortName(); //发送串口名字
void sendportInit(); //发送串口初始化函数
void getTemplate(QString temp); //获取温度
signals:
void portName(QString name); //传递串口名字信号
void serialInit(); //串口初始化信号
private:
Ui::Widget *ui;
templatePort *tPort; //定义串口对象
QThread *thread; //定义子线程对象
QString str; //接收温度字符串
};
#endif // WIDGET_H
widget.cpp
#include "widget.h"
#include "ui_widget.h"
#pragma execution_character_set("utf-8")
#include <QThread>
Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
ui->setupUi(this);
//获取系统可用串口,并显示到comboBox组件上
foreach (QSerialPortInfo portInfo, QSerialPortInfo::availablePorts()) {
ui->comboBoxPort->addItem(portInfo.portName());
}
//给线程对象分配空间
tPort = new templatePort;
//给子线程对象分配空间,并设置父对象为Widget
thread = new QThread;
//将温度显示置于子线程上
tPort->moveToThread(thread);
//使用该方法
//thread和主线程属于同一个
//但是tport类的整体属于子线程
//不过如果不使用信号和槽连接,调用的子线程函数还是属于主线程
//猜测是因为tport指针是在主线程创建的,所以直接用该指针调用函数会运行在主线程
//启动子线程
thread->start();
qDebug() << "主线程 = " << QThread::currentThread();
qDebug() << "thread = " << thread->currentThread();
qDebug() << "tport = " << tPort->thread();
//发送串口名
connect(ui->ButtonStart, &QPushButton::clicked, this, &Widget::sendPortName);
//接收串口名
connect(this, &Widget::portName, tPort, &templatePort::receivePortName);
//连接串口初始化,并调用初始化
connect(this, &Widget::serialInit, tPort, &templatePort::serialPortInit);
//发送初始化信号
connect(ui->ButtonStart, &QPushButton::clicked, this, &Widget::sendportInit);
//开启串口
connect(ui->ButtonStart, &QPushButton::clicked, tPort, &templatePort::openPort, Qt::QueuedConnection);
//将子线程发射温度信号和获取温度槽函数连接起来
connect(tPort, &templatePort::sendTemplate, this, &Widget::getTemplate, Qt::QueuedConnection);
//关闭串口
connect(ui->ButtonStop, &QPushButton::clicked, tPort, &templatePort::closePort, Qt::QueuedConnection);
}
Widget::~Widget()
{
if(thread->isRunning() == true)
{
thread->quit();
thread->wait();
delete thread;//释放thread,因为它没有赋予父对象
delete tPort; //释放tPort,因为它没有赋予父对象
qDebug() << "线程已关闭!";
}
else
{
qDebug() << "线程已关闭!";
}
delete ui;
}
void Widget::sendPortName()
{
if(ui->comboBoxPort->currentText() != "")
{
qDebug() << "串口名信号发射成功,串口名为:" << ui->comboBoxPort->currentText();
emit portName(ui->comboBoxPort->currentText());
}
else
{
qDebug() << "请插入串口设备";
disconnect(tPort, 0, 0, 0);
disconnect(ui->ButtonStart, 0, 0, 0);
}
}
void Widget::sendportInit()
{
emit serialInit(); //发送信号
}
void Widget::getTemplate(QString temp)
{
str =temp;
qDebug() << "当前温度:" << str;
double templateDisplay = str.toDouble();
//显示该字符串于lcd控件
ui->lcdNumber->display(templateDisplay);
}
效果图如下:
后续还会添加新内容和改进,比如串口参数添加波特率等等。文章来源:https://www.toymoban.com/news/detail-789004.html
总结
这次的构造函数,之前只知道构造函数会优先执行,但是具体不知道什么时候执行。应该就是创建对象或者创建指针并分配空间的时候。这时突然想到构造和析构,不就是代表一个类生命周期的开始和结束吗?创建对象调用构造函数,对象空间回收时自动调用析构函数,其他函数需要自定义调用。通过这一次,自身对代码执行顺序更了解了一些。也许关于这次的思考有误,但是只要不停思考,总会得出正确的结论。文章来源地址https://www.toymoban.com/news/detail-789004.html
到了这里,关于关于Qt用多线程实现usb温度传感器(串口通信)的数据接收中遇到的问题及猜想(不一定正确)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!