Step 7 Teile ID Speicher mit Array of Char 16Byte

tommylik

Level-2
Beiträge
136
Reaktionspunkte
0
Zuviel Werbung?
-> Hier kostenlos registrieren
Servus,

Erstmal ein paar Infos.

Es ist eine seit kurzem laufende Anlage.
Die Teile bekommen in einer Station ein 16-Stelligen Code (Datum + Uhrzeit + Zusatz).
Diese ID wird im Global DB in einem Array[1..16] of Char gespeichert.
Diese ID wandert mit dem Teil in einem DB von einer Station zur anderen Station.
Bevor das Teil in die Förderwelt kommt gibt es eine Überprüfungsstation die prüft ob alle Elemente auf dem Teil sind.

Was ich vor habe.

Wenn etwas fehlen sollte gibt die Anlage eine Störung aus. Mit dieser Störung möchte ich nun diese ID in einem anderem DB speichern.
Als erstes habe ich ein UDT angelegt.

Screenshot 2021-11-10 193215.jpg

Dann den DB.

Mit einer Struktur "Schicht" mit einem Last Index und einem großen Array mit dem UDT.
Das ist deswegen so groß weil diese IDs einige Zeit in diesem DB verbleiben sollen.

Screenshot 2021-11-10 193547.jpg
Der DB soll folgendermaßen genutzt werden.
Auf DBB2, DBB4804 oder 9606 wird die ID eingetragen in Abhängigkeit welche Schicht aktiv ist.
Im Last Index (In_Out) wird gespeichert wo der letzte Eintrag war.
Kommt ein neuer Eintrag auf dem 1. Platz muss alles 1 Platz nach unten verschoben werden. Oder vorher das weiß ich nicht so genau wie es besser ist.
Wenn der 1. Eintrag am Ende des Arrays ankommt soll er verschwinden.

Der FC.

Screenshot 2021-11-10 201927.jpg


Die ID (Nio_PrCode) übergebe ich mit einem Any als Input.

Screenshot 2021-11-10 195949.jpg
Ist das Netzwerk 3 richtig kann ich das gebrauchen?

Und ab hier weiß ich nicht weiter.
Wie ich die Schleife für den Last Index aufbauen muss.
Im welchen Moment der Eintrag zu machen ist in Abhängigkeit der aktiven Schicht und wann die Daten weiter geschoben werden müssen.
Und das rausschieben vom letzten Eintrag nichts zu vergessen.

Wer ist bereit mir bitte dabei zu helfen.


Grüße Tommylik
 
Vorschlag: Besser als sich in AWL mit Pointern die Gehirnknoten zu brechen:
- das ganze in SCL programmieren, da kannst Du direkt mit Array-Index arbeiten
- das Array als Ringpuffer organisieren anstatt alle Einträge umzuspeichern

Harald
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Hallo Harald,

Vielen Dank für deine Antwort.

Keine Ahnung ob das mit SCL besser umzusetzen ist. Davon habe ich noch weniger Ahnung.
Bei uns ist hauptsächlich alles in AWL geschrieben nur die Applikations-FBs sind in SCL.
Da kann ich dich nur bitten ob du mir das schreiben könntest. Oder wenigstens gut Beschreiben.
Ich habe gehört das Pointer mit SCL erstellen noch viel schwieriger ist.
Oder du kennst irgendwo im Internet ein ähnliches Beispiel worauf ich aufbauen kann?

Grüße Tommylik
 
Hallo Heinileini,

Vielen Dank für deine Antwort.

Das ist natürlich erfreulich, dass man keine Pointer brauch, das bringt mich trotzdem nicht weiter.

Gibt es jemanden, der bereit wäre mir zu helfen und das ganze mit AWL umzusetzen?

Grüße Tommylik
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Das ist natürlich erfreulich, dass man keine Pointer brauch, das bringt mich trotzdem nicht weiter.

Gibt es jemanden, der bereit wäre mir zu helfen und das ganze mit AWL umzusetzen?
Mach es einfach in SCL, in AWL brinchst du dir da bloß einen ab. Das ist wirklich sehr einfach in SCL!


Du nimmst in SCL dazu einfach eine FOR Schleife!

Zum Beispiel:
i : INT;

// Register um eins nach hinten schieben
FOR i := 299 TO 1 BY -1 DO
Teiledaten := Teiledaten[i - 1];
END_FOR;

// Erste Struktur neu beschreiben
Teiledaten[0] := 0; //Struktur ablöschen bevor sie neu beschrieben wird, ggfs. FILL Baustein nehmen

Teiledaten[0].ID := '123456789'; //Was auch immer du da reinschreiben möchtest
 
