8/16 bites műveletek PIC24 assembly nyelven

[Szerzői jogok]

A fejezet tartalma:

Eben a fejezetben a PIC24 mikrovezérlők utasításainak újabb csoportjaival ismerkedünk meg, mégpedig a 8/16 bites bitenkénti logikai műveletekkel, a bitkezelő (bit vizsgálata, törlése, beállítása) utasításokkal, az összehasonlítás és a vizsgálat eredményétől függő ugró utasításokkal, a programciklust szervező utasításokkal, valamint a bitforgatás és léptetés műveletekkel,.

A 4.1 táblázatban felsoroltuk a C nyelvi megfelelőjét azoknak az aritmetikai és logikai utasításoknak, melyekkel ebben a jegyzetben foglalkozunk. Ahogy az előző fejezetben láthattuk, ezeket a műveleteket az adatútra kapcsolódó aritmetikai és logikai egység (ALU) hajtja végre.

4.1 táblázat: aritmetikai és logikai utasítások

Művelet Leírás
+, - 
++, -- 
*, / 
>>, <<
&, |, ^ 
~
(+) összeadás, (–) kivonás
(++) inkrementálás, (– –) dekrementálás
(*) szorzás, (/) osztás
jobbra léptetés (>>), balra léptetés (<<)
bitenkénti AND (&), IOR (|), XOR (^)
bitenkénti komplementálás
A fenti C logikai műveletek PIC24 assembly megfelelőit keressük. Az előző fejezetben már megismerkedtünk néhány fontos aritmetikai művelettel (összeadás, kivonás, inkrementálás, dekrementálás), s most a bitenkénti logikai ÉS (&), VAGY (|), kizáró vagy (^) és a komplementálás (~) műveletekkel fogunk megismerkedni, ezek alkotják az ALU logikai utasításkészletét. A szorzással és az osztással majd később foglalkozunk.

Bitenkénti logikai műveletek

A bitenkénti jelző itt azt jelenti, hogy az AND, IOR, XOR műveleteket bitenként, az operandusok azonos helyiértékű bitjei között végezzük. Ezek a műveletek rendkívül hasznosak, ha egy-egy bitcsoportot törölni, logikai 1-be állítani vagy ellenkezőjére változtatni akarunk.   
Bitenkénti ÉS (AND, &) művelet
Assembly utasítás Jelentése C megfelelője
AND .{B} Wb,Ws,Wd (Wb)&(Ws)→Wd j = k & i;
AND.{B} f (f)&(WREG) →f  j = j & k;
AND.{B} f, WREG (f)&(WREG) →WREG j = j & k;
AND.{B} #lit10,Wn lit10 & (Wn) →Wn j = j & literal;
Mint látható, az aritmetikai utasításokhoz hasonlóan a bitenként műveleteket is végezhetjük két általános regiszter tartalmát felhasználva, vagy egy memória regiszter és a WREG (W0) között. A második és a harmadik utasítás csak a cél kijelölésében különbözik: AND f eredménye a memóriába, AND f, WREG eredménye pedig a W0 regiszterbe kerül. A .B módosító itt is 8 bites műveletet jelöl. A negyedik utasításnál lit10 egy számkonstanst jelöl, melynek értéke 16 bites műveleteknél 0..1023 közötti szám lehet, a 8 bites műveleteknél pedig csak 0..255 közötti szám lehet.
Bitenkénti MEGENGEDŐ VAGY (IOR, |) művelet
Assembly utasítás Jelentése C megfelelője
IOR .{B} Wb,Ws,Wd
(Wb) | (Ws)→Wd j = k | i;
IOR.{B} f (f) | (WREG) →f  j = j | k;
IOR.{B} f, WREG (f) | (WREG) →WREG j = j | k;
IOR.{B} #lit10,Wn  lit10 | (Wn) →Wn  j = j | literal;

Az AND utasításnál elmondottak érvényesek a megengedő (IOR) és a kizáró vagy (XOR) utasítás esetére is.
Bitenkénti KIZÁRÓ VAGY (XOR, ^) művelet
Assembly utasítás Jelentése C megfelelője
XOR .{B} Wb,Ws,Wd (Wb) ^ (Ws)→Wd j = k ^ i;
XOR.{B} f (f) ^ (WREG) →f j = j ^ k;
XOR.{B} f, WREG (f) ^ (WREG) →WREG j = j ^ k;
XOR.{B} #lit10,Wn lit10 ^ (Wn) →Wn j = j ^ literal;

A komplementálás (vagy komplemens képzés) egyoperandusos művelet, s komplemens azt jelenti hogy, hogy az operandus bitjeit bitenként az ellenkezőjére változtatjuk, az ~1 =0 és a ~0 = 1 szabályok szerint.  A második, "operandusnak látszó" cím csak azt mondja meg, hogy hová tároljuk el az eredményt.
Bitenkénti KOMPLEMENTÁLÁS (~) művelet
Assembly utasítás Jelentése C megfelelője
COM .{B} Ws,Wd ~(Ws)→Wd j = ~k;
COM.{B} f ~(f) →f  j = ~j ;
COM.{B} f, WREG  ~(f) →WREG j = ~k;
Mire valók a bitenkénti logikai műveletek? Egy egyszerű példa az ASCII karakterek nagybetűsből kisbetűsbe alakítása, vagy az ellenkező irányú konverzió. Az 'A' betű ASCII kódja például 0x41. Ha ezt a kódot megengedő VAGY kapcsolatba hozzuk a 0x20 számmal, akkor az eredmény 0x61 lesz, ami az 'a' betű ASCII kódja. Hasonló módon bármelyik betű kódját kisbetűssé alakíthatjuk a karakterkód 5. bitjének 1-be állításával (a bitek számozását a legkisebb helyiértéktől, 0 sorszámmal kezdjük). Ha a fordított irányba kell konvertálni (kisbetűk átalakítása nagybetűkké), akkor pedig 0xDF maszkkal kell bitenként ÉS kapcsolatba hozni a karakter kódjával, a 0 & x = 0 valamint az 0 & x = x azonosságokat. Tehát amelyik biten a maszk értéke 0, azon  helyiértéken az ÉS művelet eredménye a másik operandus értékétől függetlenül nulla lesz.   

Természetesen egyszerre egynél több bitet is törölhetünk, vagy állíthatunk egybe a bitenkénti logikai műveletekkel. Az alábbi táblázatban egy-egy példán keresztül mutatjuk be a bitcsoport törlését AND utasítással, egybe állítását IOR utasítással, ellenkező állapotra történő állítását XOR utasítással, s az összes bit komplementálását a komplemensképző utasítással. Az egyszerűség kedvéért 8 bites adatokon végzünk műveleteket. A komplementálás kivételével ezek a műveletek két gépi utasítást igényelnek.

Tegyük fel, hogy az adatmemóriában tárolt változók értékei: i = 0x2C;  j = 0xB3; k = 0x8A;
C kód PIC24 assembly kód Végrehajtás
uint8 i;
i = i & 0x0F;

bitcsoport törlése
AND utasítással
mov.b #0x0F,W0 ; W0 = maszk
and.b i     ; i = i & 0x0f
 i = 0x2C = 0010 1100
  &&&& &&&&
maszk = 0x0F = 0000 1111
---------
    eredmény = 0000 1100  =  0x0C
uint8 j;
j = j | 0x0E;

bitcsoport beállítása
IOR művelettel
mov.b #0x0E,w0 ; w0 = maszk
ior.b j ; j = j|0x0E
 j = 0xB3 = 1011 0011
  |||| ||||
maszk = 0x0E = 0000 1110
---------
    eredmény = 1011 1111  =  0xBF
uint8 k;
k = k^0xC0
bitek komplementálása
XOR művelettel
mov.b #0xC0,w0 ; w0 = maszk
xor.b k ; k = k^0xC0
 k = 0x8A = 1000 1010
  ^^^^ ^^^^
maszk = 0xC0 = 1100 0000
---------
    eredmény = 0100 1010  =  0x4A
uint8 k;
k = ~k;

bitenkénti
komplementálás
com.b k ; k = ~k
 k = 0x8A = 1000 1010
 
komplementálás után
---------
    eredmény = 0111 0101 =  0x75
AND műveletnél:  ha a maszk valamelyik bitje null, akkor az eredmény megfelelő bitje is nulla lesz.
IOR műveletnél: ha a maszk valamelyik bitje 1, akkor az eredmény megfelelő bitje is 1 lesz.
XOR műveletnél: ha a maszk valamelyik bitje 1, akkor az eredmény megfelelő bitje az eredeti adat ellenkezője lesz.

Bit beállító, bit törlő és bit billegtető utasítások

