Kurs AVR GCC cz 3


1 z 36
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 */
char temperatura;
;
;
;
2 z 36
/* 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: ąćęłńóśz\ ĆACÓŚyś.
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.
Rozmiar Wartość Wartość
Typ
(bitów) minimalna maksymalna
char 8
signed char 8 -128 127
unsigned char 8 0 255
short int 16 -32768 32767
unsigned short
16 0 65535
int
int 16 -32768 32767
unsigned int 16 0 65535
long int 32
-231 231-1
unsigned long
32 0
232-1
int
long long int 64
-263 263-1
unsigned long
64 0
264-1
long int
float 32
Ä…1.1810-38 Ä…3.41038
3 z 36
Rozmiar Wartość Wartość
Typ
(bitów) minimalna maksymalna
double 32
Ä…1.1810-38 Ä…3.41038
long double 32
Ä…1.1810-38 Ä…3.41038
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ą FAASZ, 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ą FAASZ.
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;
;
;
;
/* stała -1UL jest typu unsigned long int */
zz = -1UL;
= ;
= ;
= ;
4 z 36
/* 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
zró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
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 wskaznika 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.
5 z 36
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
6 z 36
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,2n-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 [-2n-1,2n-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
-----------------
7 z 36
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
%= ;
%= ;
%= ;
8 z 36
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
Sposób działania tych operatorów zale\y od tego, czy ++(--)
9 z 36
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ą FAASZ,
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 */
10 z 36
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
przykładach, wyjaśnię ró\nice w działaniu operatorów bitowych
11 z 36
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(FAASZ)
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,2n, to mo\na przesuwać bity. A
po co? Przecie\ sÄ… operatory arytmetyczne mno\enia i dzielenia: *,
/. Na przykład, \eby zoptymalizować program pod kątem szybkości.
12 z 36
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.
Priorytety i łączność operatorów
13 z 36
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 wskaznika) mają
wy\szy priorytet ni\ dwuargumentowe operatory & (bitowe AND)
i * (mno\enie).
Operatory Aą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
Lewostronna (prawostronna) łączność dwuargumentowych
operatorów oznacza, \e je\eli w wyra\eniu występuje więcej ni\
14 z 36
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
instrukcji, blok w "if-else" jest traktowany jako pojedyncza
instrukcja. Przykład:
signed char a, ;
,b;
, ;
, ;
15 z 36
/* 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( ==b) PORTA = 0x01; else PORTB = 0x0F;
(a== ) = ; = ;
( == ) = ; = ;
( == ) = ; = ;
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ę
instrukcje po słówku default, część default mo\na w instrukcji
switch pominąć. Przykład:
unsigned char a;
;
;
;
16 z 36
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( & 0x07)
(PINB )
( )
( )
{
{
{
{
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:
1. Oblicza wartość wyra\enia w nawiasach okrągłych.
2. 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.
17 z 36
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:
1. Wykonuje instrukcje wewnątrz pętli.
2. 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.
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:
1. Oblicza wyra\enie_1.
2. Oblicza wyra\enie_2.
3. 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.
4. Oblicza wyra\enie_3 i przechodzi do punktu 2.
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( = 0; i < 7; i++)
(i ; ; )
( ; ; )
( ; ; )
18 z 36
{
{
{
{
/* 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:
1. Zapisuje do zmiennej "i" wartość 0.
2. 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.
3. Wykonuje instrukcje w pętli.
4. Zwiększa wartość w zmiennej "i" o jeden.
5. Przechodzi do punktu 2.
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( j=0; i < n && PINC & 04; i++, j+=i)
(i=0, ; < && & ; )
( ; < && & ; )
( ; < && & ; )
{
{
{
{
/* 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( = 0; i < 99; i++)
(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( % 5 == 0) continue;
(i % ) ;
( % ) ;
( % ) ;
/* Dalsze instrukcje w pętli, pomijane, gdy zadziała
'continue' */
}
}
}
}
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)
( )
( )
{
{
{
{
19 z 36
for( = 0; i < 7; i++)
(i ; ; )
( ; ; )
( ; ; )
{
{
{
{
for( = 10; j >= 0; j--)
(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
/* Jeśli będą u\ywanie funkcje w rodzaju _delay_ms, _delay_us */
#include
int main( )
(void)
( )
( )
{
{
{
{
/* Instrukcje - wstępne ustawienia, konfiguracje,
inicjowanie itp. */
/* Główna pętla programu */
while(1)
(1)
(1)
(1)
{
{
{
{
20 z 36
/* 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
21 z 36
Schemat 3.1 - sposób przyłączenia do układu atmega16 zasilania, resetu i złącza
programatora. Kliknij w obrazek, \eby powiększyć.
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 wskaznik 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.
22 z 36
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.
23 z 36
Przyciski na osobnej płytce.
Przełączniki typu dip piano 2*8 te\ na osobnej płytce.
24 z 36
Buzzer z generatorem na osobnej płytce
Na osobnej płytce stabilizator 5V
25 z 36
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.
26 z 36
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 (+,-,*,/)
na 4 bitowych liczbach ze znakiem.
27 z 36
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
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( & 0x08) a |= 0xf0;
(a & ) |= ;
( & ) |= ;
( & ) |= ;
if( & 0x08) b |= 0xf0;
(b & ) |= ;
( & ) |= ;
( & ) |= ;
/* Wybiera rodzaj operacji odczytujÄ…c bity 0..3 portu B */
switch( & 0x0f)
(PINB & )
( & )
( & )
{
{
{
{
case 1: //dodaje
:
:
:
//PORTA = a+b; // PAx->R->LED->GND
PORTA = ~(a+ ); // VCC->R->LED->PAx
= ~( + );
= ~( +b);
= ~( + );
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);
= ~( % );
28 z 36
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 - czerwone
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
#include
int main( )
(void)
( )
( )
{
{
{
{
/* Deklaracja zmiennych */
unsigned char i;
;
;
;
/* PA0..PA2 - wyjścia */
DDRA = 0x07;
= ;
= ;
= ;
PORTA = 0x07;
= ;
= ;
= ;
/* PD0 wejście */
DDRD = 0x01;
= ;
= ;
= ;
29 z 36
PORTD = 0X01;
= ;
= ;
= ;
/* Główna pętla programu */
while( )
(1)
( )
( )
{
{
{
{
/* Cztery fazy: zielone, \ółte, czerwone, czerwone_\ółte */
for (i= 1; <= 4; i++)
( = ; <= ; ++)
( = ;i <= ; ++)
( = ; <= ; ++)
{
{
{
{
/* Jeśli na PD0 GND */
if(!( & 0x01)) i = 0;
(!(PIND & )) = ;
(!( & )) = ;
(!( & )) = ;
/* 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( ); // czeka 6 sekund
(6000);
( );
( );
break;
;
;
;
case 2: // Jeśli i=2, zółte
:
:
:
PORTA |= 0x05; // ustawia bity nr. 2,0
|= ;
|= ;
|= ;
PORTA &= ~0x02; // kasuje bit nr. 1
&= ~ ;
&= ~ ;
&= ~ ;
_delay_ms( ); // 6 sek
(6000);
( );
( );
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( ); // 0.6 sek
(600);
( );
( );
PORTA ^= 0x02; // odwraca bit nr. 1
^= ;
^= ;
^= ;
_delay_ms( ); // 0.6 sek
(600);
( );
( );
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
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.
30 z 36
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 sedmios-egmentowy LED
VCC->LED->R->PAx
PB4 - buzzer z generatorem
Wejścia:
PC0 - przycisk zwierajÄ…cy do GND
*/
#define F_CPU 1000000L
#include
#include
// -- 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)
( )
( )
{
{
{
{
/* Definicja zmiennych */
unsigned char i, ,l;
,n, ;
, , ;
, , ;
/* Port A - wyjścia */
DDRA = 0xFF;
= ;
= ;
= ;
PORTA = 0xFF;
= ;
= ;
= ;
/* PC0 - wejście z podciągnięciem do VCC */
DDRC = 0x00;
= ;
= ;
= ;
31 z 36
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( & 0x01) ++l;
(PINC & ) ++ ;
( & ) ++ ;
( & ) ++ ;
/* Czas na wygaśnięcie drgań styków przycisku */
_delay_ms( );
(140);
( );
( );
/* Czeka na zwolnienie przycisku*/
while(!( & 0x01)) l+= ;
(!(PINC & )) +=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( =1; i <= n; i++)
(i= ; <= ; ++)
( = ; <= ; ++)
( = ; <= ; ++)
{
{
{
{
/* Wybór jednej z sześciu mo\liwości */
switch( )
(i)
( )
( )
{
{
{
{
case 1:
:
:
:
PORTA = ~LED_1 // Wyświetla cyfrę 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.
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
32 z 36
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
#include
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ę,
druga stopniowo gaśnie
*/
for( =0; t<= ; t+= )
(t= ; <=768; +=2)
( = ; <= ; += )
( = ; <= ; += )
{
{
{
{
PORTA |= 0x01; // ustawia bit nr 0
|= ;
|= ;
|= ;
PORTA &= ~0x02; // kasuje bit nr 1
&= ~ ;
&= ~ ;
&= ~ ;
_delay_us( ); // opóznienie w mikrosekundach
(t);
( );
( );
PORTA &= ~0x01; // kasuje bit nr 0
&= ~ ;
&= ~ ;
&= ~ ;
PORTA |= 0x02; // ustawia bit nr1
|= ;
|= ;
|= ;
_delay_us( -t);
(768- );
( - );
( - );
}
}
}
}
/* Pierwsza lampka stopniowo gaśnie,
druga stopniowo rozjaśnia się
*/
33 z 36
for( =768; t>= ; t-= )
(t= ; >=0; -=2)
( = ; >= ; -= )
( = ; >= ; -= )
{
{
{
{
PORTA |= 0x01;
|= ;
|= ;
|= ;
PORTA &= ~0x02;
&= ~ ;
&= ~ ;
&= ~ ;
_delay_us( );
(t);
( );
( );
PORTA &= ~0x01;
&= ~ ;
&= ~ ;
&= ~ ;
PORTA |= 0x02;
|= ;
|= ;
|= ;
_delay_us( -t);
(768- );
( - );
( - );
}
}
}
}
}
}
}
}
}
}
}
}
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
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.
34 z 36
Pętla z miedzianego drutu umocowania na kawałku deski.
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
35 z 36
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
#include
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;
= ;
= ;
= ;
/* Głóna pętla programu */
while( )
(1)
( )
( )
{
{
{
{
/* Oczekiwanie na wciśnięcie przycisku,
wciśnięcie przycisku rozpoczyna zabawę*/
while( & 0X02);
(PINB & );
( & );
( & );
/* 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(!( & 0X01))
(!(PINB & ))
(!( & ))
(!( & ))
{
{
{
{
/* Krótki sygnał dzwiękowy */
PORTC |= 0x01; // włącza buzzer
|= ;
|= ;
|= ;
_delay_ms( ); // czeka 0.1s
(100);
( );
( );
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 */
36 z 36
/* 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( =0; m< ; m++)
(m= ; <12; ++)
( = ; < ; ++)
( = ; < ; ++)
{
{
{
{
PORTC |= 0x01; // włącza buzzer
|= ;
|= ;
|= ;
_delay_ms( ); // czeka 0.05s
(50);
( );
( );
PORTC &= ~0x01; // wyłącza buzzer
&= ~ ;
&= ~ ;
&= ~ ;
_delay_ms( ); // czeka 0.05s
(50);
( );
( );
}
}
}
}
}
}
}
}
}
}
}
}
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.
© 2009 ABXYZ Wszelkie prawa zastrze\one


Wyszukiwarka

Podobne podstrony:
Kurs AVR GCC cz 5
Kurs AVR GCC, cz 3
Kurs AVR GCC cz 2
Kurs AVR GCC cz 1
Kurs AVR GCC, cz 1
Kurs AVR GCC, cz 5
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