Opta WiFi RS485: Modbus-Request funktioniert, Reply kommt nicht an

Hallo zusammen,

ich versuche, mit einem Arduino Opta WiFi über die eingebaute RS485-Schnittstelle einen A6-RS Servo Drive per Modbus RTU anzusprechen.

Mein Ziel ist, über RS485 Parameter im Servo zu setzen und später damit den Servo zu steuern.

Aufbau

  • Controller: Arduino Opta WiFi

  • Gegenstelle: A6-RS Servo Drive

  • Verbindung: RS485 direkt vom Opta zum Servo

  • Verdrahtung:

    • Opta B(+) → Servo RS485+
    • Opta A(-) → Servo RS485-
    • Opta GND → Servo GND

Was funktioniert

Ich sende vom Opta per RS485/Modbus RTU einen Write-Befehl auf den Servo:

  • Parameter: C00.00
  • Wert: 1
  • Modbus-Frame:
    01 06 00 00 00 01 48 0A

Der Parameter wird tatsächlich korrekt gesetzt.

Das heißt für mich:

  • Slave-Adresse, CRC und Registeradresse stimmen
  • die Verdrahtung ist zumindest für Opta → Servo korrekt
  • der Servo verarbeitet den Request

Was nicht funktioniert

Der Servo sollte laut Handbuch bei Function Code 0x06 mit einem Echo desselben Frames antworten.

Genau diese Antwort bekomme ich am Opta aber nie erkannt.

Serielle Ausgabe z. B.:

10:54:42.088 -> TX: 01 06 00 00 00 01 48 0A 
10:54:42.251 -> Keine Antwort erkannt.

Noch interessanter:
Der Parameter wird trotzdem korrekt gesetzt.

Bereits getestet

Ich habe schon einiges ausprobiert:

  • verschiedene Delay-/Timeout-Werte nach dem Senden
  • RS485.flush()
  • längeres Warten auf Antwort
  • Lesen frameweise mit Timeout
  • Servo-Parameter C0A.03 (Modbus response time) testweise auf 20 ms gesetzt
  • trotzdem keine Antwort erkannt

Beispiel:

11:01:45.926 -> 1) Setze C0A.03 = 20 ms
11:01:46.406 -> TX: 01 06 0A 03 00 14 7A 1D 
11:01:46.406 -> C0A.03 = 20 ms gesendet (blind).
11:01:46.703 -> 2) Schreibe C00.00 = 1 und lese Echo
11:01:47.033 -> TX: 01 06 00 00 00 01 48 0A 
11:01:47.827 -> Keine Antwort erkannt.

Auch hier wurde der Parameter korrekt gesetzt.

Zusätzlicher RS485-Rohdaten-Test

Ich habe vorher auch einen Rohdaten-Test gemacht, um die RS485-Hardware des Opta grundsätzlich zu prüfen.

Dabei kam vom Opta am Empfänger zwar etwas an, aber das Verhalten war auffällig instabil. Beispiel:

  • bei gesendetem 0x55 kam teils 55 ff
  • teils nur ff
  • teils verfälschte Bytes

Das hat mich stutzig gemacht, ob die eingebaute RS485-Schnittstelle des Opta evtl. ein Problem beim Umschalten TX/RX oder beim Frame-Ende hat.

Meine Frage

Hat jemand den Opta RS485 schon zuverlässig in Halbduplex mit Antworttelegrammen verwendet?

Mich interessiert vor allem:

  1. Gibt es beim ArduinoRS485 auf dem Opta etwas Besonderes bei TX→RX Turnaround?
  2. Muss man nach endTransmission() noch etwas Spezielles tun?
  3. Gibt es bekannte Probleme mit der eingebauten RS485-Schnittstelle des Opta, speziell beim Empfangen von Antworten?
  4. Hat jemand funktionierenden Beispielcode für Modbus RTU Request + Antwort lesen auf dem Opta?

Ich wäre sehr dankbar für Hinweise, weil das Senden offenbar funktioniert, aber ich die Rückantwort einfach nicht sauber reinbekomme.

Danke!

poste mal den Code mit dem du die Ausgabe von 11:01:45 produziert hast. Sollte da irgendwelche Libraries verwendet worden sein - gib exakt an, welche Libraries du verwendest.

Die Ausgabe von 11:01:45 wurde mit diesem Sketch erzeugt:

#include <ArduinoRS485.h>

constexpr uint8_t SLAVE_ID = 0x01;

uint16_t modbusCRC(const uint8_t *data, size_t len) {
  uint16_t crc = 0xFFFF;
  for (size_t pos = 0; pos < len; pos++) {
    crc ^= data[pos];
    for (int i = 0; i < 8; i++) {
      if (crc & 1) {
        crc >>= 1;
        crc ^= 0xA001;
      } else {
        crc >>= 1;
      }
    }
  }
  return crc;
}

void printHex(uint8_t b) {
  if (b < 0x10) Serial.print('0');
  Serial.print(b, HEX);
}

void dumpBuf(const char* label, const uint8_t* buf, size_t len) {
  Serial.print(label);
  for (size_t i = 0; i < len; i++) {
    printHex(buf[i]);
    Serial.print(' ');
  }
  Serial.println();
}

void clearRx() {
  while (RS485.available()) RS485.read();
}

void sendFrame(uint8_t *frame, size_t len) {
  uint16_t crc = modbusCRC(frame, len - 2);
  frame[len - 2] = crc & 0xFF;
  frame[len - 1] = (crc >> 8) & 0xFF;

  dumpBuf("TX: ", frame, len);

  RS485.beginTransmission();
  RS485.write(frame, len);
  RS485.flush();
  delay(5);
  RS485.endTransmission();
}

bool readFrame(uint8_t *buf, size_t &len, unsigned long firstByteTimeoutMs = 500, unsigned long interByteGapMs = 30) {
  len = 0;
  unsigned long start = millis();

  while ((millis() - start) < firstByteTimeoutMs) {
    if (RS485.available()) break;
  }
  if (!RS485.available()) return false;

  unsigned long last = millis();
  while ((millis() - last) < interByteGapMs) {
    while (RS485.available()) {
      int c = RS485.read();
      if (c >= 0 && len < 64) {
        buf[len++] = (uint8_t)c;
        last = millis();
      }
    }
  }
  return len > 0;
}

void writeC0A03_responseTime20ms_blind() {
  uint8_t frame[8] = {0x01, 0x06, 0x0A, 0x03, 0x00, 0x14, 0x00, 0x00};
  clearRx();
  sendFrame(frame, sizeof(frame));
  Serial.println("C0A.03 = 20 ms gesendet (blind).");
}

void writeC0000_speedMode_and_read_reply() {
  uint8_t tx[8] = {0x01, 0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00};

  clearRx();
  sendFrame(tx, sizeof(tx));

  delay(10);

  uint8_t rx[64];
  size_t rxLen = 0;

  if (!readFrame(rx, rxLen, 800, 30)) {
    Serial.println("Keine Antwort erkannt.");
    return;
  }

  dumpBuf("RX: ", rx, rxLen);

  if (rxLen >= 8) {
    uint16_t rxCrc = (uint16_t)rx[rxLen - 2] | ((uint16_t)rx[rxLen - 1] << 8);
    uint16_t calc = modbusCRC(rx, rxLen - 2);

    if (rxCrc != calc) {
      Serial.println("Antwort empfangen, aber CRC falsch.");
      return;
    }

    bool echo = (rxLen == 8);
    for (int i = 0; i < 8 && echo; i++) {
      if (rx[i] != tx[i]) echo = false;
    }

    if (echo) {
      Serial.println("OK: Echo-Antwort erkannt.");
      return;
    }

    if (rx[0] == 0x01 && rx[1] == 0x90) {
      Serial.println("Servo hat Error-Frame 0x90 gesendet.");
      return;
    }

    Serial.println("Antwort empfangen, aber unerwartetes Format.");
  }
}

void setup() {
  Serial.begin(115200);
  while (!Serial) {}

  RS485.begin(115200, SERIAL_8N1);
  Serial.println("1) Setze C0A.03 = 20 ms");
  delay(500);

  writeC0A03_responseTime20ms_blind();

  delay(300);

  Serial.println("2) Schreibe C00.00 = 1 und lese Echo");
  delay(300);

  writeC0000_speedMode_and_read_reply();
}

void loop() {
}

Verwendete Library:

  • ArduinoRS485

Sonst keine zusätzlichen Libraries.

In welchen Handbuch hast du den die FunctionCodes gefunden?

