You are on page 1of 3

KURS

AVRGCC: kompilator C
mikrokontrolerw AVR, cz

W tej czci kursu skupiamy si na omwieniu zakresu zmiennych, budowie


i funkcjach plikw nagwkowych, przybliajc w ten sposb kolejne tajniki
kaprynego jak gosi nona opinia kompilatora.

Zakres zmiennych

W zalenoci od miejsca oraz


sposobu zadeklarowania zmiennych
mog mie one w naszym projekcie
rny zasig tzn. moemy z nich
korzysta w jednym pliku rdowym (module), w wielu plikach albo
tylko wewntrz kodu funkcji. Mwimy w takim przypadku o zmiennych
globalnych oraz lokalnych. Podzia
ten nie ma wpywu na typ zmiennej ale jest istotny w trakcie pisania programu, inny jest te sposb
obsugiwania zmiennych lokalnych
przez kompilator.
Do tej pory ograniczalimy si
do zmiennych globalnych (zasig
globalny jest domylny) deklarowanych i uywanych w pojedynczym
pliku (module) rdowym projektu. Utwrzmy teraz nastpny przykadowy projekt zawierajcy kilka
moduw: main.c, funkcje.c oraz
dane.h zapiszmy go w subfolderze \Projects\Kurs\Przyklad03\ jako
Test03. Dodawanie plikw do projektu jest w AvrSide bardzo proste:
wykonujemy komend menu Projekt>Dodaj pust stron (dostpna take w menu kontekstowym projektu
wywoywanym skrtem CTRL+.)
i zapisujemy now zakadk NoName jako odpowiedni typ pliku (c,
s, h) z wybran nazw (typ pliku rdowego wybieramy z listy
rozszerzenie bdzie dodane automatycznie wic nie musimy go dopisywa). Jednak najpierw musimy
wpisa do moduu jaki kod (moe
to byc na wstpie sam komentarz)
gdy AvrSide blokuje zapis pliku
pustego. W pliku main.c wstawimy
jak zwykle szablon moduu gwnego natomiast w pliku dane.h szablon nagwek danych projektu
(headdat).
Szablon danych zosta przygotowany tak aby bez wielokrotnego przepisywania deklaracji mona byo uywa w caym projekcie
wsplnych globalnych zmiennych,
funkcji oraz denicji:

Elektronika Praktyczna 7/2005

// plik nagwkowy globalnych danych


projektu
#ifndef _PROJ_DAT_H_
#dene _PROJ_DAT_H_
// #include:
// #dene:
// denicje typw typedef
// dane globalne
#ifdef _MAIN_MOD_
// denicje danych tylko w module
main()
// char x;
int test = 10;

#else
// deklaracje danych jako importowanych
w kadym innym module
// extern char x;
extern int test;
#endif
// deklaracje funkcji
// extern char Myfunc(int,char);

extern int Myfunc(char x,char y);


#endif

Wstawiamy tutaj wsplne dla


wszystkich moduw projektu pliki
nagwkowe (np. #include <avr/
io.h>), denicje konguracji i podcze sprztowych (np. #dene
LED PB2), wasne denicje typw
(np. typedef unsigned char uchar).
Po doczeniu naszego nagwka do
dowolnego moduu (#include dane.
h) mamy od razu w module dostp
do wszystkich tych ustawie.
Troch wicej komplikacji jest
z globalnymi zmiennymi. Zwyke ich zadeklarowanie spowoduje wprawdzie, e bd widoczne
w projekcie i nie zostanie zgoszony
bd na etapie kompilacji poszczeglnych moduw ale nie da sobie
z tym rady konsolidator sygnalizujc bd wielokrotnej denicji. Moemy to od razu sprawdzi dopisujc int test=10; w obu naszych
plikach rdowych c (main i funkcje): kompilacja (CTRL+F9) przebiegnie sprawnie ale projektu nie
da si zakoczy (F9 bd linkera multiple denition of test).
Z pomoc przychodzi kompilacja warunkowa: w pliku gwnym
ze zdeniowanym makrem _MAIN_
MOD_ preprocesor wstawi pen denicj int test=10;natomiast
w pozostaych plikach tylko informacj dla kompilatora, e zmienna

test ju gdzie w projekcie istnieje


