You are on page 1of 8

Programowanie

Programowanie procesorów
w języku C część 16
Dziś czeka nas dużo pracy połączonej z dobrą 12 bitów na każdy piksel. Ma to dwie ogrom- Dodajemy teraz prostą funkcję inicjacji pa-
zabawą. Poznaliśmy już pamięć programu ne zalety: możemy dowolnie ustawić, pod lety na podstawie danych z pamięci progra-
procesora oraz prostszą część zagadnienia którym indeksem kryje się jaki kolor oraz da- mu. Pokazuje ją listing 203. Potrzebna nam
umieszczania zmiennych w pamięci danych. je nam możliwość wybrania dowolnych 256 będzie także funkcja czyszczenia wyświetla-
Dziś zajmiemy się zewnętrzną przestrzenią kolorów spośród 4096. Ma także wady: trans- cza – skorzystamy z funkcji memset – iden-
adresową, wspomnimy o działaniu pamięci misja jest wolniejsza niż w trybie 8 bitów, tycznie robiliśmy to już w przypadku po-
EEPROM zawartej w mikrokontrolerze AVR, a sama paleta zajmie dodatkowe 512B pamię- przedniego wyświetlacza.
a na końcu zapoznamy się z interesującym za- ci. Jak się okaże, w świetle naszego pierwsze- Bardziej rozbudowana będzie funkcja od-
gadnieniem dynamicznego przydzielania pa- go programu, zalety przeważają. świeżania wyświetlacza. Przedstawiam ją na
mięci. Przy okazji przedstawię kilka nowych Listing 202 pokazuje dodany bufor wy- listingu 204. W jednym przebiegu pętli prze-
„sztuczek” dotyczących programowania w C świetlacza oraz paletę kolorów. Paleta zostaje syłamy dwa piksele. Jest to związane z tym,
jako takim i GCC w szczególności, jednak ze w pamięci wewnętrznej, będzie przez to ob-
względu na rozmiary kodów przedstawiam sługiwana szybciej. Listing 202 Zmienne w module wyświetlacza
przede wszystkim fragmenty ciekawe lcd_pixel lcd_buffer[LCD_SX*LCD_SY] EXMEM;
uint16_t lcd_rgb[256]; // XXXXRRRRGGGGBBBB
oraz te, które dotyczą bezpośrednio za-
gadnienia. Reszta zostanie omówiona Listing 200 Dodanie sekcji. exram w pliku makefile
ogólnie, tak aby samodzielne ich napi- LDFLAGS += -Wl,--section-start=.exram=0x800500 Listing 203 Inicjacja palety
sanie było możliwe, jednak nie wystar- void lcd_loadRGB_P(const
prog_uint16_t* pRGB, uint16_t size)
czy już tylko przepisanie podanych li- Listing 201 Makro upraszczające dostęp do. exram {
stingów, aby całość zaczęła działać. #define EXMEM __attribute__ ((section (".exram"))) memcpy_P(lcd_rgb, pRGB, size);
}
Pełne kody, jak zwykle, dostępne są w
Elportalu.

Pamięć zewnętrzna ABC... GCC ABC... C


w sekcji .exram Opcja linkera, opcja kompilatora... Wartość pseudolosowa
Zanim przejdziesz dalej, polecam przeczytanie co, gdzie i jak – funkcja rand i srand
małej ramki na tej stronie, dotyczącej opcji
Dziś będę często posługiwał się określeniami „zmień W bibliotece stdlib znajdują się, między innymi, funkcje
linkera oraz dwóch dużych omawiających
opcje linkera, dodaj opcje kompilatora”. Aby nie opisy- służące do generowania liczb pseudolosowych. Liczba
pamięć zewnętrzną od strony technicznej oraz wać za każdym razem, gdzie odpowiednich opcji szu- jest generowana na podstawie zarodka. Po starcie pro-
jej obsługi przez AVR-GCC. Po ich przeczyta- kać, już na wstępie wyjaśniam zagadnienie: gramu ma on wartość 1. Zmieniamy go za pomocą funk-
niu wszystko, co będziemy robić dalej, powin- Wszystkie opcje znajdują się w pliku makefile. cji srand. Każde wywołanie funkcji rand spowoduje wy-
no być zrozumiałe. W oryginalnym pliku opcje kompilatora zaczynają się generowanie kolejnej liczby oraz ustawienie nowego za-
w okolicy 114 linii, natomiast opcje linkera w linii 184. rodka.
Nasz program powstaje na bazie napisane-
Wszystkie opcje kompilatora zebrane są w zmiennej Wynik funkcji rand to liczba 0-RAND_MAX, przy
go w poprzedniej części sterownika wyświe- CFLAGS, natomiast opcje linkera w zmiennej czym standard ANSI-C określa, że minimalna wartość
tlacza. Rozwiniemy go o potrzebną nam funk- LDFLAGS. Przykładowy fragment ciągu opcji kompila- RAND_MAX to 32767 (0x7fff) i taka wartość jest też
cjonalność. tora pokazuje listing niżej: używana w AVR-GCC. Aby wylosować liczbę w zakre-
Pierwsze, co zrobimy, to dodamy sekcję CFLAGS = -g$(DEBUG) sie a <= x < b, stosujemy zapis:
CFLAGS += $(CDEFS) $(CINCS) rand () % (b-a) ) + a
pamięci, zawierającą zmienne w pamięci ze-
CFLAGS += -O$(OPT) Wartość (b-a) powinna być dużo mniejsza od
wnętrznej. Odpowiednią opcję pokazuje li- (...) RAND_MAX.
sting 200. Następnie w naszym pliku makra.h Dodanie opcji polega na dopisaniu linii na końcu: W normie ANSI podane są zalecane równania do
dodamy nową definicję, widoczną na listingu CFLAGS += opcja1 opcja2 generowania kolejnych liczb. Jednak przy ich stosowa-
201. Dzięki temu tworzenie zmiennej w pa- Troszkę inaczej ma się sprawa z opcjami przezna- niu wartość zarodka niewiele zmienia generowany ciąg.
czonymi dla linkera. Opcje zapisujemy jak niżej: W WinAVR stosuje się inne, niż zalecane równania, cze-
mięci zewnętrznej będzie bardziej intuicyjne.
LDLAGS += -Wl, opcja1, opcja2 go norma nie zabrania.
Teraz jesteśmy gotowi do dodawania ele- Ciąg opcji rozpoczynamy przez „-Wl,” co informu- Ważne jest, aby zrozumieć, że jedyne, co liczby
mentów do modułu lcd3310i. Wyświetlacz je, że występujące dalej opcje przeznaczone są dla linke- pseudolosowe mają wspólnego z liczbami losowymi,
będziemy obsługiwać troszkę nietypowo. ra. Poszczególne opcje oddzielamy przecinkami albo to fakt, że nie znając zarodka trudno, na podstawie kilku
Tworzony bufor wyświetlacza będzie składał przed każdą stawiamy ponownie ciąg “-Wl,”. Zauważ, następujących po sobie liczb, określić, jaka będzie na-
się z elementów ośmiobitowych. Ponieważ że po ostatniej opcji nie ma przecinka! Opcje są interpre- stępna. Jednak jeśli zaczniemy generację dwa razy od ta-
towane jako kolejne parametry linkera tak długo, jak od- kiego samego zarodka, otrzymamy identyczne ciągi
8 bitów jest naturalną wielkością zmiennej dla dzielane są one przecinkami. liczb (generowany ciąg może zależeć od kompilatora).
AVR-a, będziemy mogli na takim buforze Istnieje analogiczna komenda: „-Wa,”, informująca, Ważne staje się więc odpowiednie ustawienie zarodka
dość szybko wykonywać obliczenia. Jednak że dalsze opcje przeznaczone są dla kompilatora asem- przed rozpoczęciem korzystania z funkcji rand. Najle-
wprowadzimy dodatkową tablicę, która bę- blera. piej, jeśli będzie to liczba rzeczywiście losowa. W mi-
dzie zawierała naszą paletę kolorów. Wartość Możesz skorzystać z narzędzia „znajdź”, aby zoba- krokontrolerze liczbą taką często może być suma modu-
czyć, gdzie nasze zmienne CFLAGS oraz LDFLAGS są lo 2 wszystkich komórek pamięci.
w buforze wyświetlacza będzie indeksem dla później używane. Przykłady: listing 207 i 208.
naszej palety. Do wyświetlacza prześlemy

E l e k t ro n i k a d l a Ws z y s t k i c h 39
Programowanie

RAM oraz wolniejszego


