kursC czesc002

background image

38

Programowanie

Elektronika dla Wszystkich

Dziś będziemy starali się okiełznać dostępny
na płytce testowej wyświetlacz LED. Za
wyświetlacz zabierzemy się stopniowo. Nie
protestuj, jeśli pierwsze z napisanych progra−
mów będą, na przykład, beznadziejnie marno−
wać zasoby. Chcę, abyś przedstawione kody
traktował jako dobrą ilustrację do wiedzy o C,
którą chcę przekazać.

Ponieważ, jak już wspomniałem, do efek−

tywnego pisania programów na mikrokontro−
lery w C konieczna jest znajomość procesora,
zachęcam Cię do wyposażenia się w jakikol−
wiek opis jego rejestrów. Może być to doku−
mentacja dostępna na stronie firmy Atmel.
Może być jakaś dobra książka. Nie jest
konieczna literatura zajmująca się szczegóło−
wo asemblerem tego procesora. Potrzebna jest
natomiast wiedza o rejestrach wewnętrznych
służących do obsługi takich elementów jak
porty wejścia−wyjścia.

Małe repetytorium
– zaczynamy

Zanim zaczniesz pisać kod pro−
gramu, konieczne jest wy−
konanie kilku czynności, jakie
przedstawiłem w części 1. Zer−
knij na przedstawiony tam
rysunek 11. Ja nowy program
nazwałem LEDMulti. Zakła−
dam, że wybierzesz taką samą
nazwę i z takim założeniem
będę prowadził dalszy opis.
Utwórz folder na nowy pro−
gram. W moim przypadku był
to C:\GCC−src\Kurs\LEDmulti

Koniecznie skopiuj teraz do

naszego folderu plik makefile.
Może okazać się, że najwygod−

niej będzie skopiować plik z poprzedniego
programu. Zawsze możesz także sięgnąć do
szablonu umieszczonego w folderze
C:\WinAVR\samples. Po tej czynności uru−
chom Programmers Notepada. Utwórz nowy
projekt, wybierając z menu File−>New−>Pro−
ject.
Plik projektu zapisz w utworzonym właś−
nie katalogu. Ja nadałem mu nazwę LEDMul−
ti.pnproj –
nie wpisuj rozszerzenia, zostanie
ono nadane automatycznie.

Teraz utwórz plik główny naszego progra−

mu. Umożliwia to sekwencja File−>New−>C /
C++.
W tym przypadku możesz użyć także
znajdującego się w pasku narzędzi przycisku
tworzącego nieokreślony nowy plik tekstowy.
Plik powinien zostać zapisany oczywiście
w katalogu LEDMulti. Ja nadałem mu nazwę
LEDMulti.c – tym razem podanie rozszerze−
nia jest konieczne.

Nasz program składa się z dwóch plików:

makefile oraz LEDMulti.c. W celu wygodne−
go dostępu do nich, oba pliki dodajemy do

projektu: W okienku Projects kliknij na
nazwie LEDMulti i z rozwiniętego menu
wybierz komendę Add Files.

W tej chwili pliki powinny być już dostęp−

ne w panelu naszego projektu. Przypominam,
że powtarzam tutaj informacje, które przedsta−
wia rysunek 11 (część pierwsza cyklu). Jeśli
w którymś momencie będziesz miał problem,
być może uznasz, że opisałem tutaj coś zbyt
skrótowo – wróć do wspomnianej ilustracji.

Edytor wie już, jakie pliki wchodzą

w skład naszego programu, pora poinformo−
wać o tym kompilator. W tym celu edytuj plik
makefile. Ustawienia, jakie powinieneś tutaj
wprowadzić, pokrywają się z ustawieniami
wprowadzanymi przy okazji pierwszego ćwi−
czenia: nie zmienił się przecież typ procesora,
zakładam, że kwarc także jest taki sam, plik
wyjściowy w formacie IntelHEX bardzo nam
pasuje, optymalizacja rozmiaru kodu nadal
wydaje się dobrym rozwiązaniem, tak samo
jak typ pliku przeznaczony dla emulatora.
Różnica pojawi się w polu TARGET. Znajdź
odpowiednią linię i wpisz tutaj nazwę pliku
głównego. Będzie to najprawdopodobniej
LEDMulti (bez jakichkolwiek rozszerzeń,

brak spacji na końcu!).

Znakomicie! Jesteśmy gotowi do napisania

kolejnego programu. Przy odrobinie wprawy
wszystkie konieczne czynności wykonasz
zapewne w czasie krótszym, niż mogłoby się
wydawać z długości opisu.

Kodowanie
– część standardowa:

Skoro tworzymy od początku nowy program,
chciałbym, przy okazji, zaproponować Ci

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ęść 2

Co uważniejsi Czytelnicy zauważyli zapewne, że w części pierwszej listingi 1 i 2 są iden−
tyczne. Ci, którzy nie spostrzegli tego od razu, ale próbowali przeprowadzić na ich podsta−
wie kompilację programu, zorientują się, że jest to niemożliwe.

