Pytanie:
Dlaczego kod AVR wykorzystuje przesunięcie bitów
Blair Fonville
2018-02-22 03:41:07 UTC
view on stackexchange narkive permalink

W programowaniu AVR bity rejestru są niezmiennie ustawiane przez przesunięcie w lewo 1 do odpowiedniej pozycji bitu - i są usuwane przez uzupełnienie jedynkami.

Przykład: dla ATtiny85 mógłbym ustawić PORTB, b 4 w ten sposób:

  PORTB | = (1<<PB4);
 

lub wyczyść to w ten sposób:

  PORTB & = ~ (1<<PB4);
 

My Pytanie brzmi: Dlaczego jest to zrobione w ten sposób? Najprostszy kod kończy się bałaganem przesunięć bitowych. Dlaczego bity są definiowane jako pozycje bitów zamiast masek.

Na przykład nagłówek IO dla ATtiny85 zawiera to:

  #define PORTB _SFR_IO8 (0x18)
# zdefiniować PB5 5
# zdefiniować PB4 4
# zdefiniować PB3 3
# zdefiniować PB2 2
# zdefiniować PB1 1
# zdefiniować PB0 0
 

Dla mnie o wiele bardziej logiczne byłoby zdefiniowanie bitów jako masek (w ten sposób):

  #define PORTB _SFR_IO8 (0x18)
# zdefiniować PB5 0x20
# zdefiniować PB4 0x10
# zdefiniować PB3 0x08
# zdefiniować PB2 0x04
# zdefiniować PB1 0x02
# zdefiniować PB0 0x01
 

Moglibyśmy więc zrobić coś takiego:

  // jako maski bitowe
PORTB | = PB5 | PB3 | PB0;
PORTB & = ~ PB5 & ~ PB3 & ~ PB0;
 

, aby odpowiednio włączać i wyłączać bity b 5 , b 3 i b 0 . W przeciwieństwie do:

  // jako pola bitowe
PORTB | = (1<<PB5) | (1<<PB3) | (1<<PB0);
PORTB & = ~ (1<<PB5) & ~ (1<<PB3) & ~ (1<<PB0);
 

Kod maski bitowej czyta znacznie wyraźniej: ustaw bity PB5 , PB3 i PB0 . Ponadto wydaje się, że oszczędza operacje, ponieważ bity nie muszą już być przesuwane.

Pomyślałem, że może zostało to zrobione w ten sposób, aby zachować ogólność, aby umożliwić przeniesienie kodu z n -bitowego AVR na m -bitowy (przykład 8-bitowydo wersji 32-bitowej).Ale wydaje się, że tak nie jest, ponieważ #include <avr / io.h> rozwiązuje się jako pliki definicji specyficzne dla docelowego mikrokontrolera.Nawet zmiana celów z 8-bitowego ATtiny na 8-bitową Atmega (gdzie definicje bitów zmieniają się składniowo z PBx na PORTBx , na przykład), wymaga zmian w kodzie.

Popieram to.Nawet użycie wszechobecnego `_BV (b)` zamiast `(1 << b)` powoduje niepotrzebny bałagan.Mnemoniki bitowe zwykle definiuję za pomocą `_BV ()`, np.`# zdefiniować ACK _BV (1)`.
Kiedy zdamy sobie sprawę, że kompilator zinterpretuje je jako tę samą stałą, której użycie w * kodzie źródłowym * jest naprawdę kwestią preferencji.W swoim własnym kodzie rób wszystko, co uważasz za najmądrzejsze;modyfikując istniejące projekty, trzymaj się ich tradycji.
@ChrisStratton Zdaję sobie z tego sprawę bardzo dobrze.Myślę, że nie wyjaśniłem, że w pełni rozumiem związane z tym operacje.Moje pytanie dotyczy wyłącznie czytelności powstałego w ten sposób kodu źródłowego - co moim zdaniem ma negatywny wpływ na sposób definiowania bitów.
Ostatecznie wybór jest kwestią opinii - jeśli chcesz wiedzieć, dlaczego dana baza kodu używa określonej metody, musisz wyśledzić jej autorów i zapytać ich o źródło ich polityki.Inni mogą tylko * spekulować *, dlaczego mogą lub nie mogą korzystać z tych metod - a Stack Exchange nie jest miejscem do spekulacji.
„Ponieważ podejście z maską bitową wyraźnie dałoby bardziej czytelny kod użytkownika końcowego” - Twoja osobista opinia.Uważam, że przesunięcie jedynek i zera we właściwe miejsce jest o wiele wyraźniejsze niż zgadywanie, czy kilka dodawanych liczb to maski bitowe, czy nie.
@TomCarpenter Ciekawe.Cóż, może niechcący zadałem pytanie oparte na opiniach.Tak czy inaczej, pojawiły się dobre opinie.Wychodząc z tła DSP (TI) (gdzie maska bitowa jest normą), wydawało mi się, że składnia jest tak dziwna, że doszedłem do wniosku, że jest jakiś konkretny powód.
To absolutnie kwestia opinii.Uważam, że tworzenie maski w locie z przesunięciem bitowym jest bardziej czytelne niż założenie, że stała jest maską bitową.
@BlairFonville Może już to wiesz, ale ARM działa dokładnie tak, jak opisujesz (z maskami bitowymi).
@Chi Nie, nie wiedziałem tego.Cieszę się, że to wskazałeś!
Dwa odpowiedzi:
Curd
2018-02-22 03:44:43 UTC
view on stackexchange narkive permalink

