Hallo zusammen,
Ich bin neu hier und fange gerade an mich mit der OOP zu beschäftigen. Ich habe hier ein großes verständnis Problem, was den Aufruf von Klassen betrifft.
In der OOP soll ja möglichst alles gekapselt sein und Globale Variablen, sollten vermieden werden.
Im Netz gibt es zahlreiche Beispiele,wie LED's in einer Klasse angesteuert werden - Verstanden. Wie sieht das aber aus, wenn keine einfache LED am Port hängt, sonden ein Zugriff auf ein Ext.RAM, andere I/O Bausteine wie einen CAN Controller, Port Expander oder gar Ausgaben auf einem Ethernet Port erfolgen soll.
Auf diese Geräte wird immer durch eine eigene Klasse zugegriffen. Die Zugriffe auf den CAN Controller werden nun in mehreren Klassen von meinem Programm benötigt, da z.B. eine den Empfang erledigt, und andere senden Sensordaten oder Statusmeldungen auf den Bus.
Nun kann es doch nicht die einzige Möglichkeit sein den Controller in jeder Klasse neu zu initialisieren um dann senden zu können. Auch würde hier doch jedesmal wertvoller RAM verbraucht werden. In einer globalen Variable soll das ja nicht sein.
Wie macht man sowas? Kann mir hier bitte jemand etwas Licht ins Dunkel bringen - vielen Dank!
Das Problem liegt in der Anwendung selber. Da du einen Controller auch addressieren musst, besonders bei Port-Expandern gibt es unterschiedlichste Konfigurationsmöglichkeiten, verlierst du früher oder später den Überblick.
Wenn du z.b. um ein Paradebeispiel zu nenen 8 dieser MCP23017 in unterschiedlichster I/O Konfiguration verwenden willst, und auch noch die Rückmelde-Interrupte für Input Eingänge auswerten willst wird es verdammt unübersichtlich.
Zum Schluss entscheidet die Qualität des Compilers wie umfangreich der resultierende Code wird. Vieles in der OOP dient nur der Überschaubarkeit, und natürlich der eleganteren Weiterverarbeitung übergebener Daten. Nicht alles was in einer Klasse steht, und in jedem Anwendungsfall speziell genutzt wird, bzw. ungenutzt herumliegt wird auch vom Compiler so übersetzt, und in das schlussendliche Assemblerlisting mit welchem das Board arbeitet auch inegriert, ein Großteil der funktionallen Abläufe verschwindet dann in wenigen linear ablaufenden Programmzeilen in Maschinencode.
Am besten du nimmst dir mal ein solches Datasheet zu einem solchen Conroller zu hand, und vesucht mal zB auf einem großen PI , den Chip in C oder Python anzusteuern. Du wirst selber merken, slange du nur einen solchen Chip ansprechen willst, und die Anwendung klar definiert ist, geht das auch noch in reinem linear ablaufenden Code, willst aber auch nur eine kleine Änderung vornehmen, wird das zu einem Aufwand, der einer Neuprogrammierung gleichkommt. Deswegen erschafft man sich solche Klassen, die man Universell, Parameter Initieirt immer und immer wieder nutzen kann.
Die einzige Ausnahme stellt hier die Programmiersprache Assembler dar. Hier muss man alles Projekt- und Anwendungsbezogen händisch neu programmieren.
dann übergibst du deiner Klasse das andere Objekt z.B. als Referenz (oder als Pointer).
edit, hier hab ich ein Beispiel:
man legt einen Neopixel Strip an, und dessen Pixel wird dann in anderen Klassen übergeben, deren Objekten dann einzelne Pixel daraus verwendet:
/*
Use pixels in a strip as separate objects
by noiasca
2020-03-02 https://forum.arduino.cc/index.php?topic=668289.15
https://forum.arduino.cc/index.php?topic=668125
2020-12-31 https://forum.arduino.cc/index.php?topic=720583.0
*/
const byte ledPin = 2; // Which pin on the Arduino is connected to the NeoPixels?
const uint16_t ledCount = 8;
#include <Adafruit_NeoPixel.h>
Adafruit_NeoPixel strip(ledCount, ledPin, NEO_GRB + NEO_KHZ800);
class BlinkLed {
private:
byte state = 1; // 0 off, 1 blink high , 2 blink low
unsigned long previousMillis;
uint16_t on = 180;
uint16_t off = 320; // 180/320 is according ECE
uint32_t colorOn = 0xAA0000;
uint32_t colorOff = 0x000000;
Adafruit_NeoPixel& obj;
const byte id;
public:
BlinkLed(Adafruit_NeoPixel& obj, byte id):
obj(obj),
id(id)
{}
void set(uint16_t _on, uint16_t _off) // modify on/off times during runtime
{
on = _on;
off = _off;
}
void setState(byte _state) // 0 switch off blinking; 1 Switch on blinking;
{
state = _state;
}
void setColor(uint32_t newColorOn, uint32_t newColorOff)
{
colorOn = newColorOn;
colorOff = newColorOff;
}
void tick()
{
if (state)
{
uint32_t currentMillis = millis();
if (state == 1 && currentMillis - previousMillis >= on) {
// save the last time you blinked the LED
obj.setPixelColor(id, colorOn);
obj.show();
state = 2;
previousMillis = currentMillis;
}
if (state == 2 && currentMillis - previousMillis >= off) {
// save the last time you blinked the LED
obj.setPixelColor(id, colorOff);
obj.show();
state = 1;
previousMillis = currentMillis;
}
}
}
};
class Scanner {
private:
byte state = 1; // 0 off, 1 blink high , 2 blink low
unsigned long previousMillis;
uint16_t on = 200;
uint32_t colorOn = 0xAA0000;
uint32_t colorOff = 0x000000;
int8_t direction = 1; // direction
Adafruit_NeoPixel& obj;
const byte id; // first pixel of this group
const byte leds; // how many leds
public:
Scanner(Adafruit_NeoPixel& obj, byte id, byte leds):
obj(obj),
id(id),
leds(leds)
{}
void set(uint16_t _on) // modify on/off times during runtime
{
on = _on;
}
void setState(byte _state) // 0 switch off blinking; 1 Switch on blinking;
{
state = _state;
}
void setColor(uint32_t newColorOn, uint32_t newColorOff)
{
colorOn = newColorOn;
colorOff = newColorOff;
}
void tick()
{
if (state)
{
uint32_t currentMillis = millis();
if (currentMillis - previousMillis >= on) {
// save the last time you blinked the LED
Serial.println(state);
obj.setPixelColor(id + state - 1, colorOff);
state = state + direction;
if (state > leds) {
state = leds - 1;
direction = -1;
}
else if (state < 1)
{
state = 2;
direction = 1;
}
obj.setPixelColor(id + state - 1, colorOn);
obj.show();
previousMillis = currentMillis;
}
}
}
};
BlinkLed turnsignalLeft(strip, 0); // name of strip object, pixelId in strip
BlinkLed blinkLedFront(strip, 1); // name of strip object, pixelId in strip
BlinkLed blinkLedRear(strip, 6); // name of strip object, pixelId in strip
BlinkLed turnsignalRight(strip, 7); // name of strip object, pixelId in strip
Scanner scanner(strip, 2, 4); // name of strip object, first pixelId, number of pixels for this group
void setup() {
Serial.begin(115200);
Serial.println(F("\nStart"));
strip.begin(); // INITIALIZE NeoPixel strip object (REQUIRED)
strip.show(); // Turn OFF all pixels ASAP
strip.setBrightness(127); // Set BRIGHTNESS to about 1/5 (max = 255)
blinkLedFront.setColor(0x0000AA, 0x555533); // blink blue/white
blinkLedRear.setColor(0x0000AA, 0xFF0000); // blink blue/red
blinkLedRear.set(400, 200);
turnsignalRight.setColor(0xFF8000, 0x201000);
turnsignalRight.set(666, 666); // 1,5Hz +/-0,5Hz 30 % to 80 %
turnsignalLeft.setColor(0xFF8000, 0x201000);
turnsignalLeft.set(666, 666); // 1,5Hz +/-0,5Hz 30 % to 80 %
turnsignalLeft.setState(0); // switch off
}
void loop() {
blinkLedFront.tick();
blinkLedRear.tick();
scanner.tick();
turnsignalRight.tick();
turnsignalLeft.tick(); // is turned off, therefore not shown
}
etwas weiter ausgeführt ist das dann in meinem Noiasca Toolkit für LEDs.
Es gibt eine Blinkklasse - die kann Hardware Blinken lassen.
Dieser Blinkklasse ist es egal, ob das ein discreter Pin am Arduino oder ein Neonpixel ist.
Bei der Anlage des Objektes übergibt man dann entweder ein Pin Objekt oder ein Objekt das auf einen Neonpixel Pixel verweist. Will man nun z.B. eine LED an einem I2C Portexpander blinken lassen, würde man eben das Portexpander Objekt und den jeweiligen Ausgang der Blinkklasse übergeben. An der Oberfläche sieht man es halt nicht, weil ich das hinter Wrapper "versteckt" habe, aber im Code der Library siehst das deutlich.
Hallo,
1000 Dank für die Ausführlichen Antworten. Jetzt habe ich endlich einen Ansatz, den ich weiter verfolgen kann. Den Tip mit dem Objekt werde ich jetzt mal mit dem CAN Controller versuchen. Es wird nämlich langsam aber sicher für mich unübersichtlich...
Viele Grüße und noch ein schönes Wochenende,
Horst
ich habe den Ansatz zwar verstanden, jedoch scheitert es bei mir an der Umsetzung. Genauer gesagt, möchte ich nun eine Library für einen FRAM Speicher >Adafruit_FRAM_SPI.h < in mehreren Klassen nutzen, ohne jedes Mal alles neu zu initialisieren - habe es Versucht, geht auch garnicht.
Irgendwo im Hauptteil des Sketches wird folgendes definiert:
#include <Adafruit_FRAM_SPI.h>
Adafruit_FRAM_SPI fram = Adafruit_FRAM_SPI (40); // use hardware SPI /CS = Pin 40
... danach habe ich vollen Zugriff auf den Chip. Mit
fram.write8 (ADDR, Wert);
schreibe ich ein Byte ins FRAM.
Wenn ich jetzt eine Header + Source Datei für eine neue Klasse erzeuge, fehlt der Zugriff. Im Hauptsketch funktioniert das aber:
#include <Adafruit_FRAM_SPI.h>
Adafruit_FRAM_SPI fram = Adafruit_FRAM_SPI (40); // use hardware SPI /CS = Pin 40
class Test {
public:
void abc() {
fram.write8 (0x00000, 0x55);
fram.write8 (0x00001, 0xAA);
}
};
Wie muss ich jetzt "fram" in der .h und .cpp Datei definieren, um dann auch wieder Zugriff auf die ursprüngliche Variable zu bekommen? Du hast heute morgen das Beispiel mit den NeoPixeln und dem Pointer oder einer Referenz auf den Ursprung gebracht, jedoch scheitert es bei mir schon mit der Definition des Pointers.
meine Frage an den TO würde erstmal lauten. Warum möchtest du dir noch eine eigene Klasse erstellen wenn du schon eine Klasse namens Adafruit_FRAM_SPI zur Verfügung hast? Der Sinn von Klassen ist doch das man sich damit mehrere Objekte erstellen kann und damit alle Daten der Objekte sauber getrennt und gekapselt sind.
Hallo Doc_Arduino,
die FRAM Klasse ist ja im Prinzip nur der Speicherplatz meiner eigenen Klasse, welche wiederum einen Teil des Hauprogammes darstellt. Leider gibt es mehrere dieser Klassen, welche auf unterschiedliche Bereiche des FRAMs ihre Daten ablegen und holen müssen.
Klar könnte ich auch alles in normalen Unterprogrammen regeln aber
a: ich möchte auch langsam mit der OOP vertraut werden
b: mir gefällt die Art und Weise, wie dann Variablen und Routinen in der Klasse aufgerufen werden.
c: habe ich ein recht übersichtlichen Programmcode, wenn dann Mal alles in .h und .cpp Dateien verschwunden ist.
d: ich habe in früheren Jahren sehr viel mit 'flachen' Programmiersprachen gearbeitet. Ab einem gewissen Punkt wird es da immer kompliziert für mich.
Hallo noiasca,
nochmal 1000 Dank! Ich habe mir gestern den ganzen Abend lang den Kopf zerbrochen und habe es ncht hinbekommen
Nach einem Kaltstart heute Morgen hat es nun geklappt!!! Hier nun die funktionierende Routine:
Adafruit_FRAM_SPI fram = Adafruit_FRAM_SPI (40);
class Test {
public:
Test (Adafruit_FRAM_SPI& obj) : obj(obj) {}
void aaa(){
obj.writeEnable(true);
obj.write8(0,0x55);
obj.write8(1,0xaa);
obj.writeEnable(false);
Serial.print ("0, 1: ");
Serial.print (obj.read8(0), HEX);
Serial.print (" ");
Serial.println (obj.read8(1), HEX);
}
private:
Adafruit_FRAM_SPI& obj;
};
Test myTest(fram);
void Setup(){
if (fram.begin()) {
Serial.println(F("SPI FRAM gefunden..."));
} else {
Serial.println(F("No SPI FRAM found ... check your connections\r\n"));
while (1);
}
Serial.println ("Test:");
myTest.aaa();
while(1);
}
Somit kann ich nun in allen Klassen auf den FRAM Chip zugreifen.
Viele Grüße,
Horst
P.S. Würde mir gerne ein Buch zu diesem Thema kaufen, jedoch wird man im Netz zu diesem Thema fast erschlagen und kann hinterher wieder keine Kaufentscheidung treffen. Bin kein Anfänger mehr, habe den Grundstock vom Arduino zwar verstanden, komme aber erst jetzt so richtig mit OOP im Kontakt. Vor Arduino habe ich alles in ASM gemacht, war zwar sehr einfach zu verstehen, jedoch hat es mich ewig viel Zeit gekostet, bis ein Prog stand...