Wprowadzenie do podstaw programowania AVR (na przykładzie mikrokontrolera ATmega 16 / 32)



Podobne dokumenty
Poradnik programowania procesorów AVR na przykładzie ATMEGA8

Cwiczenie nr 1 Pierwszy program w języku C na mikrokontroler AVR

Inż. Kamil Kujawski Inż. Krzysztof Krefta. Wykład w ramach zajęć Akademia ETI

Programowanie mikrokontrolerów AVR z rodziny ATmega.

Instrukcja do ćwiczeń nr 4 typy i rodzaje zmiennych w języku C dla AVR, oraz ich deklarowanie, oraz podstawowe operatory

Programowanie mikrokontrolerów AVR z rodziny ATmega.

/* dołączenie pliku nagłówkowego zawierającego deklaracje symboli dla wykorzystywanego mikrokontrolera */ #include <aduc834.h>

Instytut Teleinformatyki

Programowanie strukturalne i obiektowe

Podstawowe urządzenia peryferyjne mikrokontrolera ATmega8 Spis treści

Mikrokontroler ATmega32. System przerwań Porty wejścia-wyjścia Układy czasowo-licznikowe

METODY I JĘZYKI PROGRAMOWANIA PROGRAMOWANIE STRUKTURALNE. Wykład 02

2 Przygotował: mgr inż. Maciej Lasota

1 Podstawy c++ w pigułce.

Niektóre piny mogą pełnić różne role, zależnie od aktualnej wartości sygnałów sterujących.

Zestaw Edukacyjny Atmega-8 (AJAWe-0711) Porty wejścia-wyjścia.

Podstawy programowania skrót z wykładów:

Część 4 życie programu

Systemy wbudowane. Wprowadzenie. Struktura. Mikrokontrolery AVR. Wprowadzenie do programowania w C

Mikrokontrolery AVR Wprowadzenie

Programowanie mikrokontrolerów AVR

Programowanie C++ Wykład 2 - podstawy języka C++ dr inż. Jakub Możaryn. Warszawa, Instytut Automatyki i Robotyki

Programowanie w C++ Wykład 2. Katarzyna Grzelak. 4 marca K.Grzelak (Wykład 1) Programowanie w C++ 1 / 44

Mikrokontroler ATmega32. Język symboliczny

Podstawy programowania C. dr. Krystyna Łapin

1 Podstawy c++ w pigułce.

Jak napisać program obliczający pola powierzchni różnych figur płaskich?

Instytut Teleinformatyki

Operatory AND, OR, NOT, XOR Opracował: Andrzej Nowak Bibliografia:

Szkolenia specjalistyczne

Komunikacja w mikrokontrolerach Laboratorium

Język C zajęcia nr 11. Funkcje

Dr inż. Grażyna KRUPIŃSKA. D-10 pokój 227 WYKŁAD 7 WSTĘP DO INFORMATYKI

Podstawy programowania w języku C i C++

Temat 1: Podstawowe pojęcia: program, kompilacja, kod

Wstęp do Programowania, laboratorium 02

Laboratorium Systemów wbudowanych Wyższa Szkoła Zarządzania i Bankowości, Informatyka studia inżynierskie

Podstawy programowania, Poniedziałek , 8-10 Projekt, część 1

Podstawy programowania. Wykład Co jeszcze... Przypomnienia, uzupełnienia. Krzysztof Banaś Podstawy programowania 1

Metody obsługi zdarzeń

XMEGA. Warsztaty CHIP Rok akademicki 2014/2015

Schemat blokowy architektury AVR

Kompilator języka C na procesor 8051 RC51 implementacja

Podstawy języka C++ Maciej Trzebiński. Instytut Fizyki Jądrowej Polskiej Akademii Nauk. Praktyki studenckie na LHC IVedycja,2016r.

1. Pierwszy program. Kompilator ignoruje komentarze; zadaniem komentarza jest bowiem wyjaśnienie programu człowiekowi.

