background image

 
Artykuł pochodzi ze strony XYZ HOBBY ROBOT (

xyz.isgreat.org

Kurs AVR-GCC cz.3

17.03.2009 ABXYZ

W poprzedniej części kursu objaśniałem 
jak programować równoległe porty 
we/wy układów AVR, bo porty 
równoległe to najprostszy i podstawowy 
sposób komunikacji mikrokontrolera 
z otoczeniem. W tej i dwóch kolejnych 
częściach kursu postaram się 
przedstawić podstawy języka C, 
oczywiście w dużym skrócie, gdyż 
trudno byłoby opisać szczegółowo 

całość języka C w trzech krótkich artykułach. Ale nie ma się czego 
obawiać, na szczęście język C nie jest zbyt obszerny, szybko można 
opanować jego postawy, które pozwolą samodzielnie pisać 
programy. 

Tematem tej części kursu będą: zmienne i stałe liczbowe, operatory 
oraz instrukcje sterujące. Wpierw omówię kolejno wymienione 
tematy, a dalej, jako ćwiczenie, uruchomimy kilka przykładowych 
programów. 

Zmienne i typy danych

Zakładam, że nie ma potrzeby objaśniać czym są zmienne w 
programie, wiadomo, zmienne przechowują dane. Każda zmienna 
posiada własną nazwę (identyfikator) oraz przypisany jeden 
z dostępnych w języku C typów. Typ zmiennej określa rodzaj 
przechowywanej danej, np. znak, liczba całkowita, liczba 
rzeczywista oraz wielkość obszaru pamięci zajmowanego przez 
zmienną, np. 8, 16, 32 bity. 

Podstawowe typy danych w języku C to: 

char - jeden znak (8-bitowa liczba całkowita);

int - liczba całkowita;

short int - liczba całkowita krótka;

long int - liczba całkowita długa;

long long int - liczba całkowita bardzo długa;

float - liczba rzeczywista (typ zmiennopozycyjny);

double - typ zmiennopozycyjny podwójnej precyzji;

long double - typ zmiennopozycyjny rozszerzonej precyzji.

W języku C należy każdą zmienną przed użyciem zdefiniować, 
definicję zmiennej można rozumieć jako tworzenie zmiennej. 
Zmienne definiuje się wpisując w linii typ zmiennej, a po nim nazwę 
zmiennej lub nazwy kilku zmiennych rozdzielone przecinkami; 
definicję kończymy średnikiem. Definiując zmienną można 
jednocześnie ją zainicjować umieszczając zaraz po nazwie zmiennej 
znak "=", a po nim wartość początkową dla zmiennej. Znaczenie 
ma miejsce definicji zmiennych, dokładnie objaśnię to przy temacie 
funkcji. W programach z tej części kursu będziemy definiować 
zmienne na początku funkcji main. Przykłady: 

int

 main(

void


   

/* Definicja zmiennej 'temperatura' typu char */

 

Page 1 of 38

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

17/04/2010

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

background image

   

char

 temperatura; 

 
   

/* Definicja zmiennej 'wysokość' typu unsigned int */

 

   

unsigned int

 wysokosc; 

 
   

/* Definicja z inicjacją zmiennej 'napięcie' typu float */

 

   

float

 napiecie = 

3.36

 
   

/* Definicja z inicjacją zmiennej 'prędkość' typu int */

 

   

int

 predkosc = 

0

;  

 
   

/* Definicja trzech zmiennych typu unsigned char */

 

   

unsigned char

 bieg, poziom = 

1

, stan; 

Nazwy zmiennych mogą składać się z liter, z cyfr i znaku "_", ale 
nie mogą zaczynać się od cyfry; wielkość liter ma znaczenie, 
np. "temp" i "Temp" to dwie różne zmienne. Nie można używać 
w nazwach zmiennych polskich liter: ąćęłńóśźż ĄĆĘŁŃÓŚŹŻ. 

W nazwach typów: short int, long int, long long int można pominąć 
słówko int, np. typ long oznacza typ long int. Dodając przed nazwę 
typu całkowitego słówko signed(unsigned) informujemy kompilator, 
że powinien traktować wartości w zmiennej jako liczby ze znakiem
(bez znaku). Liczby ze znakiem (signed) zapisywane są na bitach 
w kodzie uzupełnień do dwóch U2(two's complement). 

Zależnie od używanego kompilatora języka C, rozmiary 
poszczególnych typów zmiennych mogą się różnić. Przykładowo na 
procesorach 16-bitowych typ całkowity int zwykle ma rozmiar 16 
bitów, na procesorach 32-bitowych - 32 bity. W przypadku 
mikroprocesorów 8-bitowych typ int ma zwykle rozmiar 16 bitów, 
tak też jest w AVR-GCC; zgodnie ze standardem języka C typ int nie 
może mieć mniej niż 16 bitów. W AVR-GCC zmienne wszystkich 
typów zmiennopozycyjnych: float, double, long double kodowane są 
na 32 bitach, czyli faktycznie jest dostępny jedynie typ 
zmiennopozycyjny pojedynczej precyzji (zgodny ze standardem 
IEEE754). Zapewne wynika to z faktu, że operacje na liczbach 
zmiennopozycyjnych większej precyzji byłyby zbyt dużym 
obciążeniem dla 8-bitowych mikroprocesorów. W tabeli poniżej 
wypisałem rozmiary podstawowych typów zmiennych 
w kompilatorze AVR-GCC. 

Typ

Rozmiar 

(bitów)

Wartość 

minimalna

Wartość 

maksymalna

char

8

 

 

signed char

8

-128

127

unsigned char

8

0

255

short int

16

-32768

32767

unsigned short 

int

16

0

65535

int

16

-32768

32767

unsigned int

16

0

65535

long int

32

-2

31

2

31

-1

unsigned long 

int

32

0

2

32

-1

long long int

64

-2

63

 

2

63

-1

unsigned long 

long int

64

2

64

-1

Page 2 of 38

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

17/04/2010

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

background image

Typ

Rozmiar 

(bitów)

Wartość 

minimalna

Wartość 

maksymalna

float

32

±1.18·10

-38

±3.4·10

38

double

32

±1.18·10

-38

±3.4·10

38

long double

32

±1.18·10

-38

±3.4·10

38

Podstawowe typy zmiennych w C, rozmiary typów w kompilatorze AVR-GCC

W języku C nie istnieje specjalny typ zmiennych dla wartości 
logicznych, zwyczajnie jeśli wartość jakiejś zmiennej równa się 
zeru, to zmienna posiada wartość logiczną FAŁSZ, w przeciwnym 
przypadku zmienna posiada wartość logiczną PRAWDA. Przykład: 

if

( jakas_zmienna ) 

/* Instrukcje wykonywane jeśli zawartość zmiennej 
"jakas_zmienna" będzie różna od zera  */

 

}

Ogólna zasada. W języku C jeśli wartość liczbowa stałej, zmiennej 
lub dowolnego wyrażenia jest różna od zera, wtedy stała, zmienna, 
wyrażenie ma wartość logiczną PRAWDA, w przeciwnym przypadku 
ma wartość logiczną FAŁSZ. 

Chwilowo tyle informacji o zmiennych wystarczy. 

Stałe liczbowe

W programie można używać stałych liczbowych całkowitych 
w postaci dziesiętnej, szesnastkowej i ósemkowej; można też 
używać stałych typu zmiennopozycyjnego. Postać szesnastkową 
tworzymy wstawiając przed liczbą parę znaków 0x lub 0X 
(np. 0xFF); stałe ósemkowe rozpoczynają się od cyfry 0 (np. 077); 
a stałe całkowite w systemie dziesiątkowym piszemy zwyczajnie 
(np. 123). Stałe zmiennopozycyjne zawierają dziesiętną kropkę (np. 
3.14), mogą też zawierać wykładnik (np. 123.45E3= 123450) 

Domyślnie stałe całkowite są typu int, jeśli wartość stałej nie mieści 
się w typie int, wtedy stała otrzymuje typ long int lub long long int. 
Można nadawać stałym typ long int dopisując na ich końcu literę 
L lub l; a dopisując litery LL lub ll, typ long long int. Podobnie 
dopisując na końcu stałych całkowitych literę U lub u można nadać 
stałym typ bez znaku. Stałe zmiennopozycyjne są domyślnie typu 
double. Dopisując na końcu stałych zmiennopozycyjnych literkę 
F lub f można nadać im typ float; a dopisując literkę L lub l można 
nadać typ long double. Przykłady: 

/* stała 0xff jest typu int */

 

   DDRD = 

0xff

;   

 

/* stała 255 jest typu int */

 

   PORTD = 

255

;   

 

/* stała 12345U jest typu unsigned int */

 

   uz = 

12345U

;  

 

/* stała 12.0 jest typu double */

 

   liczba = 

12.0

 

/* stała 123456L jest typu long int */

 

   

123456L

 * zy;   

 

Page 3 of 38

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

17/04/2010

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

background image

/* stała -1UL jest typu unsigned long int */

 

   zz = 

-1UL

;  

 

/* stała 3.17f jest typu float */

 

   

3.17f

 * yz;

Rejestry I/O 

A czym są nazwy rejestrów I/O, jak np.: PORTB, PINB, DDRB ? 
Przecież język C nic nie wie o rejestrach mikrokontrolerów AVR. W 
poprzedniej części napisałem kilka słów na temat preprocesora 
języka C, który może wykonywać różne operacje na tekście 
źródłowym programu jeszcze przed właściwą kompilacją. To właśnie 
preprocesor, przed kompilacją, zmienia w tekście programu nazwy 
rejestrów I/O na właściwy kod w języku C; na przykład instrukcja: 

 PORTB = 

0x02

;

zostanie zmieniona przez preprocesor na kod: 

(*(

volatile uint8_t

 *)((

0x18

) + 

0x20

))  = 

0x02

;

Ale tylko, jeśli dołączy się do kodu programu pliki z definicjami 
rejestrów I/O, wstawiając gdzieś na początku programu linię: 

#include <avr/io.h>

A co oznacza ten fragment kodu ? Rejestry I/O układów AVR 
ulokowane są w przestrzeni adresowej pamięci danych, zaczynając 
od adresu 0x20, rejestr PORTB mieści się pod numerem 0x18 
względem adresu 0x20 (patrz datasheet AVR-a) W AVR-GCC dostęp 
do rejestrów I/O jest za pośrednictwem wskaźnika do zmiennej 
typu uint8_t (unsigned char). Jeśli ostatnie dwa zdania są 
niezrozumiałe, to absolutnie proszę się tym nie przejmować i czytać 
dalej. Z tego trzeba zapamiętać, że rejestry IO, jak na przykład: 
PORTB, PINB, DDRB, posiadają typ "unsigned char". 

Kodowanie U2. 

Liczby całkowite ze znakiem (signed) zapisywane są w pamięci 
w kodzie uzupełnień do dwóch U2 (two's complement), liczby bez 
znaku (unsigned) w naturalnym kodzie binarnym NKB. 

Proszę spojrzeć na wzory i tablice. 

Page 4 of 38

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

17/04/2010

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

background image

 

bity wartości w kodzie NKB wartości w kodzie U2

0000

0

0

0001

1

1

0010

2

2

0011

3

3

0100

4

4

0101

5

5

0110

6

6

0111

7

7

1000

8

-8

1001

9

-7

1010

10

-6

1011

11

-5

1100

12

-4

1101

13

-3

1110

14

-2

1111

15

-1

Kodowanie liczb na 4 bitach 

Page 5 of 38

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

17/04/2010

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

background image

 

Kodowanie U2 na 4 bitach. Na kole lepiej widać niż w tabelce. 

bity

wartości w kodzie NKB wartości w kodzie U2

0000 0000

0

0

0000 0001

1

1

0000 0010

2

2

0000 0011

3

3

:

:

:

0111 1101

125

125

0111 1110

126

126

0111 1111

127

127

1000 0000

128

-128

1000 0001

129

-127

1000 0010

130

-126

:

:

:

1111 1101

253

-3

1111 1110

254

-2

1111 1111

255

-1

Kodowanie liczb na 8 bitach

W naturalnym kodzie binarnym na n bitach można zapisać wartości 
z zakresu [0,2

n

-1]; czyli dla 4 bitów [0,15], dla 8 bitów [0,255], dla 

16 bitów [0,65535]. 

Natomiast w kodzie U2 na n bitach można zapisać wartości 
z zakresu [-2

n-1

,2

n-1

-1]; czyli dla 4 bitów [-8,7], dla 8 bitów [-

128 ,127], dla 16 bitów [-32768,32767]. 

W kodzie U2 najstarszy(pierwszy od lewej) bit liczby mówi o znaku, 
dla zera i wartości dodatnich najstarszy bit jest zerem, dla wartości 
ujemnych - jedynką. Aby zmienić znak liczby zapisanej w kodzie U2 
można "odwrócić" wartości wszystkich bitów liczby (jedynki zmienić 
na zera, a zera na jedynki) i do uzyskanej wartości dodać jeden. 

~  0001 0011   (19) 
 
   1110 1100    
+   
   0000 0001 

Page 6 of 38

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

17/04/2010

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

background image

----------------- 
   1110 1101  (-19) 
 
~  1110 1101  (-19) 
 
   0001 0010     
+   
   0000 0001 
----------------- 
   0001 0011  (19)

Przy dodawania/odejmowania liczb zapisanych w kodzie U2 
przeprowadza się jednakowe operacje na bitach jak przy 
dodawaniu/odejmowaniu liczb zapisanych w NKB. 

Operatory przypisania

Nową wartość można zapisać w zmiennej używając operatora 
przypisania "="; instrukcję przypisania należy zakończyć 
średnikiem. Przykłady: 

int

 main(

void


   

/* definicja zmiennych  */

 

   

unsigned int

 wynik; 

   

unsigned char

 a,b; 

 
   

/* Zapisuje w zmiennej 'a' zawartość rejestru PINC */

 

   a = PINC; 
   

/* Zapisuje w zmiennej 'b' zawartość rejestru PIND */

 

   b = PIND; 
 
   

/* Oblicza i zapisuje w zmiennej 'wynik' wartość 

wyrażenia a*b+312 */

 

   wynik =  a * b + 312; 
 
   

/* Do PORTA trafą bity 0..7 zmiennej 'wynik' */

 

   PORTA = wynik; 
   

/* Do PORTB trafą bity 8..15 zmiennej 'wynik' */

 

   PORTB = wynik >> 

8

 ;

Ta sama zmienna może stać po obu stronach operatora przypisania, 
przykład: 

h =  h + 

10

;

Nowicjuszom w programowaniu taki zapis może się wydać dziwny. 
Ale jest to instrukcja przypisania a nie równanie; w tym przykładzie 
w zmiennej h zostanie zapisana nowa wartość, która jest równa 
aktualnej wartości tej zmiennej powiększonej o 10. Tutaj lepiej 
byłoby posłużyć się operatorem przypisania +=, przykład: 

/* Zawartość zmiennej 'h' zostanie zwiększona o 10 */

 

h += 

10

;

Obok operatora += są do dyspozycji podobnie działające operatory 
przypisania: -=, *=, /=, %=, ^=, |=, &=, <<=, >>=. 

a -= 

17

;

  // a = a - 17 

 

 
a *= 

2

;

   // a = a * 2

 

 
a /= 

4

;

   // a = a / 4

 

 
a %= 

4

;

   // a = a % 4

 

Page 7 of 38

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

17/04/2010

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

background image

 
a ^= 

0xaaaa

;

   // a = a ^ 0xaaaa

 

 
a |= 

0x5555

;

   // a = a | 0x5555

 

 
a &= 

0xaaaa

;

   // a = a & 0xaaaa

 

 
a <<= 

8

;

   // a = a << 8

 

 
a >>= 

4

;

   // a = a >> 4

 

Operatory arytmetyczne 

W języku C istnieją operatory arytmetyczne: 

dodawania

odejmowania

mnożenia

dzielenia 

% dzielenia modulo (reszta z dzielenia całkowitego)

Jeśli oba argumenty operatora dzielenia / będą typu całkowitego, 
wtedy wynik operacji dzielenia będzie typu całkowitego i część 
ułamkowa wyniku będzie tracona. Resztę z dzielenia liczb 
całkowitych można wyliczyć posługując się operatorem % (dzielenia 
modulo). Inaczej jest, jeśli dzielna lub dzielnik albo oba argumenty 
operatora dzielenia / będą typu zmiennopozycyjnego (float, 
double), wtedy wynik dzielenia także będzie typu 
zmiennopozycyjnego. Przykłady: 

   

int

  reszta, x; 

   

double

 wynik; 

 
   x =  

15

 
  

/* Zmienna 'wynik' będzie zawierać wartość 7 */

 

   wynik =  x / 

2

  

/* W zmiennej 'reszta' będzie 1 */

 

   reszta =  x % 

2

 
  

/* W zmiennej 'wynik' będzie 7.5, bo stała 2.0 jest 

typu double */

 

   wynik =  x / 

2.0

;

Istnieją jeszcze jednoargumentowe operatory + , -, służące do 
zmiany znaku liczb; jednoargumentowy plus właściwie nic nie robi, 
jest tylko do pary :). 

   

int

 a, b; 

 
   a = 

15

   /* nową wartością zmiennej 'b' będzie -15 */

 

   b =  - a ;

Operatory zwiększania i zmiejszania.

W języku C istnieją operatory zwiększające lub zmniejszające 
o jeden wartości zmiennych. 

++  zwiększ

--  zmniejsz

Page 8 of 38

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

17/04/2010

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

background image

Sposób działania tych operatorów zależy od tego, czy ++(--) 
znajdują się po lewej, czy po prawej stronie zmiennej. Jeśli operator 
++(--) stoi po lewej stronie zmiennej, wartość zmiennej zwiększa
(zmniejsza) się o jeden przed użyciem wartości zmiennej. Jeśli 
operator ++(--) stoi po prawej stronie zmiennej, wartość zmiennej 
zwiększa(zmniejsza) się o jeden po użyciu wartości tej zmiennej. 

Przykłady: 

int

 a, b; 

 
   b =  

5

 
   

/* 'a' i 'b' będą równe 6 */

 

   a = ++b ; 
 
   

/*  'a' będzie równe 6, 'b' będzie równe 7 */

 

   a = b++ ; 
 
   

/* 'a' i 'b' będą równe 6 */

 

   a = --b ; 
 
   

/*  'a' będzie równe 6, 'b' będzie równe 5 */

 

   a = b-- ;

Operatory logiczne, relacji i porównania

W języku C dostępne są operatory relacji: 

większy

>=  większy równy

mniejszy

<= mniejszy równy 

Operatory przyrównania: 

== równy

!=  różny

Operatory logiczne: 

&& 

AND

|| 

OR

!  operator negacji

Wynikiem działania operatorów: relacji, przyrównania i logicznych 
są wartości liczbowe: 0 - gdy fałsz, 1 - gdy prawda. W języku C 
wartość liczbowa 0 oznacza jednocześnie wartość logiczną FAŁSZ, 
a wartość 1 i każda inna wartość liczbowa różna od zera oznacza 
wartość logiczną PRAWDA 

   

int

 a, b, c, d; 

 
   a =  

3

   b =  

5

   c =  

0

 
   

/* w zmiennej 'd' zostanie zapisana wartość 0 */

 

   d =  a == b ; 
   

/* w zmiennej 'd' będzie 1 */

 

   d =  a != b ; 
   

/* w zmiennej 'd'  będzie 1 */

 

Page 9 of 38

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

17/04/2010

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

background image

   d =  a < b ; 
   

/* w zmiennej 'd'  będzie 0 */

 

   d =  a > b ; 
   

/* w zmienne 'd'  będzie  1 */

 

   d =  a && b ; 
   

/* w zmiennej 'd' będzie 0 */

 

   d =  a && c ; 
   

/* w zmiennej  'd' będzie 1 */

 

   d =  a || c ; 
   

/* w zmiennej 'd' będzie  0 */

 

   d = 

0

 || c ; 

   

/* w zmiennej 'd' będzie  1 */

 

   d =  !

0

   

int

 a, b; 

 
   a =  

2

 
   

/* w  'b' będzie  1 */

 

   b = 

-3

 < a  && a <=  

3

   

/* w  'b' będzie  0 */

 

   b = a <= 

-3

 || a > 

3

   

/* teraz w  'b' będzie !0 czyli 1 */

 

   b = !(a <= 

-3

 || a > 

3

);

Opisane w tym punkcie operatory zwykle wykorzystuje się 
w instrukcjach z warunkiem, jak np. if-else, while, for. 

int

 a, b; 

 

if

( a == b ) 

/* Instrukcje do wykonania jeśli a' jest równe 'b'  */

 

int

 a, b; 

 

while

( a > b ) 

/* Instrukcje które będą się wykonywać  
wielokrotnie w pętli dopóki 'a' jest większe od 'b' */

 

}

Szczególnie należy uważać, by zamiast operatora przyrównania == 
nie wpisać operatora przypisania = . 

Operatory bitowe

W języku C istnieje sześć operatorów bitowych: 

bitowe OR

bitowe AND

bitowe XOR

>>  przesunięcie w prawo

<<  przesunięcie w lewo

~  dopełnienie jednykowe

Operacje bitowe działają na wartościach całkowitych i służą do 
manipulowania bitami, więc są szczególnie użyteczne przy 
programowaniu sprzętu - przy zabawie z bitami rejestrów I/O. 

Operatory bitowe & i | mogą się mylić z opisanymi wcześniej 
operatorami logicznymi && i ||, dlatego szczegółowo, na 

Page 10 of 38

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

17/04/2010

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

background image

przykładach, wyjaśnię różnice w działaniu operatorów bitowych 
i logicznych. W poniższych przykładach liczby wypisane są w postaci 
dwójkowej. 

operator bitowy "|" -  alternatywa (OR) 
 
    0 1 0 1 0 1 0 1  

    0 0 1 1 0 0 1 1  

    0 1 1 1 0 1 1 1 
 
operator logiczny OR "||" 
 
    0 1 0 1 0 1 0 1(PRAWDA bo wartość różna od zera)   

    0 0 1 1 0 0 1 1(PRAWDA)  

    0 0 0 0 0 0 0 1(PRAWDA)

operator "&" - bitowa koniunkcja (AND) 
 
    0 1 0 1 0 1 0 1 

    0 0 1 1 0 0 1 1  

    0 0 0 1 0 0 0 1 
 
operator logiczny AND "&&"  
 
    0 1 0 1 0 1 0 1(PRAWDA) 

    0 0 1 1 0 0 1 1(PRAWDA)  

    0 0 0 0 0 0 0 1(PRAWDA)

operator "~" - dopełnienie jedynkowe 
 ~ 1 0 0 1 1 0 0 1  =  0 1 1 0 0 1 1 0  
 
operator logiczny negacja "!" 
 ! 1 0 0 1 1 0 0 1(PRAWDA)  =  0 0 0 0 0 0 0 0(FAŁSZ)

operator "^" - bitowa alternatywa wykluczająca (XOR) 
 
    0 1 0 1 0 1 0 1 

    0 0 1 1 0 0 1 1  

    0 1 1 0 0 1 1 0

operator "<<" - przesunięcie w lewo 
 1 0 0 1 1 0 0 1  << 3 =  1 1 0 0 1 0 0 0

operator ">>" - przesunięcie w prawo 
 
   liczby bez znaku (unsigned)  
   1 0 0 1 1 0 0 1  >> 5 =  0 0 0 0 0 1 0 0 
    
   liczby ze znakiem (signed) 
   1 0 0 1 1 0 0 1  >> 5 =  1 1 1 1 1 1 0 0

Proszę zauważyć że, przesuwając bity liczby o jedną pozycję w lewo 
mnożymy wartość liczby razy dwa; a przesuwając o jedno pozycję 
w prawo, dzielimy liczbę przez dwa. Jeśli trzeba mnożyć(dzielić) 
liczby całkowite razy(przez) 2,4,8,2

n

, to można przesuwać bity. A 

Page 11 of 38

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

17/04/2010

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

background image

po co? Przecież są operatory arytmetyczne mnożenia i dzielenia: 
*, /. Na przykład, żeby zoptymalizować program pod kątem 
szybkości. Przesuwanie bitów liczby jest o wiele prostszą operacją 
w porównaniu z mnożeniem i dzieleniem. Dla 8 bitowych 
mikroprocesorów operacje mnożenia i dzielenia mogą być 
znaczącym obciążeniem. 

          2  * 2 =  4 
   00000010 << 1 =  00000100 
 
          3  * 4 =  12 
   00000011 << 2 =  00001100 
 
          9  * 8 =  72 
   00001001 << 3 =  01001000 
 
         67 / 16 =  4 (dzielenie całkowite) 
   01000011 >> 4 =  00000100

W liczbach ze znakiem(signed) najstarszy bit decyduje o znaku, 
więc w operacji przesuwania bitów w  prawo liczb ze znakiem 
najstarszy bit pozostanie bez zmian. W przypadku przesuwania 
w prawo liczb bez znaku(unsigned) najstarszy bit otrzymuje 
wartość 0. Przykład: 

Przesuwanie w prawo bitów liczb ze znakiem 
 
          85  /  8 =  10 
   01010101 >> 3 = 00001010 
 
         -125 / 16 = -8 
   10000011 >> 4 =  11111000

Trójargumentowy operator warunkowy. 

Trójargumentowy operator warunkowy ?: ma postać: 

wyr1 ? wyr2 : wyr3

I działa w następujący sposób: Jeśli wartość wyrażenia wyr1 różni 
się od zera (logiczna wartość PRAWDA), wtedy zwraca wartość 
wyrażenia wyr2 (całość jest równa wartości wyr2), w przeciwnym 
razie zwraca wartość wyr3. Przykłady: 

int

 a, b, c; 

 

/* Jeśli 'b' jest równe 7, w zmiennej 'a' zostanie zapisana 
wartość zmiennej 'c', w przeciwnym razie w 'a' zostanie 
zapisana wartość wyrażenia c+3 */

 

 
a =  (b == 

07

) ? c : c + 

3

/* Jeśli bit nr 2 w rejestrze PINC jest równy 1, w rejestrze 
PORTB bit nr 0 otrzyma wartość 1, w przeciwnym razie 
ustawiony zostanie bit nr 7 */

 

 
PORTB |= (PINC & 

0x04

) ? 

0x01

 : 

0x80

W przykładach warunek umieszczony jest w okrągłych nawiasach, 
podobnie jak w instrukcji if-else, nie jest to konieczne, ale poprawia 
czytelność kodu. 

Page 12 of 38

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

17/04/2010

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

background image

Priorytety i łączność operatorów

Priorytety i łączność operatorów decydują o kolejności wykonywania 
operacji przy obliczaniu wartości wyrażeń Przykładowo, operacje 
mnożenia i dzielenia mają wyższy priorytet niż dodawanie 
i odejmowanie (tak, jak w matematyce), a operatory przypisania 
mają niższy priorytet niż większość operatorów; więc w poniższym 
przykładzie wpierw wykona się mnożenie, następnie dodawanie i na 
końcu obliczona wartość wyrażenia zostanie zapisana w zmiennej 
'wynik'. 

wynik =  a + b * 

2

;

Kolejnością wykonywanych działań można sterować używając 
okrągłych nawiasów (jak w matematyce). 

wynik =  (a + b) * 

 2

;

W tabelce poniżej zestawione zostały wszystkie operatory języka C 
według malejących priorytetów. Operatory w jednym wierszu 
tabelki mają ten sam piorytet. 

Uwaga, w języku C jeden symbol (np.: +, -, *, &) może oznaczać 
dwa różne operatory, zależnie od kontekstu. Czytając tabelkę 
należy wiedzieć, że jednoargumentowe operatory zmiany znaku: 
+ , - posiadają wyższy priorytet niż dwuargumentowe operatory 
dodawania i  odejmowania. Podobnie jednoargumentowe operatory 
& (adres obiektu) i * (dostęp za pośrednictwem wskaźnika) mają 
wyższy priorytet niż dwuargumentowe operatory & (bitowe AND) i * 
(mnożenie). 

Operatory

Łączność

()  []  ->  .

lewostronna

!  ~  ++  --  +  -  *  &  (typ)  sizeof

prawostronna

*  /  %

lewostronna

+  -

lewostronna

 << >>

lewostronna

<=  <  >  >=

lewostronna

==  !=

lewostronna

&

lewostronna

^

lewostronna

|

lewostronna

&&

lewostronna

||

lewostronna

?:

lewostronna

 =  +=  -=  *=  /=  %=  ^=  &=  |=  <<=  >>=  prawostronna

,

lewostronna

Priorytety i łączność operatorów

Prawostronna łączność operatorów jednoargumentowych mówi, że 
argument stoi po prawej stronie operatora. Przykład: 

 ~ 0x01;  
  ! a

Page 13 of 38

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

17/04/2010

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

background image

Lewostronna (prawostronna) łączność dwuargumentowych 
operatorów oznacza, że jeżeli w wyrażeniu występuje więcej niż 
jeden operatorów o jednakowych priorytetach, wtedy wpierw 
wykonywany jest ten najbardziej z lewej(prawej). Na przykład 
operatory mnożenia(*), dzielenia(/) i operator obliczania reszty 
z dzielenia(%) mają jednakowy priorytet i są lewostronnie łączne, 
więc wyrażenie poniżej: 

   36 / 3 * 2 % 4 

jest równoznaczne wyrażeniu: 

   ((36 / 3) * 2) / 4

Z kolei operatory przypisania są prawostronnie łączne i na przykład 
instrukcja w postaci: 

   a = b = c = d ;

jest równoważna instrukcji 

   a = (b = (c = d));

Warto z tego zapamiętać, że całe wyrażenie przypisania, jak np: 

   c = 1;

też posiada wartość liczbową, równą wartości argumentu stojącego 
po prawej stronie operatora przypisania. 

Instrukcje wyboru

Instrukcję wyboru if-else używaliśmy już w przykładach 
z poprzedniej części kursu, if-else ma postać: 

if

( wyrażenie ) 

instrukcja_uruchamiana_jeśli_wyrażenie_jest_różne_od_zera; 

else

 

instrukcja_uruchamiana_jeśli_wyrażenie_jest_równe_zero; 
 

/* LUB */

 

 

if

( wyrażenie ) 

/* Instrukcje wykonywane jeśli wartość wyrażenia jest różna 
od zera */

 

else

 

/* Instrukcje wykonywane jeśli wartość wyrażenia jest 
równa zero */

 

Instrukcja if-else sprawdza czy warunek jest spełniony, tzn. czy 
wartość wyrażenia w nawiasach okrągłych po słówku if jest różna 
od zera. Jeśli tak, to zostanie wykonana instrukja znajdująca się 
zaraz za nawiasem ")"; w przeciwnym razie zostanie wykonana 
instrukcja po słówku else, część else można pominąć. Obejmując 
fragment kodu parą klamrowych nawiasów "{","}" tworzymy blok 

Page 14 of 38

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

17/04/2010

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

background image

instrukcji, blok w "if-else" jest traktowany jako pojedyncza 
instrukcja. Przykład: 

signed char

 a,b; 

 

/* Jeśli wartość zmiennej 'a' jest równa wartości zmiennej 'b', 
w rejestrze PORTA zostanie zapisana wartość 0x01, w przeciwnym  
razie w  PORTB zostanie zapisana wartość 0x0F */

 

 

if

(a==b) PORTA = 

0x01

else

 PORTB = 

0x0F

;

Jeśli potrzebna jest instrukcja if-else, ale z więcej niż dwoma 
wariantami kodu, to można użyć dwóch lub więcej instrukcji if-else 
zagnieżdżonych kaskadowo, jak w przykładzie poniżej. 

signed char

 a; 

 

if

( a <= 

3

 ) 

/* Jeśli 'a' jest mniejsze równe 3 */

 

else

 

if

( a <= 

7

 ) 

     { 
     

/* Jeśli 'a' jest mniejsze od 7 */

 

     } 
     

else

 

if

( a <= 

9

 ) 

          { 
          

/* Jeśli 'a' mniejsze równe od 9 */

 

          } 
          

else

 

          { 
          

/* 'a' jest większe od 9  */

 

          }

Kolejna instrukcja wyboru switch ma postać: 

switch

(wyrażenie) 


  

case

 wyrażenie_stałe_1: 

  

/* Instrukcje - wariant 1*/

 

  

break

 
  

case

 wyrażenie_stałe_2: 

  

/* Instrukcje - wariant 2 */

 

  

break

 
  

case

 wyrażenie_stałe_3: 

  

/* Instrukcje - wariant 3 */

 

  

break

  .................... 
  

case

 wyrażenie_stałe_n: 

  

/* Instrukcje - wariant n */

 

  

break

 
  

default

  

/* Instrukcje - jeśli żaden z wcześniejszych wariantów */

 

}

Instrukcja switch działa w następujący sposób: Jeśli wyrażenie 
w nawiasach okrągłych, po słówku switch, jest równe wartości stałej 
po którymś wystąpieniu słówka case, wtedy wykonują się instrukcje 
wypisane po dwukropku, aż do wystąpienia instrukcji break, która 
kończy działanie całej instrukcji switch. Jeśli brak instrukcji break, 
wtedy wykonane zostaną instrukcje kolejnego wariantu, aż do 
momentu napotkania instrukcji break lub nawiasu klamrowego } 
kończącego całą instrukcję switch. Jeśli wartość wyrażenia nie 
pasuje(nie równa się) do żadnej stałej, wtedy wykonuają się 

