1. 项目概述与核心价值
串口通信作为嵌入式开发、工业控制、物联网设备调试等领域的基础通信方式,几乎存在于所有硬件工程师的日常工作中。传统调试方式往往依赖第三方工具(如SecureCRT、Putty等),但这些工具存在功能单一、无法定制化、数据记录不便等问题。而用Qt框架开发的自定义串口工具,不仅能实现基础收发功能,还能根据项目需求灵活扩展数据解析、协议转换等高级功能。
这个项目最大的亮点在于"零依赖"——仅使用Qt原生提供的QSerialPort类实现,无需额外安装任何第三方库。这意味着:
- 编译后的程序可以在任何支持Qt的环境直接运行
- 避免了第三方库可能带来的兼容性问题
- 代码结构更清晰,便于二次开发和功能扩展
我曾在一个工业传感器项目中,用类似的自研工具替代了价值上万的商业软件,不仅实现了自动化的数据采集,还内置了CRC校验和异常报警功能。下面就来拆解这个工具的完整实现过程。
2. 开发环境准备
2.1 Qt安装与配置建议
推荐使用Qt 5.15 LTS版本(当前最新为5.15.2),这是长期支持版本中功能最完善的。安装时注意勾选:
- MSVC 2019 64-bit(Windows平台)
- MinGW 8.1.0 64-bit(跨平台)
- Qt Creator(官方IDE)
注意:虽然Qt6已发布,但其串口模块(QSerialPort)在部分平台仍有兼容性问题,生产环境建议优先选择Qt5。
2.2 项目创建关键步骤
- 新建Qt Widgets Application项目
- 在.pro文件中添加串口模块依赖:
qmake复制QT += serialport - 主窗口建议采用以下布局:
cpp复制QHBoxLayout *mainLayout = new QHBoxLayout; mainLayout->addWidget(createConfigGroup()); // 左侧配置面板 mainLayout->addWidget(createTerminalGroup()); // 右侧终端显示 setLayout(mainLayout);
3. 串口核心功能实现
3.1 端口扫描与参数配置
串口设备的动态发现是第一个技术难点。在Windows平台,注册表查询方式已经过时,正确做法是遍历所有可能的COM号并尝试打开:
cpp复制void MainWindow::refreshSerialPort()
{
ui->comboBoxPort->clear();
foreach (const QSerialPortInfo &info, QSerialPortInfo::availablePorts()) {
QString portName = info.portName();
// 过滤虚拟串口(如蓝牙)
if(!portName.startsWith("BTH")){
ui->comboBoxPort->addItem(portName);
}
}
}
波特率等参数建议提供常用预设:
cpp复制QList<qint32> baudRates = {1200, 2400, 4800, 9600, 19200, 38400, 57600, 115200};
foreach (qint32 baud, baudRates) {
ui->comboBoxBaud->addItem(QString::number(baud), baud);
}
3.2 数据收发核心逻辑
发送功能的实现要注意文本模式和HEX模式的区别:
cpp复制void MainWindow::onSendData()
{
if(!serialPort->isOpen()) return;
QString data = ui->textEditSend->toPlainText();
if(ui->checkBoxHexSend->isChecked()){
QByteArray hexData = QByteArray::fromHex(data.toLatin1());
serialPort->write(hexData);
}else{
serialPort->write(data.toUtf8());
}
}
接收数据时需要处理可能的分包情况:
cpp复制void MainWindow::onReadyRead()
{
QByteArray data = serialPort->readAll();
// HEX显示处理
if(ui->checkBoxHexDisplay->isChecked()){
QString hexString;
for(char ch : data){
hexString += QString("%1 ").arg((quint8)ch, 2, 16, QLatin1Char('0'));
}
ui->textEditRecv->append(hexString.toUpper());
}else{
// 处理非ASCII字符显示
QString text = QString::fromUtf8(data).toHtmlEscaped();
ui->textEditRecv->append(text);
}
}
3.3 流控制与超时处理
工业环境中常需要硬件流控(RTS/CTS):
cpp复制serialPort->setFlowControl(QSerialPort::HardwareControl);
超时设置对稳定通信至关重要:
cpp复制serialPort->setReadBufferSize(1024); // 根据实际数据量调整
serialPort->setDataTerminalReady(true); // 保持DTR信号
4. 界面优化与实用功能
4.1 终端显示增强
原始QTextEdit直接显示会有性能问题,建议优化:
cpp复制// 在构造函数中设置
ui->textEditRecv->setMaximumBlockCount(1000); // 限制最大行数
ui->textEditRecv->setWordWrapMode(QTextOption::NoWrap); // 禁止自动换行
// 接收数据时改用移动光标方式提升性能
QTextCursor cursor = ui->textEditRecv->textCursor();
cursor.movePosition(QTextCursor::End);
cursor.insertText(displayText);
ui->textEditRecv->setTextCursor(cursor);
4.2 自动发送与定时器
定时发送功能在设备测试中非常实用:
cpp复制void MainWindow::onAutoSendToggled(bool checked)
{
if(checked){
int interval = ui->spinBoxInterval->value();
timer->start(interval);
}else{
timer->stop();
}
}
重要提示:定时器精度受系统影响,工业级应用建议使用QElapsedTimer做补偿
4.3 数据记录与回放
添加CSV记录功能:
cpp复制void MainWindow::startRecording()
{
QString fileName = QFileDialog::getSaveFileName(this, "保存记录", "", "CSV文件 (*.csv)");
if(!fileName.isEmpty()){
logFile.setFileName(fileName);
if(logFile.open(QIODevice::WriteOnly | QIODevice::Text)){
logStream.setDevice(&logFile);
logStream << "Timestamp,Data\n"; // CSV头
}
}
}
// 收到数据时记录
logStream << QDateTime::currentDateTime().toString("hh:mm:ss.zzz") << ",";
logStream << QString(data.toHex()) << "\n";
5. 跨平台兼容性处理
5.1 Linux/Mac特殊处理
在Unix-like系统下,串口设备需要权限:
bash复制# 将用户加入dialout组
sudo usermod -a -G dialout $USER
设备路径也不同:
cpp复制#ifdef Q_OS_LINUX
serialPort->setPortName("/dev/ttyUSB0");
#elif defined(Q_OS_WIN)
serialPort->setPortName("COM3");
#endif
5.2 编码问题解决方案
跨平台文本编码统一处理:
cpp复制// 发送前统一转换
QByteArray sendData;
if(ui->checkBoxHexSend->isChecked()){
sendData = QByteArray::fromHex(ui->textEditSend->toPlainText().toLatin1());
}else{
QString text = ui->textEditSend->toPlainText();
sendData = text.toUtf8(); // 统一使用UTF-8
}
6. 常见问题排查指南
6.1 端口无法打开
典型错误及解决方案:
-
错误:"Permission denied"
- Linux:检查用户组和权限
- Windows:检查是否被其他程序占用
-
错误:"Device not found"
- 检查设备管理器是否识别硬件
- 尝试更换USB端口(某些CH340芯片需要重新插拔)
6.2 数据接收不完整
可能原因:
- 波特率不匹配(用示波器校验实际波特率)
- 硬件流控未正确配置
- 接收缓冲区溢出(调整setReadBufferSize)
调试技巧:
cpp复制// 在readyRead信号中打印调试信息
qDebug() << "Bytes available:" << serialPort->bytesAvailable();
qDebug() << "Buffer size:" << serialPort->readBufferSize();
6.3 中文乱码问题
终极解决方案:
cpp复制QTextCodec *codec = QTextCodec::codecForName("GB18030");
QString text = codec->toUnicode(data); // 适用于多数中文设备
7. 功能扩展方向
7.1 协议解析插件
定义通用解析接口:
cpp复制class ProtocolParser : public QObject {
Q_OBJECT
public:
virtual void parseData(const QByteArray &data) = 0;
signals:
void parsedResult(QString result);
};
7.2 波形显示功能
结合QCustomPlot库实现:
cpp复制// 在.pro中添加
include(/path/to/qcustomplot.h)
// 数据采样
void updateWaveform(double value) {
static QTime time(QTime::currentTime());
double key = time.elapsed()/1000.0;
ui->customPlot->graph(0)->addData(key, value);
ui->customPlot->xAxis->setRange(key, 8, Qt::AlignRight);
ui->customPlot->replot();
}
7.3 自动化测试脚本
集成QProcess调用Python脚本:
cpp复制QProcess::execute("python", QStringList() << "test_script.py" << portName);
这个串口工具在实际项目中已经迭代了多个版本,最关键的体会是:一定要做好错误处理和状态检测。比如在每次发送前检查端口状态,在断开连接时自动禁用发送按钮,这些细节决定了工具的稳定性。对于需要长时间运行的工业场景,建议添加看门狗定时器,定期检查端口连接状态。