LABORATORIUM. TIMERY w mikrokontrolerach Atmega16-32

Architektura systemów komputerowych Laboratorium 13 Symulator SMS32 Operacje na bitach

Podstawowe urządzenia peryferyjne mikrokontrolera ATmega8 Spis treści

Programowanie w C++ Wykład 2. Katarzyna Grzelak. 5 marca K.Grzelak (Wykład 1) Programowanie w C++ 1 / 41

Języki programowania C i C++ Wykład: Typy zmiennych c.d. Operatory Funkcje. dr Artur Bartoszewski - Języki C i C++, sem.

LABORATORIUM. TIMERY w mikrokontrolerach Atmega16-32

ĆWICZENIE 5. TEMAT: OBSŁUGA PORTU SZEREGOWEGO W PAKIECIE KEILuVISON WYSYŁANIE PORTEM SZEREGOWYM

Podstawy Informatyki. Inżynieria Ciepła, I rok. Wykład 10 Kurs C++

Programowanie I C / C++ laboratorium 02 Składnia pętli, typy zmiennych, operatory

Język C - podstawowe informacje

1. Wartość, jaką odczytuje się z obszaru przydzielonego obiektowi to: a) I - wartość b) definicja obiektu c) typ oboektu d) p - wartość

Podstawy programowania. Wykład 6 Wskaźniki. Krzysztof Banaś Podstawy programowania 1

Programowanie mikrokontrolerów. 5 grudnia 2007

Typy złożone. Struktury, pola bitowe i unie. Programowanie Proceduralne 1

Spis treœci. Co to jest mikrokontroler? Kody i liczby stosowane w systemach komputerowych. Podstawowe elementy logiczne

2. Architektura mikrokontrolerów PIC16F8x... 13

Podstawowe urządzenia peryferyjne mikrokontrolera ATmega8 Spis treści

PROGRAMOWALNE SYSTEMY MECHATRONIKI

Przerwania, polling, timery - wykład 9

Wstęp do programowania

Języki i metodyka programowania. Wprowadzenie do języka C

Wstęp do programowania INP003203L rok akademicki 2018/19 semestr zimowy. Laboratorium 2. Karol Tarnowski A-1 p.

SYSTEM PRZERWAŃ ATmega 32

Zajęcia nr 2 Programowanie strukturalne. dr inż. Łukasz Graczykowski mgr inż. Leszek Kosarzewski Wydział Fizyki Politechniki Warszawskiej

Po uruchomieniu programu nasza litera zostanie wyświetlona na ekranie

Język C część 1. Sformułuj problem Zanalizuj go znajdź metodę rozwiązania (pomocny może byd algorytm) Napisz program Uruchom i przetestuj czy działa

Języki i metodyka programowania. Typy, operatory, wyrażenia. Wejście i wyjście.

KOMUNIKACJA Z OTOCZENIEM MIKROKONTROLERA

Strona główna. Strona tytułowa. Programowanie. Spis treści. Sobera Jolanta Strona 1 z 26. Powrót. Full Screen. Zamknij.

Systemy wbudowane. Uniwersytet Łódzki Wydział Fizyki i Informatyki Stosowanej. Witold Kozłowski

Operatory. Operatory bitowe i uzupełnienie informacji o pozostałych operatorach. Programowanie Proceduralne 1

( wykł. dr Marek Piasecki )

Instrukcja do ćwiczeń

Podstawy programowania. Wykład: 5. Instrukcje sterujące c.d. Stałe, Typy zmiennych c.d. dr Artur Bartoszewski -Podstawy programowania, sem 1 - WYKŁAD

PROGRAMOWANIE MIKROKONTROLERÓW

Współpraca mikrokontrolera z wyświetlaczami: ciekłokrystalicznym i siedmiosegmentowym