Szczegóły techniczne wyświetlacza alfanume-
Pamięć zewnętrzna rycznego czy graficzne-
w procesorach AVR go.
Magistrala zewnętrz-
Przestrzeń pamięci danych na konfigurowana jest za
W procesorach AVR pamięć zewnętrzna jest umieszczona pomocą bitów trzech re-
w przestrzeni pamięci danych. Oznacza to, że odwołują jestrów opisanych na ry-
się do niej identyczne instrukcje jak te, z których kompi- sunku B. W tym miejscu
lator korzysta w przypadku zwyczajnych zmiennych. rozwinięcia wymaga opis
W praktyce dostęp do zewnętrznej pamięci danych może bitu XMBK. Współdzie-
być z punktu widzenia programisty niewidoczny. lone linie młodszej części
Spójrz na rysunek A w ramce. Pokazuje on przestrzeń adresu i danych (ozna-
adresową pamięci danych procesora. Przestrzenie reje- czone AD0..7) tworzone
strów uniwersalnych oraz rejestrów wejścia/wyjścia, są przez wyprowadzenia
razem z rozszerzoną przestrzenią wejścia/wyjścia, istnieją portu A. Przy domyślnie
także w innych przestrzeniach adresowych, co oznacza, skonfigurowanej magi-
że odwołują się do nich specjalne instrukcje. Rejestry uni- strali, w czasie jej nieak-
wersalne adresujemy często bezpośrednio w rozkazie tywności, na szynach da-
(R0, R1, ...) i zgodnie z zasadami procesorów RISC są one nych utrzymywany jest
docelowym punktem wszelkich obliczeń oraz pośredniczą stan wysokiej impedan-
w operacjach przesłania. Specjalna przestrzeń wej- cji. Nie jest to najlepsze
ścia/wyjścia posługuje się ośmiobitowym adresem i może możliwe wyjście. Spra-
być obsługiwana przez dwie oddzielne instrukcje IN wia to, że wszelkie wej-
i OUT. W stosunku do odpowiadających im instrukcji ścia CMOS (a takimi są
operujących na przestrzeni danych: LDS i STS, zajmują także wyprowadzenia da-
one dwa razy mniej miejsca i wymagają tyle samo mniej nych pamięci) znajdują
cykli zegara. się w nieokreślonym sta-
nie. Często ich stan bę-
dzie zmieniał się pod
wpływem powstających
na płytce zakłóceń, cza-
sem znajdą się w stanie
zabronionym. W przy-
padku niektórych ukła-
dów, w tym samego kon-
trolera, utrzymywanie ta-
kiego stanu może prowa-
dzić do niepotrzebnego
wzrostu poboru prądu.
Nasz procesor oferuje
dwie metody zaradzenia
temu problemowi:
1. Włączenie podciągania
na wyprowadzeniach
portu A. W tym celu wpi-
sujemy jedynki do reje-
stru PORTA.
2. Utrzymywanie po-
przedniego stanu na wyprowadzeniach portu A. Po usta- Dostęp do pełnej zewnętrznej
wieniu bitu XMBK, gdy wyprowadzenia portu A zostaną przestrzeni adresowej
ustawione w stan wysokiej impedancji, port zostanie wy- Dla naszego procesora ATmega162 adresowanie pamięci
sterowany tak, aby utrzymany był poprzednio ustawiony zewnętrznej rozpoczyna się od adresu 0x0500. Pierwsze
stan. Jest to optymalne energetycznie rozwiązanie. Funk- 1280 bajtów przestrzeni adresowej to odwołanie do ele-
cja ta odnosi się do portu A i działa niezależnie od tego, mentów wewnątrz mikrokontrolera. W naszym układzie
czy interfejs pamięci zewnętrznej jest włączony. modelowym pamięć została podłączona w taki sposób,
że jest aktywna, gdy stan na linii adresowej A15 jest niski
Szybkość działania – okupuje więc niższą połowę przestrzeni adresowej. Jak
Sposób działania zewnętrznej magistrali danych sprawia, w takim przypadku umożliwić dostęp do wszystkich ko-
że podłączona do niej pamięć jest wolniejsza od pamięci mórek pamięci? Przyjrzyjmy się bitom XMM2..0 na ry-
wewnętrznej mikrokontrolera. Instrukcje LD, ST, LDS, sunku B. Jeśli wpiszemy do nich wartość ‘001’, najstar-
STS, PUSH oraz POP zajmują o 1 cykl więcej czasu, jeśli szy bit adresu zostanie odpięty od portu C i odpowiadają-
Uruchomienie pamięci zewnętrznej ich wykonanie dotyczy pamięci zewnętrznej. Należy doli- ce mu wyprowadzenie stanie się zwykłym portem wej-
Uzgodniliśmy już, że z punktu widzenia programowania czyć kolejny cykl na każdy wprowadzony cykl oczekiwa- ścia/wyjścia. Teraz możemy w tym miejscu ustawić na
pamięć zewnętrzna nie sprawi nam trudności. Jednak jako nia (bity SRWn0..1). Instrukcje PUSH i POP dotyczą tyl- sztywno niski stan logiczny. Dzięki temu najniższa część
rasowi elektronicy musimy wejrzeć w sprawę odrobinę ko sytuacji, gdy stos znajduje się w pamięci zewnętrznej. pamięci będzie widoczna na samej górze obszaru adreso-
głębiej. Ponadto, jeśli umieścimy stos w pamięci zewnętrznej, mu- wego, co ilustruje rysunek C.
Pierwsza sprawa, jaką będziemy musieli się zająć, to simy się liczyć z tym, że wolniejsze będzie wykonanie in- Warto zaznaczyć, że podobna technika umożliwia do-
włączenie i konfiguracja pamięci zewnętrznej. Jak widać strukcji CALL, ICALL, RCALL, RET oraz RETI. Zajmu- stęp do wszystkich komórek, gdy mamy do czynienia
na rysunku A, przestrzeń pamięci zewnętrznej możemy ją one o 3 cykle więcej + 2 cykle zegarowe na każdy cykl z pamięcią 64KB, wtedy posługiwalibyśmy się tylko adre-
podzielić na dwa sektory. Miejsce podziału ustalamy oczekiwania (dane dla 16-bitowego licznika rozkazów). sami wyższymi niż 0x0500, a maskowanie najstarszego
z krokiem 8KB. Gdy w bity SRL2.. 0 wpiszemy wartość Ponieważ kompilator języka C dość intensywnie korzysta bitu umożliwiałoby dostęp do „ukrytego” obszaru pamię-
0, cały obszar pamięci będzie sektorem górnym. W obu ze stosu, należy unikać jego umieszczania w pamięci ze- ci, przenosząc go pod adres 0x8000 do 0x84FF. W tym,
sektorach mamy możliwość oddzielnej konfiguracji szyb- wnętrznej. Ponadto, w erratach można znaleźć informacje, normalnie ukrytym, obszarze powinniśmy umieszczać da-
kości działania pamięci. Umożliwia nam to, przykładowo, że niektóre procesory nie mogą działać ze stosem w pa- ne, do których nie wymagamy szybkiego dostępu i któ-
podłączenie na jednej magistrali danych szybkiej pamięci mięci zewnętrznej. rych kompilator nie musi obsługiwać automatycznie.

40 E l e k t ro n i k a d l a Ws z y s t k i c h
Programowanie

Listing 204 Odmalowanie wyświetlacza pix1 <<= 4;


z wykorzystaniem wewnętrznej palety // Wysłanie danych
lcd_Data((uint8_t)(pix1>>8));
pbuf = lcd_buffer;
pix1 |= (pix2>>8) & 0x0F;
void lcd_Update(void) for(n=0; n<(ELEMS(lcd_buffer)/2); n++)
lcd_Data((uint8_t)pix1);
{ {
lcd_Data((uint8_t)pix2);
uint16_t n; // Wczytuję wartość koloru
}
lcd_pixel* pbuf; uint16_t pix1 = lcd_rgb[*pbuf++];
lcd_Command(LCD_NOP);
uint16_t pix2 = lcd_rgb[*pbuf++];
}
lcd_Command(LCD_RAMWR); // Wysyłam dane

ABC... GCC
Konfiguracja sekcji w pamięci RAM
oraz dostęp do pamięci zewnętrznej

Z ramki o działaniu zewnętrznej magistrali pamięci da-


