background image

 
Artykuł pochodzi ze strony XYZ HOBBY ROBOT (

xyz.isgreat.org

Kurs AVR-GCC cz.4(v2)

17.08.2009 ABXYZ

Dużo wody upłynęło Wisłą do morza 
od czasu ukazania się trzeciej część 
niniejszego kursu i wreszcie jest 
część czwarta. Ostatnio omawiane 
były: zmienne, operatory, pętle 
i instrukcje sterujące jak: if, switch. 
W tej części kursu przerobimy 
tablice, struktury i funkcje, czyli 
dalszy ciąg podstaw języka C. Jak 
poprzednio, wpierw omówię 
wymienione tematy, a następnie 
zestawimy układ z AVRem 

i uruchomimy kilka przykładowych programów. W tej części kursu 
będziemy bawić się przyłączając do AVRa klawiaturę telefoniczną 
i piezo-buzzer(bez generatora). 

Tablice

Gdy w programie mamy większą ilość danych jednego typu, to 
zamiast tworzyć wiele pojedynczych zmiennych, lepiej będzie 
utworzyć tablicę. 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. 

W języku C tablicę przed pierwszym użyciem należy wcześniej 
zdefiniować. Tablicę definiuje się pisząc kolejno: typ danych, nazwę 
tablicy i objętą parą nawiasów kwadratowych [] liczbę elementów 
tablicy. Definicję należy zakończyć średnikiem. 

int

 main(

void


   

/* Definicja tablicy 'pomiar' o 64 elementach typu char */

 

   

char

 pomiar[

64

]; 

 
   

/* Definicja tablicy 'wysokosc' o 32 elementach typu int */

 

   

int

 wysokosc[

32

]; 

 
   

/* Definicja tablicy 'temperatura' o 16 elementach typu 

      double */

 

   

double

 temperatura[

16

];

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. 

  

/* 2 elementowa  tablica 40 elementach tablic typu char */

 

   

char

 bufor[

2

][

40

]; 

 
  

/* Tablica trójwymiarowa */

 

  

char

 jakas_tablica[

4

][

8

][

3

]; 

Definiując tablicę można jednocześnie tablicę inicjalizować 
wartościami początkowymi. W tym celu dopisuje się na końcu 

Page 1 of 27

XYZ Hobby Robot-Kurs AVR-GCC, cz.4

13/06/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=7&pv

background image

definicji: operator = , a po nim, objęte parą nawiasów klamrowych 
{}, wartości początkowe kolejnych elementów tablicy rozdzielone 
przecinkami. 

/* Definicja tablicy z jednoczesną inicjalizacją wartościami 
początkowymi dwóch pierwszych elementów tablicy */

 

  

char

 tablica[

4

] = {

0x01

,

0x02

}; 

    

/* Definicja tablicy z jednoczesną inicjalizacją wartościami 
początkowymi wszystkich czterech elementów. W takim przypadku 
można nie wpisywać w definicji ilości elementów tablicy, 
kompilator sam to wyliczy. */

 

  

char

 tablica[] = {

0x01

,

0x02

,

0x04

,

0x08

}; 

 

/* Definicja z inicjalizacją tablicy dwuwymiarowej */

 

  

char

 inna_tablica[][

2

] = { 

0x31

,

0x02

                             

0xf1

,

0x02

                             

0x41

,

0xf2

                             

0xf1

,

0xc2

                             

0x11

,

0xa2

 };

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

  

/* Przypisanie pierwszemu elementowi tablicy wartości 23.5 */

 

  temperatura[

0

]

 = 23.5

 
  

/* Przypisanie drugiemu elementowi tablicy wartości 22.0 */

 

  temperatura[

1

]

 = 22.0

 
  

/* Przypisanie ósmemu elementowi tablicy wartości 17.5 */

 

  temperatura[

7

]

 = 17.5

 
  

/* Przypisanie jakiejs_zmiennej wartości czwartego elementu 

   tablicy */

 

  jakas_zmienna = temperatura[

3

];

Trzeba też pamiętać, że jeżeli zdefiniujemy tablicę n elementową 
i spróbujemy zapisać coś pod indeksem równym lub większym n to 
kompilator nie zgłosi błędu, ale skutkować to może 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. 

unsigned char

 i,j; 

int

 tablica_1[

16

]; 

int

 tablica_2[

16

]; 

int

 tablica_3[

10

][

10

]; 

 

/* Kolejnym elementom tablicy przypisane zostaną wartości: 
0,10,20,30..150 */

 

for

(i = 

0

; i < 

16

; i++) 

         tablica_1[i] = i * 

10

 

/* Kopiowanie zawartości tablica_1 do tablica_2 */

 

for

(i = 

0

; i < 

16

; i++) 

         tablica_2[i] = tablica_1[i]; 
 

/* Wypełnienie tablicy dwuwymiarowej */

 

for

(i = 

0

; i < 

9

; i++) 

     

for

(j = 

0

; j < 

9

; j++) 

         tablica_3[i][j] = i * j; 

Page 2 of 27

XYZ Hobby Robot-Kurs AVR-GCC, cz.4

13/06/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=7&pv

background image

Na tym etapie nauki, te informacje na temat tablic mam wystarczą. 

Struktury

Strukturą w języku C nazywa się grupę zmiennych oznaczoną jedną 
nazwą. Na przykład, aby opisać okrąg (figurę geometryczną), którą 
program będzie rysował na ekranie wyświetlacza graficznego, 
potrzebujemy co najmniej trzech wartości: dwóch współrzędnych 
położenia środka okręgu oraz długość promienia. Dla jednego 
egzemplarza okręgu można by utworzyć w pamięci trzy zmienne 
o nazwach: x, y, r. Jednak, gdy przyjdzie potrzeba zapisać 
w pamięci większą liczbę okręgów, lepiej w tym celu będzie 
zdeklarować odpowiednią strukturę. 

Deklaracja struktury informuje kompilator z jakich zmiennych 
będzie składać się nowy typ struktury. Deklaracja struktury 
rozpoczyna się od słowa struct i nazwy nowego typu struktury. Po 
nazwie, pomiędzy parą nawiasów klamrowych {}, deklaruje się 
zmienne składowe struktury. Cała instrukcja deklaracji kończy się 
średnikiem. 

/* Deklaracja struktury typu 'Okrag' */

 

 

struct

 Okrag 


  

int

 x; 

  

int

 y; 

  

unsigned int

 r; 

};

Sama deklaracja struktury nie tworzy jeszcze zmiennych w pamięci, 
tworzy jedynie nowy typ. Aby utworzyć pamięci egzemplarz 
struktury danego typu należy ją zdefiniować, w podobny sposób jak 
definiuje się zmienne. Definicja zaczyna się od słówka struct, dalej 
podaje się nazwę typu zdeklarowanego wcześniej struktury i nazwę 
nowego egzemplarza struktury lub nazwy kilku egzemplarzy 
oddzielone przecinkami; na końcu deklaracji stawia się średnik. 

/* Definicja trzech struktur typu 'Okrag' o nazwach: 
okrag_1, okrag_2, okrag_3 */

 

 

struct

 Okrag okrag_1, okrag_2, okrag_3;

Należy zapamiętać różnice między deklaracją a definicją: Deklaracja 
nie rezerwuje miejsca w pamięci, a jedynie tworzy nowy typ 
struktury. Dopiero definicja tworzy w pamięci konkretne 
egzemplarze danego typu struktury. Deklarując nowy typ struktury 
można jednocześnie utworzyć(zdefiniować) jeden lub kilka jej 
egzemplarzy; w tym celu, na końcu deklaracji, między nawiasem 
zamykającym } a średnikiem, dopisuje się nazwę tworzonego 
egzemplarza struktury lub nazwy kilku egzemplarzy rozdzielone 
przecinkami, przykład: 

