Uwaga! Ta strona wysyła Ci "ciasteczko".
Użytkowników online:
1
Kursy>Kurs AVR-GCC>Kurs AVR-GCC, cz.3
printer_icon

Kurs AVR-GCC cz.3

17.03.2009 ABXYZ

Baba Jaga 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; /* 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.

TypRozmiar (bitów)Wartość minimalnaWartość maksymalna
char8  
signed char8-128127
unsigned char80255
short int16-3276832767
unsigned short int16065535
int16-3276832767
unsigned int16065535
long int32-231231-1
unsigned long int320232-1
long long int64-263 263-1
unsigned long long int640 264-1
float32±1.18·10-38±3.4·1038
double32±1.18·10-38±3.4·1038
long double32±1.18·10-38±3.4·1038
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; /* 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.

obrazek
bitywartości w kodzie NKBwartości w kodzie U2
000000
000111
001022
001133
010044
010155
011066
011177
10008-8
10019-7
101010-6
101111-5
110012-4
110113-3
111014-2
111115-1
Kodowanie liczb na 4 bitach
obrazek
Kodowanie U2 na 4 bitach. Na kole lepiej widać niż w tabelce.
bitywartości w kodzie NKBwartości w kodzie U2
0000 000000
0000 000111
0000 001022
0000 001133
:::
0111 1101125125
0111 1110126126
0111 1111127127
1000 0000128-128
1000 0001129-127
1000 0010130-126
:::
1111 1101253-3
1111 1110254-2
1111 1111255-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 ----------------- 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 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 ++(--) 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 */ 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 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,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. 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

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)  sizeofprawostronna
*  /  %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ż 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; /* 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ę 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:

  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.

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

  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(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' */ }

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

obrazek
Układ atmega16
schemat z atmega16
Schemat 3.1 - sposób przyłączenia do układu atmega16 zasilania, resetu i złącza programatora. Kliknij w obrazek, żeby powiększyć.
obrazek
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.
obrazek
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.

obrazek
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.
obrazek
Siedmiosegmentowy wyświetlacz LED - też na osobnej płytce.
obrazek
Przyciski na osobnej płytce.
obrazek
Przełączniki typu dip piano 2*8 też na osobnej płytce.
obrazek
Buzzer z generatorem na osobnej płytce
obrazek
Na osobnej płytce stabilizator 5V
obrazek
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.

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

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

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

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