Między moim komputerem a drukarnią wkradł się błąd, który należy jak najszybciej

sprostować. Na tej stronie przedstawiam poprawiony listing 1.

Dodatkowe pliki znajdujące się na stronie internetowej Elektroniki dla Wszystkich są

prawidłowe. Sposobu poprawienia błędu można także domyślić się na podstawie treści arty−
kułu – gratuluję, jeśli udało Ci się to zauważyć.

Listing 1 Prawidłowa zawartość pliku make.bat

@

set

PATH=C:\WinAVR\BIN;C:\WinAVR\UTILS\BIN;

make.e

Listing 4 Podstawowy szablon pliku głównego

////////////////////////////////////////////////////////////////////////////////

// LEDMulti.c - plik g³ówny programu obs³ugi multipleksowanego wyœwietlacza LED

// przeznaczenie: "Programowanie procesorów w jêzyku C (EdW)"

//

// Autor: Rados³aw Koppel Kompilator: WinAVR 20050214

////////////////////////////////////////////////////////////////////////////////

#include <avr\io.h>

int

main

(

void

)

{

return

0

;

}

background image

pewien dobry zwyczaj. Zanim jeszcze za−
czniemy zastanawiać się nad tym, jak w ogóle
nasz program ma działać, od razu wpiszemy
do pliku źródłowego kilka elementów, które
powinny pojawić się tutaj zawsze. Przedsta−
wia to listing 4.

Dobrym zwyczajem jest umieszczenie na

początku komentarza, w którym zawieramy
informację na temat przeznaczenia pliku,
autora oraz wykorzystanego kompilatora.
Ułatwi to orientację nie tylko innym, ale prze−
de wszystkim nam, gdy zechcemy wrócić na
przykład do jakiegoś programu pisanego kilka
miesięcy wcześniej.

Zauważ, że ja stosuję tutaj nietypowe dla C

komentarze gdzie znak // oznacza komentarz
do końca linii. Jak już wspomniałem – więk−
szość kompilatorów na to pozwala. Dla mnie
jest to kwestia nawyku. Jeśli chcesz, możesz
stosować komentarze zaczynające się od /*
i kończące na */ − uzyskasz wtedy większą
przenośność kodu.

Każdy program pisany dla kompilatora

WinAVR powinien zawierać dołączenie pliku
nagłówkowego avr\io.h. Umożliwi to stoso−
wanie symbolicznych nazw rejestrów czy
wektorów przerwań. Kompilator sam zade−
cyduje z jakiego pliku dokładnie ma skorzys−
tać. Zrobi to na podstawie informacji o wska−
zanym w pliku makefile typie procesora.

Dalej, chociażby w ciemno, możemy napi−

sać szkielet funkcji main. Stąd zawsze rozpo−
cznie się wykonywanie nowego programu.
Nieważne, czy będziemy pisać nowe funkcje
przed, czy za funkcją main. Jest to pewna róż−
nica w stosunku do BASCOM−a... Moim zda−
niem, bardzo wygodna różnica.

Zauważ, że poza kilkoma liniami, które

zdołałem już omówić w pliku, jest jeszcze...
dużo wolnego miejsca. Dokładnie tak to
powinno wyglądać. Wygodnie jest zostawić
miejsce między dołączanymi nagłówkami
a funkcją main oraz zaraz za otwartą klamer−
ką wyznaczającą ciało naszej funkcji. W tym
miejscu pojawią się już elementy, które zade−
cydują o tym, jak ma zachowywać się nasz
procesor. Przypomnę jeszcze tylko o pewnym
istotnym drobiazgu: Na koniec pliku klika−
my ENTER.
Inaczej program będzie się
kompilował, ale kompilator zgłosi niepotrzeb−
ne nam ostrzeżenia.

Kompilacja
szablonowego pliku

Teraz proponuję Ci coś, co może w pierwszej
chwili wydać się dziwne. Zapisz nasz szablon,
a następnie – skompiluj go. W tej chwili chce−
my przede wszystkim zaspokoić naszą cieka−
wość. Zobaczyć, co jest w takim „pustym”
kodzie. Czy jeśli procesor nic nie robi, to nie
ma w nim jakiegokolwiek kodu... Jednak
w późniejszym czasie znaczenie takiego dzia−
łania jest nieco inne. Na tym etapie możemy
sprawdzić, czy wszystko zostało prawidłowo
skonfigurowane. Jeśli podczas kompilacji
otrzymamy informację o błędzie, nie musimy
się długo zastanawiać, czy przyczyna leży
w konfiguracji, czy w napisanym właśnie
kodzie.

Aktualnie jednak z czystej ciekawości