/* Deklaracja struktury typu 'prostokąt' z jednoczesnym 
tworzeniem jej trzech egzemplarzy */

 

 

struct

 prostokat 


  

int

 x; 

  

int

 y; 

  

unsigned int

 width; 

  

unsigned int

 height; 

Page 3 of 27

XYZ Hobby Robot-Kurs AVR-GCC, cz.4

13/06/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=7&pv

background image

 
} prostokat1, prostokat2, prostokat3; 

Dostęp do zmiennych składowych struktury uzyskuje się podając 
kolejno: nazwę egzemplarza struktury, operator kropka i nazwę 
składowej użytej w deklaracji struktury, przykłady: 

/* Deklaruje strukturę bez podania nazwy typu (tak też 
można) i jednocześnie tworzy dwa egzemplarze struktury 
tego typu. */

 

struct

 


  

int

 x; 

  

int

 y; 

 
} punkt1, punkt2; 
 

/* Dostęp do zmiennych składowych struktury 
uzyskuje się używając operatora kropki. */

 

punkt1.x  = 17; 
punkt1.y  = 20; 
punkt2.x  = 10; 
punkt2.x  = 18; 
 

/* Składowymi struktury posługujemy się podobnie jak 
zmiennymi prostego typu */

 

jakas_ zmienna = punkt1.x + punkt2.x; 
inna_zmienna = punkt1.y + punkt2.y;

