Klasy C ++ dla abstrakcji styków we / wy


13

Szukam abstrakcji C ++ dla sprzętowych punktów We / Wy lub pinów. Rzeczy takie jak in_pin, out_pin, inout_pin, może open_collector_pin itp.

Z pewnością mogę wymyślić taki zestaw abstrakcji, więc nie szukam odpowiedzi typu „hej, możesz to zrobić w ten sposób”, ale raczej „spójrz na tę bibliotekę, która została użyta w tym i tamtym oraz ten projekt'.

Google niczego nie wykrył, może dlatego, że nie wiem, jak nazywaliby to inni.

Moim celem jest zbudowanie bibliotek we / wy, które są oparte na takich punktach, ale także zapewniają takie punkty, więc łatwo byłoby na przykład podłączyć HD44780 LCd do styków we / wy układu lub do I2C (lub SPI) Wzmacniacz I / O lub dowolny inny punkt, którym można jakoś sterować, bez żadnych zmian w klasie LCD.

Wiem, że jest to na granicy elektroniki / oprogramowania, przepraszam, jeśli tu nie należy.

@leon: wiring To jest duży pakiet oprogramowania, muszę się bliżej przyjrzeć. Wygląda jednak na to, że nie używają abstrakcji w taki sposób, jak chcę. Na przykład w implementacji klawiatury widzę

digitalWrite(columnPins[c], LOW);   // Activate the current column.

Oznacza to, że istnieje jedna funkcja (digitalWrite), która wie, jak pisać na pinie we / wy. Uniemożliwia to dodanie nowego typu styku we / wy (na przykład takiego, który jest na MCP23017, więc musi być zapisany przez I2C) bez przepisywania funkcji digitalWrite.

@Oli: przejrzałem przykład Arduino IO, ale wydaje się, że używam tego samego podejścia co biblioteka Wiring:

int ledPin = 13;                 // LED connected to digital pin 13
void setup(){
    pinMode(ledPin, OUTPUT);      // sets the digital pin as output
}

O jakim mikrokontrolerze tu mówimy?
Majenko,

To nie ma znaczenia; dla konkretnego mikrokontrolera piny io tego sterownika będą implementować odpowiednie interfejsy. Ale dotyczy to C ++, więc pomyśl o 32-bitowych układach, takich jak ARM, Cortex i MIPS.
Wouter van Ooijen

1
Nigdy go nie użyłem, ale czy Arduino nie abstrakuje wszystkich pinów w ten sposób? Możesz (lub nie) uzyskać przydatne informacje, patrząc na sposób, w jaki to zrobili.
Oli Glaser,

1
A co do przepisywania funkcji digitalWrite - spójrz na „przeładowanie” w C ++. Kilka chwil temu napisałem przeciążoną funkcję digitalWrite dla karty rozszerzeń IO dla Arduino. Dopóki użyjesz różnych parametrów (zastąpiłem pierwszą literę „int” tekstem „struct”), wybierzesz digitalWrite zamiast domyślnego.
Majenko,

1
Rozmawiałem o spotkaniu z C ++ w Berlinie na temat mojej pracy na ten temat. Można go znaleźć na youtube: youtube.com/watch?v=k8sRQMx2qUw Od tego czasu zmieniłem nieco inne podejście, ale rozmowa może być nadal interesująca.
Wouter van Ooijen

Odpowiedzi:


3

Krótka odpowiedź: niestety nie ma biblioteki do robienia tego, co chcesz. Robiłem to sam wiele razy, ale zawsze w projektach innych niż open source. Zastanawiam się nad umieszczeniem czegoś na githubie, ale nie jestem pewien, kiedy będę mógł.

Dlaczego C ++?

  1. Kompilator może używać dynamicznej oceny wyrażeń o wielkości słowa. C propaguje się do int. Maskę / przesunięcie bajtu można wykonać szybciej / mniej.
  2. Inlining
  3. Operacje tworzenia szablonów pozwalają zmieniać rozmiar słowa i inne właściwości z zachowaniem bezpieczeństwa typu.

