Cómo comunicar un M-Duino PLC y un ESP32 PLC con Modbus RTU - 1ª Parte

Descubre cómo funciona el protocolo de comunicación Modbus entre dos controladores industriales: un PLC M-Duino como maestro y un PLC ESP32 como esclavo.
14 de diciembre de 2022 por
Cómo comunicar un M-Duino PLC y un ESP32 PLC con Modbus RTU - 1ª Parte
Boot & Work Corp. S.L., Bernat Brunet Pedra

Introducción

El intercambio de datos entre controladores es muy importante para controlar fácilmente un sistema, y utilizar el protocolo Modbus es una de las mejores soluciones.

El protocolo Modbus RTU se utiliza mundialmente en muchos sectores, como la automatización y la industria, y es muy útil en sistemas maestro-esclavo.

Si quieres saber cómo funciona Modbus, consulta el siguiente post para conocer en profundidad el protocolo. En este post entenderás la comunicación entre un PLC M-Duino como maestro y el PLC ESP32 como esclavo.


Requisitos

Conexión entre PLCs industrailes

Conexión entre PLCs industrailes

Necesitarás un par de cables trenzados para conectar ambos controladores. Utilizando la configuración Half Duplex en el PLC M-Duino, conecta ambos cables a los pines RS485 B- y A+. Estos dos cables deben estar conectados a los pines RS485 del ESP32 también, así:

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

 Recuerda: Tendrás que configurar los interruptores para trabajar con RS485 y ESP32 PLC. Apagando el tercer interruptor, habilitarás la comunicación RS485.
  Comprueba la serigrafía del PLC en caso de duda.

Código M-Duino

Como el PLC basado en M-Duino actuará como maestro, es necesario programarlo para que realice peticiones cada pocos segundos. El programa hará dos tipos de peticiones: Write Single Coil o Write Multiple Coils.

Antes de cargar el código, instala las Industrial Shields boards for Arduino IDE and change the board to your M-Duino PLC model. Remember also to install the Modbus library here.