Page 15 of 38

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

17/04/2010

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

background image

instrukcje po słówku default, część default można w instrukcji 
switch pominąć. Przykład: 

unsigned char

 a; 

 

switch

(a) 


  

case

 

3

  

/* Instrukcje wykonywane jeśli 'a' równe jest  3 */

 

  

break

  

case

 

7

  

/* jeśli a = 7 */

 

  

break

  

case

 

5

  

/* jeśli a = 5 */

 

  

break

  

case

 

5 + 4

  

/* jeśli a = 9 */

 

  

break

 
  

default

  

/* jeśli żaden z wcześniejszych wariantów */

 

}

Kolejny przykład użycia switch: 

switch

(PINB & 0x07) 


  

case

 

0x00

  

/* Instrukcje wykonywane jeśli 0 */

 

  

case

 

0x01

  

/* jeśli 0 lub 1 */

 

  

case

 

0x02

  

/*  jeśli 0 lub 1 lub 2 */

 

  

break

 
  

case

 

0x03

  

/* jeśli 3 */

 

  

case

 

0x04

  

/* jeśli 3 lub 4 */

 

  

default

  

/* jeśli 3 lub 4 lub 5 lub 6 lub 7  */

 

}

Instrukcje pętli

W języku C pętle tworzy się instrukcjami: 

