TIA Wochenschaltuhr.

Aksels

Level-2
Beiträge
257
Reaktionspunkte
9
Zuviel Werbung?
-> Hier kostenlos registrieren
Liebe Mitleser,
ich habe ein Problem mit meiner Wochenschaltuhr. Die SPS geht immer mal wieder in Stop. Klar liegt das an Bereichslängenfehlern. :???:
Und ich weiss, dass diese Art zu programmieren fehleranfällig ist. ;)
Aber vielleicht findet jemand das Rohprogramm doch nützlich und schaut es sich kurz an und sagt: ja klar, Du hast da was vergessen.:cool:
Code:
FUNCTION_BLOCK "FB_Schaltuhren_SCL_1x00"
{ S7_Optimized_Access := 'FALSE' }
VERSION : 0.1
   VAR_INPUT 
      i_DaT_Zerl {InstructionName := 'DTL'; LibVersion := '1.0'} : DTL;
   END_VAR

   VAR 
      s_UhrenDaten : Array[1.."c_Anz_Schaltuhren"] of "DT_Schaltuhr_1x00" := [((), (), (), (), (), (), (), (), (), (), (), (), (), (), 1, (), (), ()), ((), (), (), (), (), (), (), (), (), (), (), (), (), (), 1, (), (), ()), ((), (), (), (), (), (), (), (), (), (), (), (), (), (), 1, (), (), ()), ((), (), (), (), (), (), (), (), (), (), (), (), (), (), 1, (), (), ()), ((), (), (), (), (), (), (), (), (), (), (), (), (), (), 1, (), (), ()), ((), (), (), (), (), (), (), (), (), (), (), (), (), (), 1, (), (), ()), ((), (), (), (), (), (), (), (), (), (), (), (), (), (), 1, (), (), ()), ((), (), (), (), (), (), (), (), (), (), (), (), (), (), 1, (), (), ()), ((), (), (), (), (), (), (), (), (), (), (), (), (), (), 1, (), (), ()), ((), (), (), (), (), (), (), (), (), (), (), (), (), (), 1, (), (), ())];
      S_Index_Akt : Int := 1;
      S_Index_Alt : Int := 1;
      s_Aktiv : "DT_Schaltuhr_1x00";
      s_AnzeigeUebernehmen : Bool;
      s_AnzeigeGeaendert : Bool;
   END_VAR

   VAR_TEMP 
      t_a : SInt;
      t_Von_Sek : UDInt;
      t_Bis_Sek : UDInt;
      t_Akt_Sek : UDInt;
      t_WoTa_OK : Bool;
      t_Von_OK : Bool;
      T_Bis_OK : Bool;
      t_Gruppen : Array[1.."c_Anz_Schaltuhren"] of Bool;
   END_VAR


