TIA 'Any-Pointer' im DB speichern?

Fireman_Frank

Level-2
Beiträge
160
Reaktionspunkte
29
Zuviel Werbung?
-> Hier kostenlos registrieren
Ich habe von einem Modbus-Server größere Datenmengen abzuholen, die in verschiedenen Strukturen in verschiedenen Registern zur Verfügung stehen. Für einen 'Datensatz' schreibe ich an den MB_Client Baustein die Registernummer, die Anzahl der Register und den Zielbereich für die Daten in meiner SPS. Soweit funktioniert das auch alles.
Um jetzt viele (auch strukturell verschiedene) Datensätze abholen zu können wollte ich einem DB erstellen mit einem array of (Struktur mit Registernummer, Anzahl und Zieladresse), und dann in einer Schleife mittels Umlaufvariable nacheinander alle Datensätze abzuholen. Aber wie lege ich einen Pointer auf den Zielbereich in einem DB ab? Das in meinem Beispiel angegebene 'DB_Any' funktioniert natürlich nicht. Oder wie löse ich das besser?

Gruß Frank

MB_1.pngMB_2.pngMB_3.png
 
Ich verzichte bei S7-1500 auf Pointer. Für genau sowas wie eben dynamisch Datenempfang und rumschieben nutze ich peek poke etc.
Bsp:

Code:
  #MBWrite: // Bereichsangabe prüfen
        // Wenn nicht beide 0 dann schreiben der Bereiche
        IF #Bereiche[#Bereichszeiger].ZielDB <> 0 THEN
            #ModbusCtrl.MB_DATA_LEN := #Bereiche[#Bereichszeiger].Lange;
            #ModbusCtrl.MB_DATA_ADDR := #Bereiche[#Bereichszeiger].Adresse;
            #ModbusCtrl.req := true;
            #ModbusCtrl.MB_Mode := #Bereiche[#Bereichszeiger].Modus;
            IF #ModbusCtrl.MB_Mode = 1 THEN // Wenn Schreiben dann DB in Buffer schreiben
                IF #ModbusCtrl.MB_DATA_ADDR >= 1 AND #ModbusCtrl.MB_DATA_ADDR <= 19999 THEN // Coils
                    FOR #i := 0 TO UINT_TO_DINT(#ModbusCtrl.MB_DATA_LEN / 8) DO
                        #Buffer[#i] := PEEK(area := 16#84,
                                            dbNumber := #ZielDB,
                                            byteOffset := #Bereiche[#Bereichszeiger].Zieloffset + #i);
                    END_FOR;
                ELSIF #ModbusCtrl.MB_DATA_ADDR >= 40001 AND #ModbusCtrl.MB_DATA_ADDR <= 49999 THEN // Register
                    FOR #i := 0 TO UINT_TO_DINT(#ModbusCtrl.MB_DATA_LEN * 2) - 1 DO
                        #Buffer[#i] := PEEK(area := 16#84,
                                            dbNumber := #ZielDB,
                                            byteOffset := #Bereiche[#Bereichszeiger].Zieloffset + #i);
                    END_FOR;
                ELSIF #ModbusCtrl.MB_DATA_ADDR >= 30001 AND #ModbusCtrl.MB_DATA_ADDR <= 39999 THEN // Register
                    FOR #i := 0 TO UINT_TO_DINT(#ModbusCtrl.MB_DATA_LEN * 2) - 1 DO
 
Irgendwie muss/will er aber in seiner Jobliste angeben, in welchem Zielbereich die jeweiligen Modbus-Werte abgelegt werden sollen.

Um welche SPS geht es hier eigentlich? Und welche TIA Version?
 
Um jetzt viele (auch strukturell verschiedene) Datensätze abholen zu können
Wenn die Datenstruktur nicht fest ist, wurde ich Serialize/Deserialize verwenden.
Dies kann man auch mit indizierte Parameter programmieren damit man dynamizieren kann.

Eigentlich glaube ich dass MB_CLIENT und SERIALIZE/DESERIALIZE nicht in die Schleife plaziert werden muss.
MB_CLIENT läuft über mehrere Aufrufe, also kann es nicht in eine Schleife sein.

Eher, eine Index steuern, nach jeden MB_CLIENT sequenz 'DONE', SERIALIZE/DESERIALIZE aufrufen und die Index inkrementieren.
 
