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

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 usw.

Ich kann sicherlich selbst mit einer solchen Reihe von Abstraktionen aufwarten, also suche ich nicht nach Antworten der Art „Hey, Sie könnten es so machen“, sondern eher nach „Schauen Sie sich diese Bibliothek an, die in diesem und diesem und verwendet wurde dieses Projekt'.

Google hat nichts gefunden, 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 wäre, ein HD44780-LCd entweder an die E / A-Pins des Chips oder an ein I2C (oder SPI) anzuschließen. E/A-Extender oder jeder andere Punkt, der irgendwie gesteuert werden kann, ohne dass sich die LCD-Klasse ändert.

Ich weiß, dass dies am Elektronik-/Software-Rand liegt, tut mir leid, wenn es nicht hierher gehört.

@leon: Verkabelung Das ist eine große Tüte Software, ich muss genauer hinsehen. Aber es scheint, dass sie keine Pin-Abstraktion verwenden, wie ich es möchte. Zum Beispiel in der Tastaturimplementierung, die ich sehe

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

Dies impliziert, dass es eine Funktion (digitalWrite) gibt, die weiß, wie auf einen I/O-Pin zu schreiben ist. Dies macht es unmöglich, einen neuen I/O-Pin-Typ hinzuzufügen (z. B. einen, der sich auf einem MCP23017 befindet und daher über I2C geschrieben werden muss), ohne die DigitalWrite-Funktion neu zu schreiben.

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

