C ++ - Klassen für die I / O-Pin-Abstraktion

13

Ich suche nach C ++ - Abstraktionen für Hardware-E / A-Punkte oder -Pins. Dinge wie in_pin, out_pin, inout_pin, vielleicht open_collector_pin, etc.

Ich kann mir mit Sicherheit selbst eine solche Sammlung von Abstraktionen einfallen lassen, also suche ich nicht nach Antworten wie "Hey, vielleicht machst du das so", sondern nach dem "Blick auf diese Bibliothek, die in diesem und jenem und jenem verwendet wurde dieses Projekt'.

Google hat nichts aufgedeckt, vielleicht weil ich nicht weiß, wie andere das nennen würden.

Mein Ziel ist es, E / A-Bibliotheken zu erstellen, die auf solchen Punkten basieren, aber auch solche Punkte bereitstellen, sodass es beispielsweise einfach ist, eine HD44780 LCd entweder an die E / A-Pins des Chips oder an einen I2C (oder SPI) anzuschließen. I / O-Extender oder jeder andere Punkt, der irgendwie gesteuert werden kann, ohne die LCD-Klasse zu ändern.

Ich weiß, dass dies auf dem neuesten Stand der Elektronik / Software ist. Tut mir leid, wenn es nicht hierher gehört.

@leon: Verkabelung Das ist eine große Tüte Software, die ich mir genauer ansehen muss. Aber es scheint, dass sie keine Stecknadelabstraktion verwenden, wie ich es will. Zum Beispiel in der Tastatur-Implementierung sehe ich

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

Dies impliziert, dass es eine Funktion (digitalWrite) gibt, die weiß, wie auf einen E / A-Pin geschrieben wird. Dies macht es unmöglich, einen neuen Typ von I / O-Pin hinzuzufügen (zum Beispiel einen, der sich auf einem MCP23017 befindet, so dass er über I2C geschrieben werden muss), ohne die digitalWrite-Funktion neu zu schreiben.

@Oli: Ich habe ein Arduino IO-Beispiel gegoogelt, aber die scheinen ungefähr den gleichen Ansatz wie die Wiring-Bibliothek zu verwenden:

int ledPin = 13;                 // LED connected to digital pin 13
void setup(){
    pinMode(ledPin, OUTPUT);      // sets the digital pin as output
}
Wouter van Ooijen
quelle
Über welchen Mikrocontroller sprechen wir hier?
Majenko
Das ist irrelevant; Für einen bestimmten Mikrocontroller implementieren die io-Pins dieses uC die entsprechenden Schnittstellen. Dies gilt jedoch für C ++. Denken Sie also an 32-Bit-Chips wie ARM, Cortex und MIPS.
Wouter van Ooijen
1
Ich habe noch nie einen benutzt, aber abstrahiert Arduino nicht alle Pins so? Möglicherweise erhalten Sie (oder auch nicht) nützliche Informationen dazu, wie sie sich verhalten haben.
Oli Glaser
1
Und was das Umschreiben der digitalWrite-Funktion angeht, siehe "Überladen" in C ++. Ich habe erst vor wenigen Augenblicken eine überladene DigitalWrite-Funktion für ein IO-Expander-Board für das Arduino geschrieben. Solange Sie andere Parameter verwenden (ich habe das erste "int" durch ein "struct" ersetzt), wird Ihr digitalWrite dem Standard vorgezogen.
Majenko
1
Ich habe einen Vortrag über C ++ in Berlin über meine Arbeit zu diesem Thema gehalten. Es kann auf youtube gefunden werden: youtube.com/watch?v=k8sRQMx2qUw Seitdem bin ich zu einem etwas anderen Ansatz übergegangen, aber der Vortrag könnte immer noch interessant sein.
Wouter van Ooijen

Antworten:

3

Kurze Antwort: Leider gibt es keine Bibliothek, in der Sie tun können, was Sie wollen. Ich habe es selbst schon oft gemacht, aber immer in Nicht-Open-Source-Projekten. Ich überlege, etwas auf Github zu bringen, bin mir aber nicht sicher, wann ich kann.

