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

Untiy Modbus 西门子 S7-1200 基础通信

Untiy Modbus 西门子 S7-1200 基础通信

  • Modbus
    • Modbus是什么
    • Modbus 协议版本
    • Modbus 通信和设备
    • Modbus 如何实现
    • Modbus 使用限制
    • Modbus 通信协议学理上的弱点分析
  • Unity
    • Unity ModbusTCP
      • Unity ModbusTCP 单个线圈读取方法
      • Unity ModbusTCP 单个线圈写入方法 Int
      • Unity ModbusTCP 单个线圈写入方法 Bool
    • Unity NModbus4
      • Unity NModbus4 读取 Int 方法
      • Unity NModbus4 读取 Float 方法
      • Unity NModbus4 写入 Int 方法
      • Unity NModbus4 写入 Float 方法
    • Unity EasyModbus
      • Unity EasyModbus 读取 Int 方法
      • Unity EasyModbus 读取 Float 方法
      • Unity EasyModbus 写入 Int 方法
      • Unity EasyModbus 写入 Float 方法
    • Unity ModbusTCP 完整代码
    • Unity NModbus4 完整代码
    • Unity easyModbus 完整代码
    • Unity 场景
      • Unity 场景 Hierarchy窗口
      • Unity 场景 脚本搭载
      • Unity 场景 效果
  • 遇到的问题

在本篇博客中,我们将探讨如何Modbus 协议来使用Unity和西门子S7-1200通信。

Modbus

Modbus是什么

在这里插入图片描述

Modbus是一种串行通信协议,是Modicon公司(现在的施耐德电气)于1979年为使用可编程逻辑控制器
(PLC)通信而发表。
Modbus已经成为工业领域通信协议事实上的业界标准,并且现在是工业电子设备之间常用的连接方式。Modbus比其他通信协议使用的更广泛的主要原因有:1. 公开发表并且无著作权要求2. 易于部署和维护3. 对供应商来说,修改移动原生的比特或字节没有很多限制Modbus允许多个 (大约240) 设备连接在同一个网络上进行通信。
举个例子,一个由测量温度和湿度的设备并且将结果发送给计算机。
在数据采集与监视控制系统(SCADA)中,Modbus通常用来连接监控计算机和远程终端控制系统(RTU)。

Modbus 协议版本

在这里插入图片描述

Modbus协议目前存在用于串口、以太网以及其他支持互联网协议的网络的版本。大多数Modbus设备通信通过串EIA-485物理层进行。对于串行连接,存在两个变种,它们在数值数据表示不同和协议细节上略有不同。
Modbus RTU是一种紧凑的,采用二进制表示数据的方式,Modbus ASCII是一种人类可读的,冗长的表示方式
这两个变种都使用串行通信(serial communication)方式。
RTU格式后续的命令/数据带有循环冗余校验的校验和,而ASCII格式采用纵向冗余校验的校验和。
被配置为RTU变种的节点不会和设置为ASCII变种的节点通信,反之亦然。对于通过TCP/IP(例如以太网)的连接,存在多个Modbus/TCP变种,这种方式不需要校验和计算。对于所有的这三种通信协议在数据模型和功能调用上都是相同的,只有封装方式是不同的。Modbus有一个扩展版本Modbus Plus(Modbus+或者MB+),不过此协议是Modicon专有的,和Modbus不同。
它需要一个专门的协处理器来处理类似HDLC的高速令牌旋转。
它使用1Mbit/s的双绞线,并且每个节点都有转换隔离设备,
是一种采用转换/边缘触发而不是电压/水平触发的设备。
连接Modbus Plus到计算机需要特别的接口,通常是支持ISASA85),PCI或者PCMCIA总线的板卡。

Modbus 通信和设备

在这里插入图片描述

Modbus协议是一个master/slave架构的协议。
有一个节点是master节点,其他使用Modbus协议参与通信的节点是slave节点。
每一个slave设备都有一个唯一的地址。
在串行和MB+网络中,只有被指定为主节点的节点可以启动一个命令
(在以太网上,任何一个设备都能发送一个Modbus命令,但是通常也只有一个主节点设备启动指令)。一个ModBus命令包含了打算执行的设备的Modbus地址。
所有设备都会收到命令,但只有指定位置的设备会执行及回应指令
(地址0例外,指定地址0的指令是广播指令,所有收到指令的设备都会执行,不过不回应指令)。
所有的Modbus命令包含了检查码,以确定到达的命令没有被破坏。
基本的ModBus命令能指挥一个RTU改变它的寄存器的某个值,控制或者读取一个I/O端口,
以及指挥设备回送一个或者多个其寄存器中的数据。有许多modems和网关支持Modbus协议,因为Modbus协议很简单而且容易复制。
它们当中一些为这个协议特别设计的。有使用有线、无线通信甚至短消息和GPRS的不同实现。
不过设计者需要克服一些包括高延迟和时序的问题。

Modbus 如何实现

在这里插入图片描述

几乎所有的实现都是官方标准的某种变体。不同的供应商设备之间可能无法正确的通信。
一些主要的变化有:数据类型1. IEEE标准的浮点数2. 32位整型数3. 8位数据4. 混合数据类型5. 整数中的位域6. multipliers to change data to/from integer. 10, 100, 1000, 256 ...协议扩展1. 16位的从站地址2. 32位的数据大小(1个地址 = 返回32位数据)3. 字交换数据

Modbus 使用限制

在这里插入图片描述

	1. Modbus是在1970年末为可编程逻辑控制器通信开发的,这些有限的数据类型在那个时代是可以被PLC理解的,大型二进制对象数据是不支持的。2. 对节点而言,没有一个标准的方法找到数据对象的描述信息,举个例子,确定一个寄存器数据是否表示一个介于30-175度之间的温度。3. 由于Modbus是一个主/从协议,没有办法要求设备“报告异常”(构建在以太网的TCP/IP协议之上,被称为open-mbus除外)- 主节点必须循环的询问每个节点设备,并查找数据中的变化。在带宽可能比较宝贵的应用中,这种方式在应用中消耗带宽和网络时间,例如在低速率的无线链路上。4. Modbus在一个数据链路上只能处理247个地址,这种情况限制了可以连接到主控站点的设备数量(再一次指出以太网TCP/IP除外)5. Modbus传输在远端通讯设备之间缓冲数据的方式进行,有对通信一定是连续的限制,避免了传输中的缓冲区漏洞的问题7. Modbus协议针对未经授权的命令或截取数据没有安全性。

Modbus 通信协议学理上的弱点分析

在这里插入图片描述

Modbus 当初设计的时候,主要着重两点,
分别是简单-易于各项系统或是设备上的实现与各项系统所需求的资源较低,以利降低成本,
另一则是通用-便于集成各式各样设备或是平台,同样地,这样也带来一些缺点,
从信息安全的角度上去解析可发现具有三个主要的弱点:1. 没有保护机制-指令明码传输透过第三方数据包侧录软件就可以截取数据包内容,无须解密。2. 没有认证机制-符合规范就执行 只要符合Modbus规范之数据包传输便可透过第三方控制软件监控接受端设备3. 有可能有实现上的问题针对未定义参考位置输入指令值,可能造成接收端传输异常进而瘫痪接收端设备。

Unity

NModbus4.dll 和 EasyModbus.dll 资源包: 包含脚本

Unity ModbusTCP

