Koło elektroniczno-informatyczne

Projektowanie płytek drukowanych w Eagle
Płytka aplikacyjna APP001

 

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 płytki aplikacyjnej APP001

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  

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

 

 

 

Funkcje logiczne

 
   
Programując mikrokontrolery, działasz na poziomie bitów. Dlatego operacje na bitach muszą stać się twoją drugą naturą. W rozdziale tym omówimy sposoby przetwarzania bitów. Wiedza ta jest wręcz kluczowa, upewnij się zatem, że wszystko rozumiesz.

W języku C mamy dwa rodzaje funkcji. Pierwszy rodzaj to funkcje operujące na wartościach logicznych (ang. logic functions) – traktują one swoje argumenty jak wartości logiczne. W języku C istnieją dwie wartości logiczne:

 

true (prawda) – dowolna wartość różna od zera
false (fałsz) – wartość równa zero

 

Język C nie ogranicza cię co do typu argumentu. Argumentem może być dowolne wyrażenie liczbowe lub logiczne. W wyrażeniach logicznych stosuje się zwykle różne operatory porównań, np.:

a == b   prawdziwe, gdy a jest równe b
a != b   prawdziwe, gdy a jest różne od b
a > b   prawdziwe, gdy a jest większe od b
a < b   prawdziwe, gdy a jest mniejsze od b
a >= b   prawdziwe, gdy a jest większe lub równe b
a <= b   prawdziwe, gdy a jest mniejsze lub równe b

Funkcji logicznych mamy trzy:

Negacja
a !a
false true
true false
  Alternatywa
a b a || b
false false false
false true true
true false true
true true true
  Koniunkcja
a b a && b
false false false
false true false
true false false
true true true

Negacja (zaprzeczenie logiczne) tworzy zaprzeczenie swojego argumentu. Jeśli argument jest fałszywy, to wartością negacji będzie prawda i na odwrót.

Alternatywa (suma logiczna) ma wartość prawdy, jeśli chociaż jeden z jej argumentów jest prawdziwy. Inaczej otrzymujemy fałsz.

Koniunkcja (iloczyn logiczny) ma wartość prawdy, jeśli wszystkie jej argumenty są prawdziwe. Inaczej otrzymujemy fałsz.

 

Funkcje logiczne pozwalają tworzyć złożone warunki, które często wykorzystujemy w programach. Na przykład:

!(a > b) – prawdziwe, jeśli a = b lub a < b, w tym przypadku można też użyć wyrażenia (a <= b).
(a < 4) || (a > 7) – prawdziwe, jeśli a leży poza przedziałem [4,7], czyli ma np. wartość 1 lub 9.

(a >= 4) && (a <= 7) – prawdziwe, jeśli a leży w przedziale [4,7], czyli ma np. wartość 4 lub 5.

 

Drugim rodzajem funkcji logicznych są funkcje operujące na poszczególnych bitach (ang. bitwise functions). Funkcje te traktują swoje argumenty nie jako wartości logiczne, lecz jako ciągi bitów. Operacja jest wykonywana na odpowiadających sobie bitach w argumentach funkcji. Wynikiem jest również ciąg bitów, w którym bity zostały ustawione zgodnie z definicją funkcji.

Logicznych funkcji bitowych mamy cztery:

Negacja
a ~a
0 1
1 0
  Alternatywa
a b a | b
0 0 0
0 1 1
1 0 1
1 1 1
  Koniunkcja
a b a & b
0 0 0
0 1 0
1 0 0
1 1 1
  Suma symetryczna
a b a ^ b
0 0 0
0 1 1
1 0 1
1 1 0

Przykłady:

Negacja:

~0b001110 → 0b110001

~   0 0 1 1 1 0
    1 1 0 0 0 1
  Alternatywa:

0b001110 | 0b000111 → 0b001111

    0 0 1 1 1 0
|   0 0 0 1 1 1
    0 0 1 1 1 1
  Koniunkcja:

0b001110 & 0b000111 → 0b000110

    0 0 1 1 1 0
&   0 0 0 1 1 1
    0 0 0 1 1 0
  Suma symetryczna:

0b001110 ^ 0b000111 → 0b001001

    0 0 1 1 1 0
^   0 0 0 1 1 1
    0 0 1 0 0 1

Zwróć szczególną uwagę na funkcję bitowej sumy symetrycznej. Jeśli bit jednego z argumentów ma wartość 1, to w wyniku na tym miejscu pojawi się zawsze zaprzeczenie bitu drugiego argumentu. Własność ta jest często wykorzystywana do zmiany stanu wybranych bitów na przeciwny.

