TIA Effizientes Auslesen eines Modbus-Servers mit verschiedenen Datentypen

Januar

Level-2
Beiträge
644
Reaktionspunkte
266
Zuviel Werbung?
-> Hier kostenlos registrieren
Ich grüße euch, Gemeinde.

Ich implementiere gerade eine Schnittstelle zu einem bestehenden System, auf dem bereits für mich ein Modbus-Server Daten bereitstellt.

Ich verwende TIA V18, eine S7-1515, Modbus TCP als Client. Der Baustein "MB_CLIENT" steht in Version V6.0 zur Verfügung, könnte also auch Funktionscode 23 ausführen. (Das bringt mir aber vermutlich nichts, weil der nur Holding Register liest/schreibt, oder?)

Meine Frage ist jetzt, wie ich die Daten am geschicktesten auslesen kann. Der Server stellt mir 64 Diskrete Inputs und 99 Analoge Inputs zur Verfügung. Von den Analogen Inputs sind 5 als Integer vorhanden, der Rest sind jeweils Low-/High-Byte eines Real-Wertes.

Übersicht:
10001-10060: Diskrete Inputs
30001-30005: Analoge Inputs (Int)
30006-30099: Analoge Inputs (Real; Low auf der geraden Adresse (30006), High auf der ungeraden Adresse (30007))

Ich hab leider noch nicht genug Erfahrung darin, als Modbus-Client mir Daten unterschiedlichen Formats abzuholen, und auch das Projektierungsbeispiel von Siemens ist sehr rudimentär gehalten (und verwendet auch nur Holding Register).

Gruß, Januar
 
Effizient lesen: so viele wie möglich Register auf einmal lesen (auch wenn uninteressante Register dazwischen sind). Als Empfangspuffer eine Struktur mit den Datentypen entsprechend der Register-Beschreibung, dann braucht nur noch aus dem Empfangspuffer in die eigenen Variablen kopiert werden ohne Konvertieren.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Also benutze ich einen MB-Client-FB, den ich durch Programmlogik zwischen dem Lesen der Diskreten und Analogen wechsle?

Kann ich bei den Analogen dann auch zwischen Int und Real unterscheiden?

Also folgendes Konzept:

Schritt 1: Lese Register 10001-10060 aus, schreibe in Ziel_1 (Array of bool), warte bis Auftrag fertig
Schritt 2: Lese Register 30001-30005 aus, schreibe in Ziel_2 (Array of Int), warte bis Auftrag fertig
Schritt 3: Lese Register 30006-30099 aus, schreibe in Ziel_3 (Array of Real), warte bis Auftrag fertig
Repeat
 
Functioncode 23 hab ich noch nie genommen, aber der scheint sich wohl eher auf Halteregister im Adressbereich 40000 zu beziehen.
MB_FunctionCodes.jpg

Deine Daten liegen im Bereich Discrete Inputs (Eingangs-Bits) und als Input Register (Eingangs-Worte).
Theoretisch könntest du das in zwei Abfragen lösen.
  • Code 01: StartAdresse 1, Anzahl 60
  • Code 04: StartAdresse 30001, Anzahl 99
Eine Modbus-Abfrage kann max. 125 Register übertragen.
Da 30001-30005 und 30006-30099 direkt hintereinander liegen, kannst du das mit einer Abfrage machen.

Bevor du auf die SPS gehst würde ich die Abfragen aber mal mit einem PC-Client testen. Nur um sicher zu gehen dass es da keine Überraschungen gibt. Z.B. Modbus-Tester von Schneider wäre da so ein Tool. Wenn du da mal Daten siehst, dann würde ich erst auf die SPS gehen.

Wenn du mit S7-1500 und MB-Client arbeitest, dann bekommst du die Daten zwangsweise als "Array eines Elementardatentypen" raus. Ich mach meist Array_of_Bool oder Array_of_Word. Wandeln musst du danach immer. Ein kopieren der Daten aus dem Puffer in eine vorgefertigte UDT-Struktur mit der "Deserialize"-Instruktion wäre denkbar. Aber ob die Byte-Drehung (Notation) stimmt musst du dann immer noch schauen. Die passt aber meistens.
 