obrazek
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)
{
  /* 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.

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.

obrazek
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ę,
       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.

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

obrazek
Pętla z miedzianego drutu umocowania na kawałku deski.
obrazek
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;  
  
  /* 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.



Kurs AVR-GCC cz.2 | Kurs AVR-GCC cz.4



legenda

Komentarze (87)

Tom
11.10.2018 (22:02)
gość
Ciekawy skąd generuje się liczba losowa w przykładzie kostką.
n = l % 6 + 1;

1 modulo 6 zawsze chyba wynosi 1...

Proszę o wyjaśnienie. Pozdrawiam!
marmez
22.03.2016 (22:10)
gość
Dzień dobry

Przykład "syrena policyjna" nie chciał się u mnie skompilować, bo programator chciał stałej jako argument funkcji _delay_us(); obszedłem to trikiem podanym w książce T. Francuza, mianowicie stworzyłem funkcję:

void Czekaj_us(int us){
for(int i=1; i<us;i++){
_delay_us(1);
}
}

i w programie wywoływałem powyższą funkcję zamiast tej _delay_us(). Wtedy działało bardzo ładnie i diody oczy cieszyły :D

Dziękuję za bardzo dużo ciekawych i prostych przykładów :)

Pozdrawiam
zyxba
25.06.2015 (11:12)
gość
Jest to najbardziej zrozumiały kurs AVR-GCC
Tylko przyklady są za mało opisane komentarzami
Ale idzie wszystko zrozumieć
zxvcv
24.01.2015 (20:14)
gość
Wydaje mi się że w przykładzie z światłami jest błąd gdyż uruchomiony program zapala te diody które powinny byc wygaszone a wygacza te które powinny byc zapalone (chyba trzeba jeszcze za każdym razem ustawic dopełnienie jedynkowe wychodzących sygnałów)
Gość
12.03.2014 (23:17)
gość
Witam mam problem, z kalkulatorem, udało mi się uzyskać tylko "mignięcie" diodek przy załączaniu lub resecie, mimo różnorakich ustawień na przełącznikach dip-owych. Dodam iż korzystałem z udostępnionego programu. Pzdr
abxyz
02.03.2014 (17:17)
autor strony
Definicja zmiennej jest równocześnie deklaracją tej zmiennej, więc tworząc zmienną mogłem w komentarzu napisać deklaracja zmiennej albo definicja zmiennej.

K,
28.02.2014 (16:16)
gość
Witam czy może ktoś wytłumaczyć dlaczego w przykładzie 3.2 jest deklaracja zmiennej a w 3.5 definicja zmiennej ?
Rafael_159
18.06.2012 (19:06)
gość
Mam problem w programie z kalkulatorem. Mianowicie diody w ogóle się nie świecą. Natomiast jeśli zamienię:
"switch(PINB & 0x0f)" na "switch(~PINB & 0x0f)" to działa poprawnie. Mógłby mi ktoś wytłumaczyć??
arek
23.04.2012 (11:56)
gość
Na schemacie 3.1 jest pomyłka w układzie stabilizującym napięcie jest podwójnie wczepiony kondensator C3
bobeusz
07.01.2012 (22:05)
gość
Witam,
Języka C uczę się krótko głównie z tego kursu, po przeczytaniu 3 części kursu chciałem zrobić coś w stylu koguta policyjnego na diodzie rgb. Planowałem użyć sprzętowego PWM na ATMega 162 z zegarem 8MHz ale jakoś to mi nie idzie. Wiem że google nie gryzie, szukałem też w datasheet od atmela ale nie dowiedziałem się niczego. Moje pytanie brzmi jak skonfigurować wyjścia aby pojawił się na nich sygnał PWM i który port procesora jest za to odpowiedzialny.
Czy może mi ktos pomóc?
beatels4
23.12.2011 (18:18)
gość
Teraz wszystko jasne ;). Dzięki.
A może jeszcze takie pytanie. Do jakiej wartości może wzrosnąć zmienna 'l'?
abxyz
22.12.2011 (23:33)
autor strony
Zmienna 'l' zmienia się swoją wartość w pętli, gdy program czeka na wciśnięcie przycisku

/* Czeka na wciśnięcie przycisku */
while(PINC & 0x01) ++l;

Natomiast znak procent "%" to operator reszty z dzielenia całkowitego,
przykładowo niech
l = 25
wtedy wyrażenie
l%6
ma wartość
1
bo
25/6 = 4 i reszta 1 ( dzielenie całkowite)


beatels4
22.12.2011 (18:15)
gość
Nie rozumiem sposobu znajdowania 'liczby losowej'.
Nie umiem pojąć kiedy zmienna 'l' zmienia swoją wartość i w jaki sposób działa tu polecenie '%6+1'.
Uruchamiałem program kilka razy, czytałem program i dalej nic z tego nie wiem.
abxyz
21.12.2011 (21:05)
autor strony
A konkretnie, co "nie pasuje" ?
beatels4
21.12.2011 (20:10)
gość
Mam problem. Nie rozumiem zasady działania programu 'elektroniczna kość do gry'. Czytałem go wiele razy i ciągle mi coś nie pasuje.
Prosiłbym o objaśnienie go krok po kroku.
Mateusz
Matis
08.05.2011 (13:40)
gość
Świetny kurs!! 1000 razy lepszy niż ten który był w Elektronice dla Wszystkich ;)
Radek
25.12.2010 (23:00)
gość
/* Definicja zmiennej 'temperatura' typu char */ char temperatura;

Proponuję zmienić na deklaracja.
Jeżeli byłoby char temperatura='c'; to bylaby definicja. Deklaracja to zapowiedź. Więc gdzie nie inicjujesz czegoś to deklarujesz. Mala różnica, ale bądźmy precyzyjni.:) Kurs bardzo fajny jak dla początkujących
hexen
10.08.2010 (22:02)
użytkownik
ano faktycznie nie doczytałem :) zwracam honor ;) !!!
a czy nie lepiej było to zrobić programowo ;) ?
abxyz
07.08.2010 (21:10)
autor strony
Nie ma błędu. Pętla została podciągnięta przez rezystor 1k do napięcia zasilania - wspomniałem o ty przy opisie piątego przykładu
hexen
07.08.2010 (15:50)
użytkownik
analizując program z zabawką zręcznościową coś mi niepasowało do tego stopnia iż postanowiłem odpalić przykład :) No i okazało się że faktycznie wykryłem błąd ;P PB0 nie jest podciągnięte do plusa w związku z tym wisi w powietrzu i w zmontowanym układzie dzieją się cuda w postaci samocznnego rozwiązywania układu :D

