基于Qt的多功能串口通信工具分享:实时数据收发与波形绘制
需要工程源码请私信
基于 Qt 框架开发的多功能串口通信工具,旨在为用户提供稳定、流畅的串口数据收发体验。该工具不仅支持基本的串口通信功能,还集成了定时发送、多线程数据处理、粘包问题解决、实时波形绘制等多种高级功能。通过使用 QSerialPort 进行串口操作,并结合 QSettings 进行配置文件管理,用户可以灵活地配置通信参数,实现对外部设备的数据交互和监控。此外,软件通过使用多线程技术确保串口通信的平稳性,避免因大量数据传输导致界面卡顿。其粘包拆解机制和波形绘制功能,帮助用户更直观地观察通信数据的变化,为硬件调试和通信测试提供了强有力的支持。
1. 多线程串口通信模块
多线程的串口通信可以避免主线程卡顿,这里我们在 SerialWorker::run()
函数中实现串口的接收和发送操作。
void SerialWorker::run() {serialPort = new QSerialPort();// 设置串口参数serialPort->setPortName(portName);serialPort->setBaudRate(baudRate);serialPort->setDataBits(dataBits);serialPort->setStopBits(stopBits);serialPort->setParity(parity);if (!serialPort->open(QIODevice::ReadWrite)) {emit errors(0, serialPort->errorString());emit connected(0, false);delete serialPort;serialPort = nullptr;return;}emit connected(0, true); // 连接成功,发送信号emit informations(0, tc("串口已打开"));// 事件循环,保证串口的读写操作不会阻塞主线程QEventLoop eventLoop;QTimer timer;timer.setInterval(10); // 设置定时器的间隔为10msconnect(&timer, &QTimer::timeout, &eventLoop, &QEventLoop::quit);timer.start();while (running) {QByteArray data;{// 从队列中取出数据并发送QMutexLocker locker(&mutex);if (!sendQueue.isEmpty()) {data = sendQueue.dequeue();}}if (!data.isEmpty()) {serialPort->write(data); // 向串口写入数据if (!serialPort->waitForBytesWritten()) {emit errors(0, serialPort->errorString());} else {emit informations(0, tc("数据已发送: %1").arg(QString::fromUtf8(data)));}}// 读取串口数据if (serialPort->waitForReadyRead(10)) {QByteArray receivedData = serialPort->readAll();emit dataReceived(receivedData); // 发射信号,传递接收到的数据// 粘包数据处理if (receivedData.contains("#") && receivedData.contains("$")) {m_buf += receivedData; // 将数据加入缓存for (auto val : parseData(m_buf)) {emit dataLine(val); // 发送提取出来的数值型数据}}}// 处理事件循环,避免阻塞信号槽机制eventLoop.exec();}serialPort->close();emit informations(0, tc("串口已关闭"));emit connected(0, false);delete serialPort;serialPort = nullptr;
}
关键点:
- 事件循环:通过
QEventLoop
实现了一个持续运行的事件循环,让串口的读写操作可以实时进行。 - 发送队列:通过
QMutexLocker
锁定互斥体,保证在多线程环境下操作安全。 - 信号槽:使用信号
emit
向外部通知连接状态、接收到的数据和错误信息。
2. 粘包拆解模块
粘包问题在串口通信中很常见。我们通过正则表达式匹配接收到的数据,将粘包数据拆解出来。
QList<float> SerialWorker::parseData(QByteArray &data) {QList<float> buf; // 存储提取出的浮点数数据QRegularExpression regex("#(-?\\d*\\.?\\d+?)\\$"); // 正则表达式,匹配 #number$ 格式QRegularExpressionMatchIterator it = regex.globalMatch(data);int lastMatchEnd = 0;while (it.hasNext()) {QRegularExpressionMatch match = it.next();QString numberStr = match.captured(1); // 提取匹配的数值字符串bool ok;float number = numberStr.toFloat(&ok); // 将字符串转换为浮点数if (ok) {buf.append(number); // 将解析出的数字添加到列表中}lastMatchEnd = match.capturedEnd(0); // 记录最后一个匹配的结束位置}// 移除已处理的部分,保留未处理的部分以便后续处理if (lastMatchEnd > 0) {data.remove(0, lastMatchEnd);}return buf; // 返回提取出的浮点数列表
}
关键点:
- 正则表达式:通过正则表达式
#(-?\\d*\\.?\\d+?)\\$
匹配粘包中的数值数据,#
和$
是包裹数值的标志,支持匹配正负浮点数。 - 移除已处理数据:每次处理后,将已解析的数据从缓存中移除,未处理的数据保留在缓存中等待下次处理。
3. 波形绘制模块
接收到的数值数据会显示在一个波形图中,以下是 WaveformWidget
的实现。
void WaveformWidget::addDataPoint(float value) {if (dataPoints.size() >= maxDataPoints) {dataPoints.pop_front(); // 如果数据点过多,则移除最旧的数据点}dataPoints.push_back(value); // 添加新的数据点totalPointsReceived++; // 统计接收到的总点数calculateStatistics(); // 计算最大值、最小值和平均值update(); // 触发界面重绘
}void WaveformWidget::paintEvent(QPaintEvent *) {QPainter painter(this);painter.setRenderHint(QPainter::Antialiasing); // 开启抗锯齿,使波形更平滑drawAxes(painter); // 绘制坐标轴drawGrid(painter); // 绘制背景网格drawData(painter); // 绘制波形数据drawStatistics(painter); // 显示最大、最小和平均值
}
关键点:
- 实时绘制:当新的数据点被添加时,调用
update()
触发重绘,显示最新的波形。 - 平滑显示:通过
QPainter::setRenderHint(QPainter::Antialiasing)
开启抗锯齿,使波形显示更加平滑。
4. 配置文件管理模块
软件通过 config.ini
文件存储配置项,QSettings
用于管理配置的读写。
void MainWindow::createConfigFile(const QString &fileName, const QStringList &values) {QFile file(fileName);if (file.exists()) return; // 如果文件已经存在,则跳过QSettings settings(fileName, QSettings::IniFormat);settings.setIniCodec(QTextCodec::codecForName("UTF-8")); // 设置编码为UTF-8int groupIndex = 1;for (const QString &value : values) {QString groupName = QString("%1").arg(groupIndex);settings.beginGroup(groupName); // 创建新的组settings.setValue(QString("%1").arg(groupIndex), value); // 写入键值对settings.endGroup();groupIndex++;}
}QStringList MainWindow::readConfigFile(const QString &fileName) {QStringList iniinfors;QSettings settings(fileName, QSettings::IniFormat);settings.setIniCodec(QTextCodec::codecForName("UTF-8"));QStringList groups = settings.childGroups(); // 读取所有组groups.sort();for (const QString &group : groups) {settings.beginGroup(group);QStringList keys = settings.childKeys(); // 读取所有键for (const QString &key : keys) {iniinfors << settings.value(key).toString(); // 获取键值}settings.endGroup();}return iniinfors;
}
关键点:
- 配置文件自动生成:如果配置文件不存在,会自动生成一个
config.ini
文件,并写入初始值。 - 配置文件读取:软件启动时,通过
readConfigFile()
函数读取配置项,并将其加载到界面上。
总结
通过上述代码与注释,软件实现了以下核心功能:
- 多线程串口通信:避免主线程阻塞,通过事件循环确保数据收发的实时性。
- 粘包数据拆解:使用正则表达式解析粘包数据,确保接收的数据是正确的。
- 实时波形绘制:接收到的数据点会动态绘制在波形图中,提供可视化的反馈。
- 配置文件管理:通过 QSettings 管理配置项,支持自动生成和读取配置文件 。