Unity ModbusTCP 单个线圈读取方法

        /// <summary>/// 构建读取线圈寄存器的请求  Int/// </summary>/// <param 设备 ID="_UnitId"></param>/// 范围为 0 到 255/// <param 起始地址="_StartAddress"></param>/// 范围为 0 到 65535/// <param 寄存器数量="_CoilCount"></param>/// 范围为 1 到 2000/// <returns></returns>public static byte[] BuildReadCoilsRequest(byte _UnitId, ushort _StartAddress, ushort _CoilCount){byte[] _Request = new byte[12];// Modbus TCP报文头部_Request[0] = 0; // 事务标识符高字节_Request[1] = 0; // 事务标识符低字节_Request[2] = 0; // 协议标识符高字节_Request[3] = 0; // 协议标识符低字节_Request[4] = 0; // 报文长度高字节_Request[5] = 6; // 报文长度低字节_Request[6] = _UnitId; // 设备ID_Request[7] = 1; // 功能码(读取线圈寄存器)// 起始地址byte[] _StartAddressBytes = BitConverter.GetBytes(_StartAddress);_Request[8] = _StartAddressBytes[1]; // 起始地址高字节_Request[9] = _StartAddressBytes[0]; // 起始地址低字节// 寄存器数量byte[] _CoilCountBytes = BitConverter.GetBytes(_CoilCount);_Request[10] = _CoilCountBytes[1]; // 寄存器数量高字节_Request[11] = _CoilCountBytes[0]; // 寄存器数量低字节return _Request;}

Unity ModbusTCP 单个线圈写入方法 Int

        /// <summary>/// 构建写单个寄存器的请求  Int/// </summary>/// <param 设备ID ="_UnitId"></param>/// 范围为 0 到 255/// <param 寄存器地址 ="_RegisterAddress"></param>/// 范围为 0 到 65535/// <param 寄存器值 ="_Value"></param>/// 范围为 0 到 65535/// <returns></returns>public static byte[] BuildWriteSingleRegisterRequest(byte _UnitId, ushort _RegisterAddress, ushort _Value){byte[] _Request = new byte[12];// Modbus TCP报文头部_Request[0] = 0; // 事务标识符高字节_Request[1] = 0; // 事务标识符低字节_Request[2] = 0; // 协议标识符高字节_Request[3] = 0; // 协议标识符低字节_Request[4] = 0; // 报文长度高字节_Request[5] = 6; // 报文长度低字节_Request[6] = _UnitId; // 设备ID_Request[7] = 6; // 功能码(写单个寄存器)// 寄存器地址byte[] _RegisterAddressBytes = BitConverter.GetBytes(_RegisterAddress);_Request[8] = _RegisterAddressBytes[1]; // 寄存器地址高字节_Request[9] = _RegisterAddressBytes[0]; // 寄存器地址低字节// 寄存器值byte[] _ValueBytes = BitConverter.GetBytes(_Value);_Request[10] = _ValueBytes[1]; // 寄存器值高字节_Request[11] = _ValueBytes[0]; // 寄存器值低字节return _Request;}

Unity ModbusTCP 单个线圈写入方法 Bool

        /// <summary>/// 构建写单个线圈的请求 Bool/// </summary>/// <param 设备ID="_UnitId"></param>/// 范围为 0 到 255/// <param 线圈地址="_CoilAddress"></param>/// 范围为 0 到 65535/// <param 线圈状态="_State"></param>/// true表示置位(ON),false表示复位(OFF)/// <returns></returns>public static byte[] BuildWriteSingleCoilRequest(byte _UnitId, ushort _CoilAddress, bool _State){byte[] _Request = new byte[12];// Modbus TCP报文头部_Request[0] = 0; // 事务标识符高字节_Request[1] = 0; // 事务标识符低字节_Request[2] = 0; // 协议标识符高字节_Request[3] = 0; // 协议标识符低字节_Request[4] = 0; // 报文长度高字节_Request[5] = 6; // 报文长度低字节_Request[6] = _UnitId; // 设备ID_Request[7] = 5; // 功能码(写单个线圈)// 线圈地址byte[] _CoilAddressBytes = BitConverter.GetBytes(_CoilAddress);_Request[8] = _CoilAddressBytes[1]; // 线圈地址高字节_Request[9] = _CoilAddressBytes[0]; // 线圈地址低字节// 线圈状态if (_State){_Request[10] = 0xFF;_Request[11] = 0x00;}else{_Request[10] = 0x00;_Request[11] = 0x00;}return _Request;}

Unity NModbus4

Unity NModbus4 读取 Int 方法

异步 方法
    /// <summary>/// 异步读取保持寄存器的值  Int/// </summary>/// <param 地址="_RegisterAddress"></param>/// <returns></returns>private async Task<int> ReadIntAsync(ushort _RegisterAddress){try{// 异步读取寄存器值ushort[] _Registers = await _ModbusMaster.ReadHoldingRegistersAsync(1, _RegisterAddress, 1);// 将ushort值转换为intint _Value = _Registers[0];return _Value;}catch (Exception ex){Debug.LogError($"读取寄存器失败: {ex.Message}");// 返回默认值或抛出异常取决于您的错误处理策略return default(int);}}
主线程 方法
    /// <summary>/// 读取 Int 方法/// </summary>/// <param 寄存器地址="_RegisterAddress"></param>/// <returns></returns>public int ReadInt(ushort _RegisterAddress){// 读取两个寄存器的值ushort[] _Registers = _ModbusMaster.ReadHoldingRegisters(1, _RegisterAddress, 1);// 将两个ushort值组合成一个int值//int _Value = (_Registers[0] << 16) | _Registers[1];int _Value = _Registers[0];return _Value;}

Unity NModbus4 读取 Float 方法

异步 方法
    /// <summary>/// 异步读取保持寄存器的值  float/// </summary>/// <param 地址="_RegisterAddress"></param>/// <returns></returns>private async Task<float> ReadFloatAsync(ushort _RegisterAddress){try{// 异步读取寄存器值ushort[] _Registers = await _ModbusMaster.ReadHoldingRegistersAsync(1, _RegisterAddress, 2);// 将寄存器值转换为浮点数byte[] _Bytes = new byte[4];_Bytes[0] = (byte)(_Registers[1] & 0xFF); // LSB of second register_Bytes[1] = (byte)(_Registers[1] >> 8);   // MSB of second register_Bytes[2] = (byte)(_Registers[0] & 0xFF); // LSB of first register_Bytes[3] = (byte)(_Registers[0] >> 8);   // MSB of first registerreturn BitConverter.ToSingle(_Bytes, 0);}catch (Exception ex){Debug.LogError($"读取寄存器失败: {ex.Message}");// 返回默认值或抛出异常取决于您的错误处理策略return default(float);}}
主线程 方法
    /// <summary>/// 读取浮点数 方法/// </summary>/// <param 寄存器地址 ="_StartAddress"></param>/// <returns></returns>public float ReadFloat(ushort _StartAddress){// 读取两个保持寄存器ushort[] _Registers = _ModbusMaster.ReadHoldingRegisters(1, _StartAddress, 2);// 将寄存器值转换为浮点数byte[] _Bytes = new byte[4];_Bytes[0] = (byte)(_Registers[1] & 0xFF); // LSB of second register_Bytes[1] = (byte)(_Registers[1] >> 8);   // MSB of second register_Bytes[2] = (byte)(_Registers[0] & 0xFF); // LSB of first register_Bytes[3] = (byte)(_Registers[0] >> 8);   // MSB of first registerreturn BitConverter.ToSingle(_Bytes, 0);}

Unity NModbus4 写入 Int 方法

