AVR-GCC kompilator C dla mikrokontroler├│w AVR, cz─Ö┼Ť─ç 10.pdf

(624 KB) Pobierz
avr_gcc_cz10.indd
K U R S
AVR–GCC: kompilator C dla
mikrokontrolerów AVR, część 10
Zakres zmiennych, pliki nagłówkowe
Zgłębiając tajniki AVR–GCC przechodzimy teraz do omówienia
sposobów deklarowania zmiennych oraz ich dopuszczalnych
zakresach. Jak pokazuje praktyka, zrozumienie tych
zagadnień ma duży wpływ na komfort pracy programisty
i – w konsekwencji – na jakość przygotowanego oprogramowania.
W praktyce zamiast rezygnować
z zalet optymalizacji lepiej jest kon-
trolować istotne dla nas zmienne
przy pomocy używanego już słowa
kluczowego volatile . Informuje ono
kompilator, żeby tak opisanej zmien-
nej nie poddawać jakimkolwiek dzia-
łaniom optymalizującym i upraszcza-
jącym i wykonywać na niej wszyst-
kie operacje przewidziane w kodzie
(chociaż z punktu widzenia optymali-
zatora mogą one wyglądać na zbęd-
ne). Główne zastosowanie tego me-
chanizmu to zabezpieczanie zmien-
nych używanych w przerwaniach (to
wynika bezpośrednio z nazwy: volati-
le – czyli ulotny, nietrwały – ozna-
cza, że wartość zmiennej może być
w każdej chwili uaktualniona przez
czynnik zewnętrzny – przerwanie –
i nie można w związku z tym pomi-
nąć żadnej związanej z nią operacji
w głównej pętli programu), jednak
często jest pomocny także w róż-
nych innych sytuacjach. Sprawdźmy
zaraz, że zmiana deklaracji na vola-
tile char a,b; (przy ponownym włą-
czeniu maksymalnej optymalizacji)
daje ten sam efekt: zmienne wędru-
ją z obszaru rejestrów na stos. Jest
to pokazane na rys. 23 .
Zobaczmy jeszcze, że takie same
nazwy zmiennych mogą być z po-
wodzeniem użyte w innej funkcji
– w tym celu definiujemy sobie do-
datkowo:
int Myfunc1(char x,char y)
{
volatile char a,b;
// 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-
dułu funkcje
static char k=2;
static char LocFunc(char Value);
// deklaracja funkcji lokalnej dla mo-
dułu funkcje
// oraz definicja tej funkcji
char LocFunc(char Value)
{
return Value + 10 +k;
}
a=x + y;
b=x – y;
return (a*b);
}
i oglądamy jak traktowane są
zmienne a oraz b przy wywoła-
niach kolejno Myfunc oraz Myfun-
c1 (dobrze jest w tym celu dodat-
kowo włączyć w AvrStudio okienko
podglądu pamięci danych jak na
rys. 23). Przekonamy się, że war-
tości chwilowe a i b zmieniają się
w zależności od tego, która funkcja
aktualnie z nich korzysta.
Może w pierwszej chwili zdzi-
wić fakt, że w momencie wejścia
do funkcji Myfunc1 a oraz b zacho-
wały wartości przypisane wewnątrz
poprzedniej funcji ( Myfunc ) – prze-
cież miały stracić ważność. Przyczy-
ną jest prostota naszego przykładu.
Kompilator nie niszczy zmiennych
lokalnych (np. przez wyzerowanie),
ale po prostu przestaje się nimi
“przejmować”. Gdyby pomiędzy wy-
wołaniami Myfunc i Myfunc1 pojawi-
ły się jakieś operacje wykorzystują-
ce stos – a i b zostałyby nadpisane.
Ponieważ jednak nic takiego nie
zachodzi wartości wstawione pod
adresy 0x45a i 0x45b pozostały nie
zmienione.
Możliwość użycia takich samych
nazw zmiennych lub funkcji jest
też czasem korzystna w odniesieniu
do poszczególnych modułów kodu
źródłowego. W C uzyskujemy to po-
przez ograniczenie zakresu ważności
zmiennej (funkcji) do pojedynczego
modułu – sprawia to słowo kluczo-
we static . Zadeklarujmy sobie takie
lokalne symbole: w module main.c
dopiszemy na przykład:
// deklaracja zmiennej lokalnej dla
modułu main
static char k=1;
// funkcje:
static char LocFunc(char Value);
Przy kompilacji stwierdzamy, że
w tym przypadku nie występuje błąd
wielokrotnej definicji. Wiąże się ztym
również ukrycie powyższych lokal-
nych nazw w oknie podglądu symboli
konsolidatora ( rys. 24 ), wyszczególnio-
ne są tylko symbole globalne (okno
podglądu symboli wywołujemy klawi-
szem F8 ).
Oczywiście, pomimo tego ukry-
cia zmienne k są fizycznie uloko-
wane w pamięci SRAM (pod adre-
sami 0x60 oraz 0x63 na rys. 25 ),
znajdziemy je też przeglądając plik
symboli Test03.smb . Użycie poszcze-
gólnych adresów zależy od modu-
łu, z którego się do naszej zmiennej
k odwołujemy (kod modułu main.
c korzysta z adresu 0x63, natomiast
moduł funkcje.c używa 0x60). Jeśli
zechcemy to prześledzić w AvrStu-
dio zauważymy, że po wstawieniu
do okienka podglądu zmiennej k
Rys. 23. Podgląd zmiennych lokal-
nych na stosie
Rys. 24. Tablica symboli pokazuje
tylko symbole globalne
108
Elektronika Praktyczna 12/2005
27883014.047.png 27883014.048.png 27883014.049.png
K U R S
strukcji należy raczej unikać (chyba,
że czytelność kodu postawimy na
absolutnie priorytetowym miejscu).
Rys. 25. Przydział pamięci dla zmiennych lokalnych
Wykorzystanie plików
nagłówkowych
Do używanych w projekcie plików
nagłówkowych *.h odwołujemy się
w dwojaki sposób: #include <file.h>
albo #include „file.h” . Różnica leży
tylko w sposobie wyszukiwania przez
kompilator na dysku pliku o podanej
nazwie. Przy odwołaniu <> w pierw-
szej kolejności sprawdzane są własne,
systemowe zasoby plików avr–gcc
(a więc foldery avr\include i lib\gcc\avr\
wersja\include ), do których nie musi-
my podawać ścieżki. Dołączamy więc
takim zapisem wszystkie potrzebne
w projekcie pliki avr–libc . Konieczne
jest jednak zaznaczenie wejścia do
ewentualnych subfolderów a więc np.
#include <avr/io.h> . Użycie unikso-
wego slasha / zamiast windowsowego
backslasha \ nie jest błędem: kompi-
lator interpretuje go poprawnie nato-
miast znacznie ułatwione jest przenie-
sienie projektu do środowiska unikso-
wego (jak Linuks).
Forma „” uruchamia wyszukiwanie
pliku od bieżącego folderu projektu
oraz dodatkowych podanych kom-
pilatorowi lokalizacji. W ten sposób
powołujemy się więc na własne po-
mocnicze pliki nagłówkowe projektu
(jak dane.h w naszym przykładzie).
Dodatkowe ścieżki przeszukiwania
wprowadzamy przy pomocy opcji
–Iścieżka. AvrSide wspiera jedną po-
mocniczą lokalizację, którą wpisuje-
my w oknie edycyjnym dialogu Kon-
figuracja projektu > Ścieżki . Zazwy-
czaj będzie to folder [ \AvrSide\Myinc ]
przewidziany na ogólne własne pliki
nagłówkowe z ulubionymi typami,
będzie ona opisana wartością i adre-
sem zależnym od modułu, do które-
go wchodzimy pracą krokową.
Podobnie jest z funkcjami – każdy
moduł odwołuje się do swojej wła-
snej lokalnej definicji LocFunc . Język
C daje nam jeszcze jedną możliwość
łączącą właściwości powyższych przy-
padków. Jeśli mianowicie użyjemy
kwalifikatora static do zmiennej lo-
kalnej deklarowanej wewnątrz cia-
ła funkcji (automatycznej) uzyskamy
następujacy efekt: zakres używania
zmiennej pozostanie nadal ograni-
czony do ciała funkcji ale zarazem
zmiennej zostaje przydzielona na stałe
przestrzeń w obszarze danych SRAM.
Po wyjściu z funkcji zmienna taka nie
jest zatem – jak poprzednio – nara-
żona na zniszczenie (nadpisanie) ale
przechowuje ostatnio przypisaną war-
tość – aż do ponownego wywołania
używającej ją funkcji. Wypróbujmy
to zaraz przepisując nieco nasze po-
przednie definicje:
int Myfunc(char x,char y)
{
static char a,b;
kator const informuje kompilator, że
jest to szablon tylko do odczytu):
int Myfunc(char x,char y)
{
const char Cyfry[] =”0123456789”;
static char a,b;
Wydawałoby się, że w trakcie
tworzenia ramki stosu dla funkcji
podczas jej wywołania powinna być
powtórzona procedura taka sama jak
dla zmiennych inicjalizowanych data
(przepisanie wartości z końca obsza-
ru kodu bezpośrednio na stos). Nie-
stety w tym przypadku avr–gcc nie
postępuje optymalnie. Sprawdźmy to
w AvrStudio – rys. 26 .
Okazuje się, że string Cyfry[]
jest już w trakcie ogólnej inicjalizacji
również przepisywany na stałe do
obszaru data SRAM (podobnie jak
wszystkie “zwykłe” zmienne inicja-
lizowane), gdzie spokojnie czeka na
wywołanie funkcji. Wtedy dopiero
spod adresu w sekcji data jest prze-
pisywany do ramki stosu.
Zamiast spodziewanych korzy-
ści mamy więc w efekcie wydłuże-
nie kodu wykonywalnego i żadnej
oszczędności RAM w porównaniu
z przypadkiem użycia tego strin-
ga jako zwykłej zmiennej globalnej
(ewentualnie lokalnej ale dla całego
modułu). Widać więc, że takiej kon-
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);
}
Prowadząc krokowy debugging
jak na rys. 23 zobaczymy teraz jak
zmieniła się lokalizacja zmiennych
a i b : mają one przydzielony obszar
w sekcji bss . Opis a oraz b w okien-
ku podglądu zmienia się w trakcie
wchodzenia i opuszczania kolejnych
funkcji. Zauważmy, że biorąc pod
uwagę przydział pamięci zmienne
te nie różnią się obecnie od zwy-
kłych lokalnych czy nawet global-
nych. Natomiast znacznie poprawia
się czytelność kodu oraz jest redu-
kowana możliwość błędów wynika-
jących z powtórzenia nazw.
Zobaczmy jeszcze jak zachowają
się zmienne automatyczne inicjalizo-
wane. Jako przykład niech posłuży
łańcuch ( string ) z cyframi (kwalifi-
Rys. 26. Zmienne lokalne funkcji w wersji inicjalizowanej
Elektronika Praktyczna 12/2005
109
a=2*x + y;
b=x + 2*y;
return (a+b+ Cyfry[1]);
}
27883014.050.png 27883014.001.png
K U R S
definicjami, pomocniczymi makrami
używanymi w wielu projektach. Za-
uważmy jednak, że narzucenie kon-
kretnej pełnej ścieżki dostępu może
sprawiać kłopoty przy przenoszeniu
projektu na inną maszynę z inaczej
zainstalowanym AvrSide – po prostu
takiej ścieżki może nie być co spo-
woduje błędy. Dla takich przenośnych
zastosowań przewidziałem dodatkowe
subfoldery foldera projektu [ \lib ] oraz
[ \inc ]. Posługują się one lokalizacją
względną nie utrudniającą przeno-
szenia. Utworzenie tych subfolderów
jest dokonywane samoczynnie przy
zapisaniu projektu z ustawioną opcją
Konfiguracja projektu>Ścieżki>Używaj
lokalnych podkatalogów lib/inc . Au-
tomatycznie jest także przy wywoła-
niu kompilatora dodawana ścieżka do
subfoldera [ \inc ].
Z lokalizacją własnych plików na-
główkowych związana jest jeszcze
jedna opcja AvrSide: Konfiguracja
projektu>AvrSide>Szukaj deklaracji
w folderach inc . Jest ona połączona
z mechanizmem podpowiedzi deklara-
cji symbolu ( F1 ). Przeszukanie plików
w poszukiwaniu deklaracji odbywa się
domyślnie wewnątrz foldera projektu.
Zaznaczenie opcji powoduje również
przeszukanie plików w dodatkowych
lokalizacjach – jest to bardziej uni-
wersalne ale może spowalniać pracę
AvrSide w przypadku nagromadzenia
dużej liczby plików.
Następne ułatwienie w pracy z pli-
kami nagłówkowymi dotyczy zależ-
ności. Mianem tym określamy w tym
przypadku ustalenie, w których plikach
źródłowych *.c oraz *.s jest używa-
ny dany plik nagłówkowy. Zmiana
w takim pliku nagłówkowym pociąga
za sobą oczywiście zmiany w tych
źródłach co wymaga ich ponowne-
go przekompilowania. Jednak bez sa-
moczynnego wsparcia musielibyśmy
albo zawsze pamiętać, których plików
mogą dotyczyć zmiany albo zawsze
po korekcie dowolnego nagłówka po-
nownie kompilować cały projekt ( Bu-
ild ), co przy rozbudowanych progra-
mach może znacznie wydłużać całą
operację. Obsługa zależności w AvrSi-
de jest dosyć uproszczona: polega na
przeszukaniu pierwszych kilkunastu
wierszy kodu każdego źródła .c i .s
i utworzeniu tabeli zależności na pod-
stawie znalezionych dyrektyw dołą-
czenia ( #include ) wszelkich otwartych
w projekcie (obecnych na zakładkach
edytora) plików nagłówkowych *.h .
Tabela ta jest później sprawdzana
przy zapisywaniu zmian w pliku na-
główkowym – na tej podstawie są
oznaczane jako zmienione (podkreśle-
nie nazwy pliku na zakładce edytora)
i włączane do polecenia Make odpo-
wiednie moduły źródłowe.
Dla przyśpieszenia działania śro-
dowiska aktualizacja tabeli zależno-
ści nie odbywa się samoczynnie;
po wprowadzeniu w projekcie korekt
takich jak dodanie lub usunięcie
pliku czy też dopisanie (usunię-
cie) dołączenia nagłówka w kodzie
źródłowym, należy użyć polecenia
menu Projekt>Aktualizuj zależności .
Nie musimy jednak tego każdorazo-
wo robić przy ładowaniu projektu
– w tym przypadku utworzenie tabe-
li jest automatyczne. Powyższe pole-
cenie jest także przydatne dla zre-
setowania tabeli w razie wystąpienia
błędu, którego niestety nie udało mi
się dotychczas zlokalizować, a który
powoduje ciągłe oznaczenie plików
źródłowych jako zmienione pomimo
ich przekompilowania.
Należy też pamiętać, że powyż-
sza obsługa jest jednopoziomowa,
nie wspiera zagnieżdżonych dołą-
czeń (w nagłówku 1.h dołączamy 2.h
a z kolei 1.h jest dołączony do 3.c
– nasza uproszczona obsługa wykryje
zmianę w 1.h ale w 2.h już nie, ko-
nieczna jest komenda Build ). Jak wi-
dać nie może się więc ona równać
z rozbudowanymi narzędziami kontroli
zależności używanymi w plikach ma-
kefile . Jednak dość dobrze zdaje eg-
zamin w przeciętnej wielkości amator-
skich projektach, dla których przede
wszystkim AvrSide powstało.
Często zbiór funkcji obsługujących
konkretne zadanie (np. obsługę urzą-
dzenia peryferyjnego) lokujemy w od-
dzielnym module źródłowym xx.c
a dostęp do niego realizujemy poprzez
skojarzony plik nagłówkowy o takiej
samej nazwie xx.h . AvrSide oferuje
dodatkowe skróty klawiszowe ( SHIFT
+ F6 albo ALT + UP ) do szybkiego
przełączania pomiędzy tak nazwaną
parą plików.
Jerzy Szczesiul, EP
jerzy.szczesiul@ep.com.pl
UWAGA!
Środowisko IDE dla AVR–GCC opracowane
przez autora artykułu można pobrać ze
strony http://avrside.ep.com.pl.
110
Elektronika Praktyczna 12/2005
27883014.002.png 27883014.003.png 27883014.004.png 27883014.005.png 27883014.006.png 27883014.007.png 27883014.008.png 27883014.009.png 27883014.010.png 27883014.011.png 27883014.012.png 27883014.013.png 27883014.014.png 27883014.015.png 27883014.016.png 27883014.017.png 27883014.018.png 27883014.019.png 27883014.020.png 27883014.021.png 27883014.022.png 27883014.023.png 27883014.024.png 27883014.025.png 27883014.026.png 27883014.027.png 27883014.028.png 27883014.029.png 27883014.030.png 27883014.031.png 27883014.032.png 27883014.033.png 27883014.034.png 27883014.035.png 27883014.036.png 27883014.037.png 27883014.038.png 27883014.039.png 27883014.040.png 27883014.041.png 27883014.042.png 27883014.043.png 27883014.044.png 27883014.045.png 27883014.046.png
Zgłoś jeśli naruszono regulamin