Anfängerfrage zur Objektorientierung

Hallo,

Ich bin ein Anfänger in der Programmierung und benötige Hilfe. Um meinen Code übersichtlich zu halten, möchte ich OOP einsetzen. Mein zukünftiger Brauautomat besteht aus mehreren Komponenten (DC Motor, Pumpen, DS8020, Relais, LCD, Taster, Encoder, RTC und Arduino MEGA 2560).
Die Idee ist, eine Klasse "Device" zu erzeugen, die alle Komponenten als private Member besitzt. Das hätte den Vorteil der Kapselung und auch der größeren Übersichtlichkeit im Hauptprogramm, weil ich dort nicht alle Komponenten einzeln ansteuern möchte. In dem vereinfachten Beispiel besteht das Gerät aus einem LCD-Display und einer LED. Ich bekomme es nicht zum Laufen und bitte um Hilfe.

Die Fehlermeldung ist: no matching function for call to 'LiquidCrystal::LiquidCrystal()

Ich hoffe es sind nicht noch zu viele andere Fehler drin und die Herangehensweise ist grundsätzlich vernünftig. Jede Hilfe ist willkommen! - Vielen Dank!

**Das Hauptprogramm:**
#include <LiquidCrystal.h>
#include "Led.h"
#include "Device.h"

LiquidCrystal lcd(42, 40, 38, 36, 34, 32);
Led led(13);
Device device(lcd, led);

void setup() 
{
device.init();
device.helloWorld();
}

void loop() 
{
}

**LED.h**
#ifndef LED_H
#define LED_H
#include <Arduino.h>

class Led
{
private:
   byte pin;
   
public:
   Led(){}  // default constructur  
   Led(byte pin);
   void init();  
   void on();
   void off();
};
#endif

**LED.cpp**
#include "Led.h"
Led::Led(byte pin)
{
  this->pin = pin;
}

void Led::init()
{
  pinMode (pin, OUTPUT);
}

void Led::on()
{
  digitalWrite(pin, HIGH);
}

void Led::off()
{
  digitalWrite(pin, LOW);
}

**DEVICE.h**
#ifndef DEVICE_H
#define DEVICE_H

#include <Arduino.h>
#include <LiquidCrystal.h>
#include "Led.h"

class Device
{
private:
   LiquidCrystal deviceLcd;
   Led deviceLed;
   
public:
   Device(){}   
   Device(LiquidCrystal lcd , Led led);   
   void init();
   void helloWorld();
};
#endif

**DEVICE.cpp**
#include "Device.h"

Device::Device(LiquidCrystal lcd , Led led)
{
  deviceLcd=lcd;
  deviceLed=led;
}

void Device::init()
{
  deviceLed.init();
  deviceLed.off();
  deviceLcd.begin(16, 2);   
}

void Device::helloWorld()
{
  deviceLed.on();
  deviceLcd.setCursor(0,0);
  deviceLcd.print("Hello World"); 
}

Led.h (234 Bytes)
Led.cpp (220 Bytes)
Device.h (491 Bytes)
Device.cpp (504 Bytes)
Main.ino (327 Bytes)

Warum fängst Du ein neues Thema an und löschst den Inhalt Deines alten Themas zum gleichen Inhalt?

Gruß Tommy

Tut mir leid- ich bin neu im Forum und habe noch einige Schwierigkeiten. Bitte um Entschuldigung das ist ein ungewollter Fehler meinerseits.

Device::Device(LiquidCrystal lcd , Led led)
{
  deviceLcd=lcd;
  deviceLed=led;
}

Das ist nicht richtig auch wenn es kompiliert. So werden die Objekte erst Default-Initialisiert und dann durch eine Kopie neu zugewiesen. Das Stichwort hier ist "Initialisierungsliste"

Die Fehlermeldung ist: no matching function for call to 'LiquidCrystal::LiquidCrystal()

Das LCD wird keinen Konstruktor ohne Parameter haben. Wenn du im Konstruktor-Körper bist wurde das Objekt schon erstellt. Oder wie hier versucht das zu tun. Durch die Initialisierungsliste geschieht das vorher.

Du kannst die Parameter im Konstruktor deiner Klasse angeben und dann per Initialisierungsliste an den Konstruktor der anderen Klassen durchreichen. Oder du hast in der Klasse nur eine Referenz auf ein Objekt, aber nicht ein Objekt selbst