Najprostszy kod kończy się bałaganem przesunięć bitowych. Dlaczego bity są definiowane jako pozycje bitów zamiast masek.

Nie. Ani trochę. Zmiany występują tylko w kodzie źródłowym C, a nie w skompilowanym kodzie maszynowym. Wszystkie pokazane przykłady mogą i zostaną rozwiązane przez kompilator w compile time, ponieważ są to proste wyrażenia stałe.

(1<<PB4) to tylko sposób na powiedzenie „bit PB4”.

  • Więc to nie tylko działa, ale nie generuje większego rozmiaru kodu.
  • Dla programisty jest również sensowne nazywanie bitów za pomocą ich indeksu (np. 5), a nie maski bitowej (np. 32), ponieważ w ten sposób kolejne liczby 0..7 mogą być używane do identyfikacji bity zamiast niezręcznej potęgi dwóch (1, 2, 4, 8, .. 128).

  • Jest jeszcze jeden powód (być może główny):
    Pliki nagłówkowe C mogą być używane nie tylko do kodu C, ale także do kodu źródłowego asemblera (lub kodu asemblera zawartego w kodzie źródłowym C). W kodzie asemblera AVR na pewno nie chcesz używać tylko masek bitowych (które można utworzyć z indeksów przez przesunięcie bitowe). W przypadku niektórych instrukcji asemblera do manipulacji bitami AVR (np. SBI, CBI, BST, BLD) należy użyć indeksów bit jako bezpośredniego operatora w ich kodzie operacyjnym.
    Tylko wtedy, gdy identyfikujesz bity SFR przez indices (nie przez maskę bitową), możesz użyć takich identyfikatorów bezpośrednio jako operand instrukcji asemblera. W przeciwnym razie musiałbyś mieć dwie definicje dla każdego bitu SFR: jedną definiującą jego indeks bitowy (który może być użyty np. Jako operand we wspomnianych wcześniej instrukcjach asemblera manipulujących bitami) i drugą definiującą jego maskę jest manipulowany).

