Operacje na bitach


Tematy pokrewne   Podrozdziały
(w budowie)
  Funkcje logiczne
Manipulacje bitami
Modyfikacja

 

 

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

 

 



List do administratora Serwisu Edukacyjnego Nauczycieli I LO

Twój email: (jeśli chcesz otrzymać odpowiedź)
Temat:
Uwaga: ← tutaj wpisz wyraz  ilo , inaczej list zostanie zignorowany

Poniżej wpisz swoje uwagi lub pytania dotyczące tego rozdziału (max. 2048 znaków).

Liczba znaków do wykorzystania: 2048

 

W związku z dużą liczbą listów do naszego serwisu edukacyjnego nie będziemy udzielać odpowiedzi na prośby rozwiązywania zadań, pisania programów zaliczeniowych, przesyłania materiałów czy też tłumaczenia zagadnień szeroko opisywanych w podręcznikach.



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

©2017 mgr Jerzy Wałaszek

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