QT c++串口通信-学习qt最合适的第一个小程序

目录

提示:编程环境与辅助程序

一、项目新建,程序完善,代码识别

1.1项目新建

1.2、完善程序,程序属性设置

1.3环境识别代码,进入愉快的编程中。

1.4添加串口界面(实际就是添加一个qt类)        

二、串口通信实现

2.1界面控件添加

2.2点击按钮,唤出窗口

2.3 串口通信界面,串口连接实现

2.3.1串口添加

2.3.2 其他串口信息添加

2.4主界面信息接收,命令下发。

        c++,与Qt学习了一段时间,感觉编写串口通信软件是比较检验自己是否初步入门qt的一个标志。c++与qt编程学习过程中,感觉知识点庞杂繁琐,很难抓住核心。最开始学习《C++Prier Puls》,从头看起,真的是痛苦。之后在工作中,在实际编程过程中,感觉还是在实际编程中学习是提升比较快的方法。在接触的各种编程实践中,我感觉串口通信程序就是一个非常好的实践例子。

        首先串口通信程序的编写有助于初学者理解C++面对对象编程的一个核心:类,以及qt的一个核心信号与槽。

1、什么是C+的类,类的成员变量、成员函数。private 、public、protected函数之间区别。

2、qt的核心,信号与槽。信号与槽的连接机制,什么时候连接,什么时候断开。

        而后,在编程过程中需要掌握两个十分重要类,用于数据存储,数据啊之间转换的两个重要类QString和QByteArray类的基本使用。学习qt前期,需要了解大量的专有类。qt为很多问题提供了专门的类,例如串口通信的QSerialPort类,类有封装好的各种函数、信号等能够解决你遇到的9成问题。

提示:编程环境与辅助程序

 qt与c++学习,编写串口通信程序,编程环境与辅助程序

1、我的用的版本是VS2015,qt5.9.6。
2、编写过程中利用串口通信助手,来收发串口消息,虚拟串口驱动来创建虚拟串口。

        环境配置,网上有很多教程,串口助手、虚拟串口驱动程序网上也可以找到,后续也会出教程。

一、项目新建,程序完善,代码识别

        第一步就是新建项目,在使用VS2015+QT组合的时候,由于涉及到串口通讯,在新建项目的时候,需要包含QSerialPort类,在项目新建的时候需要注意。

1.1项目新建

        在VS2015中新建QT窗口项目,这里我命名的是SerialPort,点击确认,后弹出一个qt提示串口,点击Next。

      模式选择DebugSerial Port模块包含。

         这个界面上中,选择Release或者Debug都可以,如果考虑到之后程序打包,Release模式程序体积会小很多。但是我这里遇到一个bug,我选择Release模式,点击QtModules,勾选SerialPort之后,回来又变成Debug模式了。很奇怪,不过没关系,这之后还能修改。

        点击确认之后,来到这个界面,这里可以选择程序基于什么类:Base Class,我这里选择的是QWidget。这个程序编辑完成之后,可很容易嵌入其他程序中中,如果选择QMianWidow,就无法嵌入另一个基于QMainWindow建立的程序,一个程序只能有一个mainwindow。如果只是单独一个程序话,两者都可以。

        点击Finish,就完成了一个一个项目的建立,如下图所示,双击ui文件,得到一个空白的ui界面,这时候程序运行,得到的也是一个空白界面。

        接下来就是项目的属性设置,

1.2、完善程序,程序属性设置

        你可能注意到上述界面中,在没有运行程序之前,程序代码中又红色波浪线,这说明VS没有识别出这代码。还有几个点需要注意。

1、管理器改为Release模式

2、项目属性设置,在左侧项目名称SerialPort右键单击,出现属性,点击进入属性界面。可以看到这里的配置是Debug模式。

        在属性页中切换配置为release,查看两个模式下 Qt Project Settings的Qt Modules,发现release模式下缺少serialport模块,这里需要手动添加,然后保存。到这里程序的基本设置就完成了,下一步:解决代码红色波浪线问题。

1.3环境识别代码,进入愉快的编程中。

        还是项目右键-生成程序,生成成功之后——重新扫描程序,这时候就发现代码中错误提示的红线消失,这时候输入代码,也有了提示。

1.4添加串口界面(实际就是添加一个qt类)        

        这里有一个问题,实际的程序中,我们希望界面简洁、有序。特别是一些控制硬件的上位机,需要控制的参数多且杂。所以我们将串口连接放到另一个界面中,串口连接与程序数据显示分开。实际就是将串口连接类与主界面类分开,两者之间用信号与槽连接,实现相互通信。这就需要在项目再添加一个qt类,开始有点儿难了,但是相信我,并不比之前有多少难度,开始!

        项目-右键,1添加-Add Qt Class-选择Qt Widgets Class,然后给类命名,点击Add

                接下来就是qt的类创建指引,这里基类还是选择QWidget。建立完成之后就会得到这样一个类。到这里我们还没有写一行代码,都是基本的操作。

