das hat der Kollege mir noch zum FiFo geschickt.
(* Network 0 *)
(*Sample clock: run TON T0 continuously while enabled (%I0.0). Timer resets automatically when done bit goes TRUE because ANDN %T0 breaks the enable. Preset in %MW0 (units of 100ms), e.g. %MW0=10 gives 1s sample interval.*)
LD %I0.0
ANDN %T0
TON T0, %MW0
(* Network 1 *)
(*Detect rising edge of timer done bit and store pulse in %M0.0 for use in subsequent networks.*)
LD %T0
R_TRIG
ST %M0.0
(* Network 2 *)
(*On each sample pulse: write the current sensor value (%MW2) into the ring buffer slot selected by the write pointer (%MW1). Slot 0 = %MW100.*)
LD %M0.0
EQ %MW1, 0
JMPCN lbl_chk1
MOVE %MW2, %MW100
JMP lbl_write_done
(* Network 3 *)
(*Write pointer index check for slot 1.*)
lbl_chk1:
EQ %MW1, 1
JMPCN lbl_chk2
MOVE %MW2, %MW101
JMP lbl_write_done
(* Network 4 *)
(*Write pointer index check for slot 2.*)
lbl_chk2:
EQ %MW1, 2
JMPCN lbl_chk3
MOVE %MW2, %MW102
JMP lbl_write_done
(* Network 5 *)
(*Write pointer index check for slot 3.*)
lbl_chk3:
EQ %MW1, 3
JMPCN lbl_chk4
MOVE %MW2, %MW103
JMP lbl_write_done
(* Network 6 *)
(*Write pointer index check for slot 4.*)
lbl_chk4:
EQ %MW1, 4
JMPCN lbl_chk5
MOVE %MW2, %MW104
JMP lbl_write_done
(* Network 7 *)
(*Write pointer index check for slot 5.*)
lbl_chk5:
EQ %MW1, 5
JMPCN lbl_chk6
MOVE %MW2, %MW105
JMP lbl_write_done
(* Network 8 *)
(*Write pointer index check for slot 6.*)
lbl_chk6:
EQ %MW1, 6
JMPCN lbl_chk7
MOVE %MW2, %MW106
JMP lbl_write_done
(* Network 9 *)
(*Write pointer index check for slot 7.*)
lbl_chk7:
EQ %MW1, 7
JMPCN lbl_chk8
MOVE %MW2, %MW107
JMP lbl_write_done
(* Network 10 *)
(*Write pointer index check for slot 8.*)
lbl_chk8:
EQ %MW1, 8
JMPCN lbl_chk9
MOVE %MW2, %MW108
JMP lbl_write_done
(* Network 11 *)
(*Write pointer index check for slot 9.*)
lbl_chk9:
EQ %MW1, 9
JMPCN lbl_chk10
MOVE %MW2, %MW109
JMP lbl_write_done
(* Network 12 *)
(*Write pointer index check for slot 10.*)
lbl_chk10:
EQ %MW1, 10
JMPCN lbl_chk11
MOVE %MW2, %MW110
JMP lbl_write_done
(* Network 13 *)
(*Write pointer index check for slot 11.*)
lbl_chk11:
EQ %MW1, 11
JMPCN lbl_chk12
MOVE %MW2, %MW111
JMP lbl_write_done
(* Network 14 *)
(*Write pointer index check for slot 12.*)
lbl_chk12:
EQ %MW1, 12
JMPCN lbl_chk13
MOVE %MW2, %MW112
JMP lbl_write_done
(* Network 15 *)
(*Write pointer index check for slot 13.*)
lbl_chk13:
EQ %MW1, 13
JMPCN lbl_chk14
MOVE %MW2, %MW113
JMP lbl_write_done
(* Network 16 *)
(*Write pointer index check for slot 14.*)
lbl_chk14:
EQ %MW1, 14
JMPCN lbl_chk15
MOVE %MW2, %MW114
JMP lbl_write_done
(* Network 17 *)
(*Write pointer index check for slot 15 (last slot).*)
lbl_chk15:
MOVE %MW2, %MW115
(* Network 18 *)
(*Increment write pointer and wrap around from 15 back to 0 to keep it in range 0-15.*)
lbl_write_done:
LD %M0.0
ST %M0.1
(* Network 19 *)
(*Increment the write pointer by 1 on each sample pulse.*)
LD %M0.1
INC %MW1
(* Network 20 *)
(*If write pointer has reached 16, reset it to 0 to complete the ring.*)
GE %MW1, 16
JMPCN lbl_sum
MOVE 0, %MW1
(* Network 21 *)
(*Accumulate sum of all 16 ring buffer slots into %MW200. Clear sum first, then add each slot. This runs every scan cycle so the average output is always up to date.*)
lbl_sum:
MOVE 0, %MW200
ADD %MW100, %MW200
ADD %MW101, %MW200
ADD %MW102, %MW200
ADD %MW103, %MW200
ADD %MW104, %MW200
ADD %MW105, %MW200
ADD %MW106, %MW200
ADD %MW107, %MW200
ADD %MW108, %MW200
ADD %MW109, %MW200
ADD %MW110, %MW200
ADD %MW111, %MW200
ADD %MW112, %MW200
ADD %MW113, %MW200
ADD %MW114, %MW200
ADD %MW115, %MW200
(* Network 22 *)
(*Calculate average by shifting sum right by 4 bits (equivalent to dividing by 16). Result stored in %MW202.*)
MOVE %MW200, %MW202
SHR 4, %MW202
Dualzonen PID... nur als Referenz für euch, leider habe ich den Prompt nicht :/
(* Network 0 *)
(*Initialisierung: %SM0.1 ist nur im ersten SPS-Zyklus aktiv. Ergebnis in %M1.0 speichern fuer nachfolgende Init-Netzwerke.*)
LD %SM0.1
ST %M1.0
(* Network 1 *)
(*Erster Zyklus: PWM-Periodendauer auf 100 Ticks (= 10 Sekunden bei 100ms Taktaufloesung) setzen.*)
(*%MW0 kann jederzeit vom Bediener ueberschrieben werden.*)
LD %M1.0
MOVE 100, %MW0
(* Network 2 *)
(*Erster Zyklus: Sollwert Zone 1 auf 2000 (= 200.0 Grad, Darstellung: Grad * 10) setzen.*)
LD %M1.0
MOVE 2000, %MW10
(* Network 3 *)
(*Erster Zyklus: Sollwert Zone 2 auf 2200 (= 220.0 Grad) setzen.*)
LD %M1.0
MOVE 2200, %MW11
(* Network 4 *)
(*Erster Zyklus: PID-Ausgangswert Zone 1 initialisieren.*)
LD %M1.0
MOVE 0, %MW20
(* Network 5 *)
(*Erster Zyklus: PID-Ausgangswert Zone 2 initialisieren.*)
LD %M1.0
MOVE 0, %MW21
(* Network 6 *)
(*Erster Zyklus: PWM-Zykluszaehler auf 0 setzen.*)
LD %M1.0
MOVE 0, %MW40
(* Network 7 *)
(*Erster Zyklus: PID-Parameterblock Zone 1 initialisieren (Beginn bei %VD300).*)
(*%VD300 = Kp (Proportionalbeiwert), %VD304 = Ti (Nachstellzeit in Sekunden)*)
(*%VD308 = Td (Vorhaltezeit in Sekunden), %VD312 = OUT_MIN, %VD316 = OUT_MAX*)
LD %M1.0
MOVE 2.0, %VD300
(* Network 8 *)
(*Erster Zyklus: Nachstellzeit Zone 1 Ti = 120 Sekunden (traege Ofenstrecke).*)
LD %M1.0
MOVE 120.0, %VD304
(* Network 9 *)
(*Erster Zyklus: Vorhaltezeit Zone 1 Td = 0 (kein D-Anteil bei Inbetriebnahme).*)
LD %M1.0
MOVE 0.0, %VD308
(* Network 10 *)
(*Erster Zyklus: Untere Ausgangsbegrenzung Zone 1 = 0.0 %.*)
LD %M1.0
MOVE 0.0, %VD312
(* Network 11 *)
(*Erster Zyklus: Obere Ausgangsbegrenzung Zone 1 = 100.0 %.*)
LD %M1.0
MOVE 100.0, %VD316
(* Network 12 *)
(*Erster Zyklus: PID-Parameterblock Zone 2 initialisieren (Beginn bei %VD500).*)
(*%VD500 = Kp, %VD504 = Ti, %VD508 = Td, %VD512 = OUT_MIN, %VD516 = OUT_MAX*)
LD %M1.0
MOVE 2.0, %VD500
(* Network 13 *)
(*Erster Zyklus: Nachstellzeit Zone 2 Ti = 120 Sekunden.*)
LD %M1.0
MOVE 120.0, %VD504
(* Network 14 *)
(*Erster Zyklus: Vorhaltezeit Zone 2 Td = 0.*)
LD %M1.0
MOVE 0.0, %VD508
(* Network 15 *)
(*Erster Zyklus: Untere Ausgangsbegrenzung Zone 2 = 0.0 %.*)
LD %M1.0
MOVE 0.0, %VD512
(* Network 16 *)
(*Erster Zyklus: Obere Ausgangsbegrenzung Zone 2 = 100.0 %.*)
LD %M1.0
MOVE 100.0, %VD516
(* Network 17 *)
(*100ms-Basistakt: TON T0 mit Preset 1 (= 100ms) laeuft kontinuierlich.*)
(*ANDN %T0 sorgt fuer automatischen Selbst-Reset sobald der Timer abgelaufen ist.*)
(*Jede 100ms erzeugt T0 einen Impuls -> Grundtakt fuer den PWM-Zykluszaehler.*)
LD %SM0.0
ANDN %T0
TON T0, 1
(* Network 18 *)
(*Steigende Flanke des Timer-Done-Bits detektieren und als 1-Zyklus-Impuls in %M0.0 ablegen.*)
LD %T0
R_TRIG
ST %M0.0
(* Network 19 *)
(*Takt-Impuls empfangen: PWM-Zykluszaehler %MW40 um 1 hochzaehlen.*)
(*Der vorhergehende LD %M0.0 im CR steuert die bedingte Ausfuehrung von INC.*)
LD %M0.0
INC %MW40
(* Network 20 *)
(*Periodenende pruefen: wenn %MW40 >= %MW0, ist ein vollstaendiger PWM-Zyklus abgelaufen.*)
(*In diesem Fall Zaehler auf 0 setzen (neuer Zyklus). Sonst direkt zu lbl_zaehler_ok springen.*)
(*Beispiel: %MW0=100 Ticks -> Periode = 100 * 100ms = 10 Sekunden.*)
GE %MW40, %MW0
JMPCN lbl_zaehler_ok
MOVE 0, %MW40
(* Network 21 *)
(*Zykluszaehler in Ordnung oder gerade zurueckgesetzt. Ab hier: Analoge Istwertverarbeitung.*)
(*Zone 1: Rohwert %AIW0 (0-27648 entspricht 0.0-300.0 Grad Celsius) in Real-Zahl wandeln.*)
(*Schritt 1: 16-Bit-Integer nach 32-Bit-Integer erweitern fuer nachfolgende Real-Konvertierung.*)
lbl_zaehler_ok:
I_TO_DI %AIW0, %VD200
(* Network 22 *)
(*Zone 1: 32-Bit-Integer in IEEE-754-Real-Zahl umwandeln.*)
DI_TO_R %VD200, %VD200
(* Network 23 *)
(*Zone 1: Skalierung auf Grad Celsius: Rohwert * (300.0 / 27648.0) = Rohwert * 0.010851.*)
(*Skalierungskonstante einmalig in %VD600 ablegen (wird auch von Zone 2 genutzt).*)
(*%VD200 enthaelt danach den Istwert in Grad Celsius als Real-Zahl.*)
MOVE 0.010851, %VD600
MUL %VD600, %VD200
(* Network 24 *)
(*Zone 1: Sollwert %MW10 (Integer, Grad * 10) in 32-Bit-Integer erweitern.*)
I_TO_DI %MW10, %VD204
(* Network 25 *)
(*Zone 1: Sollwert 32-Bit-Integer in Real-Zahl konvertieren.*)
DI_TO_R %VD204, %VD204
(* Network 26 *)
(*Zone 1: Sollwert durch 10.0 dividieren -> Grad Celsius als Real (z.B. 2000 -> 200.0 Grad).*)
(*Divisionsfaktor 0.1 einmalig in %VD604 fuer Zone 1 und Zone 2 verwenden.*)
MOVE 0.1, %VD604
MUL %VD604, %VD204
(* Network 27 *)
(*Zone 1: PID-Regler berechnen. Freigabe ueber %I0.0 (Haupt-Freigabe).*)
(*PV = %VD200 (Istwert in Grad), SP = %VD204 (Sollwert in Grad)*)
(*OUT = %VD208 (Ausgang 0.0-100.0 %), PARAM = %VD300 (Parameterblock Zone 1)*)
LD %I0.0
PID %VD200, %VD204, %VD208, %VD300
(* Network 28 *)
(*Zone 1: PID-Ausgang (Real 0.0-100.0) in Promille-Wert 0-1000 umrechnen und in %MW20 speichern.*)
(*Formel: %MW20 = INT(%VD208 * 10.0). Arbeitsvariable %VD220 als Zwischenspeicher.*)
MOVE %VD208, %VD220
MUL 10.0, %VD220
R_TO_DI %VD220, %VD220
DI_TO_I %VD220, %MW20
(* Network 29 *)
(*Zone 2: Rohwert %AIW2 (0-27648 entspricht 0.0-300.0 Grad) in 32-Bit-Integer erweitern.*)
I_TO_DI %AIW2, %VD400
(* Network 30 *)
(*Zone 2: 32-Bit-Integer in Real-Zahl umwandeln.*)
DI_TO_R %VD400, %VD400
(* Network 31 *)
(*Zone 2: Skalierung auf Grad Celsius mit demselben Faktor wie Zone 1 (%VD600 = 0.010851).*)
MUL %VD600, %VD400
(* Network 32 *)
(*Zone 2: Sollwert %MW11 (Integer, Grad * 10) in 32-Bit-Integer erweitern.*)
I_TO_DI %MW11, %VD404
(* Network 33 *)
(*Zone 2: Sollwert 32-Bit-Integer in Real-Zahl konvertieren.*)
DI_TO_R %VD404, %VD404
(* Network 34 *)
(*Zone 2: Sollwert durch 10.0 dividieren -> Grad Celsius als Real.*)
MUL %VD604, %VD404
(* Network 35 *)
(*Zone 2: PID-Regler berechnen. Freigabe ueber %I0.0.*)
(*PV = %VD400 (Istwert in Grad), SP = %VD404 (Sollwert in Grad)*)
(*OUT = %VD408 (Ausgang 0.0-100.0 %), PARAM = %VD500 (Parameterblock Zone 2)*)
LD %I0.0
PID %VD400, %VD404, %VD408, %VD500
(* Network 36 *)
(*Zone 2: PID-Ausgang (Real 0.0-100.0) in Promille-Wert 0-1000 umrechnen und in %MW21 speichern.*)
MOVE %VD408, %VD420
MUL 10.0, %VD420
R_TO_DI %VD420, %VD420
DI_TO_I %VD420, %MW21
(* Network 37 *)
(*Einschaltdauer Zone 1 berechnen: %MW30 = %MW20 * %MW0 / 1000 (Ergebnis in Ticks).*)
(*32-Bit-Arithmetik noetig: %MW20 * %MW0 kann bis 1000 * 100 = 100.000 > 32767 werden.*)
(*Beispiel Einstellung: %MW20=350 (35.0%), %MW0=100 Ticks -> %MW30 = 350*100/1000 = 35 Ticks.*)
(*MUL-Semantik: dst = dst * src -> VD100 = VD100 * VD104 = PID-Ausgang * Periodenticks.*)
I_TO_DI %MW20, %VD100
I_TO_DI %MW0, %VD104
MUL %VD104, %VD100
DIV 1000, %VD100
DI_TO_I %VD100, %MW30
(* Network 38 *)
(*Einschaltdauer Zone 2 berechnen: %MW31 = %MW21 * %MW0 / 1000 (Ergebnis in Ticks).*)
(*%VD104 enthaelt bereits die Periodenticks aus Netzwerk 37 und wird wiederverwendet.*)
(*Beispiel: %MW21=550 (55.0%), %MW0=100 Ticks -> %MW31 = 550*100/1000 = 55 Ticks.*)
I_TO_DI %MW21, %VD108
MUL %VD104, %VD108
DIV 1000, %VD108
DI_TO_I %VD108, %MW31
(* Network 39 *)
(*Versatz Zone 2 berechnen: Zone 2 startet genau dann, wenn Zone 1 abschaltet.*)
(*%MW32 = %MW30 -> Zone 2-Einschaltung beginnt bei Tick %MW30 im PWM-Zyklus.*)
(*Kernprinzip Staggered PWM: bei 35% + 55% = 90% Gesamtlast kein Ueberlapp moeglich.*)
MOVE %MW30, %MW32
(* Network 40 *)
(*Endtick Zone 2 berechnen: %MW33 = %MW32 + %MW31.*)
(*Zone 2 ist eingeschaltet von Tick %MW32 bis Tick (%MW33 - 1).*)
(*Beispiel: %MW32=35, %MW31=55 -> %MW33=90. Heizfenster Zone 2: Tick 35..89.*)
MOVE %MW32, %MW33
ADD %MW31, %MW33
(* Network 41 *)
(*Heizausgang Zone 1: %Q0.0 EIN wenn aktueller Tick < Einschaltdauer Zone 1.*)
(*Zone 1 heizt von Tick 0 bis Tick (%MW30 - 1) = Anfang des Zyklus.*)
(*Vergleich LT liefert 1 in CR wenn %MW40 < %MW30 -> direkt in %Q0.0 speichern.*)
LT %MW40, %MW30
ST %Q0.0
(* Network 42 *)
(*Heizausgang Zone 2, Bedingung 1: aktueller Tick >= Startversatz Zone 2.*)
(*Ergebnis als Zwischenbit %M0.3 sichern, da kein direkter AND zweier Vergleiche moeglich.*)
GE %MW40, %MW32
ST %M0.3
(* Network 43 *)
(*Heizausgang Zone 2, Bedingung 2: aktueller Tick < Endtick Zone 2.*)
LT %MW40, %MW33
ST %M0.4
(* Network 44 *)
(*Heizausgang Zone 2: %Q0.1 EIN wenn beide Bedingungen erfuellt (Tick im Fenster Zone 2).*)
(*Zone 2 heizt von Tick %MW32 bis Tick (%MW33 - 1) -> nahtlos nach Zone 1, kein Ueberlapp.*)
LD %M0.3
AND %M0.4
ST %Q0.1
(* Network 45 *)
(*Sicherheitsabschaltung Zone 1: wenn Freigabe %I0.0 nicht aktiv, Heizausgang sofort sperren.*)
LDN %I0.0
R %Q0.0
(* Network 46 *)
(*Sicherheitsabschaltung Zone 2: wenn Freigabe %I0.0 nicht aktiv, Heizausgang sofort sperren.*)
LDN %I0.0
R %Q0.1