TIA S7-1517 MB_Client verschiedene Adressbereiche auslesen,

IneedMoney

Level-2
Beiträge
7
Reaktionspunkte
0
Zuviel Werbung?
-> Hier kostenlos registrieren
Hallo Freunde der gepflegten SPS Kultur

Ich habe folgenden Auftrag

Ich setze eine S7-1517 ein, um unter anderen, 5 Modbusteilnehmer auszulesen und zu schreiben.

Die Verbindung zu den Teilnehmern mache ich mit den MB_CLIENT Baustein.

Folgende MB Adressbereiche muss ich lesen

10001-10310 (309Bits) Register sind 1Bit lang
40001-40029 (28Wörter) Register sind 16 Bit lang
43001-43010 (9Wörter) Register sind 16 Bit lang

Schreiben
43001-43010 (9Wörter)

Einzeln kann ich alle Bereiche Lesen und 43001 auch schreiben.


MB_Client kann nur mit einer Instanz pro Teilnehmer betrieben werden deswegen kann ich pro Adressbereich nicht ein MB_Client aufrufen.
Ursprünglich habe ich eine Sequenz geschrieben die für jeden MB Adressbereich ein MB_Client aufrief. Funktionierte logischerweise nicht.

Nun habe ich einen Datenbaustein als Buffer für die Daten erstellt. Ein Array[0..308] of Word.
Die Adressen weise ich mit einem CASE dem MB_Client zu. Soweit kein Thema
Die Daten müssen entsprechend verarbeitet werden und in einen separaten Baustein verschoben werden.
Für die Adressbereiche 40001 und 43001 funktioniert das Wunderbar.

Ich stehe jetzt bei den Daten für die Adressen 10001 an.
Im Adressbereich 10001 + werden nur Bits übertragen.
Also müsste im Register 10001, 1 oder 0 in meinen Word[0] stehen. Mache ich einen Denkfehler?


Dies scheint nicht so zu sein. Die ersten 73 Bits sollten false sein. Ab Bit 74 sind die Bits true.
Ich habe jedoch nun bereits im Word[4] bereits 16#00FC stehen.
Im word[5] steht 16#3F0C.

Mit Scatter habe ich die Words ein ein Bool Array verschoben. Das Resultat passt nicht. Auch nachdem ich die Words geswapt habe in einen Zwischenspeicher.

Ich verstehe nicht was vom MB_Client in Buffer Array[0..308] of Word geschrieben wird.
Kann mir jemand helfen?

Danke und Gruss
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Die ersten 73 Bits sollten false sein. Ab Bit 74 sind die Bits true.
Ich habe jedoch nun bereits im Word[4] bereits 16#00FC stehen.
Da werden vermutlich immer 16 Bits in ein Word gepackt.
Word[0]..Word[3] = 0 , Word[4] = 16#00FC würde bedeuten: zuerst 72 0-Bits, dann das erste 1-Bit
 
Arbeite mit mehreren "Jobs" oder einer "Jobliste" mit der selben MB_CLIENT-Instanz, d.h. nachdem ein Leseauftrag für einen Bereich abgeschlossen ist, ändere alle Auftragsparameter für den nächsten Bereich. Ein Beispiel für eine Jobliste kannst du z.B. hier finden:
Add-On-Bausteine für die Modbus/TCP-Kommunikation
Danke für den Hinweis. Dem Handbuch entnehme ich, dass die Joblist nicht für den MB_Client ausgelegt ist.
Das Zuweisen der einzelnen Adressen ist für mich auch kein Problem. Lässt sich leicht mit einem CASE erledigen.
Das Problem ist, dass ich immer den gleichen Datenbaustein einsetzen muss. Dieser passt jedoch meistens nur für einen Adressbereich.
Darum habe ich den DB als Word ausgewählt damit jedes Register (2xByte) in ein Word geschrieben werden kann. Für die Adressen 10001+ passt das gar nicht. Wenn ich einen array of Bool verwende geht es mit den 100001+ Adressen. Mit einen array of bool habe ich jedoch die Befürchtung das nur das erste Bit pro Register in den DB geschrieben wird bei den 400001+ Adressen.
 