Politechnika Poznańska Wydział Budowy Maszyn i Zarządzania. Sterowniki Urządzeń Mechatronicznych laboratorium. Ćw. 3: Timer v1.0

Zmienne, stałe i operatory

Wstęp. do języka C na procesor (kompilator RC51)

Podstawy Programowania C++

Język C, tablice i funkcje (laboratorium, EE1-DI)

Podstawy programowania. 1. Operacje arytmetyczne Operacja arytmetyczna jest opisywana za pomocą znaku operacji i jednego lub dwóch wyrażeń.

I - Microsoft Visual Studio C++

W dowolnym momencie można zmienić typ wskaźnika.

Ok. Rozbijmy to na czynniki pierwsze, pomijając fragmenty, które już znamy:

Programowanie - wykład 4

Podstawy programowania w języku C

Niezwykłe tablice Poznane typy danych pozwalają przechowywać pojedyncze liczby. Dzięki tablicom zgromadzimy wiele wartości w jednym miejscu.

Mikroprocesory i Mikrosterowniki Liczniki Timer Counter T/C0, T/C1, T/C2

Laboratorium Podstaw Informatyki. Kierunek Elektrotechnika. Ćwiczenie 1. Podstawy. Wprowadzenie do programowania w języku C. Katedra Metrologii AGH

Rejestry procesora. Nazwa ilość bitów. AX 16 (accumulator) rejestr akumulatora. BX 16 (base) rejestr bazowy. CX 16 (count) rejestr licznika

Języki C i C++ Wykład: 2. Wstęp Instrukcje sterujące. dr Artur Bartoszewski - Języki C i C++, sem. 1I- WYKŁAD

znajdowały się różne instrukcje) to tak naprawdę definicja funkcji main.

Język C, tablice i funkcje (laboratorium)

Instytut Teleinformatyki

Transkrypt:

Wprowadzenie do podstaw programowania AVR (na przykładzie mikrokontrolera ATmega 16 / 32) wersja 0.4 (20 kwietnia 2015) Filip A. Sala W niniejszym, bardzo krótkim opracowaniu, postaram się przedstawić podstawowe zagadnienia związane z programowaniem mikrokontrolerów w języku C. Typowy kod programu dla mikrokontrolera wygląda w następujący sposób: Listing 1: Przykładowy program 1 //Instrukcje dla preprocesora 2 #include <avr/io.h> //dołączenie biblioteki io (wejscia/wyjscia) 3 #include <avr/interrupt.h> //dołączenie biblioteki obsługi przerwań 4 #include <util/delay.h> //dołączenie biblioteki zawierającej funkcje opóźniające 5 #include "moja_biblioteka.h" //dołączenie biblioteki znajdującej się w głównym katalogu programu 6 #define PORT_LCD PORTA //W całym programie preprocesor w miejsce PORT_LCD wstawi PORTA 7 #define KEY_PIN (1<<5) //W całym programie zapis KEY_PIN zostanie zamieniony na (1<<5) 8 9 //Funkcja obsługi przerwania 10 ISR(TIMER0_COMP_vect) //w nawiasie podany jest wektor odpowiadający przerwaniu 11 { 12 //definicja funkcji obsługi przerwania 13 } 14 15 //Główna funkcja programu 16 int main(void) 17 { 18 sei(); //uruchomienie obsługi przerwań (o ile z nich korzystamy). 19 while(1) 20 { 21 //nieskończona pętla 22 } 23 return 0; 24 } Dyrektywa #define Mówiąc potocznie dyrektywa #define służy do zamiany jednej nazwy na inną, bardziej przystępną. Jest to instrukcja dla preprocesora, który jeszcze 1

