Modbus over TCP is nothing but the Modbus protocol over a TCP/IP interface running on Ethernet. Therefore all device addressing and routing is done through the standard IP addresses while the application protocol remains the same.

In case of Modbus protocol, only one device in the network usually acts as a master and others act as slaves. That mean, only the master device can send out commands to the slave devices. Sending out commands means reading from or writing to the registers or a sort of variable reference in the slave device. Also the slave device can read its own variable references and can act accordingly. Thus the data exchange takes place.

In this post we will learn how to use a .Net library to communicate b/w two Ethernet devices that could be PCs or PLCs. We will be developing both a Modbus master as will as a Modbus client and will try to enable communication b/w the two.

However, before starting up our development, lets just have a look at the protocol itself. In case of Modbus protocol, devices have registers that can be called variable references and they are of the following 4 types:

Discrete Outputs or Coils: These are read/write registers/references that hold only a single bit each.

Discrete Inputs: These are read only registers/references and these too hold only a single bit each. Since they are read only, master devices can read data from these references but can’t change them.

Input Registers (Input Data): These are similar to Discrete Input in that these are read only but can hold up to 16 bits each.

Holding Registers (Output Data): These are similar to Discrete output in that these are read/write but can hold up to 16 bits each.

Now that we understand the protocol, let’s fire up our Visual Studio. We will we using the NModbus4 nuget package. There are several other libraries as well that can be used. However the basic idea remains the same.

Below is the sample code for the master device and the slave device

Master

class Program
{
 static void Main(string[] args)
 {
  Console.WriteLine("Master device running!");
  string ipAddr = "127.0.0.1"; // IP Address of the device to connect to
  int tcpPort = 502; // Port to connect to

  // Create a tcp client and connect to the device
  TcpClient tcpClient = new TcpClient();
  tcpClient.BeginConnect(ipAddr, tcpPort, null, null);

  // Create modbus master device on the tcp client
  ModbusIpMaster master = ModbusIpMaster.CreateIp(tcpClient);

  ushort startRef, noOfRefs;
  // Discrete Outputs or Coils: Read/write single bit references
  // Read
  startRef = 0; // Discrete output to start reading from
  noOfRefs = 5; // Number of registers to read
  bool[] dOutputs = master.ReadCoils(startRef, noOfRefs); // dOutputs contain all the bits stored at the address

  // Write
  master.WriteMultipleCoils(startRef, new bool[] { true, false, true, false, true});

  // Discrete Inputs: Read only single bit references
  // Read
  bool[] dInputs = master.ReadInputs(startRef, 5);
  string inputStr = String.Join(" | ", dInputs);
  Console.WriteLine("Discrete Input -- " + inputStr);

  // Holding Registers (Output Data): Read/write only single 16 bit references
  // Read
  ushort[] outputRegisterData = master.ReadHoldingRegisters(startRef, noOfRefs);
  
  // Write
  master.WriteMultipleRegisters(startRef, new ushort[] {5, 10, 15, 12, 8});


  // Input Registers (Input Data): Read only single 16 bit references
  // Read
  ushort[] inputRegisterData = master.ReadInputRegisters(startRef, noOfRefs);
  string registerStr = String.Join(" | ", inputRegisterData);
  Console.WriteLine("Input Registers -- " + registerStr);

  Console.WriteLine("Press enter to exit!");
  Console.ReadLine();
 }
}

Slave

class Program
{
 static void Main(string[] args)
 {
  Console.WriteLine("Slave device running!");
  string ipAddr = "127.0.0.1"; // IP Address of the slave
  int tcpPort = 502; // Communication port

  // Create a TCP listener for the ip and port
  TcpListener listener = new TcpListener(System.Net.IPAddress.Parse(ipAddr), tcpPort);
  // Create a slave device for the TCP listener
  ModbusTcpSlave slave = ModbusTcpSlave.CreateTcp(1, listener);
  // Assign an event handler for the slave request received event
  // Will be fired whenever a new read/write request is made to the slave device
  slave.ModbusSlaveRequestReceived += Slave_ModbusSlaveRequestReceived;
  // Start listening
  slave.Listen();

  // setting discrete inputs (read only for master)
  slave.DataStore.InputDiscretes[1] = true;
  slave.DataStore.InputDiscretes[2] = true;

  // setting input registers (read only for master)
  slave.DataStore.InputRegisters[1] = 12;
  slave.DataStore.InputRegisters[3] = 7;

  Console.WriteLine("Press enter to exit!");
  Console.ReadLine();
 }


 private static void Slave_ModbusSlaveRequestReceived(object sender, ModbusSlaveRequestEventArgs e)
 {
  ModbusTcpSlave slave = sender as ModbusTcpSlave;
  Console.WriteLine("Request received!! " + e.Message.FunctionCode);

  // Discrete Outputs
  IEnumerable<bool> dOutput = slave.DataStore.CoilDiscretes.Skip(1).Take(5);
  string outputStr = String.Join(" | ", dOutput);
  Console.WriteLine("Discrete output -- " + outputStr);

  // Holding Registers
  IEnumerable<ushort> holdingRegisters = slave.DataStore.HoldingRegisters.Skip(1).Take(5);
  string registerStr = String.Join(" | ", holdingRegisters);
  Console.WriteLine("Holding registers -- " + registerStr);
  Console.WriteLine("\n");
 }
}

In order to test the above samples, run the slave device program first. And then run the master device program. As soon the master runs and sends commands to the slave device, the request event handler on the slave runs and prints the device registers/references. The following is the expected output.
Slave:

Master:

For the above sample to work, make sure that the communication port is open.

Categories: DotNet

0 Comments

Leave a Reply

Your email address will not be published. Required fields are marked *