Da werden vermutlich immer 16 Bits in ein Word gepackt.
Word[0]..Word[3] = 0 , Word[4] = 16#00FC würde bedeuten: zuerst 72 0-Bits, dann das erste 1-Bit
Habe ich zuerst auch vermutet.

Hab dann folgendes gemacht:

FOR "IG1000 Modbus Settings".i := 0 TO 5 DO
// Statement section FOR
//

SCATTER_BLK(IN := "TCP_MB_DATA".IM_KS1_Buffer_Swap["IG1000 Modbus Settings".i],
COUNT_IN := 6,
OUT => "TCP_MB_DATA".IM_KS1_DATA[0]);

;
END_FOR;

FOR "IG1000 Modbus Settings".i := 6 TO 11 DO
// Statement section FOR
//

SCATTER_BLK(IN := "TCP_MB_DATA".IM_KS1_Buffer_Swap["IG1000 Modbus Settings".i],
COUNT_IN := 6,
OUT => "TCP_MB_DATA".IM_KS1_DATA[96]);

;
END_FOR;

FOR "IG1000 Modbus Settings".i := 12 TO 17 DO
// Statement section FOR
//

SCATTER_BLK(IN := "TCP_MB_DATA".IM_KS1_Buffer_Swap["IG1000 Modbus Settings".i],
COUNT_IN := 6,
OUT => "TCP_MB_DATA".IM_KS1_DATA[192]);

;
END_FOR;

FOR "IG1000 Modbus Settings".i := 18 TO 23 DO
// Statement section FOR
//

SCATTER_BLK(IN := "TCP_MB_DATA".IM_KS1_Buffer_Swap["IG1000 Modbus Settings".i],
COUNT_IN := 6,
OUT => "TCP_MB_DATA".IM_KS1_DATA[288]);

;
END_FOR;



Das Resultat stimmte gar nicht. Habe anschliessend vermutet, dass die Bytes vertauscht sind. Hab einen Swap gemacht und anschliessend wieder gleich wie oben konvertiert. War noch schlimmer.

Ich verstehe nicht was der MB_Client mir in den DB schreibt. Desewgen komme ich nicht weiter.

Hab auch mal folgendes Probiert

FOR "IG1000 Modbus Settings".i := 0 TO 308 BY 1 DO

"TCP_MB_DATA".IM_KS1_DATA["IG1000 Modbus Settings".i] := "TCP_MB_DATA".IM_KS1_Buffer["IG1000 Modbus Settings".i].%X0;
;
END_FOR;

Leider ohne erfolg
 
Also ich habe den Fehler gefunden.

Die Bits werden einzeln in ein Word geschrieben. Sagen wir die ersten 16 Bits sind true so sind alle im ersten Word abgelegt.
Das Word muss geswapt werden.


Scatter Input muss nicht mit den For Zähler erhöht werden. Es reicht den Startbereich zu definieren:


SCATTER_BLK(IN := "TCP_MB_DATA".IM_KS1_Buffer_Swap[0],
COUNT_IN := 6,
OUT => "TCP_MB_DATA".IM_KS1_DATA[0]);
 
FOR "IG1000 Modbus Settings".i := 0 TO 5 DO
// Statement section FOR
//

SCATTER_BLK(IN := "TCP_MB_DATA".IM_KS1_Buffer_Swap["IG1000 Modbus Settings".i],
COUNT_IN := 6,
OUT => "TCP_MB_DATA".IM_KS1_DATA[0]);

;
END_FOR;

Also ich habe den Fehler gefunden.

Scatter Input muss nicht mit den For Zähler erhöht werden. Es reicht den Startbereich zu definieren:


SCATTER_BLK(IN := "TCP_MB_DATA".IM_KS1_Buffer_Swap[0],
COUNT_IN := 6,
OUT => "TCP_MB_DATA".IM_KS1_DATA[0]);

