Forum: Compiler & IDEs C++ / Arduino - Klassen und Objekte verschachteln. Wie geht's richtig?


von Marcus W. (marcusaw)


Lesenswert?

Hallo,

Ich brauche ein Objekt, das zur Laufzeit erstellt wird, welches andere 
Objekte enthält, die zur Objekterstellung erstllt werden sollen. Das 
ganze soll 1-x LEDs ansteuern.

Schlauerle in mir sagt: klar, zwei Klassen - eine LED() und eine 
LEDController().

LEDController bekommt eine attach()-Methode in der LED-Objekte zu einer 
Liste hinzugefügt werden.

So weit, so theoretisch. Nur dann steige ich mit den Pointern aus.
Bei einer "modernen" Sprache (bitte nicht hauen) würde ich in etwa 
soetwas machen:
(VORSICHT! - PseudoCode)
1
public class LEDController
2
{
3
  int ledCount;
4
  LED LEDArray[10];
5
  public LEDController()
6
  {
7
    ledCount = 0;
8
  }
9
10
  public void attach(int _pin)
11
  {
12
    LEDArray[ledCount] = new LED(_pin);
13
  }
14
15
  public LED getChannel(int _channel)
16
  {
17
    return LEDArray[_channel];
18
  }
19
20
21
}
22
23
public class LED
24
{
25
  int _pin;
26
  public LED(int pin)
27
  {
28
    pinMode(pin, OUTPUT);
29
      _pin = pin;
30
  }
31
32
  public void blink()
33
  {
34
    digitalWrite(_pin, HIGH);
35
    delay(250);
36
    digitalWrite(_pin, LOW);
37
    delay(250);   
38
  } 
39
}
40
41
42
LEDController c1 = new LEDController();
43
44
void setup()
45
{
46
  c1.attach(D0);
47
  c1.attach(D1);
48
  c1.attach(D2);
49
}
50
51
void loop()
52
{
53
  c1.getChannel(0).blink();
54
}


In der richtigen Welt hab ich folgendes probiert:

test.ino
1
#include "ledcontroller.h"
2
#include "led.h"
3
4
LEDController myLedController;
5
6
void setup()
7
{
8
  
9
  myLedController.attach(LED(D1));
10
11
  
12
}
13
14
void loop()
15
{
16
  myLedController.getChannel(0)->blink();
17
  
18
}

LEDController.h
1
#ifndef ledController_h
2
#define ledController_h
3
4
#include "Arduino.h"
5
#include "led.h"
6
7
8
class LEDController
9
{
10
  public:
11
    LEDController();
12
    void attach(LED _led);
13
    LED getChannel(int _channel);
14
15
  private:
16
    LED _ledArray[10]{};
17
    int _ledcount;
18
};
19
#endif

LEDController.cpp
1
LEDController::LEDController()
2
{
3
  _ledcount = 0;
4
}
5
6
void LEDController::attach(LED _led)
7
{
8
  _ledArray[_ledcount] = _led;
9
  _ledcount++;
10
}
11
12
LED LEDController::getChannel(int _channel)
13
{
14
  return _ledArray[_channel];
15
}


LED.h
1
#ifndef led_h
2
#define led_h
3
4
#include "Arduino.h"
5
6
class LED
7
{
8
  public:
9
    LED(int pin);
10
    LED();
11
    void blink();
12
  private:
13
    int _pin;
14
};
15
#endif

LED.cpp
1
#include "Arduino.h"
2
#include "led.h"
3
4
LED::LED(int pin)
5
{
6
  pinMode(pin, OUTPUT);
7
  _pin = pin;
8
  _state = 0;
9
}
10
11
void LED::blink()
12
{
13
  digitalWrite(_pin, HIGH);
14
  delay(250);
15
  digitalWrite(_pin, LOW);
16
  delay(250);  
17
}

Aber - klar, funktioniert so nicht. Ich muss das irgendwie miteinander 
verschwurbeln, krieg es aber nicht hin... Ich glaube zu wissen, dass die 
Lösung Pointer hat - einfach nur, weil ich sogut wie keine habe :)

: Bearbeitet durch User
von Marcus W. (marcusaw)


Lesenswert?

