当前位置: 首页 > news >正文

qt5-serialbus详细解析

qt5-serialbus是 Qt5 用于访问各类工业现场总线和协议的附加模块,自 Qt 5.8 起正式引入,内部包含Modbus(TCP/RTU)CAN Bus两大子系统,覆盖了 PLC 通信、仪表采集、汽车电子总线等工业场景。它不在qt5-base中,需通过 vcpkg 单独安装。

模块整体定位与两大子系统

维度Qt Modbus 子系统Qt CAN Bus 子系统
协议Modbus TCP / Modbus RTUCAN 2.0A/B、CAN FD
角色主站(Client)/ 从站(Server)总线节点(收发帧)
传输介质以太网(502端口)/ RS-232/485 串口各类 CAN 适配器(USB-PCIe)
核心基类QModbusDeviceQCanBusDevice
工厂类直接 new 具体子类QCanBus::instance()->createDevice()
数据单元QModbusDataUnit(4 类寄存器)QCanBusFrame(ID + payload)
异步句柄QModbusReplyframesReceived信号 +readFrame()
典型场景上位机与 PLC、仪表通信车辆 ECU、工业控制器总线

类层级结构

下面用一张类图展示整个模块的继承关系,这是理解 API 设计的关键:

CAN子系统

Modbus子系统

QObject

QModbusDevice
连接/错误/状态/参数

QModbusClient
sendRead/Write/RawRequest

QModbusServer
setMap/setData/dataWritten

QModbusTcpClient

QModbusRtuSerialMaster

QModbusTcpServer

QModbusRtuSerialSlave

QModbusReply
异步响应句柄

QModbusDataUnit
寄存器类型+地址+值

QCanBus
工厂+插件枚举

QCanBusDevice
connectDevice/writeFrame

QCanBusFrame
frameId+payload

QCanBusDeviceInfo

Modbus 侧QModbusDevice是抽象基类,提供连接状态(state())、错误处理(error()/errorString())、连接参数(setConnectionParameter())等通用能力;QModbusClientQModbusServer分别派生为主站和从站抽象,再按 TCP/RTU 细化为 4 个具体类。
CAN 侧QCanBus是单例工厂,通过插件机制加载不同厂商后端;QCanBusDevice封装具体设备的连接、配置、收发;QCanBusFrame表示一个 CAN 帧。

四种 Modbus 寄存器类型

Modbus 协议规定每个从站设备维护 4 张数据表,QModbusDataUnit::RegisterType枚举与之对应:

寄存器类型枚举值数据宽度读写权限功能码典型用途
离散输入 Discrete Inputs11 bit只读02按钮、开关状态
线圈 Coils01 bit读写01/05/15继电器输出、指示灯
输入寄存器 Input Registers316 bit只读04ADC 采样值、传感器读数
保持寄存器 Holding Registers216 bit读写03/06/16设定参数、计数器
读写权限决定了主站能用哪些功能码访问:只读表只能用读功能码,读写表既能读也能写。

主站核心 API(QModbusClient)

QModbusClient提供 4 个异步请求方法,返回QModbusReply*句柄:

方法功能对应功能码
sendReadRequest(unit, serverAddress)读寄存器01/02/03/04
sendWriteRequest(unit, serverAddress)写寄存器05/06/15/16
sendReadWriteRequest(read, write, addr)原子读写23
sendRawRequest(request, addr)发送原始 PDU任意
配置方法:setNumberOfRetries(n)(默认 3 次重试)、setTimeout(ms)(默认 1000ms)。

从站核心 API(QModbusServer)

QModbusServer维护内存中的寄存器映射表,核心方法:

  • setMap(QModbusDataUnitMap)—— 初始化 4 张表的大小与起始地址
  • setData(table, address, value)—— 写入单个寄存器(程序内部更新)
  • data(table, address, &data)—— 读取单个寄存器
  • setServerAddress(int)—— 设置从站地址(1~247)
  • 信号dataWritten(table, address, size)—— 主站写入时触发,用于业务联动

