Programování mikroprocesor· AVR v jazyce C

Transkript

Programování mikroprocesor· AVR v jazyce C
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ž

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...

Více

AVR – Instrukční soubor

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...

Více

Programování MCU ve vyšších programovacích jazycích

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...

Více

Metodika BFW 2.3 - Blind Friendly Web

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...

Více

Manuál k vývojovému kitu EvB 4.3 rev.3

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...

Více

Příručka ke cvičení z Úvodu do moderní fyziky

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

Více