Pointerek

A pointerek mutatók, melyek egy címre mutatnak. A pointerekkel tehát címekre hivatkozhatunk, vagyis merkerekre, be és kimenetekre, adatblokkokra, stb. A pointerek használatával úgy címezhetünk meg adatokat, változókat, hogy a címet dinamikusan futás közben a programból módosíthatjuk vagy a programban számolhatjuk ki. Az ilyen címzést indirekt címzésnek nevezzük.
A címzés másik módja a a direct (közvetlen) címzés. Közvetlen címzéskor a címet közvetlenül beleírjuk a programba. A közvetlen címzés lehet abszolút vagy szimbolikus. Az előbbi esetben a címet adjuk meg, pl. M6.4, DB1.DBX92.0, Q14.1, stb.



Az utóbbi esetben a címhez rendelt nevet (szimbólum nevet) adjuk meg pl.: "Automata", "Engedely", #Feltetel, "kesleltetes", stb.
Ki/bemenetek, merkerek időzítők, számlálók, periféria címek, blokkok esetén ezeket a neveket a szimbólum táblázat tartalmazza és mi adjuk meg a szimbólum tábla kitöltésekor a Symbol oszlopban:





Adat blokkokon és program blokkokon belüli szimbólumokat maguk a blokkok tartalmazzák.



A két megjelenítési mód között a ikonnal vagy a View menü Display with / Symbolic Representation pontjával lehet váltani (Ctrl-Q).
A szimbolikus címzés nem más, mint az abszolút cím névvel történő helyettesítése, ami a jobb áttekinthetőséget, az olvashatóságot szolgálja, a gép számára nincs jelentősége, a PLC nem különbözteti meg a szimbolikus és abszolút formát, mert a CPU nem használ szimbólumokat (a tárgykódban nincsenek szimbólumok).
Az indirekt címzés lényege tehát az, hogy a címet amire egy utasítás hivatkozik, nem a programozó írja be a kódba amikor a programot készíti, hanem egy változó cím, amit valamilyen művelet számol ki.
Ezt a "változó" címet nevezzük pointernek (mutatónak).
A pointerrel kétféle címzést valósíthatunk meg. Area-internal és Area-crossing címzést, vagyis területen belüli. vagy területeket átfedő címzést.

Memory-indirect címzés
Olyan címzésmód, amikor a címet (pointert) egy másik memória cím tartalmazza. Ilyenkor a konkrét cím helyett a címet tartalmazó memória címet adjuk meg (ezért indirekt).
Az indirekt címet szögletes zárójel jelzi, a címet dupla szó tartalmazza.

Példa:
L P#8.7   // A pointer betöltése az ACCU1-be
T MD2 // Pointer eltárolása az MD2 merker duplaszóba
L P#11.4 // Egy másik pointer betöltése ACCU1-be
T MD6 // A pointer tárolása MD6 merker duplaszóba
A I[MD2] // Az I8.7 bemenet állapotának lekérdezése
= Q[MD6] // A lekérdezett állapot kiírása Q11.4-es kimenetre
A fenti példában először az ACCU1-be töltjük a 8.7-es címet. A terület azonosítója, mint látjuk nem szerepl az utasításban, csak a cím.
A következő sor ezt a mutatót, ami a 8.7-es címre hivatkozik, eltárolja az MD2-es duplaszóban.
Majd a 11.4-re mutató címet tölt ACCU1-be, amit MD6-ban tárol el.
Végül az MD2 pointer által címzett bemeneti bit állapotát kérdezi le. Itt már meg van adva a terület azonosítója (I) mivel azt a pointer nem tartalmazza. Ezért az I[MD2] az I8.7-re hivatkozik.
Ezután az előbb lekérdezett I8.7 állapotát (az RLO-ból) az MD6-ban lévő című kimeneti bitre másolja. Mivel az MD6-ban 11.4-es címre hivatkozó pointer van és az utasítás = Q[MD6], a Q11.4 kapja meg az I8.7 állapotát.