Kennst du diese Seite: Getting Started with Modbus RTU on Opta™ - Finder OPTA

an deiner Stelle würde ich eine Modbus Library verwenden.
Ich mag die ModbusMaster von Doc Walker.
Die kann im Fehlerfall rausschreiben was schief läuft.
Willst die mal probieren?

Ich habe es mit ModbusMaster getestet.

Ergebnis:

  • Rückgabecode ist 0xE2 (Response Timed Out)
  • der Parameter wird dabei nicht gesetzt

Serial-Ausgabe:

14:15:59.342 -> Schreibe C00.00 = 1 ...
14:16:01.344 -> Status: 226
14:16:01.344 -> Result code: 0xE2
14:16:01.344 -> Text: Response Timed Out
14:16:01.344 -> Write nicht bestaetigt.

Mit meinem selbst aufgebauten Frame wurde der Parameter zuvor gesetzt, aber auch dort kam keine Antwort am Opta an.

Mit ModbusMaster sieht es jetzt so aus, als ob der Request in dieser Form weder bestaetigt wird noch den Parameter aendert.

Verwendete Libraries:

  • ArduinoRS485
  • ModbusMaster von Doc Walker / 4-20ma

Hier der verwendete Testcode:

#include <ArduinoRS485.h>
#include <ModbusMaster.h>

ModbusMaster node;

// Vor dem Senden: RX aus, TX ein
void preTransmission() {
  RS485.noReceive();
  RS485.beginTransmission();
}

// Nach dem Senden: TX aus, RX ein
void postTransmission() {
  RS485.endTransmission();
  RS485.receive();
}

const char* modbusResultToText(uint8_t result) {
  switch (result) {
    case ModbusMaster::ku8MBSuccess:          return "Success";
    case ModbusMaster::ku8MBInvalidSlaveID:   return "Invalid Slave ID";
    case ModbusMaster::ku8MBInvalidFunction:  return "Invalid Function";
    case ModbusMaster::ku8MBResponseTimedOut: return "Response Timed Out";
    case ModbusMaster::ku8MBInvalidCRC:       return "Invalid CRC";
    default:                                  return "Modbus Exception / Unknown Error";
  }
}

void setup() {
  Serial.begin(115200);
  while (!Serial) {}

  // A6-RS Default:
  // Slave ID 1, 115200 Baud, 8N1
  RS485.begin(115200, SERIAL_8N1);
  RS485.receive();

  node.begin(1, RS485);
  node.preTransmission(preTransmission);
  node.postTransmission(postTransmission);

  Serial.println("Schreibe C00.00 = 1 ...");

  // C00.00 = Register 0x0000, Wert = 1
  uint8_t result = node.writeSingleRegister(0x0000, 0x0001);

  Serial.print("Status: ");
  Serial.println(result);

  Serial.print("Result code: 0x");
  if (result < 0x10) Serial.print('0');
  Serial.println(result, HEX);

  Serial.print("Text: ");
  Serial.println(modbusResultToText(result));

  if (result == ModbusMaster::ku8MBSuccess) {
    Serial.println("Write erfolgreich bestaetigt.");
  } else {
    Serial.println("Write nicht bestaetigt.");
  }
}

void loop() {
}

Noch als Einordnung:
Ich arbeite eigentlich in der mechanischen Konstruktion und habe mit Programmieren bzw. Arduino nur wenig Erfahrung. Die bisherigen Versuche und Testprogramme habe ich mit Hilfe von ChatGPT aufgebaut.

Ich komme an diesem Punkt aber nicht mehr weiter und wäre deshalb wirklich sehr froh um Unterstützung oder einen konkreten Hinweis, wie man die RS485-/Modbus-Kommunikation auf dem Opta in diesem Fall korrekt zum Laufen bringt.

ich kenne den Opta nicht wirklich und weis daher auch nicht was der arduino485.h include mit der RS485 so macht und ob du in den pre/postTransmission die richtigen Methoden verwendest.

eigentlich steht da drinnen die umschaltung des Direction Pin - wenn der Opta sowas braucht. Am Schaltplan ist aber nicht vermerkt, ob es da einen dedizierten Pin dazu gibt.
Was aber am schaltplan steht - dass der Opta keine Endwiderstände am RS485 hat ... daher stellt sich mir mal die frage ob du die beiden Enden deines Buses terminisiert hast.

1 Like