异步 方法
    /// <summary>/// 异步写入保持寄存器的值  Int/// </summary>/// <param 地址="_RegisterAddress"></param>/// <param 写入值="_Value"></param>/// <returns></returns>private async Task WriteIntAsync(ushort _RegisterAddress, int _Value){try{// 异步写入寄存器await _ModbusMaster.WriteSingleRegisterAsync(1, _RegisterAddress, (ushort)_Value);}catch (Exception ex){Debug.LogError($"写入寄存器失败: {ex.Message}");}}
主线程 方法
    /// <summary>/// 写入 Int 方法/// </summary>/// <param 寄存器地址="_RegisterAddress"></param>/// <param 写入值="_Value"></param>public void WriteInt(ushort _RegisterAddress, int _Value){ 将整数值拆分成两个ushort值(高16位和低16位)//ushort[] _Registers = new ushort[2];//_Registers[0] = (ushort)(_Value >> 16);//_Registers[1] = (ushort)(_Value & 0xFFFF); 写入寄存器//_ModbusMaster.WriteMultipleRegisters(1, _RegisterAddress, _Registers);try{// 写入单个寄存器_ModbusMaster.WriteSingleRegister(1, _RegisterAddress, (ushort)_Value);}catch (Exception ex){Debug.LogError($"写入值失败 {_Value} 寄存器地址 {_RegisterAddress}: {ex.Message}");GameObject.Find("读取信息").GetComponent<Text>().text += $"\n写入值失败 {_Value} 寄存器地址 {_RegisterAddress}: {ex.Message}";}}

Unity NModbus4 写入 Float 方法

异步 方法
    /// <summary>/// 异步写入保持寄存器的值  float/// </summary>/// <param 地址="_RegisterAddress"></param>/// <param 写入值="_\Value"></param>/// <returns></returns>private async Task WriteFloatAsync(ushort _RegisterAddress, float _Value){try{// 将浮点数值转换为字节数组byte[] _Bytes = BitConverter.GetBytes(_Value);// 将字节数组拆分为两个寄存器值ushort[] _Registers = new ushort[2];_Registers[0] = BitConverter.ToUInt16(_Bytes, 2);_Registers[1] = BitConverter.ToUInt16(_Bytes, 0);// 异步写入两个保持寄存器await _ModbusMaster.WriteMultipleRegistersAsync(1, _RegisterAddress, _Registers);}catch (Exception ex){Debug.LogError($"写入寄存器失败: {ex.Message}");}}
主线程 方法
    /// <summary>/// 写入浮点数 方法/// </summary>/// <param 寄存器地址="_StartAddress"></param>/// <param 写入值="_Value"></param>public void WriteFloat(ushort _StartAddress, float _Value){// 将浮点数值转换为字节数组byte[] _Bytes = BitConverter.GetBytes(_Value);// 将字节数组拆分为两个寄存器值ushort[] _Registers = new ushort[2];_Registers[0] = BitConverter.ToUInt16(_Bytes, 2);_Registers[1] = BitConverter.ToUInt16(_Bytes, 0);try{// 写入两个保持寄存器_ModbusMaster.WriteMultipleRegisters(01, _StartAddress, _Registers);}catch (Exception ex){Debug.LogError($"写入值失败 {_Registers} 寄存器地址 {_StartAddress}: {ex.Message}");GameObject.Find("读取信息").GetComponent<Text>().text += $"\n写入值失败 {_Value} 寄存器地址 {_StartAddress}: {ex.Message}";}}

Unity EasyModbus

Unity EasyModbus 读取 Int 方法

   /// <summary>/// 读取Int/// </summary>/// <param name="startAddress"></param>private void ReadHoldingRegistersInt(int startAddress){//while (true){try{// 读取从地址40001开始的寄存器,读取2个寄存器(每个寄存器2个字节,4字节表示一个整数)int[] registers = _ModbusClient.ReadHoldingRegisters(startAddress, 2);// 将读取到的寄存器值转换为整数int intValue = ModbusClient.ConvertRegistersToInt(registers);// 处理读取到的数据GameObject.Find("读取信息").GetComponent<Text>().text += "\n读寄存器 Int: " + intValue;}catch (Exception e){Debug.LogError("读取寄存器失败: " + e.Message);GameObject.Find("读取信息").GetComponent<Text>().text += "读取寄存器失败: " + e.Message;}// 每秒读取一次//yield return new WaitForSeconds(1);}}

Unity EasyModbus 读取 Float 方法

    /// <summary>/// 读取 Float/// </summary>/// <param name="startAddress"></param>private void ReadHoldingRegistersFloat(int startAddress){//while (true){try{// 读取从地址40001开始的寄存器,读取2个寄存器(每个寄存器2个字节,4字节表示一个整数)int[] registers = _ModbusClient.ReadHoldingRegisters(startAddress, 2);// 将读取到的寄存器值转换为浮点数float floatValue = ModbusClient.ConvertRegistersToFloat(registers);// 处理读取到的数据GameObject.Find("读取信息").GetComponent<Text>().text += "\n读寄存器 Float: " + floatValue;}catch (Exception e){GameObject.Find("读取信息").GetComponent<Text>().text += "读取寄存器失败: " + e.Message;}// 每秒读取一次//yield return new WaitForSeconds(1);}}

Unity EasyModbus 写入 Int 方法

    /// <summary>/// 写入整数/// </summary>/// <param name="address"></param>/// <param name="value"></param>private void WriteSingleRegister(int address, int value){try{// 将浮点数值转换为寄存器值int[] registers = ModbusClient.ConvertIntToRegisters(value);_ModbusClient.WriteMultipleRegisters(address, registers);GameObject.Find("读取信息").GetComponent<Text>().text += "\n写地址寄存器 Int:" + address + " 写入值:" + value;}catch (Exception e){GameObject.Find("读取信息").GetComponent<Text>().text += "写寄存器失败: " + e.Message;}}

Unity EasyModbus 写入 Float 方法

    /// <summary>/// 写入浮点数/// </summary>/// <param name="address"></param>/// <param name="value"></param>private void WriteSingleRegister(int address, float value){try{// 将浮点数值转换为寄存器值int[] registers = ModbusClient.ConvertFloatToRegisters(value);_ModbusClient.WriteMultipleRegisters(address, registers);GameObject.Find("读取信息").GetComponent<Text>().text += "\n写地址寄存器 Float:" + address + " 写入值:" + value;}catch (Exception e){GameObject.Find("读取信息").GetComponent<Text>().text += "写寄存器失败: " + e.Message;}}

Unity ModbusTCP 完整代码

