You are on page 1of 6

Programowanie

Programowanie procesorów
w języku C Część 2
projektu: W okienku Projects kliknij na
Co uważniejsi Czytelnicy zauważyli zapewne, że w części pierwszej listingi 1 i 2 są iden−
nazwie LEDMulti i z rozwiniętego menu
tyczne. Ci, którzy nie spostrzegli tego od razu, ale próbowali przeprowadzić na ich podsta−
wybierz komendę Add Files.
wie kompilację programu, zorientują się, że jest to niemożliwe.
W tej chwili pliki powinny być już dostęp−
Między moim komputerem a drukarnią wkradł się błąd, który należy jak najszybciej
ne w panelu naszego projektu. Przypominam,
sprostować. Na tej stronie przedstawiam poprawiony listing 1.
że powtarzam tutaj informacje, które przedsta−
Dodatkowe pliki znajdujące się na stronie internetowej Elektroniki dla Wszystkich są
wia rysunek 11 (część pierwsza cyklu). Jeśli
prawidłowe. Sposobu poprawienia błędu można także domyślić się na podstawie treści arty−
w którymś momencie będziesz miał problem,
kułu – gratuluję, jeśli udało Ci się to zauważyć.
być może uznasz, że opisałem tutaj coś zbyt
Listing 1 Prawidłowa zawartość pliku make.bat skrótowo – wróć do wspomnianej ilustracji.
Edytor wie już, jakie pliki wchodzą
@set PATH=C:\WinAVR\BIN;C:\WinAVR\UTILS\BIN; w skład naszego programu, pora poinformo−
make.e wać o tym kompilator. W tym celu edytuj plik
makefile. Ustawienia, jakie powinieneś tutaj
wprowadzić, pokrywają się z ustawieniami
Dziś będziemy starali się okiełznać dostępny niej będzie skopiować plik z poprzedniego wprowadzanymi przy okazji pierwszego ćwi−
na płytce testowej wyświetlacz LED. Za programu. Zawsze możesz także sięgnąć do czenia: nie zmienił się przecież typ procesora,
wyświetlacz zabierzemy się stopniowo. Nie szablonu umieszczonego w folderze zakładam, że kwarc także jest taki sam, plik
protestuj, jeśli pierwsze z napisanych progra− C:\WinAVR\samples. Po tej czynności uru− wyjściowy w formacie IntelHEX bardzo nam
mów będą, na przykład, beznadziejnie marno− chom Programmers Notepada. Utwórz nowy pasuje, optymalizacja rozmiaru kodu nadal
wać zasoby. Chcę, abyś przedstawione kody projekt, wybierając z menu File−>New−>Pro− wydaje się dobrym rozwiązaniem, tak samo
traktował jako dobrą ilustrację do wiedzy o C, ject. Plik projektu zapisz w utworzonym właś− jak typ pliku przeznaczony dla emulatora.
którą chcę przekazać. nie katalogu. Ja nadałem mu nazwę LEDMul− Różnica pojawi się w polu TARGET. Znajdź
Ponieważ, jak już wspomniałem, do efek− ti.pnproj – nie wpisuj rozszerzenia, zostanie odpowiednią linię i wpisz tutaj nazwę pliku
tywnego pisania programów na mikrokontro− ono nadane automatycznie. głównego. Będzie to najprawdopodobniej
lery w C konieczna jest znajomość procesora, Teraz utwórz plik główny naszego progra− LEDMulti (bez jakichkolwiek rozszerzeń,
zachęcam Cię do wyposażenia się w jakikol− mu. Umożliwia to sekwencja File−>New−>C / brak spacji na końcu!).
wiek opis jego rejestrów. Może być to doku− C++. W tym przypadku możesz użyć także Znakomicie! Jesteśmy gotowi do napisania
mentacja dostępna na stronie firmy Atmel. znajdującego się w pasku narzędzi przycisku kolejnego programu. Przy odrobinie wprawy
Może być jakaś dobra książka. Nie jest tworzącego nieokreślony nowy plik tekstowy. wszystkie konieczne czynności wykonasz
konieczna literatura zajmująca się szczegóło− Plik powinien zostać zapisany oczywiście zapewne w czasie krótszym, niż mogłoby się
wo asemblerem tego procesora. Potrzebna jest w katalogu LEDMulti. Ja nadałem mu nazwę wydawać z długości opisu.
natomiast wiedza o rejestrach wewnętrznych LEDMulti.c – tym razem podanie rozszerze−
służących do obsługi takich elementów jak nia jest konieczne. Kodowanie
porty wejścia−wyjścia. Nasz program składa się z dwóch plików: – część standardowa:
makefile oraz LEDMulti.c. W celu wygodne− Skoro tworzymy od początku nowy program,
Małe repetytorium go dostępu do nich, oba pliki dodajemy do chciałbym, przy okazji, zaproponować Ci
– zaczynamy
Zanim zaczniesz pisać kod pro− Listing 4 Podstawowy szablon pliku głównego
gramu, konieczne jest wy−
konanie kilku czynności, jakie ////////////////////////////////////////////////////////////////////////////////
// LEDMulti.c - plik g³ówny programu obs³ugi multipleksowanego wyœwietlacza LED
przedstawiłem w części 1. Zer−
// przeznaczenie: "Programowanie procesorów w jêzyku C (EdW)"
knij na przedstawiony tam //
rysunek 11. Ja nowy program // Autor: Rados³aw Koppel Kompilator: WinAVR 20050214
nazwałem LEDMulti. Zakła− ////////////////////////////////////////////////////////////////////////////////
dam, że wybierzesz taką samą #include <avr\io.h>
nazwę i z takim założeniem
będę prowadził dalszy opis.
Utwórz folder na nowy pro− int main(void)
gram. W moim przypadku był {
to C:\GCC−src\Kurs\LEDmulti
Koniecznie skopiuj teraz do
return 0;
naszego folderu plik makefile. }
Może okazać się, że najwygod−

