Step 7 Gleitende Fehlerermittlung (Schieberegister+Auswertung)

mpoetter

Level-1
Beiträge
13
Reaktionspunkte
0
Zuviel Werbung?
-> Hier kostenlos registrieren
Guten Tag die Herrschaften,

ich habe aktuell folgendes Vorhaben:

An einer Maschine werden fehlerhafte Produkte ausgeworfen (Taktzahl der Maschine 1900/1min). Man moechte nun von den letzten 1000 Takten, den Prozentualen Anteil der ausgeworfenen Produkte haben. Zur Verfuegung habe ich in der Maschine eine 319 und 414, allerdings ohne SCL Paket bzw. Lizenz.



Mein Ansatz waere jetzt folgender gewesen:

Bit-Schieberegister von 1000 Bits mit dem FC92 erstellen und dort mithilfe des Maschientakts 0 fuer i.O. und 1 fuer "ausgeworfen" schieben.

Allerdings habe ich dann noch das Problem, wie ich die 1000 Bits denn auswerte, sprich wie ich die Anzahl der True bits auslesen kann. Mit BITSUMM bin ich ja auf ein Doppelwort begrenzt und SCL kann ich hier leider nicht nutzen.

Gibt es eine Art erweiterten BITSUMM Baustein an den ich meinen DB als ANY schreiben kann?

Oder denke ich hier viel zu kompliziert und es gibt eine ganz simple Loesung die ich einfach vor lauter Bäumen nicht sehe?
 
Auf das SchiebeRegister würde ich möglichst ganz verzichten (falls es nicht auch für andere Zwecke erforderlich ist) und an der Stelle, an der die Einsen bzw. Nullen ins SR eingetragen werden, diese direkt zählen. Das SchiebeRegister ist ein (vermutlich) unnötiger Umweg.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Auf das SchiebeRegister würde ich möglichst ganz verzichten (falls es nicht auch für andere Zwecke erforderlich ist) und an der Stelle, an der die Einsen bzw. Nullen ins SR eingetragen werden, diese direkt zählen. Das SchiebeRegister ist ein (vermutlich) unnötiger Umweg.
Du meinst es ist unnötig den ganzen Schieberegister jeden mal zu scannen. Für die "gleitender" zählen von defekte Teilen gibt es wohl kein anderen Weg als ein Schieberegister. Du willst bei den Laden und Entladen von das Schieberegister zählen wie viele Fehler eingegangen sind, wie viele ausgegangen sind, und den Differenz als den Anzahl von Fehler in Schieberegister ermitteln.

Das wäre einfacher als durch den Schiebregister jeden mal die Fehler zu zählen. Da ist aber ein Problem mit wie die Zähler initialisert werden. Wenn ein Zähler auf irgend Grund ein falschen Wert habe, wird es nie korrigiert.
 
Für die "gleitender" zählen von defekte Teilen gibt es wohl kein anderen Weg als ein Schieberegister.
Ich finde, da müsste man schon etwas genauer wissen, wie die Anforderungen sind. Bei 1900 Teilen pro Minute und einer SchiebeRegisterLänge von 1000 Bit ist die ZeitSpanne, in der man "gleiten" kann ohnehin nicht so ganz ausufernd. Das SR reicht mal gerade für eine gute halbe Minute und dann sind die Daten weg und es kann nicht mehr nachträglich noch ein sagen wir mal "kritischer" Bereich eingegrenzt und näher betrachtet werden.

PS:
Das Problem, die Zähler zu initialisieren sehe ich eigentlich nicht. Die ZeitPunkte, zu denen die Zähler ausgewertet - also die Differenzen der ZählerStände gebildet - werden, sind dann variabel, um das Gleiten zu realisieren.
Beim SchiebeRegister kann man das nachträglich festlegen und beim Zählen muss man sozusagen im Voraus für die auftretenden Eventualitäten vorsorgen.
Beim SchiebeRegister, wenn man eine "grobe Abweichung" festgestellt hat, genauer hinschauen, um den ProblemFall einzugrenzen.
Beim Zählen die (ggfs überlappenden) Intervalle von vorn herein klein halten, aber es bedarf dann keiner expliziten Suche nach Häufungen der Ereignisse.
 