(extern) i mona z niej bezpiecznie
korzysta.
Nowsze wersje avrgcc pozwalaj na pominicie tego sposobu
w przypadku zmiennych automatycznie zerowanych (sekcja bss)
taka zmienna (np. int test;) jest
samoczynnie bez dodatkowych zabiegw traktowana jako pojedyncza
pomimo wielokrotnego zdeniowania i zostaje jej przydzielony jeden
wsplny obszar w SRAM.
W przypadku funkcji mona bez
bdu uy we wszystkich moduach deklaracji extern w ten sposb funkcja (ktr dokadnie zdeniujemy tylko w jednym dowolnie
wybranym module) bdzie widoczna i moliwa do uycia w caym
projekcie. Zrbmy to zaraz deniujc w pliku funkcje.c funkcj zadeklarowan w dane.h jako extern int
Myfunc (char x, char y); (funkcja
o dwch argumentach typu char,
zwracajca rezultat typu int) (nie
zapomnijmy oczywicie o doczeniu do obu rde nagowka z danymi: #include dane.h):
int Myfunc(char x,char y)
{
char a,b;

a=2*x + y;
b=x + 2*y;
return (a+b);

Teraz w pliku gwnym main.c


moemy ju bez problemu posuy
si t funkcj:
test = Myfunc(10,5);

W funkcji celowo wprowadziem zmienne lokalne a, b (chocia


nie s dla wykonania oblicze konieczne) aby przedstawi sposb
ich obsugi przez kompilator. Takie
zmienne deniowane wewntrz
ciaa funkcji (zwane te zmiennymi automatycznymi) s dostpne i moliwe do wykorzystywania
tylko i wycznie w obrbie tego
ciaa funcji. Prba odwoania do
nich spoza funkcji powoduje bd.

89

KURS

Rys. 14. Podgld zmiennych lokalnych

Zmienne te istniej tylko w czasie


wykonywania funkcji po wywoaniu funcji, w prologu, s tworzone
albo na stosie albo (jeli optymalizator stwierdzi, e ma chwilowo
do dyspozycji odpowiedni liczb
rejestrw) w obszarze rejestrw roboczych. Po zakoczeniu dziaania
funkcji po prostu przestaj istnie
pami dla nich przydzielona
zostaje przeznaczona na inne biece cele.
Zobaczmy, jak przedstawi nam to
w dziaaniu AvrStudio. Po omawianym ju wstpnym skongurowaniu
sesji AvrStudio wstawmy do okienka
podgldu zmiennych wszystkie uyte
zmienne: test, a, b.
Test po zerowaniu przyjmuje warto 10, natomiast a i b s okrelone
jako not in scope (poza zakresem),czyli wszystko zgodnie z oczekiwaniami. Przejdmy teraz krokami
(F11) do wntrza funkcji, spotka nas
niestety niespodzianka: zmienne a i b
nadal nie s obsugiwane (location
not valid AvrStudio ma kopot
z ich umiejscowieniem w pamici).
Przyczyn jest wspomniane powyej
skuteczne dziaanie optymalizatora.
W kodzie asemblera znajdujemy:
int Myfunc(char x,char y)
{
5c: 28 2f
mov r18, r24
5e: 86 2f
mov r24, r22
char a,b;
a=2*x + y;
60: 92 2f
62: 99 0f
64: 96 0f
b=x + 2*y;
66: 88 0f
68: 82 0f
return (a+b);
6a: 29 2f
6c: 33 27
6e: 27 fd
70: 30 95
72: 99 27
74: 87 fd
76: 90 95
78: 82 0f
7a: 93 1f
7c: 08 95
}

mov r25, r18


add r25, r25
add r25, r22
add r24, r24
add r24, r18
mov r18, r25
eor r19, r19
sbrc r18, 7
com r19
eor r25, r25
sbrc r24, 7
com r25
add r24, r18
adc r25, r19
ret

Optymalizator wykona wszystkie


potrzebne dziaania w obszarze rejestrw w sposb na tyle zwizy, e
nie zasza potrzeba wyranego wy-

90

odrbniania zmiennych lokalnych. Jest


to bardzo pozytywny rezultat jednak
dla potrzeb naszego
testu wyczmy na
chwil optymalizacj
(odpowiada to opcji
O0 kompilatora).
Teraz widzimy (pamitajmy o uyciu
komendy Build a nie
Make po zmianie
opcji), e zmienne
na stosie
a oraz b s z chwil
wejcia programu do funcji tradycyjnie tworzone tymczasowo na stosie
(w moim przykadzie pod adresami
0x045A i 0x045B) i niszczone po zakoczeniu funkcji. Jednak od razu
zauwaymy te znaczcy przyrost
objtoci kodu. Moemy przy okazji
porwna generowane kody assemblera i obejrze ile poytecznej pracy
wykonuje optymalizator. Nic dziwnego, e czsto symulacja w AvrStudio nie zgadza si z naszym zapisem rdowym: nie wykorzystywane
zmienne mog byc usunite, niektre
linie kodu s eliminowane itd. Ingerencja optymalizatora moe by
na tyle dua, e ten sam program
ze zmienionym poziomem optymalizacji czasem zaczyna zachowywa
si nieco inaczej. Dlatego chwilowe
przeczanie poziomw optymalizacji
tylko po to aby lepiej obejrze wynik w symulatorze (tak jak to przed
chwil zrobilimy w celach edukacyjnych) jest generalnie kiepskim pomysem (nie ma niestety moliwoci
selektywnego ustawiania rnych poziomw optymalizacji dla poszczeglnych fragmentw kodu).
W praktyce zamiast rezygnowa z zalet optymalizacji lepiej
jest kontrolowa istotne dla nas
zmienne przy pomocy uywanego
ju sowa kluczowego volatile. Informuje ono kompilator, eby tak
opisanej zmiennej nie poddawa
jakimkolwiek dziaaniom optymalizujcym i upraszczajcym i wykonywa na niej wszystkie operacje
przewidziane w kodzie (chocia
z punktu widzenia optymalizatora
mog one wyglda na zbdne).
Gwne zastosowanie tego mechanizmu to zabezpieczanie zmiennych uywanych w przerwaniach
(to wynika bezporednio z nazwy:
volatile czyli ulotny, nietrway
oznacza, e warto zmiennej
moe by w kadej chwili uaktu-

alniona przez czynnik zewntrzny przerwanie i nie mona


w zwizku z tym pomin adnej
zwizanej z ni operacji w gwnej
ptli programu), jednak czsto jest
pomocny take w rnych innych
sytuacjach. Sprawdmy zaraz, e
zmiana deklaracji na volatile char
a,b; (przy ponownym wczeniu
maksymalnej optymalizacji) daje
ten sam efekt: zmienne wdruj
z obszaru rejestrw na stos. Jest to
pokazane na rys. 14.
Zobaczmy jeszcze, e takie
same nazwy zmiennych mog by
z powodzeniem uyte w innej funkcji w tym celu deniujemy sobie
dodatkowo:
int Myfunc1(char x,char y)
{
volatile char a,b;

a=x + y;
b=x y;
return (a*b);

i ogldamy jak traktowane s


zmienne a oraz b przy wywoaniach kolejno Myfunc oraz Myfunc1 (dobrze jest w tym celu dodatkowo wczy w AvrStudio okienko
podgldu pamici danych jak na
rys. 14). Przekonamy si, e wartoci chwilowe a i b zmieniaj si
w zalenoci od tego, ktra funkcja
aktualnie z nich korzysta.
Moe nas w pierwszej chwili
zdziwi fakt, e w momencie wejcia do funkcji Myfunc1 a oraz b
zachoway wartoci przypisane wewntrz poprzedniej funcji (Myfunc)
przecie miay straci wano.
Przyczyn jest prostota naszego
przykadu. Kompilator nie niszczy
zmiennych lokalnych (np. przez
wyzerowanie) ale po prostu przestaje si nimi przejmowa. Gdyby pomidzy wywoaniami Myfunc
i Myfunc1 pojawiy si jakie operacje wykorzystujce stos a i b
zostayby nadpisane. Poniewa jednak nic takiego nie zachodzi wartoci wstawione pod adresy 0x45a
i 0x45b pozostay nie zmienione.
Moliwo uycia takich samych
nazw zmiennych lub funkcji jest
te czasem korzystna w odniesieniu do poszczeglnych moduw
kodu rdowego. W C uzyskujemy to poprzez ograniczenie zakresu wanoci zmiennej (funkcji) do
pojedynczego moduu sprawia to
sowo kluczowe static .
Zadeklarujmy sobie takie lokalne
symbole: w module main.c dopiszemy na przykad:
// deklaracja zmiennej lokalnej dla

Elektronika Praktyczna 7/2005

KURS

Rys. 15. Tablica symboli pokazuje


tylko symbole globalne
moduu main
static char k=1;
// funkcje:
static char LocFunc(char Value);
// deklaracja funkcji lokalnej dla moduu main
// oraz denicja tej funkcji
char LocFunc(char Value)
{
return Value + 2;
}

a w module funkcje.c:

// deklaracja zmiennej lokalnej dla moduu funkcje


static char k=2;
static char LocFunc(char Value);
// deklaracja funkcji lokalnej dla moduu funkcje
// oraz denicja tej funkcji
char LocFunc(char Value)
{
return Value + 10 +k;
}

Przy kompilacji stwierdzamy, e


w tym przypadku nie wystpuje
bd wielokrotnej denicji. Wie si
z tym rwnie ukrycie powyszych
lokalnych nazw w oknie podgldu
symboli konsolidatora (rys. 15), wyszczeglnione s tylko symbole globalne (okno podgldu symboli wywoujemy klawiszem F8).
Oczywicie pomimo tego ukrycia zmienne k s zycznie ulokowane w pamici SRAM (pod adresami 0x60 oraz 0x63 na rys. 16),
znajdziemy je te przegldajc plik
symboli Test03.smb. Uycie poszczeglnych adresw zaley od moduu,
z ktrego si do naszej zmiennej k
odwoujemy (kod moduu main.c
korzysta z adresu 0x63, natomiast
modu funkcje.c uywa 0x60). Jeli
zechcemy to przeledzi w AvrStudio zauwaymy, e po wstawieniu do okienka podgldu zmiennej
k bdzie ona opisana wartoci i
adresem zalenym od moduu, do
ktrego wchodzimy prac krokow.
Podobnie jest z funkcjami kady modu odwouje si do swojej
wasnej lokalnej denicji LocFunc. Jzyk C daje nam jeszcze jedn moliwo czc waciwoci powyszych
przypadkw. Jeli mianowicie uyje-

Rys. 16. Przydzia pamici dla zmiennych lokalnych

Elektronika Praktyczna 7/2005

Rys. 17. Zmienne lokalne funkcji w wersji inicjalizowanej

my kwalikatora static do zmiennej


lokalnej deklarowanej wewntrz ciaa funkcji (automatycznej) uzyskamy
nastpujacy efekt: zakres uywania
zmiennej pozostanie nadal ograniczony do ciaa funkcji ale zarazem
zmiennej zostaje przydzielona na
stae przestrze w obszarze danych
SRAM. Po wyjciu z funkcji zmienna
taka nie jest zatem - jak poprzednio
- naraona na zniszczenie (nadpisanie) ale przechowuje ostatnio przypisan warto a do ponownego
wywoania uywajcej j funkcji. Wyprbujmy to zaraz przepisujc nieco
nasze poprzednie denicje:
int Myfunc(char x,char y)
{
static char a,b;
a=2*x + y;
b=x + 2*y;
return(a+b);
}

int Myfunc1(char x,char y)


{
static char a,b;
a=x + y;
b=x - y;
return(a*b);
}

Prowadzc krokowy debugging


jak na rys. 14 zobaczymy teraz jak
zmienia si lokalizacja zmiennych a
i b: maj one przydzielony obszar w
sekcji bss. Opis a oraz b w okienku podgldu zmienia si w trakcie
wchodzenia i opuszczania kolejnych
funkcji. Zauwamy, e biorc pod
uwag przydzia pamici zmienne te
nie rni si obecnie od zwykych
lokalnych czy nawet globalnych. Natomiast znacznie poprawia si czytelno kodu oraz jest redukowana moliwo bdw wynikajcych z powtrzenia nazw.
Zobaczmy jeszcze jak zachowaj
si zmienne automatyczne inicjalizowane. Jako przykad niech posuy
acuch (string) z cyframi (kwalika-

tor const informuje kompilator, e jest


to szablon tylko do odczytu):
int Myfunc(char x,char y)
{
const char Cyfry[] =0123456789;
static char a,b;

a=2*x + y;
b=x + 2*y;
return(a+b+ Cyfry[1]);

Wydawaoby si, e w trakcie


tworzenia ramki stosu dla funkcji
podczas jej wywoania powinna by
powtrzona procedura taka sama jak
dla zmiennych inicjalizowanych data
(przepisanie wartoci z koca obszaru kodu bezporednio na stos). Niestety w tym przypadku avr-gcc nie
postpuje optymalnie. Sprawdmy to
w AvrStudio rys. 17.
Okazuje si, e string Cyfry[] jest
ju w trakcie oglnej inicjalizacji
rwnie przepisywany na stae do
obszaru data SRAM (podobnie jak
wszystkie zwyke zmienne inicjalizowane) gdzie spokojnie czeka na
wywoanie funkcji. Wtedy dopiero
spod adresu w sekcji data jest przepisywany do ramki stosu.
Zamiast spodziewanych korzyci mamy wic w efekcie wyduenie kodu wykonywalnego i adnej
oszczdnoci RAM w porwnaniu z
przypadkiem uycia tego stringa jako
zwykej zmiennej globalnej (ewentualnie lokalnej ale dla caego moduu).
Wida wic, e takiej konstrukcji naley raczej unika (chyba, e czytelno kodu postawimy na absolutnie
priorytetowym miejscu).
Jerzy Szczesiul, EP
jerzy.szczesiul@ep.com.pl
UWAGA!
rodowisko IDE dla AVR-GCC opracowane
przez autora artykuu mona pobra ze
strony http://avrside.ep.com.pl.

91

You might also like