int ledPin = 13;                 // LED connected to digital pin 13
void setup(){
    pinMode(ledPin, OUTPUT);      // sets the digital pin as output
}
Von welchem ​​Mikrocontroller reden wir hier?
Das ist irrelevant; Für einen bestimmten Mikrocontroller implementieren die io-Pins dieses uC die entsprechenden Schnittstellen. Aber das ist für C++, also denken Sie an 32-Bit-Chips wie ARM, Cortex und MIPS.
Ich habe noch nie einen benutzt, aber abstrahiert Arduino nicht alle Pins so? Sie können (oder auch nicht) nützliche Informationen erhalten, wenn Sie sich ansehen, wie sie Dinge getan haben.
Das ist genau die dort zitierte Arduino-Schnittstelle ;) Und ja, es spielt eine Rolle, welches uC Sie verwenden, da verschiedene eine andere Schnittstelle zur Hardware bieten. Zum Beispiel verwendet der PIC speicherabgebildete Register, und ich mache oft so etwas wie: unsigned char *MyTRIS = ⧍ - oft in Verbindung mit einer Struktur: struct pin { unsigned char *tris; unsigned char *lat; unsigned char *port; unsigned char pinno; }; struct pin pins[8] = { {{&TRISA,&LATA,&PORTA,0}, {&TRISB,&LATB,&PORTB,3}...}; Das ist spezifisch für PIC und ich bezweifle, dass es auf ARM oder Atmel usw. funktionieren würde.
Und was das Umschreiben der digitalWrite-Funktion betrifft - schauen Sie sich "Überladen" in C++ an. Ich habe gerade erst eine überladene digitalWrite-Funktion für ein IO-Expander-Board für den Arduino geschrieben. Solange Sie andere Parameter verwenden (ich habe das erste "int" durch ein "struct" ersetzt), wird Ihr digitalWrite dem Standardwert vorgezogen.
Obwohl es Ihre Frage nicht beantwortet, möchten Sie vielleicht einen Blick auf den "Technical Report on C++ Performance" werfen. Es enthält mehrere Abschnitte zur Anbindung an Hardware. open-std.org/jtc1/sc22/wg21/docs/18015.html
@starblue: nette Lektüre, aber nichts zu dem Thema, das mich interessiert. Was an sich schon ein Hinweis sein könnte :(
Ich habe nichts gefunden, wonach ich gesucht habe, also versuche ich es selbst ohne Eingaben zum Stand der Technik ... Ich habe hier einen Blog embeddedrelated.com/showarticle/101.php über die Konstruktion eines solchen gestartet Bibliothek und dient als Einführung in die Verwendung von OO auf (obersten) Mikrocontrollern wie dem LPC1xxxx.
Ich habe einen Vortrag über das Treffen mit C++ in Berlin über meine Arbeit zu diesem Thema gehalten. Es ist auf youtube zu finden: youtube.com/watch?v=k8sRQMx2qUw Seitdem bin ich auf einen etwas anderen Ansatz umgestiegen, aber der Vortrag könnte immer noch interessant sein.

Antworten (5)

Kurze Antwort: Leider gibt es keine Bibliothek, die das macht, was Sie wollen. Ich habe es selbst viele Male gemacht, aber immer in Nicht-Open-Source-Projekten. Ich erwäge, etwas auf GitHub zu veröffentlichen, bin mir aber nicht sicher, wann ich das kann.

Warum C++?

  1. Der Compiler kann die dynamische Auswertung von Wortgrößenausdrücken frei verwenden. C propagiert nach int. Ihre Bytemaske/-verschiebung kann schneller/kleiner erfolgen.
  2. Einfügen.
  3. Mit Vorlagenoperationen können Sie die Wortgröße und andere Eigenschaften mit Typsicherheit variieren.

Erlauben Sie mir, mein Open-Source-Projekt https://Kvasir.io schamlos anzuschließen . Der Kvasir::Io-Teil bietet Pin-Manipulationsfunktionen. Sie müssen zuerst Ihren Pin mit einer 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 wirklich 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 ihm übergebenen Aktionen zusammenführt, wenn sie auf ein und dasselbe Register wirken, so dass ein Effizienzgewinn entsteht.

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

Da das Erstellen und Zusammenführen von Aktionen zur Kompilierzeit erfolgt, sollte dies denselben Assembler-Code wie das typische handcodierte Äquivalent ergeben:

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

Das Wiring-Projekt verwendet eine solche 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.

im Fragetext beantwortet

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

  PORTB = 0x12; /* Auf 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 man beispielsweise eine Hardwareplattform verwendet, die keine Operationen auf Bitebene, aber Registeroperationen auf Byteebene unterstützt, könnte man (wahrscheinlich mit Hilfe einiger Makros) eine statische Klasse IO_PORTS mit einem Inline-Lesen-Schreiben definieren Eigenschaften namens bbRB3 und bbLATB4, so dass die letzte obige Anweisung zu werden würde

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

was wiederum zu so etwas verarbeitet würde:

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

Ein Compiler sollte in der Lage sein, den konstanten Ausdruck im ?:-Operator zu bemerken und einfach den "wahren" Teil einzufügen. Es ist möglicherweise möglich, die Anzahl der erstellten Eigenschaften zu reduzieren, indem die Makros auf etwas wie das folgende erweitert werden:

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

oder

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

aber ich bin mir nicht sicher, ob ein Compiler in der Lage wäre, den Code so gut einzufügen.

Ich möchte auf keinen Fall andeuten, dass es unbedingt eine gute Idee ist, E/A-Ports so zu verwenden, als wären sie Variablen, aber da Sie C++ erwähnen, ist es ein nützlicher Trick, es zu wissen. Meine eigene Vorliebe in C oder C++ wäre, wenn die Kompatibilität mit Code, der den oben genannten Stil verwendet, nicht erforderlich wäre, wahrscheinlich eine Art Makro für jedes E/A-Bit zu definieren und dann Makros für "readBit", "writeBit", "setBit" und "clearBit" mit der Maßgabe, dass das an diese Makros übergebene Bit-identifizierende Argument der Name eines E/A-Ports sein muss, der für die Verwendung mit solchen Makros vorgesehen ist. Das obige Beispiel würde beispielsweise geschrieben werden als

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

und übersetzt als

  if (!!(_PORT_RB3 & _BITMASK_RB3)) _PORT_LATB4 |= _BITMASK_LATB4;

Das wäre ein bisschen mehr Arbeit für den Präprozessor als der C++-Stil, aber es wäre weniger Arbeit für den Compiler. Es würde auch eine optimale Codegenerierung für viele E/A-Implementierungen und eine anständige Codeimplementierung für fast alle ermöglichen.

Ein Zitat aus der Frage: "Ich suche keine 'Hey, du könntest es so machen' Art von Antworten" ...
Ich glaube, ich bin nicht ganz klar, was Sie suchen. Sicher würde ich erwarten, dass viele Leute, die an Klassen für die I/O-Pin-Rekonstruktion interessiert sind, auch daran interessiert wären zu wissen, dass man mit Hilfe von Eigenschaften Code erstellen kann, der für einen I/O-Stil geschrieben wurde, um so ziemlich alles andere zu verwenden. Ich habe Eigenschaften verwendet, um eine Aussage wie „LATB3=1;“ zu machen. eine E/A-Anforderung an einen TCP-Stream senden.
Ich habe versucht, in meiner Frage klar zu sein: Ich möchte in der Lage sein, neue Arten von IO-Pins aufzunehmen, ohne den Code, der IO-Pins verwendet, neu zu schreiben. Sie schreiben über benutzerdefinierte Typkonvertierungen und Zuweisungsoperatoren, die sicherlich interessant sind, ich verwende sie ständig, aber keine Lösung für mein Problem.
@Wouter van Ooijen: Welche „neuen Arten von E/A-Pins“ würden Sie erwarten? Wenn der Quellcode mit einer Syntax wie "if (BUTTON_PRESSED) MOTOR_OUT = 1;" geschrieben ist, würde ich erwarten, dass man für fast jeden Mechanismus, mit dem der Prozessor eine Tastensteuerung oder einen Motor lesen könnte, eine Bibliothek schreiben könnte, also die obige Quelle Code würde den Motor einschalten, wenn die Taste gedrückt wird. Eine solche Bibliothek stellt möglicherweise nicht die effizienteste Art dar, den Motor einzuschalten, aber sie sollte funktionieren.
@Wouter van Ooijen: Man könnte vielleicht die Effizienz verbessern, wenn man verlangen würde, dass der Quellcode irgendwann vor dem Lesen einer Eingabe ein UPDATE_IO()- oder UPDATE_INPUTS()-Makro aufruft und einige Zeit nach jeder Ausgabe ein UPDATE_IO() oder UPDATE_OUTPUTS() durchführt Die Semantik der Eingaben könnte entweder beim Code, der sie liest, oder beim vorherigen UPDATE_INPUTS()/UPDATE_IO()-Aufruf abgetastet werden. Ebenso könnten Ausgaben entweder sofort erfolgen oder verzögert werden. Wenn ein I/O unter Verwendung von so etwas wie einem Schieberegister implementiert wird, würde das Verzögern von Aktionen ermöglichen, dass mehrere Operationen konsolidiert werden.
welche "neuen Arten von E / A-Pins": zum Beispiel Pins an einem I2C- oder SPI-E / A-Extender, denken Sie an MCP23017. Oder, um wirklich pervers zu sein, ein MCP23017, der wiederum (über seine IO-Pins) einen MPC23S17 steuert. (@ supercat, aber SE eliminiert das am Anfang!)
Effizienz verbessern: natürlich, aber das muss auf einer feinkörnigeren Ebene geschehen. Nicht alle E/A sollten geleert werden, da 2 Remote-Pins aktualisiert werden müssen. Aber noch einmal, ich habe bereits diese Ideen! Ich habe die Frage gepostet, weil ich nach einer OO-Bibliothek suche, die diese (oder alternativen) Ideen implementiert und sie in nicht trivialen Anwendungen verwendet hat.
@Wouter van Ooijen: Es gibt immer Kompromisse zwischen der Ausführlichkeit des Quellcodes, der Effizienz des kompilierten Codes und der Anpassungsfähigkeit an Hardwareänderungen. Ich neige dazu zu finden, dass die beste Technik normalerweise darin besteht, eine I/O-Abstraktionsschicht für jedes Projekt zu schreiben oder anzupassen (es wäre dumm, ein ENERGIZE_LIFT_MOTOR()-Makro in einer Allzweck-I/O-Bibliothek zu haben, wenn 99 % meiner Projekte werden keinen Hubmotor haben).

Wenn Sie nach etwas wirklich Großartigem suchen, um die Hardware zu abstrahieren, und Sie sich auf Ihre C++-Kenntnisse verlassen können, sollten Sie dieses Muster ausprobieren:

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

Ich habe es in einem Versuch verwendet, 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, dass es wegen seiner statischen polymorphen Natur sehr nützlich war: dieselbe Methode für verschiedene Chips, kostenlos (im Vergleich zu dynamischer Polymorphie).

In den Jahren seit diesem Beitrag habe ich mich auf separate "Klassen" für Pin_in, Pin_out, Pin_oc und Pin_in_out festgelegt. Für eine optimale Leistung (Größe und Geschwindigkeit) verwende ich statische Klassen, die als Vorlagenparameter übergeben werden. Ich habe darüber auf Meeting C++ in Berlin gesprochen