nych procesora AVR dowiedzieliśmy się, że z punktu wi-
dzenia programisty pamięć zewnętrzna tworzy przedłuże-
nie pamięci wewnętrznej. Mimo tego, jeśli piszemy pro-
gram w GCC, nie powinniśmy w zwykły sposób tworzyć
dużych zmiennych, zakładając, że ten ich fragment, który
nie mieści się w pamięci wewnętrznej, zostanie przenie-
siony na zewnątrz. Rzeczywiście, duże zmienne w taki
właśnie sposób zostaną umieszczone w przestrzeni adre-
sowej. Jednak tracimy kontrolę nad tym które zmienne
znajdą się w szybkiej pamięci wewnętrznej, a które w wol-
niejszej pamięci zewnętrznej. Ponadto, między obszarem
„zwykłych” zmiennych a pamięcią zewnętrzną mamy jed-
ną przeszkodę: stos. Przemieszczanie istniejących sekcji %. hex: %. elf
Z ideą sekcji pamięci zapoznaliśmy się już w części Pierwszy raz przemieszczaliśmy sekcję zawierającą kod @echo
12. Teraz spójrz na rysunek w ramce. Pokazuje on domyśl- programu (.text) przy okazji pisania bootloadera w części @echo $ (MSG_FLASH) $@
ny sposób, w jaki poszczególne sekcje zostaną umieszczo- 12. Przesuwanie sekcji w pamięci danych odbywa się $ (OBJCOPY) -O $ (FORMAT)
ne w obszarze pamięci danych. w identyczny sposób. Konieczne jest jedynie dodanie do -R. eeprom -R. exram $< $@
Przypomnę znaczenie widocznych na obrazku sekcji: wybranego adresu 0x800000 dla oznaczenia, z jakim ty- W tym momencie umieszczenie zmiennej w pamięci
.data: W tej sekcji znajdują się dane które są inicjowane pem pamięci mamy do czynienia. I uwaga: jeśli przesunie- zewnętrznej ogranicza się do dopisania zapisu jak niżej
wartością, inną niż zero. W pamięci programu znajduje się my sekcję .data, sekcje .bss, .noinit i sterta zostanią prze- w chwili jej deklaracji:
obszar zawierający dane inicjujące obszar sekcji .data. sunięte automatycznie. Możemy także przenieść samą int zmienna __attribute__
Automatycznie ustawiana na początek obszaru pamięci sekcję .bss albo sekcję .noinit do pamięci zewnętrznej. ( (section (' .exram') )));
RAM. Którąkolwiek sekcję przesuniemy, wszystkie elemen- Uwaga: zmiennej takiej nie możemy przypisać warto-
.bss: Jest to obszar zmiennych nieinicjowanych wartością. ty leżące w kierunku rosnących adresów przesuną się ra- ści początkowej, ponieważ możliwe jest to tylko dla
Zawarte tutaj zmienne są zerowane podczas inicjacji pro- zem z nią. Nie dotyczy to stosu, który domyślnie jest usta- zmiennych w sekcji .data. Kompilator nie zgłosi błędu, ale
gramu. wiany na koniec pamięci wewnętrznej. Natomiast przesu- też nic nie ustawi.
.noinit: jej obszar nie jest w żaden sposób modyfikowany nięcie sekcji. noinit, leżącej na końcu sekcji .bss, przesu-
po starcie programu. Ponieważ w ANSI C sekcja .noinit nie także pozycję początku sterty. Jawne podanie adresu
nie istnieje, czasem sekcje .noinit traktujemy jako frag- Oprócz przesuwania sekcji opcją poznaną w części Skoro już mówimy o dostępie do zewnętrznego obszaru
ment sekcji .bss. 12: –section-start=.data=nnnn (tylko) w przypadku sekcji adresowego, trudno zapomnieć o możliwości pozornie
sterta: Obszar przeznaczony na pamięć alokowaną dyna- .data, .bss, oraz .text, możemy posłużyć się prostszym najprostszej. Można oczywiście podać bezpośrednio adres
micznie. Jej opis pojawi się w dalszej ramce. zapisem: -Tnazwa_sekcji=nnnn do odpowiedniej wstawki asemblerowej albo rzutować
stos: Sprzętowy stos mikrokontrolera. Wykorzystywany
odpowiednią liczbę na wskaźnik. Pierwsze rozwiązanie
do odkładania adresu powrotu z podprogramu lub prze- Tworzenie własnej sekcji
jest dobre w przypadku, gdy chcemy obsłużyć jakieś urzą-
rwania oraz, w razie konieczności, do pamiętania stanu Pełną kontrolę nad rozmieszczeniem danych da nam
dzenie wejścia/wyjścia, a nie pamięć. Pamiętaj jednak, że
zmiennych lokalnych. Stos przyrasta w kierunku zmniej- utworzenie nowej, specjalnej sekcji obejmującej pamięć
nawet w takim przypadku można utworzyć dodatkową
szających się adresów. Automatycznie ustawiany na koń- zewnętrzną. Jeśli umieścimy w niej zmienną, automatycz-
sekcję i umieścić w niej strukturę odpowiadającą reje-
cu pamięci wewnętrznej. nie znajdzie się ona w pamięci zewnętrznej. Uzyskany
strom sterowanego układu. Aby zapewnić, że kompilator
Widoczne na rysunku mnemoniki wskazujące począt- w ten sposób mechanizm jest praktycznie tak samo wy-
nie będzie optymalizował dostępu do takich rejestrów, na-
ki i końce poszczególnych obszarów można zmieniać godny w stosowaniu, jak oznaczanie miejsca umieszcze-
leży im nadać atrybut volatile.
przez odpowiednie opcje kompilatora. Zostanie to omó- nia zmiennej znane z BASCOM-a!
Natomiast jedyne, co można powiedzieć o bezpośred-
wione przy pamięci dynamicznej. Tutaj dobra wiadomość. Tworzenie nowej sekcji jest
nim rzutowaniu liczby wskazującej adres na wskaźnik to
tak samo proste, jak przesuwanie sekcji istniejącej. Ko-
tyle, że w AVR-GCC jest to możliwe i działa. Jednak,
Przesunięcie stosu nieczny będzie jednak dodatkowy krok. Tę opcję przero-
z punktu widzenia ANSI-C, wynik takiego działania jest
Kompilator zawarty w pakiecie WinAVR umożliwia dość bimy dokładniej.
nieokreślony. Nie będziemy więc rozwijać dalej tej idei.
swobodne przemieszczanie sekcji w pamięci danych. Tworzymy nową sekcję przez dodanie poniższej opcji
Pierwszym pomysłem, jaki może się pojawić, jest przesu- linkera: –section-start=.exram=0x800500
nięcie przeszkadzającego nam stosu na koniec naszej pa- Już w tej chwili możemy korzystać z nowej sekcji. Włączyć zewnętrzną magistralę
mięci zewnętrznej. Można to zrobić na dwa sposoby: Jednak gdy skompilujemy program z jakąkolwiek zmien- bardzo wcześnie
1. Tworzymy funkcję umieszczoną w sekcji. init2, nadpi- ną w sekcji .exram, czeka nas przykra niespodzianka: Pod- Pamiętaj, że jeśli w pamięci zewnętrznej znajdą się dane
sując tym samym domyślną funkcję inicjacji stosu. Pamię- czas wczytywania pliku hex może się okazać, że program potrzebne już w chwili inicjacji, trzeba dokonać jej
tamy o zerowaniu rejestru __zero_reg__(r1). nie mieści się w układzie albo nawet sam plik nie daje się włączenia, jeszcze zanim program zechce z nich skorzy-
2. Przesuwamy stos przez opcję kompilatora (w pliku ma- wczytać. Jest to związane z tym, że dane z nowej sekcji są stać. Jest to ważne, gdy dokonamy przesunięcia sekcji .da-
kefile), wprowadzamy: -minit-stack=nnnn kopiowane do pliku wyjściowego, bez przesunięcia do ze- ta albo .bss. Sekcje te są inicjowane wartościami począt-
gdzie nnnn to adres, od którego rozpocznie się nasz stos ra adresu 0x800000. kowymi, zanim program przejdzie do funkcji main. Spra-
(nie dodajemy 0x800000, piszemy: dziesiętnie – 5000, Tworzeniem plików hex oraz eep na podstawie pliku wa robi się jeszcze poważniejsza, jeśli na zewnątrz znaj-
albo szesnastkowo: 0x5000). elf zajmuje się programik avr-objcopy. W naszym pliku dzie się stos. W takich przypadkach pamięć zewnętrzną
Pamiętaj jednak, że zgodnie z poprzednią ramką, prze- makefile jest on zdefiniowany jako zmienna OBJCOPY. bezpiecznie można włączyć, umieszczając odpowiednią
sunięcie stosu do pamięci zewnętrznej zwalnia działanie Prawie na końcu pliku odnajdujemy regułę tworzącą plik funkcję w sekcji .int1 – przed inicjacją stosu lub .init3 –
całego programu i ogólnie nie jest zalecane. Zabierzmy się hex i wprowadzamy opcję, która usunie z pliku wyniko- stos jest już zainicjowany, rejestr zera (__zero_reg) przy-
raczej do reszty sekcji. wego niechcianą sekcję: gotowany.

