RS485/ModBus

Hallo zusammen,

ich habe mir den Wasserstandssensor HDL300
(4-20mA Output 5m Water Liquid Level Transmitter Sensor Hdl 300 - China Water Level Sensor and Level Sensor) gekauft. Wenn schon digitale Auswertung,so dachte ich mir, dann soll der Sensor auch ein digitales Signal abgeben und habe also die RS485-Version erworben und nicht die mit dem Spannungsausgang oder die mit dem Stromausgang. Mittlerweile denke ich, dass ich es mit denen leichter gehabt hätte, denn die Sensordaten aus der RS485-Version herauszukitzeln, will mir nicht gelingen.

Folgender Aufbau:
Spannungsversorgung 24V=, + an den roten Draht des Sensors, - an den gelben.
A und B (grün und blau) an die A/B-Klemmen eines fertigen (Eckstein) "MAX485 Module TTL Switch".
Seriell-USB-Adapter: Rx an Di des MAX-485-Moduls, Tx an Ro, umgekehrte Variante auch probiert.

Ergebnis im Terminal: nichts...
Auf meine Anfrage beim Hersteller wurde mir ein Blatt geschickt "Modbus level transmitter communication protocol"(siehe Anlage).

Aha. Also nix mit der Vorstellung, dass das Ding einfach seine Messwerte auf eine serielle Schnittstelle schickt, sondern man muss erst um Werte bitten, so zumindest verstehe ich das Blatt. Für das "Send read command" müsste ich also vom Terminal die Hex-Zeichen 01;03;00;00;00;01;84;0A schicken, dann sollte das Gerät seinerseits etwas schicken. Oder habe ich das falsch verstanden?

Bevor ich einen Aufbau mit einem Mikrocontroller mache, wollte ich erstmal per Terminal überprüfen, ob ich die Befehlsgeschichte richtig verstanden habe. Ich verwende GTKTerm unter Linux und einen USB<->Seriell-Wandler, der auch die 5 V für den 485-Baustein liefert. Di des 485 ist mit Rx verbunden, Ro mit Tx (auch umgekehrt probiert). Was mache ich mit De und Re? auf LOW oder auf HIGH legen?

Ist der Ansatz überhaupt richtig?

Vielen Dank für Hilfen, ich stehe ziemlich auf dem Schlauch...

-richard

MODBUS level transmitter communication protocol.pdf (82.2 KB)

Bei (half duplex) RS-485 muß jedes Gerät wissen, wann es senden soll und darf. Deshalb sendet der Master üblicherweise mit einem Kommando eine Geräteadresse, woraufhin der ausgewählte Slave Daten schickt. Der Master muß also sofort nach dem Abschicken des Kommandos auf Empfang umschalten, der Slave auf Senden. Deshalb bleiben alle angeschlossenen Geräte normalerweise auf Empfang, der Master schaltet auf Senden nur um ein Kommando zu übertragen. und ein Slave schaltet auf Senden nur wenn der Master Daten angefordert hat. Die Slaves machen diese Umschaltung automatisch, der Master muß dem MAX selbst sagen, wann er auf Senden gehen soll, und ansonsten auf Empfang bleiben soll.

Und je nach Arduino nicht die selbe UART (Serial) für die Verbindung zum Rechner und zum Sensor benutzen, das kann nicht funktionieren.

Hallo Richard,
es ist schon wie mein Vorredner sagt du muss am RS485 Baustein umschalten ob senden oder Emfpangen wird.
Das sind am RS485 die Eingänge
!RE (Receiver Outpu Enalble (invertiert)) und DO (Driver Output).
Dies machst du dann mit einerm Arduino (hier wäre ein MEGA ratsam da mehrere Serials) in der Software du startest eine Anfrage mit der an der bestimmten Modbus Adressee (1-255) mit den richtigen Registern und bekommst dann eine Antwort.
Es gibt fertige/billige USB/RS485 Bausteine da sind dan die TX Leitunga auf !RE/DO gebrückt damit schaltet der RS485 Baustein um (was aber nicht zuverlässig bei Modbus funktionert)
"Nur" RX auf RO und TX auf DI funktioert hier nicht. Du brauchst noch eine Halbdublex umschalteung zwischen Empfangen und Senden.
Du köntest dies auch über Software Serial machen bei 9600baud auf RS 485 und dann einen normalen Arduino Nano/Uno etc nutzen.

Gruß
DerDani

Edit:
So ein Dingen meinete ich => USB Serial

Danke für Eure Antworten. Jetzt blicke ich schon ein BISSCHEN mehr durch - hoffe ich. Ich fass mal zusammen, so wie ich es verstanden habe:

  1. Mega verwenden (ist zwar ein bisschen overkill, aber ich muss mich nicht mit software serial herumschlagen, kann ich immer noch machen und auf Nano umsteigen, wenn das Eigentliche läuft)
  2. De und /Re auf High setzen
  3. Die Hex-Werte 01;03;00;00;00;01;84;0A abschicken (wenn ich die Anleitung (siehe Attachment in meinem 1. Post) richtig interpretiere)
  4. De auf LOW setzen
  5. /Re auf LOW setzen und warten bis die Antwort (die Sensorwerte) da ist
  6. /Re und De wieder auf HIGH setzen

