Mutatók, tömbök, szubrutinok

[Szerzői jogok]

A fejezet tartalma:

Mutatók használata C programokban

Az eddigi fejezetekben csak különböző pontosságú (bitszámú)  változókkal foglalkoztunk: char (1 bájtos), int (2 bájtos), long (4 bájtos), melyek mindegyike lehet előjeles vagy előjel nélküli. Van azonban a változóknak egy olyan típusa, amelyek nem egy adatot tartalmaznak, hanem egy másik változóra mutató memóriacímet tárolnak. Az ilyen változókat mutatóknak nevezzük, s fontos szerepük van az eljárások vagy függvények esetén a paraméterátadásnál, a változótömbök vagy a veremtár kezelésénél. Tulajdonképpen ilyen mutató típusú változónak tekinthető a mikrovezérlő programszámlálója (PC) is.

Az, hogy a mutató típusú változók milyen méretűek (hány bit szélességűek) az általuk címzett memória címtartományától függ. A PC programszámláló például 23 bites. Az adatmemória címtartománya kisebb, 16 bites, ezért az adatmemóriát megcímző mutatók is 16 bitesek. Bizonyos utasítások esetében lehetőség van indirekt címzés használatára. Ilyen esetekben a W0..W15 általános célú regiszterek valamelyike működik mutató típusú változóként.  Különösen fontos szerepe van a W15 regiszternek, ugyanis ez a regiszter a veremtár mutatója. Mielőtt a regiszterek mutatóként történő használatával foglalkoznánk, nézzük meg, hogy a  C nyelvben hogyan történik a mutatók használata!

A C nyelvben a mutató típusú változó többnyire egy másik változó, vagy változótömb elérési címét tartalmazza. A tömbök folytonos memóriaterületen egymás után elhelyezett, több, azonos típusú változót jelentenek. A tömbben elhelyezett változókat a tömb elemeinek nevezzük, s külön névvel nem rendelkeznek, hanem a tömb nevével, és a tömbön belüli (nullával kezdődő) sorszámmal hivatkozhatunk rájuk. Például a[5] az a[] tömb hatodik eleme, vagy b[12] a b[] tömb tizenharmadik eleme. A mutató típusú változók egyik haszna az, hogy egy tömb elemein végiglépkedhetünk vele. A másik hasznuk az, hogy eljárás- vagy függvényhívásnál megkönnyítik a paraméterek átadását. Például egy hosszú tömb helyett csak annak kezdőcímét adjuk át.

Példa mutató  használatára (16 bites változókkal)

Az alábbi ábrán egy nagyon egyszerű C programot mutatunk be, amit az MPLAB szimulátorában ki is próbálhatunk. A program elején két 16 bites, előjel nélküli változót és egy olyan mutató típusú változót definiálunk, ami 16 bites előjel nélküli változóra mutat. A program megértéséhez az alábbi jelölésekkel kell megismerkednünk:


Az MPLAB View menüjében megnyitott File Registers ablakban követhetjük nyomon a program működésének eredményét. A File Registers ablakot célszerű Szimbolikus Nézetbe kapcsolni, s a 0x800 címtől kezdve láthatjuk a változók szimbolikus nevét, a memóriabeli címet, s a változó tartalmát. Az ábra jobb oldalán két pillanatfelvételt montíroztunk össze, amelyek a k = *p utasítás előtti, és utáni állapotot mutatják be.

Amint látjuk, a p mutató típusú változóba a j változó címe(0x800) került, majd a k = *p utasítás hatására a p mutató által hivatkozott j változó tartalma másolódott be a k változóba, ugyanúgy, mintha a k = j értékadást hajtottuk volna végre. Sok hasznunk ugyan nem volt a mutató használatából, de talán sikerült a működését megértenünk.

Példa mutató  használatára (32 bites változókkal)

A következő példa csak annyiban tér el az előzőtől, hogy a változók itt előjel nélküli, 32 bites típusúak, s ilyen változóra mutat a p mutatónk is.  Ennél a példánál is két pillanatfelvételt mutatunk be, a k = *p utasítás előtti, és utáni állapotról.


Megjegyzés: Nézzük meg figyelmesen a File Registers ablakban a változók memóriabeli címeit, s vegyük észre, hogy a p mutató ugyan 32 bites változóra mutat, de csak 16 bites méretű, hiszen az adatmemória címzéséhez csak 16 bitet használ a PIC24 mikrovezérlő. Jegyezzük meg tehát, hogy a mutató mérete független attól, hogy milyen méretű vagy típusú változóra mutat!

Mutatók használata assembly programokban

A fenti két programot egyenként is átírhattuk volna assembly nyelvre, de a fenti két programból késztettünk egy összevont C programot, s kíváncsiságból megnéztük azt is, hogy mit generált belőle a fordító. Az optimalizálást természetesen letiltottuk, mivel az ilyen értelmetlen minta programocskákat nagyon megkurtítaná az optimalizálás (pl. az egymás utáni két-két értékadás a b és a k változónak nyilvánvalóan fölösleges, az első kihagyható).

Az alábbi ábrán az összevont C programot láthatjuk, s megnyitottuk a Disassembly Listing és a File Registers ablakokat is. Az utóbbiban a k = *p értékadás előtti pillanatfelvétel látható. A Disassembly Listing ablakban a fordító által generált utasítássorozat látható, melyet most nem elemzünk, hiszen most úgyis arra készülünk, hogy másikat írjunk helyette.



Írjuk át a fenti C programot assembly nyelvre! A helyfoglalásnál arra kell ügyelni, hogy az int és a mutató típusú változóknak két bájtnyi, a long típusú változóknak pedig négy bájtnyi terület kell lefoglalni. A fenti C program első részében a 16 bites változók inicializálása, és az m mutató közvetítésével lezajló b = a értékadás (ezt helyettesíti az m = &a; b = *m; utasítássorozat) történik, ennek átírásával kezdjük az assembly programot. A változók kezdőértékkel való feltöltése semmi újdonságot sem tartalmaz, a korábbi fejezetekben tanult módon , MOV utasításokkal történik.

A kezdőértékek megadásához hasonlóan implementálhatjuk az  m = &a utasítást is, a mov #0x802,W0 és a mov W0,m utasításokkal, ahol a #0x802 adat az a változó címét jelenti (mellesleg mov #0x802,W0 helyett írhattuk volna egyszerűen azt is, hogy mov #a,W0 ).

A b = *m utasítás implementálása már tartalmaz újdonságot: W0-ba betöltjük m tartalmát (vagyis az a változó címét), W1-be betöltjük a b változó címét, majd a mov [w0],[W1] utasítással a b változóba írjuk az m mutató által hivatkozott értéket.  Lefordítás után az MPLAB szimulátorában próbáljuk is ki a programot!

