Modbus是一个免费的协议,协议设计简单,有很多成熟的库支持。比如C#版本的NModubs4就很好,入门简单,使用方便。
首先,在工程中使用NuGet添加NModbus4的包。
在使用文件中,添加引用。不同的接口模式,引用对象不同,Modbus Slave TCP模型需要以下几项:
using ;
using ;
using ;
using ;
初始化大概有几个步骤:创建TCP Listener对象、创建ModbusTcpSalve对象、启动侦听服务。
TcpListener listener = new TcpListener(("0.0.0.0"), 502);
();
ModbusTcpSlave modbusSlave = (1, listener);
//创建寄存器存储对象
= ();
Modbus有个DataStore对象,用来存储数据,对应的状态的地址如下:
//01 Coil Status, Addr: 00001
ModbusDataCollection<bool> coilDiscretes = ;
//02 Input Status, Addr: 10001
ModbusDataCollection<bool> inputDiscretes = ;
//03 Holding Register, Addr: 40001
ModbusDataCollection<ushort> holdingRegisters = ;
//04 Input Register, Addr: 30001
ModbusDataCollection<ushort> InputRegisters = ;
定义之后,就可以直接通过这些对象读写数据了。
NModbus4库已经封装了对于TCP访问数据的封装,服务端只需要维护数据更新即可。
受Modbus协议限制,HoldingRegisters和InputRegisters每个地址仅16位2个字节长度。当实际数据值超过2个字节时,需要占用多个地址空间。比如int32,float都需要4个字节,两个地址。这些数据怎么存放,高低位怎么对齐,就产生了所谓的ABCD、CDAB等转换,这个Master与Slave一致就可以。
Int32等类型数据更新时,需要同时对多个存储地址进行更新,而master可能还在高速并发读数据,需要注意使用一个同步锁,避免数据更新一半就被读取,产生数据不一致问题。
//将float类型分解成两个ushort类型
public void SetValue32(ModbusDataCollection<ushort>data,int offset, float value)
{
lock ()
{
data[offset] = BitConverter.ToUInt16((value), 0);
data[offset + 1] = BitConverter.ToUInt16((value), 2);
}
}
//将int32类型分解成两个ushort类型
public void SetValue32(ModbusDataCollection<ushort> data, int offset, Int32 value)
{
lock ()
{
data[offset] = BitConverter.ToUInt16((value), 0);
data[offset + 1] = BitConverter.ToUInt16((value), 2);
}
}
Modbus对于字符串,并没有明确约定。推进使用Unicode编码,首先是与OPC的默认字符串编码格式一致,其次的每个中英文都占用一个地址2个字节,方便计算长度。
public void SetValueString(ModbusDataCollection<ushort> data, int offset, string value)
{
const int maxStringLen = 20;
lock ()
{
byte[] dst = new byte[ * 2];
byte[] bb = (value);
for (int i = 0; i < && i < ; i++)
{
dst[i] = bb[i];
}
for (int i = 0; i < maxStringLen; i += 2)
{
data[offset + i / 2] = BitConverter.ToUInt16(dst, i);
}
}
}