2013-07-19
A GCC preprocessor-ról szóló előző írásom azért született, mert néhány gondolat körvonalazódott a fejemben lévő brain-space-ben. Gyakran szoktam az Interneten keresgélni, ki/hogyan oldott meg AVR mikróvezérlőkkel (uC) kapcsolatos problémákat C-ben. Ilyen rémisztő kódokat talál az ember:
TIMSK |= (1 << TOIE0);
EICRB = (EICRB | (1 << ISC40)) & ~ (1 << ISC41);
EIMSK |= (1 << INT4);
Hosszú ideig bogarászhatjuk, hogy melyik regiszter milyen bitjét... vagy tudjuk fejből az uC regiszter és bit készletét, vagy nem... Én az utóbbi csapatba tartozom. Ezért kezdtem el használni a makró előfeldolgozó program nyújtotta lehetőségeket, és igyekeztem szimbolikus állandókat használni. Egyszer találkoztam egy makróval, a soros port sebességének beállítására, amely megmutatta számomra, hogy állandók kiszámolására is alkalmazható a preprocesszor:
#define MYUBR (F_CPU/(BAUDRATE*16UL))-1
UBRRL= MYUBR;
UBRRH= (MYUBR) >> 8;
Nézzünk néhány korábban is használt makrómat, assembler utasítások beillesztésére:
#define IT_ENABLE __asm__ __volatile__ ("sei" ::) // megszakitas engedelyezese
#define IT_DISABLE __asm__ __volatile__ ("cli" ::) // megszakitas tiltasa
#define NOP __asm__ __volatile__ ("nop" ::) // egy ures utasitas beszurasa
A következő makrók a Z80 assembly-t idézik, és kezelhetővé teszik a regiszterekkel kapcsolatos bit műveleteket:
#define BS(sfr,bit) sfr|= ( 1 << ( bit)) // BIT-SET - egy regiszter adott bitjenek 1-re allitas
#define BC(sfr,bit) sfr&= ~( 1 << ( bit)) // BIT-CLEAR - egy regiszter adott bitjenek 0-ra allitas
#define BTS(sfr,bit) bit_is_set( sfr, bit) // BIT-TEST-SET - egy regiszter egy bitjenek vizsgalata 1-re
#define BTC(sfr,bit) bit_is_clear( sfr, bit) // BIT-TEST-CLEAR - egy regiszter egy bitjenek vizsgalata 0-ra
Ezeket használva így néz ki egy LED kezelése:
#define LED 4
#define LED_ENABLE BS( DDRD, LED)
#define LED_BE BC( PORTD, LED)
#define LED_KI BS( PORTD, LED)
int main( void)
{
LED_ENABLE;
LED_BE;
...
Ezek már egészen használható megoldások. Mikor az Internetet túrtam, Basic meg másik C, meg Arduino, meg megint más kódokat is láttam. Igaz ugyan, hogy regiszter/forráskód szinten WINAVR-ben enyém az összes lehetőség a uC programozására, de nagyon irigyeltem, hogy azokban egy-két utasítással elintézik mondjuk a soros port inicializálását. És olyan utasítások vannak bennük, amelyek beszédesek. Programozásnál külön tanulság, ha valami miatt néhány hónapig nem foglalkozom a kedvenc uC-immel, utána a saját kódomon is el kell gondolkoznom. Mindig igyekeztem olyan kódot írni, ami mások számára is értheő, de kérdéses, hogy a kódjamon mások hogyan tudnak elmenni. Sokat segítenek a weblapomra feltett munkáim, azokból ki lehet egy-két dolgot ollózni. De ha megnézzük ezeket a kódokat, és változtatni kell rajtuk, akkor megint bele kell mélyedni a regiszterekbe és bitjeikbe.
Általában problémákat szeretnénk megoldani. A megoldáshoz meg kell nézzük az adott uC milyen lehetőségeket biztosít, milyen regisztereket kell felprogramoznunk, és lekódolni az algoritmusokat. Így jött a gondolat, amely már egy másik szintre emeli a munkát, hogy probléma függő makrókat kellene készíteni, amelyek kompletten tartalmaznák a regisztereket, biteket, az adatokat, illetve kiszámolnák azokat szükség esetén. Nézzünk egy egyszerű példát a port bitek kimenetre állítására:
#define PIN_D0_OUT BS( DDRD, 0)
#define PIN_D1_OUT BS( DDRD, 1)
...
Munka közben azután úgy gondoltam, hogy ugyan a regiszterek számos kombinációra adnak lehetőséget, de a gyakorlatban nem mindegyiket használja az ember, pontosabban én :), vagyis nem minden kombinációt kell megírni, hanem csak azokat, amelyek általában előrébb mozdítják az embert. Sikerült a soros port komplett konfigurálását is egyetlen makróval megoldanom, pontosabban az általam használt értékekre egy-eggyel:
#define USART_SET_9600_8N1 UBRRL= F_CPU/ 16UL/ 9600- 1; \
UBRRH= ( F_CPU/ 16UL/ 9600- 1)>> 8; \
UCSRC= SET_UCSRC+ ASYNCRON+ BIT_8+ PARITY_NO+ STOPBIT_1; \
UCSRB= RX_ENABLE+ TX_ENABLE+ IT_RX_ENABLE
#define USART_SET_19200_8N1 UBRRL= F_CPU/ 16UL/ 19200- 1; \
UBRRH= ( F_CPU/ 16UL/ 19200- 1)>> 8; \
UCSRC= SET_UCSRC+ ASYNCRON+ BIT_8+ PARITY_NO+ STOPBIT_1; \
UCSRB= RX_ENABLE+ TX_ENABLE+ IT_RX_ENABLE
#define USART_SET_115200_8N1 UBRRL=F_CPU/ 16UL/ 115200- 1; \
UBRRH=( F_CPU/ 16UL/ 115200- 1)>> 8; \
UCSRC=SET_UCSRC+ ASYNCRON+ BIT_8+ PARITY_NO+ STOPBIT_1; \
UCSRB=RX_ENABLE+ TX_ENABLE+ IT_RX_ENABLE
Ezeket használva így néz ki a soros port inicializálása:
#define F_CPU 4000000 // orajel 4MHz
int main( void)
{
USART_SET_9600_8N1;
...
Az alábbi egy makró csomag, a T0 számláló kezeléséhez ( viszonylag ez a legegyszerűbb, a többi a NORMAL-on kívül még számos üzemmóddal rendelkezik, CTC, PWM...):
#ifndef T0_DIVIDER
#define T0_DIVIDER 1 // 1-8-64-256-1024
#endif
#define T0_STOP TCCR0=0
#if T0_DIVIDER == 1
#define T0_NORMAL TCCR0=0B00000001
#endif
#if T0_DIVIDER == 8
#define T0_NORMAL TCCR0=0B00000010
#endif
#if T0_DIVIDER == 64
#define T0_NORMAL TCCR0=0B00000011
#endif
#if T0_DIVIDER == 256
#define T0_NORMAL TCCR0=0B00000100
#endif
#if T0_DIVIDER == 1024
#define T0_NORMAL TCCR0=0B00000101
#endif
#define T0_COUNT_EXTERN_FALL TCCR0=0B00000110
#define T0_COUNT_EXTERN_RISE TCCR0=0B00000111
#define T0_IT_OVF_ENABLE BS(TIMSK,TOIE0)
#define T0_IT_OVF_DISABLE BC(TIMSK,TOIE0)
#define T0_T(usec) TCNT0=255-(usec/((T0_DIVIDER*1000000)/F_CPU))
Ezeket használva így lehet beállítani, hogy T0, kb. 100 usec múlva generáljon egy megszakítást (amit persze le kell kezelni):
#define F_CPU 4000000 // orajel 4MHz
define T0_DIVIDER 64
int main( void)
{
T0_T( 100);
T0_IT_OVF_ENABLE;
T0_NORMAL;
...
Szólnom kell arról, hogy jelenleg ATmega8 uC-t használok, az idézett kódok azon futnak. A definíciók eltérnek a standard WINAVR-hez adott .h file-tól. Egy saját tkiraaly_atmega8.h file-t használok, amibe beletettem a makróimat is. Nem vizsgáltam, hogy más AVR uC-ken is helytállóak-e? Pontosabban, korábban ATiny2313-mal dolgoztam, és voltak regiszterek, bitek, amelyek eltértek. Ha valaki nem is használja a tkiraaly_atmega8.h-t, a makrók, esetleg kis módosítással akkor is alkalmazhatóak. Nem vagyok még kész a uC összes egységének beálítására szolgáló makrókkal, ahogy haladok egyik programról a másikra, úgy gyarapodnak. Hiba is előfordulhat bennük, nem tudom kipróbálni az összes variációt.
Érdekes kérdés, hogy miért használjunk makrókat, mikor az adott modulok kezeléséhez írhatunk függvényeket, függvény könyvtárakat is? Régebben (Clipper, C) én is azt az utat jártam, hogy saját függvény könyvtárat készítettem. A GCC/WINAVR-nél még nem jártam utána hogy lehet relokálható object-ekből library-t készíteni, amiből a link-elés során csak a szükségesek fordulnának be a kódba. A makró definíciók nem kerülnek befordításra, csak ha hivatkozunk rájuk. Másrészt nem kellene vele foglalkoznom, de nem tetszik, amit függvény hívások esetén csinál a C. Ment egy csomó regisztert, néha feleslegesen, utána visszatölti, ami növeli a kódot, meg viszi a végrehajtási időt is. Ha valami gyorsabb dolgot szeretne az ember írni, ez nem mindegy. Azután van még egy fontos dolog, a makrók a fordítás során értékelődnek ki, tehát a fordító számítógépen fut, nem azon a uC-n, amelyen majd a lefordított programunk fog futni. Ez fordítva is igaz, ha valamit futás közben kell állítgatnunk, arra a makró nem biztos hogy alkalmas.
Egy kis lazítás a magasröptű makrókombinációk után. Az alábbi definíciók azért születtek, mert közismert, hogy a lusaság a fejlődés mozgatórugója, és ez alól hogy lehetnék én kivétel? :)
#define U8 unsigned char
#define U16 unsigned int
Végül egy habkönnyű megoldás LCD karakterek definiáláshoz, amit korábban az AVR LCD - Magyar betűk című lapomon mutattam be:
#define Cx________ 0
#define Cx_______X 1
#define Cx______X_ 2
#define Cx______XX 3
#define Cx_____X__ 4
#define Cx_____X_X 5
#define Cx_____XX_ 6
#define Cx_____XXX 7
#define Cx____X___ 8
#define Cx____X__X 9
#define Cx____X_X_ 10
#define Cx____X_XX 11
#define Cx____XX__ 12
#define Cx____XX_X 13
#define Cx____XXX_ 14
#define Cx____XXXX 15
#define Cx___X____ 16
#define Cx___X___X 17
#define Cx___X__X_ 18
#define Cx___X__XX 19
#define Cx___X_X__ 20
#define Cx___X_X_X 21
#define Cx___X_XX_ 22
#define Cx___X_XXX 23
#define Cx___XX___ 24
#define Cx___XX__X 25
#define Cx___XX_X_ 26
#define Cx___XX_XX 27
#define Cx___XXX__ 28
#define Cx___XXX_X 29
#define Cx___XXXX_ 30
#define Cx___XXXXX 31
const uint8_t magyar_betuk[] PROGMEM =
{
0,
Cx______X_,
Cx_____X__,
Cx____XXX_,
Cx_______X,
Cx____XXXX,
Cx___X___X,
Cx____XXXX,
Cx________,
0xFF
};
Kicsit hunyorítsunk ;-).
Itt a vége, fuss el véle, legytek az én vendégeim, innen letölthetitek a hozzávalókat összecsomagolva.