找回密码
 立即注册
收起左侧

QT做的串口助手,子线程里面waitforreadyread的问题.

7
回复
14807
查看
[复制链接]
累计签到:1 天
连续签到:1 天
来源: 2019-1-15 11:28:12 显示全部楼层 |阅读模式
1Qter豆
本帖最后由 prehistorical 于 2019-1-16 17:17 编辑

1.问题:
QT做的串口助手,主线程主要管理 GUI界面及打开、关闭、初始化串口;子线程用阻塞的方式读取串口数据;问题是子线程读数据时阻塞(waitforreadyread)里面用readALL和read读出来的数据有时正确,有时是双份的。
        例如:用虚拟串口测试(COM3/COM11),COM11发数据(用网上的稳定完善的串口助手),COM3收数据(用自己写的串口助手);COM11发送 “1”,COM3有时收到“1",有时收到"11",发的次数多了更可能收到"111"。
        
2.代码:
mythread.h

#ifndef MYTHREAD_H
#define MYTHREAD_H

#include <QObject>
#include<QSerialPort>
#include<QSerialPortInfo>

class myThread : public QObject
{
    Q_OBJECT
public:
    explicit myThread(QObject *parent = 0);

   // void readfromcom(void);

    //线程处理函数
    void myTimeout(QSerialPort *sendSerialPort);
    void setFlag(bool flag = true);

signals:
    void mySignal(QByteArray data);

public slots:

private:
    bool isStop;
   // QSerialPort *mySerialPort;//necessary or will be wrong
};

#endif // MYTHREAD_H




mywidget.h


#ifndef MYWIDGET_H
#define MYWIDGET_H

#include <QWidget>
#include<QMessageBox>
#include <QtSerialPort/QSerialPort>         //提供访问串口的功能
#include <QtSerialPort/QSerialPortInfo>     //提供系统中存在的串口的信息
#include<QDebug>
#include"mythread.h"

namespace Ui {
class MyWidget;
}

class MyWidget : public QWidget
{
    Q_OBJECT

public:
    explicit MyWidget(QWidget *parent = 0);
    ~MyWidget();

    //一般函数
    void scancom(void);//扫描可用端口,并添加到端口选项中。

    void opencom(void);//打开串口

    void closecom(void);//关闭串口
    void clearsend(void);//清发送区
    void clearrecv(void);//清接收区
    void writechr(void);//写char数据
    void readfromcom(void);//从端口读数据


    //槽函数


private slots:
    void on_pushButton_comopen_clicked();

    void on_pushButton_comclose_clicked();

    void on_pushButton_clearsd_clicked();

    void on_pushButton_clearrecv_clicked();

    void on_pushButton_send_clicked();

    void dealSignal(QByteArray data);

    void dealClose();



signals:
    void startThread(QSerialPort *sendSerialPort);//启动子线程,将当前的端口名传到


private:
    Ui::MyWidget *ui;
    QSerialPort *mySerialPort;//necessary or will be wrong
    myThread *myT;
    QThread *mythread;
};

#endif // MYWIDGET_H



mythread.cpp

#include "mythread.h"
#include<QThread>
#include<qDebug>




myThread::myThread(QObject *parent) : QObject(parent)
{
    isStop = false;
    qDebug()<<"子"
              "线程号:"<<QThread::currentThread();

}

void myThread::myTimeout(QSerialPort *sendSerialPort)//线程处理函数
{
     qDebug()<<"哪个线程号:"<<QThread::currentThread();
     int num = 0;
     QByteArray readbuff;

    while(isStop == false)
    {

       if((sendSerialPort)->waitForReadyRead(13) == true)
       {


            num = (sendSerialPort)->bytesAvailable();
          //qDebug()<<"串口数据个数:"<<num<<endl;


            readbuff = (sendSerialPort)->read(num);



            if(num >= 1)
            {
                emit mySignal(readbuff);      

             //   qDebug()<<"从串口读到的数据:"<<tr(readbuff)<<endl;

            }




/*

            num = (sendSerialPort)->bytesAvailable();

           QByteArray readbuff;

           readbuff.append(sendSerialPort->readAll());

          // while(sendSerialPort->waitForReadyRead(20))

          // readbuff.append(sendSerialPort->readAll());

           if(num >= 1)
           {
               emit mySignal(readbuff);

               qDebug()<<"从串口读到的数据:"<<tr(readbuff)<<endl;

           }
*/

        }
    }
}


