1. Modbus Tcp协议:

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坐标。
在这里插入图片描述

2. 实验目的:

实现主机\从机对任意线圈和寄存器的写入和读取。
这里用到的Dll名称是:NModbus4。 可以在NuGet或者GetHub上下载到。
在这里插入图片描述

3. 主机端:Slave

在这里插入图片描述
上代码:

//  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();
}

4. 从机端:Master

在这里插入图片描述
上代码:

 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); 
 }

5. 写入或者读取浮点类型

写到这里,可能有人会问,接收的数据类型都是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;
 }

6. 使用Modbus Poll测试

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如果有需要可以在下面留言,我看到私你…

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