przeprowadzamy kompilację. Jeśli wszystkie
narzędzia skonfigurowałeś tak jak propono−
wałem w poprzedniej części – zrobisz to naj−
prościej za pomocą przycisku F7. Kompilacja
powinna przebiec bez trudności. Końcową
część opisu jej przebiegu pokazuje rysunek 18.

Na rysunku widać wyraźnie, że kompilator

wygenerował kod zajmujący dokładnie 90
bajtów pamięci programu. Osoby lubiące
język asemblera mogą za pomocą AVRStudia
podejrzeć, co pojawia się w pliku wyjścio−
wym. Okaże się wtedy, że 22 bajty zajmują
wektory przerwań. Mimo tego, że my świado−
mie przerwań nie wykorzystujemy, kompila−
tor utworzył tutaj domyślne procedury ich
obsługi. Procedury te działają w taki sposób,

że wystąpienie któregoś z nieprzewidzianych
przerwań spowoduje praktycznie programo−
wy reset programu. Warto o tym wiedzieć,
aby w przyszłości nie zaskoczył nas ten fakt.

Reszta kodu zajmuje się inicjacją pamięci,

stosu... Ogółem przygotowaniem programu
do pracy, podobnie jak robił to kompilator
BASCOM’a. Znajdziemy tutaj także pętlę
nieskończoną, jaką kończy się funkcja main.
Dzięki temu mamy pewność, że procesor po
inicjacji zatrzyma się i na tym skończy swoje
działanie.

Definicje stałych
– zapomnij o „aliasach”

Pisząc nasz program sterowania wyświetla−
czem, moglibyśmy posługiwać się bezpośred−
nio nazwami odpowiednich portów. Takie
rozwiązanie jest jednak zarówno niewygodne,
jak i niepraktyczne. Zwykle więc nadajemy
wyprowadzeniom jakieś własne, kojarzące się
z funkcją nazwy. Zmniejsza to prawdopodo−
bieństwo popełnienia błędu oraz upraszcza
dostosowanie programu do ewentualnych
zmian układowych.

W BASCOM−ie posługiwaliśmy się w tym

celu komendą Alias. W C istnieje tylko jedna
dyrektywa służąca do tworzenia stałych licz−
bowych, symbolicznych, znakowych, a nawet
całych makr. Dyrektywa ta to #define. Jest
ona przetwarzana przez preprocesor (czyli
jeszcze zanim rozpocznie się kompilacja).
Jeśli gdzieś w kodzie pojawi się nazwa, która
została za jej pomocą zdefiniowana, zostanie
ona automatycznie zastąpiona przez odpo−
wiedni ciąg znaków. To ważne: nie będzie to
liczba, nie będzie to tekst. Użycie słowa klu−
czowego, które wcześniej zdefiniowaliśmy,
jest praktycznie równoważne z wpisaniem
w edytorze odpowiedniego ciągu znaków
(z pominięciem komentarzy). Do skutków,
jakie się z tym wiążą, jeszcze powrócimy.
W tej chwili nie sprawi nam to przykrych nie−
spodzianek. Składnię interesującej nas dyrek−
tywy przy tworzeniu prostych przypisań oma−
wia odpowiednia ramka.

39

Programowanie

Elektronika dla Wszystkich

Rys. 18 Kompilacja szablonu.

ABC... C

#define − najprostsze zastosowanie

Komendy #define preprocesora możemy
użyć w celu przypisania wygodnych dla nas
nazw dla dowolnych elementów procesora.
Najczęściej będą to rejestry wejścia−wyjścia.
W zastosowaniu takim komenda #define
znakomicie zastępuje znaną z BASCOMA
lub asemblera komendę alias. W takim
zastosowaniu jej składnię przedstawia
zawarty w ramce obrazek.

Listing 5 Zamiast „aliasów”

// Definicje wyprowadzeñ

#define LED_A 0

#define LED_B 1

#define LED_C 2

#define LED_D 3

#define LED_E 4

#define LED_F 5

#define LED_G 6

#define LED_DP 7

#define LEDPORT PORTB

#define LEDDDR DDRB

#define COM1 6

#define COM2 5

#define COM3 4

#define COM4 3

#define COMPORT PORTD

#define COMDDR DDRD

background image

Przystąpmy teraz do przypisania własnych

oznaczeń dla wyprowadzeń naszego układu.
Umieść kursor w wolnej przestrzeni za dołą−
czonymi nagłówkami. W tym miejscu powi−
nien pojawić się kod widoczny na listingu 5.
Nadajemy tutaj nazwy zarówno samym wy−
prowadzeniom, jak i odpowiednim portom.
Dodatkowo pojawia się przypisanie dla odpo−
wiednich rejestrów DDRx. Zgodnie z doku−
mentacją naszego procesora, wpisanie tutaj
logicznego 1 spowoduje ustawienie odpo−
wiedniego wyprowadzenia jako wyjścia.
Wpisanie logicznego 0 natomiast, ustawi
odpowiednie wyprowadzenie jako wejście. Ze
względu na konfigurację musimy więc mieć
dostęp do wspomnianych rejestrów.

