Gibt's eine finale Version der Combiepin? Nicht, dass jeder was anderes yieht.
Es gibt eine aktuelle Version!
Meine Hoffnung ist, dass sie nach dieser "Unterhaltung" noch besser sein wird.
Die Aktuelle Datei hänge ich hier mal als Anhang ein.
Als Code geht nicht, da zu lang.
Diese CombiePin.h bitte als Tab in die IDE einfügen
Beispiel Programm, ein Blinker:
#include "CombiePin.h" // datei als Tab in der IDE
using BuiltInLed = Combie::Pin::OutputPin<LED_BUILTIN>;
BuiltInLed led;
void setup(void)
{
led.init();
}
void loop(void)
{
led.toggle();
delay(1000);
}
Ich denke mal, so bekommt man es testbar, ohne gleich meine ganzen Libs installieren zu müssen. Auch ist die Lib dann in der IDE editierbar, was für Experimente sicherlich hilfreich ist.
Ich fange mal von oben an zu erklären, das "Ist", und das "Warum".
Alles im Schnelldurchgang, Klärung dann später.
Nicht alles hat mit OOP im engeren Sinne zu tun, aber dennoch viel mit der Denke.
Einzelne Begriffe setze ich in Anführungszeichen, für klärende Worte kann man da mal die Suchmaschine der Wahl anwerfen.
#pragma once
Das ist die moderne Form des "Include Guard"
Dann folgt etwas Präprozessor Gedönse, um den Include nur für AVRs zu erlauben.
Im nächsten Schritt werden 3 verschachtelte namespace/Namensräume aufgezogen.
Combie in dem findet sich mein gesammelter Kram
Pin, als Bereich für das Pin IO Gedöns, als Teilbereich von Combie.
Und ein anonymer Namespace welcher nur in dieser Datei sichtbar ist.
Warum?
Der Zweck ist "Kapselung".
Alles soweit wie möglich verbergen, so dass versehentliche/falsche Zugriffe möglichst ausgeschlossen sind.
So vermeidet man Namenskollisionen.
Eine solche Kollision würde zu Irritationen führen, wenn z.B. einer von euch auch eine OutputPin Klasse erzeugen würde (oder eine andere Lib nutzt), und den Wunsch verspürt sowohl meine, als auch die eigene Klasse verwenden zu wollen.
Auf das "Wie" man damit umgeht, würde ich gerne erst später zurückkommen.
Mantra:
Mache nur sichtbar, was wirklich sichtbar sein muss.
(Sichtbar, heißt in dem Zusammenhang auch Erreichbar)
Dann folgt die Definition der Arrays und der Zugriffsfunktionen, auf diese Arrays, im anonymen Namespace.
Alles als constexpr, damit der Kompiler das zur Kompilezeit auflösen kann und dann an der Stelle nichts in das resultierende Programm einbinden muss. Es verschwindet also vollständig.
Im original Arduino Code landen diese Arrays im Flash, und die Zugriffsfunktionen benötigen Laufzeit. Das mach es langsam und (unnötig)fett.
Dann erfolgt schon die Definition der Klasse Pin, als Template.
Warum als Template?
Irgendwie muss man in die Klasse die Information, welcher Pin genutzt werden soll, hinein bekommen.
Der Vorgang hat einen festen Namen "Dependency Injektion".
Üblich und in allen(?) OOP Sprachen verfügbar ist die:
- "Construktor Dependency Injection"
- "Setter Dependency Injection"
C++ bietet uns die weitere Möglichkeit, Abhängigkeiten über Templates zu injizieren
Die hier verwendete nenne ich mal "Template Dependency Injection", ob es dem Begriff schon gibt? KA!
Warum "Template Dependency Injection"?
Es befreit von der Notwendigkeit den Pin, in Form einer Variablen/Eigenschaft, in der Klassen Instanz halten zu müssen. Dadurch ist der Kompiler in der Lage die ganze Instanz weg zu optimieren. Sowohl aus dem Flash, als auch aus dem RAM.
class Pin
Diese Klasse ist eine Abstraktion des physikalisch vorhandenen einzelnen Pins.
Die Methoden bieten alles(?) was man mit einem solchen Pin überhaupt anstellen kann.
- Input oder Output initialisieren
- Internen Pullup aktivieren
- High und Low setzen
- Togglen zwischen High und Low
- Abfragen des Zustandes des Pins (ist er High oder Low)
Diese Methoden sind alle als protected definiert.
Das führt dazu, dass, selbst wenn man eine Instanz von Pin erzeugen würde, man nichts damit anstellen kann, da die Methoden nicht von außen sichtbar sind.
Warum?
Kapselung!
Es wäre z.B. ein fataler Fehler, wenn man bei einem "Open Drain" Pin den Output auf High setzt. Nur ein Output auf Low ist an der Stelle erlaubt und auch sinnvoll.
Diese Art der Kapselung verhindert wirksam, dass ein Anwender der Lib versehentlich groben Mist baut.
Die Methoden sind als inline und mit attribute((always_inline)) deklariert.
Das erscheint, und ist es vielleicht auch, doppelt.
Tut aber nicht weh.
inline ist nur eine Empfehlung an den Compiler. Er soll, aber er muss sich daran nicht halten.
attribute((always_inline)) erzwingt, dass er sich auch bei abgeschalteter Optimierung daran hält.
class Pin
Hat ein leeres Anwender Interface. Nichts ist public.
Aber dennoch ist sie ein Abbild aller Fähigkeiten des Pins.
Ist also die recht abstrakte Mutter aller weiterer Pin Definitionen
Ich sage recht abstrakt dazu, weil Abstrakt in der C++ Welt eine spezielle Bedeutung hat, welche mit dem vorhanden sein von virtuellen Methoden zu tun hat. Das ist hier nicht der Fall.
Pin ist also keine Abstrakte Klasse im eigentlichen C++ Sinn.
Aber dennoch eine Abstraktion im allgemeinen Sinn.
Die anderen Pin Definitionen erben die verborgenen Methoden der abstrakten Pin Klasse.
Sie können diese Methoden veröffentlichen, modifizieren und weitere hinzufügen.
Diese Klassen nennt man "Spezialisierungen"
class InputPin
Die Klasse InputPin erbt alles von Pin
Sie macht dann die Methoden initPullup() und isHigh() öffentlich/public.
Denn das wird vom Anwender gebraucht.
Zusätzlich fügt sie noch die Methode init() dem Interface hinzu, welche nur ein Wrapper auf initInput() darstellt.
Und ein Operator wird überladen, um ein schreibfaules Verwenden der Klasse/Instanz zu erlauben.
Ein Beispiel, welches einige der Verwendungsmöglichkeiten zeigt:
#include "CombiePin.h"
/*
* Input ist Pin 2 mit externen Pulldown.
* Der Taster haengt als zwischen Vcc und Pin
*/
Combie::Pin::InputPin<2> taster;
void setup()
{
Serial.begin(9600);
Serial.println("Start");
taster.init();
}
void loop()
{
// diese beiden sind gleichwertig
if(taster.isHigh()) Serial.println("High"); // nutzung der getter Methode
if(taster) Serial.println("High"); // nutzung des ueberladenen Casting Operators
// Pinzustand anzeigen
Serial.println(taster);
// alternativ
Serial.println(taster?"pressed":"released");
}
Schaut man sich den generierten Code an, ist die Klasse/Objekt vollständig entschwunden.
Es bleiben nur einzelne Assemblerstatements übrig.
Kein Call, Push, Pop, oder gar Platz für das Objekt wird benötigt
Ich bezweifle, dass ein geübter Assemblerspezi das noch optimieren könnte.
Die anderen Klassen sind ebenfalls einfach nur andere Spezialisierungen der Basisklasse.
Ich denke, das war schon mal ein Anfang, zu dem es sicher reichlich Fragen gibt.
Und der Zeichenvorrat für ein Posting ist auch erreicht.
CombiePin.h (10.3 KB)