using System;
using System.Collections;
using System.Net.Sockets;
using System.Threading;
using UnityEngine;
using EasyModbus;
using System.Text;/// <summary>
/// Modbus TCP 处理
/// </summary>
public class ModbusTCP_ZH : MonoBehaviour
{public static ModbusTCP_ZH _Instance;// TCP 网络private TcpClient _TcpClient;private NetworkStream _NetworkStream;private Thread _ReceiveThread;private byte[] _ReceiveBuffer = new byte[256];[Header("PLC IP地址")]public string _IpAddress = "192.168.0.100";[Header("Modbus TCP端口号")]public int _Port = 502;[Header("PLC设备的Unit ID")]public int _UnitId = 1;[Header("线圈寄存器 起始地址")]public ushort _CoilStartAddress = 0;[Header("线圈寄存器 数量")]public ushort _CoilCount = 10;[Header("写入 地址")]public ushort _IDAA;[Header("写入 值")]public ushort _IDSSValue;//连接成功 布尔[HideInInspector]public bool _ConnectSuccess = false;private void Awake(){_Instance = this;}private void Start(){//ConnectToPLC();}private void Update(){if (Input.GetKeyDown(KeyCode.T)){//ReadCoilData(1, 40001, 1);//ReadCoilData(1,0, 2);ReadRegisters(1, 1);}if (Input.GetKeyDown(KeyCode.Y)){//ControlDevice(1, 1);ControlDevice(_IDAA, _IDSSValue);}}/// <summary>/// PLC 连接/// </summary>private void ConnectToPLC(){try{_TcpClient = new TcpClient();_TcpClient.Connect(_IpAddress, _Port);_NetworkStream = _TcpClient.GetStream();_ReceiveBuffer = new byte[1024];// 启动接收数据的线程_ReceiveThread = new Thread(ReceiveData);_ReceiveThread.IsBackground = true;_ReceiveThread.Start();Debug.Log("PLC 连接成功。");}catch (Exception e){Debug.LogError("PLC 连接失败: " + e.Message);}}/// <summary>/// PLC 连接/// </summary>/// <param 地址 ="_IpAddress"></param>/// <param 端口 ="_Port"></param>public bool ConnectToPLC(string _IpAddress, int _Port){try{_TcpClient = new TcpClient();_TcpClient.Connect(_IpAddress, _Port);_NetworkStream = _TcpClient.GetStream();_ReceiveBuffer = new byte[1024];// 启动接收数据的线程_ReceiveThread = new Thread(ReceiveData);_ReceiveThread.IsBackground = true;_ReceiveThread.Start();Debug.Log("PLC 连接成功。");return _ConnectSuccess = true;}catch (Exception e){Debug.LogError("PLC 连接失败: " + e.Message);return _ConnectSuccess = false;}}/// <summary>/// 数据接收/// </summary>private void ReceiveData(){while (_TcpClient != null && _TcpClient.Connected){try{byte[] _ReceiveBuffer = new byte[1024];// 数据接收int _BytesRead = _NetworkStream.Read(_ReceiveBuffer, 0, _ReceiveBuffer.Length);if (_BytesRead > 0){// 处理接收到的数据ProcessReceivedData(_ReceiveBuffer, _BytesRead);}}catch (Exception e){Debug.LogError("接收数据 错误: " + e.Message);break;}}}/// <summary>/// 数据接收响应/// </summary>/// <param 接收数据 ="_Data"></param>/// <param 接收数据长度 ="_Length"></param>private void ProcessReceivedData(byte[] _Data, int _Length){// 解析接收到的Modbus响应// 根据协议解析数据并更新 coilData 数组//Debug.Log("读取值: " + _Data);Debug.Log("读取长度: " + _Length);Debug.Log("读取值: " + BitConverter.ToUInt16(new byte[] { _Data[10], _Data[9] }, 0));string _ReceivedData = "";for (int i = 0; i < _Data.Length; i++){_ReceivedData += _Data[i].ToString() + "\n";}Debug.Log(_ReceivedData);}/// <summary>/// Modbus 读取/// </summary>private void ReadRegisters(int _CoilStartAddress, ushort _CoilCount){try{ModbusClient _ModbusClient = new ModbusClient("192.168.2.1", 502);// 读取保持寄存器的值int[] _Values = _ModbusClient.ReadHoldingRegisters(_CoilStartAddress, _CoilCount);// 处理读取的结果StringBuilder _Builder = new StringBuilder();for (int i = 0; i < _Values.Length; i++){_Builder.AppendLine("寄存器 " + (_CoilStartAddress + i) + " 的值: " + _Values[i]);}// 输出读取的结果Debug.Log("读取结果:");Debug.Log(_Builder.ToString());}catch (Exception ex){Debug.LogError("发生错误: " + ex.Message);}}/// <summary>///  发送Modbus 读取 请求/// </summary>/// <param 设备ID="_UnitId"></param>/// <param 起始地址="_CoilStartAddress"></param>/// <param 读取的线圈寄存器的数量 ="_CoilCount"></param>private void ReadCoilData(int _UnitId, ushort _CoilStartAddress, ushort _CoilCount){//ReadCoilData(1, 40001, 1);// 构造Modbus请求byte[] _Request = ModbusUtils.BuildReadCoilsRequest((byte)_UnitId, _CoilStartAddress, _CoilCount);try{// 发送Modbus请求_NetworkStream.Write(_Request, 0, _Request.Length);_NetworkStream.Flush();}catch (Exception e){Debug.LogError("发送数据 错误: " + e.Message);}}/// <summary>///  设备状态写入  Bool/// </summary>/// <param 线圈地址="_CoilAddress"></param>/// <param 状态="_State"></param>public void ControlDevice(ushort _CoilAddress, bool _State){// 写入数据示例//ControlDevice(16, true); // 写入值 true 到地址 40016byte[] _Request = ModbusUtils.BuildWriteSingleCoilRequest((byte)_UnitId, _CoilAddress, _State);try{_NetworkStream.Write(_Request, 0, _Request.Length);_NetworkStream.Flush();}catch (Exception e){Debug.LogError("发送数据 错误: " + e.Message);}}/// <summary>/// 设备状态写入 方法  Int/// </summary>/// <param 线圈地址="_RegisterAddress"></param>/// <param 写入值="_Value"></param>public void ControlDevice(ushort _RegisterAddress, ushort _Value){// 写入数据示例//ControlDevice(16, 1234); // 写入值 1234 到地址 40016byte[] _Request = ModbusUtils.BuildWriteSingleRegisterRequest(1, _RegisterAddress, _Value);try{_NetworkStream.Write(_Request, 0, _Request.Length);_NetworkStream.Flush();print("写入成功:" + _RegisterAddress.ToString() + "_______" + _Value.ToString());}catch (Exception e){Debug.LogError("发送数据错误: " + e.Message);}}/// <summary>/// Modbus协议 处理/// </summary>public static class ModbusUtils{/// <summary>/// 构建读取线圈寄存器的请求  Int/// </summary>/// <param 设备 ID="_UnitId"></param>/// 范围为 0 到 255/// <param 起始地址="_StartAddress"></param>/// 范围为 0 到 65535/// <param 寄存器数量="_CoilCount"></param>/// 范围为 1 到 2000/// <returns></returns>public static byte[] BuildReadCoilsRequest(byte _UnitId, ushort _StartAddress, ushort _CoilCount){byte[] _Request = new byte[12];// Modbus TCP报文头部_Request[0] = 0; // 事务标识符高字节_Request[1] = 0; // 事务标识符低字节_Request[2] = 0; // 协议标识符高字节_Request[3] = 0; // 协议标识符低字节_Request[4] = 0; // 报文长度高字节_Request[5] = 6; // 报文长度低字节_Request[6] = _UnitId; // 设备ID_Request[7] = 1; // 功能码(读取线圈寄存器)// 起始地址byte[] _StartAddressBytes = BitConverter.GetBytes(_StartAddress);_Request[8] = _StartAddressBytes[1]; // 起始地址高字节_Request[9] = _StartAddressBytes[0]; // 起始地址低字节// 寄存器数量byte[] _CoilCountBytes = BitConverter.GetBytes(_CoilCount);_Request[10] = _CoilCountBytes[1]; // 寄存器数量高字节_Request[11] = _CoilCountBytes[0]; // 寄存器数量低字节return _Request;}/// <summary>/// 构建写单个线圈的请求 Bool/// </summary>/// <param 设备ID="_UnitId"></param>/// 范围为 0 到 255/// <param 线圈地址="_CoilAddress"></param>/// 范围为 0 到 65535/// <param 线圈状态="_State"></param>/// true表示置位(ON),false表示复位(OFF)/// <returns></returns>public static byte[] BuildWriteSingleCoilRequest(byte _UnitId, ushort _CoilAddress, bool _State){byte[] _Request = new byte[12];// Modbus TCP报文头部_Request[0] = 0; // 事务标识符高字节_Request[1] = 0; // 事务标识符低字节_Request[2] = 0; // 协议标识符高字节_Request[3] = 0; // 协议标识符低字节_Request[4] = 0; // 报文长度高字节_Request[5] = 6; // 报文长度低字节_Request[6] = _UnitId; // 设备ID_Request[7] = 5; // 功能码(写单个线圈)// 线圈地址byte[] _CoilAddressBytes = BitConverter.GetBytes(_CoilAddress);_Request[8] = _CoilAddressBytes[1]; // 线圈地址高字节_Request[9] = _CoilAddressBytes[0]; // 线圈地址低字节// 线圈状态if (_State){_Request[10] = 0xFF;_Request[11] = 0x00;}else{_Request[10] = 0x00;_Request[11] = 0x00;}return _Request;}/// <summary>/// 构建写单个寄存器的请求  Int/// </summary>/// <param 设备ID ="_UnitId"></param>/// 范围为 0 到 255/// <param 寄存器地址 ="_RegisterAddress"></param>/// 范围为 0 到 65535/// <param 寄存器值 ="_Value"></param>/// 范围为 0 到 65535/// <returns></returns>public static byte[] BuildWriteSingleRegisterRequest(byte _UnitId, ushort _RegisterAddress, ushort _Value){byte[] _Request = new byte[12];// Modbus TCP报文头部_Request[0] = 0; // 事务标识符高字节_Request[1] = 0; // 事务标识符低字节_Request[2] = 0; // 协议标识符高字节_Request[3] = 0; // 协议标识符低字节_Request[4] = 0; // 报文长度高字节_Request[5] = 6; // 报文长度低字节_Request[6] = _UnitId; // 设备ID_Request[7] = 6; // 功能码(写单个寄存器)// 寄存器地址byte[] _RegisterAddressBytes = BitConverter.GetBytes(_RegisterAddress);_Request[8] = _RegisterAddressBytes[1]; // 寄存器地址高字节_Request[9] = _RegisterAddressBytes[0]; // 寄存器地址低字节// 寄存器值byte[] _ValueBytes = BitConverter.GetBytes(_Value);_Request[10] = _ValueBytes[1]; // 寄存器值高字节_Request[11] = _ValueBytes[0]; // 寄存器值低字节return _Request;}}
}

