modbus_rtu_maitre.ino
El puerto RS485 de Opta consulta contadores de energía, sondas o variadores en Modbus RTU. La trama maestra FC3 y el CRC16 están construidos a mano: sin dependencias salvo ArduinoRS485, que gestiona la línea de dirección DE/RE del transceptor.
Hardware
Una Opta PLC de STMicroelectronics (STM32H747 en tiempo real + coprocesador seguro i.MX RT1052) programada con el entorno Arduino PLC, alimentada a 24 V DIN. Un esclavo Modbus RTU (sonda, contador) con terminación de 120 Ω en los extremos de la línea.
Cableado
A+ al A+ del esclavo, B- al B-, masa de referencia común en líneas largas. Opta gestiona la dirección (pin ENABLE) automáticamente con RS485Class.
Funcionamiento
El maestro envía dirección+FC3+dirección de registro+cantidad+CRC16; la respuesta se verifica por CRC antes de decodificar los registros de 16 bits. Timeout simple con available().
Ejecución
Instala el gestor de placas Arduino PLC, selecciona «Opta PLC», conecta USB/DFU y vuelca el programa. El sketch corre por el STM32H747 en tiempo real.
Variantes
Escribir consignas con FC6/FC16, consultar varios esclavos por turnos, o reenviar los valores por Modbus/TCP (pasarela RTU↔TCP).
modbus_rtu_maitre.ino
// Opta PLC : maitre Modbus RTU sur le port RS485 intégré (ArduinoRS485).
// Lecture de 2 registres holding (FC3) chez l'esclave d'adresse 1.
#include <ArduinoRS485.h>
RS485Class rs485(Serial1, PIN_SERIAL1_RX, PIN_SERIAL1_TX, PIN_SERIAL1_ENABLE);
uint16_t crc16(const uint8_t *d, int n) {
uint16_t crc = 0xFFFF;
for (int i = 0; i < n; i++) {
crc ^= d[i];
for (int b = 0; b < 8; b++)
crc = (crc & 1) ? (crc >> 1) ^ 0xA001 : crc >> 1;
}
return crc;
}
bool lire(uint8_t esclave, uint16_t adr, uint8_t qte, uint8_t *val) {
uint8_t trame[8] = { esclave, 0x03, adr >> 8, adr & 0xFF, 0, qte };
uint16_t c = crc16(trame, 6);
trame[6] = c & 0xFF; trame[7] = c >> 8;
rs485.begin(9600, SERIAL_8N1);
rs485.beginTransmission(); rs485.write(trame, 8); rs485.endTransmission();
int attendu = 5 + qte * 2;
int n = rs485.available() ? rs485.readBytes((char*)val, attendu) : 0;
return n == attendu && crc16(val, n - 2) == (val[n-1] << 8 | val[n-2]);
}
void setup() {
Serial.begin(115200);
Serial.println("Maitre Modbus RTU 9600 8N1 — esclave 1");
}
void loop() {
uint8_t rep[32];
if (lire(1, 0x0000, 2, rep)) {
uint16_t r0 = rep[3] << 8 | rep[4];
uint16_t r1 = rep[5] << 8 | rep[6];
Serial.printf("ESCLAVE1 reg0=%u reg1=%u\n", r0, r1);
} else {
Serial.println("pas de reponse (verif. cablage A/B, adresse, 9600 8N1)");
}
delay(1000);
}