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

ModbusTcp协议

一、基本概念

Modbus TCP 是一种基于 Modbus 协议的以太网通信协议。它是 Modbus 协议在 TCP/IP 网络上的实现,保留了 Modbus 协议的核心功能和数据模型,同时利用了 TCP/IP 协议的传输能力,使得设备之间可以通过以太网进行通信。

二、通信架构

采用客户端 - 服务器(Client - Server)模式。服务器端通常是数据采集设备或从设备(Slave),它在 IANA(Internet Assigned Numbers Authority,互联网号码分配当局)分配的 TCP 端口 502 上进行监听,等待客户端的连接请求。客户端一般是主控设备(Master),主动向服务器端发起连接并发送请求,服务器端接收到请求后进行处理并返回响应结果

三、数据存储区

规定了 4 个主要的存储区,这些存储区用于存储不同类型的数据:

  • 离散输入(Discrete Inputs)存储区(1 区) :用于存储从设备的数字输入点状态,只读。例如,一个传感器的状态信号可以通过离散输入存储区进行采集,客户端只能读取这些状态信息,不能对它们进行修改。

  • 线圈(Coils)存储区(0 区) :可读可写的布尔量存储区。它通常用于控制输出设备,比如继电器线圈。客户端可以向线圈存储区写入数据(数据值为 0 或 1),从而控制对应的输出设备的通断;也可以读取线圈的当前状态。

  • 输入寄存器(Input Registers)存储区(3 区) :用于存储模拟量输入或其他只读数据,只读。例如,一个温度传感器采集的温度值可能存储在输入寄存器中,客户端可以读取这些数据以了解当前温度状态,但不能修改它。

  • 保持寄存器(Holding Registers)存储区(4 区) :可读可写的寄存器存储区。它可用于存储参数设置、控制命令等需要读写操作的数据。客户端可以根据需要对保持寄存器中的数据进行读取和修改。

 四、报文结构

Modbus TCP 报文基于 TCP/IP 协议,其结构与 Modbus RTU 报文有所不同。一个完整的 Modbus TCP 报文包括以下部分: 

  1. 事务标识符(Transaction Identifier) :由两个字节组成,用于标识一个请求 - 响应事务。客户端向服务器发送请求时,会给这个标识符赋一个唯一的值;服务器在响应时,将使用相同的值,以便客户端可以关联请求和响应。

  2. 协议标识符(Protocol Identifier) :两个字节,取值为 0,用于指定协议类型为 Modbus TCP/IP。

  3. 长度字段(Length) :两个字节,表示后续字节的长度(即从单元标识符到功能码和数据的总字节长度),以便接收方可以正确解析报文。

  4. 单元标识符(Unit Identifier) :一个字节,用于在网络上有多个从设备时标识目标从设备的地址(1 - 247)。在简单的单主单从通信中,该值通常为 0,表示不指定特定的从设备(因为通信只涉及一个从设备)。

  5. 功能码(Function Code) :一个字节,表示客户端请求服务器执行的具体操作类型,例如读取寄存器、写入寄存器等。功能码的值决定了服务器端如何处理请求以及返回哪种类型的响应。

  6. 数据(Data) :用于包含请求或响应的具体数据,其内容根据功能码的不同而有所变化。例如,在读取寄存器的功能码请求中,数据部分包含要读取的寄存器起始地址和数量;在写入寄存器的请求中,数据部分包含要写入的寄存器地址和数据值。

  7. 错误检查 :与 Modbus RTU 使用的 CRC - 16 错误检查不同,Modbus TCP 凭借 TCP/IP 协议本身提供的可靠性传输机制,不需要额外的错误校验字段。

Modbus TCP 功能码详解

 一、常见功能码概述

功能码是 Modbus TCP 协议中的核心指令,用于指定客户端希望服务器执行的操作。以下是一些常用的功能码:

