How to communicate M-Duino PLC and ESP32 PLC with Modbus RTU - Part 2
Learn how to communicate open source-based industrial PLCs using Modbus protocol. No additional libraries are needed.
14 December, 2022 by
How to communicate M-Duino PLC and ESP32 PLC with Modbus RTU - Part 2
Boot & Work Corp. S.L., Bernat Brunet Pedra
ESP32 board

Introduction

The aim of this post is to show how easy it is to communicate controllers using the Modbus protocol without the need of additional libraries. 

As a continuation of Part 1, in Part 2, the ESP32 will act as the master controller, and the M-Duino PLC will be the slave, just the opposite of Part 1. Part 2 is focused on the master PLC, the slave controller will use the Industrial Shields libraries to simplify the explanation.

For more information about how to program the slave controller without extra libraries, check Part 1.

Requirements

Connection between controllers

To connect them properly, you will need a pair of twisted pair cables. Using the Half Duplex configuration on the M-Duino PLC, connect both wires to RS485 B- and A+. These two wires must be connected to the RS485 pins of ESP32s as well, like so:

M-Duino PLC pin B- to ESP32 PLC pin B-

M-Duino PLC pin A+ to M-Duino PLC pin A+

Remember that to work with RS485 and ESP32 PLC you will need to configure its switches. Turning off the third switch will enable RS485 communication. Check the serigraphy of the PLC if you have any doubts.

M-Duino code

In this case the M-Duino will be the slave controller, so to implement its program just open the Arduino IDE and go to File -> Examples -> Tools40 -> Modbus -> ModbusRTUSlave. (You can find the Tools40 repository here).

The only modification needed here is to change this line:

RS485.begin(RS485_RATE, HALFDUPLEX, SERIAL_8E1);

for this one:

RS485.begin(RS485_RATE, HALFDUPLEX, SERIAL_8N1);

This will set the RS485 communication to have 8 data bits, no parity and 1 stop bit.

Also, the code uses M-Duino 21+ mapping. If having any other model, change the definition of the array digitalOutputsPins to match all your digital outputs. For example, for M-Duino 58+ PLC, the array should be like this:

int digitalOutputsPins[] = {
#if defined(PIN_Q2_3)
    Q0_0, Q0_1, Q0_2, Q0_3, Q0_4, Q1_0, Q1_1, Q1_2, Q1_3, Q1_4, Q2_0, Q2_1, Q2_2, Q2_3,
#endif
};

The other arrays will not be used, so it makes no sense to modify them.

This code will check for Modbus requests and update its digital and analogue outputs depending on the messages  received automatically. Upload the code to your M-Duino PLC using the Industrial Shields boards, and let's jump into the ESP32 code.

ESP32 code

As seen in Part 1, the Modbus request ADU message is like so:


To send ADUs like in the image above, the following example code can be used. Following the structure from Part 1, two possible request messages can be done: writeSimpleCoil or writeMulipleCoils.