Unity NModbus4 完整代码

using Modbus.Device;
using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Threading;
using System.Threading.Tasks;
using UnityEngine;
using UnityEngine.UI;/// <summary>
/// NModbus4 读写测试
/// </summary>
public class NModbus4_ZH : MonoBehaviour
{public static NModbus4_ZH _Instance;//数据通信private TcpClient _TcpClient;private IModbusMaster _ModbusMaster;private Thread _ReceiveThread;//数据接收字典private Dictionary<int, int> _IntValueDic = new Dictionary<int, int>();private Dictionary<int, float> _FloatValueDic = new Dictionary<int, float>();[Header("写入 Button")]public Button _WriteButton;[Header("读取 Button")]public Button _ReadButton;[Header("连接 Button")]public Button _ConnectButton;[Header("IP 输入")]public InputField _IPInput;[Header("端口号 输入")]public InputField _PortInput;[Header("寄存器 输入")]public InputField _RegisterInput;[Header("写入值 输入")]public InputField _ValueInput;//连接成功 布尔[HideInInspector]public bool _ConnectSuccess = false;//读取缓存int _ReadIntCache = 109527;void Start(){_Instance = this;//写入按钮点击事件_WriteButton.onClick.AddListener(delegate{//判断 _ValueInput.text 是 int 还是 floatstring _ResultStr = CheckValueType(_ValueInput.text);if (_ResultStr == "整数"){{try{//写入单个寄存器WriteInt(Convert.ToUInt16(_RegisterInput.text), Convert.ToUInt16(_ValueInput.text));//WriteSingleRegister(01, 1);GameObject.Find("读取信息").GetComponent<Text>().text += "\n整数写入" + Convert.ToUInt16(_RegisterInput.text) + "--" + Convert.ToUInt16(_ValueInput.text);}catch (Exception ex){Debug.LogError("发送数据时发生错误: " + ex.Message);}}}else if (_ResultStr == "浮点数"){{try{//写入寄存器 float//WriteSingleRegister(Convert.ToUInt16(_RegisterInput.text), float.Parse(_ValueInput.text));WriteFloat(Convert.ToUInt16(_RegisterInput.text), float.Parse(_ValueInput.text));//SendFloatData(Convert.ToUInt16(_RegisterInput.text), float.Parse(_ValueInput.text));//WriteSingleRegister(0x0010,1.5f);GameObject.Find("读取信息").GetComponent<Text>().text += "\n浮点数数写入" + Convert.ToUInt16(_RegisterInput.text) + "--" + float.Parse(_ValueInput.text);}catch (Exception ex){Debug.LogError("发送数据时发生错误: " + ex.Message);}}}else{print("输入值不是有效的整数或浮点数");GameObject.Find("读取信息").GetComponent<Text>().text += "\n输入值不是有效的整数或浮点数";}});//读取按钮点击事件_ReadButton.onClick.AddListener(delegate{//寄存器地址int _ResultInt = Convert.ToUInt16(_RegisterInput.text);if (_ResultInt <= 8){//读取单个保持寄存器GameObject.Find("读取信息").GetComponent<Text>().text += _RegisterInput.text + "  Int:" + ReadInt(Convert.ToUInt16(_RegisterInput.text));}else if (_ResultInt >= 9 && _ResultInt <= 90){//读取多个保持寄存器GameObject.Find("读取信息").GetComponent<Text>().text += _RegisterInput.text + "  Float:" + ReadFloat(Convert.ToUInt16(_RegisterInput.text));}else{print("输入值不是有效的整数或浮点数");GameObject.Find("读取信息").GetComponent<Text>().text += "\n 请检查 寄存器地址和输入值是否够正确";}});//连接按钮点击事件_ConnectButton.onClick.AddListener(delegate{try{// 创建TCP客户端和Modbus主机_TcpClient = new TcpClient(_IPInput.text, Convert.ToInt32(_PortInput.text));_ModbusMaster = ModbusIpMaster.CreateIp(_TcpClient);// 尝试读取从设备的标识符GameObject.Find("读取信息").GetComponent<Text>().text += "\nPLC 已连接。";_ConnectSuccess = true;}catch (Exception e){GameObject.Find("读取信息").GetComponent<Text>().text += "\n" + e.Message;_ConnectSuccess = false;}});}private async void Update(){if (Input.GetKeyDown(KeyCode.Q)){GameObject.Find("读取信息").GetComponent<Text>().text = "读取信息:";}if (Input.GetKeyDown(KeyCode.R)){//向指点地址读取值var _Variable=ReadInt(80);//然后对照表格信息if (_Variable == 01){//判断值if (ReadInt(81) == 01){//执行响应WriteInt(01, 0);}}else if (_Variable == 11){//判断值if (ReadFloat(82) == 1.3f){//执行响应WriteFloat(11, 2.6f);}}}// 调用异步检查数据的方法// 每30帧调用一次,可根据需要调整频率if (Time.frameCount % 30 == 0) {await AsyncCheckData();}}/// <summary>/// 数据清除方法  程序退出执行/// </summary>private void OnApplicationQuit(){// 停止接收数据的线程并释放资源if (_ReceiveThread != null && _ReceiveThread.IsAlive){_ReceiveThread.Abort();}if (_TcpClient != null && _TcpClient.Connected){_TcpClient.Close();}if (_ModbusMaster != null){_ModbusMaster.Dispose();}StopAllCoroutines();}/// <summary>/// 数据检测方法/// </summary>private void CheckData(){//判断当前值是否有变化//规定 80 寄存器地址为判断地址int _ReadInt = ReadInt(80);if (_ReadIntCache!= _ReadInt){_ReadIntCache = _ReadInt;//检测数据if (_ReadInt == 01){//判断值 81 寄存器地址 为Int 输入地址if (ReadInt(81) == 01){//执行响应WriteInt(01, 0);}}else if (_ReadInt == 11){//判断值  82 寄存器地址 为Float 输入地址if (ReadFloat(82) == 1.3f){//执行响应WriteFloat(11, 2.6f);}}}        }/// <summary>/// 异步检查数据的方法/// </summary>/// <returns></returns>private async Task AsyncCheckData(){// 判断当前值是否有变化int _ReadInt = await ReadIntAsync(80);if (_ReadIntCache != _ReadInt){_ReadIntCache = _ReadInt;// 检测数据if (_ReadInt == 01){// 判断值if (await ReadIntAsync(81) == 01){// 执行响应await WriteIntAsync(01, 0);}}else if (_ReadInt == 11){// 判断值if (await ReadFloatAsync(82) == 1.3f){// 执行响应await WriteFloatAsync(11, 2.6f);}}}}/// <summary>/// 异步读取保持寄存器的值  Int/// </summary>/// <param 地址="_RegisterAddress"></param>/// <returns></returns>private async Task<int> ReadIntAsync(ushort _RegisterAddress){try{// 异步读取寄存器值ushort[] _Registers = await _ModbusMaster.ReadHoldingRegistersAsync(1, _RegisterAddress, 1);// 将ushort值转换为intint _Value = _Registers[0];return _Value;}catch (Exception ex){Debug.LogError($"读取寄存器失败: {ex.Message}");// 返回默认值或抛出异常取决于您的错误处理策略return default(int);}}/// <summary>/// 异步写入保持寄存器的值  Int/// </summary>/// <param 地址="_RegisterAddress"></param>/// <param 写入值="_Value"></param>/// <returns></returns>private async Task WriteIntAsync(ushort _RegisterAddress, int _Value){try{// 异步写入寄存器await _ModbusMaster.WriteSingleRegisterAsync(1, _RegisterAddress, (ushort)_Value);}catch (Exception ex){Debug.LogError($"写入寄存器失败: {ex.Message}");}}/// <summary>/// 异步读取保持寄存器的值  float/// </summary>/// <param 地址="_RegisterAddress"></param>/// <returns></returns>private async Task<float> ReadFloatAsync(ushort _RegisterAddress){try{// 异步读取寄存器值ushort[] _Registers = await _ModbusMaster.ReadHoldingRegistersAsync(1, _RegisterAddress, 2);// 将寄存器值转换为浮点数byte[] _Bytes = new byte[4];_Bytes[0] = (byte)(_Registers[1] & 0xFF); // LSB of second register_Bytes[1] = (byte)(_Registers[1] >> 8);   // MSB of second register_Bytes[2] = (byte)(_Registers[0] & 0xFF); // LSB of first register_Bytes[3] = (byte)(_Registers[0] >> 8);   // MSB of first registerreturn BitConverter.ToSingle(_Bytes, 0);}catch (Exception ex){Debug.LogError($"读取寄存器失败: {ex.Message}");// 返回默认值或抛出异常取决于您的错误处理策略return default(float);}}/// <summary>/// 异步写入保持寄存器的值  float/// </summary>/// <param 地址="_RegisterAddress"></param>/// <param 写入值="_\Value"></param>/// <returns></returns>private async Task WriteFloatAsync(ushort _RegisterAddress, float _Value){try{// 将浮点数值转换为字节数组byte[] _Bytes = BitConverter.GetBytes(_Value);// 将字节数组拆分为两个寄存器值ushort[] _Registers = new ushort[2];_Registers[0] = BitConverter.ToUInt16(_Bytes, 2);_Registers[1] = BitConverter.ToUInt16(_Bytes, 0);// 异步写入两个保持寄存器await _ModbusMaster.WriteMultipleRegistersAsync(1, _RegisterAddress, _Registers);}catch (Exception ex){Debug.LogError($"写入寄存器失败: {ex.Message}");}}/// <summary>/// 写入 Int 方法/// </summary>/// <param 寄存器地址="_RegisterAddress"></param>/// <param 写入值="_Value"></param>public void WriteInt(ushort _RegisterAddress, int _Value){ 将整数值拆分成两个ushort值(高16位和低16位)//ushort[] _Registers = new ushort[2];//_Registers[0] = (ushort)(_Value >> 16);//_Registers[1] = (ushort)(_Value & 0xFFFF); 写入寄存器//_ModbusMaster.WriteMultipleRegisters(1, _RegisterAddress, _Registers);try{// 写入单个寄存器_ModbusMaster.WriteSingleRegister(1, _RegisterAddress, (ushort)_Value);}catch (Exception ex){Debug.LogError($"写入值失败 {_Value} 寄存器地址 {_RegisterAddress}: {ex.Message}");GameObject.Find("读取信息").GetComponent<Text>().text += $"\n写入值失败 {_Value} 寄存器地址 {_RegisterAddress}: {ex.Message}";}}/// <summary>/// 读取 Int 方法/// </summary>/// <param 寄存器地址="_RegisterAddress"></param>/// <returns></returns>public int ReadInt(ushort _RegisterAddress){// 读取两个寄存器的值ushort[] _Registers = _ModbusMaster.ReadHoldingRegisters(1, _RegisterAddress, 1);// 将两个ushort值组合成一个int值//int _Value = (_Registers[0] << 16) | _Registers[1];int _Value = _Registers[0];return _Value;}/// <summary>/// 读取浮点数 方法/// </summary>/// <param 寄存器地址 ="_StartAddress"></param>/// <returns></returns>public float ReadFloat(ushort _StartAddress){// 读取两个保持寄存器ushort[] _Registers = _ModbusMaster.ReadHoldingRegisters(1, _StartAddress, 2);// 将寄存器值转换为浮点数byte[] _Bytes = new byte[4];_Bytes[0] = (byte)(_Registers[1] & 0xFF); // LSB of second register_Bytes[1] = (byte)(_Registers[1] >> 8);   // MSB of second register_Bytes[2] = (byte)(_Registers[0] & 0xFF); // LSB of first register_Bytes[3] = (byte)(_Registers[0] >> 8);   // MSB of first registerreturn BitConverter.ToSingle(_Bytes, 0);}/// <summary>/// 写入浮点数 方法/// </summary>/// <param 寄存器地址="_StartAddress"></param>/// <param 写入值="_Value"></param>public void WriteFloat(ushort _StartAddress, float _Value){// 将浮点数值转换为字节数组byte[] _Bytes = BitConverter.GetBytes(_Value);// 将字节数组拆分为两个寄存器值ushort[] _Registers = new ushort[2];_Registers[0] = BitConverter.ToUInt16(_Bytes, 2);_Registers[1] = BitConverter.ToUInt16(_Bytes, 0);try{// 写入两个保持寄存器_ModbusMaster.WriteMultipleRegisters(01, _StartAddress, _Registers);}catch (Exception ex){Debug.LogError($"写入值失败 {_Registers} 寄存器地址 {_StartAddress}: {ex.Message}");GameObject.Find("读取信息").GetComponent<Text>().text += $"\n写入值失败 {_Value} 寄存器地址 {_StartAddress}: {ex.Message}";}}/// <summary>/// 判断输入值类型/// </summary>/// <param 输入值="_InputText"></param>/// <returns></returns>public string CheckValueType(string _InputText){string _Result = string.Empty;if (IsInteger(_InputText)){_Result = "整数";}else if (IsFloat(_InputText)){_Result = "浮点数";}else{_Result = "不是有效的整数或浮点数";}return _Result;}/// <summary>/// 输入值 int 判断/// </summary>/// <param 判断值="_Input"></param>/// <returns></returns>private bool IsInteger(string _Input){int _Result;return int.TryParse(_Input, out _Result);}/// <summary>/// 输入值  float 判断/// </summary>/// <param 判断值="_Input"></param>/// <returns></returns>private bool IsFloat(string _Input){float _Result;return float.TryParse(_Input, out _Result);}
}

