kursC czesc003

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() 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

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() 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ñ

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

(a <

12

& b)

Spodziewamy siê, ¿e instrukcja if bêdzie

wykonana, jeœli a jest mniejsze od podanej

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

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

czywistoœci:

Nast¹pi porównanie liczby a 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 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 jeœli

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³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 && 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_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 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-

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


Wyszukiwarka

Podobne podstrony:
kursC czesc007
kursC czesc006
kursC czesc001
kursC czesc006a przeprowadzka
kursC czesc000 programowanie
kursC czesc018
kursC czesc008
kursC czesc002
kursC czesc013
kursC czesc011
kursC czesc010
kursC czesc007
kursC czesc018

więcej podobnych podstron