Qt实现ModbusRTU从站源码分享
一、源码
1、效果展示
2、源码
.pro
QT += core gui serialbus
.hpp
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
#include <QModbusRtuSerialServer>
#include <QSerialPort>
#include <QTimer>
QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void writeSlot(QModbusDataUnit::RegisterType table, int address, int size);
private:
Ui::MainWindow *ui;
QModbusRtuSerialServer *modbusServer = nullptr;
};
#endif // MAINWINDOW_H
.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
this->modbusServer = new QModbusRtuSerialServer(this);
this->modbusServer->setServerAddress(1);
this->modbusServer->setConnectionParameter(QModbusDevice::SerialPortNameParameter,"COM1");
this->modbusServer->setConnectionParameter(QModbusDevice::SerialParityParameter,QSerialPort::Parity::NoParity);
this->modbusServer->setConnectionParameter(QModbusDevice::SerialBaudRateParameter,QSerialPort::Baud115200);
this->modbusServer->setConnectionParameter(QModbusDevice::SerialDataBitsParameter,QSerialPort::Data8);
this->modbusServer->setConnectionParameter(QModbusDevice::SerialStopBitsParameter,QSerialPort::StopBits::OneStop);
QModbusDataUnitMap reg;//设置可接收的寄存器数据类型
reg.insert(QModbusDataUnit::Coils, { QModbusDataUnit::Coils, 0, 10 });//
reg.insert(QModbusDataUnit::DiscreteInputs, { QModbusDataUnit::DiscreteInputs, 0, 10 });
reg.insert(QModbusDataUnit::InputRegisters, { QModbusDataUnit::InputRegisters, 0, 10 });
reg.insert(QModbusDataUnit::HoldingRegisters, { QModbusDataUnit::HoldingRegisters, 0, 10 });
this->modbusServer->setMap(reg);//
this->modbusServer->connectDevice();
connect(this->modbusServer,&QModbusRtuSerialServer::dataWritten,this,&MainWindow::writeSlot);
QTimer *t = new QTimer(this);
connect(t,&QTimer::timeout,this,
[=]()
{
quint16 value;
for(int i=0;i<10;i++)
{
this->modbusServer->data(QModbusDataUnit::HoldingRegisters, i, &value);
this->ui->tableWidget->setItem(i,0,new QTableWidgetItem(QString::number(value)));
}
for(int i=0;i<10;i++)
{
if(this->ui->tableWidget->item(i,1))
{
value = this->ui->tableWidget->item(i,1)->text().toUInt();
this->modbusServer->setData(QModbusDataUnit::HoldingRegisters, i, value);
}
}
});
t->start(100);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::writeSlot(QModbusDataUnit::RegisterType table, int address, int size)
{
switch(table)
{
case QModbusDataUnit::Coils:
{
for (int i = 0; i < size; i++)
{
quint16 value;
this->modbusServer->data(QModbusDataUnit::Coils, address + i, &value);
qDebug()<<address<<":"<<value;
}
}break;
case QModbusDataUnit::HoldingRegisters:
{
for (int i = 0; i < size; i++)
{
quint16 value;
this->modbusServer->data(QModbusDataUnit::HoldingRegisters, address + i, &value);
qDebug()<<address<<":"<<value;
}
}break;
default:break;
}
}
二、实现原理
主要通过QModbusRtuSerialServer
实现:
QModbusRtuSerialServer 类详解
QModbusRtuSerialServer 是 Qt Modbus 模块中用于实现 Modbus RTU 协议串行通信服务器的核心类。它继承自 QModbusServer
,专为串行通信(RS-232/RS-485)场景设计,支持 Modbus 从站(Slave)功能。以下是详细解析:
1. 核心功能
- 协议支持:实现 Modbus RTU 协议(基于串行通信)
- 角色定位:作为从站设备响应主站请求
- 数据管理:维护四类标准 Modbus 数据区:
- 线圈(Coils): 1-bit 读写 \text{1-bit 读写} 1-bit 读写
- 离散输入(Discrete Inputs): 1-bit 只读 \text{1-bit 只读} 1-bit 只读
- 输入寄存器(Input Registers): 16-bit 只读 \text{16-bit 只读} 16-bit 只读
- 保持寄存器(Holding Registers): 16-bit 读写 \text{16-bit 读写} 16-bit 读写
2. 关键方法
// 设置串口参数
bool setConnectionParameter(int key, const QVariant &value);
// 启动/停止服务器
bool listen();
void close();
// 数据区操作
bool setData(QModbusDataUnit::RegisterType type, quint16 address, quint16 data);
QVector<quint16> data(QModbusDataUnit::RegisterType type) const;
// 错误处理
QModbusDevice::Error error() const;
3. 配置参数
通过 setConnectionParameter()
配置串行端口:
QModbusRtuSerialServer server;
// 设置串口名称 (Linux: /dev/ttyS0, Windows: COM1)
server.setConnectionParameter(QModbusDevice::SerialPortNameParameter, "COM1");
// 设置波特率 (常用值: 9600, 19200, 38400)
server.setConnectionParameter(QModbusDevice::SerialBaudRateParameter, QSerialPort::Baud9600);
// 设置数据位 (5-8 bits)
server.setConnectionParameter(QModbusDevice::SerialDataBitsParameter, QSerialPort::Data8);
// 设置校验位 (None/Even/Odd)
server.setConnectionParameter(QModbusDevice::SerialParityParameter, QSerialPort::NoParity);
// 设置停止位 (1/1.5/2)
server.setConnectionParameter(QModbusDevice::SerialStopBitsParameter, QSerialPort::OneStop);
4. 数据区初始化
初始化数据存储区示例:
// 初始化保持寄存器 (地址 0-9)
QVector<quint16> holdingRegisters(10, 0x0000);
server.setMap(QModbusDataUnit::HoldingRegisters, holdingRegisters);
// 设置单个寄存器值 (地址 5 = 0x1234)
server.setData(QModbusDataUnit::HoldingRegisters, 5, 0x1234);
5. 信号与槽机制
重要信号:
// 数据写入事件
void dataWritten(QModbusDataUnit::RegisterType type, int address, int size);
// 错误事件
void errorOccurred(QModbusDevice::Error error);
// 状态变化
void stateChanged(QModbusDevice::State state);
使用示例:
QObject::connect(&server, &QModbusRtuSerialServer::dataWritten,
[](QModbusDataUnit::RegisterType type, int addr, int size) {
qDebug() << "Data written at:" << addr << "Size:" << size;
}
);
6. 使用流程
graph TB
A[创建 QModbusRtuSerialServer] --> B[配置串口参数]
B --> C[初始化数据区]
C --> D[注册信号槽]
D --> E[调用 connectDevice 启动]
E --> F[处理主站请求]
7. 注意事项
- 线程安全:需在主线程或专用线程中操作
- 超时设置:RTU 帧间隔超时默认 1.75 字符时间,可通过
setInterFrameDelay()
调整 - 错误处理:检查
errorString()
获取详细错误信息 - 资源释放:关闭端口时调用
disconnectDevice()
三、ModbusRTU协议介绍
ModbusRTU是一种广泛应用于工业自动化领域的串行通信协议,属于Modbus协议家族的一种模式。它基于主从架构(Master-Slave),通过RS-232或RS-485物理接口实现设备间的可靠数据交换。ModbusRTU以其高效性、简单性和低成本著称,特别适合实时控制系统,如PLC(可编程逻辑控制器)、传感器和仪表等。下面我将逐步介绍其核心概念、工作原理、数据格式和关键特点。
1. 基本概念与概述
- ModbusRTU是Modbus协议的一种实现方式,使用二进制RTU(Remote Terminal Unit)格式进行数据传输。
- 它工作在OSI模型的物理层和数据链路层,支持点对点或多点通信(最多247个从站设备)。
- 协议的核心目的是实现主站(如工业计算机)与从站(如传感器或执行器)之间的命令和响应交互,例如读取寄存器数据或写入控制指令。
2. 工作原理
- 主从架构:主站主动发起请求帧(Request Frame),从站被动响应(Response Frame)。通信过程是半双工的,即同一时间只能有一个设备发送数据。
- 寻址机制:每个从站设备有唯一的地址(范围从1到247),主站通过地址字段指定目标设备。
- 通信流程:
- 主站发送请求帧,包含从站地址、功能码(Function Code)和数据。
- 从站接收并解析帧,如果地址匹配,则执行操作(如读取寄存器)。
- 从站返回响应帧,包含执行结果或错误信息。
- 主站校验响应,完成一次事务。
- 超时机制:如果从站未在指定时间内响应,主站会重试或报错,确保可靠性。
3. 数据帧格式
ModbusRTU帧采用紧凑的二进制结构,无显式起始符或结束符。一个完整的帧包括以下字段:
- 地址字段(1字节):指定目标从站地址( 0 x 01 0x01 0x01 到 0 x F 7 0xF7 0xF7)。
- 功能码字段(1字节):定义操作类型,如读取线圈(功能码 0 x 01 0x01 0x01)或写入保持寄存器(功能码 0 x 10 0x10 0x10)。
- 数据字段(可变长度):包含具体参数,例如寄存器地址或数据值(长度取决于功能码)。
- CRC校验字段(2字节):用于错误检测,基于CRC-16算法计算。
帧结构示例(以字节表示):
| 地址 | 功能码 | 数据 | CRC低字节 | CRC高字节 |
帧长度通常在3到256字节之间,具体取决于数据内容。帧间需保持至少3.5个字符时间的静默间隔(Silent Interval)以区分帧边界。
4. 校验机制
- ModbusRTU使用CRC-16(Cyclic Redundancy Check)校验确保数据完整性,多项式为 x 16 + x 15 + x 2 + 1 x^{16} + x^{15} + x^2 + 1 x16+x15+x2+1(对应标准CRC-16-IBM)。
- 计算过程:
- 初始化CRC寄存器为 0 x F F F F 0xFFFF 0xFFFF。
- 对帧中每个字节(地址、功能码、数据)进行迭代计算。
- 最终CRC值附加到帧尾。
- 接收端重新计算CRC,如果与接收到的CRC不匹配,则丢弃帧并请求重发。这大大降低了传输错误率。
5. 关键特点与优势
- 高效性:二进制格式传输,数据密度高,延迟低(典型波特率9600 bps下,响应时间<100ms)。
- 可靠性:CRC校验和超时机制保障数据准确。
- 兼容性:广泛支持各种工业设备,易于集成到现有系统。
- 局限性:无内置加密,安全性较低;仅适用于短距离通信(RS-485最大距离约1200米)。
- 应用场景:常见于工厂自动化、楼宇控制、能源监控等领域,例如读取温度传感器的寄存器值或控制电机启停。