erste Fassung der Konvertierung der xml Dateien in .scl Code aus dem Projekt von Andi Emmert

This commit is contained in:
2026-07-07 15:10:47 +02:00
commit e61cd2319a
796 changed files with 1101171 additions and 0 deletions
+460
View File
@@ -0,0 +1,460 @@
FUNCTION "DatenOut" : Void
TITLE = Datensatz in Datenliste suchen / lesen / loeschen
{ S7_Optimized_Access := 'FALSE' }
AUTHOR : IBAE
FAMILY : Module
NAME : DatenOut
//SCL-Portierung des AWL-Bausteins FC86 "DatenOut" (verhaltensgleich).
//
//Datenlisten-Kopf im Datenlisten-DB ab Byte SSDW:
// +0 Kopf (Byte, 16#DA = Datenliste, 16#DB = Sort-Datenliste)
// +1 Datensatzlaenge (Byte, in Worten, 1..128)
// +2 Listenlaenge (Int, max. Anzahl Datensaetze)
// +4 Stammdaten (Int, Anzahl Suchworte 1..9)
// +6 IndDB (Word, indizierter DB; nur bei Stammdaten=1)
// +8 IndDB_Laenge (Int, Laenge indizierter DB in Eintraegen)
// +10 IndDaten_FC (Int, Wandel-FC-Nummer fuer indizierte Suche)
// +12 Suchzeiger / letzter Zugriff (Int, Byte-Adresse)
// +16 AnzahlEintraege (Int)
// +20 Datenbereich, dahinter Endekennung 16#DAEE bzw. 16#DBEE
//
//Aufrufparameter PARA (High-Byte = Para1, Low-Byte = Para2):
// Para1: 00=suchen, E1=suchen+loeschen, F1=1.Datensatz holen,
// FF=naechsten Datensatz holen, 02..80=Offset-Suche ab Wort X
// Para2: 00=Stammdaten aus Kopf, sonst Anzahl Stammdaten (1..9)
//
//RE_VAL: Bit0=kein Datenlistenbereich, Bit1=Kopier-/SFC-Fehler, Bit2=Listenende,
// Bit3=falscher Parameter, Bit4=undef. Suchdaten, Bit5=undef. OutDaten,
// Bit6=Datensatz nicht vorhanden, Bit7=Daten vorhanden. Erfolg zusaetzlich via ENO/BR.
//
//ABWEICHUNGEN ZUM AWL-ORIGINAL:
// - SEARCHDATEN/OUTDATEN sind VARIANT statt POINTER (S7-1500/SCL).
// SEARCHDATEN muss mind. Stammdaten*2 Bytes referenzieren.
// OUTDATEN sollte exakt Datensatzlaenge*2 Bytes gross sein (groessere Ziele
// werden hinter dem Datensatz mit 0 aufgefuellt).
// - Die DB-Laengenpruefung des Such-/Out-DB (AWL NW5/NW8) entfaellt, da der
// Variant-Zugriff typsicher ist. Fehlerbit 5 (undef. OutDaten) wird nicht mehr erzeugt.
// - Der indirekte Aufruf UC FC[#Wandel_FC] ist als CASE ausgefuehrt
// (aktuell einziger Wandel-FC im Projekt: FC1000 "Wandel_FC_fuer_DB1000").
// - Position wird mit 0 initialisiert (AWL: undefiniert, wenn nicht gefunden).
VAR_INPUT
SSDB : Int; // Datenlisten-DB
SSDW : Int; // Datenlisten-1.DW (Byte-Offset des Kopfs)
PARA : Word; // Aufrufparameter (Auftrag / Anzahl Stammdaten)
SEARCHDATEN : Variant; // Such-Daten-Schnittstelle (Stammdaten Worte)
END_VAR
VAR_OUTPUT
RE_VAL : Byte; // Rueckmeldung (Fehler-/Statusbits)
Position : Int; // Daten stehen an angegebener Position in Datenliste
END_VAR
VAR_IN_OUT
OUTDATEN : Variant; // OutDaten-Schnittstelle (Datensatzlaenge Worte)
END_VAR
VAR_TEMP
Kopf : Byte; // Datenliste-Kennung (DA/DB)
Datensatzlaenge : Int; // Datensatzlaenge in Word (1..128)
Listenlaenge : Int; // Laenge der Datenliste (max. Anzahl Datensaetze)
Stammdaten : Int; // Stammdatenlaenge in Word (1..9)
IndDB : Word; // Indizierter DB (nur bei Stammdaten = 1 erlaubt !!!)
IndDB_Laenge : Int; // Laenge des indizierten DB in Eintraegen
IndDaten_FC : Int; // FC zum Wandeln der indizierten Daten
Suchzeiger : Int; // Speicherung des letzten Zugriffs / Suchzeiger (Byte-Adresse)
Para1 : Byte; // 1.Aufrufparameter
Para2 : Byte; // 2.Aufrufparameter
OffsetDaten_1 : Int; // Stammdaten beginnen ab hier (Byte-Offset)
SFC_RET_VAL : Int; // Rueckmeldung SFC-/Serialize-Aufruf
SFC_DB_LENGTH : Word; // Rueckmeldung TEST_DB-Aufruf
SFC_WRITE_PROT : Bool; // Rueckmeldung TEST_DB-Aufruf (letzter Aufruf!)
DB_Laenge : Int; // DB-Laenge nach BCD-Wandlung (wie AWL)
Datenlisten_Ende : Int; // Ende der Datenliste (Byte-Offset Endekennung)
Datenblock_inBytes : Int; // Datenblocklaenge in Bytes
Daten_1 : Word; // 1.Datenwort der Suchdaten
Daten_2 : DWord; // 1.+2.Datenwort der Suchdaten (fuer Wandel-FC)
Wandel_FC : Int; // Wandel-FC-Nummer
Suchstelle : Int; // Bei angegebenem Datensatz suchen (Position)
LoeschPos : Int; // Loesch-Position
Indok : Bool; // Indizierung moeglich
IndZeiger_ok : Bool; // Indizier-Zeiger angegeben
Weitersuchen : Bool; // Weitersuchen ist aktiv (Para1 F1/FF)
Offsetsuchen : Bool; // Offsetsuchen ist aktiv
Sortliste : Bool; // Sortliste wird durchsucht
tRE_VAL : Byte; // Rueckmeldung (Zwischenwert)
tVKE : Bool; // Ergebnis fuer ENO (BR im AWL)
tGefunden : Bool; // Vergleichs-/Suchergebnis
tDbList : DInt; // Datenlisten-DB-Nummer fuer PEEK/POKE
tDbInd : DInt; // Indizierter DB fuer PEEK/POKE
tIndex : Int; // Indexwert fuer indizierte Suche
IndByteOfs : Int; // Byte-Offset des Index-Eintrags im IndDB
tWort : Word; // Wort-Zwischenwert
tAnz : Int; // Anzahl-Zwischenwert
tAnzBytes : Int; // Anzahl zu verschiebender Bytes
tPos : DInt; // Serialize-/Deserialize-Position
i : Int; // Schleifenzaehler
tSuchPuffer : Array[0..257] of Byte; // Zwischenpuffer SEARCHDATEN
tRecPuffer : Array[0..257] of Byte; // Zwischenpuffer Datensatz -> OUTDATEN
END_VAR
BEGIN
#tRE_VAL := 16#00;
#Suchstelle := 0;
#Position := 0;
REGION NW1: Schnittstellen-DB sinnvoll ?
#tDbList := INT_TO_DINT(#SSDB);
"TEST_DB_TO_ATTR_DB"(DB_Number := INT_TO_WORD(#SSDB),
RetVal => #SFC_RET_VAL,
DB_Length => #SFC_DB_LENGTH,
Write_Prot => #SFC_WRITE_PROT);
"BCD (4stellig)_to_INT"(BCD_Wert := #SFC_DB_LENGTH, Int_Wert => #DB_Laenge);
IF (#SFC_RET_VAL <> 0) OR #SFC_WRITE_PROT THEN // schreibgeschuetzt
GOTO mErr0; // kein Datenlisten-Bereich
END_IF;
END_REGION
REGION NW2: Datenlisten-Kopf holen (AWL: Quelle-Pointer + BLKMOV, 10 Worte)
IF #SSDW < 0 OR (#SSDW + 20) > #DB_Laenge THEN
GOTO mErr1; // Kopf liegt nicht im DB (AWL: SFC20-Fehler)
END_IF;
#Kopf := PEEK(area := 16#84, dbNumber := #tDbList, byteOffset := INT_TO_DINT(#SSDW));
#Datensatzlaenge := BYTE_TO_INT(PEEK(area := 16#84, dbNumber := #tDbList, byteOffset := INT_TO_DINT(#SSDW + 1)));
#Listenlaenge := WORD_TO_INT(PEEK_WORD(area := 16#84, dbNumber := #tDbList, byteOffset := INT_TO_DINT(#SSDW + 2)));
#Stammdaten := WORD_TO_INT(PEEK_WORD(area := 16#84, dbNumber := #tDbList, byteOffset := INT_TO_DINT(#SSDW + 4)));
#IndDB := PEEK_WORD(area := 16#84, dbNumber := #tDbList, byteOffset := INT_TO_DINT(#SSDW + 6));
#IndDB_Laenge := WORD_TO_INT(PEEK_WORD(area := 16#84, dbNumber := #tDbList, byteOffset := INT_TO_DINT(#SSDW + 8)));
#IndDaten_FC := WORD_TO_INT(PEEK_WORD(area := 16#84, dbNumber := #tDbList, byteOffset := INT_TO_DINT(#SSDW + 10)));
#Suchzeiger := WORD_TO_INT(PEEK_WORD(area := 16#84, dbNumber := #tDbList, byteOffset := INT_TO_DINT(#SSDW + 12)));
END_REGION
REGION NW3: Zeigerueberpruefung
#Sortliste := (#Kopf = 16#DB); // Sort-Datenlistenkennung ?
IF NOT #Sortliste AND (#Kopf <> 16#DA) THEN // Datenlistenkennung ?
GOTO mErr0; // kein Datenlistenbereich
END_IF;
IF (#Datensatzlaenge < 1) OR (#Datensatzlaenge > 128)
OR (#Listenlaenge < 1)
OR (#Stammdaten < 1) OR (#Stammdaten > 9)
OR (#Stammdaten > #Datensatzlaenge) THEN
GOTO mErr0; // kein Datenlistenbereich
END_IF;
#Datenlisten_Ende := #Listenlaenge * #Datensatzlaenge * 2 + 20 + #SSDW;
IF #Datenlisten_Ende > #DB_Laenge THEN // Datenlisten-DB ist zu kurz!
GOTO mErr0; // kein Datenlisten-Bereich
END_IF;
#tWort := PEEK_WORD(area := 16#84, dbNumber := #tDbList, byteOffset := INT_TO_DINT(#Datenlisten_Ende));
IF (#tWort <> 16#DAEE) AND (#tWort <> 16#DBEE) THEN // Endekennung ?
GOTO mErr0; // kein Datenlisten-Bereich
END_IF;
#Datenblock_inBytes := #Datensatzlaenge * 2;
END_REGION
REGION NW4: Auftrag kontrollieren
#Indok := FALSE;
#IndZeiger_ok := FALSE;
#Weitersuchen := FALSE;
#Offsetsuchen := FALSE;
#OffsetDaten_1 := 0; // Vorerst kein Offset
#Para1 := WORD_TO_BYTE(SHR(IN := #PARA, N := 8));
#Para2 := WORD_TO_BYTE(#PARA);
IF #Para1 = 16#00 THEN // Normale Suche
GOTO mW401;
END_IF;
IF #Para1 = 16#E1 THEN // Suchen + loeschen
IF #Sortliste THEN
GOTO mErr3; // Austragen aus Sortliste nicht erlaubt !
END_IF;
GOTO mW401;
END_IF;
IF #Para1 = 16#F1 THEN // 1.Datensatz holen
IF (#Para2 <> 16#00) OR #Sortliste THEN
GOTO mErr3; // Falscher Aufrufparameter
END_IF;
#Weitersuchen := TRUE;
#Suchzeiger := #SSDW + 20;
GOTO mNw08; // OutDaten ok ?
END_IF;
IF #Para1 = 16#FF THEN // Naechsten Datensatz holen
IF (#Para2 <> 16#00) OR #Sortliste THEN
GOTO mErr3; // Falscher Aufrufparameter
END_IF;
#Weitersuchen := TRUE;
#Suchzeiger := #Suchzeiger + #Datenblock_inBytes; // Naechsten Datensatz suchen
IF (#Datenblock_inBytes * #Listenlaenge + #SSDW + 20) > #Suchzeiger THEN
GOTO mNw08; // Noch nicht am Ende
END_IF;
GOTO mErr2; // Listenende erreicht
END_IF;
// Offset-Suche: Para1 = Offset des 1.Stammdatenworts (2..128)
IF (BYTE_TO_INT(#Para1) < 2)
OR (BYTE_TO_INT(#Para1) > 128)
OR (BYTE_TO_INT(#Para1) > #Datensatzlaenge) THEN
GOTO mErr3; // Falscher Aufrufparameter
END_IF;
#OffsetDaten_1 := (BYTE_TO_INT(#Para1) - 1) * 2; // Richtiger Offset der 1.Stammdaten
#Suchzeiger := 0; // Suche ab Anfang
#Offsetsuchen := TRUE;
mW401: IF #Para2 <> 16#00 THEN
#Stammdaten := BYTE_TO_INT(#Para2); // Anzahl der Stammdaten aus Aufruf
IF (#Stammdaten > #Datensatzlaenge)
OR (#Stammdaten < 1)
OR (#Stammdaten > 9) THEN
GOTO mErr3; // Falscher Aufrufparameter
END_IF;
END_IF;
IF #Offsetsuchen THEN
IF (BYTE_TO_INT(#Para1) - 1 + #Stammdaten) > #Datensatzlaenge THEN // Letzte Stammdaten zu gross ?
GOTO mErr3; // Falscher Aufrufparameter
END_IF;
END_IF;
END_REGION
REGION NW5: Such-Daten uebernehmen (AWL: Pointer-Parsing)
#Suchzeiger := 0;
#tPos := 0;
#SFC_RET_VAL := Serialize(SRC_VARIANT := #SEARCHDATEN, DEST_ARRAY := #tSuchPuffer, POS := #tPos);
IF (#SFC_RET_VAL <> 0) OR (#tPos < INT_TO_DINT(#Stammdaten * 2)) THEN
GOTO mErr4; // Undefinierte Suchdaten
END_IF;
#Daten_1 := SHL(IN := BYTE_TO_WORD(#tSuchPuffer[0]), N := 8) OR BYTE_TO_WORD(#tSuchPuffer[1]);
#Daten_2 := SHL(IN := BYTE_TO_DWORD(#tSuchPuffer[0]), N := 24)
OR SHL(IN := BYTE_TO_DWORD(#tSuchPuffer[1]), N := 16)
OR SHL(IN := BYTE_TO_DWORD(#tSuchPuffer[2]), N := 8)
OR BYTE_TO_DWORD(#tSuchPuffer[3]);
// Mindestens ein Suchwort <> 0 ?
#tGefunden := FALSE;
FOR #i := 0 TO #Stammdaten - 1 DO
IF (#tSuchPuffer[#i * 2] <> 16#00) OR (#tSuchPuffer[#i * 2 + 1] <> 16#00) THEN
#tGefunden := TRUE;
EXIT;
END_IF;
END_FOR;
IF NOT #tGefunden THEN
GOTO mErr4; // Undefinierte Stammdaten
END_IF;
END_REGION
REGION NW6: Indiziert suchen ?
IF (#IndDB <> 16#0000) AND NOT #Offsetsuchen AND NOT #Sortliste THEN
"TEST_DB_TO_ATTR_DB"(DB_Number := #IndDB,
RetVal => #SFC_RET_VAL,
DB_Length => #SFC_DB_LENGTH,
Write_Prot => #SFC_WRITE_PROT);
"BCD (4stellig)_to_INT"(BCD_Wert := #SFC_DB_LENGTH, Int_Wert => #DB_Laenge);
IF (#SFC_RET_VAL = 0) AND NOT #SFC_WRITE_PROT // Indizier-DB vorhanden
AND NOT (#IndDB_Laenge * 2 > #DB_Laenge) // Indizier-DB lang genug
AND (#Stammdaten < 3) THEN // nicht zu viele Stammdaten
#Wandel_FC := #IndDaten_FC;
IF #Wandel_FC > 0 THEN
"MW90" := #Daten_1; // Stammdaten (1.DW) uebergeben
"MD94" := #Daten_2; // Stammdaten (1.+2.DW) uebergeben
CASE #Wandel_FC OF // AWL: UC FC[#Wandel_FC]
1000:
"Wandel_FC_für_DB1000"();
ELSE
; // unbekannter Wandel-FC: MW92 bleibt unveraendert
END_CASE;
#tIndex := WORD_TO_INT("MW92"); // Rueckwert vom FC
ELSE
#tIndex := WORD_TO_INT(#Daten_1); // Stammdaten direkt als Index
END_IF;
IF (#tIndex >= 0) AND (#tIndex < #IndDB_Laenge) THEN // Zeiger im Ind.DB ?
#IndByteOfs := #tIndex * 2; // DB byteorientiert
#tDbInd := INT_TO_DINT(WORD_TO_INT(#IndDB));
#Suchzeiger := WORD_TO_INT(PEEK_WORD(area := 16#84, dbNumber := #tDbInd,
byteOffset := INT_TO_DINT(#IndByteOfs)));
#Indok := TRUE; // Indizierung moeglich
#IndZeiger_ok := TRUE; // Indizier-Zeiger angegeben
END_IF;
END_IF;
END_IF;
END_REGION
REGION NW7: Kontrolle auf gleiche Stammdaten (Suchschleife)
IF (#Suchzeiger = 0)
OR (((#Suchzeiger - 20) MOD #Datenblock_inBytes) <> 0) THEN // Suchzeiger nicht im Raster
// Suche bei 1.Datensatz beginnen (AWL: W700)
#Suchstelle := 1;
#IndZeiger_ok := FALSE;
#Suchzeiger := #SSDW + 20;
ELSE
#Suchstelle := 0; // Bei angegebener Stelle suchen
END_IF;
REPEAT
// Stammdaten vergleichen (AWL: W701/L700-Schleife)
#tGefunden := TRUE;
FOR #i := 0 TO #Stammdaten - 1 DO
#tWort := SHL(IN := BYTE_TO_WORD(#tSuchPuffer[#i * 2]), N := 8)
OR BYTE_TO_WORD(#tSuchPuffer[#i * 2 + 1]);
IF #tWort <> PEEK_WORD(area := 16#84, dbNumber := #tDbList,
byteOffset := INT_TO_DINT(#Suchzeiger + #OffsetDaten_1 + #i * 2)) THEN
#tGefunden := FALSE;
EXIT;
END_IF;
END_FOR;
IF #tGefunden THEN
#tRE_VAL := 16#80; // Daten sind vorhanden
GOTO mNw08; // Datensatz ist in Liste enthalten
END_IF;
// AWL: W703
IF #Suchstelle = 0 THEN
// Suche bei angegebener Stelle erfolglos: ab 1.Datensatz neu (AWL: W700)
#Suchstelle := 1;
#IndZeiger_ok := FALSE;
#Suchzeiger := #SSDW + 20;
ELSE
#Suchstelle := #Suchstelle + 1;
IF #Suchstelle > #Listenlaenge THEN // Alle Datensaetze durchsucht
GOTO mErr6; // Datensatz ist nicht vorhanden
END_IF;
#Suchzeiger := #Suchzeiger + #Datenblock_inBytes; // AWL: W705
END_IF;
UNTIL FALSE END_REPEAT;
END_REGION
REGION NW8+NW9: Datensatz an OUTDATEN uebergeben
mNw08: FILL_BLK(IN := BYTE#16#00, COUNT := 258, OUT => #tRecPuffer[0]);
FOR #i := 0 TO #Datensatzlaenge * 2 - 1 DO
#tRecPuffer[#i] := PEEK(area := 16#84, dbNumber := #tDbList,
byteOffset := INT_TO_DINT(#Suchzeiger + #i));
END_FOR;
#tPos := 0;
#SFC_RET_VAL := Deserialize(SRC_ARRAY := #tRecPuffer, DEST_VARIANT := #OUTDATEN, POS := #tPos);
IF #SFC_RET_VAL <> 0 THEN
GOTO mErr1; // Kopier-Fehler (AWL: SFC20-Aufruf mit Fehler)
END_IF;
IF NOT #SFC_WRITE_PROT THEN // Datenliste kann beschrieben werden
// Speicherung letzter Zugriff
POKE(area := 16#84, dbNumber := #tDbList, byteOffset := INT_TO_DINT(#SSDW + 12),
value := INT_TO_WORD(#Suchzeiger));
#Position := #Suchstelle; // Uebergabe nach aussen
END_IF;
END_REGION
REGION NW10+NW11: Daten aus Datenliste loeschen ?
IF (#Para1 = 16#E1) AND NOT #SFC_WRITE_PROT THEN // Daten loeschen ?
IF #Indok THEN
// NW11: Bei ind. Zugriff nur loeschen, nicht verschieben, keine Anzahl!
FOR #i := 0 TO #Datensatzlaenge - 1 DO
POKE(area := 16#84, dbNumber := #tDbList,
byteOffset := INT_TO_DINT(#Suchzeiger + #i * 2), value := WORD#16#0000);
END_FOR;
POKE(area := 16#84, dbNumber := #tDbList, byteOffset := INT_TO_DINT(#SSDW + 12),
value := WORD#16#0000); // letzter Zugriff loeschen
#Suchzeiger := 0;
#IndZeiger_ok := FALSE;
ELSE
// NW10: loeschen + nachfolgende Datensaetze vorschieben
#LoeschPos := (#Suchzeiger - 20 - #SSDW) / #Datenblock_inBytes + 1; // Stelle, die geloescht wird
IF #LoeschPos <> #Listenlaenge THEN // nicht der letzte Eintrag
// Daten verschieben (AWL: BLKMOV; hier bytewise vorwaerts = sicher bei Ueberlappung)
#tAnzBytes := (#Listenlaenge - #LoeschPos) * #Datenblock_inBytes;
FOR #i := 0 TO #tAnzBytes - 1 DO
POKE(area := 16#84, dbNumber := #tDbList,
byteOffset := INT_TO_DINT(#Suchzeiger + #i),
value := PEEK(area := 16#84, dbNumber := #tDbList,
byteOffset := INT_TO_DINT(#Suchzeiger + #Datenblock_inBytes + #i)));
END_FOR;
END_IF;
// Letzten Eintrag loeschen (AWL: Last)
FOR #i := 0 TO #Datensatzlaenge - 1 DO
POKE(area := 16#84, dbNumber := #tDbList,
byteOffset := INT_TO_DINT(#Datenlisten_Ende - #Datenblock_inBytes + #i * 2),
value := WORD#16#0000);
END_FOR;
// Speicherung letzter Zugriff loeschen
POKE(area := 16#84, dbNumber := #tDbList, byteOffset := INT_TO_DINT(#SSDW + 12),
value := WORD#16#0000);
#Suchzeiger := 0;
#IndZeiger_ok := FALSE;
// Anzahl Eintraege -1 (Minimum 0)
#tAnz := WORD_TO_INT(PEEK_WORD(area := 16#84, dbNumber := #tDbList,
byteOffset := INT_TO_DINT(#SSDW + 16))) - 1;
IF #tAnz < 0 THEN
#tAnz := 0; // Minimum uebergeben
END_IF;
POKE(area := 16#84, dbNumber := #tDbList, byteOffset := INT_TO_DINT(#SSDW + 16),
value := INT_TO_WORD(#tAnz));
END_IF;
END_IF;
END_REGION
REGION NW12: Weitersuchen aktiv ? Datensatz vorhanden oder 0 ?
IF #Weitersuchen THEN
#tGefunden := FALSE;
FOR #i := 0 TO #Datensatzlaenge - 1 DO
IF (#tRecPuffer[#i * 2] <> 16#00) OR (#tRecPuffer[#i * 2 + 1] <> 16#00) THEN
#tGefunden := TRUE; // Etwas angegeben
EXIT;
END_IF;
END_FOR;
IF #tGefunden THEN
#tRE_VAL := 16#80; // Daten sind vorhanden
ELSE
#tRE_VAL := 16#40; // Daten sind nicht vorhanden
END_IF;
GOTO mEnde;
END_IF;
END_REGION
REGION NW13: Indizierung ok ?
IF #Indok AND NOT #IndZeiger_ok THEN
// Index-Eintrag im indizierten DB aktualisieren
POKE(area := 16#84, dbNumber := #tDbInd, byteOffset := INT_TO_DINT(#IndByteOfs),
value := INT_TO_WORD(#Suchzeiger));
END_IF;
END_REGION
REGION NW14+NW15: Fehlerausgabe / RET_VAL
mEnde: #tVKE := TRUE; // VKE = 1 ohne Fehler !!!
GOTO mOk;
mErr0: #tRE_VAL := 16#01; // Kein Datenlistenbereich
GOTO mErrX;
mErr1: #tRE_VAL := 16#02; // Kopier-/SFC-Fehler
GOTO mErrX;
mErr2: #tRE_VAL := 16#04; // Listenende ist erreicht
GOTO mErrX;
mErr3: #tRE_VAL := 16#08; // Falscher Parameter bei Aufruf
GOTO mErrX;
mErr4: #tRE_VAL := 16#10; // Undefinierte Suchdaten
GOTO mErrX;
mErr6: #tRE_VAL := 16#40; // Datensatz nicht vorhanden
mErrX: #tVKE := FALSE; // VKE = 0 bei Fehler !!!
mOk: #RE_VAL := #tRE_VAL;
ENO := #tVKE; // AWL: SAVE (BR-Bit fuer JBI beim Aufrufer)
END_REGION
END_FUNCTION
+303
View File
@@ -0,0 +1,303 @@
FUNCTION "FifoIn" : Void
TITLE = Datensatz in FIFO-DB eintragen
{ S7_Optimized_Access := 'FALSE' }
AUTHOR : IBAE
FAMILY : Module
NAME : FifoIn
//SCL-Portierung des AWL-Bausteins FC80 "FifoIn" (verhaltensgleich).
//
//FIFO-Kopf im Schnittstellen-DB ab Byte SSDW:
// +0 Kopf (Byte, 16#FF = FIFO-Kennung)
// +1 Datensatzlaenge (Byte, in Worten, 1..128)
// +2 FifoLaenge (Int, max. Anzahl Datensaetze)
// +4 Bringzeiger (Int, Byte-Adresse im DB)
// +6 Holzeiger (Int, Byte-Adresse im DB)
// +8 Anzahl (Int, eingetragene Datensaetze)
// +10 Status (Word)
// +12 Zusatzdaten (4 Worte)
// +40 Datenbereich, dahinter Endekennung 16#FFEE
//
//ABWEICHUNG ZUM AWL-ORIGINAL:
// - INDATEN ist jetzt VARIANT statt POINTER (S7-1500/SCL). AWL-/SCL-Aufrufer
// uebergeben weiterhin einen Bereich (z.B. P#M220.0 WORD 6 bzw. ein Symbol).
// - Registerindirekte DB-Zugriffe sind durch PEEK/POKE ersetzt (DB-Layout identisch).
// - Erfolg wird wie bisher ueber ENO/BR gemeldet (Aufrufer: JBI bzw. IF ENO).
VAR_INPUT
SSDB : Int; // Schnittstellen-DB
SSDW : Int; // Schnittstellen-1.DW (Byte-Offset des FIFO-Kopfs)
PARA : Word; // Aufrufparameter (Auftrag / Anzahl Zusatzdaten)
INDATEN : Variant; // InDaten-Schnittstelle (Datensatz, Datensatzlaenge Worte)
END_VAR
VAR_OUTPUT
RE_VAL : Byte; // Rueckmeldung (Fehlerbits)
END_VAR
VAR_TEMP
Kopf : Byte; // Fifo-Kennung (FF)
Datensatzlaenge : Byte; // Datensatzlaenge in Word (1 - 128)
FifoLaenge : Int; // Laenge des Fifo (maximale Anzahl der Datensaetze)
Bringzeiger : Int; // Zeiger, wohin naechster Eintrag gespeichert wird
Holzeiger : Int; // Zeiger, woher naechste Daten geholt werden
Anzahl : Int; // Anzahl der Datensaetze (eingetragene)
Status : Word; // Status des Fifo
Err : Byte; // Fehlerbits
Para1 : Byte; // 1.Aufrufparameter
Para2 : Byte; // 2.Aufrufparameter
Para21 : Byte; // 2.Aufrufparameter 1.Stelle
Para22 : Byte; // 2.Aufrufparameter 2.Stelle
MinBringzeiger : Int; // Minimum Bringzeiger
MaxBringzeiger : Int; // Maximum Bringzeiger
SFC_RET_VAL : Int; // Rueckmeldung TEST_DB-/Serialize-Aufruf
SFC_DB_LENGTH : Word; // DB-Laenge in Bytes
Fifo_Ende : Int; // Ende des Fifo (Byte-Offset Endekennung)
Offset : Int; // Offset 1. zu speicherndes Zusatzwort (Bytes)
SFC_WRITE_PROT : Bool; // DB ist schreibgeschuetzt
NixSave : Bool; // Status nicht speichern
tVKE : Bool; // Ergebnis fuer ENO (BR im AWL)
tDbNo : DInt; // DB-Nummer fuer PEEK/POKE
tAnzWorte : Int; // Anzahl zu kopierender Worte
tPos : DInt; // Serialize-Position
i : Int; // Schleifenzaehler
tPuffer : Array[0..257] of Byte; // Zwischenpuffer fuer INDATEN
END_VAR
BEGIN
REGION NW1: Schnittstellen-DB sinnvoll ?
#tDbNo := INT_TO_DINT(#SSDB);
"TEST_DB_TO_ATTR_DB"(DB_Number := INT_TO_WORD(#SSDB),
RetVal => #SFC_RET_VAL,
DB_Length => #SFC_DB_LENGTH,
Write_Prot => #SFC_WRITE_PROT);
#NixSave := (#SFC_RET_VAL <> 0) OR #SFC_WRITE_PROT; // Status nicht speichern
IF #NixSave THEN
#Err := 16#01; // kein Fifobereich (AWL: Err0)
GOTO mErr;
END_IF;
END_REGION
REGION NW2: FIFO-Kopf holen (AWL: Quelle-Pointer + BLKMOV)
IF #SSDW < 0 OR (#SSDW + 12) > WORD_TO_INT(#SFC_DB_LENGTH) THEN
#Err.%X1 := TRUE; // Kopf liegt nicht im DB (AWL: SFC20-Fehler, Err1)
GOTO mErr;
END_IF;
#Kopf := PEEK(area := 16#84, dbNumber := #tDbNo, byteOffset := INT_TO_DINT(#SSDW));
#Datensatzlaenge := PEEK(area := 16#84, dbNumber := #tDbNo, byteOffset := INT_TO_DINT(#SSDW + 1));
#FifoLaenge := WORD_TO_INT(PEEK_WORD(area := 16#84, dbNumber := #tDbNo, byteOffset := INT_TO_DINT(#SSDW + 2)));
#Bringzeiger := WORD_TO_INT(PEEK_WORD(area := 16#84, dbNumber := #tDbNo, byteOffset := INT_TO_DINT(#SSDW + 4)));
#Holzeiger := WORD_TO_INT(PEEK_WORD(area := 16#84, dbNumber := #tDbNo, byteOffset := INT_TO_DINT(#SSDW + 6)));
#Anzahl := WORD_TO_INT(PEEK_WORD(area := 16#84, dbNumber := #tDbNo, byteOffset := INT_TO_DINT(#SSDW + 8)));
#Status := PEEK_WORD(area := 16#84, dbNumber := #tDbNo, byteOffset := INT_TO_DINT(#SSDW + 10));
#Err := WORD_TO_BYTE(#Status AND 16#00E0); // Kapitalfehler aus frueheren Aufrufen?
IF #Err <> 16#00 THEN
GOTO mErr;
END_IF;
END_REGION
REGION NW3: Zeigerueberpruefung
IF #Kopf <> 16#FF THEN // keine Fifokennung
#NixSave := TRUE;
#Err := 16#01; // kein Fifobereich
GOTO mErr;
END_IF;
IF BYTE_TO_INT(#Datensatzlaenge) < 1 OR BYTE_TO_INT(#Datensatzlaenge) > 128 THEN
#Err := 16#01; // kein Fifobereich
GOTO mErr;
END_IF;
IF #FifoLaenge < 1 THEN
#Err := 16#01; // kein Fifobereich
GOTO mErr;
END_IF;
#MinBringzeiger := #SSDW + 40; // Speichern ab 20.DW
#MaxBringzeiger := (#FifoLaenge - 1) * BYTE_TO_INT(#Datensatzlaenge) * 2 + 40 + #SSDW;
#Fifo_Ende := BYTE_TO_INT(#Datensatzlaenge) * 2 + #MaxBringzeiger; // Zeiger Endekennung
IF #Fifo_Ende > WORD_TO_INT(#SFC_DB_LENGTH) THEN // Fifo-DB ist zu kurz!
#Err := 16#01; // kein Fifo-Bereich
GOTO mErr;
END_IF;
IF PEEK_WORD(area := 16#84, dbNumber := #tDbNo, byteOffset := INT_TO_DINT(#Fifo_Ende)) <> 16#FFEE THEN
#Err := 16#01; // kein Fifobereich (Endekennung fehlt)
GOTO mErr;
END_IF;
IF #MinBringzeiger > #Bringzeiger THEN
// Nur zulaessig, wenn alles in Grundstellung: dann initialisieren
IF #Bringzeiger <> 0 OR #Holzeiger <> 0 OR #Anzahl <> 0 THEN
#Err.%X5 := TRUE; // Bringzeiger ist ausserhalb
GOTO mErr;
END_IF;
#Bringzeiger := #MinBringzeiger;
#Holzeiger := #MinBringzeiger;
END_IF;
IF #Bringzeiger > #MaxBringzeiger THEN
#Err.%X5 := TRUE; // Bringzeiger ist ausserhalb
GOTO mErr;
END_IF;
IF (#Bringzeiger = #Holzeiger) AND (#FifoLaenge <> #Anzahl) AND (#Anzahl <> 0) THEN
#Err.%X7 := TRUE; // Versatz Bring-/Holzeiger
GOTO mErr;
END_IF;
IF (#Bringzeiger <> #Holzeiger) AND ((#FifoLaenge = #Anzahl) OR (#Anzahl = 0)) THEN
#Err.%X7 := TRUE; // Versatz Bring-/Holzeiger
GOTO mErr;
END_IF;
END_REGION
REGION NW4: Nur Zusatzdaten / als 1.Datensatz speichern ?
#Para1 := WORD_TO_BYTE(SHR(IN := #PARA, N := 8)); // 1.Aufrufparameter
#Para2 := WORD_TO_BYTE(#PARA); // 2.Aufrufparameter
IF #Para1 = 16#02 THEN // Nur Zusatzdaten speichern
IF #Para2 = 16#00 THEN
#Err.%X3 := TRUE; // Falscher Aufrufparameter
GOTO mErr;
END_IF;
GOTO mNw06; // Zusatzdaten speichern
END_IF;
IF #Para1 = 16#01 THEN // Als 1.Datensatz speichern
#Bringzeiger := #MinBringzeiger;
#Holzeiger := #MinBringzeiger;
#Anzahl := 0;
GOTO mNw06; // Zusatz-Daten speichern?
END_IF;
END_REGION
REGION NW5: Hat Fifo Platz / Datenbereich leer ?
IF #Anzahl >= #FifoLaenge THEN
IF #Anzahl = #FifoLaenge THEN
#Err.%X2 := TRUE; // Fifo ist voll
ELSE
#Err.%X7 := TRUE; // Versatz Bring-/Holzeiger
END_IF;
GOTO mErr;
END_IF;
// Ziel-Speicherzellen muessen leer (0) sein
FOR #i := 0 TO BYTE_TO_INT(#Datensatzlaenge) - 1 DO
IF PEEK_WORD(area := 16#84, dbNumber := #tDbNo,
byteOffset := INT_TO_DINT(#Bringzeiger + #i * 2)) <> 16#0000 THEN
#Err.%X4 := TRUE; // Speicherzelle ist belegt
GOTO mErr;
END_IF;
END_FOR;
END_REGION
REGION NW6: Zusatzdaten schreiben (Quelle: Merker ab MB240)
mNw06: IF #Para2 <> 16#00 THEN
#Para21 := SHR(IN := #Para2 AND 16#F0, N := 4); // 1.Stelle isolieren (von)
#Para22 := #Para2 AND 16#0F; // 2.Stelle isolieren (bis)
IF (#Para21 > #Para22) // 1.Stelle > 2.Stelle !
OR (#Para22 > 16#04) // 2.Stelle > 4 !
OR (#Para21 = 16#00) THEN // 1.Stelle = 0 !
#Err.%X3 := TRUE; // Falscher Aufrufparameter
GOTO mErr;
END_IF;
#tAnzWorte := BYTE_TO_INT(#Para22) - BYTE_TO_INT(#Para21) + 1;
#Offset := (BYTE_TO_INT(#Para21) - 1) * 2; // Offset 1. zu speicherndes Zusatzwort
// Quelle: MB240+Offset -> Ziel: DB ab SSDW+12+Offset (ab 6.DW)
FOR #i := 0 TO #tAnzWorte * 2 - 1 DO
POKE(area := 16#84, dbNumber := #tDbNo,
byteOffset := INT_TO_DINT(#SSDW + 12 + #Offset + #i),
value := PEEK(area := 16#83, dbNumber := 0,
byteOffset := INT_TO_DINT(240 + #Offset + #i)));
END_FOR;
END_IF;
END_REGION
REGION NW7: Fifodaten schreiben
IF #Para1 = 16#FF THEN // Testsendung: Zeiger nur testweise veraendern
GOTO mNw08;
END_IF;
IF #Para1 = 16#02 THEN // Nur Zusatzdaten speichern: fertig!
GOTO mEnde;
END_IF;
// INDATEN in Zwischenpuffer holen (AWL: BLKMOV ueber uebergebenen Pointer)
#tPos := 0;
#SFC_RET_VAL := Serialize(SRC_VARIANT := #INDATEN, DEST_ARRAY := #tPuffer, POS := #tPos);
IF (#SFC_RET_VAL <> 0) OR (#tPos < BYTE_TO_INT(#Datensatzlaenge) * 2) THEN
#Err.%X1 := TRUE; // Kopier-Fehler (AWL: SFC20-Aufruf mit Fehler)
GOTO mErr;
END_IF;
// Datensatz ab Bringzeiger in FIFO-DB schreiben
FOR #i := 0 TO BYTE_TO_INT(#Datensatzlaenge) * 2 - 1 DO
POKE(area := 16#84, dbNumber := #tDbNo,
byteOffset := INT_TO_DINT(#Bringzeiger + #i),
value := #tPuffer[#i]);
END_FOR;
END_REGION
REGION NW8: Bringzeiger, Anzahl erhoehen
mNw08: #Bringzeiger := BYTE_TO_INT(#Datensatzlaenge) * 2 + #Bringzeiger;
IF #Bringzeiger > #MaxBringzeiger THEN // Maximum erreicht?
#Bringzeiger := #MinBringzeiger;
END_IF;
#Anzahl := #Anzahl + 1;
END_REGION
REGION NW9: Versatz Bring-/Holzeiger
IF (#Bringzeiger = #Holzeiger) AND (#FifoLaenge <> #Anzahl) THEN
#Err.%X7 := TRUE; // Versatz Bring-/Holzeiger
GOTO mErr;
END_IF;
IF (#Bringzeiger <> #Holzeiger) AND (#FifoLaenge = #Anzahl) THEN
#Err.%X7 := TRUE; // Versatz Bring-/Holzeiger
GOTO mErr;
END_IF;
END_REGION
REGION NW10: Fehlerausgabe / RET_VAL
mEnde: #tVKE := TRUE; // VKE = 1 ohne Fehler !!!
GOTO mOk;
mErr: #tVKE := FALSE; // VKE = 0 bei Fehler !!!
mOk: #RE_VAL := #Err;
END_REGION
#Status := #Status OR BYTE_TO_WORD(#Err);
REGION NW11: Fifokopfdaten speichern
IF #Err.%X0 AND #NixSave THEN // kein gueltiger Fifobereich: nichts speichern
ENO := #tVKE;
RETURN;
END_IF;
"MW254" := INT_TO_WORD(#Anzahl - 1); // Uebergabe an FC+1 (da vorher zum Test erhoeht wurde)
IF #Para1 = 16#FF THEN // Testsendung: Zeiger wurden nur testweise geaendert!
ENO := #tVKE;
RETURN;
END_IF;
// Bringzeiger, Holzeiger, Anzahl, Status in FIFO-Kopf zuruecksichern (ab SSDW+4)
POKE(area := 16#84, dbNumber := #tDbNo, byteOffset := INT_TO_DINT(#SSDW + 4), value := INT_TO_WORD(#Bringzeiger));
POKE(area := 16#84, dbNumber := #tDbNo, byteOffset := INT_TO_DINT(#SSDW + 6), value := INT_TO_WORD(#Holzeiger));
POKE(area := 16#84, dbNumber := #tDbNo, byteOffset := INT_TO_DINT(#SSDW + 8), value := INT_TO_WORD(#Anzahl));
POKE(area := 16#84, dbNumber := #tDbNo, byteOffset := INT_TO_DINT(#SSDW + 10), value := #Status);
"MW248" := INT_TO_WORD(#FifoLaenge); // Uebergabe an FC+1 (fuer Durchlaufspeicher)
"MW250" := INT_TO_WORD(#Bringzeiger); // Uebergabe an FC+1 (fuer Durchlaufspeicher)
"MW252" := INT_TO_WORD(#Holzeiger); // Uebergabe an FC+1 (fuer Durchlaufspeicher)
"MW254" := INT_TO_WORD(#Anzahl); // Uebergabe an FC+1 (fuer Durchlaufspeicher)
END_REGION
ENO := #tVKE; // AWL: SAVE (BR-Bit fuer JBI beim Aufrufer)
END_FUNCTION
File diff suppressed because it is too large Load Diff
+146
View File
@@ -0,0 +1,146 @@
FUNCTION_BLOCK "MZylinder"
TITLE = Zylinderansteuerung
{ S7_Optimized_Access := 'FALSE' }
AUTHOR : IBAE
FAMILY : Module
NAME : MZylinder
//SCL-Portierung des AWL-Bausteins FB92 "MZylinder" (verhaltensgleich).
//Schnittstelle und Instanzdaten (UDT "UZylinder") unveraendert.
VAR_INPUT
Automatik : Bool; // Automatikbetrieb ist aktiv
Handbetrieb : Bool; // Handbetrieb ist aktiv
T_Ast : Int := 5; // Zeit bis AST erreicht ist (*100ms)
T_Gst : Int := 5; // Zeit bis GST erreicht ist (*100ms)
END_VAR
VAR_OUTPUT
ZylAst : Bool; // Vorwaerts-Ausgang des Antriebs
ZylGst : Bool; // Rueckwaerts-Ausgang des Antriebs
END_VAR
VAR
Stat { S7_SetPoint := 'True'} : "UZylinder";
END_VAR
VAR_TEMP
tZeitNullen : Bool; // AWL-Sprungziel "tdel": Zeitzelle nullen + Old_Ast nachfuehren
tFehler : Bool; // NW5: undefinierter Zustand / Befehl
tFlanke : Bool; // NW10: Flankenergebnis
END_VAR
BEGIN
REGION NW1: Zeitwert loeschen
// Endstellung erreicht oder Fahrtrichtung hat gewechselt -> Zeit nullen
#tZeitNullen := (#ZylAst AND (#Stat.B_AST OR NOT #Stat.Old_Ast))
OR (NOT #ZylAst AND (#Stat.B_GST OR #Stat.Old_Ast));
END_REGION
IF NOT #tZeitNullen THEN
REGION NW2: AST-Erreichtzeit abgelaufen ?
IF #Stat.T_AST_Akt = 9999 THEN
#Stat.T_AST_Akt := #T_Ast; // Sollwert uebergeben (9999 = kein Zeitwert angegeben)
END_IF;
IF #ZylAst THEN // Zylinder faehrt nach AST ?
#Stat.B_GST := FALSE;
IF #Stat.T_Zelle >= #Stat.T_AST_Akt THEN // Gewollter Zeitwert erreicht ?
#Stat.B_AST := TRUE; // Zylinder hat AST erreicht
#tZeitNullen := TRUE; // AWL: JC tdel
END_IF;
ELSIF NOT #Stat.Typ5_3 THEN // Zylinder haelt seine Stellung !
#Stat.B_AST := FALSE;
END_IF;
END_REGION
END_IF;
IF NOT #tZeitNullen THEN
REGION NW3: GST-Erreichtzeit abgelaufen ?
IF #Stat.T_GST_Akt = 9999 THEN
#Stat.T_GST_Akt := #T_Gst; // Sollwert uebergeben (9999 = kein Zeitwert angegeben)
END_IF;
// Zylinder faehrt nach GST ? (2-fache Ansteuerung: ZylGst / 1-fache: ZylAst aus)
IF ((#Stat.Typ5_2 OR #Stat.Typ5_3) AND #ZylGst)
OR (#Stat.Typ3_2 AND NOT #ZylAst) THEN
#Stat.B_AST := FALSE;
IF #Stat.T_Zelle >= #Stat.T_GST_Akt THEN // Gewollter Zeitwert erreicht ?
#Stat.B_GST := TRUE; // Zylinder hat GST erreicht
#tZeitNullen := TRUE; // AWL: JC tdel
END_IF;
ELSIF NOT #Stat.Typ5_3 THEN // Zylinder haelt seine Stellung !
#Stat.B_GST := FALSE;
END_IF;
END_REGION
END_IF;
IF NOT #tZeitNullen THEN
REGION NW4: Zeit laeuft
// Nicht zaehlen, wenn 2-fach angesteuerter Zylinder gar nicht angesteuert wird;
// Zaehlung nur mit dem 100ms-Impuls
IF "100ms-Impuls"
AND NOT ((#Stat.Typ5_2 OR #Stat.Typ5_3) AND NOT #ZylGst AND NOT #ZylAst) THEN
#Stat.T_Zelle := #Stat.T_Zelle + WORD_TO_INT("MW2"); // 100ms-Takte
END_IF;
END_REGION
ELSE
// AWL-Sprungziel "tdel" (aus NW1/NW2/NW3)
#Stat.T_Zelle := 0; // Zeitwert loeschen
#Stat.Old_Ast := #ZylAst; // Fuhr letzten Zyklus nach AST
END_IF;
REGION NW5: Undefinierte Zustaende / Befehle
#tFehler := (#Automatik AND #Handbetrieb) // Automatik + Hand aktiv
OR (NOT #Automatik AND NOT #Handbetrieb) // keine Betriebsart
OR (#Stat.Ast AND #Stat.Gst) // AST- + GST-fahren gleichzeitig aktiv
OR (NOT #Stat.Typ3_2 AND NOT #Stat.Typ5_2 AND NOT #Stat.Typ5_3) // undefinierter Ventil-Typ
OR (#Stat.Typ3_2 AND (#Stat.Typ5_2 OR #Stat.Typ5_3)) // mehrere Ventil-Typen
OR (#Stat.Typ5_2 AND #Stat.Typ5_3);
IF #tFehler THEN // Zylinderstellung bleibt eingefroren
IF #Stat.Typ5_3 THEN // Zylinder haelt seine Stellung !
#Stat.Ast := FALSE; // Bewegungsbits loeschen
#Stat.Gst := FALSE;
END_IF;
RETURN; // AWL: BEU
END_IF;
END_REGION
REGION NW6: Zylinder auf Arbeitsstellung fahren
#ZylAst := #Stat.Ast;
END_REGION
REGION NW7: Zylinder auf Grundstellung fahren
#ZylGst := (#Stat.Typ5_2 OR #Stat.Typ5_3) AND #Stat.Gst;
END_REGION
REGION NW8: Daten, die zurueckgesetzt werden
IF #Stat.Typ5_3 THEN // Zylinder haelt seine Stellung !
#Stat.Ast := FALSE; // Bewegungsbits loeschen
#Stat.Gst := FALSE;
END_IF;
IF #Stat.Typ3_2 THEN
#Stat.Gst := FALSE; // Es gibt kein GST fahren !
END_IF;
END_REGION
REGION NW9: Ausgaenge an OP uebergeben
#Stat.OP_Out_GST := #ZylGst;
#Stat.OP_Out_AST := #ZylAst;
END_REGION
REGION NW10: Statistik
// Positive Flanke ZylAst (AWL: FP Stat.FP_AST)
#tFlanke := #ZylAst AND NOT #Stat.FP_AST;
#Stat.FP_AST := #ZylAst;
IF #tFlanke THEN
#Stat.Statistik.AST_Schaltungen := #Stat.Statistik.AST_Schaltungen + 1;
END_IF;
// Positive Flanke ZylGst bei Typ5_3 (AWL: FP Stat.FP_AST2)
#tFlanke := #ZylGst AND #Stat.Typ5_3 AND NOT #Stat.FP_AST2;
#Stat.FP_AST2 := #ZylGst AND #Stat.Typ5_3;
IF #tFlanke THEN
#Stat.Statistik.AST2_Schaltungen := #Stat.Statistik.AST2_Schaltungen + 1;
END_IF;
END_REGION
END_FUNCTION_BLOCK
+460
View File
@@ -0,0 +1,460 @@
FUNCTION "DatenOut" : Void
TITLE = Datensatz in Datenliste suchen / lesen / loeschen
{ S7_Optimized_Access := 'FALSE' }
AUTHOR : IBAE
FAMILY : Module
NAME : DatenOut
//SCL-Portierung des AWL-Bausteins FC86 "DatenOut" (verhaltensgleich).
//
//Datenlisten-Kopf im Datenlisten-DB ab Byte SSDW:
// +0 Kopf (Byte, 16#DA = Datenliste, 16#DB = Sort-Datenliste)
// +1 Datensatzlaenge (Byte, in Worten, 1..128)
// +2 Listenlaenge (Int, max. Anzahl Datensaetze)
// +4 Stammdaten (Int, Anzahl Suchworte 1..9)
// +6 IndDB (Word, indizierter DB; nur bei Stammdaten=1)
// +8 IndDB_Laenge (Int, Laenge indizierter DB in Eintraegen)
// +10 IndDaten_FC (Int, Wandel-FC-Nummer fuer indizierte Suche)
// +12 Suchzeiger / letzter Zugriff (Int, Byte-Adresse)
// +16 AnzahlEintraege (Int)
// +20 Datenbereich, dahinter Endekennung 16#DAEE bzw. 16#DBEE
//
//Aufrufparameter PARA (High-Byte = Para1, Low-Byte = Para2):
// Para1: 00=suchen, E1=suchen+loeschen, F1=1.Datensatz holen,
// FF=naechsten Datensatz holen, 02..80=Offset-Suche ab Wort X
// Para2: 00=Stammdaten aus Kopf, sonst Anzahl Stammdaten (1..9)
//
//RE_VAL: Bit0=kein Datenlistenbereich, Bit1=Kopier-/SFC-Fehler, Bit2=Listenende,
// Bit3=falscher Parameter, Bit4=undef. Suchdaten, Bit5=undef. OutDaten,
// Bit6=Datensatz nicht vorhanden, Bit7=Daten vorhanden. Erfolg zusaetzlich via ENO/BR.
//
//ABWEICHUNGEN ZUM AWL-ORIGINAL:
// - SEARCHDATEN/OUTDATEN sind VARIANT statt POINTER (S7-1500/SCL).
// SEARCHDATEN muss mind. Stammdaten*2 Bytes referenzieren.
// OUTDATEN sollte exakt Datensatzlaenge*2 Bytes gross sein (groessere Ziele
// werden hinter dem Datensatz mit 0 aufgefuellt).
// - Die DB-Laengenpruefung des Such-/Out-DB (AWL NW5/NW8) entfaellt, da der
// Variant-Zugriff typsicher ist. Fehlerbit 5 (undef. OutDaten) wird nicht mehr erzeugt.
// - Der indirekte Aufruf UC FC[#Wandel_FC] ist als CASE ausgefuehrt
// (aktuell einziger Wandel-FC im Projekt: FC1000 "Wandel_FC_fuer_DB1000").
// - Position wird mit 0 initialisiert (AWL: undefiniert, wenn nicht gefunden).
VAR_INPUT
SSDB : Int; // Datenlisten-DB
SSDW : Int; // Datenlisten-1.DW (Byte-Offset des Kopfs)
PARA : Word; // Aufrufparameter (Auftrag / Anzahl Stammdaten)
SEARCHDATEN : Variant; // Such-Daten-Schnittstelle (Stammdaten Worte)
END_VAR
VAR_OUTPUT
RE_VAL : Byte; // Rueckmeldung (Fehler-/Statusbits)
Position : Int; // Daten stehen an angegebener Position in Datenliste
END_VAR
VAR_IN_OUT
OUTDATEN : Variant; // OutDaten-Schnittstelle (Datensatzlaenge Worte)
END_VAR
VAR_TEMP
Kopf : Byte; // Datenliste-Kennung (DA/DB)
Datensatzlaenge : Int; // Datensatzlaenge in Word (1..128)
Listenlaenge : Int; // Laenge der Datenliste (max. Anzahl Datensaetze)
Stammdaten : Int; // Stammdatenlaenge in Word (1..9)
IndDB : Word; // Indizierter DB (nur bei Stammdaten = 1 erlaubt !!!)
IndDB_Laenge : Int; // Laenge des indizierten DB in Eintraegen
IndDaten_FC : Int; // FC zum Wandeln der indizierten Daten
Suchzeiger : Int; // Speicherung des letzten Zugriffs / Suchzeiger (Byte-Adresse)
Para1 : Byte; // 1.Aufrufparameter
Para2 : Byte; // 2.Aufrufparameter
OffsetDaten_1 : Int; // Stammdaten beginnen ab hier (Byte-Offset)
SFC_RET_VAL : Int; // Rueckmeldung SFC-/Serialize-Aufruf
SFC_DB_LENGTH : Word; // Rueckmeldung TEST_DB-Aufruf
SFC_WRITE_PROT : Bool; // Rueckmeldung TEST_DB-Aufruf (letzter Aufruf!)
DB_Laenge : Int; // DB-Laenge nach BCD-Wandlung (wie AWL)
Datenlisten_Ende : Int; // Ende der Datenliste (Byte-Offset Endekennung)
Datenblock_inBytes : Int; // Datenblocklaenge in Bytes
Daten_1 : Word; // 1.Datenwort der Suchdaten
Daten_2 : DWord; // 1.+2.Datenwort der Suchdaten (fuer Wandel-FC)
Wandel_FC : Int; // Wandel-FC-Nummer
Suchstelle : Int; // Bei angegebenem Datensatz suchen (Position)
LoeschPos : Int; // Loesch-Position
Indok : Bool; // Indizierung moeglich
IndZeiger_ok : Bool; // Indizier-Zeiger angegeben
Weitersuchen : Bool; // Weitersuchen ist aktiv (Para1 F1/FF)
Offsetsuchen : Bool; // Offsetsuchen ist aktiv
Sortliste : Bool; // Sortliste wird durchsucht
tRE_VAL : Byte; // Rueckmeldung (Zwischenwert)
tVKE : Bool; // Ergebnis fuer ENO (BR im AWL)
tGefunden : Bool; // Vergleichs-/Suchergebnis
tDbList : DInt; // Datenlisten-DB-Nummer fuer PEEK/POKE
tDbInd : DInt; // Indizierter DB fuer PEEK/POKE
tIndex : Int; // Indexwert fuer indizierte Suche
IndByteOfs : Int; // Byte-Offset des Index-Eintrags im IndDB
tWort : Word; // Wort-Zwischenwert
tAnz : Int; // Anzahl-Zwischenwert
tAnzBytes : Int; // Anzahl zu verschiebender Bytes
tPos : DInt; // Serialize-/Deserialize-Position
i : Int; // Schleifenzaehler
tSuchPuffer : Array[0..257] of Byte; // Zwischenpuffer SEARCHDATEN
tRecPuffer : Array[0..257] of Byte; // Zwischenpuffer Datensatz -> OUTDATEN
END_VAR
BEGIN
#tRE_VAL := 16#00;
#Suchstelle := 0;
#Position := 0;
REGION NW1: Schnittstellen-DB sinnvoll ?
#tDbList := INT_TO_DINT(#SSDB);
"TEST_DB_TO_ATTR_DB"(DB_Number := INT_TO_WORD(#SSDB),
RetVal => #SFC_RET_VAL,
DB_Length => #SFC_DB_LENGTH,
Write_Prot => #SFC_WRITE_PROT);
"BCD (4stellig)_to_INT"(BCD_Wert := #SFC_DB_LENGTH, Int_Wert => #DB_Laenge);
IF (#SFC_RET_VAL <> 0) OR #SFC_WRITE_PROT THEN // schreibgeschuetzt
GOTO mErr0; // kein Datenlisten-Bereich
END_IF;
END_REGION
REGION NW2: Datenlisten-Kopf holen (AWL: Quelle-Pointer + BLKMOV, 10 Worte)
IF #SSDW < 0 OR (#SSDW + 20) > #DB_Laenge THEN
GOTO mErr1; // Kopf liegt nicht im DB (AWL: SFC20-Fehler)
END_IF;
#Kopf := PEEK(area := 16#84, dbNumber := #tDbList, byteOffset := INT_TO_DINT(#SSDW));
#Datensatzlaenge := BYTE_TO_INT(PEEK(area := 16#84, dbNumber := #tDbList, byteOffset := INT_TO_DINT(#SSDW + 1)));
#Listenlaenge := WORD_TO_INT(PEEK_WORD(area := 16#84, dbNumber := #tDbList, byteOffset := INT_TO_DINT(#SSDW + 2)));
#Stammdaten := WORD_TO_INT(PEEK_WORD(area := 16#84, dbNumber := #tDbList, byteOffset := INT_TO_DINT(#SSDW + 4)));
#IndDB := PEEK_WORD(area := 16#84, dbNumber := #tDbList, byteOffset := INT_TO_DINT(#SSDW + 6));
#IndDB_Laenge := WORD_TO_INT(PEEK_WORD(area := 16#84, dbNumber := #tDbList, byteOffset := INT_TO_DINT(#SSDW + 8)));
#IndDaten_FC := WORD_TO_INT(PEEK_WORD(area := 16#84, dbNumber := #tDbList, byteOffset := INT_TO_DINT(#SSDW + 10)));
#Suchzeiger := WORD_TO_INT(PEEK_WORD(area := 16#84, dbNumber := #tDbList, byteOffset := INT_TO_DINT(#SSDW + 12)));
END_REGION
REGION NW3: Zeigerueberpruefung
#Sortliste := (#Kopf = 16#DB); // Sort-Datenlistenkennung ?
IF NOT #Sortliste AND (#Kopf <> 16#DA) THEN // Datenlistenkennung ?
GOTO mErr0; // kein Datenlistenbereich
END_IF;
IF (#Datensatzlaenge < 1) OR (#Datensatzlaenge > 128)
OR (#Listenlaenge < 1)
OR (#Stammdaten < 1) OR (#Stammdaten > 9)
OR (#Stammdaten > #Datensatzlaenge) THEN
GOTO mErr0; // kein Datenlistenbereich
END_IF;
#Datenlisten_Ende := #Listenlaenge * #Datensatzlaenge * 2 + 20 + #SSDW;
IF #Datenlisten_Ende > #DB_Laenge THEN // Datenlisten-DB ist zu kurz!
GOTO mErr0; // kein Datenlisten-Bereich
END_IF;
#tWort := PEEK_WORD(area := 16#84, dbNumber := #tDbList, byteOffset := INT_TO_DINT(#Datenlisten_Ende));
IF (#tWort <> 16#DAEE) AND (#tWort <> 16#DBEE) THEN // Endekennung ?
GOTO mErr0; // kein Datenlisten-Bereich
END_IF;
#Datenblock_inBytes := #Datensatzlaenge * 2;
END_REGION
REGION NW4: Auftrag kontrollieren
#Indok := FALSE;
#IndZeiger_ok := FALSE;
#Weitersuchen := FALSE;
#Offsetsuchen := FALSE;
#OffsetDaten_1 := 0; // Vorerst kein Offset
#Para1 := WORD_TO_BYTE(SHR(IN := #PARA, N := 8));
#Para2 := WORD_TO_BYTE(#PARA);
IF #Para1 = 16#00 THEN // Normale Suche
GOTO mW401;
END_IF;
IF #Para1 = 16#E1 THEN // Suchen + loeschen
IF #Sortliste THEN
GOTO mErr3; // Austragen aus Sortliste nicht erlaubt !
END_IF;
GOTO mW401;
END_IF;
IF #Para1 = 16#F1 THEN // 1.Datensatz holen
IF (#Para2 <> 16#00) OR #Sortliste THEN
GOTO mErr3; // Falscher Aufrufparameter
END_IF;
#Weitersuchen := TRUE;
#Suchzeiger := #SSDW + 20;
GOTO mNw08; // OutDaten ok ?
END_IF;
IF #Para1 = 16#FF THEN // Naechsten Datensatz holen
IF (#Para2 <> 16#00) OR #Sortliste THEN
GOTO mErr3; // Falscher Aufrufparameter
END_IF;
#Weitersuchen := TRUE;
#Suchzeiger := #Suchzeiger + #Datenblock_inBytes; // Naechsten Datensatz suchen
IF (#Datenblock_inBytes * #Listenlaenge + #SSDW + 20) > #Suchzeiger THEN
GOTO mNw08; // Noch nicht am Ende
END_IF;
GOTO mErr2; // Listenende erreicht
END_IF;
// Offset-Suche: Para1 = Offset des 1.Stammdatenworts (2..128)
IF (BYTE_TO_INT(#Para1) < 2)
OR (BYTE_TO_INT(#Para1) > 128)
OR (BYTE_TO_INT(#Para1) > #Datensatzlaenge) THEN
GOTO mErr3; // Falscher Aufrufparameter
END_IF;
#OffsetDaten_1 := (BYTE_TO_INT(#Para1) - 1) * 2; // Richtiger Offset der 1.Stammdaten
#Suchzeiger := 0; // Suche ab Anfang
#Offsetsuchen := TRUE;
mW401: IF #Para2 <> 16#00 THEN
#Stammdaten := BYTE_TO_INT(#Para2); // Anzahl der Stammdaten aus Aufruf
IF (#Stammdaten > #Datensatzlaenge)
OR (#Stammdaten < 1)
OR (#Stammdaten > 9) THEN
GOTO mErr3; // Falscher Aufrufparameter
END_IF;
END_IF;
IF #Offsetsuchen THEN
IF (BYTE_TO_INT(#Para1) - 1 + #Stammdaten) > #Datensatzlaenge THEN // Letzte Stammdaten zu gross ?
GOTO mErr3; // Falscher Aufrufparameter
END_IF;
END_IF;
END_REGION
REGION NW5: Such-Daten uebernehmen (AWL: Pointer-Parsing)
#Suchzeiger := 0;
#tPos := 0;
#SFC_RET_VAL := Serialize(SRC_VARIANT := #SEARCHDATEN, DEST_ARRAY := #tSuchPuffer, POS := #tPos);
IF (#SFC_RET_VAL <> 0) OR (#tPos < INT_TO_DINT(#Stammdaten * 2)) THEN
GOTO mErr4; // Undefinierte Suchdaten
END_IF;
#Daten_1 := SHL(IN := BYTE_TO_WORD(#tSuchPuffer[0]), N := 8) OR BYTE_TO_WORD(#tSuchPuffer[1]);
#Daten_2 := SHL(IN := BYTE_TO_DWORD(#tSuchPuffer[0]), N := 24)
OR SHL(IN := BYTE_TO_DWORD(#tSuchPuffer[1]), N := 16)
OR SHL(IN := BYTE_TO_DWORD(#tSuchPuffer[2]), N := 8)
OR BYTE_TO_DWORD(#tSuchPuffer[3]);
// Mindestens ein Suchwort <> 0 ?
#tGefunden := FALSE;
FOR #i := 0 TO #Stammdaten - 1 DO
IF (#tSuchPuffer[#i * 2] <> 16#00) OR (#tSuchPuffer[#i * 2 + 1] <> 16#00) THEN
#tGefunden := TRUE;
EXIT;
END_IF;
END_FOR;
IF NOT #tGefunden THEN
GOTO mErr4; // Undefinierte Stammdaten
END_IF;
END_REGION
REGION NW6: Indiziert suchen ?
IF (#IndDB <> 16#0000) AND NOT #Offsetsuchen AND NOT #Sortliste THEN
"TEST_DB_TO_ATTR_DB"(DB_Number := #IndDB,
RetVal => #SFC_RET_VAL,
DB_Length => #SFC_DB_LENGTH,
Write_Prot => #SFC_WRITE_PROT);
"BCD (4stellig)_to_INT"(BCD_Wert := #SFC_DB_LENGTH, Int_Wert => #DB_Laenge);
IF (#SFC_RET_VAL = 0) AND NOT #SFC_WRITE_PROT // Indizier-DB vorhanden
AND NOT (#IndDB_Laenge * 2 > #DB_Laenge) // Indizier-DB lang genug
AND (#Stammdaten < 3) THEN // nicht zu viele Stammdaten
#Wandel_FC := #IndDaten_FC;
IF #Wandel_FC > 0 THEN
"MW90" := #Daten_1; // Stammdaten (1.DW) uebergeben
"MD94" := #Daten_2; // Stammdaten (1.+2.DW) uebergeben
CASE #Wandel_FC OF // AWL: UC FC[#Wandel_FC]
1000:
"Wandel_FC_für_DB1000"();
ELSE
; // unbekannter Wandel-FC: MW92 bleibt unveraendert
END_CASE;
#tIndex := WORD_TO_INT("MW92"); // Rueckwert vom FC
ELSE
#tIndex := WORD_TO_INT(#Daten_1); // Stammdaten direkt als Index
END_IF;
IF (#tIndex >= 0) AND (#tIndex < #IndDB_Laenge) THEN // Zeiger im Ind.DB ?
#IndByteOfs := #tIndex * 2; // DB byteorientiert
#tDbInd := INT_TO_DINT(WORD_TO_INT(#IndDB));
#Suchzeiger := WORD_TO_INT(PEEK_WORD(area := 16#84, dbNumber := #tDbInd,
byteOffset := INT_TO_DINT(#IndByteOfs)));
#Indok := TRUE; // Indizierung moeglich
#IndZeiger_ok := TRUE; // Indizier-Zeiger angegeben
END_IF;
END_IF;
END_IF;
END_REGION
REGION NW7: Kontrolle auf gleiche Stammdaten (Suchschleife)
IF (#Suchzeiger = 0)
OR (((#Suchzeiger - 20) MOD #Datenblock_inBytes) <> 0) THEN // Suchzeiger nicht im Raster
// Suche bei 1.Datensatz beginnen (AWL: W700)
#Suchstelle := 1;
#IndZeiger_ok := FALSE;
#Suchzeiger := #SSDW + 20;
ELSE
#Suchstelle := 0; // Bei angegebener Stelle suchen
END_IF;
REPEAT
// Stammdaten vergleichen (AWL: W701/L700-Schleife)
#tGefunden := TRUE;
FOR #i := 0 TO #Stammdaten - 1 DO
#tWort := SHL(IN := BYTE_TO_WORD(#tSuchPuffer[#i * 2]), N := 8)
OR BYTE_TO_WORD(#tSuchPuffer[#i * 2 + 1]);
IF #tWort <> PEEK_WORD(area := 16#84, dbNumber := #tDbList,
byteOffset := INT_TO_DINT(#Suchzeiger + #OffsetDaten_1 + #i * 2)) THEN
#tGefunden := FALSE;
EXIT;
END_IF;
END_FOR;
IF #tGefunden THEN
#tRE_VAL := 16#80; // Daten sind vorhanden
GOTO mNw08; // Datensatz ist in Liste enthalten
END_IF;
// AWL: W703
IF #Suchstelle = 0 THEN
// Suche bei angegebener Stelle erfolglos: ab 1.Datensatz neu (AWL: W700)
#Suchstelle := 1;
#IndZeiger_ok := FALSE;
#Suchzeiger := #SSDW + 20;
ELSE
#Suchstelle := #Suchstelle + 1;
IF #Suchstelle > #Listenlaenge THEN // Alle Datensaetze durchsucht
GOTO mErr6; // Datensatz ist nicht vorhanden
END_IF;
#Suchzeiger := #Suchzeiger + #Datenblock_inBytes; // AWL: W705
END_IF;
UNTIL FALSE END_REPEAT;
END_REGION
REGION NW8+NW9: Datensatz an OUTDATEN uebergeben
mNw08: FILL_BLK(IN := BYTE#16#00, COUNT := 258, OUT => #tRecPuffer[0]);
FOR #i := 0 TO #Datensatzlaenge * 2 - 1 DO
#tRecPuffer[#i] := PEEK(area := 16#84, dbNumber := #tDbList,
byteOffset := INT_TO_DINT(#Suchzeiger + #i));
END_FOR;
#tPos := 0;
#SFC_RET_VAL := Deserialize(SRC_ARRAY := #tRecPuffer, DEST_VARIANT := #OUTDATEN, POS := #tPos);
IF #SFC_RET_VAL <> 0 THEN
GOTO mErr1; // Kopier-Fehler (AWL: SFC20-Aufruf mit Fehler)
END_IF;
IF NOT #SFC_WRITE_PROT THEN // Datenliste kann beschrieben werden
// Speicherung letzter Zugriff
POKE(area := 16#84, dbNumber := #tDbList, byteOffset := INT_TO_DINT(#SSDW + 12),
value := INT_TO_WORD(#Suchzeiger));
#Position := #Suchstelle; // Uebergabe nach aussen
END_IF;
END_REGION
REGION NW10+NW11: Daten aus Datenliste loeschen ?
IF (#Para1 = 16#E1) AND NOT #SFC_WRITE_PROT THEN // Daten loeschen ?
IF #Indok THEN
// NW11: Bei ind. Zugriff nur loeschen, nicht verschieben, keine Anzahl!
FOR #i := 0 TO #Datensatzlaenge - 1 DO
POKE(area := 16#84, dbNumber := #tDbList,
byteOffset := INT_TO_DINT(#Suchzeiger + #i * 2), value := WORD#16#0000);
END_FOR;
POKE(area := 16#84, dbNumber := #tDbList, byteOffset := INT_TO_DINT(#SSDW + 12),
value := WORD#16#0000); // letzter Zugriff loeschen
#Suchzeiger := 0;
#IndZeiger_ok := FALSE;
ELSE
// NW10: loeschen + nachfolgende Datensaetze vorschieben
#LoeschPos := (#Suchzeiger - 20 - #SSDW) / #Datenblock_inBytes + 1; // Stelle, die geloescht wird
IF #LoeschPos <> #Listenlaenge THEN // nicht der letzte Eintrag
// Daten verschieben (AWL: BLKMOV; hier bytewise vorwaerts = sicher bei Ueberlappung)
#tAnzBytes := (#Listenlaenge - #LoeschPos) * #Datenblock_inBytes;
FOR #i := 0 TO #tAnzBytes - 1 DO
POKE(area := 16#84, dbNumber := #tDbList,
byteOffset := INT_TO_DINT(#Suchzeiger + #i),
value := PEEK(area := 16#84, dbNumber := #tDbList,
byteOffset := INT_TO_DINT(#Suchzeiger + #Datenblock_inBytes + #i)));
END_FOR;
END_IF;
// Letzten Eintrag loeschen (AWL: Last)
FOR #i := 0 TO #Datensatzlaenge - 1 DO
POKE(area := 16#84, dbNumber := #tDbList,
byteOffset := INT_TO_DINT(#Datenlisten_Ende - #Datenblock_inBytes + #i * 2),
value := WORD#16#0000);
END_FOR;
// Speicherung letzter Zugriff loeschen
POKE(area := 16#84, dbNumber := #tDbList, byteOffset := INT_TO_DINT(#SSDW + 12),
value := WORD#16#0000);
#Suchzeiger := 0;
#IndZeiger_ok := FALSE;
// Anzahl Eintraege -1 (Minimum 0)
#tAnz := WORD_TO_INT(PEEK_WORD(area := 16#84, dbNumber := #tDbList,
byteOffset := INT_TO_DINT(#SSDW + 16))) - 1;
IF #tAnz < 0 THEN
#tAnz := 0; // Minimum uebergeben
END_IF;
POKE(area := 16#84, dbNumber := #tDbList, byteOffset := INT_TO_DINT(#SSDW + 16),
value := INT_TO_WORD(#tAnz));
END_IF;
END_IF;
END_REGION
REGION NW12: Weitersuchen aktiv ? Datensatz vorhanden oder 0 ?
IF #Weitersuchen THEN
#tGefunden := FALSE;
FOR #i := 0 TO #Datensatzlaenge - 1 DO
IF (#tRecPuffer[#i * 2] <> 16#00) OR (#tRecPuffer[#i * 2 + 1] <> 16#00) THEN
#tGefunden := TRUE; // Etwas angegeben
EXIT;
END_IF;
END_FOR;
IF #tGefunden THEN
#tRE_VAL := 16#80; // Daten sind vorhanden
ELSE
#tRE_VAL := 16#40; // Daten sind nicht vorhanden
END_IF;
GOTO mEnde;
END_IF;
END_REGION
REGION NW13: Indizierung ok ?
IF #Indok AND NOT #IndZeiger_ok THEN
// Index-Eintrag im indizierten DB aktualisieren
POKE(area := 16#84, dbNumber := #tDbInd, byteOffset := INT_TO_DINT(#IndByteOfs),
value := INT_TO_WORD(#Suchzeiger));
END_IF;
END_REGION
REGION NW14+NW15: Fehlerausgabe / RET_VAL
mEnde: #tVKE := TRUE; // VKE = 1 ohne Fehler !!!
GOTO mOk;
mErr0: #tRE_VAL := 16#01; // Kein Datenlistenbereich
GOTO mErrX;
mErr1: #tRE_VAL := 16#02; // Kopier-/SFC-Fehler
GOTO mErrX;
mErr2: #tRE_VAL := 16#04; // Listenende ist erreicht
GOTO mErrX;
mErr3: #tRE_VAL := 16#08; // Falscher Parameter bei Aufruf
GOTO mErrX;
mErr4: #tRE_VAL := 16#10; // Undefinierte Suchdaten
GOTO mErrX;
mErr6: #tRE_VAL := 16#40; // Datensatz nicht vorhanden
mErrX: #tVKE := FALSE; // VKE = 0 bei Fehler !!!
mOk: #RE_VAL := #tRE_VAL;
ENO := #tVKE; // AWL: SAVE (BR-Bit fuer JBI beim Aufrufer)
END_REGION
END_FUNCTION
+303
View File
@@ -0,0 +1,303 @@
FUNCTION "FifoIn" : Void
TITLE = Datensatz in FIFO-DB eintragen
{ S7_Optimized_Access := 'FALSE' }
AUTHOR : IBAE
FAMILY : Module
NAME : FifoIn
//SCL-Portierung des AWL-Bausteins FC80 "FifoIn" (verhaltensgleich).
//
//FIFO-Kopf im Schnittstellen-DB ab Byte SSDW:
// +0 Kopf (Byte, 16#FF = FIFO-Kennung)
// +1 Datensatzlaenge (Byte, in Worten, 1..128)
// +2 FifoLaenge (Int, max. Anzahl Datensaetze)
// +4 Bringzeiger (Int, Byte-Adresse im DB)
// +6 Holzeiger (Int, Byte-Adresse im DB)
// +8 Anzahl (Int, eingetragene Datensaetze)
// +10 Status (Word)
// +12 Zusatzdaten (4 Worte)
// +40 Datenbereich, dahinter Endekennung 16#FFEE
//
//ABWEICHUNG ZUM AWL-ORIGINAL:
// - INDATEN ist jetzt VARIANT statt POINTER (S7-1500/SCL). AWL-/SCL-Aufrufer
// uebergeben weiterhin einen Bereich (z.B. P#M220.0 WORD 6 bzw. ein Symbol).
// - Registerindirekte DB-Zugriffe sind durch PEEK/POKE ersetzt (DB-Layout identisch).
// - Erfolg wird wie bisher ueber ENO/BR gemeldet (Aufrufer: JBI bzw. IF ENO).
VAR_INPUT
SSDB : Int; // Schnittstellen-DB
SSDW : Int; // Schnittstellen-1.DW (Byte-Offset des FIFO-Kopfs)
PARA : Word; // Aufrufparameter (Auftrag / Anzahl Zusatzdaten)
INDATEN : Variant; // InDaten-Schnittstelle (Datensatz, Datensatzlaenge Worte)
END_VAR
VAR_OUTPUT
RE_VAL : Byte; // Rueckmeldung (Fehlerbits)
END_VAR
VAR_TEMP
Kopf : Byte; // Fifo-Kennung (FF)
Datensatzlaenge : Byte; // Datensatzlaenge in Word (1 - 128)
FifoLaenge : Int; // Laenge des Fifo (maximale Anzahl der Datensaetze)
Bringzeiger : Int; // Zeiger, wohin naechster Eintrag gespeichert wird
Holzeiger : Int; // Zeiger, woher naechste Daten geholt werden
Anzahl : Int; // Anzahl der Datensaetze (eingetragene)
Status : Word; // Status des Fifo
Err : Byte; // Fehlerbits
Para1 : Byte; // 1.Aufrufparameter
Para2 : Byte; // 2.Aufrufparameter
Para21 : Byte; // 2.Aufrufparameter 1.Stelle
Para22 : Byte; // 2.Aufrufparameter 2.Stelle
MinBringzeiger : Int; // Minimum Bringzeiger
MaxBringzeiger : Int; // Maximum Bringzeiger
SFC_RET_VAL : Int; // Rueckmeldung TEST_DB-/Serialize-Aufruf
SFC_DB_LENGTH : Word; // DB-Laenge in Bytes
Fifo_Ende : Int; // Ende des Fifo (Byte-Offset Endekennung)
Offset : Int; // Offset 1. zu speicherndes Zusatzwort (Bytes)
SFC_WRITE_PROT : Bool; // DB ist schreibgeschuetzt
NixSave : Bool; // Status nicht speichern
tVKE : Bool; // Ergebnis fuer ENO (BR im AWL)
tDbNo : DInt; // DB-Nummer fuer PEEK/POKE
tAnzWorte : Int; // Anzahl zu kopierender Worte
tPos : DInt; // Serialize-Position
i : Int; // Schleifenzaehler
tPuffer : Array[0..257] of Byte; // Zwischenpuffer fuer INDATEN
END_VAR
BEGIN
REGION NW1: Schnittstellen-DB sinnvoll ?
#tDbNo := INT_TO_DINT(#SSDB);
"TEST_DB_TO_ATTR_DB"(DB_Number := INT_TO_WORD(#SSDB),
RetVal => #SFC_RET_VAL,
DB_Length => #SFC_DB_LENGTH,
Write_Prot => #SFC_WRITE_PROT);
#NixSave := (#SFC_RET_VAL <> 0) OR #SFC_WRITE_PROT; // Status nicht speichern
IF #NixSave THEN
#Err := 16#01; // kein Fifobereich (AWL: Err0)
GOTO mErr;
END_IF;
END_REGION
REGION NW2: FIFO-Kopf holen (AWL: Quelle-Pointer + BLKMOV)
IF #SSDW < 0 OR (#SSDW + 12) > WORD_TO_INT(#SFC_DB_LENGTH) THEN
#Err.%X1 := TRUE; // Kopf liegt nicht im DB (AWL: SFC20-Fehler, Err1)
GOTO mErr;
END_IF;
#Kopf := PEEK(area := 16#84, dbNumber := #tDbNo, byteOffset := INT_TO_DINT(#SSDW));
#Datensatzlaenge := PEEK(area := 16#84, dbNumber := #tDbNo, byteOffset := INT_TO_DINT(#SSDW + 1));
#FifoLaenge := WORD_TO_INT(PEEK_WORD(area := 16#84, dbNumber := #tDbNo, byteOffset := INT_TO_DINT(#SSDW + 2)));
#Bringzeiger := WORD_TO_INT(PEEK_WORD(area := 16#84, dbNumber := #tDbNo, byteOffset := INT_TO_DINT(#SSDW + 4)));
#Holzeiger := WORD_TO_INT(PEEK_WORD(area := 16#84, dbNumber := #tDbNo, byteOffset := INT_TO_DINT(#SSDW + 6)));
#Anzahl := WORD_TO_INT(PEEK_WORD(area := 16#84, dbNumber := #tDbNo, byteOffset := INT_TO_DINT(#SSDW + 8)));
#Status := PEEK_WORD(area := 16#84, dbNumber := #tDbNo, byteOffset := INT_TO_DINT(#SSDW + 10));
#Err := WORD_TO_BYTE(#Status AND 16#00E0); // Kapitalfehler aus frueheren Aufrufen?
IF #Err <> 16#00 THEN
GOTO mErr;
END_IF;
END_REGION
REGION NW3: Zeigerueberpruefung
IF #Kopf <> 16#FF THEN // keine Fifokennung
#NixSave := TRUE;
#Err := 16#01; // kein Fifobereich
GOTO mErr;
END_IF;
IF BYTE_TO_INT(#Datensatzlaenge) < 1 OR BYTE_TO_INT(#Datensatzlaenge) > 128 THEN
#Err := 16#01; // kein Fifobereich
GOTO mErr;
END_IF;
IF #FifoLaenge < 1 THEN
#Err := 16#01; // kein Fifobereich
GOTO mErr;
END_IF;
#MinBringzeiger := #SSDW + 40; // Speichern ab 20.DW
#MaxBringzeiger := (#FifoLaenge - 1) * BYTE_TO_INT(#Datensatzlaenge) * 2 + 40 + #SSDW;
#Fifo_Ende := BYTE_TO_INT(#Datensatzlaenge) * 2 + #MaxBringzeiger; // Zeiger Endekennung
IF #Fifo_Ende > WORD_TO_INT(#SFC_DB_LENGTH) THEN // Fifo-DB ist zu kurz!
#Err := 16#01; // kein Fifo-Bereich
GOTO mErr;
END_IF;
IF PEEK_WORD(area := 16#84, dbNumber := #tDbNo, byteOffset := INT_TO_DINT(#Fifo_Ende)) <> 16#FFEE THEN
#Err := 16#01; // kein Fifobereich (Endekennung fehlt)
GOTO mErr;
END_IF;
IF #MinBringzeiger > #Bringzeiger THEN
// Nur zulaessig, wenn alles in Grundstellung: dann initialisieren
IF #Bringzeiger <> 0 OR #Holzeiger <> 0 OR #Anzahl <> 0 THEN
#Err.%X5 := TRUE; // Bringzeiger ist ausserhalb
GOTO mErr;
END_IF;
#Bringzeiger := #MinBringzeiger;
#Holzeiger := #MinBringzeiger;
END_IF;
IF #Bringzeiger > #MaxBringzeiger THEN
#Err.%X5 := TRUE; // Bringzeiger ist ausserhalb
GOTO mErr;
END_IF;
IF (#Bringzeiger = #Holzeiger) AND (#FifoLaenge <> #Anzahl) AND (#Anzahl <> 0) THEN
#Err.%X7 := TRUE; // Versatz Bring-/Holzeiger
GOTO mErr;
END_IF;
IF (#Bringzeiger <> #Holzeiger) AND ((#FifoLaenge = #Anzahl) OR (#Anzahl = 0)) THEN
#Err.%X7 := TRUE; // Versatz Bring-/Holzeiger
GOTO mErr;
END_IF;
END_REGION
REGION NW4: Nur Zusatzdaten / als 1.Datensatz speichern ?
#Para1 := WORD_TO_BYTE(SHR(IN := #PARA, N := 8)); // 1.Aufrufparameter
#Para2 := WORD_TO_BYTE(#PARA); // 2.Aufrufparameter
IF #Para1 = 16#02 THEN // Nur Zusatzdaten speichern
IF #Para2 = 16#00 THEN
#Err.%X3 := TRUE; // Falscher Aufrufparameter
GOTO mErr;
END_IF;
GOTO mNw06; // Zusatzdaten speichern
END_IF;
IF #Para1 = 16#01 THEN // Als 1.Datensatz speichern
#Bringzeiger := #MinBringzeiger;
#Holzeiger := #MinBringzeiger;
#Anzahl := 0;
GOTO mNw06; // Zusatz-Daten speichern?
END_IF;
END_REGION
REGION NW5: Hat Fifo Platz / Datenbereich leer ?
IF #Anzahl >= #FifoLaenge THEN
IF #Anzahl = #FifoLaenge THEN
#Err.%X2 := TRUE; // Fifo ist voll
ELSE
#Err.%X7 := TRUE; // Versatz Bring-/Holzeiger
END_IF;
GOTO mErr;
END_IF;
// Ziel-Speicherzellen muessen leer (0) sein
FOR #i := 0 TO BYTE_TO_INT(#Datensatzlaenge) - 1 DO
IF PEEK_WORD(area := 16#84, dbNumber := #tDbNo,
byteOffset := INT_TO_DINT(#Bringzeiger + #i * 2)) <> 16#0000 THEN
#Err.%X4 := TRUE; // Speicherzelle ist belegt
GOTO mErr;
END_IF;
END_FOR;
END_REGION
REGION NW6: Zusatzdaten schreiben (Quelle: Merker ab MB240)
mNw06: IF #Para2 <> 16#00 THEN
#Para21 := SHR(IN := #Para2 AND 16#F0, N := 4); // 1.Stelle isolieren (von)
#Para22 := #Para2 AND 16#0F; // 2.Stelle isolieren (bis)
IF (#Para21 > #Para22) // 1.Stelle > 2.Stelle !
OR (#Para22 > 16#04) // 2.Stelle > 4 !
OR (#Para21 = 16#00) THEN // 1.Stelle = 0 !
#Err.%X3 := TRUE; // Falscher Aufrufparameter
GOTO mErr;
END_IF;
#tAnzWorte := BYTE_TO_INT(#Para22) - BYTE_TO_INT(#Para21) + 1;
#Offset := (BYTE_TO_INT(#Para21) - 1) * 2; // Offset 1. zu speicherndes Zusatzwort
// Quelle: MB240+Offset -> Ziel: DB ab SSDW+12+Offset (ab 6.DW)
FOR #i := 0 TO #tAnzWorte * 2 - 1 DO
POKE(area := 16#84, dbNumber := #tDbNo,
byteOffset := INT_TO_DINT(#SSDW + 12 + #Offset + #i),
value := PEEK(area := 16#83, dbNumber := 0,
byteOffset := INT_TO_DINT(240 + #Offset + #i)));
END_FOR;
END_IF;
END_REGION
REGION NW7: Fifodaten schreiben
IF #Para1 = 16#FF THEN // Testsendung: Zeiger nur testweise veraendern
GOTO mNw08;
END_IF;
IF #Para1 = 16#02 THEN // Nur Zusatzdaten speichern: fertig!
GOTO mEnde;
END_IF;
// INDATEN in Zwischenpuffer holen (AWL: BLKMOV ueber uebergebenen Pointer)
#tPos := 0;
#SFC_RET_VAL := Serialize(SRC_VARIANT := #INDATEN, DEST_ARRAY := #tPuffer, POS := #tPos);
IF (#SFC_RET_VAL <> 0) OR (#tPos < BYTE_TO_INT(#Datensatzlaenge) * 2) THEN
#Err.%X1 := TRUE; // Kopier-Fehler (AWL: SFC20-Aufruf mit Fehler)
GOTO mErr;
END_IF;
// Datensatz ab Bringzeiger in FIFO-DB schreiben
FOR #i := 0 TO BYTE_TO_INT(#Datensatzlaenge) * 2 - 1 DO
POKE(area := 16#84, dbNumber := #tDbNo,
byteOffset := INT_TO_DINT(#Bringzeiger + #i),
value := #tPuffer[#i]);
END_FOR;
END_REGION
REGION NW8: Bringzeiger, Anzahl erhoehen
mNw08: #Bringzeiger := BYTE_TO_INT(#Datensatzlaenge) * 2 + #Bringzeiger;
IF #Bringzeiger > #MaxBringzeiger THEN // Maximum erreicht?
#Bringzeiger := #MinBringzeiger;
END_IF;
#Anzahl := #Anzahl + 1;
END_REGION
REGION NW9: Versatz Bring-/Holzeiger
IF (#Bringzeiger = #Holzeiger) AND (#FifoLaenge <> #Anzahl) THEN
#Err.%X7 := TRUE; // Versatz Bring-/Holzeiger
GOTO mErr;
END_IF;
IF (#Bringzeiger <> #Holzeiger) AND (#FifoLaenge = #Anzahl) THEN
#Err.%X7 := TRUE; // Versatz Bring-/Holzeiger
GOTO mErr;
END_IF;
END_REGION
REGION NW10: Fehlerausgabe / RET_VAL
mEnde: #tVKE := TRUE; // VKE = 1 ohne Fehler !!!
GOTO mOk;
mErr: #tVKE := FALSE; // VKE = 0 bei Fehler !!!
mOk: #RE_VAL := #Err;
END_REGION
#Status := #Status OR BYTE_TO_WORD(#Err);
REGION NW11: Fifokopfdaten speichern
IF #Err.%X0 AND #NixSave THEN // kein gueltiger Fifobereich: nichts speichern
ENO := #tVKE;
RETURN;
END_IF;
"MW254" := INT_TO_WORD(#Anzahl - 1); // Uebergabe an FC+1 (da vorher zum Test erhoeht wurde)
IF #Para1 = 16#FF THEN // Testsendung: Zeiger wurden nur testweise geaendert!
ENO := #tVKE;
RETURN;
END_IF;
// Bringzeiger, Holzeiger, Anzahl, Status in FIFO-Kopf zuruecksichern (ab SSDW+4)
POKE(area := 16#84, dbNumber := #tDbNo, byteOffset := INT_TO_DINT(#SSDW + 4), value := INT_TO_WORD(#Bringzeiger));
POKE(area := 16#84, dbNumber := #tDbNo, byteOffset := INT_TO_DINT(#SSDW + 6), value := INT_TO_WORD(#Holzeiger));
POKE(area := 16#84, dbNumber := #tDbNo, byteOffset := INT_TO_DINT(#SSDW + 8), value := INT_TO_WORD(#Anzahl));
POKE(area := 16#84, dbNumber := #tDbNo, byteOffset := INT_TO_DINT(#SSDW + 10), value := #Status);
"MW248" := INT_TO_WORD(#FifoLaenge); // Uebergabe an FC+1 (fuer Durchlaufspeicher)
"MW250" := INT_TO_WORD(#Bringzeiger); // Uebergabe an FC+1 (fuer Durchlaufspeicher)
"MW252" := INT_TO_WORD(#Holzeiger); // Uebergabe an FC+1 (fuer Durchlaufspeicher)
"MW254" := INT_TO_WORD(#Anzahl); // Uebergabe an FC+1 (fuer Durchlaufspeicher)
END_REGION
ENO := #tVKE; // AWL: SAVE (BR-Bit fuer JBI beim Aufrufer)
END_FUNCTION
File diff suppressed because it is too large Load Diff
+146
View File
@@ -0,0 +1,146 @@
FUNCTION_BLOCK "MZylinder"
TITLE = Zylinderansteuerung
{ S7_Optimized_Access := 'FALSE' }
AUTHOR : IBAE
FAMILY : Module
NAME : MZylinder
//SCL-Portierung des AWL-Bausteins FB92 "MZylinder" (verhaltensgleich).
//Schnittstelle und Instanzdaten (UDT "UZylinder") unveraendert.
VAR_INPUT
Automatik : Bool; // Automatikbetrieb ist aktiv
Handbetrieb : Bool; // Handbetrieb ist aktiv
T_Ast : Int := 5; // Zeit bis AST erreicht ist (*100ms)
T_Gst : Int := 5; // Zeit bis GST erreicht ist (*100ms)
END_VAR
VAR_OUTPUT
ZylAst : Bool; // Vorwaerts-Ausgang des Antriebs
ZylGst : Bool; // Rueckwaerts-Ausgang des Antriebs
END_VAR
VAR
Stat { S7_SetPoint := 'True'} : "UZylinder";
END_VAR
VAR_TEMP
tZeitNullen : Bool; // AWL-Sprungziel "tdel": Zeitzelle nullen + Old_Ast nachfuehren
tFehler : Bool; // NW5: undefinierter Zustand / Befehl
tFlanke : Bool; // NW10: Flankenergebnis
END_VAR
BEGIN
REGION NW1: Zeitwert loeschen
// Endstellung erreicht oder Fahrtrichtung hat gewechselt -> Zeit nullen
#tZeitNullen := (#ZylAst AND (#Stat.B_AST OR NOT #Stat.Old_Ast))
OR (NOT #ZylAst AND (#Stat.B_GST OR #Stat.Old_Ast));
END_REGION
IF NOT #tZeitNullen THEN
REGION NW2: AST-Erreichtzeit abgelaufen ?
IF #Stat.T_AST_Akt = 9999 THEN
#Stat.T_AST_Akt := #T_Ast; // Sollwert uebergeben (9999 = kein Zeitwert angegeben)
END_IF;
IF #ZylAst THEN // Zylinder faehrt nach AST ?
#Stat.B_GST := FALSE;
IF #Stat.T_Zelle >= #Stat.T_AST_Akt THEN // Gewollter Zeitwert erreicht ?
#Stat.B_AST := TRUE; // Zylinder hat AST erreicht
#tZeitNullen := TRUE; // AWL: JC tdel
END_IF;
ELSIF NOT #Stat.Typ5_3 THEN // Zylinder haelt seine Stellung !
#Stat.B_AST := FALSE;
END_IF;
END_REGION
END_IF;
IF NOT #tZeitNullen THEN
REGION NW3: GST-Erreichtzeit abgelaufen ?
IF #Stat.T_GST_Akt = 9999 THEN
#Stat.T_GST_Akt := #T_Gst; // Sollwert uebergeben (9999 = kein Zeitwert angegeben)
END_IF;
// Zylinder faehrt nach GST ? (2-fache Ansteuerung: ZylGst / 1-fache: ZylAst aus)
IF ((#Stat.Typ5_2 OR #Stat.Typ5_3) AND #ZylGst)
OR (#Stat.Typ3_2 AND NOT #ZylAst) THEN
#Stat.B_AST := FALSE;
IF #Stat.T_Zelle >= #Stat.T_GST_Akt THEN // Gewollter Zeitwert erreicht ?
#Stat.B_GST := TRUE; // Zylinder hat GST erreicht
#tZeitNullen := TRUE; // AWL: JC tdel
END_IF;
ELSIF NOT #Stat.Typ5_3 THEN // Zylinder haelt seine Stellung !
#Stat.B_GST := FALSE;
END_IF;
END_REGION
END_IF;
IF NOT #tZeitNullen THEN
REGION NW4: Zeit laeuft
// Nicht zaehlen, wenn 2-fach angesteuerter Zylinder gar nicht angesteuert wird;
// Zaehlung nur mit dem 100ms-Impuls
IF "100ms-Impuls"
AND NOT ((#Stat.Typ5_2 OR #Stat.Typ5_3) AND NOT #ZylGst AND NOT #ZylAst) THEN
#Stat.T_Zelle := #Stat.T_Zelle + WORD_TO_INT("MW2"); // 100ms-Takte
END_IF;
END_REGION
ELSE
// AWL-Sprungziel "tdel" (aus NW1/NW2/NW3)
#Stat.T_Zelle := 0; // Zeitwert loeschen
#Stat.Old_Ast := #ZylAst; // Fuhr letzten Zyklus nach AST
END_IF;
REGION NW5: Undefinierte Zustaende / Befehle
#tFehler := (#Automatik AND #Handbetrieb) // Automatik + Hand aktiv
OR (NOT #Automatik AND NOT #Handbetrieb) // keine Betriebsart
OR (#Stat.Ast AND #Stat.Gst) // AST- + GST-fahren gleichzeitig aktiv
OR (NOT #Stat.Typ3_2 AND NOT #Stat.Typ5_2 AND NOT #Stat.Typ5_3) // undefinierter Ventil-Typ
OR (#Stat.Typ3_2 AND (#Stat.Typ5_2 OR #Stat.Typ5_3)) // mehrere Ventil-Typen
OR (#Stat.Typ5_2 AND #Stat.Typ5_3);
IF #tFehler THEN // Zylinderstellung bleibt eingefroren
IF #Stat.Typ5_3 THEN // Zylinder haelt seine Stellung !
#Stat.Ast := FALSE; // Bewegungsbits loeschen
#Stat.Gst := FALSE;
END_IF;
RETURN; // AWL: BEU
END_IF;
END_REGION
REGION NW6: Zylinder auf Arbeitsstellung fahren
#ZylAst := #Stat.Ast;
END_REGION
REGION NW7: Zylinder auf Grundstellung fahren
#ZylGst := (#Stat.Typ5_2 OR #Stat.Typ5_3) AND #Stat.Gst;
END_REGION
REGION NW8: Daten, die zurueckgesetzt werden
IF #Stat.Typ5_3 THEN // Zylinder haelt seine Stellung !
#Stat.Ast := FALSE; // Bewegungsbits loeschen
#Stat.Gst := FALSE;
END_IF;
IF #Stat.Typ3_2 THEN
#Stat.Gst := FALSE; // Es gibt kein GST fahren !
END_IF;
END_REGION
REGION NW9: Ausgaenge an OP uebergeben
#Stat.OP_Out_GST := #ZylGst;
#Stat.OP_Out_AST := #ZylAst;
END_REGION
REGION NW10: Statistik
// Positive Flanke ZylAst (AWL: FP Stat.FP_AST)
#tFlanke := #ZylAst AND NOT #Stat.FP_AST;
#Stat.FP_AST := #ZylAst;
IF #tFlanke THEN
#Stat.Statistik.AST_Schaltungen := #Stat.Statistik.AST_Schaltungen + 1;
END_IF;
// Positive Flanke ZylGst bei Typ5_3 (AWL: FP Stat.FP_AST2)
#tFlanke := #ZylGst AND #Stat.Typ5_3 AND NOT #Stat.FP_AST2;
#Stat.FP_AST2 := #ZylGst AND #Stat.Typ5_3;
IF #tFlanke THEN
#Stat.Statistik.AST2_Schaltungen := #Stat.Statistik.AST2_Schaltungen + 1;
END_IF;
END_REGION
END_FUNCTION_BLOCK
+91
View File
@@ -0,0 +1,91 @@
# SCL-Export — AWL→SCL-Pilotkonvertierung
Pilotbausteine der AWL→SCL-Konvertierung, bereit zum Test in TIA Portal V20.
Je Station ein Ordner; die Dateien sind **externe Quellen** (Import über
*Externe Quellen → Quelle hinzufügen → Bausteine aus Quelle generieren*).
## Inhalt
| Baustein | Original | Kategorie | Datei |
|---|---|---|---|
| MZylinder | FB 92, AWL, 11 Netzwerke | A — Verknüpfungslogik | `MZylinder.scl` |
| FifoIn | FC 80, AWL, Pointer/BLKMOV | C — Pointerzugriffe | `FifoIn.scl` |
| DatenOut | FC 86, AWL, Pointer/indirekte Aufrufe | C — Pointerzugriffe | `DatenOut.scl` |
| MHMOmniSchleuse | FB 1274, AWL, Schrittkette 52 NW | B — Ablaufsteuerung | `MHMOmniSchleuse.scl` |
Die Bausteine sind zwischen UH06 und UH07 identisch (FifoIn: nur Kommentare
verschieden) — **Ausnahme `MHMOmniSchleuse`**: die CASE-Verteiler für die
indirekten FC-Aufrufe (Leser-/Ziel-/Draussen-FC) enthalten die
stationsspezifischen FC-Nummern:
- UH06: 181/1181/2181 (Ziel), 1180 (Leser), 1182 (Draussen)
- UH07: 181..6181 (Ziel), 180..6180 (Leser), 182..6182 (Draussen)
## WICHTIG: Nicht direkt ins Produktivprojekt!
`DatenOut` und `FifoIn` haben geänderte Schnittstellen
(`POINTER`/`ANY``VARIANT`, S7-1500-konform). Die **bestehenden
AWL-Aufrufer** übergeben `P#M...`-Pointer und kompilieren nach dem Ersetzen
u.U. nicht mehr, bis sie angepasst sind. Daher:
1. **Projektkopie** anlegen (oder eigenes Test-Projekt mit PLCSIM).
2. Quellen importieren und **einzeln generieren** — Reihenfolge:
`DatenOut``MHMOmniSchleuse` (Aufrufer von DatenOut); `MZylinder` und
`FifoIn` sind unabhängig.
3. Kompilieren; Fehlerliste prüfen (erwartbar: Aufrufer von DatenOut/FifoIn,
die noch AWL-Pointer übergeben).
`MZylinder` ist schnittstellengleich und kann den AWL-Baustein direkt
ersetzen (Aufrufer und Instanz-DBs bleiben gültig, neu übersetzen genügt).
## Voraussetzungen (im Projekt bereits vorhanden)
- UDTs: `UZylinder`, `UDLHMOmniSchleuse`, `USk_ILS`, `UErrHMOmniSchleuse`, `UTrolley`
- Bausteine: `TEST_DB_TO_ATTR_DB` (SCL), `BCD (4stellig)_to_INT`,
`Wandel_FC_für_DB1000`, `Trolley_DL_Neue_Pos`, die Leser-/Ziel-/Draussen-FCs,
DB `TrolleyDL_OutDaten` (DB1011)
- PLC-Variablen: `MW2`, `MW4`, `100ms-Impuls`, `1s-Impuls`, `MW200`,
`M200.0..M201.7`, `MW190..MW198`, `MD182`, `MD192`, `M180.x`, `M199.0`,
`MW248..MW254`
- Nicht-optimierte DBs (Standard-Zugriff) — die Bausteine arbeiten mit
PEEK/POKE auf absolute Byte-Offsets, Layouts unverändert.
## Umsetzungs-Konventionen (gelten auch für die Massenkonvertierung)
- AWL-Netzwerke → `REGION NW<x>: <Titel>`; Sprünge → `IF`/`CASE`/`GOTO`
(GOTO nur für die gemeinsamen Fehler-/Verwaltungs-Enden, wie im AWL).
- `OPN DB[n]` + `DBW[AR1,..]``PEEK/POKE (area:=16#84, dbNumber:=…, byteOffset:=…)`.
- `BLKMOV` über ANY-Pointer → `Serialize`/`Deserialize` mit `VARIANT`-Parametern.
- BR-Bit (`SAVE`/`JBI`/`BEC`) → `ENO := …;` bzw. Auswertung von RE_VAL-Bit 7.
- Indirekte Aufrufe `UC FC[n]``CASE n OF` mit den projektierten FC-Nummern.
- 16-Bit-Semantik (`+I`, `*I`) bleibt `Int`; keine stille Aufweitung auf DInt.
## Bekannte, dokumentierte Abweichungen (Kopf der jeweiligen Datei!)
- **FifoIn**: `INDATEN` jetzt `Variant` (statt `POINTER`); Kopier-Fehler des
alten SFC20 werden über eigene Bereichsprüfungen nachgebildet.
- **DatenOut**: `SEARCHDATEN`/`OUTDATEN` jetzt `Variant`; die DB-Prüfungen des
Such-/Out-DB (AWL NW5/NW8) entfallen (Fehlerbit 5 wird nicht mehr erzeugt);
`OUTDATEN`-Ziel sollte exakt Datensatzlänge×2 Bytes groß sein;
`Position` wird mit 0 initialisiert (AWL: undefiniert).
- **MHMOmniSchleuse**: Merker-Scratch (MW200-Suchdaten, M220-OutDaten, MB199)
durch lokale Puffer ersetzt; Merker-Schnittstellen zu Fremd-FCs
(MW190MW198, MD182/192, M180.x, Fehlerwort MW200/M200.x/M201.x) bleiben.
NW21 war im Original tot (CLR+JU) und entfällt. In Schritt 10 („NoLesung")
verglich das AWL gegen einen **undefinierten Akku-Altwert** — umgesetzt ist
die beabsichtigte Prüfung `TrolleyID_Input <> 0`. **Bei IBN prüfen!**
## Empfohlener Vergleichstest (PLCSIM)
1. AWL-Original umbenennen (z.B. `DatenOut``DatenOut_AWL`), SCL-Baustein
generieren.
2. Test-FC schreiben, der beide mit identischen Eingangsdaten aufruft
(getrennte Kopien des Ziel-DB!) und RE_VAL/ENO/DB-Inhalte vergleicht.
3. Für `MHMOmniSchleuse`: Instanz-DBs beider Varianten nach jedem Zyklus
vergleichen (Watch-Tabelle bzw. Vergleichs-FC über die Static-Bereiche).
## Werkzeug
`tools/stlxml2awl.py` wandelt die SimaticML-XML-Exporte (`StatementList`) in
lesbaren AWL-Text um (`python stlxml2awl.py <baustein.xml>`), inkl. Interface,
Netzwerktitel, Sprungmarken und Kommentaren — Grundlage für die
Massenkonvertierung der restlichen ~230 Bausteine.
+249
View File
@@ -0,0 +1,249 @@
#!/usr/bin/env python3
"""Wandelt TIA-Openness SimaticML XML (StatementList) in lesbaren AWL-Text um."""
import re
import sys
import xml.etree.ElementTree as ET
NS_IF = "{http://www.siemens.com/automation/Openness/SW/Interface/v5}"
NS_SL = "{http://www.siemens.com/automation/Openness/SW/NetworkSource/StatementList/v5}"
TOKEN_MAP = {
"A_BRACK": "A(", "O_BRACK": "O(", "AN_BRACK": "AN(", "ON_BRACK": "ON(",
"X_BRACK": "X(", "XN_BRACK": "XN(", "BRACKET": ")",
"NE_I": "<>I", "EQ_I": "==I", "GT_I": ">I", "LT_I": "<I", "GE_I": ">=I", "LE_I": "<=I",
"NE_D": "<>D", "EQ_D": "==D", "GT_D": ">D", "LT_D": "<D", "GE_D": ">=D", "LE_D": "<=D",
"NE_R": "<>R", "EQ_R": "==R", "GT_R": ">R", "LT_R": "<R", "GE_R": ">=R", "LE_R": "<=R",
"ADD_I": "+I", "SUB_I": "-I", "MUL_I": "*I", "DIV_I": "/I",
"ADD_D": "+D", "SUB_D": "-D", "MUL_D": "*D", "DIV_D": "/D",
"ADD_R": "+R", "SUB_R": "-R", "MUL_R": "*R", "DIV_R": "/R",
"ADD": "+", "NOP_0": "NOP 0", "NOP_1": "NOP 1",
"TAR2_ACCU1": "TAR2", "TAR1_ACCU1": "TAR1",
"LAR1_ACCU1": "LAR1", "LAR2_ACCU1": "LAR2",
"OPEN_DB": "OPN", "OPEN_DI": "OPN",
"EMPTY_LINE": "", "COMMENT": "//", "Assign": "=",
}
def fmt_symbol(access):
parts = []
for comp in access.findall(f"./{NS_SL}Symbol/{NS_SL}Component"):
name = comp.get("Name")
token = name if re.fullmatch(r"[A-Za-z_][A-Za-z0-9_]*", name) else f'"{name}"'
idx = [fmt_access(a) for a in comp.findall(f"{NS_SL}Access")]
if idx:
token += "[" + ",".join(idx) + "]"
if comp.get("SliceAccessModifier"):
token += "." + comp.get("SliceAccessModifier")
parts.append(token)
sym = ".".join(parts)
if access.get("Scope") == "GlobalVariable" and parts and not parts[0].startswith('"'):
sym = f'"{parts[0][1:-1] if parts[0].startswith(chr(34)) else parts[0]}"' + (
"." + ".".join(parts[1:]) if len(parts) > 1 else "")
return sym
def fmt_address(addr):
area = addr.get("Area")
typ = addr.get("Type")
bit = int(addr.get("BitOffset", 0))
blk = addr.get("BlockNumber")
width = {"Bool": 1, "Byte": 8, "Word": 16, "DWord": 32, "Int": 16, "DInt": 32}.get(typ, 0)
sizech = {1: "", 8: "B", 16: "W", 32: "D"}.get(width, "?")
areach = {"Local": "L", "Memory": "M", "Input": "I", "Output": "Q", "DB": "DB", "DI": "DI",
"Timer": "T", "Counter": "C", "PeripheryInput": "PI", "PeripheryOutput": "PQ"}.get(area, area)
if area in ("DB", "DI"):
pre = f"DB{blk}." if blk else ""
core = f"{pre}{'DBX' if width == 1 else 'DB' + sizech}{bit // 8}"
elif area == "Timer":
return f"T {bit}"
elif area == "Counter":
return f"C {bit}"
else:
core = f"{areach}{sizech}{bit // 8}"
if width == 1:
core += f".{bit % 8}"
return core
def fmt_indirect(ind):
width = ind.get("Width")
area = ind.get("Area")
reg = ind.get("Register", "")
inner = [fmt_access(a) for a in ind.findall(f"{NS_SL}Access")]
const = ind.find(f"{NS_SL}Constant")
off = ""
if const is not None:
off = const.findtext(f"{NS_SL}ConstantValue", "")
if width == "Block": # OPN DB[var]
return f"DB[{inner[0]}]" if inner else f"DB[{off}]"
sizech = {"Bit": "X", "Byte": "B", "Word": "W", "Double": "D", "DWord": "D"}.get(width, "?")
areach = {"DB": "DB", "DI": "DI", "Local": "L", "Memory": "M", "Input": "I", "Output": "Q"}.get(area, area or "")
base = f"{areach}{sizech}" if areach not in ("DB", "DI") else f"{areach}{sizech}"
if reg: # registerindirekt
return f"{base} [{reg},{off}]"
if inner: # speicherindirekt
return f"{base} [{inner[0]}]"
return f"{base} [{off}]"
def fmt_access(access):
scope = access.get("Scope")
if scope in ("LocalVariable", "GlobalVariable", "LocalConstant", "GlobalConstant"):
return fmt_symbol(access)
if scope in ("LiteralConstant", "TypedConstant"):
c = access.find(f"{NS_SL}Constant")
val = c.findtext(f"{NS_SL}ConstantValue", "") if c is not None else ""
typ = c.findtext(f"{NS_SL}ConstantType", "") if c is not None else ""
pref = {"Word": "W#16#", "DWord": "DW#16#", "Time": "", "S5Time": "", "Byte": "B#16#"}
if typ in ("Word", "DWord", "Byte") and not val.startswith(("2#", "16#", "W#", "DW#", "B#")):
return pref[typ] + val
return val
if scope == "AddressConstant":
c = access.find(f"{NS_SL}Constant")
return "P#" + c.findtext(f"{NS_SL}ConstantValue", "") if c is not None else "P#?"
if scope == "Address":
addr = access.find(f"{NS_SL}Address")
if addr is not None:
return fmt_address(addr)
ind = access.find(f"{NS_SL}Indirect")
if ind is not None:
return fmt_indirect(ind)
return "?ADDR?"
if scope == "Label":
lab = access.find(f"{NS_SL}Label")
return lab.get("Name") if lab is not None else "?LBL?"
if scope == "LabelDefinition":
lab = access.find(f"{NS_SL}Label")
return (lab.get("Name") + ":") if lab is not None else "?LBL?:"
if scope == "Statusword":
return access.find(f"{NS_SL}Statusword").get("Name", "?SW?")
if scope == "Call":
ci = access.find(f"{NS_SL}CallInfo")
if ci is None:
ins = access.find(f"{NS_SL}Instruction")
name = ins.get("Name")
params = []
for p in ins.findall(f"{NS_SL}Parameter"):
pa = p.find(f"{NS_SL}Access")
params.append(f"{p.get('Name')}:={fmt_access(pa) if pa is not None else '?'}")
return f"CALLINFO SYS \"{name}\" (" + ", ".join(params) + ")"
name, btype = ci.get("Name"), ci.get("BlockType")
inst = ci.find(f"{NS_SL}Instance")
s = f'"{name}"'
if inst is not None:
s += " , " + fmt_symbol(inst)
params = []
for p in ci.findall(f"{NS_SL}Parameter"):
pa = p.find(f"{NS_SL}Access")
params.append(f"{p.get('Name')}:={fmt_access(pa) if pa is not None else '?'}")
if params:
s += " (" + ", ".join(params) + ")"
return f"CALLINFO {btype} {s}"
return f"?{scope}?"
def render_statement(st):
label = ""
ld = st.find(f"{NS_SL}LabelDeclaration/{NS_SL}Label")
if ld is not None:
label = ld.get("Name") + ": "
toks = st.findall(f"{NS_SL}StlToken")
accesses = st.findall(f"{NS_SL}Access")
comments = []
for lc in st.iter(f"{NS_SL}LineComment"):
t = lc.findtext(f"{NS_SL}Text", "")
if t:
comments.append(t)
for cm in st.findall(f"{NS_SL}Comment"):
t = cm.findtext(f"{NS_SL}Text", "")
if t:
comments.append(t)
if not toks:
if label:
return label.rstrip()
return "// " + " | ".join(comments) if comments else ""
tok = toks[0].get("Text")
mn = TOKEN_MAP.get(tok, tok)
if tok == "EMPTY_LINE":
return label.rstrip() if label else ""
if tok == "COMMENT":
return "// " + " | ".join(comments)
ops = []
for a in accesses:
f = fmt_access(a)
if f.startswith("CALLINFO"):
return f"{label}CALL {f[9:]}" + (" // " + " | ".join(comments) if comments else "")
ops.append(f)
line = f"{label:6s}{mn:6s} " + ", ".join(ops)
if comments:
line = f"{line:60s} // " + " | ".join(comments)
return line.rstrip()
def render_interface(root):
out = []
for sec in root.iter(f"{NS_IF}Section"):
members = sec.findall(f"{NS_IF}Member")
out.append(f" {sec.get('Name')}:")
for m in members:
out.extend(render_member(m, 4))
return out
def render_member(m, indent):
out = []
cm = m.find(f"{NS_IF}Comment/{NS_IF}MultiLanguageText")
sv = m.findtext(f"{NS_IF}StartValue")
line = " " * indent + f"{m.get('Name')} : {m.get('Datatype')}"
if sv:
line += f" := {sv}"
if cm is not None and cm.text:
line = f"{line:55s} // {cm.text}"
out.append(line)
for sub in m.findall(f"{NS_IF}Member"):
out.extend(render_member(sub, indent + 2))
return out
def main(path):
s = open(path, encoding="utf-8").read()
root = ET.fromstring(s)
blk = None
for tag in ("FB", "FC", "OB", "GlobalDB", "InstanceDB"):
blk = root.find(f".//SW.Blocks.{tag}")
if blk is not None:
btype = tag
break
attrs = blk.find("AttributeList")
name = attrs.findtext("Name")
num = attrs.findtext("Number")
lang = attrs.findtext("ProgrammingLanguage")
print(f"=== {btype} {num}: {name} [{lang}] ===")
iface = attrs.find("Interface")
if iface is not None:
secs = iface.find(f"{NS_IF}Sections")
if secs is not None:
print("INTERFACE:")
for line in render_interface(secs):
print(line)
nwno = 0
for cu in blk.iter("SW.Blocks.CompileUnit"):
nwno += 1
title = ""
for mt in cu.iter("MultilingualTextItem"):
al = mt.find("AttributeList")
if al is not None and al.findtext("Culture") == "de-DE" and al.findtext("Text"):
title = al.findtext("Text")
break
print(f"\n--- NW {nwno}: {title} ---")
src = cu.find(f".//{NS_SL}StatementList")
if src is None:
print(" (kein STL-Quelltext)")
continue
for st in src.findall(f"{NS_SL}StlStatement"):
print(render_statement(st))
if __name__ == "__main__":
for p in sys.argv[1:]:
main(p)