Struktury mogą wchodzić w skład innych struktur, w przykładzie 
poniżej struktura typu Punkt jest jest zagnieżdżona w strukturze 
typu Okrag. 

/* Deklaracja struktury typu 'Punkt'  */

 

struct Punkt

 


  

int

 x; 

  

int

 y; 

}; 
 

/* Deklaracja struktury typu 'Okrag' z jednoczesnym 
utworzeniem trzech egzemplarzy tego typu struktury  */

 

struct

 Okrag 


  

struct

 Punkt o; 

  

unsigned int

 r; 

 
}okragA, okragB, okragC; 
 

/* Dostęp do zmiennych składowych struktury uzyskuje 
się używając operatora kropki. */

 

okragC.o.x = okragA.o.x + okragB.o.x; 

Definiując strukturę można jednocześnie inicjalizować jej składowe 
wartościami początkowym. W tym celu, w definicji, po nazwie nowo 
tworzonego egzemplarza struktury, wstawia się operator '=', i dalej, 
między nawiasami klamrowymi {}, wartości początkowe kolejnych 
składowych struktury rozdzielone przecinkami. 

/* Definiując struktury można je jednocześnie inicjalizować 
wartościami początkowymi. */ 

struct

 punkt p1={12,33}, p2={20,30};

Także można inicjalizować definiowaną strukturę zawartością 
składowych innego egzemplarza struktury tego typu, przykład: 

Page 4 of 27

XYZ Hobby Robot-Kurs AVR-GCC, cz.4

13/06/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=7&pv

background image

/* Definicja struktury z jednoczesną inicjalizacją 
składowych */ 

struct

 punkt p3 = p1;

Używając operatora = można strukturę skopiować, tzn. przypisać 
składowym jednego egzemplarza struktury wartości odpowiednich 
składowych innego egzemplarza. 

/* Kopiowanie struktury */

 

p2 = p1;

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. 

 

Przebieg przykładowego programu składającego się z podprogramu głównego i dwóch 

podprogramów. Cały program zaczyna się od pierwszej instrukcji podprogramu głównego. 

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 

Page 5 of 27

XYZ Hobby Robot-Kurs AVR-GCC, cz.4

13/06/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=7&pv

background image

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. 

/* Najprostszy program-całość umieszczona w funkcji main */

 

#define F_CPU 1000000L 
#include <avr/io.h>   

 

/* Definicja funkcji main */

 