Valamelyik memóriarekesz vagy speciális funkciójú regiszter egyetlen bitjének törlése, beállítása, vagy ellenkező állapotba billentése olyan gyakran előforduló művelet, hogy külön utasítások formájában is meg vannak valósítva (bcf, bsf, btg). Természetesen az előbb ismertetett bitenként logikai műveletekkel is elvégezhetők volnának, de akkor egyenként két-két utasítást igényelnének. Ezen utasítások használatát és működését az alábbi táblázatban foglaltuk össze:
Megnevezés PIC24 assembly kód Jelentés
Bit beállítás bset{.b} Ws, #bit4
bset{.b} f,#bit4
1  →Ws<#bit4>
1  →f<#bit4>
Bit törlés bclr{.b} Ws, #bit4
bclr{.b} f,#bit4
0  →Ws<#bit4>
0  →f<#bit4>
Bit átbillentés btg{.b} Ws, #bit4
btg{.b} f,#bit4
~Ws<#bit4>0  →Ws<#bit4>
~f<#bit4>  →f<#bit4>
Nézzünk néhány egyszerű példát ezen utasítások használatára! Legyenek az adatmemóriában tárolt 8 bites változók értékei: i = 0x2C;  j = 0xB3; k = 0x8A. Töröljük k 7. bitjét, állítsuk be j 2. bitjét, s komplementáljuk i 5. bitjét!
C kód PIC24 assembly kód Végrehajtás
uint8 i,j,k;
k = k & 0x7F;

k 7. bitjének törlése
bclr.b k, #7
 k = 0x8A = 1000 1010
bclr.b k,#7
---------
 eredmény = 0000 1010  =  0x0A
uint8 i,j,k;
j = j | 0x04;

j 2. bitjének beállítása
bset.b j, #2
 j = 0x8A = 1011 0011
bset.b k,#2
---------
 eredmény = 1011 0111  =  0xB7
uint8 i,j,k;
i = i ^ 0x20;

i 5. bitjének komplementálása
btg.b i, #5
 i = 0x2C = 0010 1100
btg.b i,#5
---------
 eredmény = 0000 1100  =  0x0C
Jegyezzük meg, hogy ezen utasításoknak nincs C nyelvi megfelelője. C programokban az egyes bitek törlését, beállítását és komplementálását is a korábban ismertetett logikai műveletekkel és megfelelő maszk érték választásával lehet megvalósítani.

A státuszregiszter

A státuszregiszter egy nagyon fontos speciális funkciójú regiszter. Mostanáig nem foglalkoztunk vele, de immár  elengedhetetlen hogy megismerkedjünk vele, hiszen a feltételes program elágazásokhoz szükségünk lesz rá. A státuszregiszter több olyan jelzőbitet tartalmaz, amelyek a korábban ismertetett bitkezelő utasításokkal is törölhetők vagy beállíthatók, de elsődleges arra szolgálnak, hogy a végrehajtott utasítások mellékhatásaként, ez utasítások eredményétől függően álljanak be 1, vagy 0 értékre. Ezek a státuszbitek jelzik számunkra, hogy a végrehajtott utasítás eredménye nulla volt-e, keletkezett-e átvitel, vagy történt-e túlcsordulás, stb. 
- - - - - - - DC IPL2 IPL1 IPL0 RA N OV Z C
Az egyes bitek jelentése:
Bit Angol elnevezés A státuszbit jelentése
C Carry Átvitel történt az előző művelet során
Z Zero Nulla lett az előző művelet eredménye
OV Overflow Túlcsordulás történt az előző művelet során
N Negative Negatív szám lett az előző művelet eredménye
RA Repeat Active Programciklus aktív (még folyamatban van)
IPL0..2 Interrupt Priority Level Programmegszakítás prioritási szintje (0 - 7)
DC Decimal Carry Tízes átvitel (BCD számábrázolású műveleteknél)
- Nincs implementálva

A státuszbitek közül C, Z, OV, N és DC bitműveletekkel is törölhető, illetve beállítható, ezen kívül az aritmetikai és a logikai műveletek mellékhatásaként áll be, az eredménytől függően.

Az RA bit csak olvasható. A REPEAT utasítás hatására beáll, s csak a ciklus lefutásakor törlődik.

Az IPL0..2 bitek csak programból írhatók/olvashatók.


Honnan tudjuk, hogy melyik utasítás módosítja mellékhatásként a státuszbiteket, s azok közül melyiket? Ha fellapozzuk a dsPIC33FJ128GP802 mikrovezérlő adatlapját, akkor az utasításkészletet ismertető összefoglaló táblázat utolsó oszlopában megtaláljuk az adott utasítás által módosított státuszbitek felsorolását. Például:
Utasítás Szintaxis Jelentés Szó ciklus Érintett
státuszbitek
ADD ADD f  f = f + WREG 1 1 C,DC,N,OV,Z
MOV MOV f,Wn Wn = (f) 1 1 egyik sem
MOV MOV f f = (f) 1 1 N,Z
GOTO GOTO cím cím-hez ugrik 2 2 egyik sem
SUB SUB f  f = f - WREG 1 1 C,DC,N,OV,Z
Mint látjuk, az ADD utasítás az ALU összes státuszbitjét érinti, a MOV f utasítás csak az N és Z biteket, a MOV f,Wn utasítás egyiket sem, és így tovább.

A Carry és a Zero bitek

A státuszregiszter 0. bitje Carry (átvitel, C) bit, az 1. bit pedig Zero (nulla, Z) bit néven ismert. Ezeket a BSET/BCLR utasításokkal is beállíthatjuk, illetve törölhetjük, de sok utasítás mellékhatásaként is megváltozhat az értékük.

Összeadásnál: A Zero bit értéke 1 lesz, ha a művelet eredménye nulla. A Carry bit értéke pedig akkor lesz 1, ha egy művelet elvégzése során átvitel keletkezik a legmagasabb helyiértékű biten.
Bájt szélességű (8 bites) összeadás esetén C = 1, ha az összeg > 255 (0xFF).
Szó szélességű (16 bites) összeadás esetén C = 1, ha az összeg > 65535 (0xFFFF).
Néhány mintapélda (bájt szélességű műveletekkel):
 0xF0
+0x20
--------
0x10 Z=0,C=1
 0x00
+0x00
--------
0x00 Z=1,C=0
 0x01
+0xFF
-------
0x00 Z=1,C=1
 0x80
+0x7F
--------
0xFF Z=0,C=0
Kivonásnál: A Zero bit értéke 1 lesz, ha a művelet eredménye nulla. A Carry bit törlődik, ha egy művelet elvégzése során áthozatal történik a legmagasabb helyiértékű biten (előjel nélküli alulcsordulás, az eredmény < 0, azaz nagyobb számot vonunk ki kisebb számból). A Carry bit értéke 1 lesz, ha nem történt áthozatal. Néhány mintapélda (bájt szélességű műveletekkel):
 0xF0
-0x20
--------
0xD0 Z=0,C=1
 0x00
-0x00
--------
0x00 Z=1,C=1
 0x01
-0xFF
--------
0x02 Z=0,C=0
Kivonás eredményeként Z=1, C=0 állapot sohasem jöhet létre, mivel nullát csak akkor kapunk eredményül, ha két egyenlő számot vonunk ki egymásból,  ilyenkor viszont nem keletkezik átvitel.

A Carry bit kivonás utáni "viselkedését" a következő megfontolás alapján érthetjük meg: Az A-B kivonási műveletet a mikrovezérlő aritmetikai egysége valójában az A + (~B +1) összeadásra vezeti vissza, s a Carry bit értéke az A + (~B + 1) összeadásnak megfelelően áll be. A (~B + 1) mennyiséget egyébként B kettes komplemensének nevezzük, s a negatív számok ábrázolásánál még találkozunk vele.

Nézzük meg ezt az 0xF0 - 0x20 kivonás példáján!
0xF0-0x20 ~0x20  0xF0+(~0x20)+1
 0xF0
-0x20
--------
0xD0 Z=0,C=1
(nincs áthozat)
 0x20 = 0010 0000
~0x20 = 1101 1111

= 0xDF
 0xF0
+0xDF
+0x01
--------
0xD0 Z=0,C=1
(van átvitel)

Feltételes programvégrehajtás bitvizsgálattal

A Zero és a Carry státuszbitek felhasználásnak egyik fontos területe a programelágazások megvalósításához szükséges feltételvizsgálat. Most a "bitvizsgálat, ugorj, ha a bit törölt" (btsc - 'bit test, skip if clear') és a "bitvizsgálat, ugorj, ha a bit beállított' (btss - 'bit test, skip if set') utasításokat fogjuk használni feltételes programvégrehajtásra.

btsc{.b} f, #bit4   ;átugorja a következő utasítást, ha  f 4. bitje törölt (azaz '0')
btss{.b} f, #bit4   ;átugorja a következő utasítást, ha  f 4. bitje beállított (azaz '1')

A PIC24 mikrovezérlők a fentieken kívül számos további feltételvizsgáló és  feltételes programvégrehajtást végző utasítással rendelkeznek,amelyekkel majd később fogunk megismerkedni.

Az alábbi egyszerű program a btsc utasítás használatát mutatja be:

