cz
en

Další příklady: detekce změny hodnoty, číslo týdne, alarmový e-mail

Detekce změny hodnoty

Tento blok byl využit pro synchronizaci měření čtvrthodiny. Vstupní čtvrthodinové impulsy jsou zachycovány čítačovou kartou Wago 750-404, cílem je detekovat změnu stavu čítače (zvýšení počtu impulzů o jeden, případně přetečení čítače) a při této změně generovat pulz o délce jednoho cyklu, který by vynuloval čítač sekund ve čtvrthodině. Přímé přivedení synchronizačního impulsu z binárního vstupu na vstup čítače BD30 (viz obr. níže) by znamenalo hazard – impuls by mohl být příliš krátký na to, aby se bezpečně dostal přes vstupní karty a sběrnici do aplikačního programu.

Zde již je využita podmínka IF…ELSE…END_IF a nasazení strukturovaného textu dává smysl. Zdrojový kód je jen na několik řádků – při rozdílu vstupní a zapamatované hodnoty se vnitřní proměnná pom aktualizuje a na výstup se po dobu jednoho cyklu (do příštího porovnání) zapíše logická 1:

 

Nasazení bloku change v programu ukazuje, že i takto jednoduchá funkce kód velmi zpřehlední:

Pracujeme-li s elementárními funkcemi, proměnnými, podmínkami (IF … ELSE, CASE) a cykly (FOR, WHILE a REPEAT), největší problém asi představuje správná syntaxe.

Číslo týdne podle ISO

Jednou z nejneobvyklejších úloh technické podpory bylo algoritmizovat výpočet čísla týdne podle ISO 8601. Kompletní výpis zdrojového textu funkčního bloku je zde:

FUNCTION_BLOCK iso_week2
// As in https://webspace.science.uu.nl/~gent0113/calendar/isocalendar_text_5.htm
VAR
    today : dt; 
    wday, weekday, isoweeknr: dint; 
    m, dd, dow : sint;
    day, month, y : int;
    isoyear, year, d, d0, weekday0 : dint; 
END_VAR
VAR_OUTPUT
    iso_week2 : int;
END_VAR

today := getlocaltime();
DECOMPOSEDATE(T := today, YEAR => y, MONTH => m, DAY => dd, DAYOFWEEK => dow);
year:=to_dint(y);
month := m - 1; // 0=January, 1=February, etc.
day:=dd;
wday:=dow;

weekday := ((wday + 6) / 7) + 1; // weekdays will be numbered 1 to 7
isoyear:=year;
d0 := gregdaynumber(year,1,0);
weekday0 := to_dint(((d0+4) / 7)+1);
d := gregdaynumber(year, month + 1, day);
isoweeknr := trunc_sint((d - d0 + weekday0 + 6) / 7) - trunc_sint((weekday0 + 3) / 7);

// check whether the last few days of December belong to the next year's ISO week
if ((month = 11) and ((day - weekday) > 27)) then 
  isoweeknr:=1;
  isoyear:=isoyear+1;
end_if;
 
// check whether the first few days of January belong to the previous year's ISO week
if ((month = 0) and ((weekday - day) > 3)) then 
  d0 := gregdaynumber(year-1, 1, 0);
  weekday0 := ((d0+4) / 7) + 1;
  isoweeknr := trunc_int((d - d0 + weekday0 + 6) / 7) - trunc_int((weekday0 + 3) / 7);
  isoyear := isoyear -1;
END_IF;
iso_week2 := to_int(isoweeknr);
END_FUNCTION_BLOCK

FUNCTION gregdaynumber: dint
// computes the day number since 0 January 0 CE (Gregorian)
VAR_INPUT
    year : dint; 
    month, day : int;
END_VAR
VAR
    y: dint;
    m, d: int;
END_VAR 

y := year;
m := month;
IF month < 3 THEN 
    y := y - 1; 
    m := m + 12;
END_IF;
gregdaynumber := trunc_dint(365.25 * y) - trunc_dint(y/100) + trunc_dint(y/400) + trunc_dint(30.6*(m+1)) + day -62;
END_FUNCTION

Tento příklad uvádíme ze tří důvodů:

Jde o nepříliš často používanou funkci, která ovšem může mít význam v případě práce s časovými programy, výjimkami, spínáním zařízení v provozech pracujících na směny atd.