Jeśli teraz gdziekolwiek w programie, za

wpisanym właśnie ciągiem definicji wyko−
rzystamy dowolne z utworzonych oznaczeń,
zostanie ono zamienione na oznaczenie
rozumiane przez kompilator, odnoszące się do
interesującego nas portu lub wyprowadzenia.

Wyświetlanie
– coś prostego

Teraz, kiedy mamy już zdefiniowane wszyst−
kie potrzebne nam aktualnie wyprowadzenia,
napiszmy coś, co zacznie działać. Stworzymy
coś prostego, co pozwoli zweryfikować naszą
ideę sterowania. Pomysł jest taki, aby na
pierwszym z wyświetlaczy wyświetlić cyfrę
1. I tyle. Brak obsługi wszystkich wyświetla−
czy, zmiany wyświetlanej wartości... prak−
tycznie chodzi tylko o to, aby odpowiednio
wysterować wszystkie porty i zakończyć pro−
gram. Zauważ, że aktualna idea, chociaż nie
przedstawia dużych możliwości, jest w zasa−
dzie małym elementem programu w pełni ob−

sługującego wyświetlacz. Będziemy musieli
wtedy kolejno włączać odpowiednie wyświet−
lacze. Spróbujmy napisać taki maleńki frag−
ment.

Przenieś kursor za−

raz za klamerką wy−
znaczającą początek
naszej funkcji, jeszcze
przed komendą return.
Propozycję całej funk−
cji main przedstawia

listing 6. Dodatkowo
w zrozumieniu kodu
pomocny może być
rysunek 19 – przedsta−
wiający przyporządko−
wanie nazw segmentów
dla sterowanego

wyświetlacza. Warto zerknąć do ramki opisu−
jącej zasadę przeprowadzania obliczeń w C
oraz do wkładki zawierającej opis najpotrzeb−
niejszych nam operatorów.

Spróbujmy zanalizować, jak powinien

działać przedstawiony kod. Na początku,
w części oznaczonej jako inicjacja, następuje
ustawienie odpowiednich portów jako wyj−
ścia. Zgodnie z dokumentacją procesora AVR
wyprowadzenie portu jest traktowane jako
wyjście w chwili, gdy na odpowiadającej mu
pozycji w rejestrze DDRx znajduje się jedyn−
ka. Pierwsza linia nie powinna już budzić
wątpliwości. Jedyną nowością jest to, że
zamiast odwoływać się do odpowiednich
rejestrów bezpośrednio, korzystamy ze zdefi−
niowanych wcześniej nazw. Bardziej skom−
plikowana wydawać się może linia następna.
Aby ją zrozumieć, konieczne jest poznanie
występujących tutaj operatorów. Operator <<
daje w wyniku liczbę, która znajduje się po
jego lewej stronie, przesuniętą logicznie w le−
wo o podaną liczbę bitów, która musi znaleźć
się po stronie prawej operatora. Zapis taki, jak
widzisz, jest często stosowany tam, gdzie chce−
my mieć kontrolę nad poszczególnymi bitami,

40

Programowanie

Elektronika dla Wszystkich

Listing 6 Wyświetlanie 1 na pierwszym wyświetlaczu

int

main(

void

)

{

/////////////////////////////

// inicjacja

LEDDDR =

0xff

;

COMDDR =

1

<<COM1 |

1

<<COM2 |

1

<<COM3 |

1

<<COM4;

// koniec inicjacji

/////////////////////////////

COMPORT = ~

1

<<COM1;

LEDPORT = ~(

1

<<LED_B |

1

<<LED_C);

return

0

;

}

ABC... C

Przeprowadzanie obliczeń

Jak w (chyba) każdym języku wysokiego
poziomu, obliczenia w C przeprowadza się,
korzystając ze zbioru operatorów. Nie ma
tutaj jednak ograniczenia mówiącego na
przykład, że można wykonywać tylko jedno
działanie naraz. Tylko jeśli napiszemy bardzo
złożone wyrażenie arytmetyczne, zostaniemy
poproszeni przez kompilator o rozbicie go na
prostsze. Osobiście jednak nie pamiętam, aby
taka sytuacja w ogóle kiedykolwiek mi się
przytrafiła.

Równania zapisujemy, powiedziałbym,

w sposób naturalny. Na przykład czysto teo−
retycznie wyobraźmy sobie, że chcemy war−
tość jakiejś zmiennej a pomnożyć przez dwa
i dodać do niej 1. Kod w takim przypadku
wyglądałby następująco:

a = 2 * a + 1;

Całkowicie równoważny jest także kod:

a = 1 + 2*a;

Okazuje się, że w C każdy operator ma

