Prezentowane materiały są przeznaczone dla uczniów szkół ponadgimnazjalnych. Autor artykułu: mgr Jerzy Wałaszek, wersja1.0 Konsultacja: Wojciech Grodowski |
©2013 mgr
Jerzy Wałaszek
|
0 | 0 | 1 | 1 | 0 | 1 | 0 | 1 |
Jak to zrobić? Istnieje kilka możliwości:
Liczbę możemy zapisać jako stałą binarną. W tym celu poprzedzamy ją zerem i literką b:
Jest to sposób najprostszy, jednakże dla ludzi mało czytelny. Człowiek po prostu źle reaguje na mało zróżnicowane dane. Dla ludzi naturalnym systemem zapisu liczb jest system dziesiętny. Liczbę binarną zawsze da się przedstawić jako liczbę dziesiętną. Poszczególne bity w zapisie liczby posiadają tzw. wagi, które zaznaczyliśmy poniżej kolorem czerwonym.
128 | 64 | 32 | 16 | 8 | 4 | 2 | 1 |
0 | 0 | 1 | 1 | 0 | 1 | 0 | 1 |
Aby otrzymać wartość tej liczby binarnej, wystarczy zsumować wagi pozycji, na których bit ma wartość 1. Otrzymamy:
W wybranym porcie mikrokontrolera umieszczamy zatem wartość 53, która odpowiada wartości binarnej 00110101. Ten sposób, chociaż skuteczny, jest rzadko wykorzystywany, ponieważ wymaga przeliczeń.
Druga metoda polega na wyrażeniu naszej liczby binarnej za pomocą liczby ósemkowej. Język C obsługuje stałe ósemkowe – aby stała została potraktowana jako ósemkowa, musi rozpoczynać się od cyfry 0. Zaletą systemu ósemkowego jest to, że bardzo prosto przelicza się liczby pomiędzy nim a systemem dwójkowym. Należy nauczyć się na pamięć tabelki:
Cyfra ósemkowa |
Bity |
0 1 2 3 4 5 6 7 |
000 001 010 011 100 101 110 111 |
Jedna cyfra ósemkowa odpowiada zawsze grupie trzech bitów. Aby zatem przekształcić naszą liczbę dwójkową na ósemkową, postępujemy tak:
Bity dzielimy na grupy po 3 od strony prawej do lewej, po czym każdą z tych grup zastępujemy cyfrą ósemkową (ostatnia grupa będzie zawierała tylko dwa bity, przyjmiemy zatem, że trzeci bit ma wartość 0):
00110101 | → | 000 | 110 | 101 | → | 065 |
0 | 6 | 5 |
W porcie umieszczamy liczbę ósemkową 065, która odpowiada liczbie dwójkowej 00110101. Ten sposób jest lepszy, ponieważ po nauczeniu się na pamięć tabelki praktycznie nic nie potrzebujemy liczyć, a konwersji dokonujemy w locie.
System ósemkowy nie psuje dokładnie do rozmiarów danych binarnych. Gdy dzieliliśmy bity na grupy 3 bitowe, to porcja 8 bitów została podzielona niezbyt zgrabnie na 2b 3b 3b. Z tego powodu programiści chętniej korzystają z systemu szesnastkowego przy konwersjach liczb dwójkowych. System szesnastkowy posiada dodatkowe sześć cyfr do reprezentacji wartości od 10 do 15 (cyfry systemu dziesiętnego kończą się na 9!). Należy nauczyć się tabelki konwersji:
Cyfra szesnastkowa |
Bity |
0 1 2 3 4 5 6 7 8 9 A B C D E F |
0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111 |
W systemie szesnastkowym każda cyfra odpowiada czterem bitom. Dzięki temu bajt, czyli osiem bitów, daje się zapisać przy pomocy jedynie dwóch cyfr szesnastkowych. Zasada konwersji jest podobna do konwersji dwójkowo ósemkowej. Liczbę binarną dzielimy na grupy po 4 bity od strony prawej do lewej, a następnie każdą z tych grup zastępujemy cyfrą szesnastkową wg tabelki.
00110101 | → | 0011 | 0101 | → | 0x35 |
3 | 5 |
Stałe szesnastkowe w języku C zapisujemy z zerem i znakiem x na początku. Do portu wprowadzamy zatem stałą 0x35, która odpowiada liczbie dwójkowej 00110101.
Istnieje jeszcze prostszy sposób, który nie wymaga od programisty żadnych przeliczeń. Każdy bit w zapisie liczby dwójkowej posiada swój numer:
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
0 | 0 | 1 | 1 | 0 | 1 | 0 | 1 |
W naszej liczbie na 1 są ustawione bity o numerach 5, 4, 2 i 1. Aby przekształcić numer bitu na jego wartość w liczbie binarnej (czyli na wagę pozycji, na której ten bit się znajduje), należy skorzystać z operacji przesunięcia bitowego w lewo. Operacja ta ma postać:
Jeśli wartość jest równa 1, a liczba przesunięć jest równa numerowi bitu, to wynikiem operacji jest zawsze waga pozycji bitu o podanym numerze. Jak to działa? Przeanalizuj poniższe rachunki:
w = 00000001
(to jest
wartość 1 zapisana dwójkowo jako liczba ośmiobitowa)
w << 3 = 00000001 << 3 = 0001000 = 8
Zatem:
1 << 0 = 1
(waga bitu na
pozycji 0)1 << 1 = 2
(waga bitu na pozycji 1)1 << 2 = 4
(waga bitu na pozycji 2)Otrzymane w wyniku przesunięć wartości bitów składamy w całość za pomocą bitowej operacji | (operacja logiczna LUB, daje wynik 1, jeśli jeden z argumentów ma wartość 1) lub dodawania:
00110101 = (1 << 5) | (1 << 4) | (1 << 2) | (1 <<
0)
lub
00110101 = (1 << 5) + (1 << 4) + (1 << 2) + (1 << 0)
Bity w rejestrach mikrokontrolera posiadają zdefiniowane nazwy odpowiednie do rejestru. W kontrolerze ATTINY13 są zdefiniowane następujące nazwy portów i ich bitów (oczywiście zamiast tych nazw możesz również używać opisanych powyżej numerów bitów):
Funkcja | Rejestr | Nazwy bitów rejestru | |||||
Rejestr kierunku danych | DDRB | DDB5 | DDB4 | DDB3 | DDB2 | DDB1 | DDB0 |
Rejestr wyjścia danych | PORTB | PB5 | PB4 | PB3 | PB2 | PB1 | PB0 |
Rejestr wejścia danych | PINB | PINB5 | PINB4 | PINB3 | PINB2 | PINB1 | PINB0 |
Oczywiście, nie ma obowiązku nazywania bitów rejestru DDRB nazwami DDBi, możesz równie dobrze użyć dla nich nazw PBi, ponieważ są to faktycznie numery bitów. Jednakże raczej stosuj tę konwencję, aby nie tworzyć zamieszania w programie.
Załóżmy teraz, że chcemy w naszym ATTINY13 ustawić jako wyjścia linie portu B PB4, PB3, PB2 i PB1, a linię PB0 jako wejście. Do rejestru kierunku danych będziemy musieli wprowadzić następujące bity:
DDRB | DDB5 | DDB4 | DDB3 | DDB2 | DDB1 | DDB0 |
0 | 1 | 1 | 1 | 1 | 0 |
Zatem posłużymy się instrukcją języka C:
DDRB = (1 << DDB4) | (1 << DDB3) | (1 << DDB2) |
(1 << DDB1);
To samo uzyskamy za pomocą poniższych instrukcji, które są sobie równoważne;
DDRB = (1 << 4) | (1 << 3) | (1 << 2) | (1 << 1);
DDRB = 0b11110;
DDRB = 0x1E;
DDRB = 036;
DDRB = 30;
Wybieraj ten sposób, który w danym momencie będzie najbardziej odpowiedni.
Często zdarza się sytuacja, że w porcie musimy ustawić na 1 wybrane bity. Operację taką wykonuje się za pomocą operacji bitowej alternatywy rejestru z maską. Przez maskę będziemy rozumieli daną binarną, w której są ustawione na 1 bity do zmiany. Załóżmy, że w rejestrze PORTB chcemy ustawić na 1 bit PB3. Maską będzie wyrażenie (1 << PB3). Wykonujemy zatem operację:
PORTB |= (1 << PB3);
Operator |= jest równoważny operacji:
PORTB = PORTB | (1 << PB3);
Ktoś spyta, dlaczego nie tak:
PORTB = (1 << PB3);
Różnica jest taka, że poprzednie operacje ustawiają na 1 bit PB3, lecz nie zmieniają stanu pozostałych bitów portu B. Może być to przecież bardzo istotne, jeśli pozostałe linie sterują innymi urządzeniami, nieprawdaż? Przyjrzyj się poniższym rachunkom:
PORTB = 00010010
(1 << PB3) = 00001000
00001000
| 00010010
00011010
W podobny sposób możemy ustawić na 1 kilka bitów. Wystarczy stworzyć maskę, gdzie jest więcej bitów o wartości 1. Np. ustawmy na 1 w rejestrze DDRB bity DDB2, DDB1 i DDB0 (czyli linie portu B PB2, PB1 i PB0 będą pracować jako wyjścia danych):
DDRB |= (1 << DDB2) | (1 << DDB1) | (1 << DDB0);
Jeśli żałujesz stukania w klawisze, to ten sam efekt uzyskasz instrukcjami:
DDRB |= 7;
DDRB |=0b111;
Osobiście jednak uważam, że ten pierwszy sposób jest bardziej czytelny, bo od razu widać, co ustawiamy.
Tym razem pragniemy wyzerować określony bit (bity) rejestru. Operację taką wykonujemy za pomocą bitowej koniunkcji z negacją maski i z odpowiednim rejestrem. Negację bitową uzyskamy za pomocą operatora ~. Na przykład:
~00011011 = 11100100
Zerowanie bitu wykonujemy tak:
PORTB &= ~(1 << PB3);
Jak to działa? Przyjrzyj się poniższym rachunkom:
PORTB = 00011111
(1 << PB3) = 00001000
~00001000 = 11110111
11110111
& 00011111
00010111
Do zamiany bitu stanu bitu na przeciwny wykorzystuje się operację bitową sumy symetrycznej. Operacja ta jest dwuargumentowa:
a | b | a ^ b |
0 | 0 | 0 |
0 | 1 | 1 |
1 | 0 | 1 |
1 | 1 | 0 |
Z tabelki tej wynika, że wynikiem operacji ^ jest 1 tylko wtedy, gdy oba bity biorące w niej udział mają różne stany. Tworzymy zatem maskę z bitem ustawionym na 1 i wykonujemy bitową operację ^ tej maski z wybranym portem. Odpowiedni bit portu zostanie zmieniony na przeciwny:
Przyjrzyj się rachunkom poniżej:
PORTB = 00011111
(1 << PB3) = 00001000
00001000
^ 00011111
00010111
PORTB = 00010111
(1 << PB3) = 00001000
00001000
^ 00010111
00011111
Pozostają nam zatem linie PB0...PB4 (5 linii wejścia). Dotychczas sterowaliśmy układy cyfrowe za pomocą przycisku, który zwierał do masy wejście podpięte przez opornik do plusa zasilania:
Użyty tu opornik nosi nazwę opornika podciągającego (ang. pull-up resistor), ponieważ ma on za zadanie "podciągać" napięcie na wejściu układu w górę do wartości Vcc, gdy przycisk jest rozwarty. Mikrokontrolery posiadają już wewnątrz swojej struktury odpowiednie oporniki podciągające wejścia do Vcc. Nie musimy ich zatem stosować jako osobnych elementów w budowanym układzie. Należy jedynie pamiętać, aby te oporniki przyłączyć do odpowiednich linii portu B (normalnie są one odłączone). Załóżmy, że chcemy do linii PB0 podłączyć przycisk zwierający ją do masy. Linia ta ma pracować jako wejście. Chcemy, aby przy rozwartym przycisku na linii PB0 panował wysoki poziom logiczny. Zatem linia PB0 musi wewnętrznie być połączona z opornikiem podciągającym. Robimy to tak:
DDRB &= ~(1 << DDB0);
// Ustawiamy linię PB0 jako wejście danych (zerujemy bit 0)
PORTB |= (1 << PB0); // Do linii PB0
podłączamy opornik podciągający
...
Tutaj wykorzystujemy dodatkową funkcję PORTB. Otóż, jeśli linia PBi
pracuje jako wejście, to ustawienie na 1 bitu PBi w PORTB powoduje
podłączenie do wejścia PBi opornika podciągającego. Opornik ten ustawi
poziom logiczny 1 na tej linii.
Jeśli linię portu sterujesz za pomocą przycisku, to powinieneś wiedzieć, że przyciski są urządzeniami mechaniczno-elektrycznymi, które wewnątrz posiadają sprężyste styki. Gdy naciskasz przycisk, to styki te się zwierają. Jednak często występują przy tym krótkotrwałe drgania. W efekcie sygnał na wejściu mikrokontrolera wygląda tak:
Mikrokontroler działa szybko. Prawidłowy odczyt stanu wejścia sterowanego przyciskiem często wymaga stosowania w programie krótkich opóźnień. Problem ten postaramy się rozwiązać w projektach, które zrealizujemy na tych zajęciach. Prześledź je uważnie.
Zmontuj poniższy układ na płytce stykowej.
|
|
Ponieważ zawsze świeci się tylko jedna z diod, mogą być podłączone do masy jednym opornikiem. Tutaj zastosujemy małe diody (o średnicy 3mm, koszt za sztukę około 20...30 gr) o prądzie 5...10mA, aby zbytnio nie obciążały wyjść mikrokontrolera.
Spis elementów:
Element | Ilość | Opis |
programator ISP | 1 | do zasilania i programowania mikrokontrolera |
płytka stykowa + kable | 1 | do montażu układu |
ATTINY13 | 1 | mikrokontroler (ATTINY2313, ATMEGA8) |
opornik 270Ω/0,125W | 1 | –( )– do ograniczania napięcia i prądu diod LED |
kondensator 100nF | 1 | do eliminacji zakłóceń (nie musisz go koniecznie stosować, lecz jest to zalecane) |
czerwona dioda LED | 1 | do sygnalizacji stanu wysokiego na wyjściu PB1 |
żółta dioda LED | 1 | do sygnalizacji stanu wysokiego na wyjściu PB2 |
przycisk | 1 | do sterowania wejścia PB0 |
Nieużywane linie PB3, PB4 i PB5 ustawimy programowo jako
wejścia i podepniemy do nich wewnętrzne oporniki podciągające. W naszej
aplikacji poszczególne linie będą pracowały jako:
Linia | Tryb | Funkcja linii portu B |
PB0 | wejście | odczyt stanu przycisku, podpięta do opornika podciągającego. |
PB1 | wyjście | steruje diodą czerwoną |
PB2 | wyjście | steruje diodą żółtą |
PB3 | wejście | nieużywana, podpięta do opornika podciągającego. |
PB4 | wejście | nieużywana, podpięta do opornika podciągającego. |
PB5 | wejście | nieużywana, podpięta do opornika podciągającego. Jest to linia RESET. W środowisku bardzo zakłóconym (przemysł, bliskość silników lub pól magnetycznych) zaleca się podpięcie jej dodatkowo zewnętrznym opornikiem 10kΩ do Vcc. W układach prototypowych nie musimy tego stosować, lecz w produkcie końcowym jest to wręcz konieczne. W przeciwnym razie mikrokontroler może się przypadkowo resetować. |
Program dla mikrokontrolera ATTINY13:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
/* * ATTINY13_0005.c * * Created: 2014-05-28 21:01:42 * Author: Jerzy Wałaszek */ #define F_CPU 1000000UL #include <avr/io.h> #include <util/delay.h> int main(void) { DDRB = (1<<DDB2)|(1<<DDB1); // Linie PB1 i PB2 jako wyjścia, pozostałe jako wejścia PORTB = (1<<PB5)|(1<<PB4)|(1<<PB3)|(1<<PB0); // Wszystkie wejścia podpięte do oporników podciągających while(1) { if(PINB & (1 << PINB0)) // Czytamy stan wejścia PB0 { // Stan wysoki, przycisk rozwarty PORTB &= ~(1<<PB2); // Gasimy diodę żółtą PINB = (1<<PB1); // Mrugamy diodą czerwoną _delay_ms(300); } else // Stan niski, przycisk zwarty { PORTB &= ~(1<<PB1); // Gasimy diodę czerwoną PINB = (1<<PB2); // Mrugamy diodą żółtą _delay_ms(100); } } } |
Nie zmieniając układu, zaprogramuj mikrokontroler poniższym programem. Teraz każde naciśnięcie przycisku zmienia aktywną diodę. Zwróć uwagę na kod w wierszach 21...28.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
/* * ATTINY13_0006.c * * Created: 2014-05-28 21:01:42 * Author: Jerzy Wałaszek */ #define F_CPU 1000000UL #include <avr/io.h> #include <util/delay.h> int main(void) { short int ry = 1; // red/yellow DDRB = (1<<DDB2)|(1<<DDB1); // Linie PB1 i PB2 jako wyjścia, pozostałe jako wejścia PORTB = (1<<PB5)|(1<<PB4)|(1<<PB3)|(1<<PB0); // Wszystkie wejścia podpięte do oporników podciągających while(1) { if((PINB & (1<<PINB0)) == 0) // Sprawdzamy, czy został naciśnięty przycisk { PORTB &= ~((1<<PB2)|(1<<PB1)); // Gasimy obie diody _delay_ms(25); // Czekamy na zanik drgań styków while((PINB & (1<<PINB0)) == 0); // Czekamy na zwolnienie przycisku _delay_ms(25); // Czekamy na zanik drgań styków ry ^= 1; // Zmieniamy aktywną diodę } if(ry) // ry = 1 - aktywna dioda czerwona, ry = 0 - aktywna żółta { PORTB &= ~(1<<PB2); // Gasimy diodę żółtą PINB = (1<<PB1); // Mrugamy diodą czerwoną _delay_ms(100); } else { PORTB &= ~(1<<PB1); // Gasimy diodę czerwoną PINB = (1<<PB2); // Mrugamy diodą żółtą _delay_ms(100); } } } |
Zmontuj poniższy układ na płytce stykowej.
|
|
Spis elementów:
Element | Ilość | Opis |
programator ISP | 1 | do zasilania i programowania mikrokontrolera |
płytka stykowa + kable | 1 | do montażu układu |
ATTINY13 | 1 | mikrokontroler (ATTINY2313, ATMEGA8) |
opornik 270Ω/0,125W | 3 | –( )– do ograniczania napięcia i prądu czerwonych diod LED |
opornik 220Ω/0,125W | 1 | –( )– do ograniczania napięcia i prądu zielonej diody LED |
kondensator 100nF | 1 | do eliminacji zakłóceń (nie musisz go koniecznie stosować, lecz jest to zalecane) |
czerwona dioda LED | 3 | do sygnalizacji stanu wysokiego na wyjściach PB1, PB2 i PB3 |
zielona dioda LED | 1 | do sygnalizacji stanu wysokiego na wyjściu PB4 |
przycisk | 1 | do sterowania wejścia PB0 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 |
/* * ATTINY13_0007.c * * Created: 2014-05-29 08:24:01 * Author: Jerzy Wałaszek */ #define F_CPU 1000000UL #include <avr/io.h> #include <util/delay.h> // Wybieramy lepsze nazwy na linie portu B #define SW PB0 #define D1 PB1 #define D2 PB2 #define D3 PB3 #define DU PB4 // Funkcja sprawdza, czy jest naciśnięty przycisk podłączony do linii PB0 // Jeśli nie, to wraca natychmiast z wynikiem 0. // Jeśli tak, to czeka na zwolnienie przycisku i wraca z wynikiem 1. //----------------------------------------------------------------------- char KeyPressed(void) { if(PINB & (1<<SW)) return 0; // Testujemy stan przycisku // Przycisk wciśnięty _delay_ms(25); // Czekamy na zanik wibracji styków while(!(PINB & (1<<SW))); // Czekamy na zwolnienie przycisku _delay_ms(25); // Czekamy na zanik wibracji styków return 1; // Informujemy o naciśniętym przycisku } // ********************** // *** PROGRAM GŁÓWNY *** // ********************** int main(void) { char state = 0; // stan układu, 0 = wszystko wyłączone char cnt; // Licznik DDRB = (1<<DU)|(1<<D3)|(1<<D2)|(1<<D1); // Definiujemy linie wyjściowe portu B PORTB = (1<<PB5)|(1<<SW); // Do PB5 (RESET) i do PB0 podłączamy oporniki podciągające // Gasimy diody D1,D2,D3 i DU while(1) { while(KeyPressed() == 0); // Czekamy na wciśnięcie przycisku state = 4; // stan 4 = mrugają diody D1, D2 i D3, DU zgaszona cnt = 0; while(state) // Wykonujemy cykl roboczy { if(KeyPressed()) state = 0; // Jeśli naciśnięto przycisk, zerujemy stan układu switch(state) { case 0 : PORTB = (1<<PB5)|(1<<SW); // Gasimy wszystkie diody LED break; case 1 : PORTB = (1<<PB5)|(1<<DU)|(1<<SW); // Zaświecamy DU break; case 2 : PORTB = (1<<PB5)|(1<<D1)|(1<<SW); // Zaświecamy D1 break; case 3 : PORTB = (1<<PB5)|(1<<D2)|(1<<D1)|(1<<SW); // Zaświecamy D1 i D2 break; case 4 : PORTB = (1<<PB5)|(1<<D3)|(1<<D2)|(1<<D1)|(1<<SW); // Zaświecamy D1, D2 i D3 break; } if(state > 1) { if(cnt & 1) _delay_ms(400); // W zależności od stanu licznika raz jest opóźnienie else // długie, a raz krótkie, co daje efekt mrugnięcia { PORTB = (1<<PB5)|(1<<SW); // Gasimy diody D1, D2 i D3 _delay_ms(100); } cnt++; // Modyfikujemy licznik if(cnt == 10) // Sprawdzamy wartość końcową licznika { cnt = 0; // Zerujemy licznik i zmniejszamy stan state--; } } } } } |
Sygnalizator jest sterowany przyciskiem. Standardowo świeci się światło zielone w sekcji samochodów oraz światło czerwone w sekcji pieszych. Gdy zostanie naciśnięty i zwolniony przycisk, następuje cykl zmiany świateł (wybrane czasy nie są naturalne):
Czas | Samochody | Piesi |
2s | ( ) | ( ) |
2s | ( ) | ( ) |
2s | ( ) | ( ) |
1s | ( ) | ( ) |
10s | ( ) | ( ) |
1s | ( ) | ( ) |
2s | ( ) | ( ) |
2s | ( ) | ( ) |
koniec | ( ) | ( ) |
Zmontuj na płytce stykowej następujący układ (diody dla sekcji pieszych i samochodów umieść osobno). Diody sekcji pieszych są podłączone do linii PB1. Gdy na tej linii mamy stan niski, świeci się dioda zielona. Gdy jest stan wysoki, świeci się dioda czerwona. Wykorzystujemy jedną linię do dwóch diod LED! Jest to możliwe, ponieważ w sekcji pieszych zawsze świeci się tylko jedna z nich. Dzięki temu nasz układ wykorzystuje tylko 4 linie portu B na sterowanie 5 diod LED.
|
|
DP – diody sygnalizacji dla pieszych
DS – diody sygnalizacji dla samochodów
Spis elementów:
Element | Ilość | Opis |
programator ISP | 1 | do zasilania i programowania mikrokontrolera |
płytka stykowa + kable | 1 | do montażu układu |
ATTINY13 | 1 | mikrokontroler (ATTINY2313, ATMEGA8) |
opornik 270Ω/0,125W | 2 | –( )– do ograniczania napięcia i prądu czerwonych diod LED |
opornik 120Ω/0,125W | 3 | –( )– do ograniczania napięcia i prądu zielonych i żółtych diod LED |
kondensator 100nF | 1 | do eliminacji zakłóceń (nie musisz go koniecznie stosować, lecz jest to zalecane) |
czerwona dioda LED | 2 | sygnał zakazu przejścia/wjazdu |
żółta dioda LED | 1 | sygnał uwaga |
zielona dioda LED | 2 | sygnał droga wolna |
przycisk | 1 | do uruchamiania cyklu |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 |
/* * ATTINY13_0008.c * * Created: 2014-06-02 17:55:00 * Author: Jerzy Wałaszek */ #define F_CPU 1000000UL #include <avr/io.h> #include <util/delay.h> // Wybieramy lepsze nazwy na linie portu B #define SW PB0 #define DP1 PB1 #define DS1 PB2 #define DS2 PB3 #define DS3 PB4 // Funkcja sprawdza, czy jest naciśnięty przycisk podłączony do linii PB0 // Jeśli nie, to wraca natychmiast z wynikiem 0. // Jeśli tak, to czeka na zwolnienie przycisku i wraca z wynikiem 1. //----------------------------------------------------------------------- char KeyPressed(void) { if(PINB & (1<<SW)) return 0; // Testujemy stan przycisku // Przycisk wciśnięty _delay_ms(25); // Czekamy na zanik wibracji styków while(!(PINB & (1<<SW))); // Czekamy na zwolnienie przycisku _delay_ms(25); // Czekamy na zanik wibracji styków return 1; // Informujemy o naciśniętym przycisku } // ********************** // *** PROGRAM GŁÓWNY *** // ********************** int main(void) { // Tablica definiuje czasy dla każdego ze stanów układu char TTime[] = {2,2,2,1,10,1,2,2,0}; // Tablica definiuje światła dla każdego ze stanów char TState[] = {(1<<DS1), (1<<DS1)|(1<<DS2), (1<<DS2), (1<<DS3), (1<<DS3)|(1<<DP1), (1<<DS3), (1<<DS3)|(1<<DS2), (1<<DS2), (1<<DS1)}; unsigned char state,i; // Ustawiamy linie wyjścia portu B DDRB = (1<<DP1)|(1<<DS1)|(1<<DS2)|(1<<DS3); // Do linii PB5 i SW podłączamy oporniki podciągające, zapalamy diodę DS1 PORTB = (1<<PB5)|(1<<SW)|(1<<DS1); while(1) { while(KeyPressed()==0); // Czekamy na klawisz for(state = 0; state < 9; state++) { PORTB = (1<<PB5)|(1<<SW); // Gasimy wszystkie diody PORTB |= TState[state]; // i zapalamy właściwe for(i = 0; i < TTime[state]; i++) _delay_ms(1000); } } } |
|
|
Dzięki takiemu połączeniu diod możemy sterować świeceniem każdej diody w parze. Zawsze uaktywniamy tylko jedną parę diod (aby nie przeciążać zbytnio linii PB4), ustawiając linię PB0, PB1, PB2 lub PB3 jako wyjście. Gdy linia portu nie jest wyjściem i nie ma podłączonego wewnętrznie opornika podciągającego, nie steruje diodami i są one zgaszone.
Spis elementów:
Element | Ilość | Opis |
programator ISP | 1 | do zasilania i programowania mikrokontrolera |
płytka stykowa + kable | 1 | do montażu układu |
ATTINY13 | 1 | mikrokontroler (ATTINY2313, ATMEGA8) |
opornik 270Ω/0,125W | 1 | –( )– do ograniczania napięcia i prądu diod LED |
kondensator 100nF | 1 | do eliminacji zakłóceń (nie musisz go koniecznie stosować, lecz jest to zalecane) |
czerwona dioda LED | 8 | do linijki świetlnej |
Program tworzy ruchomy punkt świetlny przebiegający kolejno od D0 do D7.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
/* * ATTINY13_0009.c * * Created: 2014-06-06 20:41:00 * Author: Jerzy Wałaszek */ #define F_CPU 1000000UL #include <avr/io.h> #include <util/delay.h> int main(void) { unsigned char mask = 1; DDRB = 0; // Inicjujemy wszystkie linie jako wejścia PORTB = (1<<PB5); // Podłączamy opornik podciągający do RESET while(1) { DDRB = (1<<PB4) | mask; PORTB = (1<<PB5) | mask; // Zaświecamy diodę w parze _delay_ms(50); PINB = (1<<PB4) | mask; // Zaświecamy drugą diodę w parze odwracając stany PB4 i PBx _delay_ms(50); mask <<= 1; // Przesuwamy bit maski if(mask & (1<<PB4)) mask = 1; } } |
Program tworzy ruchomy punkt świetlny, który porusza się tam i z powrotem.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
/* * ATTINY13_0010.c * * Created: 2014-07-06 07:28:00 * Author: Jerzy Wałaszek */ #define F_CPU 1000000UL #include <avr/io.h> #include <util/delay.h> int main(void) { unsigned char mask = 1; unsigned char dir = 1; DDRB = 0; // Inicjujemy wszystkie linie jako wejścia PORTB = (1<<PB5); // Podłączamy opornik podciągający do RESET while(1) { DDRB = (1<<PB4) | mask; if(dir) PORTB = (1<<PB5) | mask; // Zaświecamy diodę w parze else PORTB = (1<<PB5) | (1<<PB4); _delay_ms(50); PINB = (1<<PB4) | mask; // Zaświecamy drugą diodę w parze odwracając stany PB4 i PBx _delay_ms(50); if(dir) mask <<= 1; // Przesuwamy bit maski else mask >>= 1; if(mask & (1<<PB3)) dir = 0; if(mask & (1<<PB0)) dir = 1; } } |
A ten program jest przykładem, że ATTINY13 może sterować świeceniem tych diod w dowolny sposób. Program tworzy różne efekty świetlne. Postaraj się odkryć sposób jego działania (opiera się on na bezwładności oka ludzkiego).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
/* * ATTINY13_0011.c * * Created: 2014-06-07 10:06:00 * Author: Jerzy Wałaszek */ #define F_CPU 1000000UL #include <avr/io.h> #include <util/delay.h> int main(void) { // Dane dla PORTB unsigned char T[] = {0b100000, // Zgaszone 0b100001, // Świeci D0 0b110000, // Świeci D1 0b100010, // Świeci D2 0b110000, // Świeci D3 0b100100, // Świeci D4 0b110000, // Świeci D5 0b101000, // Świeci D6 0b110000}; // Świeci D7 // Dane dla DDRB unsigned char D[] = {0b000000, // Zgaszone wszystkie LED 0b010001, // Aktywne D0/D1 0b010001, // Aktywne D0/D1 0b010010, // Aktywne D2/D3 0b010010, // Aktywne D2/D3 0b010100, // Aktywne D4/D5 0b010100, // Aktywne D4/D5 0b011000, // Aktywne D6/D7 0b011000}; // Aktywne D6/D7 unsigned char i,j,k,m = 0,c = 0; while(1) { for(i = 0; i < 9; i++) { for(j = 0; j < 10; j++) for(k = 0; k < 9; k++) { switch(c) { case 0: m = (k <= i) ? k : 0; break; case 1: m = (k <= 8 - i) ? k : 0; break; case 2: m = (k >= 8 - i) ? k : 0; break; case 3: m = (k >= i) ? k : 0; break; case 4: m = (i && (k == i)) ? k : 0; break; case 5: m = (i && (k == 8 - i)) ? k : 0; break; case 6: ; case 7: m = (i && ((k == i) | (k == 8 - i))) ? k : 0; break; } DDRB = D[m]; PORTB = T[m]; _delay_ms(1); } } c = (c + 1) & 0x7; } } |
I Liceum Ogólnokształcące |
Pytania proszę przesyłać na adres email: i-lo@eduinf.waw.pl
W artykułach serwisu są używane cookies. Jeśli nie chcesz ich otrzymywać,
zablokuj je w swojej przeglądarce.
Informacje dodatkowe