VS2022+QT6.7 Modbus RTU上位机主站与从站通信实现、协议详解、源码工具等

目录

一、Modbus RTU协议介绍

二、Modbus RTU上位机代码实现

三、实现效果

 四、工具与源码下载


一、Modbus RTU协议介绍

Modbus RTU(Remote Terminal Unit)是一种广泛使用的串行通信协议,主要用于工业自动化领域。它基于Modbus协议,但使用串行接口(如RS-232或RS-485)进行通信。Modbus RTU协议定义了数据帧的格式和通信规则,确保主设备(Master)和从设备(Slave)之间的可靠通信。

Modbus RTU协议支持几种不同类型的寄存器,这些寄存器用于存储不同类型的数据。以下是Modbus RTU中最常见的四种寄存器类型及其用途:

1. 线圈(Coils): 地址范围:0x0000 - 0xFFFF    数据类型:1位(布尔值)

功能码:

0x01:读线圈状态(Read Coils)

0x05:写单个线圈(Write Single Coil)

0x0F:写多个线圈(Write Multiple Coils)

用途:用于控制数字输出,例如继电器、开关等。

2.离散输入(Discrete Inputs):地址范围:0x0000 - 0xFFFF  数据类型:1位(布尔值)

功能码:0x02:

读离散输入状态(Read Discrete Inputs)

用途:用于读取数字输入,例如传感器状态、按钮等

3.保持寄存器(Holding Registers):地址范围:0x0000 - 0xFFFF 数据类型:16位(无符号整数)

功能码:

0x03:读保持寄存器(Read Holding Registers)

0x06:写单个寄存器(Write Single Register)

0x10:写多个寄存器(Write Multiple Registers)

用途:用于读写可编程控制器中的数据,例如温度、压力、计数值等

4.输入寄存器(Input Registers):地址范围:0x0000 - 0xFFFF   数据类型:16位(无符号整数)

功能码:

0x04:读输入寄存器(Read Input Registers)

用途:用于读取模拟输入数据,例如传感器读数等

5.举例说明

发送:01 03 00 00 00 02 C4 0B 

01:表示从机的485地址

03:功能码0x03表示读保持寄存器(Read Holding Registers)

00 00 00 02:起始地址高字节 起始地址低字节 寄存器数量高字节 寄存器数量低字节

C4 0B:CRC校验码的低字节   CRC校验码的高字节

|地址域|功能码|起始地址高字节|起始地址低字节|寄存器数量高字节|寄存器数量低字节|CRC低字节|CRC高字节|
|------|------|-------------|-------------|---------------|---------------|--------|----------|
| 0x01 | 0x03 | 0x00        | 0x00        | 0x00          | 0x02          | 0xC4   | 0x0B     |

返回:01 03 04 40 26 14 7B C6 3F

01:表示从机的 485 地址

03:功能码0x03表示读保持寄存器(Read Holding Registers)

04:表示返回的寄存器数据有 4 个字节

40 26:表示返回的第一个寄存器数据

14 7B:表示返回的第二个寄存器数据

|地址域 |功能码|   字节总数   |  寄存器数据1 |  寄存器数据2   |CRC低字节|CRC高字节|
|------|------|-------------|-------------|---------------|--------|--------|
| 0x01 | 0x03 | 0x04        | 0x40   0x26 | 0x14    0x7B  |  0xC6  | 0x3F   |

二、Modbus RTU上位机代码实现

环境与工具:VS2022、QT6.7.2、Modbus Slave(从机)、Virtual Serial port Driber Pro(虚拟串口)

实现功能如下:

1、编写主站程序,向从站读取和写入保持寄存器,写入的数据为16进制

2、自动获取可用串口功能,消息窗口自动滚屏效果,波特率选项设置

UI界面如下:

具体代码步骤实现如下:

mainwindow.cpp 源文件

#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "qstring.h"