Hallo Stocky,

Vielen Dank für deine Antwort und danke für das Mut machen.

Das ist wirklich sehr einfach in SCL!
Du hast gut Reden du kannst es ja auch im Schlaf.

Die For-Schleife i kann eine Temp variable sein? oder muss sie statisch sein? Wenn ja dann muss ich einen FB erstellen.
Was ist Teiledaten für dich? Input, statisch oder temp?

// Register um eins nach hinten schieben
Mit eins meinst du einen ganzen Block von 16 Char

Also Teil[0] auf Teil[1]

Der Wert von Byte 2 auf Byte 18
Der Wert von Byte 3 auf Byte 19

Screenshot 2021-11-11 212704.jpg

Welchen Wert muss ich für den Last Index eintragen Wo und wie?
Wie lese ich den bei SCL einen Any ein? oder brauch ich das auch nicht?

Teiledaten[0].ID := '123456789'; //Was auch immer du da reinschreiben möchtest
Kommt direkt aus einem anderen DB mit steigender Flanke.


Man das ist echt verwirrend.

Grüße Tommylik
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Man das ist echt verwirrend.
Im Gegenteil! SCL ist doch richtig entwirrend im Vergleich zu AWL mit ihrer Pointerei.
Du siehst anscheinend den Wald vor lauter PointerBäumen nicht mehr.
In SCL musst Du nicht ByteNrn nehmen und sie in BitNrn umrechnen.
Um diese Feinheiten kümmert sich der Compiler für Dich. Du schreibst in verständlicher Form Deine "AbsichtsErklärung" und der Compiler setzt es so um, dass Du Dich mit diesen Details nicht herumschlagen musst.
 
SCL hat den Vorteil, dass man die symbolische Addressierung direkt im Programmtext verwenden kann und nicht erst mit Ladeanweisungen und Pointern arbeiten muss.

Wenn man zum Beispiel irgendwas mit Teil 1 in Abhängigkeit von seinem Prägecode machen möchte, baut man eine IF-Anweisung wie folgt:
Code:
IF Schicht[1].Teil[1].PraegeCode[3] = 'Bedingung' THEN
    // Anweisungen
END_IF;
(Die Bedingung steht hier in Hochkommata, weil Du ja einen Char in Deiner Datenstruktur hast.)

Das von Dir angestrebte Verschieben geht, wie Stoky bereits andeutete, damit vergleichsweise einfach. Man kann in den symbolischen Adressen nämlich auch Variablen nutzen und verschachteln. Für das Beispiel müssen i und j als Integer im Temp-Bereich definiert werden.
Code:
FOR i := 299 TO 1 BY -1 DO
    FOR j := 1 To 16 BY 1 DO
        Schicht[1].Teil[i].PraegeCode[j] := Schicht[1].Teil[i-1].PraegeCode[j]
    END_FOR;
END_FOR;
Die äußere FOR-Anweisung ackert die Teile 299 bis 1 rückwärts durch und schreibt den Wert des jeweils vorhergehenden Teils in das Teil mit dem aktuellen Index. Die innere FOR-Anweisung sorgt jeweils dafür, dass alle PrägeCodes parallel hinten geschoben werden.

Die äußere Schleife arbeitet wegen der negativen Schrittweite BY -1 rückwärts, weil die Werte um ein Teil vorwärts geschoben werden sollen. Vorwärts durchlaufen geht dann nicht. Würde man erst Teil 1 in Teil 2 schreiben, kann man nicht mehr die ursprünglichen Werte von Teil 2 in Teil 3 schreiben, usw. Bei Teil 299 kann man aber erst die Werte von Teil 298 lesen, bevor man anschließend Teil 298 mit den Werten von Teil 297 überschreibt, usw.

i beginnt beim Index 299, weil Du 300 Teile hast und die Zählung mit Null beginnt. Man kann immer alle Teile durchlaufen, auch wenn nicht alle Einträge mit sinnvollen Werten gefüllt sind. Die ungültigen bzw. nicht sinnvollen Werte in den Einträgen mit hohen Indices werden im Laufe der Zeit dann einfach mit sinnvollen Werten überschrieben. Diese Strategie hat auch den Vorteil, dass automatisch das Teil mit der Nummer 299 beim Verschieben hinten runter fällt.

Ein Bezug zu LastIndex ist nicht nötig. Falls man das doch machen möchte, müsste man die äußere Schleife als
Code:
FOR i := (LastIndex + 1) TO 1 BY -1
definieren. LastIndex darf dann allerdings nicht größer als 298 werden. Man könnte LastIndex nach dem Aufruf der verschachtelten For-Schleife ganz einfach hochzählen:
Code:
LastIndex += 1;
IF LastIndex > 298 THEN
   LastIndex = 298;
