Migająca dioda to typowa aplikacja “Hello world” w świecie MCU. Pozwala ona przetestować toolchain czyli zestaw narzędzi za pomocą których wygenerujemy i wgramy kod. Pozwala ona również sprawdzić czy układ działa.
Do pinu PB0 należy podpiąć przez odpowiedni rezystor diodę LED (w dowolnym kierunku). Stwórz nowy projekt i w pliku main.c wklej poniższy kod:
/* * main.c * * Created on: 26-02-2011 * Author: Maciek * Najprostszy program */ //io.h zawiera informacje o portach w układzie (np DDRB, PORTB) #include <avr/io.h> //delay.h zawiera podprogram _delay_ms #include <util/delay.h> int main() { DDRB = 0xFF; PORTB = 0x00; //Nieskończona pętla //W C 0 oznacza fałsz a każda inna liczba to prawda while(1) { PORTB ^= 1; //przełącz pin 0 portu B (0->1 lub 1->0) _delay_ms(1000); //Czekaj 1 sekundę } }
Najbardziej podstawowym elementem mikroprocesora są porty wejścia/wyjścia, umożliwiają one podstawową komunikację programu z peryferiami układu. Pozwalają na proste operacje (zapalenie diody, włączenie brzęczyka, załączenie tranzystora, przekaźnika itd.) jak również na bardziej skomplikowane operacje (przetwarzanie C/A1), realizację interfejsów). Większość pinów (wyprowadzeń, nóżek układu) jest kontrolowana przez porty I/O2) (oczywiście piny posiadają różne funkcje, wiele z nich może pracować jako I/O lub pełnić inną funkcję. Patrz nota katalogowa układu rozdział 12.3 Alternate Port Functions), są niezwykle uniwersalnym narzędziem.
Praktycznie porty układu mogą znajdować się w 3 stanach (Są trójstanowe):
Co więcej za pomocą portu możemy:
Sterowaniem zachowania portów zajmują się 3 rejestry:
Każdy z powyższych rejestrów jest 8 bitowy. W przypadku układu ATmega8 zamiast x możemy wpisać B, C lub D (takie porty posiada układ - patrz nota). Pinowi numer 0 odpowiadają zerowe bity rejestrów DDRx,PORTx,PINx (analogicznie dla wszystkich 8 [0→7] pinów). Port składa się z 8 przerzutników czyli bitów informacji i można go interpretować jako 8 bitową liczbę. Porty są dostępne przez szynę adresową a ich modyfikacja w C jest bardzo prosta, wystarczy użyć ich nazwy jak zmiennej (Patrz przykłady poniżej).
Możliwe stany portów I/O przedstawia poniższa tabela:
DDR | PORT | I/O | Komentarz |
---|---|---|---|
0 | 0 | Input | Floating |
0 | 1 | Input | Hi!3)) |
1 | 0 | Output | Lo |
1 | 1 | Output | Hi |
Układ ATmega8 posiada:
To co tutaj napisałem to minimum wiedzy. Portom I/O poświęcony jest cały 12 rozdział noty katalogowej, warto go całego przeczytać i spróbować zrozumieć.
Na początku w ramach przypomnienia trochę podstawowych informacji dotyczących zapisu w C oraz trochę informacji specyficznych dla C AVR.
uint8_t # 8 bitowa liczba bez znaku 0->255 (unsigned char) int8_t # 7 bitowa liczba ze znakiem -128->127 (signed char) uint16_t # 16 bitowa liczba bez znaku (unsigned int) int16_t # 15 bitowa liczba ze znakiem (signed int) uint32_t # 32 bitowa liczba bez znaku (unsigned long int) int32_t # 31 bitowa liczba ze znakiem (signed long int) char* # Standardowy string w C
Poniższe przypadki są jednoznaczne:
uint8_t liczba = 0xCD #Zapis hexadecymalny uint8_t liczba = 0b11001101 #Zapis binarny uint8_t liczba = 205 #Zapis dziesiętny
Warto umieć płynnie zamieniać liczby pomiędzy tymi 3 systemami liczbowymi.
uint8_t i = 0b00000010; //dziesiętnie 2 i=(i<<3); //W wyniku operacji i będzie równe 0b00010000 czyli 16
Przesunięcie w lewo o 1 miejsce daje w wyniku to samo co mnożenie liczby przez 2, przesunięcie w lewo o 3 miejsca daje to samo co mnożenie przez 8 (2^3). Przesuwanie bitów jest szybsze od mnożenia! (Jeżeli liczba nie mieści się w rejestrze tracimy najstarsze bity)
uint8_t i = 0b00100000; //dziesiętnie 32 i=(i>>4); //W wyniku operacji i będzie równe 0b00000010 czyli 2
Analogicznie do powyższego przykładu można dzielić (Tracimy część ułamkową!)
LICZBA |= (1<<NUMER_BITU); //To samo co: LICZBA = LICZBA | (1<<NUMER_BITU); // | to logiczna operacja OR //Przykład: uint8_t i = 0; i |= (1<<3); //W wyniku operacji i będzie równe 0b00001000
LICZBA &= ~(1<<NUMER_BITU); // & to logiczna operacja AND // ~ to logiczna operacja NOT //Przykład: uint8_t i = 0xFF; i &= ~(1<<2); //W wyniku operacji i będzie równe 0b11111011
LICZBA ^= (1<<NUMER_BITU); //^ to logiczna operacja XOR //Przykład: uint8_t i = 0b10101010; i ^= (1<<1); i ^= (1<<2); //W wyniku operacji i będzie równe 0b10101100
W poniższych przykładach diody zapalane są stanem logicznym 1 czyli łączymy anodę przez rezystor do układu a katodę do masy. Połączenie odwrotne spowoduje że diody będą zapalane stanem logicznym niskim (anoda do VCC, katoda do uC).
/* * main.c * * Created on: 2-03-2011 * Author: Maciek * Najprostszy zegar binarny */ #include <avr/io.h> #include <util/delay.h> //Knfiguracja sprzętowa: do portu B podpięte są 4 diody do pinów 0-3 int main() { DDRB = 0x0F; //Modyfikacja rejestru DDRB: Ustaw piny 0-3 portu B jako wyjście PORTB = 0x00; //Modyfikacja rejestru PORTB: Ustaw początkowo wszystkie piny portu B na stan niski //Nieskończona pętla while(1) { PORTB++; //Inkrementacja wartości rejestru _delay_ms(1000); //Czekaj 1 sekundę } // Lepiej nie dopuszczać programu żeby wychodził z main (stosować nieskończoną pętle) // Dopoki nasz program nie korzysta z przerwań w wypadku braku pętli kompilator sam ją doda, ale z instrukcją // blokującą przerwania! (Stan na 2011, wcześniej mogło być inaczej) }
/* * main.c * * Created on: 2-03-2011 * Author: Maciek * Kolejny przykład */ #include <avr/io.h> #include <util/delay.h> //Knfiguracja sprzętowa: do portu B podpięte są 4 diody do pinów 0-3 //Dobrze jest na początku okrelić połączenia w poniższy sposób, w razie zmiany //konfiguracji sprzętowej łatwiej jest zaminić dane w jednym miejscu niż w 50 :) #define LED_DIR DDRB #define LED_P PORTB int main() { //Inicjalizacja portów LED_DIR = 0x0F; LED_P = 0x00; //Zapal pierwszą diodę LED_P |= (1<<0); _delay_ms(1000); //Zapal 3 diodę LED_P |= (1<<2); _delay_ms(1000); //Przełącz (zgaś) pierwszą diodę LED_P ^= (1<<0); _delay_ms(1000); // Zgaś 3 diodę LED_P &=~(1<<2); //Nieskończona pętla for(;;); }