/* 
    Copyright (c) 2018 Boot&Work Corp., S.L. All right 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 <RS485.h>
uint32_t lastSentTime = 0UL;
static bool coilValue = true;
static int n = 0;


/////////////////////////////////////////////////////////
void setup() {
    Serial.begin(9600UL);
    RS485.begin(38400);
}

/////////////////////////////////////////////////////////
uint16_t crc16(const uint8_t *ptr, uint16_t len) {
    int i;
    uint16_t crc = 0xffff;
    
    while(len--) {
        crc ^= *ptr++;
        for (i = 0; i < 8; ++i) {
            crc = (crc & 1) ? ((crc >> 1) ^0xA001) : (crc >> 1);
        }
    }

    return ((crc >> 8) & 0xff) | ((crc << 8) & 0xff00);
}

/////////////////////////////////////////////////////////
void sendWriteSingleCoil(uint8_t slave_address, uint16_t address, bool value) {
    uint8_t aux[8];
    
    aux[0] = slave_address;
    aux[1] = 0x05;
    aux[2] = (address >> 8);
    aux[3] = (address >> 0);
    aux[4] = (value) ? 255 : 0;
    aux[5] = 0;

    uint16_t crc = crc16(aux, 6);

    aux[6] = (crc >> 8);
    aux[7] = (crc >> 0);

    sendRequest(aux, 8);
}

/////////////////////////////////////////////////////////

void sendWriteMultipleCoils(uint8_t slave_address, uint16_t address, const bool *values, uint16_t quantity) {
    uint8_t aux[10];
    uint16_t val = 0;
    
    aux[0] = slave_address;
    aux[1] = 0x0F;
    aux[2] = (address >> 8);
    aux[3] = (address >> 0);
    aux[4] = (quantity >> 8);
    aux[5] = (quantity >> 0);

    for (int i = 0; i < quantity; i++) {
        val += pow(2,i)*(*values++);
    }

    aux[6] = (val >> 8) + 1;
    aux[7] = (val >> 0);

    uint16_t crc = crc16(aux, 8);

    aux[8] = (crc >> 8);
    aux[9] = (crc >> 0);

    sendRequest(aux, 10);
}

/////////////////////////////////////////////////////////
void sendRequest(const uint8_t *mess, uint8_t length) {
    for (int i = 0; i < length; i++) {
        RS485.write(*mess++);
    }
}

/////////////////////////////////////////////////////////
void loop() {
    uint16_t num = 14;
    
    // send a request every 1000 ms
    if (millis() - lastSentTime > 1000) {
        // change the value inside the next if to make a writeSingleCoil request or writeMultipleCoils
        if (0) {
            sendWriteSingleCoil(31, n++, coilValue);
            if (n >= num) {
                n = 0;
                coilValue = !coilValue;
            }
        }
        else {
            bool values[num];
            for (int i = 0; i < num; ++i) {
                values[i] = random() & 0x01;
            }
            sendWriteMultipleCoils(31, 0, values, num);
        }    
        lastSentTime = millis();
    }
}

First, 3 global variables are initialised. Then, Serial and RS485 objects begin with their respective baudrates. RS485's baudrate must be the same as in the M-Duino code, and also the parity (SERIAL_8N1). As this parity is the default, it is not necessary to specify it.

After the setup() function, there is the crc16() function. This function is used to calculate the CRC redundancy for the request's complete message. Because the CRC redundancy can be calculated using different algorithms, it is important to make the same operations as the slave controller.

Then, the functions whose objective is to make the respective request are defined. Each one is the same size as seen in part Part 1 (writeSingleCoil() with 8 bytes size and writeMultipleCoils() with 10 bytes size), and have the same utility as the ones implemented in the ModbusRTUMaster library. Finally, there is the sendRequest() function that sends the corresponding request to the RS485 port.

In the loop() function, a request is done every second. It may be writeSingleCoil() or writeMultipleCoils(), depending on the value inside the second "if" conditional. 

On the first hand, if that conditional is true, then writeSingleCoil is chosen. With this, every second a new digital output will be turned on for a total of num outputs. As in our case an M-Duino 58+ PLC is used, the total number is 14, so one loop will be 14 seconds in total, one for each digital output.

Change that value to adjust your total numbers of digital outputs if having any other model. After one loop has been done, the coilValue will toggle and then it will repeat the sequence but turning off all outputs. This sequence will repeat infinitely.

On the other hand, if the conditional value is false, then writeMultipleCoils is selected. In this case, an array of random numbers is generated to then make a request, requesting the slave controller to trigger all digital outputs, depending on the value of each element of the array.

For an array like the following: { 1 , 0 , 1 , 1 , 0 }, digital outputs number 0, 2 and 3 will turn on and outputs 1 and 4 will turn off. Remember to change the value of num, depending on the number of digital outputs in your PLC.

Conclusions

In these two posts, a brief explanation on how requests are done in Modbus protocol communication has been given to understand Modbus easiest and simplest applications, but this is just the start.

After reading Part 1 and Part 2, you are now ready to jump into using more complex Modbus libraries and use the protocol at its maximum potential!

Find what you are looking for
Search in our Blog

How to communicate M-Duino PLC and ESP32 PLC with Modbus RTU - Part 2
Boot & Work Corp. S.L., Bernat Brunet Pedra
14 December, 2022
Share this post
Archive

Looking for your ideal Programmable Logic Controller?

Take a look at this product comparison with other industrial controllers Arduino-based. 

We are comparing inputs, outputs, communications and other features with the ones of the relevant brands.


Industrial PLC comparison >>>

Do you want more information?

Just fill the form!

Tell me more!