prehistorical 发表于 2019-1-15 11:28:12

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

本帖最后由 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::opencom(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::Data5);
                     break;
            case 6:
                  mySerialPort->setDataBits(QSerialPort::Data6);
                     break;
            case 7:
                  mySerialPort->setDataBits(QSerialPort::Data7);
                     break;
            case 8:
                  mySerialPort->setDataBits(QSerialPort::Data8);
                     break;
      default:
                  mySerialPort->setDataBits(QSerialPort::Data8);
                     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::on_pushButton_comopen_clicked()//按下“打开串口”按钮对应的槽函数
{
    opencom();//调用打开串口的函数

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

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

}

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

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

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

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

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

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





3.报错信息:无


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

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

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

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

prehistorical 发表于 2019-1-16 17:00:29

呵呵,看来qt开源社区没什么人啊。

prehistorical 发表于 2019-1-15 11:43:01

希望各位大佬伸出援手,谢谢!

prehistorical 发表于 2019-1-16 17:02:10

还是我的问题太深奥了没人解答得了?

prehistorical 发表于 2019-1-16 17:04:42

直接看waitforreayread那段就完事了,别的代码都没有问题。

one-rabbit 发表于 2019-11-14 13:48:33

大佬 ,有没有试想过 一收一发多线程

z55716368 发表于 2020-5-9 10:58:06

我也遇到你的这个问题,waitforreayread()一直返回false,但是connect绑定的ReadyRead的槽函数可以被触发读到数据;不知道是什么情况这个

ggl 发表于 2020-6-15 16:52:42

waitForReadyread()内部应该是是等待readyRead()信号的。如果之前readyReady()信号触发过了,这个函数就不能返回true了。write()的函数执行之后,要立刻执行waitForReadyread()。中间不能让串口所在线程的循环运行,不让readyReady()信号触发。如果write和waitForReadyread()在两个槽函数中,执行完write()之后,会让Ready()信号发送,waitForReadyread()就等不到ReadyRead()信号了。
页: [1]
查看完整版本: QT做的串口助手,子线程里面waitforreadyread的问题.