Zuletzt bearbeitet:
Es gibt eigentlich keine richtige "Anforderung". Aktuell haben wir an anderen Stellen das gleich, nur dass wir dort Minütlich den Ein-/Auslauf Zähler der Maschine vergleichen. Das würde so auch reichen, das exaktere dauerhafte Abbilden ist rein persönliches Interesse um besser Programmieren zu lernen und weil ich die alte Variante "unschön" finde.

Aber ich bedanke mich hier schon mal für den Hinweis mit dem Zählen der Eingabe und Ausgabe des SR. Das sollte wesentlich praktikabler als mein Ursprungsgedanke sein.
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Ich würde hier auch einen Speicher nehmen - aber eher einen Ringspeicher.
Von der Programmier-Umsetzung wäre hier SCL aus meiner Sicht aber der bessere Ansatz ...
Der Ringspeicher hat die Größe der zu erfassenden Teile. Es gibt einen Zeiger, der festlegt, auf welche Position als nächstes geschrieben wird. Wurde geschrieben wird der Zeiger erhöht. Erreicht der Zeiger die maximale Array-Größe wird er wieder auf Anfang gesetzt.
Das sollte so von der Umsetzung her (Zykluszeitbedarf etc.) unkritisch sein. Selbst dann noch wenn du zyklisch den Speicher-Inhalt durchgehst um zu erfassen, wie die IO-NIO-Aufteilung ist ...

Gruß
Larry
 
... das exaktere dauerhafte Abbilden ist rein persönliches Interesse um besser Programmieren zu lernen ...
Das ist ja auch Grund genug, sich über andere Lösungswege Gedanken zu machen! ;)

Leider ist das Zählen der EinsBits in Bytes, Worten, DoppelWorten (u.s.w.) eine aufwändigere Angelegenheit, als man sich vorstellt, wenn man direkt eine Funktion BITSUM damit beauftragen kann und sich keine Gedanken darüber machen muss.

Für dieses Verfahren würde ich mit DoppelWorten arbeiten.
In einer Schleife das jeweils niederwertigste EinsBit löschen, so lange bis das DoppelWort 0 ist und die SchleifenDurchläufe zählen.
Dafür muss man nicht schieben und bei nur wenigen EinsBits im DoppelWort erspart man sich etliche SchleifenDurchläufe.
Die UND-Verküpfung des Inhalts mit dem ZweierKomplement des Inhalts liefert das niederwertigste EinsBit.
Die XOR-Verknüpfung des Inhalts mit dem zuvor ermittelten niederwertigsten EinsBit löscht es aus dem Inhalt.
Das lässt sich leicht in AWL umsetzen, da ja SCL anscheinend nicht zur Verfügung steht.

Ich würde hier auch einen Speicher nehmen - aber eher einen Ringspeicher.
Ein SchiebeRegister ist doch ein "RingSpeicher" - oder interpretiere ich das zu abenteuerlich? ;)
 
Zuletzt bearbeitet:
Ein SchiebeRegister ist doch ein "RingSpeicher" - oder interpretiere ich das zu abenteuerlich? ;)

Ich glaube schon, dass du verstanden hast, was ich meinte : NICHT die Bits durchschieben sondern den Schreibzeiger - das kostet Programm-technisch erheblich weniger Kraft ... 8)
Aber ein Schieberegister ist für mich auch kein Ringspeicher sondern eher eine Art Rohr - du schiebst "vorne" etwas rein und dafür fällt "hinten" etwas raus. Falls da jetzt jemand irgendwelche weiteren Analogien einfallen (im wahrsten Sinne des Wortes) - das ist durchaus vergleichbar ... ;)

Gruß
Larry
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Ein SchiebeRegister ist doch ein "RingSpeicher" - oder interpretiere ich das zu abenteuerlich? ;)
Heinrich, ich weiß daß Du den Unterschied kennst, ich erkläre es aber trotzdem:
Ein Schieberegister ist erst dann ein Ringspeicher, wenn man Anfang und Ende des Register/Speichers logisch "zusammenbiegt", so daß es einen Ring bildet, wo nach dem letzten Element das erste Element folgt: Element[Ende + 1] = Element[Anfang]
Bei einem Schieberegister wird beim Hinzufügen eines Wertes das gesamte Schieberegister um 1 Element verschoben und dann der neue Wert in das erste Element geschrieben.
Bei einem Ringspeicher wird nicht der Inhalt des Speichers verschoben, sondern lediglich ein Index-Zeiger auf das nächste Element weitergestellt und dort der neue Wert eingetragen (überschreibt dabei automatisch den ältesten Wert).

