GCC preprocessor - makró előfeldolgozó

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.

#include

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";
#define

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;
#if - #else - #endif

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!