关于Qt用多线程实现usb温度传感器(串口通信)的数据接收中遇到的问题及猜想(不一定正确)

这篇具有很好参考价值的文章主要介绍了关于Qt用多线程实现usb温度传感器(串口通信)的数据接收中遇到的问题及猜想(不一定正确)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

过程

由于是初学,仅仅对串口编程有个了解,大概的功能是通过两个按钮实现串口数据的接收和暂停,其他的功能暂不深入研究。
多线程 读取 传感器数据,qt,ui,开发语言
通过串口调试助手发现,该串口的属性设置如左所示,接收的数据转为字符串后显示格式如右所示。这里是打算将右边的温度显示在一个LCD控件中,效果如下:
多线程 读取 传感器数据,qt,ui,开发语言
设计的思路是,新建一个串口类继承于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);
}

效果图如下:
多线程 读取 传感器数据,qt,ui,开发语言
后续还会添加新内容和改进,比如串口参数添加波特率等等。

总结

这次的构造函数,之前只知道构造函数会优先执行,但是具体不知道什么时候执行。应该就是创建对象或者创建指针并分配空间的时候。这时突然想到构造和析构,不就是代表一个类生命周期的开始和结束吗?创建对象调用构造函数,对象空间回收时自动调用析构函数,其他函数需要自定义调用。通过这一次,自身对代码执行顺序更了解了一些。也许关于这次的思考有误,但是只要不停思考,总会得出正确的结论。文章来源地址https://www.toymoban.com/news/detail-789004.html

到了这里,关于关于Qt用多线程实现usb温度传感器(串口通信)的数据接收中遇到的问题及猜想(不一定正确)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 冰箱主控 32位MCU,多通道、高精度的AD采样配合温度传感器,实现冰箱各温室的精确控温;低功耗设计

    概览 小华高性价比32位MCU,多通道、高精度的AD采样配合温度传感器,实现冰箱各温室的精确控温;低功耗设计,绿色低碳、节能环保;模块化设计,充分利用丰富的通讯接口,使主控板、显示板和驱动板灵活搭配,设计方便。 方案特点 48MHz32bit ARM Cortex-M0+内核 多种低功耗模

    2024年04月28日
    浏览(31)
  • TMP512/513温度传感器

    The TMP512 (dual-channel )and TMP513triple-channel)are system monitors that includeremote sensors, a local temperature sensor, and aigh-side current shunt monitor. These systemmonitors have the capability of measuring remotetemperatures, on-chip temperatures, and systemoltage/power/current consumptionThe remote temperature sensor diode-connectedtransistors a

    2024年02月15日
    浏览(28)
  • STM32——内部温度传感器实验

    内部温度传感器框图 具体介绍: 1、STM32F40X有一个内部的温度传感器,可以用来测量CPU及周围的温度(TA)。 2、该温度传感器在内部和ADCx_IN16(F40xx/F41xx)或者ADCx_IN18(F42xx/F43xx)输入通道相连接,此通道把传感器输出的电压转换成数字值。 3、温度传感器模拟输入推荐采样时间是

    2024年02月13日
    浏览(34)
  • AD590温度传感器的介绍

    AD590是电流型温度传感器,通过对电流的测量可得到所需要的温度值。根据特性分挡,AD590的后缀以I,J,K,L,M表示。AD590L,AD590M一般用于精密温度测量电路,其电路外形如下图所示,它采用金属壳3脚封装,其中1脚为电源正端V+;2脚为电流输出端I0;3脚为管壳,一般不用。

    2024年02月12日
    浏览(28)
  • DS18B20温度传感器——测试环境温度及代码

    醒醒!,还在睡呢,开始干代码了! 单片机通过OneWire协议与DS18B20通信,最终测出环境温度 OneWire 总线的硬件接口很简单,只需要把 DS18B20 的数据引脚和单片机的一个 IO 口接上      说明:GND接地,DQ单引线用于数据的输入,VDD接电源正极(注意正负极不能接反) 通过编程,

    2024年02月09日
    浏览(56)
  • 温度传感器热电阻方面的介绍

    一、温度传感器热电偶的应用原理         温度传感器热电偶是工业上最常用的温度检测元件之一。其优点是: ①测量精度高。因温度传感器热电偶直接与被测对象接触,不受中间介质的影响。 ②测量范围广。常用的温度传感器热电偶从-50~+1600℃均可边续测量,某些特殊温

    2024年02月09日
    浏览(41)
  • 【Arduino28】LM35温度传感器实验

    LM35温度传感器:1 个 面包板:1个 杜邦线:3根 VCC引脚接 5V 电源 OUT引脚接 A0接口 GND引脚接 GND 接口 通过本次实验,我学会了 LM35 传感器的使用。

    2024年02月09日
    浏览(26)
  • STM32开发(15)----芯片内部温度传感器

    本章介绍STM32芯片温度传感器的使用方法和获取方法。 STM32 有一个内部的温度传感器,可以用来测量 CPU 及周围的温度( 内部温度传感器更适合于检测温度的变化,需要测量精确温度的情况下,应使用外置传感器 )。对于 STM32F103来说,该温度传感器在内部和 ADC1_IN16 输入通道相

    2024年02月05日
    浏览(30)
  • 毕业设计常用温度测量模块之DS18B20温度传感器介绍

    DS18B20是一种单总线数字温度传感器,测试温度范围-55℃-125℃,具有体积小,硬件开销低,抗干扰能力强,精度高的特点。 单总线通信 ,意味着没有时钟线,只有一根通信线。单总线读写数据是靠控制起始时间和采样时间来完成,所以时序要求很严格,这也是DS18B20驱动编程

    2024年02月08日
    浏览(32)
  • DS18B20温度传感器工作原理

    目录 管脚描述 综述 访问DS18B20的事件序列 通信时序 VDD:电源引脚,当采用寄生电源的时候,VDD必须连接到地 DQ:单总线运用的数据输入/输出,当采用寄生电源供电时,同时向设备提供电源 GND:地 ①DS18B20片内的ROM中都存在独一无二的64位编码,在后期通信时,是用此编码进

    2024年02月09日
    浏览(26)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包