Bevezetés a PIC24 mikrovezérlők assembly programozásába

[Szerzői jogok]

A fejezet tartalma:

A mikrovezérlők egyetlen IC tokba zárt megoldást kínálnak, szemben a mikroprocesszorokkal, amelyek működéséhez további áramköri elemeket: külső memóriát, periféria áramköröket kell hozzájuk kapcsolni. A mikrovezérlők viszont már tartalmazzák a beépített, nem felejtő programtároló memóriát, az adattároláshoz szükséges RAM memóriát, s számos funkciót el tudnak látni a beépített periféria áramkörökkel (soros kommunikáció, óra, számlálók, ADC, impuzusszélesség modulációs vezérlő, stb.).

A mikrovezérlők általában szerényebb teljesítményűek, mint az általános célú processzorok, s nem tartalmaznak virtuális memória támogatást sem. De a mikrovezérlők és mikroprocesszorok közötti határ az előbbiek rohamos fejlődése miatt elmosódni látszik.

Sok más mikrovezérlőhöz hasonlóan a PIC24H mikrovezérlő család tagjai is Harvard felépítésűek, azaz a programtár és az adattár elkülönül. A programtároló memória 24 bites szószélességű, az adatút és az adatregiszterek pedig 16 bites szélességűek. A PIC-kwik projektben használt PIC24HJ128GP502 és dsPIC33FJ128GP802 esetében a programtár mérete 128 kilobájt, azaz 43690 utasítás fér bele. Az adattároló memória mérete a PIC24HJ128GP502 esetében 8 kilobájt, a dsPIC33FJ128GP802 esetében pedig 16 kilobájt. Az adattár utolsó 2048 bájtja DMA puffer területként is használható, ami kettős hozzáférésű memóriaként kezelhető a DMA módban működő perifériák és a CPU oldaláról. (Megjegyzés: DMA = Direct Memory Access, azaz közvetlen memória hozzáférés, melynek során egyes perifériák közvetlenül a memóriába írnak, vagy onnan olvasnak, a CPU kikerülésével.) Az órajel sebessége a PIC24H és a dsPIC33 család esetében egyaránt 0-80 MHz lehet. Egy utasítás végrehajtása két órajelcikust igényel, így a PIC24H mikrovezérlők a maximális órajelfrekvencián 40 millió utasítást végrehajtására képesek (40 MIPS).

A PIC24H mikrovezérlő család tagjai gazdag beépített periféria készlettel rendelkeznek. A PIC24H128GP502 mikrovezérlőben például van aszinkron soros port, I2C, SPI, CAN, 10/12 bites Analóg-Digitális átalakító, 2 db analóg komparátor, 5 db 16 bites számláló, melyekből két 32 bites számláló is kialakítható két-két 16 bites számláló sorbakötésével, input capture és output compare modul, impulzusszélesség moduláció (PWM), hardveres Real-Time óra és naptár funkció, CRC generátor, parallel master port.

A dsPIC33FJ128GP802 mikrovezérlő a fentieken túl digitális jelfeldolgozásra alkalmas utasításkészlettel és az ezekhez szükséges speciális regiszterekkel is el van látva. Perifériakészlete pedig az alábbiakkal egészül ki: 2 db 16 bites audio DAC, valamint I2S és AC97 protokollal kompatibilis adatkonverter illesztő (Data Converter Interface).

Megjegyzések

A programtároló memória szervezése

A PIC24H és dsPIC33 mikrovezérlők a futtatandó programot 24 bites szószélességű, nem felejtő, de elektromosan törölhető és többször újraírható (ún. flash) memóriában tárolják. A memória szervezése azonban olyan, hogy a címezhető egységek 16 bites szavak legyenek, az adatút szélességéhez igazodva, s "little endian" típusú, vagyis az utasításszó alacsonyabb helyiértékű bitjei kerülnek alacsonyabb címre. Mivel az utasításszó egy adatszóban nem fér el, kettőt meg nem tölt ki, azt az áthidaló megoldást találta ki a gyártó, hogy az utasításokat egy csupa nullát tartalmazó "fantom bájttal" 32 bitesre egészítik ki, ahogy ez az alábbi ábrán is látható.

1. ábra: a progammemória címzése


Az utasításszámláló az utasítás alacsonyabb helyiértékű felének memória címére mutat. Az eggyel magasabb címen pedig az utasítás legnagyobb helyiértékű bájtja helyezkedik el, a "fantom bájttal" 16 bitesre kiegészítve. Az utasításszámláló 23 bites, de az utasítások mindig páros címen kezdődnek (az utasításszámláló legalacsonyabb helyiértékű bitje mindig nulla), így elvileg 2^22 =4194304 = 4 Mi utasítás megcímzésére van lehetőség.

A 0x000000 - 0x0001FF közötti memória címek foglaltak a reset és a hardveresen szétválogatott programmegszakítási belépési pontok (interrupt vectors) számára, a felhasználói programok tehát a 0x000200 címen kezdődhetnek.

Az adattároló memória szervezése

A Harvard felépítésnek megfelelően a PIC24 mikrovezérlők különálló adattáróló memóriával rendelkeznek, melynek adatútja és a címzése is 16 bites szószélességű. Az adatmemória címtartományának alsó fele (amikor az effektív cím 15. bitje nulla) a ténylegesen implementált adatmemória címzésére van fenntartva. A címtartomány másik fele pedig arra szolgál, hogy a programtároló memória egy szeletét adatmemóriaként is láthassuk, elérhessük benne, a mikrovezérlő programterület láthatósági (Program Space Visibility, PSV) képessége segítségével.

Bár az adattároló memória és az adatregiszterek 16 bites szószervezésűek, az adattároló memória bájonként is megcímezhető, s az utasításkészletet is úgy alakították ki, hogy adatszavakkal és bájtokkal egyaránt lehessen műveleteket végezni. Minden szószervezésű adatelérés páros címekre történhet, erre ügyelnünk kell a bájt- és szószervezésű hozzáférések keveréskor, vagy a 8 bites mikrovezérlőre írt programok adaptálásánál! Ha páratlan címre kísérlünk meg szószélességű írást, vagy olvasást, akkor "címzéshiba" kivétel (Address error trap) történik.

Mivel a utasítások 13 bites közvetlen memóriacímet tartalmazhatnak, az adatmemóriából az első 8 kilobájt érhető ezzel a rövid címzéssel (ez az ún. közeli adatmemória-tartomány). Az adatmemória többi része kiterjesztett (16 bites) vagy indirekt címzéssel érhető el.

A közeli adatmemória-tartomány első két kilobájtnyi címtartomány a Speciális Funkciójú Regiszterek (SFR) számára van fenntartva. Ide vannak leképezve a mikrovezérlő központi egysége és a perifériák működését vezérlő regiszterek és az adatátviteli csatornák adatregiszterei.

A fizikailag létező adatmemória végén helyezkedik el a DMA memória, melynek mérete a PIC24HJ128GP502 és a dsPIC33FJ128GP802 mikrovezérlők esetében egyaránt 2 kilobájt.

2. ábra: a PIC24HJ128GP502 mikrovezérlő adatmemória-térképe

Megjegyzés: A PIC24HJ128GP502 adatlapjából átvett ábrán kijavítottuk a 8 kilobájtos közeli adatterületnek az eredeti ábrán hibásan bejelölt felső határát.

