Pár náhodných střípků z praxe, které by se mohly do začátků hodit. Jestli vás zajímá něco jiného, dejte mi vědět, doplním nebo upravím.
Buď se dají použít systémové funkční bloky R_TRIG a F_TRIG, nebo to jde ručně s jednou pomocnou proměnnou:
IF aktuální_hodnota AND NOT minulá_hodnota THEN ...máme vzestupnou hranu... END_IF; IF minulá_hodnota AND NOT aktuální_hodnota THEN ...máme sestupnou hranu... END_IF; minulá_hodnota:=aktuální_hodnota;
Nejčastěji se používá typ TON, čili zpožděné zapnutí (výstup se zapíná po určité době od zapnutí vstupu). Občas se využije TP (vzestupná hrana na vstupu spustí puls o dané délce), jiné druhy se vyskytují málokdy. Časovač se používá stejně jako každý jiný funkční blok: vytvoříme si instanci typu TON (případně TON_TIME, nebo jak se v daném vývojovém prostředí jmenuje) a v kódu ji zavoláme:
VAR casovac:TON; END_VAR; ... casovac(in:=něco,pt:=T#něco,q=>někam);
Parametry: in je vstupní signál (bool), pt je doba zpoždění (time), q je výstupní signál (bool).
První typické použití - filtrace zákmitů na čidlech nebo hlídání dojezdových dob pneuválců (volejte nonstop v každém scanu):
casovac_cidla(in:=signál_z_čidla, pt:=T#100ms, q=>čidlo_opravdu_svítí); casovac_valce(in:=povel_k_vysunutí_válce AND NOT svítí_snímač_vysunuté_polohy, pt:=T#5s); IF časovač_válce.q THEN ...válec se nám asi zasekl, vyhlásíme chybu... END_IF;
Všechny takovéhle časomíry musí běžet nezávisle jedna na druhé, proto pro každé čidlo nebo válec potřebujeme samostatnou instanci časovače.
Další typická aplikace jsou různé technologické prodlevy během pracovního cyklu. Ty se většinou nescházejí ve stejný okamžik, proto si vystačíme s jedním časovačem, který voláme z různých míst:
VAR casovac_cyklu:TON; END_VAR; ... CASE krok_cyklu OF 0:casovac_cyklu(in:=false); (*Úvodní reset, abychom měli jistotu, že až časovač poprvé zavoláme, nebude z dřívějška zapnutý. Parametr PT nás nezajímá, jde jenom o vynulování výstupu Q, což zařídíme nulovým vstupem IN.*) krok_cyklu:=10; 10:ofuk:=true; casovac_cyklu(in:=true,pt:=T#2s); (*volání časovače: vstup je zapnutý, výstup naskočí po dvouvteřinové prodlevě*) IF casovac_cyklu.q then (*časovač doběhl*) ofuk:=false; casovac_cyklu(in:=false); (*reset pro příští použití*) krok_cyklu:=20; END_IF; 20:... (*V tomhle kroku časovač nepotřebujeme, takže ho nevoláme. Zůstává pořád ve stavu, v jakém byl po posledním zavolání v kroku 10, tj. s vynulovaným výstupem.*) ... ... 50:kontrolka:=true; casovac_cyklu(in:=true,pt:=T#1s); (*další použití v jiném místě a s jinou prodlevou*) IF casovac_cyklu.q then kontrolka:=false; casovac_cyklu(in:=false); krok_cyklu:=60; END_IF; 60: ...atd... END_CASE;
Pozn.: tenhle zápis by fungoval v Codesysu. TIA portal u časovače vyžaduje vyplnění všech vstupních parametrů, i když je zrovna nepotřebujeme, takže by se resety psaly nějak takhle:
časovač_cyklu(in:=false, pt:=T#0s); (*čas zcela libovolný*)
Výše uvedený příklad je zároveň osvědčený postup krokování pracovního cyklu pomocí číselné proměnné. Šlo by to zařídit i přes ify, jenom je s tím víc psaní:
if krok_cyklu=0 then ... elsif krok_cyklu=10 then ... elsif krok_cyklu=20 then ... end_if;
Kroky je dobré číslovat po desítkách nebo i větších intervalech, protože se dost často stává, že mezi dva kroky později potřebujeme vecpat ještě několik dalších a přečíslovávání je pracné a snadno se při něm udělá chyba. Obzvlášť když na ta čísla máme navázané třeba provozní hlášky na displeji (to je celkem běžná a osvědčená praxe) nebo nějaké logické podmínky, které v závislosti na fázi pracovního cyklu něco dělají (to naopak nedoporučuji, snadno kvůli tomu vznikají nepříjemné, těžko odhalitelné a někdy i hodně drahé chyby).
Další možnost krokování je, že místo jedné číselné proměnné máme hromadu boolů, pro každý krok jeden:
IF krok1 THEN ...něco... krok1:=false; krok2:=true; END_IF; IF krok2 THEN ... krok2:=false; krok3:=true; END_IF;
Atd. Hodí se to ve dvou případech: když pracujeme s procesorem, který nemá instrukce pro práci s čísly (něco takového jsem ale ještě neviděl), a když potřebujeme mít možnost spustit několik kroků současně (to se ale dá zařídit i jinak). Prakticky použité jsem to jednou viděl, ale podle mého názoru je to jenom zbytečné plýtvání pamětí a znepřehledňování kódu.
Všechny statické proměnné kromě retentivních se při zapnutí PLC buď automaticky nulují nebo inicializují ručně zadanou výchozí hodnotou (VAR proměnná:typ:=výchozí_hodnota;). Podle toho můžeme detekovat okamžik zapnutí:
VAR PraveSpusteno:BOOL:=true; END_VAR;
Někde v hlavním programu by pak mohlo být třeba tohle:
IF PraveSpusteno THEN ...udělejte všechno, co po zapnutí stroje potřebujete udělat... PraveSpusteno:=false; (*tohle zaručí, že se sem už víckrát nevrátíme*) END_IF;
Na některých PLC (třeba Simatic) je to ještě jednodušší: existuje systémová proměnná "First scan", která má automaticky během prvního scanu po zapnutí hodnotu true a potom už pořád false.
Většina strojů se chová jako stavový automat. Typická sada režimů pro typický poloautomatický montážní přípravek vypadá nějak takhle:
Aktivní režim může být vždycky jenom jeden, proto je nejlepší vyjádřit si ho jednou proměnnou, třeba číslem. Kódy režimů jsou typická situace, kdy se vyplatí využít konstanty, protože vhodně zvolený identifikátor má podstatně větší vypovídací hodnotu než jakési "magické" číslo.
Podprogram pro automatický režim u mě většinou vypadá nějak takhle:
IF Rezim=automat THEN CASE Krok OF 0:...případná úvodní inicializace proměnných... Krok:=10; 10:... ... 90:PocetVyrobenychDilu:=PocetVyrobenychDilu+1; Krok:=0;(*cyklus dokončen, jedeme znova*) ELSE Chyba:=100;(*špatné číslo kroku*) END_CASE; IF Chyba<>0 THEN Rezim:=stop; END_IF; ELSE Krok:=0; END_IF;
Volá se z hlavního programu nonstop, bez ohledu na to, v jakém režimu zrovna jsme. Důvodem je to, že některá vývojová prostředí při online zobrazení stavu programu nijak neodlišují aktivní kód od neaktivního a už se mi mockrát stalo, že jsem koukal jako puk, proč se mi program neposunul o krok dál, když je podmínka v předchozím kroku splněná, a až potom jsem zjistil, že se celý tenhle blok vůbec nevolá a ve skutečnosti jsem někde úplně jinde. Teď je to jasné: podle režimu a kroku na první pohled vidím, jestli automat jede nebo ne. Procesor se tím nezatěžuje, protože celý ten dlouhý case přeskočí.
Za krokovacím casem je podmínka, která automat vypíná při chybě. Chyba je číselná proměnná, do které se může nenulový kód dostat z mnoha různých míst: jak přímo v automatickém cyklu, tak i asynchronně od pomocných bloků hlídajících pneuválce, stav silových obvodů, motorů, tlak vzduchu a kdovíco ještě. Existují situace, kdy je potřeba na chybu reagovat složitěji (např. útěkem manipulátoru z prostoru pod lisem nebo vysláním povelu do předchozího stroje, aby sem přestal hrnout další materiál), ale ve většině případů si vystačíme s prostým zastavením stroje a zobrazením chybové hlášky.
Podprogram pro výchozí polohu vypadá podobně jako automat: taky je to nějaký pracovní cyklus zabalený do podmínky, že se má spouštět jenom v příslušném režimu. Rozdíl je v tom, že v prvním kroku nuluje Chybu a v posledním místo návratu na začátek přepíná stroj do režimu Automat.
Na hlavní program zbyde jenom ošetření úvodního nedefinovaného stavu po zapnutí, přechodů do servisního režimu a zpět a ze stopu do výchozí polohy:
IF Rezim=0 THEN (*výchozí stav po zapnutí PLC*) ...případná inicializace proměnných... Rezim:=stop; ELSIF (Rezim<>servis) AND OdemcenServisniZamek THEN Rezim:=servis; q_ZlutyMajak:=true; ELSIF (Rezim=servis) AND NOT OdemcenServisniZamek THEN Rezim:=stop; q_ZlutyMajak:=false; ELSIF (Rezim=stop) AND StisknutoTlacitkoStart THEN Rezim:=vychoziPoloha; END_IF;
Za pozornost stojí uspořádání těch podmínek: každá z nich může být splněna pouze po dobu jednoho scanu, protože sama sebe okamžitě vypne. Nemůže se tedy stát, že by se v ní stroj držel delší dobu a opakovaně nastavoval nějaké proměnné. Příkladem je ten žlutý maják signalizující servisní režim: v okamžiku odemčení se rozsvítí a při zamčení zhasne, ale mezitím zůstává jak byl, takže si s ním můžete třeba ručně blikat nebo ho může k něčemu využít třeba automatický cyklus (ne že by to chtěl dělat, jde jenom o příklad). Kdybychom naopak někam do hlavního programu napsali:
q_ZlutyMajak:=Rezim=servis;
maják by nám svítil natvrdo podle režimu a nedal by se ničím přebít, protože by to tenhle příkaz vždycky hned zase přepsal. U majáku to zrovna není nic tragického a někdy to tak opravdu chceme, ale pozor na to třeba u pneuválců. Kdykoli v online režimu zkoušíte ručně přepsat nějakou proměnnou a nejde to, je to tím, že vám ji někde něco přepisuje zpátky. Čím víc paralelně běžících úseků kódu, tím větší šance, že se navzájem o něco poperou.
Jedním z důvodů, proč se vůbec automatizace zavádí, je možnost snadno překonfigurovat stroj na jiný typ výrobku: zmáčknete pár tlačítek, vyměníte bednu s materiálem a možná nějaký přípravek a jedete dál. Receptura je obecný název pro souhrn parametrů, které definují, co vyrábíte. Může obsahovat například název výrobku, číslo programu kontrolní kamery, binární kód výměnného přípravku, dovolené rozsahy některých hodnot, různé přepínače variant pracovního cyklu a podobně, záleží na konkrétní situaci.
Některé PLC systémy mají práci s recepturami zabudovanou už od výrobce. Například u Simatiku to funguje tak, že si v paměti PLC vytvoříte proměnné vyjadřující jednu recepturu, ovládacímu panelu (HMI) dáte jejich adresy a na obrazovku si dáte předpřipravený přepínač a editační tabulku. Kdykoli na HMI přepnete recepturu, její obsah se automaticky přenese do proměnných v PLC. Výhodou je, že je s tím míň práce a že receptury nezabírají drahocennou retentivní paměť v PLC. Nevýhodou je, že předdefinované grafické prvky pro přepínání a editaci receptur ne vždy splňují vaše a zákazníkovy představy o přehlednosti a pohodlí, že je potřeba nějak vyřešit zabezpečení proti nechtěnému přepnutí v nevhodný okamžik a že to není přenositelné mezi různými PLC.
Druhá možnost je ruční přístup. Tohle je můj osvědčený algoritmus (který je samozřejmě jenom jeden z mnoha možných):
TYPE receptura:STRUCT Jmeno:STRING[40]; CarovyKod:STRING[10]; KodPripravku:BYTE; (*tohle všechno je jenom příklad*) VariantaProgramu:USINT; PouzivatLis, KontrolovatVystupek:BOOL; END_STRUCT END_TYPE; VAR RETAIN PERSISTENT Receptury:ARRAY[1..50] OF receptura; (*tohle je tabulka, kde máme všechny receptury uložené (v retentivní paměti, takže přežije i vypnutí PLC)*) END_VAR; VAR AktualniCisloReceptury:USINT:=0; (*kterou recepturu máme zrovna vybranou, slouží jako index do tabulky Receptury, nula znamená, že zatím není vybráno nic (po zapnutí stroje)*) NoveCisloReceptury:USINT:=0; (*tohle uživateli dovolíme přepisovat*) AktualniReceptura:receptura; (*kopie vybrané receptury, čte ji pracovní cyklus a řídí se podle ní a zapisuje do ní uživatel, když ji přes ovládací panel upravuje*) UlozitRecepturu:BOOL; (*povel k uložení kopie vybrané receptury do tabulky, nahazuje se nějakým tlačítkem na obrazovce*) END_VAR;
Obsluha v hlavním programu by mohla vypadat nějak takhle:
IF UlozitRecepturu THEN (*uživatel v receptuře něco změnil a chce změny trvale uložit*) IF AktualniCisloReceptury<>0 THEN Receptury[AktualniCisloReceptury]:=AktualniReceptura; (*uložení pracovní kopie na příslušnou pozici v tabulce*) END_IF; UlozitRecepturu:=FALSE; (*a hned shodíme povel, aby se nám příkaz neopakoval donekonečna*) END_IF; IF (NoveCisloReceptury<>AktualniCisloReceptury) (*uživatel si přeje změnit recepturu...*) AND(NoveCisloReceptury>=1)AND(NoveCisloReceptury<=50) (*...a nezadal nesmysl mimo rozsah tabulky*) THEN IF AktualniCisloReceptury<>0 THEN Receptury[AktualniCisloReceptury]:=AktualniReceptura; (*nejdřív automatické uložení předchozí receptury, *) END_IF; (*aby o ni uživatel nepřišel, když změny neuloží ručně*) AktualniCisloReceptury:=NoveCisloReceptury; (*přepnutí čísla - důležité, abychom poznali, že už máme přepnuto*) AktualniReceptura:=Receptury[AktualniCisloReceptury]; (*z tabulky do pracovní kopie*) Rezim:=stop; (*změna receptury za chodu je většinou nesmysl, tak radši zastavíme*) END_IF;
Výhody: absolutní kontrola nad vším, možnost vytvoření hezkého a přehledného editačního rozhraní, přenositelnost kódu. Nevýhody: tvorba editačního rozhraní je pracná a retentivní paměť někdy na uložení celé tabulky receptur nestačí.
V jednodušších aplikacích (kterých je v praxi většina) se dají chybové stavy řešit jednoduše: pokud je všechno v pořádku, normálně pracujeme, a pokud ne, všechno zastavíme a čekáme, až to obsluha vyřeší a znovu rozjede pracovní cyklus. Chybový stav obvykle odpovídá běžnému stavu "stop". Pozor, že výstupy obecně nestačí nechat jak jsou nebo paušálně všechny vynulovat, je potřeba nastavit je do nějakého rozumného a bezpečného stavu: motory zastavit, ofuky a lasery vypnout a tak. S pneuválci to není vždycky jednoznačné. Některé chceme odtlakovat, aby s nimi šlo volně hýbat a vyndat, co se v nich zapříčilo. Některé chceme zastavit tam, kde jsou. A některé naopak musíme poslat do určité polohy, kde nemůžou nikomu ublížit ani nic rozbít (třeba lisovací válec nahoru).
Složitější situace nastávají hlavně když má několik strojů spolupracovat. Když třeba robot sahá do otevřeného vstřikolisu pro díly a najednou přijde signál, že se vstřikolis zavírá, nemůžeme jen tak vyhlásit chybu, zastavit a čekat, až bude z robota placka. Musíme zůstat v automatickém režimu, z hlavního cyklu přeskočit na program pro rychlý únikový manévr a zastavit až někde v bezpečí. Na tyhle speciální případy žádný univerzální postup neexistuje, proto se jim teď nemá cenu věnovat.
Nejpohodlnější způsob hlášení chyb je číselná proměnná. Když je nulová, znamená to, že je všechno v pořádku. Když kdekoli v programu rozpoznáme nějaký chybový stav, uložíme do té proměnné příslušný kód a necháme na stavovém automatu v hlavním cyklu, aby stroj přepnul do stopu. Chybový kód můžeme rovnou použít jako index do pole s chybovými hláškami a vypsat ji na ovládací panel. Po odstranění příčiny chyby spustíme cyklus výchozí polohy, během kterého se chybový kód vynuluje.
Další způsob jsou logické příznaky: false=OK, true=chyba, jeden příznak pro každý možný chybový stav. Sadu příznaků si můžeme uspořádat třeba do bitového pole. Někteří výrobci s touto technikou počítali, třeba displeje Simatic rovnou poskytují hotové grafické okýnko na zobrazování zpráv podle bitových polí typu word (nevýhodou je titěrný font a tlačítka).
Jak se z chybového stavu dostat? Některá příznaková hlášení můžeme mít napojená přímo na příslušné podmínky, takže po odstranění příčiny chyby zhasnou sama. Číselné hlášení a obecně všechno, co se nahodilo jednorázově a teď to existuje jenom jako proměnná v paměti, musíme vynulovat. Obvykle to řeší pomocný pracovní cyklus "výchozí poloha".
Osvědčilo se mi psát na obrazovku výhradně jenom to, co je právě potřeba, dobře čitelným písmem a na jedno místo, aby lidi nemuseli pátrat, co se jich týká. Displeje většinou mají možnost ukládat texty do indexovaných polí, takže v PLC stačí měnit proměnnou "číslo hlášky" a na displeji se podle toho ukáže příslušný řádek. Často taková pole umožňují i vytvořit více jazykových variant, index je pak pro všechny stejný, takže program PLC se kvůli přepínání jazyků nemusí předělávat. Tady je příklad, jak to v praxi může vypadat:
IF i_KrytOtevren THEN CisloHlasky:=2; (*zavrete bezpecnostni kryt*) ELSIF NOT i_TotalStopUvolneny THEN CisloHlasky:=3; (*vytahnete tlacitko nouzoveho zastaveni*) ELSIF NOT i_TlakVzduchuOK THEN CisloHlasky:=5; (*otevrete privod vzduchu*) ELSIF AktualniCisloReceptury=0 THEN CisloHlasky:=6; (*vyberte recepturu*) ELSIF Rezim=vychoziPoloha THEN CisloHlasky:=VychoziPoloha.krok+100; (*hlaseni podle kroku vychozi polohy*) ELSIF Rezim=automat THEN CisloHlasky:=PracovniCyklus.krok+300; (*hlaseni podle kroku pracovniho cyklu*) ELSE CisloHlasky:=0; (*nic - bud je chyba a ta se hlasi jinde, nebo nedefinovana situace*) END_IF;
Tenhle odstaveček mám v hlavním programu a běhá v každém scanu, z žádného jiného místa na číslo hlášky nesahám, aby v tom nebyly zmatky. Za zmínku stojí čtení vnitřních statických proměnných funkčních bloků (VychoziPoloha.krok a PracovniCyklus.krok): konstanty 100 a 300 posouvají index do jiných míst seznamu hlášek, protože kroky obou cyklů používají stejné hodnoty 10, 20, 30 atd., ale psát potřebují různé věci. Za druhou zmínku stojí výraz "NOT i_TotalStopUvolneny". Proč tam není "i_TotalStopStisknuty", bez té negace? Protože signál od tlačítka má hodnotu 1, když to tlačítko stisknuté není, a protože chci ze jména proměnné poznat, co znamená, musel jsem ji pojmenovat takhle. Mimochodem: tenhle signál je jenom informativní, aby PLC vědělo, v jakém stavu stopka je. Skutečný bezpečnostní signál, natvrdo odpojující všechny silové obvody, jde po zdvojených vodičích přímo na stykače.