A __reset címke utáni bset loc,#0 utasítás miatt a loc változó legkisebb helyiértékű bitje '1' állapotba billen, így a btsc loc,#0 utasítás nem fogja átugrani a goto loc_lsb_is_1 utasítást. Így a program a hosszabb nyíl által jelzett útvonalon folytatódik, s az adat változóba a 3, 2, 1 számsorozat elemei íródnak be, s ez ismétlődik, végtelen ciklusban. A loc és az adat változók értékét az MPLAB szimulátorában a View menü File registers pontjában megnyitott ablakban figyelhetjük meg (loc a 0x800, adat pedig a 0x801 címen helyezkedik el).



Ha a loc változó értékét módosítjuk (a File registers ablakban rákattintunk a 0x800 címen levő adatra és felülírjuk, legyen pl. 0000), akkor elérhetjük, hogy a következő feltételvizsgálat már a rövidebb nyíl által jelzett útvonalat választja, s ekkor az adat változóban az 5, 4, 3, 2, 1 sorozat ismétlődik.

Arra is felhívjuk a figyelmet, hogy ha a program elején található bset loc,#0 utasítást a bclr loc,#0 utasításra cseréljük, akkor a másik ágon kezdődik el a program. Ennek a programnak természetesen nincs gyakorlati haszna, csupán a szemléltetés kedvéért mutattuk be!

Mielőtt a feltételes programvégrehajtással folytatnánk az ismerkedést, tekintsük át a C programnyelvben használatos feltételvizsgálati lehetőségeket, amelyekkel többnyire az if  és a ciklusszervező utasításokban találkozunk!
Relációs operátor Jelentése
==, != Egyenlő, Nem egyenlő
>, >= Nagyobb, Nagyobb,vagy egyenlő
<, <= Kisebb, Kisebb, vagy egyenlő
&& Logikai AND (ÉS)
|| Logikai OR (VAGY)
! Negáció (logikai tagadás)
A C programokban ha egy feltételvizsgálat nullától különböző értéket eredményez, akkor a feltételt logikailag igaznak (TRUE) tekintjük.

Vigyázzunk: A logikai tagadás és a bitenkénti komplemensképzés különböznek egymástól, eltérő eredményre vezetnek!

A fentiekhez hasonlóan különbözik egymástól a & bitenkénti logikai művelet az && logikai relációtól és az | bitenkénti logikai művelet az || logikai relációtól. A relációs operátorok (!, &&, ||) ugyanis mindig csak abban a tekintetben vizsgálják az operandus(oka)t, hogy nulla, vagy nem nulla értékűek, s a logikai vizsgálat eredménye egyetlen bitnyi adat (0 vagy 1) lesz. A bitenkénti logikai műveletek ezzel szemben bitenként, egymástól függetlenül minden helyiértéken elvégzik a kiértékelést, s az eredmény is 8 vagy 16 bites lesz. Figyeljünk ezekre az apró de annál fontosabb különbségekre, mert ellenkező esetben kellemetlen meglepetésként érhet bennünket, hogy a C programunk nem azt csinálja, amit elvárunk tőle!

Az alábbi példákon C egyenlőség- és egyenlőtlenség-vizsgálatokat mutatunk be (8 bites műveletek)
uint8 a,b,a_lt_b, a_eq_b, a_gt_b, a_ne_b;
a = 5; b = 10;
a_lt_b = (a < b);  // a_lt_b eredménye 1
a_eq_b = (a == b); // a_eq_b eredménye 0
a_gt_b = (a > b);  // a_gt_b eredménye 0
a_ne_b = (a != b); // a_ne_b eredménye 1

Példák C logikai műveletekre (8 bites műveletek) uint8 a_lor_b, a_bor_b, a_lneg_b, a_bcom_b;
a = 0xF0; b = 0x0F;
a_land_b = (a && b); //logikai ÉS, eredménye 1
a_band_b = (a & b);  //bitenkénti ÉS, eredménye 0
a_lor_b = (a || b);  //logikai VAGY, eredménye 1
a_bor_b = (a | b);   //bitenkénti VAGY, eredménye 0xFF
a_lneg_b = (!b);     //logikai negálás, eredménye 0
a_bcom_b = (~b);     //bitenkénti negálás, eredménye 0xF0

Nulla, nem nulla típusú feltételvizsgálatok

Az előző két listán szereplő utasítások nem mondhatók tipikusnak, a gyakorlatban ritkán fordulnak elő. A feltételvizsgálatokkal leggyakrabban az if vagy a ciklusszervező utasításokban találkozhatunk. A C programozási nyelvben használt if-else utasítás formáját az alábbi ábrán mutatjuk be. Jegyezzük meg, hogy az if ág programtörzse akkor kerül végrehajtásra, ha a feltétel teljesül (a feltételvizsgálat eredménye = 1), az else ág törzse pedig akkor kerül végrehajtásra, ha a feltétel nem teljesült (a feltételvizsgálat eredménye = 0).

if (feltételvizsgálat) {
     if_törzse           <-- akkor hajtódik végre, ha a vizsgálat eredmény = 1
} else  {
     else_törzs          <-- akkor hajtódik végre, ha a vizsgálat eredmény = 0
}
Az if és az else törzse egynél több utasítást is tartalmazhat. Az else ág használata opcionális, elhagyható.

Hogyan fogalmazzuk meg a feltételvizsgálatot, ha például az i változó nulla, vagy nem nulla értékétől függően akarunk végrehajtani  valamilyen utasítást, például a j = i + j  összeadást? Az alábbi ábrán több lehetőséget is bemutatunk:
Akkor hajtódik végre, ha i = 0 Akkor hajtódik végre, ha i nullától különbözik
if (!i) {
     j = i + j;
}
if (i) {
   j = i + j;
}
A fenti utasításokat így is írhatjuk:
Akkor hajtódik végre, ha i = 0 Akkor hajtódik végre, ha i nullától különbözik
if (i == 0) {
   j = i + j;
}
if (i != 0) {
   j = i + j;
}
C programok írásánál gyakori hiba az egyenlőség-vizsgálat (==) és az értékadás (=) jelének összekeverése. Az alábbi példákon megmutatjuk, hogy ez milyen meglepetést okozhat:
Hibás kód! Helyes kód
if (i = 5) {    
   j = i + j;  //Mindig végrehajtódik, mert az i=5
}              //értékadás visszatérési értéke 5 lesz!
if (i == 5) {  //Az i==5 vizsgálat csak
   j = i + j;    // akkor ad nullától különböző
}                 // értéket, amikor i értéke 5

A bitenkénti és a logikai AND művelet különbözősége

Korábban már volt róla szó, hogy a bitenkénti és a logikai ÉS műveletek is könnyen összetéveszthetők. Az alábbi példákban úgy szemléltetjük a kettő különbségét, hogy bemutatjuk a feltételvizsgálat helyes olvasatát, s egy konkrét esetre megmutatjuk, hogy a hasonlónak tűnő feltételvizsgálatok különböző eredményre vezetnek.

if (i && j) {          // Ha i nem nulla ÉS j sem nulla, akkor...
     /* tedd ezt */    // i = 0xA0, j = 0x0B esetén
}                      // i && j = 1 (tehát teljesül a feltétel)

if (i & j) {           // Ha i bitenkénti ÉS kapcsolata j-vel nem nulla, akkor...
     /* tedd ezt */    // i = 0xA0, j = 0x0B esetén  
}                      // i & j = 0 (tehát nem teljesül a feltétel)

A bitenkénti és a logikai OR művelet különbözősége

Egy kicsit más a helyzet a megengedő VAGY műveleteknél. Habár a logikai művelet (||) itt is csak az operandusok nullától való különbözőségének tényét veszi figyelembe, a bitenkénti művelet (|) pedig minden bitre külön-külön végzi el ugyanezt, a kétféle feltételvizsgálat eredménye csak számszerűségében tér el egymástól, hatása ugyanaz lesz. Az alábbi példában mindkét esetben teljesül a feltétel, tehát az if törzs végrehajtásra kerül.

if (i || j) {          // Ha i nem nulla VAGY j nem nulla, akkor...
     /* tedd ezt */    // i = 0xA0, j = 0x0B esetén
}                      // i || j = 1 (tehát teljesül a feltétel)

if (i | j) {           // Ha i bitenkénti VAGY kapcsolata j-vel nem nulla, akkor...
     /* tedd ezt */    // i = 0xA0, j = 0x0B esetén  
}                      // i & j = 0xAB (tehát teljesül a feltétel)

Nullától való különbözőség vizsgálata

