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[配置串口参数]![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/b16c5e7000a94397b5763f41cbbcfa9a.png#pic_center)

B --> C[初始化数据区]
C --> D[注册信号槽]
D --> E[调用 connectDevice 启动]
E --> F[处理主站请求]

7. 注意事项

  1. 线程安全:需在主线程或专用线程中操作
  2. 超时设置:RTU 帧间隔超时默认 1.75 字符时间,可通过 setInterFrameDelay() 调整
  3. 错误处理:检查 errorString() 获取详细错误信息
  4. 资源释放:关闭端口时调用 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),主站通过地址字段指定目标设备。
  • 通信流程
    1. 主站发送请求帧,包含从站地址、功能码(Function Code)和数据。
    2. 从站接收并解析帧,如果地址匹配,则执行操作(如读取寄存器)。
    3. 从站返回响应帧,包含执行结果或错误信息。
    4. 主站校验响应,完成一次事务。
  • 超时机制:如果从站未在指定时间内响应,主站会重试或报错,确保可靠性。

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米)。
  • 应用场景:常见于工厂自动化、楼宇控制、能源监控等领域,例如读取温度传感器的寄存器值或控制电机启停。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小灰灰搞电子

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值