prawidłowo powinno to wyglądać tak:

/* Konfiguracja portów we/wy */
DDRB = 0x00;
PORTB = 0x03;

reszta działa OK :) układ uruchamiany na Atmega32 z wewn. RC 8MHz

Pozdrowienia dla Autora oraz wszystkich użytkowników. Wbrew niektórych komentarzy pochwalam autora za solidne przygotowanie podwalin z języka C, znając go dobrze potem przejdzie się łatwo i przyjemnie do omawiania poszczególnych sprzętowych funkcji uC
hexen
20.07.2010 (20:12)
użytkownik
poniższy koment nie aktualny... ruszyło w końcu (dipswitch nie kontaktował na płytce stykowej). Pozdrowienia
hexen
20.07.2010 (20:00)
gość
"..gdy wgrywam program do układu to dla mojego kalkulatora 0+0=1 i każde inne argumenty dają w wyniku o jeden więcej. hmm... gdy w "case" zamiast dodawania wyświetlę argument a albo b to są one poprawne. Sfałszowany jest jedynie wynik działania.."

mam identyczny objaw jak pisał kolega Darek :( kod przeklejony na żywo z kursu i są problemy :/ Ktoś wie w czym może tkwić problem ?
układy uruchamiam na ATmega32
Rumcajs
23.06.2010 (08:33)
gość
/* stała -1UL jest typu unsigned long int */
zz = -1UL;
Nie wiem jak to potraktuje kompilator, ale unsigned long int to liczba bez znaku, a tu minusik. To taka mała dygresja, pozdrawiam i kibicuję :-)
ABXYZ
24.04.2010 (19:49)
autor strony
W datasheet atmega16 zalecają włączenie kondensatora między wyprowadzenie AREF a masę, aby ograniczyć wpływ zakłóceń na działanie przetwornika ADC.
AREF można by pozostawić nie podłączony, jeśli przetwornika nie jest używany.
sorontar
24.04.2010 (16:40)
gość
A dlaczego w ATMega 16 pin AREF jest połączony kondensatorem do masy? nie powinien byc zwarty z pinem AVCC(takie połączenie widziałem).
Pozdrawiam
skynet
17.04.2010 (02:28)
gość
Dzięki wielkie :)
Lecz procedura ta nie działa kiedy po dłącze 5 kogutów i 5 przycisków.
Pomału sobie radze, ale kiedy juz jedego koguta odpalę, reszta przycisków nie odpowiada załanczając kolejno koguty ;/
ABXYZ
16.04.2010 (12:10)
autor strony
Po prostu użyj instrukcji if-else.

