Płytka aplikacyjna APP001


Tematy pokrewne   Podrozdziały
(w budowie)
  Projekt płytki aplikacyjnej
Programowanie płytki APP001
Podsumowanie
Rozwiązania zadań

 

 

Projekt płytki aplikacyjnej APP001

 
   
Następnym krokiem po zbudowaniu płytki bazowej z mikrokontrolerem ATTiny13 będzie budowa dla niej różnych przystawek, które posłużą nam do nauki programowania oraz dostarczą wiele zabawy i radości. Pierwsza płytka aplikacyjna APP001 będzie zawierała 5 diod LED oraz dwa przyciski. Zbudujemy ją w wersji z elementami przewlekanymi oraz z elementami SMD. Najpierw schemat:

obrazek

Zworki J0 i J1 służą do dołączania do linii PB0 i PB1 diod D0, D1 lub przycisków W0, W1. Dzięki temu rozwiązaniu płytka APP001 stanie się bardziej uniwersalna. Dioda DP sygnalizuje gotowość płytki do pracy. Jako diody D0...D4 możesz zastosować LED'y czerwone jasne lub niebieskie jasne. Oporniki są tak dobrane, aby zbytnio nie obciążać linii PB0...PB4. Linia PB5 nie jest używana. Złącze kątowe męskie będzie wpinane w złącze żeńskie na płytce bazowej. Patrząc na nie od przodu mamy następujące przypisania sygnałów:

PB4 PB2 PB0 Vcc
PB5 PB3 PB1 GND

 

Projekt nr 11 – płytka aplikacyjna APP001 w wersji przewlekanej

Uruchom aplikację Eagle. W panelu sterowania otwórz Projects, kliknij prawym przyciskiem myszki w katalog eagle i z menu kontekstowego wybierz opcję New Project. Utwórz nowy projekt o nazwie APP001. Nadaj mu odpowiedni opis.

obrazek

Kliknij prawym przyciskiem myszki w projekt APP001 i z menu wybierz New → Schematic. Na ekranie pojawi się znany ci już edytor schematów. Zapisz schemat pod nazwą app001.sch. Dodaj do niego ramkę jak w poprzednich projektach. Na schemacie umieść następujące elementy biblioteczne:

 

pinhead  PINHD-2X4  PINHD-2X4/90 x 1 (goldpiny kątowe męskie do połączenia z płytką bazową)

pinhead PINHD-1X3 PINHD-1X3 x 2 (zworki)

switch-omron  10-XX x 1 (przyciski)

supply1   Vcc x 1 (zasilanie)

supply1   GND x 4 (masa)

led   LED   LED3MM x 6 (diody LED)

rlc   R-EU   R-EU_0204/7 x 6 (oporniki)

 

obrazek

Teraz rozmieść elementy zgodnie ze schematem i połącz je przewodami (goldpiny kątowe JP1 obróć do góry nogami i odbij lustrzanie). Nadaj elementom odpowiednie nazwy i wartości:

obrazek

Zapisz schemat i przełącz się do edytora PCB. Ułóż elementy jak poniżej i dopasuj odpowiednio wielkość płytki PCB:

obrazek

Uruchom narzędzie Autorouter, warstwę górną (Top) ustaw na nieaktywną (N/A) i każ utworzyć ścieżki.

obrazek

Za pomocą poznanych narzędzi porozsuwaj odpowiednio ścieżki i pogrub je. Niektóre ścieżki poprowadź inaczej. Postaraj się wyeliminować przejścia ścieżek pomiędzy niektórymi polami lutowniczymi.

obrazek

Projekt płytki jest gotowy. Poniżej masz odpowiednie grafiki, do obróbki i do wydruku na drukarce laserowej.

Wersja przewlekana
APP001.sch
APP001.brd
APP001_a.png
APP001_b.png
APP001.svg
Element Ilość Opis
złącze męskie goldpin kątowe 2x4 1 złącze wejściowe
goldpin 1x3 prosty 2 przełączniki J0 i J1
opornik 1kΩ/0,125W 1 –(                )–
opornik 470Ω/0,125W 5 –(                )–
dioda LED 3mm 1 niebieska
dioda LED 3mm 5 czerwona
przycisk Omron 2  

 

Projekt nr 12 – płytka aplikacyjna APP001 w wersji SMD

W katalogu projektowym skopiuj plik app001.sch i zmień nazwę kopii na app001_smd.sch. Uruchom Eagle i załaduj plik app001_smd.sch do edytora schematów. Za pomocą narzędzia Replace zamień elementy elektroniczne na ich odpowiedniki SMD:

rlc   R-EU   R-EU_M0805   (oporniki)

SparkFun-LED   LED    LED0603 (dioda LED DN)

led   LED   LEDSML0805   (pozostałe diody LED)

Usuń ze schematu przyciski W0 i W1 i w ich miejsce wstaw:

SparkFun-Electromechanical   SWITCH-MOMENTARY-2   SWITCH-MOMENTARY-2SMD-1101NE (przyciski)

Przejdź do edytora SMD i ułóż elementy wg poniższego rysunku:

obrazek

Uruchom Autoruter. Tym razem wybierz obie warstwy Top i Bottom.

obrazek

Otrzymany układ ścieżek należy poprawić (muszą być grubsze i w miarę możności należy unikać przechodzenia ścieżki pomiędzy blisko położonymi polami lutowniczymi), aby otrzymać coś podobnego do poniższego obrazka:

obrazek