Area-internal címzés
Hasonlóan az előzőhöz, a pointer, itt sem tartalmazza a terület azonosítóját, csak magát a címet. A cím azonban nem egy másik címen van, hanem egy speciális regiszter tartalma és egy eltolási cím (offset) alapján jön létre. Az indirekt címet itt is szögletes zárójelek határolják. A speciális regiszter az address regiszter (cím regiszter). S7-ben ebből kettő van, az 1-es és a 2-es: AR1 és AR2. Az AR1-et általában szabadon felhasználhatjuk, de az AR2-t a rendszer is használja. Ezért az AR2-t ne használjuk, vagy tartalmát állítsuk vissza mielőtt másik blokkot hívunk meg vagy kilépünk a blokkból.

Példa:
L P#8.7          // A pointer betöltése az ACCU1-be
LAR1 // ACCU1-ben lévő cím betöltése az AR1 cím regiszterbe
A I[AR1,P#0.0] // Az AR1+0.0 ofszet című bemenet lekérdezése
= Q[AR1,P#1.1] // Az állapot AR1+1.1 című kimenetre írása
Az első két sor a 8.7-es címre mutató értéket helyez el az AR1 cím regiszterben. A következő sor lekérdezi az AR1+0.0 című bemenetet. A P#0.0 az utasításban ofszetet jelent, ami hozzáadódik az AR1-ben lévő címhez. Mivel most az ofszet 0.0, az AR1 változatlan marad és az ofszet hozzáadása után is a 8.7-es címet tartalmazza.
A kódba fixen beleírtuk a terület azonosítóját (I) ezért a címzett bit mindenképpen az input területre esik.
Ezután az = Q[AR1,P#1.1] következik, ami egy kimeneti bitet fog címezni, de itt már van ofszet is, mégpedig 1.1.
Nyilván nem nehéz kitalálni, hogy az ofszet ponttal elválasztott jobb oldali része a bit címre vonatkozó eltolás, a bal oldali része pedig a byte címre vonatkozó eltolás.
Mivel az AR1-ben 8.7 van, a hozzáadott eltolással 10.0 címet fogunk kapni.
Hogy miért? Mert a bit cím nem lehet 7-nél nagyobb, hiszen egy byte-ban nincs 8-as bit. Így összeadáskor keletkezik egy átvitel a byte címre:
  1.4
+ 0.2
= 1.6

0.6
+ 0.2
= 1.0
Így tehát ha a 8.7-hez 1.1-et adunk, 10.0 lesz, A beolvasott bemenet állapotát így a Q10.0 kimenet kapja.

Area-crossing címzés
Terület átfedő címzés az előzőtől abban tér el, hogy a pointer tartalmazza a terület azonosítóját is és a címet is

Példa:
L P#I8.7          // A pointer betöltése az ACCU1-be
LAR1 // ACCU1-ben lévő cím betöltése az AR1 cím regiszterbe
L #Q8.7 // A másik pointer betöltése az ACCU1-be
LAR2 // ACCU1-ben lévő cím betöltése az AR2 cím regiszterbe
A [AR1,P#0.0] // Az AR1+0.0 című bemenet lekérdezése
= [AR2,P#1.1] // Az állapot AR2+1.1 című kimenetre írása
Ilyen címzést használva programból szinte bármilyen terület címét elő tudjuk állítani.
A példában AR2 is szerepel, de az AR2-t a rendszer is használja. Ezért az AR2-t ne használjuk, vagy tartalmát állítsuk vissza mielőtt másik blokkot hívunk meg vagy mielőtt kilépünk a blokkból.

Mire jó az indirekt címzés?
Akkor hasznos, amikor a címet "gazdaságosabb" inkább kiszámítani, mint fixen beírni. Konkrétan azért, mert fix címekkel adott esetben rengeteg összehasonlítást kellene elvégezni.
Erre rengeteg példát fel lehetne hozni, nézzünk egyet:
Ha egészen pontosan tudni akarjuk mi a fenére lehet jó egy ilyen funkció, akkor egy kis kitérőt kell tennem:

Képzeljünk el egy olyan feladatot, ahol mérés eredményét kell kiértékelni. A mérésre 10 tűréshatár van megadva, és minden tűréshatárra van egy számláló. Amikor jön egy munkadarab, a berendezés megméri, a mérési eredményt ezután összehasonlítgatja a tűréshatárokkal, és amelyiknek megfelel, annak a számlálóját egyel növeli. Így adott számú munkadarab mérése után látható lesz, hogy abból az egyes tűréshatároknak hány darab felelt meg. Esetleg ki is válogathatja.
Célszerű a tűréshatárok beállításait módosíthatóvá tenni (és nem fixen beleírni a programba) hogy a kezelő szabadon meghatározhassa őket pl. egy OP-ról.

Ha ezt indirekt címzés nélkül akarjuk megoldani, akkor 20 db összehasonlításra van szükség.






Az adatblokk szerkezete, ami a számlálókat és a tűréshatárokat tartalmazza, a kövtkező:

 

A 10-es értékcsoportoknak folytonosan kell elhelyezkednie a DB-ben! Tehát a 10 számláló egymás után kell hogy legyen, növekvő sorrendben, a 10 db minimum határérték szintén, stb.
Azonban a 10-es csoportok (táblázatok) bárhol lehetnek a DB-n belül, mivel ezek kezdőcímét adjuk meg a blokk hívásakor, ami a következőképpen néz ki:


A Dummy nevű változók csak a példa kedvéért vannak a DB-ben, hogy a 3 táblázat ne közvetlenül egymás után legyen.
A blokk tesztelésére egy WinCC Flexible project is készült.



A slider (csúszka) reprezentálja a blokk #Value bemenetét. A képernyőn beállítható mindegyik határérték és láthatóak a számlálók is.

Hátrányok
Minden jóban van valami rossz!
Ezúttal is így van ez. Először is az indirekt címzés megnehezíti a program megértését mások (és néha magunk) számára. A program olvashatósága tehát romlik, különösen ha azt is megemlítem, hogy ilyen címzések STL kódban szoktak előfordulni, ami már önmagában is nehezen áttekinthető. Az indirekt címzés pedig tovább rontja ezt. A rosszabb áttekinthetőség nehezíti a programban a hibakeresést is.
Gondoljunk csak bele: Egy hibásan kiszámított cím, ami egy DB-ben címez, nem csak olyan lehet ami kívül mutat a DB-n, hanem olyan is, ami létező, de rosz címet ad. Ez sokkal alattomosabb hiba, mert olyan hibajelenségeket okozhat, amit nem tudunk összefüggésbe hozni a hiba igazi okával. Pl. a fenti példában lévő számlálást valósítjuk meg, de a DB1-ben vannak még egyéb adatok is, pl. beállítások. És hibás címzés miatt felölíródik egy beállítás, ami azonnal a berendezés hibás működését eredményezi. teszem azt nullára ír egy nyomás küszöb értéket, ami miatt a rendszer azonnal túlnyomás hibára fut. Az azért elég vicces tud lenni, hisz a hibát egy olyan programrész okozta, aminek semmi köze nincs ahhoz a nyomáshoz.
Ha indirekt címzéssel operálunk ami után megmagyarázhatatlan jelenségeket lehet tapasztalni, akkor kezdhetünk ilyesmire gyanakodni. :)
De semmi pánik, az indirekt címzést meg lehet csinálni jól is!

Másodszor a keresztreferencia táblázatba nem kerülnek bele azok a címek, amelyeket indirekt címzéssel érünk el. Ennek az az oka, hogy a keresztreferenciát a fejlesztői környezet generálja a forrásprogramból. Az indirekt hivatkozás címe pedig csak futás közben derül ki. Így a Step7 nem képes előre megállapítani hogy az indirekt címzés milyen címekre fog majd hivatkozni amikor a program futni fog.
Ezt nem is lehet előre kiszámolni, hiszen a kalkulált cím függhet olyan körülményektől, amelyek nem ismertek előre.
És ha nincs keresztreferenciában, akkor a Go To Location ablakban sem lesz, mivel az is a keresztreferencia adatokat használja.



A példában látható, hogy a DB1.DBW1-et nem mutatja a Go To Location ablak, pedig a program írja és olvassa ezt a címet. Csak épp indirekt címzéssel.


Nagyon fontos, hogy az indirekt címzést tartalmazó programokat igen gondosan le kell tesztelni extrém értékekkel is! Ha nem így járunk el, esetleg jóval később egy hibás értékből rossz címet számol ki és leállítja a CPU-t. Volt már ilyen, és lesz is. De legyen minél kevesebb :)


Kapcsolódó írások:
S7 300/400 indirekt címzés, pointerek
Az ANY paramétertípus

Felhasznált irodalom:
Programming with STEP 7



Szirty