Mint látható, a páros címeken az alacsonyabb helyiértékű bájtok (LSB) foglal helyet,a páratlan címeken pedig a magasabb helyiértékű bájtok (MSB) érhetők el. A 0x0000 - 0x1FFF címtartomány a rövid címzéssel elérhető "közeli adatterület". A Speciális Funkciójú Regiszterek tartományát a PIC24HJ128GP502 mikrovezérlő estében 8 kilobájtos tárterület (X adattár) követi, melynek utolsó két kilobájtnyi területe (0x2000 - 0x2800) kettős hozzáférésű DMA memóriaként is használható. Az X adatmemória 0x8000 - 0xFFFF közötti címtartományába lehet leképezni a programtároló memória egy 16 kilobájtos szeletét, melynek kezdőcímét a 8 bites PSV regiszterben lehet beállítani.

X és Y adatterület

Az eddigiekben nem tértünk ki arra, hogy mire szolgál az X adatmemória jelölés. Ez a jelölés akkor nyer értelmet, ha megnézzük a digitális jelfeldolgozási funkciókkal kibővített dsPIC33FJ128GP802 mikrovezérlő adatlapját. A dsPIC33 adatmemóriájának kialakítása abban különbözik a PIC24H mikrovezérlőkétől, hogy két adatterülete van (X és Y) amelyeket két, egymástól függetlenül is kezelhető területnek is tekinthetünk, de egyetlen, összefüggő adattárként is. Az X és Y adatterületethez két, független címkezelő (Address Generátor Unit, AGU) és két független adatút kapcsolódik.  Ez a tulajdonság lehetővé teszi, hogy olyan utasítások, mint pl. a szorzás és összegzés (Multiply and Accumulate, MAC) egyszerre két, különböző tárhelyet címezzenek meg, és az onnan vett adatokkal dolgozzanak, s így hatékony jelfeldolgozó algoritmusokat valósíthassunk meg (pl. FIR szűrők, gyors Fourier-transzformáció).

3. ábra: a dsPIC24FJ128GP802 mikrovezérlő adatmemória-térképe

Az X adatterület bármely utasítással és címzési móddal használható, ez tehát lényegében a mikrovezérlő alapvető adatmemória kezelési módja, ezért a PIC24H mikrovezérlők adatmemóriáját is X típusú adatterületnek tekintjük, illetve nevezzük. Az Y tárterület az X adatterülettel kombinálva arra használható, hogy (pl. a MAC típusú utasítások számára) két, konkurens adatelérési út álljon rendelkezésre.

Mind az X, mind az Y adatmemória modulo címzési móddal is használható. A fordított bitsorrendű címzés viszont csak az X memóriába történő írásnál használható. Az X és Y adatterületek közötti határvonal helye eszközfüggő, és a felhasználó által nem változtatható meg.

Címzési módok

Azt a módot, ahogy az utasításokban az adat forrásának és a cél helyének címét megadjuk, címzési módnak nevezzük. A PIC24H mikrovezérlők sokféle címzési módot ismernek:
A PIC24H mikrovezérlő legtöbb utasítása egyetlen utasításciklusban hajtja végre a memóriában vagy a munkaregiszterekben tárolt adatokkal végzett műveleteket, így pl. a C = A + B utasítás is egyetlen ciklus alatt hajtódik végre.

Aritmetikai és logikai egység (ALU)

Az összeadáson, inkrementáláson, dekrementáláson és a bitenkénti logikai műveleteken kívül az aritmetikai és logikai egység (ALU) 17x17 bites szorzás elvégzésére is képes, előjeles, előjel nélküli vagy vegyes 16 bites (vagy 8 bites) adatokkal. A szorzás is egyetlen ciklusban hajtódik végre.

A 16 bites aritmetikai egység 32 bites számok 16 bites egésszel történő előjeles vagy előjel nélküli osztását is támogatja, az iteratív osztási utasítást a REPEAT ciklusszervező utasítással kombinálva. Minden osztás 19 utasításciklust vesz igénybe.

A CPU regiszterei

A programozás oldaláról nézve a műveletvégzés és az eredmények kiolvasása a CPU regiszterein keresztül történik, így elengedhetetlen azok ismerete. A PIC24H mikrovezérlők központi egysége az alábbi regisztereket tartalmazza:
Regiszter Méret Funkció
W0 - W15 16 bit Munkaregiszterek. Közülük W0 a 8 bites mikrovezérlők W regiszterének felel meg, W14 a Frame Pointer, W15 pedig a veremmutatóként használható
PC 23 bit Utasításszámláló (a soron következő utasításra mutat)
SR 16 bit Státusz regiszter (DC, IPL, RA, N, OV, Z, C)
SPLIM 16 bit A veremmutató felső határértékét tartalmazza
TBLPAG 8 bit Táblázatos memóriaművelethez a memórialap címét tartalmazza
PSVPAG 8 bit Programterület láthatóságához (PSV) a memórialap címét tartalmazza
RCOUNT 16 bit REPEAT utasítás ciklus számlálója
CORCON 16 bit CPU vezérlőregiszter
A fenti regiszterek közül W0-W3-nak, valamint a státusz regiszter N, OV, Z, C, DC bitjeinek van egy másodpéldánya is (az ún. árnyékregiszterek), s a PUSH.S utasítás ezekbe elmenti (átmásolja), a POP.S utasítás pedig visszaállítja az eredeti regiszterek tartalmát. Arra ügyelni kell, hogy elmentés csak egyszintű lehet, tehát két PUSH.S utasítás nem követheti egymást (POP.S nélkül), mert a második mentés felülírja az elsőt.

A dsPIC33 mikrovezérlők CPU regiszterei

A dsPIC33 mikrovezérlő család tagjai a PIC24H mikrovezérlők regiszterein túlmenően az alábbi CPU regisztereket is tartalmazzák:
Regiszter Méret Funkció
ACCA, ACCB 40 bit Akkumulátor DSP regiszterek a "szorzás és összegzés" műveletek eredményeinek tárolásához
DCOUNT 16 bit DO ciklus számlálója
DOEND 23 bit DO ciklus végének utasításcíme
DOSTART 23 bit DO ciklus kezdetének utasításcíme
SR 16 bit A státusz regiszter további, a DSP egységhez tartozó biteket is tartalmaz

A PC programszámláló beállítása

Az előzőekben már volt róla szó, hogy a mikrovezérlő az utasításokat a programtároló memóriából veszi. A programszámláló regiszter (PC) tartalmazza azt a memóriacímet, ahonnan a soron következő utasítást kell venni. A programszámláló regiszter minden utasítás elővételekor automatikusan inkrementálódik, így mindig a soron következő utasításra mutat. De hogyan indul el a program, honnan veszi a mikrovezérlő az első utasítás címét? A program úgy indul el, hogy a tápfeszültség megjelenéskor, vagy egy RESET impulzus hatására a programszámláló regiszter nullázódik, így az első végrehajtott utasítás az lesz, ami a 0x000000 címen kezdődik. 

A program futása során előfordul, hogy nem a memóriabeli elhelyezkedés sorrendjében következő utasítást kell végrehajtani, hanem egy másik memóriaterületen kell folytatni a program futását, hogy ciklusokat szervezhessünk, vagy különféle feltételektől függő elágazásokat iktathassunk a programba. Ilyenkor közvetlenül vagy közvetve a programszámláló regiszter értékét módosítjuk a feltételes vagy feltétel nélküli ugróutasítások segítségével. 

Több-bájtos adatok kezelése

Mielőtt elkezdenénk az ismerkedést a 16 bites mikrovezérlők utasításaival, foglaljuk össze röviden a több-bájtos adatok számábrázolására és a memóriában történő eltárolásukra vonatkozó ismereteket! Mint láttuk, 8 bites bájtokkal, valamint 16 és 32 bites szavakkal lesz dolgunk (Emlékeztetőül: a 24 bites utasításokat egy csupa nullát tartalmazó fantom bájt 32 bites szavakra egészíti ki).

