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

(305 KB) Pobierz
ep_03_107-108_avr_gcc_cz13.indd
K U R S
AVR–GCC: kompilator C dla
mikrokontrolerów AVR, część 13
Obsługa obszarów pamięci
mikrokontrolerów AVR, część 1
Pamięci te są umieszczone w od-
dzielnych obszarach adresowych
i wyposażone w oddzielne sprzętowe
mechanizmy dostępu (architektura
typu Harvard ) obsługiwane różny-
mi instrukcjami maszynowymi –
rys. 30 . W obszarze RAM dodatko-
wo wydzielone są sekcje rejestrów
roboczych i rejestrów wejść/wyjść
wyposażone w odrębny sposób ob-
sługi (m.in. występuje adresowanie
bitowe) – rys. 31 .
Wszystko to jest „chlebem po-
wszednim” dla konstruktorów dobrze
zaznajomionych z budową mikrokon-
trolerów i programowaniem w assem-
blerze. Jednak z punktu widzenia ję-
zyka C sprawy się komplikują. Język
ten pochodzi ze świata dużych ma-
szyn o całkowicie odmiennej organi-
zacji pamięci (jeden obszar o wspól-
nym sposobie adresowania i dostępu
– czyli architektura von Neumanna )
i nie posiada żadnych standardo-
wych mechanizmów wspierających
powyższe zróżnicowanie.
Kompilatory C tworzone dla kon-
kretnych rodzin mikrokontrolerów są
od razu wyposażane w specyficzne
rozszerzenia obsługi różnych pamię-
ci. Na przykład w SDCC dla rodzi-
ny '51 klasyfikator data identyfikuje
podstawową pamięć danych, idata
oznacza obszar rozszerzonej pamię-
Jedną z wielkich zalet mikrokontrolerów AVR (podobnie
zresztą jak układów wielu innych rodzin) jest integracja
w jednym układzie wszystkich potrzebnych rodzajów
pamięci (SRAM, EEPROM i Flash), co pozwala na znaczne
uproszczenie budowanych urządzeń. Ich obsługa nie zawsze
jest jednoznaczna, co sprawia duże trudności programistom,
zwłaszcza tym, którzy są przyzwyczajeni do korzystania z pełni
możliwości języka C.
ci wewnętrznej adreso-
wanej pośrednio, xdata
– zewnętrzną pamięć
RAM, zaś code ozna-
cza umieszczenie stałej
(wyłącznie do odczytu)
ulokowanej w pamię-
ci programu. Przypisa-
nie zmiennej lub stałej
określonego klasyfikato-
ra pozwala kompilatoro-
wi w momencie jej uży-
cia na generację kodu
odpowiedniego dla za-
deklarowanego obszaru
pamięci (np. zastoso-
wanie instrukcji MOVX
w przypadku zewnętrz-
nej pamięci '51). Na
ogół jest modyfikowana
też budowa wskaźników
tak, aby mieściły one dodatkową in-
formację o typie pamięci dla aktual-
nie wskazywanego obiektu.
Niestety AVR–GCC nie jest na-
rzędziem od podstaw dedykowanym
rodzinie AVR, ale jedynie jednym
z wielu portów uniwersalnego zesta-
wu kompilatorów GNU GCC. GCC
natomiast – tak samo jak nadmienio-
no powyżej o języku C – jest przy-
stosowany do ciągłego modelu pa-
mięci. Skłonienie go do współpracy
z tak nietypową dla niego platformą
sprzętową wymagało wielu wysiłków
i kompromisów. Nie udało się nieste-
ty do tej pory uzyskać pełnej wy-
gody użytkowania spotykanej w ko-
mercyjnych narzędziach – AVR–GCC
wymaga dodatkowo poznania kilku
specyficznych dla niego technik. Nie
jest to mocno uciążliwe, ale potrafi
zaskoczyć programistów przyzwycza-
jonych do PC lub innych kompilato-
rów C dla AVR.
Rys. 30. Rodzaje pamięci w AVR. Każ-
da z nich jest liniowym obszarem ad-
resowanym od zera. Stałe RAMEND ,
E2END i FLASHEND są zdefiniowane
w plikach nagłówkowych poszczegól-
nych typów mikrokontrolerów
Obsługa SFR oraz obszaru I/O
Ten temat był już wielokrotnie
omawiany w poprzednich odcinkach.
Obecnie AVR–GCC oferuje pełne
wsparcie dla takich operacji. Nie
są konieczne dawniejsze dodatkowe
makra typu inp czy outp – stosu-
jemy zwyczajne instrukcje przypisa-
nia. Przy tym optymalizator potrafi
samodzielnie określić możliwości
zaadresowania danego rejestru i wy-
brać najprostsze i najkrótsze instruk-
cje ( in , out , cbi , sbi ) – pokazywali-
śmy to na różnych przykładach. Je-
dynym mankamentem tego postępu
Elektronika Praktyczna 3/2006
107
Rys. 31. Struktura wewnętrznej pamięci RAM mikro-
kontrolerów AVR
54796117.001.png 54796117.002.png
K U R S
Rys. 32. Przemieszczanie sekcji da-
nych do zewnętrznej pamięci
– Wl,––section–start=.data=adres_
startowy
– Wl,––section–start=.bss=adres_
startowy
– Wl,––section–start=.noinit=adres_
startowy
albo (dla sekcji. data i. bss ) wer-
sje skrócone:
– Wl,–Tdata=adres_startowy
– Wl,–Tbss=adres_startowy
W AvrSide wprowadzamy je
w polu edycyjnym dodatkowych
opcji w zakładce Linker okna kon-
figuracji projektu – ale bez pre-
fiksu –Wl, – AvrSide dodaje go
automatycznie (czyli np. wpisuje-
my po prostu: –Tdata=adres ). Je-
śli używamy makefile dodajemy
potrzebną opcję do LDFLAGS. Na-
leży tylko pamiętać, że przesu-
nięcie sekcji przemieszcza samo-
czynnie wszystkie znajdujące się
za nią z zachowaniem domyślnej
kolejności ( rys. 32 ). Korzyść z ta-
kiego przemieszczenia jest podwój-
na: otrzymujemy do dyspozycji
dużą przestrzeń na dane a zarazem
nie musimy się kłopotać o wiel-
kość stosu i potencjalne nadpisanie
przez stos części danych. Moż-
liwa jest także oczywiście ope-
racja odwrotna: przeniesienie do
zewnętrznej pamięci stosu (słu-
ży do tego dyrektywa kompilatora
–minit–stack=nnnn albo zamien-
nie bezpośrednie zadeklarowanie
początku stosu dyrektywą linke-
ra –defsym, __stack=nnnn ). Ale
w praktyce nie ma to większego
sensu, gdyż dostęp do stosu po-
przez zewnętrzną magistralę będzie
znacznie wolniejszy, co pogorszy
efektywność całego programu.
Przy takim przemieszczeniu
sekcji danych musimy samodziel-
nie zadbać o odpowiednio wcze-
sne uruchomienie magistrali ext–
–ram – musi być ona aktywna
już w momencie inicjalizacji da-
nych . data oraz zerowania ob-
szaru . bss . Zwykłe wpisanie kon-
figuracji magistrali na początku
naszego programu nie wystarczy,
gdyż (jak widzieliśmy w podglą-
dzie assemblera) kod inicjalizacji
( __do_copy_data oraz __do_cle-
ar_bss ) wykonywany jest przed
wejściem do funkcji main . Musi-
my zmodyfikować jedną z sekcji
startowych (podział samoczynnie
tworzonych sekwencji rozpoczyna-
jących i kończących program na
sekcje startowe init0 ... init9 i zamy-
kające fini0 ... fini9 jest dokładnie
opisany w dokumentacji avr–libc)
– na ogół wykorzystujemy prze-
znaczoną dla użytkownika sekcję
init1 . Umieszczenie funkcji w okre-
ślonej sekcji realizuje atrybut sec-
tion(nazwa). Dodatkowo zastosu-
jemy znany juz atrybut naked ,
który pozbawi funkcję samoczyn-
nie tworzonego prologu i epilogu.
W efekcie uzyskujemy bezpośred-
nie wstawienie kodu w potrzebnym
miejscu, np. tak (przykład dla
Atmega 128):
void EnableExtRam(void) __attribu-
te__ ((naked)) __attribute__ ((section
(„.init1”)));
void EnableExtRam(void)
{
XMCRA = _BV(SRW00);
XMCRB = _BV(XMBK);
MCUCR = _BV(SRE);
}
jest częściowy brak kompatybilności
ze starszymi projektami – jednak
dla rozpoczynających naukę nie bę-
dzie to żadną przeszkodą.
Obsługa pamięci danych
Sposób zarządzania pamięcią da-
nych także był już dość dokładnie
przedstawiony (podział na sekcje,
lokalizacja stosu itp.). Kompilator
posługuje się tą pamięcią jako do-
myślnym obszarem – nie musimy
stosować żadnych dodatkowych kla-
syfikatorów (oprócz wynikających
z zastosowanej sekcji – jak NOINIT ,
czy wynikających ze struktury pro-
gramu – jak static czy volatile ).
Także zawartość wskaźników do-
myślnie wskazuje na adresy w pa-
mięci danych (z wyjątkiem wskaź-
ników na funkcje odnoszących się
do adresów w obszarze kodu). Nie-
które układy z rodziny ATmega są
wyposażone w sprzętowy interfejs
zewnętrznej pamięci danych. Do-
stęp do zewnętrznej pamięci jest
zorganizowany bardzo prosto: od-
wołanie się programu do adresu
RAM wykraczającego poza pojem-
ność wbudowaną w kostkę powo-
duje samoczynne wygenerowanie
odpowiedniej sekwencji sygnałów
na magistrali ext–ram . Nie są więc
potrzebne (w przeciwieństwie do
np. '51) żadne oddzielne instrukcje
maszynowe. Wynika stąd od razu,
że AVR–GCC może taką pamięć
obsługiwać na zwykłych zasadach,
bez żadnych dodatkowych adapta-
cji. Jeśli jednak przypomnimy oma-
wiany wcześniej schemat domyślne-
go podziału RAM na sekcje ( .data ,
. bss , .noinit , stos) od razu stwier-
dzimy, że obszar zewnętrzny jest
ulokowany poza stosem i nie będzie
mógł być bezkolizyjnie wykorzysta-
ny przez linker. Mamy do dyspo-
zycji kilka sposobów uniknięcia
tej przeszkody. Jeden z najczęściej
stosowanych to poinstruowanie lin-
kera o nowym, pasującym do przy-
gotowanego sprzętu rozmieszczeniu
sekcji. Służą do tego odpowiednie
opcje linii komend AVR–GCC:
Sprawdźmy, że rzeczywiście od-
powiedni kod pojawił się zaraz za
wektorami przerwań. Zwróćmy też
uwagę, że w tym przypadku wy-
starcza sama deklaracja i definicja
funkcji – nigdzie w programie jej
jawnie nie wywołujemy – byłoby
to wręcz błędem, gdyż spowoduje
skok z powrotem do sekcji . init1
praktycznie resetując program. Je-
śli zrobimy kilka eksperymentów
stwierdzimy także, że w sekcji . init1
można spowodować błąd jawnym
przypisaniem zera (np. XMCRA=0 ).
Kompilator użyje rejestru zerowe-
go r1 ( sts 0x006D, r1) , który tutaj
może mieć jeszcze wartość przy-
padkową gdyż jest inicjalizowany
dopiero w ustawiającej stos sekcji.
init2 . W razie konieczności przenie-
śmy więc nasz kod do następnej
startowej sekcji użytkownika . init3 .
Można też zamiennie dołączyć
do projektu mały moduł assemblero-
wy, który realizuje tylko to zadanie:
// uruchomienie ext–ram:
#include <avr/io.h>
.global MemInit
.section.init1,”ax”,@progbits
MemInit:
ldi r24,0x80
out _SFR_IO_ADDR(MCUCR),r24
ldi r24,_BV(SRW00)
sts XMCRA,r24
ldi r24,_BV(XMBK)
sts XMCRB,r24
ale tu już musimy sami pamię-
tać o rozmieszczeniu SFR w prze-
strzeniach IO oraz extended–sfr
i stosować odpowiednie instrukcje
( out albo sts ).
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.
108
Elektronika Praktyczna 3/2006
54796117.003.png 54796117.004.png
Zgłoś jeśli naruszono regulamin