background image

Artykuł pochodzi ze strony XYZ HOBBY ROBOT (

xyz.isgreat.org

)

Kurs AVR-GCC cz.5 (v1)

29.03.2010 ABXYZ

Uwaga, to jest dopiero szkic artykułu, tekst jest wciąŜ w trakcie
edycji, wszelkie uwagi, podpowiedzi i komentarze będą bardzo
pomocne.

Ostatnio omawiane były tablice i funkcje, a jeszcze wcześniej:
zmienne, pętle i instrukcje warunkowe. W tej części kursu tematem
przewodnim będzie tekst i działania na tekście. Napiszę teŜ kilka
zdań na temat preprocesora języka C. Kolejnym omawianym
zagadnieniem będzie podział kodu źródłowego programu na
oddzielnie kompilowane pliki. W części praktycznej będziemy bawić
się alfanumerycznym wyświetlaczem LCD, przyłączymy do AVRa
termometr cyfrowy ds18b20, a dalej połączymy AVRa
z komputerem PC poprzez port szeregowy RS232C.

Programy z tekstem

Dotąd w przykładach z kursu uŜywane były jedynie zmienne
liczbowe. A co z tekstem ? Oczywiście tekst przechowuję się
w pamięci komputera równieŜ w postaci liczb. Po prostu małym i
wielkim literom alfabetu, cyfrom oraz wszystkim innym znakom
przyporządkowuje się kolejne liczby z pewnego zakresu. Zwykle
jeden znak zajmuje w pamięci komputera jeden bajt (osiem bitów),
najczęściej uŜywanym bywa kodowanie 

ASCII

 lub jego

rozszerzenia.

Tablica kodów ASCII. Literom alfabetu, cyfrom oraz wszystkim innym znakom

przyporządkowuje się kolejne liczby. Kliknij w obrazek, Ŝeby obejrzeć całość.

Inaczej inŜ jest w wielu innych językach programowania w C nie
przewidziano specjalnego typu zmiennej przeznaczonego do
przechowywania tekstu. W języku C do przechowywania tekstu
wykorzystuje się tablice typu char. Aby zapamiętać tekst kolejne
pola tablicy wypełniane są kodami ASCII znaków tworzących tekst.

Tablica wypełniona kodami ASCII kolejnych liter tworzących napis "Siemka!". W języku C

1 z 40

background image

tekst przechowuje się po prostu w tablicach typu char.

W kodzie źródłowym programu moŜna posługiwać się stałymi
znakowymi i stałymi napisowymi. Stała znakowa ma postać znaku
objętego pojedynczymi cudzysłowami i posiada wartość liczbową
kodu ASCII tego znaku.

  

int

 jeden_znak ;

;

;

;

  

char

 jakis_napis[

7

];

;

;

;

/* W zmiennej jeden_znak znajdzie się wartość
65(kod ASCII znaku A) */

  jeden_znak = 

'A'

;

;

;

;

/* Zapisujemy tekst do tablicy znak po znaku */

  jakis_napis[

0

] = 

'S'

;

;

;

;

  jakis_napis[

1

] = 

'i'

;

;

;

;

  jakis_napis[

2

] = 

'e'

;

;

;

;

  jakis_napis[

3

] = 

'm'

;

;

;

;

  jakis_napis[

4

] = 

'k'

;

;

;

;

  jakis_napis[

5

] = 

'a'

;

;

;

;

  jakis_napis[

6

] = 

'!'

;

;

;

;

Stałe napisowe tworzy się obejmując fragment tekstu parą
podwójnych cudzysłowów. Definiując tablicę znakową moŜna ją
jednocześnie zainicjować stałą napisową. Tym sposobem tablica,
w momencie jej tworzenie, zostanie wypełniona kodami ASCII
kolejnych znaków tworzących napis.

  

/*Tworzona tablica zostanie wypełniona ciągiem znaków */

  

char

 jakis_napis[] = 

"Siemka!"

;

;

;

;

  

/* Zawartość tablicy:

     jakis_napis[0] = 'S'

  

   jakis_napis[1] = 'i'

  

   jakis_napis[2] = 'e'

  

   jakis_napis[3] = 'm'

  

   jakis_napis[4] = 'k'

  

   jakis_napis[5] = 'a'

  

   jakis_napis[6] = '!'

  

   jakis_napis[7] =  0x0 */

W przykładzie wyŜej, w tablicy za ostatnim znakiem napisu,
kompilator dodatkowo wstawi bajt o wartości zero. Znak o kodzie
zero pełni tu rolę znacznika końca ciągu znaków. Jest zasadą
w języku C Ŝe ciągi znaków kończą się znakiem o kodzie równym
zero. Tekst moŜe mięć dowolną długość, aby się tylko zmieścił w 
tablicy wzraz z ograniczającym go bajtem zero.