Zuletzt bearbeitet:
Zuviel Werbung?
-> Hier kostenlos registrieren
und dann in einer Schleife mittels Umlaufvariable nacheinander alle Datensätze abzuholen
In einer Schleife geht das nicht weil der MB_CLIENT für einen Auftrag mehrere Durchläufe braucht. Du musst warten, bis ein Leseauftrag fertig ist (DONE oder ERROR) und dann mit anderen Auftragsparametern den nächsten Auftrag starten.
Die Auftragsparameter kann man in einer Jobliste hinterlegen und vor Auftragsstart in die MB_CLIENT-Aufrufparameter umladen, oder bei nur wenigen verschiedenen Aufträgen kann man auch mehrere komplett parametrisierte MB_CLIENT-Aufrufe programmieren und immer nur einen davon aufrufen.
 
Irgendwie muss/will er aber in seiner Jobliste angeben, in welchem Zielbereich die jeweiligen Modbus-Werte abgelegt werden sollen.

Ja, genau darum geht es. Das mit mehreren 'MB_Client'-aufrufen sozusagen 'zu Fuß' erledigen würde ich schon hinbekommen. Dachte das mit meiner Idee vielleicht eleganter hinzubekommen

Um welche SPS geht es hier eigentlich? Und welche TIA Version?

CPU 1516-3 PN/DP und TIA V18

Eigentlich glaube ich dass MB_CLIENT und SERIALIZE/DESERIALIZE nicht in die Schleife plaziert werden muss.
MB_CLIENT läuft über mehrere Aufrufe, also kann es nicht in eine Schleife sein.

In einer Schleife geht das nicht weil der MB_CLIENT für einen Auftrag mehrere Durchläufe braucht. Du musst warten, bis ein Leseauftrag fertig ist (DONE oder ERROR) und dann mit anderen Auftragsparametern den nächsten Auftrag starten.

Ja, das ist soweit klar.

Die Auftragsparameter kann man in einer Jobliste hinterlegen und vor Auftragsstart in die MB_CLIENT-Aufrufparameter umladen,

Wie würde man so eine Jobliste denn deklarieren?

bei nur wenigen verschiedenen Aufträgen kann man auch mehrere komplett parametrisierte MB_CLIENT-Aufrufe programmieren und immer nur einen davon aufrufen.

So habe ich es bisher immer gemacht, davon will ich gerne weg, siehe oben.

Gruß Frank
 