swoją wagę. Operatory są wykonywane
w określonej przez nią kolejności. Gdy stosu−
jemy typowe operacje znane jeszcze ze szko−
ły podstawowej – możemy śmiało założyć, że
działania zostaną wykonane dokładnie tak jak
byliśmy tego nauczeni. Problemy zaczynają
się dla operatorów, które praktycznie poza
programowaniem nie mają swoich odpowied−
ników.

W dodatku do tego odcinka znajdziesz

tabelę skrótowo omawiającą najpotrzebniej−
sze nam operatory. Znajdziesz także zesta−
wienie pokazujące kolejność wykonywania
operatorów. Jeśli jakieś operatory znajdują się
na tym samym poziomie, obliczenia są prze−
prowadzane od lewej do prawej – znowu
zgodnie z intuicją.

Co jednak, jeśli chcemy na przykład naj−

pierw dodawać a później mnożyć? Znów
możemy zastosować szkolną wiedzę: służą
do tego zwykłe nawiasy. Przykładowo:

a = 2 * (a + 1);

spowoduje przemnożenie przez 2 poprzedniej
wartości zmiennej, a powiększonej o 1.

Warto tutaj zauważyć, że operator przypi−

sania, taki jak znak równości, ma najniższy
piorytet. Oznacza to, że zawsze najpierw war−
tość będzie obliczona, a dopiero później zapi−
sana we wskazane miejsce (także i tutaj
można stosować nawiasy w celu zakłócenia
tej kolejności... jednak to już zupełnie inna
historia).

Nie bój się obliczanych stałych

Warto wiedzieć, że nawet jeśli optymalizacja
kodu zostanie wyłączona, standard ANSI C
nakazuje, aby wszystkie stałe zostały wyli−
czone przez kompilator.

Oznacza to, że jeśli to wygodne lub bar−

dziej czytelne, możesz podać kompilatorowi
całe długie równanie ze stałych. Ostatecznie
w kodzie asemblerowym pojawi się tylko
jedna liczba.

Rys. 19

background image

a z jakichkolwiek powodów okazuje się
to wygodniejsze niż zapis szesnastkowy.
Operator | wykonuje logiczną operację OR na
argumentach umieszczonych po obu stronach.
Na mojej klawiaturze znaczek ten znajduje się
na tym samym klawiszu, gdzie umieszczono
backslash (\).

Jeśli, mimo omówienia działania poszcze−

gólnych operatorów, idea takiego zapisu wy−
daje Ci się niejasna – spojrzyj na rysunek 20.

Powinien wyjaśnić znacznie lepiej, co się
dzieje niż nawet długi i dokładny opis. Ważne
jest to, że operator << ma wyższy priorytet niż
operator sumy logicznej |.

Warto pamiętać, że mimo iż w naszym

zapisie pojawia się aż 8 działań (4 przesunię−
cia, trzy sumy logiczne, jedno przypisanie),
kodzie maszynowym pojawi się tylko wpisa−
nie do rejestru stałej wartości. Dlaczego tak
się dzieje – mówi o tym ramka o obliczeniach
w C.

W kolejnej linii chcemy włączyć tranzys−

tor sterujący pierwszym wyświetlaczem LED.
Ponieważ tranzystory aktywujące wyświetla−

cze są włączane przez 0 logiczne,
po ustawieniu na odpowiednim
miejscu jedynki dokonujemy ne−

gacji, bit po bicie, całego uzyskanego wyniku.
Wykonuje to operator tyldy ~, umieszczonej
przed wyrażeniem.

Zauważ teraz w jak wygodny sposób

możemy zapisać znak odpowiadający zaświe−
ceniu jedynki na wyświetlaczu. Zapomnij
o przeliczaniu na kartce papieru wyglądu cyfr
na liczby dziesiętne. Zapis jak przedstawiony
w listingu 6 jest znacznie bardziej czytelny,
przenośny i łatwiejszy w modyfikacji. Myślę,
że jesteś już w stanie zrozumieć działanie tej
linii. Pamiętaj jedynie, że dany segment
wyświetlacza jest aktywowany przez wysta−
wienie logicznego 0 na odpowiednie wypro−
wadzenie portu.

Przeprowadź teraz kompilację programu

i załaduj go do procesora. Jeśli nie uzyskałeś
efektu takiego jaki nas interesował – nie
przejmuj się... u mnie zaświeciły się wszyst−
kie wyświetlacze. Zapewne domyślasz się już,

że skoro mamy taki sam
wynik, nie należy szukać
błędu w sprzęcie. Jeśli
chcesz, w ramach ćwicze−
nia możesz przesymulo−
wać program. Ja tak zro−
biłem. Przypomnij sobie,
w jaki sposób skonfiguro−
wać środowisko AVRS−
tudio.
Jeśli to zrobisz,
zauważysz, że do portu
sterującego tranzystorami
wyświetlaczy wpisywana
jest wartość 0x80. Ozna−
cza to 10000000(b). Zu−
pełnie nie to chcieliśmy
uzyskać. Spójrz jeszcze

