串口类实现
- SerialServer类简介
我们创建了一个SerialServer类,它继承自QObject,用于处理串口的相关操作。该类提供了打开、关闭串口,发送和接收串口数据的功能,并通过信号槽机制与主界面线程进行通信。
2. SerialServer类声明
#ifndef SERIALSERVER_H
#define SERIALSERVER_H
#include <QObject>
#include <QSerialPort>
#include <QWaitCondition>
#include <QMutex>
#include <QCoreApplication>
class SerialServer : public QObject {
Q_OBJECT
public:
SerialServer();
~SerialServer();
// 打开串口
bool openSerialPort(const QString &portName, unsigned int baudRate, unsigned char dataBits = 8, unsigned char parity = 0, unsigned char stopBits = 1);
void closeSerialPort(); // 关闭串口
bool isSerialPortOpen() const; // 串口是否打开
public slots:
void ReceviceSerialData(); // 接收串口数据
void SendSerialData(const QByteArray &data); // 发送串口数据
signals:
void serialDataReceivedReady(const QByteArray &data);
void serialDataSendReady(const QByteArray &data);
private:
QSerialPort *m_serial;
QByteArray m_DataBuf;
};
#endif // SERIALSERVER_H
- SerialServer类定义
在SerialServer类的定义中,主要实现了串口的打开与关闭、数据的接收与发送功能,并使用信号槽机制通知主线程数据的接收。
#include "SerialServer.h"
#include <QDebug>
#include <QThread>
SerialServer::SerialServer() : m_serial(new QSerialPort(this)) {
}
SerialServer::~SerialServer() {
closeSerialPort();
}
bool SerialServer::openSerialPort(const QString &portName, unsigned int baudRate, unsigned char dataBits, unsigned char parity, unsigned char stopBits) {
if (m_serial->isOpen()) {
return true;
}
m_serial->setPortName(portName);
m_serial->setBaudRate(baudRate);
m_serial->setDataBits(static_cast<QSerialPort::DataBits>(dataBits));
m_serial->setParity(static_cast<QSerialPort::Parity>(parity));
m_serial->setStopBits(static_cast<QSerialPort::StopBits>(stopBits));
m_serial->setFlowControl(QSerialPort::NoFlowControl);
if (m_serial->open(QIODevice::ReadWrite)) {
connect(m_serial, &QSerialPort::readyRead, this, &SerialServer::ReceviceSerialData);
return true;
} else {
return false;
}
}
void SerialServer::closeSerialPort() {
if (m_serial->isOpen()) {
m_serial->close();
}
}
bool SerialServer::isSerialPortOpen() const {
return m_serial->isOpen();
}
void SerialServer::ReceviceSerialData()
{
// 获取串口接收到的数据
QByteArray data = m_serial->readAll();
if (!data.isEmpty()) {
m_DataBuf.append(data); // 将接收到的数据追加到缓冲区
int nBufLen = m_DataBuf.size(); // 获取当前缓冲区中的数据长度
// 如果数据长度不足10,则返回,继续等待接收
if (nBufLen < 10) {
return;
}
// 如果长度大于等于10,则处理所有的数据(不固定字节数)
QString hexData;
for (int i = 0; i < m_DataBuf.size(); ++i) {
hexData.append(QString::asprintf("0x%02X ", static_cast<uint8_t>(m_DataBuf[i]))); // 将数据转换为16进制格式
}
qDebug() << "接收数据(长度大于等于10):" << hexData << QThread::currentThread();
// 发送接收到的数据到GUI窗口
emit serialDataReceivedReady(m_DataBuf);
// 清空缓冲区,准备接收新的数据
m_DataBuf.clear();
}
}
void SerialServer::SendSerialData(const QByteArray &data)
{
if (m_serial->isOpen()) {
m_serial->write(data); // 接收GUI数据并发送
#if 1 \
// 发送数据打印
QString hexData;
for (int i = 0; i < data.size(); ++i) {
hexData.append(QString::asprintf("0x%02X ", static_cast<uint8_t>(data[i])));
}
qDebug() << "SEND(HEX):" << hexData << QThread::currentThread();
#endif
qDebug() << "SEND(HEX): " << data.toHex().toUpper() << QThread::currentThread();
}
}
串口服务与GUI主界面
- 主窗口类简介
主窗口类aMainWindow通过QThread将SerialServer对象移动到子线程中,以实现串口通信的异步处理。这种设计确保了UI界面在处理串口数据时不会被阻塞,从而提升了用户体验。
2. 主窗口类的声明与定义
主窗口类aMainWindow的主要功能包括:初始化串口配置、连接和断开串口、发送和接收数据,以及更新UI。
#ifndef AMAINWINDOW_H
#define AMAINWINDOW_H
#include <QMainWindow>
#include <QThread>
#include <qDebug>
#include <QSerialPortInfo>
#include <QThread>
#include <QTime>
#include "serialserver.h"
static const unsigned int use_BAUD[8]={1200,2400,4800,9600,19200,38400,57600,115200};
static const QSerialPort::StopBits use_STOPBITS[4]={QSerialPort::OneStop,QSerialPort::OneAndHalfStop,QSerialPort::TwoStop,QSerialPort::UnknownStopBits};
static const QSerialPort::Parity use_PARITY[5]={QSerialPort::NoParity,QSerialPort::EvenParity,QSerialPort::OddParity,QSerialPort::SpaceParity,QSerialPort::MarkParity};
static const QSerialPort::DataBits use_DATABITS[4]={QSerialPort::Data5,QSerialPort::Data6,QSerialPort::Data7,QSerialPort::Data8};
QT_BEGIN_NAMESPACE
namespace Ui {
class aMainWindow;
}
QT_END_NAMESPACE
class aMainWindow : public QMainWindow
{
Q_OBJECT
public:
aMainWindow(QWidget *parent = nullptr);
~aMainWindow();
public:
void initControlProcess(); // 控件初始化
void FillSerialControl(); // 填充串口控件数据
void updateControlState(bool flag); // 控件状态更新
void displaySendData(const QByteArray &data); // 显示发送数据
void appendColoredText(const QString &prefix, const QString &data, const QColor &color); // 颜色区分数据
QString formatDataBasedOnHexCheck(const QByteArray &data);
private slots:
void on_pushButton_Connect_clicked(); // 连接
void on_pushButton_disConnect_clicked(); // 断开
void on_pushButton_update_clicked(); // 刷新可用串口
void on_pushButton_cleanrecv_clicked(); // 接收区清空
void on_pushButton_send_clicked(); // 发送
void on_pushButton_cleansend_clicked(); // 发送区清空
void onSerialDataReceived(const QByteArray &data); // 接收的串口数据
void updateButtonState_cleanrecv(); // 接收区/发送区状态判断
void updateButtonState_cleansend();
void on_checkBox_Hex_stateChanged(int arg1);// 更新勾选状态
private:
Ui::aMainWindow *ui;
SerialServer *serialServer;
QThread *serialThread;
};
#endif // AMAINWINDOW_H
- 主窗口类的实现
主窗口类的实现中,重点在于如何启动和管理串口服务线程,并通过信号槽机制实现串口数据的收发和UI的更新。
#include "amainwindow.h"
#include "ui_amainwindow.h"
#include <QTextFormat>
aMainWindow::aMainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::aMainWindow)
{
ui->setupUi(this);
qDebug() << "GUI:" << QThread::currentThread();
initControlProcess();
serialServer = new SerialServer();
serialThread = new QThread(this);
serialServer->moveToThread(serialThread); // 将 SerialServer 对象移动到新线程
connect(serialServer, &SerialServer::serialDataReceivedReady, this, &aMainWindow::onSerialDataReceived);
serialThread->start(); // 启动线程
connect(serialThread, &QThread::finished, serialServer, &QObject::deleteLater);
connect(ui->plainTextEdit_recv, &QPlainTextEdit::textChanged, this, &aMainWindow::updateButtonState_cleanrecv);
connect(ui->pushButton_cleanrecv, &QPushButton::clicked, this, &aMainWindow::on_pushButton_cleanrecv_clicked);
connect(ui->plainTextEdit_send, &QPlainTextEdit::textChanged, this, &aMainWindow::updateButtonState_cleansend);
connect(ui->pushButton_cleansend, &QPushButton::clicked, this, &aMainWindow::on_pushButton_cleansend_clicked);
}
aMainWindow::~aMainWindow()
{
serialThread->quit();
serialThread->wait();
delete serialServer; // 删除 serialServer 对象
delete ui;
}
void aMainWindow::initControlProcess()
{
FillSerialControl();
ui->label_ConnectState->setText("未连接");
ui->label_RS->setText("UTF-8");
ui->pushButton_cleanrecv->setEnabled(false);
ui->pushButton_cleansend->setEnabled(false);
updateControlState(false);
}
void aMainWindow::FillSerialControl()
{
// 填充波特率
for (int i = 0; i < 8; ++i) {
ui->comboBox_Baud->addItem(QString::number(use_BAUD[i]));
}
// 填充数据位
QStringList dataBitsLabels = {"5", "6", "7", "8"};
for (int i = 0; i < 4; ++i) {
ui->comboBox_Databits->addItem(dataBitsLabels[i], use_DATABITS[i]);
}
// 填充校验位
QStringList parityLabels = {"No Parity", "Even Parity", "Odd Parity", "Space Parity", "Mark Parity"};
for (int i = 0; i < 5; ++i) {
ui->comboBox_Parity->addItem(parityLabels[i], use_PARITY[i]);
}
// 填充停止位
QStringList stopBitsLabels = {"1", "1.5", "2", "Unknown"};
for (int i = 0; i < 4; ++i) {
ui->comboBox_Stopbits->addItem(stopBitsLabels[i], use_STOPBITS[i]);
}
// 自动获取可用串口号
ui->comboBox_Com->clear();
const auto ports = QSerialPortInfo::availablePorts();
for (const QSerialPortInfo &portInfo : ports) {
ui->comboBox_Com->addItem(portInfo.portName());
}
// 如果没有找到可用的串口
if (ui->comboBox_Com->count() == 0) {
ui->comboBox_Com->addItem(QStringLiteral("无可用串口"));
}
}
void aMainWindow::updateControlState(bool flag)
{
QList<QPushButton*> buttons = {ui->pushButton_disConnect,ui->pushButton_send};
for (QPushButton* button : buttons) {
button->setEnabled(flag);
}
}
void aMainWindow::displaySendData(const QByteArray &data)
{
QString strSend = formatDataBasedOnHexCheck(data);
appendColoredText("<SEND>: ", strSend, Qt::blue);
}
void aMainWindow::appendColoredText(const QString &prefix, const QString &data, const QColor &color)
{
QDateTime dateTime = QDateTime::currentDateTime();
QString s_time = dateTime.toString("hh:mm:ss.zzz"); // 获取当前时间
QMetaObject::invokeMethod(this, [=] {
QTextCursor cursor(ui->plainTextEdit_recv->textCursor());
cursor.movePosition(QTextCursor::End);
ui->plainTextEdit_recv->setTextCursor(cursor);
QTextCharFormat format;
format.setForeground(color); // 设置传入的颜色
cursor.insertText(s_time + prefix, format); // 插入时间戳和前缀
format.setForeground(Qt::black); // 恢复为黑色字体(数据部分)
cursor.insertText(data + "\n", format);
}, Qt::QueuedConnection);
}
QString aMainWindow::formatDataBasedOnHexCheck(const QByteArray &data)
{
QString formattedData;
if (ui->checkBox_Hex->isChecked()) {
formattedData = data.toHex(' ').toUpper(); // hex 格式
ui->label_RS->setText("HEX");
} else {
formattedData = QString::fromUtf8(data); // 原始数据格式
ui->label_RS->setText("UTF-8");
}
return formattedData;
}
void aMainWindow::on_pushButton_Connect_clicked()
{
QString portName = ui->comboBox_Com->currentText();
unsigned int baudRate = ui->comboBox_Baud->currentText().toUInt();
QSerialPort::DataBits dataBits = static_cast<QSerialPort::DataBits>(ui->comboBox_Databits->currentData().toInt());
QSerialPort::Parity parity = static_cast<QSerialPort::Parity>(ui->comboBox_Parity->currentData().toInt());
QSerialPort::StopBits stopBits = static_cast<QSerialPort::StopBits>(ui->comboBox_Stopbits->currentData().toInt());
QMetaObject::invokeMethod(serialServer, [=]{
if (serialServer->openSerialPort(portName, baudRate, dataBits, parity, stopBits)) {
qDebug() << "串口打开成功:" << portName << baudRate << dataBits << stopBits << parity;
// 更新UI在主线程中完成
QMetaObject::invokeMethod(this, [=] {
ui->label_ConnectState->setText("已连接");
ui->pushButton_Connect->setEnabled(false);
updateControlState(true);
}, Qt::QueuedConnection);
} else {
qDebug() << "串口打开失败,请检查串口是否被占用或配置是否正确:" << portName << baudRate << dataBits << stopBits << parity;
}
}, Qt::QueuedConnection);
}
void aMainWindow::on_pushButton_disConnect_clicked()
{
QMetaObject::invokeMethod(serialServer, [=] {
serialServer->closeSerialPort();
}, Qt::QueuedConnection);
qDebug() << "断开串口连接";
ui->label_ConnectState->setText("未连接");
ui->pushButton_Connect->setEnabled(true);
updateControlState(false);
}
void aMainWindow::on_pushButton_update_clicked()
{
// 自动获取可用串口号
ui->comboBox_Com->clear();
const auto ports = QSerialPortInfo::availablePorts();
for (const QSerialPortInfo &portInfo : ports) {
ui->comboBox_Com->addItem(portInfo.portName());
}
// 如果没有找到可用的串口
if (ui->comboBox_Com->count() == 0) {
ui->comboBox_Com->addItem(QStringLiteral("无可用串口"));
}
qDebug() << "串口已刷新,请注意保存";
}
void aMainWindow::on_pushButton_cleanrecv_clicked()
{
if (ui->plainTextEdit_recv->toPlainText().isEmpty()) {
ui->pushButton_cleanrecv->setEnabled(false);
} else {
ui->plainTextEdit_recv->clear();
}
}
void aMainWindow::on_pushButton_send_clicked()
{
QString text = ui->plainTextEdit_send->toPlainText();
QByteArray data = text.toUtf8();
if (!text.isEmpty()) {
// 发送数据的部分放入serialServer的线程
QMetaObject::invokeMethod(serialServer, [=] {
serialServer->SendSerialData(data);
}, Qt::QueuedConnection);
// UI更新部分在主线程中执行
QDateTime dateTime = QDateTime::currentDateTime();
QString s_time = dateTime.toString("hh:mm:ss.zzz");
QString strSend = QString(data);
appendColoredText("<SEND>: ", strSend, Qt::blue);
} else {
qDebug() << "发送数据不可为空";
ui->statusbar->showMessage("发送数据不可为空.", 1500);
}
}
void aMainWindow::on_pushButton_cleansend_clicked()
{
ui->plainTextEdit_send->clear();
if (ui->plainTextEdit_send->toPlainText().isEmpty()) {
ui->pushButton_cleansend->setEnabled(false);
} else {
ui->plainTextEdit_send->clear();
}
}
void aMainWindow::onSerialDataReceived(const QByteArray &data)
{
QString strRecv = formatDataBasedOnHexCheck(data);
appendColoredText("<RECV>: ", strRecv, Qt::green);
}
void aMainWindow::updateButtonState_cleanrecv()
{
ui->pushButton_cleanrecv->setEnabled(!ui->plainTextEdit_recv->toPlainText().isEmpty());
}
void aMainWindow::updateButtonState_cleansend()
{
ui->pushButton_cleansend->setEnabled(!ui->plainTextEdit_send->toPlainText().isEmpty());
}
void aMainWindow::on_checkBox_Hex_stateChanged(int arg1)
{
if (ui->checkBox_Hex->isChecked()) {
ui->label_RS->setText("HEX");
} else {
ui->label_RS->setText("UTF8");
}
}
通过本文,我们实现了一个基于QThread的串口通信类,SerialServer。该类能够在单独的线程中处理串口的打开、关闭以及数据的收发操作,并通过信号槽机制与主线程通信。这种方式可以有提高应用程序的响应性,避免界面卡顿。
在实际使用中,可以根据项目的具体需求对SerialServer类进行扩展和改进,比如增加对异常情况的处理、支持更多的串口配置选项等。
希望这篇文章对你在Qt中的串口通信开发有所帮助