Méret Ábrázolási tartomány (előjel nélkül) hexadecimális
8-bit 0 - 28-1 = 0 - 255 0 - 0xFF
16-bit 0 - 216-1 = 0 - 65 535 0 - 0xFFFF
32-bit 0 - 232-1 = 0 - 4 294 967 295 0 - 0xFFFFFFFF

A 16 vagy 32 bites szavak legalacsonyabb helyiértékű bájtját LSB-vel jelöljük (a Least Significant Byte elnevezés alapján), a legmagasabb helyiértékű bájtjukat pedig MSB-vel (Most Significant Byte).

A több-bájtos szavak a "little endian" sorrendnek megfelelően helyezkednek el a memóriában, azaz a legkisebb helyiértékű bájt (LSB) kerül a legalacsonyabb memóriacímre, a többi bájt pedig helyiértéke szerint a növekvő sorrendben következő memória rekeszbe kerül.

Nézzünk egy egyszerű példát:

Tegyük fel, hogy van egy 16 bites, 0x8B1A értékű adat a 0x1000 memória címen.
Tegyük fel továbbá, hogy van egy 32 bites, 009025AC értékű adat a 0x1002 memória címen.

Ekkor bájt- illetve szószervezésben így néz ki az adatmemória érintett szelete:
Cím Tartalom Cím Tartalom
0x1000 1A <- LSB 0x1000 8B1A
0x1001 8B 0x1002 25AC
0x1002 AC <- LSB 0x1004 0090
0x1003 25 0x1006 ????
0x1004 90 0x1008 ????
0x1005 00 0x100A ????

Bájtcímzéssel ábrázolt memória szelet
Szócímzéssel ábrázolt memória szelet

A PIC24 és dsPIC33 mikrovezérlők utasításkészlete

A Microchip 16 bites mikrovezérlőinek (dsPIC30/33, PIC24) utasításkészlete a jelfeldolgozó utasításoktól eltekintve megegyezik, a PIC24 mikrovezérlők lényegében a DSP egységüktől megfosztott dsPIC33 vezérlőnek tekinthetők. Nem meglepő tehát, hogy a 16 bites mikrovezérlők utasítás készletének részletes leírása kizárólag a dsPIC30F/33F Programmer's Reference Manual-ban (programozói referencia kézikönyvben) található meg.

A gazdag utasításkészlet az alábbi tíz csoportba osztályozható:

Adatmozgató utasítások

Az adatmozgató utasítások feladata az, hogy átmásolják az adatot a forrás helyéről (jelölése: src, vagy röviden s)  a célhelyre (jelölése: dst, vagy röviden d). Szimbolikus jelöléssel így is írhatjuk az adatmozgatás:

(src) → dst         ahol '()'  jelentése = "tartalma" vmi-nek.
 
Ez a művelet két operandust használ (a forrás és a célhely címét).


A MOV{.B} Wns, Wnd utasítás

Jelentése: “Másold a Wns regiszter tartalmát a Wnd regiszterbe”. Az utasítás általános formája:

mov{.b} Wns, Wnd       (Wns) → Wnd

ahol a Wns operandus a 16 munkaregiszter (W0-W15) egyike, s az adatmozgatás forrását adja meg, Wnd pedig (amely szintén a W0-W15 munkaregiszterek egyike)  az adatmozgatás célhelyét adja meg. A kapcsos zárójelek közé zárt opcionális .B toldalékot akkor használjuk, ha csak 8 bites adatmozgatást kérünk.

mov W3, W5     jelentése tehát: (W3) → W5 (16 bites művelet)
mov.b W3, W5  jelentése pedig: (W3.LSB) → W5.LSB (bájtszélességű adatmozgatás).

W3 munkaregiszter tartalmát a W5 munkaregiszterbe másoljuk. A 'másolás' szó jelzi, hogy az adatátvitel során a forrás tartalma változatlanul megmarad, annak egy másolata kerül a célhelyre. Amint láthatjuk, az adatmozgatás lehet bájt vagy szószélességű.
 
Ezt a címzésmódot egyébként közvetlen regisztercímzésnek nevezzük. Az adatmozgató utasítás természetesen másfajta címzésmódok használatát is megengedi.

Vizsgáljuk meg az utasítások működését az MPLAB szimulátorában!

Hozzunk létre egy új MPLAB projektet a varázslóval, s válasszuk ki a PIC24HJ128GP502 mikrovezérlőt és a Microchip ASM30 Toolsuite fejlesztőeszközt! A Projekt/Add New File to Project menüpontban hozzunk létre egy forrásállományt (pl. asm01.s névvel), s írjuk bele az alábbi programot!
.include "p24hxxxx.inc"
.global __reset                 ; Az első utasítás cimkéje legyen globális! 
.text                           ; A programkód kezdete
__reset:
        MOV   W3, W2
        MOV.B W4, W2      
        MOV   W4, W2
vege:
        GOTO     vege           ; Egy végtelen ciklus zárja a programot
.end                            ; Forráskód vége jelző
A programba az .include direktívával becsatolt állomány definiálja a kényelmes programíráshoz szükséges szimbolikus neveket (pl. a W0-W15 és a többi SFR címéhez  rendelt nevet). A .global direktíva a program kezdetét jelölő __reset cimkét globális hozzáférésűként definiálja. A .text direktíva pedig jelzi, hogy programterület következik. A programot célszerűen egy végtelen ciklussal és az .end direktívával zárjuk.

A Debugger/Select Tool menüben válasszuk az MPLAB SIM eszközt, majd a Project/Make menüponttal elindított fordítás és linkelés sikeres lefutása után nyissuk meg a View menüben a Special Function Registers nevű ablakot! Ebben fogjuk majd nyomon követni az adatregiszterek tartalmát. Az Address oszlop fejlécére kattintva állítsuk címek szerint növekvő sorrendbe az SFR memória rekeszeinek listáját, s görgessük az ablak tartalmát úgy, hogy a W0..W5 regiszterek  láthatóak legyenek! A kettesével növekvő címekből láthatjuk, hogy 16 bites megjelenítési módban működik az adatmemória első két kilobájtnyi szelete tartalmának megjelenítése.

A nyomkövetést irányítsuk az első utasításra az F8 billentyű egyszeri megnyomásával! A MOV W3,W2 utasítás előtt egy zöld nyílnak kell megjelennie, jelezve, hogy az a soron következő (még nem végrehajtott) utasítás.

Mielőtt tovább mennénk, töltsük fel adatokkal az első néhány munkaregisztert! Az SFR ablakban a kiválasztott regiszter tartalmát úgy módosíthatjuk, hogy az értéket tartalmazó oszlopban duplakattintással szerkesztésre megnyitjuk az adott memóriarekeszt. Ne feledjük, hogy 16 bites értékekkel dolgozunk! Az ábrán látható, hogy a W0-W6 munkaregisztereknek (amelyeket itt WREG0 ... WREG6 néven találunk meg) minden hexadecimális helyiértékére a  regiszter sorszámát írtuk be. Sok értelme ugyan nincs ezeknek az értékeknek, de az adatmozgatásoknál legalább könnyű lesz majd  nyomon követni, hogy honnan került oda az adat.

Ha esetleg nem látjuk a Hex feliratú oszlopot, akkor az egér jobb gombjával csalogathatjuk elő a táblázat tulajdonságait, ahol beállíthatjuk az adatkijelzés módját is (bináris, decimális, hexadecimális vagy karakteres).