45

Programowanie

Elektronika dla Wszystkich

Rys. 21 Efekt występowania

priorytetów operatorów.

Rys. 20 Zasada obliczania wartości

wpisywanej do COMDDR na listin−
gu 6.

ABC... C

Zmienne – podstawy

Zakładam tutaj, że wiesz, co to są zmienne
i masz pojęcie do czego służą. Ze względu na
skrótowy charakter tego kursu tutaj tylko
napiszę „jak to się robi w C”.

Typy proste

Zmiennej takiej używa się bardzo podobnie
jak miało to miejsce w BASCOM−ie. Niewiel−
ka różnica pojawia się w sposobie jej dekla−
racji. Nie ma tutaj słowa kluczowego Dim.
Zmienną tworzymy, jak pokazuje przykład:

typ nazwa [=wartość początkowa];

Typy proste zmiennych jakie udostępnia

C, znajdziesz w tabeli 1.

Zacznijmy globalnie

Na początek posłużymy się tylko zmiennymi
globalnymi. Powód takiego działania jest
prosty: ich wykorzystanie i sposób obsługi
jest najbardziej zbliżony do zmiennych, jakie

poznałeś już dobrze, uczestnicząc w kursie
BASCOM. Zgodnie ze standardem, wszyst−
kie zmienne globalne trzeba utworzyć jeszcze
zanim pojawi się jakakolwiek funkcja. GCC
umożliwia jednak utworzenie zmiennej w
dowolnym miejscu, poza wnętrzem funkcji.
Nie będziemy z tego korzystać, ponieważ
jedyne, co możemy przez to uzyskać, to
malowniczy bałagan w kodzie.

Warto wiedzieć, że jeśli zmiennej global−

nej nie nadasz określonej wartości początko−
wej, zostanie ona domyślnie wyzerowana
jeszcze przed rozpoczęciem wykonywania
programu.

Tablice jednowymiarowe

C umożliwia tworzenie wielowymiarowych
tablic. W prostych przypadkach jednak oka−
zuje się, że tablice jednowymiarowe są całko−
wicie wystarczające. Dla wielu osób będą one
zapewne wyglądały bardzo przyjaźnie. Ich
tworzenie jest zbliżone do tablic BASCOM−
owych:

typ nazwa[ilość] [={inicjacja}];

W samej obsłudze pojawią się tylko dwie

różnice w stosunku do BASCOM−a:

1. Zamiast nawiasów okrągłych, korzysta−

my z nawiasów kwadratowych.

2. Indeksy tablicy zaczynają się od 0

i mają wartości do ilość−1, zamiast BASCOM−

owego zakresu od 1 do ilość.

Plik <inttypes.h>

W praktycznych programach proponuję Ci
wykorzystanie pliku, o którym właśnie mo−
wa. Definiuje on bardzo wygodne nazwy ty−
pów zmiennych całkowitych, z których mo−
żesz (ale nie musisz) korzystać:

background image

raz na listing 6. Jest w nim ewidentny błąd.
Spróbuj zauważyć go sam. Porównaj wyko−
nywane działania z tabelką we wkładce, pre−
zentującą kolejność obliczeń.

Pozornie prosty zapis:

COMPORT = ~1<<COM1;

jest w rzeczy samej wyrażeniem składającym
się z dwóch działań, przesunięcia oraz negac−
ji. Chcemy przesunąć jedynkę na pozycję
odpowiadającą tranzystorowi pierwszego
wyświetlacza, a następnie wynik zanegować.
Jednak operator negacji ma przecież wyższy
priorytet niż operator przesunięcia! Efekt ilus−
truje rysunek 21. Nie wdając się w dalsze
szczegóły, na listingu 7 prezentuję sposób
poradzenia sobie z problemem.

Przykład ten ilustruje, jak ważna jest różna

kolejność wykonywania działań przez kompi−
lator. Gdy teraz skompilujesz poprawiony pro−
gram – wszystko będzie tak jak planowaliśmy.

Wyświetlanie
– wyższy stopień

Podsumujmy to, co już mamy. Umiemy
wywołać aktywację wybranego wyświetlacza.
Gdy spojrzysz na utworzony program –
myślę, że nie będziesz miał wątpliwości, jak
wybrać inny wyświetlacz lub też w jaki spo−
sób wyświetlić inny znak. Teraz chcielibyśmy
mieć możliwość sterowania wszystkimi czte−
rema wyświetlaczami jednocześnie. Oczywiś−
cie odbędzie się to na zasadzie ich szybkiego
przełączania. Myślę, że na podstawie kursu
BASCOM−a wiesz już, że tym razem najwy−
godniejsze będzie zastosowanie kilku zmien−
nych. Będziemy potrzebowali zmiennej,
w której przechowamy informację na temat
tego, który z wyświetlaczy jest aktualnie
obsługiwany. Dodatkowo wykorzystamy tab−
licę, składającą się z czterech elementów, każ−
dy zawierający obraz tego, co chcemy
wyświetlić na odpowiednim wyświetlaczu.
Jeśli zaprzyjaźniamy się już z tablicami – od
razu wykorzystamy jeszcze jeden element
tego typu w celu zapisania informacji, w jaki