Funkcje logiczne i logiczne bitowe można ze sobą łączyć. Na przykład:

!(PINB & 0b000001) – prawdziwe, jeśli bit PINB0 ma wartość 0.

(PORTB & 0b010001) || !(PORTB & 0b00010) – prawdziwe, jeśli jeden z bitów PB4/PB0 ma wartość 1 lub bit PB1 jest wyzerowany

(PINB & 0b000011) && (PORTB & 0b011000) – prawdziwe, jeśli wartość 1 ma jeden z bitów PINB0/PINB1 oraz jeden z bitów PB3/PB4.

Jak widzisz, funkcje logiczne pozwalają mikrokontrolerowi testować różne skomplikowane warunki.

 

 

Manipulacje bitami

 
   
W operacjach na bitach pojawia się pojęcie maski bitowej (ang. bit mask). Jest to po prostu jeden z argumentów bitowej funkcji logicznej, w którym w odpowiedni sposób zostały ustawione bity, tak aby otrzymać pożądany wynik operacji. Cechą wspólną wszystkich przedstawionych tutaj operacji bitowych będzie to, że zmianie ulegną wybrane bity. Pozostałe będą niezmienione. Jest to bardzo istotne, ponieważ w rejestrach mikrokontrolera, co zobaczymy później, bity mogą posiadać różne znaczenia i zwykle chcemy zmienić stan określonych bitów, które kontrolują jakąś funkcję. Pozostałe bity powinny pozostać w swoim stanie.

Ustawianie bitów na 1

Operacja ta jest bardzo prosta do wykonania. Tworzymy maskę bitową, w której pożądane bity. Następnie wykonujemy bitową operację alternatywy argumentu z maską. W wyniku otrzymujemy wartość, w której zostaną ustawione na 1 bity odpowiadające bitom maski 1. Pozostałe bity zachowają swój stan z argumentu.

Przykład:

    0 0 1 1 0 1 0  – argument
|   0 0 0 0 1 1 1  – maska bitowa
    0 0 1 1 1 1 1  – wynik

Podłącz do płytki bazowej APP000 płytkę APP001, płytkę bazową połącz z programatorem, a programator z gniazdem USB twojego komputera. Ustaw obie zworki J0 i J1 na płytce APP001 w położenie górne (diody D0 i D1 mają być dołączone do linii PB0 i PB1). Następnie uruchom środowisko Eclipse, utwórz w nim nowy projekt dla ATTINY13, skopiuj poniższy program do edytora, skompiluj go i prześlij do programatora.

Program tworzy w zmiennej licznik  liczący w górę. Stan licznika z ustawionym na 1 bitem b2 jest przesyłany do rejestru PORTB. W ten sposób dioda D2 zawsze świeci.

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

#include <avr/io.h>
#include <util/delay.h>
#define MASKA 0b000100

int main(void)
{
    int8_t licznik = 0;
    DDRB = 0b011111; // PB0...PB4 jako wyjścia
    while(1)
    {
        PORTB = licznik++ | MASKA;
        _delay_ms(100);
    }
}

W programie pojawiła się nowa konstrukcja: stała. Stała jest nazwą, której przypisano stałą wartość. Stałe definiujemy za pomocą konstrukcji:

 

#define nazwa wyrażenie

 

Nie umieszczaj za definicją stałej średnika. Powodem jest to, że powyższa deklaracja tworzy nazwę, która w programie zostanie zastąpiona podanym wyrażeniem wraz ze wszystkimi znakami, które to wyrażenie zawiera. Tak zdefiniowanej stałej nie możesz w programie zmieniać, ponieważ nie jest ona zmienną. Jeśli bity maski muszą się zmieniać, to maskę tworzymy dynamicznie lub w zmiennej.

Zerowanie bitów

W tym przypadku tworzymy maskę, w której bity do wyzerowania są ustawione na 0. Następnie wykonujemy bitową operację koniunkcji argumentu z maską. Wynikiem jest wartość, w której zostaną wyzerowane bity odpowiadające bitom o stanie 0 w masce. Pozostałe bity zachowają swój stan z argumentu.

Przykład:

    1 0 1 1 0 1 1  – argument
&   1 1 1 1 0 0 0  – maska bitowa
    1 0 1 1 0 0 0  – wynik

 

Program tworzy w zmiennej licznik  liczący w górę. Stan licznika z wyzerowanym bitem b2 jest przesyłany do rejestru PORTB. W ten sposób dioda D2 zawsze jest zgaszona.

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

#include <avr/io.h>
#include <util/delay.h>
#define MASKA 0b111011