while()
{
if( warunek )
{
/* działa kogut 1 */
}
else
{
/* działa kogut 2 */
}
}
skynet
15.04.2010 (03:55)
gość
Witam. Dołączam sie do większości ludzi korzystających z kursu, otóż jest wprost niebywale fantastyczny !
Mam dziwne pytanie (albo i nie dziwne):
Jakiej funkcji i w którym miejscu aby podłączyć drugi komplet żarówek i do tego 2 switcha, tak, aby wcisniecie jednego powodowało uruchomienie programu kogut a nacisniecie sw2 uruchomienie drugiego koguta dołączonego do innego portu, ale tak aby podczas dzałania drugiego pierwszy sie wyłączył. I tak na przemian.
Napisałm program, ale niestety po resecie uC zadziałą tylko jeden z nich. żeby właczyc koguta drygiego musze zresetowac proca i dopiero wciskając drugiego switcha uruchomic drugiego koguta, a chciałem aby działo sie to bez resetowania. Poprostu: właczam jeden to drugi sie wyłacza i na odwrót. ?? siedze i kombinuje ale nie wychodzi ;/
darek
09.04.2010 (20:33)
gość
No to niezła skuchę odwaliłem (;
Dzięki za sugestie
ABXYZ
09.04.2010 (01:38)
autor strony
Zauważ, że jeżeli któreś wejście AVRa jest zwarte do masy, to z rejestru PINx odczytujemy wartość odpowiedniego bitu równą zero, a nie jeden.
Zatem, przykładowo, gdy chcesz wybrać operacje dodawanie (1), to zwierasz wyprowadzenia PB1..PB3 z masą, zaś PB0 pozostawiasz rozwarte.

Druga rzecz. W tym przykładzie diody LED są przyłączone w następujący sposób:
VCC->R-LED->PAx
czyli LED świeci się, gdy odpowiedni bit w PORTA ustawi się na wartość 0.
darek
08.04.2010 (23:53)
gość
SZACUN dla autora!
Siedzę już drugi wieczór nad kalkulatorem i nie mogę go skłonić do poprawnego liczenia. Tzn. kod jest dobry bo sprawdziłem wszystko w symulatorze avrstudio, ale gdy wgrywam program do układu to dla mojego kalkulatora 0+0=1 i każde inne argumenty dają w wyniku o jeden więcej. hmm... gdy w "case" zamiast dodawania wyświetlę argument a albo b to są one poprawne. Sfałszowany jest jedynie wynik działania.

Układ mam zbudowany na atmedze32.W kodzie, żeby zadziałał case musiałem odwrócić bity switcha: (~(PINB) & 0x0f) w innym przypadku zawsze uciekał na default.
patrze na dataschet tego układu i nie dostrzegam jakiejś istotnej różnicy, która mogłaby spowodować taki babol. macie może jakieś sugestie?
20rafalo
03.04.2010 (10:50)
gość
Witam
Fajnie by było, aby do w każdej następnej części kursu począwszy od 5, oprócz podstaw "C" autor wyjaśniał działanie po 1 specjalnej funkcji uC np XTAL, TMS itd i ich zastosowanie.
Do języka C znajdziemy wiele dobrych książek, zaś do uC już ciężko dla początkujących.

Dlatego prosił bym aby autor nakładał trochę większy nacisk na uC, oczywiście tłumaczenie "C" pozostawił bez zmian.
Dopiero gdy by doszły wskaźniki, klasy położyć nacisk równomiernie na "C++"; i uC

Sporo wymagam, ale myślę, że dla autora to nie problem :-)
Bylibyśmy wdzięczni.
Co o tym myślicie?

Jeszcze raz wielkie dzięki za kurs.
20rafalo
02.04.2010 (16:30)
gość
Drobny szczegół:
W przykładzie - sygnalizacja świetlna. W komentarzu powinno być PA2- zielone.

Super kurs.

Przydało by się trochę więcej szczegółowych komentarzy. Jak to mawiano w firmie IBM - każda linijka komentarza to linijka kodu :-)
Wiem, że komentarze traktuje się jako drugorzędna sprawa, ale z doświadczenia wiem, że pisząc mało wyjaśnień we własnym programiku potem żałuje. A już nie mówię, jak by ktoś inny to analizował.
Pozdrawiam
ABXYZ
01.04.2010 (22:21)
autor strony
Gościu, ja w tym przykładzie nie znalazłem żadnego błędu :)
gość
01.04.2010 (19:15)
gość
4-bitowy kalkulator nie działa poprawnie - jest tylko mrugnięcie wszystkich diod przy starcie.
Diody do vcc przez PORTA;różne konfig przełączników do masy przez PORTD; "wybór operacji" przez portB wybrany do masy.

Innym działa program?
Zuku
24.02.2010 (16:07)
gość
Witam
Świetny kurs, dzięki ci za to ;)
ABXYZ
10.02.2010 (21:55)
autor strony
Tak, już to poprawiłem. Wszystkie przykłady z kursu wcześniej uruchamiam, jednak, przy edycji strony, zwykłe dopisuję komentarze, usuwam puste linie, wprowadzam drobne zmiany, dlatego taki błąd miał okazję się pojawić. W następnych artykułach, już po przetestowaniu przykładowego programiku, nie będę wprowadzał żadnych zmian.