sposób aktywować wybrany wyświetlacz.

Spróbujmy napisać wciąż jeszcze prosty

kod, który jednak umożliwi wyświetlenie na
kolejnych wyświetlaczach różnych znaków.

W odpowiedniej ramce znajdziesz podsta−

wowe informacje, jak najprościej poradzić
sobie z aktualnie potrzebnymi nam zmienny−
mi. Listing 8 z kolei pokazuje proponowany
kod. Wykropkowałem tylko definicje wypro−
wadzeń, które cały czas pozostają bez zmian.

Skompiluj program i wyślij

go do procesora. Jeśli wszystko pos−
zło dobrze, powinieneś zobaczyć na
wyświetlaczu kolejne cyfry.

W kodzie inicjacja portów nie

uległa zmianie. Teraz jednak za−

miast wpisywać wygląd cyfry bezpośrednio
do portu, wpisujemy go w przeznaczoną do

tego celu tablicę. Pamiętaj, że chociaż to wpi−
sywanie wygląda na rozbudowane działanie,
zostanie zamienione tylko na przesłanie stałej
wartości do odpowiedniej komórki RAM pro−
cesora. Tak więc jeszcze raz to powtórzę, nie
ma sensu dokonywać ręcznych przeliczeń
wyglądu znaków na odpowiednie liczby.

Następnie rozpoczyna się pętla nieskoń−

czona, gdzie włączane są kolejno odpowied−
nie wyświetlacze. Zauważ, że wybrany wy−
świetlacz jest aktywowany za pomocą danej
zawartej w tabeli g_DaneCom. Na koniec
zmienna g_AktWyswietlacz jest zwiększana,
przy czym jeśli przekroczy wartość 3 – cykl
rozpoczyna się od początku.

Oczywiście − wypracowane rozwiązanie

nie jest jeszcze optymalne. Jeśli pisałeś już
analogiczny program w BASCOM−ie lub
orientujesz się w temacie, możesz z miejsca
wskazać dwie sprawy, które można poprawić:

1. Obsługa wyświetlacza powinna odbywać

się w przerwaniach!, tak aby nie angażować
do tego celu całej dostępnej mocy procesora.

2. Tablica g_DaneCom zajmuje miejsce

w cennej pamięci RAM, jednocześnie
w pamięci programu przechowywana jest
kopia jej zawartości w celu wstępnego

46

Programowanie

Elektronika dla Wszystkich

Zauważ, że przed nazwami

zmiennych dodałem oznaczenie
g_. W ten sposób zaznaczam, że
jest to zmienna globalna. Jest to
dobry zwyczaj, który w przys−
złości ułatwi Ci orientowanie się
w kodzie.

Listing 8 Pierwsze starowanie wyświetlaczem z wykorzystaniem zmiennych

#include <avr\io.h>

#include <inttypes.h>

(...)

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;

// 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

(;;)

{

// 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³ugi-

wany wyœwietlacz

++g_AktWyswietlacz;

if

(g_AktWyswietlacz >

3

)

g_AktWyswietlacz =

0

;

}

return

0

;

}

Dobrym zwyczajem jest stosowanie się

do reguły, która mówi, że jeżeli nie jesteś
pewien, w jakiej kolejności wykonywane są
dane operacje – stosuj w takich miejscach
nawiasy. Umieszczenie dwóch nawiasów
więcej nie zaszkodzi, a czasami przyczynia
się nawet do wzrostu czytelności kodu –
optycznie wydziela pewne logiczne frag−
menty obliczeń.

Listing 7 Poprawa programu z listingu 6

(...)

COMPORT = ~(

1

<<COM1);

LEDPORT = ~(

1

<<LED_B |

1

<<LED_C);

return

0

;

}

background image

ustawienia. Jest to oczywiste marnotrawstwo.
Powinna istnieć przecież możliwość odczyty−
wania tego typu danych bezpośrednio z pa−
mięci programu.

Oba zarzuty są jak najbardziej słuszne. Od

umieszczenia odpowiedniej tablicy w pamięci
danych oraz wykorzystania przerwań dzieli
nas już tylko krok. Teraz jednak chcę zwrócić
Twoją uwagę na inny drobiazg: Zastanówmy
się, co się dzieje z portem, który wykorzy−
stujemy do sterowania anodami wyświetla−
czy. Zawsze zapisujemy tam na sztywno wy−
braną przez nas wartość. Chwilowo możemy
nie zauważać problemu, jaki nam to sprawi,
jednak gdy zechcemy wykorzystać do czegoś
pozostałe wyprowadzenia tego portu, może
okazać się to niemożliwe, a już na pewno nie−
wygodne. Musimy więc poprawić kod tak,
żeby zmieniane były tylko wybrane wypro−
wadzenia, resztę pozostawiając bez zmian.