二、串口通信实现

2.1界面控件添加

        现在虽然两个界面都添加了,但是他们之间并没有相互练习,程序运行之后只能看到第一个界面,无法看到第二个界面。这就需要在一个界面唤醒机制。

目标:主界面增加一个按钮,点击按钮,第二个界面显示。

        在ui界面左侧通过拖动相应控件到界面上就可以,下图我添加了一些控件用于显示数据。         串口通信过程中需要知道串口名称、波特率、数据位、停止位、校验位,向ui界面中拖入PushButton,ComBox,和Label控件,并为其命名。如下图所示。

我们的逻辑是,软件运行,打开界面,点击刷新后,下拉框出现串口,选择串口后,选择波特率、数据位、停止位、校验位,确定后,点击连接,实现串口连接。接下来就是在代码中实现我们预设的逻辑。

2.2点击按钮,唤出窗口

        QT中不同类之间的数据传递通过信号与槽连接。一个类发送信号,另一个类的槽函数接收信号。qt的类中很多自带有信号Signal,这个可以在qt的帮助文件中搜索相应的类来查看。这里QPushButton其中的一个信号就是clicked()。

        在qt中通常的做法是利用connnect来连接信号与槽,但是也有一个方便的机制,就是按名称连接。比如我们希望点击界面的刷新按钮后,能够转到一个槽函数来执行串口的刷新操作。

就可以这样做:在SerialPort类中,h头文件中:

1、首先包含PortWidgets.h;

2、私有成员中增加一个PortWidgets私有成员。

3、添加private slots:然后添加私有槽函数: void on_pushButton_connect_clicked();这个函数是这样命名的:on_控件名_信号名();这样qt就能自动将QPushButton的clicked()信号与该函数连接。

接下来在cpp文件中实现该函数。点击按钮,串口界面出现。

首先在构造函数中,将串口类指针初始化。接下俩在槽函数中,第一步是判断串口指针是否为空,为空就新建类,然后显示。这里就涉及到类构造,及公共函数public的使用。portwidgets是类的一个实例,它在类外,只能够使用公共函数,public函数,比如这个showNormal();

SerialPort::SerialPort(QWidget *parent)
    : QWidget(parent)
{
    ui.setupUi(this);
	portwidgets = NULL;
}
//按钮点击
void SerialPort::on_pushButton_connect_clicked()
{
	if (portwidgets == NULL)
	{
		portwidgets = new PortWidgets();
	}
	portwidgets->showNormal();//界面显示
}

一切正常后,运行程序,点击按钮,第二个界面就会出现。

2.3 串口通信界面,串口连接实现

        现在实现界面串口界面打开,接下来就是实现串口刷星,波特率、数据位、停止位、校验位,及串口连接。

2.3.1串口添加

第一步:扫描电脑所有串口。需要用到虚拟串口模拟器:搜索Configure Virtual Serial Port Driver,安装就可以。然后添加一对相互连接的串口。com1--com2.接下来就是:实现点击按钮实现扫描所有串口功能。槽函数添加方式与之前一样,不再赘述。串口刷新需要包含两个头文件。

#include "qserialport.h"
#include "qserialportinfo.h"

        然后在实际编程过程中可以在槽函数中添加一个输出比如cout<<0<<endl,来检验信号与槽函数是否正常连接。这段代码主要就是扫描电脑当前所有可用串口,获得名称,添加到响应的下拉框中,然后设置下拉框当前显示第一个串口。

//刷新串口
void PortWidgets::on_pushButton_refresh_clicked()
{
	QStringList portNameList;
	QList<QSerialPortInfo> serialPortInfos = QSerialPortInfo::availablePorts();
	ui.comboBox_portlist->clear();
	// 遍历所有可用的串行端口信息  
	for (const QSerialPortInfo &info : serialPortInfos) 
	{
		portNameList.append(info.portName()); // 添加端口名到列表中  
	}
	// 将端口名列表添加到下拉框中  
	ui.comboBox_portlist->addItems(portNameList);
	// 默认情况下选择第一个端口
	if (!portNameList.isEmpty())
	{
		ui.comboBox_portlist->setCurrentIndex(0);
	}
}

2.3.2 其他串口信息添加

        波特率、数据位、停止位、校验位,这里用了unordered_map文件中的键-值对应方法,这样在类实现中,就能够简化代码。头文件中类代码如下。

1、类中添加了void on_pushButton_portconnect_clicked();点击按钮,转到此函数中,实现串口连接。

2、代码中,增加了void receive_data();槽函数,用于接收串口传输数据。串口类中有信号,readyread。串口收到信号就转到该槽函数处理。这个就需要手动连接:

connect(myPort, &QSerialPort::readyRead, this, &PortWidgets::receive_data);

格式:connect(发送指针,&发送类::信号,接收指针,&接收类::接收槽函数)。槽函数实现中,

    QByteArray data = myPort->readAll();
    emit receivedata(data);

通过myPort读取到所有串口数据,然后有一个emit,是发送信号,这个信号由主界面的槽函数来接收。

#pragma once

#include <QWidget>
#include "ui_PortWidgets.h"
#include "qserialport.h"
#include "qserialportinfo.h"
#include <unordered_map> //非排序图
#include <iostream>
#include <qbytearray.h>
#include <qthread.h>
using namespace std;

class PortWidgets : public QWidget
{
	Q_OBJECT

public:
	PortWidgets(QWidget *parent = Q_NULLPTR);
	~PortWidgets();

signals:
	void receivedata(QByteArray data);

	private slots:
	void on_pushButton_refresh_clicked();
	void on_pushButton_portconnect_clicked();
	void receive_data();
public:
	void write_data(QByteArray writedata);

private:
	Ui::PortWidgets ui;
	QSerialPort *myPort;//串口指针

	std::unordered_map<int, QSerialPort::BaudRate> baudRateMap =//波特率
	{
		{ 0, QSerialPort::Baud1200 },{ 1, QSerialPort::Baud2400 },{ 2, QSerialPort::Baud4800 },
		{ 3, QSerialPort::Baud9600 },{ 4, QSerialPort::Baud19200 },{ 5, QSerialPort::Baud38400 },
		{ 6, QSerialPort::Baud57600 },{ 7, QSerialPort::Baud115200 }
	};
	std::unordered_map<int, QSerialPort::DataBits>dataMap =//数据位
	{
		{ 0,QSerialPort::Data5 },{ 1,QSerialPort::Data6 },{ 2,QSerialPort::Data7 },{ 3,QSerialPort::Data8 }
	};
	std::unordered_map<int, QSerialPort::Parity>parityMap =//校验位
	{
		{ 0,QSerialPort::NoParity },{ 1,QSerialPort::OddParity },{ 2,QSerialPort::EvenParity }
	};
	std::unordered_map<int, QSerialPort::StopBits>stopMap =//停止位
	{
		{ 0,QSerialPort::OneStop },{ 1,QSerialPort::OneAndHalfStop },{ 2,QSerialPort::TwoStop }
	};
};

cpp,类实现。

#include "PortWidgets.h"
PortWidgets::PortWidgets(QWidget *parent)
	: QWidget(parent)
{
	ui.setupUi(this);
	QStringList baudratelist = { "1200","2400","4800","9600","19200","38400","57600","115200" };
	ui.comboBox_BaudRate->addItems(baudratelist);
	ui.comboBox_BaudRate->setCurrentIndex(3);

	QStringList datalist = { QString::fromLocal8Bit("5位"),QString::fromLocal8Bit("6位"),
		QString::fromLocal8Bit("7位"),QString::fromLocal8Bit("8位") };
	ui.comboBox_Data->addItems(datalist);
	ui.comboBox_Data->setCurrentIndex(3);

	QStringList checklist = { QString::fromLocal8Bit("无校验"),QString::fromLocal8Bit("偶校验"),
		QString::fromLocal8Bit("奇校验") };
	ui.comboBox_Check->addItems(checklist);
	ui.comboBox_Check->setCurrentIndex(0);

	QStringList stoplist = { QString::fromLocal8Bit("1位"),QString::fromLocal8Bit("1.5位"),
		QString::fromLocal8Bit("2位") };
	ui.comboBox_Stop->addItems(stoplist);
	ui.comboBox_Stop->setCurrentIndex(0);

	on_pushButton_refresh_clicked();

	myPort = NULL;
	myPort = new QSerialPort(this);

	connect(myPort, &QSerialPort::readyRead, this, &PortWidgets::receive_data);
}

