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