从站 setMap 初始化寄存器映射示例

从站必须先调用setMap()划分 4 张表的地址空间,主站才能读写:

// 创建 TCP 从站QModbusTcpServer*server=newQModbusTcpServer(this);server->setConnectionParameter(QModbusDevice::NetworkAddressParameter,"0.0.0.0");server->setConnectionParameter(QModbusDevice::NetworkPortParameter,502);server->setServerAddress(1);// 初始化 4 张数据表:各 100 个条目,起始地址 0QModbusDataUnitMap reg;reg.insert(QModbusDataUnit::DiscreteInputs,{QModbusDataUnit::DiscreteInputs,0,100});reg.insert(QModbusDataUnit::Coils,{QModbusDataUnit::Coils,0,100});reg.insert(QModbusDataUnit::InputRegisters,{QModbusDataUnit::InputRegisters,0,100});reg.insert(QModbusDataUnit::HoldingRegisters,{QModbusDataUnit::HoldingRegisters,0,100});server->setMap(reg);// 设置初始值server->setData(QModbusDataUnit::HoldingRegisters,0,1234);// 监听主站写入connect(server,&QModbusServer::dataWritten,[](QModbusDataUnit::RegisterType table,intaddress,intsize){qDebug()<<"主站写入:"<<table<<"addr="<<address<<"size="<<size;});server->connectDevice();

RTU 主站完整示例(读保持寄存器)

这是工业上位机最典型的用法:配置串口 → 连接 → 异步读 → 在finished信号中取结果。

#include<QModbusRtuSerialMaster>#include<QModbusDataUnit>#include<QModbusReply>#include<QSerialPort>#include<QDebug>// 1. 创建 RTU 主站QModbusRtuSerialMaster*client=newQModbusRtuSerialMaster(this);// 2. 配置串口参数client->setConnectionParameter(QModbusDevice::SerialPortNameParameter,"COM3");client->setConnectionParameter(QModbusDevice::SerialBaudRateParameter,QSerialPort::Baud9600);client->setConnectionParameter(QModbusDevice::SerialDataBitsParameter,QSerialPort::Data8);client->setConnectionParameter(QModbusDevice::SerialParityParameter,QSerialPort::NoParity);client->setConnectionParameter(QModbusDevice::SerialStopBitsParameter,QSerialPort::OneStop);client->setTimeout(1000);client->setNumberOfRetries(3);// 3. 连接if(!client->connectDevice()){qDebug()<<"连接失败:"<<client->errorString();return;}// 4. 读保持寄存器:从站地址=1,起始地址=0,数量=10QModbusDataUnitreadUnit(QModbusDataUnit::HoldingRegisters,0,10);if(auto*reply=client->sendReadRequest(readUnit,1)){if(!reply->isFinished()){connect(reply,&QModbusReply::finished,[reply](){if(reply->error()==QModbusDevice::NoError){constQModbusDataUnit unit=reply->result();for(inti=0;i<unit.valueCount();++i)qDebug()<<"Reg["<<unit.startAddress()+i<<"] ="<<unit.values().at(i);}else{qDebug()<<"读错误:"<<reply->errorString();}reply->deleteLater();});}else{deletereply;// 同步完成(广播请求)}}

TCP 主站只需替换两行

把 RTU 示例中的创建与配置换成 TCP 版本,其余sendReadRequest/finished逻辑完全一致:

QModbusTcpClient*client=newQModbusTcpClient(this);client->setConnectionParameter(QModbusDevice::NetworkAddressParameter,"192.168.1.100");client->setConnectionParameter(QModbusDevice::NetworkPortParameter,502);client->connectDevice();

写寄存器示例