Odpowiednią poprawkę pokazuje listing 9.

W jego zrozumieniu może pomóc ramka

„Co z bitami?”.

W tym przypadku jednak kompilator nie

będzie generował instrukcji ustawiających
poszczególne bity. Zwartość portu będzie
wczytana do rejestru, wykonana zostanie od−
powiednia funkcja logiczna, a następnie dana
zostanie przesłana do rejestru wyjściowego.
Daje to 3 instrukcje. Gdybyśmy chcieli zrobić
to samo za pomocą ustawiania / zerowania
poszczególnych bitów, konieczne byłoby
zastosowanie 4 instrukcji. Dodatkowo poja−
wiałyby się niewielkie opóźnienia między
przełączaniem poszczególnych bitów. Dla nas
w tej chwili nie miałoby to znaczenia – ale

kompilator o tym nie wie i musi założyć, że
jeśli napisaliśmy funkcję tak jak na listingu
9,
to chcemy, aby wszystkie wyprowadze−
nia zmieniły swój stan w tym samym
momencie. Nieczęsto, ale czasami może
okazać się to bardzo ważne.

Podsumowanie

Dziś udało nam się napisać kilka progra−
mów. Starałem się pokazać Ci, jak eleganc−
ko rozpocząć nowy plik z kodem źródło−
wym. Zobaczyłeś, jak radzić sobie z przy−
pisywaniem dogodnych nazw do wyprowa−
dzeń. Poznałeś wstępnie ideę przeprowa−
dzania obliczeń w C. Dowiedziałeś się, tro−
chę o zmiennych, bez których nie obejdzie
się rzeczywisty program. Dowiedziałeś się,
jak zerować i ustawiać poszczególne bity.
Przy okazji udało nam się stworzyć pro−
gram, który naprawdę obsługuje wyświet−

lacz multipleksowany. Fakt, że ma on jeszcze
kilka niedociągnięć i że brakuje mu kilku

kroczków do praktyczności... postaramy się
rozwiązać już za miesiąc.

Jeśli chcesz spróbować swoich aktualnych

sił, zobacz, czy uda Ci się już dziś stworzyć
tablicę zawierającą wszystkie znaki kodu
szesnastkowego (0−F). Czy uda Ci się za jej
pomocą wpisywać wartości do tablicy
wyświetlacza?

Radosław Koppel

47

Programowanie

Elektronika dla Wszystkich

* Rozmiar typu int jest różny dla różnego typu maszyny. Dla pro−

cesora 32− i więcej bitowego będzie on miał 32 bity. Dla procesora 16−
i mniej bitowego − 16 bitów.

** AVR−GCC nie obsługuje formatu liczb zmiennoprzecinko−

wych o podwójnej precyzji. Ze względu na zgodność ze standardem
można deklarować zmienne typu double, jednak kompilator potraktu−
je je tak, jakby były to zmienne typu float.

Tabela 1 Typy proste zmiennych

dostępne w C

ABC... C

Co z bitami?

W C brak jest instrukcji operujących bezpo−
średnio na bitach

*

. Jednak możemy opero−

wać na nich, stosując prostą sztuczkę. Była
o tym mowa wcześniej. Załóżmy, że chce−
my operować na 6 wyprowadzeniu portu D:

Aby je ustawić:

PORTD |= 1<<6;

Wyzerować:

PORTD &= ~(1<<6);

Okazuje się, że GCC doskonale wie o co

chodzi. Mimo tego, że wydaje się, że na
przykład przedstawione ustawienie wypro−
wadzenia portu wywoła cały ciąg operacji,
w asemblerze jest generowany tylko nastę−
pujący kod:

sbi PORTD, 6

Czyli najprościej, jak się dało!

*

Nie jest to do końca prawdą, istnieje specjalny typ

zmiennej – tak zwane pole bitowe. Wiele prostych kom−
pilatorów jednak nie obsługuje takiej opcji. Standard
także niejasno tłumaczy ich działanie, co może powodo−
wać trudności z przenośnością kodu.

Listing 9 Ulepszenie programu z listingu 8.

// Wygaszenie wyœwietlaczy

COMPORT |=

1

<<COM1 |

1

<<COM2 |

1

<<COM3 |

1

<<COM4;

// Wystawienie odpowiedniej danej

LEDPORT = g_DaneWyswietlacza[g_AktWyswietlacz];

// W³¹czenie odpowiedniego wyœwietlacza

COMPORT &= g_DaneCom[g_AktWyswietlacz];

R

E

K

L

A

M

A


Wyszukiwarka

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

więcej podobnych podstron