Harald
 
Moin,

in "AWL-Sprech" könnte man auch sagen:

- Schieberegister => Schiebebefehle: SSI/SSD/SLW/SRW/SLD/SRD
- Ringspeicher => Rotierbefehle: RLD/RRD/RLDA/RRDA

Vielleicht könnte man ja ein Schieberegister nehmen mit einer Bitanzahl von 1024. Zur Auswertung 32x Bitsum aufrufen und die Ergebnisse addieren.

VG

MFreiberger
 
Zuviel Werbung?
-> Hier kostenlos registrieren
AWL Quelle von Bitsumme für beliebige DB

Code:
 FUNCTION_BLOCK "Bitsumme"
{ S7_Optimized_Access := 'FALSE' }
VERSION : 0.1
   VAR_INPUT 
      DBNR : Int;
   END_VAR

   VAR_OUTPUT 
      Anzahl_Bits : DInt;
   END_VAR

   VAR_TEMP 
      DBNR_t : Int;
      Pointer_daten_bit_t : DWord;
      SchleifenZaehler_T : DInt;
      Bitzaehler_t : DInt;
   END_VAR


BEGIN
NETWORK
TITLE = 
      L #DBNR                     ;//Indirektes Öffenen des Datenbausteines über In Parameter
      T #DBNR_t;
      AUF DB[ #DBNR_t];
//******************************************************************************** ***********
      LAR1 P#0.0                     ;//Initialisierung des Datenbitpointers 
//******************************************************************************** ***********
      L DINT#0                       ;//Initialisierung des Bitzaehlers
      T #Bitzaehler_t;
//******************************************************************************** ***********
      L DBLG                      ;//auslesen der Datenbausteinlänge in Byte
      L DINT#8                       ;//multiplikationfaktor Byte nach Bit 
      *D;
next:      T #SchleifenZaehler_T       ;//Vorspannen des Schleifenzaehlers in DINT,da die Schleife grösser als 32767 sein Könnte
      CLR                             ;//Erzwingen VKE=0 
      U DBX[ AR1, P#0.0];
      SPBN m001                      ;//wenn Bit=0 Springe
      L DINT#1                       ;//Zaehlen wenn ein Bit=1
      L #Bitzaehler_t;
      +D;
      T #Bitzaehler_t;
m001:      +AR1 P#0.1                     ;//Pointererhöhung für nächsten Schleifendurchlau
      L #SchleifenZaehler_T       ;//dekrementierung SchleifenZaehler_T um 1
      L DINT#1;
      -D;
      L DINT#1;
      >=D;
      SPB nex2                      ;// Rücksprung in Schleife wenn noch nicht alle Bits des DB auf 1 Geprüft
      SPA m002;
nex2:      TAK;
      SPA next;
m002:      NOP 0;
//******************************************************************************** ***********
      L #Bitzaehler_t             ;// Übergabe der Temporär gezaehlten 1=Bit an Output
      T #Anzahl_Bits;


END_FUNCTION_BLOCK
 
Ich meine, wir hatten vor einigen Jahren mal das gleiche oder ein sehr ähnliches Thema gehabt. Vielleicht erinnert sich jemand genauer? Suchbegriff "Ausschussrate" oder so ähnlich.
 
Du willst bei den Laden und Entladen von das Schieberegister zählen wie viele Fehler eingegangen sind, wie viele ausgegangen sind, und den Differenz als den Anzahl von Fehler in Schieberegister ermitteln.

Das wäre einfacher als durch den Schiebregister jeden mal die Fehler zu zählen. Da ist aber ein Problem mit wie die Zähler initialisert werden. Wenn ein Zähler auf irgend Grund ein falschen Wert habe, wird es nie korrigiert.
Eigentlich muß man nicht bei jedem hinzukommenden Bit die 1000 Bits komplett durchzählen wieviele davon 1 sind, sondern es reicht, wenn man den Wert des herausfallenden Bits und den Wert des hinzukommenden Bits anschaut und entsprechend 1 abzieht bzw. 1 dazuzählt (Regel: der Zählerwert darf nicht kleiner 0 und nicht größer Registergröße werden). Initialisieren: man kann bei Neustart der CPU (oder der Anlage oder der Charge) das Register komplett löschen und den/die Zähler auf 0 setzen. Korrektur gegen (eigentlich unmöglichen) falschen Zählerwert: ab Neustart der CPU die hinzukommenden 1-Bits in einen zweiten "Schattenzähler" zählen, und nach jeweils 1000 hinzugekommenen Bits (Registergröße) den Schattenzähler-Wert in den gleitenden "offiziellen" Zähler übernehmen. Dann stimmt der Wert spätestens nach einmal vollfüllen des Registers. (hier im Beispiel: nach knapp 30 Sekunden)

Harald
 
Zuviel Werbung?
-> Hier kostenlos registrieren
AWL-Quelle für Programm entsprechend Beschreibung in Beitrag #14
Code:
FUNCTION_BLOCK "IONIO_Register"
TITLE =gleitender Zähler 1-Bits mit Ringpuffer-Register
AUTHOR : PN_DP
VERSION : 0.1

VAR_INPUT
  Add : BOOL ;   //bei TRUE Bit hinzufügen
  Bit : BOOL ;   //das hinzuzufügende Bit
  Reset : BOOL ; //Reset/Init bei TRUE
END_VAR
VAR_OUTPUT
  Anz0Bits : INT ; 
  Anz1Bits : INT ; 
END_VAR
VAR_IN_OUT
  cntAdds : INT ; 
END_VAR
VAR
  Size : INT  := 1000;              //Größe des Ringpuffer-Registers
  Buf : ARRAY  [0 .. 999] OF BOOL ; //Ringpuffer 
  BufIndex : INT ;                  //Zeiger auf ältesten Wert des Ringpuffers
  cnt1Bits : INT ;                  //Zähler Anzahl 1-Bits
  cnt1Bits2 : INT ;                 //Schattenzähler Anzahl 1-Bits
END_VAR
VAR_TEMP
  cnt : INT ;                       //Schleifenzähler
  BufAdr : DWORD ;                  //Anfangsadresse des Ringpuffers im IDB
END_VAR
BEGIN
NETWORK
TITLE =

//*** Reset?
      U     #Reset; 
      SPBN  MADD; 

//Ringpuffer-Register löschen
      TAR2  ;               //Multiinstanz-Offset aus AR2
      UD    DW#16#FFFFFF;   //Bereichskennung (DB) entfernen
      L     P##Buf;         //Adresse des Ringpuffers in dieser Instanz mit Bereichskennung (DI)
      +D    ; 
      LAR1  ;               //Adresse des Ringpuffers im IDB ( ADR(Buf[0]) ) in AR1 übernehmen

      CLR   ;               //VKE = 0
      L     #Size;          //Größe des Ringpuffer-Registers
MLOP: T     #cnt;           //Schleifenzähler (zählt rückwärts)
      =      [AR1,P#0.0];   //VKE=0 in Ringpuffer speichern

      +AR1  P#0.1;          //Zeiger in Ringpuffer auf nächstes Element weiterstellen
      L     #cnt;           //Schleifenzähler (zählt rückwärts)
      LOOP  MLOP; 

//Zähler löschen
      L     0; 
      T     #cnt1Bits; 
      T     #cnt1Bits2; 
      T     #cntAdds; 
      T     #BufIndex; 

//*** Ein Bit hinzufügen?
MADD: U     #Add;           //Bit hinzufügen?
      SPBN  MOUT;           //nichts hinzuzufügen, nur Ausgaben aktualisieren

//*** herausfallendes Bit verrechnen
      TAR2  ;               //Multiinstanz-Offset aus AR2
      UD    DW#16#FFFFFF;   //Bereichskennung (DB) entfernen
      L     P##Buf;         //Adresse des Ringpuffers in dieser Instanz mit Bereichskennung (DI)
      +D    ; 
      T     #BufAdr;        //Adresse des Ringpuffers im IDB merken

      L     #BufIndex;      //zeigt auf ältesten Wert des Ringpuffers
      L     #Size; 
      MOD   ;               //BufIndex auf 0..Size-1 begrenzen
      L     #BufAdr;        //Ringpuffer Anfangsadresse dazu
      +D    ; 
      LAR1  ;               //in AR1 ist nun die Adresse von Buf[BufIndex]

      L     #BufIndex; 
      +     1;              //auf nächstes Element im Ringpuffer weiterstellen
      L     #Size; 
      MOD   ;               //BufIndex auf 0..Size-1 begrenzen
      T     #BufIndex;      //und merken

      L     #cnt1Bits;      //Zähler 1-Bits
      U      [AR1,P#0.0];   //herausfallendes Bit
      SPBN  MCHK;           //bei 0-Bit Zähler nur prüfen/korrigieren
      +     -1;             //bei 1-Bit Zähler - 1
MCHK: L     0; 
      <I    ; 
      SPB   MT1A;           //Zählerstand ist < 0 ! --> auf 0 setzen
      POP   ;               //Zählerstand ok, zurück in AKKU1
MT1A: T     #cnt1Bits;      //(neuen) Zählerstand merken

//*** hinzukommendes Bit in Ringpuffer-Register zufügen und verrechnen
      U     #Bit;           //neues Bit
      =      [AR1,P#0.0];   //in Ringpuffer speichern
      SPBN  MADS;           //bei 0-Bit das Bit nicht als 1-Bit zählen

//hinzukommendes 1-Bit zählen
      L     #cnt1Bits;      //gleitender Zähler 1-Bits
      +     1;              //um 1 erhöhen
      L     #Size;          //Max möglicher Wert ist Ringpuffer-Größe
      >I    ; 
      SPB   MT1B;           //Zählerstand ist > Ringpuffer-Größe ! --> auf Maxwert setzen
      POP   ;               //Zählerstand ok, zurück in AKKU1
MT1B: T     #cnt1Bits;      //(neuen) Zählerstand merken

      L     #cnt1Bits2;     //Schattenzähler 1-Bits
      +     1;              //um 1 erhöhen
      L     #Size;          //Max möglicher Wert ist Ringpuffer-Größe
      >I    ; 
      SPB   MT1S;           //Zählerstand ist > Ringpuffer-Größe ! --> auf Maxwert setzen
      POP   ;               //Zählerstand ok, zurück in AKKU1
MT1S: T     #cnt1Bits2;     //(neuen) Zählerstand merken

//Anzahl hinzugefügte Bits für ggf. Korrektur, falls Zähler 1-Bits falsch war
//(Nicht #Index verwenden weil remanent. Der Zählerstand #cntAdds sollte nicht remanent sein.)
MADS: L     #Size;          //Größe des Ringpuffers
      L     #cntAdds;       //Zähler Anzahl Hinzufügungen
      +     1; 
      <=I   ;               //hat Größe des Ringpuffers erreicht (also einmal vollgefüllt)?
      SPBN  MTAD;           //nein, nur Zählerstand merken

      L     #cnt1Bits2;     //ja, Schattenzähler 1-Bits
      T     #cnt1Bits;      //zu gleitendem Zähler 1-Bits übernehmen

      L     0;              //und Schattenzähler und Hinzufüge-Zähler wieder auf 0
      T     #cnt1Bits2; 
MTAD: T     #cntAdds; 

//*** Wert Bit-Zähler ausgeben
MOUT: L     #Size;          //Größe des Ringpuffers
      L     #cnt1Bits;      //Anzahl 1-Bits
      T     #Anz1Bits; 
      -I    ;               //der Rest (Size - Anz1Bits) ist die Anzahl 0-Bits
      T     #Anz0Bits; 

MEND: SET   ; 
      SAVE  ;               //ENO setzen

END_FUNCTION_BLOCK
 
Vielen Dank an alle, für die vielen Gedanken, Erklärungen und sogar Bausteine

Ich wäre selbst nicht im Stande gewesen das so zu Programmieren, meine AWL Kenntnisse sind einfach zu schlecht.

Den Baustein habe ich gestern an einem Testrack ausprobieren können und er funktioniert exact wie beschrieben.

Aber mir ist bewusst geworden wie wenig ich über AWL weiß und womit ich mich die nächste Zeit mehr beschäftigen werde.


Mal schauen wann der Tag kommt, an dem ich hier jemandem bei einem Problem behilflich sein kann.
 
Zurück
Oben