JeŜeli w stałej napisowej potrzeba wstawić znak podwójnego
cudzysłowu, to naleŜy go poprzedzić znakiem backslash (\"). A jeśli
chcemy wstawić sam znak backslash, to naleŜy wpisać dwa znaki
backslash (\\). Są to tzw. sekwencje specjalne zaczynające się od
znaku backslash, dalej jeszcze będę o nich pisał.

  

/* Do tablicy zapisany zostanie ciąg znaków:

     abcdef"gh\i\jklmnop"qrs'tuv'wxyz */

  

char

 jakis_napis[] = 

"abcdef\"gh\\i\\jklmnop\"qrs'tuv'wxyz"

;

;

;

;

Jeśli jakaś funkcja oczekuje jako argumentu tablicy typu char, to
jako argument, zamiast nazwy tablicy, moŜna wstawić stałą
napisową.

/* Definicja tablicy  */

2 z 40

background image

char

 tablica[]

[]

[]

[]=

=

=

=

"KURS AVR-GCC"

;

;

;

;

/* Definicja przykładowej funkcji, która jak
argumentu oczekuje tablicy typu char */

void

 funkcja(

(

(

(

char

 tablica[]

[]

[]

[])

)

)

)

{

{

{

{
}

}

}

}

int

 main(

(

(

(

void

)

)

)

)

{

{

{

{

/* Wywołanie funkcji */

  funkcja(

(

(

(tablica)

)

)

);

/* Jako argument moŜna wstawić stałą napisową */

  funkcja(

(

(

(

"KURS AVR-GCC"

);

);

);

);

W języku C brakuje równieŜ operatorów przeznaczonych do działań
na tekście, takie operacje jak porównywanie czy łączenie napisów
pozostaje zaprogramować samemu. Nie jest to nic specjalnie
trudnego, oto kilka przykładów prostych operacji na tekstach:

Wykorzystując instrukcję pętli moŜna porównywać dwa ciągi
znaków.

/* Przyrównanie ciągu znaków */

   

unsigned

 

char

 i;

;

;

;

   

char

 str1[]=

[]=

[]=

[]= 

"KURS AVR-GCC"

;

;

;

;

   

char

 str2[]=

[]=

[]=

[]= 

"KURS AVR-GCC"

;

;

;

;

   

for

(

(

(

(i=

=

=

=

0

;

;

;

; str1[

[

[

[i]==

]==

]==

]==str2[

[

[

[i]

]

]

] &&

&&

&&

&& str1[

[

[

[i];

];

];

]; i++);

++);

++);

++);

   

/* Jeśli warunek spełniony,  to porównywane

   ciągi znaków róźnią się */

   

if

(

(

(

(str1[

[

[

[i]

]

]

] ||

||

||

|| str2[

[

[

[i])

])

])

]) 

Podobne uŜywając instrukcji pętli moŜna połączyć dwa lub więcej
napisów w jeden tekst.

   

/* Łączenie ciągów znaków  */

   

unsigned

 

char

 i,

,

,

,j;

;

;

;

   

char

 str1[]=

[]=

[]=

[]= 

"KURS"

;

;

;

;

   

char

 str2[]=

[]=

[]=

[]= 

" AVR-GCC"

;

;

;

;

   

char

 str3[]=

[]=

[]=

[]= 

" cz.5"

;

;

;

;

   

char

 buffer[

[

[

[

18

];

];

];

];

    

/* Łączy trzy ciągi znaków . Całość  

    zostanie  zapisana w tablicy 'buffer[]'  */

   

for

(

(

(

(i=

=

=

=

0

,

,

,

,j=

=

=

=

0

;

;

;

;  buffer[

[

[

[j]=

]=

]=

]=str1[

[

[

[i];

];

];

];  i++,

++,

++,

++,j++);

++);

++);

++);

   

for

(

(

(

(i=

=

=

=

0

      ;

;

;

;  buffer[

[

[

[j]=

]=

]=

]=str2[

[

[

[i];

];

];

];  i++,

++,

++,

++,j++);

++);

++);

++);

   

for

(

(

(

(i=

=

=

=

0

      ;

;

;

;  buffer[

[

[

[j]=

]=

]=

]=str3[

[

[

[i];

];

];

];  i++,

++,

++,

++,j++);

++);

++);

++);

A tak z pomocą instrukcji pętli for moŜna wyznaczyć długość ciągu
znaków zakończonego zerem.

/* Obliczanie długości ciągu znaków */

   

char

 s[]

[]

[]

[] =

=

=

"KURS AVR-GCC"

;

;

;

;

   

unsigned

 

char

 i;

;

;

;

   

/* Zmienna 'i' zawierać będzie długość ciągu

   znaków  w tablicy 's[]' . Bajt o wartości zero 
   na końcu  ciągu nie jest liczony. 
   */

   

for

(

(

(

(i=

=

=

=

0

;

;

;

; s[

[

[

[i];

];

];

]; i++);

++);

++);

++);

JeŜeli zamierzamy na wyświetlaczu alfanumerycznym pokazać
wartość zmiennej liczbowej, to koniecznym będzie zamienić wartość
liczbową na ciąg znaków. Kawałek kodu poniŜej zmienia 16-bitową

3 z 40

background image

liczbę całkowitą bez znaku na odpowiadający jej ciąg cyfr (kodów
ASCII cyfr). Wartość liczbowa w zmiennej 'a' jest cyklicznie w pętli
dzielona przez 10, dopóki nie stanie się zerem - dzielenie
całkowite. Obliczana w kaŜdej iteracji pętli reszta z dzielenia
stanowi cyfrę stojącą na kolejnej pozycji w liczbie, idąc w kierunku
od cyfry najmniej znaczącej do najbardziej znaczącej. Reszta
z dzielenia (liczba z zakresu 0..9) zmieniana jest na kod ASCII
cyfry przez dodanie do niej wartości kodu ASCII cyfry zero (48).

/* Zmiana liczby na ciąg znaków ASCII */

   

signed

 

char

 i;

;

;

;

   

unsigned

 

int

 a ;

;

;

;

   

char

 buffer[

[

[

[

6

];

];

];

];

   
   a =

=

=

65535

;

;

;

;

  
   

/* Wypełnia tablicę 'buffer[]' kodami ASCII cyfr

   skaładających się na liczbę  w zmiennej 'a' */

  

 
   

for

(

(

(

(i=

=

=

=

4

,

,

,

,buffer[

[

[

[

5

]=

]=

]=

]=

0

;

;

;

; a;

;

;

; a/=

/=

/=

/=

10

,

,

,

,i--)

--)

--)

--) 

           buffer[

[

[

[i]=

]=

]=

]= a%

%

%

%

10

 +

+

+

'0'

;

;

;

;

   
   

for

(;

(;

(;

(; i>=

>=

>=

>=

0

;

;

;

; i--)

--)

--)

--)

           buffer[

[

[

[i]

]

]

] =

=

=

' '

;

;

;

;

Opisane działania na tekstach moŜna zrealizować równieŜ
wykorzystując funkcje z biblioteki standardowej języka C.

Do przekształcenia wartości liczbowych na ciągi znaków i w ogóle
do formowania komunikatów tekstowy uŜyteczne mogą być
standardowe funkcje printf() i sprintf(); aby móc z nich skorzystać
naleŜy gdzieś na początku pliku wstawić polecenie:

#include <stdio.h>

Funkcje printf i sprintf róŜnią się od siebie tym, Ŝe sprintf zapisuje
dane do tablicy, zaś printf do standardowego wyścia. Na kilku
prostych przykładach wyjaśnię działanie funkcji sprintf.

char

 buf[

[

[

[

32

];

];

];

];

  

int

  t =

=

=

21

;

;

;

;

  

int

  p =

=

=

1013

;

;

;

;

  

int

  v =

=

=

10

;

;

;

;

  
  

/* W tablicy 'buf' zostanie zapisany ciąg znaków:

  'Temperatura powietrza: 21°C' */

  sprintf(

(

(

(buf,

,

,

,

"Temp. powietrza: %d°C"

,

,

,

,t);

);

);

);

  

/* W tablicy 'buf' zostanie zapisany ciąg znaków:

  'T:21°C, P:1013hPa, V:10m/s' */

  sprintf(

(

(

(buf,

,

,

,

"T:%d°C, P:%dhPa, V:%dm/s"

,

,

,

,t,

,

,

,p,

,

,

,v);

);

);

);

Pierwszym argumentem funkcji sprintf() jest tablica znakowa,
miejsce w pamięci, gdzie zostanie zapisany ciąg znaków
zakończony zerem - tworzony komunikat. Drugim argumentem
sprintf() jest ciąg znaków zawierający format komunikatu. Format
zawiera stałą treść komunikatu wraz z tzw. "specyfikacjami
przekształceń". Specyfikacje przekształceń są to takie "znaczki-
krzaczki":) w rodzaju: %d, %4d, %u, %x, %6.1f i podobnie
wyglądające, które zostaną w wyjściowym komunikacie zastąpione
jakąś treścią. A czym ? W naszym przykładzie, w miejscu
pierwszego wystąpienia specyfikacji %d zostanie wstawiona wartość
trzeciego argumentu funkcji sprintf, czyli wartość zmiennej
't' wypisana w postaci liczby dziesiętnej. Tak samo następne

4 z 40

background image

występujące w formacie specyfikacje zostaną zmienione
wartościami kolejnych argumentów funkcji sprintf. Funkcją sprintf
nie ma ustalonej liczby argumentów, więc w formacie komunikatu
moŜna umieszczać dowolną ilość specyfikacji przekształceń. Na
temat funkcji o zmiennej liczbie argumentów napiszę jak się
nadarzy okazja.

Specyfikacje przekształcenia zaczynają się od znaku
procenta % i kończą znakiem przekształcenia. Na przykład: %d
-zastąpione zostanie liczbą całkowitą; %u-liczbą całkowitą bez
znaku; o-liczbą ósemkową; %x-liczbą szesnastkową; c-jednym
znakiem; $s-ciągiem znaków; %f-liczbą
niecałkowitą(zmiennoprzecinkową) w postaci: część całkowita,
kropka, część ułamkowa (np.: 12.345). Aby wypisać sam znak
procent % wstawia się dwa znaki procent %%. Następne przykłady
przekształceń: %6d -liczba całkowita zajmująca co najmniej 6
znaków (na przykład: [][][]123); %6.2f -liczba zmiennopozycyjna
o długości 6 znaków i z dwiema cyframi po przecinku (na przykład:
[]12.34)

char

 buf[

[

[

[

32

];

];

];

];

  

double

  l =

=

=

12.3456

;

;

;

;

  

int

  valve =

=

=

80

;

;

;

;

  

/* W tablicy 'buf' zostanie zapisany ciąg znaków:

  'Valve:    80%' */

  sprintf(

(

(

(buf,

,

,

,

"Valve%6d%%"

,

,

,

,valve);

);

);

);

  

/* W tablicy 'buf' zostanie zapisany ciąg znaków:

  'Length= 12.34' */

  sprintf(

(

(

(buf,

,

,

,

"Length=%6.2f"

,

,

,

,l);

);

);

);

Opisałem tu jedynie część moŜliwości funkcji sprintf(), po resztę
odsyłam do podręcznika języka C i do dokumentacji biblioteki
AVRLibc.

Ale uwaga, uŜycie w programie dla 8-bitowego mikrokontrolera
dość rozbudowanych funkcji sprintf, printf skutkuje znaczącym
wzrostem wielkości kodu wynikowego. W AVRLibC domyślnie, celem
oszczędzania pamięci, funkcje printf, sprintf nie obsługują liczb
zmiennoprzecinkowych. Aby włączyć obsługę liczb
zmiennoprzecinkowych uruchamiamy program MFile, wczytujemy
plik makefile naszego projektu i w menu "Makefile->printf()options"
zaznaczamy opcję "floating point".

5 z 40

background image

Okno programu MFile. Opcja "floating point" włącza dla funkcji printf obsługę zmiennych

zmiennopozycyjnych.

Kto wcześniej uczył się języka C pisząc programy na komputer PC,
ten na pewno pamięta funkcję printf, jej uŜywa się najczęściej aby
coś napisać na ekranie tekstowym. Funkcja printf róŜni się od
omawianej wcześniej sprintf tym, Ŝe wysyła formatowany
komunikat do tzw. standardowego sterumienia wyjściowego
(stdout); funkcja sprintf zapisuje wynik do tablicy znakowej.

Funkcja printf wysyła dane do standardowego wyjścia. Domyślnie na komputerze PC

standardowe wyście to ekran tekstowy.

W chwili uruchomienia programu tworzone są strumienie danych:
standardowe wejście(stdin), standardowe wyjście(stdout) i
standardowe wyjście dla komunikatów o błędach(stderr) W
przypadku programów uruchamianych na komputerze PC
standardowy strumień wyjściowy kierowany jest domyślnie na

6 z 40

background image

ekran monitora, ale moŜe być teŜ przekierowany do innego
urządzenia jak drukarka lub plikiem na dysku; standardowe wejście
domyślnie połączone jest z klawiaturą komputera PC. W przypadku
programów uruchamianych na mikrokontrolerze, gdy brak monitora
i klawiatury, moŜna powiązać standardowe strumienie danych
(stdin i stdout) z portem szeregowym uC. Praktycznie wszystkie
mikrokontrolery posiadają wbudowane układy transmisji
szeregowej. MoŜna podłączyć przewodem uC z komputerem PC
poprzez port szeregowy RS232C i komunikować się z 
mikrokontrolerem uŜywając monitora i klawiatury.

W stałych znakowych i napisowych moŜna umieszczać tzw.
sekwencje specjalne. Sekwencje specjalne zaczynają się od znaku
backslash "\", przykładowo sekwencja "\n" to znak nowego wiersza
(LF Line Feed, kod ASCII 0x0A). Jeśli tekst składa się z wielu
wierszy, to kaŜdy wiersz zakończony jest znakiem nowego wiersza.

W przykładzie poniŜej uruchomiłem programik, który instrukcją
printf wypisuje trzy linijki tekstu. Dalej wypisany tekst pokazany
jest w postać kodów ASCII. Czerwonym kolorem podkreśliłem
sekwencje specjalne \n w tekście i odpowiadające im kody ASCII
0x0A.

Znak nowego wiersza '\n' kodowany jest bajtem o wartości 0x0A. Kliknij w obrazek, Ŝeby

obejrzeć całość

Programik ten został skompilowany i uruchomiony w systemie
Linux, ale jeśli ten sam programik skompilujemy w produkcie o
nazwie "Windows", to znak nowej linii \n kodowany będzie z
uŜyciem dwóch bajtów: 0x0D,0x0A.

7 z 40

background image

W Windowsie znak nowego wiersza '\n' kodowany jest z uŜyciem dwóch bajtów: 0x0D,

0x0A. Kliknij w obrazek, Ŝeby obejrzeć całość

Preprocesor języka C

Nie naleŜ mylić poleceń preprocesora z instrukcjami programu,
polecenia preprocesora zaczynają się znakiem hash "#".
Preprocesor przystępuje do działania jeszcze przed właściwą
kompilacją i automatycznie edytuje tekst źródłowy programu.
Preprocesor potrafi wykonywać kilka rodzajów prostych ale
uŜytecznych operacji na tekście. W programach najczęściej moŜna
spotkać polecenia #include i #define. Polecenie #include, w miejscu
jego wystąpienia, wkleja zawartość innego wskazanego pliku
tekstowego. JeŜeli nazwa dołączanego pliku jest objęta parą
cudzysłowów, wtedy plik poszukiwany jest w katalogu projektu.

#include "plik.h"

JeŜeli nazwa wklejanego pliku objęta jest parą znaków <>, wtedy
plik poszukiwany jest w katalogu, gdzie znajdują się standardowe
pliki nagłówkowe.

#include <stdio.h>

Zwykle nazwy plików dołączanym poleceniem #include posiadają
rozszerzenie .h i nazywane są plikami nagłówkowymi. A co
zawierają dołączane pliki? Mogą zawierać dowolny kod w języku C,
zwykle zawierają deklaracje funkcji i róŜne makrodefinicje.

W najprostszym sposobie uŜycia polecenie #define (makrodefinicja)
zastępuje w tekście źródłowym programu kaŜde wystąpienie
wskazanej nazwy na inny podany ciąg znaków - podobnie jak działa
opcja "Zmień" typowego edytora tekstu.

#define NAZWA  zastępujący ciąg znaków

Zastępujący ciąg znaków rozciąga się do końca linii, aby go
kontynuować w kolejnych liniach tekstu, naleŜy kaŜdą przedłuŜaną
linię zakończyć znakiem backslash \ .

#define NAZWA  zastępujący\
ciąg znaków

8 z 40

background image

Polecenie #define działa od miejsca wystąpienia do końca pliku albo
do wystąpienia polecenia #undef NAZWA, a zawartość stałych
napisowych jest przy zastępowaniu pomijana. Przyjęło się, Ŝe
nazwy w makrodefinicji pisane są wielkimi literami aby się
odróŜniały od zmiennych i stałych programu.

Dla pokazania jak działa polecenie #define skompilowałem niewielki
programik z opcją kompilatora -E. Przy wywołaniu kompilatora GCC
z opcją -E, proces tłumaczenia kodu źródłowego programu
zatrzymuje się po przejściu preprocesora i w wyniku otrzymujemy
plik z tekstem programu przetworzonym tylko przez preprocesor.
Warto zapamiętać tę opcję, moŜe się przydać przy szukaniu błędów.

Efekt działania polecenia #define. Programik w pliku zabawa.c skompilowałem z opcją

kompilatora -E. W wyniku kompilator GCC zwrócił plik zabawa.txt zawierający tekst

źródłowy programu przetworzony jedynie przez preprocesor.

A teraz praktyczny przykład wykorzystania makrodefinicji.
Przypuśćmy, Ŝe jest dioda LED, która ma coś sygnalizować,
przyłączona do jednego z portów we/wy AVRa. A po całym
programie rozsiane są instrukcje włączające lub wyłączające diodę
LED w rodzaju:

PORTD |= (1<<PD0);

PORTD &= ~(1<<PD0);

W instrukcjach tych bezpośrednio wskazano nazwę portu i numer
bitu. Taki sposób pisania programu nie jest dobry. Bo jeŜeli
zdecydujemy się na zmiany w projekcie i na nowym schemacie
dioda LED będzie przyłączona do innego wyprowadzenia niŜ
poprzednio, to wtedy trzeba będzie w całym programie wszystkie
instrukcje sterujące diodą LED odszukać i zmodyfikować. Lepiej
odrazu pisać programy w taki sposób, aby w przyszłości, przy
zmianie schematu, modyfikacja programu nie nastręczała wielkich
problemów. I właśnie do tego celu moŜe się przydać
prepreprocesor. W przykładowym programie poniŜej moŜna
wskazać port, do którego przyłączono diodę LED, edytując

9 z 40

background image

makrodefinicje na początku programu. Poleceniem #define
zdefiniowany trzy nazwy: SET_OUT_LED, SET_LED, CLR_LED, które
preprocesor zastąpi odpowiednim kodem. Nazwy te mogą być
uŜywane jak instrukcje programu, SET_LED włącza diodę LED,
CLR_LED - wyłącza, SET_OUT_LED ustawia port, do którego
przyłączono diodę LED, jako wyjście.

#include <avr/io.h>

/* Początkowo diodę LED przyłączono do
wyprowadzenia PD0 */
/*
#define SET_OUT_LED  DDRD  |=  (1<<PD0)
#define SET_LED      PORTD |=  (1<<PD0)
#define CLR_LED      PORTD &= ~(1<<PD0)
*/

/* Schemat się zmienił, aktualnie dioda LED
przyłączona jest do wyprowadzenia PB3 */

#define SET_OUT_LED  DDRB  |=  (1<<PB3)
#define SET_LED      PORTB |=  (1<<PB3)
#define CLR_LED      PORTB &= ~(1<<PB3)

int

 main(

(

(

(

void

)

)

)

)

{

{

{

{

  

/* Ustawia PB3 jako wyjście */

  

/* Preprocesor zastąpi SET_OUT_LED instrukcją

  DDRB |= (1<<PB3) */

  SET_OUT_LED;

  

/* Jakiś kawałek kodu */

  
  

/* Zapala diodę LED */

  

/* Preprocesor zastąpi SET_LED instrukcją

  PORTB |= (1<<PB3) */

  SET_LED;

  

/* Jakiś kawałek kodu */

  
  

/* Gasi diodę LED */

  

/* Preprocesor zastąpi CLR_LED instrukcją

  PORTB &= ~(1<<PB3) */

  CLR_LED;

  

/* Dalsze instrukcje  programu */

Istnieje moŜliwość tworzenia makrodefinicji z argumentami. Listę
argumentów makra umieszcza się między parą nawiasów okrągłych
(), podobnie jak w definicji funkcji. W przykładzie poniŜej
utworzone zostało uŜyteczne makro _BV(numer_bitu). W tekście
programu kaŜde wystąpienie _BV(numer_bitu) preprocesor zastąpi
wartością (1<<numer_bitu), czyli jedynką przesuniętą w lewo
o ilość pozycji podaną jako argument makra.

#include <avr/io.h>

/* Makrodefinicja z argumentem */

#define  _BV(bit) (1 << (bit))

int

 main(

(

(

(

void

)

)

)

)

{

{

{

{

  

/* Preprocesor zastąpi _BV(3) wyraŜeniem (1 << (3))*/

  PORTB |= _BV(3);

Nie ma potrzeby samemu definiować _BV(bit), jest juŜ takie makro
zdefiniowane w jednym z plików dołączanych poleceniem:

10 z 40

background image

#include <avr/io.h>

Z pomocą instrukcji preprocesora moŜna wskazać kompilatorowi,
które fragmenty kodu programu mają być wzięte pod uwagę
w procesie kompilacje, a które fragmenty kodu mają być pominięte.
Nazywa się to kompilacją warunkową i uŜywane są do tego celu
polecenia: #if, #elif, #else, #endif oraz #ifdef, #ifndef. Z poleceń
tych tworzy się konstrukcje w rodzaju:

#if WARUNEK_1

/* Fragment kodu włączany do porgramu
jeśli spełniony jest WARUNEK_1*/

#elif WARUNEK_2

/* Fragment kodu włączany do porgramu
jeśli spełniony jest WARUNEK_2*/

#else

/* Fragment kodu włączany do programu jeśli
Ŝaden w warunków nie został spełniony*/

#endif

Gdzie #elif i #else nie muszą wystąpić. I tak, jeśli spełniony jest
warunek stojący zaraz po #if lub #elif, wtedy następne linie kodu,
aŜ do wystąpienia #endif, #elif lub #else, są włączane do
programu. Jeśli Ŝaden z warunków nie jest spełniony, wtedy częścią
programu staje się fragment kodu między #else i #endif. W
przykładzie poniŜej zdefiniowano nazwę F_CPU z przypisaną
częstotliwość pracy mikrokontrolera; zaleŜnie od częstotliwości
preprocesor włącza do programu jeden z trzech fragmentów kodu.

//#define F_CPU 1000000UL

#define F_CPU 4000000UL

int

 main()

()

()

()

{

{

{

{

#if F_CPU <= 1000000

/* Fragment kodu włączany do programu
jeŜeli  F_CPU <= 1MHz */

#elif 1000000 < F_CPU && F_CPU <= 8000000

/* Fragment kodu włączany do programu
jeŜeli 1MHz < F_CPU <= 8MHz */

#else

/* Fragment kodu włączany do programu
jeŜeli F_CPU > 8MHz*/

#endif

W warunku po #if i #elif moŜna wstawiać wyraŜenie
defined(NAZWA), wyraŜenie to przyjmuje wartość logiczną
PRAWDA, jeŜeli dana nazwa została wcześnie zdefiniowana
poleceniem #define; w przeciwnym przypadku wyraŜenie to posiada
wartość logiczną FAŁSZ.

#define MICRO

11 z 40

background image

int

main()

()

()

()

{

{

{

{

#if defined(MICRO)

/* 
  Ten fragment kodu zostanie włączony do programu,
  bo  wcześniej zdefiniowano nazwę MICRO
*/

#else

/* 
   Ten fragment kodu byłby włączony do programu,
   gdyby nie zdefiniowano nazwy MICRO
*/

#endif

Istnieją takŜe polecenia #ifdef i #ifndef. Kod występujący po #ifdef
NAZWA jest włączany do programu, jeŜeli wcześniej zdefiniowano
nazwę poleceniem #define NAZWA. Natomiast kod #ifndef NAZWA
jest włączany do programu, jeŜeli nie zdefiniowano wcześniej
nazwy poleceniem #define NAZWA. Polecenie #if z warunkiem
defined(NAZWA) moŜna zastąpić #ifdef NAZWA.

Aby zapobiec sytuacji, Ŝe jakiś plik mógłby być włączony
poleceniem #include wielokrotnie, treść dołączanego pliku
umieszcza się między parą poleceń #ifndef i #endif.

/* Zawartość  przykładowego pliku dołączanego 
   poleceniem #include "plik.h"*/

/* JeŜeli wcześniej nazwa PLIK  została zdefiniowana 
   poleceniem #define PLIK, to dalsza część pliku nie
   zostanie włączona do programu */

#ifndef  PLIK

/* Definicja nazwy PLIK, aby zapobiec 
   wielokrotnemu dołączaniu plik.h */

#define PLIK

/* Deklaracje funkcji, makrodefinicje itp.*/

#endif

/* KONIEC PLIKU */

Podział kodu źródłowego programu na osobno kompilowane pliki

Dotychczas, we wszytkich programikach naszego kursu, całość kodu
źródłowego umieszczana była w jednym pliku, jak w poniŜszym
przykładzie.

//-----------------------------------------------------------
//  plik "main.c" 
//----------------------------------------------------------- 

#include <avr/io.h> 

/* Definicje zmiennych globalnych */

char

  tytul[]=

[]=

[]=

[]=

"KURS AVR-GCC, cz.5"

;

;

;

;

int

   czesc =

=

=

5

;

;

;

;

/* Definicje kilku funkcji */

void

 funkcja_1(

(

(

(

int

  a,

,

,

int

 b)

)

)

{

{

{

{
}

}

}

}

int

 funkcja_2(

(

(

(

void

)

)

)

)

{

{

{

{
    funkcja_4(

(

(

(tytul);

);

);

);

    

12 z 40

background image

    funkcja_1(

(

(

(czesc,

,

,

1

);

);

);

);

}

}

}

double

 funkcja_3(

(

(

(

double

 x)

)

)

{

{

{

{  
    funkcja_1(

(

(

(

2

,

,

,

6

);

);

);

); 

    funkcja_2();

();

();

();

}

}

}

char

 funkcja_4(

(

(

(

char

 s[])

[])

[])

[]) 

{

{

{


}

}

}

}

/* Główna funkcja programu */

 

int

 main(

(

(

(

void

)

)

)

)

{

{

{

{  
  funkcja_1(

(

(

(czesc,

,

,

6

);

);

);

);  

  funkcja_2();

();

();

();  

  funkcja_3(

(

(

(

3.14

);

);

);

);

  funkcja_4(

(

(

(tytul);

);

);

);

}

}

}

}

Ale istnieje moŜliwość podziału kodu źródłowego programu na
mniejsze fragmenty umieszczane w osobno kompilowanych plikach.
Przykładowo moŜemy podzielić nasz programik w następujący
sposób: funkcja_1 i funkcja_2 do pliku "file1.c"; funkcja_3
i funkcja_4 do pliku "file2.c"; zmienne globalne i funkcja main do
pliku "main.c"

//-----------------------------------------------------------
// plik "file1.c" 
//-----------------------------------------------------------

#include <avr/io.h> 

/* Deklaracje zmiennych i funkcji zdefiniowanych poza 
plikiem "file1.c" */

extern

 

char

 tytul[];

[];

[];

[];

extern

 

int

 czesc;

;

;

;

char

   funkcja_4(

(

(

(

char

 s[]);

[]);

[]);

[]); 

void

 funkcja_1(

(

(

(

int

  a,

,

,

int

 b)

)

)

{

{

{

{
}

}

}

}

int

 funkcja_2(

(

(

(

void

)

)

)

)

{

{

{

{
    funkcja_4(

(

(

(tytul);

);

);

);

    
    funkcja_1(

(

(

(czesc,

,

,

,

1

);

);

);

);

}

}

}

//-----------------------------------------------------------
// plik "file2.c" 
//-----------------------------------------------------------

/* Deklaracje funkcji zdefiniowanych poza plikiem "file2.c" */

void

   funkcja_1(

(

(

(

int

  ,

,

,

int

 );

);

);

); 

int

    funkcja_2(

(

(

(

void

);

);

);

);

double

 funkcja_3(

(

(

(

double

 x)

)

)

{

{

{

{  
    funkcja_1(

(

(

(

2

,

,

,

,

6

);

);

);

); 

    funkcja_2();

();

();

();

}

}

}

char

 funkcja_4(

(

(

(

char

 s[])

[])

[])

[]) 

13 z 40

background image

{

{

{


    

return

 

0

;

;

;

;

}

}

}

}

//-----------------------------------------------------------
// plik "main.c" 
//-----------------------------------------------------------

#include <avr/io.h> 

/* Definicje zmiennych globalnych */

char

  tytul[]=

[]=

[]=

[]=

"KURS AVR-GCC, cz.5"

;

;

;

;

int

   czesc =

=

=

5

;

;

;

;

/* Deklaracje funkcji zdefiniowanych poza plikem "main.c" */

void

   funkcja_1(

(

(

(

int

  ,

,

,

int

 );

);

);

); 

int

    funkcja_2(

(

(

(

void

);

);

);

); 

double

 funkcja_3(

(

(

(

double

);

);

);

); 

char

   funkcja_4(

(

(

(

char

 []);

[]);

[]);

[]); 

/* Główna funkcja programu */

 

int

 main(

(

(

(

void

)

)

)

)

{

{

{

{  
  funkcja_1(

(

(

(czesc,

,

,

,

6

);

);

);

);  

  funkcja_2();

();

();

();  

  funkcja_3(

(

(

(

3.14

);

);

);

);

  funkcja_4(

(

(

(tytul);

);

);

);

}

}

}

}

Aby mieć moŜliwość uŜycia funkcji zdefiniowanej w innym pliku,
naleŜy gdzieś wcześniej w programie umieścić deklarację tej
funkcji. Deklaracja funkcji wygląda prawie tak samo jak pierwsza
linia definicji funkcji zakończona średnikiem.

typ nazwa_funkcja(

(

(

( typ_arg_1, typ_arg_2, ... );

);

);

);

Przypominam, Ŝe definicja oznacza tworzenie funkcji(zmiennej),
natomiast deklaracja jedynie informuje kompilator jakiego typu
wartość funkcja zwraca i jakich oczekuje argumentów. Zatem
definicja funkcji jest jednocześnie jej deklaracją, ale deklaracja nie
definiuje(tworzy) funkcji. I jak wcześniej pisałem, Ŝeby mieć
moŜliwość uŜycia funkcji zdefiniowanej(utworzonej) w oddzielnie
kompilowanym pliku, naleŜy wcześniej przed uŜyciem tę funkcje
zdeklarować - właśnie, Ŝeby poinformować kompilator jakiego typu
wartość funkcja zwraca i jakich oczekuje argumentów.

Zmienne mogą być definiowane wewnątrz funkcji(zmienne lokalne)
albo poza wszystkimi funkcjami(zmienne globalne), o tym pisałem
w poprzedniej części kursu. Zmienne definiowane wewnątrz funkcji
tworzone są w chwili wywołania funkcji i przestają istnieć w
 momencie powrotu z funkcji, przechowywane dane są tracone.
Przy kaŜdym wywołaniu funkcji zmienne tworzone są od nowa. Jeśli
zaleŜy nam Ŝeby zmienna deklarowane w funkcji istniała przez cały
okres działania programu i dane w zmiennej nie były tracone po
wyjściu z funkcji, to naleŜy deklaracje zmiennej poprzedzić
słówkiem static; takie zmienne nazywa się statycznymi. Zmienne
deklarowane na początku pliku, poza wszystkimi funkcjami pliku
istnieją przez cały czas działania programu i są dostępne we
wszystkich funkcjach w pliku. Aby mieć dostęp do zmiennej
globalnej zdefiniowanej w osobno kompilowanym pliku, naleŜy
przed uŜyciem wcześniej tę zmienną zdeklarować; dodatkowo przed
taką deklaracją naleŜy wstawić słówko extern.

14 z 40

background image

extern typ_zmiennej nazwa_zmiennej;

;

;

;

Z kolei Ŝeby ograniczyć zasięg widoczności takiej zmiennej jedynie
do pliku, w którym została zdefiniowana, poprzeda się deklaracje
zmienej słówkiem static.

static typ_zmiennej nazwa_zmiennej;

;

;

;

Podobnie jeśli definicję funkcji poprzedzimy słówkiem static, funkcja
ta będzie dostępna tylko w tym jednym pliku, w którym została
zdefiniowana

Zwykle deklaracje funkcji umieszcza się w osobnych plikach, tzw.
plikach nagłówkowych z rozszerzeniem .h I wtedy, aby móc
wykorzystać funkcję zdefiniowaną w innym pliku, dołącza się plik
nagłówkowy zawierający definicję tej funkcji, wstawiając gdzieś na
początku instrukcję preprocesora #include "nazwa.h"

//----------------------------------------------------------
// plik "file1.c" 
//----------------------------------------------------------

#include <avr/io.h>

/* Dołącza plik  zawierający  deklaracje  funkcji i zmiennych 
zdefiniowanych poza plikiem "file1.c" */

#include "rozne.h" 

void

 funkcja_1(

(

(

(

int

  a,

,

,

int

 b)

)

)

{

{

{

{
}

}

}

}

int

 funkcja_2(

(

(

(

void

)

)

)

)

{

{

{

{
    funkcja_4(

(

(

(tytul);

);

);

);

    
    funkcja_1(

(

(

(czesc,

,

,

,

1

);

);

);

);

}

}

}

//----------------------------------------------------------
// plik "file2.c" 
//----------------------------------------------------------

/* Dołącza plik  zawierający  deklaracje  funkcji i zmiennych 
zdefiniowanych poza plikiem "file2.c" */

#include "rozne.h" 

double

 funkcja_3(

(

(

(

double

 x)

)

)

{

{

{

{  
    funkcja_1(

(

(

(

2

,

,

,

,

6

);

);

);

); 

    funkcja_2();

();

();

();

     

return

 

0

;

;

;

;    

}

}

}

char

 funkcja_4(

(

(

(

char

 s[])

[])

[])

[]) 

{

{

{


}

}

}

//----------------------------------------------------------
// plik "rozne.h" 
//----------------------------------------------------------
// Deklaracje kilku funkcji i zmniennych

/*  PoniŜsze polecenia preprocesora zapobiegną przypadkowi,
w którym "rozne.h" mógłby być kilkakrotnie włączony
do jednedo pliku */

#ifndef ROZNE_FUNKCJE
#define ROZNE_FUNKCJE

15 z 40

background image

extern

 

char

  tytul[];

[];

[];

[];

extern

 

int

     czesc;

;

;

;

void

     funkcja_1(

(

(

(

int

,

,

,

int

);

);

);

); 

int

       funkcja_2(

(

(

(

void

);

);

);

); 

double

 funkcja_3(

(

(

(

double

);

);

);

); 

char

    funkcja_4(

(

(

(

char

 []);

[]);

[]);

[]); 

#endif

//---------------------------------------------------------
// plik "main.c" 
//---------------------------------------------------------

#include <avr/io.h> 

/* Dołącza plik  zawierający  deklaracje  funkcji i zmiennych 
zdefiniowanych poza plikiem "main.c" */

#include "rozne.h" 

/* Definicje zmiennych globalnych */

char

  tytul[]=

[]=

[]=

[]=

"KURS AVR-GCC, cz.5"

;

;

;

;

int

   czesc =

=

=

5

;

;

;

;

/* Definicje funkcji */

static void

 funkcja(

(

(

(

void

)

)

)

)

{

{

{

{
}

}

}

}

/* Główna funkcja programu */

 

int

 main(

(

(

(

void

)

)

)

)

{

{

{

{  
  funkcja_1(

(

(

(czesc,

,

,

,

6

);

);

);

);  

  funkcja_2();

();

();

();  

  funkcja_3(

(

(

(

3.14

);

);

);

);

  funkcja_4(

(

(

(tytul);

);

);

);

}

}

}

}

Aby skompilować nasz pokrojony programik, potrzebujemy
utworzyć w katalogu projektu odpowiedni plik makefile. Robimy to
w podobny sposób, jak poprzednio, z pomocą programu MFile.
Wpierw klikamy w menu opcję Makfile->Main file name.. i w 
okienku które się pojawi wpisujemy nazwę pliku "main" (wpisujemy
nazwę pliku bez rozszerzenia .c) Dodatkowo naleŜy wybrać z menu
opcję Makefile->C/C++source files(s) i w okienku wpisać nazwy
plików "file1.c" i "file2.c" (tym razem naleŜy wpisać nazwy plików
wraz z rozszerzeniami .c)

16 z 40

background image

Okno programu MFile. Wpisujemy nazyw wszytkich plków *.c projektu. Kliknij w obrazek,

Ŝeby zobaczyć całość.

Przyjrzyjmy się teraz przebiegowi kompilacji naszego pokrojonego
programiku. Program make wywołuje kompilator avr-gcc cztery
razy. Wpierw wszystkie pliki projektu z rozszerzeniem .c (main.c,
file1.c, file2.c) są pojedynczo kompilowane, w  wyniku powstają
trzy plik pośrednie: main.o, file1.o, file2.o Za czwartym razem
avr-gcc uruchamiany jest Ŝeby połączyć wymienione pliki pośrednie
w całość; tworzony jest plik wynikowy main.elf Na koniec
uruchamiany jest program narzędziowy avr-objdump, który tworzy
z pliku wynikowego main.elf pliki dla programatora main.hex
i main.epp. Warto zauwaŜyć, Ŝe przy kolejnej próbie kompilacji
projektu program make kompiluje jedynie te plik .c, które zostały
w międzyczasie edytowanie.

Etapy kompilacja projekty złoŜnego z kilku plików *.c Kliknij w obrazek, Ŝeby obejrzeć

całość.

17 z 40

background image

Wyświetlacz alfanumeryczny LCD.

Dla uruchamiania przykładowych programików będzie potrzebny
wyświetlacz alfanumeryczny LCD, moduł z układem HD44780. Ale
bez obaw, takie wyświetlacze są łatwo dostępne i  nie kosztują
drogo. Ja przyłączyłem do AVRa moduł widoczny na fotografii
poniŜej, na którym moŜna wyświetlić dwie linie tekstu po
szesnaście znaków, moduły 2X16 są chyba najczęściej spotykane.
Do tego celu moŜna równieŜ wykorzystać wyświetlacze
o dwudziestu lub czterdziestu znakach w linii, lecz w takim
przypadku pewnie potrzebne będą drobne modyfikacje w kodzie
przykładów.

Moduł wyświetlacza LCD 2X16 wykorzystany przy uruchomieniu przykładów.

W kursie nie będę szczegółowo tłumaczy jak programować tego
typu wyświetlacz, jest to temat na osobny artykuł, który
w przyszłości napiszę i umieszczę na stronie w dziale artykuły.
W zamian przygotowałem zestaw gotowych funkcji do obsługi
wyświetlaczy LCD ze sterownikiem HD44780. W przykładach,
w których będzie wykorzystywany wyświetlacz, trzeba będzie
skopiować do katalogu projektu plik: hd44780.c, hd44780.h Plik
hd44780.c zawiera definicje funkcji do obsługi wyświetlaczy, zaś
w pliku hd44780.h znajdują się deklaracje tych funkcji,
makrodefinicje przypisujące sygnały wyświetlacza do wybranych
wyprowadzeń AVRa oraz kilka przydatnych makroinstrukcji. Dalej w
tekści, przy opisie uruchamianych przykładów, objśnię jak tych
funkcji uŜywać.

PoniŜej na schemacie pokazane jest w jaki sposób przyłączyłem
wyświetlacz do AVRa atmega16.

18 z 40

background image

Schemat 5.1 Schemat przyłączenia wyświetlacza LCD(hd44780) do AVRa ATMEGA16.

Dla sterowania wyświetlaczem potrzebne jest siedem linii we/wy
mikrokontrolera: trzy linie sterujące: RS, RW, E i cztery linie
danych D4,D5,D5,D7. Będziemy programować wyświetlacz w trybie
4-bitowym (tylko cztery linie danych), wyprowadzenia wyświetlacza
D0, D1, D2 ,D3 nie będą wykorzystywane. Ja przyłączyłem
wyświetlacz do portów PA0..PA6 uC atmega16, ale nic nie stoi na
przeszkodzie aby wykorzystać dowolne siedem linii we/wy AVRa.
W tym celu naleŜy zmodyfikować makrodefinicje w poniŜszym
fragmencie pliku hd44780.h

/* RS */

#define SET_OUT_LCD_RS  DDRA  |=  _BV(PA0)
#define SET_LCD_RS      PORTA |=  _BV(PA0)
#define CLR_LCD_RS      PORTA &= ~_BV(PA0)

/* RW */

#define SET_OUT_LCD_RW  DDRA  |=  _BV(PA1)
#define SET_LCD_RW      PORTA |=  _BV(PA1)
#define CLR_LCD_RW      PORTA &= ~_BV(PA1)

/* E */

#define SET_OUT_LCD_E   DDRA  |=  _BV(PA2)
#define SET_LCD_E       PORTA |=  _BV(PA2)
#define CLR_LCD_E       PORTA &= ~_BV(PA2)

/* D4 */

#define SET_OUT_LCD_D4  DDRA  |=  _BV(PA3)
#define SET_IN_LCD_D4   DDRA  &= ~_BV(PA3)
#define SET_LCD_D4      PORTA |=  _BV(PA3)
#define CLR_LCD_D4      PORTA &= ~_BV(PA3)
#define IS_SET_LCD_D4   PINA  &   _BV(PA3)

/* D5 */

#define SET_OUT_LCD_D5  DDRA  |=  _BV(PA4)
#define SET_IN_LCD_D5   DDRA  &= ~_BV(PA4)
#define SET_LCD_D5      PORTA |=  _BV(PA4)
#define CLR_LCD_D5      PORTA &= ~_BV(PA4)
#define IS_SET_LCD_D5   PINA  &   _BV(PA4)

/* D6 */

#define SET_OUT_LCD_D6  DDRA  |=  _BV(PA5)
#define SET_IN_LCD_D6   DDRA  &= ~_BV(PA5)
#define SET_LCD_D6      PORTA |=  _BV(PA5)
#define CLR_LCD_D6      PORTA &= ~_BV(PA5)
#define IS_SET_LCD_D6   PINA  &   _BV(PA5)

/* D7 */

19 z 40

background image

#define SET_OUT_LCD_D7  DDRA  |=  _BV(PA6)
#define SET_IN_LCD_D7   DDRA  &= ~_BV(PA6)
#define SET_LCD_D7      PORTA |=  _BV(PA6)
#define CLR_LCD_D7      PORTA &= ~_BV(PA6)
#define IS_SET_LCD_D7   PINA  &   _BV(PA6)

Przykładowo, jeŜeli linia sygnału D7 wyświetlacza ma być przyłącza
do portu PD0 AVRa, wtedy naleŜy zmodyfikować w poniŜszym
fragmęcie pliku hd44780.h to, co zaznaczone jest na czerwono,
czyli nazwę portu (A,B,C,D) i numer bitu.

/* D7 */

#define SET_OUT_LCD_D7  DDR

D

  |=  _BV(

PD0

)

#define SET_IN_LCD_D7   DDR

D

  &= ~_BV(

PD0

)

#define SET_LCD_D7      PORT

D

 |=  _BV(

PD0

)

#define CLR_LCD_D7      PORT

D

 &= ~_BV(

PD0

)

#define IS_SET_LCD_D7   PIN

D

  &   _BV(

PD0

)

Termometr cyfrowy DS18B20

Obok wyświetlacza i przycisków przyłączymy do AVRa takŜe
termometr cyfrowy DS18B20. Wszystkie potrzebne informacje na
temat układu DS18B20 moŜna znaleźć w jego karcie katalogowej

ds18b20.pdf

Termometr cyfrowy ds18b20

Układ scalony DS18B20 jest czujnikiem cyfrowym z interfejsem
1-wire, mikrokontroler komunikuje się z DS18B20 wykorzystując
tylko jedną linię we/wy.

20 z 40

background image

Schemat 5.2 Schemat przyłączenia termometru cyfrowego ds18b20 do AVRa ATMEGA16.

W tej części kursu jeszcze nie będę tłumaczył jak działa magistrala
1-wire i jak programować układ ds18b20, w zamian przygotowałem
gotowy zestaw funkcji - minimum kodu do odczytu wartości
temperatury z ds18b20. W przykładach, w których będzie
wykorzystywany termometr ds18b20 naleŜy do katalogu projektu
skopiować pliki: ds18b20.h i ds18b20.c. Ja przyłączyłem układ
ds18b20 do wyprowadzenia PD7 AVRa atmega16. Ale moŜna
wykorzystać dowolny port AVRa, w tym celu naleŜy zmodyfikować
makrodefinicje w poniŜszym fragmencie pliku ds18b20.h Wystarczy
odpowiednio zmienić, zaznaczone kolorem czerwony, nazwę
portu(A,B,C,D) i numer bitu (0..7).

/* DS18B20 przyłączony do portu  PD7 AVRa  */

#define SET_ONEWIRE_PORT     PORT

D

  |=  _BV(

7

)

#define CLR_ONEWIRE_PORT     PORT

D

  &= ~_BV(

7

)

#define IS_SET_ONEWIRE_PIN   PIN

D

   &   _BV(

7

)

#define SET_OUT_ONEWIRE_DDR  DDR

D

   |=  _BV(

7

)

#define SET_IN_ONEWIRE_DDR   DDR

D

   &= ~_BV(

7

)

SprzęŜenie AVRa z komputerem PC poprzez port szeregowy

Kolejny schemat przedstawia sposób połączenia portu szeregowego
AVRa atmega16 z interfejsem RS232C komputera PC.

Schemat 5.3 Schemat połączenia portu szeregowego AVRa atmega16 z interfejsem rs232c

komputera PC. Kliknij w obrazek, Ŝeby powiększyć.

Potrzebne są: złącze DB-9 Ŝeńskie, przewód trójŜyłowy (około
60cm), układ scalony MAX232 i kilka kondensatorów jak na
schemacie. Do połączenia portu szeregowego mikrokontrolera
z portem szeregowym rs232 komputera PC konieczny jest
konwerter napięć RS232C<=>TTL - na przykład układ scalony
MAX232. Ja umieściłem MAX232 i współpracujące z nim

21 z 40

background image

kondensatory na osobnej płytce.

Przewód łączący AVRa z komputerem PC i na osobnej płytce układ MAX232 wraz ze

współpracującymi z nim kondensatorami.

Niektóre komputery, te nowsze, mogą nie posiadać portu rs232c.
Brak ten moŜna obejść stosując adapter, przejściówkę z USB na
RS232. Taka przejściówka kosztuje niewiele, a  moŜe być bardzo
uŜyteczna, szczególnie do laptoka :)

Przykłady do uruchomienia

Przygotowałem cztery przykładowe programiki do uruchamiania
jako ćwiczenia. Starałem się maksymalnie uprościć kod przykładów,
zgodnie z zasadą: dobry przykład to krótki przykład :) Ala zalecam
przed przystąpieniem do uruchamiania przykładów chociaŜ
przejrzeć artykuł, wtedy nikt nie powinien mieć trudności ze
zrozumieniem jak działają.

Przykład pierwszy - Powitanie

W pierwszym przykładzie program wypisuje na ekranie
wyświetlacza kilka słów powitania.

Animacja pokazuje efekt działania programu "Powitanie"

śeby skompilować program, naleŜy do katalogu projektu skopiować
trzy zamieszczone poniŜej pliki: main.c, hd44780.h, hd44780.c W

22 z 40

background image

pliku "main.c" znajduje się kod naszego przykładu.

/*
   Plik main.c

   KURS AVR-GCC cz.5
   Wyświetlacz alfanumeryczny LCD HD44780
   (schemat i opis działania w artykule)   
   
   układ atmega16 (1MHz)
*/

#include <avr/io.h>
#include <util/delay.h>

/* Wstawia w tym miejscu zawartość 
pliku hd44780.h*/

#include "hd44780.h"

int

 main(

(

(

(

void

)

)

)

)

{

{

{

{
    

/* Napisy przechowujemy w tablicach */

    

char

 str1[]

[]

[]

[] =

=

=

"KURS"

;

;

;

;

    

char

 str2[]

[]

[]

[] =

=

=

" AVR-GCC"

;

;

;

;

    

char

 str3[]

[]

[]

[] =

=

=

"cz.5"

;

;

;

;

    

/* Funkcja inicjalizuje wyświetlacz*/

    lcd_init();

();

();

();

    

/* Włącza wyświetlanie */

    LCD_DISPLAY(

(

(

(LCDDISPLAY);

);

);

);

    

while

(

(

(

(

1

)

)

)

)

    {

{

{

{    

        

/* Czyści cały ekran */

        LCD_CLEAR;

;

;

;            

        

/* Ustawia kursor w pozycji: 

        pierwszy wiersz, szósta kolumna */

        LCD_LOCATE(

(

(

(

5

,

,

,

,

0

);

);

);

);

        

/* Wysyła do wyświetlacza jeden znak*/

        LCD_WRITE_DATA(

(

(

(

'S'

);

);

);

);

        _delay_ms(

(

(

(

200

);

);

);

);

        LCD_WRITE_DATA(

(

(

(

'i'

);

);

);

);

        _delay_ms(

(

(

(

200

);

);

);

);

        LCD_WRITE_DATA(

(

(

(

'e'

);

);

);

);

        _delay_ms(

(

(

(

200

);

);

);

);

        LCD_WRITE_DATA(

(

(

(

'm'

);

);

);

);

        _delay_ms(

(

(

(

200

);

);

);

);    

        LCD_WRITE_DATA(

(

(

(

'k'

);

);

);

);

        _delay_ms(

(

(

(

200

);

);

);

);

        LCD_WRITE_DATA(

(

(

(

'a'

);

);

);

);

        _delay_ms(

(

(

(

200

);

);

);

);

        LCD_WRITE_DATA(

(

(

(

'!'

);

);

);

);

        _delay_ms(

(

(

(

2000

);

);

);

);    

        LCD_CLEAR;

;

;

;

        LCD_LOCATE(

(

(

(

2

,

,

,

,

0

);

);

);

);

        

/* Funkcja lcd_puts wysyła do 

        wyświetlacza ciąg  znaków */

        lcd_puts(

(

(

(str1);

);

);

);

        _delay_ms(

(

(

(

800

);

);

);

);    

        lcd_puts(

(

(

(str2);

);

);

);

        _delay_ms(

(

(

(

800

);

);

);

);    

23 z 40

background image

        LCD_LOCATE(

(

(

(

6

,

,

,

,

1

);

);

);

);

        lcd_puts(

(

(

(str3);

);

);

);    

        _delay_ms(

(

(

(

2000

);

);

);

);

        LCD_CLEAR;

;

;

        LCD_LOCATE(

(

(

(

1

,

,

,

,

0

);

);

);

);

        

/* Jako argumentu funkcji moŜna 

           wstawić stałą napisową */

        lcd_puts(

(

(

(

"Programy"

);

);

);

);

        _delay_ms(

(

(

(

800

);

);

);

);

        LCD_LOCATE(

(

(

(

4

,

,

,

,

1

);

);

);

);

        lcd_puts(

(

(

(

"z tekstem:)"

);

);

);

);

        
        

/* Czeka 2.5 sek. */

        _delay_ms(

(

(

(

2500

);

);

);

);    

        
    }

}

}

}

    

return

 

0

;

;

;

;

}

}

}

}

Listing 5.1 Powitanie

Plik hd44780.c zawiera zestaw funkcji do obsługi wyświetlacza.

/*
  Plik hd44780.c

  Definicje kilku funkcji do obsługi alfanumerycznego
  wyświetlacza LCD HD44780
*/

#include<avr/io.h>
#include<util/delay.h>
#include "hd44780.h"

/*--------------------------------------------------------*/
/* Zapis danej lub instrukcji */

void

 WriteToLCD (

(

(

(

unsigned

 

char

 v,

,

,

,

unsigned

 

char

  rs)

)

)

)

{

{

{

{
    

unsigned

 

char

 bf;

;

;

;

    SET_OUT_LCD_D4;

;

;

;

    SET_OUT_LCD_D5;

;

;

;

    SET_OUT_LCD_D6;

;

;

;

    SET_OUT_LCD_D7;

;

;

;

    

if

(

(

(

(v&

&

&

&

0x10

)

)

)

) SET_LCD_D4;

;

;

else

 CLR_LCD_D4;

;

;

;

    

if

(

(

(

(v&

&

&

&

0x20

)

)

)

) SET_LCD_D5;

;

;

else

 CLR_LCD_D5;

;

;

;

    

if

(

(

(

(v&

&

&

&

0x40

)

)

)

) SET_LCD_D6;

;

;

else

 CLR_LCD_D6;

;

;

;

    

if

(

(

(

(v&

&

&

&

0x80

)

)

)

) SET_LCD_D7;

;

;

else

 CLR_LCD_D7;

;

;

;

 
    CLR_LCD_E;

;

;

;

    

if

(

(

(

(rs)

)

)

) SET_LCD_RS;

;

;

;

else

 CLR_LCD_RS;

;

;

;

    CLR_LCD_RW;

;

;

;

    LCD_NOP;

;

;

;

    SET_LCD_E;

;

;

;

    LCD_NOP;

;

;

    CLR_LCD_E;

;

;

;

    LCD_NOP;

;

;

;

 
    

if

(

(

(

(v&

&

&

&

0x01

)

)

)

) SET_LCD_D4;

;

;

else

 CLR_LCD_D4;

;

;

;

    

if

(

(

(

(v&

&

&

&

0x02

)

)

)

) SET_LCD_D5;

;

;

else

 CLR_LCD_D5;

;

;

;

    

if

(

(

(

(v&

&

&

&

0x04

)

)

)

) SET_LCD_D6;

;

;

else

 CLR_LCD_D6;

;

;

;

    

if

(

(

(

(v&

&

&

&

0x08

)

)

)

) SET_LCD_D7;

;

;

else

 CLR_LCD_D7;

;

;

;

 
    LCD_NOP;

;

;

;

    SET_LCD_E;

;

;

;

    LCD_NOP;

;

;

24 z 40

background image

    CLR_LCD_E;

;

;

;

    LCD_NOP;

;

;

;

 
    SET_IN_LCD_D4;

;

;

;

    SET_IN_LCD_D5;

;

;

;

    SET_IN_LCD_D6;

;

;

;

    SET_IN_LCD_D7;

;

;

;

    CLR_LCD_RS;

;

;

;

    SET_LCD_RW;

;

;

;

    SET_LCD_D7;

;

;

;

/* Przydałby się pełny odczyt */

    

do

    {

{

{

{

        LCD_NOP;

;

;

;

        SET_LCD_E;

;

;

;

        LCD_NOP;

;

;

;

        bf =

=

=

= IS_SET_LCD_D7;

;

;

;

        CLR_LCD_E;

;

;

;

        LCD_NOP;

;

;

;

        SET_LCD_E;

;

;

;

        LCD_NOP;

;

;

;

        LCD_NOP;

;

;

;

        CLR_LCD_E;

;

;

;

        
    }

}

}

}

while

(

(

(

( bf );

);

);

);

}

}

}

}

/*--------------------------------------------------------*/
/* Funkcja odczytuje adres i flage zajetosci */

unsigned

 

char

 ReadAddressLCD (

(

(

void

)

)

)

)

{

{

{

{
    

unsigned

 

char

 g =

=

=

0

 ;

;

;

;

    CLR_LCD_RS;

;

;

;

    SET_LCD_RW;

;

;

    SET_IN_LCD_D4;

;

;

;

    SET_IN_LCD_D5;

;

;

;

    SET_IN_LCD_D6;

;

;

;

    SET_IN_LCD_D7;

;

;

;

    LCD_NOP;

;

;

;

    SET_LCD_E;

;

;

;

    LCD_NOP;

;

;

;

    

if

(

(

(

(IS_SET_LCD_D4)

)

)

) g+=

+=

+=

+=

16

;

;

;

;

    

if

(

(

(

(IS_SET_LCD_D4)

)

)

) g+=

+=

+=

+=

32

;

;

;

;

    

if

(

(

(

(IS_SET_LCD_D4)

)

)

) g+=

+=

+=

+=

64

;

;

;

;

    

if

(

(

(

(IS_SET_LCD_D4)

)

)

) g+=

+=

+=

+=

128

;

;

;

;

 
    CLR_LCD_E;

;

;

;

    LCD_NOP;

;

;

;

    SET_LCD_E;

;

;

;  

    LCD_NOP;

;

;

;

  
    

if

(

(

(

(IS_SET_LCD_D4)

)

)

) g+=

+=

+=

+=

8

;

;

;

;

    

if

(

(

(

(IS_SET_LCD_D4)

)

)

) g+=

+=

+=

+=

4

;

;

;

;

    

if

(

(

(

(IS_SET_LCD_D4)

)

)

) g+=

+=

+=

+=

2

;

;

;

;

    

if

(

(

(

(IS_SET_LCD_D4)

)

)

) g+=

+=

+=

+=

1

;

;

;

;

  
    CLR_LCD_E;

;

;

    

return

  g ;

;

;

;

}

}

}

}

/*---------------------------------------------------------*/
/* Inicjalizacja wyświetlacza */

void

 lcd_init(

(

(

(

void

)

)

)

)

25 z 40

background image

{

{

{

{
    _delay_ms(

(

(

(

15

);

);

);

);    

   
    SET_OUT_LCD_RS;

;

;

;

    SET_OUT_LCD_RW;

;

;

;

    SET_OUT_LCD_E;

;

;

;

    SET_OUT_LCD_D4;

;

;

;

    SET_OUT_LCD_D5;

;

;

;

    SET_OUT_LCD_D6;

;

;

;

    SET_OUT_LCD_D7;

;

;

;

    CLR_LCD_E;

;

;

;

    CLR_LCD_RS;

;

;

;

    CLR_LCD_RW;

;

;

;

    SET_LCD_D4;

;

;

;

    SET_LCD_D5;

;

;

;

    CLR_LCD_D6;

;

;

;

    CLR_LCD_D7;

;

;

;        

  
    LCD_NOP;

;

;

;

    SET_LCD_E;

;

;

;

    LCD_NOP;

;

;

    CLR_LCD_E;

;

;

;

    LCD_NOP;

;

;

;

    _delay_ms(

(

(

(

5

);

);

);

);

    LCD_NOP;

;

;

;

    SET_LCD_E;

;

;

;

    LCD_NOP;

;

;

    CLR_LCD_E;

;

;

;

    LCD_NOP;

;

;

;

    _delay_ms(

(

(

(

1

);

);

);

);

    LCD_NOP;

;

;

;

    SET_LCD_E;

;

;

;

    LCD_NOP;

;

;

    CLR_LCD_E;

;

;

;

    LCD_NOP;

;

;

;

    _delay_ms(

(

(

(

1

);

);

);

);

    CLR_LCD_D4;

;

;

;

    LCD_NOP;

;

;

;

    SET_LCD_E;

;

;

;

    LCD_NOP;

;

;

    CLR_LCD_E;

;

;

;

    LCD_NOP;

;

;

;

    _delay_us(

(

(

(

40

);

);

);

);

    WriteToLCD (

(

(

(

0x28

 ,

,

,

, LCDCOMMAND)

)

)

) ;

;

;

;

    LCD_DISPLAY(

(

(

(

0

)

)

)

) ;

;

;

;

    LCD_CLEAR ;

;

;

;

    LCD_ENTRY_MODE(

(

(

(LCDINCREMENT)

)

)

) ;

;

;

;

}

}

}

}

/*--------------------------------------------------------*/
/* Wyswietla tekst na aktualnej pozycji kursora */

void

 lcd_puts(

(

(

(

char

 *

*

*

*str)

)

)

)

{

{

{

{
    

unsigned

 

char

 i =

=

=

=

0

;

;

;

;

    

while

(

(

(

( str[

[

[

[i])

])

])

])

        LCD_WRITE_DATA(

(

(

(str[

[

[

[i++])

++])

++])

++]) ;

;

;

;

}

}

}

}

Listing 5.2 Zestaw funkcje do obsług wyświetlacza LCD (hd44780)

W pliku hd44780.h znajdują się deklaracje funkcji do obsługi
wyświetlacza, makrodefinicje przypisujące sygnały wyświetlacza do
wybranych wyprowadzeń AVRa oraz kilka przydatnych
makroinstrukcji.

/*

26 z 40

background image

Plik hd44780.h
*/

#ifndef LCD_HD44780
#define LCD_HD44780

/* RS */

#define SET_OUT_LCD_RS  DDRA  |=  _BV(PA0)
#define SET_LCD_RS      PORTA |=  _BV(PA0)
#define CLR_LCD_RS      PORTA &= ~_BV(PA0)

/* RW */

#define SET_OUT_LCD_RW  DDRA  |=  _BV(PA1)
#define SET_LCD_RW      PORTA |=  _BV(PA1)
#define CLR_LCD_RW      PORTA &= ~_BV(PA1)

/* E */

#define SET_OUT_LCD_E   DDRA  |=  _BV(PA2)
#define SET_LCD_E       PORTA |=  _BV(PA2)
#define CLR_LCD_E       PORTA &= ~_BV(PA2)

/* D4 */

#define SET_OUT_LCD_D4  DDRA  |=  _BV(PA3)
#define SET_IN_LCD_D4   DDRA  &= ~_BV(PA3)
#define SET_LCD_D4      PORTA |=  _BV(PA3)
#define CLR_LCD_D4      PORTA &= ~_BV(PA3)
#define IS_SET_LCD_D4   PINA  &   _BV(PA3)

/* D5 */

#define SET_OUT_LCD_D5  DDRA  |=  _BV(PA4)
#define SET_IN_LCD_D5   DDRA  &= ~_BV(PA4)
#define SET_LCD_D5      PORTA |=  _BV(PA4)
#define CLR_LCD_D5      PORTA &= ~_BV(PA4)
#define IS_SET_LCD_D5   PINA  &   _BV(PA4)

/* D6 */

#define SET_OUT_LCD_D6  DDRA  |=  _BV(PA5)
#define SET_IN_LCD_D6   DDRA  &= ~_BV(PA5)
#define SET_LCD_D6      PORTA |=  _BV(PA5)
#define CLR_LCD_D6      PORTA &= ~_BV(PA5)
#define IS_SET_LCD_D6   PINA  &   _BV(PA5)

/* D7 */

#define SET_OUT_LCD_D7  DDRA  |=  _BV(PA6)
#define SET_IN_LCD_D7   DDRA  &= ~_BV(PA6)
#define SET_LCD_D7      PORTA |=  _BV(PA6)
#define CLR_LCD_D7      PORTA &= ~_BV(PA6)
#define IS_SET_LCD_D7   PINA  &   _BV(PA6)

#define LCD_NOP __asm("nop")

#define LCDCOMMAND 0
#define LCDDATA    1

#define LCD_LOCATE(x,y)  WriteToLCD(0x80|((x)+((y)*0x40)), LCDCOMMAND)

#define LCD_CLEAR              WriteToLCD(0x01, LCDCOMMAND)
#define LCD_HOME               WriteToLCD(0x02, LCDCOMMAND)

/* IDS */

#define LCDINCREMENT           0x02
#define LCDDECREMENT           0x00
#define LCDDISPLAYSHIFT        0x01

#define LCD_ENTRY_MODE(IDS)    WriteToLCD(0x04|(IDS), LCDCOMMAND)

/* BCD */

#define LCDDISPLAY             0x04
#define LCDCURSOR              0x02
#define LCDBLINK               0x01

#define LCD_DISPLAY(DCB)       WriteToLCD(0x08|(DCB), LCDCOMMAND)

27 z 40

background image

/* RL */

#define LCDLEFT                0x00
#define LCDRIGHT               0x04

#define LCD_SHIFT_DISPLAY(RL)  WriteToLCD(0x18|(RL), LCDCOMMAND)
#define LCD_SHIFT_CURSOR(RL)   WriteToLCD(0x10|(RL), LCDCOMMAND)

#define LCD_CGRAM_ADDRESS(A)   WriteToLCD(0x40|((A)&0x3f), LCDCOMMAND)
#define LCD_DDRAM_ADDRESS(A)   WriteToLCD(0x80|((A)&0x7f), LCDCOMMAND)

#define LCD_WRITE_DATA(D)      WriteToLCD((D),LCDDATA)

void

 lcd_init(

(

(

(

void

);

);

);

);

void

 WriteToLCD(

(

(

(

unsigned

 

char

 v,

,

,

,

unsigned

 

char

 rs);

);

);

);

unsigned

 

char

 ReadAddressLCD(

(

(

(

void

);

);

);

);

void

 lcd_puts(

(

(

(

char

 *

*

*

*str);

);

);

);

#endif

Listing 5.3

Następnie naleŜy utworzyć w katalogu projektu odpowiedni plik
Makefile, z pomocą programu Mfile, tak jak robiliśmy to przy
poprzednich przykładach. Dodatkowo naleŜy wybrać z menu
programu Mfile opcję Makefile->C/C++source files(s) i w okienku
wpisać nazwę pliku: hd44780.c (nazwę pliku trzeba wpisać wraz
z rozszerzeniem .c)

Okno programu MFile. Wpisujemy nazwy wszytkich plików *.c projektu. Kliknij w obrazek,

Ŝeby zobaczyć całość.

A jeszcze musimy wpisać w pliku Makefile częstotliwość sygnału
taktującego procesor; częstotliwość wpisujemy ręcznie, gdyŜ brak
takiej opcji w menu. Aby móc edytować treść tworzonego pliku
Makefile trzeba zaznaczyć w menu "Makefile" opcję "Enable Editing
of Makefile"

28 z 40

background image

Okno programu MFile. Wpisujemy ręcznie częstotliwość pracy mikrokontrolera.

JeŜeli w programie wykorzystywane są funkcje _delay_ms lub
_delay_us, naleŜy dodać informacje o częstotliwość sygnału
taktującego procesor, inaczej funkcje te nie będą działać
prawidłowo. Informacje tę moŜna przekazać umieszczając na
początku kaŜdego pliku z kodem makrodefinicje

#define F_CPU 1000000UL

Jednak znacznie wygodniej będzie wspiać wczęstotliwość pracy
procesora w pliku Makefile, raz dla wszytkich plików programu.

Celem tego przykładu jest pokazanie w jaki sposób napisać
cokolwiek na ekranie wyświetlacza z uŜyciem funkcji zapisanych
w pliku hd44780.c Pierwsza rzecz to naleŜy gdzieś na początku
pliku programu wstawić polecenie preprocesora #include
dołączające plik hd44780.h zawierający deklaracje funkcji
zdefiniowanych w pliku hd44780.c

#include "hd44780.h"

W kolejnym kroku, zanim zaczniemy pisać na wyświetlaczu, trzeba
wywołać funkcję lcd_init, funkcja ta realizuje procedurę
programowej inicjalizacji wyświetlacza i następnie przełącza
interfejs wyświetlacza do trybu 4-bitowego. Po wykonaniu funkcji
lcd_init wyświetlacz zostaje wygaszony. Włączyć wyświetlanie
moŜna z pomocą makroinstrukcji LCD_DISPLAY.

/* Funkcja inicjalizuje wyświetlacz*/

lcd_init();

();

();

();

/* Włącza wyświetlanie */

LCD_DISPLAY(

(

(

(LCDDISPLAY);

);

);

);

Z pomocą makra LCD_LOCATE wskazujemy pozycje na ekranie

29 z 40

background image

wyświetlacza (kolumnę i wiersz), gdzie zamierzamy coś napisać.
Kolumny i wiersze liczone są zaczynając od zera.

/* szósta kolumna, pierwszy wiersz */

LCD_LOCATE(

(

(

(

5

,

,

,

,

0

);

);

);

);

Pojedyncze znaki moŜna wysyłać do wyświetlacza wykorzystując
makro LCD_WRITE_DATA.

/* Wysyła do wyświetlacza jeden znak*/

LCD_WRITE_DATA(

(

(

(

'S'

);

);

);

);

Po wykonaniu LCD_WRITE_DATA numer kolumny zwiększa się
o jeden, więc jeśli wyślemy kilka znaków jeden za drugim, to
zobaczymy na ekranie wyświetlacza napis. Napisy (ciągi znaków
zakończone zerem) moŜna wysyłać do wyświetlacza funkcją
lcd_puts. Funkcja lcd_puts po prostu wysyła do wyświetlacza znak
po znaku cały napis wywołując w pętli makro LCD_WRITE_DATA.
Jako argument funkcji lcd_puts wstawia się nazwę tabeli z tekstem
lub stałą napisową.

/* Funkcja lcd_puts wysyła do wyświetlacza ciąg  znaków */

lcd_puts(

(

(

(str1);

);

);

);

/* Jako argumentu funkcji moŜna wstawić stałą napisową */

lcd_puts(

(

(

(

"Programy"

);

);

);

);

Makro LCD_CLEAR czyści cały ekran wyświetlacza i ustawia
aktualny numer kolumny i wiersza na 0.

/* Czyści cały ekran */

LCD_CLEAR;

;

;

;

I jeszcze kursor, tzw. znak zachęty, zachęcający do prowadzania
danych z klawiaturki. Kursor ma postać poziomej kreski
wyświetlanej pod kratką, gdzie ma zostać wpisany kolejny znak.
Kursor moŜna pokazać uruchamiając makro LCD_DISPLAY.

/* Włącza wyświetlanie i kursor */

LCD_DISPLAY(

(

(

(LCDDISPLAY|LCDCURSOR);

);

);

);

W następnej części kursu pokaŜę jeszcze jak uzyskać na ekranie
wyświetlacza polskie znaki z "ogonkami" ąćęńłóśźŜ oraz
przedstawię kilka innych, ciekawych moŜliwości jakie oferują tego
typu wyświetlacze.

Przykład drugi - Licznik owiec

Po prostu licznik. Licząc owce lub gwiazdy na niebie lub cokolwiek
innego nie trudno o pomyłkę, zatem jak widać taki licznik moŜe być
szalenie uŜyteczny:) Obok wyświetlacza dołączyłem do AVRa trzy
przyciski, pierwszy przycisk zwiększa licznik o jeden, drugi przycisk
zmniejsza licznik o jeden, a trzeci przycisk zeruje licznik. Przyciski
przyłączone są do portów PB0..PB2 AVRa.

30 z 40

background image

Animacja pokazuje sposób działa program "Licznik owiec". Pierwszy przycisk zwiększa

licznik o jeden, drugi zmniejsza o jeden, a trzeci przycisk zeruje licznik.

Działanie programu jest bardzo proste, jest w pamięci zmienna
całkowita (32 bity) - licznik. KaŜdorazowo przy zmianie stanu
licznika wartość liczbowa w zmiennej zmieniana jest na ciąg
znaków (kodów ASCII), który jest wysyłany do wyświetlacza.

Aby skompilować program, naleŜy do katalogu projektu, obok pliku
main.c, skopiować pliki: hd44780.h, hd44780.c i oczywiście
stworzyć odpowiedni plik Makefile - tak jak w poprzednim
przykładzie.

/*
   Plik "main.c"

   KURS AVR-GCC cz.5 (przykład nr. 2)
   Licznik owiec :)
   (schemat i opis działania w artykule)   
   
   uC atmega16 (1MHz)
*/

#include <avr/io.h>
#include <util/delay.h>

/* Dołącza deklaracje funkcji obsługujących 
   wyświetlacz */

#include "hd44780.h"

/* ZMIENNE GLOBALNE */

/* W tablicy będą formowane komunikaty 
   wysyłane do wyświetlacza */

unsigned

 

char

 str1[

[

[

[

17

]=

]=

]=

]=

"----------------"

;

;

;

;

/* DEFINICJE FUNKCJI */

/* Funkcja aktualizuje zawartość ekranu */

static

 

void

 lcd(

(

(

(

unsigned

 

long

 

int

 a)

)

)

)

{

{

{

{
    

signed

 

char

 i;

;

;

;

   

/* Zamiana 32 bitowej liczby bez znaku 
   na ciąg znaków ASCII */

    

    

for

(

(

(

(i=

=

=

=

12

;

;

;

; i>=

>=

>=

>=

3

;

;

;

; a/=

/=

/=

/=

10

 ,

,

,

,i--)

--)

--)

--) 

                str1[

[

[

[i]

]

]

] =

=

=

= a %

%

%

10

 +

+

+

+

'0'

;

;

;

;

/* Ustawia kursor w pierwszej kolumnie
   pierwszego wersza */

    

    LCD_LOCATE(

(

(

(

0

,

,

,

,

0

);

);

);

);

/* Wysyła do wyświetlacza ciąg znaków z 
   tablicy str1 */

31 z 40

background image

    lcd_puts(

(

(

(str1);

);

);

);

}

}

}

}

/* GŁÓWNA FUNKCJA */

int

 main(

(

(

(

void

)

)

)

)

{

{

{

{

  

/* Zmienna przechowuje stan licznika */

    

  

unsigned

 

long

 

int

 n=

=

=

=

0

;

;

;

;

  

/* PB0,PB2  wejściami z podciągnięciem do VCC */

  DDRB  =

=

=

0x00

;

;

;

;

  PORTB =

=

=

0x07

;

;

;

;

  
  

/* Programowa inicjalizacja wyświetlacza */

  lcd_init();

();

();

();

  

/* Włącza wyświetlanie */

  LCD_DISPLAY(

(

(

(LCDDISPLAY);

);

);

);  

  

/* Czyści  ekran */

  LCD_CLEAR;

;

;

;            

  

/* Wyświetla początkowy stan licznika */

  lcd(

(

(

(n);

);

);

);

  

  

/* Główna pętla  */

  

while

(

(

(

(

1

)

)

)

)

  {

{

{

{

     

/* Jeśli pierwszy przycisk wciśnięty */

     

if

(!(

(!(

(!(

(!(PINB &

&

&

0x01

))

))

))

))

     {

{

{

{  

/* Czas na wygaśnięcie drgań styków przycisku*/

     

        _delay_ms(

(

(

(

80

);

);

);

);

    

/* Oczekuje na zwolnienie przycisku*/

        

while

(!(

(!(

(!(

(!(PINB &

&

&

0x01

))

))

))

)) {}

{}

{}

{}

/* Czas na wygaśnięcie drgań styków przycisku*/

  

    _delay_ms(

(

(

(

80

);

);

);

);

        
    

/* Zwiększa licznik o 1 */

        n++;

++;

++;

++;

    

/* Aktualizuje zawartość ekranu */

     lcd(

(

(

(n);

);

);

);

    }

}

}

}

    

/* Jeśli drugi przycisk wciśnięty */

    

else

 

if

(!(

(!(

(!(

(!(PINB &

&

&

0x02

))

))

))

))

    {

{

{

{

        _delay_ms(

(

(

(

80

);

);

);

);

        

while

(!(

(!(

(!(

(!(PINB &

&

&

0x02

))

))

))

)) {}

{}

{}

{}

        _delay_ms(

(

(

(

80

);

);

);

);

    

/* Zmniejsza licznik o 1 */

    n--;

--;

--;

--;

    

/* Aktualizuje zawartość ekranu */

     lcd(

(

(

(n);

);

);

);

    }

}

}

}

    

/* Jeśli trzeci przycisk wciśnięty */

    

else

 

if

(!(

(!(

(!(

(!(PINB &

&

&

0x04

))

))

))

))

    {

{

{

{

        _delay_ms(

(

(

(

80

);

);

);

);

        

while

(!(

(!(

(!(

(!(PINB &

&

&

0x04

))

))

))

)) {}

{}

{}

{}

        _delay_ms(

(

(

(

80

);

);

);

);

        
    

/* Zeruje licznik */

    n=

=

=

=

0

;

;

;

;

    

/* Aktualizuje zawartość ekranu */

     lcd(

(

(

(n);

);

);

);

    }

}

}

}

  }

}

}

}

}

}

}

}

Listing 5.4 Licznik owiec

Przykład trzeci - termometr cyfrowy

32 z 40

background image

W tym przykładzie, obok wyświetlacza LCD, dołączyłem do AVRa
scalony termometr cyfrowy DS18B20. AVR komunikuje się
z układem DS18B20 poprzez szeregową magistralę 1-wire.
Termometr DS18B20 jest inteligentnym czujnikiem cyfrowym,
w wyniku pomiaru otrzymujemy gotową wartość liczbową -
temperaturę wyskalowaną w stopniach Celsjusza. Jak pisałem
wcześniej, w tej części kursu nie będę jeszcze szczegółowo
tłumaczył jak działa magistrala 1-wire i jak programować
termometr DS18B20, jest to temat na oddzielny artykuł, w zamian
przygotowałem gotowy zestaw funkcji z pomocą których odczytamy
temperaturę z DS18B20.

Animacja prezentuje działanie programu "Termometr cyfrowy"

Celem tego przykładu jest pokazanie sposobu uŜycia funkcji sprintf.
W skrócie program działa w następujący sposób: Najpierw
następuje odczyt wartości temperatury z czujnika DS18B20.
Aktualna wartość temperatury przechowywana jest w zmiennej
rzeczywistej (typ double) o nazwie 'temp'. Następnie funkcja
standardowa sprintf zmienia wartość liczbową w zmiennej 'temp',
na ciąg znaków i formuje w tablicy 'str' komunikat tekstowy. Dalej
komunikat w tablicy 'str' wysyłany jest do wyświetlacza LCD. Odczyt
temperatury i wyświetlenie wyniku wykonuje się w głównej pętli
programu.

śeby skompilować program, naleŜy do katalogu projektu skopiować
następujące pliki: main.c, hd44780.h, hd44780.c, ds18b20.h,
ds18b20.c.

/*
   Plik "main.c"

   KURS AVR-GCC cz.5
   (xyz.isgreat.org)

   Termometr cyfrowy, przykład nr. 3
   (schemat i opis działania w artykule)
   atmega16 (1MHz)
*/

#include <stdio.h>
#include <avr/io.h>
#include <util/delay.h>
#include "hd44780.h"
#include "ds18b20.h"

/* W tablicy będą formowane komunikaty tekstowe
   wysyłane do wyświetlacza */

33 z 40

background image

char

 str[

[

[

[

17

]=

]=

]=

]=

"   Termometr    "

;

;

;

;

int

 main(

(

(

(

void

)

)

)

)

{

{

{

{
  

/* Zmienna przechowuje aktualną wartość temperatury */

        

  

double

 temp;

;

;

;

  

/* W tablicy zapisywane będą dane odczytane z układu ds18b20 */

  

unsigned

 

char

 ds18b20_pad[

[

[

[

9

];

];

];

];

  
  

/* Funkcja inicjalizuje wyświetlacz */

  lcd_init();

();

();

();

  

/* Włącza wyświetlanie */

  LCD_DISPLAY(

(

(

(LCDDISPLAY);

);

);

);  

  

/* Czyści  ekran */

  LCD_CLEAR;

;

;

;            

  
  

/* Wyświetla tytuł */

  

  LCD_LOCATE(

(

(

(

0

,

,

,

,

0

);

);

);

);

  lcd_puts(

(

(

(str);

);

);

);

  

while

(

(

(

(

1

)

)

)

)

  {

{

{

{

    

/* Funkcja 'ds18b20_ConvertT' wysyła do układu ds18b20 

       polecenie pomiaru */

      

     

if

(

(

(

(ds18b20_ConvertT())

())

())

())

    {

{

{

{

      

/* Odczyt z układu ds18b20, dane zapisywane są w tablicy ds18b20_pad. 

         Dwie pierwsze pozycje w tablicy to kolejno mniej znaczący bajt i bardziej 
     znaczący bajt wartość zmierzonej temperatury */

            

       ds18b20_Read(

(

(

(ds18b20_pad);

);

);

);

          
      

/* Składa dwa bajty wyniku pomiaru w całość. Cztery pierwsze bity mniej

         znaczącego bajtu to część ułamkowa wartości temperatury, więc całość
         dzielona jest przez 16 */

       

       temp =

=

=

= ((

((

((

((ds18b20_pad[

[

[

[

1

]

]

]

] <<

<<

<<

<< 

8

)

)

)

) +

+

+

+ ds18b20_pad[

[

[

[

0

])

])

])

]) /

/

/

16.0

 ;

;

;

;

      
      

/* Formułuje komunikat w tablicy 'str' */

       sprintf(

(

(

(str,

,

,

,

"%4.1f\xdf""C"

,

,

,

, temp);

);

);

);

       LCD_LOCATE(

(

(

(

5

,

,

,

,

1

);

);

);

);

      

/* Wysyła komunikat do wyświetlacza */

       lcd_puts(

(

(

(str);

);

);

);

    }

}

}

}

  }

}

}

}

}

}

}

}

Listing 5.5 Termometr cyfrowy

/*
   Plik ds18b20.h

   (xyz.isgreat.org)  
*/

#ifndef DS18B20_H
#define DS18B20_H

/* DS18B20 przyłączony do portu  PD7 AVRa  */

#define SET_ONEWIRE_PORT     PORTD  |=  _BV(7)
#define CLR_ONEWIRE_PORT     PORTD  &= ~_BV(7)
#define IS_SET_ONEWIRE_PIN   PIND   &   _BV(7)
#define SET_OUT_ONEWIRE_DDR  DDRD   |=  _BV(7)
#define SET_IN_ONEWIRE_DDR   DDRD   &= ~_BV(7)

unsigned

 

char

 ds18b20_ConvertT(

(

(

(

void

);

);

);

);

int

 ds18b20_Read(

(

(

(

unsigned

 

char

 []);

[]);

[]);

[]);

void

 OneWireStrong(

(

(

(

char

);

);

);

);

unsigned

 

char

 OneWireReset(

(

(

(

void

);

);

);

);

void

 OneWireWriteByte(

(

(

(

unsigned

 

char

);

);

);

);

unsigned

 

char

 OneWireReadByte(

(

(

(

void

);

);

);

);

#endif

Listing 5.6

34 z 40

background image

/*
   Plik ds18b20.c
   (minimum kodu do odczytu temperatury z ds18b20)

   xyz.isgreat.org
*/

#include <avr/io.h>
#include <util/delay.h>
#include "ds18b20.h"

/**********************************************************/

unsigned

 

char

 ds18b20_ConvertT(

(

(

(

void

)

)

)

)

{

{

{

{
  

if

 (!

(!

(!

(!OneWireReset())

())

())

()) 

return

 

0

;

;

;

;

  OneWireWriteByte(

(

(

(

0xcc

);

);

);

); 

// SKIP ROM

  OneWireWriteByte(

(

(

(

0x44

);

);

);

); 

// CONVERT T

  

return

 -

-

-

-

1

;

;

;

;

}

}

}

}

/***********************************************************/

int

 ds18b20_Read(

(

(

(

unsigned

 

char

 scratchpad[])

[])

[])

[])

{

{

{

{
  

unsigned

 

char

 i;

;

;

;    

  

if

 (!

(!

(!

(!OneWireReset())

())

())

()) 

return

 

0

;

;

;

;

  OneWireWriteByte(

(

(

(

0xcc

);

);

);

); 

// SKIP ROM

  OneWireWriteByte(

(

(

(

0xbe

);

);

);

); 

// READ SCRATCHPAD

  

for

(

(

(

(i=

=

=

=

0

;

;

;

; i<

<

<

<

10

;

;

;

; i++)

++)

++)

++) scratchpad[

[

[

[i]

]

]

] =

=

=

= OneWireReadByte();

();

();

();

 
  

return

 

1

;

;

;

;

}

}

}

}

/**********************************************************/

void

 OneWireStrong(

(

(

(

char

 s)

)

)

)

{

{

{

{
  

if

 (

(

(

(s)

)

)

)

  {

{

{

{

     SET_ONEWIRE_PORT;

;

;

     SET_OUT_ONEWIRE_DDR;

;

;

  }

}

}

}

  

else

  {

{

{

{

     SET_IN_ONEWIRE_DDR;

;

;

  }

}

}

}

}

}

}

}

/**********************************************************/

unsigned

 

char

 OneWireReset()

()

()

()

{

{

{

{
  CLR_ONEWIRE_PORT;

;

;

  

if

 (!(

(!(

(!(

(!(IS_SET_ONEWIRE_PIN))

))

))

)) 

return

 

0

;

;

;

;  

  SET_OUT_ONEWIRE_DDR;

;

;

  _delay_us(

(

(

(

500

);

);

);

);

  SET_IN_ONEWIRE_DDR;

;

;

  _delay_us(

(

(

(

70

);

);

);

);

  

if

(!(

(!(

(!(

(!(IS_SET_ONEWIRE_PIN))

))

))

))

  {

{

{

{

    _delay_us(

(

(

(

500

);

);

);

);

    

return

(

(

(

(

1

);

);

);

);

  }

}

}

}

35 z 40

background image

  _delay_us(

(

(

(

500

);

);

);

);

return

(

(

(

(

0

);

);

);

);

}

}

}

}

/**********************************************************/

void

 OneWireWriteByte(

(

(

(

unsigned

 

char

 byte)

)

)

)

{

{

{

{
   

unsigned

 

char

 i;

;

;

;

   CLR_ONEWIRE_PORT;

;

;

   

for

 (

(

(

(i=

=

=

=

0

;

;

;

; i<

<

<

<

8

;

;

;

; i++)

++)

++)

++)

   {

{

{

{

     SET_OUT_ONEWIRE_DDR;

;

;

     

if

 (

(

(

(byte &

&

&

0x01

)

)

)

)

     {

{

{

{

       _delay_us(

(

(

(

7

);

);

);

);

       SET_IN_ONEWIRE_DDR;

;

;

       _delay_us(

(

(

(

70

);

);

);

);

     }

}

}

}

     

else

     {

{

{

{

        _delay_us(

(

(

(

70

);

);

);

);

        SET_IN_ONEWIRE_DDR;

;

;

        _delay_us(

(

(

(

7

);

);

);

);

     }

}

}

}

     byte >>=

>>=

>>=

>>= 

1

;

;

;

;

   }

}

}

}

}

}

}

}

/***********************************************************/

unsigned

 

char

 OneWireReadByte(

(

(

(

void

)

)

)

)

{

{

{

{
  

unsigned

 

char

 i,

,

,

, byte =

=

=

0

;

;

;

;

  SET_IN_ONEWIRE_DDR;

;

;

  
  

for

 (

(

(

(i=

=

=

=

0

;

;

;

; i<

<

<

<

8

;

;

;

; i++)

++)

++)

++)

  {

{

{

{

     SET_OUT_ONEWIRE_DDR;

;

;

     _delay_us(

(

(

(

7

);

);

);

);

     SET_IN_ONEWIRE_DDR;

;

;

     _delay_us(

(

(

(

7

);

);

);

);

     byte >>=

>>=

>>=

>>= 

1

;

;

;

;

     
     

if

(

(

(

(IS_SET_ONEWIRE_PIN)

)

)

) byte |=

|=

|=

|= 

0x80

;

;

;

;

     _delay_us(

(

(

(

70

);

);

);

);

  }

}

}

}

  

return

 byte;

;

;

;

}

}

}

}

Listing 5.7 Minimum kodu do odczytu temperatury z ds18b20

Następnie naleŜy utworzyć w katalogu projektu odpowiedni plik
Makefile, tak jak robiliśmy to przy poprzednich przykładach.
Dodatkowo naleŜy wybrać z menu programu Mfile opcję
Makefile->C/C++source files(s) i w okienku, które się pojawi wpisać
nazwy plików: hd44780.c ds18b20.c

36 z 40

background image

MFile-edytor plików Makefile. Wpisujemy nazwy wszytkich plików *.c projektu.

Tak jak pisałem wcześniej, domyślnie funkcje printf i sprintf nie
obsługują liczb zmiennopozycyjnych. Zatem trzeba teŜ w menu
"Makefile->printf() options" zaznaczyć opcję "floating point".

37 z 40

background image

MFile-edytor plików Makefile. Opcja "floating point" włącza dla funkcji: printf, sprintf obsługę

zmiennych zmiennopozycyjnych.

Tu warto zauwaŜyć, Ŝe uŜycie w programie funkcji 'sprintf' lub
'printf', szczególnie z opcją 'floating point', skutkuje znacznym
wzrostem długości kodu wynikowego.

Przykład czwarty - Transmisja szeregowa do komputera PC

W tym przykładzie wykorzystywany jest teŜ termometr ds18b20 i
dodatkowo połączymy przewodem port szeregowy AVRa
z interfejsem rs233c komputera PC. Wyniki pomiaru temperatury
będą trafiały do komputera PC.

Przykład kompilujemy podobnie jak poprzednie przykłady,
w katalogu projektu powinny znajdować się pliki: main.c,
ds18b20.h, ds18b20.c.

Programik działa w następujący sposób: W pętli, z układu ds18b20,
odczytywana jest wartość temparatury i następnie informacja ta
wysyłana jest poprzez port szeregowy AVRa do komputera PC.
Komunikat tekstowy zawierający informacje o wartości zmierzonej
temperatury formowany jest z uŜyciem standardowej funkcji printf.
Wynik działania funkcji printf, znak po znaku, wysyłany jest do
standardowego wyjścia 'stdout'. Z kolei strumień danych 'stdout'
został w programie skierowany do nadajnika portu szeregowego
AVRa.

/*
   Plik "main.c"

   KURS AVR-GCC cz.5
   (xyz.isgreat.org)

   Transmisja szeregowa do komputera PC, przykład nr. 4
   (schemat i opis działania w artykule)

   atmega16 (1MHz)
*/

/* Prędkość transmisji 2400 */

#define BAUD 2400
#define MYUBRR  F_CPU/BAUD/16-1

#include <stdio.h>
#include <avr/io.h>
#include <util/delay.h>
#include "ds18b20.h"

/* Inicjuje port szeregowy AVRa */

void

 USART_init(

(

(

(

unsigned

 

int

 myubrr)

)

)

)

{

{

{

{
    

/* Ustala prędkość transmisji */

    UBRRH =

=

=

= (

(

(

(

unsigned

 

char

)(

)(

)(

)(myubrr>>

>>

>>

>>

8

);

);

);

);

    UBRRL =

=

=

= (

(

(

(

unsigned

 

char

)

)

)

)myubrr;

;

;

;

    

/* Włącza nadajnika */

    UCSRB =

=

=

= (

(

(

(

1

<<

<<

<<

<<TXEN);

);

);

);

  
    

/* Format ramki: 8 bitów danych, 1 bit stopu, brak bitu parzystości */

    UCSRC =

=

=

= (

(

(

(

1

<<

<<

<<

<<URSEL)|(

)|(

)|(

)|(

3

<<

<<

<<

<<UCSZ0);

);

);

); 

}

}

}

}

/* Wysyła znak do portu szeregowego */

static

 

int

 USART_Transmit(

(

(

(

char

 c,

,

,

, FILE *

*

*

*stream)

)

)

)

{

{

{

{
    

while

(!(

(!(

(!(

(!(UCSRA &

&

&

& (

(

(

(

1

<<

<<

<<

<<UDRE)));

)));

)));

)));

38 z 40

background image

    UDR =

=

=

= c;

;

;

;

    

return

 

0

;

;

;

;

}

}

}

}

/* Tworzy strumienia danych o nazwie 'mystdout' połączony
    z funkcją 'USART_Transmit' */

static

 FILE mystdout =

=

=

= FDEV_SETUP_STREAM(

(

(

(USART_Transmit,

,

,

, NULL,

,

,

, _FDEV_SETUP_WRITE);

);

);

);

/*  GŁÓWNA FUNKCJA  */

int

 main(

(

(

(

void

)

)

)

)

{

{

{

{
  

/* Zmienna 'temp' przechowuje wartość temperatury */

    

  

double

 temp;

;

;

;

  

/* Do tej tablicy zapisywane będą dane odczytane z układu ds18b20 */

  

unsigned

 

char

 ds18b20_pad[

[

[

[

9

];

];

];

];

  

/* Inicjalizuje  port szeregowy AVRa */

  USART_init(

(

(

(MYUBRR);

);

);

);

  

/* Przekierowuje standardowe wyjście do  'mystdout' */

  stdout =

=

=

= &

&

&

&mystdout;

;

;

;

  

/* Główna pętla */

  

while

(

(

(

(

1

)

)

)

)

  {

{

{

{

    

/* Funkcja 'ds18b20_ConvertT' wysyła do układu ds18b20 

       polecenie pomiaru */

    

if

(

(

(

(ds18b20_ConvertT())

())

())

())

    {

{

{

{

      

/* Odczyt z układu ds18b20, dane zapisywane są w tablicy ds18b20_pad. 

         Dwie pierwsze pozycje w tablicy to kolejno mniej znaczący bajt, bardziej 
         znaczący bajt wartość zmierzonej temperatury */

               

       ds18b20_Read(

(

(

(ds18b20_pad);

);

);

);

 
      

/* Składa dwa bajty wyniku pomiaru w całość. Cztery pierwsze bity mniej

         znaczącego bajtu to część ułamkowa wartości temperatury, więc całość
         dzielona jest przez 16 */

       temp =

=

=

= ((

((

((

((ds18b20_pad[

[

[

[

1

]

]

]

] <<

<<

<<

<< 

8

)

)

)

) +

+

+

+ ds18b20_pad[

[

[

[

0

])

])

])

]) /

/

/

16.0

 ;

;

;

;

      
      

/* Wysyła komunikat do portu szeregowego */

      printf(

(

(

(

"Temperatura powietrza:%5.2C°\n"

,

,

,

, temp);

);

);

);

    }

}

}

}

  }

}

}

}

}

}

}

}

Listing 5.8 Transmisja szeregowa do PC

W pliku main.c znajdują się dwie funkcje do obsługi portu transmisji
szeregowej USART AVRa. Pierwsza funkcja 'USART_init' włącza
nadajnik portu szeregowego i ustawia parametry transmisji, druga
funkcja 'USART_Transmit' wysyła znak do portu szeregowego.
A znajdująca się na początku pliku main.c makrodefinicja

#define BAUD 2400

ustala prędkość transmisji. W tej części kursu nie będę jeszcze
szczegółowo wyjaśniał jak programować układ transmisji
szeregowej USART AVRa, wykorzystamy gotowe funkcje bez
zagłębiania się w rejestry i bity.

Jednym z argumentów funkcji 'USART_Transmit' jest znak, który
funkcja wstawia do nadajnika portu szeregowego. W programie
standardowe wyjście 'stdout' skojarzone zostało z funkcją
'USART_Transmit', oznacza to. Ŝe, dla kaŜdego wysłanego do
'stdout' znaku zostaje uruchomiona funkcja 'USART_Transmit',
która wpisuje otrzymany znak do nadajnika portu szeregowego.

39 z 40

background image

Celem komunikacji poprzez interfejs rs232c potrzebujemy
uruchomić na komputerze PC odpowiedni program, terminal rs232.
Z pomocą terminala rs232 moŜemy najprościej odczytywać
komunikaty, które przyszły do komputera PC poprzez port rs232c.
Jednym z popularniejszych tego rodzaju programów działających
w Windows jest darmowy "Bray's Terminal" 

bray_termimal.zip

Po uruchomieniu programu terminala naleŜy ustawić następujące
parametry transmisji 2400,8,N,1, na ilustracji poniŜej zaznaczyłem
te opcje na czerwono. Następnie naleŜy wybrać numer
wykorzystywanego portu (COM1, COM2) i kliknąć przycisk
"Connect".

Okno programu "Bray's Terminal"- odczyt danych z portu rs232c.

W następnej części kursu.

W następnej części zakończę omawiać podstawy języka C. Napiszę
teŜ o przerwaniach oraz kilka zdań na temat danych w pamięci
Flash.

Uwaga! MoŜna otrzymać powiadomienie pocztą elektroniczną o
ukazaniu się kolejnych części kursu. W tym celu naleŜy wysłać
e-mail na adres 

abxyz@o2.pl 

z tematem "KURS AVR-GCC

INFORMACJA".

© 2009 ABXYZ Wszelkie prawa zastrzeŜone

40 z 40