// 写单个保持寄存器:地址 5 写入 0x1234QModbusDataUnitwriteUnit(QModbusDataUnit::HoldingRegisters,5,QVector<quint16>{0x1234});if(auto*reply=client->sendWriteRequest(writeUnit,1)){if(!reply->isFinished()){connect(reply,&QModbusReply::finished,[reply](){if(reply->error()==QModbusDevice::NoError)qDebug()<<"写入成功";elseqDebug()<<"写入失败:"<<reply->errorString();reply->deleteLater();});}else{deletereply;}}

CAN Bus 子系统快速上手

CAN 侧采用插件机制,通过QCanBus工厂创建设备:

#include<QCanBus>#include<QCanBusFrame>#include<QDebug>// 1. 枚举可用插件qDebug()<<"可用插件:"<<QCanBus::instance()->plugins();// 2. 创建设备(Linux 用 socketcan,Windows 用 peakcan)QString err;QCanBusDevice*device=QCanBus::instance()->createDevice("socketcan","can0",&err);if(!device){qWarning()<<"创建失败:"<<err;return;}// 3. 配置波特率device->setConfigurationParameter(QCanBusDevice::BitRateKey,250000);// 4. 连接接收信号connect(device,&QCanBusDevice::framesReceived,[device](){while(device->framesAvailable()){QCanBusFrame frame=device->readFrame();qDebug()<<"ID:"<<QString::number(frame.frameId(),16)<<"Data:"<<frame.payload().toHex(' ');}});// 5. 连接并发送device->connectDevice();QCanBusFrame txFrame;txFrame.setFrameId(0x123);txFrame.setPayload(QByteArray::fromHex("0102030405"));device->writeFrame(txFrame);

支持的 CAN 插件列表

Qt SerialBus 通过插件支持主流 CAN 硬件厂商:

插件 key厂商平台
socketcanLinux 内核 SocketCANLinux
passthrucanSAE J2534 Pass-ThruWindows
peakcanPEAK-System (PCAN)全平台
vectorcanVector InformatikWindows
systeccanSYS TEC electronic全平台
tinycanMHS Elektronik全平台
virtualcan虚拟 CAN(TCP/IP)全平台(测试用)

完整工程封装建议

一个健壮的 Modbus 通信层应分层封装:

QModbusReply::finished

QModbusServer::dataWritten

配置层 ModbusConfig
串口/IP/波特率/超时

通信层 ModbusManager
connectDevice/sendReadRequest

协议层 ModbusProtocol
帧解析/重试/队列

应用层 BusinessLogic
setData/读结果更新UI

  • 配置层:把所有连接参数封装为结构体,支持从配置文件或 UI 读取
  • 通信层:封装QModbusClient生命周期,对外暴露readRegisters()/writeRegisters()异步接口
  • 协议层:处理重试、超时、请求队列、字节序转换
  • 应用层:响应finisheddataWritten信号,更新 UI 或执行业务

常见问题与排查

① RTU 通信超时(TimeoutError)
默认超时 1000ms,部分仪表响应慢需调大;RTU 还有setTurnaroundDelay()(周转延迟)参数,控制主站发完请求到从站开始响应的最短间隔,设备响应慢时需调大。
② 串口参数不匹配导致乱码
波特率、校验位、数据位、停止位必须与从站完全一致。工业仪表常见 9600/8/N/1 或 19200/8/E/1,需查设备手册。参数不一致时主站会收到ResourceError或乱码。
③ TCP 连接被拒
检查目标 IP 是否可达、502 端口是否开放、防火墙是否放行;部分 PLC 需在工程中显式启用 Modbus TCP Server 功能。
④ 32 位浮点数字节序问题
Modbus 寄存器是 16 位,存储 32 位 float/long 时存在大端/小端和字交换 4 种组合。Qt 不自动转换,需用QByteArray+QDataStream手动处理,或按设备手册的字节序拼接两个quint16
⑤ QModbusReply 内存泄漏
sendReadRequest返回的reply必须在finished槽中调用deleteLater(),否则内存泄漏;若是广播请求(serverAddress=0),isFinished()立即为 true,需手动delete
⑥ RTU 帧间隔不足
Modbus RTU 协议要求帧间至少 3.5 个字符时间的空闲。高波特率下若主站连续发送过快,从站可能无法识别帧边界。可通过setInterFrameDelay()调整。
⑦ 开启 Modbus 调试日志
main()中添加以下代码,可看到完整的请求/响应报文,排查问题极为方便:

