跳到主要内容 C++ 串口应用开发详解:Qt、Boost 与 Modbus 实战 | 极客日志
C++
C++ 串口应用开发详解:Qt、Boost 与 Modbus 实战 本文详解 C++ 串口开发流程,涵盖 Qt QSerialPort 基础操作、同步与异步读写、错误处理及测试。对比 Boost.Asio 高性能异步方案,并介绍 libmodbus 库在 Modbus RTU 协议中的应用。包含跨平台配置、代码示例、性能优化及常见问题解决,适用于工业控制与嵌入式场景。
DotNetGuy 发布于 2026/3/25 0 浏览C++ 串口应用开发详解
引言
串口(Serial Port)是计算机与外部设备(如传感器、测试仪、嵌入式板)通信的经典接口。在 C++ 中,串口开发常用于工业控制、物联网、硬件调试和半导体测试机等场景。Qt 框架提供跨平台的串口支持(QSerialPort),而标准 C++ 需依赖系统 API(如 Windows 的 Win32 API 或 Linux 的 termios)。
本文从基础到实战,详解 C++ 串口开发流程,包括配置、读写、错误处理。所有示例基于 Qt 6.x(推荐跨平台方案),并提供 Boost.Asio 高性能异步方案及 libmodbus 协议库应用。
1. 串口基础概念
串口是异步通信接口,使用 RS-232/485 协议:
波特率 :数据传输速度(如 9600、115200 bps)
数据位 :每个字节位数(通常 8 位)
校验位 :奇偶校验(None/Odd/Even)
停止位 :1/1.5/2 位
流控制 :None/RTS-CTS/Xon-Xoff在 C++ 中,使用 QSerialPort(Qt)或 Boost.Asio(跨平台库)实现。Qt 最简单,适用于桌面/嵌入式。
2. C++ 串口开发准备
安装 Qt 下载 Qt 6.x,包含 Qt SerialPort 模块。
find_package(Qt6 REQUIRED COMPONENTS SerialPort)
target_link_libraries(MyApp PRIVATE Qt6::SerialPort)
QT += serialport
CONFIG += c++17
3. 基本操作:打开、配置、关闭串口
打开:open(QIODevice::ReadWrite)
配置:setBaudRate()、setDataBits() 等
关闭:close()
#include <QSerialPort>
#include <QDebug>
void configureSerialPort (QSerialPort &port, const QString &portName) {
port.setPortName (portName);
port.setBaudRate (QSerialPort::Baud115200);
port.setDataBits (QSerialPort::Data8);
port.setParity (QSerialPort::NoParity);
port.setStopBits (QSerialPort::OneStop);
port.setFlowControl (QSerialPort::NoFlowControl);
if (port.open (QIODevice::ReadWrite)) {
qDebug () << "串口打开成功:" << portName;
} else {
qDebug () << "串口打开失败:" << port.errorString ();
}
}
void closeSerialPort (QSerialPort &port) {
if (port.isOpen ()) {
port.close ();
qDebug () << "串口关闭成功" ;
}
}
4. 数据读写:同步 vs 异步
同步读写 :阻塞主线程,适合简单脚本。
异步读写 :使用 readyRead 信号 + 槽,非阻塞。
void syncReadWrite (QSerialPort &port, const QByteArray &data) {
port.write (data);
port.waitForBytesWritten (1000 );
if (port.waitForReadyRead (1000 )) {
QByteArray response = port.readAll ();
qDebug () << "收到响应:" << response;
} else {
qDebug () << "读取超时" ;
}
}
class SerialHandler : public QObject {
Q_OBJECT
public :
SerialHandler (QSerialPort *port) : m_port (port) {
connect (m_port, &QSerialPort::readyRead, this , &SerialHandler::handleReadyRead);
connect (m_port, &QSerialPort::errorOccurred, this , &SerialHandler::handleError);
}
private slots:
void handleReadyRead () {
QByteArray data = m_port->readAll ();
qDebug () << "异步收到:" << data;
}
void handleError (QSerialPort::SerialPortError error) {
if (error != QSerialPort::NoError) {
qDebug () << "串口错误:" << m_port->errorString ();
}
}
private :
QSerialPort *m_port;
};
5. 错误处理与调试
端口不存在 / 被占用:检查设备管理器(Windows)或 ls /dev/tty*(Linux)
权限问题:Linux 加组 sudo usermod -aG dialout $USER
超时:增加 waitForXXX 时间或用异步
port.errorString() 获取错误消息
Qt Creator 调试器查看端口状态
工具:串口助手监控数据
6. 实战示例:简单串口收发器 #include <QCoreApplication>
#include <QSerialPort>
#include <QDebug>
#include <QTimer>
int main (int argc, char *argv[]) {
QCoreApplication app (argc, argv) ;
QSerialPort port;
configureSerialPort (port, "COM3" );
SerialHandler handler (&port) ;
QTimer timer;
int count = 0 ;
connect (&timer, &QTimer::timeout, [&] {
QByteArray data = QByteArray::number (count++) + "\n" ;
port.write (data);
});
timer.start (1000 );
return app.exec ();
}
7. 测试用例 #include <QtTest>
#include <QSerialPort>
class SerialTest : public QObject {
Q_OBJECT
private slots:
void test_open_close () {
QSerialPort port;
port.setPortName ("COM3" );
QVERIFY (port.open (QIODevice::ReadWrite));
QVERIFY (port.isOpen ());
port.close ();
QVERIFY (!port.isOpen ());
}
void test_write_read () {
QSerialPort port ("COM3" ) ;
port.open (QIODevice::ReadWrite);
QByteArray data = "test\n" ;
port.write (data);
port.waitForBytesWritten (1000 );
QVERIFY (port.waitForReadyRead (1000 ));
QByteArray response = port.readAll ();
QCOMPARE (response, data);
}
};
QTEST_MAIN (SerialTest)
#include "serial_test.moc"
qmake && make && ./serial_test
8. 优化与高级技巧
多线程 :采集用 QThread,避免 UI 卡顿
缓冲区 :setReadBufferSize(4096) 优化大流
超时处理 :setReadTimeout(1000)
自定义协议 :添加 CRC 校验防错
9. Qt QSerialPort 与 Boost.Asio 对比
Qt QSerialPort 官方文档关键点(Qt 6.8)
跨平台(Windows、Linux、macOS、Android、嵌入式)
同步 + 异步读写均支持
异步通过 readyRead() 信号 + 槽函数
支持所有常用串口参数:波特率、数据位、校验、停止位、流控制
错误处理统一(errorOccurred 信号 + error() 方法)
类别 方法/信号 说明 端口枚举 QSerialPortInfo::availablePorts()返回所有可用串口列表 打开/关闭 open(QIODevice::ReadWrite) / close()打开读写模式 配置 setBaudRate()、setDataBits() 等波特率 9600~115200 等 同步读写 write()、readAll()、waitForReadyRead()阻塞式,适合简单脚本 异步读写 readyRead() 信号数据到达时触发,推荐生产环境 错误处理 errorOccurred 信号 + errorString()统一错误报告 超时 setReadTimeout()(Qt 6 新增)异步模式下读超时控制
Boost.Asio SerialPort(高级异步)
纯异步、非阻塞(基于 io_context / io_service)
跨平台(Windows、Linux、macOS)
支持 strand(序列化操作,避免数据竞争)
可与 Boost.Beast、gRPC 等无缝集成
性能极高(单线程处理数万连接)
需要手动管理缓冲区、超时、重连
对比总结 项目 Qt QSerialPort Boost.Asio SerialPort 推荐场景 开发难度 极低(信号槽 + 同步/异步一键切换) 中高(需手动管理 io_context、strand、缓冲) Qt 更快上手 性能 中等(单线程异步可达数千连接) 极高(单线程可达数万连接) 高并发服务器选 Boost 跨平台性 优秀(Qt 官方维护) 优秀(Boost 社区维护) 两者相当 异步模型 信号槽(readyRead) 回调 / 协程(C++20 co_await) Qt 更符合 Qt 生态,Boost 更灵活 错误处理 统一 errorOccurred 信号 每个 async 操作返回 error_code Qt 更简单 线程安全 信号槽自动跨线程 需手动 strand 或 io_context strand Qt 更安全 集成难度 与 Qt Widgets/QML 无缝 需桥接(可与 Qt 混合,但较复杂) GUI 选 Qt,纯后台选 Boost 维护成本 低(Qt 官方长期支持) 中(Boost 社区活跃) Qt 更稳定
桌面/嵌入式 GUI + 串口 → 首选 Qt QSerialPort(开发快、生态好)
高并发后台服务器 / 纯 C++ 项目 → 首选 Boost.Asio(性能极致、灵活)
10. libmodbus 串口协议开发详解 libmodbus 是一个轻量级、跨平台的开源 Modbus 协议库,支持 Modbus RTU (串口)和 Modbus TCP ,广泛用于工业控制、PLC 通信、传感器数据采集、半导体测试设备等场景。
1. libmodbus 核心优势与适用场景 特性 说明 对比其他库(如 QModbus、FreeModbus) 跨平台 Windows、Linux、macOS、BSD、嵌入式(无 OS 也可) 几乎所有平台都支持 性能 极轻量(单文件编译 ~100KB),零依赖(除标准 C 库) 比 Qt/QModbus 轻量 10 倍以上 协议支持 Modbus RTU、ASCII、TCP、TCP/RTU over TCP、Modbus over UDP 最全面 异步支持 原生支持非阻塞 I/O(select/poll/epoll) 内置异步,性能优于同步阻塞库 维护状态 活跃(2025 年仍有更新),社区大 比 FreeModbus 更活跃 许可证 LGPL v2.1(可商用,闭源链接合法) 商用友好
与 PLC、变频器、功率分析仪通信
采集多通道电压/电流/温度(Modbus RTU over RS-485)
控制继电器、设置阈值
批量设备轮询(多从站)
2. 安装与编译
Linux / macOS
sudo apt update
sudo apt install libmodbus-dev
brew install libmodbus
git clone https://github.com/stephane/libmodbus.git
cd libmodbus
./autogen.sh
./configure
make -j$(nproc )
sudo make install
Windows (MSVC / MinGW) mkdir build && cd build
cmake .. -G "Visual Studio 17 2022" -A x64
cmake --build . --config Release
3. 核心 API 速查表(Modbus RTU 为主) 功能 函数原型 说明 创建上下文 modbus_new_rtu(device, baud, parity, ...)创建 RTU 上下文 设置从站 ID modbus_set_slave(ctx, slave_id)设置目标从站地址(1–247) 连接 modbus_connect(ctx)打开串口并建立连接 读寄存器 modbus_read_registers(...)读保持寄存器(功能码 03) 写寄存器 modbus_write_register(...)写单个寄存器(功能码 06) 读线圈 modbus_read_bits(...)读离散输入/输出(功能码 01/02) 异步 / 非阻塞 modbus_set_response_timeout(...)设置超时,结合 select/poll 使用 关闭与释放 modbus_close(ctx); modbus_free(ctx)必须成对调用,防止泄漏 错误处理 modbus_strerror(errno)获取详细错误字符串
4. 完整示例代码(Modbus RTU 主站)
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <modbus.h>
#define SLAVE_ID 1
#define SERIAL_PORT "/dev/ttyUSB0"
#define BAUDRATE 115200
#define TIMEOUT_SEC 1
#define TIMEOUT_USEC 0
int main (void ) {
modbus_t *ctx = NULL ;
uint16_t regs[10 ] = {0 };
int rc;
ctx = modbus_new_rtu(SERIAL_PORT, BAUDRATE, 'N' , 8 , 1 );
if (ctx == NULL ) {
fprintf (stderr , "无法创建 Modbus 上下文:%s\n" , modbus_strerror(errno));
return 1 ;
}
modbus_set_slave(ctx, SLAVE_ID);
modbus_set_response_timeout(ctx, TIMEOUT_SEC, TIMEOUT_USEC);
if (modbus_connect(ctx) == -1 ) {
fprintf (stderr , "连接失败:%s\n" , modbus_strerror(errno));
modbus_free(ctx);
return 1 ;
}
printf ("Modbus RTU 已连接:%s @ %d baud\n" , SERIAL_PORT, BAUDRATE);
while (1 ) {
rc = modbus_read_registers(ctx, 0 , 10 , regs);
if (rc == -1 ) {
fprintf (stderr , "读取失败:%s\n" , modbus_strerror(errno));
} else {
printf ("读取成功 (%d 个寄存器):\n" , rc);
for (int i = 0 ; i < rc; i++) {
printf ("寄存器 %02d: %5u (0x%04X)\n" , i, regs[i], regs[i]);
}
}
sleep(1 );
}
modbus_close(ctx);
modbus_free(ctx);
return 0 ;
}
gcc -o modbus_rtu_master modbus_rtu_master.c -lmodbus
sudo ./modbus_rtu_master
gcc -o modbus_rtu_master.exe modbus_rtu_master.c -lmodbus -lws2_32
5. 测试用例
5.1 单元测试(使用 Check 框架) #include <check.h>
#include <modbus.h>
START_TEST(test_modbus_new_rtu) {
modbus_t *ctx = modbus_new_rtu("/dev/ttyUSB0" , 115200 , 'N' , 8 , 1 );
ck_assert_ptr_ne(ctx, NULL );
modbus_free(ctx);
} END_TEST
START_TEST (test_modbus_connect_fail) {
modbus_t *ctx = modbus_new_rtu("/dev/invalid" , 9600 , 'N' , 8 , 1 );
ck_assert_int_eq(modbus_connect(ctx), -1 );
ck_assert_int_eq(errno, ENOENT);
modbus_free(ctx);
} END_TEST
Suite *modbus_suite (void ) {
Suite *s = suite_create("libmodbus" );
TCase *tc_core = tcase_create("Core" );
tcase_add_test(tc_core, test_modbus_new_rtu);
tcase_add_test(tc_core, test_modbus_connect_fail);
suite_add_tcase(s, tc_core);
return s;
}
int main (void ) {
Suite *s = modbus_suite();
SRunner *sr = srunner_create(s);
srunner_run_all(sr, CK_NORMAL);
int failures = srunner_ntests_failed(sr);
srunner_free(sr);
return (failures == 0 ) ? 0 : 1 ;
}
gcc -o test_modbus test_modbus.c -lmodbus -lcheck -lm -lpthread -lrt
./test_modbus
5.2 集成测试(Python 模拟从站) from pymodbus.server.sync import StartSerialServer
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.datastore import ModbusSequentialDataBlock
from pymodbus.datastore import ModbusSlaveContext, ModbusServerContext
store = ModbusSlaveContext(
di=ModbusSequentialDataBlock(0 , [17 ]*100 ),
co=ModbusSequentialDataBlock(0 , [17 ]*100 ),
hr=ModbusSequentialDataBlock(0 , [17 ]*100 ),
ir=ModbusSequentialDataBlock(0 , [17 ]*100 )
)
context = ModbusServerContext(slaves=store, single=True )
identity = ModbusDeviceIdentification()
identity.VendorName = 'Test'
identity.ProductCode = 'TEST'
identity.VendorUrl = 'http://github.com/pymodbus-dev/pymodbus/'
identity.ProductName = 'pymodbus Test Slave'
identity.ModelName = 'Test Sensor'
identity.MajorMinorRevision = '1.0'
StartSerialServer(
context=context,
identity=identity,
port='/dev/ttyUSB0' ,
baudrate=115200 ,
timeout=1
)
运行 Python 从站
运行 C 主站程序
观察主站是否能正确读取从站寄存器值(17)
6. 高级技巧与优化(半导体测试机常用)
CRC 校验 (libmodbus 已内置,无需手动实现)
非阻塞模式 (高级):使用 modbus_set_response_timeout() + select() / poll() 实现异步
int retries = 3 ;
while (retries--) {
if (modbus_read_registers(...) >= 0 ) break ;
usleep(100000 );
}
for (int slave = 1 ; slave <= 8 ; slave++) {
modbus_set_slave(ctx, slave);
modbus_read_registers(ctx, 0 , 10 , regs);
}
7. 常见问题与解决 问题 原因 解决方法 打开失败:Permission denied Linux 权限不足 sudo chmod 666 /dev/ttyUSB0 或加 dialout 组读写乱码 波特率/校验/停止位不匹配 确认设备手册参数,统一设置 连接超时 超时时间太短或设备未响应 增加 modbus_set_response_timeout() Windows COM 端口打不开 端口被占用(串口助手未关闭) 关闭其他串口工具,检查设备管理器 编译报错:undefined reference 没链接 -lmodbus gcc ... -lmodbus
总结 C++ 串口开发核心是配置 + 读写 + 错误处理。Qt QSerialPort 是跨平台首选,适合 GUI 应用;Boost.Asio 适合高并发后台;libmodbus 则是 Modbus 协议的最佳实践。
先用 Qt QSerialPort 快速验证硬件通信(开发快)
需要高性能/嵌入式/纯 C → 切换 libmodbus
超高并发后台 → 考虑 Boost.Asio + libmodbus 结合
微信扫一扫,关注极客日志 微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具 Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
Base64 文件转换器 将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
Markdown 转 HTML 将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML 转 Markdown 互为补充。 在线工具,Markdown 转 HTML在线工具,online
HTML 转 Markdown 将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML 转 Markdown在线工具,online
JSON 压缩 通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
JSON美化和格式化 将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online