Vielen Dank für die schnelle Antwort! Ich durchsuche das Forum nach "Initialisierungsliste". Wenn du einen Link zu einem funktionierenden Beispiel hättest, wäre das natürlich super!

Hier gibts Informationen Constructors and member initializer lists - cppreference.com

Hallo, ich habe recherchiert und den Quellcode unten erzeugt, den ich aber selbst noch nicht ganz zu 100 Prozent verstanden habe. Er funktioniert fast vollständig und kompiliert ohne Fehler. Die LED blinkt und tut damit was sie soll. Aber das LCD funktioniert noch nicht vollständig (normale Zeichenausgabe funktioniert):

  1. Problem - Custom Chars:

Wenn ich im Hauptprogramm mit
device.lcd.createChar(POS,theta);
das Zeichen erzeuge, funktioniert alles einwandfrei und das kleine theta wird angezeigt.

Wenn ich aber im Hauptprogramm mit
device.createChar(POS,theta);
das Zeichen erzeuge, wird etwas Kryptisches angezeigt.

Damit der Fehler besser ausprobierbar wird habe ich die Instanzen auf public gesetzt. Kann vielleicht jemand den Fehler finden?

  1. Verständnisproblem: So richtig verstehe ich leider noch nicht in Device.h die Zeilen:
    LiquidCrystal lcd;
    Led led;
    
    Device();
    ~Device();

Wenn ihr mir die noch erklären würdet :slight_smile: dann kann ich evtl. verstehen was ich getan habe. Für mich sieht das so aus als würde ich lcd und led Parameter-los erzeugen und dann im Konstruktor von Device () die Initialisierungsliste übergeben. Die Zeile ~Device(); verstehe ich nicht. Danke noch einmal jetzt schon für Eure Hilfe!

Das Hauptprogramm

#include "Device.h"

Device    device;

byte theta[8] = 
     {
            0b00100,
            0b01010,
            0b01010,
            0b00111,
            0b10010,
            0b01010,
            0b01010,
            0b00100,
     };

const byte   kCOLS = 16;
const byte   kROWS =  2;
const byte   POS =1;

void loop()
{
    device.on();  ///
    delay(1000);
    device.off();
    delay(1000);
}

void setup()
{
    device.init();
    device.lcd.createChar(POS,theta);
    //device.createChar(POS,theta);    
    device.begin(kCOLS, kROWS);
    device.print(" a");  //
    device.print("\x01");  //
}

Device.h

#ifndef DEVICE_H
#define DEVICE_H
#include <Arduino.h>
#include <LiquidCrystal.h>
#include "Led.h"

class Device
{
private:
//
public:

    LiquidCrystal lcd;
    Led led;
    
    Device();
    ~Device();

    void begin(byte cols, byte rows);

    void print(String);

    void createChar(byte address, byte customChar);

    void on();

    void off();

    void init();
};
#endif

Device.cpp

#include <Arduino.h>
#include <LiquidCrystal.h>
#include "Device.h"

Device::Device():lcd(42,40,38,36,34,32),led(13)
{}

Device::~Device()
{}

void Device::begin(byte cols, byte rows)
{
    lcd.begin(cols, rows);
}

void Device::print(String string)
{
    lcd.print(string);
}

void Device::createChar(byte no, byte customChar)
{
    lcd.createChar(no,customChar);
}

void Device::on()
{
  led.on();
}

void Device::off()
{
  led.off();
}

void Device::init()
{
  led.init();
}

LED.h

#ifndef LED_H
#define LED_H

#include <Arduino.h>

class Led
{
private:
   byte pin;
   
public:
   Led(){}  // default constructur, do not use, must be created every time   
   Led(byte pin);
   
   void init();
   
   void init(byte defaultState);
   
   void on();
   
   void off();
};

#endif

LED.cpp

#include "Led.h"
Led::Led(byte pin)
{
  this->pin = pin;
}

void Led::init()
{
  pinMode (pin, OUTPUT);
}

void Led::init(byte defaultState)
{
  init();
  if (defaultState == HIGH)
  {
    on();
  }
  else
  {
    off();
  }
}

void Led::on()
{
  digitalWrite(pin, HIGH);
}