Az alábbi ábrán a File Registers ablakok a mov [w0],[W1] utasítás előtti és utáni állapotot mutatják. Az érdekesség kedvéért a regiszterek állapotát is nyomon követhetjük a Special Function Registers ablak megnyitásával, s az utasításokon végiglépkedve (F8-as gomb) megfigyelhetjük W0 és W1 regiszterek (amelyeket az MPLAB WREG0 és WREG1 néven mutat) változásait.



A fentiekhez hasonlóan írhatjuk meg a program második felét is (lásd a következő ábrán, az "uint32 változókkal" megjegyzéssel kezdődő szakaszt). A 32 bites változók kezdőértékének beállítása természetesen két lépésben történik (LSW és MSW feltöltése, a sorrend tetszőleges). A mutató beállítása ugyanúgy történik, mint az előző esetben, hiszen a mutató mérete nem függ az általa hivatkozott változók méretétől.

A k = *p értékadásnál újabb trükköt tanulunk: mov [W0],[W1] helyett most a mov [W0++],[W1++] utasítást használjuk. Ez a mov [W0],[W1]-nek megfelelő adatmozgatás után mind W0 mind W1 értékét megnöveli (mégpedig a 16 bites szóhossznak megfelelően kettővel!). A mov [W0++],[W1++] utasítás tehát a

mov [W0],[W1]     ; j.LSW -> k.LSW
add #2,W0            ; vagy inc2 W0,W0
add #2,W1            ; vagy inc2 W1,W1

utasítás sorozatnak felel meg, csak mindezt egyetlen utasításban, egyetlen utasításciklusnyi idő alatt hajtja végre. A mutatók megnövelésének az az értelme, hogy W0 most pontosan j.MSW-re, W1 pedig k.MSW-re mutat, tehát minden készen áll arra, hogy egy újabb  mov [W0],[W1] utasítással a 32 bites j változó magasabb helyiértékű felét is átmásoljuk k magasabb helyiértékű felébe.

Mutatunk még egy trükköt: ha itt a mov [W0],[W1] utasítás helyett a mov [W0--],[W1--] utasítást használjuk, akkor az adatmozgatás után W0 és W1 kettővel csökkennek, vagyis visszaállnak az eredi címre, j és k kezdőcímére. Így tehetjük "újrafelhasználhatóvá" a mutatókat. Ennek akkor van jelentősége ha egy mutatót többször is fel akarunk használni a program (vagy egy programrész) futása során. Érdemes tehát megjegyezni ezt a fogást!

Az alábbi ábrán a File Registers ablakok a k = *p  32 bites értékadó utasítás előtti és utáni állapotot mutatják. Természetesen, érdemes az egész programon utasításonként végig lépkedni, megfigyelve a regiszterek és a változók tartalmában bekövetkező változásokat.


A PIC24 indirekt címzésmódjainak áttekintése

Az indirekt címzésmódok áttekintésének segítésére az alábbi táblázatban foglaltuk össze a regiszter indirekt címzések lehetséges változatait. Az adatmozgató utasításokon kívül sok más utasításnál is használhatunk indirekt címzést. Azt,  hogy konkrétan melyik utasításnál és milyen esetleges korlátozással használhatjuk az indirekt címzést, a dsPIC30F/33F Programmer's Reference Manual 5. fejezetében, az utasítások részletes leírásánál nézhetjük meg.
Szintaxis Effektív cím (EA) és a járulékos műveletek Címzésmód
elnevezése
Bájt  (.B) Szó  (.W) Duplaszó  (.D)
[Wn] EA = (Wn) EA = (Wn) EA = (Wn) Regiszter indirekt
[++Wn] Wn+=1; EA = (Wn) Wn+=2; EA = (Wn) Wn+=4; EA = (Wn) Regiszter indirekt,
előzetes növeléssel
[--Wn] Wn-=1; EA = (Wn) Wn-=2; EA = (Wn) Wn-=4; EA = (Wn) Regiszter indirekt,
előzetes csökkentéssel
[Wn++] EA = (Wn); Wn+=1 EA = (Wn); Wn+=2 EA = (Wn); Wn+=4 Regiszter indirekt,
utólagos növeléssel
[Wn--] EA = (Wn); Wn-=1 EA = (Wn); Wn-=2 EA = (Wn); Wn-=4 Regiszter indirekt,
utólagos csökkentéssel
[Wn + Wb] EA = (Wn) + (Wb) EA = (Wn) + (Wb) nem használható Regiszter indirekt,
regiszter eltolással
[Wn + Slit10] EA = (Wn) + Slit10 EA = (Wn)+2*Slit10 nem használható Regiszter indirekt,
eltolással
Megjegyzések:
  1. A táblázat utolsó sorában bemutatott "regiszter indirekt címzés, eltolással" esetében egyidejűleg vagy csak a forrás vagy csak a cél címezhető indirekten, s a használata a mov{.b} [Ws+Slit10],Wd és a mov{.b} Ws,[Wd+Slit10] utasításokra korlátozódik. A Slit10  elnevezés legfeljebb 10 bites előjeles konstans (literális) értéket jelöl, mellyel bájtműveleteknél -512..511, szószélességű műveleteknél pedig -1024..1022 (csak páros szám lehet!) mértékű eltolási címet adhatunk meg.
  2. A duplaszavas adatmozgatásnál (MOV.D Wns,Wnd) egyidejűleg vagy csak a forrás vagy csak a cél címezhető indirekten, s eltolás sem alkalmazható.

Tömbök használata C programokban

Az alábbi mintapéldában egy négyelemű tömböt definiálunk, s a tömb elemeihez kezdőértékeket is rendelünk (a programhoz a C fordító és a linker automatikusan hozzáfűzi azt a gyári eljárást, ami gondoskodik a változók kezdőértékének beállításáról). A tömbelemeket 8 bites, előjel nélküli bájtoknak deklaráltuk, így a négy tömbelem összesen két 16 bites adatszót foglal le, s az egyes bájtok sorban egymás után helyezkednek el. Az alábbi ábrán láthatjuk, hogy a Files Registers ablakról a program futása során több pillanatfelvételt is készítettünk, így az adatmemóriában történő változásokat nyomon követhetjük. Az ábra jobb felső sarkában a változók kezdőértékeinek beállítása utáni állapot látható. A v[] tömb a 0x800 címen kezdődik. Az első adatszó alacsonyabb helyiértékű felében (LSB) található az első tömbelem, v[0], melynek tartalma 0xFB. A magasabb helyiértékű bájtban (MSB) található a második tömbelem: v[1]=0x42.

A többi ablakban az egyes utasítások végrehajtása utáni állapotokat rögzítettük. Piros szín jelzi, hogy melyik memória rekeszben volt változás. Az alábbi programban tömbelemek közötti értékadásra és tömbelemek mutatóval történő címzésére láthatunk példákat.

