background image

Elektronika dla Wszystkich

37

Programowanie

Kontynuujemy  tworzenie  programu  obs³ugi

wyœwietlacza multipleksowanego. Tak jak obie-

ca³em, doprowadzimy teraz program do pe³nej

funkcjonalnoœci. 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³¹czaliœmy  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êliœmy  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,  poœwiêæmy  chwilê  na  prze-

myœlenie,  gdzie  umieœcimy  obs³ugê  naszego

wyœwietlacza.  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  oœmiobitowy  oraz  bar-

dziej rozbudowany o rozdzielczoœci 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.  Wyjœciowym

folderem,  w  którym  odbywa  siê  w  takim

przypadku poszukiwanie pliku, jest 

C:\WinAVR\avr\include.  Jeœli  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ê, jeœli 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³ywaliœmy  siê  do  podkatalogu  avr. 

W  miejscu  tym  zosta³y  umieszczone  pliki,

których  standard  C  w  ¿aden  sposób  nie

ujmuje i s¹ one œciœle 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³aœnie  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,  jeœli  poszukujemy  nazwy  jakie-

goœ rejestru, do którego w³aœnie chcemy siê

odwo³aæ.

W oddzielnej ramce umieœci³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, jeœli chce-

my do³¹czyæ po prostu jakiœ swój plik, czy to

z  w³asnymi,  uniwersalnymi  funkcjami,  czy

te¿ pobrany z sieci.

Z mo¿liwoœci 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ñ,

wœród nich sei() 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

background image

Obs³uga wyœwietlacza 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 wyœwietlacza 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.

Oczywiœcie nie jest tutaj wa¿na du¿a do-

k³adnoœæ odmierzenia czasu. Jeœli procedura

obs³ugi wyœwietlacza bêdzie wywo³ywana

nawet 500 razy na sekundê, spowoduje to

tylko wiêksze obci¹¿enie procesora, a jedno-

czeœnie 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

doœwiadczenia 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. Jeœli 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 jeœli 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 powinniœmy ujrzeæ znajomy

nam widok – wyœwietlane kolejne

cyfry na wyœwietlaczach. 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³aœnie

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”.

Jeœli 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-

myœlane 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() 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ñ

background image

dostrzeg¹  jednak  migotanie  wyœwietlaczy.

Przyjrzyjmy siê naszemu programowi – z jak¹

czêstotliwoœci¹  obs³ugujemy  wyœwietlacze?

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  wyœwietlaczach  otrzymuje-

my  czêstotliwoœæ  odœwie¿ania  ca³ego  pola

rzêdu 60Hz. Niby wiêcej ni¿ granica czêstot-

liwoœci,  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 rzeczywiœcie ca³kowicie usta³o.

Warto  zobaczyæ,  jak  takie  przyspieszenie

wyœwietlacza  obci¹¿y³o  procesor.  Z  pomoc¹

przyjdzie  nam  tutaj  AVRStudio. Zak³adam,

¿e  jego  uruchomienie  oraz  wczytanie  pliku

programu  nie  sprawia  Ci  problemów.  Jeœli

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.

Jeœli  pechowo  trafi³eœ  w³aœnie  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. Jeœli 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 dojœcie do kursora dodaje do tej war-

toœci 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 wyœwietlacza 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 wartoœci

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 wyœwietlaczy

COMPORT |= 

1

<<COM1 | 

1

<<COM2 | 

1

<<COM3 | 

1

<<COM4; 

// Wys³anie odpowiedniej danej

LEDPORT = g_DaneWyswietlacza[g_AktWyswietlacz]; 

// W³¹czenie odpowiedniego wyœwietlacza

COMPORT &= g_DaneCom[g_AktWyswietlacz]; 

// Zwiêkszenie stanu zmiennej wskazuj¹cej na 

// obs³ugiwany wyœwietlacz

++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 zmieœci³ 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êkszoœci 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ê  nieznoœnie

d³uga, mo¿na zastosowaæ na koñcu znak ukoœnika (\). Spowoduje to,

¿e  kompilator  potraktuje  liniê  nastêpn¹  jako  przed³u¿enie  poprzed-

niej.

background image

¿e dok³adnie co taki czas zmienna g_AktWys-

wietlacz jest  zerowana.  Ju¿  teraz  zapewne

domyœlasz siê, ¿e te kilkadziesi¹t cykli nie jest

du¿¹ wartoœci¹ 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  wyœwietla-

czy, dopisaæ liniê:

TCNT0 = 128;

Wartoœæ  128,  odpowiadaj¹ca  po³owie

zakresu  zliczania,  spowoduje  dwukrotne

