Obie płytki APP000 i APP001 posłużą nam do nauki
programowania mikrokontrolera ATTiny13. Gdy obie zworki J0 i J1
znajdują się w górnej pozycji, to po połączeniu ze sobą obu
płytek do poszczególnych linii PB0...PB4 są dołączone diody LED
D0...D4. Napiszemy kilka prostych programów, które sterują tymi
diodami. Przy okazji poznamy praktycznie nowe polecenia języka
C. Na początek nauczmy się sterować świeceniem poszczególnych
diod.
Program mrugający naprzemiennie dwoma diodami
Pierwszy program będzie mrugał naprzemiennie dwoma skrajnymi
diodami LED z częstotliwością około 1Hz. Diody skrajne są
połączone z liniami PB0 (górna dioda LED)
i PB4 (dolna dioda LED). Pozostałe
diody nas nie będą tu interesować.
Aby sterować diodą LED podłączoną do linii portu PBx, należy
ustawić tę linię jako wyjście. Jeśli teraz ustawimy bit x
rejestru PORTB na 0, to dioda LED będzie zgaszona. Gdy bit x
zostanie ustawiony na 1, dioda LED zaświeci się. Zmianę stanu
bitu linii PBx można również uzyskać przez wpisanie bitu o
wartości 1 do rejestru PINB. Normalnie rejestr PINB służy do
odczytu stanu linii PB0...PB4. Jeśli zatem zapisujemy coś do
niego, to jest to operacja specjalna i oznacza zmianę stanu bitu
na przeciwny w porcie PORTB . Musisz jednak pamiętać, że funkcja
ta nie jest dostępna na wszystkich mikrokontrolerach AVR.
Wprowadzono ją tylko dla serii ATTiny, aby zmniejszyć długość
programów. Układy ATMEGA nie wykonują jej i tam trzeba zmieniać
stan bitów bezpośrednio w rejestrze PORTB (lub
PORTC i PORTD).
Najpierw wersja dla ATTINY13. Podłącz programator do płytki
bazowej APP000. Płytkę aplikacyjną APP001 podłącz do płytki
APP000 i na końcu programator podłącz do gniazda USB w swoim
komputerze.
Uruchom Eclipse, utwórz projekt C dla ATTiny13 i wprowadź do
edytora poniższy program. Przeczytaj wszystkie komentarze i
upewnij się, że rozumiesz sposób jego działania.
/*
* main.c
*
* Created on: 17 wrz 2015
* Author: Geo
*/
#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
DDRB = 0b10001; // PB0 i PB4 jako wyjścia
PORTB = 0b00001; // D0 zaświecona, D4 zgaszona
while(1)
{
PINB = 0b10001; // Zmieniamy na przeciwny stan PB0 i PB4
_delay_ms(500); // Czekamy 1/2 sekundy
// I od nowa w pętli
}
}
|
Przy operacjach na portach mikrokontrolera stosowaliśmy
dotychczas stałe binarne, np. 0b11100. Na dłuższą metę takie
postępowanie nie jest zbyt dobre. Dlaczego? Otóż wyobraź
sobie, że pewien program zechcesz uruchomić na innym
mikrokontrolerze. Co się stanie, jeśli nie będzie on bitowo
kompatybilny z ATTiny13? Co to znaczy bitowo kompatybilny?
Znaczy to tyle, że bity o tych samych funkcjach znajdują się
w odpowiednich rejestrach na tych samych pozycjach. Jeśli
nie, to mikrokontrolery nie są bitowo kompatybilne. Jak to
rozwiązać? Bardzo prosto. Otóż w pliku io.h znajdują się
definicje każdego bitu wszystkich rejestrów. Tak naprawdę
Eclipse dołącza do twojego programu nie plik io.h, lecz plik
ioxxx.h dla zdefiniowanego w projekcie mikrokontrolera. Np.
dla ATTiny13 jest to plik iotn13.h. Wszystko to dzieje się
za twoimi plecami i w sumie nie musisz nawet o tym wiedzieć.
W pliku iotn13.h znajdują się dokładne definicje wszystkich
rejestrów sterujących oraz zawartych w nich bitów. Definicje
te wykorzystujesz w swoich programach, np. PORTA, PINB,
DDRB. W każdym z tych portów zdefiniowane są nazwy bitów.
Nazwy te określają położenia bitów w porcie:
Port |
DDRB |
Bit |
DDB5 |
DDB4 |
DDB3 |
DDB2 |
DDB1 |
DDB0 |
Położenie |
5 |
4 |
3 |
2 |
1 |
0 |
Port |
PORTB |
Bit |
PB5 |
PB4 |
PB3 |
PB2 |
PB1 |
PB0 |
Położenie |
5 |
4 |
3 |
2 |
1 |
0 |
Port |
PINB |
Bit |
PINB5 |
PINB4 |
PINB3 |
PINB2 |
PINB1 |
PINB0 |
Położenie |
5 |
4 |
3 |
2 |
1 |
0 |
Musisz teraz poznać kilka operacji bitowych. Na początek
przesunięcie w lewo: <<. Operacja przesuwa bity lewego argumentu
o tyle pozycji w lewo, ile wynosi prawy argument.
Przykłady:
0b0000111 << 2 daje w wyniku 0b0011100
0b0000011 << 4 daje w wyniku 0b0110000
0b0000001 << 3 daje w wyniku 0b0001000
1 << 0 daje w wyniku 0b00000001
1 << 1 daje w wyniku 0b00000010
1 << 2 daje w wyniku 0b00000100
1 << 3 daje w wyniku 0b00001000
1 << 4 daje w wyniku 0b00010000
...
Jeśli chcemy otrzymać wyrażenie, które ma ustawiony na 1 bit
na pozycji DDB3, to użyjemy (1<<DDB3), co daje w wyniku
0b001000.
Operacja alternatywy bitowej: |. Alternatywa działa
na odpowiadających sobie bitach swoich argumentów. Wynik
definiuje tabelka:
a |
b |
a | b |
0 |
0 |
0 |
0 |
1 |
1 |
1 |
0 |
1 |
1 |
1 |
1 |
Przykład:
0b00010000 |
| 0b00000110 |
0b00010110 |
Alternatywa bitowa łączy bity 1 obu argumentów. Pozwala nam
to ustawiać wybrane bity na 1. Na przykład chcemy otrzymać
wyrażenie, w którym na 1 są ustawione bity na pozycjach PB4, PB2
i PB1. Będzie to: (1<<PB4)|(1<<PB2)|(1<<PB1). Dlaczego? Spójrz:
1<<PB4 daje w wyniku 0b010000
1<<PB2 daje w wyniku 0b000100
1<<PB1 daje w wyniku 0b000010
Teraz za pomocą alternatywy bitowej łączymy ze sobą te
wyrażenia:
0b010000 |
0b000100 |
| 0b000010 |
0b010110 |
Dzięki tym dwóm operacjom stałe binarne możemy teraz
zapisywać w bardziej czytelnej formie (jeśli
będzie to konieczne). Nasz program wygląda następująco:
/*
* main.c
*
* Created on: 18 wrz 2015
* Author: Geo
*/
#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
DDRB = (1<<DDB4)|(1<<DDB0); // PB0 i PB4 jako wyjścia
PORTB = (1<<PB0); // D0 zaświecona, D4 zgaszona
while(1)
{
PINB = (1<<PINB4)|(1<<PINB0); // Zmieniamy na przeciwny stan PB4 i PB0
_delay_ms(500); // Czekamy 1/2 sekundy
// I od nowa w pętli
}
}
|
No dobrze, ale co zrobić, jeśli nasz mikrokontroler nie
posiada funkcji zapisu do PINB? W takim przypadku będziemy
musieli wykonać odpowiednią operację bezpośrednio na
rejestrze PORTB. Wykorzystamy tutaj funkcję logiczną sumy
symetrycznej. Podobnie jak alternatywa bitowa działa ona na
poszczególnych bitach swoich argumentów. Wynik operacji
definiuje tabelka:
a |
b |
a ^ b |
0 |
0 |
0 |
0 |
1 |
1 |
1 |
0 |
1 |
1 |
1 |
0 |
Jeśli jeden z bitów ustawiony jest na 1, to wynik jest
zaprzeczeniem drugiego bitu.
Przykład:
Przykład:
0b10110001 |
^ 0b11111111 |
0b01001110 |
W naszym programie utworzymy wyrażenie, w którym bity PB4 i
PB0 są ustawione na 1, a reszta bitów jest wyzerowana. Następnie
wykonamy operację ^ tego wyrażenia z zawartością rejestru PORTB
i wynik wpiszemy z powrotem do rejestru PORTB. W rezultacie bity
PB4 i PB0 zmienią stan na przeciwny.
/*
* main.c
*
* Created on: 18 wrz 2015
* Author: Geo
*/
#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
DDRB = (1<<DDB4)|(1<<DDB0); // PB0 i PB4 jako wyjścia
PORTB = (1<<PB0); // D0 zaświecona, D4 zgaszona
while(1)
{
PORTB = PORTB ^ ((1<<PB4)|(1<<PB0)); // Zmieniamy na przeciwny stan PB0 i PB4
_delay_ms(500); // Czekamy 1/2 sekundy
// I od nowa w pętli
}
}
|
Wyrażenie (1<<PB4)|(1<<BP0)
musi być wzięte
w nawiasy z uwagi na kolejność wykonywania operacji
logicznych ^ oraz |.
Program przesuwający punkt świetlny
Kolejny program tworzy przesuwający się punkt. Wykorzystamy
tutaj poznaną operację przesuwu bitowego. Przepisz do edytora
Eclipse poniższy kod, skompiluj go i prześlij do mikrokontrolera
na płytce APP000.
/*
* main.c
*
* Created on: 18 wrz 2015
* Author: Geo
*/
#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
DDRB = (1<<DDB4)|(1<<DDB3)|(1<<DDB2)|(1<<DDB1)|(1<<DDB0); // PB0..PB4 jako wyjścia
PORTB = 0; // Wszystkie diody zgaszone
while(1)
{
if(PORTB == 0) // Sprawdzamy, czy wszystkie diody zgaszone
PORTB = (1<<PB0); // Jeśli tak, to zapalamy diodę D0
_delay_ms(200); // Czekamy 1/5 sekundy
PORTB = PORTB << 1; // Przesuwamy wszystkie bity o 1 w lewo
}
}
|
Przesuw bitów w prawo uzyskujemy za pomocą operatora >>.
Drobna modyfikacja programu pozwala przesuwać punkt w prawo:
/*
* main.c
*
* Created on: 18 wrz 2015
* Author: Geo
*/
#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
DDRB = (1<<DDB4)|(1<<DDB3)|(1<<DDB2)|(1<<DDB1)|(1<<DDB0); // PB0..PB4 jako wyjścia
PORTB = 0; // Wszystkie diody zgaszone
while(1)
{
if(PORTB == 0) // Sprawdzamy, czy wszystkie diody zgaszone
PORTB = (1<<PB4); // Jeśli tak, to zapalamy diodę D4
_delay_ms(200); // Czekamy 1/5 sekundy
PORTB = PORTB >> 1; // Przesuwamy wszystkie bity o 1 w prawo
}
}
|
Program odbijający punkt świetlny
Następny program tworzy odbijający się punkt. W programie
musimy zastosować zmienną. Zmienna przechowuje informacje. Przed
pierwszym użyciem zmienną musimy zdefiniować. Definicja wygląda
następująco:
typ nazwa;
Typ określa rodzaj przechowywanej informacji. Tutaj
zastosujemy typ o nazwie int8_t. Określa on daną 8 bitową.
Nazwa zmiennej musi spełniać następujące warunki:
- Nie może być identyczna z żadnym słowem kluczowym języka
C.
- Może zawierać duże i małe litery łacińskie, cyfry oraz
znak podkreślenia _.
- Duże i małe litery są rozróżniane.
- Nazwa nie może rozpoczynać się od cyfry
(to jest zarezerwowane dla stałych
liczbowych).
Dane umieszczamy w zmiennej za pomocą operatora przypisania
=. Np. a = 10;
W programie tworzymy zmienną d, która będzie zawierała
informację o kierunku przesuwania bitów. Umówmy się, że d = 0
oznacza przesuw w lewo, a d = 1 oznacza przesuw w prawo.
Będziemy również badać położenie bitu 1 w rejestrze PORTB. Jeśli
bit ten znajdzie się na pozycji PB0, to przesuw zmienia się na
przesuw w lewo. Jeśli bit 1 znajdzie się na pozycji PB4, to
przesuw zmienia się na przesuw w prawo.
/*
* main.c
*
* Created on: 18 wrz 2015
* Author: Geo
*/
#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
int8_t d = 0; // Tworzymy zmienną d i nadajemy jej wartość 0 (przesuw w lewo)
DDRB = (1<<DDB4)|(1<<DDB3)|(1<<DDB2)|(1<<DDB1)|(1<<DDB0); // PB0..PB4 jako wyjścia
PORTB = (1<<PB0); // Zapalamy D0
while(1)
{
if(PORTB == (1<<PB0)) // Czy bit 1 na pozycji PB0?
d = 0; // Tak, zmieniamy na przesuw w lewo
else if(PORTB == (1<<PB4)) // Czy bit 1 na pozycji PB4?
d = 1; // Tak, zmieniamy na przesuw w prawo
_delay_ms(100); // Czekamy 1/10 sekundy
if(d) PORTB = PORTB >> 1; // Jeśli d różne od zera, przesuw w prawo
else PORTB = PORTB << 1; // Inaczej przesuw w lewo
}
}
|
Program tworzący efekt węża
Tutaj wykorzystamy przesuw bitowy oraz operację alternatywy
bitowej. Zasada jest dosyć prosta. W pętli wykonujemy przesuw w
lewo zawartości rejestru PORTB, wpisując w PB0 bit 1, gdy wąż
jest tworzony, lub 0, gdy wąż ma być usuwany. Ten dopisywany bit
będziemy pamiętać w zmiennej b. Gdy PORTB osiągnie stan
0, bit b ustawiamy na 1. Gdy PORTB osiągnie stan 0b11111, bit b
będziemy ustawiać na 0.
/*
* main.c
*
* Created on: 19 wrz 2015
* Author: Geo
*/
#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
int8_t b; // Dopisywany bit
DDRB = (1<<DDB4)|(1<<DDB3)|(1<<DDB2)|(1<<DDB1)|(1<<DDB0); // PB0..PB4 jako wyjścia
PORTB = 0; // Gasimy wszystkie diody
while(1)
{
_delay_ms(50); // Czekamy 1/20 sekundy
if(PORTB == 0) b = 1; // Ustawiamy odpowiednio dopisywany bit
else if(PORTB == 0b11111) b = 0;
PORTB = ((PORTB << 1)|b)&0b11111; // Przesuwamy PORTB z dopisaniem bitu b
}
}
|
Operacja &0b11111 ma na celu ograniczenie zmian w PORTB do 5
bitów od PB0 do PB4.
Ćwiczenie nr 19
- Zmodyfikuj program, tak aby wąż przesuwał się w prawo.
- Napisz program, w którym wąż porusza się wahadłowo tam i
z powrotem.
Kolejne programy będą wykorzystywały przyciski dostępne na
płytce APP001. Przełóż zworkę J0 w dolne (lewe
– patrząc od strony diod LED) położenie. W ten sposób do
linii PB0 zostanie dołączony przycisk W0. Dioda D0 już nie
będzie sterowana przez tę linię. Przycisk W0 zwiera linię PB0 do
masy, zatem przy odczycie stan wysoki linii PB0 świadczy o tym,
że przycisk W0 nie jest wciśnięty. Stan niski na linii PB0
oznacza wciśnięcie przycisku W0. Wewnętrznie linia PB0 musi być
podpięta przez rezystor podciągający do Vcc. Uzyskamy to
konfigurując odpowiednio port B w mikrokontrolerze na początku
programu (podpięcie linii PBx do opornika
podciągającego otrzymujemy wtedy, gdy bit DDBx jest ustawiony w
rejestrze DDRB na zero, czyli jako wyjście, a w rejestrze PORTB
bit PBx ma wartość 1).
Program monitorujący stan przycisku W0
Na rozgrzewkę napiszemy prosty program, który w pętli
monitoruje stan linii PB0 połączonej z przyciskiem W0. Jeśli
przycisk będzie naciśniety, to zostaną zapalone diody D1...D4.
Nie zapomnij o przełączeniu zworki J0.
/*
* main.c
*
* Created on: 20 wrz 2015
* Author: Geo
*/
#include <avr/io.h>
int main(void)
{
DDRB = (1<<DDB4)|(1<<DDB3)|(1<<DDB2)|(1<<DDB1); // PB1...PB4 jako wyjścia
PORTB = (1<<PB0); // Gasimy diody D1...D4.
// Do PB0 podpinamy opornik podciągający
while(1)
{
if(PINB & (1<<PINB0)) // Przycisk zwolniony?
PORTB = (1<<PB0); // Gasimy diody D1...D4.
// Do PB0 podpinamy opornik podciągający
else // Przycisk naciśniety?
PORTB = 0b11111); // Zapalamy diody D1...D4.
}
}
|
Program "bombowiec"
Następny program również monitoruje stan przycisku W0.
Jednakże tym razem nie są zapalane wszystkie diody.
Mikrokontroler przesuwa w lewo bity PB1...PB4 rejestru PORTB, po
czym dopisuje na pozycję PB1 zanegowany bit PINB0. Daje to efekt
opadających punktów świetlnych po każdym naciśnięciu przycisku
W0.
/*
* main.c
*
* Created on: 20 wrz 2015
* Author: Geo
*/
#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
DDRB = (1<<DDB4)|(1<<DDB3)|(1<<DDB2)|(1<<DDB1); // PB1...PB4 jako wyjścia
PORTB = (1<<PB0); // Gasimy diody D1...D4.
// Do PB0 podpinamy opornik podciągający
while(1)
{
_delay_ms(100); // Czekamy 1/10 sekundy
PORTB = ((PORTB&0b01110)<< 1)|(((PINB&0b00001)^0b00001)<<1)|0b00001;
}
}
|
Wyjaśnienia wymaga wyrażenie wpisywane do rejestru PORTB.
Przeanalizujmy je:
a = (PORTB$0b01110) |
Z PORTB wydzielamy bity PB1...PB3. Jest
to konieczne, ponieważ zawartość ta będzie przesuwana w
lewo. Do bitu PB1 zostanie wstawiony bit PB0, a ten w
rejestrze PORTB ma zawsze wartość 1, ponieważ steruje
dołączeniem opornika podciągającego. |
b = ((a) << 1) |
Bity PB1...PB3 przesuwamy w lewo o 1
pozycję. Na pozycję PB1 zostanie wstawiony bit 0, a na
pozycję PB4 trafi bit PB3. |
c = (PINB&0b00001) |
Odczytujemy rejestr wejścia PINB i
wydzielamy z niego ostatni bit PINB, czyli stan
przycisku W0. |
d = ((c)^0b00001) |
Ta operacja zaneguje ten bit. Jest to
konieczne, ponieważ stan 1 oznacza przycisk zwolniony, a
stan 0 naciśnięty. |
e = ((d)<<1) |
Otrzymany w ten sposób bit przesuwamy
na pozycję PB1. |
f = (e)|0b00001 |
Dodajemy bit 1 na pozycji PB0, który
musi być zawsze ustawiony, ponieważ kontroluje
dołączanie opornika podciągającego |
PORTB=b|f |
Otrzymane bity łączymy z podwyrażeniem
b i wpisujemy z powrotem do rejestru PORTB |
Program Enigma
W czasie II Wojny Światowej wojska niemieckie wykorzystywały
maszynę Enigma do kodowania przesyłanych wiadomości. Maszyna
była wyposażona w specjalny mechanizm, który po naciśnięciu
przycisku zapalał odpowiednią lampkę sygnalizacyjną, po czym
obracał wewnętrznymi pierścieniami kodującymi w taki sposób, że
ponowne naciśnięcie przycisku powodowało zaświecenie innej
lampki.
My nie skonstruujemy maszyny Enigma, bo jest to zbyt
skomplikowane. Jednak stworzymy coś, co ją przypomina. Otóż po
naciśnięciu przycisku W będzie zapalana jedna z diod D1...D4 w
sposób losowy. Jak uzyskać losowość? Utworzymy licznik, który
będzie bardzo szybko liczył. Gdy przycisk zostanie wciśnięty,
stan licznika posłuży do zapalenia jednej z diod D1...D4. Po
zwolnieniu przycisku stan diod LED nie będzie zmieniany, lecz
licznik zacznie szybkie zliczanie. To właśnie ta szybkość
zliczania da nam liczby przypadkowe.
/*
* main.c
*
* Created on: 20 wrz 2015
* Author: Geo
*/
#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
int8_t licznik=0;
DDRB = (1<<DDB4)|(1<<DDB3)|(1<<DDB2)|(1<<DDB1); // PB1...PB4 jako wyjścia
PORTB = (1<<PB0); // Gasimy diody D1...D4.
// Do PB0 podpinamy opornik podciągający
while(1)
{
licznik++; // Zwiększamy o 1 stan licznika
if((PINB&1) == 0) // Jeśli przycisk W0 wciśnięty,
{
PORTB = (1<<(1+licznik%4))|1; // Licznik steruje zaświeceniem diody
_delay_ms(10); // Czekamy na wygaśnięcie drgań styków
while((PINB&1)==0); // Czekamy na zwolnienie przycisku W0
_delay_ms(10); // Czekamy na wygaśnięcie drgań styków
}
}
}
|
Podobnie jak w poprzednim programie przeanalizujmy wyrażenie,
którego wartość jest umieszczana w rejestrze PORTB:
a = (licznik%4) |
obliczana jest reszta z dzielenia
(% jest operatorem reszty z dzielenia)
zmiennej licznik przez 4. W wyniku
otrzymujemy liczbę od 0 do 3. |
b = (1+(a)) |
resztę a zwiększamy o 1,
aby otrzymać pozycję b zapalanej diody LED
od 1 do 4
(BP1...PB4). |
c = (1<<(b)) |
w tym wyrażeniu otrzymujemy wartość, w
której bit 1 jest na pozycji b
(1...4). |
PORTB = (c)|1 |
do wyrażenia c dołączamy
na końcu bit 1 (PB0), który
steruje dołączeniem opornika podciągającego. |
Pętla while(PINB&1 == 0);
jest pętlą pustą, tzn.
nie zawiera żadnej instrukcji. W pętli sprawdzany jest tylko
stan bitu PINB0, czyli stan przycisku W0. Jeśli przycisk W0 jest
wciśnięty, to bit PINB0 ma wartość 0, warunek kontynuacji pętli
while jest spełniony i pętla wykonuje się w kółko. Gdy
przycisk W0 zostanie zwolniony, bit PINB0 przyjmie wartość 1 i
warunek pętli przestanie być spełniany, pętla zakończy się. W
efekcie program czeka na zwolnienie przycisku W przed losowaniem
kolejnej pozycji. Przed pętlą i za nią występują opóźnienia po
10ms, które są niezbędne, aby zanikły ewentualne drgania styków
przycisku. Sprawdź działanie programu bez tych opóźnień
(możesz je sobie zakomentować, tzn. umieścić
przed nimi //).
Program licznika dwójkowego
Dokonując drobnej poprawki w poprzednim programie, otrzymamy
licznik dwójkowy, który zlicza naciśnięcia przycisku W0. Wynik
jest wyświetlany na diodach LED D1...D4. Diody te posiadają
następujące wagi:
/*
* main.c
*
* Created on: 20 wrz 2015
* Author: Geo
*/
#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
int8_t licznik=0;
DDRB = (1<<DDB4)|(1<<DDB3)|(1<<DDB2)|(1<<DDB1); // PB1...PB4 jako wyjścia
PORTB = (1<<PB0); // Gasimy diody D1...D4.
// Do PB0 podpinamy opornik podciągający
while(1)
{
if((PINB&1) == 0) // Jeśli przycisk W0 wciśnięty,
{
PORTB = ((++licznik)<<1)|1; // Licznik steruje zaświeceniem diod
_delay_ms(10); // Czekamy na wygaśnięcie drgań styków
while((PINB&1)==0); // Czekamy na zwolnienie przycisku W0
_delay_ms(10); // Czekamy na wygaśnięcie drgań styków
}
}
}
|
Analizujemy wyrażenie, którego wartość jest wpisywana do
rejestru PORTB:
a = (++licznik) |
zwiększa o 1 stan licznika i używa tej
nowej wartości w wyrażeniu. |
b = ((a)<<1) |
bity licznika przesuwamy w lewo o 1
pozycję, aby znalazły się na miejscu bitów PB1...PB4. |
PORTB = (b)|1 |
na końcu dołączamy bit 1 na pozycji PB0
sterujący opornikiem podciągającym. |
Ćwiczenie nr 20
Przerób program, tak aby nie była w nim potrzebna zmienna
licznik.
Program sterujący położeniem punktu świetlnego
W tym programie wykorzystane będą oba przyciski W0 i W1.
Dlatego przełącz obie zworki J0 i J1 w położenie dolne. W ten
sposób do linii PB0 i PB1 zostaną dołączone przyciski W0 i W1.
Diody D0 i D1 nie będą już świeciły.
Program ma działać
następująco. Na diodach D2, D3 i D4 będzie wyświetlany punkt
świetlny. Naciskanie W0 przesuwa ten punkt w górę. Naciskanie W1
przesuwa punkt w dół. Jeśli żaden z przycisków W0 i W1 nie jest
naciśniety, to punkt zajmuje swoją ostatnio wybraną pozycję.
Jeśli punkt znajduje się w skrajnej pozycji, to jego dalsze
przesuwanie nie występuje (inaczej zniknąłby
nam z pola widzenia).
/*
* main.c
*
* Created on: 21 wrz 2015
* Author: Geo
*/
#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
DDRB = (1<<DDB4)|(1<<DDB3)|(1<<DDB2); // PB2...PB4 jako wyjścia
PORTB = (1<<PB2)|(1<<PB1)|(1<<PB0); // Do PB0 i PB1 oporniki podciągające
// Zaświecamy D2
while(1)
{
if((PINB&0b11)!=0b11) // Jeśli jest wciśnięty jeden z przycisków
{
_delay_ms(10); // Czekamy na wygaśnięcie drgań styków
if(((PINB&0b10)==0)&&((PORTB&0b10000)==0)) // Jeśli wciśnięty W1 i punkt nie w dolnym położeniu
PORTB = ((PORTB&0b1100)<<1)|0b11; // przesuwamy punkt w dół
if(((PINB&0b01)==0)&&((PORTB&0b00100)==0)) // Jeśli wciśnięty W0 i punkt nie w górnym położeniu
PORTB = ((PORTB&0b11000)>>1)|0b11; // przesuwamy punkt w górę
while((PINB&0b11)!=0b11); // Czekamy na zwolnienie przycisku
_delay_ms(10); // Czekamy na wygaśnięcie drgań styków
}
}
}
|
Program autodestrukcji
Nie, nie będziemy jeszcze nic wysadzać w powietrze.
Zaprojektujemy prosty układ "autodestrukcji" z odliczaniem
wstecznym. Będzie on działał następująco:
Po
naciśnięciu przycisku W0 rozpoczyna się odliczanie wstecz od
7 do 0 na diodach D2...D4. Jeśli w trakcie odliczania
zostanie naciśnięty przycisk W1, to odliczanie zostanie
przerwane i program wróci do stanu początkowego. Jeśli
przycisk W1 nie zostanie naciśniety zanim licznik osiągnie
0, to włączy się "detonator" i wszystkie 3 diody D2, D3 i D4
zaczną szybko mrugać. W tym stanie można jedynie zresetować
mikrokontroler przyciskiem RESET na płytce bazowej APP000.
/*
* main.c
*
* Created on: 26 wrz 2015
* Author: Geo
*/
#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
int8_t d;
DDRB = (1<<DDB4)|(1<<DDB3)|(1<<DDB2); // PB2...PB4 jako wyjścia
PORTB = (1<<PB1)|(1<<PB0); // Do PB0 i PB1 oporniki podciągające
while(1)
{
if((PINB&(1<<PINB0))==0) // Naciśnięto W0?
{
_delay_ms(10); // Wygaśnięcie drgań styków
PORTB = 0b11111; // Ustawiamy licznik na 7 (bity PB2...PB4)
d = 0;
while(PINB&(1<<PINB1)) // Czekamy na naciśnięcie W1
{
_delay_ms(10); // Opóźnienie 1/100 sekundy
if(d++ == 100) // Co setny obieg pętli
{
d = 0;
if(PORTB == 0b00011) // Licznik wyzerowany?
while(1) // Tak, wchodzimy w pętlę nieskończoną
{
PINB = 0b11100; // Mrugamy diodami D2...D4
_delay_ms(100);
}
PORTB -= 4; // Od licznika odejmujemy 1
}
}
_delay_ms(10); // Wygaśnięcie drgań styków
while((PINB&0b11)!=0b11); // Czekamy na zwolnienie przycisków
_delay_ms(10); // Wygaśnięcie drgań styków
PORTB = 0b00011; // Gasimy diody D2...D4
}
}
}
|