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. 

Page 1 of 41

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

17/04/2010

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

background image

 

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

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

Page 2 of 41

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

17/04/2010

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

background image

  

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

 

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"

Page 3 of 41

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

17/04/2010

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

background image

   

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

Page 4 of 41

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

17/04/2010

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

background image

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

Page 5 of 41

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

17/04/2010

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

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 

Page 6 of 41

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

17/04/2010

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

background image

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

Page 7 of 41

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

17/04/2010

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

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

Page 8 of 41

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

17/04/2010

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

background image

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

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 

Page 9 of 41

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

17/04/2010

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

background image

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

Page 10 of 41

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

17/04/2010

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

background image

 
  

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

#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 

Page 11 of 41

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

17/04/2010

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

background image

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

 

 

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

 

Page 12 of 41

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

17/04/2010

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

background image

/* Definicje kilku funkcji */

 

void

 funkcja_1(

int

  a, 

int

 b)  



 

int

 funkcja_2(

void


    funkcja_4(tytul); 
     
    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

); 

 

Page 13 of 41

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

17/04/2010

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

background image

double

 funkcja_3(

double

 x)  

{   
    funkcja_1(

2

,

6

);  

 
    funkcja_2(); 
}  
 

char

 funkcja_4(

char

 s[])  

{  
    

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 

Page 14 of 41

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

17/04/2010

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

background image

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. 

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"  

Page 15 of 41

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

17/04/2010

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

background image

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

 

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) 

Page 16 of 41

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

17/04/2010

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

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

Page 17 of 41

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

17/04/2010

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

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. 

Page 18 of 41

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

17/04/2010

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

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 C
#define SET_LCD_RW      PORTA |=  _BV(PA1) 
#define CLR_LCD_RW      PORTA &= ~_BV(PA1)

 

/* E */

 

#define SET_OUT_LCD_E   DDRA  |=  _

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

 

/* D4 */

 

#define SET_OUT_LCD_D4  DDRA  |=  _

#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  |=  _

#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  |=  _

#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  |=  _

#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

Page 19 of 41

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

17/04/2010

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

background image

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

 

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 

Page 20 of 41

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

17/04/2010

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

background image

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 
kondensatory na osobnej płytce. 

Page 21 of 41

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

17/04/2010

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

background image

 

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" 

Page 22 of 41

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

17/04/2010

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

background image

Żeby skompilować program, należy do katalogu projektu skopiować 
trzy zamieszczone poniżej pliki: main.c, hd44780.h, hd44780.c W 
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); 

Page 23 of 41

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

17/04/2010

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

background image

        _delay_ms(

800

);     

 
        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; 

Page 24 of 41

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

17/04/2010

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

background image

    LCD_NOP;  
    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

Page 25 of 41

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

17/04/2010

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

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. 

Page 26 of 41

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

17/04/2010

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

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)

 

Page 27 of 41

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

17/04/2010

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

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" 

Page 28 of 41

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

17/04/2010

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

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

Page 29 of 41

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

17/04/2010

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

background image

/* Włącza wyświetlanie */

 

LCD_DISPLAY(LCDDISPLAY);

Z pomocą makra LCD_LOCATE wskazujemy pozycje na ekranie 
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 

Page 30 of 41

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

17/04/2010

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

background image

o jeden, a trzeci przycisk zeruje licznik. Przyciski 
przyłączone są do portów PB0..PB2 AVRa. 

 

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'

Page 31 of 41

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

17/04/2010

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

background image

 

/* Ustawia kursor w pierwszej kolumnie 
   pierwszego wersza */

     

    LCD_LOCATE(

0

,

0

); 

 

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

 

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

Page 32 of 41

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

17/04/2010

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

background image

 
}

Listing 5.4 Licznik owiec

Przykład trzeci - termometr cyfrowy 

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) 

Page 33 of 41

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

17/04/2010

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

background image

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

 

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) 

 

Page 34 of 41

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

17/04/2010

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

background image

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

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

); 

Page 35 of 41

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

17/04/2010

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

background image

  SET_IN_ONEWIRE_DDR;  
  _delay_us(

70

); 

 
  

if

(!(IS_SET_ONEWIRE_PIN)) 

  { 
    _delay_us(

500

); 

    

return

(

1

); 

  } 
 
  _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 

Page 36 of 41

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

17/04/2010

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

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

Page 37 of 41

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

17/04/2010

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

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. A i jeszcze 
koniecznie musimy wpisać w pliku Makefile częstotliwość 
sygnału taktującego procesor, tak jak opisałem to 
w pierszym przykładzie. 

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. 

Program kompilujemy podobnie jak poprzednie przykłady, 
w katalogu projektu powinny znajdować się pliki: main.c, 
ds18b20.h, ds18b20.c. W pliku Makefile należy wpisać 
częstotliwość sygnału taktującego procesor oraz włączyć 
obsługę zmiennych zmiennopozycyjnych dla funkcji printf. 

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 

Page 38 of 41

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

17/04/2010

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

background image

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

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

 

Page 39 of 41

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

17/04/2010

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

background image

    

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.2f°C\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. 

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

Page 40 of 41

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

17/04/2010

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

background image

 

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

I to był już ostatni przykładowy program w tej części kursu. 
Jeśliby ktoś miał problemy z kompilacją przykładów, to 
dokładam jeszcze gotowe pliki hex. Dołączone pliki hex 
przeznaczone są wyłącznie dla AVRa atmega16(1MHz) 

p5_1.hex

 

p5_2.hex

 

p5_3.hex

 

p5_4.hex

 

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, o programowaniu fusebitów i zmianie 
częstotliwości pracy mikrokontrolera. 

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

 

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

Page 41 of 41

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

17/04/2010

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