modbus_rtu_maitre.ino

Arduino Opta (micro PLC, STM32H747) · File : modbus_rtu_maitre.ino · Language : Arduino · Use case : Interroger un instrument en Modbus RTU sur RS485

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);
}

← Back to the board