Dále ukazuje, že součástí kódu funkčního bloku (iso_week2) mohou být i další definice – například funkce gregdaynumber, která se pak v kódu bloku volá třikrát (a tedy se vyplatí tento výpočet oddělit v podobě funkce). Definice funkce následuje až za jejím voláním, což nevadí. Je ohraničena slovy FUNCTION … END_FUNCTION.

A za třetí - je v něm v ST volána funkce DECOMPOSEDATE, která – jako všechny funkce, jak už jsme si řekli v popisu rozdílných vlastností funkce a funkčního bloku, – má jen jeden implicitní výstup, ale může poskytovat data pro více proměnných. Ukážeme si na ní různé způsoby, jakými si funkce vyměňují data s okolím. K volání dochází hned na druhém řádku kódu, po deklaraci proměnných a načtení aktuálního času do proměnné today.

Implicitní výstup funkce „=>“ podle nápovědy vrací hodnotu 1, pokud převod proběhl úspěšně. Tato informace nás ale v programu nezajímá, my potřebujeme, aby funkce zapsala aktuální hodnotu roku, měsíce, dne a dne v týdnu do příslušných proměnných bloku. To se dělá přiřazením pomocí operátoru „=>“. Naopak vstupní proměnná today se na vstup funkce T připojí pomocí přiřazení „:=“.

DECOMPOSEDATE(T := today, YEAR => y, MONTH => m, DAY => dd, DAYOFWEEK => dow);

Tyto operátory jsou zásadní pro práci s funkcemi a funkčními bloky volanými v ST. Toto „napojení“ funkce na proměnné se nazývá předávání hodnot pomocí formálního seznamu parametrů. Nezáleží zde na pořadí parametrů, neuvedené vstupy mají implicitní hodnoty.

Další možností, jak osadit vstupy a výstupy funkce, je zapsat jako argumenty názvy proměnných přímo, pomocí neformálního seznamu parametrů:

DECOMPOSEDATE(today, y, m, dd, dow);

V tomto případě ale musí být využity všechny pozice, a to v pořadí, v jakém jsou uvedeny v nápovědě. Pokud bychom například nepotřebovali hodnotu roku, volání

DECOMPOSEDATE(today, m, dd, dow); // CHYBNĚ

skončí chybou kompilace – zde nekompatibilita typů: m je SINT, očekáván je INT pro YEAR, ale i kdyby typy odpovídaly, ve volání bude málo argumentů, je očekáván jeden vstup a čtyři výstupy. Správné volání při omezeném počtu argumentů je pomocí operátoru:

DECOMPOSEDATE(T := today, MONTH => m, DAY => dd, DAYOFWEEK => dow);

Smíšené volání, tedy např.

DECOMPOSEDATE(T := today, y, m, DAY => dd, DAYOFWEEK => dow); // CHYBNĚ

není přípustné.

Jako další příklad uveďme odeslání alarmového e-mailu:

Alarmový e-mail

Následující struktura ukazuje ne funkční blok, ale rovnou celý program. Každý program začíná klíčovým slovem PROGRAM a opět může na začátku obsahovat deklarace proměnných.

Proměnná SendLast hlídá, aby funkce messaging.sendmail nebyla volána při každém zavolání programu (tedy třeba co 20 ms), ale jen při změně hodnoty proměnné Send z False do True. V proměnné Result je výsledek operace, hodnota 0 znamená, že odeslání se povedlo. (Nejčastější chybou je zde výsledek 16 = neznámý e-mailový kanál, tj. buď není v PLC v Definici hlášení alarmový kanál definován, nebo je ve volání funkce použito chybné jméno – pozor, u názvu se rozlišují malá a velká písmena, takže alrchannel a ALRCHANNEL jsou dvě různá jména.)

PROGRAM alarm_email
 VAR        
    Send : bool := False; //náběžná hrana odešle e-mail
    SendLast : bool;
    Channel : string := 'alrchannel';
    MailFrom  : string := 'sender@domain.com';
    MailTo  : string := 'recipient@domain.com';
    Subject  : string := 'Alarm!';
    Message : string := 'There is an alarm in the PLC.';
    Result: SINT;
 END_VAR     
 
 IF Send AND (NOT SendLast) THEN
   Result := messaging.sendmail(channel, MailFrom, MailTo, Subject, Message);
 END_IF;
 SendLast := Send; 
END_PROGRAM

Tolik tedy k volání funkcí s argumenty.

 Všechny díly