BEGIN
    #t_Akt_Sek :=  USINT_TO_UDINT(#i_DaT_Zerl.SECOND) + USINT_TO_UDINT(#i_DaT_Zerl.MINUTE) * 60 + USINT_TO_UDINT(#i_DaT_Zerl.HOUR) * 60 * 60;
    //Kontrolle Index
    IF #S_Index_Akt <= 0 THEN
        #S_Index_Akt := "c_Anz_Schaltuhren";
    END_IF;
    IF #S_Index_Akt > "c_Anz_Schaltuhren" THEN
        #S_Index_Akt := 1;
    END_IF;
    //Genauso viele Schaltuhren es gibt, so viel Gruppen kann man anlegen. Wenn eine Schaltuhr true ausgibt wird der zugehörige Gruppenausgang auch true.
    //Hier werden die Temporärdaten initialisiert. Und geprüft ob ungültige Werte in den Gruppen stehen. Wichtig, sonst geht SPS in Stop.
    FOR #t_a := 1 TO "c_Anz_Schaltuhren" DO
        #t_Gruppen[#t_a] := false;
        IF #s_UhrenDaten[#t_a].i_Gruppe_Nr <= 0 THEN
            #s_UhrenDaten[#t_a].i_Gruppe_Nr := 1;
        END_IF;
        IF #s_UhrenDaten[#t_a].i_Gruppe_Nr > "c_Anz_Schaltuhren" THEN
            #s_UhrenDaten[#t_a].i_Gruppe_Nr :="c_Anz_Schaltuhren";
        END_IF;
        
    END_FOR;
    //Schaltuhren bearbeiten.
    FOR #t_a := 1 TO "c_Anz_Schaltuhren" DO
       
        CASE #i_DaT_Zerl.WEEKDAY OF
            1:  // Sonntag
                #t_WoTa_OK:=#s_UhrenDaten[#t_a].I_So;
            2:  // Montag
                #t_WoTa_OK:=#s_UhrenDaten[#t_a].I_Mo;
            3: //Dienstag
                #t_WoTa_OK:=#s_UhrenDaten[#t_a].I_Di;
            4: //Mittwoch
                #t_WoTa_OK:=#s_UhrenDaten[#t_a].I_Mi;
            5: //Donnerstag
                #t_WoTa_OK:=#s_UhrenDaten[#t_a].I_Do;
            6: //Freitag
                #t_WoTa_OK:=#s_UhrenDaten[#t_a].I_Fr;
            7: //Samstag
                #t_WoTa_OK:=#s_UhrenDaten[#t_a].I_Sa;
            ELSE  // Darf nicht vorkommen, sonst Daten korrupt.
                ;
        END_CASE;
        // In Sekunden rechnet es sich einfacher. 
        #t_Von_Sek:=USINT_TO_UDINT(#s_UhrenDaten[#t_a].I_UZ_Von_h) * 60 * 60 + USINT_TO_UDINT(#s_UhrenDaten[#t_a].I_UZ_Von_m) * 60 + USINT_TO_UDINT(#s_UhrenDaten[#t_a].I_UZ_Von_s);
        #t_Bis_Sek := USINT_TO_UDINT(#s_UhrenDaten[#t_a].I_UZ_Bis_h) * 60 * 60 + USINT_TO_UDINT(#s_UhrenDaten[#t_a].I_UZ_Bis_m) * 60 + USINT_TO_UDINT(#s_UhrenDaten[#t_a].I_UZ_Bis_s);
        //Ist Aus kleiner ein geht die Schaltzeit über 0:00 hinüber.
        IF #t_Von_Sek > #t_Bis_Sek THEN 
            //Von > Bis
            //AKtuelle Zeit zwischen Von und 23:59:00
            IF #t_Akt_Sek >= #t_Von_Sek AND #t_Akt_Sek <= 86399  THEN
                #t_Von_OK := true;
            ELSE
                #t_Von_OK := false;
            END_IF;
            //Aktuelle Zeit zwischen 0:00:00 und Bis
            IF #t_Akt_Sek < #t_Bis_Sek AND #t_Akt_Sek>=0 THEN
                #T_Bis_OK := true;
            ELSE
                #T_Bis_OK := false;
            END_IF;
        ELSE
            //Von < Bis
            IF #t_Akt_Sek >= #t_Von_Sek  THEN
                #t_Von_OK:=true;
            ELSE
                #t_Von_OK := false;
            END_IF;
            IF #t_Akt_Sek < #t_Bis_Sek THEN
                #T_Bis_OK := true;
            ELSE
                #T_Bis_OK := false;
            END_IF;
        END_IF;
        #s_UhrenDaten[#t_a].O_Ein := #T_Bis_OK AND #t_Von_OK AND #t_WoTa_OK AND #s_UhrenDaten[#t_a].I_Aktiv;
        IF #s_UhrenDaten[#t_a].O_Ein THEN
            //Temporärdaten für Gruppe aktualisieren. Heikel. Hier darf nie eine falsche Gruppe drin stehen, sonst geht SPS in Stop. Wird deswegen ganz oben abgeprüft.
            #t_Gruppen[#s_UhrenDaten[#t_a].i_Gruppe_Nr] := true;
        END_IF;
    END_FOR;
    //Richtige Gruppen mit Temporärdaten befüttern.
    FOR #t_a := 1 TO "c_Anz_Schaltuhren" DO
        #s_UhrenDaten[#t_a].O_Gruppe_Ein:= #t_Gruppen[#t_a] ;
    END_FOR;
    
    //Ab hier Anzeige aufbereiten.
    //Wurde der Index geändert? Dann Anzeigedaten aufbereiten
    IF #S_Index_Akt = #S_Index_Alt THEN
        #s_Aktiv.O_Ein := #s_UhrenDaten[#S_Index_Akt].O_Ein;
    ELSE
        #S_Index_Alt := #S_Index_Akt;
        #s_Aktiv := #s_UhrenDaten[#S_Index_Akt];
        
    END_IF;
    //Wenn auf Speichern geklickt wurde Anezigedaten in Datenbestand übernehmen.
    IF #s_AnzeigeUebernehmen THEN
        #s_AnzeigeUebernehmen := false;
        #s_UhrenDaten[#S_Index_Akt] := #s_Aktiv;
    ELSE
        ;
    END_IF;
    //Auswerten, ob Angezeigte Daten geändert wurden. Dann kann man den Speichernbutton einblenden und blinken lassen.
    //Gefällt mir noch nicht. Muss man doch eleganter lösen können (Bei änderung eines Feldes im HMI)?
    #s_AnzeigeGeaendert := false;
    
    IF #s_Aktiv.I_Name = #s_UhrenDaten[#S_Index_Akt].I_Name THEN
        ;
    ELSE
        #s_AnzeigeGeaendert := true;
    END_IF;
    IF #s_Aktiv.I_Aktiv = #s_UhrenDaten[#S_Index_Akt].I_Aktiv THEN
        ;
    ELSE
        #s_AnzeigeGeaendert := true;
    END_IF;
    IF #s_Aktiv.I_Mo = #s_UhrenDaten[#S_Index_Akt].I_Mo THEN
        ;
    ELSE
        #s_AnzeigeGeaendert := true;
    END_IF;
    IF #s_Aktiv.I_Di = #s_UhrenDaten[#S_Index_Akt].I_Di THEN
        ;
    ELSE
        #s_AnzeigeGeaendert := true;
    END_IF;
    IF #s_Aktiv.I_Mi = #s_UhrenDaten[#S_Index_Akt].I_Mi THEN
        ;
    ELSE
        #s_AnzeigeGeaendert := true;
    END_IF;
    IF #s_Aktiv.I_Do = #s_UhrenDaten[#S_Index_Akt].I_Do THEN
        ;
    ELSE
        #s_AnzeigeGeaendert := true;
    END_IF;
    IF #s_Aktiv.I_Fr = #s_UhrenDaten[#S_Index_Akt].I_Fr THEN
        ;
    ELSE
        #s_AnzeigeGeaendert := true;
    END_IF;
    IF #s_Aktiv.I_Sa = #s_UhrenDaten[#S_Index_Akt].I_Sa THEN
        ;
    ELSE
        #s_AnzeigeGeaendert := true;
    END_IF;
    IF #s_Aktiv.I_So = #s_UhrenDaten[#S_Index_Akt].I_So THEN
        ;
    ELSE
        #s_AnzeigeGeaendert := true;
    END_IF;
    IF #s_Aktiv.I_UZ_Von_h = #s_UhrenDaten[#S_Index_Akt].I_UZ_Von_h THEN
        ;
    ELSE
        #s_AnzeigeGeaendert := true;
    END_IF;
    IF #s_Aktiv.I_UZ_Bis_h = #s_UhrenDaten[#S_Index_Akt].I_UZ_Bis_h THEN
        ;
    ELSE
        #s_AnzeigeGeaendert := true;
    END_IF;
    IF #s_Aktiv.I_UZ_Von_m = #s_UhrenDaten[#S_Index_Akt].I_UZ_Von_m THEN
        ;
    ELSE
        #s_AnzeigeGeaendert := true;
    END_IF;
    IF #s_Aktiv.I_UZ_Bis_m = #s_UhrenDaten[#S_Index_Akt].I_UZ_Bis_m THEN
        ;
    ELSE
        #s_AnzeigeGeaendert := true;
    END_IF;
    IF #s_Aktiv.I_UZ_Von_s = #s_UhrenDaten[#S_Index_Akt].I_UZ_Von_s THEN
        ;
    ELSE
        #s_AnzeigeGeaendert := true;
    END_IF;
    IF #s_Aktiv.I_UZ_Bis_s = #s_UhrenDaten[#S_Index_Akt].I_UZ_Bis_s THEN
        ;
    ELSE
        #s_AnzeigeGeaendert := true;
    END_IF;
    IF #s_Aktiv.i_Gruppe_Nr = #s_UhrenDaten[#S_Index_Akt].i_Gruppe_Nr THEN
        ;
    ELSE
        #s_AnzeigeGeaendert := true;
    END_IF;
    
