Table of Contents
0x02 Migająca dioda
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.
Wymagania
- Działający programator, połączenie z układem
- Skonfigurowane środowisko
- Diody LED, odpowiednie rezystory
- Dobra znajomość języka C
- Znajomość praw logiki Boolea, podstawowych operacji NOT, AND, OR, XOR
Pierwszy program
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ę } }
Porty wejścia wyjścia
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):
- Wysokim (HI, 1) Prąd wypływa z układu, napięcie = napięciu zasilania
- Niskim (LO, 0) Prąd wpływa do układu, napięcie = potencjałowi masy
- Wysokiej impedancji (Hi-Z, floating) prąd nie wpływa ani nie wypływa. Można wyobrażać sobie jakby wyprowadzenie nie było do niczego podłączone lub jakby rezystancja wewnętrzna dążyła do nieskończoności.
Co więcej za pomocą portu możemy:
- Wpisywać dane (write) uC ustawia wyprowadzenie w określony stan logiczny.
- Wczytywać dane (read) uC sprawdza w jakim stanie logicznym znajduje się wyprowadzenie.
Sterowaniem zachowania portów zajmują się 3 rejestry:
- DDRx - Data Direction Register - kontroluje kierunek działania portu (wejście czy wyjście)
- PORTx - Pozwala na ustawianie stanu logicznego portu (HI/LO)
- PINx - Pozwala odczytywać stan logiczny portu
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:
- 8 pinów portu B (PB0→PB7)
- 6 pinów portu C (PC0→PC5)4)
- 8 pinów portu D (PD0→PD7)
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ć.
Programowanie w C
Na początku w ramach przypomnienia trochę podstawowych informacji dotyczących zapisu w C oraz trochę informacji specyficznych dla C AVR.
Typy danych
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
Zapis liczb
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.
Podstawowe operacje na bitach
- Bity numerujemy od 0 np. 8 bitowa liczba ma bity od 0 do 7. Znaczenie bitów rośnie od prawej do lewej.
- Przesunięcie bitowe w lewo np:
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)
- Przesunięcie bitowe w prawo np:
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ą!)
- Zapalanie bitu (set bit)
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
- Gaszenie bitu (clear bit)
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
- Przełączanie bitu (toggle bit)
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
Przykłady
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(;;); }
Zadania
- Zrealizuj efekt podobny do “Oka Cylona”
- Zrealizuj program zapalający diodę na coraz krótsze odcinki czasu
Uwagi
- Podłączenie programatora nie zakłóci pracy urządzenia (wysoka impedancja na złączu programatora) ale obecność czegokolwiek na pinach mosi miso sck na pewno zakłóci proces programowania, na czas programowania należy odłączyć wszystko co jest podpięte pod te złącza.
- Chwilowa obciążalność prądowa portu I/O w układzie ATmega8A to 40 [mA]5) nie powinno się obciążać wyjścia prądem ciągłym większym niż 30 [mA]!
- Nie wiesz czegoś? Sprawdź notę katalogową!