需要工程源码请私信
基于 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); // 设置定时器的间隔为10ms
connect(&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-8
int 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 管理配置项,支持自动生成和读取配置文件 。