QLoggingCategory::setFilterRules(QStringLiteral("qt.modbus* = true"));

若要查看更底层的字节内容,使用qt.modbus.lowlevel = true

选型建议

  • 与 PLC/仪表通信:选 Modbus 子系统,以太网环境用QModbusTcpClient,串口环境用QModbusRtuSerialMaster
  • 模拟从站供主站调试:用QModbusTcpServerQModbusRtuSerialSlave,配合setMap()建数据表
  • 汽车电子/ECU 通信:选 CAN 子系统,Linux 用socketcan,Windows 用peakcanvectorcan
  • 无硬件开发调试:CAN 用virtualcan插件;Modbus 用虚拟串口 + Modbus Poll/Slave 工具
  • 非 Qt 项目或需更轻量:可考虑libmodbus(纯 C 库)作为替代方案
http://www.jsqmd.com/news/1164973/

相关文章:

  • 4-20mA 电流环 2线制 vs 3线制:5个关键性能指标对比与选型指南
  • 终极网站永久保存方案:WebSite-Downloader完整离线下载指南
  • 同样看两小时屏幕,为什么有的孩子眼睛没事?
  • 阿里云通义万相AI视频生成实战:从提示词到完整短片制作
  • 平板可以当电脑用吗 平板怎么远程控制电脑
  • 如何获取硅基流动API调用大模型
  • 【MySQL】常用命令汇总
  • AI安全挑战深度解析:从深度伪造到自主武器的防护策略
  • TensorFlow 1.x MultiRNNCell 实战:构建3层RNN,解决维度不匹配ValueError
  • 通知栏适配:各平台通知样式与渠道管理(134)
  • 华为 eNSP 模拟器实战:智能化小区三层网络架构配置与 8 大协议测试
  • 【UniApp小程序知识点总结】分包与预下载
  • 流式输出为什么会中断?聊天、写作、问答产品常见的体验问题怎么解决
  • HarmonyOS 应用开发《掌上英语》第2篇:ArkTS 模块化开发:Index.ets 统一导出模式深度解析
  • 晶体管放大电路静态工作点:温度漂移 5°C 对增益影响的仿真分析与 3 种补偿方案
  • NAATI翻译户口本怎么收费的?2026价格、流程及避坑指南全说清
  • 中秋企业福利怎么选才不踩坑?实用指南看这里
  • 工业负载控制方案:TPD2017FN与STM32的完美结合
  • 开源大模型推理框架Llama.cpp实战指南
  • 【二叉树的广度优先遍历BFS---队列实现】
  • PIC18F86J11与PAM8904实现低功耗智能警报系统
  • 卡地亚中国官方售后服务中心|服务电话及详细网点地址权威信息公告(2026年7月最新) - 卡地亚官方售后中心
  • Claude Code不是IDE:它是可编程AI工作流系统
  • Python 3.7/3.9 多版本共存:3种 pip 精准安装方案对比与实战
  • Coding Plan不是任务清单,而是风险前置的决策日志
  • GPT-4o语音安全断层:鲁棒性塌方、意图失控与多模态错位
  • JDK 25 LTS工程化实践:路径、环境变量与四层验证
  • GitHub精准下载革命:如何用DownGit解决代码仓库的“过度下载“难题
  • 风箱说紫微能看哪些内容?事业、财运、感情、性格和阶段变化
  • 万国中国官方售后服务中心|详细维修地址与官方电话权威信息声明(2026年7月最新) - 万国中国官方服务中心