while,

do-while,

for.

Instrukcja while ma postać: 

while

( wyrażenie ) instrukcja_w_pętli; 

 

/* LUB  */

 

 

while

( wyrażenie ) 

{    

/* Obejmując fragment kodu parą nawisów klamrowych { } 
tworzy się  blok instrukcji, który jest traktowany 
jako pojedyncza instrukcja - instrukcja złożona. */

 

}

I działa w następujący sposób: 

Oblicza wartość wyrażenia w nawiasach okrągłych.

1.

Page 16 of 38

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

17/04/2010

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

background image

Jeśli wartość liczbowa wyrażenia jest różna od zera, wykonuje 
instrukcje wewnątrz pętli, po czym przechodzi do punkut 1; 
w przeciwnym przypadku działanie instrukcji pętli jest 
zakończone. 

2.

Instrukcja do-while ma postać: 

do

 instrukcja_w_pętli; 

while

( wyrażenie ); 

 

/* LUB */

 

 

do

 

{    
 

/* Instrukcje w pętli. */

 

while

( wyrażenie );

I działa w następujący sposób: 

Wykonuje instrukcje wewnątrz pętli.

1.

Oblicza wyrażenie w nawisach okrągłych. Jeśli wartość 
liczbowa wyrażenia jest różna od zera, przechodzi do 
punkut 1; w przeciwnym przypadku działanie instrukcji pętli 
jest zakończone.

2.

Przykłady:

/* Pętla nieskończona utworzona instrukcją 'while' */

 

while

(

1

{    

/* Instrukcje w nieskończonej pętli */

 

}

int

 a; 

 

while

( a <= 

17

 ) 

/* Instrukcje które będą się wykonywać wielokrotnie  
w pętli dopóki 'a' będzie mniejsze lub równe 17 */

 

}

Instrukcja for ma postać: 

for

(wyrażenie_1; wyrażenie_2; wyrażenie_3) instrukcja_w_pętli; 

 

/* LUB  */

 

 

for

(wyrażenie_1; wyrażenie_2; wyrażenie_3) 

{    
  

/* Instrukcje wykonywane w pętli */

 

}

I działa w następujący sposób: 

Oblicza wyrażenie_1.

1.

Oblicza wyrażenie_2.

2.

Jeśli wartość liczbowa wyrażenia_2 jest różna od zera 
(wartość logiczna PRAWDA), wykonuje instrukcję w pętli 
i przechodzi do punktu 4; w przeciwnym przypadku działanie 
instrukcji pętli jest zakończone. 

3.

Oblicza wyrażenie_3 i przechodzi do punktu 2.

4.

Page 17 of 38

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

17/04/2010

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

background image

Na przykład, jeśli jest potrzeba, żeby jakiś fragment kodu wykonał 
się określoną ilość razy, można wtedy zbudować pętlę z użyciem 
instrukcji for. 

unsigned char

 i; 

 

for

(i = 

0

; i < 

7

; i++) 

{    
  

/* Instrukcje w pętli wykonane zostaną 7 razy */

 

}

W tym przykładzie użyto zmiennej "i" jako licznika iteracji pętli; 
przykład działa w następujący sposób: 

Zapisuje do zmiennej "i" wartość 0.

1.

Sprawdza czy wartość w zmiennej "i" jest mniejsza od 7; jeśli 
tak, przechodzi do następnego punktu, w  przeciwnym 
wypadku kończy działanie pętli.

2.

Wykonuje instrukcje w pętli.

3.

Zwiększa wartość w zmiennej "i" o jeden.

4.

Przechodzi do punktu 2.

5.

Kolejny przykład z instrukcją for. Instrukcje w pętli będą 
wykonywane dopóki wartość zmiennej "i" będzie mniejsza od 
wartości zmiennej "n" i jednocześnie bit numer 2 rejestru PINC 
będzie miał wartość 1. Zmienna "i" pracuje jako licznik iteracji pętli. 

unsigned char

 i, j, n=10; 

 

for

(i=

0

, j=

0

; i < n && PINC & 

04

; i++, j+=i) 

{    
  

/* Instrukcje w pętli */

 

}

Działanie instrukcji pętli: while, do-while i for można wcześniej 
zakończyć używając instrukcji break, po "break" wstawiamy 
średnik. Przykład: 

while

(

1

{    
 

/* Jeśli w PINC bit nr 0 jest jedynką, to wyście z pętli */

 

  

if

( PINC & 

0x01

break

}

Istnieje jeszcze instrukcja continue, która powoduje pominięcie 
dalszych instrukcji w pętli i przejście do początku kolejnej iteracji 
pętli; po continue wstawiamy średnik. 

unsigned char

 i; 

 

for

(i = 

0

; i < 

99

; i++) 

{    
 

/* Instrukcje w pętli */

 

 

/* Jeśli reszta z dzielenia wartości zmiennej 'i' przez 5 
będzie równa 0, dalsze instrukcje zostaną pominięte 
i nastąpi przejście do początku kolejnej iteracji pętli. */

 

  

if

(i % 

5

 == 

0

continue

 
 

/* Dalsze instrukcje w pętli, pomijane, gdy zadziała 

'continue' */

 

}

Page 18 of 38

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

17/04/2010

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

background image

Z wielokrotnie zagnieżdżonej pętli, jak w przykładzie poniżej, 
poręcznie jest "wyskoczyć" wykorzystują instrukcję goto. Przykład: 

unsigned char

 i, j; 

 

while

(

1


    

for

(i = 

0

; i < 

7

; i++) 

    { 
       

for

(j = 

10

; j >= 

0

; j--) 

       { 
 
       

/* Jeśli bit nr 2 w rejestrze PIND ma wartość 1, 

to skok do etykiety 'dalej' */

 

       

if

( PIND &  

0x02

goto

 dalej; 

 
       

/* Dalsze instrukcje */

 

 
       } 
    { 

 

/* etykieta 'dalej' */

 

dalej: 

/* Dalsza część programu */

Instrukcja goto ma postać: 

goto

 nazwa_etykiety; 




nazwa_etykiety:

Po napotkaniu instrukcji goto następuje skok do miejsca w  
programie oznaczonego etykietą, etykietę powinna kończyć się 
dwukropkiem. 

Z pomocą instrukcji goto można skakać w obrębie całej funkcji, 
a nawet tworzyć pętlę. Jednak nadużywanie goto prowadzi to 
powstania nieczytelnych, zagmatwanych kodów, więc zaleca się 
używania instrukcji goto tylko w tych sytuacjach, gdy w inny sposób 
nie da się tego napisać. Teoretycznie zawsze można się obejść bez 
użycia goto. 

Przykładowe programy

Przygotowałem kilka przykładowych programów, bardzo prostych 
i chyba zabawnych :) . Proponuje, jako ćwiczenie i zabawę, 
uruchomić wszystkie. 

Jak poprzednio, wszystkie te przykładowe programy napisane są 
według jednego, prostego schematu: Całość algorytmu zapisana 
jest w funkcji 'main', wpierw wykonują się instrukcje inicjujące 
(definicja zmiennych, konfiguracja portów we/wy itp.), a  następnie 
program przechodzi do wykonania instrukcji umieszczonych w 
nieskończonej pętli - nazwę ją główną pętlą programu. 

/* Szkielet prostego programu dla avr-gcc */

 

 

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

/* Jeśli będą używanie funkcje w rodzaju _delay_ms, _delay_us */

 

#include <util/delay.h>       

 

Page 19 of 38

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

17/04/2010

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

background image

int

 main(

void


  

/* Instrukcje - wstępne ustawienia, konfiguracje, 

inicjowanie itp. */

 

 
  

/* Główna pętla programu */

 

  

while

(1) 

  {    
  

/* Instrukcje w pętli  */

 

  } 
}

Schematy połączeń

W poprzedniej części kursu przykłady uruchamiane były na układzie 
atmaga8, teraz, dla odmiany, będziemy wykorzystywać 
mikrokontroler atmega16. 

Jak widać na ilustracji poniżej, wybierając układ atmega16, mamy 
do dyspozycji cztery 8 bitowe porty we/wy (A,B,C,D). Ale w 
fabrycznie nowym atmega16 domyślnie jest włączony interfejs JTAG 
i w tej konfiguracji nie można wykorzystywać pinów PC2-PC5 jako 
cyfrowych wejść/wyjść. 

 

Układ atmega16

Page 20 of 38

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

17/04/2010

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

background image

 

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

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

 

Page 21 of 38

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

17/04/2010

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

background image

Schemat 3.2 - sposób przyłączenia do AVRa siedmiosegmentowego wyświetlacza LED. 

Poszczególne segmenty LED włączane są niskim stanem napięcia na portach 

mikrokontrolera. Taki, bezpośredni sposób przyłączenia wyświetlacza nie jest najlepszym 

rozwiązaniem (zależało mi na maksymalnym uproszczeniu schematu). Przez porty AVRa 

może przepływać jedynie niewielki prąd, a segmenty wyświetlacza zwykle zużywają 

znacznie więcej prądu niż małe diody LED, więc wyświetlane cyfry mogą być ledwie 

widoczne. 

 

Uwaga! W sprzedaży spotkać można dwa rodzaje siedmiosegmentowych wyświetlaczy led: 

o wspólnej anodzie i o wspólnej katodzie. 

Podobnie jak w poprzednio, będziemy bawić się przyłączając do 
portów mikrokontrolera diody LED, przyciski, przełączniki, buzzer. 

Poprzednio mikrokontroler oraz wszystkie wykorzystywane 
elementy elektroniczne umieszczałem na płytce stykowej, lecz 
montowanie za każdym razem wszystkiego od nowa przy zmianie 
schematu okazało się być uciążliwe, więc zdecydowałem dalej 
układać na płytce stykowej jedynie mikrokontroler i ewentualnie 
inne układy scalone współpracujące z mikrokontrolerem. A na 
osobnych, niewielkich kawałkach płytki drukowanej umieściłem 
takie często wykorzystywane części, jak: 

osiem diod LED;

siedmiosegmentowy wskaźnik LED;

cztery miniaturowe przyciski monostabilne;

buzer(z generatorem);

dwa przełączniki typu piano dip switch 8;

stabilizator napięcia 5V.

A w dalszej części kursu, na osobnych płytkach umieszczane będą 
klawiatura, różnego typu wyświetlacze oraz inne ciekawsze 
podzespoły. 

Page 22 of 38

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

17/04/2010

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

background image

 

Osiem diod LED wraz z rezystorami ograniczającymi prąd wlutowałem na osobnym 

kawałku płytki drukowanej. Diody będą przyłączane do wyprowadzeń mikrokontrolera na 

płytce stykowej giętkimi przewodami; do końców przewodów przylutowane są kawałki 

drutu (odcięte, fragmenty długich wyprowadzeń tranzystorów, rezystorów itp.) 

zabezpieczone przed oberwaniem elastyczną termokurczliwą koszulką. Wykorzystałem 

giętkie przewody ze starego kabla do drukarki, przewody ze skrętki komputerowej, 

którymi robię połączenia na płytce stykowej, tutaj się nie nadają, są zbyt sztywne. 

 

Siedmiosegmentowy wyświetlacz LED - też na osobnej płytce.

Page 23 of 38

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

17/04/2010

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

background image

 

Przyciski na osobnej płytce.

 

Przełączniki typu dip piano 2*8 też na osobnej płytce. 

Page 24 of 38

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

17/04/2010

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

background image

 

Buzzer z generatorem na osobnej płytce

 

Na osobnej płytce stabilizator 5V

Page 25 of 38

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

17/04/2010

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

background image

 

Przyłączenie kilku diod LED, przycisków czy buzzera do dowolnych wyprowadzeń 

mikrokontrolera na płytce stykowej zajmuje jedynie chwilkę.  

Kliknij w obrazek, aby powiększyć. 

Przykład pierwszy. 4 bitowy kalkulator.

Program wykonuje podstawowe operacje arytmetyczne na 
czterobitowych liczbach ze znakiem. Warto uruchomić ten 
programik żeby poćwiczyć kodowanie U2, jeśli ktoś jeszcze nie jest 
w tym biegły. 

Pierwsza liczba odczytywana jest z linii PD0..PD3, druga z 
PD4..PD7, wynik wyświetlany jest na ośmiu diodach LED 
przyłączonych do portu A. Rodzaj operacji wybiera się na liniach 
PB0..P3: 1-dodawanie, 2-odejmowanie, 3-mnożenie, 4-dzielenie 
całkowite, 5-obliczanie reszty z dzielenia liczb całkowitych. 

Page 26 of 38

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

17/04/2010

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

background image

 

Ilustracja działania programu "4 bitowy kalkulator". 

Dla wygody, do wejść mikrokontrolera podłączyłem przełączniki 
typu piano_dip_switch, które zwierają poszczególne wejścia uC z 
GND; oczywiście można zwierać wejścia uC na płytce stykowej do 
masy zwyczajnie, przewodami. Osiem diod LED podłączone zostały 
do wyprowadzeń portu A atmega16 na sposób: VCC->R->LED-
>PAx, czyli świecą się gdy odpowiedni bit w rejestrze PORTA ma 
wartość 0. 

 

Układ do uruchomienia programu "4-bitowy kalkulator" zmontowany na płytce stykowej. 

Kliknij w obrazek, aby powiększyć.

/*  
KURS AVRGCC, przykład 031 
4 bitowy kalkulator 
Progam wykonuje operacje artymetyczne (+,-,*,/)  

Page 27 of 38

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

17/04/2010

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

background image

na 4 bitowych liczbach ze znakiem. 
 
Układ ATmega16  
 
Wejścia: 
PD..PD3  - pierwsza liczba,  
PD4..PD7 - druga liczba, 
PB0..PB3 - wybór operacji  
 
Wyjścia 
PA0..PA7 - wynik operacji 
 
Do PA0..PA7 podłączone są diody LED na sposób:  
VCC->R->LED->PAx 
*/

 

 

#include <avr/io.h> 

 

int

 main(

void


  

/* Definicja zmiennych */

 

  

signed

 

char

 a,b; 

 
  

/* Wszystkie linie portu A wyjściami */

 

  DDRA  = 

0xFF

;   

 
  

/* Wszystkie linie portu D wejściami */

 

  DDRD  = 

0x00

;  

  PORTD = 

0XFF

;   

   
  

/* PB0..PB3 wejściami z podciągnięciem do Vcc */

 

  DDRB  = 

0x00

;  

  PORTB = 

0X0F

;     

 
  

/* Główna pętla programu */

 

  

while

(

1

  { 
    

/* W zmiennej 'a' zapisuje bity 0..3 z rejestru PIND */

 

    a = PIND & 

0x0f

  
    

/* w zmiennej 'b' bity 4..7 */

 

    b = PIND >> 

4

   

/* Rozszerza 4 bitowe liczby ze znakiem do 8 bitów. Jeśli bit nr 3  
zmiennej 'a' ma wartość 1, czyli odczytano liczbę ujemną,to  
bity 4..7 zmiennej 'a' też ustawiane są na wartość 1 */

 

    

if

(a & 

0x08

) a |= 

0xf0

 
    

if

(b & 

0x08

) b |= 

0xf0

     

/* Wybiera rodzaj operacji odczytując bity 0..3 portu B  */

 

    

switch

(PINB & 

0x0f

    {       
      

case

 

1

:          

//dodaje 

      

//PORTA = a+b;   // PAx->R->LED->GND     

      PORTA = ~(a+b); 

// VCC->R->LED->PAx 

      

break

       
      

case

 

2

:         

//odejmuje 

      

//PORTA = a-b; 

      PORTA = ~(a-b);  
      

break

;   

   
      

case

 

3

:         

//mnoży 

      

//PORTA = a*b; 

      PORTA = ~(a*b); 
      

break

       
      

case

 

4

:         

//dzieli 

      

//PORTA = a/b;    

      PORTA = ~(a/b); 
      

break

;     

   
      

case

 

5

:        

//oblicza resztę z dzielenia 

      

//PORTA = a%b;        

      PORTA = ~(a%b);  

Page 28 of 38

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

17/04/2010

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

background image

      

break

 
      

default

:      

// inne   

      

//PORTA = 0;   

      PORTA = ~(

0

);   

    } 
  } 

Listing 3.1 4-bitowy kalkulator

Przykład 2. Sygnalizacja świetlna

Po prostu sygnalizacja świetlna. Świecą się kolejno światła: zielone, 
żółte, czerwone, czerwone i żółte , i ponownie zielone; a w  
przypadku podania na wyprowadzenie PD0 napięcia GND, światło 
żółte pulsujące. 

 

Ilustracja działania programu "Sygnalizacja świetlna". 

/*  
 KURS AVRGCC przykład 032  
 Sygnalizacja świetlna  
  
 ATmega16  1MHz  
  
 Wyjścia:  
 PA0 - czerwone, PA1 - zółte, PA2 - zielone  
  
 Do PA0..PA2 podłączone są diody LED na sposób:  
 VCC->R->LED->PAx 
 
 wejścia 
 PD0 - GND na PD0 włącza pulsujące żółte 
*/

 

 

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

 
 

int

 main(

void


  

/* Deklaracja zmiennych */

 

  

unsigned

 

char

 i; 

 
  

/* PA0..PA2 - wyjścia */

 

  DDRA  = 

0x07

  PORTA = 

0x07

   
  

/* PD0 wejście */

 

  DDRD  = 

0x00

Page 29 of 38

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

17/04/2010

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

background image

  PORTD = 

0x01

   
  

/* Główna pętla programu */

 

  

while

(

1

  {  
    

/* Cztery fazy: zielone, żółte, czerwone, czerwone_żółte */

 

    

for

 (i= 

1

;i <= 

4

; i++)  

    { 
      

/* Jeśli na PD0 GND */

 

      

if

(!(PIND & 

0x01

)) i = 

0

;  

      
      

/* Wybór jednego z 5 wariantów */

 

      

switch

(i) 

      { 
        

case

 

1

:           

// Jeśli i=1, zapala się zielone 

        PORTA |= 

0x03

;    

// ustawia bity nr. 0,1  

        PORTA &= ~

0x04

;   

// kasuje bit nr. 2  

        _delay_ms(

6000

);  

//  czeka 6 sekund  

        

break

         
        

case

 

2

:           

// Jeśli i=2, zółte 

        PORTA |= 

0x05

;    

// ustawia bity nr. 2,0 

        PORTA &= ~

0x02

;    

// kasuje bit nr. 1 

        _delay_ms(

6000

);  

// 6 sek     

        

break

         
        

case

 

3

:           

// Jeśli i=3, czerwone 

        PORTA |= 

0x06

;    

// ustawia bity nr. 2,1  

        PORTA &= ~

0x01

;   

// kasuje bit nr. 0 

        _delay_ms(

6000

);           

        

break

         
        

case

 

4

:           

// Jeśli i=4, czerowne,zółte 

        PORTA |= 

0x01

;    

// ustawia  bit nr. 0 

        PORTA &= ~

0x03

;   

// kasuje bity nr. 0,1 

        _delay_ms(

6000

);           

        

break

   
        

default

:          

//w innym razie, żółte pulsujące 

        PORTA |= 

0x07

;    

// ustawia bity nr. 0,1,2 

        PORTA ^= 

0x02

;    

// odwraca  bit nr. 1 

        _delay_ms(

600

);   

// 0.6 sek 

        PORTA ^= 

0x02

;    

// odwraca  bit nr. 1 

        _delay_ms(

600

);   

// 0.6 sek 

      }                   
    } 
  } 

Listing 3.2 Synglizacja świetlna

Przykład 3. Elektroniczna kość do gry

Elektroniczna kość do gry. Po wciśnięciu i zwolnieniu przycisku na 
siedmio-segmentowym wyświetlaczu LED pokazują się cyfry: 
program liczy od 1 do losowo wybranej liczby z zakresu 1..6, 
animacja poniżej. 

Page 30 of 38

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

17/04/2010

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

background image

 

Ilustracja działania programu "Elektroniczna kość do gry". 

/* 
 Kurs AVRGCC, przykład 033 
 Elektroniczna kość do gry 
  
 ATmega16  1MHz  
  
 Wyjścia: 
 PA0..PA7 - wyświetlacz siedmiosegmentowy LED 
 VCC->LED->R->PAx 
  
 PB4 - buzzer z generatorem 
  
 Wejścia: 
 PC0 - przycisk zwierający do GND  
*/

 

 

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

 
/* 
Instrukcje zaczynają się znakiem hash "#", to polecenia 
preprocesora. Preprocesor języka C przeprowadza rozmaite 
operacje na tekście programu jeszcze przed rozpoczęciem 
właściwej kompilacji programu.  
Przykładowo pierwsze z poniższych poleceń zmienia w tekście  
programu wszystkie wystąpienia ciągu znaków LED_1 na 0x06, 
identycznie jak opcja "replace" w edytorze teksu. 
*/ 

 

//   -- A -- 
//  |       | 
//  F       B 
//  |       | 
//   -- G -- 
//  |       | 
//  E       C 
//  |       |  
//   -- D --   

 

#define LED_1   0x06; 

// 0000 0110 

#define LED_2   0x5b; 

// 0101 1011 

#define LED_3   0x4f; 

// 0100 1111 

#define LED_4   0x66; 

// 0110 0110 

#define LED_5   0x6d; 

// 0110 1101 

#define LED_6   0x7d; 

// 0111 1101 

 

int

 main(

void

Page 31 of 38

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

17/04/2010

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

background image

  

/* Definicja zmiennych */

 

  

unsigned

 

char

 i,n,l; 

 
  

/* Port A - wyjścia */

 

  DDRA  = 

0xFF

;   

  PORTA = 

0xFF

  
  

/* PC0 - wejście z podciągnięciem do VCC */

 

  DDRC  = 

0x00

;  

  PORTC = 

0X01

;   

   
  

/* PB4 - Wyjście */

 

  DDRB  = 

0x10

;  

  PORTB = 

0x00

;     

 
  

/* Główna pętla programu*/

 

  

while

(

1

  { 
      

/* Czeka na wciśnięcie przycisku */

 

      

/* Zmienna 'l' posłuży do uzyskania liczb losowych */

 

      

while

(PINC & 

0x01

) ++l; 

       
      

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

 

      _delay_ms(

140

);  

       
      

/* Czeka na zwolnienie przycisku*/

 

      

while

(!(PINC & 

0x01

)) l+=

2

;   

   
      

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

       

      _delay_ms(

140

);       

       
      

/* Pozyskanie liczby losowej z zakresu 1..6 */

 

      n = l % 

6

 + 

1

;       

 
      

/* Liczy od 1 do n */

 

      

for

(i=

1

; i <= n; i++) 

      {  
       

/* Wybór jednej z sześciu możliwości */

 

        

switch

(i) 

        { 
          

case

 

1

       

/* Wyświetla cyfrę 1. Uwaga, poszczególne 

       segmenty LED wyświetlacza świecą się przy 
       niskim stanie napięcia na portach uC, 
       dlatego użyto tu operatora "~" */

 

          PORTA = ~LED_1 
          

break

          

case

 

2

          PORTA = ~LED_2; 

// wyświetli 2 

          

break

          

case

 

3

          PORTA = ~LED_3; 

// 3 

          

break

          

case

 

4

          PORTA = ~LED_4; 

// 4 

          

break

          

case

 

5

          PORTA = ~LED_5; 

// 5  

          

break

          

case

 

6

          PORTA = ~LED_6; 

// 6 

      } 
      

/* Krótki sygnał dzwiękowy */

 

      PORTB |= 

0X10

;  

// Włącza buzzer 

      _delay_ms(

50

); 

      PORTB &= ~

0X10

// Wyłącza buzzer 

      _delay_ms(

450

); 

    } 
  } 

Listing 3.3 Elektroniczna kość do gry.

Page 32 of 38

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

17/04/2010

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

background image

Przykład 4. Kogut policyjny

Program steruje natężeniem świecenia dwóch kolorowych 
żaróweczek, powstaje efekt przypominający światło policyjnego 
koguta, animacja poniżej. Tym razem, dla większego efektu, 
zamiast diód LED, zastosowałem żaróweczki od latarki (4,5V 0.3A) 
przyłączone do portów we/wy mikrokontrolera za pośrednictwem 
układu scalonego ULN2803A. Żarówki zasilane są napięciem 
impulsowym (PWM), program zmieniając szerokość impulsu steruje 
natężeniem świecenia żaróweczek. Programik nie działa całkiem 
zgodnie z oczekiwaniem, lampki nie gasną całkowicie, gdy powinny, 
po prostu mikroprocesor atmega 1MHz jest zbyt wolny od tego 
kodu. Sygnał PWM lepiej generować sprzętowo wykorzystując 
układy czasowe mikrokrokotrolera albo pisać w asemblerze, 
oczywiście będziemy się tym tematem w dalszej części kursu. 

 

Ilustracja działania programu "Kogut policyjny". 

/* 
 Kurs AVRGCC, przykład 034  
 Kogut policyjny 
 
 Do linii PA0,PA1, za pośrednictwem układu ULN2803A, 
 przyłączone są dwie kolorowe żaróweczki (4,5V 0.3A). 
 Żarówki zasilane są napięciem impulsowym (ok 1KHz), 
 program, sterując szerokością impulsów zmienia  
 płynnie natężeniem światła żaróweczek. 
 
 ATmega16 1MHz  
 
 Wyjścia: PA0,PA1 
*/

 

 

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

 

int

 main(

void


  

/* Definicja zmiennych */

 

  

int

 t; 

   
  

/* P0,P1 -  wyjścia */

 

  DDRA  = 

0x03

;   

  PORTA = 

0x00

  
  

/* Główna pętla */

 

  

while

(

1

  {  
    

/* Pierwsza lampka stopniowo rozjaśnia się, 

Page 33 of 38

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

17/04/2010

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

background image

       druga stopniowo gaśnie 
    */

 

    

for

(t=

0

; t<=

768

; t+=

2

    {   
      PORTA |= 

0x01

;    

// ustawia bit nr 0 

      PORTA &= ~

0x02

;   

// kasuje bit nr 1     

      _delay_us(t); 

// opóźnienie w mikrosekundach 

 
      PORTA &= ~

0x01

;   

// kasuje bit nr 0 

      PORTA |= 

0x02

;    

// ustawia bit nr1      

      _delay_us(

768

-t); 

    }   
    

/* Pierwsza lampka stopniowo gaśnie, 

       druga stopniowo rozjaśnia się 
    */

     

    

for

(t=

768

; t>=

0

; t-=

2

    {   
      PORTA |= 

0x01

      PORTA &= ~

0x02

      _delay_us(t); 
      PORTA &= ~

0x01

      PORTA |= 

0x02

      _delay_us(

768

-t);   

    } 
  }   
}

Listing 3.4 Kogut policyjny

Przykład 5. Kluczyk - zabawka zręcznościowa 

Zabawa polega na prowadzeniu klucza nawleczonego na pętlę 
z drutu, trzeba tak przeprowadzić klucz na drugi koniec pętli by nie 
dotknął ani razu pętli. Wciśnięcie przycisku rozpoczyna zabawę. 
Każde zwarcie klucza z pętlą sygnalizowanie jest krótkim dzwiękiem 
z buzzera i przygaśnięciem jednej diody LED. Początkowo świecą się 
cztery diody LED, można popełnić cztery błędy, piąte zwarcie 
kończy zabawę co sygnalizowane jest długim dziwiękiem 
przerywanym. 

 

Ilustracja działania programu "Kluczyk zabawka zręcznościowa". 

Pętlę wykonałem z kawałka miedzianego drutu o średnicy ok 
2,5mm, klucz też, prawdziwe klucze raczej się nie nadają. Klucz 
przyłączyłem giętkim przewodem do masy, a pętlę do 
wyprowadzenia PB0 mikrokontrolera skonfigurowanego jako 

Page 34 of 38

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

17/04/2010

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

background image

wejście. Dodatkowo pętla została podciągnięta przez rezystor 1k do 
napięcia zasilania. Przy zetknięciu klucza z pętlą na wyprowadzeniu 
PB0 powinno pojawić się napięcie GND i wartość bitu nr. 0 
odczytana z rejestru PORTB będzie 0. Przycisk rozpoczynający grę 
przyłączony został między GND a wyprowadzenie PB1 
skonfigurowane jako wejście z wewnętrzny podciągnięciem do VCC, 
czyli przy wciśniętym przycisku wartość bitu nr. 1 odczytana 
z rejestru PORTB będzie 0. Diody LED zostały przyłączone do 
wyprowadzeń PD3..PD0 na sposób: VCC->R->LED->PDx, czyli 
świecą się gdy odpowiedni bit w rejestrze PORTD ma wartość 0. 
Buzzer został przyłączony poprzez tranzystor npn do 
wyprowadzenia PC0 i działa, gdy w rejestrze PORTC bit nr. 0 
zosanie ustawiony na wartość 1. 

 

Pętla z miedzianego drutu umocowania na kawałku deski. 

Page 35 of 38

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

17/04/2010

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

background image

 

Kluczyk, też wykonany z miedzianego drutu. 

/* 
 Kurs AVRGCC Przykład 035 
 Klucz - zabawka zręcznościowa  
 Zabawa polega na prowadzeniu klucza nawleczonego na pętlę 
 z drutu, trzeba tak przeprowadzić klucz na drugi koniec 
pętli by nie dotknął ani razu pętli. 
  
 Układ ATmega16 1MHz 
 
 wejścia: 
 PB0 - pętla z drutu 
 Dodatkowo pętla ma być podciągnięta przez rezystor 
 1k do Vcc; a klucz - połączony z GND. 
  
 PB1 - przycisk 
 
 wyjścia: 
 PC0 - buzzer 
  
 PD0..PD3 - diody LED przyłączone na sposób: 
 VCC->R->LED->PDx 
 
*/

 

 

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

 

int

 main(

void


  

/* Definicja zmiennych */

 

  

unsigned

 

char

  m ; 

    
 
  

/* Konfiguracja portów we/wy */

 

  DDRB  = 

0x00

;   

  PORTB = 

0x02

;   

 
  DDRC  = 

0x01

;   

  PORTC = 

0x00

   
  DDRD  = 

0x0F

;   

  PORTD = 

0x0F

;   

Page 36 of 38

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

17/04/2010

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

background image

   
  

/* Głóna pętla programu */

 

  

while

(

1

  {    
    

/* Oczekiwanie na wciśnięcie przycisku, 

    wciśnięcie przycisku rozpoczyna zabawę*/

 

    

while

(PINB & 

0X02

);  

     
    

/* Początkowa wartość zmiennej 'm' dwójkowo 0001 0000 */

 

    m = 

0x10

;   

 
    

/* Kasując bity nr 0..3 rejestru PORTD  

       zapala wszystkie 4 diody LED */

 

    PORTD &= 

0XF0

;   

     
    

/* Pętla wykonuje się dopóki m będzie różne od 0 */

 

    

while

(m) 

    { 
      

/* Jeśli nastąpi zwarcie klucza z pętlą */

 

      

if

(!(PINB & 

0X01

)) 

      {     
        

/* Krótki sygnał dzwiękowy */

 

        PORTC |= 

0x01

;  

// włącza buzzer 

        _delay_ms(

100

); 

// czeka 0.1s 

        PORTC &= ~

0x01

// wyłącza buzzer 

               
        

/* Dodatkowy czas na odsunięcie klucza */

 

        _delay_ms(

300

);  

   
        

/* Za każdym przejściem  przesuwa bity  

           w zmiennej 'm' o jedną pozycje w prawo  */

   

        m>>=

1

 
        

/* 0001 0000  początkowa wartość zmiennej 'm' */

 

        

/* 0000 1000  wartość 'm' po pierwszym przejściu  */

 

        

/* 0000 0100  po drugim przejściu  */

 

        

/* 0000 0010  po trzecim przejściu */

 

        

/* 0000 0001  po drugim przejściu */

 

        

/* 0000 0000  po piątym przejściu */

 

 
        

/* Za każdym przejściem gasi jedną diodę LED,  

           w kolejności od czwartej do pierwszej. 
           Diody LED przyłączone są na sposób: VCC->R->LED->PDx 
           i gasną, gdy odpowiedni bit w PORTD ma wartość 1 
        */

     

        PORTD |= m;   
      } 
    } 
   
   

/* Długi dzwięk przerywany sygnalizuje koniec zabawy */

 

    

for

(m=

0

; m<

12

; m++) 

    { 
      PORTC |= 

0x01

;  

// włącza buzzer 

      _delay_ms(

50

);  

// czeka 0.05s 

      PORTC &= ~

0x01

// wyłącza buzzer 

      _delay_ms(

50

);  

// czeka 0.05s 

    } 
  }  
}

Listing 3.5 Kluczyk - zabawka zręcznościowa.

Myślę, że zamieszczone w tej części przykładowe programy są na 
tyle proste, iż nie ma potrzeby objaśniać ich działania linia po linii; 
i że wystarczą animacje oraz komentarze dołączone w kodzie. Ale 
jeśli jest inaczej, to proszę dać znać co jest niezrozumiałe, wtedy 
dopiszę objaśnienia. 

W następnej części

Tematem następnej części kursu będą tablice i funkcje - czyli dalszy 
ciąg podstaw języka C. 

Page 37 of 38

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

17/04/2010

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

background image

Kurs AVR-GCC cz.4

 

 

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

Page 38 of 38

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

17/04/2010

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