C++Qt学习笔记——实现一个串口通信界面
- 一.界面
- 二、项目结构
- 三、头文件
- 1. 文件头部
- 2. 类定义
- 3. 构造函数和析构函数
- 4. 成员函数
- 5. 成员变量
- 四、代码解析
- `ReceiveAeraInit` 函数解析
- SerialHelper 构造函数解析
- 1. 为什么有两个 `SerialHelper`?
- 2. 为什么用 `::` 和 `:`?
- 1. `this` 的作用
- 2. `connect` 的作用
- 1. 接收区初始化
- 2. 发送区初始化
- 3. 串口配置初始化
- 4. 串口连接与断开
- 5. 串口通信实现
- 6. 动态更新串口列表
- 7. 主窗口初始化
参考以下视频
一.界面
二、项目结构
本项目包含以下功能模块:
- 接收区初始化:用于显示接收到的数据。
- 发送区初始化:用于输入并发送数据。
- 串口配置初始化:用于配置串口参数(如波特率、数据位等)。
- 串口操作:连接和断开串口,以及数据的发送和接收。
- 动态更新串口列表:定时扫描系统中的串口并更新下拉菜单。
三、头文件
#pragma once
#include <QtWidgets/QMainWindow>
#include "ui_SerialHelper.h"
#include <QPlainTextEdit>
#include <QPushButton>
#include <QComboBox>
#include <QLabel>
#include <QDebug>
#include <QSerialPort>
#include<QSerialPortInfo>
#include<QMessageBox>
class SerialHelper : public QMainWindow
{
Q_OBJECT
public:
SerialHelper(QWidget *parent = nullptr);
~SerialHelper();
void ReceiveAeraInit(void);
void SendAeraInit(void);
void SetupInit(void);
void BeginUSART(void);
void USART(void);
void timerEvent(QTimerEvent*);
private:
QPlainTextEdit* sendAera;
QPlainTextEdit* receiveAera;
QPushButton* sendButton;
QPushButton* startUSART;
QPushButton* endUSART;
QComboBox* port;
QComboBox* baudRate;
QComboBox* dataSize;
QComboBox* stopSize;
QComboBox* check;
QComboBox* receiveMode;
QComboBox* sendMode;
QSerialPort* serialPort;
QVector<QString> ports;
};
这段代码是一个基于Qt的串口助手工具的类定义,它声明了SerialHelper
类及其成员变量和成员函数。以下是详细解析:
1. 文件头部
#pragma once
#pragma once
是一个预处理器指令,用于防止头文件的重复包含。它确保该头文件在编译过程中只被包含一次,从而避免重复定义问题。
#include <QtWidgets/QMainWindow>
#include "ui_SerialHelper.h"
#include <QPlainTextEdit>
#include <QPushButton>
#include <QComboBox>
#include <QLabel>
#include <QDebug>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QMessageBox>
这些是必要的头文件:
QtWidgets/QMainWindow
:QMainWindow
是Qt提供的一个主窗口类,SerialHelper
类继承自它。ui_SerialHelper.h
:这是Qt Designer生成的UI类的头文件,通常包含窗口的界面布局。- 其他头文件:提供了Qt中各种控件和功能的类,如
QPlainTextEdit
(多行文本编辑框)、QPushButton
(按钮)、QComboBox
(下拉菜单)、QSerialPort
(串口通信类)等。
2. 类定义
class SerialHelper : public QMainWindow
{
Q_OBJECT
class SerialHelper
:定义了一个名为SerialHelper
的类。: public QMainWindow
:表示SerialHelper
类继承自QMainWindow
,即SerialHelper
是一个主窗口类。Q_OBJECT
:这是一个宏,用于Qt的信号和槽机制,它使得类可以使用信号和槽功能。
3. 构造函数和析构函数
public:
SerialHelper(QWidget *parent = nullptr);
~SerialHelper();
SerialHelper(QWidget *parent = nullptr)
:构造函数,用于初始化SerialHelper
对象。QWidget *parent
是一个指向父窗口的指针,默认值为nullptr
,表示该窗口没有父窗口。~SerialHelper()
:析构函数,用于清理SerialHelper
对象占用的资源。
4. 成员函数
void ReceiveAeraInit(void);
void SendAeraInit(void);
void SetupInit(void);
void BeginUSART(void);
void USART(void);
void timerEvent(QTimerEvent*);
ReceiveAeraInit(void)
:初始化接收区域的函数。SendAeraInit(void)
:初始化发送区域的函数。SetupInit(void)
:初始化串口设置区域的函数。BeginUSART(void)
:初始化串口连接和断开按钮的函数。USART(void)
:配置串口参数并打开串口的函数。timerEvent(QTimerEvent*)
:定时器事件处理函数,用于定时更新串口列表。
5. 成员变量
private:
QPlainTextEdit* sendAera;
QPlainTextEdit* receiveAera;
QPushButton* sendButton;
QPushButton* startUSART;
QPushButton* endUSART;
QComboBox* port;
QComboBox* baudRate;
QComboBox* dataSize;
QComboBox* stopSize;
QComboBox* check;
QComboBox* receiveMode;
QComboBox* sendMode;
QSerialPort* serialPort;
QVector<QString> ports;
QPlainTextEdit* sendAera
:发送区域的文本编辑框。QPlainTextEdit* receiveAera
:接收区域的文本编辑框。QPushButton* sendButton
:发送按钮。QPushButton* startUSART
:串口连接按钮。QPushButton* endUSART
:串口断开按钮。QComboBox* port
:串口号下拉菜单。QComboBox* baudRate
:波特率下拉菜单。QComboBox* dataSize
:数据位下拉菜单。QComboBox* stopSize
:停止位下拉菜单。QComboBox* check
:校验位下拉菜单。QComboBox* receiveMode
:接收模式下拉菜单。QComboBox* sendMode
:发送模式下拉菜单。QSerialPort* serialPort
:串口对象,用于串口通信。QVector<QString> ports
:存储当前系统中可用串口的名称。
四、代码解析
ReceiveAeraInit
函数解析
void SerialHelper::ReceiveAeraInit(void) {
receiveAera = new QPlainTextEdit(this); // 创建一个 QPlainTextEdit 对象,并将其存储在 receiveAera 成员变量中
receiveAera->setFixedSize(800, 400); // 设置接收区的固定大小为 800x400 像素
receiveAera->move(30, 20); // 将接收区移动到窗口的 (30, 20) 位置
receiveAera->setReadOnly(true); // 设置接收区为只读模式
QPushButton* clearReceive = new QPushButton(QString::fromLocal8Bit("清空接收区"), this); // 创建“清空接收区”按钮
clearReceive->setFixedSize(150, 50); // 设置按钮的固定大小为 150x50 像素
clearReceive->move(680, 430); // 将按钮移动到窗口的 (680, 430) 位置
connect(clearReceive, &QPushButton::clicked, [&]() { // 将按钮的点击事件连接到槽函数
receiveAera->clear(); // 清空接收区的内容
});
}
SerialHelper 构造函数解析
SerialHelper::SerialHelper(QWidget *parent)
: QMainWindow(parent) // 继承自QMainWindow
{
this->setFixedSize(1200, 750); // 设置窗口的固定大小为1200x750像素
this->setWindowTitle(QString::fromLocal8Bit("串口助手")); // 设置窗口标题为“串口助手”
}
1. 为什么有两个 SerialHelper
?
- 第一个
SerialHelper
:这是类名,表示构造函数所属的类。 - 第二个
SerialHelper
:这是构造函数的名称,表示正在定义的函数。在C++中,构造函数的名称必须与类名相同。
2. 为什么用 ::
和 :
?
-
::
(作用域解析运算符):- 用于指定某个成员(如函数或变量)属于哪个类。
- 在
SerialHelper::SerialHelper
中,::
表示SerialHelper
构造函数属于SerialHelper
类。
-
:
(初始化列表符号):- 用于在构造函数中初始化类的成员变量或调用基类的构造函数。
- 在
: QMainWindow(parent)
中,:
表示初始化列表的开始,QMainWindow(parent)
是调用基类QMainWindow
的构造函数。
1. this
的作用
- 在
new QPlainTextEdit(this)
中,this
表示当前SerialHelper
对象。 - 作为父窗口传递给
QPlainTextEdit
,表示QPlainTextEdit
是SerialHelper
窗口的一部分。
2. connect
的作用
connect(clearReceive, &QPushButton::clicked, [&]() { receiveAera->clear(); });
-
connect
:- 单词解释:
connect
是 Qt 框架中的一个函数,用于连接信号和槽。 - 作用:在这里,
connect
将按钮的clicked
信号连接到一个槽函数。
- 单词解释:
-
clearReceive
:- 单词解释:这是按钮对象的变量名。
- 作用:表示“清空接收区”按钮。
-
&QPushButton::clicked
:- 符号解释:
&
是 C++ 中的取地址运算符。 - 作用:在这里,
&QPushButton::clicked
表示QPushButton
类的clicked
信号。
- 符号解释:
-
[&]()
:- 单词解释:
[&]
是 C++11 中的 lambda 表达式捕获列表,表示捕获当前作用域的所有变量。 - 作用:在这里,
[&]
表示捕获receiveAera
对象,以便在 lambda 表达式中使用。
- 单词解释:
-
receiveAera->clear();
:- 单词解释:
clear
是QPlainTextEdit
类的一个方法,用于清空文本内容。 - 作用:在这里,
clear
方法用于清空接收区域的内容。
- 单词解释:
::
:用于指定成员属于哪个类。:
:用于初始化类的成员变量或调用基类的构造函数。this
:表示当前对象,用于将子控件添加到父窗口。connect
:用于连接信号和槽,实现事件驱动编程。lambda
表达式:用于简化代码,捕获当前作用域的变量。
1. 接收区初始化
接收区是一个只读的文本框,用于显示从串口接收到的数据。我们还添加了一个按钮用于清空接收区内容。
void SerialHelper::ReceiveAeraInit(void) {
receiveAera = new QPlainTextEdit(this); // 创建一个文本编辑框
receiveAera->setFixedSize(800, 400); // 设置固定大小
receiveAera->move(30, 20); // 设置位置
receiveAera->setReadOnly(true); // 设置为只读
QPushButton* clearReceive = new QPushButton(QString::fromLocal8Bit("清空接收区"), this); // 创建清空按钮
clearReceive->setFixedSize(150, 50); // 设置按钮大小
clearReceive->move(680, 430); // 设置按钮位置
connect(clearReceive, &QPushButton::clicked, [&]() { // 绑定按钮点击事件
receiveAera->clear(); // 清空接收区
});
}
2. 发送区初始化
发送区是一个可编辑的文本框,用于输入要发送的数据。我们还添加了一个发送按钮和一个清空发送区的按钮。
void SerialHelper::SendAeraInit(void) {
sendAera = new QPlainTextEdit(this); // 创建发送区文本框
sendAera->setFixedSize(800, 100); // 设置大小
sendAera->move(30, 500); // 设置位置
sendButton = new QPushButton(QString::fromLocal8Bit("发送"), this); // 创建发送按钮
sendButton->setFixedSize(150, 50); // 设置按钮大小
sendButton->move(500, 630); // 设置按钮位置
sendButton->setDisabled(true); // 初始状态禁用
connect(sendButton, &QPushButton::clicked, [&]() { // 绑定发送按钮点击事件
QString data = sendAera->toPlainText(); // 获取发送区内容
if (sendMode->currentText() == "HEX") { // 如果发送模式为HEX
QByteArray arr;
for (int i = 0; i < data.size(); ++i) {
if (data[i] == ' ') continue; // 跳过空格
int num = data.mid(i, 2).toUInt(nullptr, 16); // 将两个字符转换为一个字节
++i;
arr.append(num);
}
serialPort->write(arr); // 写入串口
} else {
serialPort->write(data.toLocal8Bit().data()); // 文本模式直接发送
}
});
QPushButton* clearSend = new QPushButton(QString::fromLocal8Bit("清空发送区"), this); // 创建清空发送区按钮
clearSend->setFixedSize(150, 50); // 设置按钮大小
clearSend->move(680, 630); // 设置按钮位置
connect(clearSend, &QPushButton::clicked, [&]() { // 绑定按钮点击事件
sendAera->clear(); // 清空发送区
});
}
3. 串口配置初始化
串口配置部分包括串口号、波特率、数据位、停止位、校验位等参数的设置。
void SerialHelper::SetupInit(void) {
port = new QComboBox(this); // 创建串口号下拉菜单
baudRate = new QComboBox(this); // 创建波特率下拉菜单
dataSize = new QComboBox(this); // 创建数据位下拉菜单
stopSize = new QComboBox(this); // 创建停止位下拉菜单
check = new QComboBox(this); // 创建校验位下拉菜单
receiveMode = new QComboBox(this); // 创建接收模式下拉菜单
sendMode = new QComboBox(this); // 创建发送模式下拉菜单
// 添加波特率选项
baudRate->addItem("4800");
baudRate->addItem("9600");
baudRate->addItem("19200");
baudRate->addItem("38400");
baudRate->addItem("115200");
// 添加数据位选项
dataSize->addItem("8");
// 添加停止位选项
stopSize->addItem("1");
stopSize->addItem("1.5");
stopSize->addItem("2");
// 添加校验位选项
check->addItem(QString::fromLocal8Bit("无校验"));
check->addItem(QString::fromLocal8Bit("奇校验"));
check->addItem(QString::fromLocal8Bit("偶校验"));
// 添加接收和发送模式选项
receiveMode->addItem("HEX");
receiveMode->addItem(QString::fromLocal8Bit("文本"));
sendMode->addItem("HEX");
sendMode->addItem(QString::fromLocal8Bit("文本"));
// 创建标签并设置位置
QLabel* portLabel = new QLabel(QString::fromLocal8Bit("串口号"), this);
QLabel* baudLabel = new QLabel(QString::fromLocal8Bit("波特率"), this);
QLabel* dataLabel = new QLabel(QString::fromLocal8Bit("数据位"), this);
QLabel* stopLabel = new QLabel(QString::fromLocal8Bit("停止位"), this);
QLabel* checkLabel = new QLabel(QString::fromLocal8Bit("校验位"), this);
QLabel* receiveLabel = new QLabel(QString::fromLocal8Bit("接收格式"), this);
QLabel* sendLabel = new QLabel(QString::fromLocal8Bit("发送格式"), this);
// 设置下拉菜单和标签的位置
QVector<QComboBox*> setups = {port, baudRate, dataSize, stopSize, check, receiveMode, sendMode};
QVector<QLabel*> labels = {portLabel, baudLabel, dataLabel, stopLabel, checkLabel, receiveLabel, sendLabel};
for (int i = 0; i < setups.size(); i++) {
setups[i]->setFixedSize(200, 50);
setups[i]->move(850, 20 + 80 * i);
labels[i]->move(1080, 25 + i * 80);
}
}
4. 串口连接与断开
串口连接和断开功能通过两个按钮实现。连接按钮用于打开串口,断开按钮用于关闭串口。
void SerialHelper::BeginUSART(void) {
startUSART = new QPushButton(QString::fromLocal8Bit("串口连接"), this); // 创建连接按钮
endUSART = new QPushButton(QString::fromLocal8Bit("串口断开"), this); // 创建断开按钮
startUSART->setFixedSize(150, 50);
endUSART->setFixedSize(150, 50);
startUSART->move(1000, 630);
endUSART->move(840, 630);
endUSART->setDisabled(true); // 初始状态禁用断开按钮
// 绑定断开按钮点击事件
connect(endUSART, &QPushButton::clicked, [&]() {
sendButton->setDisabled(true); // 禁用发送按钮
startUSART->setDisabled(false); // 启用连接按钮
endUSART->setDisabled(true); // 禁用断开按钮
serialPort->close(); // 关闭串口
});
// 绑定连接按钮点击事件
connect(startUSART, &QPushButton::clicked, [&]() {
QString port1 = port->currentText(); // 获取串口号
QString baud = baudRate->currentText(); // 获取波特率
QString data = dataSize->currentText(); // 获取数据位
QString stop = stopSize->currentText(); // 获取停止位
QString ch = check->currentText(); // 获取校验位
QString receive = receiveMode->currentText(); // 获取接收模式
QString send = sendMode->currentText(); // 获取发送模式
if (port->currentText() != "") {
startUSART->setDisabled(true); // 禁用连接按钮
endUSART->setDisabled(false); // 启用断开按钮
sendButton->setDisabled(false); // 启用发送按钮
USART(); // 调用USART函数连接串口
} else {
QMessageBox::critical(this, QString::fromLocal8Bit("串口打开失败"), QString::fromLocal8Bit("请确认串口是否正确连接"));
}
});
}
5. 串口通信实现
USART
函数用于配置串口参数并打开串口。一旦串口打开成功,我们通过QSerialPort::readyRead
信号接收数据,并将其显示在接收区。
void SerialHelper::USART(void) {
QSerialPort::BaudRate Baud; // 波特率
QSerialPort::DataBits Data; // 数据位
QSerialPort::StopBits Stop; // 停止位
QSerialPort::Parity Check; // 校验位
QString por = port->currentText(); // 获取串口号
QString baud = baudRate->currentText(); // 获取波特率
QString data = dataSize->currentText(); // 获取数据位
QString stop = stopSize->currentText(); // 获取停止位
QString ch = check->currentText(); // 获取校验位
// 根据下拉菜单选择配置串口参数
if (baud == "4800") Baud = QSerialPort::Baud4800;
else if (baud == "9600") Baud = QSerialPort::Baud9600;
else if (baud == "19200") Baud = QSerialPort::Baud19200;
else if (baud == "38400") Baud = QSerialPort::Baud38400;
else if (baud == "115200") Baud = QSerialPort::Baud115200;
if (data == "8") Data = QSerialPort::Data8;
if (stop == "1") Stop = QSerialPort::OneStop;
else if (stop == "1.5") Stop = QSerialPort::OneAndHalfStop;
else if (stop == "2") Stop = QSerialPort::TwoStop;
if (ch == QString::fromLocal8Bit("无校验")) Check = QSerialPort::NoParity;
else if (ch == QString::fromLocal8Bit("奇校验")) Check = QSerialPort::OddParity;
else if (ch == QString::fromLocal8Bit("偶校验")) Check = QSerialPort::EvenParity;
serialPort = new QSerialPort(this); // 创建串口对象
serialPort->setBaudRate(Baud); // 设置波特率
serialPort->setPortName(por); // 设置串口号
serialPort->setDataBits(Data); // 设置数据位
serialPort->setParity(Check); // 设置校验位
serialPort->setStopBits(Stop); // 设置停止位
// 打开串口
if (serialPort->open(QSerialPort::ReadWrite)) {
// 绑定数据接收信号
connect(serialPort, &QSerialPort::readyRead, [&]() {
QByteArray data = serialPort->readAll(); // 读取数据
if (receiveMode->currentText() == "HEX") { // 如果接收模式为HEX
QString hex = data.toHex(' '); // 转换为十六进制字符串
receiveAera->appendPlainText(hex); // 显示在接收区
} else { // 如果接收模式为文本
QString str = QString(data); // 转换为文本
receiveAera->appendPlainText(str); // 显示在接收区
}
});
} else {
QMessageBox::critical(this, QString::fromLocal8Bit("串口打开失败"), QString::fromLocal8Bit("请确认串口是否正确连接"));
}
}
6. 动态更新串口列表
通过timerEvent
函数,我们定时扫描系统中的串口,并更新下拉菜单中的串口列表。
void SerialHelper::timerEvent(QTimerEvent* e) {
QVector<QString> temp; // 临时存储当前可用串口
for (const QSerialPortInfo& info : QSerialPortInfo::availablePorts()) {
temp.push_back(info.portName()); // 获取所有可用串口的名称
}
qSort(temp.begin(), temp.end()); // 对串口名称排序
if (temp != ports) { // 如果当前串口列表与之前不同
this->port->clear(); // 清空下拉菜单
this->ports = temp; // 更新串口列表
for (auto& a : ports) {
this->port->addItem(a); // 将新串口名称添加到下拉菜单
}
}
}
7. 主窗口初始化
在构造函数中,我们初始化窗口大小、标题,并调用上述初始化函数。
SerialHelper::SerialHelper(QWidget* parent) : QMainWindow(parent) {
this->setFixedSize(1200, 750); // 设置窗口大小
this->setWindowTitle(QString::fromLocal8Bit("串口助手")); // 设置窗口标题
ReceiveAeraInit(); // 初始化接收区
SendAeraInit(); // 初始化发送区
SetupInit(); // 初始化串口配置
BeginUSART(); // 初始化串口连接按钮
this->startTimer(1000); // 启动定时器,每秒更新串口列表
}