Super, że internauci dokładnie czytają te artykuły!
mmm
10.02.2010 (15:49)
gość
Bardzo dobry kurs! Jednakże mam pytanie co do sygnalizacji swietlnej:
Czy zawarty w programie zapis:
/* PD0 wejście */
DDRD = 0x01;
nie konfiguruje PD0 jako wyjście?
Marcin
21.11.2009 (22:01)
gość
Witam
Czy szanowny autor kursu mógłby umieścić schemat podłączenie wyświetlacza LED? bo mam problem z jego podłączniem.
Pozdrawiam
ABXYZ
09.09.2009 (00:10)
autor strony
Wszystkie przykładowe programy z tej części kursu były uruchamiane na atmega16 1MHz, ale z atmega128 też powinny działać.
Mirek
07.09.2009 (23:41)
gość
Witam witam :) nie to żebym się czepiała ale w sygnalizacji jest zły opis z kodu wynika że żółta dioda zapali się poprzez PD0 a Ty opisałeś jako PB0 taka drobna pomyłka :) aha i mam jeszcze pytanie ponieważ mam ATmege 128 i nie każdy przykład z tych ćwiczeń działa może masz jakiś pomysł by to rozwiązać ? pozdrawiam
PolGraphic
01.09.2009 (22:43)
gość
Bardzo dobrze przygotowany kurs :-)
Ja np. znam język C, ale z AVR nie miałem dotąd żadnej styczności, tak więc bardzo mi się on przydaje.

Pokłon za trud włożony w napisanie tak obszernego kursu i podzielenie się nim z nami :)

Howgh
Emu
31.08.2009 (11:04)
gość
Kurs na wysokim poziomie :-). Wszystko przedstawione w bardzo przystępny sposób. Wielki krok aby oderwać ludzi od innych "języków" programowania :-)
mikro
13.08.2009 (22:11)
gość
Kurs rewela!! lepszego nie znalazlem w necie. Prosto i zrozumiale a jednoczesnie konkretnie! czekam z niecierpliwoscia na dalsza czesc! i gorące dzieki za te 3 :)
sokool
10.08.2009 (22:08)
gość
Świetny kurs:) A będzie coś na temat programowania ADC w atmedze8?
Kamil
11.07.2009 (09:36)
gość
Świetny kurs! Czegoś takiego jak ten kurs jeszcze nie było, początki zawsze są najtrudniejsze, również w programowaniu - dzieki temu kursowi stają się dużo łatwiejsze!

Dzięki!
Rafał
07.07.2009 (13:41)
gość
Bardzo ciekawie i rzeczowo napisany kurs. Przyznam że z miłą chęcią go studiuję i coraz bardziej przekonuję się do mikrokontrolerów i języka C. Z niecierpliwością czekam na dalsze części i życzę wytrwałości w pisaniu kolejnych części, a w przyszłości kto wie, być moze nawet ciekawej książki...
ABXYZ
04.07.2009 (16:52)
autor strony
Planuje w wakacje napisać przynajmniej dwie części tego kursu, ale kolejna, czwarta część nie pojawi się wcześniej niż za trzy tygodnie. Po wakacjach, od września, zamierzam publikować kolejne części kursu co miesiąc.

Jednak to są jedynie plany, jak będzie to się okaże :)
gość niedzielny
04.07.2009 (00:47)
gość
Zaglądam tu co kilka dni z nadzieją na część IV :)
R E W E L A C J A !!!
kiedyś na studiach otarłem się o 51 i 2051
po latach wracam z sentymentem do uC
czekam z niecierpliwością na C.D.N. ;)
Sparagus
03.07.2009 (15:54)
gość
Kiedy następna część ??
ABXYZ
02.07.2009 (22:29)
autor strony
W pakiecie WinAVR kompilator avr-gcc pozwala używać stałych w postaci binarnej, istnieje specjalny patch dla avr-gcc z takim rozszerzeniem. Jednak nie jest to zgodne ze standardem języka C. Myślę, że lepiej trzymać się standardów :)
Matys
02.07.2009 (21:01)
gość
"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."
u mnie da się w binarnym
x = 0b01010101;
lub
x = 0B10101010;
miszczu
01.07.2009 (22:54)
gość
A ja mam moim zdaniem ciekawy pomysł na programik.....
Może by tak napisać jakiś prosty programik dla avr-ka do obsługi wyświetlacza do starych noki.Są one łatwo dostępne,cena niewygórowana, a frajda ze zrobienia czegoś takiego byłaby duża...
matys
25.06.2009 (13:45)
gość
Świetna sprawa. Tyle szukałem jakiegoś kursu dla początkujących w c, ale nigdzie nic nie znalazłem, a ten jest po prostu świetny.
Gratuluje super roboty i czekam na następne części.
Atrax
18.05.2009 (22:58)
gość
Bardzo fajny kurs, ale też uważam, że troszkę zbyt dużo uwagi poświęcasz samemu C.
Ja osobiście chętniej bym widział więcej opisów procedur obsługi peryferiów uC.
Tak czy inaczej gratuluję kursu i pozdrawiam :)
Northwind
17.05.2009 (19:31)
gość
I dobrze ze uniwerslany jezyk C.