Warum C ++?

  1. Der Compiler kann die dynamische Auswertung von Ausdrücken in Wortgröße verwenden. C pflanzt sich zu int fort. Ihre Bytemaske / Verschiebung kann schneller / kleiner erfolgen.
  2. Inlining.
  3. Mit Vorlagenvorgängen können Sie die Wortgröße und andere Eigenschaften mit typenbezogener Sicherheit variieren.
Jim Frimmel
quelle
5

Gestatten Sie mir, mein Open-Source-Projekt https://Kvasir.io unverschämt einzustecken . Der Kvasir :: Io-Teil bietet Stiftmanipulationsfunktionen. Sie müssen zuerst Ihren Pin mit einem Kvasir :: Io :: PinLocation wie folgt definieren:

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

Beachten Sie, dass dies nicht wirklich RAM verwendet, da es sich um Constexpr-Variablen handelt.

In Ihrem gesamten Code können Sie diese Pin-Positionen in 'Action Factory'-Funktionen wie makeOpenDrain, set, clear, makeOutput usw. verwenden. Eine 'Action Factory' führt die Aktion nicht aus, sondern gibt eine Kvasir :: Register :: Action zurück, die mit Kvasir :: Register :: apply () ausgeführt werden kann. Der Grund dafür ist, dass apply () die Aktionen zusammenführt, die ihm übergeben werden, wenn sie auf ein und dasselbe Register angewendet werden, sodass ein Effizienzgewinn erzielt wird.

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

Da die Erstellung und Zusammenführung von Aktionen zur Kompilierungszeit erfolgt, sollte dies denselben Assembler-Code ergeben wie das typische handcodierte Äquivalent:

PORT0DIR |= (1<<4) | (1<<8);
PORT0OD |= (1<<4) | (1<<8);
odinthenerd
quelle
3

Das Wiring-Projekt verwendet die folgende Abstraktion:

http://wiring.org.co/

und der Compiler ist in C ++ geschrieben. Sie sollten viele Beispiele im Quellcode finden. Die Arduino-Software basiert auf Wiring.

Leon Heller
quelle
beantwortet im
Fragenkörper
2

In C ++ ist es möglich, eine Klasse zu schreiben, sodass Sie E / A-Ports so verwenden können, als wären sie Variablen, z

  PORTB = 0x12; / * In einen 8-Bit-Port schreiben * /
  wenn (RB3) LATB4 = 1; / * Ein E / A-Bit lesen und ein anderes bedingt schreiben * /

ohne Rücksicht auf die zugrunde liegende Implementierung. Wenn Sie beispielsweise eine Hardware-Plattform verwenden, die keine Operationen auf Bitebene unterstützt, aber Registeroperationen auf Byteebene unterstützt, können Sie (möglicherweise mithilfe einiger Makros) eine statische Klasse IO_PORTS mit Inline-Lese- / Schreibzugriff definieren Eigenschaften mit den Namen bbRB3 und bbLATB4, sodass die letzte obige Anweisung zu wird

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

was wiederum in etwas verarbeitet werden würde:

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

Ein Compiler sollte in der Lage sein, den konstanten Ausdruck im?: -Operator zu erkennen und einfach den "wahren" Teil einzuschließen. Möglicherweise können Sie die Anzahl der erstellten Eigenschaften verringern, indem Sie die Makros folgendermaßen erweitern:

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

oder

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

Ich bin mir aber nicht sicher, ob ein Compiler den Code so gut in die Zeile schreiben kann.

Ich möchte keinesfalls implizieren, dass die Verwendung von E / A-Ports als Variablen zwangsläufig eine gute Idee ist, aber da Sie C ++ erwähnen, ist es ein nützlicher Trick, dies zu wissen. Meine eigene Präferenz in C oder C ++, wenn Kompatibilität mit Code, der den oben genannten Stil verwendet, nicht erforderlich wäre, wäre wahrscheinlich, für jedes E / A-Bit einen Makrotyp zu definieren und dann Makros für "readBit", "writeBit" zu definieren. "setBit" und "clearBit" mit der Maßgabe, dass das an diese Makros übergebene bitidentifizierende Argument der Name eines E / A-Ports sein muss, der für die Verwendung mit solchen Makros vorgesehen ist. Das obige Beispiel würde zum Beispiel geschrieben werden als

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