int main(void)
{
    int8_t licznik = 0;
    DDRB = 0b011111; // PB0...PB4 jako wyjścia
    while(1)
    {
        PORTB = licznik++ & MASKA;
        _delay_ms(100);
    }
}

Kolejny program działa identycznie jak poprzedni. Pokazujemy w nim jedynie jak utworzyć maskę dynamicznie bezpośrednio w wyrażeniu:

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

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

int main(void)
{
    int8_t licznik = 0;
    DDRB = 0b011111; // PB0...PB4 jako wyjścia
    while(1)
    {
        PORTB = licznik++ & ~(1<<PB2);
        _delay_ms(100);
    }
}

Przeanalizujmy to wyrażenie:

a = 1<<PB2 otrzymujemy a = 0b00000100
b = ~(a) otrzymujemy b = 0b11111011

Widzisz zatem, że została utworzona maska z wyzerowanym bitem PB2.

Negacja bitów

Operacja jest równie prosta jak ustawianie bitu na 1. Tworzymy maskę bitową z ustawionymi na 1 bitami, które mają zostać zanegowane. Następnie wykonujemy operację bitowej sumy symetrycznej argumentu z maską. W wartości wynikowej bity argumentu odpowiadające bitom maski 1 zostaną zanegowane.

Przykład:

    1 0 1 1 0 1 1  – argument
^   0 1 1 1 0 0 0  – maska bitowa
    1 1 0 0 0 1 1  – wynik

Program tworzy w zmiennej licznik  liczący w górę. Stan licznika z zanegowanym bitem b2 jest przesyłany do rejestru PORTB.

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

#include <avr/io.h>
#include <util/delay.h>
#define MASKA 0b000100

int main(void)
{
    int8_t licznik = 0;
    DDRB = 0b011111; // PB0...PB4 jako wyjścia
    while(1)
    {
        PORTB = licznik++ ^ MASKA;
        _delay_ms(100);
    }
}

Operacje złożone

Wykorzystując podane tutaj operacje bitowe, możemy tworzyć różne skomplikowane wyrażenia. W następnym programie tworzymy licznik, który zlicza w górę co 1/10 sekundy. Następnie przenosimy do rejestru PORTB bity b3 i b4 licznika (na linie PB0 i PB1). Po tej operacji bity PB1...PB3 przesuwamy o 1 pozycję w lewo na bity PB2...PB4. Otrzymujemy w ten sposób dosyć ciekawy efekt świetlny.

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

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

int main(void)
{
    int8_t licznik = 0;
    DDRB = 0b011111; // PB0...PB4 jako wyjścia
    while(1)
    {
        PORTB = (PORTB & 0b11100) | ((licznik++ >> 3) & 0b00011);
        PORTB = (PORTB & 0b00011) | ((PORTB << 1) & 0b11100);
        _delay_ms(100);
    }
}

 

 

 

Modyfikacja

 
   
Modyfikacja rejestru lub zmiennej polega na zmianie zawartości na podstawie tego, co dana zmienna lub rejestr zawiera. Brzmi to może i skomplikowanie, lecz w rzeczywistości jest bardzo proste. Wyobraźmy sobie, że chcemy dodać do zmiennej np. liczbę 3. Możemy wykonać to następująco:

 

zmienna = zmienna + 3;

 

A gdybyśmy chcieli ją pomnożyć przez 3, to zastosowalibyśmy polecenie:

 

zmienna = zmienna * 3;

 

To właśnie jest modyfikacja, czyli zmiana zawartości, lecz nie na zupełnie nową, oderwaną od poprzedniej, tylko na wartość, która w jakiś sposób jest powiązana z poprzednią zawartością. Ponieważ tego rodzaju konstrukcje programowe są często wykorzystywane, w języku C istnieją specjalne operatory modyfikacji zmiennych i rejestrów.

Jeden z operatorów modyfikacji już poznałeś: ++, czyli zwiększanie o 1. Można go stosować na dwa sposoby:

 

zmienna++
++zmienna

 

Jeśli takie wyrażenie stosujesz samodzielnie, to nie ma znaczenia, który z nich wybierzesz. W obu wypadkach zmienna zwiększy swoją zawartość o 1. Różnica pojawia się, gdy użyjesz takiej konstrukcji w wyrażeniu. Np.:

 

a = b++;
a = ++b;

 

W pierwszym przypadku wartością wyrażenia b++ jest pierwotna zawartość zmiennej b. Dopiero później b zostaje zwiększone o 1. Czyli do zmiennej a trafi b sprzed modyfikacji.

