Programování mikroprocesor· AVR v jazyce C Ji°í Bourek 16. kv¥tna 2007
1
1
Úvod
Oproti b¥ºným procesor·m pouºívaným v osobních po£íta£ích jsou mikroprocesory vcelku jednoduchá za°ízení a je tedy moºné je vcelku snadno programovat p°ímo v assembleru. Takto napsané programy bývají velice rychlé, ale vývoj jako takový trvá p°íli² dlouho. Z toho d·vodu se mikroprocesory b¥ºn¥ programují v jazyce C.
2
Knihovna avr-libc
2.1 P°eklada£ avr-gcc a projekt WinAVR Aby nemuseli d¥lat v²echnu práci odznova, vývojá°i p°eklada£e jazyka C pro architekturu AVR pouºili jako základ p°eklada£ gcc (GNU C Compiler) a ostatní utility pouºívané pro vývoj na opera£ním systému Linux a dal²ích unixových systémech. P°eklada£ gcc jako takový byl pozd¥ji portován i na Windows, takºe i avr-gcc je moºné v tomto OS pouºívat a dokonce ho i integrovat do vývojového prost°edí AVR Studio od rmy Atmel. Pro snadnou instalaci a pouºití p°eklada£e avr-gcc vznikl projekt WinAVR (http://winavr.sourceforge.net/), který obsahuje p°eklada£, ostatní utility a dal²í software. Pro architekturu AVR byla portována i knihovna libc. Tuto knihovnu je moºné voln¥ pouºít pro vývoj jak uzav°ených, tak open source program·. Je obsaºena v balíku WinAVR, ale lze ji stáhnout i samostatn¥ ze stránek projektu (http://savannah.nongnu.org/projects/avr-libc/)
Poznámka: Tento dokument je psán podle sou£asné verze avr-libc; pokud je n¥kde uvedeno, jak p°eklada£ p°eloºí kód, platí to pro avr-gcc verze 4.1.0.
2.2 Vybrané hlavi£kové soubory -
Tato hlavi£ka umoº¬uje práci s hardwarem mikroprocesoru, zp°ístup¬uje I/O registry. Protoºe se po£et a jména registr· li²í podle typu, obsahuje tento hlavi£kový soubor pouze ty registry, které jsou spole£né pro v²echny procesory AVR, které se dají programovat v C. P°i p°ekladu dostává avr-gcc jako parametr typ mikroprocesoru, pro který se program p°ekládá. P°i preprocesingu denuje makro, které tomuto parametru odpovídá, toto makro se pouºije p°i podmín¥ném p°ekladu a p°i vloºení se automaticky vloºí dal²í hlavi£kový soubor, který odpovídá konkrétnímu za°ízení. K port·m je moºné p°istupovat jménem, které je uvedné v manuálu. Následující p°íklad p°epne celý port A do reºimu výstup a výstupní piny nastaví do logické 1.
#include int main () { DDRA = 0xff; PORTA = 0xff; }
2.3 Tento hlavi£kový soubor vkládá funkce a makra slouºící k obsluze p°eru²ení. Funkce, která obsluhuje p°eru²ení, je deklarována takto:
ISR (p°eru²ení) { } Místo p°eru²ení je pot°eba zadat jméno vektoru p°eru²ení, nap°íklad obsluha vn¥j²ího p°eru²ení je pojmenována
1
INT0_vect . Po startu procesoru je v registru p°íznak· vynulován bit I (globální povolení p°eru²ení). Ten je moºné z programu
2
nastavit zavoláním funkce sei() a vynulovat voláním cli() . Pokud n¥jaké p°eru²ení £eká na obslouºení, po povolení p°eru²ení funkcí sei() se vykoná je²t¥ jedna instrukce a poté procesor sko£í do obsluºné funkce.
1 Kompletní
seznam
je
vypsán
v
dokumentaci
avr-libc
na
webu
na
adrese
http://www.nongnu.org/avr-libc/user-
manual/group__avr__interrupts.html
2 sei()
ani cli() ve skute£nosti nejsou funkce, ale makra, která na dané místo kódu vloºí instrukci sei (cli)
2
Následuje nástin programu, který bude reagovat na vn¥j²í p°eru²ení:
#include ISR (INT0_vect) { // n¥jaká akce } int main () { // inicializace - povolení vn¥j²ího p°eru²ení sei(); loop: // hlavní smy£ka programu goto loop; } V obsluze p°eru²ení je pot°eba uloºit na zásobník hodnoty registr· a registr p°íznak· - to automaticky zajistí p°eklada£. P°i vstupu do obsluºné funkce procesor automaticky vynuluje bit I v registru p°íznak· a tím zabra¬uje obsluze dal²ího p°eru²ení do doby, neº je obslouºeno p°eru²ení první. Po návratu z obsluhy p°eru²ení je p°íznak I znovu nastaven a m·ºe být zavolána dal²í obsluºná funkce. Pokud je ºádoucí, aby obsluha n¥jakého p°eru²ení mohla být p°eru²ena, lze to p°eklada£i oznámit atributem interrupt:
ISR (INT0_vect) __attribute__ ((interrupt)); ISR (INT0_vect) { // obsluha p°eru²ení } Tento atribut zp·sobí, ºe p°eklada£ p°ed první instrukci v obsluze p°eru²ení vloºí instrukci sei a tím op¥t povolí p°eru²ovací systém. Pokud tedy na obsluhu £eká dal²í p°eru²ení, vykoná se jedna instrukce (zpravidla push r1) a procesor sko£í do £ekající obsluºné funkce. Vektory p°eru²ení, pro které nejsou denovány ºádné obsluºné funkce, dodenuje p°eklada£ tak, ºe procesor sko£í na programovou adresu 0 a vyresetuje se. Tomuto chování lze zabránit denováním prázdného p°eru²ení:
EMPTY_INTERRUPT (INT1_vect); Prázdné p°eru²ení obsahuje po p°eloºení pouze instrukci reti.
2.4 Procesory AVR disponují n¥kolika reºimy spánku, ve kterých procesor spot°ebovává mén¥ energie. Pokud je pot°eba procesor uspat, je nutné postupovat v t¥chto krocích. 1. Nastavit typ reºimu spánku - procesor má podle typu n¥kolik reºim· spánku, které se li²í tím, kolik energie se u²et°í, jak dlouho trvá probuzení a jaké události mohou probuzení zp·sobit. Jednotlivé reºimy jsou denovány t¥mito makry:
SLEEP_MODE_ADC SLEEP_MODE_IDLE SLEEP_MODE_STANDBY SLEEP_MODE_EXT_STANDBY SLEEP_MODE_PWR_DOWN SLEEP_MODE_PWR_SAVE Do kterého reºimu procesor p°ejde, je nastaveno funkcí set_sleep_mode (), jméno reºimu se jí p°edává jako parametr. 2. Povolit uspání procesoru - i kdyº je nastaveno, do jakého reºimu má procesor p°ejít, stále není moºné ho uspat, protoºe po resetu to není povoleno. Povolení reºimu spánku se provede zavoláním funkce sleep_enable(), op¥tovné
3
zakázání tohoto reºimu funkcí sleep_disable()
3. Zavolat funkci pro uspání - pokud je uspávání povoleno, procesor se do reºimu spánku p°epne voláním funkce
4
sleep_cpu() .
Poznámka: Procesor se probouzí, pokud nastane p°eru²ení. P°ed voláním sleep_cpu() je tedy nutné volat funkci sei(). 3 Tyto funkce jsou ve skute£nosti makra, která nastaví nebo vynulují I/O registr, kterým 4 Ve skute£nosti jde o makro, které na místo funkce vloºí assemblerovskou instrukci sleep
3
se reºim slánku ovládá.
2.5 <stdlib.h> Poznámka: Z této knihovny jsou vybrány jenom n¥které funkce.
2.5.1
malloc(), free()
Jak pí²í auto°i dokumentace, implementovat dynamické alokace pam¥ti na procesorech, které mají minimum pam¥ti, není nic jednoduchého. Standardní rozvrºení pam¥ti je takové, ºe na za£átek se umis´ují sekce .data a .bss. Za t¥mito sekcemi se nachází halda, zásobník za£íná na konci pam¥ti a roste sm¥rem k niº²ím adresám. To znamená, ºe pokud je alokováno hodn¥ dynamické pam¥ti (ale také pokud dojde k fragmentaci pam¥ti) a zárove¬ je pot°eba uloºit p°íli² mnoho dat na zásobník, m·ºe dojít ke kolizi. Pokud je k dispozici vn¥j²í pam¥´, je moºné rozvrºení m¥nit, napríklad lze vyhradit vnit°ní RAM pouze pro zásobník a v²echny prom¥nné umístit do pam¥ti vn¥j²í. Návod je k dispozici v dokumentaci - http://www.nongnu.org/avrlibc/user-manual/malloc.html Parametrem funkce malloc() je, kolik pam¥ti je pot°eba alokovat; vrací ukazatel na alokované místo nebo NULL. Funkce free() má jako parametr ukazatel.
2.5.2
exit()
Volání funkce exit() ukon£í program. Protoºe v mikroprocesoru není nad°azené prost°edí, do kterého by bylo moºné se vrátit, program je ukon£en nekone£nou smy£kou. Parametrem funkce je celé £íslo, které je ignorováno. P°eklada£ automaticky vloºí nekone£nou smy£ku na konec programu - pokud je funkce main() n¥jak p°eru²ena (b¥h programu dojde na konec nebo pouºitím p°íkazu return), program sko£í na tuto nekone£nou smy£ku.
3
Programování a p°eklad v¥t²ích projekt·
3.1 Jednoduchý program Následující jednoduchý p°íklad pouºije pin 1 na portu A jako výstupní a st°ídá na n¥m logickou 0 a 1. #include int main () { unsigned int a; DDRA |= (1 < < DDA1); loop: PORTA |= (1 < < PORTA1); for (a=1; a; a++); PORTA &= ~(1 < < PORTA1); for (a=1; a; a++); goto loop; } Kód uloºíme do souboru prvni.c a p°eloºíme z p°íkazové °ádky (musíme být v adresá°i, ve kterém je zdrojový kód uloºen):
avr-gcc -g2 -Wall -mmcu=atmega16 -o prvni.elf prvni.c Parametry p°edávané p°eklada£i v tomto p°ípad¥ zajistí, ºe výstup bude obsahovat ladící informace (-g2), p°i p°ekladu budou vypisována v²echna varování (-Wall), p°ekládáme pro procesor AtMega 16 (-mmcu). Pokud se pokusíme p°eloºit program pro procesor, který nemá port A, p°eklada£ vrátí chybové hlá²ení: error: 'DDRA' undeclared (first use in this function) error: (Each undeclared identifier is reported only once error: for each function it appears in.) V tuto chvíli bychom rádi pro kontrolu vid¥li, jaký assemblerovský kód p°eklada£ vygeneroval. K tomu slouºí dal²í utilita - avr-objdump
avr-objdump -h -S prvni.elf >prvni.lst
4
Tímto p°íkazem vygenerujeme soubor prvni.lst, který obsahuje hlavi£ky sekcí a disassemblovaný kód, který je tam, kde
5
je to moºné, uvozen C kódem, který k n¥mu pat°í . Kdyº si soubor prohlédneme, zjistíme, ºe po spu²t¥ní programu p°eklada£ vloºil kód, který jsme nepsali. V n¥m zaji²´uje zkopírování dat uloºených v programové pam¥ti (konstanty, °et¥zce) do pam¥ti opera£ní a inicializuje globální prom¥nné. Protoºe ani jedno z toho v tomto p°íkladu není pouºito, kód se p°esko£í. Poté uº následuje ná² program:
int main () { 8e: cd e5 90: d4 e0 92: de bf 94: cd bf unsigned int a;
ldi ldi out out
r28, 0x5D r29, 0x04 0x3e, r29 0x3d, r28
; ; ; ;
93 4 62 61
Jako první p°eklada£ inicializuje ukazatel zásobníku (stack pointer, SP) tém¥° na konec pam¥ti (p°idáváním dat na zásobník se hodnota v SP sniºuje). Nad ukazatelem nechává 2B místa pro prom¥nnou a.
DDRA |= 96: 98: 9a: 9c: 9e: a0: a2:
(1 aa b0 ea f0 80 82 8c
< < DDA1); e3 e0 e3 e0 81 60 93
ldi ldi ldi ldi ld ori st
r26, 0x3A r27, 0x00 r30, 0x3A r31, 0x00 r24, Z r24, 0x02 X, r24
; ; ; ;
58 0 58 0
; 2
Tato sekce nastaví pin 1 na portu A jako výstupní. Na výstupu je logická nula.
loop: PORTA |= (1 < < PORTA); a4: ab e3 ldi a6: b0 e0 ldi a8: eb e3 ldi aa: f0 e0 ldi ac: 80 81 ld ae: 82 60 ori b0: 8c 93 st
r26, 0x3B r27, 0x00 r30, 0x3B r31, 0x00 r24, Z r24, 0x02 X, r24
; ; ; ;
59 0 59 0
; 2
Zde program p°e£te hodnotu aktuáln¥ zapsanout v PORTA, logicky ji se£te s dvojkou (piny se £íslují od nuly, pin 1 tedy odpovídá bitu 2) a výsledek znovu zapí²e do PORTA. Tím na výstupu nastaví logickou 1.
for (a=1; b2: 81 b4: 90 b6: 9a b8: 89 ba: 05 bc: 89 be: 9a c0: 01 c2: 9a c4: 89 c6: 89 c8: 9a ca: 00 cc: b9
a; a++); e0 e0 83 83 c0 81 81 96 83 83 81 81 97 f7
ldi ldi std std rjmp ldd ldd adiw std std ldd ldd sbiw brne
r24, r25, Y+2, Y+1, .+10 r24, r25, r24, Y+2, Y+1, r24, r25, r24, .-18
0x01 0x00 r25 r24 Y+1 Y+2 0x01 r25 r24 Y+1 Y+2 0x00
; ; ; ; ; ; ; ; ; ; ; ; ; ;
1 0 0x02 0x01 0xc6 <main+0x38> 0x01 0x02 1 0x02 0x01 0x01 0x02 0 0xbc <main+0x2e>
Aby se logická hodnota na výstupu nem¥nila p°íli² rychle, vloºíme do programu £ekací smy£ku. Na za£átku cyklu uloºí do prom¥nné a jedni£ku a v kaºdé iteraci cyklu program na£te hodnotu prom¥nné a z pam¥ti, zvý²í ji o 1 a
6
znovu ji uloºí. Kdyº hodnota p°ete£e z 65535 zpátky na 0, program z cyklu vysko£í. .
5 Ne
vºdy se to poda°í - ve sloºit¥j²ím kódu se £asto stane, ºe v souboru najdeme nejprve dlouhý blok C kódu a teprve potom dlouhý
blok assemblerovských instrukcí - v takovém p°ípad¥ se dost t¥ºko ur£uje, co k £emu pat°í a obtíºn¥ se hledají p°ípadné chyby.
6V
jazyce C neexistuje prom¥nná typu logická hodnota - nenulová hodnota znamená TRUE, nula je FALSE. V tomto p°ípad¥ tedy
cyklus b¥ºí tak dlouho, dokud hodnota v a není nulová.
5
PORTA &= ~(1 < < PORTA1); ce: ab e3 ldi d0: b0 e0 ldi d2: eb e3 ldi d4: f0 e0 ldi d6: 80 81 ld d8: 8d 7f andi da: 8c 93 st
r26, 0x3B r27, 0x00 r30, 0x3B r31, 0x00 r24, Z r24, 0xFD X, r24
; ; ; ;
59 0 59 0
; 253
Nyní pin nastavíme zp¥t do logické nuly. Program na£te hodnotu v PORTA, logicky ji vynásobí s hodnotou, ve které jsou v²echny bity krom¥ bitu 2 v logické 1, a výsledek uloºí zp¥t.
for (a=1; a; a++); dc: 81 e0 de: 90 e0 e0: 9a 83 e2: 89 83 e4: 05 c0 e6: 89 81 e8: 9a 81 ea: 01 96 ec: 9a 83 ee: 89 83 f0: 89 81 f2: 9a 81 f4: 00 97 f6: b9 f7 goto loop; f8: d5 cf
ldi ldi std std rjmp ldd ldd adiw std std ldd ldd sbiw brne
r24, r25, Y+2, Y+1, .+10 r24, r25, r24, Y+2, Y+1, r24, r25, r24, .-18
rjmp
.-86
0x01 0x00 r25 r24 Y+1 Y+2 0x01 r25 r24 Y+1 Y+2 0x00
; ; ; ; ; ; ; ; ; ; ; ; ; ;
1 0 0x02 0x01 0xf0 <main+0x62> 0x01 0x02 1 0x02 0x01 0x01 0x02 0 0xe6 <main+0x58>
; 0xa4 <main+0x16> 7
ekací cyklus je stejný, kdyº dob¥hne, program sko£í na za£átek smy£ky a v²e se opakuje . Tento kód je na první pohled velice neefektivní - jenom nastavení I/O registru DDRA zabere dev¥t hodinových cykl· a pouºívá se p°itom p¥t registr·. V £ekacím cykl· se prom¥nná a zbyte£n¥ ukládá do pam¥ti, p°itom by mohla
8
z·stat v registrech a b¥h programu by se tím zrychlil . Proto program p°eloºíme je²t¥ jednou a tentokrát zapneme optimalizace:
avr-gcc -g2 -Wall -O2 -mmcu=atmega16 -o prvni.elf prvni.c avr-objdump -h -S prvni.elf >prvni.lst Disassemblovaný program bude vypadat takto:
int main () { 8e: cf e5 90: d4 e0 92: de bf 94: cd bf unsigned int a; DDRA |= (1 < < DDA1); 96: d1 9a
ldi ldi out out
r28, 0x5F r29, 0x04 0x3e, r29 0x3d, r28
sbi
0x1a, 1 ; 26
loop: PORTA |= (1 < < PORTA1); 98: d9 9a sbi for (a=1; a; a++); PORTA &= ~(1 < < PORTA1); 9a: d9 98 cbi 9c: fd cf rjmp
; ; ; ;
95 4 62 61
0x1b, 1 ; 27 0x1b, 1 ; 27 .-6
; 0x98 <main+0xa> 9
V nastavení registru DDRA je vid¥t zna£né zlep²ení: p°eklada£ pouºil instrukci sbi - nastavení bitu v I/O registru ,
7V
kaºdé knize o programování po£íta£· se pí²e, ºe p°íkaz goto by se m¥l pouºívat omezen¥, nebo lépe v·bec, protoºe zp·sobí, ºe
procesor nem·ºe vyuºít pipelining a program se zpomaluje. P°i programování mikroprocesor· tomu tak není a pouºití goto je (pokud je pot°eba n¥kam sko£it) naprosto regulérní.
8 Dalo
by se °íct, ºe kdyº vyrábíme
£ekací
smy£ku, n¥jaká pomalost nám nevadí. V tomto p°ípad¥ ne, ale pokud bychom pot°ebovali
£ekat n¥jaký p°esný £as, je vhodné, aby doba prob¥hnutí jednoho cyklu byla co nejkrat²í, protoºe ji násobíme celým £íslem - £ím krat²í doba cyklu, tím p°esn¥j²í £asování m·ºeme získat.
9 Pro£
tuto instrukci nepouºívá automaticky? Instrukce sbi a cbi nefungují pro v²echny I/O registry, takºe je nutné zkoumat kód a zjistit,
jaký registr se nastavuje, coº spadá pod optimalizace.
6
stejnou instrukci pouºil pro manipulaci s registrem PORTA. Nicmén¥ je také vid¥t, ºe se n¥co nepovedlo - p°eklada£ analyzoval kód a zjistil, ºe hodnota prom¥nné a se nikde nepouºívá, proto zcela vylou£il oba £ekací cykly a pro samotnou prom¥nnou ani nealokoval místo v pam¥ti. Abychom tomu zabránili, musíme deklarovat prom¥nnou a jinak:
volatile unsigned int a; Slovo volatile °íká p°eklada£i, ºe hodnota prom¥nné a se m·ºe kdykoliv v b¥hu programu zm¥nit, aniº by o tom v¥d¥l. To se vztahuje nap°íklad na prom¥nné, se kterými pracujeme jak v hlavní smy£ce programu, tak v obsluze p°eru²ení. P°eklada£ nijak nezkoumá, jestli se hodnota v a opravdu m·ºe zm¥nit a jestli ji opravdu pouºíváme v n¥jaké obsluze p°eru²ení (zde se zm¥nit nem·ºe, ºádné p°eru²ení neobsluhujeme), proto kód, ve kterém s touto prom¥nnou pracujeme, neodstraní (to chceme), ale na druhou stranu po kaºdé zm¥n¥ novou hodnotu uloºí do pam¥ti.
3.2 Jednoduchý Makele Neustálé vypisování avr-gcc ....., kdyº pot°ebujeme program p°eloºit, se po n¥kolika opakováních zna£n¥ omrzí, takºe vyuºijeme utilitu make, která nám zna£n¥ u²et°í práci. V adresá°i, ve kterém máme uloºen program, vytvo°íme soubor Makele
10 , který bude obsahovat následující:
PRG OBJ MCU_TARGET
= prvni = prvni.o = atmega16
CC OBJCOPY OBJDUMP
= avr-gcc = avr-objcopy = avr-objdump
override CFLAGS
= -g2 -Wall -O2 -mmcu=$(MCU_TARGET) $(DEFS)
program: $(PRG).elf lst all: $(PRG).elf lst $(PRG).elf: $(OBJ) $(CC) $(CFLAGS) -o $@ $^ $(LIBS) lst: $(PRG).lst %.lst: %.elf $(OBJDUMP) -h -S $< > $@ D·leºitá poznámka - °ádky, které jsou odsazené (nap°. $(OBJDUMP) -h -S $< > $@), jsou odsazené dv¥ma tabulátory. Pokud zvolíte jiný zp·sob, Makele nebude fungovat. Nyní program p°eloºíme prostým zadáním p°íkazu make na p°íkazové °ádce v adresá°i, ve kterém je uloºen zdrojový kód a Makele.
3.3 Program sloºený z více soubor· P°edpokládejme, ºe máme sloºit¥j²í program a chceme ho rozd¥lit do n¥kolika men²ích celk·. Tuto situaci m·ºeme demonstrovat na p°edchozím programu odd¥lením £ekacího cyklu. Vytvo°íme nový adresá° a hlavní program uloºíme do souboru druhy.c:
10 Tento
Makele je zjednodu²ená verze p°íkladu, který je uveden na webových stránkách projektu avr-libc - http://www.nongnu.org/avr-
libc/user-manual/group__demo__project.html
7
#include #include int main DDRA |=
"cekani.h" () { (1 < < DDA1);
loop: PORTA |= (1 < < PORTA1); wait(); PORTA &= ~(1 < < PORTA1); wait(); goto loop; } Vytvo°íme hlavi£kový soubor cekani.h a implementaci v n¥m deklarovaných funkcí cekani.c:
/* cekani.h */ #ifndef _CEKANI_H__ #define _CEKANI_H__ void wait(void); #endif
/* cekani.c */ #include "cekani.h" void wait(void) { volatile unsigned int a; for (a=1; a; a++); } Z p°edchozího p°íkazu zkopírujeme Makele a nahradíme první dva °ádky takto:
PRG OBJ
= druhy = druhy.o cekani.o
Poté spustíme make, program se p°eloºí a výsledek si m·ºeme prohlédnout v souboru druhy.lst
3.4 Projekt psaný v C i v assembleru V n¥kterých p°ípadech narazíme na to, ºe kód vygenerovaný z jazyka C je p°íli² pomalý. V p°edchozích p°íkladech se to projevilo v £ekací funkci, která je bu¤ pomalá kv·li neustálému ukládání do pam¥ti a na£ítání, nebo ji p°eklada£ odstraní p°i optimalizaci. Tuto funkci tedy napí²eme v assembleru. Vytvo°íme nový adresá° a z p°edchozího p°íkladu zkopírujeme Makele a druhy.c (p°ejmenujeme na treti.c) Struktura hlavi£kového souboru cekani.h je ur£ena tím, ºe ho pouºívá jak p°eklada£ assembleru, tak p°eklada£ C - je tedy nutné rozli²it, která £ást pat°í komu.
/* cekani.h */ #ifndef _CEKANI_H__ #define _CEKANI_H__
#ifdef __ASSEMBLER__ #define waitl r24 #define waith r25 #else void wait (void); #endif #endif Pokud tento soubor pouºije p°eklada£ assembleru, zapamatuje si makra, která pojmenovávají registry r24 a r25. Pokud ho pouºije p°eklada£ jazyka C, najde deklaraci funkce wait(). Samotnou assemblerovskou implementaci £ekací funkce uloºíme do souboru cekani.S
8
/* cekani.S */ .nolist #include "cekani.h" .list .global wait .func wait wait: eor waitl, waitl eor waith, waith ; vynulování £ítacích registr· wait_loop: adiw waitl, 1 or waitl, waith brne wait_loop ; pokud nejsou oba nulové, opakování cyklu ret Je vid¥t, ºe práce s pam¥tí odpadá a v²echno probíhá £ist¥ v registrech. Pouºitím této £ekací funkce dosáhneme v¥t²í p°esnosti neº s tou funkcí, kterou vygeneruje p°eklada£ jazyka C. P°izp·sobíme nastavení Makele sou£asné situaci:
PRG = treti OBJ = treti.o cekani.o
V tuto chvíli make program nep°eloºí, neví totiº, jak p°eloºit kód psaný v assembleru; na konec Makele proto p°idáme následující instrukce
ASFLAGS ALL_ASFLAGS
11 :
= -Wa,-adhlns=$(<:.S=.lst),-gstabs = -mmcu=$(MCU_TARGET) -I. -x assembler-with-cpp $(ASFLAGS)
%.o : %.S $(CC) -c $(ALL_ASFLAGS) $< -o $@
3.5 Zásady pro kombinování assembleru a C P°i psaní funkcí v assembleru je nutné zachovávat n¥které konvence, s jejichº dodrºováním po£ítá p°eklada£ jazyka C.
3.5.1
Datové typy
Pokud se funkci p°edávají n¥jaké parametry, pop°ípad¥ se odebírá návratová hodnota, je nutné zachovat délku prom¥nných - char má 8 bit·, int 16 bit·, long 32 bit·, long long 64 bit·, oat a double jsou 32 bitové, ukazatele mají 16 bit·.
3.5.2
Vyuºívání registr·
P°eklada£ rozli²uje registry podle toho, jak se k nim chovají volané funkce, na:
•
Nezachovávané p°i volání funkce (call-used) (r18-r27, r30-r31) - funkce tyto registry m·ºe voln¥ pouºívat; pokud je program, který volá funkci, pouºívá, musí si jejich hodnoty odloºit jinam.
•
Zachovávané p°i volání funkce (call-saved) (r2-r17, r28-r29) - pokud chce funkce pouºít tyto registry, musí uloºit hodnoty v nich obsaºené a p°ed návratem je zase obnovit.
•
Pevné registry (r0, r1) - r0 je do£asný registr, ve funkcích je moºné jeho hodnotu libovoln¥ m¥nit (hodnotu zachovávají pouze obsluhy p°eru²ení). r1 je nulový registr, p°i vykonávání kódu v C vºdy obsahuje nulu. V assemblerovských funkcích je moºné ho pouºít i jinak, ale v takovém p°ípad¥ je nutné ho p°ed návratem op¥t vynulovat.
3.5.3
Konvence pro p°edávání parametr·
Parametry se p°ednostn¥ p°edávají v registrových párech r25 aº r8 (parametry, které zabírají lichý po£et byt·, nad sebou mají vºdy jeden nevyuºitý registr). Pokud v registrech není pro v²echny parametry dostatek místa, zbylé se p°edají p°es zásobník. V následujícím p°íkladu se parametr i p°edá v registrech r24:r25 (v po°adí niº²í byte - vy²²í byte) a parametr c v registru r22.
void funkce (int i, unsigned char c) 11 P°evzato
z Makele projektu Avrt
9
Návratové hodnoty funkce se ukládají do registr· od nejvy²²ího:
int funkce (); /* vrací návratovou hodnotu v registrech r24:r25 */ long funkce2 (); /* vrací návratovou hodnotu v registrech r22:r25 */
3.6 Kopírování programu do mikroprocesoru P°eloºený program p°eklada£ uloºí ve formátu ELF (Executable Linux Format), který ale není moºné nahrát do mikroprocesoru. Proto ho pot°ebujeme nejprve zkonvertovat do souboru ve formátu Intel HEX. K tomu vyuºijeme utilitu avr-objdump, do Makele p°idáme následující °ádky:
hex: $(PRG).hex
%.hex: %.elf $(OBJCOPY) -j .text -j .data -O ihex $< $@ Soubor HEX potom vytvo°íme zavoláním p°íkazu make hex. Podobn¥ m·ºeme za°ídit i nahrávání programu do mikroprocesoru - necháme make, aby zavolal program avrdude - do Makele p°idáme:
install: hex avrdude -c programátor -p kód_mcu -U flash:w:$(PRG).hex:i
Slovo programátor je pot°eba nahradit jménem programátoru, který avrdude zná (nap°. dapa, bsd), kód_mcu je kód, kterým avrdude identikuje procesor - nap°íklad m16 pro Atmega 16.
4
Záv¥r
Programování v¥t²ího projektu v assembleru je v podstat¥ nemyslitelné - kód se velmi rychle stává nep°ehledným, programování trvá dlouho a to výsledný program prodraºuje. Pouºít n¥jaký vy²²í jazyk je tedy nezbytnost. Dne²ní mikroprocesory mají navíc dostatek výpo£etního výkonu, aby si mohly dovolit vykonat n¥které instrukce, které p°eklada£ generuje navíc. Na druhou stranu je pot°eba dávat pozor na to, co p°eklada£ vygeneruje tam, kde záleºí na £asování. V takových p°ípadech bývá výhodn¥j²í p°epsat kód do assembleru.
Reference [1] Webové stránky projektu Avr-libc: http://www.nongnu.org/avr-libc/
10