QT做串口助手及QTThread学习经验
项目要求:
用QT做一个串口助手,利用线程 阻塞通信,正常的收发数据并且显示:
技能要求:QT界面 熟练掌握QT新建工程 要熟悉QT的界面布局,信号与槽机制、各种控件的使用,以及线程的的创建和线程函数的启动。
使用开发环境:
QT5.7.0+QT_create 4.0.2 windoW X86 环境下开发
我的 Widget.cpp 文件 也是比较核心的内容
#include "widget.h"
#include "mythread.h"
#include "ui_widget.h"
#include"qpushbutton.h"
#include<QThread>
#include<QDebug>
#include<QMessageBox>
#include<QMainWindow>
Widget::Widget(QWidget *parent) :
QWidget(parent),
ui(new Ui::Widget)
{
ui->setupUi(this);
//固定窗口大小
this->setFixedSize( this->width (),this->height ());
scancom();//扫描可用端口
mySerialPort = NULL;
//创建一个串口对象
//mySerialPort = new QSerialPort;
//启动按照0.5s扫描
//timerID = this->startTimer(3000);
//初始化标志位
isStop = false;
//自定义线程不指定父亲
myT = new MyThread;
//分配子线程空间
thread = new QThread(this);
//将自定义线程移动到子线程
myT->moveToThread(thread);
connect(myT, &MyThread::MySignals, this, &Widget::dealSignals);
qDebug() << "主线程号 11:" << QThread::currentThread() <<endl;
//将线程启动信号和线程处理函数关联
connect(this, &Widget::startThread, myT, &MyThread::myTimeout);
//将关闭窗口信号和回收线程函数关联
connect(this, &Widget::destroyed, this, &Widget::stopthread);
}
Widget::~Widget()
{
delete ui;
}
void Widget::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 Widget::closecom(void)
{
qDebug()<<"端口"<<ui->comboBox_port->currentText()<<"已关闭"<<endl;
mySerialPort->close();//实现关闭
//温柔的关闭线程
thread->quit();
myT->setFlag(true);
thread->wait();
//串口设置的下拉控件重新使能
ui->comboBox_baudrate->setEditable(true);
ui->comboBox_checkbit->setEditable(true);
ui->comboBox_databit->setEditable(true);
ui->comboBox_port->setEditable(true);
ui->comboBox_stopbit->setEditable(true);
ui->pushButton_comopen->setEnabled(true);//使能“open”
ui->pushButton_comclose->setEnabled(false);//失能"close"
}
//实现打开串口功能
void Widget::opencom(void)
{
qDebug()<<"端口"<<ui->comboBox_port->currentText()<<"已打开"<<endl;
//mySerialPort->open();//实现打开
//设置好标志位 打开
myT->setFlag(false);
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(100);//设置缓冲区的大小。如果读串口数据不能一次性读完,则数据会存入内部缓冲区。
//串口设置的下拉控件重新使能
ui->comboBox_baudrate->setEditable(false);
ui->comboBox_checkbit->setEditable(false);
ui->comboBox_databit->setEditable(false);
ui->comboBox_port->setEditable(false);
ui->comboBox_stopbit->setEditable(false);
ui->pushButton_comopen->setEnabled(false);//使能“open”
ui->pushButton_comclose->setEnabled(true);//失能"close"
}
else
{
QMessageBox::warning(this,tr("警告"),tr("无可用串口!"),QMessageBox::Ok);
}
}
//清空发送区
void Widget::clearsend(void)
{
ui->textEdit_send->clear();
}
//清空接收区
void Widget::clearrecv(void)
{
ui->textEdit_recv->clear();
}
//从发送区写char数据
void Widget::writechr(void)
{
//获取界面上的数据并转换成utf8格式的字节流
QByteArray sendData = ui->textEdit_send->toPlainText().toUtf8();
//转码格式用于汉字输入
//System.Text.UTF8Encoding utf8 = new System.Text.UTF8Encoding();
//Byte[] writeBytes = utf8.GetBytes("你好世界");
//serialPort.Write(writeBytes, 0, writeBytes.Length);
if ((false == sendData.isEmpty() ) && (false == sendData.isNull()))
{
mySerialPort->write(sendData);
//write 直接写函数
}
}
//时间处理时从端口读数据,显示到接收区
/*void Widget::readfromcom(void)
{
QByteArray buf;
//先判断有没有数据
if(0 < mySerialPort->bytesAvailable())
{
buf = mySerialPort->readAll();
if(buf.isEmpty() == 0)
{
qDebug()<<"1"<<endl;
QString str = ui->textEdit_recv->toPlainText();
str += tr(buf);
ui->textEdit_recv->clear();
ui->textEdit_recv->setText(str);
}
}
buf.clear();
}*/
void Widget::on_pushButton_clearrecv_clicked(void)
{
clearrecv();
}
void Widget::on_pushButton_clearsd_clicked(void)
{
clearsend();
}
void Widget::on_pushButton_send_clicked(void)
{
writechr();
}
void Widget::on_pushButton_comopen_clicked(void)
{
//关闭扫描端口事件
//this->killTimer(timerID);
opencom();
if(thread->isRunning() == true)
{
return ;
}
//判断一下 开启线程
thread->start();
//设置标志位
myT->setFlag(false);
//启动线程
emit startThread(mySerialPort);
}
void Widget::on_pushButton_comclose_clicked(void)
{
if(NULL != mySerialPort)
{
closecom();
//关闭串口后启动扫描串口事件
//timerID = this->startTimer(3000);
}
}
//计时器的函数
/*void Widget::timerEvent(QTimerEvent *e)
{
//内部有判断有没有数据来串口
//读数据
//Widget::readfromcom();
delete mySerialPort;
Widget::scancom();
}*/
void Widget::dealSignals(QByteArray data)
{
qDebug()<<"1"<<endl;
QString str = ui->textEdit_recv->toPlainText();
str += tr(data);
ui->textEdit_recv->clear();
ui->textEdit_recv->setText(str);
// ui->textEdit_recv->append(str);
}
//实现关闭窗口槽函数
void Widget::stopthread()
{
//判断开启过线程
if(NULL != myT)
{
on_pushButton_comclose_clicked();//关闭串口
delete myT;//回收线程分配的空间
}
}
/********************************************************************************************************
/**********************************几个重点要注意的地方*****************************************************
1.注意创建子线程启动有两种方法,一种是添加QThread的 直接调 start()函数启动 唯一的run()函数就行 属于比较老 简单的方法
另一种是添加OBject文件 需要创建一个自定义线程(不指定父对象)和一个子线程指定父对象 将自定义线程move到子线程去,
利用signal和slot来启动线程函数。
2.QT中connect()的第五个参数,分情况 一:多线程队列,子线程和主线程不会在同一线程
二:直接 会将处理的槽函数放入信号发出者同一线程
3.QT中的 waitForReadyRead()阻塞函数只能检测到大于等于两个字节的消息,所以需要自己宁外加一判断
例如:
while(false == isStop)
{
if(sendSerialPort->waitForReadyRead(10) == true)
{
int num = sendSerialPort->bytesAvailable();
qDebug()<<"串口数据个数:"<<num<<endl;
readbuf = sendSerialPort->read(num);
if(num > 0)
{
emit MySignals(readbuf);
qDebug()<<"读到的数据:"<<tr(readbuf)<<endl;
}
}//阻塞只能检测到大于两个bit的数 人工加了个判断
else if(1 == sendSerialPort->bytesAvailable())
{
int sum = sendSerialPort->bytesAvailable();
qDebug()<<"串口数据个数:"<<sum<<endl;
readbuf = sendSerialPort->read(sum);
if(1 == sum)
{
emit MySignals(readbuf);
qDebug()<<"读到的数据:"<<tr(readbuf)<<endl;
}
}