To.Większość kompilatorów wykonuje ciągłą propagację w ramach procesu optymalizacji.
Rozumiem, że.Nie wątpię, czy to działa, czy nie.Wiem, że tak.Pytam, dlaczego definicje są napisane takimi, jakimi są.Według mnie znacznie poprawiłoby to czytelność kodu, gdyby były zdefiniowane jako maski zamiast pozycji bitów.
Najprawdopodobniej prawie wszyscy.Byłby to bardzo zły kompilator, gdyby nie rozwiązał takich prostych wyrażeń stałych.
@Blair Fonville: Zobacz moje rozszerzenie odpowiedzi.Jeśli jesteś do tego bardziej przyzwyczajony, programista jest jeszcze bardziej czytelny i sensowne jest używanie indeksów bitów zamiast ich masek bitowych (jak proponowałeś).
Nie jestem pewien, czy zgadzam się, że „PORTB | = PB4” jest mniej czytelny niż „PORTB | = (1 << PB4)”.W obu przypadkach programista rzadko musi faktycznie wiedzieć, co reprezentuje PB4 - czy jest równe „4” czy „0x10” jest niewidoczne dla programisty.W rzeczywistości jego rzeczywista wartość jest ukryta w podwójnie zmapowanym pliku dołączania, który jest skierowany na rzeczywisty chip.
Nie chcę twierdzić, że „PORTB | = PB4” jest mniej czytelny niż „PORTB | = (1 << PB4)”.tylko, że różnica jest dobrze znośna, zwłaszcza jeśli istnieje dobry powód.
Twoja odpowiedź sprawia, że od tego czasu, ale odgrywanie roli adwokata diabła: naprawdę nie wydaje się, aby istniał „dobry powód”.To dużo zanieczyszczenia kodu z pozoru bez korzyści.Plik definicji może z łatwością zawierać pojedyncze makro mapujące maskę do indeksu.Nie byłoby potrzeby stosowania „dwóch definicji dla każdego SFR…”.Makro (w C i / lub asm) nie obchodziło czy 0x10 jest maską dla PORTB czy EEARH ... tak czy inaczej, zawsze jest to maska dla bitu 4. To załatwiłoby kilka wywołań asm, które wymagają indeksu.
Myślę, że ta odpowiedź mija się z celem.Nigdy nie mówi o wydajności kodu ani kompilatorze.Chodzi o bałagan w _kodzie źródłowym_.
@Blair Fonville: nie ma łatwego sposobu na zdefiniowanie takiego makra.Musiał obliczyć logarytm do podstawy 2. Nie ma funkcji preprocesora obliczającej logarytm.To znaczy.można to zrobić tylko przy użyciu stołu i myślę, że byłby to bardzo zły pomysł.
@Curd Myślę, że proste makro LUT działałoby dobrze.Istnieje tylko 8-bitowe indeksy (lub 32, dla kontrolerów 32-bitowych).
@pipe: Nie mówię o tym, ponieważ po prostu nie uważam tego za „zanieczyszczenie kodu” lub „bałagan w kodzie źródłowym” (czy jakkolwiek chcesz to nazwać).Wręcz przeciwnie, myślę, że warto nawet przypomnieć programiście / czytelnikowi, że stała, której używa, jest potęgą dwójki (i jest to robione za pomocą wyrażenia shift).
Czy mógłbyś zdefiniować makro w C, które po prostu umożliwia używanie PORTB setbit (PB5);i Clearbit PORTB (PB5);oczyścić kod źródłowy?Jestem pewien, jak działa rozszerzenie makr ...
@RJR, Blair Fonville: oczywiście jest łatwo zdefiniować takie makra, ALE używając prostych definicji preprocesora jest w porządku Unikałbym makr preprocesora (aka preprosessor functios) kiedy tylko jest to możliwe, ponieważ mogą przeprowadzać debugowanie (przechodzenie przez kod źródłowy C za pomocą debuggera)wyjątkowo nieprzejrzysty.
kkrambo
2018-02-22 19:41:10 UTC
view on stackexchange narkive permalink

Być może przesunięcie bitów nie jest jedynym przypadkiem użycia definicji PB * . Być może są inne przypadki użycia, w których definicje PB * są używane bezpośrednio, a nie jako kwoty przesunięcia. Jeśli tak, to uważam, że zasada DRY doprowadziłaby do zaimplementowania jednego zestawu definicji, które mogą być używane w obu przypadkach użycia (takich jak te definicje PB * ) zamiast dwóch różnych zestawy definicji, które zawierają powtarzające się informacje.

Na przykład napisałem aplikację, która może dokonywać pomiarów z maksymalnie 8 kanałów ADC. Posiada jeden interfejs do rozpoczęcia nowego pomiaru, w którym można określić wiele kanałów za pomocą 8-bitowego pola, po jednym bitu na każdy kanał. (Pomiary są wykonywane równolegle, jeśli określono wiele kanałów). Następnie ma inny interfejs, który zwraca wyniki pomiarów dla pojedynczego kanału. Tak więc jeden interfejs używa numeru kanału jako przesunięcia do pola bitowego, a drugi bezpośrednio używa numeru kanału. Zdefiniowałem pojedyncze wyliczenie, aby objąć oba przypadki użycia.

  typedef wyliczenie
{
    CHANNEL_XL_X = 0,
    CHANNEL_XL_Y = 1,
    CHANNEL_XL_Z = 2,
    CHANNEL_G_X = 3,
    CHANNEL_G_Y = 4,
    CHANNEL_G_Z = 5,
    CHANNEL_AUX1 = 6,
    CHANNEL_AUX2 = 7
} ChannelNum;

struct MeasurementResult;

void StartMeasurement (uint8_t channel_mask);
MeasurementResult ReadMeasurementResult (ChannelNum numer_kanału);

Główny
{
    ...

    StartMeasurement ((1 << CHANNEL_XL_X) | (1 << CHANNEL_XL_Y) | (1 << KANAŁ_XL_Z));

    Meas_result_x = ReadMeasurementResult (CHANNEL_XL_X);
    Meas_result_y = ReadMeasurementResult (CHANNEL_XL_Y);
    Meas_result_z = ReadMeasurementResult (CHANNEL_XL_Z);
}
 


To pytanie i odpowiedź zostało automatycznie przetłumaczone z języka angielskiego.Oryginalna treść jest dostępna na stackexchange, za co dziękujemy za licencję cc by-sa 3.0, w ramach której jest rozpowszechniana.
Loading...