Ha ezzel megvagyunk, akkor nyomjuk meg ismét az F8 billentyűt, melynek hatására végrehajtódik a MOV W3, W2 utasítás, s az SFR ablakban láthatjuk az eredményét (pirossal az a rekesz lesz megjelölve,amelynek a tartalma megváltozott. Amint várható, és az alábbi ábrán is látható, ez az utasítás csak WREG2 tartalmát módosította: belemásolta WREG3 tartalmát, annak mind a 16 bitjét.  OK, ezt vártuk, úgyhogy mehetünk tovább! 
A MOV W3, W2 utasítás hatására
megváltozik WREG2 tartalma (0x2222
helyett most 0x3333 a tartalma)

A MOV.B W4,W2 utasítás hatására
a WREG2 regiszternek csak az alacsony
helyiértékű bájtja változik meg

Most a zöld nyílnak a második MOV utasítás előtt kell állnia. Nyomjuk le mégegyszer az F8 gombot, hogy lefuthasson ez az utasítás is! A fenti (jobboldali) ábrán láthatjuk, hogy a MOV.B utasítás a célhelyként megadott W2 regiszternek csak az alacsony helyiértékű bájtját írta felül, a magasabb helyiértékű 8 bit változatlan maradt.

Ha mégegyszer megnyomjuk az F8 gombot, a MOV W4, W2 utasítás hatására a W2 regiszter tartalma 0x4444 lesz, azaz a szószélességű átvitel miatt a magasabb helyiértékű bájt is felülíródik. A végeredményről nem készült ábra, azt ellenőrizze önállóan a kedves olvasó!

A dsPIC30F/33F Programmer's Reference Manual 5-153. oldalán megtaláljuk az utasítás általánosabb (másféle címzési módot is használó) alakját, valamint az utasítás gépi kódját:
0111 1www wBhh hddd dggg ssss
 ahol:
wwww az indirekt címzésnél alkalmazható eltolást tartalmazó regiszter címét jelenti, egyébként nulla
B        az adatátvitel szélességét adja meg: '0' szóátvitel, '1' bájtszélességű adatátvitel
hhh     a forrás címzési módját adja meg (közvetlen regisztercímzés esetén: 000)
dddd   a forrás regiszter címét adja meg
ggg     a cél címzési módját adja meg (közvetlen regisztercímzés esetén: 000)
ssss    a cél regiszter címét adja meg

Nézzük meg a fenti programunk utasításainak gépi kódját az MPLAB szimulátorában! A program sikeres lefordítása után nyissuk meg a View menüben a Program Memory ablakot, és görgessük a listát a 0x00200-as címhez! Ahogy az alábbi ábrán is láthatjuk, ebben az ablakban a programtároló memória tartalma vizsgálható. Az első oszlop a memória címet mutatja, a második oszlopban a 24 bites utasításkód látható, mellette pedig a címke (ha van) és az utasítás szimbolikus alakja jelenik meg.



Mint látható, a MOV W3, W2 utasítás kódja 780103 = 0111 1000 0000 0001 0000 0011, tehát a műveleti kód = 0111 1, wwww = 0000 (nincs eltolás, sem indirekt címzés), a közvetlen regisztercímzés miatt hhh és ggg = 000, B=0 (szóátvitel), a cél és a forrás pedig dddd = 0010 és ssss = 0011.

Hasonlóan elemezhető a MOV W4, W2 utasítás is. Az előzőhöz képest annyi a különbség csupán, hogy ebben az utasításban a forrás helye más: ssss = 0100 = 4. A MOV.B W4, W2 utasítás pedig abban különbözik tőle, hogy a B bit '1'-be van állítva, a bájtszélességű átvitel jelzésére. 


A MOV Wns, f utasítás

Az adatmozgató utasítások egy másik csoportja a munkaregiszterek és az adatmemória egy tetszőleges helye között végez adatátvitelt. A MOV Wns, f jelentése: "Másold a Wns regiszter tartalmát az f memóriacímre". Az f jelölés elnevezése onnan ered, hogy a korábbi Microchip mikrovezérlőknél az adatmemória általános célú (tehát az SFR tartományon kívüli) rekeszeit "file register"-nek hívják. Az utasítás általános formája:

mov Wns, f       (Wns) → f

ahol a Wns operandus a 16 munkaregiszter (W0-W15) egyike, s az adatmozgatás forrását adja meg, f pedig egy adatmemória címet képvisel. Ez az utasítás csak szavak (16 bites adatok) mozgatására használható. Például:

MOV W3, 0x1000       (W3) → 0x1000

A W3 regiszter tartalmát másoljuk a 0x1000 című helyre. A címzés módja mind a forrás, mind a cél esetében regiszter közvetlen típusú.

A MOV Wns, f utasítás gépi kódja = 1000 1fff ffff ffff ffff ssss  alakú, ahol 'f....f' a 16 bites memóriacím felső 15 bitje (a legalacsonyabb helyiértékű bitet automatikusan 0-nak veszi a CPU), 'ssss' pedig a forrásként megadott regiszter (W0..W16) sorszáma.

Például MOV W3, 0x1002 gépi kódja = 0x888013 = 1000 1000 1000 0000 0001 0011, ahol zölddel jelöltük a műveleti kódot, pirossal a memóriacím felső 15 bitjét tartalmazó 'f..f' biteket (ezek után tehát a CPU még tesz egy nullát!), s kék színnel a forrás munkaregiszter címét.  


A MOV f, Wnd utasítás

Ez az előző utasítás fordítottja. A MOV f, Wnd jelentése: "Másold az f memóriacímen található adatot a Wnd regiszterbe". Az utasítás általános formája:

mov f, Wnd       (f) → Wnd

ahol a Wnd operandus a 16 munkaregiszter (W0-W15) egyike, s az adatmozgatás célhelyét adja meg, f pedig egy adatmemória címet képvisel. Ez az utasítás is csak szavak (16 bites adatok) mozgatására használható. Például:

MOV 0x1000,W3       (0x1000)  → W3

A 0x1000 című memóriarekesz tartalmát másoljuk a W3 regiszterbe. A címzés módja mind a forrás, mind a cél esetében regiszter közvetlen típusú.

A MOV f, Wnd utasítás gépi kódja = 1000 0fff ffff ffff ffff dddd  alakú, ahol 'f....f' a 16 bites memóriacím felső 15 bitje (a legalacsonyabb helyiértékű bitet automatikusan 0-nak veszi a CPU), 'dddd' pedig a célként megadott regiszter (W0..W16) sorszáma.

Például MOV  0x1002, W3 gépi kódja = 0x808013 = 1000 0000 1000 0000 0001 0011, ahol zölddel jelöltük a műveleti kódot (ez csupán utolsó bitjében különbözik az előző MOV Wns,f utasítástól), pirossal a memóriacím felső 15 bitjét tartalmazó 'f..f' biteket (ezek után tehát a CPU még tesz egy nullát!), s kék színnel a cél munkaregiszter címét.  

Megjegyzés: Természetesen programozás közben nincs szükségünk a gépi utasításkódok ismeretére, ezeket pusztán a jobb megértés kedvéért mutattuk be. A továbbiakban nem is foglalkozunk ezek részletes bemutatásával, hiszen az összes utasítást és azok gépi kódját a kedves olvasó önállóan is megtalálja a dsPIC30F/33F Programmer's Reference Manual -ban. Ahogy korábban már említettük, a PIC24H mikrovezérlők utasításkészlete részhalmaza a dsPIC33 mikrovezérlők utasításkészletének, csupán a DSP utasítások nincsenek implementálva bennük. Ezért a PIC24H mikrovezérlők utasításait is a fenti kézikönyv ismerteti.


A MOV{.B} WREG, f utasítás

 Jelentése: "Másold a WREG regiszter tartalmát a megadott című memória rekeszbe!". Általános formája:

MOV{.B} WREG , f         (WREG) → f

Ez az utasítás a korábbi PIC mikrovezérlőkkel való kompatibilitást szolgálja. A WREG a PIC24 W0 regiszterét jelenti, f pedig egy memóriarekesz címét ami az adatmemória első 8192 bájtos tartományában (az ún. közeli memória tartományban) helyezkedhet el. Az utasítás bájt és szó mozgatására is használható.

MOV WREG, 0x1000    szó szélességű adatmozgatás
MOV.B WREG, 0x1001 a W0 regiszter tartalmának alsó 8 bitjét az 0x1001 című memória bájtba másolja

A szó szélességű adatmozgatásnál csak páros memóriacímet adhatunk meg!

Megjegyzés: a korábban bemutatott MOV Wns, f utasítás nem használható bájt szélességű adatmozgatásra!


A MOV{.B} f, WREG utasítás

Jelentése: "Másold az f címen levő memóriarekesz tartalmát a WREG regiszterbe!". Általános formája:

MOV{.B} f, WREG         (f) → WREG

MOV{.B} f                      (f) → f

Ez az utasítás is a korábbi PIC mikrovezérlőkkel való kompatibilitást szolgálja. A WREG a PIC24 mikrovezérlő W0 regiszterét jelenti, f pedig egy memóriarekesz címét, ami az adatmemória első 8192 bájtos tartományában (az ún. közeli memóriatartományban) helyezkedhet el. Az utasítás bájt és szó mozgatására is használható.

MOV 0x1000 WREG    szó szélességű adatmozgatás
MOV.B 0x1001, WREG  az 0x1001 című memória bájt tartalmát  a W0 regiszter alsó 8 bitjébe másolja
MOV 0x1000                a memóriarekeszt önmagába másolja (majd később látjuk meg, hogy ez mire jó)

A szó szélességű adatmozgatásnál csak páros memóriacímet adhatunk meg!

Megjegyzés: a korábban bemutatott MOV f,Wns utasítás nem használható bájt szélességű adatmozgatásra!
 

Konstans betöltése a munkaregiszterbe

Betölti az utasításban szereplő számkonstans értékét a munkaregiszterbe. A '#' számjel azt jelzi, hogy az utasításban szereplő számnak az értékét akarjuk felhasználni, s nem memóriacímet jelöl. Az utasítás általános alakja:

MOV #lit16, Wnd      lit16 → Wnd (szó szélességű adatmozgatás)
MOV.B #lit8, Wnd     lit8 → Wnd.lsb (bájt szélességű adatmozgatás)

Ennek az utasításnak a forrás operandusa az utasításban szereplő szám. Ez közvetlen címzési mód.
Példák:
MOV #0x1000, W2     0x1000 → W2
MOV.B #0xAB, W3     0xAB → W3.lsb
 
Figyeljük meg, hogy a következő két utasítás hatása mennyire különbözik egymástól:

MOV #0x1000, W2     Végrehajtása után W2 tartalma 0x1000 lesz, W2 = 0x1000
MOV 0x1000,W2         Az 0x1000  memóriacímen található szám másolódik be W2-be, W2 = (0x1000)


Indirekt címzés

Adatmozgatás indirekt címzéssel:

mov{.b} [Wns], [Wnd]      ((Wns)) → (Wnd)

A '[]' szögletes zárójel azt jelzi, hogy indirekt címzést használunk, tehát az effektív címet (EA) nem a regiszter sorszáma, hanem a regiszter tartalma adja meg. A forrás effektív címét (EAs) a Wns regiszter tartalma adja meg,amit (Wns)-sel jelölünk, a cél effektív címét (EAd) pedig a Wnd regiszter tartalma, azaz (Wnd). A MOV utasítás az effektív forráscímről az effektív cél címére másol, azaz:

(EAs) → EAd     ami így írható: ((Wns)) → (Wnd)

Az indirekt és a regiszter közvetlen címzést természetes keverhetjük is, lehet pl. a forrás címzése indirekt, a cél regiszteré direkt, vagy fordítva. Az alábbi példák gondos áttanulmányozásával igyekezzünk elmélyíteni a címzési módokról eddig tanultakat:

a.) Utasítás:  MOV W0, W1          Mind a forrás, mind a cél címzése "regiszter közvetlen".