Völlig richtig, denn in deiner ersten For-Schleife machst du folgendes:
Du nimmst 6 WORD, startend bei "TCP_MB_DATA".IM_KS1_Buffer_Swap[0] und schreibst diese Bit für Bit auf den Bereich von "TCP_MB_DATA".IM_KS1_DATA[0] bis "TCP_MB_DATA".IM_KS1_DATA[95]. Dann nimmst du nacheinander jeweils 6 WORD, startend bei den Adressen "TCP_MB_DATA".IM_KS1_Buffer_Swap[1] bis "TCP_MB_DATA".IM_KS1_Buffer_Swap[5] und schreibst die jeweils Bit für Bit auf den gleichen Bereich.
Das führt im Endeffekt dazu, dass du 5 unnötige SCATTER_BLK aufrufst und danach der Bereich "TCP_MB_DATA".IM_KS1_DATA[0] bis "TCP_MB_DATA".IM_KS1_DATA[95] mit den Bits aus "TCP_MB_DATA".IM_KS1_Buffer_Swap[5] bis "TCP_MB_DATA".IM_KS1_Buffer_Swap[10] beschrieben ist.

Kleine Bemerkung noch nebenan: Für die Laufvariable in FOR-Schleifen solltest du am besten temporäre Variablen verwenden. Damit vermeidest du, dass merkwürdige Sachen passieren, wenn beispielsweise Interrupt-OBs deine Laufvariable manipulieren.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Hier die Lösung wie man mehrere Adressbereiche mit einer MB_Client instanz auslesen kann:

(*
Funktion zum Auslesen eines Modbusservers auf einer Comap Steuerung
*)



CASE "NEA2 Connection Data".Counter_Sequenz OF
1: // Auslesen der Binaries auf dem Comap Controller

//Verbindungen zum Modbusserver trennen, für die zuweisung der Adressen
"NEA2 Connection Data".Connection.MB_Disconnect := TRUE;

//Modbusadressen zuweisen und länge der Nachricht definieren
"NEA2 Connection Data".Connection.MB_Data_Addr := 10001;
"NEA2 Connection Data".Connection.MB_Data_Len := 488;
"NEA2 Connection Data".Connection.MB_Mode := 0;


//Verbinden und Modbus Adresse auslesen
"NEA2 Connection Data".Connection.MB_Disconnect := FALSE;
"NEA2 Connection Data".Connection.MB_REQ := TRUE;
#End_CASE := false;


//Verschieben der Daten in die zugehörigen Datenbausteine
//Bei den Binaries werden die einzelnen Bits in den Word des Buffers gefüllt. Die ersten 16 Bits sind im ersten Word usw
//
IF "NEA2 Connection Data".Connection.MB_Done = TRUE //Wenn MB_Client die Daten bezogen hat wird das schieben der Daten gestartet. Kann mit MB_Transaction ID des MB_Clients ergänzt werden.
THEN

"NEA2 Connection Data".Connection.MB_REQ := FALSE; //Modbus Request beenden

FOR #i_Swap := 0 TO 30 BY 1 DO // Daten müssen vorab geswapt werden bevor die Bits aus den Word separiert werden können

#RetValSwap := SWAP("NEA2 MB DATA".Buffer[#i_Swap]);

#Swap_Data[#i_Swap] := #RetValSwap;
;
END_FOR;

// Die geswapten Daten in den Datenbaustein schieben

