modbus_rtu_maitre.ino
Opta's RS485 port polls energy meters, probes or drives over Modbus RTU. The FC3 master frame and CRC16 are hand-built: no dependency beyond ArduinoRS485, which handles the DE/RE direction line of the transceiver.
Hardware
An STMicroelectronics Opta PLC (real-time STM32H747 + secure i.MX RT1052 companion) programmed with the Arduino PLC environment, 24 V DIN-rail supply. One Modbus RTU slave (probe, meter) with 120 Ω termination at the line ends.
Wiring
A+ to slave A+, B- to B-, common reference ground on long runs. Opta handles direction (ENABLE pin) automatically through RS485Class.
How it works
The master sends address+FC3+register address+count+CRC16; the reply is CRC-checked before decoding the 16-bit registers. Simple timeout via available().
Running it
Install the Arduino PLC boards manager, select “Opta PLC”, wire USB/DFU and upload. The sketch runs on the real-time STM32H747 side.
Variants
Write setpoints with FC6/FC16, poll several slaves round-robin, or forward values over Modbus/TCP (RTU↔TCP gateway).
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);
}