E l e k t ro n i k a d l a Ws z y s t k i c h 41
Programowanie
co już wskazaliśmy w poprzedniej części – odwołanie się bezpośrednio do bufora wy- W ten sposób przekonaliśmy się, że obsłu-
dwa piksele wymagają trzech bajtów i dane świetlacza. Później znajduje się tablica kolo- ga zewnętrznej pamięci danych przebiega
w nich rozłożone są w taki sposób, że łatwiej rów naszego płomienia. Makro LCD_palRGB praktycznie identycznie jak pamięci wewnętrznej
przesyłać je jako jeden element. Na koniec zamienia poszczególne składowe na 12-bito-
wysyłamy instrukcję NOP, o czym także była wy kolor. Podobne makra mamy już przero- Listing 206 Przed funkcją main()
mowa przy okazji omawiania wyświetlacza. bione. void before_main(void)
To wszystko, czego w tej chwili potrzebu- Na listingu 206 widzimy, co dzieje się __attribute__((naked))
jemy w naszej bibliotece. Do bufora wyświe- przed funkcją main. To umożliwia nam włą- __attribute__((section(„.init3“)));

tlacza „dobierzemy się” bezpośrednio w pro- czenie pamięci zewnętrznej bardzo wcześnie. void before_main(void)
{
gramie. Na listingu 207 przedstawiam zawartość pętli MCUCR = 1<<SRE;
rysowania płomienia. Nie przedstawiam ini- SFIOR = 1<<XMBK | 1<<XMM0 /*A15*/;
„Podpalanie” wyświetlacza cjacji portów, włączenia wyświetlacza oraz // Ustawienie A15 na 0
DDRC = 0x80;
Za pomocą naszej nowej biblioteki stworzy- ładowania palety. PORTC &= ~(1<<7);
}
my program realizujący realistyczną animację Na początku generowanych jest 80 (dobra-
płomienia. Wbrew pozorom, stworzenie reali- no eksperymentalnie) punktów o maksymal-
Listing 207 Zawartość pętli rysowania
stycznie wyglądającego płomienia nie wyma- nej jasności. Pozycja zmienia się tak, aby
ga wielkich mocy obliczeniowych ani żad- punkt zmieścił się w odstępie 2 pikseli od lcd_Update();

nych zdolności obsługi programów graficz- bocznych brzegów wyświetlacza i na wyso- // Podsycanie ognia
uint8_t n;
nych. Udowodnimy to, generując ładną wizu- kości od 2 do 6 pikseli od jego dolnej krawę- for(n=0; n<80; n++)
alizację ognia za pomocą naszego zestawu. dzi. {
// Losowanie pozycji
Nasz płomień będzie korzystał ze specy- Algorytm rozmywania wydaje się oczywi- uint8_t x = rand() % (LCD_SX-4) + 2;
ficznej palety, która poszczególnym tempera- sty. W pierwszej chwili może dziwić dodawa- uint8_t y = LCD_SY – rand() % 5 - 3;
// Wypisanie piksela
turom będzie przypisywać odpowiedni kolor. nie 2 do wskaźnika ppix na koniec zewnętrz- lcd_Pixel(x, y, FIRE_MAX);
Najniższa temperatura będzie miała indeks 0, nej pętli. Jest to związane z tym, że nie obli- }

będzie to kolor czarny. Im wyższa temperatu- czamy wartości pierwszego i ostatniego pik- // Rozmywanie obrazu
ra, tym większe natężenie czerwonego, póź- sela w linii (przypominam, że są one cały czas lcd_pixel *ppix = lcd_buffer+LCD_SX+1;
for(uint8_t y=1; y<LCD_SY-1; y++)
niej rośnie składowa zielona – da to przejście czarne) i musimy je pominąć. {
od czerwieni do koloru żółtego, ostatecznie Dodatkowo, przy rozmywaniu, jeśli nie for(uint8_t x=1;
x<LCD_SX-1; x++, ppix++)
dodajemy składową niebieską – nasz ogień mamy do czynienia z minimalną temperaturą, {
w „najcieplejszym” miejscu będzie miał kolor minimalnie obniżamy temperaturę punktu lcd_pixel temp;
temp = *ppix + *(ppix+LCD_SX+1) +
biały. Ogień jest „podsycany” na spodzie wy- płomienia. Ten element został wprowadzony *(ppix+LCD_SX-1) +
świetlacza. Podsycanie nie jest równomierne, po pierwszych eksperymentach i zaowocował *(ppix+LCD_SX);
if(temp != 0)
tylko rozbłyska i przygasa w sposób z grubsza bardziej postrzępionym szczytem „płomie- temp -=
= 1;
*ppix = temp / 4;
losowy. Jest to pole do wykorzystania oma- nia”. }
wianej w jednej z ramek funkcji rand. Ciepło ppix += 2;
}
płomienia rozchodzi się w górę, przy czym
następuje jego stopniowe rozmycie. Odpo- Listing 205 Zmienne w pliku main.c
wiedni efekt da nam obliczanie koloru pikse- extern lcd_pixel lcd_buffer[];
la zgodnie z rysunkiem 71. Zauważ, że dzię- prog_uint16_t g_fireRGB[] =
ki sprytnej sztuczce z paletą, obliczenia prze- {
LCD_palRGB(0, 0, 0),
prowadzamy niejako na wartościach tempera- ...
tury, a nie na poszczególnych składowych ko- LCD_palRGB(15, 0, 0),
...
loru. Co więcej, jeśli ograniczymy ilość kolo- LCD_palRGB(15, 15, 0),
rów do 64, obliczenia będzie można wykony- ...
LCD_palRGB(15, 15, 15),
wać na liczbach ośmiobitowych – to bardzo };
przyspiesza działanie programu. Dzielenie
przez 4 także jest bardzo wygodne – kompila-
tor zoptymalizuje ją na przesunięcie logiczne. ABC... GCC Fot. 11 Płomień na wyświetlaczu
Dla uproszczenia obliczeń pozostawiamy Maksymalny rozmiar
z każdej strony generowanego obrazka 1 pik- obsługiwanej zmiennej
ABC... GCC
sel czarny – tutaj nie rysujemy, tylko korzy-
stamy z tych pikseli przy obliczeniach. Przy tworzeniu zmiennych trzeba pilnować, aby nie Pętla do-while (0)
Listing 205 pokazuje początek programu przekroczyć maksymalnego rozmiaru zmiennej, jaki – alternatywa dla goto
AVR-GCC jest w tanie obsłużyć. Maksymalny rozmiar
w pliku main. Pierwsza linia umożliwia nam pojedynczej zmiennej wynosi 32767B. Ograniczenie to Część wczytująca dane przesyłane przez komputer zo-
wynika z przyjętego typu zmiennej wskazującej rozmiar stała umieszczona we wnętrzu pętli do-while. Jednak jej
size_t. Jest to liczba 16-bitowa ze znakiem. Mimo tego, warunek został ustawiony na 0. Jaki to ma sens? Ciało
Rys. 71 Zasada rozmywania płomienia
że rozmiar nie może być ujemny, niektóre funkcje zwra- takiej funkcji będzie zawsze wykonane tylko raz. Jednak
cają wartość ujemną, aby zaznaczyć wystąpienie błędu. zyskujemy jedną znakomitą możliwość: możemy w do-
Zaznaczam, że ograniczenie dotyczy rozmiaru pojedyn- wolnym momencie wyskoczyć z ciała pętli (jeśli, na
czej zmiennej. W programie mogą pojawić się, na przy- przykład, komputer nie przesłał żądania programowa-
kład, dwie tablice o rozmiarze 30kB każda i zostaną one nia) bez użycia instrukcji goto (patrz część 11). Po pro-
prawidłowo obsłużone (jeśli tylko mamy fizycznie od- stu użyjemy instrukcji break. Ta ciekawa sztuczka jest
powiednią ilość pamięci). często stosowana i warto o niej pamiętać. Ostatecznie,
W pliku <stdint.h> znajduje się definicja stałej często tak napisany kod jest bardziej przejrzysty.
SIZE_MAX oznaczającej maksymalny rozmiar poje- Listing nie jest prezentowany w artykule. Kod do-
dynczej zmiennej. stępny na stronie Elportalu.