void myThread::setFlag(bool flag)
{
    isStop = flag;
}





mywidget.cpp

#include "mywidget.h"
#include "ui_mywidget.h"
#include"qpushbutton.h"
#include<QThread>
#include<QDebug>
#include<QMessageBox>

MyWidget::MyWidget(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::MyWidget)
{
    ui->setupUi(this);
    setFixedSize(this->width(), this->height());//将主窗口设置为初始大小,禁止将主窗口变大。
    scancom();//扫描可用端口
    ui->pushButton_comclose->setEnabled(false);//使“关闭串口”按钮失能

    qDebug()<<"主"
              "线程号:"<<QThread::currentThread();


    //动态分配空间,不能指定父对象
    myT =  new myThread;
    //创建子线程
    mythread = new QThread(this);
    //把自定义线程加入到子线程中
    myT->moveToThread(mythread);

    connect(myT,&myThread::mySignal,this,&MyWidget::dealSignal);//子线程将数据通过带参信号将数据传给主线程处理。

    connect(this,&MyWidget::startThread,myT,&myThread::myTimeout);//startThread带参信号将主线程串口指针传给子线程,槽函数是线程处理函数

    connect(this,&MyWidget::destroyed,this,&MyWidget::dealClose);//关窗口会产生destroy信号,槽函数保证线程正常退出

    //线程处理函数内部不允许操作图形界面,否则程序执行错误,根本执行不了

    //重要问题:解释一下connect()的第五个参数
    //connect()的第五个参数表示连接方式,主要有默认、直接、队列三种方式;
    //多线程时才有意义
    //如果是多线程,默认使用队列(队列:槽函数所在线程和接收者一样)
    //如果是单线程,默认使用直接(槽函数所在线程和发送者一样)
    //最好、一般用默认,就是不填。


}

MyWidget::~MyWidget()
{
    delete ui;
}

void MyWidget::dealClose()//主窗口关闭时处理的槽函数
{
    on_pushButton_comclose_clicked();//关闭串口
    delete myT;//养成好习惯,及时回收动态分配的空间
}

void MyWidget::dealSignal(QByteArray data)//真正的实现现象的函数
{
    //将接收到的数据显示在接收区
    QString string= ui->textEdit_recv->toPlainText();

    qDebug()<<"收到的数据:"<<tr(data)<<endl;

    string += tr(data);
    ui->textEdit_recv->setText(string);
    qDebug()<<"显示的数据:"<<string<<endl;

    qDebug()<<"主线程号:"<<QThread::currentThread();

}

void MyWidget::scancom(void)//扫描可用端口并加入端口下拉框
{
    mySerialPort = new QSerialPort;//新建串口类对象
    if(NULL != mySerialPort)
    {
        foreach(const QSerialPortInfo &info,QSerialPortInfo::availablePorts())//查找可用串口。找不到存在串口是不会进入到foreach内部的,存在不一定可用.
        {
            QSerialPort availableport;
            int i;
            availableport.setPortName(info.portName());

            if(availableport.open(QIODevice::ReadWrite))//看串口是否可用(即可读写)。
            {
                int IsHaveInItemList =0;

                for(i=0; i<ui->comboBox_port->count(); i++)//看串口是否在列表中
                {
                    if(ui->comboBox_port->itemData(i) == availableport.portName())
                    {
                        IsHaveInItemList = 1;
                    }
                }

                if(IsHaveInItemList == 0)
                {
                    ui->comboBox_port->addItem(availableport.portName());//加UI的下拉框里面
                    qDebug()<<info.portName()<<endl;
                }
                availableport.close();
            }

        }
    }
    else
    {
        QMessageBox::warning(this,tr("警告"),tr("串口没有初始化成功"),QMessageBox::Ok);
    }
}


