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
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:
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.
¡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.
Cómo comunicar un M-Duino PLC y un ESP32 PLC con Modbus RTU - 1ª Parte