Pozdrawiam
Northwind
17.05.2009 (19:09)
gość
Kawał dobrej roboty. Napisane zrozumiałym jezykiem. Mam nadzieje ze bedzie wiecej...
granat
13.05.2009 (16:36)
gość
Kurs ogólnie spoko, ale wg mnie niepotrzebnie poświęcasz się językowi C do którego jest pełno kursów w necie. Wg mnie powinieneś bardziej się skupić na bibliotekach C dla uC i właściwościach szczególnych języka w zastosowaniach wbudowanych. Ogólnie spoko, szacun, pozdrawiam
PrzemoKRK
10.05.2009 (12:27)
gość
Fajny kurs napisany w niezwykle przystępny sposób . Gratulacje dla autora . Ponowie pytanie poprzednika. Kiedy można się spodziewać kolejnej części kursu ?
bob
04.05.2009 (23:53)
gość
Nie żebym pośpieszał, ale kiedy możemy spodziewać się kolejnego artykułu tego wspaniałego kursu??
ABXYZ
20.04.2009 (18:29)
autor strony
W kolejnej części kursu, przy temacie funkcji, będzie też przykład użycia przerwań zewnętrznych.
waldemar
19.04.2009 (23:30)
gość
Przyłączam się do prośby o omówienie przerwań. W assemblerze przerwania jakoś bardziej intuicyjnie się robi. W C jakoś nie mogę dojść co i jak.
electro
15.04.2009 (14:51)
gość
Jaki 8-bitowy uC AVR ma najszybsze taktowanie?

Ja nie widziałem nic większego niż 16MHZ.
elemn
10.04.2009 (21:14)
gość
Nie, niestety VMLAB może symulować tylko program uC a drugiego nie mogę zainstalować, chodzi mi o coś takiego jak LIVEWIRE, CROOCODILS ClIPS czyli o symulator obwodów z możliwością włączenia w układ uC z programem.
ABXYZ
10.04.2009 (13:19)
autor strony
Na przykład:

Proteus
http://www.labcenter.co.uk

VMLAB (free)
http://www.amctools.com/
elemn
10.04.2009 (10:09)
gość
Czy istniene jakiś program który może symulować pracę uC w układzie? Chodzi mi o coś jak LiveWire tylko z tą symulacja.
spioch
08.04.2009 (22:36)
gość
Z tym wyświetlaczem 7segmentwym to troche nie działa bynajmniej u mnie mam atmege8 a wydajność prądowa jest zbyt mała (max 4 ledy na raz zapala). Skoro mają być podstawy C to może odrazu o mówił byś przerwania przy okazji omawiania funkcji (mozna zrobić zegarek binarny czy coś w tym stylu)
sebi
04.04.2009 (16:54)
gość
Witam, mam pytanie: czy mogę używać wszystkich potów poza reset jako wy\we?
pawel
03.04.2009 (12:46)
gość
mam taki pomysł żeby po każdej części kursu była jakieś zadania do rozwiązania aby karzdy mógł się sprawdzić
ABXYZ
01.04.2009 (23:42)
autor strony
Tak jak napisałem, program "Kogut policyjny" nie jest doskonały, z przyłączonym kolorowymi żaróweczkami uzyskałem nawet całkiem ładny efekt, jak na animacji; z diodami LED nie próbowałem, być może w ogóle niewiele będzie widać. To nie jest istotne, tematem artykułu nie jest PWM, tematem są podstawy języka C.