Ich hab jetzt eine - wahrscheinlich völlig ungeeignete, dennoch 
funktionierende Lösung:
1
#include <vector>
2
3
class LED
4
{
5
  private:
6
    int _LEDPin;
7
8
  public:
9
    LED (int newLEDPin)
10
    {
11
      _LEDPin = newLEDPin;
12
      pinMode(_LEDPin, OUTPUT);
13
      digitalWrite(_LEDPin, LOW);
14
    }
15
16
    void blink()
17
    {
18
      digitalWrite(_LEDPin,HIGH);
19
      delay(1000);
20
      digitalWrite(_LEDPin, LOW);
21
      delay(1000);
22
    }
23
};
24
25
class LEDController
26
{
27
  private:
28
    std::vector<LED> LEDList;
29
  
30
  public:
31
    void attach(int pin)
32
    {
33
      LEDList.push_back(LED(pin));
34
    }
35
36
    LED getChannel(int channel)
37
    {
38
      return LEDList[channel];
39
    }
40
};
41
42
LEDController myController;
43
void setup()
44
{
45
  myController.attach(D1);
46
  myController.attach(D2);
47
}
48
 
49
void loop()
50
{
51
  myController.getChannel(1).blink();  
52
}

von tictactoe (Gast)


Lesenswert?

Ist ja schon nicht schlecht! Auf Pointer zu verzichten ist normalerweise 
genau richtig.

Wahrscheinlich ist dein Problem jetzt, dass du nicht nur eine LED 
blinken lassen willst, sondern alle gleichzeitig. Dazu ist deine blink() 
Routine ungeeignet, weil sie sich zwei Sekunden mit einer LED 
beschäftigt und der Zeit kann sonst nichts passieren. Du musst die 
Funktion in eine Einschalt- und eine Ausschaltfunktion aufteilen und die 
beiden von loop() in einer Schleife aufrufen.

von M.K. B. (mkbit)


Lesenswert?

Marcus W. schrieb:
> Ich hab jetzt eine - wahrscheinlich völlig ungeeignete, dennoch
> funktionierende Lösung:

Ich finde die gar nicht so schlecht. Du verwendest keine Pointer, die zu 
Leak führen könnten.
Das Programm ist soweit funktional korrekt, folgende Tipps könnten es 
aber noch verbessern. Ich setze mal C++11 als Sprachversion voraus. Frag 
einfach, wenn etwas für dich noch nicht verständlich ist.

Du erzeugst immer eine Kopie der LED Klasse. Das ist jetzt nicht 
schlimm, weil eh nur der Integer kopiert wird, führt aber zu Fehlern 
wenn du dir einen Zustand in LED merken willst (z.B. für Toggle). 
Außerdem kann man LED auch ohne LEDPin anlegen.
Man kann das Kopieren von Objekten verbieten indem man den 
Kopierkonstruktor und den Zuweisungsoperator verbietet, allerdings 
werden diese vom Vektor benötigt, damit dieser sich vergrößern kann.
1
class LED
2
{
3
  private:
4
    int _LEDPin;
5
6
  public:
7
    LED() = delete; // LED kann nicht ohne Parameter angelegt werden
8
    LED (int newLEDPin)
9
      :_LEDPin(newLEDPin) // Initialisierungsliste (macht bei int keinen Unterschied, bei anderen Objekten schon)
10
    {
11
      pinMode(_LEDPin, OUTPUT);
12
      digitalWrite(_LEDPin, LOW);
13
    }
14
15
    void blink()
16
    {
17
      // ... Code unverändert
18
    }
19
};

Verhinderung der Kopie.
1
    void attach(int pin)
2
    {
3
      // Das Element wird direkt erzeugt und nicht erst lokal
4
      // erzeugt und dann kopiert. Aber selbst mit Zustand in LED 
5
      // wäre das kopieren kein Problem gewesen.
6
      LEDList.emplace_back(pin);
7
    }
8
9
    // Referenz zurückgeben auf das Element im Vektor. Damit 
10
    // wirkt sich eine Zustandsänderung auf das Element im Vektor
11
    // aus. Bei Kopie würde sich nur die Kopie ändern und das 
12
    // Element im Vektor bleibt unverändert.
13
    LED& getChannel(int channel)
14
    {
15
      // Wenn Channel außerhalb von LEDList liegt, dann ist das 
16
      // Verhalten undefiniert.
17
      return LEDList[channel];
18
    }

von Marcus W. (marcusaw)


Lesenswert?

Das war auch schon mein ganzes Problem. Ich hab das & reingesetzt und 
schon funktioniert alles wie ich will.

Der ESP8266 hat total verückt gespielt, als ich das blinken auf 
non-blocking umgestellt habe, weil der vector 1000x umkopiert wurde.
Mir ist es dann total um die Ohren geflogen und "nowTime-lastTime >= 
2000" bzw "(240 - 0) >= 2000" hat aufeinmal 'true' ergeben.

Ein verdammtes Zeichen killt mir den ganzen Code! :)
Danke für die Hilfe. Die Initialisierung hab ich inzwischen auch 
umgestellt.

von Marcus W. (marcusaw)


Lesenswert?

1
LED *getChannel(int channel)
2
{
3
  return &LEDList[channel];
4
}
5
6
// und dann 
7
myController.getChannel[0]->blink();

