Qt开发 系列文章 - Serial-port(十二)
目录
前言
Qt作为一个跨平台的应用程序开发框架,在串口编程方面提供了方便易用的API,使得开发者能够轻松实现基于串口通信的各种应用。
Qt框架中提供的一个串口通讯类QtSerialPort,它属于Qt自带的模块类,专门用于进行串行通信,使用时只需要在工程文件pro内添加QT += serialport即可,这种实现方式封装程度高、使用简单,与Qt框架集成紧密,利用Qt的信号与槽机制进行事件处理采用,但给我的感觉在一些高速数据处理时,不是很好用。因此本文推荐采用Windows API调用相关串口功能函数,用于从文件或设备中读取数据。
一、SerialPort
在Qt平台中,调用window相关API,例如CreatFile、readFile、writeFile等函数,以实现串口通讯,并采用C语言实现。
二、实现方式
本文将采用线程实现对串口数据的收发,创建一个线程类,将该类作为主窗口的私有变量使用。
在该线程类中,通过Windows,串口被抽象为文件,对串口的读、写,实际上就是对文件的读写。
1.创建类
在Qt项目上创建一个串口类,属于线程public QThread,具体实现如下。
#include <QThread>
#include <stdio.h>
#include <windows.h>
//缓冲区大小
#define BUF_SIZE 1000
class comt4hread : public QThread
{
Q_OBJECT
public:
comt4hread();
~comt4hread(){}
bool start_flag;
bool save_flag;
QByteArray filebuf;
QByteArray sendbuf;
const char *ComName;
int BaudValue;
int BitValue;
int ParitySelt;
int StopSelt;
protected:
HANDLE OpenSerial(const char *com, //串口名称,如COM1,COM2
int baud, //波特率:常用取值:CBR_9600、CBR_19200、CBR_38400、CBR_115200、CBR_230400、CBR_460800
int byteSize, //数位大小:可取值7、8;
int parity, //校验方式:可取值NOPARITY、ODDPARITY、EVENPARITY、MARKPARITY、SPACEPARITY
int stopBits);
void run(void);
void ProData(QByteArray);
};
在上面代码中,需要添加头文件#include <Windows.h>
,该函数的具体用法可以参考微软MS的官方文档Win32 应用 |Microsoft 学习,里面比较详细。
2.相关功能函数
该线程类的实现:首先在构造函数初始化相关变量;然后通过OpenSerial函数完成对串口的一系列设置,包括波特率、校验位等;最后启动线程run后,在while循环中实现对串口的读(ReadFile函数)、写(WriteFile函数)。代码如下(示例):
#include "com4thread.h"
comt4hread::comt4hread(){
start_flag = false;
ComName = "COM4";
}
HANDLE comt4hread::OpenSerial(const char *com, //串口名称,如COM1,COM2
int baud, //波特率:常用取值:CBR_9600、CBR_19200、CBR_38400、CBR_115200、CBR_230400、CBR_460800
int byteSize, //数位大小:可取值7、8;
int parity, //校验方式:可取值NOPARITY、ODDPARITY、EVENPARITY、MARKPARITY、SPACEPARITY
int stopBits) //停止位:ONESTOPBIT、ONE5STOPBITS、TWOSTOPBITS;
{
DCB dcb;
BOOL b = FALSE;
COMMTIMEOUTS CommTimeouts;
HANDLE comHandle = INVALID_HANDLE_VALUE;
//打开串口
WCHAR wszClassName[10];
memset(wszClassName, 0, sizeof(wszClassName));
MultiByteToWideChar(CP_ACP, 0, com, int(strlen(com)+1), wszClassName,
sizeof(wszClassName) / sizeof(wszClassName[0]));
comHandle = CreateFile(wszClassName, //串口名称
GENERIC_READ | GENERIC_WRITE, //可读、可写
0, // No Sharing
NULL, // No Security
OPEN_EXISTING,// Open existing port only
// FILE_ATTRIBUTE_NORMAL, // Non Overlapped I/O
0, //同步方式
NULL); // Null for Comm Devices
if (INVALID_HANDLE_VALUE == comHandle) {
qDebug() << "CreateFile fail.";
return comHandle;
}
// 设置读写缓存大小
b = SetupComm(comHandle, BUF_SIZE, BUF_SIZE);
if (!b)
qDebug() << "SetupComm fail.";
//设定读写超时
CommTimeouts.ReadIntervalTimeout = MAXDWORD;//读间隔超时
CommTimeouts.ReadTotalTimeoutMultiplier = 0;//读时间系数
CommTimeouts.ReadTotalTimeoutConstant = 0;//读时间常量
CommTimeouts.WriteTotalTimeoutMultiplier = 1;//写时间系数
CommTimeouts.WriteTotalTimeoutConstant = 1;//写时间常量
b = SetCommTimeouts(comHandle, &CommTimeouts); //设置超时
if (!b)
qDebug() << "SetCommTimeouts fail.";
//设置串口状态属性
GetCommState(comHandle, &dcb); // 获取当前
dcb.BaudRate = ulong(baud); // 波特率
dcb.ByteSize = uchar(byteSize); // 每个字节有位数
dcb.Parity = uchar(parity); // 无奇偶校验位
dcb.StopBits = uchar(stopBits); // 一个停止位
b = SetCommState(comHandle, &dcb);//设置
if (!b)
qDebug() << "SetCommState fail.";
return comHandle;
}
void comt4hread::run(void)
{
BOOL err = FALSE;
DWORD wRLen = 0;
DWORD wWLen = 0;
char buf[BUF_SIZE] = {0};
HANDLE comHandle = INVALID_HANDLE_VALUE;//串口句柄
QByteArray tempbuf;
//打开串口
comHandle = OpenSerial(ComName, CBR_115200, 8, NOPARITY, ONESTOPBIT);
//comHandle = OpenSerial(ComName, BaudValue, BitValue, ParitySelt, StopSelt);
qDebug() << comHandle;
if (INVALID_HANDLE_VALUE == comHandle) {
qDebug() << "OpenSerial COM fail!";
return;
}
qDebug() << "Open COM Successfully!";
//循环接收消息,收到消息后将消息内容
while(start_flag){
wRLen = 0;
//读串口消息
err = ReadFile(comHandle, buf, sizeof(buf)-1, &wRLen, NULL);
if (err && wRLen > 0) //读成功并且数据大小大于0
{
tempbuf.append(buf, int(wRLen));
if(save_flag)
filebuf.append(tempbuf);
//处理数据
ProData(tempbuf);
tempbuf.clear();
}
//写串口消息
if(!sendbuf.isEmpty()){
err = WriteFile(comHandle, sendbuf, size_t(sendbuf.size()), &wWLen, NULL);
if (!err)
qDebug() << "SendData_WriteFile fail.";
sendbuf.clear();
}
}
}
3.用户使用
创建完上面的线程类后,用户需要调用/使用它,首先在构造函数初始化串口、定时器相关变量,然后通过定时器实时获取串口,当串口有效时,启动串口,具体含义实现如下。
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow){
ui->setupUi(this);
/****串口初始化*****/
Initcomthread();
/****定时器初始化****/
InitTimerEvent();
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::Initcomthread(void)
{
comth = new comt4hread();
comth->start_flag = false;
comth->save_flag = false;
//comth->start(QThread::HighPriority);
//connect(this, SIGNAL(comconfig(QStringList)), comth, SLOT(comconfig(QStringList)));
}
void MainWindow::InitTimerEvent(void)
{
timecnt = 0;
ftimer_flag = true;
show_1s_flag = true;
m_pluseTimeid = startTimer(100); // 100毫秒事件处理
}
//发送数据
void MainWindow::on_sendButton_clicked()
{
QByteArray temp = ui->textEdit->toPlainText().toLatin1();
comth->sendbuf.append(temp);
// comth->send_flag = true;
ui->listWidgetRecv->addItem("本地发送数据:" + temp.toHex(' ').toUpper());
ui->listWidgetRecv->setCurrentRow(ui->listWidgetRecv->count() - 1);
}
void MainWindow::timerEvent(QTimerEvent *t)
{
if(t->timerId() == m_pluseTimeid) // 100毫秒事件处理
{
static quint8 cnt=0;
cnt++;
if(cnt == 10){
//查找可用的串口
if(ftimer_flag)
{
if(!(ui->PortBox->currentText().isEmpty()))
{
ui->lineEdit_6->setText("获取可用串口");
ui->lineEdit_6->setStyleSheet("color:red;");
ftimer_flag = false;
show_1s_flag = true;
}
else
{
ui->lineEdit_6->setText(QString::number(timecnt));
timecnt++;
foreach(const QSerialPortInfo &info, QSerialPortInfo::availablePorts())
{
QSerialPort serialtemp;
serialtemp.setPort(info);
if(serialtemp.open(QIODevice::ReadWrite))
{
ui->PortBox->addItem(serialtemp.portName());
serialtemp.clear();
serialtemp.close();
}
}
}
}
else
{
if(ui->PortBox->currentText().isEmpty()){
ftimer_flag = true;
timecnt = 0;
}
show_1s_flag = true;
}
cnt = 0;
}
}
}
void MainWindow::on_PortBox_activated(const QString &arg1)
{
if(arg1.isEmpty())
return;
comth->ComName = ui->PortBox->currentText().toUtf8();
comth->BaudValue = ui->BaudBox->currentText().toInt();
comth->BitValue = ui->BitNumBox->currentText().toInt();
comth->ParitySelt = ui->ParityBox->currentText().toInt();
comth->StopSelt = ui->StopBox->currentText().toInt();
comth->start(QThread::HighPriority);
}
void MainWindow::on_clear_clicked()
{
ui->listWidgetRecv->clear();
}
4.效果演示
软件搜索到没有串口时,开始计时。
有串口时,选择相应串口,Qt输出栏提示Open COM Successful!
5.拓展应用-实时刷新
一般我们工业上使用串口,为了实现对数据实时采集、并通过曲线显示出来。这时我们一般采取的是,先在线程中将串口收到的数据进行解析处理ProData,然后将解析出来的结果值通过变量传递给主界面上,让其知晓,然后通过主窗口的定时器或者信号槽函数显示在UI界面上。
如果是要绘制曲线,可以参考博文Qt之第三方库QCustomPlot使用(二)-CSDN博客,了解曲线绘制的特性及使用方法。
总结
博文中相应的工程代码Qt-Case.zip 利用Qt开发软件进行编的例程,为博文提供案例-CSDN文库。