zwiêkszenie  czêstotliwoœci  wywo³ywania

przerwania.

Jakkolwiek postanowimy rozwi¹zaæ prob-

lem  niskiej  czêstotliwoœci  przemiatania

wyœwietlacza i czy w ogóle postanowimy go

rozwi¹zywaæ,  poznaj¹c  oraz  wykorzystuj¹c

przerwania  dokonaliœmy  wielkiego  skoku

jakoœciowego  –  teraz,  w  w¹tku  g³ównym,

mo¿emy  zapomnieæ  o  prze³¹czaniu  wy-

œwietlaczy, poœwiêcaj¹c uzyskane miejsce na

inne  dzia³ania.  Z  tego  punktu  widzenia

wyœwietlacz  sta³  siê  tablic¹  czterech  zmien-

nych  dok³adnie  odwzorowuj¹cych  wygl¹d

wyœwietlanych 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  domyœliæ  siê 

z  kontekstu.  Dla  porz¹dku  i  aby  rozwiaæ

wszystkie w¹tpliwoœci, 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  wyœwietlaczy  –

g_DaneComm,  marnuje  nasze  zasoby  –  nie-

potrzebnie  zajmuje  cenn¹  pamiêæ  danych,

której  mamy  tylko  128B.  Jednoczeœnie  jej

kopia jest przechowywana w pamiêci progra-

mu w celu inicjacji wartoœci... których tak na-

prawdê  nigdy  nie  chcemy  zmieniaæ.  Ze

zmiennej  tylko  czytamy.  Bardziej  optymal-

nym rozwi¹zaniem w tym przypadku wydaje

siê  bezpoœrednie  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  wejœcia  wyjœcia  oraz

rejestrów...  jednak  jednoczeœnie  adresy  te  s¹

jak najbardziej w³aœciwe 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:

Jeœli warunek jest spe³niony, wykonywa-

na  jest  instrukcja  znajduj¹ca  siê  zaraz  za

komend¹ if. Jeœli 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,  jeœli

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]

background image

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 wyjaœniaj¹ wspomniane

ramki. Aby u³atwiæ Ci pracê oraz zrozumienie

ca³oœci – 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ê  wyœwietlacza  LED  jest  kompletna  i  w

pe³ni funkcjonalna. Jeœli 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?  SprawdŸmy

wiêc, czym i jak zap³aciliœmy 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-

toœci  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.  Jeœli  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 wartoœci: 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

(<

12

b)

Spodziewamy siê, ¿e instrukcja if bêdzie

wykonana,  jeœli  jest  mniejsze  od  podanej

liczby  i  jednoczeœnie  jest  ró¿ne  od  zera.

Jednak zastanówmy siê, co siê stanie w rze-

czywistoœci: 

Nast¹pi porównanie liczby z 12. Dzia³a-

nie  to  zwróci  prawdê  lub  fa³sz,  liczbowo

równe 1 lub 0. Jeœli porównanie bêdzie fa³-

szywe  –  instrukcja  zadzia³a  tak,  jak  tego

oczekiwaliœmy  –  bitowy  iloczyn  z  jak¹kol-

wiek liczb¹ zwróci wartoœæ sta³¹. Jeœli 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ê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

(<

12

&& b)

Zapis  ten  jest  te¿  o  tyle  optymalny, 

¿e  obliczenia  s¹  przerywane  w  momencie,

gdy znany jest ju¿ wynik. Oznacza to, ¿e jeœli

oka¿e siê, na przyk³ad, ¿e pierwsze porówna-

nie zwróci fa³sz – wartoœæ zmiennej nawet

nie  bêdzie  sprawdzana.  Uwaga:  standard

definiuje  takie  w³aœnie  zachowanie,  jednak

nie  definiuje,  w  jakiej  kolejnoœci  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.

Pomyœlmy  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¹  wartoœci  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<

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¿liwoœci.  Jeœli  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 jeœli 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 wyœwietlacza

COMPORT &= pgm_read_byte(&g_DaneCom[g_AktWyswietlacz]);

(...)

}

background image

Zaczynamy 

tworzyæ funkcje

Mo¿e i Tobie, tak jak i mnie znudzi³o siê prze-

twarzanie programu tak, aby na wyœwietlaczu

wci¹¿  widzieæ  to  samo.  Niezale¿nie  od  tego

jakie  wymyœlne  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 odœwie¿aniem wyœwietlacza

– 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.

Najproœciej bêdzie wyœwietlaæ kolejne liczby.

Okazuje  siê  tak¿e,  ¿e  bardzo

³atwo  tworzy  siê  funkcjê

