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到计算机需要特别的接口,通常是支持ISA(SA85),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值转换为int
int _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 register
return 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 register
return 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 到地址 40016
byte[] _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 到地址 40016
byte[] _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 还是 float
string _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值转换为int
int _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 register
return 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 register
return 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 还是 float
string _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 基础通信的需求。
如果您有任何特定的问题或需要更深入的讨论,请随时提出。
路漫漫其修远兮,与君共勉。