ULN2803A nie robi tu nic mądrego, jedynie wzmacnia prąd, użyte żaróweczki zasilane są znacznie większym prądem (0.3A, 4,5V) niż diody LED (do zasilania LED wystarczy kilka mA). Wiadomo, nie można żaróweczek przyłączyć bezpośrednio do portów mikrokontrolera. W datasheet atmega16 można znaleźć maksymalne, dopuszczalne wartości prądów dla portów we/wy.
student
01.04.2009 (20:01)
gość
Czy testował ktoś program "kogut policyjny" bo u mnie nie działa. Diody cały czas świecą i jedynie mrugną co 3 sekundy. Chyba ze diody też muszę podłączyć przez układ ULN2803A
shadow_man
24.03.2009 (22:27)
gość
Ja już chyba trochę zrozumiałem ;) Najpierw ABXYZ przedstawi nam język C, dopiero potem pokarze jak zabrać się za bajery wbudowane w uC.
Spioch
24.03.2009 (20:22)
gość
Mam pytanie odnośnie publikacji kolejnej części kursu. Bardzo mnie interesuje wykorzystanie PWM&#039;a i sterowanie wyświetlaczem LCD (graficzny i alfanumeryczny). Świetny kurs pozdrawiam.
ABXYZ
23.03.2009 (00:50)
autor strony
Nie ma takiej potrzeby, dorobię opcję "Wersja do druku" i będzie można wydrukować z przeglądarki do pliku pdf.
Roman
20.03.2009 (16:48)
gość
2Mich:
... a program PDFCreator znasz?
marek
20.03.2009 (14:36)
gość
czy przy przesunięciu w prawo nie powinno być 85/8=10
Roman
20.03.2009 (07:00)
gość
Cześć,
fajny kurs.
Ale znalazłem błąd?
W miejscu gdzie mówi się o mnożeniu/dzieleniu razy/przez 2,4,8 itd jest na drugim rządku przykładu błąd:
Masz:
3 * 4 = 6
00000011 << 2 = 00000110

a wg mnie ma być, jeśli dobrze zrozumiałem:

3 * 4 = 12
00000011 << 2 = 00001100
ABXYZ
20.03.2009 (00:05)
autor strony
Zależy od standardu, istnieją co najmniej dwa standardy języka C, w skrócie C89 i C99(nowszy). W C89 nie można używać zmiennych typu bool, natomiast w C99 można, trzeba tylko na początku programu dopisać linię: #include&lt;stdbool.h&gt;

Chociaż GCC jest częściowo zgody z C99, to zdecydowałem, że w kursie będę się trzymał standardu C89 oraz że nie będę stosował żadnych niestandardowych rozszerzeń, jak na przykład zapis stałych w postaci dwójkowej. Myślę, że tak będzie lepiej :) Z jednym mały wyjątkiem, będę używał komentarzy liniowych - tych zaczynających się od znaków // i rozciągających się do końca linii. W C89 można pisać tylko komentarze blokowe /* komentarz */, a w C99 można stosować oba rodzaje komentarzy.

Niewykluczone, że przy okazji omawiania poszczególnych elementów języka C będę opisywać ewentualne różnice między C89 i C99.
Ja
19.03.2009 (21:25)
gość
A nie ma zmiennej bool do wartości logicznych??
ABXYZ
19.03.2009 (20:23)
autor strony
Nie. Tematem tej części kursu nie są układy czasowe AVRa, teraz przerabiamy podstawy języka C i przykłady są odpowiednie do tematu. Trzeba poznać podstawy, aby wiedzieć co się robi :)
shadow_man
19.03.2009 (19:18)
gość
Hmm... A czy nie lepiej było by użyć sprzętowego PWM ? No ale dobra nie czepiam się ;) Jak zwykle przeczytałem z ciekawością:)
Special11
19.03.2009 (15:33)
gość
Genialny kursik:)

Pozdro
ciekawy
19.03.2009 (10:00)
gość
Witam, nowa odsłona kursu świetna, ale mam taką małą prośbę czy mógłby Pan zamieścić jakieś schematy do tych małych płytek z diodami, buzzerem, wyświetlaczem.., bo nie jestem elektronikiem i nie wiem jak to do końca ma być zrobione, a po tych zdjęciach nie za bardzo widać ścieżki, ani jakie rezystory są używane (wiem że to się jakoś sprawdza, ale nie wiem jak), z góry dziękuję.
pasta20
17.03.2009 (17:32)
gość
Wszystko fajnie szkoda tylko ,że ten kursik tak powoli idzie do przodu :( Fajnie było by jakby się szybciej rozwijał.

Pozdrawia Oby tak dalej ;P
waad
16.03.2009 (23:41)
gość
Jest 23.30 wchodzę na stronę i ... JEST !!!

Dzięki wielkie.
Mich
16.03.2009 (22:10)
gość
Super kolejna część kursu!:) A byłaby możliwość udostępnienia kursu również jako PDF (chodzi o wersje do druku)?
Kursy>Kurs AVR-GCC>Kurs AVR-GCC, cz.3
Ostatnie artykuły