Oder?

Gruß,
-richard

Der Normalzustand von De/Re sollte Empfang (LOW) sein. Nur wenn ein Kommando ausgegeben wird beide auf HIGH (Senden) setzen, gleich danach wieder auf Empfang (LOW). Dann kann am Modbus auch mal ein anderer Master das Kommando übernehmen.

Mit Modbus habe ich keine konkrete Erfahrung. Warum soll dort ein gebrücktes De/Re Signal problematisch sein?

Re ist invertiert, sprich Low-aktiv. Deswegen können die beuden ja gebrückt werden, dass man nur einen Pin am Arduino zum umschalten braucht.

Ja wir wir zwischenzeitlich wissen, wäre einer mit Stromschnittstelle wesentlich einfach gewesen.

@ Modbus: ich hab auch DE/RE einfach gebrückt auf einem PIN und das Funktioniert problemlos.

PS: für Modbus nehm ich immer noch die alte SimpleModbus von Juan Bester:
https://drive.google.com/drive/folders/0B0B286tJkafVSENVcU1RQVBfSzg

in dem Zip gibts auch gute Beschreibungen und ein ausführliches Manual.

Danke. Bevor ich die letzten Postings gelesen habe, habe ich für einen Mega folgendes geschrieben:

// MAX485 an Pin 18 (Rx) und 19 (Tx). De und Re gemeinsam an Pin 13

const int Serial1Control = 13;    // Umschalten Lesen/Schreiben
int byteReceived;

void setup() {
Serial.begin(9600);               // Serial Monitor
Serial1.begin(9600);              // MAX485
pinMode(Serial1Control, OUTPUT);
}

void loop() {
  if (Serial.available()){
    digitalWrite(Serial1Control, HIGH);// auf Schreiben schalten
    delay(100);
    Serial1.write(0x01);            // "Send read"-Kommando laut Datenblatt        
    Serial1.write(0x03);
    Serial1.write(0x00);
    Serial1.write(0x00);
    Serial1.write(0x00);
    Serial1.write(0x01);
    Serial1.write(0x84);
    Serial1.write(0x0A);
    //delay(100); 
    digitalWrite(Serial1Control, LOW);// auf Lesen schalten
  }
  if (Serial1.available())
  {
    byteReceived = Serial1.read();    //Daten empfangen und ausgeben
    Serial.write(byteReceived);
    delay(100);
  }

}

Leider ohne Erfolg. Hier habe ich, wie auch von den Vorpostern angeregt, Re und De gemeinsam an Pin 13 angeschlossen, der das Umschalten zwischen Lesen und Schreiben steuert.

Dem Vorschlag von noiasca werde ich gleich mal nachgehen.

@ noiasca:

Ich sehe übrigens gerade auf Deiner HP, dass Du einen ganz ähnlichen Sensor hast wie ich, bloß eben mit Stromschnittstelle. Hätte, hätte...
Jammern hilft nix..., muss da jetzt durch :frowning:

ja, der läuft gut :wink:
Und ich habe mich bewusst für die Stromschnittstelle entschieden obwohl ich auch schon mit Modbus gearbeitet habe (und das mit der oben angegeben Lib auch super läuft). Auch wenn die 30 EUR weh tun: wenn du noch nie etwas mit Modbus gemacht hast ... ich würde mir einen einfacheren Sensor besorgen.

Damit will ich nicht sagen, dass du das nicht auch schaffen wirst, aber mir wäre es die Zeit nicht wert.

Die Schleife ist Murks :frowning:

Zuerst wird mit Serial.available() gewartet, bis etwas vom PC kommt. Dann sollte aber auch etwas abgeholt werden, sonst wird bei jedem Durchlauf von loop() wieder das Kommando abgeschickt.

Dann wird für die Ausgabe De/Re auf HIGH gesetzt - müßte das nicht genau andersrum sein?

Und wenn dann ein Zeichen auf Serial1 ankommt, wird anschließend 100ms gewartet - worauf? Der Sensor sendet seine Zeichen ohne Unterbrechung, da kann durch Warten höchstens noch was verloren gehen.

Das alles hat noch garnichts mit ModBus zu tun, das sind ganz banale Fehler im Umgang mit einer seriellen Schnittstelle :frowning:

Danke für die Hinweise. Code geändert, aber immer noch ohne Erfolg:

const int Serial1Control = 13;    // Umschalten Lesen/Schreiben
int byteReceived;

void setup() {
Serial.begin(9600);               // Serial Monitor
Serial1.begin(9600);              // MAX485
pinMode(Serial1Control, OUTPUT);
}