42 E l e k t ro n i k a d l a Ws z y s t k i c h
– ze względu na architekturę AVR miejsce Pamięć EEPROM – dodaje- danymi, zostaną one umieszczone w pliku
umieszczenia zmiennej od strony kodu nie ma my możliwość konfiguracji .eep. Modyfikację pokazuje listing 209.
znaczenia. Zmienia się jedynie czas dostępu. Nasz płomień wygląda bardzo efektownie, ale Do funkcji obsługujących wyświetlacz do-
Uzyskany efekt wygląda naprawdę bardzo fajnie byłoby mieć możliwość jego dowolnej damy funkcję umożliwiającą wczytanie no-
atrakcyjnie, o czym przekonuje częściowo fo- konfiguracji bez konieczności każdorazowe- wej palety – patrz listing 210. Porównaj wi-
tografia 11. Jednak ruchoma animacja spra- go kompilowania programu. Pomoże nam doczny tutaj kod z listingiem 203. Sposoby
wia znacznie lepsze wrażenie. w tym nieulotna pamięć danych typu obsługi pamięci programu i EEPROM są bar-
EEPROM. Przejrzyj mówiącą o niej ramkę... dzo podobne.
i zabieramy się do pracy. W celu obsługi transmisji użyjemy modu-
ABC... GCC Idea, od strony programowania, będzie jak łu rs, który pojawił się po raz pierwszy już
Dostęp do pamięci EEPROM najprostsza: jeśli przy uruchamianiu układu w części 8. Dalej nie będę pisał o inicjacji
przytrzymamy którykolwiek przycisk, proce- portu UART ani dokładnie omawiał sposobu
Z punktu widzenia AVR-GCC obszar pamięci EEPROM sor będzie czekał na transmisję. Odbywa się transmisji – wszystko to już znamy. Skupiam
jest specjalną sekcją. Aby zaznaczyć, że mamy do czy- ona z prędkością 1200 bodów. Komputer mu- się natomiast na obsłudze pamięci EEPROM.
nienia z kolejnym obszarem adresowym, do adresu sek-
cji umieszczonej w tej pamięci dodajemy przesunięcie
si przesłać ciąg „fire”, na co program odpowie Ważny dla nas fragment pokazuje listing
0x810000. Zmienną możemy umieścić w obszarze pa- potwierdzeniem „@”. Teraz przesyłamy in- 211. Przy korzystaniu z biblioteki
mięci EEPROM jak niżej: formację o ilości danych do nowej palety i za- <avr/eeprom.h> dostęp do pamięci EEPROM
int zmienna __attribute__ raz po niej 16-bitowe wartości palety. Ilość jest dość intuicyjny. Na łamach Elporatalu
( (section ('.eeprom') ))) = 12; ; danych i same dane przesyłane są prosto – znajdzie się nie tylko pełny kod programu, ale
albo prościej, korzystając z makra znajdującego się
w pliku <avr/eeprom.h>:
w postaci binarnej. Jednocześnie są one odsy- także program służący do tworzenia i przesy-
int zmienna EEMEM = 12; ; łane w celu kontroli. łania palety płomienia z poziomu komputera
Przykładowa wartość 12 zostanie zapisana w odpo- Pierwsze, co zmienimy, to zamiast tablicy PC.
wiednim miejscu pliku eep. Jeśli nie nadamy zmiennej umieszczonej w pamięci programu, stworzy- Gdyby podczas działania programu poja-
wartości, będzie ona domyślnie wyzerowana. my specjalną strukturę w pamięci EEPROM. wiły się problemy z komunikacją, pomóc mo-
Przy obszarze pamięci EEPROM natkniemy się na
podobny problem, jaki mieliśmy przy umieszczaniu ta-
Będzie ona zawierała dane palety oraz maksy- że przełączenie procesora na zewnętrzny
blic w pamięci programu. Zmienne takie obsługujemy za malną temperaturę płomienia (najwyższy in- kwarc 8MHz. W czasie eksperymentów z we-
pomocą specjalnych funkcji. Proste funkcje umożliwia- deks palety). Jeśli strukturę tę zainicjujemy wnętrznym generatorem RC problemów ta-
jące obsługę obszaru EEPROM deklarowane są w pliku kich nie stwierdzono.
<avr/eeprom.h>. Wszystkie dostępne tutaj funkcje dzia-
Listing 208 Zarodek zmiennej pseudolosowej
łające na pamięci EEPROM przed swoim wykonaniem
Pamięć przydzielana
dynamicznie
czekają na zakończenie poprzedniej operacji zapisu. uint16_t *pmem;
Może to zatrzymać wykonywanie programu na około uint16_t seed = 0;
8,5ms.
uint16_t n; Odstawiamy już naszą animację płomienia.
pmem = (uint16_t*)0;
eeprom_is_ready() – zwraca 0, jeśli pamięć EEPROM for(n=0; n<(RAMEND+1)/2; n++) Można oczywiście nad nią pracować i uzy-
jest zajęta, { skać jeszcze ciekawszy efekt. Jednak wyczer-
eeprom_busy_wait() – czeka, aż pamięć EEPROM za- seed ^= *pmem++;
} paliśmy możliwości dydaktyczne tego przy-
kończy zapis, srand(seed);
eeprom_read_byte (addr), eeprom_read_word (addr) – kładu. Zajmiemy się teraz bardzo ciekawym
funkcje zwracają bajt oraz słowo dostępne pod podanym zagadnieniem. Stworzymy prostą przeglądar-
adresem w pamięci EEPROM, Listing 209 Dane w pamięci EEPROM kę obrazków. Obrazki będziemy przesyłać
eeprom_read_block (ram, eeprom, n) – odczytuje n baj- z komputera, i zakładamy, że pamięć o nich
struct
tów z pamięci EEPROM i zapisuje je w buforze w pa- { ma być zachowana tylko do chwili wyłącze-
mięci RAM, uint8_t max_power;
eeprom_write_byte (addr, value), eeprom_write_word uint16_t fireRGB[64]; nia zasilania. Co jednak dla nas istotne, każdy
(ddr, value) – funkcje zapisują bajt oraz słowo pod poda-
}eeprom EEMEM = obrazek będzie mógł mieć inny rozmiar.
{
ny adres w pamięci EEPROM, 45, Oznacza to, że do naszego układu możemy
eeprom_write_block (ram, eeprom, n) – zapisuje n baj- { przesłać, na przykład, 3 obrazki o rozmiarze
tów z pamięci RAM do pamięci EEPROM. LCD_palRGB(0, 0, 0),
... całego wyświetlacza albo 10 o rozmiarze
}
Struktury w pamięci EEPROM };
50x50 każdy. Jak sobie z tym poradzić?
a pojedyncze zmienne W tym przypadku wspomoże
Dobrym zwyczajem jest tworzenie w pamięci EEPROM Listing 210 Wczytanie palety z pamięci EEPROM
nas wbudowana w AVR-GCC
tylko jednej struktury zawierającej wszystkie dane. Jeśli możliwość dynamicznej aloka-
umieścimy w pamięci pojedyncze zmienne, nie mamy void lcd_loadRGB_EE(const uint16_t* peeRGB, uint16_t size)
cji pamięci. Wszystko, co ko-
{
pewności, czy zostaną one rozmieszczone w kolejności
eeprom_read_block(lcd_rgb, peeRGB, size); nieczne, opisują dwie kolejne
zgodnej z kolejnością ich definicji. Co więcej, kolejna }
wersja WinAVR może rozłożyć je w inny sposób. Staje
ramki. Tutaj od razu zabieramy
się to ważne, jeśli stworzymy nowszą wersję oprogramo- się do działania.
wania i zechcemy umożliwić jego wgranie bez kasowa- Listing 211 Zapis konfiguracji do pamięci EEPROM
nia nastaw zawartych w pamięci EEPROM. Jeśli w pa-
uint8_t count; Program „przeglądarka”
mięci EEPROM znajdzie się tylko jedna struktura, count = rs_get(); Nasz nowy program będzie działał z wy-
mamy pewność, że rozmieszczenie danych się nie zmie- eeprom_write_byte(&eeprom.max_power, count-1);
ni. Jeśli pojawi się konieczność dodania nowych opcji – rs_put(count); świetlaczem w trybie ośmiobitowym.
dopisujemy je na końcu struktury, tak aby nie kolidowa- W module wyświetlacza rezygnujemy
// Pobieranie danych
ły ze starymi ustawieniami: uint8_t n; więc ze zmiennej zawierającej paletę. Ko-
struct for(n=0; n<count; n++)
{
nieczne jest jednak dodanie kilku linii na
{ końcu procedury inicjacji naszego modu-
uint16_t rgb = (uint16_t)rs_get()<<8;
int dana1= =0,, dana2= =5;; rs_put(rgb>>8);
... rgb |= rs_get();
łu. Pokazuje je listing 212. Uprości się
}eeprom EEMEM; ; eeprom_write_word(&eeprom.fireRGB[n], rgb); znacznie funkcja odmalowywania wy-
rs_put((uint8_t)rgb);
Przykłady: listing 209 do 211. }
świetlacza – wystarczy teraz wysłać za-
wartość bufora (listingu nie pokazuję).