void Led::off()
{
  digitalWrite(pin, LOW);
}
  1. Problem - Custom Chars:
    in deinem device.createChar übernimmst du nur ein byte, dein theta besteht aber aus einem Array. Eigentlich würde ich erwarten dass du ein Warning beim Compilieren erhältst - gibst du dir auch alle Warnings des Compilers aus?

Hol dir dein globales Array theta in deine member function, dann brauchst du keine Referenz/keinen Pointer zum durchreichen vom Array. Außerdem setzt du damit 8 byte SRAM Globals frei.

p.s.: schau dir mal im Standard-Zeichensatz vom LCD die Position 0xF2 an. Das könnte man meines erachtens als Theta verwenden.

  1. Verständnisproblem:
    die erste Zeile legt ein Member lcd vom typ LiquidCrystal an
    die zweite Zeile ein member led vom Typ LED
    die dritte Zeile ist ein Konstruktor
    die vierte Zeile ist ein Destructor

Wollte ich dir schon gestern abend schreiben, aber dann hast du deinen Erst-Post gelöscht:
Wenn du ein ähnliches Beispiel sehen willst:
ein LCD,
eine Button Klasse
und dann ein POS "device" das eine Komposition aus einem LCD + 3 Buttons nutzt:
https://wokwi.com/projects/327664466288181843

Ich bedanke mich SEHR für die Antwort! Unbedingt ist das ähnliche Beispiel hilfreich. Eigentlich sollten ja alle Anfänger irgendwann in dieses Problem reinlaufen - z.B. wenn der Code mehr als 500 Zeilen bekommt und man den Wunsch verspürt diesen auch noch nach 3 Monaten verstehen zu können. Mich überrascht, dass es zu diesem Standardproblem relativ wenig Forums-Beiträge gibt (oder ich bin unfähig diese zu finden...).

"Eigentlich würde ich erwarten dass du ein Warning beim Compilieren erhältst - gibst du dir auch alle Warnings des Compilers aus?"
Antwort: Wahrscheinlich nicht - ich habe bei der Arduino-IDE alles bei den Standardeinstellungen belassen und bin froh das es funktioniert ...

Vielleicht in diesem Zusammenhang doch noch eine letzte Frage: Gibt es generellen Link/Tutorial zu den Thema "Wie schreibe ich übersichtlichen, gut wartbaren Code?" das wäre vor Allem für mich als Anfänger super, weil ich jetzt jede Menge (wahrscheinlich schlechten) Code erzeuge und dann richtig viel Nacharbeit brauche oder frustriert aufgebe, weil ich meine Fehler nicht finde (das wäre allerdings bitter, weil der Hardware komplett steht und "nur noch" das bisschen Code fehlt :-)).

ich hätte da drei Quellen:
a) gibts ein paar Artikel von Arduino.cc z.B.
https://docs.arduino.cc/learn/contributions/arduino-creating-library-guide
b) lies dir mal z.B das LCD API 1.0 durch, das nehme ich als Anhaltspunkt für alles was irgendwie nach "Display" aussieht.
https://playground.arduino.cc/Code/LCDAPI
c) Von Sparkfun gibts ein Tutorial:
How to write a great library

geht zwar alles Richtung "Library", aber das ist ja auch nur Code :wink:

EDIT:
Bin etwas durcheinander gekommen. Daher eine andere Erklärung

"Im" Konstruktor bedeutet (zumindest für mich) zwischen den geschweiften Klammern. An der Stelle wurde das Objekt schon erstellt und es zu spät irgendwas zu Initialisieren. Hier gibt es nur Zuweisungen nach einer Default-Initialisierung. Bzw. es schlägt fehl weil es wie am Anfang keinen passenden Konstruktor gab.

Die Initialisierungsliste wird zeitlich vor dem Konstruktor-Körper ausgeführt und zum Zeitpunkt der Objekt-Instanziierung. So kann man dadurch Objekte, Konstanten oder Referenzen initialisieren.

Hallo Serenifly, tatsächlich hast du Recht, es liegen einige Verständnisprobleme vor und zum Thema Initialisierungsliste habe ich offensichtlich kein für mich verständliches, einfaches Beispiel gefunden, das ich auf mein Problem anwenden könnte. Aber das obige, von nioasca verlinkte, Beispiel nutzt wahrscheinlich eine - ich schaue es mir an...

Ich danke dir nochmals sehr! Allerdings scheint der erste und der dritte Link nicht zu funktionieren. Kannst du das mal prüfen? Grüße, Stefan

