C suli 6 - Bitek, byte-ok, regiszterek

2016-10-27

 

A C suli 4-ben megnéztük a logikai kapcsolatokat a feltétel vizsgálatokban. Egyes byte-ok bitjein is végezhetünk logikai műveleteket. Például így néz ki az bitenkénti ÉS:


unsigned char a= 0B01010101;
unsigned char m= 0B00001111;
unsigned char c;
c= a & m;                              // c= 0B00000101

Mire jó ez? Ezt úgy is hívjuk, hogy maszkolás. Olyankor alkalmazzuk, ha az a byte-nak csak meghatározott bitjeire van szükségünk. A c eredmény byte-ba csak azoknak a biteknek az értéke fog bekerülni, amelyeknél az m maszk byteban az adott bit 1, a többi bit 0 lesz. Másképpen is írhatjuk:


unsigned char a= 0B01010101;
unsigned char c;
c= a & 0B00001111;                     // c= 0B00000101

Lehet olyan probléma, hogy az a byte-nak meghatározott bitjeit szeretnénk 0-ra állítani:


unsigned char a= 0B01010101;
a= a & 0B00001111;                     // a= 0B00000101

Lusta programozók ezt így oldják meg:


unsigned char a= 0B01010101;
a &= 0B00001111;                       // a= 0B00000101

Lehet olyan probléma, hogy az a byte-nak meghatározott bitjeit szeretnénk 1-ra állítani. Ilyenkor használjuk a bitenkénti VAGY-ot:


unsigned char a= 0B01010101;
a= a | 0B00001111;                     // a= 0B01011111

Lusta programozók ezt így oldják meg:


unsigned char a= 0B01010101;
a |= 0B00001111;                       // a= 0B01011111

Érdekes művelet a bitenkénti invertálás, amikor minden bit éppen az ellenkezőjére változik:


unsigned char a= 0B01010101;
a= ~ a;                                // a= 0B10101010

Van egy olyan művelet, hogy a byte bitjeit balra lépteti, jobbról 0-kat léptet be a byte-ba, ami meg balra kilép, az elveszik. Ez kettes számrendszerben 2-vel való szorzást jelent:


unsigned char a= 0B00000001;
a= a << 1;                             // a= 0B00000010
a= a << 1;                             // a= 0B00000100
a= a << 3;                             // a= 0B00100000

Lehet ezt jobbra is csinálni, ez a kettes számrendszerben a 2-vel való osztás:


unsigned char a= 0B00100000;
a= a >> 1;                             // a= 0B00010000
a= a >> 1;                             // a= 0B00001000
a= a >> 3;                             // a= 0B00000001

Az ATmega8 doksijából, a regisztereket összefoglaló táblázatból kiollóztam egy darabot:

A bal szélső oszlopban látható az egyes regiszterek (byte-ok) címe, azután a neve, majd az egyes bitek neve. A regiszterek hasonlítanak a memória cellákra, de a regiszterek bitjeihez áramkörök kapcsolódnak, amiket a regiszterekbe írt bitekkel vezérlünk, vagy éppen kiolvasva lekérdezünk. Olyan ez, mint egy műszerfal, egy irányító pult. Ahogyan a memória cellákat írhatjuk, olvashatjuk, úgy a regisztereket is. Sajnos C-ben nincs bit szintű írás, olvasás, de a fenti műveltekkel meg tudjuk ezeket oldani.

Például a PORTB regiszter 5. bitjének 1-be allítása így néz ki:


PORTB |=  0B00100000;

Interneten nagyon sok helyen az alábbi formát alkalmazzák, amit én nem szeretek. Vesznek egy byte-t, aminek az értéke 1, ennek tartalmát balra léptetik az 5. bitnek megfelelően. Mivel ez csupa állandóből áll, már a fordító elvégzi a műveletet és csak a kiszámolt értéket teszi bele a programba, tehát nem fog futás közben késlekedést okozni.


PORTB |=  1 << 5;

Ezért írtam egy makrót, amivel szerintem olvashatóbban lehet dolgozni:


