modbus_rtu_maitre.ino
Le port RS485 de l'Opta interroge compteurs d'énergie, sondes ou variateurs en Modbus RTU. La trame maître FC3 et le CRC16 sont construits à la main : aucune dépendance hors ArduinoRS485, qui gère la ligne de direction DE/RE du transceiver.
Matériel requis
Une Opta PLC STMicroelectronics (STM32H747 temps réel + coprocesseur sécurisé i.MX RT1052) programmée avec l'environnement Arduino PLC, alimentée en 24 V DIN. Un esclave Modbus RTU (sonde, compteur) avec terminaison 120 Ω sur les extrémités de la ligne.
Câblage
A+ vers A+ de l'esclave, B- vers B-, masse de référence commune si ligne longue. L'Opta gère la direction (broche ENABLE) automatiquement via RS485Class.
Fonctionnement
Le maître envoie adresse+FC3+adresse registre+quantité+CRC16 ; la réponse est vérifiée par CRC avant décodage des registres 16 bits. Timeout simple par available().
Exécution
Installez le gestionnaire de cartes Arduino PLC, sélectionnez « Opta PLC », câblez en USB/DFU puis téléversez. Le programme tourne sur le STM32H747 côté temps réel.
Variantes
Écrire des consignes avec FC6/FC16, interroger plusieurs esclaves en tournus, ou remonter les valeurs en Modbus/TCP (passerelle 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);
}