Jeżeli kiedykolwiek czytając czyjś kod natknąłeś się na zapis typu:
syg3 = (frame & 0xFF000000) >> 24;
i sam widok Cię wzdragał, doskonale Cię rozumiem. Ten format jest trudny do ogarnięcia na pierwszy rzut oka. Ale jak tylko oswoisz się z powyższym zapisem, maski i przesunięcia okażą się nieocenionym przyjacielem w wielu sytuacjach.
Masek i przesunięć bitowych używamy wtedy, gdy chcemy z pewnej liczby (np. 0x12345678) wyciągnąć tylko jej część (np. 0x3456) i dalej posługiwać się tą skróconą formą.
Nazwa maski nie jest przypadkowo zbieżna z częścią garderoby zakrywającą twarz. Maska w odniesieniu do programowania również ma za zadanie zasłonić nam pewne rzeczy i skupić się jedynie na części istotnej dla nas.
Zanim omówimy maskę na konkretnym przykładzie, dwa słowa o konstrukcji ramek w automotive.
Ramki, czyli komunikaty wysyłane na magistrali są kontenerami zawierającymi w sobie zbiór zmiennych (inaczej: sygnałów) zapakowanych razem w jeden wagon. Załóżmy, że mam pewną ramkę składającą się z następujących sygnałów :
syg1 = 0xZZZZ
syg2 = 0xSSSS
syg3 = 0xPP
syg4 = 0xQQ
syg5 = 0xGGGG
Sygnały te są “zapakowane” w ramkę, która przychodzi do nas w formacie:
frame = 0xZZZZSSSSPPQQGGGG
Często w automotive używa się zapisu takich szesnastkowych ciągów bez “0x” na początku i dzieląc cyfry na pary znaków (czyli na bajty) dla większej czytelności.
Taka ramka może mieć więc zapis:
ZZ ZZ SS SS PP QQ GG GG
… prawda, że czytelniejsze?
Zanim przejdziesz dalej: zakładam, że wiesz jak działa koniunkcja bitowa. Jeśli nie – rzuć okiem do wikipedi.
No dobrze, ale jesteśmy wybrednymi testerami. Nie potrzebujemy całej tej liczby, lecz chcemy tylko dobrać się do wartości syg3 (czyli miejsc PP), aby w swoim przypadku testowym porównać ją z wartością oczekiwaną.
Przeprowadzamy więc mnożenie bitowe:
ZZ ZZ SS SS PP QQ GG GG
& 00 00 00 00 FF 00 00 00
_____________________________
= 00 00 00 00 PP 00 00 00
Co tu się stało?
Na pozycjach, które nas interesowały postawiliśmy jedynki na każdym bicie (czyli 0xF szesnastkowo), na pozostałych zaś zera. W rezultacie po pomnożeniu dostaliśmy wartość:
0xPP000000
ale to jeszcze nie nasza wartość syg3, ponieważ mamy zera z prawej strony, które zmieniają nam wartość liczby. Musimy więc dokonać jeszcze przesunięcia bitowego w prawą stronę, pamiętając że każdy znak szesnastkowy to 4 bity. Chcąc pozbyć się sześciu zer z prawej strony, przesuwamy liczbę o 4*6 = 24 bity
0xPP000000 >> 24 = 0xPP
// powyższa linijka to działanie matematyczne, znak = oznacza "równa się". Liczba po lewej stronie przesunięta o 24 bity jest równa liczbie po prawej stronie. Nie jest to fragment kodu.
mamy to!
wyłuskaliśmy samo 0xPP.
Skracając więc ten wywód, moglibyśmy zapisać od samego początku:
syg3 = (frame & 0xFF000000) >> 24;
Dla jasności przykładu, użyłem tu tylko eF-ów i zer. Możliwe jednak, że w layoucie ramki sygnał będzie zajmował np nie 8 a 7 bitów. Wtedy maska może wyglądać np. tak: 0x7F00.
Podsumowanie i linki
Temat operacji logicznych jest rozległy i celowo omówiłem tylko jego skrawek, metod, których najczęściej używałem przy testowaniu elektroniki samochodowej. Jeśli jednak chcesz poznać szerzej powyższe zagadnienia w ujęciu ogólnym, zachęcam Cię do zapoznania się z tą stroną:
https://binaryupdates.com/bitwise-operations-in-embedded-programming/
Zachęcam do pozostawienia komentarza pod wpisem, zwłaszcza w przypadku pytań lub niejasności.
Dodaj komentarz