b.) Utasítás:  MOV [W0], [W1]     Mind a forrás, mind a cél címzése regiszter indirekt.


c.) Utasítás:  MOV W0, [W1]     A forrás címzése regiszter közvetlen, a cél címzése regiszter indirekt.


Matematikai utasítások

Az ADD{.B} Wb, Ws, Wd utasítás

Három operandusos összeadás, regiszterből regiszterbe. Az utasítás formája:

ADD{.B} Wb, Ws, Wd      (Wb) + (Ws) → Wd

ahol Wb, Ws és Wd a 16 munkaregiszter bármelyike lehet. Például:

ADD W0, W1, W2          (W0) + (W1) → W2
ADD W2, W2, W2          W2 = W2 + W2 =  2*W2
ADD.B W0, W1, W2       W0 és W1 alacsonyabb helyiértékű bájtjának összegét W2 alacsony
                                  helyiértékű bájtjába írjuk

Az alábbi példákban figyeljük meg, hogy az összeadó utasítások hatására hogyan változik a munkaregiszterek tartalma:



A SUB{.B} Wb, Ws, Wd utasítás

Három operandusos kivonás, regiszterből regiszterbe. Az utasítás formája:

SUB{.B} Wb, Ws, Wd      (Wb) - (Ws) → Wd

ahol Wb, Ws és Wd a 16 munkaregiszter bármelyike lehet.

Legyünk óvatosak! Az összeadás kommutatív tulajdonsága miatt az ADD Wx,Wy,Wz utasítás ugyanarra az eredményre vezet, mint az ADD Wy,Wx,Wz utasítás, vagyis az összeadandók felcserélhetők. A kivonásnál azonban a kisebbítendő és a kivonandó nem cserélhetők fel, a SUB Wx,Wy,Wz és a  SUB Wy,Wx,Wz utasítások jelentése különböző.

Példák:
SUB W0, W1, W2          (W0) - (W1) → W2
SUB W1, W0, W2          (W1) - (W0) → W2

SUB.B W0, W1, W2       W0 és W1 alacsonyabb helyiértékű bájtjának különbségét W2 alacsony
                                  helyiértékű bájtjába írjuk

Az alábbi példákban figyeljük meg, hogy az utasítások hatására hogyan változik a munkaregiszterek tartalma!



Konstans kivonása/hozzáadása

Három operandusos művelet, regiszterekkel és számkonstanssal.

ADD{.B} Wb, #lit5, Wd      (Wb) – #lit5 → Wd
SUB{.B} Wb, #lit5, Wd      (Wb) – #lit5 → Wd

Ahol #lit5 egy 5 bites, azaz 0-31 közötti előjel nélküli számértéket (literális) jelöl. Ez az utasítás lehetőséget nyújt arra, hogy valamelyik munkaregiszter tartalmához hozzáadjunk egy kisebb számot, vagy elvegyünk belőle.  Például:
ADD W0, #4, W2      (W0) + 4 → W2
SUB.B W1,#8, W3    (W1) – 8 → W3
ADD W0, #60, W1     érvénytelen utasítás, mert 60 nagyobb, mint 31!

ADD{.B} f {,WREG} Instruction

Két operandusos összeadás: összeadja az f címen levő memóriarekesz tartalmát a WREG regiszter tartalmát. Az utasítás formájától függően az eredmény vagy az f memóriacímre vagy a WREG regiszterbe íródik be.