Az alábbi programrészletben egy if utasítás szerepel, amely a k változó nullától való különbözőségét vizsgálja. Az if(k) feltételvizsgálat ekvivalens az if(k!=0)-val, nincs előnye egyiknek sem a másikhoz képest. Assembly nyelven a feltételvizsgálatot a mov k utasítással készíthetjük elő, ami a változó értékét önmagába másolja vissza. A látszólag értelmetlen utasítás azonban mellékhatásként beállítja a státuszregiszter N és Z bitjeit. Ezek közül most a Z bit érdekel bennünket, ami '0' lesz, ha a vizsgált változó értéke nullától különböző. Ezután egy btfsc SR,#Z utasítás (bitvizsgálat, és ugrás, ha Z=0) átugorja a következő utasítást, ha Z=0, s így az if törzs végrehajtásra kerül. Ha azonban Z=1, akkor a goto end_if utasítás kerül végrehajtásra,ami átugorja az if törzset, s a program az end_if címkétől folytatódik.


Assembly nyelven másképp is megírhatjuk a feltételes elágazást, ha a btsc és goto utasításpárt egy bra Z utasítással helyettesítjük. A bra Z jelentése "branch if zero", azaz: elágazás, ha a Z státuszbit = '1'. A bra Z utasítás egyike a PIC24 elágaztató utasításainak, amelyek feltételes ugróutasításként működnek, egy vagy több státuszbit beállítottságától függően.

Az egy státuszbit állapotát vizsgáló egyszerű elágaztató utasításokat az alábbi táblázatba foglaltuk össze:
BRA Z, <címke> A címkéhez ugrik, ha Z=1
BRA NZ, <címke> A címkéhez ugrik, ha Z=0 (nem nulla)
BRA C, <címke> A címkéhez ugrik, ha C=1
BRA NC, <címke> A címkéhez ugrik, ha C=0 (nincs átvitel)
BRA N, <címke> A címkéhez ugrik, ha N=1
BRA NN, <címke> A címkéhez ugrik, ha N=0 (nem negatív)
Meg kell említeni még a BRA <címke> utasítást is, ami a GOTO utasításhoz hasonlóan feltétel nélküli ugrást jelent. A feltétel nélküli BRA és a GOTO utasítás azonban különböznek egymástól. A BRA utasítások egyszavasak, s kódjuk egy legfeljebb 16 bites relatív címet tartalmazhat, ami azt jelenti, hogy az aktuális programszámlálóhoz képest 32 K utasítást ugorhatunk át előre, vagy visszafelé. Ez azt jelenti, hogy a 128 KB memóriával rendelkező  PIC24HJ128GP502 mikrovezérlőben BRA utasítással nem tudunk elugrani a programmemória egyik végétől a másikba. Természetesen a relatív cím kiszámítását  a fordítóprogram elvégzi helyettünk. A GOTO utasítás ezzel szemben két memóriaszót vesz igénybe, s a maximális (4M) címtartomány megcímezhető vele.

Nézzük akkor meg, hogy a bra utasítással hogy néz ki a módosított programtöredék!

A programunk így nemcsak rövidebb lett (a GOTO utasítás elhagyása miatt 2 utasítás-szóval, vagyis 6 bájttal kevesebb a memória-felhasználás), hanem az áttekinthetőség is javult.

Figyeljük meg, hogy az assembly programban használt BRA utasítás működése ellentétes a C programbeli if utasításéval: az if utasítás végrehajtja, a BRA utasítás pedig átugorja az if_törzs-et, ha teljesül az utána írt feltétel. Így nem meglepő, hogy a helyes működéshez a BRA utasításnál az if feltételének ellentettjét (a logikai negáltját) kell írnunk. Ha például a fenti programban a k!=0 feltétel helyett a k==0 feltétel teljesülését akarnánk előírni, akkor a C programban if (!k), az assembly programban pedig bra NZ,end_if feltételt kellene írni.

A feltételes programelágazás általánosabb (if-else) alakja

Az alábbi ábrán láthatjuk a feltételes programelágazás általánosabb formáját C nyelven és a helyenként pszeudo-utasításokkal megtűztelt assembly megfelelőjét. Mint látható, két BRA utasítással megoldható az if-else szerkezet leutánzása: az első BRA utasítás feltételes ugrást hajt végre, ha az if utasításban előírt feltétel nem teljesül (kihagyja az if_törzs-et). A második BRA utasítás feltétel nélküli ugrás, ami az if_törzs végéről az else_törzs utáni első utasításra ugrik. Ez biztosítja, hogy az else_törzs ne kerüljön végrehajtásra, ha az if utasításban megfogalmazott eredeti feltétel teljesült.

Ne feledkezzünk meg róla, hogy a C program if utasításban szereplő feltétel ellentettjét kell az Assembly program bra utasításába írni!

Egyenlőség és nem egyenlőség vizsgálata