E l e k t ro n i k a d l a Ws z y s t k i c h 43
Programowanie
Pracę ułatwi nam funkcja lcd_Draw, która ry- cza alfanumerycznego. Funkcja system_run tu bez jakichkolwiek zmian.
suje na podanym miejscu ekranu obrazek została zmodyfikowana, aby konfigurować 3. Moduł rs. Rozbudowany, aby poprawić
o podanym rozmiarze, czerpiąc jego dane odpowiednio aktualny sprzęt. Dodano tutaj bezpieczeństwo działania funkcji rs_get.
z pamięci RAM. Funkcja jest prosta w imple- obsługę zdarzeń portu UART, zgodnie z ram- W ten sposób jesteśmy gotowi do pisania
mentacji, tak więc i tym razem listing nie po- ką w lewym dolnym rogu strony 46. w programie nowego modułu zajmującego się
jawia się na łamach kursu. 2. Moduł kbd z części 11. Dodany do projek- dynamiczną obsługa obrazków.
W module wyświetlacza to koniec zmian.
Nasz nowy program składał się będzie jednak
ABC... C
z większej liczby modułów, które napisaliśmy void*calloc (size_t n, size_t size)
już w czasie trwania kursu: Dynamiczne przydzielanie pamięci Zajmuje blok pamięci na n obiektów, każdy o rozmiarze
size. Inicjuje pamięć zerami. Zwraca wskaźnik na blok pa-
1. Moduł system z części 11. Usunięte zostały Specyfikacja ANSI-C określa zestaw funkcji do obsługi mięci albo NULL, jeśli polecenie nie może być wykonane.
z niego jedynie funkcje dotyczące wyświetla- pamięci przydzielanej dynamicznie. Dla wszystkich funk- void*malloc (size_t size)
cji działających na wskaźnikach, wskaźnik do obszaru Funkcja zajmuje blok pamięci o rozmiarze size. Nie ini-
przydzielonego dynamicznie powinien być widziany i ob- cjuje go żadną wartością. Zwracane wartości jak w calloc.
sługiwany tak samo jak wskaźnik na zwyczajną zmienną. void*realloc (void *p, size_t size)
Listing 212 Inicjacja trybu 256 kolorów LCD O tym, że w AVR-GCC rzeczywiście tak jest, mogliśmy Umożliwia zmianę rozmiaru (zarówno w górę, jak
lcd_Command(LCD_COLMOD); przekonać się przy okazji części 8 gdy stosowaliśmy sztucz- i w dół) bloku pamięci przydzielonego wcześniej. Zwraca
lcd_Data(LCD_COL8bpp); kę aby utworzyć w sposób „statyczny” zmienną FILE. wskaźnik na nowy blok danych. Zależnie od implementa-
// Paleta dla trybu 8bpp Działanie cji może być równy p albo może wskazywać inny blok.
lcd_Command(LCD_RGBSET);
// R W języku C pamięć dynamiczna umieszczona jest na Tak czy inaczej dane zawarte w p znajdują się w obszarze
lcd_Data(0x00); // 0 „stercie”. Sterta ta rośnie przy zajmowaniu kolejnych blo- wskazywanym przez zwracaną wartość. Jeśli rozmiar blo-
lcd_Data(0x02); // 1 ków pamięci. To czy sterta ma możliwość zmniejszania ku jest zwiększany, dodany obszar nie jest inicjowany –
lcd_Data(0x04); // 2
lcd_Data(0x06); // 3
swojej objętości w przypadku zwalniania pamięci zależy zawiera przypadkowe dane.
lcd_Data(0x08); // 4 od implementacji. Tak samo jak problem fragmentacji pa- Funkcja może zwrócić NULL. Oznacza to, że zmiana
lcd_Data(0x0A); // 5 mięci. Można go uniknąć w dużych komputerach z MMU rozmiaru nie może być wykonana. Może być to spowodo-
lcd_Data(0x0C); // 6 (jednostkami zarządzania pamięcią). Jednak jak wyniknie wane przykładowo brakiem pamięci. W takim przypadku
lcd_Data(0x0F); // 7
// G z następnej ramki, w przypadku prościutkiego AVR’a mu- zawartość bloku p nie ulega zmianie.
Identycznie jak dla R simy o tym pamiętać. free (void* p)
// B Obsługa Zwalnia pamięć przydzieloną dynamicznie. Parametr p
lcd_Data(0x00); // 0
lcd_Data(0x03); // 1 Do obsługi pamięci dynamicznej służą cztery funkcje musi być wskaźnikiem zwróconym przez calloc albo mal-
lcd_Data(0x07); // 2 z biblioteki standardowej – konieczne jest dołączenie loc. W innym przypadku zachowanie funkcji jest nieokre-
lcd_Data(0x0F); // 3
pliku <stdlib.h>. ślone. Nie zwraca wartości.

wcześniej, są one łączone w jeden większy blok pamięci. Fragmentacja


Jak to robi GCC
Jeśli teraz wywołamy funkcję malloc lub calloc, naj- Przedstawione rozwiązanie nie omija całkowicie problemu
Dynamiczne przydzielanie pamięci pierw przeszukana zostanie lista zwolnionych bloków. Je- fragmentacji pamięci. Problem polega na tym, że możemy
śli zostanie odnaleziony taki, który swoim rozmiarem do- posiadać wystarczającą ilość pamięci, aby umieścić w niej
Szczegóły implementacji kładnie odpowiada naszemu żądaniu, jest on wyłączany naszą daną, jednak brak nam jednego ciągłego obszaru o od-
Zaimplementowanie funkcji dynamicznego przydzielania z łańcucha zwolnionych bloków i wskaźnik do niego zo- powiedniej wielkości. Wynika z tego, że bezpieczniejsze jest
pamięci w mikrokontrolerze, który nie jest wyposażony stanie zwrócony. W innym przypadku odbywa się prze- alokowanie na przykład tylko bloków o jednym określonym
w jednostkę zarządzania pamięcią oraz którego zasoby pa- szukiwanie wszystkich bloków pod kątem najbardziej pa- rozmiarze... dalej nie będziemy zagadnienia rozwijać. Ko-
mięciowe są raczej skromne, to nie lada wyzwanie. Twór- sującego do żądania. Znaleziony blok jest dzielony na nieczne jest jednak zdawanie sobie sprawy z jego istnienia.
com AVR-GCC udała się ta sztuka... i to udała się całkiem dwie części – zajętą i taką, która pozostaje wolna. Jednak
zgrabnie. w przypadku gdyby wolna część była mniejsza niż 2 baj- Konfiguracja obszaru sterty
Każdy tworzony blok pamięci poprzedzony jest dwo- ty, potrzebne do zapisania wskaźnika na następny wolny z poziomu kompilatora
ma bajtami zawierającymi informację o jego rozmiarze. blok, jest ona dołączana do zajmowanego bloku. Jeśli Sterta zaczyna okupować pamięć dopiero z chwila pierw-
Ważne jest więc, aby żadna z funkcji nie nadpisała infor- i w tym przypadku nic nie znaleziono, odbywa się powięk- szego wykorzystania funkcji malloc albo calloc. Wcze-
macji znajdujących się przed zwracanym przez malloc szanie sterty. Jeśli okaże się to niemożliwe, pamięć nie jest śniej możemy ją odpowiednio skonfigurować.
i calloc wskaźnikiem, ponieważ tak „uszkodzony” blok zajmowana i funkcja zwraca NULL. W niebieskiej ramce o konfiguracji sekcji w pamięci
nie będzie nadawał się do zwolnienia funkcją free. Funkcja realloc została napisana w taki sposób, aby RAM znajduje się obrazek pokazujący domyślne położenie
Zwolniony blok wprowadzany jest do specjalnej listy. wykonać swoje zadanie w jak najprostszy sposób. Jeśli sterty. Wskaźniki __malloc_heap_start, __malloc_he-
Lista ta nie jest oddzielną strukturą, a raczej składa się blok jest zmniejszany, sprawa jest prosta: jego rozmiar się ap_end i __malloc_heap_margin, zaznaczone na zielono,
z samych wolnych bloków, tworząc specyficzny łańcuch. zmniejsza, a pozostała część jest dopisywana do listy ele- są tak naprawdę zmiennymi umieszczonymi w pamięci pro-
Taka implementacja pozwoliła na redukcję zapotrzebowa-
mentów zwolnionych. Jeśli rozszerzany blok znajduje się cesora. Pojawiają się one, tylko jeśli pamięć dynamiczna
nia na pamięć. Konieczne jest jedynie zapamiętanie
wskaźnika do pierwszego wolnego bloku. Ideę prezentuje na końcu sterty, podejmowana jest próba jej powiększenia. jest używana. Za ich pomocą możemy przesunąć stertę,
rysunek B (należy traktować go raczej poglądowo). Jeśli to się uda, operacja zostanie zakończona. Jeśli za roz- ustawiając je na przykład w funkcji main. Musimy zrobić
Gdy zwalniany blok leży zaraz obok bloku zwolnionego szerzanym blokiem znajduje się blok wcześniej zwolnio- to przed pierwszym odwołaniem do pamięci dynamicznej.
ny o wystarczającym rozmiarze, jest on do- Uwaga: jeśli przesuniemy początek sterty za obszar
łączany do bloku powiększanego. Jeśli te stosu, konieczne jest odpowiednie ustawienie końca. Przy
metody się nie powiodą, wykonywana jest domyślnym ustawieniu końca na 0, program będzie spraw-
próba zajęcia nowego obszaru pamięci, dzał, czy jej wierzchołek nie znalazł się zbyt blisko stosu,
skopiowania do niego danych i zwolnienia podczas gdy obszar stosu już dawno został przekroczony!
starego obszaru. Ostatecznie niepowodze-
nie i tej próby powoduje Konfiguracja obszaru sterty
zwrócenie wartości NULL. z poziomu linkera
Sama idea dynamicz- Znacznie bardziej elegancką metodą jest wykorzystanie
nego przydzielania pamięci faktu, że kompilator automatycznie ustawia interesujące
w AVR-GCC wygląda dość nas zmienne na widoczne na obrazku, przypisane im, sym-
prosto, jednak procesor bole: __heap_start i __heap_end. Wartość kryjącą się pod
musi przeanalizować sporo tymi symbolami możemy zmienić przez odpowiednią
przypadków. W efekcie od- opcję linkera:
powiednie funkcje zajmują —defsym=__heap_start=0x802000
trochę pamięci programu. (pamiętaj o konieczności dodania znaków „Wl,”).