Ich sitze aktuell noch im Büro und bereite die Kommunikation erst vor. Für Testzwecke habe ich selbst ein Client-Programm, aber danke für den Hinweis.

Also lege ich die Daten aus den Analogen Registern in ein Array of Word und schreib mir eine kleine SCL-Schleife, die mir meine "zerstückelten Reals" wieder zusammenbaut.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Wenn du mit S7-1500 und MB-Client arbeitest, dann bekommst du die Daten zwangsweise als "Array eines Elementardatentypen" raus.
Ach, kann man bei MB_Client keine Struktur an MB_DATA_PTR übergeben? Auch nicht bei DB mit Standard Zugriff? Oder über einen ANY-Pointer auf den Adressbereich der Struktur P#DB1.DBX0.0 BYTE 198 oder P#DB1.DBX0.0 WORD 99 ? Man könnte wohl über den Merkerbereich gehen, bei PLC-Variablen die MW und MD im Empfangspuffer mit den Datentypen deklarieren und an MB_DATA_PTR P#M1000.0 WORD 99 übergeben.
Oder im DB ein Array of WORD (oder DWORD) oder Array of REAL anlegen, und dann beim Herauskopieren WORD_TO_... oder DWORD_TO_REAL angeben. Oder zum Herauskopieren einen FC aufrufen mit dem Array of WORD als InOut und im FC die Struktur per AT über das InOut-Array legen (geht AT in InOut?). Dann wird am wenigsten umkopiert.
 
@PN/DP
Mit nicht optimierten DBs gehen auch beliebige Strukturen und ANY-Pointer.
Nur bei optimiert bist du auf Arrays limitiert.
 
In dem aktuellen Projekt werden wegen Standard-Vorgaben und WinCC-Schnittstelle sowieso Merker und Standard-DBs verwendet, da sind sowieso sämtliche Siemens-Style-Guides über Board geworfen worden.

Also jetzt doch folgendes:

Schritt 1: Diskrete Inputs in Standard-DB lesen über den Zeiger auf das erste Bool.
Schritt 2: 5 Integer lesen, auf Standard-DB schreiben, wo 5 Integer deklariert sind
Schritt 3: 47 Real lesen und auf Standard-DB schreiben, wo 47 Real deklariert sind.

Würde das funktionieren?
 
Meinst du damit explizit ein Struct? Eigentlich müsste es doch auch funktionieren, wenn ich 5 Int und 47 Real hintereinander in der richtigen Reihenfolge deklariere, oder?
 
Du kannst einfach die Variablen hintereinander weg im DB deklarieren. Du musst aber auch den ganzen Bereich an den MB_Client an MB_DATA_PTR angeben, da ist es hilfreich, die ganzen Variablen in ein Struct zu packen, dann kannst du das Struct symbolisch an MB_DATA_PTR angeben. Wenn der Bereich kein Symbol hat, dann musst du den Bereich unsymbolisch als ANY-Pointer angeben, z.B. P#DB1.DBX0.0 BYTE 198
Also Struct ist einfach schöner und einfacher.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Wie kann ich dann zwischen den beiden Structs wechseln?

Würde es funktionieren je nach Auftrag zwischen zwei Strings zu wechseln? tempStringPTR wird je nach Auftrag mit einer anderen Zeichenkette gefüllt, die Zeichenkette selbst ist dann der Pointer auf meinen Datenbereich. tempStringPTR ist dann der Input für den Client-FB. Zieladresse und Auftragsart wird dann nach gleichem Prinzip angepasst.
 

Anhänge

  • 1720170833960.png
    1720170833960.png
    17,3 KB · Aufrufe: 37