PortWidgets::~PortWidgets()
{
}
//刷新串口
void PortWidgets::on_pushButton_refresh_clicked()
{
	QStringList portNameList;
	QList<QSerialPortInfo> serialPortInfos = QSerialPortInfo::availablePorts();
	ui.comboBox_portlist->clear();
	// 遍历所有可用的串行端口信息  
	for (const QSerialPortInfo &info : serialPortInfos) 
	{
		portNameList.append(info.portName()); // 添加端口名到列表中  
	}
	// 将端口名列表添加到下拉框中  
	ui.comboBox_portlist->addItems(portNameList);
	// 默认情况下选择第一个端口
	if (!portNameList.isEmpty())
	{
		ui.comboBox_portlist->setCurrentIndex(0);
	}
}
//串口连接
void PortWidgets::on_pushButton_portconnect_clicked()
{
	myPort->setPortName(ui.comboBox_portlist->currentText());
	int baudindex = ui.comboBox_BaudRate->currentIndex();
	myPort->setBaudRate(baudRateMap[baudindex]);
	int dataindex = ui.comboBox_Data->currentIndex();
	myPort->setDataBits(dataMap[dataindex]);
	int stopindex = ui.comboBox_Stop->currentIndex();
	myPort->setStopBits(stopMap[stopindex]);
	int checkindex = ui.comboBox_Check->currentIndex();
	myPort->setParity(parityMap[checkindex]);
	myPort->setFlowControl(QSerialPort::NoFlowControl);
	
	if (!myPort->isOpen())
	{
		myPort->open(QIODevice::ReadWrite);
		if (myPort->isOpen())
			cout << "Connection success!" << endl;
		else
			cout << "Connection failed!" << endl;
	}
}
//下发命令
void PortWidgets::write_data(QByteArray writedata)
{
	myPort->write(writedata);
}
//接收来自串口的数据,发送到主串口
void PortWidgets::receive_data()
{
	QByteArray data = myPort->readAll();
	emit receivedata(data);
}

2.4主界面信息接收,命令下发。

        到这里,串口界面的基本功能已经具备了,也能够接收串口信息。下面就到了两个界面交互的部分。首先给出主界面类的定义及实现。

类定义:类中增加了几个私有槽函数;一个信号

1、void receive_data(QByteArray data);这是接收数据槽函数,用于响应串口类发送信号。

2、void on_lineEdit_data_returnPressed();这是响应界面中lineEdit按下enter后,进入该槽函数,在这里可以进行一些操作。

3、void writedata(QByteArray data);该信号就是与串口类的下发命令连接。

#pragma once

#include <QtWidgets/QWidget>
#include "ui_SerialPort.h"
#include "PortWidgets.h"

class SerialPort : public QWidget
{
    Q_OBJECT

public:
    SerialPort(QWidget *parent = Q_NULLPTR);

	private slots:
	void on_pushButton_connect_clicked();
	void on_lineEdit_data_returnPressed();
	void receive_data(QByteArray data);//接收串口数据的槽
	void on_pushButton_clear_clicked();//清除接收框的数据

signals:
	void writedata(QByteArray data);

private:
    Ui::SerialPortClass ui;
	PortWidgets *portwidgets;
};

类实现:

1、可以看到,我们在串口连接的时候,对两个类之间的信号与槽进行了连接,这里需要注意的是,如果我们在中途想要断开连接一段时间后,再次连接串口。需要将信号与槽的连接也断开,可以自己尝试一下,实现这个功能。

2、数据发送中,我们在void SerialPort::on_lineEdit_data_returnPressed()槽函数中,获取了当前编辑框的信息转为int,接着定义了一个QByteArray,通过如下的方式将该int转为16进制,

data.resize(1);
        data[0] = value & 0xff;
        emit writedata(data);

并发送信号,然后就转到串口类的槽函数中,完成命令下发。

void PotWidgets::write_data(QByteArray writedata)
{
    myPort->write(writedata);
}

#include "SerialPort.h"

SerialPort::SerialPort(QWidget *parent)
    : QWidget(parent)
{
    ui.setupUi(this);
	portwidgets = NULL;
}
//按钮点击
void SerialPort::on_pushButton_connect_clicked()
{
	if (portwidgets == NULL)
	{
		portwidgets = new PortWidgets();
		connect(portwidgets, &PortWidgets::receivedata, this, &SerialPort::receive_data);//接收来自串口界面的消息
		connect(this, &SerialPort::writedata, portwidgets, &PortWidgets::write_data);    //发送数据到串口界面
	}
	portwidgets->showNormal();//界面显示
}

//数据发送,编辑框回车确定
void SerialPort::on_lineEdit_data_returnPressed()
{
	if (ui.lineEdit_data->text() != NULL)
	{
		int value = ui.lineEdit_data->text().toInt();
		QByteArray data;
		data.resize(1);
		data[0] = value & 0xff;
		emit writedata(data);
	}
}
//接收数据,显示
void SerialPort::receive_data(QByteArray data)
{
	bool ok;
	QString str = data.toHex(' ').toUpper();
	ui.textEdit_receive->append(str);
}
//清除数据显示框
void SerialPort::on_pushButton_clear_clicked()
{
	ui.textEdit_receive->clear();
}

后面的内容我没有写的很详细,但是源代码都给出了,初学者经过前面的操作后,然后看代码,不断理解,应该可以掌握该程序的精髓。同时这个代码还是不完善的。

这里指出:

1、串口连接之后,并没没有提示是否连接

2、串口连接之后,没有断开操作。

3、这里串口接收消息,只能处理简单数据,实际工作汇总,串口接收数据快,且多,需要单独线程处理。

  • 46
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值