Operacje bitowe i maski

Operacje bitowe 

Podczas programowania mikro-kontrolerów w języku C bardzo przydatne mogą okazać się operacje bitowe dzięki którym możemy modyfikować wartość całego portu lub tylko jednego z bitów w prosty sposób. Poniżej przedstawione zostało 6 podstawowych operacji bitowych. Z czego 3 z nich będą towarzyszyć każdemu programiście mikro-kontrolerów przez cały czas a dokładnie „&” , „|”, „<<”

Podstawowe operacje bitowe

int a = 92; // binarnie: 0000000001011100
int b = 101; // binarnie: 0000000001100101
int c = a & b; // wynik: 0000000001000100
int d = a | b; // wynik: 0000000001111101
 
int x = 12; // binarnie: 1100
int y = 10; // binarnie: 1010
int z = x ^ y; // wynik: 0110
 
int a = 103; // binarnie: 0000000001100111
int b = ~a; // wynik: 1111111110011000
 
int a = 5; // binarnie: 0000000000000101
int b = a << 3; // binarnie: 0000000000101000
int c = b >> 3; // binarnie: 0000000000000101

Wymagane podstawy – przypomnienie

W poniższych przykładach użyjemy bezpośredniego adresowania. Polecenie możemy używać we wszystkich programach pisanych dla ARM (SAM3 i SAM4 przetestowane) które są kompilowane za pomocą GCC (GNU Compiler Collection). Nie wymaga ono podpinania żadnych bibliotek czy definicji. Przykładowe użycie:

(*(volatile unsigned int*)0x400E1010U) = 1<<27;

Jeżeli jednak zdecydujemy się używać ASF (Atmel Studio Framework) to możemy skrócić zapis do:

 (*(WoReg*)0x400E1000U) = 1<<27;

Alternatywą do ASF jest użycie definicji:

#define WoReg volatile unsigned int
(*(WoReg*)0x400E1000U) = 1<<27;

Powyższe polecenie wpisuje do rejestru o nr 0x400E1000U liczbę bitową 1 na pozycję 27 licząć od LSB (Najmniej znaczący bit).  W procesorze SAM3x8e rejestr 0x400E1000U jest to „PIO Controller PIO Enable Register”. Ustawienie 1 w tym rejestrze na w odpowiednim włącza możliwość sterowania odpowiednim pinem procesora. (Więcej w rozdziale „IO Port 32bit”).

Adresowanie / przesuniecie bitowe w praktyce

By dobrze zrozumieć mechanizm działania przesunięcia bitowego posłuzymy się specyfikacją procesora SAM3x8e (Ardruino due). Interesujący nas rejestr znajduje się na stronie 663 specyfikacji tego procesora. Rejestr 0x400E1000U odpowiedzialny jest za załączenie kontroli nad PIOB (Port Input Output B / Port wejscia wyjscia B). Każdy bit w tym rejestrze odpowiada za jeden pin wejscia/wyjsci w procesorze.

Załóżmy że chcemy załączyć sterowanie dla 1 pinu procesora a dokładniej Port B Pin 27 oznaczonego w specyfikacji jako PB27. By tego dokonać musimy wpisać do 32bitowego rejsestru liczbę odpowiadającą temu pinowi. Wartość którą należy wpiać to:

134217728 = 00001000 00000000 00000000 00000000 //BIN

w programie będzie to wyglądało tak:

(*(WoReg*)0x400E1000U) = 134217728; // wartość w systemie dziesiętnym
(*(WoReg*)0x400E1000U) = 0x8000000; // wartość w systemie szesnastkowym
(*(WoReg*)0x400E1000U) = B1000000000000000000000000100; // binarnie

Jeżeli jednak zechcemy od razu załączyć dwa porty dla przykładu PB27 oraz PB2 należy wpisać do rejestru sumę liczb 134217728 (PB27) + 4 (PB2).

(*(WoReg*)0x400E1000U) = 134217732; // wartość w systemie dziesiętnym 
(*(WoReg*)0x400E1000U) = 0x8000004; // wartość w systemie szesnastkowym 
(*(WoReg*)0x400E1000U) = B1000000000000000000000000000; // binarnie

Powyżej przedstawione są dwa problemy.

Pierwszy to łatwe i czytelne wpisywanie liczb do rejestru. Zapis

(*(WoReg*)0x400E1000U) = 134217728; //Załącz PB27

Jest podatny na popełnienie błędów przez programistę dlatego zaleca się użycie operacji bitowej przesunięcia oznaczonej symbolem „<<„. Dzieki niej wpisujemy liczbę 1 i przesuwamy ją na odpowiednie miejsce. W rejestrze 0x400E1000U wartość portu znajduje się na 27 bit/bicie. W rejestrze występuje liczenie zaczyna się od bit 0;

1<<27 = B1000000000000000000000000000
1<<27 = 134217728

Analogicznie możemy teraz załączyć inne piny portu B.

(*(WoReg*)0x400E1000U) = 1<<27;  //Załącz PB27
(*(WoReg*)0x400E1000U) = 1<<4;   //Załącz PB4
(*(WoReg*)0x400E1000U) = 1<<0;   //Załącz PB0
(*(WoReg*)0x400E1000U) = 1<<10;  //Załącz PB10
(*(WoReg*)0x400E1000U) = 1<<31;  //Załącz PB31

Zapis wygląda estetycznie i bardziej zrozumiale dla każdego początkującego programisty.

Drugi problem z jakim został przedstawiony powyżej to wpisanie dwóch wartości do rejestru w jednym poleceniu.

(*(WoReg*)0x400E1000U) = 1<<27; //Załącz PB27
(*(WoReg*)0x400E1000U) = 1<<4; //Załącz PB4

Powyższy zapis możemy zastąpić poleceniem przedstawionym w poprzednich przykładach.Wpisać do rejestru sumę liczb 134217728 (PB27) + 4 (PB2). jednak taki zapis jest mało intuicyjny:

(*(WoReg*)0x400E1000U) = 134217732;

Do poprawnego zapisu należy użyć oratora logicznego „OR” o oznaczeniu „|”. Więcej informacji o operatorach znajdziecie na początku tego rozdziału. Podany powyżej przykład będzie wyglądał teraz tak:

(*(WoReg*)0x400E1000U) = 1<<27 | 1<<4;    //Załącz PB27 i PB4

Oto inny przykład:

(*(WoReg*)0x400E1000U) = 1<<27 | 1<<4| 1<<0| 1<<10| 1<<31;    
//Załącz PB27, PB4, PB0, PB10, PB31

Ostatnim etapem jest zmiana cyferek na ludzkie wartości czyli użycie definicji.

#define PortB27 (1u << 27) /**< \nazwa która kontroluje pin PB27 */
(*(WoReg*)0x400E1000U) = PIOB27;

Poniżej znajduje się kolejny przykład do przeanalizowania.

#define PB27 (1u << 27)
#define PB4 (1u << 4)
#define PB0 (1u << 0)
#define PB10 (1u << 10)
#define PB31 (1u << 31)

(*(WoReg*)0x400E1000U) = PB27 | PB4| PB0| PB10| PB31;     
//Załącz PB27, PB4, PB0, PB10, PB31

Maski bitowe:

By stworzyć maskę bitową posłużymy się operacją AND oznaczoną symbolem „&”

 

Problem w tym że jeżeli odczytamy cały port trudno będzie wybrać wartosć na jednym pinie . Tu muszę opisać jak wybrac wartos pinu