void MyWidget:pencom(void)//打开串口及串口初始化
{
    if(NULL == mySerialPort)
    {
        mySerialPort = new QSerialPort();
    }

    if(ui->comboBox_port->count() != 0)//存在可用端口就进入
    {
        mySerialPort->setPortName(ui->comboBox_port->currentText());//设置端口名
        mySerialPort->open(QIODevice::ReadWrite);//打开串口
        mySerialPort->setBaudRate(ui->comboBox_baudrate->currentText().toInt());//设置波特率

        switch(ui->comboBox_databit->currentText().toInt())//设置数据位
        {
            case 5:
                    mySerialPort->setDataBits(QSerialPort:ata5);
                     break;
            case 6:
                    mySerialPort->setDataBits(QSerialPort:ata6);
                     break;
            case 7:
                    mySerialPort->setDataBits(QSerialPort:ata7);
                     break;
            case 8:
                    mySerialPort->setDataBits(QSerialPort:ata8);
                     break;
        default:
                    mySerialPort->setDataBits(QSerialPort:ata8);
                     break;
        }

         switch(ui->comboBox_checkbit->currentIndex())//设置校验位
         {
            case 0:
                    mySerialPort->setParity(QSerialPort::NoParity);
                    break;
            case 1:
                    mySerialPort->setParity(QSerialPort::OddParity);
                    break;
            case 2:
                    mySerialPort->setParity(QSerialPort::EvenParity);
                    break;
         }

         switch(ui->comboBox_stopbit->currentText().toInt())//设置停止位
         {
            case 1:
                 mySerialPort->setStopBits(QSerialPort::OneStop);
                 break;
         case 2:
                 mySerialPort->setStopBits(QSerialPort::TwoStop);
                 break;
         }

        mySerialPort->setFlowControl(QSerialPort::NoFlowControl);
        mySerialPort->setReadBufferSize(68);//设置缓冲区的大小。如果读串口数据不能一次性读完,则数据会存入内部缓冲区。

        //串口设置的下拉控件失能
        ui->comboBox_baudrate->setEnabled(false);
        ui->comboBox_checkbit->setEnabled(false);
        ui->comboBox_databit->setEnabled(false);
        ui->comboBox_port->setEnabled(false);
        ui->comboBox_stopbit->setEnabled(false);

        ui->pushButton_comopen->setEnabled(false);//使“打开串口”按钮失能
        ui->pushButton_comclose->setEnabled(true);//使能“关闭串口”按钮

    }
    else
    {
         QMessageBox::warning(this,tr("警告"),tr("无可用串口!"),QMessageBox::Ok);
    }

}


void MyWidget::closecom(void)//关闭串口
{
    qDebug()<<"端口"<<ui->comboBox_port->currentText()<<"已关闭"<<endl;
    mySerialPort->close();

    //串口设置的下拉控件使能
    ui->comboBox_baudrate->setEnabled(true);
    ui->comboBox_checkbit->setEnabled(true);
    ui->comboBox_databit->setEnabled(true);
    ui->comboBox_port->setEnabled(true);
    ui->comboBox_stopbit->setEnabled(true);

    ui->pushButton_comopen->setEnabled(true);//使能“打开串口”按钮
    ui->pushButton_comclose->setEnabled(false);//使“关闭串口”按钮失能

}

void MyWidget::clearsend(void)//清发送区
{
    ui->textEdit_send->clear();
}

void MyWidget::clearrecv(void)//清接收区
{
    ui->textEdit_recv->clear();
}

void MyWidget::writechr(void)//写char数据进串口
{
    //获取界面上的数据并转换成utf8格式的字节流
    QByteArray sendData = ui->textEdit_send->toPlainText().toUtf8();
        if ((sendData.isEmpty() == 0) && (sendData.isNull() == 0)) {
            mySerialPort->write(sendData);
        }
}

/*
void MyWidget::readfromcom(void)//读串口的数据
{
    QByteArray buf;
    buf = mySerialPort->readAll();//readAll()函数,读取串口的数据

    if(buf.isEmpty() == 0)
    {
        qDebug()<<"哪个线程号?111:"<<QThread::currentThread();

                QString str = ui->textEdit_recv->toPlainText();

                str += tr(buf);//tr()函数

                //ui->textEdit_recv->clear();
                ui->textEdit_recv->setText(str);
                //ui->textEdit_recv->append(str);

    }
    qDebug()<<"哪个线程号?:"<<QThread::currentThread();
    qDebug()<<"2"<<endl;
    buf.clear();
}
*/