Płytkę wykonujesz dokładnie tak samo jak płytkę bazową. Ponieważ płytka jest dwustronna, otwory wiercisz od strony lutowania elementów przewlekanych, czyli od strony spodniej. Staraj się wiercić je prostopadle do powierzchni płytki, aby wyloty otworów znajdowały się w obrębie pól lutowniczych. Pod goldpiny otwory wiercisz wiertłem 0,9mm. Resztę otworów wiercisz wiertłem 0,7mm. Poniżej masz nasze pliki projektu Eagle oraz grafiki i strona do wydruku na drukarce laserowej w programie Inkscape.

Wersja SMD
APP001_SMD.sch
APP001_SMD.brd
APP001_SMD_a.png
APP001_SMD_b.png
APP001_SMD_t.png
APP001_SMD.svg
Element Ilość Opis
złącze męskie goldpin kątowe 2x4 1 złącze wejściowe
goldpin 1x3 prosty 2 przełączniki J0 i J1
opornik SMD 1kΩ 0805 1  
opornik SMD 470Ω 0805 5  
dioda SMD LED 0603 1 kolor dowolny
dioda SMD LED 0603 5 kolor dowolny
przycisk SMD 2  

obrazek obrazek

 

 

Programowanie płytki APP001

 
   
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.

obrazek

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:

  1. Nie może być identyczna z żadnym słowem kluczowym języka C.
  2. Może zawierać duże i małe litery łacińskie, cyfry oraz znak podkreślenia _.
  3. Duże i małe litery są rozróżniane.
  4. 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

  1. Zmodyfikuj program, tak aby wąż przesuwał się w prawo.
  2. 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:

D4 D3 D2 D1
8 4 2 1

 

/*
 * 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
        }
    }
}

 

 

 

Podsumowanie

 
   

Operacje bitowe

Przesuw w lewo:

wyrażenie << liczba_pozycji

Przesuw w prawo:

wyrażenie >> liczba_pozycji

Bitowa alternatywa:

wyrażenie | wyrażenie

Bitowa koniunkcja:

wyrażenie & wyrażenie

Bitowa suma symetryczna:

wyrażenie ^ wyrażenie

Zmienne

Definicja
typ nazwa;
typ nazwa = wartość;

Przypisanie

nazwa = wyrażenie;

Zwiększanie zawartości o 1

++nazwa;
nazwa++;

Typ int8_t oznacza daną 8 bitową ze znakiem o zakresie od -128 do 127.
Typ uint8_t oznacza daną 8 bitową bez znaku o zakresie od 0 do 255.

 

 

Rozwiązania zadań

 
   
Jeśli nie mogłeś sobie poradzić z napisaniem programów dla płytki APP001, to tutaj znajdziesz rozwiązania. Jednakże pożądanym byłoby, abyś do nich doszedł samodzielnie.

Ćwiczenie 19, program 1

Program tworzy węża świetlnego przesuwającego się w prawo. Zmieniamy jedynie położenie bitu b z PB0 na PB4 oraz operację przesuwu na przesuw w prawo. W tym przypadku staje się zbędna operacja &0b11111, ponieważ przy przesuwie w prawo bity  o wartości 1 nigdy nie wyjdą poza PB4.

/*
 * 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/10 sekundy
        if(PORTB == 0)            b = (1<<PB4); // ustawiamy odpowiednio dopisywany bit
        else if(PORTB == 0b11111) b = 0;
        PORTB = (PORTB >> 1)|b;   // przesuwamy PORTB z dopisaniem bitu b
    }
}

Ćwiczenie 19, program 2

Program tworzy węża świetlnego przesuwającego się wahadłowo tam i z powrotem. Do podstawowego programu dodajemy zmienną d, w której zapamiętujemy aktualny kierunek ruchu. Umawiamy się, że dla d = 0 wąż przesuwa się w lewo, a dla d = 1 w prawo. Kierunek ruchu zmieniamy na przeciwny po każdym przejściu węża, czyli gdy PORTB osiągnie wartość 0. Wykorzystujemy tutaj operację bitowej sumy symetrycznej d z 1, co daje w efekcie w zmiennej d bit o wartości przeciwnej.

/*
 * main.c
 *
 *  Created on: 19 wrz 2015
 *      Author: Geo
 */

#include <avr/io.h>
#include <util/delay.h>

int main(void)
{
    int8_t b,d=1;   // dopisywany bit b oraz kierunek ruchu w prawo
    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) d = d ^ 1; // zmiana kierunku ruchu węża
        if(d == 1)                // wąż w prawo
        {
            if(PORTB == 0)            b = (1<<PB4); // ustawiamy odpowiednio dopisywany bit
            else if(PORTB == 0b11111) b = 0;
            PORTB = (PORTB >> 1)|b;                 // przesuwamy PORTB z dopisaniem bitu b
        }
        else
        {
            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
        }
    }
}

Ćwiczenie nr 20

Zamiast stosować osobną zmienną licznik, możemy wykorzystać sam rejestr PORTB. Można to zrobić na wiele różnych sposobów. Oto jeden z nich:

/*
 * 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)
    {
        if((PINB&1) == 0) // jeśli przycisk W0 wciśnięty,
        {
            PORTB = (PORTB+2)|1;
            _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
        }
    }
}

Do rejestru PORTB dodajemy 2, ponieważ licznik obejmuje bity PB1...PB4. Wartość PB1 w systemie dwójkowym to 2. Dodanie 1 zmieniałoby bity PB0...PB4:

PB4 PB3 PB2 PB1 PB0
16 8 4 2 1

 

 


   I Liceum Ogólnokształcące   
im. Kazimierza Brodzińskiego
w Tarnowie

©2024 mgr Jerzy Wałaszek

Dokument ten rozpowszechniany jest zgodnie z zasadami licencji
GNU Free Documentation License.

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