ModBus Tcp是基于TCP/IP的报文协议,采用主\从方式通信,但是主从之间的端口是固定的:502
ModBus地址:由5位数字组成(PS:40001-49999表示HoldingRegister),包括起始数据类型代号,以及后面的偏移地址
寄存器:
CiolRegister(线圈寄存器)占一个位Bit,数据范围0-1之间,在C#中表示一个Bool类型;
ps:可以用来做设备控制点位,以及状态字。
HoldingRegister(保持寄存器)占2个Byte,16Bit,数据范围:0-65535之间,在C#一个HR记作一个ushort,表示一个无符号(正数)16位整数;
ps:视觉定位中发送XYR坐标。
实现主机\从机对任意线圈和寄存器的写入和读取。
这里用到的Dll名称是:NModbus4。 可以在NuGet或者GetHub上下载到。
// Modbus TCP
using Modbus.Device;
using System.Net.Sockets;
using System.Net;
using Modbus.Data;
using System.Threading;
using Modbus.Utility;
private TcpListener listener;
private ModbusSlave slave;
//主机端监听端口
private void btn_SlaveListen_Click(object sender, EventArgs e)
{
listener = new TcpListener(IPAddress.Parse(txt_SlaveIP.Text), (int)nud_SlavePort.Value);
listener.Start();
slave = ModbusTcpSlave.CreateTcp(1, listener);
//创建寄存器存储对象
slave.DataStore = DataStoreFactory.CreateDefaultDataStore();
//订阅数据到达事件,可以在此事件中读取寄存器
slave.DataStore.DataStoreWrittenTo += new EventHandler<DataStoreEventArgs>((obj, o) =>
{
switch (o.ModbusDataType)
{
case ModbusDataType.Coil: //code 5
ModbusDataCollection<bool> discretes = slave.DataStore.CoilDiscretes;
if (ckb_CD_1.InvokeRequired)
{
this.BeginInvoke(new Action(delegate
{
ckb_CD_1.Checked = discretes[1];
}));
}
break;
case ModbusDataType.HoldingRegister: //code 15
ModbusDataCollection<ushort> holdingRegisters = slave.DataStore.HoldingRegisters;
if (txt_HR_1.InvokeRequired)
{
this.BeginInvoke(new Action(delegate
{
txt_HR_1.Text = holdingRegisters[1].ToString();
}));
}
break;
}
});
//此事件,待补充
slave.ModbusSlaveRequestReceived += new EventHandler<ModbusSlaveRequestEventArgs>((obj, o) =>
{
});
//此事件,待补充
slave.WriteComplete += new EventHandler<ModbusSlaveRequestEventArgs>((obj, o) =>
{
});
slave.Listen();
}
//写入寄存器
private void btn_SlaveSend_Click(object sender, EventArgs e)
{
//CoilDiscretes表示一个Bit,也就是一个bool类型
slave.DataStore.CoilDiscretes[(int)nud_SlaveCoilAds.Value] = nud_SlaveCoilVal.Value == 1 ? true : false;
//HoldingRegisters表示一个无符号的16位整数(2的16次幂:0-65535)
slave.DataStore.HoldingRegisters[(int)nud_SlaveHRAds.Value] = (ushort)nud_SlaveHRVal.Value;
}
//停止监听
private void btn_SlaveStop_Click(object sender, EventArgs e)
{
slave.Dispose();
}
private TcpClient client;
private ModbusIpMaster master;
//连接
private void btn_MasterConnect_Click(object sender, EventArgs e)
{
client = new TcpClient();
client.Connect(IPAddress.Parse(txt_SlaveIP.Text.Trim()), (int)nud_SlavePort.Value);
master = ModbusIpMaster.CreateIp(client);
}
//写入寄存器
private void btn_MasterSend_Click(object sender, EventArgs e)
{
master.WriteSingleCoil((ushort)nud_MasterCoilAds.Value, nud_MasterCoilVal.Value == 1 ? true : false);
master.WriteSingleRegister((ushort)nud_MasterHRAds.Value, (ushort)nud_MasterHRVal.Value);
}
//定时器中循环读取线圈和寄存器的值,当然,你也可以使用异步的方式
private void timer1_Tick(object sender, EventArgs e)
{
bool[] coils = master.ReadCoils(1, 0, 9);
ushort[] holding_register = master.ReadHoldingRegisters(1, 0, 9);
}
写到这里,可能有人会问,接收的数据类型都是ushort类型,那万一对方发送的是负数或者浮点型该怎么办?再或者需要发送float类型怎么办?
答案:这里需要将ushort[]和float类型做互转。
//将接收到的ushort[]转换为float类型
private float GetFloatFormUshortArray(ushort[] val)
{
if (val != null && val.Length == 2)
{
List<byte> result = new List<byte>();
result.AddRange(BitConverter.GetBytes(val[0]));
result.AddRange(BitConverter.GetBytes(val[1]));
return BitConverter.ToSingle(result.ToArray(), 0);
}
else
{
return 0.0f;
}
}
//将float类型分解成两个ushort类型
public void SetValue32(int offset, float value)
{
ushort lowOrderValue = BitConverter.ToUInt16(BitConverter.GetBytes(value), 2);
ushort highOrderValue = BitConverter.ToUInt16(BitConverter.GetBytes(value), 0);
ModbusDataCollection<ushort> data = slave.DataStore.HoldingRegisters;
data[offset] = lowOrderValue;
data[offset + 1] = highOrderValue;
}
Modbus Poll写入浮点数到Slave端:
读取代码并写入UI的代码:注意这里传入GetFloatFormUshortArray()函数的寄存器顺序是反的。
txt_HrFloat1.Text = Math.Round(GetFloatFormUshortArray(new ushort[] {
holdingRegisters[2], holdingRegisters[1] }), 2).ToString();
txt_HrFloat2.Text = Math.Round(GetFloatFormUshortArray(new ushort[] {
holdingRegisters[4], holdingRegisters[3] }), 2).ToString();
txt_HrFloat3.Text = Math.Round(GetFloatFormUshortArray(new ushort[] {
holdingRegisters[6], holdingRegisters[5] }), 2).ToString();
txt_HrFloat4.Text = Math.Round(GetFloatFormUshortArray(new ushort[] {
holdingRegisters[8], holdingRegisters[7] }), 2).ToString();
Slave端写入浮点数到Modbus Poll测试工具:
Slave写入时已经在Set32Value函数中进行了CDAB顺序的转换,所以Modbus Poll端正常使用ABCD顺序读取就可以。
float f = -3.14f;
Set32Value(1, f);
以上关于NModbus4的DLL如果有需要可以在下面留言,我看到私你…