XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 1 of 41
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 zró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.
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 2 of 41
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 zró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ł.
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 3 of 41
/* 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óznią 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.
/* Aączenie ciągów znaków */
unsigned char i,j;
char str1[]= "KURS";
char str2[]= " AVR-GCC";
char str3[]= " cz.5";
char buffer[18];
/* Aą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";
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 4 of 41
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
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);
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 5 of 41
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".
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 6 of 41
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
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 7 of 41
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.
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 8 of 41
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 zró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
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 zró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 \ .
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 9 of 41
#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 zró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
zró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<PORTD &= ~(1<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
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 10 of 41
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
/* Początkowo diodę LED przyłączono do
wyprowadzenia PD0 */
/*
#define SET_OUT_LED DDRD |= (1<#define SET_LED PORTD |= (1<#define CLR_LED PORTD &= ~(1<*/
/* Schemat się zmienił, aktualnie dioda LED
przyłączona jest do wyprowadzenia PB3 */
#define SET_OUT_LED DDRB |= (1<#define SET_LED PORTB |= (1<#define CLR_LED PORTB &= ~(1<int main(void)
{
/* Ustawia PB3 jako wyjście */
/* Preprocesor zastÄ…pi SET_OUT_LED instrukcjÄ…
DDRB |= (1< SET_OUT_LED;
/* Jakiś kawałek kodu */
/* Zapala diodÄ™ LED */
/* Preprocesor zastÄ…pi SET_LED instrukcjÄ…
PORTB |= (1< SET_LED;
/* Jakiś kawałek kodu */
/* Gasi diodÄ™ LED */
/* Preprocesor zastÄ…pi CLR_LED instrukcjÄ…
PORTB &= ~(1< 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<podanÄ… jako argument makra.
#include
/* Makrodefinicja z argumentem */
#define _BV(bit) (1 << (bit))
int main(void)
{
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 11 of 41
/* 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
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
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 12 of 41
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ą
FAASZ.
#define MICRO
intmain()
{
#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 zródłowego programu na osobno kompilowane pliki
Dotychczas, we wszytkich programikach naszego kursu, całość kodu
zródłowego umieszczana była w jednym pliku, jak w poniższym
przykładzie.
//-----------------------------------------------------------
// plik "main.c"
//-----------------------------------------------------------
#include
/* Definicje zmiennych globalnych */
char tytul[]="KURS AVR-GCC, cz.5";
int czesc = 5;
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 13 of 41
/* 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 zró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
/* 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);
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 14 of 41
double funkcja_3(double x)
{
funkcja_1(2,6);
funkcja_2();
}
char funkcja_4(char s[])
{
return 0;
}
//-----------------------------------------------------------
// plik "main.c"
//-----------------------------------------------------------
#include
/* 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
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 15 of 41
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
/* 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"
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 16 of 41
//----------------------------------------------------------
// 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
/* 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)
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 17 of 41
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ść.
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 18 of 41
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.
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 19 of 41
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 DDRD |= _BV(PD0)
#define SET_IN_LCD_D7 DDRD &= ~_BV(PD0)
#define SET_LCD_D7 PORTD |= _BV(PD0)
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 20 of 41
#define CLR_LCD_D7 PORTD &= ~_BV(PD0)
#define IS_SET_LCD_D7 PIND & _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 znalezć 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
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 21 of 41
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 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)
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.
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 22 of 41
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"
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 23 of 41
Ż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
#include
/* 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);
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 24 of 41
_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
#include
#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;
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 25 of 41
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)
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 26 of 41
{
_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.
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 27 of 41
/*
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)
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 28 of 41
/* 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"
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 29 of 41
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();
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 30 of 41
/* 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" ąćęńłóśzż
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
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 31 of 41
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
#include
/* 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';
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 32 of 41
/* 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);
}
/* GAÓ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);
}
}
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 33 of 41
}
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)
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 34 of 41
atmega16 (1MHz)
*/
#include
#include
#include
#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)
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 35 of 41
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
#include
#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);
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 36 of 41
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
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 37 of 41
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".
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 38 of 41
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
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 39 of 41
'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
#include
#include
#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< /* Format ramki: 8 bitów danych, 1 bit stopu, brak bitu parzystości */
UCSRC = (1<}
/* Wysyła znak do portu szeregowego */
static int USART_Transmit(char c, FILE *stream)
{
while(!(UCSRA & (1< 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);
/* GAÓ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 */
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 40 of 41
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".
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
XYZ Hobby Robot-Kurs AVR-GCC, cz.5 Page 41 of 41
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
http://hobby.abxyz.bplaced.net/index.php?pid=4&aid=8&pv 17/04/2010
Wyszukiwarka
Podobne podstrony:
Kurs AVR GCC cz 5
Kurs AVR GCC, cz 3
Kurs AVR GCC cz 2
Kurs AVR GCC cz 3
Kurs AVR GCC cz 1
Kurs AVR GCC, cz 1
Kurs AVR GCC, cz 4
Kurs AVR GCC, cz 2
Kurs AVR GCC cz 4
Kurs AVR GCC Wyświetlacz LCD od Nokii310
XYZ Hobby Robot Kurs AVR GCC Gamepad od PlayStation
Kurs AVR GCC Dekoder RC5
Using the EEPROM memory in AVR GCC
AVR GCC w Linuksie przykład instalacji ze źródeł
Zestaw uruchomieniowy do procesorow rodziny AVR i 51, cz 2
więcej podobnych podstron