ADD{.B} f               (f) + (WREG) → f
ADD{.B} f, WREG     (f) + (WREG) → WREG

ahol WREG a W0 munkaregiszter, f pedig az adatmemória első 8192 bájtnyi tartományába eső cím.

Az operandusok egyike, vagy f vagy WREG mindig felülíródik!
Példák:

ADD 0x1000                (0x1000) + (WREG) → 0x1000
ADD 0x1000,WREG       (0x1000) + (WREG) → WREG
ADD.B 0x1001, WREG    (0x1001) + (WREG.lsb) → WREG.lsb

Nézzük meg ezeknek az utasításoknak a működését közelebbről is:


SUB{.B} f {,WREG} Instruction

Két operandusos kivonás: az f címen levő memóriarekesz tartalmából kivonja a WREG regiszter tartalmát. Az utasítás formájától függően az eredmény vagy az f memóriacímre vagy a WREG regiszterbe íródik be.

SUB{.B} f               (f) - (WREG) → f
SUB{.B} f, WREG     (f) - (WREG) → WREG

ahol WREG a W0 munkaregiszter, f pedig az adatmemória első 8192 bájtnyi tartományába eső cím.

Az operandusok egyike, vagy f vagy WREG mindig felülíródik!
Példák:

SUB 0x1000                (0x1000) - (WREG) → 0x1000
SUB 0x1000,WREG       (0x1000) - (WREG) → WREG
SUB.B 0x1001, WREG    (0x1001) - (WREG.lsb) → WREG.lsb

Nézzük meg ezeknek az utasításoknak a működését közelebbről is:


Inkrementáló utasítás

Eggyel megnöveli a forrásként szolgáló operandus tartalmát, és eltárolja a célként megnevezett regiszterbe. Az utasítás alakja és jelentése:

INC{.B} Ws, Wd         (Ws) +1 → Wd

Az inkrementáló utasítás memóriába vagy  memóriából a WREG regiszterbe is használható

INC{.B} f                   (f) + 1 → f
INC{.B} f, WREG         (f) + 1 → WREG

Ahol az f memóriacím az adatmemória első 8192 bájtnyi területére mutató cím lehet.
Példák:
INC W2, W4                (W2) + 1 → W4
INC.B W3, W3             (W3.lsb) + 1 → W3.lsb
INC 0x1000                (0x1000) +1 → 0x1000
INC.B 0x1001,WREG    (0x1001)+1 → WREG.lsb

Dekrementáló utasítás

Eggyel csökkenti a forrásként szolgáló operandus tartalmát, és eltárolja a célként megnevezett regiszterbe. Az utasítás alakja és jelentése:

DEC{.B} Ws, Wd         (Ws) -1 → Wd

A dekrementáló utasítás memóriába vagy  memóriából a WREG regiszterbe is használható.

DEC{.B} f                   (f) - 1 → f
DEC{.B} f, WREG         (f) - 1 → WREG

Ahol az f memóriacím az adatmemória első 8192 bájtnyi területére mutató cím lehet.
Példák:
DEC W2, W4                (W2) - 1 → W4
DEC.B W3, W3             (W3.lsb) - 1 → W3.lsb
DEC 0x1000                (0x1000) -1 → 0x1000
DEC.B 0x1001,WREG    (0x1001)-1 → WREG.lsb

A GOTO utasítás

A program menetét befolyásoló utasítások közül az egyik legegyszerűbb a GOTO utasítás, amellyel már találkoztunk az MPLAB szimulátorában futtatott kis próbaprogramunk végén.

A GOTO utasítás az operandus értékét beírja a programszámlálóba, kijelölve ezzel a következő végrehajtandó utasítás címét. Az operandus értéke az az előjel nélküli 23 bites szám, amelyet a fordító a GOTO utáni kifejezés kiértékeléséből előállít. Az utasítás alakja:

GOTO Expr      lit23 → PC

ahol az Expr kifejezés vagy egy címke, vagy egy kifejezés, amit a futtatható programot összeállító linker program egy 23 bites memóriacímként (aminek páros számnak kell lennie!) értelmezni tud. A lit23 jelölés ezt a 23 bites értéket jelöli.

A GOTO utasítás - a benne tárolt hosszú cím miatt - csak két utasításszóban fér el: az első szóban az utasításkód mellett a cím alsó 16 bitje helyezkedik el. A legalacsonyabb helyiértékű bit mindig nulla!  A magasabb helyiértékű maradék 7 bit a következő utasításszó alsó 7 bitjében helyezkedik el.
Assembly utasítás Gépi kód
GOTO 0xnnnnnn
 0000 0100 nnnn nnnn nnnn nnn0
0000 0000 0000 0000 0nnn nnnn
GOTO 0x000800 0x040800; 0x000000
A GOTO utasítás feltétel nélküli ugrást eredményez.

Egy egyszerű program anatómiája

Írjunk egy egyszerű utasítás-sorozatot! Ha C nyelven írnánk, akkor valahogy így nézne ki:
uint8 i,j,k;    // Helyet foglalunk az adatoknak: előjel nélküli 8 bites változók

i = 100;         // i = 100
i = i + 1;       // i++, i = 101
j = i;           // j értéke 101
j = j - 1;       // j--, j értéke 100
k = j + i;       // k = 100 + 101 = 201
Természetesen ez nem egy komplett program, de ez most nem fontos. Nézzük meg, hogy hogyan lehet ezeket a C nyelvű parancsokat az eddig tanult assembly utasításokkal megfogalmazni! Előbb azonban tisztáznunk kell, hogy a változók értékei hol legyenek tárolva. Egy logikus válasz erre az, hogy tároljuk ezeket az adatmemória szabad területének (ami a 0x800 címen kezdődik, mivel a 0x000-0x7ff terület - az SFR terület - már foglalt) első néhány bájtján. Rendeljük tehát a változókhoz a következő memória címeket: i címe legyen 0x0800, a j változó címe legyen 0x0801, végül k címe legyen 0x0802.

A fentiek  felhasználásával megírhatjuk az itt pirossal jelölt C utasításoknak megfelelő assembly utasításokat. Az alábbi listában a pontosvesszővel kezdődő megjegyzések nem tartoznak a programhoz, csak az áttekintést segítik.
; i = 100
mov.b #100, W0        ; W0 = 100
mov.b WREG,0x800      ; i = 100

; i = i + 1
inc.b 0x800           ; i = i + 1

; j = i
mov.b 0x800,WREG      ; W0 = i
mov.b WREG,0x801      ; j = W0

; j = j – 1
dec.b 0x801           ; j= j - 1

; k = j + i
mov.b 0x800,WREG      ; W0 = i
add.b 0x801,WREG      ; W0 = W0+j (WREG is W0)
mov.b WREG,0x802      ; k = W0

Mint látható, az assembly programrész nem túl olvasmányos, nehezen áttekinthető. Az is látszik, hogy a C-hez képest az assembly nyelv kevésbé hatékony, ugyanazt a műveletet több utasítással tudjuk csak megfogalmazni.  
Az assembly utasításokból a gépi kódot elvileg kézzel is elő tudnánk állítani (az egyes utasítások gépi kódjait tartalmazó táblázat alapján), de sokkal kényelmesebb és hatékonyabb ezt az MPLAB fejlesztői környezet Assembler fordító programjának segítségével végezni. Ehhez azonban a fenti utasítás sorozatot további információkkal kell kiegészíteni, hogy a fordítóprogram és a végrehajtható programot összeállító (linker) program számára értelmezhető és egyértelmű legyen a feladat. Magunk számára pedig azzal javítjuk a program áttekinthetőségét, hogy az adattárolásra lefoglalt tárhelyekhez szimbolikus neveket (i, j, k) rendelünk.