zanim zacznie działać kompilator, w całym kodzie zamienia jeden zapis na drugi. Na przykład linijka #define KEY_PIN (1<<5) oznacza tyle co wstawienie w kodzie źródłowym zapisu (1<<5) wszędzie tam gdzie występuje KEY_PIN. Dyrektywa #define może służyć albo do skrócenia zapisu, bądź też do nadania zapisowi bardziej przystępnej nazwy. Na przykład #define KEY_DOWN!(PIND & (1<<5)) definiuje nam sprawdzenie stanu pinu 5 na porcie D. Jeżeli podłączymy do tego pinu przycisk zwierający do masy to w prosty sposób możemy sprawdzać czy został przyciśnięty przycisk, sprawdzając jedynie czy KEY_DOWN jest równe 0 czy 1. Na przykład tak: 1 if (KEY_DOWN) 2 { 3 //polecenia wykonywane po wciśnięciu przycisku 4 } Używanie dyrektywy #define jest również bardzo istotne z punktu widzenia przenośności i łatwości utrzymania kodu źródłowego. Wyobraźmy sobie, że piszemy program, który ma sterować silnikiem krokowym. Wykorzystaliśmy do tego celu PORTA. W wielu miejscach w programie występuje ta nazwa. Później jednak okazuje się, że w tym samym programie chcielibyśmy skorzystać z przetwornika A/D (analogowo/cyfrowego), który na nasze nieszczęście również korzysta z pinów portu A. Gdybyśmy port zdefiniowali przy użyciu dyrektywy #define to wystarczyłoby zmienić tę definicję. Natomiast tak, zmuszeni jesteśmy w każdym miejscu programu zamieniać PORTA na inny. Kolejną bardzo ważną rzeczą z punktu wiedzenia czytelności i przenośności kodu jest używanie zdefiniowanych już nazw rejestrów. Wszystkie rejestry oraz nazwy poszczególnych bitów zdefiniowane są w odpowiednich plikach nagłówkowych. Dołączając zatem plik io.h dołączamy zdefiniowane nazwy wszystkich rejestrów oraz ich bitów dla danego mikrokontrolera. Na przykład jeżeli chcemy ustawić tryb pracy licznika 8-bitowego (Timer0) jako CTC (tryb porównania) to oczywiście możemy to zrobić w ten sposób: 1 TCCR0 =(1<<3); lub jeszcze mniej czytelnie: 1 TCCR0 =0b00001000; Jednak gdy napiszemy: 1 TCCR0 =(1<<WGM01); to kod będzie nie tylko bardziej czytelny, ale i bardziej przenośny. Jeżeli będziemy chcieli przenieść program na inny mikrokontroler, to może się okazać, że chociaż nazwy pozostają 2

te same (jak w tym przypadku WGM01), to umiejscowione są na innym bicie danego rejestru. Wtedy też musimy wprowadzić zmiany w kodzie programu. W przypadku używania zdefiniowanych już nazw najczęściej nie musimy już nic zmieniać. Dyrektywa #include służy do wstawiania do kodu źródłowego zawartości jakiegoś innego pliku. Najczęściej dyrektywę tę wykorzystujemy do dołączania plików nagłówkowych bibliotek. W przypadku standardowych bibliotek kod wygląda następująco: 1 #include <avr/io.h> 2 #include <util/delay.h> //biblioteka zawierająca funkcje opóźniające 3 #include <stdint.h> //biblioteka zdefiniowanych typów np. uint8_t, uint16_t... 4 #include <avr/interrupt.h> //biblioteka wykorzystywana przy korzystaniu z przerwań W przypadku biblioteki znajdującej się w tym samym katalogu co kod źródłowy naszego programu nazwę pliku nagłówkowego biblioteki (plik o rozszerzeniu *.h) umieszczamy w cudzysłowach: 1 #include "lib_rs232.h" W taki właśnie sposób najprościej jest dołączyć do programu własne biblioteki. Typy zmiennych Większość typów znanych z języka C dla komputerów PC jest również dostępna przy programowaniu mikrokontrolerów. Z punktu widzenia mikrokontrolerów bardzo istotne jest czy dany typ jest 8, 16, 32 czy 64-bitowy. Dlatego też w bibliotece stdint.h zdefiniowane zostały typy całkowite w następujący sposób: 1 typedef signed char int8_t //zakres -127... 128 2 typedef unsigned char uint8_t //zakres 0... 255 3 typedef signed int int16_t // zakres -32768... 32767 4 typedef unsigned int uint16_t //zakres 0... 65535 5 typedef signed long int int32_t //zakres -2 147 483 648... 2 147 483 647 6 typedef unsigned long int uint32_t //zakres 0... 4 294 967 295 7 typedef signed long long int int64_t //zakres -9 223 372 036 854 775 808... 9,223 372 036 854 775 807 8 typedef unsigned long long int uint64_t //zakres 0... 18 446 744 073 709 551 615 Widzimy zatem, że stosowany często typ uint8 t (którego nazwa pochodzi od unsigned integer 8-bit, czyli liczba całkowita bez znaku, 8-bitowa), jest niczym innym jak typem unsigned char. Natomiast typ int16 t to signed int (zwracam uwagę, iż jest to odpowiednik znanego w C typu short int, ponieważ jest to typ 16-bitowy. W języku C dla PC typ int jest 32-bitowy!). 3