Az alábbi programrészlet bemutatja, hogyan fogalmazhatjuk meg assembly nyelven a j==k feltételt vizsgáló if utasítást. Az egyenlőség-vizsgálathoz a j-k kivonást használjuk fel, amit egy BRA NZ utasítás követ. A kivonás művelete ugyanis a kivonás eredményétől függően állítja be a státuszregiszter bitjeit,így a Z=0 állapot jelzi, ha j és k nem egyenlők egymással (ekkor kell átugrani az if_törzs-et. Itt most nem számít, hogy a j-k vagy a k-j különbséget képezzük, egyenlőség esetén ugyanúgy nullát kell kapnunk.

A fentiek alapján könnyen átírhatjuk a programrészletet arra az esetre, ha a j!=k feltételt akarjuk vizsgálni, csak a bra NZ,end_if utasítást kell kicserélni egy bra Z,end_if utasításra.

Egyenlőtlenség vizsgálata kivonással, a Z és C bitek alapján

A kisebb, nagyobb, kisebb vagy egyenlő, nagyobb vagy egyenlő vizsgálatokat a korábban bemutatottakhoz hasonlóan kivonással is vizsgálhatjuk. Két változó (például j és k) esetén bármelyiket kivonhatjuk a másikból, ennek megfelelően a Z és a C státuszbitek alakulását nehéz áttekinteni. Az áttekintés segítésére az alábbi ábrákon összefoglaltuk az összes lehetőséget.  


Megjegyzés:
k <= j a DeMorgan azonosság miatt azonos ~(k>j)-vel, ami a ~(C & ~Z), azaz a (~C|Z) feltétellel vizsgálható. Hasonlóan: (k < j) = ~(k>=j) = ~C.


Megjegyzés:
k < j a DeMorgan azonosság miatt azonos ~(k>=j)-vel, ami a ~(!C|Z), azaz a (C&~Z) feltétellel vizsgálható. Hasonlóan: (k <= j) = ~(k>j) = ~(~C)=C.
Az alábbi példában a k > j feltételt vizsgáljuk, a k - j kivonást használva.

A k > j feltétel ellentettje (ami az if törzs kikerüléséhez szükséges) k<=j alakba írható. Mivel a k-j kivonást választottuk, a C=0 és Z=1 esetén kell átugrani az if törzset, tehát két BRA utasításra van szükség.

Nézzük meg a másik lehetőséget is, amikor a k > j feltételt a j - k kivonást használva vizsgáljuk! Az if törzs kikerüléséhez szükséges feltétel k<=j lesz, melynek feltétele C=1. Tehát ebben az esetben egyetlen BRA C utasításra van csak szükség.

Összehasonlítás, elágazás előjel nélküli feltételvizsgálattal

Az előzőekben példákat láttunk arra, hogy az egyenlő, nem egyenlő, kisebb, stb. feltételvizsgálatokat hogyan valósíthatjuk meg egy kivonás és egy (vagy két) bra utasítással. A kivonás és a státuszbitek nyomonkövetése nem könnyű feladat. Egyes esetekben gondot okozhat, az is, hogy a vizsgálat során az egyik regiszter tartalmát felülírjuk. Kényelmesebb utat kínálnak a PIC24 mikrovezérlők összehasonlító (CP, Compare) utasításai, melyeket az előjel nélküli feltételvizsgáló utasításokkal kombinálunk.

Az alább felsorolt előjel nélküli összehasonlítást végrehajtó utasítások is kivonással dolgoznak, de nem módosítják egyik operandus tartalmát sem. Szerepük csupán az, hogy a kivonás eredményének megfelelően állítsák be a státuszregiszter DC, N, OV, Z, C bitjeit.
A művelet leírása Szintaxis Művelet
Az f memóriarekesz összehasonlítása WREG regiszterrel CP{.B} f f – WREG
A Wb és Ws regiszterek összehasonlítása  CP {.B} Wb,Ws Wb – Ws
A ws regiszter összehasonlítása egy max. 5 bites számmal CP{.B} Wb,#lit5 Wb – #lit5
Az f memóriarekesz összehasonlítása 0-val CP0{ B} f f - 0
Ws összehasonlítása nullával CP0{.B} Ws Ws – 0
Az elágazás előjel nélküli összehasonlítás esetén az alább felsorolt utasításokkal végezhető, melyek egy vagy több státuszbitet vizsgálnak meg, az összehasonlítás módjától függően. Ezeket az elágaztató utasításokat egy előjel nélküli vizsgálatot végző CP utasítás kell, hogy megelőzze. Tehát a munkamegosztás úgy néz ki, hogy a CP utasítás beállítja a státuszbiteket, a BRA utasítás pedig teszteli azok valamilyen kombinációját.
A művelet leírása Szintaxis Ugrás akkor, ha..
Elágazás, >, előjel nélküli BRA GTU, címke C=1 && Z=0
Elágazás, >=, előjel nélküli BRA GEU, címke C=1
Elágazás <, előjel nélküli BRA LTU, címke C=0
Elágazás <=, előjel nélküli BRA LEU, címke C=0 || Z=1
Természetesen ezek a BRA utasítások is relatív címzésű ugrásokat végeznek ±32K utasításszónyi határon belül. Az utasítások alakjának megjegyzés könnyebben megy, ha feloldjuk a rövidítéseket (FORTRAN programozók előnnyel indulnak!): BRA = branch (elágazás), GT = greater than (nagyobb, mint...), GE = greater or equal (nagyobb, vagy egyenlő mint...), LT = less than (kisebb, mint...), LE = less or equal  (kisebb, vagy egyenlő mint...), az U toldalék pedig az unsigned (előjel nélküli) vizsgálatot jelzi.

Nézzünk néhány egyszerű mintapéldát a CP és BRA utasítások használatára!

Előjel nélküli összehasonlítás, k > j vizsgálata

Az if törzs kikerülésének feltétele k <=j, tehát a bra LEU,címke utasítást kell használnunk.

If - else példa

Mivel a C programban az előző példához képest megfordítottuk az if utasítás feltételét, az assembly programban is ellentettjére kell változtatni a feltételt. Most k > j teljesülése esetén kell kihagyni az if törzset, tehát  egy bra GTU,címke utasításra lesz szükségünk. Az if törzsének végén a korábban tanult módon, egy feltétel nélküli ugrással kerüljük ki az else ágat!

Számkonstanssal történő előjel nélküli összehasonlítás

Az alábbi két példa között csupán az a különbség, hogy az első esetben egy kis számmal történik az összehasonlítás, s ez "belefér"  a CP Wb,#lit5 utasítás elfogadott értéktartományába (0..31), így ennek az utasításnak a használatát mutatjuk be, míg a második esetben egy nagyobb számmal történik az összeadás, amit már csak egy 16 bites regiszterben tudunk elhelyezni. Ha ez a W0 regiszter, akkor a CP f utasítást használhatjuk.

Megjegyzés: Ha egy másik regiszterbe töltenénk (pl. W1-be) a számkonstansunkat, akkor csak a CP Wb,Ws utasítást használhatnánk, amihez előbb be kellene tölteni a k változót is egy regiszterbe, tehát hosszabb lenne a program, és eggyel több regisztert használna. 


A switch utasítás megvalósítása assembly nyelven

A C programokban gyakran előfordulnak láncolt if-else struktúrák, amelyekben egy változó értékétől függően kerül valamelyik ág kiválasztásra, s fut le az abban az ágban előírt tevékenység. Ez a feladatválasztásos szerkezet annyira  alapvető, hogy a C programnyelvbe külön utasításként be is építették switch néven, s ezzel tömörebben,áttekinthetőbben fogalmazhatjuk meg, ahogy az alábbi ábrán is láthatjuk.
Láncolt if-else szerkezet Switch szerkezet
unsigned char i, j, k;
if (i == 1) {
   k++;
}
else if (i == 2) {
   j--;
}
else if (i == 3) {
   j = j + k;
}
else {
   k = k - j;
}
unsigned char i, j, k;
switch (i) {
  case 1: k++;
    break;

  case 2: j--;
    break;

  case 3: j = j + k;
    break;

  default: k = k - j;
}
A switch utasítás minden case blokkja megfelel egy-egy if (feltétel) ágnak, s mindig a switch utasításban megnevezett változó értékét hasonlítja a case mellett álló számkonstanshoz. Természetesen a számkonstansoknak nem kell sorbarendezve szerepelniük, s értékük is tetszőleges. A default ág akkor kerül végrehajtásra, ha egyik case ág feltétele sem teljesült. Fontos szerepe van a case ágak végén elhelyezett break utasításnak: ez gondoskodik róla, hogy a program ne "csorogjon rá" a következő case ágra.  


Megjegyzés: az i változó értékét a W0 regiszterbe töltjük, s minden összehasonlítást azon végezzük el. A piros aláhúzással megjelölt helyeken nem okoz gondot, hogy felülírjuk W0 értékét, mert akkor már túl vagyunk az összes feltételvizsgálaton. 

A fenti programban is használt CP W0,#lit5 utasításokban a literális értéke 5 bites terjedelmű (0 - 31 közötti értékű) lehet.

Az előjel nélküli feltételvizsgálatok összefoglalása

Az alábbi táblázatban összefoglaltuk az előjel nélküli mennyiségekkel dolgozó feltételes vizsgálatokat. Az első oszlopban az előírt feltétel szerepel, C szintaxisban. A második oszlop azt mutatja, hogy milyen kivonással (SUB vagy CP utasítás) állíthatók be  a státuszbitek az adott feltétel vizsgálatához. A harmadik és a negyedik oszlop pedig azt mutatja meg, hogy milyen ugróutasítással állapíthatjuk meg a feltétel teljesülését ("Igaz" ág), vagy nem teljesülését ("Hamis" ág). 
Feltétel Vizsgálat "Igaz" ághoz ugrik "Hamis" ághoz ugrik
i == 0  i − 0  bra Z  bra NZ
i != 0    i − 0 bra NZ bra Z
i == k   i − k bra Z  bra NZ
i != k    i − k bra NZ bra Z
i > k i − k  bra GTU  bra LEU
i >= k  i − k   bra GEU bra LTU
i < k   i − k  bra LTU bra GEU
i <= k   i − k  bra LEU bra GTU

További PIC24 összehasonlító utasítások

A PIC24 utasításkészlete  néhány további összehasonlító utasítást is tartalmaz:
Utasítás Név jelentése Leírás
CPSEQ Wb,Wn Compare, skip is equal Átugorja a következő utasítást, ha Wb==Wn
CPSNE Wb,Wn Compare,skip if not equal Átugorja a következő utasítást, ha Wb!=Wn
CPSGT Wb,Wn Compare, skip if greather than... Átugorja a következő utasítást, ha Wb>Wn
CPSLT Wb,Wn Compare, skip if less than... Átugorja a következő utasítást, ha Wb<Wn

Összetett feltételek vizsgálata

A gyakorlati programozás során előfordul, hogy összetett feltételeket kell vizsgálnunk. Ezeknél a feladatoknál  a De Morgan azonosságok felhasználásával vezethetjük le az if törzs kikerülését jelentő negált feltételeket. Például az if (feltétel1 && feltétel2) { valamit csinál } típusú szerkezeteknél  az eredeti feltétel negálásánál az !(A && B) = !A || !B azonosságot alkalmazhatjuk az if törzsét kikerülő BRA utasítások feltételeinek meghatározásához. Az !A || !B feltétel olvasata: akár az A, akár a B feltétel nem teljesül, kerüld ki az if törzsét! Nézzünk erre egy egyszerű mintapéldát!

Feladat: Írjuk meg a C nyelvi if (i && j) { k++; } utasítás megfelelőjét PIC24 assembly nyelven!

Megoldás: Az i && j feltétel ekvivalens az (i!=0) && (j!=0) feltétellel, vagyis az inc k utasítást csak akkor kell végrehajtani, ha i is és j is nullától különbözik. Mivel a BRA utasításhoz (az if törzs, az inkrementálás kikerüléséhez)  ezen feltételek ellentettjét kell megadni, így akár i==0, akár j==0 teljesülése esetén át kell ugrani az if törzsét.

    .include "p24Hxxxx.inc"
    .global __reset       

         .bss        ;inicializálatlan adatterület
  i:     .space 2   
  j:     .space 2   
  k:     .space 2

         .text       ;Kódterület eleje
__reset: 
     mov  i          ;i értékének vizsgálata 
     bra Z,end_if    ;If törzs kihagyása, ha i==0
     mov j           ;j értékének vizsgálata
     bra Z,end_if    ;If törzs kihagyása, ha j==0
     inc k           ;k++ (If törzs) 
end_if:
     ; program folytatása

vege:
     goto    vege  ;végtelen ciklus
.end       ;Forráskód vége
Ha a fenti programot beírjuk egy új MPASM30 projektbe, akkor az MPLAB szimulátorában kipróbálhatjuk a program működését. Az i,j,k (16 bites, előjel nélküli) változók a szokásos helyen, a File Registers ablakban a 0x800, 0x802 és 0x804 címen figyelhetők meg és írhatók felül. Mielőtt a programot elindítanánk, írjunk ezekbe a változókba valamilyen kezdőértéket. Pl. 1, 2, 3-at. Ekkor a program végigfut, s k értéke eggyel növekszik (3-ból 4-re). Ha a Debugger-t reseteljük, és új értéket írunk a változókba (pl. j=0), akkor a program a második feltételvizsgálatnál ugrik, k értéke nem növekszik. Ha i értékét nullázzuk, akkor pedig már az első feltételvizsgálatnál ugrik, a második feltételvizsgálatra már sor sem kerül. 

Valamivel bonyolultabb feltételeket kell vizsgálnunk a következő mintapéldában, ahol egy egyenlőtlenség és egy nem egyenlőség együttes teljesülését követeljük meg az if ágban és egy else ágat is előírtunk:

Az if törzs átugrásának feltétele itt (i>=k) || (j==20) alakban írható, tehát akár az i>=k, akár a j==20 feltétel teljesül, az else_ag-ra kell ugrani.

Komplex feltétel  (A || B)

Más logika szerint érdemes szervezni a programot, ha a komplex feltételben VAGY kapcsolatban állnak egymással az egyes részfeltételek. Ez azt jelenti, hogy akár az egyik, akár a másik (vagy n-edik...) feltétel teljesül, az if törzs végrehajtásával kell folytatni a programot, s az else ágra csak akkor kerül a vezérlés, ha egyik részfeltétel sem teljesül. Ez azt jelenti, hogy most a korábbiaktól eltérően az eredeti feltételeket vizsgáljuk, s az if törzsre ugrunk, ha a megfogalmazott feltétel teljesül. Nézzünk erre is egy mintapéldát!

A C és az Assembly program összevetése most egyszerűbb, hiszen az utóbbiban is az if törzs végrehajtásának részfeltételei szerepelnek, kivéve a pirossal bekeretezett bra Z,else_if utasítást. Ez utóbbinál azért használtuk az ellentétes feltételt (a korábban tanultakhoz hasonló módon), hogy a programot rövidítsük. Az eredeti logika szerint ugyanis egy bra NZ,if_ag utasításnak kellene szerepelnie, de ez után egy feltétel nélküli ugrást (bra else_ag) is el kellene helyezni, hogy a harmadik feltétel vizsgálatának nem teljesülése esetén a program ne "csorogjon rá" az if törzsre.  
 

Programciklusok

A while ciklusszervező utasítás hasonló szerkezetű, mint az if utasítás, csupán annyi a különbség, hogy a while utasítás törzse mindaddig ciklikusan ismétlődik, amíg a while utasítás feltételvizsgáló részében a feltétel teljesül.

A feltételvizsgálat ugyanúgy történik, mint az if utasításnál. Ha a feltétel nem teljesül, akkor a program a while törzs végére ugrik.  Ha pedig a feltétel teljesül, akkor végrehajtásra kerül a while törzs, majd egy feltétel nélküli ugrással a program visszatér a feltételvizsgálathoz. A fenti példában a feltétel: i > j, a nemteljesülés feltétele tehát i <= j, vagy megfordítva: j>=i. A while törzsben kihasználtuk azt, hogy i értéke már a feltételvizsgálat miatt be van töltve W0-ba, így fölösleges mégegyszer elővenni. Emlékeztetőül: a cp j utasítás a j - WREG kivonásnak megfelelően állítja be a státuszregiszer Z és C bitjeit, amelyet a bra GEU utasítás vizsgál. Az add j utasítás pedig a j + WREG összeadást végzi el, s az eredmény a j változóba kerül.

Ha összehasonlítjuk a while és az if utasítás assembly nyelven írt kódját, akkor észrevehetjük, hogy a programban az egyetlen különbséget a while törzs végén elhelyezett feltétel nélküli ugrás jelenti, ami a ciklikus ismétlést valósítja meg.

Megjegyzés: ha nem gondoskodunk róla, hogy a while utasítás feltételét jelentő reláció logikai értéke a while törzsben valahány ciklus végrehajtása során hamisra ne változzon, akkor a program végtelen ciklusba kerül!
 
Az előltesztelő while utasításhoz hasonlóan működik a hátultesztelő do {} while utasítás. A különbség annyi, hogy do {} while utasítás esetén a feltételvizsgálat a do-while törzs végrehajtása után történik. Ez azt is jelenti, hogy legalább egyszer mindenképpen lefutnak a  do-while törzs utasításai, akkor is, ha lehetetlen feltételt adunk meg. Ez a legfontosabb tulajdonsága, s ez dönti el, hogy egy programrészben while vagy do-while utasítást kell használnunk.

Amint a fenti egyszerű példában láthatjuk, a do-while ciklus szerkezete assembly nyelven egyszerűbb és áttekinthetőbb, hiszen csak egyetlen ugrást tartalmaz. A programot némiképp már optimalizáltuk, a cp j utasítás helyén ugyanis az eredeti logika szerint egy mov j,W0 majd egy cp i utasításnak kellene állnia, s  akkor a visszatérés feltétele bra GTU volna, az előírt i>j feltételnek megfelelően. Így most egy utasítás megtakarítása kedvéért megfordítottuk a kivonásban a változók sorrendjét (kihasználva, hogy i már be van töltve W0-ba). Emiatt azonban elsikkadt a do-while-nak az a kellemes tulajdonsága, hogy a C programról való átírásnál az eredeti feltételt kell használnunk, ami a C programban is szerepel.

Ha valaki kíváncsi az "optimalizálatlan" programra, akkor ki is próbálhatja az MPLAB szimulátorában. Az i változó címe szokás szerint 0x800, a j változóé pedig 0x802. A program indítása előtt nyissuk meg a File registers ablakot, s az i változó értékét állítsuk be például 5-re, a j változó pedig legye 1! A program utasításait egyenként léptetve (F8 gombot nyomogatva) láthatjuk, hogy j értéke 5-tel növekszik, s a visszatérés feltétele nem teljesül.
    .include "p24Hxxxx.inc"
    .global __reset      

         .bss        ;inicializálatlan adatterület
  i:     .space 2  
  j:     .space 2  

         .text       ;Kódterület eleje
__reset:
top_while:
     mov i,W0        ;W0=i
     add j           ;j=j+i
     mov j,W0        ;W0=j
     cp   i              ;i-j
     bra GTU,top_while  ;ismétlés, ha i>j   
     ; program folytatása

vege:
     goto    vege  ;végtelen ciklus
.end     

A C programnyelvben gyakran használják a for ciklusszervező utasítást is. Az alábbi ábrán bemutatott összehasonlításból láthatjuk, hogy a for utasítás csupán egy kompaktabb felírási módja a while ciklusszervező utasításnak. A for utasításban a feltételvizsgálaton kívül egy kezdőérték beállítása és egy "léptető" utasítás szerepel. A while utasítás esetén a kezdőérték beállítását a while utasítás előtt kell elhelyezni, a ciklusváltozó léptetését pedig a ciklus törzsében kell elvégezni.

A fenti programok az MPLAB szimulátorában kipróbálhatók. Az "stdio.h" állomány becsatolására a printf függvény használata miatt van szükség. A main függvény argc és argv[] paramétereinek itt nincs jelentősége. A program összeadja a természetes számokat 1-től 100-ig,s az eredményt a standard outputra írja ki (UART1). A szimulátorban engedélyezni kell a képernyőre történő kiírást az Uart1 kimenetről, s akkor a program lefutása után az output ablakban az UART1 fülre kattintva a fenti kimenetet kell kapnunk.

Ellenőrizhetjük azt is, hogy ha az i változó kezdőértékét kellően nagyra választjuk (pl. i=101), hogy az i<=100 feltétel ne teljesüljön, akkor a ciklus egyszer sem fut le. A for ciklus tehát az előltesztelő while utasításnak felel meg.

Léptetés és bitforgatás

A hardverközelinek számító C nyelv az aritmetikai és logikai műveleteken kívül olyan műveleteket is ismer, mint például egy változó bitjeinek balra vagy jobbra léptetése. Mivel a balra léptetés azt jelenti, hogy a binárisan ábrázolt szám minden bitje eggyel magasabb helyiértékre kerül, ezért gyakorlatilag kettővel való szorzást jelent (ugyanúgy, ahogyan tízes számrendszerben is tízzel való szorzást jelent, ha egy szám után egy nullát írunk, amivel minden számjegy eggyel magasabb helyiértékre kerül). Hasonló okok miatt  a jobbra léptetés kettővel való osztásnak felel meg.

Mit jelent a C nyelvű programban az i >> 1 kifejezés? Ha az i változó előjel nélküli egész típusú, vagy előjeles egész típusú, de nemnegatív értékű, akkor az i >> 1 kifejezés a logikai jobbra léptetésnek felel meg (lásd az alábbi ábrán!). Ha viszont i előjeles egész típusú és nemnegatív értékű, akkor az i >> 1 kifejezés értelmezése implementációfüggő, vagyis az adott fordítóprogramtól függ, hogy hogyan kezeli ezt az esetet. Az MPLAB C30 fordítója elég "tisztességes" ebből a szempontból: nem a logikai, hanem az aritmetikai jobbra léptető utasítást használja az i >> 1 kifejezés kifejtésekor, ha az i változó előjeles egész, tehát jobbra léptetéskor megőrzi a szám előjelét. 

Térjünk vissza az egyszerűbb esethez: i >> 1  előjel nélküli számokkal, logikai jobbra léptetéssel! Amint az ábrán is látható, az eredeti érték minden bitje jobbra lép egy hellyel. A legalacsonyabb helyiértékű bit elvész, a legmagasabb helyiértékre pedig 0 kerül. Az egyszerűség kedvéért itt csak 8 bitet jelöltünk, de nagyobb bitszámmal is ugyanígy működik a léptetés. [Az előjeles számok ábrázolásával még nem ismerkedtünk meg, ezért az aritmetikai jobbra léptetéssel most részletesen nem foglalkozunk, elég annyit tudnunk, hogy a logikai jobbra léptetéssel ellentétben nem nullát tesz a legfelső bitbe, hanem az odébb léptetett MSb-t (az ábrán Bit7) megkettőzi.]

A jobbra léptetéshez hasonlóan működik a C programokban az i << 1 balra léptetés is, csak ellenkező irányban: minden bit eggyel magasabb helyiértékre kerül. A legmagasabb helyiértékű bit elvész, a legalacsonyabb helyiértékre pedig 0 kerül.

A PIC24 mikrovezérlő család bitforgató és léptető utasításainak készlete meglehetősen gazdag, áttekintő összefoglalásuk a dsPIC30F/33F Programozói Referencia Kézikönyv 3_5 táblázatában található. Eszerint a PIC24, dsPIC30 és dsPIC33 család ebben a kategóriában az alábbi utasításokkal rendelkezik:
Assembly utasítás Az utasítás funkciója .B
ASR f {,WREG} cél =  (f) aritmetikai jobbra léptetéssel  +
ASR Ws,Wd Wd = (Ws) aritmetikai jobbra léptetéssel +
ASR Wb,#lit4,Wnd Wnd = (Wb) #lit4 helyiértékű aritmetikai jobbra léptetéssel  -
ASR Wb,Wns,Wnd Wnd = (Wb) Wns helyiértékű aritmetikai jobbra léptetéssel -
LSR f {,WREG} cél = (f) logikai jobbra léptetéssel +
LSR Ws,Wd Wd = (Ws) logikai jobbra léptetéssel +
LSR Wb,#lit4,Wnd Wnd = (Wb) #lit4 helyiértékű logikai jobbra léptetéssel -
LSR Wb,Wns,Wnd Wnd = (Wb) Wns helyiértékű logikai jobbra léptetéssel -
RLC f {,WREG} cél =  (f) balra forgatással a Carry biten keresztül  +
RLC Ws,Wd Wd = (Ws) balra forgatással a Carry biten keresztül  +
RLNC f {,WREG} cél = (f) balra forgatással   +
RLNC Ws,Wd Wd = (Ws) balra forgatással  +
RRC f {,WREG} cél = (f) jobbra forgatással a Carry biten keresztül  +
RRC Ws,Wd Wd = (Ws) jobbra forgatással a Carry biten keresztül  +
RRNC f {,WREG} cél = (f) jobbra forgatással +
RRNC Ws,Wd Wd = (Ws) jobbra forgatással +
SL f {,WREG} cél = (f) balra léptetéssel +
SL Ws,Wd Wd = (Ws) balra léptetéssel +
SL Wb,#lit4,Wnd Wnd = (Wb) #lit4 helyiértékkel balra léptetve -
SL Wb,Wns,Wnd Wnd = (Wb) Wns helyiértékkel balra léptetve -
Megjegyzés: azoknál az utasításoknál, ahol WREG opcionális operandusként szerepel a táblázatban, a WREG megadása esetén az eredmény a WREG regiszterbe kerül. Ha elhagyjuk, akkor pedig az eredmény az f memória rekeszbe kerül. Az utolsó oszlopban azt jelöltük, hogy az adott utasításnak van-e 8 bites változata (amikor a .B módosító használható).

Mint látható, mindegyik utasításnak többféle variánsa van, tág teret engedve az operandus(ok) változatos címzésének. A léptetést végezhetjük egy memóriarekesz (f) tartalmán, s az eredményt tehetjük akár vissza, a memória rekeszbe , akár a WREG regiszterbe. Végezhetjük a léptetést egy általános célú regiszter tartalmán is, ez esetben egyszerre akár több helyiértékkel is. A léptetendő helyiértékek számát tartalmazhatja számkonstansként közvetlenül az utasításkód (azonnali, vagyis immediate típusú operandus), vagy változóként egy általános célú regiszter (regiszter közvetlen címzés).

A fentebb említett i >> 1 jobbra léptetést a Microchip C30 fordító előjel nélküli egészek (pl. unsigned int i;) esetén az LSR logikai léptetésre, előjeles változók (pl. int i;) esetén pedig az ASR aritmetikai jobbra léptető utasításra fordítja. Az i << 1 balra léptetés assembly megfelelője pedig az SL logikai balra léptetés.


Ahogy korábban említettük, az aritmetikai jobbra léptetésnél a legmagasabb helyiértékű bit visszamásolódik a megürülő helyre (ezáltal megkettőződik). A kiléptetett legalacsonyabb helyiértékű bit pedig az SR státuszregiszter Carry bitjébe (C) másolódik. 

Az alábbi utasításoknál használható a .B módosító, ami 8 bites műveletet ír elő:
ASR{.B} f                 f>>1 → f            f-be visszaíródik a jobbra léptetett érték
ASR{.B} f ,WREG       f>>1 → W0         W0-ba kerül f jobbra léptetett értéke
ASR{.B} Ws,Wd        Ws>>1 → Wd      Wd-be kerül Ws jobbra léptetett értéke

Az alábbi utasítások egyetlen utasításciklusban több (akár 15) helyiértékkel is képesek jobbra léptetni a forrás operandus értékét, de csak 16 bites változatuk van:
ASR Wb,#lit4,Wnd     Ws>>lit4 → Wd    Wd-be kerül Ws lit4 helyiértékkel jobbra léptetett értéke
ASR Wb,Ws,Wd         Wb>>Ws → Wd    Wd-be kerül Wb Ws helyiértékkel jobbra léptetett értéke


logikai jobbra léptetésnél a legmagasabb helyiértékű bitre nulla íródik be. A kiléptetett legalacsonyabb helyiértékű bit pedig az SR státuszregiszter Carry bitjébe (C) másolódik. Ez az utasítás csak előjel nélküli,vagy nemnegatív előjeles változók esetében használható aritmetikai célokra (kettővel, vagy kettő hatványaival történő osztás). Ezen kívül logikai célra (pl. 8 vagy 16 bites adatok soros kiléptetése, maszk értékek előállítása,vagy más helyiértékre történő léptetése) használják elterjedten.

Az alábbi utasításoknál használható a .B módosító, ami 8 bites műveletet ír elő:
LSR{.B} f                 f>>1 → f            f-be visszaíródik a jobbra léptetett érték
LSR{.B} f ,WREG       f>>1 → W0         W0-ba kerül f jobbra léptetett értéke
LSR{.B} Ws,Wd        Ws>>1 → Wd      Wd-be kerül Ws jobbra léptetett értéke

Az alábbi utasítások egyetlen utasításciklusban több (akár 15) helyiértékkel is képesek jobbra léptetni a forrás operandus értékét, de csak 16 bites változatuk van:
LSR Wb,#lit4,Wnd     Ws>>lit4 → Wd    Wd-be kerül Ws lit4 helyiértékkel jobbra léptetett értéke
LSR Wb,Ws,Wd         Wb>>Ws → Wd    Wd-be kerül Wb Ws helyiértékkel jobbra léptetett értéke


logikai balra léptetésnél a legalacsonyabb helyiértékű bitre nulla íródik be. A kiléptetett legmagasabb helyiértékű bit pedig az SR státuszregiszter Carry bitjébe (C) másolódik. Ez az utasítás a C nyelvi balra léptető operátor megfelelője. Ezen kívül logikai célra (pl. 8 vagy 16 bites adatok soros ki-/beléptetése, maszk értékek előállítása,vagy más helyiértékre történő léptetése) használják elterjedten.

Az alábbi utasításoknál használható a .B módosító, ami 8 bites műveletet ír elő:
SL{.B} f                 f<<1 → f            f-be visszaíródik a balra léptetett érték
SL{.B} f ,WREG       f<<1 → W0         W0-ba kerül f balra léptetett értéke
SL{.B} Ws,Wd        Ws<<1 → Wd      Wd-be kerül Ws balra léptetett értéke

Az alábbi utasítások egyetlen utasításciklusban több (akár 15) helyiértékkel is képesek balra léptetni a forrás operandus értékét, de csak 16 bites változatuk van:
SL Wb,#lit4,Wnd     Ws<<lit4 → Wd    Wd-be kerül Ws tartalma, lit4 helyiértékkel balra léptetve
SL Wb,Ws,Wd         Wb<<Ws → Wd    Wd-be kerül Wb tartalma, Ws helyiértékkel balra léptetve


A bitforgató utasításoknak nincs C nyelvi megfelelője, csak speciális célokra használják. Ha a forgatás a Carry biten keresztül történik, akkor a megürülő bitre a Carry bit tartalma másolódik be, a kicsorduló bit pedig a Carry bit új tartalma lesz. Az utasítás a Carry biten kívül az SR regiszter N és Z bitjeire van hatással. Az ábra a 16 bites üzemmódot mutatja, de a bitforgató utasítások mindegyikénél használható a .B módosító, s akkor az alacsonyabb helyiértékű bájton történik a műveletvégzés.

RLC{.B} f              f  = f<<1 + C;  C = f kicsorduló bitje
RLC f,WREG        W0  = f<<1 + C;  C = f kicsorduló bitje
RLC Ws,Wd         Wd  = Ws<<1 + C;  C = Ws kicsorduló bitje

RRC f                     f  = C.f>>1;  C = f kicsorduló bitje
RRC f,WREG         W0  = C.f>>1;  C = f kicsorduló bitje
RRC Ws,Wd          Wd = C.Ws>>1;  C = Ws kicsorduló bitje
Itt a  C.f illetve C.Ws azt jelzi, hogy össze kell olvasni C és f (vagy Ws) bitjeit.


A bitforgatás végezhető a Carry biten kiiktatásával is, ekkor a megürülő bitre az éppen kicsorduló bit másolódik be. Ezeknél az utasításoknál a Carry bit értéke nem módosul, az utasítás csupán az SR regiszter N és Z bitjeire van hatással. Az ábra a 16 bites üzemmódot mutatja, de a bitforgató utasítások mindegyikénél használható a .B módosító, s akkor az alacsonyabb helyiértékű bájton történik a műveletvégzés.

RLNC{.B} f              f  = f<<1 + C;  C = f kicsorduló bitje
RLNC{.B} f,WREG        W0  = f<<1 + C;  C = f kicsorduló bitje
RLNC{.B} Ws,Wd         Wd  = Ws<<1 + C;  C = Ws kicsorduló bitje

RRNC{.B} f                     f  = C.f>>1;  C = f kicsorduló bitje
RRNC{.B} f,WREG         W0  = C.f>>1;  C = f kicsorduló bitje
RRNC{.B} Ws,Wd          Wd = C.Ws>>1;  C = Ws kicsorduló bitje
Itt a  C.f illetve C.Ws azt jelzi, hogy össze kell olvasni C és f (vagy Ws) bitjeit.

Egyszerű példák a léptetésre

Az alábbi programrészletben láthatjuk, hogy a C programnyelvben használt több-helyiértékes léptetéseket az egy helyiértékkel történő léptetés ismételt végrehajtásával is megvalósíthatjuk. Néha azonban hatékonyabb, ha az egy utasítással több helyiértéket is léptető változatot használjuk.

Figyeljük meg, hogy i előjelnélküli 8 bites változó, ezért a logikai jobbra léptetés 8 bites változatát használjuk. A léptetést közvetlenül az adatmemóriában is végezhetjük. Ez a leghatékonyabb kód, mert ha egy utasításban akarnánk elvégezni a léptetést, akkor ahhoz előbb be kellene tölteni az i változó tartalmát egy általános célú regiszterbe, majd a léptetés után vissza kellene másolni az eredményt az i változóba, így minimum három utasítás kellene ahhoz, amit itt kettővel is megtudtunk oldani. 

A második esetben p és k 16 bites változók, ezért a léptető utasítás 16 bites változatát kell használni. Itt a műveletet nem végezhetjük el közvetlenül a memóriában, mert p változó értékét változatlanul kell hagyni. A p változó értékét ezért be kell tölteni egy általános célú regiszterbe, s azon kell elvégezni a műveletet, végül az eredményt a k változóba kell másolni. Magát a léptetést viszont elegánsan, egyetlen utasítással is elintézhetjük.   

Aritmetikai kifejezések kiértékelése

Használjuk eddigi ismereteinket egy valamivel összetettebb feladatra, egy kifejezés helyettesítési értékének kiszámítására! Legyen az egyszerűség kedvéért minden változónk előjel nélküli 16 bites egész, s írjunk át assembly utasításokra az n = j + (i<<3) - k értékadást!


Célszerű a kiértékelést i<<3 kiszámításával kezdeni.

A kifejezés kiértékelésének lépései:

1. Töltsük be az i változó értékét W0-ba!
2. Léptessük balra 3 helyiértékkel W0 tartalmát!
3. Adjuk hozzá W0-hoz a j változó tartalmát!
4. Vonjuk ki W0 tartalmából k értékét!
5. Az eredményt tároljuk el az n változóba!

Megjegyzések:
  1. Figyeljünk rá, hogy egy memóriarekesz és a WREG közötti aritmetikai műveleteknél (pl. ADD f{,WREG}, SUB f{,WREG}) nem írhatunk W0-át, hanem kötelezően WREG-et kell írni, annak ellenére, hogy ugyanarról a regiszterről van szó, amelyre korábban W0 néven hivatkoztunk!
  2. A program optimalizálása céljából itt most nem a korábban tanult SUB f{,WREG} utasítást használjuk (ami a k változóból vonná ki WREG  tartalmát), hanem a céljainknak jobban megfelelő SUBR f,WREG utasítást, ami fordított irányban működik, vagyis WREG tartalmából vonja ki a k változó értékét. Már csak ezért is érdemes lapozgatni a dsPIC30F/33F Programmer's Reference Manual-t, hogy ilyen kitűnő lehetőségekre bukkanjunk benne!
Mintaprogram: A fenti programrészletet könnyen kiegészíthetjük, hogy egy lefordítható és az MPLAB szimulátorában kipróbálható programot kapjunk. Helyet kell foglalnunk a változóknak (az alábbi példában i szokás szerint a 0x800, j pedig a 0x802 címre kerül, és így tovább). Kényelmi szempontból a változók értékét is beállítjuk (i=8; j=300; k=30), de a program indításakor kézzel is beírhatjuk a kívánt adatokat a File Registers ablakban a fenti címekre.

A megfigyelhető mennyiségek: W0 (alias WREG) az SFR ablakban, és az i,j,k,n változók a File Registers ablakban, a 0x800, 0x802, 0x804 és 0x806 címen. A fentebb említett kezdőértékekkel a végeredmény 0x14E = 334 lesz.
    .include "p24Hxxxx.inc"
    .global __reset      

         .bss        ;inicializálatlan adatterület
  i:     .space 2  
  j:     .space 2  
  k:     .space 2
  n:     .space 2

         .text       ;Kódterület eleje
__reset:
     mov #300,W0
     mov W0,j                ;j=300
     mov #8,W0              
     mov W0,i                ;i=8
     mov #30,W0
     mov W0,k                ;k=30
     clr W0              
     mov W0,n                ;n=0

     ; n = j + i<<3 - k
     mov  i,W0          ;W0 = i
     sl   W0,#3,W0      ;i<<3
     add  j,WREG        ;W0=j+i<<3  
     subr k,WREG        ;W0=j+i<<3-k
     mov  W0,n          ;n = W0  (0x14e = 334)

vege:
     goto    vege  ;végtelen ciklus
.end       ;Forráskód vége

Típuskonverzió: 8 és 16 bites mennyiségek összeadása

Mi történik, ha egy kifejezésben vegyesen szerepelnek 8 és 16 bites változók? Hogyan végezzük el a műveleteket? Nézzük például azt az egyszerű esetet, amikor egy 16 bites (a példában p nevű)  változóhoz egy 8 bites (a példában i) változó értékét kell hozzáadni! Az alábbi ábra baloldalán láthatjuk a C nyelvű változatot, jobboldalt pedig az assembly nyelven írt megfelelőjét - két változatban. A megoldás elve az, hogy WREG-be (azaz W0-ba) betöltjük az i változó tartalmat, majd WREG tartalmát hozzáadjuk a p változóhoz. Az ADD p utasítás eredménye közvetlenül a p változóba íródik be.

Az ábrán az első assembly utasítás-sorozat hibás, mert nem gondoskodtunk a W0 regiszter magasabb helyiértékű felének beállításáról, így annak tartalma határozatlan, ami az összeadásnál hibás eredményre vezethet. 


Egy lehetséges megoldás az, ha egy ZE W0,W0 utasítással kinullázzuk a W0 regiszter magasabb helyiértékű felét. Egy másik lehetséges megoldás az volna, hogy az i változó betöltése előtt a W0 regisztert teljes egészében kinulláznánk egy CLR W0 utasítással. Ezekkel a törlőutasításokkal eddig nem foglalkoztunk, ezek használatát is a dsPIC30F/33F Programmer's Reference Manual-ból kell kisilabizálni - tág teret engedve az önálló felfedezésnek.