Blokk hívás, változók és paraméter átadás


S7 PLC-ben egy vezérlő program több program blokkban is lehet. Természetesen megírhatjuk az egész programot egyetlen egy blokkban is, de nagyobb programokban célszerű a program egyes részeit külön blokkban elhelyezni. Áttekinthetőbb lesz és a szorosan össze nem tartozó részek jobban elkülönülnek. Külön blokkokba nem csak egy berendezés funkcionálisan elkülönülő részeit vezérlő programokat írhatjuk, hanem rész feladatokat ellátó program részleteket is.
Pl. számításokat, adat konverziókat, függvényeket, stb. Az ilyen blokkoknál gyakran szükség van adat átadásra és adatok/állapotok tárolására. A blokknak tehát adatokra van szüksége, amelyekkel foglalkozik, majd az eredményt valamilyen módon közli a hívó blokkal.
Ez a cikk arról szól, hogyan történhet ez az adat átadás.

A példa
A különböző módszerek bemutatásához egy egyszerű példát fogok használni.
A példaprogram két boolean típusú bemeneti paramétert igényel. Ezek neve a továbbiakban az egyszerű megkülönböztetés érdekében legyen UP és DN. Továbbá van 6 boolean kimeneti paramétere. Ezeket nevezzük S1, S2, S3, S4, S5 és S6-nak.
Az UP bemeneti logikai változóval a 6 db kimeneti változót egymás után, sorban be lehet kapcsolni. A blokk meghívásakor ha aktív (TRUE) az UP, akkor bekapcsolja S1-et ami úgy is marad. A következő hívásnál ha az UP még mindig aktív és S1 már be van kapcsolva, akkor bekapcsolja S2-t és így tovább S6-ig. Ha a hívásnál UP aktív és már mind a 6 kimenet be van kapcsolva, akkor nem csinál semmit. Amikor a DN bemenet aktív a kimeneteket sorban kikapcsolja. Tehát ha pl. minden kimenet aktív, akkor kikapcsolja S6-ot, ha S1-S5 aktív, akkor S5-öt stb. Ha UP és DN is inaktív (FALSE) nem változtat a kimenetek állapotán.
 

1.

A legelső esetben, ami a leginkább hasonlít a "hagyományos" programokra a blokknak nem adunk át semmilyen paramétert, hanem fixen meghatározzuk azokat a címeket amikkel dolgozni fog. Vagyis "bele betonozzuk" a blokkba. Az UP és DN "bemenet" egy-egy bemeneti bit lesz, míg az S1-S6 "kimenet" egy-egy merker bit lesz, amit konkrétan megcímzünk a blokkban:







A példában a fenti két network-öt az FC1- blokkban helyztem el.
Mivel a blokkban az UP és DN nem élvezérelt, élvezérelté kell tennünk a blokk hívása alőtt. Különben ha aktív az UP vagy DN és a blokkot minden PLC ciklusban meghívjuk, akkor gyakorlatilag azonnal be vagy ki kapcsolja az összes kimenetét (S1-S6).



Így már megfelelően működik.
Ez a módszer semmiben nem tér el attól, mint ha a blokkban lévő programrészt beleírtuk volna a programunkba ahelyett hogy külön blokkba tesszük.
A legnagyobb hátránya az, hogy a blokkba helyezett, kimeneteket sorban be és kikapcsoló funkció ki és bemeneti paraméterei fixek. Ezért ezt a blokkot nem tudjuk újra felhasználni arra hogy más bemenetekkel és kimenetekkel csinálja ugyanezt. Ehhez a blokkot le kell másolni és a másodpéldányban át kell írni a ki és bemenetek hivatkozásait.

2.

A másik módszer a tényleges paraméter átadás. Erre a Step7 biztosít lehetőséget. A blokkokhoz be és kimenetek rendelhetünk hozzá a blokk interfész részében.
Az interfész részhez a LAD/STL/FBD szerkesztő ablak felső részének lejjebb húzásával férhetünk hozzá (ha nem látható éppen).
Először deklaráljuk a blokk bemeneteit az IN szekcióban, majd a kimeneteit az OUT szekcióban.



A be és a kimenetekre ezután a blokkon belül azok nevével hivatkozhatunk, a név elé írva egy # karaktert.
Az 1-es példában lévő program tehát az alábbi módon módosul:







A blokk hívásánál szembeötlő a különbség az előző megoldáshoz képest:



Itt a blokknak (ha létrában vagy FBD-ben van megjelenítve a hívás) "kivezetései" vannak. Illetve több van neki mint eddig.
Ezek a be és kimenetek.
A paraméter átadás lényege egyszerű. A blokkon belül deklarált minden egyes be és kimeneti változó számára létrejön a blokknak egy paramétere, ami a blokk hívásakor annak LAD/FBD szimbóluma kivezetésként mutat. Ide a blokkon "kívül" (híváskor) írhatjuk be ténylegesen azokat a címeket, amelyekkel a blokk belül dolgozni fog.
A blokknak híváskor megadott címek és értékek a blokkon belül a paraméter nevével helyettesítődnek. A fenti példa szerint S1 paraméternek a blokk hívásakor M1.0 merker bitet adtuk meg. Ezért a blokkon belül az #S1 kimeneti változó M1.0 merker bitre fog hivatkozni. Minden ami a blokkon belül #S1 változóval történik, az M1.0 bittel fog történni. #S1 tehát megfelel a blokkon belül M1.0-nak.

Miért jobb ez, mint az előző megoldás? Azért, mert így a blokkot változtatás nélkül többször is felhasználhatjuk úgy, hogy más ki és bemeneti paraméterekkel dolgozzon.



Ez azt jelenti, hogy a blokknak más paramétereket adunk. Az első hívásnál az M1.0-M1.5 biteket léptette fel és le a az I0.0 és I0.1 bemenet. A második hívásnál az M10.0-M10.5 biteket lépteti az I0.2 és I0.3 bemenet. Méghozzá egymástól függetlenül. Tehát ha az I0.0 bemenet kapcsol be, akkor M1.0-M1.5 merkerek közül kapcsol be a következő, de ha I0.2 bemenet aktiválódik, akkor M10.0-M10.5 merkerek közül kapcsol be a következő, miközben M1.0-M1.5 merkerek állapota nem változik.
A két hívás pontosan ugyanazt csinálja (hiszen ugyanaz a blokk fut le mindkettőnél) de egymástól függetlenül. Más bitekkel dolgozik és ezért a másik hívástól eltérő állapottal is rendelkezhet. A blokkok ilyen használata hatékony, amikor a blokkal egy olyan rész feladatot oldunk meg, amelyet a programban több változóval is el kell végezni. Vagy a blokk olyan általános funkciót tartalmaz amit több különböző projectünkben is használunk. Az új projectbe csak be kell másolni a más kész blokkot és megfelelően paraméterezve meg kell hívni.

Fontos megjegyezni, hogy az ilyen funkció blokkokat, amiket várhatóan többször is felhasználunk egy programban külön teszteljük le jó alaposan.
Ha monitorozunk egy olyan blokkot, amit több helyről is hív a programunk, akkora nem biztos hogy azt fogjuk látni amit szeretnénk.. A többszörös hívás miatt a blokkon belüli változók állapota többféle lehet (minden hívásnál más és más). Ha mégis felmerül a gyanú, hogy egy ilyen blokkban van a hiba, hibakeresés előtt gondoskodjunk róla, hogy csak egy hívás legyen aktv, amíg a hibakeresést végezzük.

3.

Az előző két példában a fel és a lefele léptetés bemenetnek felfutó impulzust állítottunk elő még a blokk hívása előtt, mert a blokk bemenete nem élvezérelt.
Ha a él helyett folyamatos állapotot adunk az UP vagy DN bemenetre, akkor a blokk szinte azonnal be vagy kikapcsolja az összes bemenetét. Pontosabban egymás után kapcsolja őket ekkor is, de minden PLC ciklusban elvégzi a következő be vagy kikapcsolását, tehát igen gyorsan (5-10ms-onként egy kimenet) ezért úgy tűnik hogy egyszerre mindet be vagy ki kapcsolja.
Ezért kell a bemenet felfutó élekor létrehozott impulzust átadni a blokk bemenetére (természetesen lefutó él is lehetne).
Akkor miért nem írjuk meg a blokkot úgy, hogy eleve élvezérelt legyen? Megtehetjük, de ehhez szükség van egy változóra ami nem bemenet és nem is kimenet, hanem a blokk használja fel belül a saját céljaira.
Nyilván észrevettük, hogy a blokk interface részében nem csak in meg out szekciók vannak, hanem van pl. TEMP is.



A TEMP-ben is létrehozhatunk változókat, ahogy IN és OUT-ban is, de ezek nem fogják a blokk ki vagy bemenetét képezni. A TEMP változók csak a blokkon belül létező, ún lokális változók.
Részeredmények tárolására használhatjuk őket. Van azonban egy fontos dolog, amit tudni kell róluk! A TEMP változók tartalma csak a blokk lefutásáig marad meg. Amikor a blokk újra lefut, tartalma már határozatlan lesz. Tehát amennyiben egy ilyen változónak értéket adunk, azt a blokkban fel kell használni mielőtt a blokk lefut. Ne felejtsük el, hogy a PLC programunk ciklikusan újra és újra lefut másodpercenként sok százszor. A blokk lefutását úgy kell érteni, hogy ebből a százból egy futás. Egy TEMP változó tartalma tehát másodpercenként több százszor elvész ha úgy tetszik.

Sajnos emiatt a TEMP változó a mi példánkban nem alkalmas a felfutó él impulzusának előállítására.
Az alábbi programrész tehát rossz!