(一)0x01(功能码 1):读线圈状态

  • 用途 :用于读取离散输入存储区中的输入状态,这些状态通常是表示设备的输入点是否被激活(如开关状态、传感器报警等)。        

  • 请求报文结构
  1. 功能码(Function Code) :0x01。

  2. 起始地址(Starting Address) :两个字节,指定要读取的第一个线圈的地址。例如,地址 0x0000 表示从第一个线圈开始读取。

  3. 读取数量(Quantity of Coils) :两个字节,指定要读取的线圈数量,取值范围为 1 - 2000。例如,读取 10 个线圈,则数量为 10。

响应报文结构 : 

  1. 功能码(Function Code) :0x01。

  2. 字节计数(Byte Count) :一个字节,表示接下来数据部分的字节数。例如,如果读取 10 个线圈,由于每个线圈占用 1 位,10 位需要 2 个字节(一个字节 8 位)来存储,字节计数为 2。

  3. 线圈数据(Coil Data) :若干字节,每个字节包含 8 个线圈的状态,每个线圈状态用 1 位表示(0 表示 OFF,1 表示 ON)。例如,数据为 0x0F(二进制 00001111)表示该字节对应的最后 4 个线圈为 ON,前面 4 个线圈为 OFF。

(二)0x02(功能码 2):读离散输入状态 

  • 用途 :与读线圈状态类似,但读取的是离散输入存储区中的输入状态。离散输入通常用于采集外部设备的数字信号,如按钮按下状态、开关位置等。

  • 请求报文结构 :与功能码 0x01 的请求报文结构相同,也是包含功能码、起始地址和读取数量。

  • 响应报文结构 :与功能码 0x01 的响应报文结构相似,包含功能码、字节计数和离散输入数据(每个字节包含 8 个离散输入状态位)。

(三)0x03(功能码 3):读保持寄存器 

  • 用途 :用于读取保持寄存器中的数据。保持寄存器可以存储参数设置、计算结果等需要读写的数据,并且这些数据在设备断电后通常会保持其值(具体取决于设备实现)。

  • 请求报文结构

  1. 功能码(Function Code) :0x03。
  2. 起始地址(Starting Address) :两个字节,指定要读取的第一个保持寄存器的地址。例如,地址 0x0004 表示从第 5 个保持寄存器开始读取(地址从 0 开始计数)。

  3. 读取数量(Quantity of Registers) :两个字节,指定要读取的保持寄存器数量,取值范围为 1 - 125。例如,读取 5 个寄存器。

  • 响应报文结构

  1. 功能码(Function Code) :0x03。

  2. 字节计数(Byte Count) :一个字节,表示接下来数据部分的字节数。每个保持寄存器为 16 位(即 2 个字节),所以字节计数 = 读取数量 × 2。例如,读取 5 个寄存器,字节计数为 10。

  3. 寄存器数据(Register Data) :若干字节,按顺序包含读取到的保持寄存器的值。例如,寄存器数据为 0x000A 0x000B 0x000C(假设读取 3 个寄存器),表示第一个寄存器值为 0x000A,第二个为 0x000B,第三个为 0x000C。

(四)0x04(功能码 4):读输入寄存器 

  • 用途 :用于读取输入寄存器中的数据。输入寄存器通常用于存储模拟量输入或其他只读数据,如传感器采集的温度、压力等值。这些数据一般只能读取,不能被客户端修改。

  • 请求报文结构 :与功能码 0x03 的请求报文结构一致,包括功能码、起始地址和读取数量。

  • 响应报文结构 :与功能码 0x03 的响应报文结构类似,包含功能码、字节计数和输入寄存器数据。每个输入寄存器为 16 位(2 字节),数据顺序与寄存器地址顺序一致。