SCATTER_BLK(IN := #Swap_Data[0],
COUNT_IN := 6,
OUT => #Scatter_Data[0]);



SCATTER_BLK(IN := #Swap_Data[6],
COUNT_IN := 6,
OUT => #Scatter_Data[96]);



SCATTER_BLK(IN := #Swap_Data[12],
COUNT_IN := 6,
OUT => #Scatter_Data[192]);



SCATTER_BLK(IN := #Swap_Data[18],
COUNT_IN := 6,
OUT => #Scatter_Data[288]);



SCATTER_BLK(IN := #Swap_Data[24],
COUNT_IN := 6,
OUT => #Scatter_Data[384]);

SCATTER_BLK(IN := #Swap_Data[30],
COUNT_IN := 1,
OUT => #Scatter_Data[480]);

//Variablen vom Array in den udt schieben. Wird mit einem FC ausgeführt der Serialize und deserialize enthält
"POOL_COPY"(RetValSerialize=>#RetValSerialize,
RetValDeserialize=>#RetValDeserialize,
Source:=#Scatter_Data,
Target:="NEA2 MB DATA".Binaries);


#End_CASE := TRUE; // end Value wird benöitgt um erst in den nächsten schritt zu gelangen, wenn das Schieben der Daten abgeschlossen ist

END_IF;

IF #End_CASE = TRUE THEN

"NEA2 Connection Data".Counter_Sequenz := "NEA2 Connection Data".Counter_Sequenz + 1; // Schritt um 1 erhöhen um in die nächste Sequenz zu gelangen
END_IF ;

;
2: // Auslesen der Values vom Comap Controller

//Verbindungen zum Modbusserver trennen, für die zuweisung der Adressen
"NEA2 Connection Data".Connection.MB_Disconnect := TRUE;

//Modbusadressen zuweisen und länge der Nachricht definieren
"NEA2 Connection Data".Connection.MB_Data_Addr := 41001;
"NEA2 Connection Data".Connection.MB_Data_Len := 88;
"NEA2 Connection Data".Connection.MB_Mode := 0;

//Verbinden und Modbus Adresse auslesen
"NEA2 Connection Data".Connection.MB_Disconnect := FALSE;
"NEA2 Connection Data".Connection.MB_REQ := TRUE;
#End_CASE := false;

//Values vom Buffer in den Datenbaustein verschieben
//
IF "NEA2 Connection Data".Connection.MB_Done = TRUE
THEN
"NEA2 Connection Data".Connection.MB_REQ := FALSE; // Modbus request beenden

FOR #i_Values := 0 TO 87 BY 1 DO
"NEA2 MB DATA".DATA_Values[#i_Values] := "NEA2 MB DATA".Buffer[#i_Values];

END_FOR;

IF #i_Values > 87 THEN // end Value wird benöitgt um erst in den nächsten schritt zu gelangen, wenn das Schieben der Daten abgeschlossen ist
#End_CASE := TRUE;
END_IF;

END_IF;

IF #End_CASE = TRUE THEN

"NEA2 Connection Data".Counter_Sequenz := "NEA2 Connection Data".Counter_Sequenz + 1; // Schritt um 1 erhöhen um in die nächste Sequenz zu gelangen
END_IF;
;

3: //Auslesen der Setpoints vom Comap Controller

//Verbindungen zum Modbusserver trennen, für die zuweisung der Adressen
"NEA2 Connection Data".Connection.MB_Disconnect := TRUE;

//Modbusadressen zuweisen und länge der Nachricht definieren
"NEA2 Connection Data".Connection.MB_Data_Addr := 43001;
"NEA2 Connection Data".Connection.MB_Data_Len := 9;
"NEA2 Connection Data".Connection.MB_Mode := 0;

//Verbinden und Modbus Adresse auslesen
"NEA2 Connection Data".Connection.MB_Disconnect := FALSE;
"NEA2 Connection Data".Connection.MB_REQ := TRUE;
#End_CASE := FALSE;

//Setpoints vom Buffer in den Datenbaustein verschieben
IF "NEA2 Connection Data".Connection.MB_Done = TRUE
THEN
"NEA2 Connection Data".Connection.MB_REQ := false;// Modbus request beenden

FOR #i_Setpoints := 0 TO 12 BY 1 DO
"NEA2 MB DATA".DATA_Setpoints[#i_Setpoints] := "NEA2 MB DATA".Buffer[#i_Setpoints];

END_FOR;
IF #i_Setpoints > 12 THEN// end Value wird benöitgt um erst in den nächsten schritt zu gelangen, wenn das Schieben der Daten abgeschlossen ist
#End_CASE := TRUE;
END_IF;

END_IF;

IF #End_CASE = TRUE THEN

"NEA2 Connection Data".Counter_Sequenz := "NEA2 Connection Data".Counter_Sequenz + 1; // Schritt um 1 erhöhen um in die nächste Sequenz zu gelangen
END_IF;

4:// Schreiben der Setpoints
IF "NEA2 Connection Data".Write_Data THEN //Es wird nur geschrieben sofern notwendig um den Speicher der Comap Controller zu schonen

//Verbindungen zum Modbusserver trennen, für die zuweisung der Adressen
"NEA2 Connection Data".Connection.MB_Disconnect := TRUE;

//Modbusadressen zuweisen und länge der Nachricht definieren
"NEA2 Connection Data".Connection.MB_Data_Addr := 43001;
"NEA2 Connection Data".Connection.MB_Data_Len := 9;
"NEA2 Connection Data".Connection.MB_Mode := 1;

//Zu schreibende Daten in den Daten Buffer verschieben
FOR #i_write := 0 TO 12 BY 1 DO
"NEA2 MB DATA".Buffer[#i_write] := "NEA2 MB DATA".Write_Setpoints[#i_write];
END_FOR;


//Verbinden und Modbus Adresse auslesen
"NEA2 Connection Data".Connection.MB_Disconnect := FALSE;
"NEA2 Connection Data".Connection.MB_REQ := TRUE;

IF "NEA2 Connection Data".Connection.MB_Done THEN
#End_CASE := TRUE;
END_IF;

IF #End_CASE = TRUE THEN
"NEA2 Connection Data".Connection.MB_REQ := FALSE;
"NEA2 Connection Data".Write_Data := FALSE;
"NEA2 Connection Data".Counter_Sequenz := 1;
END_IF;

ELSE
"NEA2 Connection Data".Counter_Sequenz := 1;
;
END_IF;
ELSE
;


END_CASE;


(*
MB_Client, Kommuniziert mit dem Modbusserver auf der zugewiesenen IP Adresse
*)

"MB_CLIENT_NEA2".MB_Unit_ID := 16#2; // schreiben der ID Nummer des Controller in den MB Client Baustein (Gleich CAN Adresse des Comap Controllers)

"MB_CLIENT_NEA2"(REQ:="NEA2 Connection Data".Connection.MB_REQ,
DISCONNECT:="NEA2 Connection Data".Connection.MB_Disconnect,
MB_MODE:="NEA2 Connection Data".Connection.MB_Mode,
MB_DATA_ADDR:="NEA2 Connection Data".Connection.MB_Data_Addr,
MB_DATA_LEN:="NEA2 Connection Data".Connection.MB_Data_Len,
DONE=>"NEA2 Connection Data".Connection.MB_Done,
BUSY=>"NEA2 Connection Data".Connection.MB_Busy,
ERROR=>"NEA2 Connection Data".Connection.MB_Error,
STATUS=>#RetValMBClient,
MB_DATA_PTR:="NEA2 MB DATA".Buffer,
CONNECT:="NEA2 Connection Data".Connection.ConectParamClient);



//Schreiben des Fehlercodes bei einen Error des MB_Clients

IF "NEA2 Connection Data".Connection.MB_Error = TRUE //OR "NEA2 Connection Data".Connection.MB_Done =TRUE // Done ist nur für den Test Betrieb und muss entfernt werden

THEN
"NEA2 Connection Data".Connection.MB_Status:= #RetValMBClient; //Status des MB Clients
//"NEA2 Connection Data".Connection.MB_REQ := FALSE; // Nur für den Test Betrieb
END_IF;
 
Im Prinzip kann man aber auch für jeden Modbus Auftrag den Pointer dynamisch austauschen und gleich in einen Datenbaustein reinschreiben, dann entfällt die ganze Kopieraktion. Mit der AT Deklaration kann man das recht einfach machen.
1709663592401.png

1709664470979.png
#anyPtr.Syntax_ID := b#16#10;
#anyPtr.DataTyp := 2;//Byte
#anyPtr.Count := UINT_TO_WORD(#_LEN);
#anyPtr.DB_Nummer := UINT_TO_WORD(#DB_Nr);
IF #DB_Nr > 0 THEN
#anyPtr.Byte_Pointer := dw#16#84000000;//DATENBAUSTEINE
ELSE
#anyPtr.Byte_Pointer := dw#16#83000000;//MERKER
END_IF;
#anyPtr.Byte_Pointer := #anyPtr.Byte_Pointer OR (SHL(IN := WORD_TO_DWORD(UINT_TO_WORD(#DW)), N := 3));



Dem Modbus Baustein dann den Any Pointer MB_DATA_PTR := #DATA_PTR übergeben.
 
@NOBS

Danke, genau nach sowas habe ich gesucht. Ursprünglich wollte ich für den MB_DATA_PTR einen "Platzhalter" schreiben, der auf die gewünschten Datenbausteine zeigte. Habe ich leider nicht hinbekommen.
Da das Projekt eilt musste ich die Kommunikation zum laufen bringen....
Würde deine Lösung aber gern verstehen.

Dein Daten_PTR ist dem DATA_PTR_UDT überlegt. Als Typ ANY zeigt er auf einen Datenbaustein? Verstehe ich das richtig?

Was alles im DATA_PTR stehen muss verstehe ich nicht ganz:

- Syntax ID, für was steht die Syntax ID?
-DatenTyp, sagt dieser welcher Datentyp im Datenbaustein geschrieben wird?
-Count, wofür steht Count? Anzahl Variablen oder Anzahl DatenTyp?
-DB_Nummer, entspricht das der Nummer des gewünschten Datenbausteins?
-Byte_Pointer, ist die Adresse der Variablen innerhalb des Datenbausteins?
Wenn diese Variablen in einen UDT stehen betrachtet ANY dies als ein Array of Byte?
-Wozu: (SHL(IN := WORD_TO_DWORD(UINT_TO_WORD(#DW)), N := 3));?

1709675576545.png

Anhand von meinen DB oben müsste ich folgende eingaben machen. Ich möchte z.B auf Values verweisen

#anyPtr.Syntax_ID := b#16#?;
#anyPtr.DataTyp := 2;//Byte //Weil Values ein UDT ist wähle ich Byte?
#anyPtr.Count := UINT_TO_WORD(22); // 22 weil Values
#anyPtr.DB_Nummer := UINT_TO_WORD(4); //Weil DB Nr 4?
IF #DB_Nr > 0 THEN
#anyPtr.Byte_Pointer := dw#16#84000000_02D400000; //DATENBAUSTEINE Weil Values auf 724.0 liegen?
ELSE
#anyPtr.Byte_Pointer := dw#16#83000000;//MERKER //könnt ich weglassen?
END_IF;
#anyPtr.Byte_Pointer := #anyPtr.Byte_Pointer OR (SHL(IN := WORD_TO_DWORD(UINT_TO_WORD(#DW)), N := 3)); //Links schieben kann ich weglassen?

Die Beschreibung von ANY in der Hilfe ist auf jeden Fall mager.

Probiers morgen aus ob Ichs zum laufen bringe. Das ich nun AT wird meine Arbeit leichter machen.


Nochmals Danke
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Hallo

Es kommt ja drauf an wie viele Bereiche man zu übertragen hat mit der Übergabe von Integer Variablen kann man die Adressen gut aus einem Array zuweisen in dem viele Aufträge sind. Wenn du nur ein paar Adressen übergeben willst kannst du den Zeiger auch direkt nehmen. Beispielsweise in einem Case in IDX ist die Nummer des aktuellen Auftrags. Das wäre bei dir:

CASE IDX OF
1:
#DATA_PTR := "Kopfstation2 MB DATA".Binaries;
2:
#DATA_PTR := "Kopfstation2 MB DATA".Values;
3:
#DATA_PTR := "Kopfstation2 MB DATA".Setpoints;
END_CASE;

Dem Modbus Baustein dann den Any Pointer MB_DATA_PTR := #DATA_PTR übergeben.
 
Das unübersichtliche/fehlerträchtige Zusammenbasteln des einen ANY-Pointers ist doch nur der halbe Part. Die Auftragsparameter (Registernummern, Anzahl, Funktion) müssen ja auch noch umkopiert werden. Da kann man auch gleich eine richtige Jobliste programmieren. Das ist bei Modbus eh' üblich und es gibt auch fertige Siemens Beispiele für solche Joblisten.
 
Das ist so, mit dem vorgeschlagenen CASE benötigt man aber keinen AT Pointer und das macht den Code von IneedMoney deutlich kürzer. Für den Fall von vielen Aufträgen hatte ich ja gesagt, dass hier die Übergabe von Integer Werten besser ist.
Wenn man das AT Pointer Handling und die Übergabe der Modbus Adressen richtig in einer Funktion kapselt ist ein Auftragshandler recht einfach zusammenzustellen. Mir ging es darum eine Lösung zu zeigen, welche das Umkopieren aus dem Modbus erübrigt. Die Übergabe der Modbus Adressen ist ja recht einfach.
 
Zurück
Oben