int

 main(

void


  

/* Instrukcje naszego  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 (). 

/* Funkcje _delay_ms, _delay_us  oczekują przy wywołaniu 
jednego argumentu typu double -wartości opóźnienia */

 

 

/* Argumentem funkcji jest wartość stała 80 */

 

_delay_ms(

80

); 

 

/* Argumentem funkcji będzie wartość wyrażenia 
jakas_zmienna+20 */

 

_delay_us(jakas_zmienna + 

20

); 

 

/* Funkcje mogą oczekiwać jednego lub większej 
liczby argumentów albo żadnego */

 

 

/* Jeśli jakaś funkcja oczekuje kilku argumentów, kolejne 
argumenty oddziela się przecinkiem. */

 

jakas_funkcja(

10

17.3

, jakas_zmienna); 

 

Page 6 of 27

XYZ Hobby Robot-Kurs AVR-GCC, cz.4

13/06/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=7&pv

background image

/* Inna funkcja nie oczekuje żadnego argumentu */

 

inna_funkcja();

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 rand, obliczająca liczbę losową, zwraca  
wartość typu int */

 

jakas_ zmienna = rand() + 

100

 

/* Jeśli wartość zwrócona przez funkcje rand będzie większa  
od 10, wtedy warunek w instrukcji if będzie spełniony */

 

if

( rand() > 

10

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. 

/* Definicja funkcji  */

 

typ nazwa_funkcji(parametry) 

  

/* Instrukcje wewnątrz 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. 

/* Definicja funkcji które  niczego nie zwraca i nie oczekuje  
ż

adnych argumentów */

 

void 

io_init(

void


  DDRD  = 

0x0f

  PORTD = 

0xf0

 
  DDRB  = 

0x0f


 

/* Definicja funkcji main */

 

int

 main(

void


   

/* Wywołanie funkcji io_init */

 

   io_init();

Poniżej znajduje się przykład definicji funkcji, która przyjmuje dwa 
argumenty typu unsigned int i nie zwraca niczego. Wartości 

Page 7 of 27

XYZ Hobby Robot-Kurs AVR-GCC, cz.4

13/06/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=7&pv

background image

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. 

/* Definicja funkcji beep */

 

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); 
    } 

 

/* Definicja funkcji main */

 

int

 main(

void


    

int

 f = 440, d = 250; 

 
   

/* Przykłady wywołanie funkcji beep */

 

   beep(

1000

,

1000

); 

 
   

/* Do funkcji zostaną przekazane wartości zmiennych f i d */

 

   beep(f,n);

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

/* Funkcja zmienia wartość argumentu na bajt w kodzie BCD */

 

unsigned char

 bin2bcd(

unsigned char

 bin) 

/* Jeśli bin > 99, to funkcja zwraca wartość argumentu bin */

 

  

if

(bin>

99

return

 bin; 

 

/* Funkcja zwraca wartość wyrażenia po prawej stronie 
instrukcji return */

 

  

return

 bin/

10

<<

4

 | bin%

10


 

/* Wywołanie funkcji bin2bcd, zwrócona przez funkcję 
wartość 0x99 zostanie przypisane zmiennej */

 

jakas_zmienna = bin2bcd(

99

);

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. 

Page 8 of 27

XYZ Hobby Robot-Kurs AVR-GCC, cz.4

13/06/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=7&pv

background image

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: 

/* Definicja funkcji */

 

void

 funkcja(

int

 n, 

int

 tablica[]) 


  

/* Wypełnia tablicę */

 

  

for

(; n; n--) 

         tablica[n-1] = n; 

 

int

 main(

void


  

/* Definicja tablicy  */

 

  

int 

 tablica[

16

]; 

 
  

int

 n = 

16

 

/* Zmienne przekazywane są do funkcji przez wartość, a tablice  
przez wskaźnik. Po wykonaniu funkcji wartość zmiennej 
n nie zmieni się, natomiast tablica zostanie wypełniona */

 

  funkcja(n, tablica);

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. 

/* Zmienna dostępna we wszystkich funkcja w całym pliku */

 

int

 zmienna_globalna ;   

 

/* Definicja funkcji */

 

void

 funkcja(

int

 n, 

int

 tablica[]) 


  

/* Zmienna dostępna tylko w tej funkcji */

 

  

double

 zmienna_lokalna;   

 
  zmienna_globalna = 

17


 

/* Definicja innej funkcji */

 

void

 inna_funkcja(

char

 s ) 


  

/* Zmienna dostępna tylko w tej funkcji */

 

  

double

 zmienna_lokalna;   

 
  zmienna_lokalna = zmienna_globalna; 
 
}

Jeśli w funkcji zostanie utworzona zmienna o nazwie identycznej 
z nazwą istniejącej zmiennej utworzonej poza funkcjami, wtedy, 

Page 9 of 27

XYZ Hobby Robot-Kurs AVR-GCC, cz.4

13/06/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=7&pv

background image

w funkcji, pod tą nazwą dostępna będzie zmienna deklarowane 
w funkcji. 

/* Zmienna dostępna we wszystkich funkcja w całym pliku */

 

int

 jakas_zmienna ;   

 

/* Definicja funkcji */

 

int

 jakas_funkcja(

void


  

/* Zmienna dostępna tylko w tej funkcji */

 

  

int

 jakas_zmienna;   

 
  

/* Zapis nastąpi do zmiennej zdefiniowanej w tej funkcji. */

 

  jakas_zmienna = 

17

}

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. 

/* Definicja funkcji */

 

int

 licz(

int

 n) 


  

/* Definicja statycznej zmiennej licznik, która będzie 

istnieć przez cały okres działania programu */

 

  

static int

 licznik;  

 
  licznik += n; 
 
  

return

 licznik;  


 

int

 main(

void


   

/* Funkcja zwróci wartość 2 */

 

   jakas_zmienna = licz(

2

); 

 
   

/* Funkcja zwróci wartość 5 */

 

   jakas_zmienna = licz(

3

);

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 (system dziesiętny)

0000 0000

00

0000 0001

01

Page 10 of 27

XYZ Hobby Robot-Kurs AVR-GCC, cz.4

13/06/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=7&pv

background image

bity

liczba (system dziesiętny)

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

Kodowanie BCD 

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ę telefoniczną, dwa przekaźniki oraz brzęczyk(bez 
generatora, sam przetwornik piezo). 

Cały schemat podzieliłem na kilka części: 

Page 11 of 27

XYZ Hobby Robot-Kurs AVR-GCC, cz.4

13/06/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=7&pv

background image

 

Schemat 4.1 - sposób przyłączenia do układu atmega16 zasilania, resetu i złącza 

programatora. Kliknij w obrazek, żeby powiększyć. 

Segmenty wyświetlacza LED potrzebują więcej prądu niż 
pojedyncze diody LED, więc tym razem każdy z wyświetlaczy LED 
będzie przyłączony do mikrokontrolera poprzez układ scalony 7447
(dekoder kodu BCD na kod wyświetlacza 7-segmentowego). 

Page 12 of 27

XYZ Hobby Robot-Kurs AVR-GCC, cz.4

13/06/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=7&pv

background image

 

Schemat 4.2 Dwa 7-segmentowe wyświetlacze LED przyłączone do mikrokontrolera 

poprzez układy 7447 (dekoder kodu BCD na kod wyświetlacza 7-segmentowego). Kliknij 

w obrazek, aby powiększyć. 

 

Fot. 4.1 Dwa 7 segmentowe wyświetlacze LED wraz z dekoderami 7447 umieszczone na 

osobnej płytce. 

Page 13 of 27

XYZ Hobby Robot-Kurs AVR-GCC, cz.4

13/06/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=7&pv

background image

 

Schemat 4.3 Schemat klawiatury 3x4 i sposób jej przyłączenia do portów we/wy mC 

atmega16. Kliknij w obrazek, żeby powiększyć. 

 

Fot. 4.2 Klawiatura wymontowana z aparatu telefonicznego. 

Page 14 of 27

XYZ Hobby Robot-Kurs AVR-GCC, cz.4

13/06/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=7&pv

background image

 

Fot. 4.3 Klawiatura wykonana z przycisków micro swich na płytce uniwersalnej. 

 

Schemat. 4.4 Sposób przyłączenia przetwornika piezo. 

Page 15 of 27

XYZ Hobby Robot-Kurs AVR-GCC, cz.4

13/06/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=7&pv

background image

 

Fot. 4.4 Buzzer bez generatora ( przetwornik piezo - membrana z puszką ) wylutowany ze 

starego, popsutego kalkulatora. 

 

Fot. 4.5 Przetwornik piezo - membrana przylutowana do płytki obwodu drukowanego. 

 

Schemat 4.5 Sposób przyłączenia przekaźników. 

Page 16 of 27

XYZ Hobby Robot-Kurs AVR-GCC, cz.4

13/06/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=7&pv

background image

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

(;;) 

    { 
    } 
}

Szkielet prostego programu.

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: 

Page 17 of 27

XYZ Hobby Robot-Kurs AVR-GCC, cz.4

13/06/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=7&pv

background image

częstotliwość sygnału w hercach i dułgość 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 jednostkę. 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 dułgość czasu trwania sygnału (ms). 

Page 18 of 27

XYZ Hobby Robot-Kurs AVR-GCC, cz.4

13/06/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=7&pv

background image

*/

 

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

,

15

); 

      play(koziolek,

0

,

15

); 

   
      

/*  Chwila spokoju :) */

    

      _delay_ms(

6000

);  

   } 
   

return

 

0

}

Listing 4.1 Program "Pozytywka elektroniczna". 

Klawiaturka "telefoniczna".

W tym przykładzie program odczytuje w pętli stan klawiatury 
i wyświetla numer ostatniego wciśniętego przycisku. 

 

Page 19 of 27

XYZ Hobby Robot-Kurs AVR-GCC, cz.4

13/06/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=7&pv

background image

Animacja prezentuje sposób działania programu "Klawiatura telefoniczna". 

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. 

 

Sposób testowania przycisków klawiatury. 

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

 

Page 20 of 27

XYZ Hobby Robot-Kurs AVR-GCC, cz.4

13/06/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=7&pv

background image

 

/* 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); 
}

Listing 4.2 Program "Klawiatura telefoniczna". 

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 

Page 21 of 27

XYZ Hobby Robot-Kurs AVR-GCC, cz.4

13/06/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=7&pv

background image

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. 

 

Animacja prezentuje działanie programu "Zamek na szyfr". 

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

Page 22 of 27

XYZ Hobby Robot-Kurs AVR-GCC, cz.4

13/06/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=7&pv

background image

 */

 

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 dułgość 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 */

 

Page 23 of 27

XYZ Hobby Robot-Kurs AVR-GCC, cz.4

13/06/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=7&pv

background image

         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

            } 
         } 
      } 
   } 
}

Listing 4.3 Program "Zamek na szyfr" 

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. 

 

Animacja prezentuje działanie programu "Wyłącznik czasowy" 

Page 24 of 27

XYZ Hobby Robot-Kurs AVR-GCC, cz.4

13/06/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=7&pv

background image

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

 

Page 25 of 27

XYZ Hobby Robot-Kurs AVR-GCC, cz.4

13/06/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=7&pv

background image

   

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 

      }  
   }  
}

Listing 4.4 Program "Wyłącznik czasowy". 

Page 26 of 27

XYZ Hobby Robot-Kurs AVR-GCC, cz.4

13/06/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=7&pv

background image

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. 

Kurs AVR-GCC cz.5

 

 

Copyright © 2009-2010 ABXYZ - Wszelkie prawa zastrzeżone 

Page 27 of 27

XYZ Hobby Robot-Kurs AVR-GCC, cz.4

13/06/2010

http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=7&pv