MainWindow::MainWindow(QWidget* parent)
	: QMainWindow(parent)
	, ui(new Ui::MainWindow)
{
	ui->setupUi(this);
	//1、自动滚屏
	QTextCursor cursor = ui->textEdit_5->textCursor();
	cursor.movePosition(QTextCursor::End);
	ui->textEdit_5->setTextCursor(cursor);

	//2、添加串口选项
	QStringList comport = get_avail_sp_();
	ui->comboBox_serialport->addItems(comport);

	//3、添加波特率选项
	ui->baudRateComboBox->addItem("9600", QVariant::fromValue(QSerialPort::Baud9600));
	ui->baudRateComboBox->addItem("19200", QVariant::fromValue(QSerialPort::Baud19200));
	ui->baudRateComboBox->addItem("38400", QVariant::fromValue(QSerialPort::Baud38400));
	ui->baudRateComboBox->addItem("57600", QVariant::fromValue(QSerialPort::Baud57600));
	ui->baudRateComboBox->addItem("115200", QVariant::fromValue(QSerialPort::Baud115200));

	//4、连接打开按钮
	connect(ui->on_btn_open, &QPushButton::clicked, this, [=]() {
		//获取选中的波特率
		int baudRate = ui->baudRateComboBox->currentData().toInt();
		//获取串口号
		QString port = ui->comboBox_serialport->currentText();
		//定义串口对象
		modbusMaster = new QModbusRtuSerialMaster(this);
		modbusMaster->setConnectionParameter(QModbusDevice::SerialPortNameParameter, port);                 //串口号    
		modbusMaster->setConnectionParameter(QModbusDevice::SerialBaudRateParameter, baudRate);             //波特率
		modbusMaster->setConnectionParameter(QModbusDevice::SerialParityParameter, QSerialPort::NoParity);  //无校验          
		modbusMaster->setConnectionParameter(QModbusDevice::SerialDataBitsParameter, QSerialPort::Data8);   //数据位         
		modbusMaster->setConnectionParameter(QModbusDevice::SerialStopBitsParameter, QSerialPort::OneStop); //停止位

		//打开串口按钮
		if (!modbusMaster->connectDevice())
		{
			ui->statusbar->showMessage("连接失败", 2000);
		}
		else
		{
			ui->statusbar->showMessage("连接成功", 2000); value = true;
		}
		});

	//5、连接关闭按钮
	connect(ui->on_btn_close, &QPushButton::clicked, this, [=]() {
		if (value)
		{
			modbusMaster->disconnectDevice();
			delete modbusMaster;
			ui->statusbar->showMessage("串口关闭", 1000);
			value = false;
		}
		});

	//6、读寄存器按钮
	connect(ui->read_Registers, &QPushButton::clicked, this, [=]() {
		//检测串口是否打开
		if (TestPort() != true) return;
		//获取从机地址
		QString url = ui->url->toPlainText();
		//获取线圈起始地址
		QString coiladdress_start = ui->coil_start->toPlainText();
		QString coiladdress_end = ui->coil_finish->toPlainText();
		//定义读写单元
		QModbusDataUnit readUnit(QModbusDataUnit::HoldingRegisters, coiladdress_start.toInt(), coiladdress_end.toInt());    //0是所要读取的寄存器起始地址,10是所要读取的寄存器个数
		//发送请求
		auto* reply = modbusMaster->sendReadRequest(readUnit, url.toInt());
		if (reply)
		{
			connect(reply, &QModbusReply::finished, this, &MainWindow::readRegisters);
		}
		else
		{
			ui->statusbar->showMessage("无效请求", 1000);
		}
		});

	//7.写入寄存器按钮,获取水位值
	connect(ui->write_Registers, &QPushButton::clicked, this, [=]() {
		//检测串口是否打开
		if (TestPort()!=true) return;
		//获取从机地址
		QString url = ui->url->toPlainText();
		//分割字符串
		QString str = ui->textEdit_write->toPlainText();
		//按空格分割文本
		QStringList parts = str.split(' ', Qt::SkipEmptyParts);
		//定义读写单元
		QModbusDataUnit writeUnit(QModbusDataUnit::HoldingRegisters, 0, parts.size());

		for (int i = 0; i < parts.size(); i++)
		{
			bool ok;
			quint16 value = parts[i].toUInt(&ok, 16); //16进制转换为十进制
			if (ok)
			{
				ui->statusbar->showMessage("写入成功 获取水位值", 1000);
			}
			else
			{
				ui->statusbar->showMessage("写入失败", 1000);
				return;
			}
			writeUnit.setValue(i, value);
		}
		ui->textEdit_5->append(str); //向消息窗口行尾添加数据
		//发送写入请求
		modbusMaster->sendWriteRequest(writeUnit, 1);
		});
}