END_FUNCTION_BLOCK

Gruß,
Aksels
 
Habe mich noch nicht so intensiv durch Deinen Code gekämpft. Vielleicht hilft Dir dies bei dem TeilProblem weiter, zu bestimmen, wann eingeschaltet sein soll?
 
Zuletzt bearbeitet:
Zuviel Werbung?
-> Hier kostenlos registrieren
Danke. Interessanter Link. Aber einschalten tut mein Programm zuverlässig. Ich habe bei der Untersuchung eines anderen SPS-Problems allerdings eine mögliche Erklärung gefunden. Ich dachte seither, dass auch Schreibzugriffe vom HMI aus nur zu Zyklusbeginn übernommen werden, wie Digitaleingänge auch. Jetzt habe ich allerdings nachgelesen, dass ein HMI zu jeder beliebigen Zeit während eines Zyklusses in den Speicher schreiben kann. Das bedeutet auch zwischen der Prüfung auf ungültige Werte und den Array-Zugriffen. Im HMI habe ich ein Bild, in dem man mit Pfeiltasten die gewünschte Uhr-Nummer erhöhen oder erniedrigen kann. Wenn die Aktive UhrNr zu groß wird wird in der SPS auf die erste umgeschaltet. Wenn man nun genau nach dieser Umschaltung auf Pfeil rechts (plus) drückt kommt es zu einem Bereichslängenfehler. Ich war bisher der Auffassung, dass niemand in der Nähe des HMI war, wenn die SPS auf Stop ging. Nichtdestotrotz ist es eine Fehlerquelle. Ich muss mir mit Temporärvariablen ein Prozessabbild schaffen. Ich poste das Programm, wenn es geändert ist.
Vielleicht sieht jemand solange noch eine Fehlerquelle.
Gruß,
Aksels
 
Zuletzt bearbeitet:
Hier die Änderung:
Code:
FUNCTION_BLOCK "FB_Schaltuhren_SCL_1x00"
{ S7_Optimized_Access := 'FALSE' }
VERSION : 0.1
   VAR_INPUT 
      i_DaT_Zerl {InstructionName := 'DTL'; LibVersion := '1.0'} : DTL;
   END_VAR

   VAR 
      s_UhrenDaten : Array[1.."c_Anz_Schaltuhren"] of "DT_Schaltuhr_1x00" := [((), (), (), (), (), (), (), (), (), (), (), (), (), (), 1, (), (), ()), ((), (), (), (), (), (), (), (), (), (), (), (), (), (), 1, (), (), ()), ((), (), (), (), (), (), (), (), (), (), (), (), (), (), 1, (), (), ()), ((), (), (), (), (), (), (), (), (), (), (), (), (), (), 1, (), (), ()), ((), (), (), (), (), (), (), (), (), (), (), (), (), (), 1, (), (), ()), ((), (), (), (), (), (), (), (), (), (), (), (), (), (), 1, (), (), ()), ((), (), (), (), (), (), (), (), (), (), (), (), (), (), 1, (), (), ()), ((), (), (), (), (), (), (), (), (), (), (), (), (), (), 1, (), (), ()), ((), (), (), (), (), (), (), (), (), (), (), (), (), (), 1, (), (), ()), ((), (), (), (), (), (), (), (), (), (), (), (), (), (), 1, (), (), ())];
      S_Index_Akt : Int := 1;
      S_Index_Alt : Int := 1;
      s_Aktiv : "DT_Schaltuhr_1x00";
      s_AnzeigeUebernehmen : Bool;
      s_AnzeigeGeaendert : Bool;
   END_VAR

   VAR_TEMP 
      t_a : SInt;
      t_Von_Sek : UDInt;
      t_Bis_Sek : UDInt;
      t_Akt_Sek : UDInt;
      t_WoTa_OK : Bool;
      t_Von_OK : Bool;
      t_Bis_OK : Bool;
      t_Gruppen : Array[1.."c_Anz_Schaltuhren"] of Bool;
      t_Index_Akt : Int;
   END_VAR