void loop() {
    digitalWrite(Serial1Control, HIGH);// auf Schreiben schalten
    Serial1.write(0x01);            // "Send read"-Kommando laut Datenblatt        
    Serial1.write(0x03);
    Serial1.write(0x00);
    Serial1.write(0x00);
    Serial1.write(0x00);
    Serial1.write(0x01);
    Serial1.write(0x84);
    Serial1.write(0x0A);
    digitalWrite(Serial1Control, LOW);// auf Lesen schalten
  
    if (Serial1.available())
  {
    byteReceived = Serial1.read();    //Daten empfangen und ausgeben
    Serial.write(byteReceived);
    delay(100);
  }

}

M.E. ist die HIGH/LOW-Schaltung von De/Re richtig herum: LOW = Lesen, HIGH = Schreiben

du ballerst ja deinen Sensor zu.

a) gib mal nach dem HIGH ein kurzes ... 500ms Delay rein. und schalte auch eine LED Parallel damit du siehst dass das Ding wirklich aufblinkt.

b) bau deinen "Request" um auf "BlinkWithoutDelay" - nicht blockierend alle 5 Sekunden oder so

c) im Loop fragst du permanent auf Serial.Available ab und gibst einfach mal aus, was du so hörst. Das spiest sich zwar mit der Sensor-Antwort, aber damit sollst du sehen, ob du irgendwas vom Sensor hörst und

d) gehe auf einen Mega oder baue um auf Soft-Serial damit du nicht alles auf einer Seriellen Schnittstelle hast. So kannst kaum vernünftig debuggen. - (tja das kommt von Codeschnippselposten...)

wenn es nach d) immer noch nicht geht, mach aussagekräftige Bilder von deinem Aufbau und einen Schaltplan damit wir uns was vorstellen können. So ist das Karpfenfischen (im trüben Wasser)

e) probiere eine Modbus-Lib

f) kauf einen anderen Sensor.

    digitalWrite(Serial1Control, HIGH);// auf Schreiben schalten
    Serial1.write(0x01);            // "Send read"-Kommando laut Datenblatt       
    Serial1.write(0x03);
    Serial1.write(0x00);
    Serial1.write(0x00);
    Serial1.write(0x00);
    Serial1.write(0x01);
    Serial1.write(0x84);
    Serial1.write(0x0A);
    digitalWrite(Serial1Control, LOW);// auf Lesen schalten

Du schaltest auf Lesen, bevor der Buffer leer ist.
Folge:

  1. Die Daten landen zum Teil im Nirvana.
  2. Dein Kommando wird verstümmelt.
  3. Du bekommst keine Antwort.

Stimmt, man sollte mit Serial.flush(); auf die Ausgabe aller Zeichen warten, bevor wieder auf Lesen umgeschaltet wird.

Danke an alle Poster! Habe mich jetzt für noiascas Tipp Nr. f entschieden.
Schöne Grüße,

-richard

Hi r_a_mueller. I have the same sensor and same issue. Do you suceed to get data from it? In case positive, can you post your solution?

cwidow:
Hi r_a_mueller. I have the same sensor and same issue. Do you suceed to get data from it? In case positive, can you post your solution?

The solution (option f in post 12):
bay an other sensor.

Bye Uwe

Here the solution:
// ---------------------------------------------------------------------------
// Include the base required libraries
// ---------------------------------------------------------------------------
#include <Arduino.h>
#include <SensorModbusMaster.h>

// Define the sensor's modbus address
byte modbusAddress = 0x01; // The sensor's modbus address, or SlaveID

// Define pin number variables
const int DEREPin = 13;//-1; // The pin controlling Recieve Enable and Driver Enable
// on the RS485 adapter, if applicable (else, -1)
// Setting HIGH enables the driver (arduino) to send text
// Setting LOW enables the receiver (sensor) to send text
// Construct the modbus instance
modbusMaster modbus;

void setup() {
if (DEREPin > 0) pinMode(DEREPin, OUTPUT);

Serial.begin(57600); // Main serial port for debugging via USB Serial Monitor

Serial1.begin(9600, SERIAL_8N1); // port for communicating with sensor
modbus.begin(modbusAddress, &Serial1, DEREPin);
// Turn on debugging
// modbus.setDebugStream(&Serial);

// Start up note
Serial.println("Full scan of all input and holding registers");

// Allow the sensor and converter to warm up
Serial.println("Waiting for sensor and adapter to be ready.");
delay(500);

}

void loop() {

float nivel_f = 0;
unsigned int nivel_ui = 0;

Serial.print("Nivel : ");

if (modbus.getRegisters(0x03, 4, 1)) //if (modbus.getRegisters(0x03, i, 2))
{

nivel_ui = modbus.uint16FromFrame(bigEndian, 3);
nivel_f = nivel_ui / 100.0;

// printPaddedInt16(modbus.uint16FromFrame(bigEndian, 3));
//Serial.print(", ");
Serial.print(nivel_ui);
Serial.print(", ");
Serial.print(nivel_f);

Serial.println();

}
// else Serial.println("Read Register Failed!");
//i++;
// }

delay(1000);

}

Thank you for your help. But I must say, that I followed uwefed's recommendation and bought another sensor. Since also other (experienced) people didn't succeed to get anything out of the sensor I assumed it to be broken and disassembled it. Now I have a 4-20 mA type, and not an Modbus-sensor anymore to test the proposal.