K U R S
AVR GCC: kompilator C
mikrokontrolerów AVR, część 5
W tej części kursu skupiamy się na omówieniu zakresu zmiennych, budowie
i funkcjach plików nagłówkowych, przybliżając w ten sposób kolejne tajniki
kapryśnego jak głosi nośna opinia kompilatora.
// plik nagłówkowy globalnych danych
projektu
Zakres zmiennych
#ifndef _PROJ_DAT_H_
W zależności od miejsca oraz #define _PROJ_DAT_H_
// #include:
sposobu zadeklarowania zmiennych test już gdzieś w projekcie istnieje
// #define:
mogą mieć one w naszym projekcie (extern) i można z niej bezpiecznie
// definicje typów typedef
różny zasięg tzn. możemy z nich korzystać.
// dane globalne
korzystać w jednym pliku zródło- Nowsze wersje avr gcc pozwa-
#ifdef _MAIN_MOD_
// definicje danych tylko w module
wym (module), w wielu plikach albo lają na pominięcie tego sposobu
main()
tylko wewnątrz kodu funkcji. Mówi- // char x; w przypadku zmiennych automa-
my w takim przypadku o zmiennych tycznie zerowanych (sekcja bss)
int test = 10;
globalnych oraz lokalnych. Podział taka zmienna (np. int test;) jest
#else
// deklaracje danych jako importowanych
ten nie ma wpływu na typ zmien- samoczynnie bez dodatkowych za-
w każdym innym module
// extern char x;
nej ale jest istotny w trakcie pisa- biegów traktowana jako pojedyncza
extern int test;
nia programu, inny jest też sposób pomimo wielokrotnego zdefiniowa-
obsługiwania zmiennych lokalnych #endif nia i zostaje jej przydzielony jeden
przez kompilator. wspólny obszar w SRAM.
// deklaracje funkcji
// extern char Myfunc(int,char);
Do tej pory ograniczaliśmy się W przypadku funkcji można bez
extern int Myfunc(char x,char y);
do zmiennych globalnych (zasięg błędu użyć we wszystkich modu-
#endif
globalny jest domyślny) deklarowa- łach deklaracji extern w ten spo-
nych i używanych w pojedynczym Wstawiamy tutaj wspólne dla sób funkcja (którą dokładnie zdefi-
pliku (module) zródłowym projek- wszystkich modułów projektu pliki niujemy tylko w jednym dowolnie
tu. Utwórzmy teraz następny przy- nagłówkowe (np. #include
kładowy projekt zawierający kilka io.h>), definicje konfiguracji i pod- na i możliwa do użycia w całym
modułów: main.c, funkcje.c oraz łączeń sprzętowych (np. #define projekcie. Zróbmy to zaraz definiu-
dane.h zapiszmy go w subfolde- LED PB2), własne definicje typów jąc w pliku funkcje.c funkcję zade-
rze \Projects\Kurs\Przyklad 03\ jako (np. typedef unsigned char uchar). klarowaną w dane.h jako extern int
Test03. Dodawanie plików do pro- Po dołączeniu naszego nagłówka do Myfunc (char x, char y); (funkcja
jektu jest w AvrSide bardzo proste: dowolnego modułu (#include dane. o dwóch argumentach typu char,
wykonujemy komendę menu Projek- h ) mamy od razu w module dostęp zwracająca rezultat typu int) (nie
t>Dodaj pustą stronę (dostępna tak- do wszystkich tych ustawień. zapomnijmy oczywiście o dołącze-
że w menu kontekstowym projektu Trochę więcej komplikacji jest niu do obu zródeł nagłowka z da-
wywoływanym skrótem CTRL+.) z globalnymi zmiennymi. Zwy- nymi: #include dane.h ):
int Myfunc(char x,char y)
i zapisujemy nową zakładkę NoNa- kłe ich zadeklarowanie spowodu-
{
char a,b;
me jako odpowiedni typ pliku (c, je wprawdzie, że będą widoczne
s, h) z wybraną nazwą (typ pli- w projekcie i nie zostanie zgłoszony a=2*x + y;
b=x + 2*y;
ku zródłowego wybieramy z listy błąd na etapie kompilacji poszcze- return (a+b);
}
rozszerzenie będzie dodane auto- gólnych modułów ale nie da sobie
matycznie więc nie musimy go do- z tym rady konsolidator sygnalizu- Teraz w pliku głównym main.c
pisywać). Jednak najpierw musimy jąc błąd wielokrotnej definicji. Mo- możemy już bez problemu posłużyć
wpisać do modułu jakiś kod (może żemy to od razu sprawdzić dopi- się tą funkcją:
test = Myfunc(10,5);
to byc na wstępie sam komentarz) sując int test=10; w obu naszych
gdyż AvrSide blokuje zapis pliku plikach zródłowych c (main i funk- W funkcji celowo wprowadzi-
pustego. W pliku main.c wstawimy cje): kompilacja (CTRL+F9) prze- łem zmienne lokalne a, b (chociaż
jak zwykle szablon modułu główne- biegnie sprawnie ale projektu nie nie są dla wykonania obliczeń ko-
go natomiast w pliku dane.h sza- da się zakończyć (F9 błąd linke- nieczne) aby przedstawić sposób
blon nagłówek danych projektu ra multiple definition of test ). ich obsługi przez kompilator. Takie
(headdat). Z pomocą przychodzi kompila- zmienne definiowane wewnątrz
Szablon danych został przygo- cja warunkowa: w pliku głównym ciała funkcji (zwane też zmien-
towany tak aby bez wielokrotne- ze zdefiniowanym makrem _MAIN_ nymi automatycznymi) są dostęp-
go przepisywania deklaracji moż- MOD_ preprocesor wstawi peł- ne i możliwe do wykorzystywania
na było używać w całym projekcie ną definicję int test=10;natomiast tylko i wyłącznie w obrębie tego
wspólnych globalnych zmiennych, w pozostałych plikach tylko infor- ciała funcji. Próba odwołania do
funkcji oraz definicji: mację dla kompilatora, że zmienna nich spoza funkcji powoduje błąd.
Elektronika Praktyczna 7/2005
89
K U R S
odrębniania zmien- alniona przez czynnik zewnętrz-
nych lokalnych. Jest ny przerwanie i nie można
to bardzo pozytyw- w związku z tym pominąć żadnej
ny rezultat jednak związanej z nią operacji w głównej
dla potrzeb naszego pętli programu), jednak często jest
testu wyłączmy na pomocny także w różnych innych
chwilę optymalizację sytuacjach. Sprawdzmy zaraz, że
(odpowiada to opcji zmiana deklaracji na volatile char
O0 kompilatora). a,b; (przy ponownym włączeniu
Teraz widzimy (pa- maksymalnej optymalizacji) daje
miętajmy o użyciu ten sam efekt: zmienne wędrują
komendy Build a nie z obszaru rejestrów na stos. Jest to
Make po zmianie pokazane na rys. 14.
Rys. 14. Podgląd zmiennych lokalnych na stosie opcji), że zmienne Zobaczmy jeszcze, że takie
a oraz b są z chwilą same nazwy zmiennych mogą być
Zmienne te istnieją tylko w czasie wejścia programu do funcji tradycyj- z powodzeniem użyte w innej funk-
wykonywania funkcji po wywoła- nie tworzone tymczasowo na stosie cji w tym celu definiujemy sobie
niu funcji, w prologu, są tworzone (w moim przykładzie pod adresami dodatkowo:
int Myfunc1(char x,char y)
albo na stosie albo (jeśli optyma- 0x045A i 0x045B) i niszczone po za-
{
volatile char a,b;
lizator stwierdzi, że ma chwilowo kończeniu funkcji. Jednak od razu
do dyspozycji odpowiednią liczbę zauważymy też znaczący przyrost a=x + y;
b=x y;
rejestrów) w obszarze rejestrów ro- objętości kodu. Możemy przy okazji
return (a*b);
}
boczych. Po zakończeniu działania porównać generowane kody assem-
funkcji po prostu przestają istnieć blera i obejrzeć ile pożytecznej pracy i oglądamy jak traktowane są
pamięć dla nich przydzielona wykonuje optymalizator. Nic dziw- zmienne a oraz b przy wywoła-
zostaje przeznaczona na inne bie- nego, że często symulacja w AvrStu- niach kolejno Myfunc oraz Myfun-
żące cele. dio nie zgadza się z naszym zapi- c1 (dobrze jest w tym celu dodat-
Zobaczmy, jak przedstawi nam to sem zródłowym: nie wykorzystywane kowo włączyć w AvrStudio okienko
w działaniu AvrStudio. Po omawia- zmienne mogą byc usunięte, niektóre podglądu pamięci danych jak na
nym już wstępnym skonfigurowaniu linie kodu są eliminowane itd. In- rys. 14). Przekonamy się, że war-
sesji AvrStudio wstawmy do okienka gerencja optymalizatora może być tości chwilowe a i b zmieniają się
podglądu zmiennych wszystkie użyte na tyle duża, że ten sam program w zależności od tego, która funkcja
zmienne: test, a, b. ze zmienionym poziomem optyma- aktualnie z nich korzysta.
Test po zerowaniu przyjmuje war- lizacji czasem zaczyna zachowywać Może nas w pierwszej chwili
tość 10, natomiast a i b są określone się nieco inaczej. Dlatego chwilowe zdziwić fakt, że w momencie wej-
jako not in scope (poza zakrese- przełączanie poziomów optymalizacji ścia do funkcji Myfunc1 a oraz b
m),czyli wszystko zgodnie z oczeki- tylko po to aby lepiej obejrzeć wy- zachowały wartości przypisane we-
waniami. Przejdzmy teraz krokami nik w symulatorze (tak jak to przed wnątrz poprzedniej funcji (Myfunc)
(F11) do wnętrza funkcji, spotka nas chwilą zrobiliśmy w celach edukacyj- przecież miały stracić ważność.
niestety niespodzianka: zmienne a i b nych) jest generalnie kiepskim po- Przyczyną jest prostota naszego
nadal nie są obsługiwane ( location mysłem (nie ma niestety możliwości przykładu. Kompilator nie niszczy
not valid AvrStudio ma kłopot selektywnego ustawiania różnych po- zmiennych lokalnych (np. przez
z ich umiejscowieniem w pamięci). ziomów optymalizacji dla poszczegól- wyzerowanie) ale po prostu prze-
Przyczyną jest wspomniane powyżej nych fragmentów kodu). staje się nimi przejmować . Gdy-
skuteczne działanie optymalizatora. W praktyce zamiast rezygno- by pomiędzy wywołaniami Myfunc
W kodzie asemblera znajdujemy: wać z zalet optymalizacji lepiej i Myfunc1 pojawiły się jakieś ope-
int Myfunc(char x,char y)
jest kontrolować istotne dla nas racje wykorzystujące stos a i b
{
5c: 28 2f movr18, r24
zmienne przy pomocy używanego zostałyby nadpisane. Ponieważ jed-
5e: 86 2f movr24, r22
char a,b;
już słowa kluczowego volatile. In- nak nic takiego nie zachodzi war-
a=2*x + y;
formuje ono kompilator, żeby tak tości wstawione pod adresy 0x45a
60: 92 2f movr25, r18
62: 99 0f addr25, r25
opisanej zmiennej nie poddawać i 0x45b pozostały nie zmienione.
64: 96 0f addr25, r22
b=x + 2*y; jakimkolwiek działaniom optymali- Możliwość użycia takich samych
66: 88 0f addr24, r24
zującym i upraszczającym i wyko- nazw zmiennych lub funkcji jest
68: 82 0f addr24, r18
return (a+b);
nywać na niej wszystkie operacje też czasem korzystna w odniesie-
6a: 29 2f movr18, r25
6c: 33 27 eorr19, r19
przewidziane w kodzie (chociaż niu do poszczególnych modułów
6e: 27 fd sbrc r18, 7
70: 30 95 comr19
z punktu widzenia optymalizatora kodu zródłowego. W C uzyskuje-
72: 99 27 eorr25, r25
74: 87 fd sbrc r24, 7
mogą one wyglądać na zbędne). my to poprzez ograniczenie zakre-
76: 90 95 comr25
78: 82 0f addr24, r18 Główne zastosowanie tego mecha- su ważności zmiennej (funkcji) do
7a: 93 1f adcr25, r19
nizmu to zabezpieczanie zmien- pojedynczego modułu sprawia to
7c: 08 95 ret
}
nych używanych w przerwaniach słowo kluczowe static .
Optymalizator wykonał wszystkie (to wynika bezpośrednio z nazwy: Zadeklarujmy sobie takie lokalne
potrzebne działania w obszarze reje- volatile czyli ulotny, nietrwały symbole: w module main.c dopisze-
strów w sposób na tyle zwięzły, że oznacza, że wartość zmiennej my na przykład:
// deklaracja zmiennej lokalnej dla
nie zaszła potrzeba wyraznego wy- może być w każdej chwili uaktu-
Elektronika Praktyczna 7/2005
90
K U R S
Rys. 15. Tablica symboli pokazuje
tylko symbole globalne
modułu main
static char k=1;
// funkcje:
static char LocFunc(char Value);
// deklaracja funkcji lokalnej dla mo-
dułu main
// oraz definicja tej funkcji
char LocFunc(char Value)
{
return Value + 2;
}
a w module funkcje.c:
// deklaracja zmiennej lokalnej dla mo-
Rys. 17. Zmienne lokalne funkcji w wersji inicjalizowanej
dułu funkcje
static char k=2;
static char LocFunc(char Value);
// deklaracja funkcji lokalnej dla mo- my kwalifikatora static do zmiennej tor const informuje kompilator, że jest
dułu funkcje
lokalnej deklarowanej wewnątrz cia- to szablon tylko do odczytu):
// oraz definicja tej funkcji
char LocFunc(char Value)
int Myfunc(char x,char y)
ła funkcji (automatycznej) uzyskamy
{
{
return Value + 10 +k;
const char Cyfry[] = 0123456789 ;
następujacy efekt: zakres używania
}
static char a,b;
Przy kompilacji stwierdzamy, że zmiennej pozostanie nadal ograni-
a=2*x + y;
w tym przypadku nie występuje czony do ciała funkcji ale zarazem b=x + 2*y;
return(a+b+ Cyfry[1]);
błąd wielokrotnej definicji. Wiąże się zmiennej zostaje przydzielona na
}
z tym również ukrycie powyższych stałe przestrzeń w obszarze danych Wydawałoby się, że w trakcie
lokalnych nazw w oknie podglądu SRAM. Po wyjściu z funkcji zmienna tworzenia ramki stosu dla funkcji
symboli konsolidatora (rys. 15), wy- taka nie jest zatem - jak poprzednio podczas jej wywołania powinna być
szczególnione są tylko symbole glo- - narażona na zniszczenie (nadpisa- powtórzona procedura taka sama jak
balne (okno podglądu symboli wy- nie) ale przechowuje ostatnio przy- dla zmiennych inicjalizowanych data
wołujemy klawiszem F8). pisaną wartość aż do ponownego (przepisanie wartości z końca obsza-
Oczywiście pomimo tego ukry- wywołania używającej ją funkcji. Wy- ru kodu bezpośrednio na stos). Nie-
cia zmienne k są fizycznie uloko- próbujmy to zaraz przepisując nieco stety w tym przypadku avr-gcc nie
wane w pamięci SRAM (pod adre- nasze poprzednie definicje: postępuje optymalnie. Sprawdzmy to
int Myfunc(char x,char y)
sami 0x60 oraz 0x63 na rys. 16), w AvrStudio rys. 17.
{
static char a,b;
znajdziemy je też przeglądając plik Okazuje się, że string Cyfry[] jest
a=2*x + y;
symboli Test03.smb. Użycie poszcze- b=x + 2*y; już w trakcie ogólnej inicjalizacji
return(a+b);
gólnych adresów zależy od modułu, również przepisywany na stałe do
}
z którego się do naszej zmiennej k obszaru data SRAM (podobnie jak
int Myfunc1(char x,char y)
{
odwołujemy (kod modułu main.c wszystkie zwykłe zmienne inicja-
static char a,b;
a=x + y;
korzysta z adresu 0x63, natomiast lizowane) gdzie spokojnie czeka na
b=x - y;
return(a*b);
moduł funkcje.c używa 0x60). Jeśli wywołanie funkcji. Wtedy dopiero
}
zechcemy to prześledzić w Avr- spod adresu w sekcji data jest prze-
Studio zauważymy, że po wstawie- Prowadząc krokowy debugging pisywany do ramki stosu.
niu do okienka podglądu zmiennej jak na rys. 14 zobaczymy teraz jak Zamiast spodziewanych korzy-
k będzie ona opisana wartością i zmieniła się lokalizacja zmiennych a ści mamy więc w efekcie wydłuże-
adresem zależnym od modułu, do i b: mają one przydzielony obszar w nie kodu wykonywalnego i żadnej
którego wchodzimy pracą krokową. sekcji bss. Opis a oraz b w okien- oszczędności RAM w porównaniu z
Podobnie jest z funkcjami każ- ku podglądu zmienia się w trakcie przypadkiem użycia tego stringa jako
dy moduł odwołuje się do swojej wchodzenia i opuszczania kolejnych zwykłej zmiennej globalnej (ewentual-
własnej lokalnej definicji LocFunc. Ję- funkcji. Zauważmy, że biorąc pod nie lokalnej ale dla całego modułu).
zyk C daje nam jeszcze jedną możli- uwagę przydział pamięci zmienne te Widać więc, że takiej konstrukcji na-
wość łączącą właściwości powyższych nie różnią się obecnie od zwykłych leży raczej unikać (chyba, że czytel-
przypadków. Jeśli mianowicie użyje- lokalnych czy nawet globalnych. Na- ność kodu postawimy na absolutnie
tomiast znacznie poprawia się czytel- priorytetowym miejscu).
ność kodu oraz jest redukowana moż- Jerzy Szczesiul, EP
liwość błędów wynikających z powtó- jerzy.szczesiul@ep.com.pl
rzenia nazw.
Zobaczmy jeszcze jak zachowają UWAGA!
Środowisko IDE dla AVR-GCC opracowane
się zmienne automatyczne inicjalizo-
przez autora artykułu można pobrać ze
Rys. 16. Przydział pamięci dla zmien- wane. Jako przykład niech posłuży
strony http://avrside.ep.com.pl.
nych lokalnych łańcuch (string) z cyframi (kwalifika-
Elektronika Praktyczna 7/2005
91
Wyszukiwarka