Du könntest zwei Aufrufe der selben MB_Client-Instanz mit verschiedenen festen Aufrufparametern (Auftragsparametern) programmieren und immer nur eine davon aufrufen, bis der Auftrag beendet ist, dann wechseln.
(irgendwo hier im Forum ist auch ein Programmbeispiel, wo die Eingangsparameter aus einer Job-Liste entnommen werden)
 
Also zwei MB-Client-Bausteine mit der selben Instanz-Bezeichnung, aber je nach Job-Counter wird nur einer der beiden ein TRUE am Enable haben?
Die Parametrierung würde dann ja pro Baustein unterschiedlich erfolgen.

Ich hab jetzt erstmal eine Instanz projektiert und je nach Job-Counter werden die unterschiedlichen Parameter auf die Eingangsvariablen des MB-Client-Baustein geschrieben.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Also zwei MB-Client-Bausteine mit der selben Instanz-Bezeichnung, aber je nach Job-Counter wird nur einer der beiden ein TRUE am Enable haben?
Die Parametrierung würde dann ja pro Baustein unterschiedlich erfolgen.
ja, genau so
 
Würde es funktionieren je nach Auftrag zwischen zwei Strings zu wechseln?
Das würde nicht passen, mit dem S-MOVE in deinem Beispiel kopierst du Daten ab DB604.DBX0.0 in einen String-Typen.

Du bräuchtest eine ANY-Zeiger-Variable mit umschaltbarer Definition (Adresse, Länge), welchen du dann dem MB_CLIENT füttern kannst.
ANY-Zeiger (also deren Definition) lassen sich aber nicht einfach umkopieren. Den musst du bauen/umbauen.

Ein Weg wäre mit AT-SICHT, als Überlagerung.
Siehe Screenshot.
Wie man in TIA eine AT-Sicht deklariert
FAQ zu ANY-Pointern

Wenn man nur weniger verschiedene Anfragen hat, kann man dass mit den mehrfachen MB_Client-Deklaration mit selber Multiinstanz, so von PN/DP vorgeschlagen, auch sehr gut lösen. Da kann man bei jedem MB_Client-Aufruf gleich am Baustein die Modbus-Anfrage-Parameter fest anschreiben und braucht diese nicht auch noch zu dynamisieren.
 

Anhänge

  • ANY-Pointer_AT-Sicht_S7-1500.jpg
    ANY-Pointer_AT-Sicht_S7-1500.jpg
    162 KB · Aufrufe: 36
Zuletzt bearbeitet:
Das heißt, bei meinem Beispiel müsste ich mich bloß noch darum kümmern, den Job-Counter zu bespielen, damit der Wert zwischen den beiden Konstanten alterniert (umschalten wenn #statStructMBC.Done) und dann sollte es hoffentlich passen. Die Pointer zeigen jeweils auf ein Struct, beim ersten liegen 64 Bool, beim zweiten 5 Int und dann 47 Real.
 

Anhänge

  • 1720177883609.png
    1720177883609.png
    37,7 KB · Aufrufe: 44
Zuviel Werbung?
-> Hier kostenlos registrieren
Na, das sieht doch viel übersichtlicher aus, als die Parameter dynamisch zu versorgen :)

Ich bin mir gerade nicht sicher was besser ist: Umschaltung über EN oder über REQ? Sollte aber so mit EN funktionieren. Das kannst du vielleicht mal ausprobieren.
Eventuell würde ich an den Ausgängen verschiedene Variablen spendieren, zumindest wenn EN immer aktiv ist und beide Anweisungen aufgerufen werden.
 
1739270733967.png1739270785032.png
1739270876345.png

Mit dieser Methode geht es.
Zur Beachtung, beide FB Aufrufe haben den gleichen IDB --> #MB_CLIENT_Instance_001
Das einzige Problem ist, wenn man in einem Error kommt, dann wird es schwer wieder zum Laufen zu kommen ohne über
SPS Stop zu gehen. Nach einem STOP--> RUN Wechsel läuft das System sofort los.

Wie löst ihr den den Error Reset?
 
Zurück
Oben