END_IF;
LastIndex += 1; ist dabei eine Kurzform für LastIndex := LastIndex +1;.

Nachdem nun alle Teile um eins nach hinten geschoben wurden, sind die Einträge in Schicht[1].Teil[0] und Schicht[1].Teil[1] identisch. Man muss also Schicht[1].Teil[0] mit den neuen Werten füllen. Wenn die neuen Werte aus einem anderen DB kommen, kann man wieder eine For-Schleife verwenden:
Code:
FOR j := 1 TO 16 BY 1 DO
    Schicht[1].Teil[0].PraegeCode[j] := Quell_DB.PraegeCode[j]
END_FOR;
j kann hier erneut verwendet werden, weil die erste For-Anweisung bereits abgefrühstückt ist und j mit dem := Operator neu gesetzt wird.

SCL ist keine Raketenwissenschaft. AWL war in den 1970er en vogue, die Zeiten ändern sich. 😇

Viele Grüße
Zini

P.S.: Warum eigentlich ein Array mit Char? Wäre es nicht einfacher, den 16-stelligen Code gleich als String[16] zu speichern? Dann könnte die innere FOR-Schleife wegfallen
 
P.S.: Warum eigentlich ein Array mit Char? Wäre es nicht einfacher, den 16-stelligen Code gleich als String[16] zu speichern? Dann könnte die innere FOR-Schleife wegfallen
Da die Arrays identisch von der Länge und Datentyp sind, kann man die trotzdem direkt, ohne die innere Schleife, zuweisen.
(Als String[16] fände ich aber eigentlich auch schöner)

Code:
FUNCTION TestArrayCopy : VOID

VAR_TEMP
    i: INT;
END_VAR

FOR i := 299 TO 0 BY -1 DO
    "TestArray".Schicht[1].Teil[i] := "TestArray".Schicht[1].Teil[i - 1];
END_FOR;

END_FUNCTION

Der SCL-Compiler macht dann intern 4x Lade/Transfer mit Doppelworten draus...