Az utasítások értelmezése:
v[1] = v[0]; - a v tömb első elemének tartalmát átmásolja a második elembe.
p = &v[1];   - a v tömb második elemének címét (0x800 + 1*1 = 0x801) másolja a p mutatóba
p++;  - a p mutató tartalmát megnöveli a hivatkozott változótípus méretével (itt eggyel)
v[1] = *p; - a v tömb második elemébe bemásoljuk a p mutató által hivatkozott elem (v[2]) tartalmát

Megjegyzés: Mutató típusú változó esetén az inkrementálás mindig a hivatkozott változó méretével növeli a mutató tartalmát, így a a fenti példában p++ ugyanazt jelenti, mint p = p + sizeof(uint8) vagy p = p + 1. Hasonlóan működik a tömb elemeinek indexelése is: &v[i] egy olyan mutatóérték, amely a tömb kezdőcíméhez a tömbelem méretének i-szeresét adja hozzá. Tehát &v[i] = &v[0] + i*sizeof(<v[] típusa>). Ebben a példában v[] típusa uint8, ami  1 (ne feledjük, hogy az adatmemória címzése bájtonként történik).

Uint16 típusú tömb használata

A fenti programot módosítottuk  egy kicsit, most a v[] tömb elemei 16 bites előjel nélküli számok, így egy-egy adatszónyi területet foglalnak le. A négyelemű tömb tehát az előző példához képest kétszer annyi helyet foglal a memóriában. Most kevesebb állapotról készítettünk pillanatfelvételt, de a program megértése az előző példa után már gyerekjáték. 

Az utasítások értelmezése:
v[2] = v[0]; - a v tömb első elemének tartalmát átmásolja a harmadik elembe.
p = &v[1];   - a v tömb második elemének címét (0x800 + 1*2 = 0x802) másolja a p mutatóba
p++;  - a p mutató tartalmát megnöveli a hivatkozott változótípus méretével (itt kettővel!)
v[1] = *p; - a v tömb második elemébe bemásoljuk a p mutató által hivatkozott elem (v[2]) tartalmát

Tömbök kezelése assembly programokban

A fentiekben példákon keresztül megismerkedtünk a tömbök használatával és kezelésével. Ezen ismeretek birtokában vágunk neki egy komolyabb feladatnak, s egyúttal megnézzük azt is, hogy assembly nyelven hogyan történik a tömbök kezelése, a fejezet elején tanult indirekt címzésmód felhasználásával.

Két tömb összeadása

Két tömb elemeinek páronkénti összeadása a c[i]=a[i]+b[i] képlet alapján a matematikában és fizikában tanult vektoriális összeadással analóg művelet. Az alábbi ábrán erre mutatunk be egy példát, 10 elemű tömbökkel. A C programban kezdőértékeket is megadtunk, a program tehát az MPLAB szimulátorában is kipróbálható. Érdemes megnézni a Disassembly Listing ablakban is!

Az ábrán látható assembly program nem teljes, csak a helyfoglalást és  a for ciklus kifejtését mutatjuk be. Egy kicsit "csaltunk" a program egyszerűsítése érdekében azzal, hogy az i<10 tesztelést a ciklus törzsének végrehajtása után végezzük (hátultesztelő ciklus). Ezzel egy ugró utasítást "megtakarítottunk".  További egyszerűsítési lehetőség az is, hogy a ciklusváltozót nem mentjük el a memóriában, hanem csak a W0 regiszterben tároljuk. Regiszter változók használatára egyébként a PIC24 C fordító is lehetőséget ad, s a C programban az i változót például így is deklarálhattuk volna:  register int i asm("w0"); 

Az assembly programban a már említett W0 regiszteren kívül munkaváltozóként felhasználjuk a W1..W4 regisztereket is: W1, W2, W3 az a, b, c tömbök i-edik elemére mutató változók lesznek, W4-be pedig az a[i] tömbelem értékét töltjük be az összeadás előtt. Erre azért van szükség, mert az ADD Wb,Ws,Wd utasításnál csak a második és a harmadik operandus használhat regiszter indirekt címzést, az első összeadandónak már valamelyik regiszterben kell lennie.
 

Az assembly program utasításainak értelmezése:

clr W0         - W0 törlése, ami a for ciklus kezdőérték beállító utasításának (i=0) felel meg
mov #a,W1 - W1 mutatót az a[] tömb első elemére állítja (W1 = &a[0])
mov #b,W2 - W2 mutatót a b[] tömb első elemére állítja (W2 = &b[0])
mov #c,W3 - W3 mutatót a c[] tömb első elemére állítja (W3 = &c[0])
mov [W1++],W4 - a W4 regiszterbe tölti  az a[] tömb soron következő elemét, és lépteti a mutatót
add W4,[w2++],W[3++] -az előző utasítással együtt elvégzi a c[i]=a[i]+b[i] értékadást és lépteti a mutatókat
inc W0,W0 - a for ciklus ciklusváltozó léptető utasításának (i++) felel meg
cp W0,#10  - az i-10 kivonásnak megfelelően állítja be a státuszbiteket
bra LTU,ciklus - visszaugrik a ciklustörzs elejére, ha az i<10 feltétel teljesül (LTU=Less then, unsigned)

Karakterfüzérek kezelése

A C nyelvben nincsen külön karakterfüzér (string) típusú változó, ezért többnyire karakter típusú tömböket használunk a karakterfüzérek tárolására. A C nyelvben hagyományosan egy nulla kódú karakter jelzi a karakterfüzér végét, ezért egy pl. tíz karakterből álló füzér tárolásához egy legalább 11 elemű tömböt kell deklarálnunk.  Ha deklaráláskor mindjárt kezdőértékkel is feltöltjük a tömböt, akkor kényelmesebb, ha a tömbméret meghatározását és a nullával való lezárást a fordítóprogramra hagyjuk, ahogy ezt a következő mintaprogramban is tettük.

Karakterfüzér átalakítása nagybetűsről kisbetűsre

A baloldalt látható C program egy karakterfüzért alakít át úgy, hogy a nagybetűket kisbetűre konvertálja. Emlékeztetőül: a 65 és 90 közé eső ASCII kódokra a latin nagybetűk, melyekből úgy lesz kisbetű, hogy a 32 (0x20) helyiértékű bitet 1-be állítjuk (az 1-be állítás IOR utasítással történhet, 0x20 maszk felhasználásával). 

A programban a karakterfüzért tároló karakter tömb elemeit a karakter típusra mutató típusú p változóval címezzük meg. Mivel a karaktereket nem számoltuk meg, nem for ciklust használunk, hanem while ciklust, ami addig fut, amíg a sorra elővett karakterek kódja nullától különbözik. A while(*p != 0) feltételt egyszerűen így is írhattuk volna:  while(*p).