/* 
    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 <ModbusRTUMaster.h>
#include <RS485.h>
ModbusRTUMaster master(RS485);
uint32_t lastSentTime = 0UL;
const uint32_t baudrate = 38400UL;
//////////////////////////////////////////////////////////////////////////
void setup() {
    Serial.begin(9600);
    RS485.begin(baudrate, HALFDUPLEX, SERIAL_8E1);
    
    master.begin(baudrate);
}
//////////////////////////////////////////////////////////////////////////
void loop() {
    if (millis() - lastSentTime > 1500) {
        if (random() & 0x01) {
            bool values[5];
            for (int i = 0; i < 5; i++) {
                values[i] = random() & 0x01;
            }
            if (!master.writeMultipleCoils(31, 0, values, 5)) {
                // Failure treatment
                Serial.println("Request failed");
            }
        }
        else {
            if (!master.writeSingleCoil(31, 0, random() & 0x01)) {
                // Failure treatment
                Serial.println("Request failed");
            }
        }
        lastSentTime = millis();
    }
    // Check available responses often
    if (master.isWaitingResponse()) {
        ModbusResponse response = master.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 {
                // Get the discrete inputs values from the 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");
                }
            }
        }
    }
}

Fíjate en la función de bucle. Cada 1,5 segundos, hay un 50 % de posibilidades de que el programa envíe una petición writeMultipleCoils() o una writeSingleCoil().

Por un lado, si se eliges writeMultipleCoils(), primero crea un array con 5 valores aleatorios, uno por cada salida digital del controlador esclavo. A continuación se realiza la petición con la dirección del esclavo (31), el primer bucle a escribir a escribir (dirección 0), el array y el número de salidas a disparar.

Por otro lado, si eliges writeSingleCoil(), esta realiza una petición con el número de dirección del esclavo (31), el bucle a escribir (dirección 0) y el valor a escribir (0 ó 1).

Código ESP32

Antes de entrar en el código ESP32, utiliza un código simple para ver lo que es el PLC M-Duino (maestro) de envío.

En primer lugar, cambia la placa a tu modelo de PLC ESP32 en el IDE de Arduino. A continuación, ve a Archivo -> Ejemplos -> RS485 -> Recibir. Cárgalo y abre el Serial Monitor. Si has programado el PLC M-Duino correctamente, verás algo como esto:

Código ESP32

Se trata de una ADU (Unidad de Datos de Aplicación) Modbus. El primer byte muestra la dirección del esclavo, 31 (0x1F) en el ejemplo, y el segundo nos dice qué tipo de petición está intentando hacer el maestro, writeMultipleCoils (0x0F) en este caso.

Luego se utilizan 2 bytes para indicar la primera dirección en la que escribir (0x0000 en este caso). Los dos siguientes indican a cuántas salidas está intentando escribir el maestro (0x0005).

El siguiente (0x01) te da la cuenta de bytes, y el siguiente te dice qué escribir en cada bucle (0x05). 0x05 en binario es 00000101, por lo que sólo se encenderán las salidas 0 y 2 (los bits número 0 y 2 se ponen a 1). Los últimos 2 bytes son para la redundancia crc.

Ahora que ya sabes para qué sirve cada byte en una ADU Modbus, vamos a entrar en el código. Fíjate que no se han usado librerías extra, ya que sólo tienes 2 tipos de peticiones y es muy fácil implementar las funciones::

/* 
    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>
const int slave_address = 31;
int relayOutputsPins[] = {
    R0_4, R0_5, R0_6, R0_7, R0_8,
};
//////////////////////////////////////////////////////////////////////////
void setup() {
    Serial.begin(9600);
    RS485.begin(38400);
}
//////////////////////////////////////////////////////////////////////////
void doWriteMultipleCoils(uint8_t total_pins, uint8_t values) {
    // turn on relays
    for (int i = 0; i < total_pins; i++) {
        (values & (1<<i)) ? digitalWrite(relayOutputsPins[i], HIGH) : digitalWrite(relayOutputsPins[i], LOW);
    }
}
//////////////////////////////////////////////////////////////////////////
void doWriteSingleCoil(uint8_t value) {
    // turn on digital output
    if (value == 255) {
        digitalWrite(Q0_0, HIGH);
        digitalWrite(Q0_1, LOW);
    }
    else {
        digitalWrite(Q0_0, LOW);
        digitalWrite(Q0_1, HIGH);
    }
}
//////////////////////////////////////////////////////////////////////////
void readModbus(uint8_t aux[]) {
    int b = 0;
    int max_bits;
    while (1) {
        if (RS485.available()) {
            byte rx = RS485.read();
            aux[b] = rx;
            if (b == 1) max_bits = (rx == 0x05) ? 8 : 10;
            if (++b == max_bits) break;
        }
    }
}
//////////////////////////////////////////////////////////////////////////
void loop() {
    uint8_t aux[10];
    readModbus(aux);
    if (aux[0] == slave_address) {
        if (aux[1] == 0x05) doWriteSingleCoil(aux[4]);
        else if (aux[1] == 0x0F) doWriteMultipleCoils(aux[5], aux[7]);
    }
    else Serial.println("Bad slave address");
}

Primero, define la dirección del esclavo, 31, igual que en el código maestro. Después de eso, define los 5 pines de salida que deseas activar. En este ejemplo se ha utilizado un PLC ESP32 38R, pero tal vez tu modelo de PLC no tiene salidas de relé, así que tenlo en cuenta y cambia la matriz para que coincida con tus pines de salida.

En la función de configuración, Serial y RS485 se inician con sus propios baudrates (tanto los baudrates del objeto Modbus del PLC M-Duino, como los del RS485 del ESP32 deben ser los mismos). Dicho esto, vamos a saltar directamente a la función de bucle.

Allí, se llama a la función readModbus() que obtendrá todas las ADUs Modbus del maestro y las almacenará en el array llamado aux. Ya identifica entre ADUs de 8 bytes (writeSingleCoil()) y ADUs de 10 bytes (writeMultipleCoils()).

Entonces, todo lo que queda es diferenciar entre escrituras Simples y Múltiples y llamar a la función apropiada con los parámetros necesarios.

Si quieres implementar ,o tipos de peticiones Modbus, simplemente comprueba el código de función para esa petición y añade un "else if (aux[1] == código de función)" debajo de la última y crea otra función para hacer el trabajo, siguiendo la estructura de las funciones ya creadas. Para implementar una petición de lectura, recuerda que necesitarás enviar algunos datos de vuelta al controlador maestro.

Cómo comunicar M-Duino PLC y ESP32 PLC con Modbus RTU - Parte 1

¡Y ya está!

En este post has aprendido cómo funciona Modbus ADU y lo fácil que es procesar peticiones y actuar en consecuencia sin necesidad de librerías externas.

Ahora es el momento de revisar la Parte 2 para aprender el caso contrario, ESP32 PLC como maestro y M-Duino PLC como esclavo.

 

Buscar en nuestro blog

Cómo comunicar un M-Duino PLC y un ESP32 PLC con Modbus RTU - 1ª Parte
Boot & Work Corp. S.L., Bernat Brunet Pedra 14 de diciembre de 2022
Compartir

¿Estás buscando tu Controlador Lógico Programable ideal?

Echa un vistazo a esta comparativa de producto de varios controladores industriales basados en Arduino.

Comparamos entradas, salidas, comunicaciones y otras especificaciones con las de los equipos de otras marcas destacadas.


Industrial PLC comparison >>>