最近一直在工业领域干活,学习下Modbus协议,这里做个记录,理解不对的地方希望大佬指出修正。
一、先上测试工具和Unity脚本。
1.测试工具使用的 Modsim32
2.Unity测试脚本如下
/*
· 0x01:读线圈
· 0x05:写单个线圈
· 0x0F:写多个线圈
· 0x02:读离散量输入
· 0x04:读输入寄存器
· 0x03:读保持寄存器
· 0x06:写单个保持寄存器
· 0x10:写多个保持寄存器
*/
using Modbus.Device;
using Modbus.Extensions.Enron;
using System;
using System.Collections;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using Unity.Collections.LowLevel.Unsafe;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.Networking;
using UnityEngine.UI;
/// <summary>
/// Modbus Tcp/IP
/// </summary>
public class TestModBus : MonoBehaviour
{
[Header("设备ID")]
public byte deviceId;
[Header("读取起始位置")]
public ushort readStart;
[Header("读取的长度")]
public ushort readLength;
[Header("写入的起始位置")]
public ushort writeStart;
[Header("写入的数据")]
public ushort[] udata = new ushort[] { 100 };
public bool[] coils;
[Header("区域类型")]
public ModbusPointType modbusPointType;
public ModbusMaster modbusIpMaster;
public TcpClient tcpClient;
[Header("连接IP")]
IPAddress address = new IPAddress(new byte[] { 127, 0, 0, 1 });
[Header("连接端口")]
public int port = 502;
// Start is called before the first frame update
void Start()
{
if (Connect(address.ToString(), port))
{
Debug.Log("连接成功");
}
else
{
Debug.Log("连接失败");
}
}
public bool Connect(string ip, int port)
{
try
{
tcpClient = new TcpClient(ip, port);
tcpClient.SendTimeout = 1;
modbusIpMaster = ModbusIpMaster.CreateIp(tcpClient);
return true;
}
catch (Exception ex)
{
if (tcpClient!=null)
{
tcpClient.Close();
}
Debug.LogError(ex.Message);
return false;
}
}
private void OnDestroy()
{
if (tcpClient!=null)
{
tcpClient.Close();
}
}
/// 10进制转16进制
private byte GetHex(string msg)
{
byte hex = Convert.ToByte(msg);
return hex;
}
///16进制转10进制
public int GetDex(string msg)
{
int res = Convert.ToInt32(msg, 16);
return res;
}
//退出的时候关闭连接
private void OnApplicationQuit()
{
if (tcpClient!=null)
{
tcpClient.Close();
}
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.W))
{
WriteData(modbusPointType);
//WriteMultipleRegisters(设备地址,寄存器起始地址, 要写入的值)
//modbusIpMaster.WriteMultipleRegisters(0x02, star, udata);
}
if (Input.GetKeyDown(KeyCode.R))
{
ReadData(modbusPointType);
//ReadHoldingRegisters(设备地址, 寄存器起始地址, 读取数量); 读取多个寄存器
//ushort[] msg = modbusIpMaster.ReadHoldingRegisters(0x01, 1, 0x06);
}
}
/// <summary>
/// (设备地址,寄存器起始地址, 要写入的值)
/// </summary>
/// <param name="type"></param>
private void WriteData(ModbusPointType type)
{
switch (type)
{
case ModbusPointType.Coil:
modbusIpMaster.WriteMultipleCoils(deviceId, writeStart, coils);
for (int i = 0; i < coils.Length; i++)
{
Debug.LogFormat("Coil, {0},写入数据={1}", i, coils[i]);
}
break;
case ModbusPointType.HoldRegister:
modbusIpMaster.WriteMultipleRegisters(deviceId, writeStart, udata);
for (int i = 0; i < udata.Length; i++)
{
Debug.LogFormat("HoldRegister, {0},写入数据={1}", i, udata[i]);
}
break;
default:
break;
}
}
/// <summary>
/// (设备地址, 寄存器起始地址, 读取数量)
/// </summary>
/// <param name="type"></param>
private void ReadData(ModbusPointType type)
{
bool[] msgBool;
ushort[] msgShort;
switch (type)
{
case ModbusPointType.Coil:
msgBool = modbusIpMaster.ReadCoils(deviceId, readStart, readLength);
for (int i = 0; i < msgBool.Length; i++)
{
Debug.LogFormat("Coil, {0},接收到的数据={1}", i,msgBool[i]);
}
return;
case ModbusPointType.Input:
msgBool = modbusIpMaster.ReadInputs(deviceId, readStart, readLength);
for (int i = 0; i < msgBool.Length; i++)
{
Debug.LogFormat("Input, {0},接收到的数据={1}", i, msgBool[i]);
}
break;
case ModbusPointType.InputRegister:
msgShort = modbusIpMaster.ReadInputRegisters(deviceId, readStart, readLength);
for (int i = 0; i < msgShort.Length; i++)
{
Debug.LogFormat("InputRegister, {0},接收到的数据={1}", i, msgShort[i]);
}
break;
case ModbusPointType.HoldRegister:
msgShort = modbusIpMaster.ReadHoldingRegisters(deviceId, readStart, readLength);
for (int i = 0; i < msgShort.Length; i++)
{
Debug.LogFormat("HoldRegister, {0},接收到的数据={1}", i, msgShort[i]);
}
break;
default:
break;
}
}
}
public class IPTest
{
public string ip;
}
public enum ModbusPointType
{
Coil,//线圈 发送的二进制格式 可读写
Input,//离散量输入 发送的二进制格式 只读
InputRegister,//输入寄存器 发送的ushort格式 只读
HoldRegister//保持寄存器 发送的ushort格式 可读写
}
二、连接创建。
1.创建连接,上方菜单栏点击连接设置,选择Modbus链接,其他的猜测是串口通信测试,没做具体研究。
2.大概百度学习了下,我理解的modbus是通过TCP链接使用的一种通信协议。
3.代码中直接使用的SocketTCP相关的代码创建的链接,只不过我之前使用的都是私有协议,自己定义消息协议,消息头、消息体之类的,modbus是大厂制定的一种通用消息协议,被沿用至今。
三、四种Point Type,代码中已经注释写明。
1.个人理解是四种数据类型,两个都可读,两个可写,可传输的值不同,适配不同的使用需求。
2.经过测试,向Coil中写入数据,再从Input中可读取到上一步写入的数据。
3.以下是对实际应用猜测,也是学习中不太确定的地方,希望有专业人士指导。
理解实际应用中比如报警状态识别功能,由硬件设备向Coil中写入报警状态,然后显示层可以从Input中读取对应的位置,这样避免显示层对报警状态做出修改,只有硬件本身可以设置报警状态,所以采用二进制的数据格式就可以了。
HoldRegister可以存储更多的数据,可用来记录复杂的操作,比如操作流程,或者可编程式的运行规则,同样是HoldRegister写入后,InputRegister提供了只读的访问方法,可以访问到HoldRegister写入的数据。
四、数据发送读取。
代码中已经封装读写功能,基本思路就是选择设备id,start起始位置,然后对应设置写入的数值,数值长度不能超过测试软件设置的数据结构的count大小。这个就类似socketTCP中拼协议,从索引第几位开始,写入多长的数据。