Możliwe jest również używanie typu zmiennoprzecinkowego float. Jest to typ 32- bitowy (4 bajty). Chociaż możliwe jest użycie typu double (podwójna precyzja) to nie jest on jednak wspierany i operacje wykonywane są tak samo jak dla typu float. Z uwagi jednak na to, iż mikrokontrolery AVR nie posiadają koprocesora operacje na takich zmiennych są bardzo wolne. Ponadto konieczne jest dołączenie odpowiednich bibliotek co powoduje znaczące zwiększenie rozmiaru programu. Ma to duże znaczenie dla mikrokontrolerów, które zazwyczaj mają bardzo ograniczone rozmiary pamięci Flash, do której możemy wgrać program. Przerwania W trakcie pracy procesor wykonuje polecenia znajdujące się w głównej funkcji programu (main). Gdy pojawia się przerwanie (ang. interrupt), którym może być niemal dowolne zdarzenie, jak na przykład przepełnienie się licznika, zakończenie pracy przetwornika analogowo-cyfrowego czy też odczytanie wartości przez magistralę szeregową RS232, procesor wstrzymuje wykonywanie funkcji głównej i przechodzi do funkcji obsługi przerwania. W ogólności funkcja ta wygląda w następujący sposób: 1 ISR (wektor_przerwania) 2 { 3 //definicja funkcji 4 } Funkcja obsługi przerwania powinna być jak najkrótsza. Nie może zawierać opóźnień! Wynika to między innymi z tego, iż w momencie pojawienia się przerwania obsługa przerwań jest wyłączana. Oznacza to, że jeśli funkcja obsługi przerwania będzie zbyt długa to nie obsłużymy w porę przychodzących nowych przerwań. Żadne nowe przerwanie nie zostanie bowiem obsłużone dopóki nie zakończy się aktualnie wykonywana funkcja obsługi przerwania. Co prawda możliwe jest programowe włączenie obsługi przerwania w przerwaniu jednak jest to bardzo niewskazane gdyż rodzi wiele problemów! Sam wektor przerwania najprościej jest znaleźć w dokumentacji avr-libc. Jeżeli chcemy obsługiwać kilka różnych przerwań, na przykład dla Timera0 oraz Timera1 musimy zdefiniować dwie funkcje obsługi przerwania na przykład w następujący sposób: 1 ISR (TIMER0_COMP_vect) 2 { 3 //definicja funkcji obsługi Timera0 4 } 5 ISR (TIMER1_COMPA_vect) 6 { 7 //definicja funkcji obsługi Timera1 4

8 } Ponadto aby przerwania działały konieczne jest wywołanie funkcji sei(), która włącza obsługę przerwań. Funkcję tę należy wywołać tylko raz w całym programie. Najlepiej na samym początku. W żadnym wypadku funkcja ta nie może znaleźć się w pętli while(1){ }. Bardzo ważną rzeczą przy pisaniu funkcji obsługi przerwania jest świadomość iż procesor przechowuje wartości zmiennych w rejestrach. Oznacza to, że jeżeli wewnątrz funkcji obsługi przerwania zmienimy wartość jakiejś zmiennej globalnej to dla głównego programu (funkcji main()) wartość ta się nie zmieni gdyż procesor nadal będzie korzystał z zapisanej w rejestrze wartości. Przeanalizujmy następujący kod: 1 #include <avr/io.h> 2 #include <avr/interrupt.h> 3 uint8_t temp; 4 ISR (TIMER0_COMP_vect) 5 { 6 temp=temp+1; //wartość zmiennej temp jest zwiększana 7 } 8 9 int main(void) 10 { 11 //(... inicjalizacja Timera0...) 12 sei(); 13 temp=20; 14 while (1) 15 { 16 //wewnątrz pętli zmienna "temp" ma wciąż wartość 20 17 } 18 return 0; 19 } Pomimo, iż co jakiś czas wywoływana jest funkcja obsługi przerwania, która zwiększa wartość zmiennej temp, to wewnątrz funkcji main(), a dokładniej wewnątrz pętli while(1), wartość tej zmiennej wciąż jest równa 20, ponieważ procesor pobiera wartość z rejestrów, a nie z pamięci. Aby poradzić sobie z tym problemem możemy użyć polecenie volatile przed deklaracją zmiennej. Jest to instrukcja, która mówi kompilatorowi aby wartość zmiennej pobierał z pamięci, a nie z rejestrów. Deklaracja zmiennej temp powinna zatem wyglądać tak: 1 volatile uint8_t temp; 5