void MyWidget:n_pushButton_comopen_clicked()//按下“打开串口”按钮对应的槽函数
{
    opencom();//调用打开串口的函数

        if(mythread->isRunning() == true)
        {
            return ;
        }
        //启动线程,但没有启动线程处理函数
        mythread->start();
        myT->setFlag(false);

        //不能直接调用线程处理函数
        //只能通过signal - slot方法调用
        emit startThread(mySerialPort);//将主线程的QSerialPort 对象通过带参信号传给子线程

}

void MyWidget:n_pushButton_comclose_clicked()//按下“关闭串口”按钮对应的槽函数
{
    if(mythread->isRunning() == false)
    {
        return ;
    }

    //
    myT->setFlag(true);
    mythread->quit();//退出线程,不建议使用terminate(),因为会导致严重的内存问题.
    mythread->wait();//等待线程结束

    closecom();//调用关闭串口的函数
}

void MyWidget:n_pushButton_clearsd_clicked()//按下“清发送区”按钮对应的槽函数
{
    clearsend();
}

void MyWidget:n_pushButton_clearrecv_clicked()//按下“清接收区”按钮对应的槽函数
{
    clearrecv();
}

void MyWidget:n_pushButton_send_clicked()//按下“发送”按钮对应的槽函数
{
    writechr();
}





3.报错信息:无


4.尝试过的方式、方法:
(1)  一直感觉是waitforreadyread的问题。
为了排除是线程的问题,用改写run的方法创建了线程,还是一样的现象。
在主线程里无法用阻塞读写,不然无法执行软件界面。
所以不是线程问题。
(2)  在while循环里面,隔多少秒读一次串口数据(非阻塞),没有任何问题,都是完善的;但是我想要的是阻塞读能正确读取串口数据。
(3)问题就在waitforreadyread这个阻塞函数上面,但是也没有别的读串口数据的阻塞函数了呀。

5.相关截图:
截图不用了,没什么用,是思想问题,不是表面的报错问题。

特别提示:个人感觉问题主要在  mythread.cpp 的mytimeout函数(线程处理函数)里面(mytimeout名字乱起的,不用理会其意思)。 用/*   */ 注释的不用太理会。

希望有大佬能帮忙解决,谢谢。

回复

使用道具 举报

累计签到:1 天
连续签到:1 天
2019-1-16 17:00:29 显示全部楼层
呵呵,看来qt开源社区没什么人啊。
回复

使用道具 举报

累计签到:1 天
连续签到:1 天
2019-1-15 11:43:01 显示全部楼层
希望各位大佬伸出援手,谢谢!
回复

使用道具 举报

累计签到:1 天
连续签到:1 天
2019-1-16 17:02:10 显示全部楼层
还是我的问题太深奥了没人解答得了?
回复

使用道具 举报

累计签到:1 天
连续签到:1 天
2019-1-16 17:04:42 显示全部楼层
直接看waitforreayread那段就完事了,别的代码都没有问题。
回复

使用道具 举报

尚未签到

2019-11-14 13:48:33 显示全部楼层
大佬 ,有没有试想过 一收一发多线程
回复

使用道具 举报

累计签到:256 天
连续签到:1 天
2020-5-9 10:58:06 显示全部楼层
我也遇到你的这个问题,waitforreayread()一直返回false,但是connect绑定的ReadyRead的槽函数可以被触发读到数据;不知道是什么情况这个
回复

使用道具 举报

累计签到:1 天
连续签到:1 天
2020-6-15 16:52:42 显示全部楼层
waitForReadyread()内部应该是是等待readyRead()信号的。如果之前readyReady()信号触发过了,这个函数就不能返回true了。  write()的函数执行之后,要立刻执行waitForReadyread()。中间不能让串口所在线程的循环运行,不让readyReady()信号触发。如果write和waitForReadyread()在两个槽函数中,执行完write()之后,会让Ready()信号发送,waitForReadyread()就等不到ReadyRead()信号了。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

公告
可以关注我们的微信公众号yafeilinux_friends获取最新动态,或者加入QQ会员群进行交流:190741849、186601429(已满) 我知道了