(五)0x05(功能码 5):写单个线圈 

  • 用途 :用于向线圈存储区写入单个线圈的状态,以控制输出设备的通断,例如启动或停止电机、控制继电器等。

  • 请求报文结构

  1. 功能码(Function Code) :0x05。

  2. 输出地址(Output Address) :两个字节,指定要写入的线圈的地址。例如,地址 0x0001 表示要写入第二个线圈(地址从 0 开始计数)。

  3. 输出值(Output Value) :两个字节,表示要写入的线圈状态值,FF 00 表示 ON(线圈激活),00 00 表示 OFF(线圈未激活)。例如,将线圈状态设为 ON,则输出值为 FF 00。

  •  响应报文结构
  1. 功能码(Function Code) :0x05。

  2. 输出地址(Output Address) :与请求中的输出地址相同,用于确认被写入的线圈地址。

  3. 输出值(Output Value) :与请求中的输出值相同,用于确认写入的线圈状态值。

(六)0x06(功能码 6):写单个保持寄存器 

  • 用途 :用于向保持寄存器存储区写入单个保持寄存器的值。这可以用于设置设备的参数、发送控制命令等操作,例如设置电机的转速、设定温度控制器的目标温度等。

  • 请求报文结构

  1. 功能码(Function Code) :0x06。

  2. 寄存器地址(Register Address) :两个字节,指定要写入的保持寄存器的地址。例如,地址 0x0002 表示第三个保持寄存器。

  3. 寄存器值(Register Value) :两个字节,表示要写入的保持寄存器的数值。例如,写入值 0x001A。

  • 响应报文结构

  1. 功能码(Function Code) :0x06。

  2. 寄存器地址(Register Address) :与请求中的寄存器地址相同,用于确认被写入的寄存器地址。

  3. 寄存器值(Register Value) :与请求中的寄存器值相同,用于确认写入的寄存器值。

二、其他功能码简述 

除了上述常用功能码,还有其他一些功能码用于特定的操作:

  • 0x07(功能码 7):读异常状态 :用于读取设备的异常状态信息,帮助诊断设备是否出现故障或异常情况。

  • 0x0F(功能码 15):写多个线圈 :客户端可以使用此功能码同时写入多个线圈的状态,适用于批量控制多个输出设备的场合。请求报文包含起始地址、线圈数量和一个字节数组(每个字节包含 8 个线圈状态位),响应报文用于确认写入操作的起始地址和线圈数量。

  • 0x10(功能码 16):写多个保持寄存器 :用于向保持寄存器存储区写入多个保持寄存器的值。请求报文包括起始地址、寄存器数量和一个字节数组(每个寄存器值占 2 字节),响应报文用于确认写入操作的起始地址和寄存器数量。

  • 0x11(功能码 17):报告从机 ID :客户端发送此请求时,服务器将返回其设备的标识信息,包括设备的唯一 ID、设备信息等,用于设备识别和管理。

 Modbus TCP 在 C# 中的应用示例

一、使用 NModbus4 库

(一)安装库

在 Visual Studio 中打开 “解决方案资源管理器”,右键点击项目,选择 “管理 NuGet 包”,搜索 NModbus4 并安装。安装完成后,项目中就包含了 NModbus4 库的引用,可以使用其中的 Modbus 相关类和方法。

(二)创建客户端

引入 using Modbus.Device; 命名空间,使用 ModbusIpMaster 类创建 Modbus TCP 客户端实例,并指定服务器的 IP 地址和端口。例如:

using System;
using Modbus.Device;
using System.Net.Sockets;class Program
{static void Main(string[] args){// 创建 TCP 客户端TcpClient tcpClient = new TcpClient();tcpClient.Connect("192.168.1.100", 502); // 假设服务器 IP 地址为 192.168.1.100,端口为 502// 创建 Modbus TCP 客户端实例IModbusSerialMaster modbusMaster = ModbusIpMaster.CreateIp(tcpClient);}
}

(三)读写操作示例

1. 读保持寄存器

使用 ReadHoldingRegisters 方法读取保持寄存器的值。该方法需要两个参数:起始地址和要读取的寄存器数量。例如,读取从地址 0 开始的 5 个保持寄存器:

try
{// 读取保持寄存器,起始地址为 0,读取 5 个寄存器ushort[] registers = modbusMaster.ReadHoldingRegisters(0, 5);Console.WriteLine("保持寄存器的值:");foreach (ushort register in registers){Console.WriteLine(register);}
}
catch (Exception ex)
{Console.WriteLine("读取保持寄存器出错:" + ex.Message);
}
2. 写单个保持寄存器

使用 WriteSingleRegister 方法向单个保持寄存器写入值。需要指定寄存器地址和要写入的值。例如,向地址为 2 的保持寄存器写入值 0x001A:

try
{modbusMaster.WriteSingleRegister(2, 0x001A);Console.WriteLine("成功向保持寄存器地址 2 写入值 0x001A");
}
catch (Exception ex)
{Console.WriteLine("写入保持寄存器出错:" + ex.Message);
}
3. 读输入寄存器

使用 ReadInputRegisters 方法读取输入寄存器的值。同样需要起始地址和读取数量参数。例如,读取从地址 3 开始的 3 个输入寄存器:

try
{ushort[] inputRegisters = modbusMaster.ReadInputRegisters(3, 3);Console.WriteLine("输入寄存器的值:");foreach (ushort inputRegister in inputRegisters){Console.WriteLine(inputRegister);}
}
catch (Exception ex)
{Console.WriteLine("读取输入寄存器出错:" + ex.Message);
}
4. 写单个线圈

使用 WriteSingleCoil 方法向单个线圈写入状态值。需要指定线圈地址和状态值(true 表示 ON,false 表示 OFF)。例如,将地址为 1 的线圈状态设为 ON:

try
{modbusMaster.WriteSingleCoil(1, true);Console.WriteLine("成功向线圈地址 1 写入状态 ON");
}
catch (Exception ex)
{Console.WriteLine("写入线圈出错:" + ex.Message);
}
5. 读离散输入状态

使用 ReadDiscreteInputs 方法读取离散输入的状态。指定起始地址和读取数量。例如,读取从地址 0 开始的 8 个离散输入状态:

try
{bool[] discreteInputs = modbusMaster.ReadDiscreteInputs(0, 8);Console.WriteLine("离散输入的状态:");foreach (bool discreteInput in discreteInputs){Console.WriteLine(discreteInput ? "ON" : "OFF");}
}
catch (Exception ex)
{Console.WriteLine("读取离散输入状态出错:" + ex.Message);
}

二、使用 EasyModbus 库

(一)安装库

同样通过 NuGet 包管理器安装 EasyModbus 库,在 Visual Studio 中找到并安装该库后,即可在项目中使用。

(二)连接服务器

创建 EasyModbusTCPClient 类的实例,传入服务器的 IP 地址和端口号来建立连接。例如:

using EasyModbus;class Program
{static void Main(string[] args){EasyModbusTCPClient client = new EasyModbusTCPClient("192.168.1.100", 502);}
}

(三)数据读写示例

1. 读保持寄存器

使用 ReadHoldingRegisters 方法读取保持寄存器的值。例如,读取地址 0 开始的 5 个保持寄存器:

try
{ushort[] registers = client.ReadHoldingRegisters(0, 5);Console.WriteLine("保持寄存器的值:");foreach (ushort register in registers){Console.WriteLine(register);}
}
catch (Exception ex)
{Console.WriteLine("读取保持寄存器出错:" + ex.Message);
}
2. 写单个保持寄存器

使用 WriteSingleRegister 方法向单个保持寄存器写入值。例如,向地址为 2 的保持寄存器写入值 0x001A:

try
{client.WriteSingleRegister(2, 0x001A);Console.WriteLine("成功向保持寄存器地址 2 写入值 0x001A");
}
catch (Exception ex)
{Console.WriteLine("写入保持寄存器出错:" + ex.Message);
}
3. 读输入寄存器

使用 ReadInputRegisters 方法读取输入寄存器的值。例如,读取地址为 3 开始的 3 个输入寄存器:

try
{ushort[] inputRegisters = client.ReadInputRegisters(3, 3);Console.WriteLine("输入寄存器的值:");foreach (ushort inputRegister in inputRegisters){Console.WriteLine(inputRegister);}
}
catch (Exception ex)
{Console.WriteLine("读取输入寄存器出错:" + ex.Message);
}
4. 写单个线圈

使用 WriteSingleCoil 方法向单个线圈写入状态值。例如,将地址为 1 的线圈状态设为 ON:

try
{client.WriteSingleCoil(1, true);Console.WriteLine("成功向线圈地址 1 写入状态 ON");
}
catch (Exception ex)
{Console.WriteLine("写入线圈出错:" + ex.Message);
}
5. 读离散输入状态

使用 ReadDiscreteInputs 方法读取离散输入的状态。例如,读取从地址 0 开始的 8 个离散输入状态:

try
{bool[] discreteInputs = client.ReadDiscreteInputs(0, 8);Console.WriteLine("离散输入的状态:");foreach (bool discreteInput in discreteInputs){Console.WriteLine(discreteInput ? "ON" : "OFF");}
}
catch (Exception ex)
{Console.WriteLine("读取离散输入状态出错:" + ex.Message);
}

专有名词解释

  • Modbus TCP :基于 Modbus 协议的以太网通信协议,用于在设备之间通过以太网进行数据传输,具有简单、可靠的特点,广泛应用于工业自动化领域。

  • 客户端 - 服务器模式(Client - Server Model) :一种网络通信架构,客户端主动向服务器请求服务,服务器负责处理请求并返回结果。在 Modbus TCP 中,客户端通常是主控设备,服务器是数据采集设备或从设备。

  • 数据存储区(Data Storage Area) : Modbus 协议中用于存储不同类型数据的区域,包括线圈、离散输入、保持寄存器和输入寄存器存储区,每个存储区用于特定类型数据的读写操作。

  • 功能码(Function Code) : Modbus 协议中的指令代码,用于告诉服务器要执行的具体操作,如读取寄存器、写入寄存器、读取线圈状态等。不同的功能码对应不同的操作类型和数据格式。

相关文章:

  • 第五章 面向对象(进阶)
  • qt之开发大恒usb3.0相机三
  • 第五十七节:综合项目实践-智能监控系统原型
  • AI预测3D新模型百十个定位预测+胆码预测+去和尾2025年5月28日第91弹
  • linux版本vmware修改ubuntu虚拟机为桥接模式
  • 篇章五 数据结构——链表(一)
  • maven离线将jar包导入到本地仓库中
  • linux安装ffmpeg7.0.2全过程
  • Maven 项目中集成数据库文档生成工具
  • [cg][ds] 八面体映射编码Normal
  • 61、【OS】【Nuttx】编码规范解读(九)
  • SpringBoot 自动装配原理深度解析:从源码到实践
  • Bootstrap法进行随机模拟
  • 班翎流程平台 | 流程变量赋值事件,简化流程配置,灵活构建流程
  • micromamba安装 配置 pythonocc安装
  • LMEval ,谷歌开源的统一评估多模态AI模型框架
  • 树莓派设置静态ip 永久有效 我的需要设置三个 一个摄像头的 两个设备的
  • FastAPI 依赖注入
  • web-css
  • 2.2 C++之循环结构
  • 188建站系统源码/免费网站统计
  • 免费做网站凡科/上海网站建设关键词排名
  • asp企业网站源码下载/企业网站推广方法实验报告
  • 全国网站建设排名/爱站工具查询
  • 阿里云网站备案流程/怎么推广软件
  • 专业做传奇网站解析/个人如何做seo推广