Look for in our Blog. You will find multiple applications, solutions, code examples. Navigate using the tag cloud or search using specific criteria

Modbus TCP Master with Industrial Arduino based PLC's

Introduction

Modbus is a standard serial communication protocol that has been used for PLC's since 1979. Modbus allow the communication between devices in the same network. In this case it is showed how to use a Modbus TCP Industrial Shields library.  Modbus TCP/IP is a variant of Modbus that is used for communications over TCP/IP networks and over the port 502. 

Modbus object types:


Object typeAccessSize
CoilRead-write1bit
Discrete inputRead1 bit
Input registerRead16 bits
Holding registersRead-write 16 bits

Normally coils a are used to write digital values to an outputs. Discrete inputs are used to read digital inputs. Registers are use to communicate data between the devices and also usually used for analog I/Os. 


Requirements

Modbus Library: Tools40 library (include Modbus)

M-Duino family PLC:  M-Duino (Ethernet family)


Modbus TCP master functions

The functions to read and write slave values are:

readCoils(client, slave_address, address, quantity);
readDiscreteInputs(client, slave_address, address, quantity);
readHoldingRegisters(client, slave_address, address, quantity);
readInputRegisters(client, slave_address, address, quantity);
writeSingleCoil(client, slave_address, address, value);
writeSingleRegister(client, slave_address, address, value);
writeMultipleCoils(client, slave_address, address, values, quantity);
writeMultipleRegisters(client, slave_address, address, values, quantity);

Where

  • client is the EthernetClient connected to the slave.
  • slave_address is the Modbus TCP slave address.
  • address is the coil, digital input, holding register or input register address. Usually this address is the coil, digital input, holding register or input register number minus 1: the holding register number 40009 has the address 8.
  • quantity is the number of coils, digital inputs, holding registers or input registers to read/write.
  • value is the given value of the coil or holding registers on a write operation. Depending on the function the data type changes. A coil is represented by a bool value and a holding register is represented by a uint16_t value.

On a multiple read/write function the address argument is the first element address. On a multiple write function the valuesargument is an array of values to write.

It is important to notice that these functions are non-blocking, so they don't return the read value. They return true or falsedepending on the current module state. If there is a pending Modbus request or the client is not connected, they return false.

// Read 5 holding registers from address 0x24 of slave with address 0x10
if (master.readHoldingRegisters(client, 0x10, 0x24, 5)) {
	// OK, the request is being processed
} else {
	// ERROR, the master is not in an IDLE state
}

There is the available() function to check for responses from the slave.

ModbusResponse response = master.available();
if (response) {
	// Process response
}

The ModbusResponse implements some functions to get the response information:

hasError();
getErrorCode();
getSlave();
getFC();
isCoilSet(offset);
isDiscreteInputSet(offset);
isDiscreteSet(offset);
getRegister(offset);
ModbusResponse response = master.available();
if (response) {
	if (response.hasError()) {
		// There is an error. You can get the error code with response.getErrorCode()
	} else {
		// Response ready: print the read holding registers
		for (int i = 0; i < 5; ++i) {
			Serial.println(response.getRegister(i);
		}
	}
}

The possible error codes are:

0x01 ILLEGAL FUNCTION
0x02 ILLEGAL DATA ADDRESS
0x03 ILLEGAL DATA VALUE
0x04 SERVER DEVICE FAILURE


Software

After seeing the bases of the Modbus TCP Master library we can proceed to develop a code to communicate with another Modbus device in our network. In this code is showed how to read registers and how to write coils from another Modbus TCP/IP slave. This example will write random numbers to digital coils every second and also will read 6 values from the slave every 500 milliseconds. 


/*
   Copyright (c) 2018 Boot&Work Corp., S.L. All rights reserved
   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU Lesser General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Lesser General Public License for more details.
   You should have received a copy of the GNU Lesser General Public License
   along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include <ModbusTCPMaster.h>
#if defined(MDUINO_PLUS)
#include <Ethernet2.h>
#else
#include <Ethernet.h>
#endif

// Ethernet configuration values
uint8_t mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(10, 10, 10, 3);
IPAddress slaveIp(10, 10, 10, 4);
uint16_t slavePort = 502;

// Define the ModbusTCPMaster object
ModbusTCPMaster modbus;

// Ethernet client object used to connect to the slave
EthernetClient slave;
uint32_t lastSentTime = 0UL;
uint32_t lastSentTimeReadInputs = 0UL;

////////////////////////////////////////////////////////////////////////////////////////////////////
void setup() {
  Serial.begin(9600UL);
  // Begin Ethernet
  Ethernet.begin(mac, ip);
  Serial.println(Ethernet.localIP());
  // NOTE: it is not necessary to start the modbus master object
}

////////////////////////////////////////////////////////////////////////////////////////////////////
void loop() {
  // Connect to slave if not connected
  // The ethernet connection is managed by the application, not by the library
  // In this case the connection is opened once
  if (!slave.connected()) {
    slave.stop();
    slave.connect(slaveIp, slavePort);
    if (slave.connected()) {
      Serial.println("Reconnected");
    }
  }
  // Send a request every 1000ms if connected to slave
  if (slave.connected()) {
    if (millis() - lastSentTime > 1000) {
      // Set random values
      bool values[5];
      for (int i = 0; i < 5; ++i) {
        values[i] = random() & 0x01;
      }
      // Send a Write Multiple Coils request to the slave with address 31
      // It requests for setting 5 coils starting in address 0
      // IMPORTANT: all read and write functions start a Modbus transmission, but they are not
      // blocking, so you can continue the program while the Modbus functions work. To check for
      // available responses, call modbus.available() function often.
      if (!modbus.writeMultipleCoils(slave, 31, 0, values, 5)) {
        // Failure treatment
        Serial.println("Request fail");
      }
      lastSentTime = millis();
    }
    // Check available responses often
    if (modbus.isWaitingResponse()) {
      ModbusResponse response = modbus.available();
      if (response) {
        if (response.hasError()) {
          // Response failure treatment. You can use response.getErrorCode()
          // to get the error code.
          Serial.print("Error ");
          Serial.println(response.getErrorCode());
        } else {
          Serial.println("Done");
        }
      }
    }
    
    if (millis() - lastSentTimeReadInputs > 500) {
      // Send a Read Input Registers request to the slave with address 31
      // It requests for 6 registers starting at address 0
      // IMPORTANT: all read and write functions start a Modbus transmission, but they are not
      // blocking, so you can continue the program while the Modbus functions work. To check for
      // available responses, call master.available() function often.
      if (!modbus.readInputRegisters(slave, 31, 0, 6)) {
        // Failure treatment
      }
      lastSentTimeReadInputs = millis();
    }
    
    if (modbus.isWaitingResponse()) {
      ModbusResponse response = modbus.available();
      if (response) {
        if (response.hasError()) {
          // Response failure treatment. You can use response.getErrorCode()
          // to get the error code.
        } else {
          // Get the input registers values from the response
          Serial.print("Input registers values: ");
          for (int i = 0; i < 6; ++i) {
            Serial.print(response.getRegister(i));
            Serial.print(',');
          }
          Serial.println();
        }
      }
    }
  }
}

Do you want more information?

Open Source technology allows you to develop your installations.

Just fill the form and we will contact you as soon as we can.

Send