38 Elektronika dla Wszystkich


Programowanie
pewien dobry zwyczaj. Zanim jeszcze za− Kompilacja że wystąpienie któregoś z nieprzewidzianych
czniemy zastanawiać się nad tym, jak w ogóle szablonowego pliku przerwań spowoduje praktycznie programo−
nasz program ma działać, od razu wpiszemy Teraz proponuję Ci coś, co może w pierwszej wy reset programu. Warto o tym wiedzieć,
do pliku źródłowego kilka elementów, które chwili wydać się dziwne. Zapisz nasz szablon, aby w przyszłości nie zaskoczył nas ten fakt.
powinny pojawić się tutaj zawsze. Przedsta− a następnie – skompiluj go. W tej chwili chce− Reszta kodu zajmuje się inicjacją pamięci,
wia to listing 4. my przede wszystkim zaspokoić naszą cieka− stosu... Ogółem przygotowaniem programu
Dobrym zwyczajem jest umieszczenie na wość. Zobaczyć, co jest w takim „pustym” do pracy, podobnie jak robił to kompilator
początku komentarza, w którym zawieramy kodzie. Czy jeśli procesor nic nie robi, to nie BASCOM’a. Znajdziemy tutaj także pętlę
informację na temat przeznaczenia pliku, ma w nim jakiegokolwiek kodu... Jednak nieskończoną, jaką kończy się funkcja main.
autora oraz wykorzystanego kompilatora. w późniejszym czasie znaczenie takiego dzia− Dzięki temu mamy pewność, że procesor po
Ułatwi to orientację nie tylko innym, ale prze− łania jest nieco inne. Na tym etapie możemy inicjacji zatrzyma się i na tym skończy swoje
de wszystkim nam, gdy zechcemy wrócić na sprawdzić, czy wszystko zostało prawidłowo działanie.
przykład do jakiegoś programu pisanego kilka skonfigurowane. Jeśli podczas kompilacji
miesięcy wcześniej. otrzymamy informację o błędzie, nie musimy Definicje stałych
Zauważ, że ja stosuję tutaj nietypowe dla C się długo zastanawiać, czy przyczyna leży – zapomnij o „aliasach”
komentarze gdzie znak // oznacza komentarz w konfiguracji, czy w napisanym właśnie Pisząc nasz program sterowania wyświetla−
do końca linii. Jak już wspomniałem – więk− kodzie. czem, moglibyśmy posługiwać się bezpośred−
szość kompilatorów na to pozwala. Dla mnie Aktualnie jednak z czystej ciekawości nio nazwami odpowiednich portów. Takie
jest to kwestia nawyku. Jeśli chcesz, możesz przeprowadzamy kompilację. Jeśli wszystkie rozwiązanie jest jednak zarówno niewygodne,
stosować komentarze zaczynające się od /* narzędzia skonfigurowałeś tak jak propono− jak i niepraktyczne. Zwykle więc nadajemy
i kończące na */ − uzyskasz wtedy większą wałem w poprzedniej części – zrobisz to naj− wyprowadzeniom jakieś własne, kojarzące się
przenośność kodu. prościej za pomocą przycisku F7. Kompilacja z funkcją nazwy. Zmniejsza to prawdopodo−
Każdy program pisany dla kompilatora powinna przebiec bez trudności. Końcową bieństwo popełnienia błędu oraz upraszcza
WinAVR powinien zawierać dołączenie pliku część opisu jej przebiegu pokazuje rysunek 18. dostosowanie programu do ewentualnych
nagłówkowego avr\io.h. Umożliwi to stoso− zmian układowych.
wanie symbolicznych nazw rejestrów czy W BASCOM−ie posługiwaliśmy się w tym
wektorów przerwań. Kompilator sam zade− celu komendą Alias. W C istnieje tylko jedna
cyduje z jakiego pliku dokładnie ma skorzys− dyrektywa służąca do tworzenia stałych licz−
tać. Zrobi to na podstawie informacji o wska− bowych, symbolicznych, znakowych, a nawet
zanym w pliku makefile typie procesora. całych makr. Dyrektywa ta to #define. Jest
Dalej, chociażby w ciemno, możemy napi− ona przetwarzana przez preprocesor (czyli
sać szkielet funkcji main. Stąd zawsze rozpo− jeszcze zanim rozpocznie się kompilacja).
cznie się wykonywanie nowego programu. Jeśli gdzieś w kodzie pojawi się nazwa, która
Nieważne, czy będziemy pisać nowe funkcje została za jej pomocą zdefiniowana, zostanie
przed, czy za funkcją main. Jest to pewna róż− ona automatycznie zastąpiona przez odpo−
nica w stosunku do BASCOM−a... Moim zda− wiedni ciąg znaków. To ważne: nie będzie to
niem, bardzo wygodna różnica. liczba, nie będzie to tekst. Użycie słowa klu−
Zauważ, że poza kilkoma liniami, które czowego, które wcześniej zdefiniowaliśmy,
zdołałem już omówić w pliku, jest jeszcze... jest praktycznie równoważne z wpisaniem
dużo wolnego miejsca. Dokładnie tak to Rys. 18 Kompilacja szablonu. w edytorze odpowiedniego ciągu znaków
powinno wyglądać. Wygodnie jest zostawić (z pominięciem komentarzy). Do skutków,
miejsce między dołączanymi nagłówkami Na rysunku widać wyraźnie, że kompilator jakie się z tym wiążą, jeszcze powrócimy.
a funkcją main oraz zaraz za otwartą klamer− wygenerował kod zajmujący dokładnie 90 W tej chwili nie sprawi nam to przykrych nie−
ką wyznaczającą ciało naszej funkcji. W tym bajtów pamięci programu. Osoby lubiące spodzianek. Składnię interesującej nas dyrek−
miejscu pojawią się już elementy, które zade− język asemblera mogą za pomocą AVRStudia tywy przy tworzeniu prostych przypisań oma−
cydują o tym, jak ma zachowywać się nasz podejrzeć, co pojawia się w pliku wyjścio− wia odpowiednia ramka.
procesor. Przypomnę jeszcze tylko o pewnym wym. Okaże się wtedy, że 22 bajty zajmują
istotnym drobiazgu: Na koniec pliku klika− wektory przerwań. Mimo tego, że my świado−
my ENTER. Inaczej program będzie się mie przerwań nie wykorzystujemy, kompila− Listing 5 Zamiast „aliasów”
kompilował, ale kompilator zgłosi niepotrzeb− tor utworzył tutaj domyślne procedury ich
ne nam ostrzeżenia. obsługi. Procedury te działają w taki sposób, // Definicje wyprowadzeñ
#define LED_A 0
#define LED_B 1
ABC... C #define LED_C 2
#define − najprostsze zastosowanie #define LED_D 3
#define LED_E 4
Komendy #define preprocesora możemy #define LED_F 5
użyć w celu przypisania wygodnych dla nas #define LED_G 6
#define LED_DP 7
nazw dla dowolnych elementów procesora. #define LEDPORT PORTB
Najczęściej będą to rejestry wejścia−wyjścia. #define LEDDDR DDRB
W zastosowaniu takim komenda #define #define COM1 6
znakomicie zastępuje znaną z BASCOMA #define COM2 5
lub asemblera komendę alias. W takim #define COM3 4
#define COM4 3
zastosowaniu jej składnię przedstawia #define COMPORT PORTD
zawarty w ramce obrazek. #define COMDDR DDRD