A konverziót akkor végezzük, ha a  60 < *p < 91 feltétel teljesül, vagyis a karakterkód 65-90 közé eső érték. A programban hexadecimális értékeket használtunk, de ez természetesen nem kötelező. A konverziót a *p=*p | 0x20 utasítással végezzük, ami az a[i]=a[i]+32 értékadás megfelelője. A ciklustörzs végén a mutatót léptetni kell, hogy a következő elemre mutasson.

Megjegyzés: Ne essünk abba a hibába, hogy a léptetést az if utasítás törzsében helyezzük el, mert a léptetés nem köthető az if feltételéhez (az első nem betű karakternél végtelen ciklusba kerülne a program)!

Az alábbi ábrán láthatunk egy lehetséges megvalósítást assembly nyelven is. A programot helyfoglalással és a karakter tömb feltöltésével kezdjük. Az egyszerűség kedvéért két-két karakter hexadecimális kódját írjuk be egy-egy 16 bites adatmozgató utasítással. A p mutatót nem a memóriában, hanem a W0 regiszterben tároljuk, így egyszerűbb és hatékonyabb a kezelése.

A programot az MPLAB szimulátorban futtatva a File Registers és a Special Function Registers ablakokban nyomon követhetjük a változók tartalmának változásait. Az ábrán a ciklusba való belépés és a ciklusból történő kilépés pillanataihoz tartozó állapotokat rögzítettük.

Az assembly program utasításainak értelmezése:

mov.b [W0],W1    - a W1 regiszterbe tölti az a[] tömb soron következő elemét
cp0.b W1            - megvizsgálja, hogy a betöltött karakter nulla-e
bra Z,vege          - h a karakter kódja nulla (Z =1), akkor vége a programnak
mov.b #65,W2     - 65-öt ('A' kódja) töltünk W2-be (ez lesz a kivonandó)
sub.b W1,W2,W2  - a karakterkódból kivonjuk az 'A' kódját, és az eredményt W2-be tesszük
cp.b W2,#25        -  ha a kódunk nagybetű, akkor a 0 <= W2 <= 25 feltételeknek kell teljesülnie
bra GTU,atlep       -  átugorja a konverziót, ha a karakterkód nem nagybetűt jelent
ior.b #0x20,W1    - ez a NAGYBETŰ -> kisbetű konverzió (csak nagybetűkre hajtódik végre!)
mov.b W1,[W0++] - visszaírjuk a tömbbe az új értéket, és léptetjük a mutatót
bra ciklus              - visszaugrik a ciklus törzsének elejére

Megjegyzések:
  1. A bra GTU,atlep utasításnál az U (unsigned) módosító miatt a negatív számokat is nagy pozitív számnak vesszük, így a 0 <= W2 <= 25  feltételek mindkét felét egyetlen vizsgálattal (W2 <= 25) letudjuk!!!
  2. Apró szépséghiba a programban, hogy akkor is visszaírjuk a karakterkódot a tömbbe, ha nem történt változtatás rajta. Ezt úgy lehetne elkerülni, ha a visszaírást és a mutató léptetését külön választanánk, ami egy többlet utasítást jelentene.

A REPEAT utasítás

A korábbi példákban többször előfordult már, hogy egy-egy tevékenységet ciklikusan ismételni kellett, amit a C programokban for vagy while ciklusokkal oldottunk meg, az assembly programokban pedig feltételes ugróutasításokkal. Van azonban a PIC24 utasítások között egy olyan utasítás, ami az egyszerű, egy utasítással elvégezhető műveleteket az előírt számszor megismétli, s ez a REPEAT utasítás, ami két formában használható:

A REPEAT #lit14 utasítás a REPEAT utasítást közvetlenül követő utasítást ismétli (lit14 + 1) alkalommal. Hatására az RCOUNT regiszterbe beíródik az előírt ismétlések száma, az ismételten végrehajtandó utasítás (az úgynevezett target utasítás) kódja pedig  az utasítás regiszterben marad mindvégig, amíg RCOUNT értéke nullára nem csökken. RCOUNT értéke a target utasítás minden végrehajtásakor eggyel csökken. Amikor RCOUNT értéke nullára csökkent, akkor a target utasítás mégegyszer végrehajtódik, majd a program normális futása folytatódik. A #lit14 számkonstans legfeljebb 14 bites, előjel nélküli érték lehet. A REPEAT utasítás az RA státuszbit 1-be billentésével jelzi, hogy REPEAT ciklus van folyamatban.

A REPEAT Wn utasítás is hasonlóan működik, csak annyi a különbség, hogy az ismétlések számát itt Wn (a W1..W15 regiszterek bármelyike lehet) regiszter 0..13 bitjei adják.

Nem kell Sherlock Holmes-nak lenni annak kitalálásához, hogy a REPEAT utasítás az RCOUNT beírásán kívül csak annyit csinál, hogy bekapcsol egy mechanizmust, ami addig nem engedi az utasításszámlálót növelni, amíg RCOUNT nullára nem csökkent. Ebből az egyszerű megoldásból következnek a REPEAT utasítás tulajdonságai és korlátai:
  1. Ha az előírt ismétlések száma 0, akkor a REPEAT utasítás NOP utasításként működik (nem csinál semmit, az RA státuszbitet sem billenti 1-be).
  2. A REPEAT utasítást követő target utasítás nem lehet:
Az alábbi egyszerű programban egy 64 elemű tömb törlését végezzük el. A C program mellett bemutatjuk annak assembly nyelvű megvalósítását "hagyományos módon", és a REPEAT utasítás felhasználásával.  
Megjegyzés: Az a.) változat erősen optimalizált megvalósítása 5 utasítást igényel, s a 64 elem törlése 257 utasításciklust vesz igénybe. A b.) változat a REPEAT utasítással mindössze három utasítás, s a 64 elem törlését 66 utasításciklus alatt végzi el. 

A veremtár