//6.1读保持寄存器函数
void MainWindow::readRegisters()
{
	//接收信号
	auto reply = qobject_cast<QModbusReply*>(sender());
	if (!reply)
	{
		ui->statusbar->showMessage("无效回复", 1000);
		return;
	}
	//处理响应
	const QModbusDataUnit unit = reply->result();
	// 将数据值转换为字符串
	QString dataStr;
	for (int i = 0; i < unit.valueCount(); ++i) {
		dataStr += QString::number(unit.value(i)) + " ";
	}
	if(dataStr.size()!=0)
	{
		ui->textEdit_5->append(dataStr);
		ui->statusbar->showMessage("读取成功", 1000);
	}
	else
	{
		ui->textEdit_5->append("无数据");
	}

}

//检测串口是否打开
bool MainWindow::TestPort()
{
	if (value)
	{
		ui->statusbar->showMessage("正常连接", 1000);
		return true;
	}
	else
	{
		ui->statusbar->showMessage("串口已关闭", 1000);
		value = false;
		return false;
	}
}

MainWindow::~MainWindow()
{
	delete ui;
}

manwindow.h 头文件

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QSerialPort>
#include <QSerialPortInfo>
#include <QtSerialBus>
#include <QModbusDataUnit>
#include <QModbusClient>
#include <QModbusRtuSerialMaster>
#include <QModbusReply>
#include <QStringList>
#include <QSettings>
#include <QDebug>
#include <QTextStream>
#include <QString>

QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT
private:
    QModbusRtuSerialMaster* modbusMaster; //创建modbus对象

public slots:
    void readRegisters();  //读寄存器函数
  
public:
    bool value=false;      //标志位
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

public:
    //检测串口是否打开
    bool TestPort();

    //获取可用串口
    QStringList get_avail_sp_() noexcept {
        QStringList list_avail_sp;
        foreach(const QSerialPortInfo & info, QSerialPortInfo::availablePorts()) {
            QSerialPort serial;
            serial.setPort(info);
            try {
                if (serial.open(QIODevice::ReadWrite)) {
                    list_avail_sp.append(serial.portName());
                    serial.close();
                }
            }
            catch (const std::exception& e) {
                qWarning() << "Error opening serial port" << info.portName() << ":" << e.what();
            }
        }
        return list_avail_sp;
    }

private:
    Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H

三、实现效果

1.运行Virtual Serial Port Driver Pro 创建虚拟串口 COM1-COM2,创建完成后设备管理器中可以观察到,如图1所示

(图1)

 2.运行Modbus Slave从机模拟软件,如图2、图3所示

(图2) 设置从机地址、寄存器模式、线圈地址开始-结束

(图3) 选择COM2->COM1 波特率等如图所示 其他默认

3.从机连接COM2->COM1,上位机连接COM1->COM2,如图4所示

(图4 )上位机做主站

 4.从机模拟软件设置线圈地址为0-10,上位机点击“读保持寄存器线圈”按钮,效果如图5所示;

(图5)

5.写指令文本框中输入要写入的指令,用空格分开,以16进制传输,从机接收数据后转为10进制,效果如图6、图7所示;

图6

 (图7)

 四、工具与源码下载

通过百度网盘分享的文件:Modbus_RTU_QT通信源码与工具.zip
链接:https://pan.baidu.com/s/1AdwZrDn5-AoZo3q5NAoIpA
提取码:ym5a

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值