So habe ich es bisher immer gemacht, davon will ich gerne weg, siehe oben.
Wie gesagt Mit peek poke kann man die Daten aus dem Buffer und in den Buffer am ModbusCtrl schreiben.
Code:
  #MBRead:
        IF #ModbusCtrl.DONE THEN
            #Bereiche[#Bereichszeiger].Fehltelegramme := 0;
            IF #ModbusCtrl.MB_Mode = 0 THEN // Wenn Lesen dann DB mit Buffer überschreiben
                #ModbusCtrl.MB_DATA_LEN := #Bereiche[#Bereichszeiger].Lange;
                IF #ModbusCtrl.MB_DATA_ADDR >= 1 AND #ModbusCtrl.MB_DATA_ADDR <= 19999 THEN // Coils
                    FOR #i := 0 TO UINT_TO_DINT(#ModbusCtrl.MB_DATA_LEN / 8) DO
                        POKE(area := 16#84,
                             dbNumber := #ZielDB,
                             byteOffset := #Bereiche[#Bereichszeiger].Zieloffset + #i,
                             value := #Buffer[#i]);
                    END_FOR;
                ELSIF #ModbusCtrl.MB_DATA_ADDR >= 40001 AND #ModbusCtrl.MB_DATA_ADDR <= 49999 THEN // Register
                    FOR #i := 0 TO UINT_TO_DINT(#ModbusCtrl.MB_DATA_LEN * 2) - 1 DO
                        POKE(area := 16#84,
                             dbNumber := #ZielDB,
                             byteOffset := #Bereiche[#Bereichszeiger].Zieloffset + #i,
                             value := #Buffer[#i]);
                    END_FOR;

done abwarten dann den Buffer lesen bzw. beschreiben dann den nächsten Bereich anvisieren.
 
Für genau deinen Anwendungsfall hab ich mir vor einiger Zeit einen ganzen Baustein gebaut, der aus einzelnen Modbus-Datenpunkten "Blöcke" erstellt welche dann sequentiell gelesen bzw. geschrieben werden. Damit kann ich mir sehr schnell die einzelnen DP anlegen und auch hunderte Punkte über verstreute Adressen vernünftig lesen.

Der Block:
1772792784163.png

Bilden kannst du die Blöcke zB. aus einzelnen Datenpunkten, in denen du deinen Speicherbereich und die Adresse hinterlegst.
Im oberen Modbus_Block siehst du einen DpIdx hinterlegt, in meinem Fall steht dort der Start-Arrayindex des ersten Datenpunkts, so werden die Daten auch direkt wieder passend zurückgeschrieben.
1772792795017.png

Beim Schreiben gehe ich sehr ähnlich vor.
1772792665905.png

Die MB_Client selbst schreiben zuerst in einen Buffer, den ich je nach benötigter Area umschalte (Mode 101 / 102 / 115 verlangen einen Byte-Buffer, während 103 / 104 / 116 Words wollen) .
1772793566354.png
1772793538663.png

Ich komme damit im ganzen Baustein sehr gut ohne Pointer / Peek / Poke aus, auf Kosten von 250 zusätzlichen Bytes im Arbeitsspeicher.
 

Anhänge

  • 1772793519640.png
    1772793519640.png
    17,9 KB · Aufrufe: 11
Da mache ich einen Puffer der für die maximale Länge ausgelegt ist, einen Int welche Länge gerade gefordert ist und gut ist. Dann kann im mit Serialize Daten hin und her schaufeln.
 
Das Problem beim Siemens Baustein ist doch, dass er eine feste Datenlänge anhand der verschalteten Struktur/UDT-Größe erwartet. Wenn ich das umgehe, kann ich beliebige Datengrößen verwenden und eine maximale Größe verschalten und muss dem Baustein nur sagen, wie groß die tatsächliche Größe ist. Wieder ein typisches Beispiel von gut gemeint und schlecht gemacht. Wenn ich das universeller gestalte, kann ich die Daten von meinem universellen Puffer hin- und herkopieren wie ich will.
 
Ok, wenn ein selbstgeschriebener Baustein universeller oder gar besser als Siemens ist, wie ist da das Problem gelöst:
Wenn man mit einer Jobliste mit mehreren Leseaufträgen verschiedener Register-Bereiche arbeiten will: wie speichert man da am besten die Zieladresse in den einzelnen Jobs? (was die eigentliche Frage hier im Thread ist)
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Es gibt keine Adresse. Es gibt Register Nummern und eine Anzahl von Worten. Das blöde am Siemens Baustein ist doch, dass er es dem Benutzer einfach machen will und deshalb automatisch bei der Länge von der kompletten Länge der außen angelegten Struktur ausgeht.
 
Es gibt keine Adresse.
Also keine Lösung... Oder ist das wirklich so schwer zu verstehen, was der TE wissen will?

Das blöde am Siemens Baustein ist doch, dass er es dem Benutzer einfach machen will und deshalb automatisch bei der Länge von der kompletten Länge der außen angelegten Struktur ausgeht.
Ich weiß nicht, welchen "Siemens Baustein" du meinst, aber bei dem MB_CLIENT wird die Anzahl/Länge der zu lesenden Register nicht durch
die Größe der Struktur an MB_DATA_PTR festgelegt, sondern durch MB_DATA_LEN. Der Empfangs-Puffer an MB_DATA_PTR darf auch größer als nötig sein.
 
Ich habe mir das jetzt alles durchgelesen, aber ich verstehe das Problem nicht.
Irgendwie geht hier etwas durcheinander oder ihr redet an einander vorbei.

Ich versuche mal wiederzugeben worum es geht:

Es sollen auszuwählende Daten, von irgendwo, gelesen und in ein auszuwählendes Ziel gespeichert werden.
Das Ganze soll flexibel über eine Art "Jobliste" parametreirt werden.

Der erste Teil sollte kein Problem darstellen: Die Jobliste.
Der Zweite Teil sollte auch kein Problem sein: Daten an Kommunikationsbaustein übergeben und auf die Daten Warten.

Was hier wichtig ist: Die Empfangenen Daten sollten immer in einen Empfangs-Puffer geschrieben werden.
Vorteil die "alten" Daten können bis zum Eintreffen weiter verwendet werden (Zugriff auf den Empfangspuffer sind nicht erlaubt während der Übertragung)

Ist der Empfang dann komplett, können die Daten dann in ein Ziel kopiert werden.
Und hier scheint das Problem zu liegen...:

Entweder man baut die Quelle nach: DB-Anlegen mit der selben Struktur und dann mit Deserialize die Daten kopieren
Oder man kopiert das in ein Array (auch mehrdimensional)
In der Jobliste kann man einen "Index" angeben der nach dem Empfang das jeweilige "Parsen" der Daten macht (z.B. switch case)

Was mir wichtig scheint, ist das dies vollsymbolisch bleibt.
Any-Pointer und Peek/Poke sollten der Vergangenheit angehören.


P.S.: Sollte ich hier völlig daneben liegen lösche ich den Inhalt auch gerne wieder.
 
Zurück
Oben