Elektronika dla Wszystkich
37
Programowanie
Kontynuujemy tworzenie programu obs³ugi
wywietlacza multipleksowanego. Tak jak obie-
ca³em, doprowadzimy teraz program do pe³nej
funkcjonalnoci. Zmienimy elementy marnuj¹-
ce cenne zasoby. Wa¿ne jest jednak to, aby wie-
dzia³, ¿e bêdziemy musieli w tym celu odejæ od
standardu ANSI C. W standardzie tym nie
wystêpuje twór taki jak przerwania oraz oddziel-
na pamiêæ danych i programu. Jedyne, co w tym
zakresie zosta³o zdefiniowane, to fakt, ¿e wszyst-
kie nietypowe problemy twórca kompilatora
powinien... rozwi¹zaæ we w³asnym zakresie.
Wiêcej o plikach
nag³ówkowych
Do tej pory niewiele pisa³em o tytu³owych
plikach. Nag³ówki do³¹czalimy do ka¿dego
z tworzonych plików. Przypomnê, ¿e prak-
tycznie ka¿dy program posiada³ dyrektywê
#include <avr\io.h>. W poprzedniej czêci
zaczêlimy korzystaæ tak¿e z dobrodziejstw,
jakie daje nam plik <inttypes.h>. Z dyrekty-
wy #include bêdziemy korzystaæ coraz inten-
sywniej, dlatego te¿ jest to najwy¿sza pora na
jej dok³adniejsze opisanie. Informacje na ten
temat znajduj¹ siê w ramce u do³u strony.
AVR-GCC przerwania
Zanim zabierzemy siê do dalszych przeróbek
naszego kodu, powiêæmy chwilê na prze-
mylenie, gdzie umiecimy obs³ugê naszego
wywietlacza. Na pewno bêdzie to jedno
z przerwañ zegara licznika. Warto w celu
jego wybrania zerkn¹æ do dokumentacji opi-
suj¹cej procesor AT90S2313. Uk³ad ten ma
dwa liczniki. Prosty omiobitowy oraz bar-
dziej rozbudowany o rozdzielczoci szesnastu
bitów.
P
P
r
r
o
o
g
g
r
r
a
a
m
m
o
o
w
w
a
a
n
n
i
i
e
e
p
p
r
r
o
o
c
c
e
e
s
s
o
o
r
r
ó
ó
w
w
w
w
j
j
ê
ê
z
z
y
y
k
k
u
u
C
C
Czêæ 3
ABC... C
Dyrektywa
#include
i pliki nag³ówkowe
Jak wszystkie komendy zaczynaj¹ce siê zna-
kiem hash (#), tak te¿ i #include nie jest
instrukcj¹ jêzyka C. Jest to komenda prze-
znaczona dla kompilatora, steruj¹ca jego
prac¹.
#include <plik.h>
Gdy kompilator natknie siê na wspomnian¹
dyrektywê, wstawi w jej miejsce odpowiedni
plik. Uwa¿aj teraz: wa¿ne jest to, jakimi zna-
kami otoczymy nazwê pliku. Do tej pory ca³y
czas stosujemy zapis <plik.h>. Dziêki temu
kompilator wie, ¿e chcemy do³¹czyæ jeden ze
standardowych nag³ówków. Wyjciowym
folderem, w którym odbywa siê w takim
przypadku poszukiwanie pliku, jest
C:\WinAVR\avr\include. Jeli jeszcze tego
nie zrobi³e warto aby tam zajrza³ i zoba-
czy³, jakie standardowe pliki oferuje GCC.
Przejrzyj, co znajduje siê w ich wnêtrzu.
Nie przejmuj siê, jeli wydaj¹ siê teraz niesa-
mowicie zagmatwane. W praktyce posiadaj¹
one bardzo rozbudowane komentarze, które
bywaj¹ lepsz¹ dokumentacj¹ ni¿ znajduj¹cy
siê w katalogu C:\WinAVR\doc\avr-libc pros-
ty manual. Zwróæ uwagê tak¿e na to, ¿e
w plikach tych czêsto znajduje siê do³¹cze-
nie innych plików. Mo¿esz spróbowaæ prze-
ledziæ, jak odbywaj¹ siê kolejne do³¹cze-
nia.
Wielokrotnie do³¹czaj¹c plik nag³ówka,
odwo³ywalimy siê do podkatalogu avr.
W miejscu tym zosta³y umieszczone pliki,
których standard C w ¿aden sposób nie
ujmuje i s¹ one cile przynale¿ne do zasto-
sowanych procesorów. Zajrzyj do naszego
niezbêdnika pliku io.h. Jest to plik na
pierwszy rzut oka doæ skomplikowany.
Okazuje siê jednak, ¿e jego rdzeñ stanowi
warunkowe do³¹czenie innych plików, zale¿-
nie od wybranego typu procesora. Zajrzyj do
pliku io2313.h. Tutaj w³anie znajduj¹ siê
najwa¿niejsze z naszego punktu widzenia
definicje rejestrów, wektorów przerwañ
i nazw poszczególnych bitów naszego proce-
sora. Czasami warto tu zajrzeæ, przede
wszystkim, jeli poszukujemy nazwy jakie-
go rejestru, do którego w³anie chcemy siê
odwo³aæ.
W oddzielnej ramce umieci³em skrótowe
informacje o najbardziej nas interesuj¹cych
plikach znajduj¹cych siê w tym folderze.
#include plik.h
Nazwa pliku mo¿e byæ tak¿e umieszczona
miêdzy znakami cudzys³owiu. W takim przy-
padku poszukiwanie odpowiedniego pliku
bêdzie siê odbywaæ z poziomu folderu,
w którym znajduj¹ siê pliki naszego progra-
mu. Ma to niebagatelne znaczenie w chwili
pisania du¿ego programu oraz jego rozbijania
na wiele plików. Jest tak¿e wa¿ne, jeli chce-
my do³¹czyæ po prostu jaki swój plik, czy to
z w³asnymi, uniwersalnymi funkcjami, czy
te¿ pobrany z sieci.
Z mo¿liwoci tej nie bêdziemy dzi jesz-
cze korzystaæ, jednak warto ju¿ teraz wie-
dzieæ, ¿e taka mo¿liwoæ istnieje.
<avr\io.h>
Plik, który powinien byæ do³¹czony do ka¿dego programu. Definiuje adresy rejes-
trów procesora, w tym jego porty. Dziêki temu mo¿emy odwo³aæ siê do nich za pomoc¹ sta-
³ych symbolicznych. To, jakie rejestry zostan¹ zdefiniowane, zale¿y od typu wybranego mik-
rokontrolera wskazanego w pliku makefile.
<inttypes.h>
Bardzo wa¿ny plik, który czêsto bêdzie w u¿yciu w naszych programach
definiuje proste nazwy dla zmiennych ca³kowitych. Wiêcej szczegó³ów znajduje siê
w ramce o zmiennych (pojawi³a siê w czêci 2).
<avr\interrupt.h>
Umo¿liwia wykorzystanie kilku prostych funkcji dotycz¹cych przerwañ,
wród nich sei() i cli() globalne odblokowanie i zablokowanie przerwañ.
<avr\signal.h>
Umo¿liwia proste tworzenie funkcji obs³ugi przerwañ.
<avr\pgmspace.h>
Upraszcza korzystanie ze zmiennych umieszczonych w pamiêci progra-
mu.
<avr\eeprom.h>
Zawiera funkcje obs³ugi pamiêci eeprom.
<avr\wdt.h>
Kilka pomocniczych funkcji obs³ugi timera watchdog.
<avr\delay.h>
Proste (a tak¿e niedok³adne) funkcje realizuj¹ce opónienia.
Najczêciej stosowane, standardowe pliki nag³ówkowe
udostêpniane przez AVR-GCC
Obs³uga wywietlacza wymaga od nas
wysterowania ka¿dej cyfry tak, aby niedo-
strzegalne by³o migotanie ca³ego pola odczy-
towego. Za granicê widocznego przez cz³o-
wieka migotania uznaje siê oko³o 50Hz.
Ka¿de przerwanie powodowaæ bêdzie wy³¹-
czenie bie¿¹cego wywietlacza i uruchomie-
nie nastêpnego. Bior¹c pod uwagê, ¿e wy-
wietlaczy mamy 4 dochodzimy do wnios-
ku, ¿e musimy odpowiedni¹ procedurê wywo-
³aæ co najmniej 200 razy w ci¹gu sekundy.
Oczywicie nie jest tutaj wa¿na du¿a do-
k³adnoæ odmierzenia czasu. Jeli procedura
obs³ugi wywietlacza bêdzie wywo³ywana
nawet 500 razy na sekundê, spowoduje to
tylko wiêksze obci¹¿enie procesora, a jedno-
czenie mniej zauwa¿alne migotanie wy-
wietlacza. Ostatecznie pod koniec ca³ego po-
wy¿szego toku rozumowania mo¿emy stwier-
dziæ, ¿e ca³kowicie wystarczy nam zastoso-
wanie prostszego timera. Bardziej rozbudo-
wany pozostawiamy w celu jego ewentualne-
go wykorzystania do bardziej ambitnych
zadañ. Ze wzglêdu natomiast na prostotê
wspomnianego licznika, wyboru w przerwa-
niach nie mamy bêdzie to przerwanie
wywo³ywane w momencie przepe³nienia licz-
nika.
Koniecznie zapoznaj siê teraz z ramk¹
mówi¹c¹ o tym, jak w naszym kompilatorze
wygl¹da obs³uga przerwañ. Tabela 2 prezen-
tuje nazwy przerwañ procesora AT90S2313.
Wszystkie nazwy, jakie udostêpnia nasz kom-
pilator, mo¿na odnaleæ w manualu (przypo-
minam: C:\WinAVR\doc\avr-libc). Z mojego
dowiadczenia wynika jednak, ¿e znacznie
wygodniej jest zajrzeæ do odpowiedniego
pliku definiuj¹cego wszelkie sta³e symbolicz-
ne dla wykorzystanego procesora. W naszym
przypadku bêdzie to C:\WinAVR\avr\inclu-
de\avr\ io2313.h. Listing 10 pokazuje, jak
wygl¹da definicja wektorów przerwañ. W ten
sposób czêsto ³atwiej znaleæ interesuj¹c¹ nas
nazwê ni¿ za pomoc¹ przedzierania siê przez
dokumentacjê, w której brak podzia³u na
poszczególne procesory.
Uzbrojeni w wiedzê o przerwaniach mo¿e-
my podj¹æ siê ju¿ zadania napisania naszego
kodu z ich wykorzystaniem. Spójrz na listing
11. Przedstawi³em na nim prawie kompletny
kod naszego programu. Program zosta³ napi-
sany na bazie tego, do czego uda³o nam siê
dojæ w poprzedniej czêci. Spójrz na linie
oznaczone na szaro. S¹ to linie, które nie uleg-
³y zmianom. Mo¿e zdziwi Ciê wyszarzenie
wnêtrza naszej funkcji obs³ugi przerwania?
Zrobi³em to specjalnie. Zauwa¿, ¿e zawarty
tutaj kod to nic innego jak poprzednia zawar-
toæ naszej pêtli g³ównej! Dziêki temu, ¿e
wykorzystujemy sprawdzony ju¿ fragment
kodu, unikamy pracy oraz zmniejszamy mo¿-
liwoæ pomy³ki.
Zanim wiêc zaczniesz pisaæ nowy pro-
gram, warto oszczêdziæ sobie pracy i przyj-
rzeæ siê co tak naprawdê jest do zrobienia
w stosunku do jego wersji z poprzedniego
miesi¹ca. Jeli chcesz zachowaæ kopiê po-
przedniego programu, skopiuj go teraz. Na-
stêpnie otwórz go w Programmers Notepa-
dzie. Tak naprawdê konieczne jest jedynie
dodanie dwóch plików nag³ówkowych poma-
gaj¹cych w obs³udze przerwañ. Konieczne
jest dopisanie trzech linii inicjacji, utworzenie
szkieletu funkcji obs³ugi przerwania oraz
przeniesienia do jej cia³a wnêtrza
naszej pêtli g³ównej. Sam¹ (pust¹
ju¿) pêtlê g³ówn¹ proponujê zosta-
wiæ. Zmiany opisa³em tutaj bardzo
skrótowo, ale jeli podeprzesz siê
jeszcze listingiem 11 wprowadze-
nie odpowiednich zmian nie bêdzie
trudne.
Po skompilowaniu programu
i jego wys³aniu do naszego uk³adu tes-
towego powinnimy ujrzeæ znajomy
nam widok wywietlane kolejne
cyfry na wywietlaczach. Niektórzy
!&
Programowanie
Elektronika dla Wszystkich
Listing 10 Definicja nazw przerwañ w pliku io2313.h
/* Interrupt vectors */
#define SIG_INTERRUPT0 _VECTOR(1)
#define SIG_INTERRUPT1 _VECTOR(2)
#define SIG_INPUT_CAPTURE1 _VECTOR(3)
#define SIG_OUTPUT_COMPARE1A _VECTOR(4)
#define SIG_OVERFLOW1 _VECTOR(5)
#define SIG_OVERFLOW0 _VECTOR(6)
#define SIG_UART_RECV _VECTOR(7)
#define SIG_UART_DATA _VECTOR(8)
#define SIG_UART_TRANS _VECTOR(9)
#define SIG_COMPARATOR _VECTOR(10)
#define _VECTORS_SIZE 22
ABC... C
Obs³uga przerwañ w AVR-GCC
Przypominam jeszcze raz: standard ANSI C
nie definiuje sposobu obs³ugi przerwañ. To,
o czym bêdziemy dalej mówiæ, dotyczy ci-
le kompilatora zastosowanego w pakiecie
WinAVR.
Tworzenie funkcji obs³ugi przerwania
znakomicie upraszcza do³¹czenie do progra-
mu pliku <avr\signal.h>. Definiuje on 3
ró¿ne makra. Po jego do³¹czeniu funkcjê
obs³ugi przerwania tworzymy jak to przed-
stawia listing:
Czy¿ nie wygl¹da prosto? Takie w³anie
w istocie jest.
Najczêciej bêdziemy korzystaæ z zaleca-
nego makra SIGNAL. Powoduje ono utwo-
rzenie funkcji obs³ugi w typowy dla AVR-ów
sposób, gdzie w czasie obs³ugi jednego prze-
rwania zerowana jest globalna flaga zezwole-
nia na przerwanie. Nie oznacza to, ¿e proce-
sor jest lepy i g³uchy na inne wo³ania.
Jeli w tym czasie bêdzie zg³oszone ¿¹danie
obs³ugi innego zdarzenia, ustawiona zostanie
odpowiednia flaga i bêdzie ono obs³u¿one,
gdy tylko przerwania zostan¹ odbloko-
wane (nastêpuje to automatycznie przy
koñczeniu obs³ugi aktualnego prze-
rwania).
Czasami jednak chcemy, aby nowe zda-
rzenie mog³o przerwaæ bie¿¹ce zadanie.
Mo¿emy w takim przypadku przy tworzeniu
funkcji obs³ugi s³owo SIGNAL zamieniæ na
INTERRUPT. Kompilator doda wtedy auto-
matyczne odblokowanie przerwañ zaraz po
wywo³aniu jego obs³ugi. Makra tego nale¿y
u¿ywaæ bardzo ostro¿nie. Ka¿de przerwanie
odk³ada na stos pewne informacje. Nieprze-
mylane u¿ywanie makra typu INTERRUPT
mo¿e doprowadziæ do zapchania stosu i nie-
przewidzianego dzia³ania programu.
Pozosta³o nam jeszcze trzecie, rzadziej
stosowane makro. Umo¿liwia ono utworze-
nie tak zwanego pustego przerwania. Wywo-
³ane przerwanie natychmiast zwróci sterowa-
nia do programu g³ównego. Jest to o tyle
wa¿ne, ¿e standardowo wyst¹pienie nieprze-
widzianego przerwania spowoduje restart
ca³ego programu. Puste przerwanie mo¿emy
utworzyæ jak ni¿ej:
Nie nale¿y definiowaæ cia³a funkcji
makro tworzy je automatycznie.
W³¹czanie/wy³¹czanie przerwañ
Plik <avr/interrupt.h> definiuje bardzo przy-
datne funkcje sei() i cli(). Obie t³umaczone s¹
na jedn¹ instrukcjê asemblera o nazwie iden-
tycznej z nazw¹ funkcji. Powoduj¹ odpo-
wiednio: globalne w³¹czenie i globalne wy³¹-
czenie przerwañ.
Istnieje tutaj tak¿e funkcja aktywacji prze-
rwañ liczników. Jednak chocia¿by ze wzglê-
du na kompatybilnoæ z innymi kompilatora-
mi oraz banalnoæ rêcznego realizowania
takiego dzia³ania nie warto z niej korzystaæ.
SIGNAL(Nazwa_przerwania)
{
// Cia³o funkcji
}
EMPTY_INTERRUPT(Nazwa_przerwania);
Tabela 2 Przyk³adowe nazwy przerwañ
dostrzeg¹ jednak migotanie wywietlaczy.
Przyjrzyjmy siê naszemu programowi z jak¹
czêstotliwoci¹ obs³ugujemy wywietlacze?
Dobrze by³oby, gdyby móg³ razem ze mn¹
zajrzeæ do karty katalogowej naszego mikro-
kontrolera. Nasz timer wywo³uje przerwanie
po ka¿dym przepe³nieniu oznacza to prze-
rwanie co 256 zliczonych impulsów. Dodat-
kowo rejestr TCCR0 zosta³ ustawiony w taki
sposób, ¿e licznik zlicza impulsy zegara sys-
temowego, podzielone w preskalerze przez
64. Jak ³atwo obliczyæ:
4MHz/(256*64) = 244Hz
Przy czterech wywietlaczach otrzymuje-
my czêstotliwoæ odwie¿ania ca³ego pola
rzêdu 60Hz. Niby wiêcej ni¿ granica czêstot-
liwoci, jak¹ cz³owiek zauwa¿a, jednak
w praktyce czêsto tak niska czêstotliwoæ jest
nieprzyjemna w odbiorze. Mo¿na spróbowaæ
j¹ podwy¿szyæ, zmniejszaj¹c stopieñ preska-
lera. Znów zagl¹damy do dokumentacji. Oj
niedobrze... najbli¿szy, ni¿szy stopieñ jest 8
razy mniejszy. Mo¿na zobaczyæ, jaki bêdzie
efekt. Zamieñ pierwsz¹ linijkê inicjacji timera
na:
TCCR0 =
1
<<CS01;
Migotanie rzeczywicie ca³kowicie usta³o.
Warto zobaczyæ, jak takie przyspieszenie
wywietlacza obci¹¿y³o procesor. Z pomoc¹
przyjdzie nam tutaj AVRStudio. Zak³adam,
¿e jego uruchomienie oraz wczytanie pliku
programu nie sprawia Ci problemów. Jeli
pracujesz na poprzednim kodzie w katalogu
powinien nawet znajdowaæ siê ju¿ plik o roz-
szerzeniu aps, którego dwukrotne klikniêcie
otworzy od razu AVRStudio ze skonfiguro-
wanym symulatorem.
Uruchom teraz na chwilê program w trybie
pracy ci¹g³ej. Zatrzymaj go w dowolnym
momencie za pomoc¹ klawisza pauzy na
pasku narzêdziowym (CTRL+F5). Zale¿y
nam, aby zatrzymaæ program ju¿ po inicjacji
czêci sprzêtowej, w pustej pêtli g³ównej.
Jeli pechowo trafi³e w³anie na obs³ugê
przerwania uruchom program jeszcze na
sekundê i ponownie zatrzymaj. Innym, bar-
dziej eleganckim sposobem zatrzymania pro-
gramu w interesuj¹cym nas punkcie jest usta-
wienie kursora na instrukcji for i wybranie
z paska narzêdzi komendy Run to cursor
(CTRL+F10).
Jest wiele sposobów, ¿eby sprawdziæ czas,
przez jaki bêdzie wykonywaæ siê dana proce-
dura. Jeli jest to podprogram, mo¿na na przy-
k³ad wyzerowaæ licznik czasu tu¿ przed jego
wywo³aniem, kazaæ symulatorowi go prze-
skoczyæ (F10) i ju¿ mamy czas wykonania
procedury. Próbê dobrze jest powtórzyæ kilka-
krotnie i za czas wy-
konania podprogramu
uznaæ otrzymany czas
redni. W naszym
przypadku mamy do
czynienia z przerwa-
niem. Zasada bêdzie
wiêc troszkê zmienio-
na, aczkolwiek ogól-
nie pozostaje zacho-
wana.
Spójrz na rysunek
22. Przedstawi³em na
nim widok prawid³o-
wo skonfigurowane-
go symulatora AVRS-
tudio razem z dodat-
kowymi komentarza-
mi. Spróbuj doprowa-
dziæ do podobnej
sytuacji. Przyjrzyj siê, jakie zak³adki zosta³y
przeze mnie rozwiniête w lewym panelu. Naj-
bardziej interesuje nas licznik cykli. Wykonu-
j¹c kilkakrotnie komendê Run to cursor,
stwierdzimy, ¿e wykonanie pêtli for zajmuje
procesorowi dwa cykle. Zapamiêtujemy tê
wartoæ. Gdy program jest ju¿ zatrzymany,
zerujemy licznik cykli, tak jak opisuj¹ to
komentarze na obrazku. Ustawiamy flagê
symuluj¹c¹ wyst¹pienie przerwania przepe³-
nienia licznika 0 i wykonujemy ponownie
komendê Run to cursor. U mnie licznik wska-
za³ 82 cykle. Wiemy, ¿e wykonanie samej
pêtli i dojcie do kursora dodaje do tej war-
toci dwa cykle aby uzyskaæ dok³adny czas
zajmowany przez przerwanie, nale¿y wartoæ
tê odj¹æ od uzyskanej. Po kilkakrotnej próbie,
raz na 3 przerwania, czas wykonania jego obs³u-
gi wynosi³ 83 cykle. Jest to o tyle oczywiste,
39
Programowanie
Elektronika dla Wszystkich
Listing 11 Obs³uga wywietlacza LED w przerwaniach
#include <avr\io.h>
#include <inttypes.h>
#include <avr\signal.h>
#include <avr\interrupt.h>
(... definicje wyprowadzeñ - bez zmian...)
uint8_t g_AktWyswietlacz =
0
;
uint8_t g_DaneWyswietlacza[
4
];
uint8_t g_DaneCom[
4
] =
{~(
1
<<COM1), ~(
1
<<COM2), ~(
1
<<COM3), ~(
1
<<COM4)};
int
main(
void
)
{
/////////////////////////////
// inicjacja
LEDDDR =
0xff
;
COMDDR =
1
<<COM1 |
1
<<COM2 |
1
<<COM3 |
1
<<COM4;
// Timer0
TCCR0 =
1
<<CS01|
1
<<CS00;
TIMSK =
1
<<TOIE0;
// Globalne zezwolenie na przerwania
sei();
// koniec inicjacji
/////////////////////////////
// Wpisanie do tablicy próbnych wartoci
g_DaneWyswietlacza[
0
] = ~(
1
<<LED_B |
1
<<LED_C);
g_DaneWyswietlacza[
1
] = ~(
1
<<LED_A |
1
<<LED_B |
1
<<LED_D |
1
<<LED_E |
1
<<LED_G);
g_DaneWyswietlacza[2] = ~(1<<LED_A | 1<<LED_B |
1
<<LED_C |
1
<<LED_D |
1
<<LED_G);
g_DaneWyswietlacza[3] = ~(1<<LED_B | 1<<LED_C |
1
<<LED_F |
1
<<LED_G);
for
(;;)
{
}
return
0
;
}
//_____________________________________________
// Obs³uga przerwañ
SIGNAL(SIG_OVERFLOW0)
{
// Wygaszenie wywietlaczy
COMPORT |=
1
<<COM1 |
1
<<COM2 |
1
<<COM3 |
1
<<COM4;
// Wys³anie odpowiedniej danej
LEDPORT = g_DaneWyswietlacza[g_AktWyswietlacz];
// W³¹czenie odpowiedniego wywietlacza
COMPORT &= g_DaneCom[g_AktWyswietlacz];
// Zwiêkszenie stanu zmiennej wskazuj¹cej na
// obs³ugiwany wywietlacz
++g_AktWyswietlacz;
if
(g_AktWyswietlacz >
3
)
g_AktWyswietlacz =
0
;
}
ABC... C
C a format linii
Mo¿e zauwa¿y³e, ¿e w kolejno przedstawianych kodach ród³owych
dzielê liniê praktycznie tam, gdzie jest to dla mnie wygodne, listing
opracowujê tak, aby zmieci³ siê ³adnie w ramce? Wydawaæ by siê
mog³o, ¿e jest to tylko zabieg maj¹cy zmniejszyæ objêtoæ ca³ego tek-
stu, okazuje siê jednak, ¿e jakkolwiek wpisa³by kod wszystko
bêdzie dzia³aæ.
C jest jêzykiem niezale¿nym od formatu linii. Wa¿ne jest to,
aby ka¿d¹ instrukcjê pojedyncz¹ zakoñczyæ znakiem rednika, wa¿ne
jest by zawsze zamkn¹æ otwarty nawias czy klamrê. Natomiast
w wiêkszoci przypadków zupe³nie nie ma znaczenia, gdzie rozpo-
czniemy now¹ liniê, wstawimy znak tabulacji czy wiêcej znaków
spacji. Oznacza to, ¿e zwykle mo¿esz przerwaæ pisanie bie¿¹cej linii
i rozpocz¹æ je w linii nastêpnej. W obliczeniach na przyk³ad mo¿esz
to zrobiæ przed lub po znaku dzia³ania.
W rzadkich przypadkach, w których koniec linii ma znaczenie
(przyk³adem jest dyrektywa #define), a linia staje siê nieznonie
d³uga, mo¿na zastosowaæ na koñcu znak ukonika (\). Spowoduje to,
¿e kompilator potraktuje liniê nastêpn¹ jako przed³u¿enie poprzed-
niej.
¿e dok³adnie co taki czas zmienna g_AktWys-
wietlacz jest zerowana. Ju¿ teraz zapewne
domylasz siê, ¿e te kilkadziesi¹t cykli nie jest
du¿¹ wartoci¹ dla procesora. Aby siê o tym
upewniæ, policzmy co ile cykli pojawia siê
przerwanie. 256 zliczeñ licznika, który zlicza
co ósmy takt zegara systemowego 2048
cykli. Nie potrzeba ju¿ wiêcej obliczeñ.
W tej chwili by³o to tylko æwiczenie, jed-
nak pozwoli³o na zaznajomienie siê z rzeczy-
wistym problemem. Warto wiedzieæ, jak
sprawdziæ czas wykonywania przerwania,
zw³aszcza ¿e w rzeczywistym, rozbudowa-
nym programie mo¿e pojawiæ siê koniecznoæ
jego optymalizacji. Je¿eli jednak nie chcemy
przyspieszaæ wywo³ywania przerwania wy-
wietlacza a¿ 8 razy, istnieje inna mo¿liwoæ.
Mo¿emy przywróciæ poprzedni¹ wartoæ
preskalera licznika, a w obs³udze przerwania
na jego przepe³nienie wpisywaæ do niego ja-
k¹ wartoæ pocz¹tkow¹. W tym celu wystar-
czy, jeszcze przed wygaszeniem wywietla-
czy, dopisaæ liniê:
TCNT0 = 128;
Wartoæ 128, odpowiadaj¹ca po³owie
zakresu zliczania, spowoduje dwukrotne
zwiêkszenie czêstotliwoci wywo³ywania
przerwania.
Jakkolwiek postanowimy rozwi¹zaæ prob-
lem niskiej czêstotliwoci przemiatania
wywietlacza i czy w ogóle postanowimy go
rozwi¹zywaæ, poznaj¹c oraz wykorzystuj¹c
przerwania dokonalimy wielkiego skoku
jakociowego teraz, w w¹tku g³ównym,
mo¿emy zapomnieæ o prze³¹czaniu wy-
wietlaczy, powiêcaj¹c uzyskane miejsce na
inne dzia³ania. Z tego punktu widzenia
wywietlacz sta³ siê tablic¹ czterech zmien-
nych dok³adnie odwzorowuj¹cych wygl¹d
wywietlanych znaków.
Warunki i instrukcja if
Do tej pory milcz¹co zak³ada³em, ¿e u¿ywana
w programie instrukcja if jest w pe³ni zro-
zumia³a. Wydaje mi siê, ¿e tak jest w istocie
zosta³a tutaj wykorzystana jej najprostsza
sk³adnia, a dzia³ania mo¿na domyliæ siê
z kontekstu. Dla porz¹dku i aby rozwiaæ
wszystkie w¹tpliwoci, na tej i nastêpnej stro-
nie przedstawiam dwie ramki. Zwróæ szcze-
góln¹ uwagê na ramkê Warunki. Z zawar-
tych tam informacji bêdziemy jeszcze wielo-
krotnie korzystaæ.
Zmienne
w pamiêci programu
Na koniec poprzedniej czêci wspomnia³em
o tym, ¿e zmienna zawieraj¹ca informacje
aktywacji poszczególnych wywietlaczy
g_DaneComm, marnuje nasze zasoby nie-
potrzebnie zajmuje cenn¹ pamiêæ danych,
której mamy tylko 128B. Jednoczenie jej
kopia jest przechowywana w pamiêci progra-
mu w celu inicjacji wartoci... których tak na-
prawdê nigdy nie chcemy zmieniaæ. Ze
zmiennej tylko czytamy. Bardziej optymal-
nym rozwi¹zaniem w tym przypadku wydaje
siê bezporednie czytanie danych z pamiêci
programu.
W tym miejscu zwraca siê przeciw nam
architektura procesorów AVR. Mimo i¿ pro-
ducent zapewnia w swoich materia³ach, ¿e
procesor ten by³ opracowywany pod k¹tem
wykorzystania jêzyków wysokiego poziomu,
takich jak C, okazuje siê, ¿e specyfikacja
ANSI C zosta³a stworzona dla procesorów
niewyró¿niaj¹cych pamiêci danych i progra-
mu. W takim przypadku rozró¿nienie, do któ-
rej pamiêci chcemy siê odwo³aæ, powinno
nastêpowaæ za pomoc¹, tylko i wy³¹cznie,
ró¿nych adresów. W naszym procesorze nie-
stety oba wymienione rodzaje pamiêci pod
wzglêdem adresów nachodz¹ na siebie
(pierwsze 224 adresy odnosz¹ siê do pamiêci
danych, przestrzeni wejcia wyjcia oraz
rejestrów... jednak jednoczenie adresy te s¹
jak najbardziej w³aciwe dla pamiêci progra-
mu!). Jedynym sposobem odró¿nienia obu
pamiêci s¹ ró¿ne instrukcje dostêpu.
Producenci kompilatorów w ró¿ny sposób
rozwi¹zuj¹ przedstawiony problem. W GCC
postanowiono utworzyæ oddzielne instrukcje
umo¿liwiaj¹ce dostêp do pamiêci programu.
Warto o tym pamiêtaæ sam wielokrotnie
wpada³em w pu³apkê, gdy chcia³em wykorzys-
taæ zmienn¹ umieszczon¹ w pamiêci progra-
mu tak samo, jakby by³a ona umieszczona
w pamiêci danych.
Du¿o informacji o wykorzystaniu pamiê-
ci programu znajdziesz w kolejnej ramce
ABC... C. Gdy tylko przejrzysz zawarte tam
informacje, mo¿emy rozpocz¹æ dalsz¹
modyfikacjê naszego, coraz doskonalszego,
programu.
Mo¿e ju¿ zauwa¿y³e, ¿e w ramkach napi-
sa³em, co musimy zmieniæ w naszym kodzie.
Teraz, tylko dla porz¹dku, zbiorê te informa-
cjê w jednym miejscu. Umieæ wiêc kursor
zaraz na koñcu sekwencji dyrektyw #include.
Dodaj potrzebny nam nag³ówek za pomoc¹
komendy #include <avr\pgmspace.h>. Od tej
chwili mo¿emy korzystaæ z pamiêci programu
w sposób opisany w ramkach. Nastêpnie
"
Programowanie
Elektronika dla Wszystkich
Rys. 22 Badanie liczby cykli obs³ugi przerwania
ABC... C
Instrukcja warunkowa if
Instrukcja if wystêpuje praktycznie w ka¿-
dym jêzyku programowania. W C jej dzia-
³anie nie odbiega od ogólnie przyjêtej
normy. Jej sk³adnia jest nastêpuj¹ca:
Jeli warunek jest spe³niony, wykonywa-
na jest instrukcja znajduj¹ca siê zaraz za
komend¹ if. Jeli nie, to albo wykonywana
jest instrukcja znajduj¹ca siê za komend¹
else, albo w przypadku jej braku pro-
gram przechodzi do dalszego dzia³ania.
Pamiêtaj, ¿e w obu przypadkach, jeli
wystêpuje instrukcja prosta, musi koñczyæ
siê ona znakiem rednika. Za pomoc¹ kla-
mer mo¿na wprowadziæ tak¿e instrukcje
z³o¿one.
if
(warunek)
instrukcja
[
else
instrukcja]
zmieniamy typ tablicy g_DaneCom na
prog_uint8_t. Zgodnie z opisem mo¿esz
zamiast tego dodaæ zaraz za deklaracj¹ zmien-
nej s³owo PROGMEM. Sposób, jaki wybie-
rzesz, nie ma znaczenia. Efekt bêdzie taki
sam. Ostatnim koniecznym do zmiany punk-
tem jest miejsce, gdzie doczytujemy dane
z naszej, przeniesionej do pamiêci programu,
tablicy. Wszystko to wyjaniaj¹ wspomniane
ramki. Aby u³atwiæ Ci pracê oraz zrozumienie
ca³oci konieczne zmiany przedstawia ca³o-
ciowo listing 12. Tablica zosta³a umieszczo-
na tutaj w pamiêci programu za pomoc¹ s³owa
PROGMEM.
Po wprowadzeniu zmian skompiluj pro-
gram... uda³o siê? Kompilacja powinna prze-
biec bez problemów, po za³adowaniu do pro-
cesora nie zauwa¿ymy z zewn¹trz ró¿nicy
w porównaniu do poprzedniej wersji. W tej
chwili czêæ programu odpowiedzialna za ob-
s³ugê wywietlacza LED jest kompletna i w
pe³ni funkcjonalna. Jeli jeszcze wprowadzi-
my do niej jakie zmiany bêd¹ to poprawki
kosmetyczne.
Spodziewasz siê mo¿e, ¿e dostêp do
pamiêci programu, od strony kodu maszyno-
wego, jest troszkê bardziej skomplikowany
ni¿ dostêp do pamiêci RAM? Sprawdmy
wiêc, czym i jak zap³acilimy za zwolnione 4
komórki cennej pamiêci danych. Rysunek 23
przedstawia wyliczon¹ przez kompilator zajê-
toæ pamiêci w przypadku programu. Podsu-
mowanie takie jest wykonywane po prawid³o-
wym zakoñczeniu ka¿dej kompilacji. Obydwa
programy, zgodnie z proponowan¹ modyfi-
kacj¹, zawiera³y wpisanie pocz¹tkowej war-
toci to licznika co zajmuje dodatkowe 4
bajty. Jak widaæ, koniecznoæ odwo³ywania
siê do pamiêci programu zaowocowa³a
zwiêkszeniem programu jedynie o 2 bajty!
Okazuje siê wiêc, ¿e dzia³anie takie nie jest a¿
tak bardzo pamiêcioch³onne. Jeli chcesz,
w ramach æwiczenia mo¿esz sprawdziæ, ile
cykli zajmie teraz procesorowi obs³uga nasze-
go przerwania.
"#
Programowanie
Elektronika dla Wszystkich
ABC... C
Warunki
Warunki s¹ to pewne specyficzne wyra¿enia,
które mog¹ przyj¹æ dwie wartoci: true lub
false, czyli po prostu prawda lub fa³sz.
Zwykle konstruujemy je za pomoc¹ operato-
rów porównañ i ewentualnie operatorów
logicznych, przy czym te drugie s³u¿¹ do z³o-
¿enia prostych warunków w jedn¹ ca³oæ.
Realizuj¹ one funkcje i, lub oraz nie.
Dowolna liczba mo¿e zostaæ automatycz-
nie zamieniona na warunek, przy czym 0
oznacza fa³sz, inna wartoæ prawdê. Poni¿-
sze zapisy s¹ równowa¿ne:
if
(a)
ó
if
(a!=
0
)
Mo¿na dowolnie mieszaæ w jednym wyra-
¿eniu operatory warunków, arytmetyczne,
logiczne czy bitowe (operatory zosta³y
przedstawione na wk³adce do czêci 2,
dostêpnej na stronie www). Wa¿ne jest jed-
nak dobre zrozumienie zasady wykonywania
obliczeñ w C.
Operatory logiczne
a operatory bitowe
Do tworzenia z³o¿onych warunków wyko-
rzystujemy operatory logiczne. Trzeba byæ
tutaj bardzo ostro¿nym, poniewa¿ kompilator
nie zg³osi b³êdu przy instrukcji na przyk³ad
jak poni¿sza:
if
(a <
12
& b)
Spodziewamy siê, ¿e instrukcja if bêdzie
wykonana, jeli a jest mniejsze od podanej
liczby i jednoczenie b jest ró¿ne od zera.
Jednak zastanówmy siê, co siê stanie w rze-
czywistoci:
Nast¹pi porównanie liczby a z 12. Dzia³a-
nie to zwróci prawdê lub fa³sz, liczbowo
równe 1 lub 0. Jeli porównanie bêdzie fa³-
szywe instrukcja zadzia³a tak, jak tego
oczekiwalimy bitowy iloczyn z jak¹kol-
wiek liczb¹ zwróci wartoæ sta³¹. Jeli b jest
równe 0, na tej samej zasadzie wszystko bê-
dzie pozornie w porz¹dku. Problem zacznie
siê w pozosta³ej sytuacji. Gdy porównanie
zwróci wartoæ 1, a b bêdzie ró¿ne od zera,
taki bitowy iloczyn mo¿e byæ równy 0 lub 1,
zale¿nie od tego, czy najm³odszy bit b jest
ustawiony, czy wyzerowany.
Prawid³owy zapis powinien wygl¹daæ
nastêpuj¹co:
if
(a <
12
&& b)
Zapis ten jest te¿ o tyle optymalny,
¿e obliczenia s¹ przerywane w momencie,
gdy znany jest ju¿ wynik. Oznacza to, ¿e jeli
oka¿e siê, na przyk³ad, ¿e pierwsze porówna-
nie zwróci fa³sz wartoæ zmiennej b nawet
nie bêdzie sprawdzana. Uwaga: standard
definiuje takie w³anie zachowanie, jednak
nie definiuje, w jakiej kolejnoci testowanie
ma byæ wykonywane.
5<a<10 oj...
chyba nie o to chodzi³o
Powy¿sze wyra¿enie mo¿e wydawaæ siê
poprawne. Kompilator nie zg³osi b³êdu.
Pomylmy jednak, jakie operacje zostan¹
wykonane: liczba 5 zostanie porównana ze
zmienn¹ a, dopiero wynik tego porównania
zostanie porównany ze sta³¹ 10. Co jest
wynikiem pierwszego dzia³ania? true lub
false, które maj¹ wartoci liczbowe 1 i 0.
Widzisz ju¿, co siê stanie? Ca³e wyra¿enie
bêdzie zawsze spe³nione.
Dzia³anie takie musisz rozbiæ na dwa:
if
(
5
<a && a<
10
)
Teraz wszystko bêdzie dzia³a³o zgodnie
z nasz¹ intencj¹.
Nie wszystko naraz
Widzisz mo¿e teraz, ¿e warunki w C pozwa-
laj¹ na tworzenie nawet bardzo z³o¿onych
wyra¿eñ. Aby dobrze z tym sobie radziæ,
konieczna jest praktyka. Nie trzeba przecie¿
od razu wykorzystywaæ wszystkich z³o¿o-
nych mo¿liwoci. Jeli jest to dla Ciebie
wygodne, mo¿esz z³o¿ony warunek rozbiæ na
prostsze. Wy¿ej zapisan¹ instrukcjê mo¿na
by tak¿e zapisaæ w nastêpuj¹cy sposób:
if
(
5
<a)
{
if
(a<
10
)
{
//Program dojdzie do tego
//miejsca jeli 5<a<10
}
}
Listing 12 Tablica g_DataCom w pamiêci programu
#include <avr\io.h>
#include <inttypes.h>
#include <avr\signal.h>
#include <avr\interrupt.h>
#include <avr\pgmspace.h>
(...)
uint8_t g_DaneCom[
4
] PROGMEM =
{~(
1
<<COM1), ~(
1
<<COM2), ~(
1
<<COM3), ~(
1
<<COM4)};
int
main(
void
)
{
(...)
}
//____________________________________________________________
// Obs³uga przerwañ
SIGNAL(SIG_OVERFLOW0)
{
(...)
// W³¹czenie odpowiedniego wywietlacza
COMPORT &= pgm_read_byte(&g_DaneCom[g_AktWyswietlacz]);
(...)
}
Zaczynamy
tworzyæ funkcje
Mo¿e i Tobie, tak jak i mnie znudzi³o siê prze-
twarzanie programu tak, aby na wywietlaczu
wci¹¿ widzieæ to samo. Niezale¿nie od tego
jakie wymylne znaki zostan¹ wypisane
wci¹¿ bêdzie to nieruchomy obraz. Mo¿e nad-
szed³ wiêc czas, by co w tym miejscu zmie-
niæ?
Skoro praktycznie nie musimy ju¿ zajmo-
waæ siê ci¹g³ym odwie¿aniem wywietlacza
dziêki przerwaniom bêdzie dzia³o siê to nie-
jako ca³kowicie niezale¿nie mo¿emy wyko-
rzystaæ pêtlê g³ówn¹ do tworzenia animacji.
Najprociej bêdzie wywietlaæ kolejne liczby.
Okazuje siê tak¿e, ¿e bardzo
³atwo tworzy siê funkcjê
wywietlaj¹c¹ liczby w kodzie
heksadecymalnym.
Mylê, ¿e po poznaniu
wszystkich informacji, jakie do
tej pory przedstawi³em, jeste
ju¿ w stanie mniej wiêcej wyob-
raziæ sobie, jak bêd¹ wygl¹da³y
poszczególne dzia³ania. Ca³y
program mo¿na by napisaæ
w funkcji main. Takie przyzwy-
czajenia niektórzy mogli wy-
nieæ z BASCOM-a tam opis
dzia³ania najczêciej zawiera³o
siê w jednym ci¹gu. Twór taki
jak funkcja by³ nieco sztuczny.
Jeli podzielasz takie w³anie mylenie,
chcia³bym, aby zacz¹³ przyzwyczajaæ siê do
czego zupe³nie innego. Troszkê innej jako-
ci. W C wykorzystanie funkcji staje siê ca³ko-
wicie naturalne. Program praktycznie zawsze
powinien sk³adaæ siê z zespo³u funkcji wywo-
³ywanych z poziomu programu g³ównego.
Nawet bardziej rozbudowana obs³uga prze-
rwania mo¿e korzystaæ z innych funkcji. Mam
nadziejê, ¿e przekonasz siê, ¿e takie mylenie
jest bardzo wygodne. Dziêki temu, ¿e skom-
plikowane dzia³anie mo¿na rozbiæ na wiêcej
prostych program pisze siê ³atwiej i bardziej
intuicyjnie. Wa¿ne jest tak¿e to, ¿e raz utwo-
rzon¹ funkcjê mo¿na wykorzystaæ w wielu
miejscach co zmniejsza objêtoæ kodu.
Wyprzedzaj¹c trochê dzisiejszy zakres mate-
ria³u, wspomnê tylko o bardzo wygodnej
w³aciwoci funkcji, któr¹ jest, ca³kowicie
naturalna, mo¿liwoæ tworzenia zmiennych
lokalnych praktycznie nieistniej¹cych poza
funkcj¹. Zobaczysz ju¿ nied³ugo, jak wygod-
ny jest brak koniecznoci zastanawiania siê,
czy gdzie indziej nie wykorzystujê tej w³a-
nie zmiennej.
Jednak koñcz¹c zachwalaæ to, co dopiero
przedstawiê w przysz³oci, przechodzê do
rzeczy. Dzi przedstawiam ca³kowite podsta-
wy, umo¿liwiaj¹ce stworzenie programu
dok³adnie takiego, jak chcielimy: licz¹cego
i wywietlaj¹cego kolejne liczby w kodzie
heksadecymalnym. Jak zwykle proponujê
zapoznanie siê z ramk¹ zawieraj¹c¹ odpo-
wiednie informacje. Tym razem jest to ramka
z informacjami o podstawach tworzenia oraz
korzystania z funkcji.
Pamiêtasz mo¿e moje pytanie na koñcu
poprzedniej czêci? Jeli podj¹³e wtedy
wyzwanie i stworzy³e tablicê zawieraj¹c¹
wygl¹d wszystkich cyfr kodu szesnastkowe-
go, masz teraz okazjê porównaæ swoje dzie³o
z moj¹ propozycj¹. Ze wzglêdu na objêtoæ
pe³nego kodu ród³owego oraz na to, ¿e wiele
elementów wci¹¿ siê nie zmienia, nie przed-
stawiam go w ca³oci. Mylê, ¿e jeli opiszê
krok po kroku wprowadzane zmiany nie
bêdziesz mia³ problemów z przerobieniem
poprzedniego programu, a dodatkowo ³atwiej
przyjdzie zrozumienie nowego kodu.
Na pocz¹tek zajmiemy siê stworzeniem
tablicy zawieraj¹cej wygl¹d poszczególnych
znaków na wywietlaczu. Ja umieci³em j¹
"$
Programowanie
Elektronika dla Wszystkich
ABC... C
Wykorzystanie pamiêci
programu w AVR-GCC
Oddzielna przestrzeñ adresowa dla danych i pro-
gramu to kolejny element, który nie jest zgodny
z ANSI C. Trzeba liczyæ siê z tym, ¿e w innym kom-
pilatorze problem ten mo¿e byæ rozwi¹zany zupe³-
nie inaczej!
Najwygodniejszym sposobem pos³ugiwania siê
pamiêci¹ programu jest wykorzystanie nag³ówka
<avr\pgmspace.h>. Definiuje on wszystko, czego
potrzeba do umieszczenia zmiennych w obsza-
rze programu oraz do dostêpu do nich.
Pamiêtaj - jeli chcesz wykorzystaæ w praktyce
informacje z tej ramki koniecznie do³¹cz wspom-
niany plik do swojego programu za pomoc¹ dyrek-
tywy #include.
Jak tworzyæ zmienne:
Wspomniany wy¿ej nag³ówek daje dwie mo¿li-
woci poinformowania kompilatora o naszym
¿yczeniu specjalnego potraktowania zdeklarowa-
nej w³anie zmiennej. Jednym z nich jest wyko-
rzystanie s³owa kluczowego PROGMEM. W ta-
kim przypadku deklaracja naszej tablicy wygl¹da-
³aby nastêpuj¹co:
uint8_t g_DaneCom[
4
] PROGMEM =
{~(
1
<<COM1)
(...)
};
S³owo kluczowe PROGMEM umieszcza siê
za deklaracj¹ zmiennej, ale przed jej inicjacj¹.
Drug¹ mo¿liwoci¹ jest wykorzystanie jednego
ze specjalnych typów zmiennych, jakie deklaruje
do³¹czenie pliku pgmspace.h. Pojawiaj¹ siê tutaj
wszystkie typy zdeklarowane w pliku inttypes.h
(przypominam, zmienne w formie [u]intN_t
czêæ 2 kursu), jednak poprzedzone dodatkowo
przedrostkiem prog_. Jak zwykle wszystko najle-
piej t³umaczy przyk³ad:
prog_uint8_t g_DaneCom[
4
] =
{~(
1
<<COM1)
(...)
};
To, który sposób wyda Ci siê wygodniejszy
i bardziej czytelny, zale¿y tylko i wy³¹cznie od
Twojego gustu. Ja praktycznie wykorzystujê obie
mo¿liwoci naprzemiennie.
Niezmienne zmienne?
Zauwa¿ pewn¹ bardzo wa¿n¹ rzecz: jeli tworzy-
my twór taki jak zmienna w pamiêci programu
nie mamy mo¿liwoci zapisania do niej danych,
mo¿emy z niej tylko czytaæ. Nie ma znaczenia to,
¿e niektóre z procesorów pozwalaj¹ na pisanie do
pamiêci programu to jest zupe³nie inna historia.
Z punktu widzenia kompilatora zmienna w pamiê-
ci programu zawsze jest TYLKO DO ODCZYTU.
Aby z tworu takiego mieæ jakikolwiek u¿ytek,
konieczne jest pocz¹tkowe przypisanie mu war-
toci.
(ci¹g dalszy w nastêpnej ramce...)
Rys. 23 Porównanie zu¿ycia pamiêci
Listing 13 Tablica zawieraj¹ca definicjê wygl¹du poszczególnych znaków
prog_uint8_t g_WzorCyfr[
16
] =
{
~(
1
<<LED_A |
1
<<LED_B |
1
<<LED_C |
1
<<LED_D |
1
<<LED_E |
1
<<LED_F),
// 0
~(
1
<<LED_B |
1
<<LED_C),
// 1
~(
1
<<LED_A |
1
<<LED_B |
1
<<LED_G |
1
<<LED_E |
1
<<LED_D),
// 2
~(
1
<<LED_A |
1
<<LED_B |
1
<<LED_C |
1
<<LED_D |
1
<<LED_G),
// 3
~(
1
<<LED_B |
1
<<LED_C |
1
<<LED_G |
1
<<LED_F),
// 4
~(
1
<<LED_A |
1
<<LED_F |
1
<<LED_G |
1
<<LED_C |
1
<<LED_D),
// 5
~(
1
<<LED_A |
1
<<LED_F |
1
<<LED_E |
1
<<LED_D |
1
<<LED_C |
1
<< LED_G),
// 6
~(
1
<<LED_A |
1
<<LED_B |
1
<<LED_C),
// 7
~(
1
<<LED_A |
1
<<LED_B |
1
<<LED_C |
1
<<LED_D |
1
<<LED_E |
1
<<LED_F |
1
<<LED_G),
// 8
~(
1
<<LED_G |
1
<<LED_F |
1
<<LED_A |
1
<<LED_B |
1
<<LED_C |
1
<<LED_D),
// 9
~(
1
<<LED_A |
1
<<LED_B |
1
<<LED_C |
1
<<LED_E |
1
<<LED_F |
1
<<LED_G),
// A
~(
1
<<LED_C |
1
<<LED_D |
1
<<LED_E |
1
<<LED_F |
1
<<LED_G),
// B
~(
1
<<LED_A |
1
<<LED_D |
1
<<LED_E |
1
<<LED_F),
// C
~(
1
<<LED_B |
1
<<LED_C |
1
<<LED_D |
1
<<LED_E |
1
<<LED_G), /
/ D
~(
1
<<LED_A |
1
<<LED_D |
1
<<LED_E |
1
<<LED_F |
1
<<LED_G),
// E
~(
1
<<LED_A |
1
<<LED_E |
1
<<LED_F |
1
<<LED_G) /
/ F
};
zaraz za ostatnio zdeklarowan¹ zmienn¹
(g_DaneCom). Wpisz tutaj kod przedstawio-
ny na listingu 13. Jest to nic innego jak de-
klaracja nowej tablicy w pamiêci programu.
Na kolejnych jej pozycjach znajduj¹ siê war-
toci opisuj¹ce wygl¹d odpowiedniej cyfry.
Zajrzyj teraz na listing 14. Przedstawi³em
te czêci kodu, które siê zmieni³y. Nie przepi-
suj ca³oci od góry do do³u. Spróbujmy napi-
saæ to w takiej kolejnoci, w jakiej kod
by³ tworzony. Najpierw przepisz funkcjê
WyswietlHex. Umieæ j¹ nad funkcj¹ main
umo¿liwi nam to unikniêcie pisania jej dekla-
racji. Funkcja nie jest optymalna. Ale zosta³a
napisana w taki sposób, aby nie wykorzysty-
waæ nic ponad to, co do tej pory zdo³a³em
przedstawiæ.
Gdy funkcja ta jest ju¿ gotowa, zastanów-
my siê, co dalej. Chcemy zliczaæ i wywietlaæ
wartoæ aktualnego zliczenia. Procesor oczy-
wicie potrafi dodawaæ bardzo szybko, wiêc
aby cokolwiek widzieæ, bêdziemy musieli
troszkê spowolniæ jego dzia³anie. W tym celu
skorzystamy z funkcji zawartych w pliku
<avr\delay.h>. Dodaj wiêc jeszcze i ten plik
do naszego programu za pomoc¹ dyrektywy
#include.
Nastêpnie stworzymy zmienn¹ przecho-
wuj¹c¹ aktualnie zliczon¹ wartoæ. Ja umie-
ci³em j¹ zaraz za ostatnio dodan¹ tablic¹,
nadaj¹c jej nazwê g_Licznik.
W funkcji main nic nie zmieniamy w czê-
ci odpowiedzialnej za inicjacjê. Po jej zakoñ-
czeniu usuwamy jednak czêæ odpowiedzialn¹
za wywietlenie na wywietlaczach sta³ych
cyfr, a w pêtli nieskoñczonej tworzymy pros-
ty algorytm, jak na listingu 14. Uff... jeli
wszystko siê uda³o po przeprowadzeniu
kompilacji i za³adowaniu programu do proce-
sora program zacznie dzia³aæ zgodnie z na-
szym aktualnym ¿yczeniem. W razie proble-
mów pe³ne kody ród³owe mo¿esz pobraæ ze
strony internetowej Elektroniki dla Wszyst-
kich. Korzystaj z nich mia³o! Czasami lepiej
spojrzeæ na pe³ny kod i w ten sposób znaleæ
problem, ni¿ próbowaæ walczyæ z nim ambit-
nie a¿ do zniechêcenia.
Podsumowanie
W tym odcinku opowiedzia³em Ci o tym,
w jaki sposób korzystaæ z przerwañ, jak
umieciæ zmienn¹ w pamiêci programu
i jak siê do niej dostaæ. Pozna³e praktyczne
wykorzystanie operatora adresowego &. Opo-
wiedzia³em Ci o instrukcji if oraz o specyficz-
nych wyra¿eniach, którymi s¹ warunki.
Wreszcie zaczêlimy zajmowaæ siê samo-
dzielnym tworzeniem funkcji.
W miêdzyczasie nasz program zosta³ roz-
winiêty i na koñcu sta³ siê czym w rodzaju
szesnastkowego licznika czasu. Pisany pro-
gram jest coraz ciekawszy, ale oczywicie
mo¿na napisaæ go lepiej i bardziej wydajnie.
Czy wiesz w jaki sposób zamiast funkcji
_delay_loop_2 wykorzystaæ do generowania
opónienia nasze przerwanie wywietlacza,
czy wiesz jak poradziæ sobie z dwoma przy-
ciskami znajduj¹cymi siê na p³ytce? Razem
zrobimy to ju¿ w nastêpnym odcinku.
"%
Programowanie
Elektronika dla Wszystkich
ABC... C
Dostêp do danych
w pamiêci programu w AVR-GCC
W AVR-GCC nie ma mo¿liwoci wykorzystania
zmiennych umieszczonych w pamiêci programu
w taki sam sposób, jakby by³a to zwyk³a zmienna.
Gdy ju¿ umiecilimy nasz¹ tablicê g_DaneCom
w pamiêci programu, odwo³anie siê do niej w spo-
sób jak do tej pory:
nie spowoduje co prawda zg³oszenia b³êdu
przez kompilator, jednak zachowanie programu
bêdzie nieokrelone. Zostanie wykonany dostêp
do komórki pamiêci RAM o adresie takim, jaki
posiada nasza zmienna w obszarze ROM!
Makra pgm_read
Najprostszy dostêp do wartoci zapisanych w pa-
miêci programu umo¿liwiaj¹ niewielkie makra,
które udostêpnia nam plik <avr\pgmspace.h>.
Maj¹ one postaæ:
pgm_read_byte(adres)
pgm_read_word(adres)
pgm_read_dword(adres)
Pozwalaj¹ one na odczytanie z podanego adre-
su (kolejno) baitu, s³owa oraz podwójnego s³owa.
Wyprzedzaj¹co warto zwróciæ uwagê, ¿e makra
te umo¿liwiaj¹ dostêp tylko do obszaru 64kB
pamiêci. Teraz nie ma to znaczenia, ale w przypad-
ku potrzeby dostêpu do danych umieszczonych
powy¿ej 64kB w procesorze o wiêkszym rozmia-
rze pamiêci (na przyk³ad ATmega128), konieczne
jest wykorzystanie analogicznych makr, jednak
z przyrostkiem _far zajmuj¹ one wiêcej zasobów
oraz dzia³aj¹ wolniej, lecz umo¿liwiaj¹ dostêp do
obszaru pamiêci o teoretycznym rozmiarze do
4GB, jednak przy uwzglêdnieniu
ich aktualnej implementacji, teo-
retycznie mo¿liwa do obs³u¿enia pamiêæ to tylko
16MB.
Jeli wiesz cokolwiek o asemblerze AVR, pro-
ponujê Ci zajrzenie do pliku <avr\pgmspace.h>
i przyjrzenie siê, jak wygl¹daj¹ opisane makra.
Funkcje z przyrostkiem _P
Warto wiedzieæ, ¿e praktycznie wszystkie funkcje
standardowe, operuj¹ce na danych, maj¹ swoje
odpowiedniki z przyrostkiem _P. Przyrostek taki
oznacza, ¿e funkcja ta operuje na danych umiesz-
czonych w pamieci programu. Niewiele jeszcze
mówi³em o funkcjach sa-
mych w sobie, nie przej-
muj siê wiêc, jeli ten aka-
pit wydaje siê niejasny
temat zostanie rozwiniêty w niedalekiej przysz³o-
ci.
Operator adresu &
Przy opisie makr dostêpu do pamiêci beztrosko
wspomnia³em o tym, ¿e konieczne jest podanie im
adresu, pod jakim nasza zmienna siê znajduje. Nie
wdaj¹c siê aktualnie w szczegó³y, podam Ci jedy-
nie kuchenny przepis jak uzyskaæ potrzebny nam
adres jakiejkolwkiek zmiennej. W tym celu
wykorzystujemy operator &. Umieszczamy go
przed nazw¹ zmienej. Ostro¿nie jednak z tablica-
mi. Okazuje siê, ¿e z punktu widzenia kompila-
tora, wszêdzie, gdzie u¿yjemy samej nazwy tab-
licy, bêdzie to adres jej pierwszego elementu!
Jednak ju¿ wykorzystanie nawiasów kwadrato-
wych powoduje dostêp do odpowiedniej wartoci.
Najlepiej jak zwykle wyjania to przyk³ad. Poni¿-
sze zapisy s¹ równowa¿ne:
pgm_read_byte(&g_DaneCom[0]);
pgm_read_byte( g_DaneCom );
W naszym programie prawid³owy dostêp do
pamiêci programu bêdzie wygl¹da³ nastêpuj¹co
(porównaj z pierwszym przedstawionym w tej
ramce listingiem):
COMPORT &= g_DaneCom[g_AktWyswietlacz];
COMPORT &=
pgm_read_byte(&g_DaneCom[g_AktWyswietlacz]);
Listing 14 Zmiany wprowadzone w program w celu wywietlania licznika
(...)
#include <avr\delay.h>
(...)
uint16_t g_Licznik =
0
;
// Funkcja wywietla w kodzie heksadecymalnym podan¹ liczbê
void
WyswietlHex(uint16_t var)
{
g_DaneWyswietlacza[
0
] = pgm_read_byte(&g_WzorCyfr[var>>(
3
*
4
)]);
g_DaneWyswietlacza[
1
] = pgm_read_byte(&g_WzorCyfr[(var>>(
2
*
4
))&
0xf
]);
g_DaneWyswietlacza[
2
] = pgm_read_byte(&g_WzorCyfr[(var>>(
1
*
4
))&
0xf
]);
g_DaneWyswietlacza[
3
] = pgm_read_byte(&g_WzorCyfr[var&
0xf
]);
}
// Start
int
main(
void
)
{
/////////////////////////////
(...)
// koniec inicjacji
/////////////////////////////
for
(;;)
{
WyswietlHex(g_Licznik);
_delay_loop_2(
0xffff
);
g_Licznik++;
}
return
0
;
}
Elektronika dla Wszystkich
"&
Programowanie
ABC... C
Funkcje
Stworzenie nowej funkcji w C jest bardzo proste.
Tak naprawdê ca³y czas tworzymy przecie¿ funkc-
je. Do tej pory stworzylimy funkcjê main, oraz za
pomoc¹ makra, funkcjê obs³ugi przerwania. By³y
to funkcjê standardowe. Teraz rozwiniemy temat
na tyle, aby móc utworzyæ funkcjê o dowolnej
nazwie, dowolnych parametrach i dowolnej zwra-
canej wartoci.
Na obrazku w tej ramce pokaza³em z czego
sk³ada siê definicja funkcji. Przyk³adowa funkcja
zaczerpniêta z tworzonego aktualnie programu ma
za zadanie wywietliæ podan¹ liczbê w kodzie hek-
sadecymalnym. Nie skupiamy siê teraz na tym, jak
ona dzia³a, wa¿ne jest to, jak zosta³a zapisana.
Funkcja sk³ada siê z informacji dla kompilatora,
jak¹ wartoæ zwraca. Czêsto, zamiast mówiæ opiso-
wo, ¿e funkcja zwraca wartoæ typu (przyk³adowo)
int, mówimy prosto, ¿e funkcja jest typu int.
W naszym przypadku u¿ywamy void. Jest to bar-
dzo specyficzny typ wartoci, który... nie istnieje.
W ten sposób informujemy kompilator, ¿e funkcja
nie zwraca wartoci. Jeli pisa³e programy
w BASCOM-ie, mo¿e zakrzykniesz teraz to ju¿
znamy tylko do tej pory funkcjê niezwracaj¹c¹
danych nazywalimy procedur¹ (SUB). Znakomi-
cie, jeli zauwa¿asz analogiê. Teraz jednak zarów-
no procedury, jak i funkcjê tworzy siê tak samo.
Ró¿nica w zapisie polega tylko na poinformowa-
niu kompilatora, ¿e nie chcemy zwracaæ ¿adnej
wartoci.
Nastepnie pojawia siê nazwa funkcji. Jest to
identyfikator, wiêc nie mo¿e zaczynaæ siê od cyfry.
Poza tym mo¿e zawieraæ wszystkie litery alfabetu,
jednak bez polskich ogonków. Dodatkowo mo¿na
wykorzystaæ znak podkrelenia. Przypominam, ¿e
rozmiar liter ma znaczenie. Z tego identyfikatora
bêdziemy korzystaæ w celu wywo³ania funkcji.
Zaraz za identyfikatorem, w nawiasach,
umieszczamy wymagane przez funkcjê parametry.
Jeli potrzebujemy wiêcej ni¿ jednego parametru,
oddzielamy je przecinkami. Kolejno podajemy typ
zmiennej, nastêpnie jej nazwê. Wa¿ne jest, ¿e para-
metry te bêd¹ dostêpne tylko i wy³¹cznie dla two-
rzonej funkcji. Uwa¿aj aby nie nadaæ parametro-
wi nazwy jakiej zmiennej globalnej. Przeciwnie
do BASCOM-a, tutaj nie tylko nie jest to wymaga-
ne, ale wrêcz niewskazane.
Miêdzy nawiasami klamrowymi umieszczamy
cia³o funkcji, czyli po prostu opis jej dzia³ania.
Mo¿emy wykorzystaæ teraz wszelkie funkcje stan-
dardowe, wywo³aæ inn¹ w³asn¹ funkcjê, a nawet
funkcja mo¿e wywo³aæ sam¹ siebie... Tak czy
inaczej cia³o funkcji pisalimy ju¿ kilka razy, tak
wiêc nie jest to ju¿ niczym nowym.
Parametry
Parametry w C przesy³ane s¹ poprzez wartoæ.
Oznacza to, ¿e funkcja nie ma dostêpu do zmien-
nych na poziomie funkcji wywo³uj¹cej. Mo¿na
wiêc z przes³anych danych korzystaæ do woli. Jak
do zwyk³ej zmiennej mo¿esz wpisaæ tutaj na przy-
k³ad wynik cz¹stkowy jakiego obliczenia... Na
pewno nie zmieni to wartoci zmiennej w funkcji
wywo³uj¹cej.
Wywo³anie
w³asnej funkcji
Gdy mamy ju¿ utworzon¹
w³asn¹ funkcjê, potrzebujemy
jeszcze mo¿liwoci jej wywo-
³ania. Okazuje siê, ¿e jest to
bardzo intuicyjne. Nie ma ró¿-
nicy pomiêdzy sposobem
wywo³ywania w³asnej funkcji
a jednej z funkcji standarto-
wej, z jakich korzystalimy do
tej pory. Tak wiêc wywo³anie
naszej funkcji przebiegnie
nastêpuj¹co:
WyswietlHex(wartosc);
Wartoæ w nawiasach mo¿e byæ ró¿nego typu.
Mo¿e byæ to zmienna, mo¿e byæ wartoæ sta³a,
mo¿e byæ ca³e wyra¿enie... Co bardzo ciekawe,
mo¿na tutaj tak¿e wywo³aæ inn¹ funkcjê. Za³ó¿my
czysto teoretycznie, ¿e chcemy wywietliæ wartoæ
jakiej zmiennej umieszczonej w pamiêci pro-
gramu. Jak pokazywa³em wczeniej, w tym celu
konieczne jest wykorzystanie makra pgm_read.
Przyk³ad takiego dzia³ania przedstawia poni¿szy
pseudokod (dzia³aj¹cy przyk³ad udostêpniam na
stronie internetowej):
W ten sposób podalimy jako parametr naszej
funkcji wynik dzia³ania innej funkcji. Istnienie
takiej mo¿liwoci sprawia, ¿e mo¿emy oszczêdziæ
na zmiennych tymczasowych. Inaczej konieczne
by³oby utworzenie dodatkowej zmiennej, wpisanie
do niej wyniku dzia³ania funkcji odczytu z pamieci
programu i dopiero przes³anie tej zmiennej do
wywietlania.
Z drugiej strony, w bardzo z³o¿onych przypad-
kach, rozwi¹zanie ze zmienn¹ tymczasow¹ mo¿e
znacznie uprociæ zapis. Pamiêtaj wtedy, ¿e przed-
stawiona przed chwil¹ mo¿liwoæ jest tylko mo¿li-
woci¹. C nie zmusza nikogo do pisania w taki,
a nie inny sposób.
Deklaracja i definicja
Powinno pojawiæ siê, ju¿ nawet wczeniej, pyta-
nie: No dobrze... ale czy funkcjê mo¿na wywo³aæ
ot tak sobie: zawsze i w dowolnym miejscu?.
Odpowied jest bardziej ni¿ oczywista: Niestety,
nie mo¿na. Zanim funkcja zostanie wywo³ana,
musi byæ zdeklarowana. Deklaracja funkcji to
powiadomienie kompilatora o tym, ¿e dana funkc-
ja istnieje oraz o tym, jakie przyjmuje paremetry.
Od strony kuchennej deklaracja to nic innego jak
powtórzona nazwa funkcji, razem z jej typem oraz
informacj¹ o parametrach. Deklaracja jednak
w odró¿nieniu od definicji nie zawiera cia³a funkc-
ji. Zamiast tego, zaraz za nawiasem z parametrami,
umieszczamy znak rednika. Jest to konieczne, aby
kompilator wiedzia³ o co chodzi i nie szuka³ po-
cz¹tku cia³a funkcji.
Uwa¿aj teraz. Okazuje siê, ¿e zdefiniowanie
funkcji oznacza tak¿e jej zdeklarowanie. Sprawa
jest prostsza, ni¿ wynika³oby ze skomplikowania
poprzedniego zdania. Oznacza to tylko tyle, ¿e
jeli definiujesz funkcjê (przypominam stwo-
rzysz pe³ny opis razem z opisem dzia³ania) przed
miejscem jej wywo³ania, nie jest konieczne
umieszczanie nigdzie jej deklaracji. Dlatego te¿
czêsto funkcjê main umieszcza siê na koñcu. Dziê-
ki temu unikamy wpisywania dodatkowych linijek
kodu. Jednak nie zawsze umieszczanie definicji
ponad miejscem wywo³ania jest wygodne. Czasa-
mi mo¿e okazaæ siê nawet niemo¿liwe. Deklarac-
jê w takim przypadku umieszcza siê najczêciej
zaraz za lub zaraz przed zmiennymi globalnymi.
Ogólniej ju¿ po do³¹czeniu wszystkich plików
nag³ówkowych, ale jeszcze przed definicj¹ jakiej-
kolwiek funkcji.
Ogólnie ilustruje to poni¿szy obrazek (pseudo-
kod):
prog_uint16_t g_Stala =
0xcdef
;
(...)
WyswietlHex(pgm_read_word(&g_Stala));
Rados³aw Koppel