1 Qter豆
本帖最后由 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名字乱起的,不用理会其意思)。 用/* */ 注释的不用太理会。
希望有大佬能帮忙解决,谢谢。
我来回答