5

Pozwól mi bezwstydnie podłączyć mój projekt open source https://Kvasir.io . Część Kvasir :: Io zapewnia funkcje manipulacji pinami. Najpierw musisz zdefiniować swój pin za pomocą Kvasir :: Io :: PinLocation w następujący sposób:

constexpr PinLocation<0,4> led1;    //port 0 pin 4
constexpr PinLOcation<0,8> led2;

Zauważ, że tak naprawdę nie używa pamięci RAM, ponieważ są to zmienne constexpr.

W całym kodzie możesz używać tych lokalizacji pinów w funkcjach „fabryki akcji”, takich jak makeOpenDrain, set, clear, makeOutput i tak dalej. „Fabryka akcji” tak naprawdę nie wykonuje akcji, a raczej zwraca Kvasir :: Register :: Action, którą można wykonać za pomocą Kvasir :: Register :: apply (). Powodem tego jest to, że apply () łączy działania przekazane mu, gdy działają na jednym i tym samym rejestrze, dzięki czemu uzyskuje się wzrost wydajności.

apply(makeOutput(led1),
    makeOutput(led2),
    makeOpenDrain(led1),
    makeOpenDrain(led2));

Ponieważ tworzenie i łączenie akcji odbywa się w czasie kompilacji, powinno to dać ten sam kod asemblera, co typowy ręcznie kodowany odpowiednik:

PORT0DIR |= (1<<4) | (1<<8);
PORT0OD |= (1<<4) | (1<<8);


2

W C ++ można napisać klasę, aby można było używać portów I / O tak, jakby były zmiennymi, np

  PORTB = 0x12; / * Zapis do 8-bitowego portu * /
  jeśli (RB3) LATB4 = 1; / * Przeczytaj jeden bit we / wy i warunkowo napisz inny * /

bez względu na podstawowe wdrożenie. Na przykład, jeśli używa się platformy sprzętowej, która nie obsługuje operacji na poziomie bitów, ale obsługuje operacje rejestru na poziomie bajtów, można (prawdopodobnie przy pomocy niektórych makr) zdefiniować statyczną klasę IO_PORTS z wbudowanym odczytem i zapisem właściwości o nazwie bbRB3 i bbLATB4, takie, że zmieni się w ostatnią instrukcję powyżej

  if (IO_PORTS.bbRB3) IO_PORTS.bbLATB4 = 1;

które z kolei zostaną przetworzone na coś takiego:

  if (!! (PORTB & 8)) (1? (PORTB | = 16): (PORTB & = ~ 16));

Kompilator powinien być w stanie zauważyć ciągłe wyrażenie w operatorze?: I po prostu dołączyć część „true”. Może być możliwe zmniejszenie liczby właściwości utworzonych przez rozwinięcie makr do czegoś takiego jak:

  if (IO_PORTS.ppPORTB [3]) IO_PORTS.ppPORTB [4] = 1;

lub

  if (IO_PORTS.bb (addrPORTB, 3)) IO_PORTS.bbPORTB (addrPORTB, 4) = 1;

ale nie jestem pewien, czy kompilator byłby w stanie tak ładnie wstawić kod.

W żadnym wypadku nie chcę sugerować, że używanie portów I / O tak, jakby były zmiennymi, jest koniecznie dobrym pomysłem, ale skoro wspominasz o C ++, warto wiedzieć. Moimi własnymi preferencjami w C lub C ++, jeśli zgodność z kodem korzystającym z wyżej wymienionego stylu nie była wymagana, prawdopodobnie byłoby zdefiniowanie pewnego rodzaju makra dla każdego bitu I / O, a następnie zdefiniowanie makr dla „readBit”, „writeBit”, „setBit” i „clearBit” pod warunkiem, że argument identyfikujący bit przekazany do tych makr musi być nazwą portu we / wy przeznaczonego do użycia z takimi makrami. Na przykład powyższy przykład zostałby zapisany jako

  if (readBit (RB3)) setBit (LATB4);