und übersetzt als

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

Das wäre für den Präprozessor etwas mehr Arbeit als für den C ++ - Stil, aber für den Compiler weniger. Es würde auch eine optimale Codeerzeugung für viele E / A-Implementierungen und eine angemessene Codeimplementierung für fast alle ermöglichen.

Superkatze
quelle
3
Ein Zitat aus der Frage: "Ich suche nicht nach 'Hey, vielleicht machst du das so' Antworten" ...
Wouter van Ooijen
Ich glaube, mir ist nicht ganz klar, wonach Sie suchen. Natürlich würde ich davon ausgehen, dass viele Leute, die sich für Klassen zur I / O-Pin-Rekonstruktion interessieren, auch daran interessiert sind, dass man mit Eigenschaften Code erstellen kann, der für einen I / O-Stil geschrieben wurde und so gut wie alles andere verwendet. Ich habe Eigenschaften verwendet, um eine Anweisung wie "LATB3 = 1;" Senden Sie eine E / A-Anforderung an einen TCP-Stream.
Supercat
Ich habe versucht, in meiner Frage klar zu sein: Ich möchte in der Lage sein, neue Arten von E / A-Pins aufzunehmen, ohne den Code, der E / A-Pins verwendet, neu zu schreiben. Sie schreiben über benutzerdefinierte Typkonvertierungen und Zuweisungsoperatoren, die sicherlich interessant sind. Ich verwende sie die ganze Zeit, aber keine Lösung für mein Problem.
Wouter van Ooijen
@Wouter van Ooijen: Welche "neuen Arten von I / O-Pins" erwarten Sie? Wenn der Quellcode mit einer Syntax wie "if (BUTTON_PRESSED) MOTOR_OUT = 1;" geschrieben wird, würde ich erwarten, dass für nahezu jeden Mechanismus, mit dem der Prozessor eine Tastensteuerung oder einen Motor lesen könnte, eine Bibliothek geschrieben werden könnte, so dass die obige Quelle Der Code würde den Motor einschalten, wenn die Taste gedrückt wird. Eine solche Bibliothek stellt möglicherweise nicht die effizienteste Methode zum Einschalten des Motors dar, sollte jedoch funktionieren.
Supercat
@Wouter van Ooijen: Man könnte vielleicht die Effizienz verbessern, wenn man benötigt, dass der Quellcode irgendwann vor dem Lesen einer Eingabe ein UPDATE_IO () - oder UPDATE_INPUTS () -Makro aufruft und einige Zeit nach einer Ausgabe ein UPDATE_IO () oder UPDATE_OUTPUTS () durchführt Die eingegebenen Semantiken konnten entweder beim Code, der sie liest, oder beim vorherigen Aufruf von UPDATE_INPUTS () / UPDATE_IO () abgetastet werden. Ebenso könnten Ausgaben entweder sofort erfolgen oder aufgeschoben werden. Wenn eine E / A unter Verwendung eines Schieberegisters implementiert wird, können durch Verschieben von Aktionen mehrere Operationen konsolidiert werden.
Supercat
1

Wenn Sie nach etwas wirklich Tollem suchen, um die Hardware zu abstrahieren, und sich auf Ihre C ++ - Fähigkeiten verlassen können, sollten Sie dieses Muster ausprobieren:

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

Ich habe es in einem Versuch verwendet, um Hardware für einen Cortex-M0-Chip zu abstrahieren. Ich habe noch nichts über diese Erfahrung geschrieben (ich werde es eines Tages tun), aber glauben Sie mir, es war sehr nützlich wegen seiner statischen polymorphen Natur: die gleiche Methode für verschiedene Chips, ohne Kosten (verglichen mit dynamischem Polymorphismus).

Francisco Rodríguez
quelle
In den Jahren seit diesem Post habe ich mich auf separate "Klassen" für pin_in, pin_out, pin_oc und pin_in_out konzentriert. Für eine optimale Leistung (Größe und Geschwindigkeit) verwende ich statische Klassen, die als Vorlagenparameter übergeben werden.Ich habe darüber auf dem Meeting C ++ in Berlin gesprochen
Wouter van Ooijen