BEGIN
    #t_Index_Akt := #S_Index_Akt;
    #t_Akt_Sek :=  USINT_TO_UDINT(#i_DaT_Zerl.SECOND) + USINT_TO_UDINT(#i_DaT_Zerl.MINUTE) * 60 + USINT_TO_UDINT(#i_DaT_Zerl.HOUR) * 60 * 60;
    //Kontrolle Index
    IF #t_Index_Akt <= 0 THEN
        #t_Index_Akt := "c_Anz_Schaltuhren";
    END_IF;
    IF #t_Index_Akt > "c_Anz_Schaltuhren" THEN
        #t_Index_Akt := 1;
    END_IF;
    #S_Index_Akt := #t_Index_Akt;
    //Genauso viele Schaltuhren es gibt, so viel Gruppen kann man anlegen. Wenn eine Schaltuhr true ausgibt wird der zugehörige Gruppenausgang auch true.
    //Hier werden die Temporärdaten initialisiert. Und geprüft ob ungültige Werte in den Gruppen stehen. Wichtig, sonst geht SPS in Stop.
    FOR #t_a := 1 TO "c_Anz_Schaltuhren" DO
        #t_Gruppen[#t_a] := false;
        IF #s_UhrenDaten[#t_a].i_Gruppe_Nr <= 0 THEN
            #s_UhrenDaten[#t_a].i_Gruppe_Nr := 1;
        END_IF;
        IF #s_UhrenDaten[#t_a].i_Gruppe_Nr > "c_Anz_Schaltuhren" THEN
            #s_UhrenDaten[#t_a].i_Gruppe_Nr :="c_Anz_Schaltuhren";
        END_IF;
        
    END_FOR;
    //Schaltuhren bearbeiten.
    FOR #t_a := 1 TO "c_Anz_Schaltuhren" DO
       
        CASE #i_DaT_Zerl.WEEKDAY OF
            1:  // Sonntag
                #t_WoTa_OK:=#s_UhrenDaten[#t_a].I_So;
            2:  // Montag
                #t_WoTa_OK:=#s_UhrenDaten[#t_a].I_Mo;
            3: //Dienstag
                #t_WoTa_OK:=#s_UhrenDaten[#t_a].I_Di;
            4: //Mittwoch
                #t_WoTa_OK:=#s_UhrenDaten[#t_a].I_Mi;
            5: //Donnerstag
                #t_WoTa_OK:=#s_UhrenDaten[#t_a].I_Do;
            6: //Freitag
                #t_WoTa_OK:=#s_UhrenDaten[#t_a].I_Fr;
            7: //Samstag
                #t_WoTa_OK:=#s_UhrenDaten[#t_a].I_Sa;
            ELSE  // Darf nicht vorkommen, sonst Daten korrupt.
                ;
        END_CASE;
        // In Sekunden rechnet es sich einfacher. 
        #t_Von_Sek:=USINT_TO_UDINT(#s_UhrenDaten[#t_a].I_UZ_Von_h) * 60 * 60 + USINT_TO_UDINT(#s_UhrenDaten[#t_a].I_UZ_Von_m) * 60 + USINT_TO_UDINT(#s_UhrenDaten[#t_a].I_UZ_Von_s);
        #t_Bis_Sek := USINT_TO_UDINT(#s_UhrenDaten[#t_a].I_UZ_Bis_h) * 60 * 60 + USINT_TO_UDINT(#s_UhrenDaten[#t_a].I_UZ_Bis_m) * 60 + USINT_TO_UDINT(#s_UhrenDaten[#t_a].I_UZ_Bis_s);
        //Ist Aus kleiner ein geht die Schaltzeit über 0:00 hinüber.
        IF #t_Von_Sek > #t_Bis_Sek THEN 
            //Von > Bis
            //AKtuelle Zeit zwischen Von und 23:59:00
            IF #t_Akt_Sek >= #t_Von_Sek AND #t_Akt_Sek <= 86399  THEN
                #t_Von_OK := true;
            ELSE
                #t_Von_OK := false;
            END_IF;
            //Aktuelle Zeit zwischen 0:00:00 und Bis
            IF #t_Akt_Sek < #t_Bis_Sek AND #t_Akt_Sek>=0 THEN
                #t_Bis_OK := true;
            ELSE
                #t_Bis_OK := false;
            END_IF;
        ELSE
            //Von < Bis
            IF #t_Akt_Sek >= #t_Von_Sek  THEN
                #t_Von_OK:=true;
            ELSE
                #t_Von_OK := false;
            END_IF;
            IF #t_Akt_Sek < #t_Bis_Sek THEN
                #t_Bis_OK := true;
            ELSE
                #t_Bis_OK := false;
            END_IF;
        END_IF;
        #s_UhrenDaten[#t_a].O_Ein := #t_Bis_OK AND #t_Von_OK AND #t_WoTa_OK AND #s_UhrenDaten[#t_a].I_Aktiv;
        IF #s_UhrenDaten[#t_a].O_Ein THEN
            //Temporärdaten für Gruppe aktualisieren. Heikel. Hier darf nie eine falsche Gruppe drin stehen, sonst geht SPS in Stop. Wird deswegen ganz oben abgeprüft.
            #t_Gruppen[#s_UhrenDaten[#t_a].i_Gruppe_Nr] := true;
        END_IF;
    END_FOR;
    //Richtige Gruppen mit Temporärdaten befüttern.
    FOR #t_a := 1 TO "c_Anz_Schaltuhren" DO
        #s_UhrenDaten[#t_a].O_Gruppe_Ein:= #t_Gruppen[#t_a] ;
    END_FOR;
    
    //Ab hier Anzeige aufbereiten.
    //Wurde der Index geändert? Dann Anzeigedaten aufbereiten
    IF #t_Index_Akt = #S_Index_Alt THEN
        #s_Aktiv.O_Ein := #s_UhrenDaten[#t_Index_Akt].O_Ein;
    ELSE
        #S_Index_Alt := #t_Index_Akt;
        #s_Aktiv := #s_UhrenDaten[#t_Index_Akt];
        
    END_IF;
    //Wenn auf Speichern geklickt wurde Anzeigedaten in Datenbestand übernehmen.
    IF #s_AnzeigeUebernehmen THEN
        #s_AnzeigeUebernehmen := false;
        #s_UhrenDaten[#t_Index_Akt] := #s_Aktiv;
    ELSE
        ;
    END_IF;
    //Auswerten, ob Angezeigte Daten geändert wurden. Dann kann man den Speichernbutton einblenden und blinken lassen.
    //Gefällt mir noch nicht. Muss man doch eleganter lösen können (Bei änderung eines Feldes im HMI)?
    #s_AnzeigeGeaendert := false;
    
    IF #s_Aktiv.I_Name = #s_UhrenDaten[#t_Index_Akt].I_Name THEN
        ;
    ELSE
        #s_AnzeigeGeaendert := true;
    END_IF;
    IF #s_Aktiv.I_Aktiv = #s_UhrenDaten[#t_Index_Akt].I_Aktiv THEN
        ;
    ELSE
        #s_AnzeigeGeaendert := true;
    END_IF;
    IF #s_Aktiv.I_Mo = #s_UhrenDaten[#t_Index_Akt].I_Mo THEN
        ;
    ELSE
        #s_AnzeigeGeaendert := true;
    END_IF;
    IF #s_Aktiv.I_Di = #s_UhrenDaten[#t_Index_Akt].I_Di THEN
        ;
    ELSE
        #s_AnzeigeGeaendert := true;
    END_IF;
    IF #s_Aktiv.I_Mi = #s_UhrenDaten[#t_Index_Akt].I_Mi THEN
        ;
    ELSE
        #s_AnzeigeGeaendert := true;
    END_IF;
    IF #s_Aktiv.I_Do = #s_UhrenDaten[#t_Index_Akt].I_Do THEN
        ;
    ELSE
        #s_AnzeigeGeaendert := true;
    END_IF;
    IF #s_Aktiv.I_Fr = #s_UhrenDaten[#t_Index_Akt].I_Fr THEN
        ;
    ELSE
        #s_AnzeigeGeaendert := true;
    END_IF;
    IF #s_Aktiv.I_Sa = #s_UhrenDaten[#t_Index_Akt].I_Sa THEN
        ;
    ELSE
        #s_AnzeigeGeaendert := true;
    END_IF;
    IF #s_Aktiv.I_So = #s_UhrenDaten[#t_Index_Akt].I_So THEN
        ;
    ELSE
        #s_AnzeigeGeaendert := true;
    END_IF;
    IF #s_Aktiv.I_UZ_Von_h = #s_UhrenDaten[#t_Index_Akt].I_UZ_Von_h THEN
        ;
    ELSE
        #s_AnzeigeGeaendert := true;
    END_IF;
    IF #s_Aktiv.I_UZ_Bis_h = #s_UhrenDaten[#t_Index_Akt].I_UZ_Bis_h THEN
        ;
    ELSE
        #s_AnzeigeGeaendert := true;
    END_IF;
    IF #s_Aktiv.I_UZ_Von_m = #s_UhrenDaten[#t_Index_Akt].I_UZ_Von_m THEN
        ;
    ELSE
        #s_AnzeigeGeaendert := true;
    END_IF;
    IF #s_Aktiv.I_UZ_Bis_m = #s_UhrenDaten[#t_Index_Akt].I_UZ_Bis_m THEN
        ;
    ELSE
        #s_AnzeigeGeaendert := true;
    END_IF;
    IF #s_Aktiv.I_UZ_Von_s = #s_UhrenDaten[#t_Index_Akt].I_UZ_Von_s THEN
        ;
    ELSE
        #s_AnzeigeGeaendert := true;
    END_IF;
    IF #s_Aktiv.I_UZ_Bis_s = #s_UhrenDaten[#t_Index_Akt].I_UZ_Bis_s THEN
        ;
    ELSE
        #s_AnzeigeGeaendert := true;
    END_IF;
    IF #s_Aktiv.i_Gruppe_Nr = #s_UhrenDaten[#t_Index_Akt].i_Gruppe_Nr THEN
        ;
    ELSE
        #s_AnzeigeGeaendert := true;
    END_IF;
    