Unity easyModbus 完整代码

using EasyModbus;
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.UI;/// <summary>
/// EasyModbus 读取输入
/// </summary>
public class EasyModbus_ZH : MonoBehaviour
{private ModbusClient _ModbusClient;[Header("IP 地址")]public string _IpAddress = "192.168.2.1";[Header("端口")]public int _Port = 502;[Header("寄存器起始地址")]public ushort _StartAddress = 1;[Header("寄存器数量")]public ushort _NumRegisters = 1;[Header("写入值 ushort")]public ushort _Value = 1;[Header("写入值 float")]public float _ValueFloat = 3.5f;[Header("写入 Button")]public Button _WriteButton;[Header("读取 Button")]public Button _ReadButton;[Header("连接 Button")]public Button _ConnectButton;[Header("IP 输入")]public InputField _IPInput;[Header("端口号 输入")]public InputField _PortInput;[Header("寄存器 输入")]public InputField _RegisterInput;[Header("写入值 输入")]public InputField _ValueInput;private void Start(){//连接按钮点击事件_ConnectButton.onClick.AddListener(delegate{try{// 创建TCP客户端和Modbus主机_ModbusClient = new ModbusClient(_IPInput.text, Convert.ToInt32(_PortInput.text));_ModbusClient.Connect();// 尝试读取从设备的标识符GameObject.Find("读取信息").GetComponent<Text>().text += "\nPLC 已连接。";}catch (Exception e){GameObject.Find("读取信息").GetComponent<Text>().text += "\n" + e.Message;}});//读取按钮点击事件_ReadButton.onClick.AddListener(delegate{//寄存器地址int _ResultInt = Convert.ToUInt16(_RegisterInput.text);if (_ResultInt <= 8){//读取单个保持寄存器//GameObject.Find("读取信息").GetComponent<Text>().text += _RegisterInput.text + "  Int:" + ;ReadHoldingRegistersInt(Convert.ToUInt16(_RegisterInput.text));}else if (_ResultInt >= 9 && _ResultInt <= 90){//读取多个保持寄存器//GameObject.Find("读取信息").GetComponent<Text>().text += _RegisterInput.text + "  Float:" + ReadFloat(Convert.ToUInt16(_RegisterInput.text));//StartCoroutine(ReadHoldingRegistersFloat(Convert.ToUInt16(_RegisterInput.text)));ReadHoldingRegistersFloat(Convert.ToUInt16(_RegisterInput.text));}else{print("输入值不是有效的整数或浮点数");GameObject.Find("读取信息").GetComponent<Text>().text += "\n 请检查 寄存器地址和输入值是否够正确";}});//写入按钮点击事件_WriteButton.onClick.AddListener(delegate{//判断 _ValueInput.text 是 int 还是 floatstring _ResultStr = CheckValueType(_ValueInput.text);if (_ResultStr == "整数"){{try{//写入单个寄存器WriteSingleRegister(Convert.ToUInt16(_RegisterInput.text), Convert.ToUInt16(_ValueInput.text));//WriteSingleRegister(01, 1);//GameObject.Find("读取信息").GetComponent<Text>().text += "\n整数写入" + Convert.ToUInt16(_RegisterInput.text) + "--" + Convert.ToUInt16(_ValueInput.text);}catch (Exception ex){Debug.LogError("发送数据时发生错误: " + ex.Message);}}}else if (_ResultStr == "浮点数"){{try{//写入寄存器 float//WriteSingleRegister(Convert.ToUInt16(_RegisterInput.text), float.Parse(_ValueInput.text));WriteSingleRegister(Convert.ToUInt16(_RegisterInput.text), (float)Math.Round(float.Parse(_ValueInput.text), 2));//SendFloatData(Convert.ToUInt16(_RegisterInput.text), float.Parse(_ValueInput.text));//WriteSingleRegister(0x0010,1.5f);//GameObject.Find("读取信息").GetComponent<Text>().text += "\n浮点数数写入" + Convert.ToUInt16(_RegisterInput.text) + "--" + float.Parse(_ValueInput.text);}catch (Exception ex){Debug.LogError("发送数据时发生错误: " + ex.Message);}}}else{print("输入值不是有效的整数或浮点数");GameObject.Find("读取信息").GetComponent<Text>().text += "\n输入值不是有效的整数或浮点数";}});}/// <summary>/// 程序退出时关闭连接/// </summary>private void OnDestroy(){if (_ModbusClient!=null){_ModbusClient.Disconnect();}}private void Update(){if (Input.GetKeyDown(KeyCode.Q)){GameObject.Find("读取信息").GetComponent<Text>().text = "读取信息:";}if (Input.GetKeyDown(KeyCode.P)){StopAllCoroutines();// 读取保持寄存器StartCoroutine(ReadHoldingRegistersIntIE(Convert.ToUInt16(_RegisterInput.text)));//ReadHoldingRegistersInt(7);}}/// <summary>/// 读取Int/// </summary>/// <param name="startAddress"></param>private void ReadHoldingRegistersInt(int startAddress){//while (true){try{// 读取从地址40001开始的寄存器,读取2个寄存器(每个寄存器2个字节,4字节表示一个整数)int[] registers = _ModbusClient.ReadHoldingRegisters(startAddress, 2);// 将读取到的寄存器值转换为整数int intValue = ModbusClient.ConvertRegistersToInt(registers);// 处理读取到的数据GameObject.Find("读取信息").GetComponent<Text>().text += "\n读寄存器 Int: " + intValue;}catch (Exception e){Debug.LogError("读取寄存器失败: " + e.Message);GameObject.Find("读取信息").GetComponent<Text>().text += "读取寄存器失败: " + e.Message;}// 每秒读取一次//yield return new WaitForSeconds(1);}}/// <summary>/// 读取Int/// </summary>/// <param name="startAddress"></param>private IEnumerator ReadHoldingRegistersIntIE(int startAddress){while (true){try{// 读取从地址40001开始的寄存器,读取2个寄存器(每个寄存器2个字节,4字节表示一个整数)int[] registers = _ModbusClient.ReadHoldingRegisters(startAddress, 2);// 将读取到的寄存器值转换为整数int intValue = ModbusClient.ConvertRegistersToInt(registers);// 处理读取到的数据GameObject.Find("读取信息").GetComponent<Text>().text = "\n读寄存器 Int: " + intValue;}catch (Exception e){Debug.LogError("读取寄存器失败: " + e.Message);GameObject.Find("读取信息").GetComponent<Text>().text += "读取寄存器失败: " + e.Message;}// 每秒读取一次yield return new WaitForSeconds(0.15f);}}/// <summary>/// 读取 Float/// </summary>/// <param name="startAddress"></param>private void ReadHoldingRegistersFloat(int startAddress){//while (true){try{// 读取从地址40001开始的寄存器,读取2个寄存器(每个寄存器2个字节,4字节表示一个整数)int[] registers = _ModbusClient.ReadHoldingRegisters(startAddress, 2);// 将读取到的寄存器值转换为浮点数float floatValue = ModbusClient.ConvertRegistersToFloat(registers);// 处理读取到的数据GameObject.Find("读取信息").GetComponent<Text>().text += "\n读寄存器 Float: " + floatValue;}catch (Exception e){GameObject.Find("读取信息").GetComponent<Text>().text += "读取寄存器失败: " + e.Message;}// 每秒读取一次//yield return new WaitForSeconds(1);}}/// <summary>/// 写入整数/// </summary>/// <param name="address"></param>/// <param name="value"></param>private void WriteSingleRegister(int address, int value){try{// 将浮点数值转换为寄存器值int[] registers = ModbusClient.ConvertIntToRegisters(value);_ModbusClient.WriteMultipleRegisters(address, registers);GameObject.Find("读取信息").GetComponent<Text>().text += "\n写地址寄存器 Int:" + address + " 写入值:" + value;}catch (Exception e){GameObject.Find("读取信息").GetComponent<Text>().text += "写寄存器失败: " + e.Message;}}/// <summary>/// 写入浮点数/// </summary>/// <param name="address"></param>/// <param name="value"></param>private void WriteSingleRegister(int address, float value){try{// 将浮点数值转换为寄存器值int[] registers = ModbusClient.ConvertFloatToRegisters(value);_ModbusClient.WriteMultipleRegisters(address, registers);GameObject.Find("读取信息").GetComponent<Text>().text += "\n写地址寄存器 Float:" + address + " 写入值:" + value;}catch (Exception e){GameObject.Find("读取信息").GetComponent<Text>().text += "写寄存器失败: " + e.Message;}}/// <summary>/// 判断输入值类型/// </summary>/// <param 输入值="_InputText"></param>/// <returns></returns>public string CheckValueType(string _InputText){string _Result = string.Empty;if (IsInteger(_InputText)){_Result = "整数";}else if (IsFloat(_InputText)){_Result = "浮点数";}else{_Result = "不是有效的整数或浮点数";}return _Result;}/// <summary>/// 输入值 int 判断/// </summary>/// <param 判断值="_Input"></param>/// <returns></returns>private bool IsInteger(string _Input){int _Result;return int.TryParse(_Input, out _Result);}/// <summary>/// 输入值  float 判断/// </summary>/// <param 判断值="_Input"></param>/// <returns></returns>private bool IsFloat(string _Input){float _Result;return float.TryParse(_Input, out _Result);}
}

Unity 场景

Unity 场景 Hierarchy窗口

我是这样搭建的 可以参考一下,比较好调试。当然大家可以自由发挥

在这里插入图片描述

Unity 场景 脚本搭载

这里使用的是 NModbus4_ZH 脚本根据需求进行使用

在这里插入图片描述

Unity 场景 效果

Game 运行窗口

在这里插入图片描述

TIA 和 软件执行情况

在这里插入图片描述

遇到的问题

感觉最好用和最完善的是 NModbus4_ZH 脚本 因为异步和主线程执行方法都有,大家根据需求来使用就行。1. 为什么无法连接上设备?:查看自己的电脑是否和设备处于一个局域网内。2. 为什么无法正确读取 real 类型?:因为在Unity中 real类型类同于float类型,查看是否使用正确的方法。3. 为什么我无法读取40之后的地址?:查看博图软件中授权的读取地址是否大于自己的读取地址。4. 为什么我连接上设备就会变得特别卡?:查看 Update 中是否在持续使用读取方法;也有可能是你再读取的时候正在使用写入方法,注意下数据冲突问题。最好使用异步的方法来进行读写。5. 为什么我读取出来的值跟博图的值对不上?:一般来说是没有这个问题的,除非你接收到数据之后又进行了数据转换。6. 为什么我只能读取 Int 类型?:要注意检查方法啊,一般 int\word 类型就是用 Int读取方法,real类型使用 Float读取方法,写入同理。具体按需求来就行。7. 为什么有的数据我一直在读取,有的时候能读取到,有的时候读取不到?:因为程序运行都有运行时间的,如果不做存储就会丢失。解决方法当然也很简单,就是用队列,来一个存一个就行了,使用完踢出就行了。8. 为什么重启之后就无法用了?:除非特殊情况一般不会出现这个问题,如果经常出现,就要使用 OnApplicationQuit 方法,程序退出之后关闭所有的连接和所有的协程方法。

希望这些信息能够进一步满足您对Untiy Modbus 西门子 S7-1200 基础通信的需求。
如果您有任何特定的问题或需要更深入的讨论,请随时提出。

路漫漫其修远兮,与君共勉。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • wps云字库字体下载到电脑
  • 使用 Prometheus 和 Grafana 监控 FastAPI 服务
  • access函数
  • LabVIEW软件开发的未来是什么?
  • fastadmin 修改弹窗大小
  • WPP多重值绑定
  • 基于JAVA的外来人口管理系统设计与实现,源码、部署+讲解
  • java消息队列ActiveMQ
  • 【Python零基础学习】变量和简单数据类型
  • 【Python】Jupyter Notebook的安装及简单使用
  • 【流媒体协议】RTMP协议概述
  • c++精品小游戏(无错畅玩版)
  • 一文打通pytorch中几个常见的张量操作
  • 第43集《大佛顶首楞严经》
  • 贪吃蛇游戏的实现:C++ 控制台版
  • JavaScript-如何实现克隆(clone)函数
  • 【跃迁之路】【585天】程序员高效学习方法论探索系列(实验阶段342-2018.09.13)...
  • 0x05 Python数据分析,Anaconda八斩刀
  • android 一些 utils
  • EventListener原理
  • golang 发送GET和POST示例
  • Java多线程(4):使用线程池执行定时任务
  • leetcode388. Longest Absolute File Path
  • MySQL数据库运维之数据恢复
  • orm2 中文文档 3.1 模型属性
  • PHP的Ev教程三(Periodic watcher)
  • Python进阶细节
  • Vue ES6 Jade Scss Webpack Gulp
  • Vue--数据传输
  • 从 Android Sample ApiDemos 中学习 android.animation API 的用法
  • 缓存与缓冲
  • 基于axios的vue插件,让http请求更简单
  • 模型微调
  • 使用 Xcode 的 Target 区分开发和生产环境
  • 小程序 setData 学问多
  • 学习JavaScript数据结构与算法 — 树
  • 延迟脚本的方式
  • 哈罗单车融资几十亿元,蚂蚁金服与春华资本加持 ...
  • 整理一些计算机基础知识!
  • #Datawhale X 李宏毅苹果书 AI夏令营#3.13.2局部极小值与鞍点批量和动量
  • #pragma multi_compile #pragma shader_feature
  • (06)Hive——正则表达式
  • (24)(24.1) FPV和仿真的机载OSD(三)
  • (70min)字节暑假实习二面(已挂)
  • (BFS)hdoj2377-Bus Pass
  • (MonoGame从入门到放弃-1) MonoGame环境搭建
  • (pojstep1.1.2)2654(直叙式模拟)
  • (附源码)spring boot校园拼车微信小程序 毕业设计 091617
  • (附源码)计算机毕业设计SSM在线影视购票系统
  • (力扣)循环队列的实现与详解(C语言)
  • (六)vue-router+UI组件库
  • (三)uboot源码分析
  • (十) 初识 Docker file
  • (十八)Flink CEP 详解
  • (提供数据集下载)基于大语言模型LangChain与ChatGLM3-6B本地知识库调优:数据集优化、参数调整、Prompt提示词优化实战