Programowanie mikrokontrolerów AVR z rodziny ATmega. Materiały pomocnicze Jakub Malewicz jakub.malewicz@pwr.wroc.pl Wszelkie prawa zastrzeżone. Kopiowanie w całości lub w częściach bez zgody i wiedzy autora surowo zabronione. Strona1
Podstawy języka C char c; int i; float f: double d; o Zmienne // zmienna 8-bitowa // zmienna 16- lub 32-bitowa stałoprzecinkowa // zmienna 32-bitowa zmiennoprzecinkowa // zmienna 64-bitowa zmiennoprzecinkowa MODYFIKATORY short int i; long int i; signed int; 1 // zmienna 16-bitowa stałoprzecinkowa // zmienna 32-bitowa stałoprzecinkowa // zmienna ze znakiem unsigned int; // zmienna bez znaku o Sposoby zapisu liczb w języku C: unsigned char x = 15; // liczba w kodzie dziesiętnym unsigned char x = 0b00001111; unsigned char x = 0x0f; // liczba w kodzie binarnym // liczba w kodzie heksadecymalnym o Operacje na zmiennych Na zmiennych można wykonywać wszystkie proste operacje arytmetyczne: dodawanie, odejmowanie, mnożenie, dzielenie, wynikiem jest liczba. Zmienne można także porównywać, co jest wykorzystywane głównie w instrukcjach sterujących. Wynikiem porównania jest prawda lub fałsz, które również są wartościami liczbowymi: fałsz ma wartość 0, prawda to wartość różna od 0. c = a + b; c = a - b; c = a * b; c = a / b; // jeśli tylko modyfikujemy zmienną np.: a=a+3; to możemy zapisać a+=3; // j.w. b=b-4; to możemy zapisać b-=4; // j.w. b=b*2; to możemy zapisać b*=2; // j.w. b=b/10; to możemy zapisać b/=10; 1 Modyfikacji ulega nie tylko typ (znak lub jego brak), ale i zakres. Przykład: zmienna int (załóżmy że jest 16-bitowa) pozwala na zapis liczb z zakresu -32 768 32 767 dla typu signed i 0 65535 dla typu unsigned. Strona2
i++; i--; a>b a<b a==b a!=b // i=i+1; // i=i-1; // jeśli a większe od b, to wyrażenie jest prawdą // jeśli a mniejsze od b, to wyrażenie jest prawdą // jeśli a jest równe b, to wyrażenie jest prawdą // jeśli a jest różne od b, to wyrażenie jest prawdą o Instrukcje sterujące Instrukcje pozwalające sterować kierunkiem i sposobem wykonywania programu, a także umożliwiające jego rozgałęzianie. if else if (warunek) // jeśli warunek jest spełniony (jest prawdą), to wykonaj // zestaw A, w przeciwnym przypadku zestaw B // zestaw czynności A else 2 // zestaw czynności B PRZYKŁAD if(a>b) PORTA = 0x01; else PORTA = 0x02; if(a==b) PORTB = 0x00; 2 Ta część instrukcji warunkowej jest opcjonalna Strona3
switch case switch (a) case wartosc_1: case wartosc_2: default: // wybiera case, który odpowiada aktualnej wartości a // wykonaj zestaw czynności A break: // wykonaj zestaw czynności B break; // wykonaj domyślny zestaw czynności PRZYKŁAD switch (a) case 1: PORTD = 1; break; case 2: PORTD = 2; break; default: PORTD = 0; for // nadaje zmiennej wartość początkową i wykonuje pętlę tak długo, // jak długo spełniony jest warunek trwania, // modyfikator wykonuje operacje na zmiennej po każdym przebiegu pętli for (wartosc_poczatkowa;warunek_trwania; modyfikator_zmiennej_sterujacej) // wykonaj zestaw czynnosci PRZYKŁAD for(i=0;i<6;i++) PORTD = PORTD+1; delay_ms(ms); while / do while while (warunek) // wykonaj zestaw czynnosci // jeśli warunek jest spełniony, to wykonuj poniższe do // wykonaj zestaw czynnosci while (warunek) // wykonuj poniższe // jeśli warunek jest spełniony, to wykonuj powyższe Strona4
PRZYKŁAD while(a<5) PORTB = a; a++; do PORTB = a; a++; while (a<5); o Operatory logiczne Są przydane przy konstruowania złożonych warunków dla instrukcji sterujących. && - iloczyn logiczny (warunek_1)&&(warunek_2) PRZYKŁAD If ( (a>3) && (a<5) ) PORTB = 4; // jeśli warunek pierwszy i drugi jest spełniony, to wyrażenie // jest prawdą //jeśli a jest większe od 3 i mniejsze od 5 wykonaj poniższe - suma logiczna (warunek_1) (warunek_2) PRZYKŁAD If ( (a<2) (a>6) ) PORTB = 0b00001111; // jeśli warunek pierwszy lub drugi jest spełniony to wyrażenie // jest prawdą //jeśli a jest mniejsze od 2 lub większe od 6 wykonaj poniższe! - negacja (! (warunek)) // jeśli warunek jest spełniony, to wyrażenie jest fałszem // i odwrotnie jeśli warunek nie jest spełniony, to całe // wyrażenie jest prawdą PRZYKŁAD If (! (a==0)) //jeśli a jest różne od 0 to wykonaj poniższe PORTB = 0x00; Strona5
o Budowa programu #include // dołączanie bibliotek #define 3 // zdefiniowane makra zmienne globalne // definiowanie zmiennych funkcje 4 // sekcja funkcji int main (void) // Ta sekcja wykonuje się tylko jeden raz while(1) 5 // Pętla wykonuje się nieskończenie wiele razy PRZYKŁAD #define F_CPU 1000000UL // 1 MHz #include <util/delay.h> #include <avr/io.h> void delay_ms( int ms) volatile long unsigned int i; for(i=0;i<ms;i++) _delay_ms(1); int main(void) //ustalenie kierunku pinow DDRD=0xFF; //definiowanie zmiennej unsigned int i=0; while(1) PORTD=i; I++; delay_ms(ms); return(0); 3 Czasami elementy sekcji #define występują przed sekcją #include 4 Tutaj znajdują się również procedury obsługi przerwania 5 Inny zapis pętli nieskończonej for(;;) Strona6
Wprowadzenie do programowania ATmega16 (rodzina: AVR, producent: ATMEL). o Definicje Pin można utożsamić z jednym z wyprowadzeń mikrokontrolera - małe, metalowe, wystaje z obudowy; potocznie nazywany nóżką; Port grupa pinów, zwykle 8; nadane im są oznaczenia literowe (A, B, C, D); w ramach portu piny są rozróżniane na podstawie numerów (0-7). Piny w mikrokontrolerze mogą pracować w wielu trybach, jednym z nich jest tryb ogólnego przeznaczenia: wejście/wyjście (I/O). Ponadto piny mogą pełnić funkcje specjalne, ale takie zostaną przedstawione później. Do obsługi pinów w danym porcie służą trzy rejestry 8-bitówe: DDRx rejestr ten ustala kierunek pracy poszczególnych pinów w ramach portu, każdy bit odpowiada określonemu pinowi; 1 pin pracuje jako wyjście, 0 pin pracuje jako wejście. PORTx jeżeli pin pracuje jako wyjście, wpisanie 0 do odpowiadającego mu bitu w rejestrze PORTx spowoduje ustawienie stanu niskiego na odpowiednim pinie, natomiast wpis 1 powoduje ustawienie stanu wysokiego. Odczyt tego rejestru powoduje odczytanie poprzednio ustawionych, a nie stanu pinów. PINx jeżeli pin pracuje jako wejście, odczyt odpowiadającego mu bitu w rejestrze powoduje sprawdzenie stanu na fizycznym wyprowadzeniu mikrokontrolera. x należy zastąpić literą reprezentującą dany port Ustawienie kierunku portów: DDRD = 0x27; Nr bitu 7 6 5 4 3 2 1 0 DDRD 0 0 1 0 0 1 1 1 Tryb Wejście Wejście Wyjście Wejście Wejście Wyjście Wyjście Wyjście Ustawienie stanu pinów na wyjściu: PORTD = 0xe5; Nr bitu 7 6 5 4 3 2 1 0 PORTD 1 1 1 0 0 1 0 1 Stan Wysoki Wysoki Wysoki Niski Niski Wysoki Niski Wysoki Odczyt stanu pinów: x = PIND; Nr bitu 7 6 5 4 3 2 1 0 Stan Wysoki Niski Niski Wysoki Niski Wysoki Niski Wysoki PIND 1 0 0 1 0 1 0 1 Strona7
o Operacje bitowe Operacje na poszczególnych bitach bajtów; nie należy ich mylić z operacjami logicznymi, stosowanymi w instrukcjach warunkowych oraz pętlach. Operacja binarna o Operacje na dwóch bajtach Operacja logiczna & && ~! ^ brak odpowiednika Iloczyn binarny AND wynik = x&y; Bajt x 1 1 1 0 0 1 0 1 Bajt y 0 1 1 0 1 1 0 0 Wynik & 0 1 1 0 0 1 0 0 Suma binarna OR wynik = x y; Bajt x 1 1 1 0 0 1 0 1 Bajt y 0 1 1 0 1 1 0 0 Wynik 1 1 1 0 1 1 0 1 Alternatywa wykluczająca XOR wynik = x^y; Bajt x 1 1 1 0 0 1 0 1 Bajt y 0 1 1 0 1 1 0 0 Wynik ^ 1 0 0 0 1 0 0 1 o Operacje na jednym bajcie Negacja NOT wynik = ~x; Bajt x 1 1 1 0 0 1 0 1 Wynik ~ 0 0 0 1 1 0 1 0 Częsty błąd: wynik =!x; Bajt x 1 1 1 0 0 1 0 1 Wynik! 0 0 0 0 0 0 0 0 Bajt x 0 0 0 0 0 0 0 0 Wynik! 0 0 0 0 0 0 0 1 Strona8
Przesunięcia bitowe: a. w lewo o x pozycji zmienna << x; b. w prawo o x pozycji zmienna >> x; Bity, które zostaną wypchnięte z bajtu, giną bezpowrotnie, natomiast puste miejsca są uzupełniane 0. wynik = (zmienna<<3); zmienna 1 1 1 0 0 1 0 1 wynik 0 0 1 0 1 0 0 0 wynik = (zmienna>>3); zmienna 1 1 1 0 0 1 0 1 wynik 0 0 0 1 1 1 0 0 Strona9
Wyświetlacz 7-segmentowy Wyświetlacz skonstruowany w oparciu o diody LED siedmiu segmentów ułożonych w cyfrę 8 oraz diodę kropki, występuje w dwóch rodzajach: ze wspólną anodą wszystkie anody diod wchodzących w skład wyświetlacza są zwarte Aby segment udało się włączyć wspólną anodę należy podłączyć do wyższego potencjału, a katodę wybranego segmentu do niższego potencjału. W przypadku sterowania przy pomocy mikrokontrolera wspólną anodę podłączamy do +5V, a katody do pinów mikrokontrolera. Ustawienie na wybranym pinie 0 powoduje świecenie. ze wspólną katodą wszystkie anody diod wchodzących w skład wyświetlacza są zwarte Aby segment udało się włączyć wspólną katodę należy podłączyć do niższego potencjału, a anodę wybranego segmentu do wyższego potencjału. W przypadku sterowania przy pomocy mikrokontrolera wspólną katodę podłączamy do GND, a anody do pinów mikrokontrolera. Ustawienie na wybranym pinie 1 powoduje świecenie. Każdy segment opisywanego wyświetlacza ma swoje oznaczenie literowe, segmenty nazywane są jak przedstawiono na rysunku. Strona10
Obsługa wejść Do obsługi wejść służy rejestr PINx 6, po odpowiednim skonfigurowaniu kierunku pracy pinów w rejestrze DDRx 7. DDR x PORTx PUD (SFIOR) 8 Typ wejścia 0 0 x Trójstanowe (Tri-state; Hi-Z) 0 1 0 Podwieszone (Pull-up) 0 1 1 Trójstanowe (Tri-state; Hi-Z) o Typy wejść podwieszone (Pull-up) jeżeli nic nie jest podłączone do wybranego pinu, to mikrokontroler odczyta stan pinu jako wysoki, w przeciwnym przypadku odczytana wartość będzie zgodna z podłączonym sygnałem trójstanowe (Tri-state) jeżeli nic nie jest podłączone do wybranego pinu, to stan odczytany przez mikrokontroler może być wysoki lub niski Zalecane jest używanie typu wejść z podwieszeniem, jako bezpieczniejsze. o Sposób podłączenia przycisku do mikrokontrolera 6 Należy wstawić odpowiednią literę portu. 7 j.w. 8 PUD nazwa bitu w rejestrze SFIOR Strona11
Przykłady programów o Obsługa diod #define F_CPU 1000000UL // 1 MHz #include <util/delay.h> #include <avr/io.h> void delay_ms( int ms) volatile long unsigned int i; for(i=0;i<ms;i++) _delay_ms(1); int main(void) //ustalenie kierunku pracy pinow DDRD=0xFF; while(1) PORTD=0xaa; delay_ms(100); PORTD=0x55; delay_ms(100); ; return(0); Strona12
o Wyświetlacz 7-segmentowy #define F_CPU 1000000UL // 1 MHz #include <util/delay.h> #include <avr/io.h> void delay_ms(int ms) volatile long unsigned int i; for(i=0;i<ms;i++) _delay_ms(1); int main(void) char seg[] = 0x03, 0x9f, 0x25, 0x0d, 0x99, 0x49, 0x41, 0x1f, 0x01, 0x09 ; PORTD=0xFF; DDRD=0xFF; int ms=500; while(1) for(int i=0;i<10;i++) PORTD=seg[i]; delay_ms(ms); ; return(0); Strona13
o Przyciski #define F_CPU 1000000UL // 1 MHz #include <util/delay.h> #include <avr/io.h> void delay_ms(int ms) volatile long unsigned int i; for(i=0;i<ms;i++) _delay_ms(1); int main(void) int klawisz; DDRA = 0x0f; PORTA = 0x0f; while(1) delay_ms(1); klawisz = 0; if(!(pina&0x10)) klawisz=1; if(!(pina&0x20)) klawisz=2; PORTA = klawisz; ; return 0; Strona14
o LCD #define F_CPU 1000000UL // 1 MHz #include <avr/io.h> #include <stdio.h> #include <util/delay.h> #include <string.h> void delay_ms(int ms) volatile long unsigned int i; for(i=0;i<ms;i++) _delay_ms(1); void delay_us(int us) volatile long unsigned int i; for(i=0;i<us;i++) _delay_us(1); //RS PA0 //RW PA1 //E PA2 //DATA PD #define RS 0 #define RW 1 #define E 2 void LCD2x16_init(void) PORTA &= ~(1<<RS); PORTA &= ~(1<<RW); PORTA = (1<<E); PORTD = 0x38; // dwie linie, 5x7 punktow PORTA &=~(1<<E); _delay_us(120); PORTA = (1<<E); PORTD = 0x0e; // wlacz wyswietlacz, kursor, miganie PORTA &=~(1<<E); _delay_us(120); PORTA = (1<<E); PORTD = 0x06; PORTA &=~(1<<E); _delay_us(120); Strona15
void LCD2x16_clear(void) PORTA &= ~(1<<RS); PORTA &= ~(1<<RW); PORTA = (1<<E); PORTD = 0x01; PORTA &=~(1<<E); delay_ms(120); void LCD2x16_putchar(int data) PORTA = (1<<RS); PORTA &= ~(1<<RW); PORTA = (1<<E); PORTD = data; PORTA &=~(1<<E); _delay_us(120); void LCD2x16_pos(int wiersz, int kolumna) PORTA &= ~(1<<RS); PORTA &= ~(1<<RW); PORTA = (1<<E); delay_ms(1); PORTD = 0x80+(wiersz-1)*0x40+(kolumna-1); delay_ms(1); PORTA &=~(1<<E); _delay_us(120); int main(void) char linia1[16] = " Szkolenie ARAW "; char linia2[16] = " LCD 2 x 16 "; char tmp[16]; int i; int j=4; DDRD = 0xff; PORTD = 0x00; DDRA = 0xff; PORTA = 0x00; _delay_ms(200); LCD2x16_init(); Strona16
LCD2x16_clear(); for(i=0;i < 16;i++) LCD2x16_putchar(linia1[i]); LCD2x16_pos(2,1); for(i=0;i < 16;i++) LCD2x16_putchar(linia2[i]); delay_ms(3000); LCD2x16_clear(); for(i=0;i < 16;i++) LCD2x16_putchar(linia1[i]); while(1) //LCD2x16_clear(); LCD2x16_pos(2,1); sprintf(tmp,"dzialam juz %2is ",j); j++; for(i=0;i < 16;i++) LCD2x16_putchar(tmp[i]); delay_ms(1000); return 0; Strona17