END_FUNCTION_BLOCK
 
Verbesserte Version:
Code:
TYPE "DT_Schaltuhr_1x00_in"
VERSION : 0.1
   STRUCT
      I_Aktiv { S7_SetPoint := 'True'} : Bool := True;
      I_Mo { S7_SetPoint := 'True'} : Bool := TRUE;
      I_Di { S7_SetPoint := 'True'} : Bool := TRUE;
      I_Mi { S7_SetPoint := 'True'} : Bool := TRUE;
      I_Do { S7_SetPoint := 'True'} : Bool := TRUE;
      I_Fr { S7_SetPoint := 'True'} : Bool := TRUE;
      I_Sa { S7_SetPoint := 'True'} : Bool := TRUE;
      I_So { S7_SetPoint := 'True'} : Bool := TRUE;
      I_UZ_Von_h { S7_SetPoint := 'True'} : USInt;
      I_UZ_Von_m { S7_SetPoint := 'True'} : USInt;
      I_UZ_Von_s { S7_SetPoint := 'True'} : USInt;
      I_UZ_Bis_h { S7_SetPoint := 'True'} : USInt;
      I_UZ_Bis_m { S7_SetPoint := 'True'} : USInt;
      I_UZ_Bis_s { S7_SetPoint := 'True'} : USInt;
      i_Gruppe_Nr { S7_SetPoint := 'True'} : USInt;
      I_Name { S7_SetPoint := 'True'} : String[200] := 'Schaltuhr';
   END_STRUCT;

