2013-07-06
Ez most nem egy kis szines - képes - szagos írás lesz, de reményeim szerint annál hasznosabb. Legalább is annak, aki érdeklődik az AVR mikrovezérlők C-ben történő munkára fogása iránt. Inkább ismeretterjesztőnek szánom az írásom, részletesebb infókért kéretik az interneten szétnézni link).
Nem a C nyelvnek, hanem a C fordító programnak a része az úgynevezett preprocessor, vagy makró előfeldolgozó program, amely makrohelyettesítésre, feltételes fordításra és állományok beiktatására képes. Úgy működik, hogy mielőtt ténylegesen a fordító elkezdené az általunk összekalapált C nyelvű program szövegének lefordítását gépi kódra, előbb a preprocessor kapja meg a szöveget. A preprocessor a #-kal (ejtsd hesmárk) kezdődő sorokat értelmezi, ezen utasítások alapján módosítja a programunk szövegét, majd ezt adja át a C fordítónak. Ez rugalmassá, és ha ügyesen használjuk, jobban olvashatóvá teszi a programunk forráskódját.
Lehetőségünk van egy sornál hosszabb, vagy több sorra tagolt utasításra, ilyenkor a sorok végére \ karaktert kell tennünk. Az utasítások érvényességi tartománya a definíció helyétől a forrásállomány végéig tart. A definiált szimbólumok újradefiniálhatók. A definíciók korábbi definíciókat használhatnak.
Aki C-ben programozott, vagy nézegetett C kódot, az előtt ismert, hogy egy program általában úgy kezdődik, hogy #include. Ez az utasítás arra szolgál, hogy a forráskódunkba beszúrjuk egy másik file szövegét. Tipikus alkalmazása *.h, úgynevezett header file-k beszúrása, de lehet teljesen tetszőleges egyéb szöveg file is. (Egy C fordítóhoz általában mellékelnek még előre elkészített/lefordított program rutinokat/függvényeket, külön relokálható object-ekben, vagy library-kbe összecsomagolva, ezek használatához szükségesek a szintén előre megírt header file-k.) Két formája van:
A fordító program a file-t a rendszer header file-k között keresi:
#include <filename>
A fordító program a a file-t a C program file könyvtárában keresi, itt vannak ugyebár a saját dolgaink
#include "filename";
Ez az utasítás elsősorban szimbólikus állandók létrehozására szolgál, de meg fogjuk látni, hogy felhasználható makrók definiálására is. Programokban nem jó bűvös számokat beleírni, hogy majd náhány hónap után magunk sem tudjuk melyik mi volt. Különösen nem jó, ha egy állandó több helyen is előfordul a program szövegében. Sokkal célszerűbb a programkód elején szimbólikusan definiálni, és a prgramban később ezt használni. Célszerű ezeket a szimbólikus neveket nagy betüvel írni. Az alábbi példa úgy működik, hogy az előfeldolgozó végigfut a program kódunkon, és ahol azt a szöveget találja, hogy BAUDRATE, azt kicseréli 9600-ra.
#define BAUDRATE 9600
Hasonlóan vannak definiálva header file-okban az adott mikrovezérlőre jellemző regiszterek bit-jei. Saját használatra bármi addig nem használt szimbólumot létrehozhatunk:
#define LED 5
AVR programokban általában használjuk az F_CPU állandót, amely a processzor órajelének frekvenciáját tartalmazza, Az F_CPU azért érdekes, mert több dolog beállításához szükséges ez az érték. Egy 4MHz-es kapcsolás esetében így néz ki:
#define F_CPU 4000000
Nem csak számokkal, hanem tetszőleges stringekkel is működik. Az alábbi példával teljesen meg lehet kavarni egy tisztességben megőszült C programozót:
#define then
#define begin {
#define end }
if (i>0) then
begin
a= 1;
b= 2;
end
Én például utálom kiírni a leggyakoribb változó deklarálást, hogy unsigned char, ezért definiáltam az alábbi helyettesítést:
#define UC unsigned char
Nézzük a makró definiálást, mire is lehet ezt használni? Ez tulajdonképpen egy mini függvény. Ami az érdekessége, hogy az előfordító értelmezi és hajtja végre, még a program lefordítása előtt, és nem az AVR mikrovezérlőnkön fog programként futni. Az alábbi makrók segítségével sokkal szebben lehet egy regiszter adott bitjét 1-be vagy 0-ba állítani (Bit Set, Bit Clear), mondjuk az adott kapcsolásban a LED-et bekapcsolni:
#define BS(sfr,bit) sfr|= ( 1 << ( bit))
#define BC(sfr,bit) sfr&= ~( 1 << ( bit))
BC( PORTB, 5);
A programba ténylegesen az _SFR_IO8( 0x18) &= 0B11101111 fog befordulni, vagyis a PORTB 5. bitje 0-ba lesz állítva.
Nincs akadálya, hogy több definíciót egymásba ágyazva használjuk:
#define BS(sfr,bit) sfr|= ( 1 << ( bit))
#define BC(sfr,bit) sfr&= ~( 1 << ( bit))
#define LED 5
#define LED_ENGED BS( DDRB, LED)
#define LED_BE BC( PORTB, LED)
#define LED_KI BS( PORTB, LED)
LED_ENGED;
LED_BE;
Az alábbi példa ATmega8 soros portjának beállításáról szól. Az alábbi definiciók segítségével jól értelmezhető a programkód és könnyű megváltoztatni a paramétereket. A kölünböző állandók meghatározását/kiszámítását az előfordítóra bízzuk
#define F_CPU 4000000
#define BAUDRATE 9600
#define MYUBRR F_CPU/ 16UL/ BAUDRATE - 1
#define IT_RX_ENABLE 0B10000000
#define RX_ENABLE 0B00010000
#define TX_ENABLE 0B00001000
#define SET_UCSRC 0B10000000
#define ASYNCRON 0
#define PARITY_NO 0
#define STOPBIT_1 0
#define BIT_8 0B00000110
UBRRL= MYUBRR;
UBRRH= ( MYUBRR)>> 8;
UCSRC= SET_UCSRC+ ASYNCRON+ BIT_8+ PARITY_NO+ STOPBIT_1;
UCSRB= RX_ENABLE+ TX_ENABLE+ IT_RX_ENABLE;
Lehetőségünk van meghatározni, hogy egyeses programrészek adott feltételtől függően forduljanak be a kódba. Én még nem használtam ezt a lehetőséget.
#define F_CPU 4000000
#if F_CPU > 2000000
a= 4;
#else
a= 1;
#endif
Lehetősgünk van megvizsgálni, hogy egy szimbólum már definálva lett-e korábban az előfordítnak és az adott program részt ettől függően befordítani. Gyakan használják több header file esetén a többszörös újradefiniálás elkerülésére, vagy típusfüggetlen kód íráskor. Már úgy értem, olyan kód írásakor, ami többféle processzorra is lefordítható.
#define T_ATMEGA8
#ifdef T_ATMEGA8
a= 15;
b= 7;
#endif
#ifdef T_ATTINY2313
a= 12;
b= 8;
#endif
Azt hiszem, sikerült összefoglalnom a gyakorabban előforduló utasításokat. Vagyis, akinek szükséges, nézzen utána, még léteznek mások is. Érdemes a fordítóhoz mellékelt header file-kba belekukucskálni, egyes megoldásaikat megvizsgálni. Jó munkát kívnok mindenkinek!