Kurs AVR-GCC cz.4
W poprzedniej części kursu omawiałem zmienne, operatory, pętle oraz instrukcje sterujące jak if i switch. W tej części kursu wprowadzimy do programu funkcje i tablice, omówię temat zakresu widoczności zmiennych oraz kod BCD. W uruchamianych przykładach będziemy wykorzystywać diody LED, siedmiosegmentowe wyświetlacze LED, klawiaturę wykonaną z dwunastu przycisków oraz buzzer.
Tablice
Jeśli w programie jest większa liczba danych jednego typu ,to zamiast deklarować wiele pojedynczych zmiennych, lepiej jest utworzyć tablice. W tablicy można przechowywać jednocześnie wiele zmiennych jednakowego typu. Wszystkie elementy tablicy są ponumerowane, dostęp do dowolnej zmiennej w tablicy uzyskuje się podając nazwę tablicy i numer elementu.
Tablicę deklaruje się pisząc kolejno: typ danych, nazwę tablicy i objętą parą nawiasów kwadratowych [] liczbę elementów tablicy.
Elementami tablic mogą być wartości typów: char, int, float, double oraz: struktury, unie, pola bitowe i wskaźniki o których będzie mowa w następnej części kursu. Elementami tablicy mogą być też same tablice; w taki sposób tworzy się w języku C tablice wielowymiarowe.
Definiując tablicę można jednocześnie tablicę inicjalizować wartościami początkowymi. W tym celu dopisuje się na końcu definicji: operator = , a po nim, objęte parą nawiasów klamrowych {}, wartości początkowe kolejnych elementów tablicy rozdzielone przecinkami.
W programie elementami tablicy posługujemy się w identyczny sposób jak pojedynczymi zmiennymi. Każdy element tablicy ma nazwę składającą się z nazwy tablicy oraz objętego parą nawiasów kwadratowych indeksu(numeru elementu). Elementy tablicy numerowane są zaczynając od zera, a nie od jedynki, należy to koniecznie zapamiętać.
Trzeba też pamiętać, że jeżeli zdeklarujemy tablicę n elementową i spróbujemy zapisać coś pod indeksem równym lub większym n to kompilator może nie zgłosić błędu, ale skutkować to będzie nieprawidłowym działaniem programu.
Przy operacjach na tablicach szczególnie użyteczna może być instrukcja pętli for, gdzie licznik pętli będzie indeksem tablicy.
Na tym etapie nauki, te informacje na temat tablic mam wystarczą, dalej w kursie jeszcze powrócimy do tablic..
Funkcje
Program składa się z ciągu instrukcji zapisanych w pamięci i wykonywanych jedna po drugiej. Obok instrukcji, które realizują konkretne zadania, na przykład włączają i wyłączają diodę LED, istnieją też specjalne instrukcje sterujące przebiegiem programu jak np.: if-else, switch, for, while; które były tematem poprzedniej części kursu.
Obok wspomnianych instrukcji sterujących istnieje możliwość tworzenia podprogramów. Zwykle dłuższe programy dzielone są na odrębne fragmenty, nazywane podprogramami lub procedurami. Spośród podprogramów wyróżnić można podprogram pierwszy (główny), od którego pierwszej instrukcji rozpoczyna się działanie całego programu. Umieszczając w programie specjalną instrukcję - instrukcję wywołania podprogramu - można wykonać wybrany podprogram.
A jaki może być pożytek z podziału programu na podprogramy ? Na przykład, jeśli jakiś dłuższy fragment kodu powtarza się w wielu miejscach programu, oczywistym rozwiązaniem będzie umieścić ten fragment w odrębnym podprogramie. W wyniku otrzymamy znacznie krótszy program o przejrzystej strukturze. W tak napisanym programie łatwiej jest wprowadzać zmiany. Oczywiście sposób podziału programu nie bywa przypadkowy, podprogramy zwykle realizują konkretne zadania np. odczyt czujnika temperatury, włączenie sygnalizacji albo uruchomienie serwomechanizmu.
W języku C podprogramy nazywa się funkcjami. Każdy program w języku C musi zawierać funkcje o nazwie "main" - każda funkcja ma nadaną nazwę (identyfikator). Funkcja main jest wspomnianym wcześniej podprogramem pierwszym(głównym), od którego pierwszej instrukcji zaczyna się wykonanie całego programu.
Obok funkcji main programista może tworzyć(definiować) własne funkcje, można też wykorzystywać gotowe funkcje z biblioteki standardowej języka C, dostarczonej razem z kompilatorem. Razem z kompilatorem avr-gcc wykorzystuje się bibliotekę AVR Libc, która pełni rolę biblioteki standardowej, dopasowanej do możliwości 8-bitowych mikrokontrolerów AVR. W przykładach z kursu często używana jest funkcja z biblioteki AVR Libc o nazwie _delay_ms. Funkcja ta wprowadza do programu opóźnienie o zadanym w milisekundach okresie czasu; w avr-libc dostępna jest także podobna funkcja _delay_us, dla której długość opóźnienia podaje się w mikrosekundach.
Instrukcja wywołania funkcji składa się z nazwy funkcji i objętej parą nawiasów okrągłych listy argumentów, i kończy się średnikiem. Argumenty funkcji to dane przekazywane do funkcji, jak na przykład wartość opóźnienia w funkcji _delay_ms. Jeśli funkcja oczekuje kilku argumentów, kolejne argumenty oddziela się przecinkami. Gdy funkcja nie oczekuje żadnego argumentu, to uruchamiając ją, po nazwie funkcji wstawiamy "pustą" parę nawiasów okrągłych ().
Po zakończeniu działania funkcje mogą zwracać dane. Na przykład funkcja rand z biblioteki standardowej zwraca wartość typu int, wartością tą jest liczba pseudolosowa z przedziału od 0 do RAND_MAX(0x7FFF). Jeżeli funkcja zwraca wartość, wtedy całe wyrażenie: nazwa_funkcji(argumenty_funkcji) posiada wartość i typ, podobnie zmienne. Wtedy, przykładowo, można instrukcję wywołania funkcji "nazwa_funkcji(argumenty_funkcji)" postawić po prawej stronie operatora przypisania, żeby zwróconą przez funkcję wartość przypisać od zmiennej; albo umieścić w warunku instrukcji if-else.
Funkcja może zwracać tylko wartość jednej zmiennej lub nie zwracać niczego.
Własną funkcję tworzy(definiuje) się wpisując kolejno: typ zwracanej wartości, nazwę nowej funkcji, listę parametrów funkcji objętą parą nawiasów okrągłych (); deklaracje kolejnych parametrów oddziela się przecinkami. W definicji funkcji argumenty nazywa się parametrami. Następnie, między parą nawiasów klamrowych {}, umieszcza się instrukcja po instrukcji kod funkcji.
Jeśli w definicji funkcji jako zwracany typ wstawi się słówko void, oznaczać to, że funkcja nie będzie niczego zwracać; a jeśli wstawimy void w miejsce listy parametrów, to funkcja nie będzie oczekiwać żadnych argumentów. W naszych krótkich programach definicje funkcji będą umieszczane w pliku przed funkcją main.
Poniżej znajduje się przykład definicji funkcji, która przyjmuje dwa argumenty typu unsigned int i nie zwraca niczego. Wartości argumentów dostępne są wewnątrz funkcji w specjalnie tworzonych zmiennych. W chwili wywołania funkcji, tworzone są zmienne o typach i nazwach jakie znajdują się na liście parametrów. Zmienne te są inicjalizowane wartościami argumentów podanych przy wywołaniu funkcji. W przykładzie poniżej, w funkcji beep dostępne są dwie zmienne o nazwach: frequency i duration zawierające wartości wysłanych do funkcji argumentów.
Kolejny przykład to definicja funkcji, która zwraca wartość typu unsigned char i oczekuje argumentu typu unsigned char. Tu nową rzeczą jest instrukcja return, wymusza ona natychmiastowy powrót z funkcji. Jeżeli funkcja coś zwraca, wtedy po prawej stronie instrukcji return wstawia się wyrażenie, którego wartość zostanie zwrócona po wyjściu z funkcji. Jak już pisałem, gdy funkcja coś zwraca, instrukcja wywołania funkcji, całe wyrażenie: "nazwa_funkcji(argumenty_funkcji)" ma przypisaną wartość zwracaną przez funkcję.
Tablice przekazywane są do funkcji w odmienny sposób, niż zmienne. Jeśli przekazuje się do funkcji argumenty nie będące tablicami, to w momencie wywołania funkcji tworzone są zmienne o nazwach parametrów funkcji, zmienne te inicjalizowane są wartościami argumentów podanymi w instrukcji wywołania funkcji. Natomiast, jeśli argumentem funkcji jest tablica, to w momencie wywołania funkcji, nie jest tworzona kopia tej tablicy; wewnątrz funkcji wszystkie działania przeprowadzane są na oryginalnej tablicy podanej jako argument w instrukcji wywołania funkcji. Przykład:
Podsumowując, argumenty do/z funkcji przekazywane są przez wartość, z wyjątkiem tablic które przekazywane są poprzez wskaźnik. Wskaźniki omówię szczegółowo w kolejnej części kursu.
Zakres widoczności zmiennych
Zmienne mogą być deklarowane wewnątrz funkcji(zmienne lokalne) albo poza wszystkimi funkcjami(zmienne globalne). Zmienne deklarowane poza wszystkimi funkcjami, na początku pliku, istnieją przez cały czas działania programu i są dostępne we wszystkich funkcjach w pliku. Zmienne deklarowane wewnątrz funkcji tworzone są w momencie wywołania funkcji i istnieją jedynie do momentu zakończenia działania funkcji. Zmienne deklarowane wewnątrz funkcji dostępne są tylko w obrębie funkcji, w której zostały zdeklarowane. W różnych funkcjach mogą istnieć zmienne o tej samej nazwie.
Jeśli w funkcji zostanie utworzona zmienna o nazwie identycznej z nazwą istniejącej zmiennej utworzonej poza funkcjami, wtedy, w funkcji, pod tą nazwą dostępna będzie zmienna deklarowane w funkcji.
Zmienne deklarowane wewnątrz funkcji tworzone są w chwili wejścia do funkcji i istnieją tylko do momentu wyjścia z funkcji. Zawartość tych zmiennych po wyjściu z funkcji jest bezpowrotnie tracona. Jeżeli zależy nam, aby zmienna deklarowana wewnątrz funkcji istniała przez cały okres działania programu, i aby pomiędzy kolejnymi wywołaniami funkcji zawartość tej zmiennej nie była tracona, to należy deklaracje tej zmiennej poprzedzić słówkiem static, takie zmienne nazywa się zmiennymi statycznymi. Zmienne deklarowane poza wszystkimi funkcjami otrzymują wartość początkową równą zero, natomiast zmienne deklarowane wewnątrz funkcji mają nieokreśloną, przypadkową wartość początkową, ale nie zmienne statyczne, te również otrzymują wartość początkową równą zero.
Kod BCD
W elektronice cyfrowej nierzadko wykorzystywany jest kod BCD(ang. Binary-Coded Decimal), czyli dziesiętny zakodowany dwójkowo. My będziemy przyłączać do mikrokontrolera 7-segmentowe wyświetlacze LED poprzez układy 7447(dekoder kodu BCD na kod wyświetlacza 7-segmentowego). Zaletą kodu BCD jest prostota: posługujemy się systemem dziesiętnym i każda cyfra (0,1..9) liczby zapisywana jest na czterech bitach.
bity | liczba |
---|---|
0000 0000 | 00 |
0000 0001 | 01 |
0000 0010 | 02 |
0000 0011 | 03 |
0000 0100 | 04 |
0000 0101 | 05 |
0000 0110 | 06 |
0000 0111 | 07 |
0000 1000 | 08 |
0000 1001 | 09 |
0001 0000 | 10 |
0001 0001 | 11 |
0001 0010 | 12 |
0001 0011 | 13 |
0001 0100 | 14 |
. | . |
. | . |
. | . |
1001 0111 | 97 |
1001 1000 | 98 |
1001 1001 | 99 |
Elementy i schematy
Zależnie od przykładu, będziemy do mikrokontrolera atmega16 przyłączać: osiem diod LED, dwa 7-segmentowe wyświetlacze LED, klawiaturę wykonaną z dwunastu przycisków, dwa przekaźniki oraz brzęczyk(bez generatora, sam przetwornik piezo).
Cały schemat podzieliłem na kilka części:
Przykładowe programy
Przygotowałem cztery przykładowe programy, do uruchomienia jako ćwiczenia. Do przykładów dołączone są animacje pokazującą efekt działania programów oraz krótki opis. Wszystkie przykłady są raczej proste, nikt nie powinien mieć problemów ze zrozumieniem jak działają.
Poniżej znajduje się szkielet programu, wszystkie przykłady napisane są według tego wzoru.
/**** PLIKI NAGŁÓWKOWE ****/ #include <avr/io.h> #include <util/delay.h> #define F_CPU 1000000L /**** ZMIENNE GLOBALNE ****/ /**** DEFINICJE FUNKCJI ****/ /* Inicjalizacja, konfiguracja sprzętu */ void init(void) { } /* Inne funkcje */ /**** POCZĄTEK PROGRAMU ****/ /* Definicja funkcji main */ int main(void) { /* Deklaracje zmiennych */ /* Inicjalizacja */ init(); /* Główna pętla programu */ for(;;) { } }
Pozytywka elektroniczna.
Ten przykładowy programik odgrywa krótką melodyjkę z pomocą buzzera, nagranie dźwięku:
W programie najwięcej pracuje funkcja beep, która generuje sygnał prostokątny na wyprowadzeniach PB0 i PB1, gdzie przyłączony jest buzzer (przetwornik piezo). Funkcja ta przyjmuje dwa argumenty: częstotliwość sygnału w hercach i długość czasu trwania sygnału w milisekundach. Generowanie dźwięku odbywa się programowo, bez użycia układów czasowych AVRra. Do uzyskania krótkich opóźnień w programie używana się funkcji: void _delay_loop_2(unsigned int __count) z biblioteki avr-libc, która wprowadza opóźnienie czterech cykli procesora na jeden __count. Uwaga! Funkcja beep będzie działać właściwie tylko dla uC AVR pracującego z częstotliwością 1MHz.
Inna funkcja, o nazwie play, odgrywa melodyjkę dźwięk po dźwięku wywołując funkcję beep. Funkcja play oczekuje argumentów: tablicy dźwięków oraz indeks pierwszego i ostatniego dźwięku. Elementami tablicy dźwięków mają być tablice o dwóch elementach typu int: częstotliwość podana w hercach i długość trwania dźwięku w milisekundach. W programie zdefiniowano, poza funkcjami,tablicę dźwięków o nazwie koziolek. Tablice definiowane poza funkcjami dostępne są bezpośrednio we wszystkich funkcjach w całym pliku. Jednak w przykładzie tablica jest przekazywana do funkcji jako jeden z argumentów, dzięki temu struktura programu jest bardziej przejrzysta, a funkcja jest bardziej uniwersalna.
/* KURS AVR-GCC cz.4 Program, z pomocą buzzera (przetwornika piezo), odgrywa krótką melodyjkę. układ atmega 1MHz PB0 -> R(330Ohm) -> BUZZER -> PB1 */ #define F_CPU 1000000L #include <avr/io.h> #include <util/delay.h> /**** ZMIENNE GLOBALNE ****/ /* Tablica dzwięków: częstotliwść(Hz), czas_trwania(ms), częstotliwość, ... */ int koziolek[][2]={ 523,125, 587,125, 659,250, 698,125, 659,125, 587,250, 523,250, 1047,250, 784,250, 523,250, 1047,250, 784,250, 523,250, 1047,250, 784,1000 }; /**** DEFINICJE WŁASNYCH FUNKCJI ****/ /* Konfiguruje porty we/wy uC */ void init(void) { /* PB0,PB1 - wyścia */ DDRB = 0x03; PORTB = 0x00; } /* Funkcja generuje sygnał prostokątny na wyprowadzeniach PB0 i PB1, gdzie przyłączony jest buzzer. Funkcja przyjmuje argumenty: częstotliwość(Hz) sygnału i długość czasu trwania sygnału (ms). */ void beep(unsigned int frequency, unsigned int duration) { unsigned int i,t,n; t = 125000/frequency; n = (250UL*duration)/t; PORTB |= 0x01; PORTB &= ~0x02; for(i=0; i < n; i++) { PORTB ^= 0x01; PORTB ^= 0X02; _delay_loop_2(t); } } /* Odgrywa melodyjkę dzwięk po dzwięku. Jako argumentów funkcja oczekuje tablicy dzwięków oraz numerów pierwszego i ostatniego dzwięku. Elementami tablicy dźwięków są tablice o dwóch elementach typu int (częstotliwość w Hz i długość trwania dzwięku w ms). */ void play(int nots[][2], unsigned int start, unsigned int stop) { int n; for(n=start; n <= stop; n++) beep(nots[n][0], nots[n][1]); } /**** POCZĄTEK PROGRAMU ****/ /* Definicja głównej funkcji */ int main(void) { /* Konfiguracja sprzętu */ init(); /* Nieskończona pętla */ while (1) { /* Gra dwukrotnie ten sam "kawałek" */ play(koziolek,0,14); play(koziolek,0,14); /* Chwila spokoju :) */ _delay_ms(6000); } return 0; }
Klawiaturka "telefoniczna".
W tym przykładzie program odczytuje w pętli stan klawiatury i wyświetla numer ostatniego wciśniętego przycisku.
W programie kluczową rolę odgrywa funkcja read_keypad, która sprawdza kolejno wszystkie przyciski klawiatury i zwraca numer pierwszego wciśniętego przycisku, albo zero, jeśli żaden przycisk nie został wciśnięty. Czytanie klawiatury odbywa się w następujący sposób: Wybierane są kolejno wiersz po wierszu kalawiatury i dla każdego wiersza testowane są przyciski kolumna po kolumnie.
Każdy przycisk klawiatury jednym wyprowadzeniem przyłączony jest do jednej z czterech linii wierszy i drugim wyprowadzeniem do jednej z trzech linii kolumn. Linie wierszy klawiatury przyłączone są do wyjść P0..PD3 uC AVR, a linie kolumn do wejść PD4..PD6. Początkowo wszystkie cztery wyjścia ustawiana są na stan wysoki napięcia. Wiersz, którego przyciski mają być testowane, wybiera się wymuszając na tej linii stan niski. Wejścia uC, do których przyłączone są linie kolumn klawiatury, są wewnętrzne podciągnięte przez rezystor do napięcia zasilania, więc normalnie występuje na wejściach stan wysoki. Gdy w wybranym wierszu zostanie wciśnięty przycisk, wtedy linia wiersza zewrze się z linią kolumny, i na linii kolumny też pojawi się stan niski.
/* KURS AVR-GCC cz.4 Klawiatura telefonu układ atmega16 (1MHz) */ #define F_CPU 1000000L #include <avr/io.h> #include <util/delay.h> /**** DEFINICJE WŁASNYCH FUNKCJI ****/ /* Konfiguracja sprzętu */ void init(void) { /* Konfiguruj portu A jako wyjścia */ /* Wyświetlacz */ DDRA = 0xFF; PORTA = 0xFF; /* Klawiaturka PD0..PD7 */ DDRD = 0x0f; PORTD = 0x7f; } /* Funkcja zmienia bajt w kodzie binarnym na bajt zakodowany w BCD */ unsigned char bin2bcd(unsigned char bin) { if(bin>99) return bin; return bin/10<<4 | bin%10; } /* Funkcja sprawdza kolejno wszystkie przyciski klawiatury i zwraca numer pierwszego wciśniętego przycisku, albo zero, gdy żaden przycisk nie został wciśnięty. */ unsigned char read_keypad(void) { unsigned char row,col,key; for(row=0x7e,key=1; row>=0x77; row=(row<<1|0x1)&0x7f) { PORTD = row; for(col=0x10; col< 0x80; col<<=1, key++) if(!(PIND & col)) return key; } return 0; } /**** POCZĄTEK PROGRAMU ****/ /* Definicja funkcji main */ int main(void) { unsigned char key; /* Konfiguracja portów we/wy */ init(); /* Nieskończona pętla */ for(;;) if(key = read_keypad()) PORTA = bin2bcd(key); }
Zamek na szyfr
W tym przykładzie wykorzystywane są: klawiatura (PD0..PD7), bzzer(PB0,PB1), i sześć diod led (PA0..PA5); linia PC0 steruje blokadą rygla zamka. Aby otworzyć zamek należy wybrać na klawiaturze sześć właściwych cyfr. Wciskając przycisk z gwiazdką można skasować wszystkie wprowadzone cyfry i rozpocząć wpisywanie kodu od początku. Przy wciśnięciu każdej cyfry zapala się kolejna dioda led i słychać krótki sygnał dźwiękowy. Po wprowadzeniu właściwego kodu natychmiast następuje odblokowanie rygla zamka. Otwarcie zamka sygnalizowane jest migotaniem wszystkich sześciu diod i sygnałem dźwiękowym o zmieniającej się częstotliwości. Wpisanie niewłaściwego kodu sygnalizowane jest wygaszeniem wszystkich sześciu diod LED i długim, "niskim", dźwiękiem.
W przykładzie wykorzystywane są omówione wcześnie funkcje beep i read_keypad.
/* KURS AVR-GCC cz.4 Zamek na szyfr (schemat i opis działania w artykule) układ atmega16 (1MHz) */ /**** PLIKI NAGŁÓWKOWE ****/ #define F_CPU 1000000L #include <avr/io.h> #include <util/delay.h> /**** DEFINICJE FUNKCJI ****/ /* Inicjalizacja i konfiguracja sprzętu */ void init(void) { /* Konfiguruje portów we/wy */ /* PA0..PA6 - diody led */ DDRA = 0x3F; PORTA = 0x3F; /* PB0,PB1 - piezo buzzer */ DDRB = 0x03; PORTB = 0x00; /* Klawiaturka */ DDRD = 0x0f; PORTD = 0x7f; /* PC0 - rygiel zamka */ DDRC = 0x01; PORTC = 0x00; } /* Funkcja sprawdza kolejno wszystkie przyciski klawiatury i zwraca numer pierwszego wciśniętego przycisku, albo zero, jeśłi żaden przycisk nie został wciśnięty. */ unsigned char read_keypad(void) { unsigned char row,col,key; /* Wybiera wiersz po wierszu i testuje przyciski kolumna po kolumnie, zmienna key przechowuje numer przycisku 1-12. */ for(row=0x7e,key=1; row>=0x77; row=(row<<1|0x1)&0x7f) { PORTD = row; for(col=0x10; col< 0x80; col<<=1, key++) if(!(PIND & col)) return key; } /* Jeśli żaden z przycisków nie został wciśnięty, wyjście z kodem zero */ return 0; } /* Funkcja generuje sygnał prostokątny na wyprowadzeniach PB0 i PB1, gdzie przyłączony jest buzzer. Funkcja przyjmuje argumenty: częstotliwość(Hz) sygnału i długość czasu trwania sygnału (ms). */ void beep(unsigned int frequency, unsigned int duration) { unsigned int i,t,n; t = 125000/frequency; n = (250UL*duration)/t; PORTB |= 0x01; PORTB &= ~0x02; for(i=0; i < n; i++) { PORTB ^= 0x01; PORTB ^= 0X02; _delay_loop_2(t); } } /**** POCZĄTEK PROGRAMU ****/ /* Definicja funkcji main */ int main(void) { /* Deklaracje zmiennych */ unsigned char i,j,key,err=0; /* 6 cyfrowy klucz */ unsigned char pass[] = {7,8,7,8,7,8}; /* Konfiguracja i inicjalizacja sprzętu */ init(); /* Główna pętla programu */ for(;;) { /* Blokada rygla zamka */ PORTC &= ~0x01; /* Wygaszenie diod led */ PORTA |= 0x3f; /* Zeruje licznik błędów */ err = 0; /* Odczyt i porównanie z wzorem 6 cyfr klucza */ for(i=0; i<6; i++) { /* Oczekiwanie na wciśnięcie przycisku klawiatury */ while(!(key = read_keypad())); /* Jeśli wciśnięto przycisk gwiazdka *, to kasowanie wprowadzonych cyfr */ if(key == 10) break; /* Sygnalizacja postępu wprowadzania kolejnych cyfr klucza */ PORTA &= ~(1<<i); beep(880,100); /* Oczekiwanie na zwolnienie przycisku */ while(read_keypad()); /* Zatrzymanie aż wygasną drgania styków przycisku */ _delay_ms(80); /* Każda wprowadzona cyfra klucza jest porównywana z wzorem w tablicy "pass", jeśli nie pasuje, licznik błędów w zmiennej "err" jest zwiększany o jeden */ if(key != pass[i]) err++; /* Jeśli wprowadzona cyfra klucza jest ostatnią */ if(i == 5) { /* Jeśli wprowadzono właściwy klucz */ if(!err) { /* Zwolnienie blokady rygla zamka */ PORTC |= 0x01; /* Sygnalizacja otwarcia zamka */ for(j=0; j<6; j++) { PORTA ^= 0x3f; beep(880,200); PORTA ^= 0x3f; beep(440,200); } } else { /* Sygnalizacja błędu */ beep(200, 600); PORTA |= 0x3f; } } } } }
Wyłącznik czasowy
Jak w tytule punktu, wyłącznik czasowy. Wybiera się na klawiaturze liczbę sekund 1-99 i następnie, wciskając przycisk gwiazdka uruchamia się odliczanie od wybranej liczby do zera. Dwucyfrowe liczby wprowadza się wciskając daw przyciski w krótkim odstępie czasu, pierwszą - dziesiątki, drugą - jedności.
/* KURS AVR-GCC cz.4 Wyłącznik czasowy (schemat i opis działania w artykule) układ atmega16 (1MHz) */ #define F_CPU 1000000L #include <avr/io.h> #include <util/delay.h> /**** DEFINICJA WŁASNYCH FUNKCJI ****/ /* Inicjalizacja, konfiguracja sprzętu */ void init(void) { /* Konfiguruje portów we/wy */ /* Wyświetlacz */ DDRA = 0xFF; PORTA = 0xFF; /* Klawiaturka */ DDRD = 0x0f; PORTD = 0x7f; /* Przekaźnik */ DDRC = 0x01; PORTC = 0x00; } /* Funkcja zmienia 8-bit wartość w kodzie dwójkowym na wartość w kodzie BCD */ unsigned char bin2bcd(unsigned char bin) { if(bin > 99) return bin; return bin/10<<4 | bin%10 ; } /* Funkcja sprawdza kolejno wszystkie przyciski klawiatury i zwraca numer pierwszego wciśniętego przycisku, albo zero, gdy żaden przycisk nie został wciśnięty. */ unsigned char read_keypad(void) { unsigned char row,col,key; /* Wybiera wiersz po wierszu i testuje przyciski kolumna po kolumnie, zmienna key przechowuje numer przycisku 1-12. */ for(row=0x7e,key=1; row>=0x77; row=(row<<1|0x1)&0x7f) { PORTD = row; for(col=0x10; col< 0x80; col<<=1, key++) if(!(PIND & col)) return key; } /* Jeśli żaden z przycisków nie został wciśnięty, wyjście z kodem zero */ return 0; } /**** POCZĄTEK PROGRAMU ****/ /* Definicja funkcji main */ int main(void) { /* Deklaracje zmiennych */ unsigned char i,key; signed char v = 0; /* Tablica kodowania przycisków klawiatury */ unsigned char codetab[]= {0xff,1,2,3,4,5,6,7,8,9,0,0,0}; /* Konfiguracja, inicjalizacja sprzętu */ init(); /* Główna pętla programu */ for(;;) { /* Funkcja read_keypad zwraca wartość (kod przycisku) różną od zera, gdy wykryje wciśnięcie przycisku na klawiaturze. */ key = read_keypad(); /* Wprowadzenie liczby sekund (0-99) czasu zliczania */ if(key && key < 10) { v = bin2bcd(codetab[key]); /* Wyświetla pierwszą cyfrę */ PORTA = v; /* Opóźnienie dla wygaśnięcia drgań styków przycisku */ _delay_ms(20); /* Oczekiwanie na zwolnienie przycisku */ while(read_keypad()){}; _delay_ms(20); /* W pętli, 20 razy odczytuje klawiaturę oczekując wprowadzenia drugiej cyfry */ for(i=20; i>0; i--) { if((key = read_keypad())) { _delay_ms(20); /* Składa obie wprowadzone cyfry w jedną liczbę */ v = v*10 + codetab[key]; /* Przesuwa pierwszą cyfrę wyświetlacza pozycję na lewo */ PORTA <<= 4; /* Wyświetla drugą cyfrę */ PORTA |= codetab[key]; /* Czeka na zwolnienie przycisku */ while(read_keypad()){}; _delay_ms(20); break; } /* Dodatkowe opóźnienie między kolejnymi odczytami klawiatury*/ _delay_ms(50); /* Wygasza pierwszą cyfrę na wyświetlaczu, jeśli wprowadzono liczbę jednocyfrową */ if(i == 1) PORTA |= 0XF0; } } /* Wciśnięcie przycisku z gwiazdką, gdy wcześnie wprowadzona liczba sekund była większa od zera, rozpoczyna odliczanie */ else if (key == 10 && v) { PORTC |= 0x01; // włącza przekaźnik /* Odliczanie od v do zera, zmienna v zawiera wprowadzoną wcześniej liczbę sekund */ do{ _delay_ms(960); // Prawie sekunda opóźnienia v--; /* Wyświetla aktualny stan licznika */ PORTA = bin2bcd(v); }while(v>0); PORTC &= ~0x01; // wyłącza przekaźnik } } }
W następnej części kursu..
W następnej części kursu tematem przewodnim będą tekst i działania na tekście. Kolejnym tematem będzie podział kodu źródłowego programu na oddzielnie kompilowane pliki. Napiszę też kilka zdań na temat preprocesora języka C. W części praktycznej będziemy bawić się alfanumerycznym wyświetlaczem LCD HD44780, przyłączymy też AVRa do komputera PC poprzez port szeregowy rs232.
/* Konfiguruje portów we/wy */
/* PA0..PA6 - diody led */
DDRA = 0x3F;
PORTA = 0x3F;
Powinno być PA0..PA5