Code:
...
      AUF   "TestArray"
      L     DBD [AR1,P#0.0]
      TAR1  LD    12
      LAR1  LD     4
      T     DBD [AR1,P#0.0]
      TAK  
      LAR1 
      L     DBD [AR1,P#4.0]
      LAR1  LD     4
      T     DBD [AR1,P#4.0]
      TAK  
      LAR1 
      L     DBD [AR1,P#8.0]
      LAR1  LD     4
      T     DBD [AR1,P#8.0]
      TAK  
      LAR1 
      L     DBD [AR1,P#12.0]
      LAR1  LD     4
      T     DBD [AR1,P#12.0]
...
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Hallo an alle,

Vielen Dank für die ganzen Antworten.

Ihr habt mich echt überzeugt das zu probieren. Und mit den SCL Bausteinen von den Applikationen
habe ich bis jetzt (00:48) mit dem Simulator probiert und probiert und ich habe es so gar geschafft.

Code:
FUNCTION X_FC2390_NIOTeile : VOID
TITLE = 'Datenbank'
//
// Baustein-Kommentar ...
//
VERSION : '1.0'
AUTHOR  : author
NAME    : name
FAMILY  : family


// Bausteinparameter
VAR_INPUT
    Req                 : BOOL;
    Schicht1_aktiv      : BOOL;
    Schicht2_aktiv      : BOOL;
    Schicht3_aktiv      : BOOL;      
   
END_VAR

VAR_IN_OUT
    // Durchgangsparameter
    NIO_PrCode          : ANY;
    NIO_Teile_Schicht1  : ANY;
    NIO_Teile_Schicht2  : ANY;
    NIO_Teile_Schicht3  : ANY;
   
END_VAR

VAR_OUTPUT
    // Ausgangsparameter
    Done                : BOOL;

END_VAR

VAR_TEMP
    // temporäre Variablen
    i                   : INT;
    j                   : INT;
    ret_val_SFC20       : INT;
    ret_val_SFC21       : INT;
    Leerzeichen         : BYTE;  

END_VAR

BEGIN

Leerzeichen := B#16#20;   //Leerzeichen für Initalisierung

IF Req = TRUE THEN
   
    IF Schicht1_aktiv = TRUE THEN
       
        ret_val_SFC20  :=  BLKMOV(SRCBLK :=  NIO_PrCode, DSTBLK :=  NIO_Teile_Schicht1);  //kopieren
       
        FOR i := 299 TO 1 BY -1 DO
            FOR j := 1 TO 16 BY 1 DO
                "X_DB2390_NIOTeile".Schicht[1].Teil[i].PraegeCode[j] := "X_DB2390_NIOTeile".Schicht[1].Teil[i-1].PraegeCode[j];              
                             
            END_FOR;
        END_FOR;
       
        Done    :=  TRUE;

    END_IF;

    IF Schicht2_aktiv = TRUE THEN
       
        ret_val_SFC20  :=  BLKMOV(SRCBLK :=  NIO_PrCode, DSTBLK :=  NIO_Teile_Schicht2);  //kopieren
       
        FOR i := 299 TO 1 BY -1 DO
            FOR j := 1 TO 16 BY 1 DO
                "X_DB2390_NIOTeile".Schicht[2].Teil[i].PraegeCode[j] := "X_DB2390_NIOTeile".Schicht[2].Teil[i-1].PraegeCode[j];
            END_FOR;
        END_FOR;
       
        Done    :=  TRUE;

    END_IF;

    IF Schicht3_aktiv = TRUE THEN
       
        ret_val_SFC20  :=  BLKMOV(SRCBLK :=  NIO_PrCode, DSTBLK :=  NIO_Teile_Schicht3);  //kopieren
       
        FOR i := 299 TO 1 BY -1 DO
            FOR j := 1 TO 16 BY 1 DO
                "X_DB2390_NIOTeile".Schicht[3].Teil[i].PraegeCode[j] := "X_DB2390_NIOTeile".Schicht[3].Teil[i-1].PraegeCode[j];
            END_FOR;
        END_FOR;
       
        Done    :=  TRUE;

    END_IF;

END_IF;

IF Schicht1_aktiv AND Done = TRUE THEN
   
    ret_val_SFC21  :=   FILL(BVAL :=  Leerzeichen, BLK :=  NIO_Teile_Schicht1);  //intialisieren
   
END_IF;

IF Schicht2_aktiv AND Done = TRUE THEN
   
    ret_val_SFC21  :=   FILL(BVAL :=  Leerzeichen, BLK :=  NIO_Teile_Schicht2);  //intialisieren
   
END_IF;

IF Schicht3_aktiv AND Done = TRUE THEN
   
    ret_val_SFC21  :=   FILL(BVAL :=  Leerzeichen, BLK :=  NIO_Teile_Schicht3);  //intialisieren
   
END_IF;

IF Req AND Done THEN
   
    Done := False;
   
END_IF;

END_FUNCTION

Schaut mal ob ich den so lassen kann. Was ich nicht weiß was passiert wenn mal ein Array am Ende angelangt ist.
300 Arrays wollte ich jetzt nicht voll machen.

Ich habe Array of Char genommen, weil der DB von dem die Daten übernommen werden auch mit Array of Char aufgebaut wurde.
Wie müsste ich den Baustein abändern wenn ich das mit Array of String machen würde?

Super von Euch mit der tollen Unterstützung.

Grüße und schönes Wochenende

Tommylik
 
Schaut mal ob ich den so lassen kann.
Sieht ganz gut aus. Herzlichen Glückwunsch.
Ich vermisse noch den Teil, in dem der jeweils erste Eintrag mit den Einträgen aus dem Quell-DB gefüllt wird. Dur schiebst hier "nur" nach hinten.

Ein paar kleine Optimierungsmöglichkeiten setzte ich ans Ende dieses Beitrags.
Was ich nicht weiß was passiert wenn mal ein Array am Ende angelangt ist.
300 Arrays wollte ich jetzt nicht voll machen.
Was meinst Du mit "Am Ende angelangt"?

Wenn ich Deinen Eröffnungsbeitrag richtig verstehe, hast Du mehrere DB mit jeweils 300 Teilen. Und die ackert Dein Code in jedem Zyklus immer vollständig durch. Du hast jedes Char im PraegeCode mit einem Leerzeichen ' ' als Startwert initialisiert, d.h. es werden Leerzeichen nach inten geschoben, bis das Array vollständig gefüllt ist.

Der jeweils letzte Eintrag verfällt bei einem Durchlauf. Wenn z.B. Teil[299].PraegeCode[1] = B ist und Teil[298].PraegeCode[1]=C, dann ist nach dem Aufruf der Schiebefunktion Teil[299].PraegeCode[1] = C, weil der Wert von Nummer 298 übernommen wurde. Der Wert B verdampft ins digitale Nirwana. 🙃

Ob ein DB "voll" oder "leer" ist, macht für die SPS keinen Unterschied, der Speicherbedarf ist gleich. Lediglich die Laufzeit des Programms könnte man optimieren, wenn man nur die vollen Einträge nach hinten schiebt. Das erkauft man sich dann allerdings mit einer schlechteren Nachvolziehbarkeit, weil dann Konstrukte mit wie der von Dir ursprünglich mal angedachte LastIndex erforderlich sind. Und nach spätestens 300 Aufrufen der Schiebefunktion wird der DB dann immer voll durchgeackert.
Ich habe Array of Char genommen, weil der DB von dem die Daten übernommen werden auch mit Array of Char aufgebaut wurde.
Wie müsste ich den Baustein abändern wenn ich das mit Array of String machen würde?
Du müsstest Dir den String aus den Chars des Quell-DBs zusammensetzen.

Man könnte dazu eine sog. AT-Überlagerung verwenden, das ist für Menschen mit wenig Erfahrung allerdings immer eine hohe Hürde.

Eine Alternative, die man sich wiederum mit etwas höherer Laufzeit erkauft, ist die Nutzung der Funktion CONCAT. Das würde dann etwa so aussehen:
Code:
VAR_TEMP
    dumpstring: STRING[16];
END_VAR

FOR i := 1 TO 16 By 1 DO
    dumpstring := CONCAT(IN1:= dumpstring IN2:=Quell-DB.Array[i])
END_FOR;
Der dumpstring ist ist dann von links nach rechts mit den Buchstaben 1 bis 16 gefüllt und kann dann als erster Eintrag in Deinen Speicher-DB geschrieben werden.

Jeder Schleifendurchlauf der Schiebefunktion schiebt dann den schon fertigen String.

Nun zu den Optimierungsmöglichkeiten (mit Anspruch auf Unvollständigkeit):

Das DONE-Konstrukt kannst du weglassen. Oben fragst Du verschachtelt REQ AND Schicht<n>_aktiv ab. Wenn das erfüllt ist, wird DONE auf TRUE gesetzt. Unten fragst Du DONE AND Schicht<n>_aktiv ab. Das ist bei Deinem Code mit REQ AND Schicht<n>_aktiv äquivalent.

Die Abfrage eines Boolschen Werts mit =TRUE kann komplett weggelassen werden:
Code:
IF BoolscheVariable1 THEN //Abfrage auf TRUE
IF NOT BoolscheVariable1 THEN //Abfrage auf FALSE
Macht den Code IMO viel lesbarer. Das = ist nur erforderlich, wenn man einen Zahlenwert oder eine Zeichenkette als Bedingung abfragt.

Das Leerzeichen-Konstrukt kann man sich in SCL auch sparen
Code:
ret_val_SFC21  := "                ";
sind 16 Leerzeichen. Alternativ kannst Du überlegen, ob nicht ret_val_SFC21 := ""; (leerer String) auch ginge. Kann ich nicht beurteilen, weil ich nicht weiß, wie ret_val_SFC21 weiterverarbeitet wird.

Offene Frage 🤔: Mir ist nicht ganz klar, warum Du (noch) mit ANY-Pointern arbeitest bzw. welchen Zweck die BLKMOV-Anweisungen verfolgen. Kann es sein, dass NIO_PrCode den Code bereits als String enthält?

Viele Grüße
Zini
 
Hallo Wuslon,

Vielen Dank für deine Antwort.

Ich vermisse noch den Teil, in dem der jeweils erste Eintrag mit den Einträgen aus dem Quell-DB gefüllt wird. Dur schiebst hier "nur" nach hinten.

Das ist diese Zeile. Sie gibt es 3 mal. NIO_PrCode kommt aus dem Quell-DB.

Code:
ret_val_SFC20  :=  BLKMOV(SRCBLK :=  NIO_PrCode, DSTBLK :=  NIO_Teile_Schicht1);  //kopieren

Was meinst Du mit "Am Ende angelangt"?
Ich meine damit was passiert wenn alle 300 Arrays gefüllt sind und der 301 Eintrag kommt.
Du hast es ja erklärt er verschwindet ins digitale Nirwana.

Wenn ich Deinen Eröffnungsbeitrag richtig verstehe, hast Du mehrere DB mit jeweils 300 Teilen.

Nein, Ich habe einen UDT erstellt:

Screenshot 2021-11-10 193215.jpg

Und einen DB mit 3 Strukturen "Schicht" wie z.B. Frühschicht.
Und jede Struktur hat 300 Arrays vom obigen UDT.

Screenshot 2021-11-13 115020.jpg

Das DONE-Konstrukt kannst du weglassen.
Das ist zum löschen der jeweils erste 16 Einträge (Teil0) nachdem geschoben wurde.
Ich weiß doch sonst nicht wann die Daten geschoben wurde.

Screenshot 2021-11-13 121827.jpg

Vielleicht wäre es auch sinnvoller erst die 16 Einträge zu löschen bevor die Quelldaten eingetragen werden. Was meinst du?

Offene Frage 🤔: Mir ist nicht ganz klar, warum Du (noch) mit ANY-Pointern arbeitest bzw. welchen Zweck die BLKMOV-Anweisungen verfolgen. Kann es sein, dass NIO_PrCode den Code bereits als String enthält?

Der ANY-Pointer NIO_PrCode sind die 16 Char aus dem Quell-DB

VAR_IN_OUT
// Durchgangsparameter
NIO_Teile_Schicht1 : ANY;
NIO_Teile_Schicht2 : ANY;
NIO_Teile_Schicht3 : ANY;

END_VAR

Diese 3 ANY-Pointer sind jeweils die ersten 16 Char im Ziel-DB wie oben im Screenshot zu sehen ist.

Vielen Dank nochmal für deine Hilfe.

Grüße Tommylik
 

Anhänge

  • Screenshot 2021-11-13 115020.jpg
    Screenshot 2021-11-13 115020.jpg
    39,7 KB · Aufrufe: 1
Zuviel Werbung?
-> Hier kostenlos registrieren
Das ist diese Zeile. Sie gibt es 3 mal. NIO_PrCode kommt aus dem Quell-DB.

Code:
ret_val_SFC20  :=  BLKMOV(SRCBLK :=  NIO_PrCode, DSTBLK :=  NIO_Teile_Schicht1);  //kopieren
Sie schreibt den Inhalt in die Variable ret_val_SFC_20, aber nicht in Deinen DB.
Es sei denn, Deine ANY-Pointer verweisen irgendwie in die DBs. Das geht aus Deinem Code allerdings nicht hervor.

Und wenn Du die Zeile vor der FOR-Schleife ausführst, wird der erste Eintrag geschrieben, bevor geschoben wird. Ich denke, Du möchtest das nach dem Schieben erledigen, oder?

Ich würde komplett ohne Pointer arbeiten und mit direkten Bezügen arbeiten. Etwa in dieser Art:
Code:
"X_DB2390_NIOTeile".Schicht[1].Teil[1] := NIO_PrCode;
Das Beispiel wird so allerdings nicht funktionieren, weil vermutlich die Datentypen nicht zueinander passen.
Und einen DB mit 3 Strukturen "Schicht" wie z.B. Frühschicht.
Und jede Struktur hat 300 Arrays vom obigen UDT.
OK, also nur ein DB mit drei Arrays für jede Schicht. Macht trotzdem keinen Unterschied, die For-Schleifen durchlaufen immer alle 300 Teile, egal, wo und wie man die speichert. Ob man einen oder drei DBs nutzt, ist Geschmackssache.
Das ist zum löschen der jeweils erste 16 Einträge (Teil0) nachdem geschoben wurde.
Ich weiß doch sonst nicht wann die Daten geschoben wurde.
[...]
Vielleicht wäre es auch sinnvoller erst die 16 Einträge zu löschen bevor die Quelldaten eingetragen werden. Was meinst du?
Hmm, ich habe Dein eingangs geschildertes Problem etwas anders verstanden. Nach meiner Lesart brauchst Du einen Vorratsspeicher für die Teilekennungen (egal, ob einzeln in Char oder als String gespeichert), bei dem die neuesten Teilekennungen immer an der ersten Stelle bzw. im Eintrag [0] stehen. Das würde bedeuten, zuerst zu schieben, um den ersten Eintrag für einen neuen Wert vorzubereiten, und dann den ersten Wert neu zu belegen. Warum Du den ersten Eintrag dann löschst, verstehe ich nicht.

Dass geschoben wurde, weißt Du, denn das passiert immer dann, wenn REQ gesetzt ist (in jedem Zyklus wieder). Ich hoffe, das REQ für Dich so eindeutig ist, dass wirklich nur dann geschoben wird, wenn Du es möchtest.

Diese 3 ANY-Pointer sind jeweils die ersten 16 Char im Ziel-DB wie oben im Screenshot zu sehen ist.
In Deinem FC musst Du die Pointer beim Aufruf passend mit den DB-Adressen belegen. Halte ich für unnötig umständlich angesichts der Tatsache, dass Du sonst mit festen Referenzen arbeitest. Du kannst die DB-Verweise direkt auflösen, statt Pointer zu benutzen. Siehe oben.

Pointer wären IMO dann sinnvoll, wenn man den FC mehrfach verwenden möchte. Wobei ich persönlich™ ANY-Konstrukte generell meide. Wenn ich etwas mit variablen Eingangswerten machen möchte, schaffe ich mir im Baustein immer die gleiche, zur Übergabe passende Datenstruktur, die ich dann im INPUT oder als IN/OUT verwende und von außen mit den Daten befüttere. Das erleichtert das Beobachten und damit die Fehlersuche.

Viele Grüße
Zini
 
Hallo Wuslon,

Vielen Dank für deine Antworten und Hilfe.

Der Baustein sieht so aus.

Screenshot 2021-11-13 135557.jpg

Ich habe ein paar deiner Anmerkungen umgesetzt.
Also der Baustein tut was er soll.

Wenn das NIO Signal kommt wird entsprechend der Schicht als erstes Teil 0 gelöscht
die neuen Quelldateien eingelesen und dann verschoben.

Hier ein Beispiel es funktioniert.
Screenshot 2021-11-13 140239.jpg


Code:
FUNCTION X_FC2390_NIOTeile : VOID
TITLE = 'Datenbank'
//
// Baustein-Kommentar ...
//
VERSION : '1.0'
AUTHOR  : author
NAME    : name
FAMILY  : family

// Bausteinparameter
VAR_INPUT
    Req                 : BOOL;
    NIO_PrCode          : ANY;
    Schicht1_aktiv      : BOOL;
    Schicht2_aktiv      : BOOL;
    Schicht3_aktiv      : BOOL;         
    
END_VAR

VAR_IN_OUT
    // Durchgangsparameter   
    NIO_Teile_Schicht1  : ANY;
    NIO_Teile_Schicht2  : ANY;
    NIO_Teile_Schicht3  : ANY;
    
END_VAR

VAR_OUTPUT
    // Ausgangsparameter
    
END_VAR

VAR_TEMP
    // temporäre Variablen
    i                   : INT;
    j                   : INT;
    ret_val_SFC20       : INT;
    ret_val_SFC21       : INT; 
    Leerzeichen         : BYTE;   

END_VAR

BEGIN

Leerzeichen := B#16#20;   //Leerzeichen für Initalisierung


IF Req THEN
    
    IF Schicht1_aktiv THEN
        
        ret_val_SFC21  :=   FILL(BVAL :=  Leerzeichen, BLK :=  NIO_Teile_Schicht1);  //intialisieren
        
        ret_val_SFC20  :=  BLKMOV(SRCBLK :=  NIO_PrCode, DSTBLK :=  NIO_Teile_Schicht1);  //kopieren
        
        FOR i := 299 TO 1 BY -1 DO
            FOR j := 1 TO 16 BY 1 DO
                "X_DB2390_NIOTeile".Schicht[1].Teil[i].PraegeCode[j] := "X_DB2390_NIOTeile".Schicht[1].Teil[i-1].PraegeCode[j];               
                              
            END_FOR;
        END_FOR;       

    END_IF;

    IF Schicht2_aktiv THEN
        
        ret_val_SFC21  :=   FILL(BVAL :=  Leerzeichen, BLK :=  NIO_Teile_Schicht2);  //intialisieren
        
        ret_val_SFC20  :=  BLKMOV(SRCBLK :=  NIO_PrCode, DSTBLK :=  NIO_Teile_Schicht2);  //kopieren
        
        FOR i := 299 TO 1 BY -1 DO
            FOR j := 1 TO 16 BY 1 DO
                "X_DB2390_NIOTeile".Schicht[2].Teil[i].PraegeCode[j] := "X_DB2390_NIOTeile".Schicht[2].Teil[i-1].PraegeCode[j];
            END_FOR;
        END_FOR;
      
    END_IF;

    IF Schicht3_aktiv THEN
        
        ret_val_SFC21  :=   FILL(BVAL :=  Leerzeichen, BLK :=  NIO_Teile_Schicht3);  //intialisieren
        
        ret_val_SFC20  :=  BLKMOV(SRCBLK :=  NIO_PrCode, DSTBLK :=  NIO_Teile_Schicht3);  //kopieren
        
        FOR i := 299 TO 1 BY -1 DO
            FOR j := 1 TO 16 BY 1 DO
                "X_DB2390_NIOTeile".Schicht[3].Teil[i].PraegeCode[j] := "X_DB2390_NIOTeile".Schicht[3].Teil[i-1].PraegeCode[j];
            END_FOR;
        END_FOR;
              
    END_IF;

END_IF;

END_FUNCTION

Vielen Dank für deine Hilfe.

Grüße Tommylik
 
Die Einträge von Teil[0] und Teil[1] sind aber immer identisch, weil die Schieberei nach dem Eintragen der neuen Daten in Teil[0] passiert.
Damit verschwendest Du den SpeicherPlatz Teil[0].

PS: Probier doch mal "spasseshalber", was passiert, wenn Du ...
Code:
FOR i := 299 TO 1 BY -1 DO
    FOR j := 1 TO 16 BY 1 DO
        "X_DB2390_NIOTeile".Schicht[1].Teil[i].PraegeCode[j] := "X_DB2390_NIOTeile".Schicht[1].Teil[i-1].PraegeCode[j];
    END_FOR;
END_FOR;
... ersetzt durch ...
Code:
FOR i := 299 TO 1 BY -1 DO
    "X_DB2390_NIOTeile".Schicht[1].Teil[i] := "X_DB2390_NIOTeile".Schicht[1].Teil[i-1] ;
END_FOR;
 
Zuletzt bearbeitet:
Zuviel Werbung?
-> Hier kostenlos registrieren
Hallo Heinileini,

Das ist richtig das Teil[0] und Teil[1] immer identisch sind deswegen habe ich es geändert das vorher Teil 0 gelöscht wird.
Damit sicher gestellt ist das von Teil 0 immer neue Daten geschoben werden.
Ich habe es probiert es funktioniert genauso. Teil 0 und Teil 1 bleibt aber identisch.
Das Teil 0 und Teil 1 identisch sind, ist ja 1. nicht so schlimm und 2. habe ich dafür eine Lösung.
Und zwar wird Teil 0 gelöscht, wenn die Station leer ist.

Ich habe viel dazu gelernt vielen Dank.

Grüße Tommylik
 
Der Baustein sieht so aus.
Sieht okay aus.

Ich sehe noch weiteres Potential für Verkürzung des Quellcodes. Und würde bei der Gelegenheit, wie bereits angedeutet, die ANY-Pointer über Bord werfen:
Code:
       FUNCTION X_FC2390_NIOTeile : VOID
TITLE = 'Datenbank'
//
// Baustein-Kommentar ...
//
VERSION : '1.0'
AUTHOR  : author
NAME    : name
FAMILY  : family

// Bausteinparameter
VAR_INPUT
    Req                 : BOOL
    Schicht1_aktiv      : BOOL;
    Schicht2_aktiv      : BOOL;
    Schicht3_aktiv      : BOOL;      
 
END_VAR

VAR_IN_OUT
    // Durchgangsparameter
END_VAR

VAR_OUTPUT
    // Ausgangsparameter
 
END_VAR

VAR_TEMP
    // temporäre Variablen
    i                   : INT;
    j                   : INT;
    Schicht        : INT;
END_VAR

BEGIN
IF Schicht1_aktiv AND NOT Schicht2_aktiv AND NOT Schicht3_aktiv THEN
    Schicht := 1;
ELSIF NOT Schicht1_aktiv AND Schicht2_aktiv AND NOT Schicht3_aktiv THEN
    Schicht :=2;
ELSIF NOT Schicht1_aktiv AND NOT Schicht2_aktiv AND Schicht3_aktiv THEN
    Schicht := 3;
ELSE Schicht := 0;
END_IF;

IF Req AND (Schicht > 0) THEN
        FOR i := 299 TO 1 BY -1 DO
            FOR j := 1 TO 16 BY 1 DO
                "X_DB2390_NIOTeile".Schicht[Schicht].Teil[i].PraegeCode[j] := "X_DB2390_NIOTeile".Schicht[Schicht].Teil[i-1].PraegeCode[j];
            END_FOR;
        END_FOR;

    FOR j:= 1 TO 16 BY 1 DO
         "X_DB2390_NIOTeile".Schicht[Schicht].Teil[1].PraegeCode[j] := "X_DB100_NIO_Teile".NIO_ID[j];
    END_FOR;
END_IF;
END_FUNCTION
Falls Du irgendwo extern die Informationen brauchst, die Du in ret_val_SFC20 speicherst, könntest Du direkt auf "X_DB2390_NIOTeile".Schicht[Schicht].Teil[1] zugreifen.

Viele Grüße
Zini
 
Falls Du irgendwo extern die Informationen brauchst, die Du in ret_val_SFC20 speicherst, könntest Du direkt auf "X_DB2390_NIOTeile".Schicht[Schicht].Teil[1] zugreifen.
Das verstehe ich nicht. Denn ret_val_SFC20 und ret_val_SFC21 waren als temporäre INT-Variablen deklariert, um irgendeinen Status der Bausteine FILL bzw. BLKMOV aufzunehmen. Davon soll etwas in "X_DB2390_NIOTeile".Schicht[Schicht].Teil[1] auffindbar sein? Ich hoffe nicht.
 
Zurück
Oben