Programování mikroprocesor· AVR v jazyce C
Transkript
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 - <avr/io.h> 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í <avr/io.h> 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 <avr/io.h> int main () { DDRA = 0xff; PORTA = 0xff; } 2.3 <avr/interrupt.h> 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 <avr/interrupt.h> 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 <avr/sleep.h> 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 <avr/io.h> 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 |= <avr/io.h> "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
Podobné dokumenty
Implementace USB rozhraní AVR mikrořadičem Jan Smrž
Firma Atmel vyrábí rodinu mikrokontrolérů AVR. Jedná se o jednočipové mikropočítače harwardovské architektury s redukovanou instrukční sadou (RISC). Obsahují tři
typy pamětí, 8-bitovou sběrnici a m...
AVR – Instrukční soubor
o K6 = 6b konstanta 0-63 (nebo konstantní výraz)
o K8 = 8b konstanta 0-255 (nebo konstantní výraz)
o P = registry pro nepřímé adresování, P může být kterýkoli z X, Y ,Z
o Q = registry pro nepřímé a...
Programování MCU ve vyšších programovacích jazycích
o harvardská architektura (program je odděleně od dat)
o přerušení (obsluha, povolení/zakázání)
program v assembleru = pouze výkonný kód
program v HLL =
o výkonný kód
o inicializace CPU pro běh pro...
Metodika BFW 2.3 - Blind Friendly Web
že ji zpracují do podoby itelné pro nevidomého a nevidomý návšt vník si ji poté m že za ít prohlížet
( íst). Pokud b hem tohoto tení dojde k automatické obnov stránky, je stránka znovu ode íta em
o...
Manuál k vývojovému kitu EvB 4.3 rev.3
v České republice velmi populárními mikrokontroléry AVR Atmel ATmega16,
ATmega32 a ATmega64.
Deska je osazena řadou senzorů, čidel a periferních zařízení, které jsou
připojeny na hřebínkové konekto...
Příručka ke cvičení z Úvodu do moderní fyziky
poc. Rovnice musí obsahovat závislou prom¥y(x). Po£áte£ní podmínky nech´ jsou ve
y(a)=h