W przypadku drugim zmienna b jest najpierw zwiększana, a następnie wynik (czyli b zwiększone o 1) staje się wartością wyrażenia. Czyli do zmiennej a trafi wartość b już zwiększona o 1.

Istnieje prosty sposób zapamiętania działania operatora ++. Wyrażenie czytamy od strony lewej do prawej, jeśli natkniemy się najpierw na zmienną, to wartością wyrażenia jest zawartość zmiennej (mówimy wtedy o tzw. późnej modyfikacji). Jeśli natkniemy się na operator ++, to wartością wyrażenia jest zwiększona o 1 zawartość zmiennej (mówimy o tzw. wczesnej modyfikacji).

W podobny sposób działa operator --, który zmniejsza zawartość zmiennej o 1:

 

zmienna--
--zmienna

 

Dla każdej operacji dwuargumentowej mamy odpowiednie operatory modyfikacji:

 

zmienna = zmienna operator wyrażenie;  →  zmienna operator= wyrażenie;

 

Na przykład operator += zwiększa zawartość zmiennej o wartość wyrażenia:

 

zmienna += wyrażenie;
a += 5;   // do zmiennej a dodaj 5
a += b-1; // do zmiennej a dodaj b-1

 

W poniższej tabeli zebraliśmy operatory modyfikacji dla poznanych dotychczas operacji arytmetycznych, logicznych i bitowych:

Operator Opis Przykład
+= dodaj
a += 10; // do a dodaj 10
-= odejmij
a -= b; // od a odejmij b
*= pomnóż
a *= 2; // a pomnóż przez 2
/= podziel
a /= 10; // a podziel przez 10
%= weź resztę z dzielenia
a %= 2; // w a zostaw resztę z dzielenia a przez 2
<<= przesuń bity w lewo
a <<= 1; // przesuń bity w a o 1 pozycję w lewo
>>= przesuń bity w prawo
a >>=3; // przesuń bity w a o 3 pozycje w prawo
|= wykonaj alternatywę bitową
a |= 0b00111; // ustaw w a trzy ostatnie bity na 1, reszty nie zmieniaj
&= wykonaj koniunkcję bitową
a &= 0b11; // pozostaw w a dwa ostatnie bity, resztę wyzeruj
^= wykonaj bitową sumę symetryczną
a ^= 0b11; // zaneguj w a dwa ostatnie bity, reszty nie zmieniaj
||= wykonaj alternatywę logiczną
a ||= b; // do a trafi wynik logiczny a || b
&&= wykonaj koniunkcję logiczną
a &&= b; // do a trafi wynik logiczny a && b

 

Ciekawą cechą operacji przypisania i modyfikacji w języku C jest to, że posiadają one wartość i mogą uczestniczyć jako argumenty w wyrażeniach. Wyrażenie z operatorem przypisania = ma wartość przypisywaną. Na przykład wyrażenie:

 

a = 5;

 

ma wartość 5, ponieważ tyle przypisujemy zmiennej a. Pozwala to wykonywać łańcuchowe przypisania:

 

a = b = c = d = 10;

 

Do wszystkich zmiennych a, b, c i d trafi liczba 10. A całość wyrażenia dalej ma wartość 10.

Wartością wyrażenia z operatorem modyfikacji jest wartość zmodyfikowanej zmiennej. Jeśli np. zmienna a zawiera 10, to wyrażenie:

 

a += 5;

 

Przyjmie wartość 15, ponieważ taką wartość będzie posiadać zmodyfikowane a. Jeśli teraz użyjemy tej operacji w wyrażeniu:

 

b = 5 * (a += 5);

 

to do zmiennej a  trafi 15, a do zmiennej b  trafi 15 x 5, czyli 75. Możliwości te mogą kusić początkujących programistów, jednakże ja preferuję czytelny, zrozumiały kod od poniższego horroru:

 

a *= ++b - (d %= (a /= c++ + --d)) * (b &= --c - d++);

Poniższy program tworzy w rejestrze PORTB licznik zwiększający swą zawartość o 3 przy każdym obiegu pętli (co 1 sekundę). Licznik zlicza od 0 do 30. Program jest przeznaczony dla płytki APP001. Zworki J0 i J1 na płytce APP001 ustaw w górnym położeniu.

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

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

int main(void)
{
    DDRB = 0b011111; // PB0...PB4 jako wyjścia
    PORTB = 0;       // Zerujemy licznik
    while(1)
    {
        if(PORTB < 30) PORTB += 3;
        else           PORTB = 0;
        _delay_ms(1000);
    }
}

 

 


   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