44 E l e k t ro n i k a d l a Ws z y s t k i c h
Moduł imglist ment. To ciekawa konstrukcja. Jednak w na-
Listing 218 Dodawanie elementu do listy
Nasze dynamiczne obrazki będą obsługiwane szym przypadku zwracanie właśnie wskaźni-
przez dodatkowy moduł nazwany imglist. ka na wskaźnik daje ogromne zalety: Zobacz, int imgl_AddImg(uint8_t sx, uint8_t sy)
{
Idea jest podobna, jak przedstawiona w ramce jak intuicyjna staje się funkcja dodawania ele- IMGLIST_DATA *pdata=NULL;
o dynamicznym przydzielaniu pamięci lista mentu do listy, widoczna na listingu 218. Sta- IMGLIST_DATA **pplast;
int index;
wolnych bloków. Spójrz na listing 215. Za- ła IMGL_ACCESLAST_INDEX ma po pro- // Sprawdzenie rozmiaru
if(sx == 0 || sy == 0)
warta tutaj struktura zawiera wskaźnik na na- stu bardzo dużą wartość. Sprawia to, że funk- return -2;
stępną strukturę opisującą kolejny obrazek. cja przeszukująca zatrzyma się na ostatnim // Zajmowanie pamięci
pdata = malloc(
W ostatnim obrazku pole to jest ustawione na elemencie tablicy. Zwrócony przez nią wskaź- (uint16_t)sx*(uint16_t)sy
NULL, co oznacza koniec danych. nik wskazuje albo na nasz wskaźnik początku + sizeof(IMGLIST_DATA));
if(pdata == NULL)
W strukturze tej korzystamy z tablicy nie- tablicy (jeśli brak w niej elementów), albo na return -1;
kompletnej, o której mowa w odpowiedniej pole pnext ostatniego obrazka. Niezależnie od // Ustawienie zmiennych struktury
pdata->
>pnext = NULL;
ramce. tego, z którym przypadkiem mamy do czynie- pdata->
>sx = sx;
Jedyną zmienną zdeklarowaną na stałe nia, dopisanie nowego elementu odbywa się pdata->
>sy = sy;
// Pobieram wskaźnik koniec
w module, jest wskaźnik początku listy w identyczny sposób. pplast =
(pierwszego obrazka), widoczny na listingu imgl_Access(
Listing 217 Przeszukiwanie listy IMGL_ACCESLAST_INDEX, &index);
216. *pplast = pdata;
Sercem modułu jest, widoczna na listingu static IMGLIST_DATA** imgl_Access
return index;
(int index, int* pindex)
217, funkcja przeszukująca listę i zwracająca { }
wskaźnik do wskaźnika na znaleziony ele- int n=0;
// Wskaźnik na aktualny obiekt
IMGLIST_DATA **ppdata = &imgl_pStart; Listing 219 Usuwanie elementu z listy

Listing 215 Struktura opisująca obrazek // Poszukiwanie int imgl_DeleteImg(int index)


while(index > 0 && *ppdata != NULL) {
typedef struct IMGLIST_DATA { IMGLIST_DATA **ppdata =
{ ppdata = &((*ppdata)->
>pnext); imgl_Access(index, NULL);
// Uchwyt do kolejnej listy —index; IMGLIST_DATA *pnext;
struct IMGLIST_DATA *pnext; ++n; // Sprawdzam czy element istnieje
// Wymiary obrazka } if(*ppdata == NULL)
uint8_t sx, sy; return -1;
// Dane obrazka if(pindex != NULL) // Zapamiętuję element następny
uint8_t data[]; *pindex = n; // oraz usuwam aktualny
}IMGLIST_DATA; pnext = (*ppdata)->
>pnext;
return ppdata; free(*ppdata);
} *ppdata = pnext;
Listing 216 Wskaźnik na pierwszy obrazek
return 0;
static IMGLIST_DATA* imgl_pStart = NULL; Idea rozwiązania }

Bezpieczna funkcja rs_get


ABC... C
Idea rozwiązania
Funkcja rs_get z naszej biblioteki ma to do siebie, że po- Tablica o typie niekompletnym
woduje zatrzymanie całego programu do czasu odebra-
Interpretacja i obsługa komend nia znaku przez port szeregowy. Stwarza to ryzyko, że Tablica o typie niekompletnym informuje kompilator je-
gdy transmisja zostanie przerwana, na przykład przez dynie o fakcie swojego istnienia. Kompilator nie jest
Trzymając się zasady utworzonego modułu systemu,
nieprawidłowe działanie komputera, spowoduje to także w stanie określić jej rozmiaru. Tworzymy ją przez pozo-
mówiącej, że zdarzenie wyłapywane jest w przerwa-
„zawieszenie” działania programu. Jeśli komputer zosta- stawienie pustych nawiasów kwadratowych i nie inicju-
niach, a obsługiwane w pętli głównej przy pośrednictwie
nie później uruchomiony, przesyłane przez niego ko- jąc tablicy (w przypadku inicjacji jej rozmiar jest obli-
kolejki komunikatów, stworzony został specyficzny spo-
mendy mogą być traktowane jako dalszy ciąg przesyła- czany automatycznie). W naszym przypadku tablicę ta-
sób obsługi komend przychodzących do układu przez
nych poprzednio danych. ką tworzymy na końcu struktury zawierającej dane ob-
port rs.
Możemy temu zapobiec w prosty sposób, wprowa- razka. Jeśli teraz odwołamy się do elementu naszej tabli-
Do modułu system dodane zostały dwie funkcje sys-
dzając czas, po jakim funkcja ma zakończyć swoje dzia- cy, zostanie wykonany dostęp do pamięci zaraz za struk-
tem_enableUARTCommand oraz system_disableUART-
łanie, jeśli znak nie został odebrany. Funkcja w takim turą. Musimy zadbać o to, aby w miejscu tym znajdowa-
Command. Ich zadaniem jest aktywacja lub wyłączenie
przypadku zwróci wartość ujemną, a konkretnie predefi- ły się odpowiednie dane. Zrobimy to, rezerwując na na-
przerwania odebrania znaku przez odpowiednie ustawie-
niowaną w pliku <stdio.h> stałą _FDEV_ERR. szą strukturę pamięć o rozmiarze równym sumie jej
nie bitu RXCIE0 w rejestrze UCSR0B. Funkcja obsługi
Proponowana implementacja korzysta z funkcji wielkości oraz ilości danych. Operator sizeof poda roz-
przerwania została napisana w taki sposób, że jeśli ode-
opóźnień oferowanych przez moduł system. Dodatkowa miar struktury bez niekompletnej tablicy.
brany zostanie znak odpowiadający jednej z komend, do
funkcja rs_settimeout pozwala ustawić żądany czas Ważne jest to, że w strukturze tablica taka musi zna-
kolejki dodana zostanie odpowiedni komunikat, a samo
opóźnienia z rozdzielczością 10ms. Wybranie wartości leźć się na jej końcu. Spróbuj dodać za nią jakąś zmien-
przerwanie zostanie wyłączone. Teraz program użyt-
0 oznaczać będzie wyłączenie opcji przerywania funkcji ną i wtedy skompilować program. Nie uda się to, ponie-
kownika może swobodnie obsłużyć zdarzenie, a po jego
po określonym czasie. waż kompilator nie jest w stanie określić położenia do-
zakończeniu ponownie uruchomić przerwanie, włącza-
Zmienioną funkcję rs_get pokazuje listing 214. danej zmiennej, nie znając rozmiaru zmiennej leżącej
jąc tym samym systemowy interpretator komend. Frag-
przed nią.
ment funkcji obsługi omawianego przerwania pokazuje
Przykład: listing 215
listing 213. Stała COMMAND_ADD jest definiowana Listing 214 Bezpieczna funkcja rs_get
w pliku nagłówkowym.
int rs_get(void)
{ Podobnie ma się sprawa z widocznym na
Listing 213 Interpretacja komend char znak; listingu 219 usuwaniem elementu. Zobacz,
system_delaySet(rs_timeout);
ISR(USART0_RXC_vect) // Oczekiwanie na daną w jaki sposób z łańcucha usuwane jest wybra-
{
uint8_t znak = UDR0;
while(!(1<<RXC0 & UCSR0A)) ne ogniwo, a jednocześnie element następny
{
switch(znak) if(rs_timeout != 0) jest dołączany do poprzedniego.
{ if(system_delayGet()==0)
case COMMAND_ADD:
system_msgPut(IDM_CMD_ADD); }
return _FDEV_ERR;
Uruchomienie
system_disableUARTCommand();
break;
znak = UDR0; Jeśli po samym napisaniu kodu uruchomisz
return znak;
... } program, okaże się, że jest on w stanie przyjąć
tylko bardzo małe obrazki. Jest to związane

E l e k t ro n i k a d l a Ws z y s t k i c h 45
Programowanie

on używany. Jeśli symbol pojawia się gdzie indziej, linker


dy, nawet jeśli wydaje się na pierwszy rzut
ABC... GCC będzie z niego korzystał. W przypadku zapisu zwykłego oka skomplikowana. Na dłuższą metę jest jed-
Skrypty linkera przypisania: __heap_start = . ; nak wygodniejsza.
próba ustawienia z poziomu makefile symbolu __he-
Skrypty linkera opisują sposób w jaki sekcje są łączone, ap_start zakończyłaby się komunikatem o błędzie. Podsumowanie
gdzie są one umieszczane i jak wyrównywane. Ponadto Domyślne skrypty kompilacji znajdują się w katalogu
C:\WinAVR\avr\lib\ldscripts. Interesują nas pliki z rozsze-
W dzisiejszej części poznaliśmy działanie ze-
w tym miejscu ustawianych jest sporo symboli, które póź-
rzeniem. x, które dotyczą generowania typowych plików wnętrznej pamięci danych. Jednocześnie wy-
niej wpływają na działanie programu. Nie będziemy oma-
wiać tutaj dokładnie działania tych skryptów. Zapoznamy wyjściowych. Znajdziemy tutaj pięć plików, od avr1.x do czerpaliśmy tematykę sekcji pamięci. Poznali-
się z nimi na tyle aby korzystać z nich w najprostszy spo- avr5.x. Wybór odpowiedniego ułatwi rozdział 7.10 in- śmy możliwości dynamicznej alokacji pamięci
sób – odpowiednio lokalizując stertę względem naszej strukcji avr-libc „Using the GNU tools”. Procesor ATme- – coś, co może brzmi skomplikowanie i w su-
sekcji exram. Język skryptów linkera jest częściowo zbli- ga162 posiada architekturę avr5 tak więc kopiujemy plik
mie działa w skomplikowany sposób, jednak od
żony do C, a jego dokładny opis powinien znajdować się avr5.x do katalogu z programem. Proponuję zmienić jego
nazwe na avr5_exram.x. strony programisty całość ogranicza się do uży-
na Twoim dysku pod następującą ścieżką: C:\Wi-
nAVR\doc\binutils\ld. Wprowadzoną modyfikację pokazuje listing 221. wania czterech instrukcji. Ostatecznie mam na-
Oto maleńka garść potrzebnych nam informacji o je- Elementy szare pozostają bez zmian. Wprowadzamy no- dzieję, że zainteresowałem Cię skryptami linke-
go składni: wą sekcję. Jej adres ustawiamy zaraz za. noinit. Jest to tyl- ra, które dają już bardzo duże możliwości kon-
Komentarze – piszemy je identycznie jak w C: mię- ko formalność – i tak przesuniemy ją, z poziomu pliku
troli tego, co dostanie się do pliku wynikowego.
dzy znakami /* */. makefile, na właściwe miejsce. Jednak to co najważniej-
sze zaznaczyłem kolorem żółtym: Zaprzęgamy linker do Jednocześnie aktualna część wyczerpała
Operator kropki – oznacza licznik lokacji. Używając
go posługujemy się jakby adresem jaki będzie w danym odpowiedniego ustawienia początku sterty. już prawie cały założony zakres materiału
miejscu. Można go także ustawiać, jednak w rozbudowa- Wybór, innego niż domyślny, sktyptu kompilacji do- kursu. Najbliższa część będzie już luźniejsza,
nych skryptach używa się, zamiast tego, innych metod. konujemy opcją linkera: -Tścieżka_do_pliku przedstawię kilka rzadziej stosowanych funk-
PROVIDE – ustawienie symbolu tylko jeśli nie jest Całą potrzebną opcję pokazuje listing 222.
cji i podsumujemy zebrany materiał.
Na stronie Elportalu znajdą się, poza listingami,
Listing 220 Przesunięcie sterty pod określony adres dwa programy przygotowane na komputer PC.
LDFLAGS += -Wl,—defsym=__heap_start=0x802000,—defsym=__heap_end=0x807fff Służą one do współpracy z naszą płytką wykonu-
jącą dzisiejsze programy. Ponadto znajdziesz tam
z tym, że nasza sterta cały czas znajduje się padku musimy zadbać o to, aby sterta znalazła film prezentujący generowany płomień. Zachęcam
w pamięci wewnętrznej procesora. Proponuję się w pamięci zewnętrznej za danymi sekcji więc do odwiedzin.
ją przesunąć na jeden z dwóch sposobów – je- .exram. Adres 0x2000 jest ustawiony Radosław Koppel
den polega na dopisaniu do pliku makefile li- z pewnym zapasem, jednak jeśli dodamy radoslaw.koppel@elportal.pl
nii widocznej na listingu 220. W tym przy- do tej sekcji nowe da-
ne, musimy pa-
K R Z Y Ż Ó W K A
Listing 221 Modyfikacja skryptu linkera ustawiająca stertę miętać o „ręcz-
.noinit SIZEOF(.bss) + ADDR(.bss) : nym” sprawdze-
{ niu, czy nie poja-
PROVIDE (__noinit_start = .) ;
*(.noinit*) wia się potrzeba
PROVIDE (__noinit_end = .) ; przesunięcia ster-
_end = . ;
/* PROVIDE (__heap_start = .) ;*/ ty. Rozwiązanie
} > data w pełni automa-
.exram SIZEOF(.noinit) + ADDR(.noinit) :
{ tyczne pokazuje
PROVIDE (__exram_start = .) ; ramka o skryp-
*(.exram*)
PROVIDE (__exram_end = .) ; tach linkera.
PROVIDE (__heap_start = .) ;
} > data
Spróbuj tej meto-

Listing 222 Przesunięcie sterty przy pomocy skryptu linkera


LDFLAGS += -Wl,-Tavr5_exram.x,—defsym=__heap_end=0x807fff

R E K L A M A

Odgadnięte hasła należy dopasować do odpowiednich rzędów w krzy-


żówce. Dla ułatwienia podane zostały niektóre litery. Hasło odczytuje-
my z zaznaczonej kolumny.
Lampa obrazowa, nie tylko w telewizorze.
Bipolarny lub unipolarny.
Jeden z półprzewodników.
Np. prostownicza.
Jednostka pojemności.
Kondensator dostrojczy.
Element liczący.
Element całkujący.
Płynie w przewodach.
Dwukierunkowy tyrystor.
Dioda świecąca.

Autorem krzyżówki jest Grzegorz Tarnawa z Godziszki.

46 E l e k t ro n i k a d l a Ws z y s t k i c h

You might also like