Brauche Unterstützung bei Modbus

  • VB.NET

Es gibt 41 Antworten in diesem Thema. Der letzte Beitrag () ist von hal2000.

    Brauche Unterstützung bei Modbus

    Hallo, ich bin in meinem Projekt auf dem Endpunkt angelangt, ich brauche "nur noch" die Kommunikation mit dem Endgerät, um Rohdaten zu bekommen und zu schreiben.

    Ich weiß, dass es hier einige Threads zum Thema Modbus gibt, ich komme aber in meinen Überlegungen nicht weiter.

    Ist jemand anwesend, der da Erfahrung hat?

    Danke und LG
    Mir geht es nicht darum, wie Modbus aufgebaut ist. Mir geht es darum, wie ich mein Code aufbaue.
    Soll ich nach jedem Senden auf die Antwort warten? Soll ich eventgesteuert arbeiten? Soll ich diese schrittwese Umsetzung machen, in jedem Schritt unterschiedliche Buffer auslesen und schreiben, wenn notwendig, und jeder Schritt nach 100 ms oder so?
    Es gibt unterschiedliche Vorgehensweisen, die sich widersprechen, z.B. hier:
    codeproject.com/Articles/20929…bus-Protocol-in-C-NET-2-0

    und hier:
    [VB 2010] Timer Probleme ...
    Welche Lib?
    Ich verwende keine Lib vom Link. Und der erste Link läuft eben nicht eventgesteuert, er schickt was ab und wartet auf die Antwort. Zumal ich die "niederen" Funktionen schon habe (ReadMultipleRegisters, WriteCoil usw), ich habe hier eine DLL. Mir geht es um Aufbau von meinem Programm.
    Es gibt mehrere Sachen, die gelesen werden müssen, einige, die auf Abruf geschrieben werden müssen, einige, die auf Abruf gelesen werden müssen, einige, die eine mehrstufige Kommunikation erfordern (warten, bis Gerät fertig ist und dann Daten abfragen).
    Und ich will wissen, wie ich es am besten aufbaue.
    Lib==DLL

    Sende wenn Du senden willst, Empfange wenn das Event DataReceived kommt. Lies das ganze in eine Struktur die lauf Vorgabe vom Modbusprotokoll aufgebaut ist. Was da drinnen steht wertest Du anhand der Werte der einzelnen Felder aus.
    Um zu senden schreibst Du in die Struktur je nachdem was gesendet werden soll. Auch diese Struktur entspricht dem Modbusprotokoll. Und dann das ganze senden.
    Das ganze senden und Empfangen kannst Du ja in eine DLL auslagern. Hier wird auf das Event reagiert, ausgewertet und ein Event für das Programm generiert, wenn die Daten ausgewertet vorliegen. Genauso die Funktionen zum senden. Gewünschten wert an die DLL übergeben und Rausschicken

    Gruß
    Der Beitrag wurde aus 100% wiederverwendbaren Elektronen erstellt!

    wolfi_bayern schrieb:

    Empfange wenn das Event DataReceived kommt.

    Genau da hänge ich noch: woher weiß ich im Event, welche Daten gerade zurückkamen? Ich muss doch wissen, was ich gerade getan habe, das weiß Event nicht. D.h. ich brauche einen Enum-State oder so.

    wolfi_bayern schrieb:

    Lies das ganze in eine Struktur die lauf Vorgabe vom Modbusprotokoll aufgebaut ist.

    Was hat MEINE Datenstruktur mit Modbusprotokoll zu tun?
    Ich habe die Adressen von Registern, die ich auslesen und beschreiben will. Das Auslesen soll kontinuierlich laufen, das Beschreiben auf "Befehl", einiges auslesen auch auf Befehl, plus noch eine mehrstufige Kommunikation auf Befehl.
    Im Event muss ich jeweils wissen, was gerade läuft.

    wolfi_bayern schrieb:

    Auch diese Struktur entspricht dem Modbusprotokoll.

    Wie schon gesagt, das verstehe ich jetzt nicht, was meine Daten mit Protokoll zu tun haben sollen.

    wolfi_bayern schrieb:

    Das ganze senden und Empfangen kannst Du ja in eine DLL auslagern. Hier wird auf das Event reagiert, ausgewertet und ein Event für das Programm generiert, wenn die Daten ausgewertet vorliegen.

    Dafür brauche ich keine DLL.

    Ich habe eine Modbus-DLL, die mir ermöglicht, als Master zu schreiben, zu lesen usw. Da muss ich nur Daten, Registeradresse usw. übergeben. Mir geht es um obengenannten Ablauf, wie ich die empfangenen Daten sortiere, die im Event einfach da sind. Und in welcher Reihenfolge ich verschiedene Abfragen/Senden usw mache, wenn einiges auf Befehl und einiges kontinuierlich laufen soll.
    Hallo!

    Die Daten welche Du empfangen oder senden willst sind im Modbusprotokoll verpackt (siehe Wikipedia). Wie da beschrieben benötigen Die daten genau den Aufbau wie im Protokoll. Das Heist also zum senden:

    Serieller Port darf nix machen für die Dauer von 3,5 Zeichen.
    Dann wird das Protokoll folgendermaßen gesendet:
    1 Byte mit der Adresse,
    1 byte welches die Funktion angibt,
    Deine Daten mit soviel Byte wie Du Daten senden willst.
    2 Byte lange Prüfsumme.
    und dann wieder für 3,5 Zeichen nix machen.
    ... dies gilt für den RTU-Modus

    wenn du ASCII-Mode verwendest dann den entsprechend anderen Protokollaufbau verwenden.

    Ich würd das ganze in eine eigene Klasse auslagern welche die ganze Kommunikation abwickelt. Dann sind vom Hauptprogramm nur noch die gewünschten Daten und die Funktion zu übergeben. Hier gehört dann auch der SerialPort mit rein mit dem EventHandling.
    Der Beitrag wurde aus 100% wiederverwendbaren Elektronen erstellt!
    Ich glaube, wir reden aneinander vorbei.

    sonne75 schrieb:

    Ich habe eine Modbus-DLL, die mir ermöglicht, als Master zu schreiben, zu lesen usw. Da muss ich nur Daten, Registeradresse usw. übergeben.


    Ich muss mich um Modbusprotokoll nicht kümmern, wie ich es verstanden habe, weil diese DLL es für mich tut. Hier zwei Screenshots aus dem Objektkatalog:




    Was ich will, ist:

    sonne75 schrieb:

    Mir geht es um obengenannten Ablauf, wie ich die empfangenen Daten sortiere, die im Event einfach da sind. Und in welcher Reihenfolge ich verschiedene Abfragen/Senden usw mache, wenn einiges auf Befehl und einiges kontinuierlich laufen soll.
    Hallo!

    Ja kann sein daß wir aneinander vorbeigeredet haben...

    Wenn Du den lesebefehl aufrufst mußt Du die Slaveadresse und die Anzahl der zu lesenden Bytes angeben. Somit weißt Du was das Ergebnis ist. Ein Slave sendet nicht von alleine. Er benötigt immer erst eine Anfrage vom Master.

    modbustools.com/ModbusPollManual.pdf (Kapitel 5.4 als Beispiel)

    Wie oft Du eine Anfrage machen kannst ergibt sich aus der Baudrate. Siehe Beispiel im Link. Somit würd ich sagen das ganze läuft über einen TIMER. Wenn Du den Lesebefehl alle 500ms aufrufst dann sollte es schon mal funktionieren. Obs schneller geht mußt Du dann ausprobieren (Vielleicht kann man's auch rechnen... keine Ahnung). Auf jeden Fall wird jede Leseanforderung abgearbeitet bevor die nächste ausgeführt wird (Sollte so sein).

    Gruß
    Der Beitrag wurde aus 100% wiederverwendbaren Elektronen erstellt!
    Wir reden immer noch aneinander vorbei. Ich sende aus einer Funktion etwas, die Daten bekomme ich in einer anderen Funktion (DataReceived-Event) - in dieser Funktion weiß das Programm nicht, was ich in einer anderen gesendet habe.

    Du gehst von einem "Warten auf die Antwort"-Code aus, ich wollte eventgesteuert arbeiten:

    Soll ich nach jedem Senden auf die Antwort warten? Soll ich eventgesteuert arbeiten?


    Hier nochmal Zusammenfassung:

    Ich kommuniziere dabei mit einem Gerät und will seine Register lesen und schreiben. Einige Register will ich z.B. alle 500 ms lesen, immer. Einige Register werden auf Useraktion gelesen. Einige Register werden auf Useraktion geschrieben (und dabei evtl. noch was gelesen). Dann gibt es noch "Anlernen" von einem Drittgerät, dabei wird erst was an das Gerät geschickt, dann muss ich pollen (immer wieder prüfen, ob ein Bit im Register gesetzt ist), wenn ja, dann muss ich Daten auslesen, aus einem anderen Register.

    Mir geht es um Ablauf: zu schnell darf die Kommunikation nicht passieren, sonst kommt Slave durcheinander. Ich habe ein Dataset, das ich dann später mit erhaltenen Daten befülle, bzw. auch Daten zum Versenden von da nehme (nicht nur).
    Ich habe mir gedacht, ich mache so ein Enum mit States, wo er gerade ist (kontinuierliches Lesen, Lesen auf Abruf Register X, Lesen Reg Y, Senden Reg Z, Anlernen, Pollen), damit ich im DataReceived-Event (läuft über RS232) weiß, was ich mit empfangenen Daten anfangen soll (und wie viele da kommen).
    Und dann dachte ich, ich lege mir eine Struktur an (oder halt Klasse), mit allen möglichen Daten, die ich zur Kommunikation brauchen könnte, als Zwischending zwischen DataSet+Variablen und dem Protokoll.
    Hallo!

    Jetzt reden wir nichtmehr aneinander vorbei. Ich hab Dich schon verstanden!

    Es ist aber so, daß der Modbusslave nix macht und nix kann. Bekommt er einen Schreibbefehlt, Schreibt er die Daten in seine Register. Bekommt er einen Lesebefehl liest er seine Register aus und sendet die Daten zurück an den Master. Das wars. Wann Du eine Useranfrage oder eine zyklische Anfrage machst liegt an Deinem Programm. Aber es kommt immer nur das zurück was Du gerade als Anforderung gesendet hast.
    Der Beitrag wurde aus 100% wiederverwendbaren Elektronen erstellt!
    @sonne75:

    Du kommunizierst mit deinem Gerät offenbar unsynchronisiert, vielleicht noch mit mehreren Threads (tl;dr) und in mehreren Funktionen im Wild-West-Stil. Das führt natürlich zu Chaos, wie du gerade bemerkst. Schreibe also einen Scheduler, der sich um die Kommunikation kümmert. Der sieht folgendermaßen aus:
    - Ein Workerthread läuft in einer Schleife und arbeitet eine Queue ab. Er blockiert, sofern die Queue gerade leer ist. Wenn es Arbeit gibt, wird diese aus der Queue entfernt und ein Kommunikationsvorgang durchgeführt. Dabei wird darauf geachtet, dass das Gerät nicht überlastet wird.
    - Die "Arbeit" besteht aus einer Transaktion, die einen Kommunikationsvorgang mit dem Gerät + Zusatzinformationen darstellt. Jedes Transaktionsobjekt benötigt einen Sende- / Empfangspuffer (ggf. getrennt), einen Befehlspuffer, ein Callback und ein Synchronisationsobjekt. Das Callback wird aufgerufen, wenn der Vorgang abgeschlossen ist (wenn das Gerät geantwortet hat).
    - Möchtest du nun alle x Millisekunden Daten abfragen, lädst du in einer Schleife entsprechende Transaktionen beim Scheduler ab. Möglicherweise möchtest du zwischendurch Daten in das Gerät schreiben - kein Problem - sags dem Scheduler. Der kümmert sich um die Ausführung.
    - Weil du jeder Transaktion ein Callback mitgibst, weißt du genau, wo die Antwort zurückkommt. Das Callback muss natürlich Informationen zur Transaktion bekommen, damit du weißt, um welche Transaktion es aktuell geht.

    Anmerkung: Sowas ist nicht ganz trivial. Windows implementiert ein ähnliches Konzept mit seinen Nachrichtenschleifen / Message Loops. Wenn du danach suchst, finden sich Unmengen an Informationen.
    Gruß
    hal2000
    @wolfi_bayern
    Deine Vorgehensweise stimmt nur, wenn man:

    VB.NET-Quellcode

    1. SendeDaten(xx)
    2. EmpfangeDaten(xxx)


    macht. Also Datenempfang in der gleichen Funktion aufruft, in der man den Absendebefehl gesendet hat. Denn dann weiß man auch, was für Daten erwartet werden. Das wollte ich aber nicht machen, sondern so, wie @hal2000: es beschreibt.

    hal2000 schrieb:

    Sowas ist nicht ganz trivial.

    Ja, das ist das Problem dabei, es soll in 2 Wochen fertig sein.
    Ich will diese Queue simulieren, nur viel abgespeckter, damit ich schneller fertig bin (ja, ich weiß :whistling: ). Zumal es beim "Anlernen" plötzlich viel mehr zu tun gibt, und auch da kommen die Daten immer wieder rein.
    Ich werde mir gleich Gedanken machen, wie man so eine Queue aufbauen kann, da es sehr wenige unterschiedliche Vorgänge gibt (unter 5), kann ich zur Not noch mit "Select Case state" arbeiten, jeder Queueeintrag wird seinen eigenen Statewert haben. Und den State kann ich im DataReceived-Event abfragen, je nachdem, wird das oder das gemacht.

    Was ist ein Callback genau eigentlich? AddressOf von einer Funktion, die dann aufgerufen wird?
    Und was ist ein Synchtronisationsobjekt?

    Ich habe so verstanden, dass Queue aus Transaktionsobjekten bestehen, jeder hat sein State, Sendedaten, Buffer für Empfangsdaten und Callback (im Sinne von AddressOf). Und beim "Anlernen" muss dann wohl im Callback eine Funktion sein, die das gleiche Transaktionsobjekt wieder einfügt (beim Pollen), solange die richtigen Daten nicht kommen.

    Sowas ähnliches habe ich schon für ein anderes Projekt gemacht, nur ohne Queue, weil der Ablauf da ganz fest war, nur den DataReceived-Event musste ich strukturieren.
    @sonne75:: Möglicherweise hab ich ein ähnliches Problem.
    Ich kommuniziere mit einem Messgerät über RS232.
    Das Problem hier ist, dass das Gerät bei manchen Befehlen nach dem Senden der Antwort (oder es gibt keine Antwort) noch nicht bereit ist, den nächsten Befehl zu empfangen.
    So kann man nicht eventgesteuert kommunizieren. ;(
    So sind da bei mir einige Sleep(x)-Befehle drin.
    Vorgehensweise:
    Ich habe 2 Funktionen:

    VB.NET-Quellcode

    1. SendCommand(CMD, wait)
    2. answer = SendCommandWait4Answer(CMD, wait)
    und die wait-Zeit wird für jeden Befehl vorgegeben.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    sonne75 schrieb:

    kein DataReceived-Event drin
    So isses, das läuft bei mir alles in einem Thread.
    Ich würde also in einem Timer die entsprechende Funktion aufrufen, sie allerdings mit einem Mutex oder solch schützen.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    RodFromGermany schrieb:

    sie allerdings mit einem Mutex oder solch schützen.

    Wie meinst du das?

    @hal2000
    Ich habe bei MSDN die Queue(T)-Klasse gefunden, da kann ich doch meine Transaktionsobjekte reinpacken, oder?
    Die Frage ist nur, wie ich ein Element wieder an Anfang der Queue hinzufüge, statt ans Ende, aber das kann ich morgen im Allgemein-Forum nachfragen.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „sonne75“ ()