Kolejnym ważnym poleceniem jest static. Jeżeli chcemy korzystać ze zmiennej tylko wewnątrz funkcji obsługi przerwania, ale nie chcemy żeby za każdym razem była tworzona i inicjalizowana, tylko aby wartość tej zmiennej przechowywana była pomiędzy wywołaniami funkcji obsługi przerwania, to możemy zadeklarować taką zmienną w następujący sposób: 1 ISR(wektor_przerwania) 2 { 3 static uint8_t temp=10; 4 temp++; 5 } Zmienna temp przy pierwszym wywołaniu funkcji zostanie utworzona i zainicjalizowana wartością 10, a następnie jej wartość zostanie zwiększona o 1. Przy kolejnych już wywołaniach funkcji wartość zmiennej temp będzie jedynie zwiększana o 1. Operacje wejścia/wyjścia (ang. I/O input/output) W tej części omówione zostaną operacje na portach. Operacje wejścia/wyjścia będziemy rozumieć jako operacje odczytania wartości z portu oraz przypisania wartości do portu. W przypadku mikrokontrolera ATmega16 mamy do dyspozycji cztery porty 8-bitowe oznaczone jako A, B, C oraz D. Ponadto istnieją trzy rodzaje rejestrów odpowiedzialnych za obsługę portów. Są to PORTx, DDRx oraz PINx (gdzie x = A, B, C, D). ˆ PORTx - służy do przypisania wartości do portu x ˆ PINx - służy do odczytania wartości z portu x ˆ DDRx - (ang. Data Direction Register) służy do ustawienia kierunku przesyłania danych Aby podstawić na przykład wartość do portu A możemy to zrealizować w następujący sposób: 1 DDRA=0xFF; //ustawienie portu w trybie wyjścia (0xFF = 255 = 0b11111111) 2 PORTA=0x01; //przypisanie do portu A wartości 1 Jeżeli chcielibyśmy odczytać wartość z portu należałoby to zrobić tak: 1 DDRA=0x00; //ustawienie portu w trybie wejścia (0x00 = 0 = 0b00000000) 2 uint8_t temp; 3 temp=pina; //przypisanie do zmiennej "temp" wartości z portu A 6