B_1( PORTB, 5);

Így néz ki a makróm.


#define B_1(sfr,bit)         sfr|= ( 1 << ( bit))

Az sfr a uC regisztereit jelenti. Ha fordító program találkozik a B_1( PORTB, 5) kifejezéssel, azt lecseréli PORTB |= ( 1 << ( 5)) kifejezésre. Arra már nem emlékszem, miért tettem bele ennyi zárójelet, de ez nem okoz gondot a fordítónak. Inkább hasznájunk zárójeleket, semmint, hogy a fordító más sorendbe hajtsa végre a műveleteket, mint ahogy szeretnénk.

Vegyük azt, hogy a PORTB regiszter 3. bitjének 0-ba kell állítani, az így néz ki:


PORTB &=  0B11110111;

Sok helyen megint az általam nem szeretett formát alkalmazzák.


PORTB &= ~( 1 >> 3)

Az általam írt makróval ez így néz ki:


B_0( PORTB, 3);

Ez pedig a makróm.


#define B_0(sfr,bit)         sfr&= ~( 1 << ( bit))

Ha fordító program találkozik a B_0( PORTB, 3) kifejezéssel, azt lecseréli PORTB &= ~( 1 << ( 3)) kifejezésre.

Ez pedig a bitet kiolvasó makróm.


#define B_R(sfr,bit)         ( sfr & ( 1 << ( bit)))

A C suli 3-ban a B port 0. bitjével villogtattuk a LED-et.

Ezt a feladatot "tkiraaly_avr.h"-ban definiált PB0_OUTPUT, PB0_0, PB0_1 makróimmal oldottuk meg. Most már elegendő a tudásunk, hogy megnézzük ezek hogyan működnek? A makrókban jó, hogy csak akkor fordítja be a tartalmukat a C fordító, ha használjuk őket a programban. Ezért tehettem meg, hogy a regiszterek összes bitjére elkészítettem a lehetséges kombinációkat. Sőt azokra a regiszterekre is, amelyek csak az AVR család 100 lábú tagjaiba fordulnak elő. Az ATmega8 ki/bemeneti lábai B-C-D portokba vanak szervezve. A portokat egyenként 3 regiszter segítségével tudjuk kezelni. Mi most megelégszünk a B porttal, annak is a 0. bitjével. A DDRB regiszter segítségével a port minden egyes bitjére meghatározhatjuk, hogy azt bemenetként - 0, vagy kimenetként - 1 kívánjuk használni. Az alapértelmezett állapot (bekapcsolás követően) a bemenet. Amelyik lábat kimenetként akarjuk hasznáni, a DDRB regiszternek azt a bitjét 1-be kell állítsuk. Nézzük a 0. bit állítását:


#define PB0_OUTPUT            B_1( DDRB, 0)

#define PB0_INPUT             B_0( DDRB, 0)

A kimenetként használt lábak állapotát a PORTB regiszter megfelelő bitje határozza meg, ez ugye bitenként lehet 1 vagy 0.


#define PB0_1                 B_1( PORTB, 0)

#define PB0_0                 B_0( PORTB, 0)

Ha az összes lábat egyszerre akarjuk 0-ba állítani, akkor nem kell a bitekkel bűvészkednünk, hanem egyszerűen ezt írjuk:


PORTB= 0;                                     // PORTB= 0B00000000

A bemenetként használt lábak állapotát a PINB regiszteren keresztül tudjuk lekérdezni.


#define PB0_RD                B_R( PINB, 0)

Lehetőség van a port lábakra belső, felhúzó ellenállást (PULLUP) kapcsolni. Ha egy kapcsolót akarunk a port lábon figyelni, akkor így egy külső felhúzó ellenállást tudunk megtakarítani. Úgy lehet beállítani, ha az adott lábat a DDRB regiszterben bemenetnek konfiguráljuk, a PORTB regiszter megfelelő bitjét pedig 1-re állítjuk.


#define PB0_PULLUP            PB0_INPUT; PB0_1