A PIC24 mikrovezérlők szoftveresen kezelhető veremtárat biztosítanak, ami a függvényhívások és a kivételkezelések rendelkezésre áll. A W15 regiszter az alapértelmezett veremmutató (SP - stack pointer),ami Reset után 0x800 értékre (a legalsó szabad memóriacímre) inicializálódik. Ez biztosítja, hogy minden 16 bites processzor esetén érvényes RAM területre mutasson, ha valami kivéltelhiba történne, mielőtt a felhasználói program beállítaná SP-t. A felhasználó azután tetszőleges értékre átírhatja SP-t a programjában.  A verem a magasabb címek felé növekszik, s a veremmutató mindig a verem tetején levő legelső szabad helyre mutat (Top of Stack). A POP (olvasás, adat elővétele) művelet tehát a veremmutató előzetes csökkentésével történik (a POP Wd utasítás MOV [--W15],Wd utasításnak felel meg. A PUSH Wd (írás, adat elhelyezése a verem tetején) utasítás pedig a MOV Wd,[W15++] adatmozgató utasításnak felel meg.

A veremtárat szoftveresen a PUSH és a POP utasításokkal kezelhetjük, melyeknek lehetséges formáit az alábbi táblázatban foglaltuk össze:
Elnevezés Utasítás formája A végrehajtott művelet
Push push Ws
push f
(Ws) → (W15); (W15)+2 → W15
(f) → (W15); (W15)+2 → W15
Push (duplaszavas) push.d Wns (Wns) → (W15); (W15)+2 → W15
(Wns+1) → (W15); (W15)+2 → W15
Pop pop Wd
pop f
(W15)-2 → W15; (W15) → (Wd)
(W15)-2 → W15; (W15) → (f)
Pop (duplaszavas) pop.d Wnd (W15)-2 → W15; (W15) → (Wd)
(W15)-2 → W15; (W15) → (Wd+1)
Az alábbi ábrán egy egyszerű példát mutatunk a veremtár használatára. Mielőtt a vermet használnánk, be kell állítani a veremtár mutató kezdőértékét, és célszerű beállítani az SPLIM nevű regiszterben a veremtár felső határát is. Ha a veremtár mérete túlnőne ezen a határon, akkor kivétel keletkezik, s a felhasználói program futása megszakad. Az ábrán látható programban a __reset címke utáni első három utasítással állítjuk be SP (azaz W15) és SPLIM kezdőértékét. Az __SP_init és az __SPLIM_init szimbólumok értékét a linker program állítja elő, s ha másképp nem definiáltuk, a linker a lehetséges maximumot állítja be. A W0 és W1 regiszterekbe tetszőleges számokat írva vizsgáljuk a PUSH és a POP utasítások hatására történő adatmozgásokat.



A fenti program veremkezelő utasításainak hatását az alábbi ábra mutatja be:


A veremtárat nem csak adatok tárolására használjuk. Fontos szerepe van a szubrutinok hívásánál (a veremtár őrzi meg a visszatérési címet), a programmegszakításnál (a veremtárban tárolódik el a megszakított program folytatási címe), a rekurzív eljárások változóinak tárolására. Ezekhez azonban nem elég a PUSH/POP utasítások használata, a mikrovezérlő hardveres támogatása is szükséges hozzá. Az ezzel kapcsolatos utasítások közül néhánnyal a következő  szakaszban a szubrutinhívások kapcsán ismerkedünk meg. Egy későbbi fejezetben a programmegszakítások kapcsán is találkozunk még újabb, a veremtárral kapcsolatos utasításokkal.

Szubrutinok

A programozás során gyakran használunk szubrutinokat (függvény, eljárás, metódus, alprogram)  egy nagyobb program kódjának részeként, ami egy körülhatárolt feladatot hajt végre, a kód többi részétől viszonylag függetlenül és többször felhasználható módon.

A szubrutinok használatnak előnyei:
A szubrutinok összetevői lehetnek:
Több programozási nyelvben (pl. Pascal, FORTRAN) különbséget tesznek a függvények és az eljárások között (függvények = amelyek értéket adnak vissza, eljárások = amelyek nem adnak vissza értéket), és ezeket külön kulcsszavakkal definiálják (pl. function/procedure).

A C programozási nyelvben nincs ilyen különbségtétel, minden szubrutint "függvénynek" hívunk (function). Ha nincs visszatérési értéke, akkor ezt void visszatérési típussal jelöljük.

Függvények definiálása C nyelven

A függvények használata kapcsán néhány fontos fogalommal kell megismerkednünk.

deklarálás
Az a pont, ahol a definált névhez valamilyen típust rendelünk. Ha egy függvényre korábban hivatkozunk, mielőtt deklarálására sor került volna, akkor implicit deklarációra kerülhet sor (általában ilyen esetekben int típust feltételez a fordító.
definiálás
Ez is magában foglalja a deklarálást, de annyival több, hogy ezen a ponton a nevesített objektumhoz már valamennyi tárterület is lefoglalásra kerül. A függvénydeklaráció akkor válik definiálássá, amikor a függvény törzsét is megadjuk, kapcsos zárójelek közé zárt összetett utasítás(ok) formájában
formális paraméterek
Azoknak a változóneveknek a felsorolása amelyekre a függvény hivatkozhat az argumentumainak eléréséhez.
aktuális paraméterek
Azok az értékek, amelyeket a függvény meghívásakor argumentumként átadunk. Más szavakkal: azok az értékek amelyekkel a formális paraméterek rendelkeznek, a függvénybe történő belépéskor. 
visszatérési érték
Az az érték, amely a függvény eredményeként keletkezik, s amelyet visszaad a hívó alprogramnak.
helyi változók
Helyi (lokális) változók azok a regiszter vagy dinamikus típusú változók, amelyek csak a meghívott függvény  kontextusán belül érhetők el, s a függvényből való visszatérés után elveszítik értéküket.

Egy mintapélda a függvénydefinícióra

Az alábbi példán keresztül mutatjuk be a függvény definiálásának és használatának módját a C nyelvű programokban. Az MPLAB szimulátorában is kipróbálható programban egy fact() nevű függvényt definiáltunk, amely egyetlen, uint16 típusú paramétert vár, s egy uint32 típusú értéket ad vissza, a bemenő paraméter faktoriálisát.

Emlékeztető: n faktoriális (vagy röviden: n!) az a szám, amelyet úgy kapunk, hogy 1-től n-ig összeszorozzuk a természetes számokat. Tehát n! = 1*2*3*...(n-1)*n. Megegyezés szerint a nulla faktoriálisa is értelmezett, s értéke 1, azaz 0! = 1.


A program futásának eredménye az alábbi ábrán látható. Megfigyelhetjük, hogy a számok faktoriális rohamosan növekvő számsorozatot alkot, s 8! a legnagyobb érték, ami  16 bites előjel nélküli számként még ábrázolható. Így a 9! és 10! ábrázolhatósága miatt a függvény visszatérési típusát 32 bitesre kellett bővíteni.

A paraméterátadás szabályai függvényhívásnál

Ha C programból assembly nyelvű függvényt, vagy assembly programból C függvényt akarunk hívni, akkor tisztában kell lennünk a paraméterátadás szabályaival. A Microchip C30 fordítója az alábbi szabályok szerint szervezi a paraméterátadást:

Szubrutinhívás és visszatérés assembly programokban

Az alábbi táblázatban összefoglaltuk a szubrutinhívással kapcsolatos assembly utasításokat.
Megnevezés Szintaxis A művelet
Hívás CALL címke_lit23 A visszatérési címet a verem tetejére teszi,
SP=SP+4, majd címke_lit23  → PC
Indirekt hívás CALL Wn A visszatérési címet a verem tetejére teszi,
SP=SP+4, majd (Wn)  → PC
Relatív hívás RCALL címke_slit16 A visszatérési címet a verem tetejére teszi,
SP=SP+4, majd (PC) + 2*slit16 → PC
Relatív indirekt hívás RCALL Wn A visszatérési címet a verem tetejére teszi,
SP=SP+4, majd (PC) + 2*(Wn) → PC
Visszatérés RETURN A verem tetejéről vett visszatérési címet PC-be tölti, SP = SP-4
Visszatérés literálissal RETURN{.B} #lit10,Wn A verem tetejéről vett visszatérési címet PC-be tölti, SP = SP-4, továbbá Wn-be tölti lit10 értékét.
Megjegyzések:
  1. A CALL címke utasítás 2 szavas, a többi utasítás 1 utasításszót foglal le.
  2. A CALL Wn utasítás estén az utasításszámlálónak csak az alsó 16 bitjét tudjuk megadni (mivel a W regiszterek 16 bitesek). Az utasításszámláló  magasabb helyiértékű bitjei nullával lesznek feltöltve. Ezzel az utasítással tehát csak az első 32767 utasítás címezhető meg.
  3. Relatív hívásnál (RCALL) az utasításszámláló aktuális értékéhez képest címezhetünk meg 32K utasítást előre, vagy hátra irányban.
  4. A visszatérés literálissal (RETURN #lit10 utasítás esetében a szószélességú változatnál 0..1023 közötti, a bájt szélességű változatnál (.B módosító) pedig 0..255 közötti értéket adhatunk meg.
Az alábbi mintapéldában egy olyan szubrutint definiáltunk, ami egy 32 bites előjel nélküli változótömb két elemét megcseréli. A szubrutinnak nincs visszatérési értéke, tehát pl. a Pascal nyelv fogalmai szerint nem függvény, hanem eljárás. A szubrutin három paramétert vár bementként: egy mutatót, ami a tömb első elemére mutat, és két sorszámot, ami megmondja, hogy melyik két elemet kell megcserélni.

Az assembly program egy kicsit meg lett kurtítva, hogy ne legyen hosszú a lista. Kihagytuk a változó tömb és a veremtár inicializálást végző programrészt, s a főprogram is csak a szubrutin hívást tartalmazza, a kiíratást már nem. De a szimulátor segítségével a File Registers ablakban így is könnyen nyomon követhetjük az eseményeket.

Megjegyzés: Vigyázzunk arra, hogy a főprogramban csak 8 bites értékeket adunk át, ezért a szubrutinban ki kell egészítenünk azt 16 bitesre, mielőtt 16 bites műveletekben felhasználnánk. Erre szolgálnak a ze w1,w1 és a ze w2,w2 utasítások, amelyek nullával töltik fel az ismeretlen tartalmú magasabb helyiértékű bájtot. (Bővebben lásd a "8/16 bites műveletek PIC24 assembly nyelven" c. fejezet végén a  "Típuskonverzió: 8 és 16 bites mennyiségek összeadása" c. szakaszt.)

Szubrutinhívás és a veremtár kapcsolata

Az alábbi ábrán bemutatjuk, hogy a veremtár segítségével hogyan működik szubrutinhívás - akár több szinten is, egymásba ágyazott hívásokkal. A követhetőség kedvéért az ábrán sorszámoztuk a szubrutinhívások szempontjából lényeges lépéseket:
  1. A főprogram egy CALL utasítással meghívja az A szubrutint. A CALL utasítás a visszatérési címet (a CALL utasítás után következő utasítás memóriabeli címét, ahol a főprogramot majd folytatni kell) a veremtár tetejére helyezi.
  2. Az A szubrutin futása során egy CALL utasítással meghívja a B szubrutint. A CALL utasítás a folytatás címét (SubA folyt.) a veremtár tetejére helyezi.
  3. A B szubrutin futása során egy CALL utasítással meghívja a C szubrutint. A CALL utasítás a folytatás címét (SubB folyt.) a veremtár tetejére helyezi.
  4. A C szubrutin egy RETURN utasítással fejezi be a működését. A RETURN utasítás a verem tetejéről kiveszi a visszatérési címet, s az abban talált helyen (SubB folyt.) folytatódik a program futása.
  5. A B szubrutin egy RETURN utasítással fejezi be a működését. A RETURN utasítás a verem tetejéről kiveszi a visszatérési címet, s az abban talált helyen (SubA folyt.) folytatódik a program futása.
  6. Az A szubrutin egy RETURN utasítással fejezi be a működését. A RETURN utasítás a verem tetejéről kiveszi a visszatérési címet, s az abban talált helyen (FP folyt.) folytatódik a program futása.


Rekurzív szubrutinhívás

A többszintű szubrutinhívás egy speciális esete az, amikor egy szubrutin saját magát hívja meg. Ezt rekurzív függvényhívásnak nevezzük. A kellemetlen meglepetések elkerülése érdekében a rekurzív függvényhívás megtervezésekor az alábbi szempontokat vegyük figyelembe:
  1. A rekurzív hívások láncolatát meg kell szakítani, ellenkező esetben végtelen ciklusba kerül a program és túlcsordul a veremtár. Ehhez az kell, hogy, a rekurzív függvény valamelyik bemenő paramétere változzon meg, s legalább egy feltétel teljesüléséhez kösse az újabb rekurzív hívást. 
    • Az alábbi példában minden hívásnál csökkentjük a bemenő paraméter értékét eggyel vagy kettővel, s a hívások láncolata megszakad, ha 1-et vagy 0-át elértük.
  2. Mivel a hívó és a hívott függvény ugyanaz, most a W8-W14 regisztereken kívül a W0-W7 regiszterek tartalmát is el kell menteni, ha a függvényben módosulnak, s vissza kell állítani tartalmukat a visszatérés előtt.
  3. A munkaváltozókat a rekurzív hívás előtt célszerű elmenteni a veremtárba, s visszatéréskor vissza kell venni onnan. 
    • Az alábbi példa assembly programjában az első rekurzív hívás előtt W0 értékét (n) mentjük el, a második rekurzív hívás előtt pedig W1 tartalmát (az előző hívás visszatérési értékét tárolja).
    • C programok esetében a függvényen belül deklarált lokális változók (lásd a korábban bemutatott faktoriális függvény f változóját) dinamikus változók, elmentésükről a fordítóprogram gondoskodik.
Példa rekurzív hívásra: a Fibonacci számok kiszámítása rekurzióval. A Fibonacci számok olyan számsorozatot alkotnak, melynek valamelyik tagja az előző két tag összege: f(i) = f(i-1) + f(i-2). Az első két tag pedig: f(0) = 0  és f(1) = 1.


Megjegyzés: Az assembly program estében csak a rekurzív függvényt dolgoztuk ki részletesen, a főprogram csupán a fibo(5) hívást hajtja végre! A szimulátorban futtatott C nyelvű változat eredménye az alábbi ábrán látható:



Dinamikus változók

A rekurzív függvények helyes működéséhez dinamikusan kell tárhelyet foglalni a lokális változóknak. A dinamikus helyfoglalás vagy a szabad regiszterekben, vagy a veremtárban történik.

Az alábbi példában a számok faktoriálisát számítjuk ki rekurzív függvénnyel, az n! = n*(n-1)! képlet felhasználásával. Az ábrán azt az esetet mutatjuk be, amikor 2 faktoriálisát (2!) számítja ki a program. Az a.) esetben dinamikus helyfoglalás történik, s a vissztérési érték helyes. A b.) esetben egy statikus (globális) változót használunk, ami a második hívásnál felülíródik, így rossz lesz a visszatérési érték.

Veremkeret (paraméterátadásra és lokális változókhoz)

Ahogy az előző példában láttuk, rekurzív függvények/eljárások esetén létfontosságú, hogy a lokális változókat dinamikusan hozzuk létre a verem felhasználásával. Ennek szokásos megvalósítása a veremkeret (stack frame), melynek kezeléséhez a PIC24 mikrovezérlő hardveres segítséget is nyújt két speciális utasítás (LNK és ULNK) formájában. A veremkeret jól használható akkor is, ha az átadni kívánt paraméterek nem férnek el a regiszterekben, s a veremtárban kell átadni azokat. 

A veremkeret lényege az, hogy a veremmutatónak (SP) a lokális változók helyének lefoglalása előtti állapotát a keretmutató regiszterben (FP) tároljuk (a PIC24 mikrovezérlők esetében erre a W14 regisztert használjuk alapértelmezetten). A lokális változók és az átadott paraméterek elérése a keretmutatóhoz képest, bázisrelatív címzéssel történhet.

A veremkeret használatát az alábbi ábrán mutatjuk be:

1. Az átadni kívánt paramétereket a hívó program sorra elhelyezi a verem tetején, majd meghívja a szubrutint.

2. A szubrutinhívás hatására a visszatérési cím is eltárolódik a veremben.

3. A meghívott szubrutin elmenti a Frame Pointer (FP, keretmutató) értékét a verem tetején.

4. A meghívott szubrutin a keretmutatót (FP) beállítja a verem következő üres helyére. Itt kezdődik majd a lokális változók területe.

5. A meghívott szubrutin a lokális változók számára lefoglalni kívánt terület méretével megnöveli a veremmutató (SP) értékét.

6. Ezen előkészítő lépések után  a lokális változók és az átadott paraméterek az FP keretmutató segítségével címezhetők meg, bázisrelatív címzéssel, azaz a lokális változókat és a paramétereket a keretmutató bázisregiszterhez képest fix eltolással lehet megtalálni.

7. A szubrutin visszatérési értékét a W0 regiszterben (32 vagy 64 bites adat esetén W0-tól kezdődően) kell elhelyezni.

8. Felszabadítjuk a veremben azt helyet, amit lefoglaltunk (FP -> SP)

9. A hívó veremkeretének a visszaállítása (FP eredeti értékének visszaállítása: POP FP)

10. Visszatérés a hívó programhoz (RETURN)

11. A hívó program felszabadítja a bemenő paraméterek által lefoglalt helyet (SP értékét csökkentjük a paraméterek által lefoglal terület méretével). 

Megjegyzés: Bár elkerülhető a használata, de hatékonyabbá teszi a programozást a LNK #lit14 utasítás, amely a fenti lista  3., 4. és 5. lépését egyetlen utasítciklusban hajtja végre. A 14 bites literális érték a lokális változók számára lefoglalt terület méretét adja meg bájtokban. Mivel a veremtár szószervezésű, ezért a LNK utasítás lit14 paramétere csak páros szám lehet! Az LNK utasítás ellentettje az ULNK utasítás, amely a 8.és 9. pontban leírt tevékenységeket végzi el. Az ULNK utasításnak nincs paramétere.

A Fibonacci program újratervezése veremkeret használatával

A Rekurzív szubrutinhívás c. szakaszban bemutatott mintaprogramot (a Fibonacci számok kiszámítása rekurzív függvényhívással) most írjuk át úgy, hogy a verem segítségével történjen a paraméterátadás, és veremkeret segítségével, a veremben elhelyezkedő lokális változóban tároljuk a rekurzív függvényhívások részeredményeit is!

Az alábbi ábrán a főprogramot láthatjuk. Két globális változónak foglalunk helyet, mindkettő 16 bites, előjel nélküli egész típusú. Az assembly programban most létfontosságú a verem helyes inicializálása, hiszen a veremnek kulcsszerepe lesz a program működése során. Hívás előtt a verem tetejére kell helyezni az átadni kívánt paramétert, visszatérés után pedig el kell távolítani onnan (veremtakarítás).


Az alábbi ábrán a rekurzív eljárást mutatjuk be. A veremkeret létrehozása a lnk #2 utasítással történik (egy 16 bites lokális változónak foglalunk helyet). A főprogram által átadott paraméter a keretmutatóhoz képest 8 bájttal mélyebben van (át kell lépni FP elmentett előző értékét, valamint a visszatérési címet), lásd a korábbi ábrán!

Nem szabad megfeledkeznünk a rekurzív hívások utáni veremtakarításról (sub W15,#2,W15), és kilépés előtt a veremkeret eltakarításáról (ulnk utasítás)!

Vegyük észre, hogy a szubrutinban fibo_sf(n-1) és fibo_sf(n-1) hívása ugyanolyan utasítás-sorozattal történik, mint a főprogramban! A veremkeret használatának egyik fő előnye tehát az, hogy tipizált módszert ad a programozó kezébe, nem ad hoc rögtönzésekkel keresünk helyet a megőrizni kívánt változóknak.

Természetesen ára is van ennek a kényelmesen használható általánosított módszernek:
  1. Megnöveli a veremben tárolt adatok mennyiségét (mivel a  keretmutatót is el kell menteni minden új keret létrehozásakor).
  2. Megnöveli a  program méretét: A fibo_sf() függvényünk például 17 utasítás lett, míg a Rekurzív szubrutinhívás c. szakaszban bemutatott fibo() függvény csak 13 utasítás volt, s a főprogramot is meg kellett toldanunk két utasítással (W0 veremre helyezésével és a hívás utáni veremtakarítással.

Programterület láthatóság (PSV) 

A PIC24 mikrovezérlők bemutatásánál már említettük, hogy a Microchip mikrovezérlői a nagyobb teljesítmény  elérése érdekében Harvard felépítésűek, azaz elkülönül bennük és külön adatsínnel rendelkezik  a programtár és az adattároló memória. Ennek eredményeként az adatáramlási sebesség megduplázódik, hiszen amíg az adatút el van foglalva az utasítás végrehajtásával, vele egyidőben már folyhat a következő utasítás elővétele a másik adatsínen. Ez komoly előny a Neumann elvű mikrovezérlőkkel szemben. Azonban a Harvard felépítésnek is van hátrányos tulajdonsága: nehézkes és körülményes a programmemóriában tárolt konstansokhoz és adattáblázatokhoz történő hozzáférés.

A PIC24 vezérlők kétféle lehetőséget biztosítanak a programmemóriában tárolt adatok eléréséhez: az egyik módszer egy speciális, táblázatolvasó utasításon alapul (tblrd), a másik módszer pedig az úgynevezett Programterület láthatóság (Program Space Visibility, vagy röviden: PSV). Ez egy ablakot nyit, amelyen keresztül a programmemória egy 32 kilobájtnyi területe elérhetővé válik az adatbusz felől is, az adatmemória felső címtartományában.

Megjegyezzük azonban, hogy a programmemória 24 bites szószélességű, az adatmemória és az adatút pedig csak 16 bites. PSV hozzáférés esetén ez a különbséget úgy hidalják át, hogy a programmemória rekeszeinek csak az alsó 16 bitje válik láthatóvá, a felső 8 bit pedig nem elérhető (a táblázatolvasó utasítással minden bájt elérhető, viszont csak a speciális tblrd utasítással férhetünk hozzá a programtárban elhelyezett adatokhoz).

Az alábbi ábrán a PIC24HJ128GP502 mikrovezérlő memóriatérképe és az adatmemória címtartományában elhelyezkedő programterület láthatósági ablaka látható. A PSVPAG regiszter tartalma szabja meg, hogy a programmemória melyik 32 kilobájtos lapja legyen látható, a láthatóságot  pedig a CORCON regiszter 2-es sorszámú bitjében lehet engedélyezni, illetve letiltani.

A PSV használatához az alábbi lépésekre van szükség:

  1. Engedélyeznünk kell a programterület láthatóságot a CORCON regiszter PSV bitjének 1-be állításával.
  2. Be kell írnunk az elérni kívánt memórialap sorszámát a PSVPAG regiszterbe. A lap sorszámának meghatározásához használhatjuk a psvpage() operátort.
  3. Be kell állítanunk egy mutatót az elérni kívánt adat PSV ablakban érvényes címére. Ennek meghatározásához használhatjuk a psvoffset() operátort.
Fentiek után ugyanúgy olvashatjuk (pl. MOV utasítással)  az adatokat, mintha az adatmemóriában volnának. Az alábbi ábrán egy konkrét példán keresztül mutatjuk be a PSV használatát.

; Mintaprogram a PSV használatának bemutatására
; --------------------------------------------
; Forrás: Microchip: MPLAB ASSEMBLER, LINKER AND
; UTILITIES FOR PIC24 MCUs AND dsPIC® DSCs USER’S GUIDE
; Document ID: DS51317G, © 2008 Microchip Technology Inc.
; 4.5.1.2 PROGRAM SPACE VISIBILITY (PSV) DATA WINDOW

; Létrehozunk egy 16 bites adatokból álló konstans tömböt
  .section .const,psv
fib_data:
  .word 0, 1, 2, 3, 5, 8, 13, 21, 34, 55

  .text
; Engedélyezzük a Programterület láthatóságot
bset.b CORCONL, #PSV

; Mutasson PSVPAG arra a lapra,amelyik
; a fib_data adattömböt tartalmazza!
mov #psvpage(fib_data), w0
mov w0, _PSVPAG

; Beállítunk egy mutatót, ami a PSV ablakban
; a fib_data tömbre mutat.
mov #psvoffset(fib_data), w0
; Az első adatszó beolvasása
mov [w0++], w1

Globális változók inicializálása

Az alábbi programban inicializált (tehát meghatározott kezdőértékű) globális változók használatára mutatunk egy példát. A C program esetében a fordító elrejti előlünk az inicializálás részleteit, az assembly változatban viszont nekünk kell megoldani a feladatot. Itt most az előző szakaszban leírtakat alkalmazva, a Programterület láthatóság (PSV) felhasználásával állítjuk be a globális változók kezdőértékét.

A program egyébként ugyanazt a nagybetűsből kisbetűsre történő átalakítást végzi, amellyel a Karakterfüzérek kezelése c. alfejezetben már foglalkoztunk, így a kisbetu nevű C eljárást, illetve a neki megfeleltethető tolower assembly eljárást itt külön nem részletezzük.

Hogyan működik az init_var eljárás, hogyan történik a változók kezdeti értékkel történő feltöltése?

Ha a szövegkonstansokat a .const vagy a .text szakaszban adjuk meg, akkor a fordítóprogram azt a programtároló memóriában helyezi el. A psv opció jelzi, hogy PSV kompatibilis módon, azaz csak az alsó 16 bitet használva kerülnek elhelyezésre az adatok. Inicializáléskor kétféle módon férhetünk hozzá az adatokhoz: táblázatolvasással, vagy a Programterület Láthatóság (PSV) bekapcsolásával. Mi most az utóbbi használatával oldottuk meg a szövegkonstansok kiolvasását:
  1. A PSVPAG regiszterbe betöltjük a szövegkonstansaikat tartalmazó programmemória lap sorszámát (ilyen kis programoknál ez a lépés kihagyható, mert a RESET után beálló alapértelmezett 0 lapcím pont az,ami most nekünk kell).
  2. A CORCON regiszter 2-es sorszámú (PSV) bitjének 1-be állításával engedélyezzük a Programterület Láthatóságot. Ennek hatására az adatmemória 0x8000 fölötti címtartományában a PSVPAG regiszterrel megcímzett programmemória terület alsó 16 bitje (32 kbájt) válik láthatóvá, csak olvasható módon.
  3. A szövegkonstans memória címének alsó 15 bitjét (a 16. bit kötelezően 1)  egy általános célú regiszterbe írva, regiszter indirekt címzéssel kiolvashatjuk a progammemória láthatóvá tett szeletéből az eltárolt adatot.
  4. Mivel a karakterfüzérek olvasásának végét a záró nulla karakterkód jelzi, ezért több karakterfüzér átmásolását az strcopy eljárás többszöri hívásával  kell megoldanunk.

Az strcopy eljárás két, már beállított mutatót vár: W1 a kiolvasás helyére (a PSV ablakba), W2 pedig az írás helyére (a feltöltendő változóra) mutat. Az eljárás lényege a mov.b [W1++],[W2++] utasítás, ami egy karaktert másol és lépteti a mutatókat. Sajnos, ez az utasítás nem vizsgálja az átvitt karakter értékét, ezért kell még egy művelet, amellyel a W0 regiszterbe is betöltjük az adatot. Az adat vizsgálata a cp0 W0 utasítással történik, s csak akkor folytatjuk a ciklust, ha nem nulla az utolsónak átvitt adat. Mivel karakterfüzért záró nulla karakterkódot is át kell vinni, itt indokolt a hátultesztelő ciklus alkalmazása.