i przetłumaczone jako

  if (!! (_ PORT_RB3 & _BITMASK_RB3)) _PORT_LATB4 | = _BITMASK_LATB4;

Byłoby to trochę więcej pracy dla preprocesora niż w stylu C ++, ale byłoby mniej pracy dla kompilatora. Umożliwiłoby to również optymalne generowanie kodu dla wielu implementacji I / O i przyzwoitą implementację kodu dla prawie wszystkich.


3
Cytat z pytania: „Nie szukam odpowiedzi typu„ hej, możesz to zrobić w ten sposób ”...
Wouter van Ooijen

Chyba nie do końca rozumiem, czego szukasz. Z pewnością spodziewałbym się, że wiele osób zainteresowanych klasami do rekonstrukcji pinów I / O byłoby również zainteresowanych, aby wiedzieć, że używając właściwości można stworzyć kod napisany dla jednego stylu I / O, używający praktycznie wszystkiego innego. Użyłem właściwości, aby utworzyć instrukcję typu „LATB3 = 1;” wysłać żądanie We / Wy do strumienia TCP.
supercat

W moim pytaniu starałem się jasno: chcę być w stanie pomieścić nowe typy pinów IO bez przepisywania kodu, który używa pinów IO. Piszesz o konwersjach typów zdefiniowanych przez użytkownika i operatorach przypisań, które z pewnością są interesujące, używam ich cały czas, ale nie jest to rozwiązanie mojego problemu.
Wouter van Ooijen

@Wouter van Ooijen: Jakich „nowych typów styków we / wy” spodziewałbyś się? Jeśli kod źródłowy jest napisany ze składnią typu „jeśli (BUTTON_PRESSED) MOTOR_OUT = 1;”, oczekiwałbym, że dla niemal każdego mechanizmu, za pomocą którego procesor może odczytać kontrolkę przycisku lub silnik, mógłby napisać bibliotekę, aby powyższe źródło kod włączy silnik, jeśli przycisk zostanie wciśnięty. Taka biblioteka może nie reprezentować najskuteczniejszego sposobu włączania silnika, ale powinna działać.
supercat

@Wouter van Ooijen: Być może można by poprawić wydajność, gdyby wymagał, aby kod źródłowy wywoływał makro UPDATE_IO () lub UPDATE_INPUTS () jakiś czas przed odczytem dowolnego wejścia, i wykonać UPDATE_IO () lub UPDATE_OUTPUTS () jakiś czas po dowolnym wyjściu za pomocą semantyka na wejściach może być próbkowana albo w kodzie, który je czyta, albo w poprzednim wywołaniu UPDATE_INPUTS () / UPDATE_IO (). Podobnie dane wyjściowe mogą wystąpić natychmiast lub zostać odroczone. Jeśli we / wy zostanie zaimplementowane przy użyciu czegoś takiego jak rejestr przesuwny, odroczenie działań umożliwi konsolidację wielu operacji.
supercat

1

Jeśli szukasz czegoś naprawdę niesamowitego do wyodrębnienia sprzętu i jesteś pewien swoich umiejętności w C ++, powinieneś wypróbować ten wzór:

https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern

Użyłem go w jednej próbie wyodrębnienia sprzętu dla układu Cortex-M0. Nie napisałem jeszcze nic o tym doświadczeniu (kiedyś to zrobię), ale wierzcie, że było bardzo przydatne ze względu na jego statyczny polimorficzny charakter: ta sama metoda dla różnych układów, bez żadnych kosztów (w porównaniu z dynamicznym polimorfizmem).


Przez lata od tego postu ćwiczyłem osobne „klasy” dla pin_in, pin_out, pin_oc i pin_in_out. Dla optymalnej wydajności (wielkości i prędkości) używam klas statycznych, przekazywanych jako parametry szablonu. Mówiłem o tym na spotkaniu C ++ w Berlinie
Wouter van Ooijen
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.