Teljesen kézenfekvő pedig hogy így csináljuk.
Miért rossz?
Mert a fel és lefutó él impulzusának előállításához használt P utasítás számára szükséges változó tartalmának meg kell maradnia a blokk következő hívásáig. Mivel az él érzékelését úgy végzi, hogy a P utasítás előtti logikai sor eredményét összehasonlítja a P utasítás paramétereként megadott átmeneti változó értékével és ha az utóbbi FALSE míg az előbbi TRUE,  a P eredménye TRUE lesz (felfutó él), majd az átmeneti változóba tölti a P előtt lévő logikai eredményt.
Más szóval a P azt vizsgálja, hogy az előtte lévő logikai feltétel eredménye változott-e (FALSE -> TRUE átmenet) az előző végrehajtás óta. A változót, amit P-nek meg kell adnunk, ezt az előző állapotot tárolja el. A blokkon belül deklarált TEMP változók elvesztik a tartalmukat a következő hívásig, így nem alkalmasak a felfutó (vagy lefutó) él detektálására.

Olyan változót kell tehát a P utasításnak megadni, ami nem felejt.
A problémára több megoldás is kínálkozik. A legegyszerűbb az, amit az első példában is láthattunk: közvetlenül adunk neki egy merker bitet, avagy bebetonozunk egy merkert. Sajnos így megint felmerül a probléma, hogy nem használhatjuk emiatt ezt a blokkot a programunkban több helyen, mert a fix hivatkozás miatt nem tudunk neki másik átmeneti változót megadni. Ahhoz, hogy a blokk többször felhasználva jól működjön, minden egyes híváshoz másik átmeneti változó szükséges a P utasítás számára, hisz más más állapotokat kell tárolnia.

Ezt a változót paraméterként is megadhatjuk a blokknak. Így a blokk a következőképpen módosul:







A blokk hívása:


 
Az él figyeléséhez szükséges két változót az UPTMP és DNTMP paraméterben lehet megadni a blokknak. Ezeket a blokk belül felhasználja a P utasításhoz. Amennyiben a blokkot többször is hívjuk más paraméterekkel,  UPTMP és DNTMP paraméterben is más változókat kell megadni neki. Továbbá a programban máshol nem használhatjuk fel ezeket (az állapotukat nem szabad megváltoztatni máshol, mert akkor hibásan fog működni az él figyelés).

4.

Ezzel még mindig nem fogytunk ki a lehetőségekből, mert használhatunk FB blokkot is a programblokkhoz.
Az FC-hez képest a legszembetűnőbb különbség az, hogy az FB mellé ún instance adatblokkot lehet rendelni. Ez az adatblokk fogja tárolni az FB változóit (kivéve TEMP).
Ha létrehozunk egy FB-t, a blokk interface részében láthatjuk, hogy megjelent egy STAT szekció is.



Minden változó számára, amit az IN, OUT, IN_OUT és STAT-on belül hozunk létre, létrejön egy vele azonos nevű és típusú bejegyzés az FB-hez rendelt instance DB-ben is.
Készítsük el az FB-t.
IN és OUT változói azonosak lesznek a korábbi példákban szereplő változókkal, de az UPTMP és DNTMP változókat a STAT szekcióban hozzuk létre és ne az IN_OUT-ban:



A program 1-2 network-je teljesen azonos a 3-as példában szereplővel, így azt nem rakom újra ide.
Amikor a blokkot hívását írjuk a programba, töltsük ki a ki és bemenetekhez tartozó változókat.



A blokk szimbólum tetején piros kérdőjel figyelmeztet arra, hogy a blokk számára meg kell határoznunk egy adatblokkot.
Ha ezt a blokkot nem kézzel akarjuk elkészíteni, akkor a legegyszerűbb, ha ide egyszerűen beírjuk egy olyan DB számát, ami még nem létezik. Én a DB10-et adtam meg. Mivel ez a blokk még nem létezik, erre figyelmeztet egy ablakban és egyből fel is ajánlja, hogy létrehozza. Ha yes választ adunk, a DB10 létrejön.



Ha most belenézünk a DB10-be, láthatjuk, hogy minden be és kimenetnek, valamint STAT változónak készített egy bejegyzést.



Ez a DB fogja tárolni futás közben az FB1 belső változóinak értékét, így azok nem vesznek el amikor a blokk lefut. A következő ciklusban (a blokk következő hívásakor) a változók tartalma az lesz mint ami a blokkból való kilépéskor volt.

Figyelem!  Az FB-ben is lehet TEMP változó, annak tartalma ugyanúgy elvész a blokk lefutása után mint az FC esetében!

A fel és lefutó él előállításához most már nem szükséges paraméterként átmeneti tárolót megadnunk, az automatikusan a DB-ben lesz tárolva.
Ha blokkot többször is hívjuk, akkor minden hívásnál másik instance DB-t kell megadni neki:



Kapcsolódó írások:
Az S7 PLC programozása


Szirty