Hozzunk létre egy új MPASM30 projektet az MPLAB fejlesztői környezetben, és írjuk be az alábbi programot mptst_byte.s néven! Elemezzük a programot!

.include "p24Hxxxx.inc"
.global __reset

         .bss           ; adatterület
i:       .space 1       ; 1 bájt i-nek
j:       .space 1       ; 1 bájt j-nek
k:       .space 1       ; 1 bájt k-nek

.text                   ; kódterület
__reset:                ; program kezdete
    adat = 100
;i = 100 
    mov.b #adat, W0     ; W0 = 100
    mov.b WREG,i        ; i = 100

; i = i + 1
    inc.b   i           ; i = i + 1

; j = i
    mov.b   i,WREG      ; W0 = i
    mov.b   WREG,j      ; j = W0

; j = j - 1 
    dec.b   j           ; j--

; k = j + i
    mov.b   i,WREG      ; W0 = i (WREG valójában W0)
    add.b   j,WREG      ; W0 = W0+j
    mov.b   WREG,k      ; k = W0

done:
    goto    done        ; végtelen ciklus
.end       ; program vége

A programban szereplő, ponttal kezdődő szavak nem a mikrovezérlőnek szánt utasítások, hanem a fordítónak szóló eligazítások, úgynevezett direktívák. Ilyen például az .include "p24Hxxxx.inc", amely azt az eszközleíró állományt csatolja be, amely tartalmazza az adott mikrovezérlő regisztereinek és konfigurációs bitjeinek szimbolikus neveit. Ennek becsatolása teszi lehetővé, hogy pl. a WREG regiszterre név szerint hivatkozhassunk. Szintén a fordítónak szól a .global __reset direktíva, ami a program belépési pontját globális szimbólumként definiálja.

A fordító figyelmen kívül hagyja a pontosvesszővel kezdődő jelsorozatot, egészen az adott sor végéig. A pontosvesszővel kezdődő karaktersorozat tehát csupán megjegyzés (comment), a programra nézve nincs hatása.

A .bss direktíva azt jelzi, hogy az utána következő helyfoglalások az inicializálatlan adatmemória területére vonatkoznak. Az "inicializálatlan" azt jelenti, hogy az adatmemória ezen területének tartalma meghatározatlan a program indulásakor, tehát addig nincs értelme olvasni belőle, amíg valami értelmes adattal előbb fel nem töltöttük.  A .space 1 direktíva egy bájtnyi memóriaterületet foglal le, folytatólagosan. Így - mivel az első szabad hely a 0x800 címen van (a 0x000-07ff közötti címtartományt az SFR foglalja le) - az i változó címe 0x800, j címe 0x801, k címe pedig 0x802 lesz.
         .bss           ; adatterület
i:       .space 1       ; 1 bájt i-nek
j:       .space 1       ; 1 bájt j-nek
k:       .space 1       ; 1 bájt k-nek

A változók számára történő helyfoglalás után nézzük, mi kerüljön a programmemóriába! A .text direktíva jelzi, hogy a programtároló memóriában kell elhelyezni az utána következő utasításokat. Mivel másként nem rendelkeztünk, a kódterület az interrupt vetorok számára lefoglalt 0-0x1ff címtartomány mögött kezdődik, ennek megfelelően a __reset címkéhez a 0x200 cím tartozik. 

Az adat = 100 sor segítségével egy szimbólumot definiálunk, amelynek minden előfordulásánál a fordító behelyettesíti az itt definiált számértéket. Megjegyzés: ez a számérték tízes számrendszerben értendő!
.text                   ; kódterület
__reset:                ; program kezdete
    adat = 100
;i = 100 
    mov.b #adat, W0     ; W0 = 100
    mov.b WREG,i        ; i = 100