Operowanie jednak na całych portach i rejestrach jest niewygodne. Zazwyczaj chcemy zmieniać tylko pojedyncze bity. Można to oczywiście zrobić używając literałów heksadecymalnych (np. 0xFF) bądź binarnych (np. 0b00000011) jednak jest to nie tylko niewygodne, ale powoduje, że kod staje się nieczytelny i mało uniwersalny. Dlatego też stosujemy tzw. przesunięcia bitowe. Najczęściej wykorzystywane jest przesunięcie bitowe w lewo, dużo rzadziej w prawo. Przesunięcie bitowe w lewo o jeden polega na przepisaniu poszczególnych bitów na pozycję o jeden w lewo oraz dopisaniu 0 na miejscu najmniej znaczącego bitu. Przykład przesunięcia bitowego w lewo przedstawia rysunek 1. Jeżeli chcemy zatem Rysunek 1: Przesunięcie bitowe w lewo ustawić wartość n-tego bitu w porcie A na 1 to musimy napisać: 1 uint8_t n=2; 2 DDRA=(1<<n); 3 PORTA=(1<<n); Taki jednak zapis powoduje nie tylko przypisanie wartości 1 do n-tego bitu, ale również wyzerowanie pozostałych bitów (w powyższym przykładzie zarówno w rejestrze DDRA jak i PORTA). Dlatego też do przypisania wartości 1 do portu czy rejestru stosuje się alternatywę bitową (sumę bitową) OR ( ): 1 uint8_t n=2; 2 DDRA =(1<<n); // jest to skrócony zapis DDRA= DDRA (1<<n); - ustawienie portu w trybie wyjścia 3 PORTA =(1<<n); // jest to skrócony zapis PORTA= PORTA (1<<n); Taki zapis gwarantuje nam, że jedynie n-ty bit (w naszym przypadku n=2) zostanie zmieniony na 1, a pozostałe bity pozostaną nienaruszone. W przypadku gdy chcemy do określonego bitu portu bądź rejestru przypisać wartość 0, stosujemy koniunkcję bitową (iloczyn bitowy) AND (&) wraz z negacją bitową ( ): 7

1 uint8_t n=2; 2 DDRA =(1<<n); // jest to skrócony zapis DDRA= DDRA (1<<n); - ustawienie portu w trybie wyjścia 3 PORTA&=~(1<<n); // jest to skrócony zapis PORTA= PORTA & ~(1<<n); - przypisanie wartości 0 do n-tego bitu //portu A Jeżeli natomiast chcemy zmienić wartość wszystkich bitów portu bądź rejestru na przeciwne to używamy do tego celu (bitowej) alternatywy wykluczającej XOR (ˆ): 1 uint8_t n=2; 2 DDRA =(1<<n); // ustawienie portu w trybie wyjścia 3 PORTA^=0xFF; // jest to skrócony zapis PORTA= PORTA ^ 0xFF; - zamiana bitów na przeciwne Przeanalizujmy teraz pełny kod programu, który odczytuje i przypisuje wartości do poszczególnych bitów portu: Listing 2: Operacje I/O 1 #include <avr/io.h> //dołączenie biblioteki io.h 2 3 int main(void) 4 { 5 //Ustawianie Data Direction Register 6 7 //Bity 1 oraz 0 zostały ustawione w trybie wyjścia 8 DDRA =(1<<1) (1<<0); //podstawienie 1 do bitów 0 oraz 1. 9 10 //Bity 2 oraz 3 zostały ustawione w trybie wejścia 11 DDRA&=~(1<<3)&~(1<<2); //podstawienie 0 do bitów 2 oraz 3 12 13 while(1) 14 { 15 if (!(PINA & (1<<3))) //jeżeli na pinie 3 portu A wystąpi 0 to.. 16 { 17 PORTA =(1<<0); //ustawienie wartości 1 na pinie 0 portu A 18 PORTA&=~(1<<1); //ustawienie wartości 0 na pinie 1 portu A 19 } 20 if (!(PINA & (1<<2))) //jeżeli na pinie 2 portu A wystąpi 0 to.. 21 { 22 PORTA =(1<<1); //ustawienie wartości 1 na pinie 1 portu A 23 PORTA&=~(1<<0); //ustawienie wartości 0 na pinie 0 portu A 24 } 25 } 8