Da ist vermutlich aus der Lesezeichenliste kopiert worden :slight_smile: How to Write A Great Arduino Library - News - SparkFun Electronics und https://docs.arduino.cc/learn/contributions/arduino-creating-library-guide/

Tut mir leid, ich bin durcheinander kommen und hatte was falsches geschrieben :frowning: Da war ich etwas zu eilig.
Ich habe es editiert und eine Erklärung geschrieben.

Ansonsten stimmt es dass das vor allem eine Frage von genauen Definitionen von Begriffen ist. z.B. wenn du schreibst "und dann im Konstruktor von Device () die Initialisierungsliste übergeben". Ich weiß nicht wie es andere handhaben und für mich ist "im Konstruktor" eigentlich der Konstruktor-Körper selbst. Da gibt es einen massiven zeitlichen Unterschied zwischen dem und der Initialisierungsliste. Diese kommt vorher dran. Deshalb kannst du da zum Zeitpunkt der Initialisierung alle relevanten Parameter übergeben.

Was du im deinem ersten Code gemacht hast ein häufiger Anfängerfehler bei der Sache da diese Initialisierungsgeschichte eine Eigenheit von C++ ist. In manchen anderen Sprachen verhält sich da nicht so.

done.

Ja tut es ^^. Genaugenommen haben dort alle Klassen auch eine Initialisierungsliste. Ich muss ja die Konstanten bei der Objektanlage initialisieren können.

Da gibts es tausend Regeln.

Das fängt schon beim Include Guard an. Du verwendest den alten.
Hübscher ist der moderne, #pragma once

Dann rate ich dazu, auf *.cpp Dateien möglichst zu verzichten. Also Header only.

Das ist die Deklaration eines Destruktors.

Sowas macht mich kirre!
Warum einen Konstruktor erzeugen, den man nicht benutzen darf?

Dann doch lieber so:
Led() = delete; // default constructor abgeschaltet

Ich nehme gerade folgenden Kurs, in dem aber (bisher) die aktuelle Problematik nicht behandelt wird.

Im Kurs wird auf der Erstellung der CPP-Files beharrt. Ebenso das mit dem Led(){} default constructor ist aus dem Kurs wörtlich übernommen (Bei meinem Kenntnisstand würde ich mir solche eigenen Statements nicht erlauben :-)). Aber ich will nicht rummeckern - der Kurs ist gut, deckt aber nicht die komplette Tiefe ab, die man offensichtlich benötigt um was Gescheites hinzubekommen. Da ich aber noch nicht ganz durch bin kommt es vielleicht noch dran.

Aber vielen Dank für die Tipps - ich werde das alles nach und nach in den Quellcode einarbeiten.

Also das Beispiel von noiasca ist super (!), ich habe folgenden Konstruktor der Klasse POS gefunden, die analog zu meiner Device Klasse ist und als Komponenten drei Buttons und ein LCD besitzt:

POS (byte addPin, byte reducePin, byte enterPin, byte lcdAddr, byte cols, byte rows) : btnAdd{addPin}, btnReduce{reducePin}, btnEnter{enterPin}, lcd(lcdAddr, cols, rows) {}

Ich werde versuchen, das in meinem Projekt umzusetzen und poste dann den Code nochmal.

Aua!
Ich habe da mal kurz reingesehen, da, wofür man nicht bezahlen muss.
Da stimmt ja einiges. Aber auch einiges nicht.

Mein Rat:
Nimm den Kurs.
Prüfe jede einzelne Aussage gegen die C++ Spezifikation.
Prüfe die jeweiligen alternativen Wege.
Schalte das Hirn dabei ein und nimm die besseren Wege.

Grundsätzlich:
Einen Konstruktor zu definieren, wenn man ihn nicht verwenden darf, ist schlicht irre.
Jenseits von Gut und Böse!

Bedenke:

Manche Aussagen sind so dermaßen falsch, dass noch nicht einmal das Gegenteil richtig ist.

Das darf der Mann.
Nur eben ohne mich.
Wenn es in seltenen Fällen nicht anders geht, dann ok.
Aber aus Prinzip, WARUM?


Gut...
Dann werde ich mich hier mal raus halten.
Denn ich habe keine Lust, wie Don Quijote gegen Windmühlen (Tutorials) zu kämpfen.