wyœwietlaj¹c¹  liczby  w  kodzie

heksadecymalnym. 

Myœlê,  ¿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.

Jeœli  podzielasz  takie  w³aœnie  myœlenie,

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 myœlenie

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³aœciwoœci  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  koniecznoœci  zastanawiania  siê,

czy gdzieœ indziej nie wykorzystujê tej w³aœ-

nie zmiennej. 

Jednak koñcz¹c zachwalaæ to, co dopiero

przedstawiê  w  przysz³oœci,  przechodzê  do

rzeczy. Dziœ przedstawiam ca³kowite podsta-

wy,  umo¿liwiaj¹ce  stworzenie  programu

dok³adnie  takiego,  jak  chcieliœmy:  licz¹cego 

i  wyœwietlaj¹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?  Jeœli  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³oœci. Myœlê, ¿e jeœli 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  wyœwietlaczu.  Ja  umieœci³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 - jeœli 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-

woœci  poinformowania  kompilatora  o  naszym

¿yczeniu  specjalnego  potraktowania  zdeklarowa-

nej  w³aœnie  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¿liwoœci¹ 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¿liwoœci naprzemiennie. 

Niezmienne zmienne? 

Zauwa¿ pewn¹ bardzo wa¿n¹ rzecz: jeœli tworzy-

my twór taki jak „zmienna” w pamiêci programu –

nie  mamy  mo¿liwoœci  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-

toœci.

(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

};

background image

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-

toœci 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³oœci od góry do do³u. Spróbujmy napi-

saæ  to  w  takiej  kolejnoœci,  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 wyœwietlaæ

wartoœæ aktualnego zliczenia. Procesor oczy-

wiœcie  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  wyœwietlenie  na  wyœwietlaczach  sta³ych

cyfr, a w pêtli nieskoñczonej tworzymy pros-

ty  algorytm,  jak  na  listingu  14.  Uff...  jeœli

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

umieœciæ  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êliœmy  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  oczywiœcie

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  wyœwietlacza,

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¿liwoœci  wykorzystania

„zmiennych” umieszczonych w pamiêci programu

w taki sam sposób, jakby by³a to zwyk³a zmienna.

Gdy  ju¿  umieœciliœmy  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  nieokreœlone.  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 wartoœci 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. 

Jeœli 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, jeœli 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 wartoœci.

Najlepiej jak zwykle wyjaœnia to przyk³ad. Poni¿-

sze zapisy s¹ równowa¿ne:

pgm_read_byte(&g_DaneCom[0]);

pgm_read_byteg_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 wyœwietlania licznika

(...)

#include <avr\delay.h>

(...)

uint16_t g_Licznik =

0

;

// Funkcja wyœwietla 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

;

}

background image

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 stworzyliœmy 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 wartoœci.

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 wyœwietliæ 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 wartoœci, który... nie istnieje.

W ten sposób informujemy kompilator, ¿e funkcja

nie  zwraca  wartoœci.  Jeœli  pisa³eœ  programy 

w BASCOM-ie, mo¿e zakrzykniesz teraz – to ju¿

znamy  –  tylko  do  tej  pory  funkcjê  niezwracaj¹c¹

danych nazywaliœmy procedur¹ (SUB). Znakomi-

cie, jeœli 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

wartoœci. 

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 podkreœlenia. 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.

Jeœli 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 pisaliœmy 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 wartoœci zmiennej w funkcji

wywo³uj¹cej. 

Wywo³anie 

w³asnej funkcji

Gdy  mamy  ju¿  utworzon¹

w³asn¹  funkcjê,  potrzebujemy

jeszcze mo¿liwoœci 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 korzystaliœmy 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 wyœwietliæ wartoœæ

jakiejœ  „zmiennej”  umieszczonej  w  pamiêci  pro-

gramu.  Jak  pokazywa³em  wczeœniej,  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 podaliœmy jako parametr naszej

funkcji  wynik  dzia³ania  innej  funkcji.  Istnienie

takiej mo¿liwoœci 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

wyœwietlania. 

Z drugiej strony, w bardzo z³o¿onych przypad-

kach,  rozwi¹zanie  ze  zmienn¹  tymczasow¹  mo¿e

znacznie uproœciæ zapis. Pamiêtaj wtedy, ¿e przed-

stawiona przed chwil¹ mo¿liwoœæ jest tylko mo¿li-

woœci¹.  C  nie  zmusza  nikogo  do  pisania  w  taki, 

a nie inny sposób. 

Deklaracja i definicja

Powinno  pojawiæ  siê,  ju¿  nawet  wczeœniej,  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

jeœli  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