26 return 0; 27 } Wyjaśnienia może wymaga jeszcze fragment ((PINA & (1<<3))). Jest to nic innego jak sprawdzenie czy na 3 pinie portu A występuje 1. W powyższym kodzie na początku występuje jeszcze negacja (!) stąd też warunek określa sprawdzanie czy na 3 pinie portu A występuje 0. Liczniki Liczniki numerowane są od 0 przy czym Timer0 jest 8-bitowy (256 stanów), Timer1 jest 16-bitowy (65536 stanów), Timer2 8-bitowy, Timer3 16bitowy itd. Oczywiście nie w każdym mikrokontrolerze istnieje ich aż tyle. Zdarza się, że występuje tylko jeden - Timer0. ATmega16 posiada 3 liczniki: dwa 8-bitowe (Timer0, Timer2) oraz jeden 16- bitowy (Timer1). Poniżej znajduje się kod przedstawiający wykorzystanie Timera0: Listing 3: Obsługa licznika 8-bitowego 1 #include <avr/io.h> 2 #include <avr/interrupt.h> 3 #define LED_PIN (1<<0) 4 #define LED_TOG PORTC^= LED_PIN //zmiana wartości pinu LED_PIN na przeciwną (tzw. toggle) 5 //Funkcja obsługi przerwania 6 ISR (TIMER0_COMP_vect) 7 { 8 LED_TOG; 9 } 10 11 int main(void) 12 { 13 //Właczenie Timera0 w trybie porównania (CTC) 14 TCCR0 =(1<<WGM01); 15 //Ustawienie źródła taktowania (prescalera) na 1024. 16 TCCR0 =((1<<CS02) (1<<CS00)); 17 //Ustawienie rejestru porównania na 100 18 OCR0=100; 19 //Włączenie przerwania dla Timera0 20 TIMSK =(1<<OCIE0); 21 //Ustawienie pinu LED_PIN (0) w trybie wyjścia 22 DDRC =LED_PIN; 23 //Włączenie przerwań 24 sei(); 25 while (1) { 9

26 //Główna pętla programu jest pusta 27 } 28 return 0; 29 } Powyższy program inicjalizuje Timer0 w trybie porównania CTC (ang. Clear Timer on Compare). Preskaler ustawiony jest na 1024. Oznacza to, że przepełnienie licznika następować będzie z częstotliwością F_CPU/(1024*256) (gdzie F_CPU - częstotliwość mikroprocesora w Hz). Wartość 256 pochodzi od tego iż licznik jest 8-bitowy (256 stanów). Jednak ponieważ licznik pracuje w trybie porównania częstotliwość z jaką licznik będzie zerowany wyniesie F_CPU/(1024*(OCR0+1)). W ten sposób możemy w płynny sposób dobrać pożądaną częstotliwość. Z taką częstotliwością licznik będzie zerowany i będzie pojawiało się przerwanie i wywoływana funkcja obsługi przerwania. Operacje logiczne a b a & b (AND) a b (OR) a ˆ b (XOR, EOR) 0 0 0 0 0 0 1 0 1 1 1 0 0 1 1 1 1 1 1 0 Tabela 1: Matryce logiczne dla operacji koniunkcji (AND, suma logiczna), alternatywy (OR, iloczyn logiczny) oraz alternatywy wykluczającej (XOR, EOR, różnica symetryczna) 10