gegen
1
LED &getChannel(int channel)
2
{
3
  return LEDList[channel];
4
}
5
6
myController.getChannel[0].blink();

Was ist eigentlich "besser", bzw wo liegt der Unterschied?

: Bearbeitet durch User
von M.K. B. (mkbit)


Lesenswert?

Marcus W. schrieb:
> Was ist eigentlich "besser", bzw wo liegt der Unterschied?

Für den Prozessor ist es kein Unterschied.

Bei einer Referenz ist der Zugriff immer gültig. Ein Pointer kann auch 0 
sein. Als Anwender der Funktion kann ich die Referenz einfach verwenden, 
den Pointer sollte ich prüfen.
Der Pointer ist sinnvoll, wenn du auch den Wert ungültig zurückgeben 
willst, z.B. bei einem Index, der ungültig ist.

Marcus W. schrieb:
> Der ESP8266 hat total verückt gespielt, als ich das blinken auf
> non-blocking umgestellt habe, weil der vector 1000x umkopiert wurde.

Das kann ich aus deinem Code so nicht schließen. Es wird ja in LED nur 
der int member kopiert. Im Fall der Referenz wir im Prinzip ein Pointer 
auf die LED, der normalerweise auch so groß wie ein int ist, erzeugt. 
Der Vektor wird niemals kopiert. Außerdem liegt beides auf dem Stack. 
Das dürfte normalerweise keinen Unterschied machen, außer es ist ein 
Debugbuild der beim Kopieren von Objekten noch mehr Checks einbaut.

: Bearbeitet durch User
von Marcus W. (marcusaw)


Lesenswert?

Ich hatte das folgendermaßen:
1
class LED 
2
{ 
3
  
4
  private: 
5
    int       _pin;
6
    uint8_t   _state = LED_OFF; 
7
    
8
    unsigned long _last = 0;
9
    char _msg[50];
10
11
  public:
12
    enum { LED_OFF = LOW, LED_ON = HIGH };
13
14
    //LED (int pin):pin(pin),state(false){}
15
   
16
    LED (int pin)
17
    {
18
      _pin = pin;
19
20
      pinMode(_pin, OUTPUT);
21
      digitalWrite(_pin, _state);
22
    }
23
        
24
    void fade()
25
    {
26
      for (int i=0; i<_maxValue; i++)
27
      {
28
        analogWrite(_pin,i);
29
      }
30
    }
31
32
    void blink(int duration=1000)
33
    {
34
      unsigned long _now = millis();
35
36
      if (_now - _last >= duration)
37
      {
38
        Serial.print(_now - _last);
39
        Serial.print(":");
40
41
        _last = _now;
42
        _state = _state==LED_OFF ? LED_ON : LED_OFF;
43
        digitalWrite(_pin, _state);
44
45
        Serial.print(_now - _last);
46
        Serial.print(":");
47
      }
48
      
49
    }
50
};
LEDController Klasse:
1
class LEDController
2
{
3
  private:
4
    std::vector<LED> LEDList;
5
  
6
  public:
7
    LEDController()
8
    {
9
      LEDList.reserve(5);
10
    }
11
12
    
13
    void attach(int pin)
14
    {
15
      LEDList.emplace_back(pin);
16
    }
17
18
    LED getChannel(int channel)
19
    {
20
      return LEDList[channel];
21
    }
22
}

Wenn ich das so aufgespielt hab, triggert er im Millisekundentakt und 
printed mir dann nonsense wie
1
0:0 1:0 3:0 5:0 240:0 [...]

Im Moment wo (_now - _last) > 1000 ist, sollte er überhaupt nicht zum 
print kommen...

Setzte ich den Pointer, funktioniert alles wie erwartet.

: Bearbeitet durch User
von Christoph M. (mchris)


Lesenswert?


von äxl (Gast)


Lesenswert?

Christoph M. schrieb:
> Vielleicht wäre das noch was für's Thema:
> https://learn.adafruit.com/multi-tasking-the-arduino-part-1/a-classy-solution

... danke (hab mitgeesen) ...

von dino (Gast)


Lesenswert?

#include <vector>

woher kommt der? daher?:
https://github.com/mike-matera/ArduinoSTL

von Eric B. (beric)


Lesenswert?

Ich würde das blinken dem Controller überlassen. Die LED kann ja nur ein 
oder aus sein. Das macht es auch einfacher verschiedene LEDs 
gleichzeitig blinken zu lassen.

: Bearbeitet durch User
von Eric B. (beric)


Angehängte Dateien:

Lesenswert?

Etwa so wie im Anhang.

EDIT: Ah, es fehlt den '#include <Arduino>' am Anfang, aber sonst müsste 
es kompilieren & laufen

: Bearbeitet durch User
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.