Elektronika dla Wszystkich 39


Programowanie
Przystąpmy teraz do przypisania własnych
Listing 6 Wyświetlanie 1 na pierwszym wyświetlaczu
oznaczeń dla wyprowadzeń naszego układu.
Umieść kursor w wolnej przestrzeni za dołą− int main(void)
czonymi nagłówkami. W tym miejscu powi− {
nien pojawić się kod widoczny na listingu 5. /////////////////////////////
Nadajemy tutaj nazwy zarówno samym wy− // inicjacja
LEDDDR = 0xff;
prowadzeniom, jak i odpowiednim portom. COMDDR = 1<<COM1 | 1<<COM2 | 1<<COM3 | 1<<COM4;
Dodatkowo pojawia się przypisanie dla odpo− // koniec inicjacji
wiednich rejestrów DDRx. Zgodnie z doku− /////////////////////////////
mentacją naszego procesora, wpisanie tutaj
logicznego 1 spowoduje ustawienie odpo− COMPORT = ~1<<COM1;
LEDPORT = ~(1<<LED_B | 1<<LED_C);
wiedniego wyprowadzenia jako wyjścia.
Wpisanie logicznego 0 natomiast, ustawi return 0;
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 sługującego wyświetlacz. Będziemy musieli Spróbujmy zanalizować, jak powinien
wpisanym właśnie ciągiem definicji wyko− wtedy kolejno włączać odpowiednie wyświet− działać przedstawiony kod. Na początku,
rzystamy dowolne z utworzonych oznaczeń, lacze. Spróbujmy napisać taki maleńki frag− w części oznaczonej jako inicjacja, następuje
zostanie ono zamienione na oznaczenie ment. ustawienie odpowiednich portów jako wyj−
rozumiane przez kompilator, odnoszące się do Przenieś kursor za− ścia. Zgodnie z dokumentacją procesora AVR
interesującego nas portu lub wyprowadzenia. raz za klamerką wy− wyprowadzenie portu jest traktowane jako
znaczającą początek wyjście w chwili, gdy na odpowiadającej mu
Wyświetlanie naszej funkcji, jeszcze pozycji w rejestrze DDRx znajduje się jedyn−
– coś prostego przed komendą return. ka. Pierwsza linia nie powinna już budzić
Teraz, kiedy mamy już zdefiniowane wszyst− Propozycję całej funk− wątpliwości. Jedyną nowością jest to, że
kie potrzebne nam aktualnie wyprowadzenia, cji main przedstawia zamiast odwoływać się do odpowiednich
napiszmy coś, co zacznie działać. Stworzymy listing 6. Dodatkowo rejestrów bezpośrednio, korzystamy ze zdefi−
coś prostego, co pozwoli zweryfikować naszą w zrozumieniu kodu niowanych wcześniej nazw. Bardziej skom−
ideę sterowania. Pomysł jest taki, aby na pomocny może być plikowana wydawać się może linia następna.
pierwszym z wyświetlaczy wyświetlić cyfrę rysunek 19 – przedsta− Aby ją zrozumieć, konieczne jest poznanie
1. I tyle. Brak obsługi wszystkich wyświetla− wiający przyporządko− występujących tutaj operatorów. Operator <<
czy, zmiany wyświetlanej wartości... prak− Rys. 19 wanie nazw segmentów daje w wyniku liczbę, która znajduje się po
tycznie chodzi tylko o to, aby odpowiednio dla sterowanego jego lewej stronie, przesuniętą logicznie w le−
wysterować wszystkie porty i zakończyć pro− wyświetlacza. Warto zerknąć do ramki opisu− wo o podaną liczbę bitów, która musi znaleźć
gram. Zauważ, że aktualna idea, chociaż nie jącej zasadę przeprowadzania obliczeń w C się po stronie prawej operatora. Zapis taki, jak
przedstawia dużych możliwości, jest w zasa− oraz do wkładki zawierającej opis najpotrzeb− widzisz, jest często stosowany tam, gdzie chce−
dzie małym elementem programu w pełni ob− niejszych nam operatorów. my mieć kontrolę nad poszczególnymi bitami,

ABC... C Okazuje się, że w C każdy operator ma spowoduje przemnożenie przez 2 poprzedniej


Przeprowadzanie obliczeń swoją wagę. Operatory są wykonywane wartości zmiennej, a powiększonej o 1.
w określonej przez nią kolejności. Gdy stosu− Warto tutaj zauważyć, że operator przypi−
Jak w (chyba) każdym języku wysokiego jemy typowe operacje znane jeszcze ze szko− sania, taki jak znak równości, ma najniższy
poziomu, obliczenia w C przeprowadza się, ły podstawowej – możemy śmiało założyć, że piorytet. Oznacza to, że zawsze najpierw war−
korzystając ze zbioru operatorów. Nie ma działania zostaną wykonane dokładnie tak jak tość będzie obliczona, a dopiero później zapi−
tutaj jednak ograniczenia mówiącego na byliśmy tego nauczeni. Problemy zaczynają sana we wskazane miejsce (także i tutaj
przykład, że można wykonywać tylko jedno się dla operatorów, które praktycznie poza można stosować nawiasy w celu zakłócenia
działanie naraz. Tylko jeśli napiszemy bardzo programowaniem nie mają swoich odpowied− tej kolejności... jednak to już zupełnie inna
złożone wyrażenie arytmetyczne, zostaniemy ników. historia).
poproszeni przez kompilator o rozbicie go na W dodatku do tego odcinka znajdziesz
prostsze. Osobiście jednak nie pamiętam, aby tabelę skrótowo omawiającą najpotrzebniej−
taka sytuacja w ogóle kiedykolwiek mi się sze nam operatory. Znajdziesz także zesta−
przytrafiła. wienie pokazujące kolejność wykonywania Nie bój się obliczanych stałych
Równania zapisujemy, powiedziałbym, operatorów. Jeśli jakieś operatory znajdują się
w sposób naturalny. Na przykład czysto teo− na tym samym poziomie, obliczenia są prze− Warto wiedzieć, że nawet jeśli optymalizacja
retycznie wyobraźmy sobie, że chcemy war− prowadzane od lewej do prawej – znowu kodu zostanie wyłączona, standard ANSI C
tość jakiejś zmiennej a pomnożyć przez dwa zgodnie z intuicją. nakazuje, aby wszystkie stałe zostały wyli−
i dodać do niej 1. Kod w takim przypadku Co jednak, jeśli chcemy na przykład naj− czone przez kompilator.
wyglądałby następująco: pierw dodawać a później mnożyć? Znów Oznacza to, że jeśli to wygodne lub bar−
możemy zastosować szkolną wiedzę: służą dziej czytelne, możesz podać kompilatorowi
a = 2 * a + 1; do tego zwykłe nawiasy. Przykładowo: całe długie równanie ze stałych. Ostatecznie
Całkowicie równoważny jest także kod: w kodzie asemblerowym pojawi się tylko
a = 1 + 2*a; a = 2 * (a + 1); jedna liczba.

40 Elektronika dla Wszystkich


Programowanie
a z jakichkolwiek powodów okazuje się Powinien wyjaśnić znacznie lepiej, co się gacji, bit po bicie, całego uzyskanego wyniku.
to wygodniejsze niż zapis szesnastkowy. dzieje niż nawet długi i dokładny opis. Ważne Wykonuje to operator tyldy ~, umieszczonej
Operator | wykonuje logiczną operację OR na jest to, że operator << ma wyższy priorytet niż przed wyrażeniem.
argumentach umieszczonych po obu stronach. operator sumy logicznej |. Zauważ teraz w jak wygodny sposób
Na mojej klawiaturze znaczek ten znajduje się Warto pamiętać, że mimo iż w naszym możemy zapisać znak odpowiadający zaświe−
na tym samym klawiszu, gdzie umieszczono zapisie pojawia się aż 8 działań (4 przesunię− ceniu jedynki na wyświetlaczu. Zapomnij
backslash (\). cia, trzy sumy logiczne, jedno przypisanie), o przeliczaniu na kartce papieru wyglądu cyfr
Jeśli, mimo omówienia działania poszcze− kodzie maszynowym pojawi się tylko wpisa− na liczby dziesiętne. Zapis jak przedstawiony
gólnych operatorów, idea takiego zapisu wy− nie do rejestru stałej wartości. Dlaczego tak w listingu 6 jest znacznie bardziej czytelny,
daje Ci się niejasna – spojrzyj na rysunek 20. się dzieje – mówi o tym ramka o obliczeniach przenośny i łatwiejszy w modyfikacji. Myślę,
w C. że jesteś już w stanie zrozumieć działanie tej
W kolejnej linii chcemy włączyć tranzys− linii. Pamiętaj jedynie, że dany segment
Rys. 20 Zasada obliczania wartości tor sterujący pierwszym wyświetlaczem LED. wyświetlacza jest aktywowany przez wysta−
wpisywanej do COMDDR na listin− Ponieważ tranzystory aktywujące wyświetla− wienie logicznego 0 na odpowiednie wypro−
gu 6. cze są włączane przez 0 logiczne, wadzenie portu.
po ustawieniu na odpowiednim Przeprowadź teraz kompilację programu
miejscu jedynki dokonujemy ne− 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−
Rys. 21 Efekt występowania kie wyświetlacze. Zapewne domyślasz się już,
priorytetów operatorów. ż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

ABC... C poznałeś już dobrze, uczestnicząc w kursie W samej obsłudze pojawią się tylko dwie
Zmienne – podstawy BASCOM. Zgodnie ze standardem, wszyst− różnice w stosunku do BASCOM−a:
kie zmienne globalne trzeba utworzyć jeszcze 1. Zamiast nawiasów okrągłych, korzysta−
Zakładam tutaj, że wiesz, co to są zmienne zanim pojawi się jakakolwiek funkcja. GCC my z nawiasów kwadratowych.
i masz pojęcie do czego służą. Ze względu na umożliwia jednak utworzenie zmiennej w 2. Indeksy tablicy zaczynają się od 0
skrótowy charakter tego kursu tutaj tylko dowolnym miejscu, poza wnętrzem funkcji. i mają wartości do ilość−1, zamiast BASCOM−
napiszę „jak to się robi w C”. Nie będziemy z tego korzystać, ponieważ owego zakresu od 1 do ilość.
jedyne, co możemy przez to uzyskać, to
Typy proste malowniczy bałagan w kodzie. Plik <inttypes.h>
Zmiennej takiej używa się bardzo podobnie Warto wiedzieć, że jeśli zmiennej global− W praktycznych programach proponuję Ci
jak miało to miejsce w BASCOM−ie. Niewiel− nej nie nadasz określonej wartości początko− wykorzystanie pliku, o którym właśnie mo−
ka różnica pojawia się w sposobie jej dekla− wej, zostanie ona domyślnie wyzerowana wa. Definiuje on bardzo wygodne nazwy ty−
racji. Nie ma tutaj słowa kluczowego Dim. jeszcze przed rozpoczęciem wykonywania pów zmiennych całkowitych, z których mo−
Zmienną tworzymy, jak pokazuje przykład: programu. żesz (ale nie musisz) korzystać:

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


C umożliwia tworzenie wielowymiarowych
Typy proste zmiennych jakie udostępnia tablic. W prostych przypadkach jednak oka−
C, znajdziesz w tabeli 1. zuje się, że tablice jednowymiarowe są całko−
wicie wystarczające. Dla wielu osób będą one
Zacznijmy globalnie zapewne wyglądały bardzo przyjaźnie. Ich
Na początek posłużymy się tylko zmiennymi tworzenie jest zbliżone do tablic BASCOM−
globalnymi. Powód takiego działania jest owych:
prosty: ich wykorzystanie i sposób obsługi
jest najbardziej zbliżony do zmiennych, jakie typ nazwa[ilość] [={inicjacja}];

Elektronika dla Wszystkich 45


Programowanie
raz na listing 6. Jest w nim ewidentny błąd. sposób aktywować wybrany wyświetlacz. tego celu tablicę. Pamiętaj, że chociaż to wpi−
Spróbuj zauważyć go sam. Porównaj wyko− Spróbujmy napisać wciąż jeszcze prosty sywanie wygląda na rozbudowane działanie,
nywane działania z tabelką we wkładce, pre− kod, który jednak umożliwi wyświetlenie na zostanie zamienione tylko na przesłanie stałej
zentującą kolejność obliczeń. kolejnych wyświetlaczach różnych znaków. wartości do odpowiedniej komórki RAM pro−
Pozornie prosty zapis: W odpowiedniej ramce znajdziesz podsta− cesora. Tak więc jeszcze raz to powtórzę, nie
COMPORT = ~1<<COM1; wowe informacje, jak najprościej poradzić ma sensu dokonywać ręcznych przeliczeń
jest w rzeczy samej wyrażeniem składającym sobie z aktualnie potrzebnymi nam zmienny− wyglądu znaków na odpowiednie liczby.
się z dwóch działań, przesunięcia oraz negac− mi. Listing 8 z kolei pokazuje proponowany Następnie rozpoczyna się pętla nieskoń−
ji. Chcemy przesunąć jedynkę na pozycję kod. Wykropkowałem tylko definicje wypro− czona, gdzie włączane są kolejno odpowied−
odpowiadającą tranzystorowi pierwszego wadzeń, które cały czas pozostają bez zmian. nie wyświetlacze. Zauważ, że wybrany wy−
wyświetlacza, a następnie wynik zanegować. świetlacz jest aktywowany za pomocą danej
Jednak operator negacji ma przecież wyższy Zauważ, że przed nazwami zawartej w tabeli g_DaneCom. Na koniec
priorytet niż operator przesunięcia! Efekt ilus− zmiennych dodałem oznaczenie zmienna g_AktWyswietlacz jest zwiększana,
truje rysunek 21. Nie wdając się w dalsze g_. W ten sposób zaznaczam, że przy czym jeśli przekroczy wartość 3 – cykl
szczegóły, na listingu 7 prezentuję sposób jest to zmienna globalna. Jest to rozpoczyna się od początku.
poradzenia sobie z problemem. dobry zwyczaj, który w przys− Oczywiście − wypracowane rozwiązanie
złości ułatwi Ci orientowanie się nie jest jeszcze optymalne. Jeśli pisałeś już
Listing 7 Poprawa programu z listingu 6 w kodzie. analogiczny program w BASCOM−ie lub
orientujesz się w temacie, możesz z miejsca
(...) Skompiluj program i wyślij wskazać dwie sprawy, które można poprawić:
COMPORT = ~(1<<COM1);
LEDPORT = ~(1<<LED_B | 1<<LED_C); go do procesora. Jeśli wszystko pos− 1. Obsługa wyświetlacza powinna odbywać
zło dobrze, powinieneś zobaczyć na się w przerwaniach!, tak aby nie angażować
return 0; wyświetlaczu kolejne cyfry. do tego celu całej dostępnej mocy procesora.
} W kodzie inicjacja portów nie 2. Tablica g_DaneCom zajmuje miejsce
uległa zmianie. Teraz jednak za− w cennej pamięci RAM, jednocześnie
Przykład ten ilustruje, jak ważna jest różna miast wpisywać wygląd cyfry bezpośrednio w pamięci programu przechowywana jest
kolejność wykonywania działań przez kompi− do portu, wpisujemy go w przeznaczoną do kopia jej zawartości w celu wstępnego
lator. Gdy teraz skompilujesz poprawiony pro−
gram – wszystko będzie tak jak planowaliśmy.
Listing 8 Pierwsze starowanie wyświetlaczem z wykorzystaniem zmiennych
Dobrym zwyczajem jest stosowanie się #include <avr\io.h>
do reguły, która mówi, że jeżeli nie jesteś #include <inttypes.h>
pewien, w jakiej kolejności wykonywane są (...)
uint8_t g_AktWyswietlacz = 0;
dane operacje – stosuj w takich miejscach uint8_t g_DaneWyswietlacza[4];
nawiasy. Umieszczenie dwóch nawiasów uint8_t g_DaneCom[4] =
więcej nie zaszkodzi, a czasami przyczynia {~(1<<COM1), ~(1<<COM2), ~(1<<COM3), ~(1<<COM4)};
się nawet do wzrostu czytelności kodu – int main(void)
optycznie wydziela pewne logiczne frag− {
menty obliczeń. /////////////////////////////
// inicjacja
LEDDDR = 0xff;
COMDDR = 1<<COM1 | 1<<COM2 | 1<<COM3 | 1<<COM4;
Wyświetlanie // koniec inicjacji
– wyższy stopień /////////////////////////////
Podsumujmy to, co już mamy. Umiemy // Wpisanie do tablicy próbnych wartoœci
wywołać aktywację wybranego wyświetlacza. g_DaneWyswietlacza[0] = ~(1<<LED_B | 1<<LED_C);
Gdy spojrzysz na utworzony program – g_DaneWyswietlacza[1] = ~(1<<LED_A | 1<<LED_B | 1<<LED_D |
myślę, że nie będziesz miał wątpliwości, jak 1<<LED_E | 1<<LED_G);
g_DaneWyswietlacza[2] = ~(1<<LED_A | 1<<LED_B | 1<<LED_C |
wybrać inny wyświetlacz lub też w jaki spo− 1<<LED_D | 1<<LED_G);
sób wyświetlić inny znak. Teraz chcielibyśmy g_DaneWyswietlacza[3] = ~(1<<LED_B | 1<<LED_C | 1<<LED_F |
mieć możliwość sterowania wszystkimi czte− 1<<LED_G);
rema wyświetlaczami jednocześnie. Oczywiś−
for(;;)
cie odbędzie się to na zasadzie ich szybkiego {
przełączania. Myślę, że na podstawie kursu // Wygaszenie wyœwietlaczy
BASCOM−a wiesz już, że tym razem najwy− COMPORT = 1<<COM1 | 1<<COM2 | 1<<COM3 | 1<<COM4;
// Wys³anie odpowiedniej danej
godniejsze będzie zastosowanie kilku zmien− LEDPORT = g_DaneWyswietlacza[g_AktWyswietlacz];
nych. Będziemy potrzebowali zmiennej, // W³¹czenie odpowiedniego wyœwietlacza
w której przechowamy informację na temat COMPORT = g_DaneCom[g_AktWyswietlacz];
tego, który z wyświetlaczy jest aktualnie // Zwiêkszenie stanu zmiennej wskazuj¹cej na obs³ugi-
wany wyœwietlacz
obsługiwany. Dodatkowo wykorzystamy tab− ++g_AktWyswietlacz;
licę, składającą się z czterech elementów, każ− if(g_AktWyswietlacz > 3)
dy zawierający obraz tego, co chcemy g_AktWyswietlacz = 0;
wyświetlić na odpowiednim wyświetlaczu. }
Jeśli zaprzyjaźniamy się już z tablicami – od return 0;
razu wykorzystamy jeszcze jeden element }
tego typu w celu zapisania informacji, w jaki