A MOV utasításoknál a .B módosító jelzi, hogy 8 bites adatokkal és bájtszintű memóriacímzéssel dolgozunk. Az első utasítás a WREG regiszterbe (ami tulajdonképpen W0-nak felel meg) betölti egy számkonstans értékét (a #adat literálist a fordító #100-zal, azaz #0x64-gyel helyettesíti). Itt arra kell ügyelnünk, hogy a számkonstans értéke ne haladja meg a 8 biten történő ábrázolhatóság felső korlátját, a decimális 255 értéket!

A második utasítás a WREG regiszter alsó 8 bitjét (tehát a fent említett 100-at) bemásolja az i változóba (a z adatmemória 0x800 című bájtjába. Ezzel a két utasítással tehát megtörtént az i = 100 értékadás.

Az i = i +1 értékadás lényegében egy inkrementálással elintézhető:
; i = i + 1
    inc.b   i           ; i = i + 1
Figyeljünk arra, hogy 8 bites inkrementálást kell végezni, ezért itt is kell a .B módosító!

Hasonló egyszerűséggel, egy dekrementálással elvégezhető a j = j -1 művelet is:
; j = j - 1 
    dec.b   j           ; j--

Valamivel komplikáltabb eset a k = i + j összeadás. A komplikációt az okozza, hogy az adatok a memóriában vannak, nem az általános célú regiszterekben, ezért elő kell venni az összeadandókat, illetve a végén el kell tárolni az eredményt.
; k = j + i
    mov.b   i,WREG      ; W0 = i
    add.b   j,WREG      ; W0 = W0+j
    mov.b   WREG,k      ; k = W0

Először az i változó értékét bemásoljuk a W0 regiszterbe, majd hozzáadjuk a j változó tartalmát, s a végeredményt átmásoljuk a k változóba. Itt is mindenhol 8 bites műveleteket végzünk!

A programot egy végtelen ciklussal zárjuk (az utolsó utasítás önmagára ugrik vissza), nehogy "kiszaladjunk" a memóriából. A forráskód legvégét pedig az .end direktíva zárja.
done:
    goto    done        ; végtelen ciklus
.end       ; program vége

Ha sikerült lefordítani a programot, akkor készüljünk fel a program szimulátorban történő futtatására és a nyomkövetésre! A Debugger menüben válasszuk az MPLAB Simulatort! A WREG (W0) tartalmát a Special Function Registers ablakban, az adatmemóriát pedig a File Registers nevű ablakban ellenőrizhetjük. Tartsuk nyitva a forrásfájl ablakát is, és rendezzük el célszerűen, például az alábbi ábrán látható módon!

Az F8 gomb első megnyomására megjelenik egy zöld nyíl a __reset címke utáni első utasítás mellett.


Az utasításokat az F8 gomb nyomogatásával egyenként hajtatjuk végre a szimulátorral, s közben figyeljük a fejleményeket.

Az első utasítás decimális 100-at tölt be a WREG munkaregiszterbe, azaz hexadecimálisan 64-et (6x16+4 = 100). A Special Function Registers ablakban láthatjuk az utasítás eredményét.

Megjegyzés: a fenti ábrán látható programból kifelejtettem az adat szimbólum definiálását, ezért szerepel a konkrét számérték (#100) a mov.b #100, WREG utasításban.

A következő utasítás átmásolja WREG alsó 8 bitjét az i változóba, az adatmemória 0x800 című bájtjába. Ennek eredménye a File Registers ablakban jelenik meg, ahogy ezt az ábrán is láthatjuk.

Figyeljünk arra, hogy a File Registers ablakban is szószervezésű a megjelenítés, ezért (és a "little endián" típusú memóriakezelésből következően) balról jobbra olvasva a 0x801 című (magasabb helyiértékű) bájt megelőzi a 0x800 címen tárolt, alacsonyabb helyiértékű bájtot!


A soron következő utasítás eggyel megnöveli az i változó értékét, 101 azaz hexadecimálisan 0x65 lesz. Figyeljük meg azt az itten ábrákon nem rögzített állapotot, amely szerint az i értéke megnőtt, miközben a WREG értéke változatlan (0x64) maradt! Ez a Microchip vezérlők egyik előnyös tulajdonsága: közvetlenül az adat memóriában tárolt adatokon is végezhetünk bizonyos műveleteket, nem szükséges ehhez egy általános célú regiszterbe betölteni majd visszamásolni.

A következő két utasítás az i változó tartalmát a WREG regiszteren keresztül átmásolja a j regiszterbe. Ennek a műveletsornak a végeredménye látható az ábrán. Itt i, j és WREG tartalma egyaránt 0x65, azaz 101.

A következő utasítás j értékét csökkenti eggyel (dekrementálás). A 0x801 című bájt értéke 0x64-re csökken. A művelet egyszerűsége miatt erről az állapotról nem készítettünk külön ábrát. 

A soron következő utasítások a k = i +j összeadást végzik el. Először betöltjük i értékét a WREG munkaregiszterbe. Egy optimalizáló fordító észrevehetné, hogy ez a művelet fölösleges, hiszen sem i sem WREG értéke nem változott meg a legutóbbi mov.b i,WREG utasítás óta. De a Microchip ASM30 nem végez optimalizálást, csupán gépi kódra fordítja az általunk leírt assembly utasításokat.

Ezután WREG tartalmához hozzáadjuk a j változó értékét. Az eredmény a WREG regiszterbe kerül. Ellenőrizzük az összeadást!

WREG = 101 + 100 = 201,  hexadecimálisan: 0x65 + 0x64 = C9

Másképp felírva: WREG = (6x16 +5) + (6x16+4) = 12x16 + 9 

Végül az így kapott összeget beírjuk a k változóba (a 0x802 című memóriabájtba). A végeredmény az ábrán látható.

Mennyi idő alatt fut le a programunk?

A program futási idejének kiszámításához két dolgot kell tisztázni:
A PIC24H és a dsPIC33 mikrovezérlők maximális órajel frekvenciája 80 MHz, de egy utasításciklus két órajelperiódust igényel. Így a 80 MHz futó mikrovezérlő másodpercenként 40 millió utasításciklust hajt végre. Mivel a periódusidő a frekvencia reciproka ( T = 1/f), a mikrovezérlőnk egy órajelperiódusa T = 1 /80 000 000 = 12,5 ns (nanoszekundum, 1 ns = 10-9 s). Egy utasításciklus pedig két órajel periódusig tart, tehát 25 ns-ot vesz igénybe.

Az, hogy az egyes utasítások hány utasításciklust vesznek igénybe, kiolvasható az utasításkészletre vonatkozó táblázatokból, amelyek a mikrovezérlő adatlapjában vagy a dsPIC30F/33F Programmer's Reference Manual-ban találhatók. A legtöbb utasítás egy utasításciklust vesz igénybe. Azok az utasítások, amelyek megváltoztatják a programszámláló értékét, általában két utasításciklust igényelnek. Az iteratív osztási művelet azonban 18 utasításciklusig tart.

Az említett források felhasználásával számoljuk most össze az mptst_byte.s program utasításainak végrehajtási idejét! Természetesen csak a __reset és a done címkék közötti utasításokat vesszük figyelembe. A done címke utáni végtelen ciklust még 80 MHz-en is túl lassan hajtja végre a mikrovezérlő... :-)
Az utasítás kódja utasításciklusok
mov.b #adat, W0 1
mov.b WREG,i     1
inc.b   i           1
mov.b   i,WREG 1
mov.b   WREG,j 1
dec.b   j 1
mov.b   i,WREG 1
add.b   j,WREG  1
mov.b   WREG,k 1
Összesen: 9 utasításciklus
Tehát 80 MHz-es órajelet feltételezve 9 x 25 = 225 ns alatt lefut a program, a milliomod másodperc egynegyede is elég neki!

Használjunk 16 bites változókat és műveleteket!

Mi lenne, ha 16 bites változókat használnánk? Erre rá is kényszerülünk akkor, ha például a 8 biten ábrázolható, legfeljebb  255-g terjedő számnál nagyobb értékekkel akarunk számolni. Ha például 100 helyett 2047-re módosítjuk a kiinduló értéket,  az előző programunk C nyelvi megfelelője így nézne ki:
uint16 i,j,k;    // előjel nélküli 16 bites változók

i = 2047;        // i = 2047
i = i + 1;       // i++, i = 2048
j = i;           // j értéke 2048
j = j - 1;       // j--, j értéke 2047
k = j + i;       // k = 2047 + 2048 = 4095

Az assembly nyelven megírt programunk sem fog sokban különbözni a korábbitól, csak arra kell ügyelnünk, hogy 16 bites tárhelyet foglaljunk le a változóknak, s a műveleteknél el kell hagyni a .B módosítókat. Írjuk be az alábbi programot, s legyen a forrásfájl neve mptst_word.s (emlékeztetőül, hogy most szószélességű adatokkal dolgozunk).
.include "p24Hxxxx.inc"
.global __reset

         .bss           ; adatterület
i:       .space 2       ; 2 bájt i-nek
j:       .space 2       ; 2 bájt j-nek
k:       .space 2       ; 2 bájt k-nek

.text                   ; kódterület
__reset:                ; program kezdete
    adat = 2047
;i = 2047 
    mov #adat, W0       ; W0 = 2047
    mov WREG,i          ; i = 2047

; i = i + 1
    inc     i           ; i = i + 1

; j = i
    mov   i,WREG        ; W0 = i
    mov   WREG,j        ; j = W0

; j = j - 1 
    dec   j             ; j--

; k = j + i
    mov   i,WREG        ; W0 = i (WREG valójában W0)
    add   j,WREG        ; W0 = W0+j
    mov   WREG,k        ; k = W0

done:
    goto    done        ; végtelen ciklus
.end       ; program vége
Mint látható, a .space 2 direktíva használható a 16 bites helyfoglalásra. Most i címe 0x800, j címe 0x802, k címe pedig 0x804 lesz.

A nyomkövetés és az ellenőrzés folyamatát itt nem ismertetjük, hiszen ugyanúgy történik, mint az előző esetben. Tanulságos lesz azonban annak összeszámolása, hogy mennyi az mptst_word.s program utasításainak végrehajtási ideje! Most is csak a __reset és a done címkék közötti utasításokat vesszük figyelembe.
Az utasítás kódja utasításciklusok
mov #adat, W0 1
mov WREG,i     1
inc   i           1
mov   i,WREG 1
mov   WREG,j 1
dec   j 1
mov   i,WREG 1
add   j,WREG  1
mov   WREG,k 1
Összesen: 9 utasításciklus
Megállapíthatjuk tehát, hogy az mptst programunk 16 bites változata is ugyanannyi utasításciklust vesz igénybe, ugyanannyi idő alatt fut le, mint a 8 bites változat. Ez azért van így, mert a PIC24 mikrovezérlő család 16 bites felépítésű, tehát a 16 bites adatokkal is ugyanolyan hatákonyan bánik, mint a 8 bites adatokkal.

Egy 8 bites mikrovezérlő esetében azonban, amilyen például a PIC18 vagy a PIC16 mikrovezérlő család nagyjából kétszer ennyi utasításciklusra lenne szükség a 16 bites műveletek elvégzéséhez.

A PIC24 mikrovezérlőn is kétszer ennyi utasításciklust venne igénybe a program, ha 32 bites adatokkal szeretnénk elvégezni a fenti műveletsort. A 32 bites adatokon végzett műveletekkel majd a későbbiekben találkozunk.