END_TYPE

Code:
TYPE "DT_Schaltuhr_1x00_out"
VERSION : 0.1
   STRUCT
      O_Ein : Bool;
      O_Gruppe_Ein : Bool;
   END_STRUCT;

END_TYPE

Code:
TYPE "DT_Schaltuhr_1x00"
VERSION : 0.1
   STRUCT
      input : "DT_Schaltuhr_1x00_in";
      output { S7_SetPoint := 'False'} : "DT_Schaltuhr_1x00_out";
   END_STRUCT;

END_TYPE

Code:
FUNCTION_BLOCK "FB_Schaltuhren_SCL_1x00"
{ S7_Optimized_Access := 'FALSE' }
VERSION : 0.1
   VAR_INPUT 
      i_DaT_Zerl {InstructionName := 'DTL'; LibVersion := '1.0'} : DTL;
   END_VAR

   VAR 
      s_UhrenDaten : Array[1..#c_Anz_Schaltuhren] of "DT_Schaltuhr_1x00";
      S_Index_Akt : Int := 1;
      S_Index_Alt : Int := 1;
      s_Aktiv : "DT_Schaltuhr_1x00";
      s_AnzeigeUebernehmen : Bool;
      s_AnzeigeGeaendert : Bool;
   END_VAR

   VAR_TEMP 
      t_a : SInt;
      t_Von_Sek : UDInt;
      t_Bis_Sek : UDInt;
      t_Akt_Sek : UDInt;
      t_WoTa_OK : Bool;
      t_Von_OK : Bool;
      t_Bis_OK : Bool;
      t_Gruppen : Array[1..#c_Anz_Schaltuhren] of Bool;
      t_Index_Akt : Int;
   END_VAR

   VAR CONSTANT 
      c_Anz_Schaltuhren : Int := 16;
   END_VAR


BEGIN
    #t_Index_Akt := #S_Index_Akt;
    #t_Akt_Sek :=  USINT_TO_UDINT(#i_DaT_Zerl.SECOND) + USINT_TO_UDINT(#i_DaT_Zerl.MINUTE) * 60 + USINT_TO_UDINT(#i_DaT_Zerl.HOUR) * 60 * 60;
    //Kontrolle Index
    IF #t_Index_Akt <= 0 THEN
        #t_Index_Akt := #c_Anz_Schaltuhren;
    END_IF;
    IF #t_Index_Akt > #c_Anz_Schaltuhren THEN
        #t_Index_Akt := 1;
    END_IF;
    #S_Index_Akt := #t_Index_Akt;
    //Genauso viele Schaltuhren es gibt, so viel Gruppen kann man anlegen. Wenn eine Schaltuhr true ausgibt wird der zugehörige Gruppenausgang auch true.
    //Hier werden die Temporärdaten initialisiert. Und geprüft ob ungültige Werte in den Gruppen stehen. Wichtig, sonst geht SPS in Stop.
    FOR #t_a := 1 TO #c_Anz_Schaltuhren DO
        #t_Gruppen[#t_a] := false;
        IF #s_UhrenDaten[#t_a].input.i_Gruppe_Nr <= 0 THEN
            #s_UhrenDaten[#t_a].input.i_Gruppe_Nr := 1;
        END_IF;
        IF #s_UhrenDaten[#t_a].input.i_Gruppe_Nr > #c_Anz_Schaltuhren THEN
            #s_UhrenDaten[#t_a].input.i_Gruppe_Nr :=#c_Anz_Schaltuhren;
        END_IF;
        
    END_FOR;
    //Schaltuhren bearbeiten.
    FOR #t_a := 1 TO #c_Anz_Schaltuhren DO
        
        CASE #i_DaT_Zerl.WEEKDAY OF
            1:  // Sonntag
                #t_WoTa_OK := #s_UhrenDaten[#t_a].input.I_So;
            2:  // Montag
                #t_WoTa_OK := #s_UhrenDaten[#t_a].input.I_Mo;
            3: //Dienstag
                #t_WoTa_OK := #s_UhrenDaten[#t_a].input.I_Di;
            4: //Mittwoch
                #t_WoTa_OK := #s_UhrenDaten[#t_a].input.I_Mi;
            5: //Donnerstag
                #t_WoTa_OK := #s_UhrenDaten[#t_a].input.I_Do;
            6: //Freitag
                #t_WoTa_OK := #s_UhrenDaten[#t_a].input.I_Fr;
            7: //Samstag
                #t_WoTa_OK := #s_UhrenDaten[#t_a].input.I_Sa;
            ELSE  // Darf nicht vorkommen, sonst Daten korrupt.
                ;
        END_CASE;
        // In Sekunden rechnet es sich einfacher. 
        #t_Von_Sek := USINT_TO_UDINT(#s_UhrenDaten[#t_a].input.I_UZ_Von_h) * 60 * 60 + USINT_TO_UDINT(#s_UhrenDaten[#t_a].input.I_UZ_Von_m) * 60 + USINT_TO_UDINT(#s_UhrenDaten[#t_a].input.I_UZ_Von_s);
        #t_Bis_Sek := USINT_TO_UDINT(#s_UhrenDaten[#t_a].input.I_UZ_Bis_h) * 60 * 60 + USINT_TO_UDINT(#s_UhrenDaten[#t_a].input.I_UZ_Bis_m) * 60 + USINT_TO_UDINT(#s_UhrenDaten[#t_a].input.I_UZ_Bis_s);
        //Ist Aus kleiner ein geht die Schaltzeit über 0:00 hinüber.
        IF #t_Von_Sek > #t_Bis_Sek THEN
            //Von > Bis
            //AKtuelle Zeit zwischen Von und 23:59:00
            IF #t_Akt_Sek >= #t_Von_Sek AND #t_Akt_Sek <= 86399 THEN
                #t_Von_OK := true;
            ELSE
                #t_Von_OK := false;
            END_IF;
            //Aktuelle Zeit zwischen 0:00:00 und Bis
            IF #t_Akt_Sek < #t_Bis_Sek AND #t_Akt_Sek >= 0 THEN
                #t_Bis_OK := true;
            ELSE
                #t_Bis_OK := false;
            END_IF;
        ELSE
            //Von < Bis
            IF #t_Akt_Sek >= #t_Von_Sek THEN
                #t_Von_OK := true;
            ELSE
                #t_Von_OK := false;
            END_IF;
            IF #t_Akt_Sek < #t_Bis_Sek THEN
                #t_Bis_OK := true;
            ELSE
                #t_Bis_OK := false;
            END_IF;
        END_IF;
        #s_UhrenDaten[#t_a].output.O_Ein := #t_Bis_OK AND #t_Von_OK AND #t_WoTa_OK AND #s_UhrenDaten[#t_a].input.I_Aktiv;
        IF #s_UhrenDaten[#t_a].output.O_Ein THEN
            //Temporärdaten für Gruppe aktualisieren. Heikel. Hier darf nie eine falsche Gruppe drin stehen, sonst geht SPS in Stop. Wird deswegen ganz oben abgeprüft.
            #t_Gruppen[#s_UhrenDaten[#t_a].input.i_Gruppe_Nr] := true;
        END_IF;
    END_FOR;
    //Richtige Gruppen mit Temporärdaten befüttern.
    FOR #t_a := 1 TO #c_Anz_Schaltuhren DO
        #s_UhrenDaten[#t_a].output.O_Gruppe_Ein:= #t_Gruppen[#t_a] ;
    END_FOR;
    
    //Ab hier Anzeige aufbereiten.
    //Wurde der Index geändert? Dann Anzeigedaten aufbereiten
    IF #t_Index_Akt = #S_Index_Alt THEN
        #s_Aktiv.output.O_Ein := #s_UhrenDaten[#t_Index_Akt].output.O_Ein;
    ELSE
        #S_Index_Alt := #t_Index_Akt;
        #s_Aktiv.input := #s_UhrenDaten[#t_Index_Akt].input;
        
    END_IF;
    //Wenn auf Speichern geklickt wurde Anzeigedaten in Datenbestand übernehmen.
    IF #s_AnzeigeUebernehmen THEN
        #s_AnzeigeUebernehmen := false;
        #s_UhrenDaten[#t_Index_Akt].input := #s_Aktiv.input;
    ELSE
        ;
    END_IF;
    //Auswerten, ob Angezeigte Daten geändert wurden. Dann kann man den Speichernbutton einblenden und blinken lassen.
    //Gefällt mir noch nicht. Muss man doch eleganter lösen können (Bei änderung eines Feldes im HMI)?
    #s_AnzeigeGeaendert := false;
    
    IF #s_Aktiv.input = #s_UhrenDaten[#t_Index_Akt].input THEN
        ;
    ELSE
        #s_AnzeigeGeaendert := true;
    END_IF;
    
END_FUNCTION_BLOCK

Gruß,
Aksels
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Ich verstehe nicht so ganz, wo die "korrupten Daten" herkommen (GruppenNrn <1 oder >AnzSchaltUhren sowie die nicht definierten WochenTage) und noch weniger, warum sie - wenn sie denn auftreten sollten - willkürlich durch nicht korrupte ersetzt werden, statt ihr Auftreten zu melden und die ArrayZugriffe zu unterbinden.
Die ArrayDimensionierung wird doch nicht zur Laufzeit verändert und die Eingabe unzulässiger GruppenNrn sollte doch zu verhindern sein.
Wenn tatsächlich korrupte GruppenNrn irgendwann auftreten sollten, wäre es denn dann sinnvoll, damit Ausgänge zu schalten, die gar nicht gemeint sind?

PS:
Die Einhalltung des WerteBereichs könnte man übersichtlicher mit LIMIT (oder MIN und MAX) "korrigieren".

PPS:
Ich könnte mir vorstellen, die GruppenNr 0 in den Arrays zuzulassen [0..AnzSchaltUhren] aber die Gruppe 0 nicht als aktivierbare Gruppe auszuwerten.
 
Das Problem kann am fehlenden Zykluskontrollpunkt liegen.
Das HMI kann unkoordiniert zu jedem Zeitpunkt im Zyklus schreiben.
Daher Index oder ähnliche Variablen im Programm einlesen, prüfen und dann den gültigen Wert in eine neue Variable schreiben.
 
Guten Morgen.
Der Index_Aktiv ist ein Zeiger auf das Arrayelement, das ich im HMI sehen will. Es wird aus dem Array herauskopiert in das HMI-Anzeigeelement. Im HMI kann man mit Pfeiltasten den Index_Aktiv erhöhen und erniedrigen. Wird er größer als die Anzahl Arrayelemente soll er auf 1 umschnappen. Genau da liegt das Problem. Das HMI erhöht den Index_Aktiv und schreibt ihn direkt nach dem Check auf ungültige Werte in den DB. Der Wechsel wird erkannt und versucht das neue Element in die HMI-Anzeige zu kopieren und zack! Array-Zugriff ausserhalt des gültigen Bereiches.
Durch das kopieren in Temporäre Variablen und schaffe ich einen eigenen Zykluskontrollpunkt (danke, den Begriff kannte ich noch nicht) wird das vermieden.
Das nahe-beieinander-lesen, kontrollieren und schreiben der HMI-Variablen Index_Aktiv erniedrigt die Wahrscheinlichkeit, dass das SPS-Programm eine vom HMI geschriebene Änderung überschreibt.
Gruß,
Aksels
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Das nahe-beieinander-lesen, kontrollieren und schreiben der HMI-Variablen Index_Aktiv erniedrigt die Wahrscheinlichkeit, dass das SPS-Programm eine vom HMI geschriebene Änderung überschreibt.

Letztlich ist es ganz einfach:
Du brauchst 2 getrennte Variablen.
Eine Index-Nr fürs HMI und eine für das eigentliche SPS-Programm.
Auch wenn es nahe beieinander liegt, kracht es doch irgendwann mal.
 
Habe ja jetzt zwei unterschiedliche Variablen.
Wenn es jetzt "kracht", dann war der Klick des Users im HMI halt umsonst, nichts passierte und er muss nochmal clicken.
Ach ja: korrupte Daten können von der Reinitialisierung des DB kommen. Dann steht in allen Gruppen plötzlich 0 drin, wenn ich es bei der definition versäumt habe zu ändern.
Limit macht den Code übrigens wirklich schöner. Hab ich gleich eingebaut.
Danke euch beiden.
Aksels
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Sorry, ich verstehe es trotzdem nicht.
In der SPS hast Du eine IndexVariable, mit der Du in diversen FOR-Schleifen auf ArrayElemente zugreifst. Der Inhalt dieser IndexVariablen wird doch ausschliesslich durch die FOR-Schleifen bestimmt. Unabhängig davon, ob Du in der HMI eine IndexVariable hast, deren Inhalt Du in der SPS auf GrenzÜberschreitungen überwachst.
Mit GruppenNrn aus der Tabelle greifst Du auch zu. Aber wenn hier keine unzulässigen GruppenNrn drin stehen, produziert selbst der Zugriff auf eine GruppenNr aus einer falschen (aber zulässigen) Zeile keinen ZugriffsFehler.
Wo kommt es zu Inkonsistenzen der Daten, die die SPS in Stopp gehen lassen? Wenn, dann könnte doch nur die HMI "ins Leere" greifen, aber nicht die SPS. Die SPS geht dann trotzdem in Stopp???
 
Zuletzt bearbeitet:
Ich habe eine Variable S_Index_Ak. Diese wird vom HMI verändert. Stell dir vor ich hätte die Schaltuhren von 1-16 deklariert. Die Variable S_Index_Akt steht auf der 16. Die 16. Schaltuhr wird also im HMI angezeigt. Nun drückt der User auf den Pfeil nach rechts um die nächste zu sehen. In S_Index_Aktsteht nun eine 17. Wenn die SPS versucht das Arrayelement 17 in die aktive Anzeige zu kopieren geht sie in stop. Eigentlich soll dann auf die 1 gewechselt werden. Aber es kann passieren, dass auf die 17 zugegriffen wird, wenn die 17 genau nach dem check auf ungültige Werte in S_Index_Akt und vor dem Kopieren in das Anzeige-Strukt geschrieben wird.
Deswegen kopiere ich S_Index_Akt nun in eine temporäre Variable, checke diese auf gültige Werte und schreibe sie sofort zurück in S_Index_Akt, benutze danach aber nur noch die temporäre Variable, die von der SPS nach den Check nicht mehr vberändert wird. Das HMI kann also egal wann wieder in die Variable schreiben, ohne ungültige Werte im Programm zu verursachen.
Ich hoffe ich habe das gut erklärt. Ich glaube dass diese Erkenntnis, dass das HMI am Prozessabbild vorbei schreibt recht wichtig für komplexere Programme ist.
Gruß,
Aksels
 
Was mir gerade noch aufgefallen ist:
Du hast öfters 60 * 60 im Code stehen.
Änder das doch einfach auf 3600.
Ich mache das auch öfters, damit ich später sehe woher der Wert von hard coded magic numbers kommt. Das macht den Code etwas verständlicher, ohne extra Konstanten mit Namen festzulegen.
Ein guter Compiler zieht die Konstanten zusammen und setzt nur das Ergebnis in den Programmcode. Ob der TIA-SCL-Compiler das macht weiß ich nicht. (Man könnte es testen, ob das übersetzte Programm kleiner wird, wenn man 3600 im Quellcode schreibt anstatt 60*60)

Harald
 
Zuviel Werbung?
-> Hier kostenlos registrieren
Limit macht den Code übrigens wirklich schöner. Hab ich gleich eingebaut.
Näher am Original ...
Code:
    //Kontrolle Index
    IF #S_Index_Akt <= 0 THEN
        #S_Index_Akt := "c_Anz_Schaltuhren";
    END_IF;
    IF #S_Index_Akt > "c_Anz_Schaltuhren" THEN
        #S_Index_Akt := 1;
    END_IF;
... wäre:
Code:
[FONT=Verdana,Arial,Tahoma,Calibri,Geneva,sans-serif]    #S_Index_Akt := (#S_Index_Akt - 1) MOD "c_Anz_Schaltuhren" + 1 ;
[/FONT]
 
Zurück
Oben