46 Elektronika dla Wszystkich


Programowanie
ustawienia. Jest to oczywiste marnotrawstwo.
Listing 9 Ulepszenie programu z listingu 8.
Powinna istnieć przecież możliwość odczyty−
wania tego typu danych bezpośrednio z pa− // Wygaszenie wyœwietlaczy
COMPORT |= 1<<COM1 | 1<<COM2 | 1<<COM3 | 1<<COM4;
mięci programu.
// Wystawienie odpowiedniej danej
Oba zarzuty są jak najbardziej słuszne. Od LEDPORT = g_DaneWyswietlacza[g_AktWyswietlacz];
umieszczenia odpowiedniej tablicy w pamięci // W³¹czenie odpowiedniego wyœwietlacza
danych oraz wykorzystania przerwań dzieli COMPORT &= g_DaneCom[g_AktWyswietlacz];
nas już tylko krok. Teraz jednak chcę zwrócić
Twoją uwagę na inny drobiazg: Zastanówmy „Co z bitami?”.
się, co się dzieje z portem, który wykorzy− W tym przypadku jednak kompilator nie ABC... C
stujemy do sterowania anodami wyświetla− będzie generował instrukcji ustawiających Co z bitami?
W C brak jest instrukcji operujących bezpo−
czy. Zawsze zapisujemy tam na sztywno wy− poszczególne bity. Zwartość portu będzie
średnio na bitach*. Jednak możemy opero−
braną przez nas wartość. Chwilowo możemy wczytana do rejestru, wykonana zostanie od−
wać na nich, stosując prostą sztuczkę. Była
nie zauważać problemu, jaki nam to sprawi, powiednia funkcja logiczna, a następnie dana
o tym mowa wcześniej. Załóżmy, że chce−
jednak gdy zechcemy wykorzystać do czegoś zostanie przesłana do rejestru wyjściowego.
my operować na 6 wyprowadzeniu portu D:
pozostałe wyprowadzenia tego portu, może Daje to 3 instrukcje. Gdybyśmy chcieli zrobić
Aby je ustawić:
okazać się to niemożliwe, a już na pewno nie− to samo za pomocą ustawiania / zerowania
PORTD |= 1<<6;
wygodne. Musimy więc poprawić kod tak, poszczególnych bitów, konieczne byłoby
Wyzerować:
żeby zmieniane były tylko wybrane wypro− zastosowanie 4 instrukcji. Dodatkowo poja−
PORTD &= ~(1<<6);
wadzenia, resztę pozostawiając bez zmian. wiałyby się niewielkie opóźnienia między
Okazuje się, że GCC doskonale wie o co
przełączaniem poszczególnych bitów. Dla nas
chodzi. Mimo tego, że wydaje się, że na
Tabela 1 Typy proste zmiennych w tej chwili nie miałoby to znaczenia – ale
przykład przedstawione ustawienie wypro−
dostępne w C kompilator o tym nie wie i musi założyć, że
wadzenia portu wywoła cały ciąg operacji,
jeśli napisaliśmy funkcję tak jak na listingu
w asemblerze jest generowany tylko nastę−
9, to chcemy, aby wszystkie wyprowadze−
pujący kod:
nia zmieniły swój stan w tym samym
sbi PORTD, 6
momencie. Nieczęsto, ale czasami może
Czyli najprościej, jak się dało!
okazać się to bardzo ważne.
*
Nie jest to do końca prawdą, istnieje specjalny typ
Podsumowanie zmiennej – tak zwane pole bitowe. Wiele prostych kom−
pilatorów jednak nie obsługuje takiej opcji. Standard
Dziś udało nam się napisać kilka progra− także niejasno tłumaczy ich działanie, co może powodo−
mów. Starałem się pokazać Ci, jak eleganc− wać trudności z przenośnością kodu.
ko rozpocząć nowy plik z kodem źródło−
wym. Zobaczyłeś, jak radzić sobie z przy−
pisywaniem dogodnych nazw do wyprowa− kroczków do praktyczności... postaramy się
dzeń. Poznałeś wstępnie ideę przeprowa− rozwiązać już za miesiąc.
* Rozmiar typu int jest różny dla różnego typu maszyny. Dla pro− dzania obliczeń w C. Dowiedziałeś się, tro− Jeśli chcesz spróbować swoich aktualnych
cesora 32− i więcej bitowego będzie on miał 32 bity. Dla procesora 16− chę o zmiennych, bez których nie obejdzie sił, zobacz, czy uda Ci się już dziś stworzyć
i mniej bitowego − 16 bitów.
** AVR−GCC nie obsługuje formatu liczb zmiennoprzecinko− się rzeczywisty program. Dowiedziałeś się, tablicę zawierającą wszystkie znaki kodu
wych o podwójnej precyzji. Ze względu na zgodność ze standardem jak zerować i ustawiać poszczególne bity. szesnastkowego (0−F). Czy uda Ci się za jej
można deklarować zmienne typu double, jednak kompilator potraktu−
je je tak, jakby były to zmienne typu float. Przy okazji udało nam się stworzyć pro− pomocą wpisywać wartości do tablicy
gram, który naprawdę obsługuje wyświet− wyświetlacza?
Odpowiednią poprawkę pokazuje listing 9. lacz multipleksowany. Fakt, że ma on jeszcze
W jego zrozumieniu może pomóc ramka kilka niedociągnięć i że brakuje mu kilku Radosław Koppel

R E K L A M A

Elektronika dla Wszystkich 47

You might also like