Wie array mit Instanzen einer Klasse anlegen?

Hallo,

Ich erstelle 4 Instanzen. Zum Bsp.

GrayEncoder_v2 Encoder0 (&PINA, 3, 4);  // Port.A, Bit 3&4
GrayEncoder_v2 Encoder1 (&PINA, 5, 6);  
GrayEncoder_v2 Encoder2 (&PINB, 3, 4);  
GrayEncoder_v2 Encoder3 (&PINB, 5, 6);

Was muss ich machen um ein Array anzulegen worin ich 4 Instanzen definieren kann? Damit ich diese später in einem Rutsch über einen Indexdurchlauf auf einmal updaten kann. Geht das nur über Vectoren oder gibt es einfachere Lösungen?

eine Datenstruktur ist angelegt

struct encoder_t
{
	int8_t  direction;
	int32_t counter;
	uint8_t phaseA;
	uint8_t phaseB;
} encoder[4];

Ziel ist es mittels for Schleife nicht 16 Zeilen Fehlerträchtigen Code zu schreiben.

void update_Encoder ()		
{   
	for (uint8_t i=0; i<4; i++) {
		Encoder[i].encode();
		encoder[i].direction = Encoder[i].getDirection();
		encoder[i].counter   = Encoder[i].getCounter();
		encoder[i].phaseA    = Encoder[i].getA();
		encoder[i].phaseB    = Encoder[i].getB();
	}
}

Dann habe ich an Hand von Bsp. folgendes probiert.

#include "GrayEncoder_v2.h"

GrayEncoder_v2 **Encoder = new GrayEncoder_v2*[4];
Encoder[0] = new GrayEncoder_v2(&PINA, 3, 4);  
Encoder[1] = new GrayEncoder_v2(&PINA, 5, 6);  
Encoder[2] = new GrayEncoder_v2(&PINB, 3, 4);  
Encoder[3] = new GrayEncoder_v2(&PINB, 5, 6); 

// definiere Encoder Eigenschaften
struct encoder_t
{
  int8_t  direction;
  int32_t counter;
  uint8_t phaseA;
  uint8_t phaseB;
} Encoder;


void setup() {
  
}

void loop() {
  
}

Fehlermeldung: previous declaration as ‘GrayEncoder_v2** Encoder’

Ungetestet:

GrayEncoder_v2 Encoder[] = { 
                              GrayEncoder_v2(&PINA, 3, 4),
                              GrayEncoder_v2(&PINA, 5, 6),  
                              GrayEncoder_v2(&PINB, 3, 4),  
                              GrayEncoder_v2(&PINB, 5, 6), 
                            };

void loop()
{
    for(GrayEncoder_v2 & enc:Encoder)  enc.tuwas();
}

Was hast du dir dabei gedacht?

GrayEncoder_v2 **Encoder = new GrayEncoder_v2*[4];

Wenn mit dynamischen Speicher dann ein Array aus Zeigern. Diese Array-Variable entspricht dann einem Zeiger auf einen Zeiger, ab das man muss nicht explizit tun.

Aber mach das wie gesagt statisch! Kein new

Wie combie oben schreibt ist korrekt. Klassisch muss man hier den Konstruktor explizit aufrufen.
C++11 hat auch die Initialisierung vereinheitlicht. Vorher gab es da leichte Abweichungen je nach Kontext. Jetzt kann man auch hier geschweifte Klammern verwenden wie z.B. bei einem zwei-dimensionalen Array:

Test test[] = { { ... }, { ... }, ... };

Hallo,

nicht viel, den Syntax hatte ich aus einem C++ Forum für mich abgeleitet. :-[

Das folgende kompiliert erstmal fehlerfrei. Vielen Dank. Obs das tut wie gewollt muss ich noch testen. Ein leeren Standard Konstruktur der Klasse wird in dem Fall demnach nicht benötigt? Ich hatte davon immer wieder gelesen wo vorgeschlagen wurde das mit Zeiger oder vector zu machen. Ich blickte da echt nicht durch.

#include "GrayEncoder_v2.h"

GrayEncoder_v2 Encoder[] = {
                              GrayEncoder_v2(&PINA, 3, 4),
                              GrayEncoder_v2(&PINA, 5, 6), 
                              GrayEncoder_v2(&PINB, 3, 4), 
                              GrayEncoder_v2(&PINB, 5, 6),
                            };

// definiere Encoder Eigenschaften
struct encoder_t
{
  int8_t  direction;
  int32_t counter;
  uint8_t phaseA;
  uint8_t phaseB;
} enc[4];


void setup() {
  
}

void loop() {
  
}


void update_Encoder ()    
{     
  for (byte i=0; i<4; i++) {
    Encoder[i].encode();
    enc[i].direction = Encoder[i].getDirection();
    enc[i].counter   = Encoder[i].getCounter();
    enc[i].phaseA    = Encoder[i].getA();
    enc[i].phaseB    = Encoder[i].getB();
  }  
}

Doc_Arduino:
Ein leeren Standard Konstruktur der Klasse wird in dem Fall demnach nicht benötigt?

Der Konstruktor wird automatisch generiert. Einen leeren Konstruktor willst du aber nicht. Die Variablen sollen ja initialisiert werden.

Ich hatte davon immer wieder gelesen wo vorgeschlagen wurde das mit Zeiger oder vector zu machen. Ich blickte da echt nicht durch.

Für PCs ja. Und dafür sind die üblichen Tutorials geschrieben. Bei kleinen Mikrocontrollern ist das was anderes. Vektoren gibt es hier gar nicht (da keine STL)

Hallo,

aha. gut zu wissen, dass mit den kleinen aber feinen Unterschieden zum PC.

Danke euch vielmals. Habe das mittlerweile mit 2 Sensorpaaren getestet. Funktioniert mittels Indexdurchlauf. :slight_smile:

Natürlich gibt es auch Vectoren für Arduino.
Irgendwo.
Und auch viele andere Dinge aus der Standard Lib.

Der Punkt ist: "Wir" wollen keine dynamische Speicherverwaltung.

Aber nicht als Teil der Arduino Software, sondern nur als externe Library. Vor zig Versionen gab es mal eine STL Portierung. Die kleineren Dinge wie Vektoren und Strings waren auch gar nicht mal so schlimm was die Speichernutzung anging. Komplexere Container sind was anderes.

Hallo,

habe noch eine Frage zum Konstruktor und Methode.
Momentan übergebe ich pro Instanz einen gemeinsamen Portnamen und 2 Portbits.
Jetzt geht das mit dem Paarweisen Portbits nicht auf am gleichen Port.
Ein Sensorpaar muss an 2 verschiedene Ports angeklemmt werden.

Jetzt habe ich in meiner Lib einen neuen Konstruktor hinzugefügt. Unterscheidet sich in der Signatur, womit es keine Probleme geben sollte. Aber wie sage ich meiner eigentlichen encode Methode das sie andere Variablen verwenden muss?

Das einfachste wäre für mich nur den neuen Konstruktor zuverwenden und gut ist, hätte jedoch gern gewusst wie man die encode Methode ändern muss. Falls das überhaupt möglich ist. Vererbungsorgien wollte ich nicht anfangen. Ich wüßte jetzt auch nichts halbwegs sinnvolles was ich an der Methoden Signatur ändern könnte. Variablen per Parameterübergabe sind ja innerhalb der Klasse sinnlos, weil eh bekannt. Wenn ich eine abgewandelte Methode erstelle, was ja möglich wäre, dann kann ich wiederum nicht mittels Indexdurchgang drübergehen im Hauptprogramm.

Wenn möglich wäre ich dankbar zum lernen, wenn nicht, nehme ich den neuen Konstruktor für alles.

GrayEncoder_v2 Encoder[] = {
				GrayEncoder_v2(&PINA, 3, 4),
				GrayEncoder_v2(&PINA, 5, 6),
				GrayEncoder_v2(&PINA, 7, &PINB, 2),
				GrayEncoder_v2(&PINB, 1, 0),
};
class GrayEncoder_v2			// Klassenname "GrayEncoder_v2"
{
  protected:				// private wird zu protected, nur intern zugängliche Elemente ...   
    volatile uint8_t const *PORT;	// gemeinsam verwendeter Port für Phase A & B
	volatile uint8_t const *PORTphA;// verwendeter Port Phase A
	volatile uint8_t const *PORTphB;// verwendeter Port Phase B
    uint8_t const PinA;			// Pin Port.Bit erste Phase
    uint8_t const PinB;			// Pin Port.Bit zweite Phase
	int8_t enc_last;
    volatile int32_t enc_counter;	// Zähler
    int8_t direction;			// Richtungsanzeige  +1 / -1
    uint8_t Phase_A;			// erste Phase
    uint8_t Phase_B;			// zweite Phase
	
  public:							// von außen zugängliche Elemente ...
    GrayEncoder_v2 (volatile uint8_t*, uint8_t, uint8_t);   // Port, Port.Bit, Port.Bit
	GrayEncoder_v2 (volatile uint8_t*, uint8_t, volatile uint8_t*, uint8_t);   // Port, Port.Bit, Port, Port.Bit
    void encode(void);
	void delCounter(void);				// nullt den Counter
	void setCounter( int32_t value );		// Counter-Wert ändern
    int32_t getCounter()  { return enc_counter; }	// Getter-Methode, Zählerrückgabe
    int8_t getDirection()  { return direction; }	// Getter-Methode, Richtungsanzeige    	
    uint8_t getA(void)  { return Phase_A; }    		// Getter-Methode, Signal von Phase A
    uint8_t getB(void)  { return Phase_B; }    		// Getter-Methode, Signal von Phase B	
};
#include "GrayEncoder_v2.h"

// Konstruktor ::
GrayEncoder_v2::GrayEncoder_v2 (volatile uint8_t *p_Port, uint8_t _A, uint8_t _B):
  // Initialisierungsliste
  PORT(p_Port),	
  PinA(_A),
  PinB(_B),
  enc_last(0x02),	// Startwert B11, weil TLE4905 low aktiv
  enc_counter(0),	// Zähler
  direction(0),		// Richtungsanzeige  +1 / -1
  Phase_A(1),     	// default 1, weil TLE4905 low aktiv
  Phase_B(1)		// default 1, weil TLE4905 low aktiv
{ }	


GrayEncoder_v2::GrayEncoder_v2 (volatile uint8_t *p_Port_a, uint8_t _A, volatile uint8_t *p_Port_b,  uint8_t _B):
// Initialisierungsliste
PORTphA(p_Port_a),
PORTphB(p_Port_b),
PinA(_A),
PinB(_B),
enc_last(0x02),	// Startwert B11, weil TLE4905 low aktiv
enc_counter(0),	// Zähler
direction(0),	// Richtungsanzeige  +1 / -1
Phase_A(1),     // default 1, weil TLE4905 low aktiv
Phase_B(1)	// default 1, weil TLE4905 low aktiv
{ }


// ab hier Scopes/Bereichsoperator ::
void GrayEncoder_v2::encode(void)  
{
  int8_t i = 0;
  
  Phase_A = (*PORT >> PinA & 1);	
  Phase_B = (*PORT >> PinB & 1);
  
  // Phase_A = (*PORTphA >> PinA & 1);
  // Phase_B = (*PORTphB >> PinB & 1);  

  if( Phase_A )  {
    i = 1;
  }
  
  if( Phase_B )  {
    i ^= 3;           // convert gray to binary
  }
  
  i -= enc_last;      // difference new - last

  if( i & 1 )  {				// bit 0 = value (1)
    enc_last += i;				// store new as next last
    enc_counter += (int32_t) (i & 2) - 1;	// bit 1 = direction (+/-)
    direction = (i & 2) - 1;			// (+1 / -1)
  }
  
}

Dem Array ist es völlig wurscht mit welchem Konstruktor Du die Einträge erzeugst. Hauptsache es kommt ein gültiger GrayEncoder_v2 da raus.

Bei der Bearbeitung über Schleifen hat der Konstruktor keine Relevanz mehr, wenn intern die richtigen Weichen gestellt werden bei der Parameterübergabe.

Oder habe ich Dich falsch verstanden und Du wolltest etwas anderes wissen?

Gruß Tommy

Hallo,

leider falsch verstanden. :wink: Momentan müsste ich zwei Methoden erstellen, jeweils passend zu den übergebenen Elementen. Nützt mir jedoch nichts. Aussehen würde das jedoch so. Einmal ist es ein gemeinsamer Port und einmal müssen es 2 verschiedene Ports sein.
Wie gesagt, wenn das nicht geht oder der Aufwand übermäßig höher wird, auch mit Hinblick auf RAM Verbrauch, dann nehme ich nur den neuen Konstruktor und passe die eine encode Methode an. Wollte aber fragen ob und was machbar wäre ...

// alt
void GrayEncoder_v2::encode(void)  // alt
{
  int8_t i = 0;
  
  Phase_A = (*PORT >> PinA & 1);	// ein gemeinsamer Port
  Phase_B = (*PORT >> PinB & 1);
    
  if( Phase_A )  {
    i = 1;
  }
  
  if( Phase_B )  {
    i ^= 3;           // convert gray to binary
  }
  
  i -= enc_last;      // difference new - last

  if( i & 1 )  {				// bit 0 = value (1)
    enc_last += i;				// store new as next last
    enc_counter += (int32_t) (i & 2) - 1;	// bit 1 = direction (+/-), Zähler
    direction = (i & 2) - 1;				
  }
}


// neu
void GrayEncoder_v2::encode(void)  // neu
{
  int8_t i = 0;
  
  Phase_A = (*PORTphA >> PinA & 1);   // 2 verschiedene Ports
  Phase_B = (*PORTphB >> PinB & 1);  
    
  if( Phase_A )  {
    i = 1;
  }
  
  if( Phase_B )  {
    i ^= 3;           // convert gray to binary
  }
  
  i -= enc_last;      // difference new - last

  if( i & 1 )  {				/ bit 0 = value (1)
    enc_last += i;				// store new as next last
    enc_counter += (int32_t) (i & 2) - 1;	// bit 1 = direction (+/-), Zähler
    direction = (i & 2) - 1;				
  }
}
GrayEncoder_v2 Encoder[] = {
				GrayEncoder_v2(&PINA, PA3, &PINA, PA4),
				GrayEncoder_v2(&PINA, PA5, &PINA, PA6),
				GrayEncoder_v2(&PINA, PA7, &PINB, PB2),
				GrayEncoder_v2(&PINB, PB1, &PINB, PB0),
};

Dann brauchst du nur einen Konstruktor.

Ohne den Code verifiziert zu haben: Das ist pro EncoderInstanz ein byte mehr. Da würde ich für alle den neuen Code nehmen.

Gruß Tommy

Wie gesagt, wenn das nicht geht oder der Aufwand übermäßig höher wird, auch mit Hinblick auf RAM Verbrauch, dann nehme ich nur den neuen Konstruktor und passe die eine encode Methode an. Wollte aber fragen ob und was machbar wäre ...

Dann nimm doch die Arduino Pinnummern, dann musste nicht 6 Bytes für die 2 Pins in der Klasse speichern, sondern nur 2

Eine Einsparung von 66,666% Ram, in dem Punkt, auf kosten der Laufzeit.

Hallo,

ich hatte irgendwie gedacht man kann die encode Methode(n) auch irgendwie unterscheidbar machen ähnlich der Signaturerkennung beim Konstruktur. Aber gut, dann ändere ich das für alle um, kein Problem. Ist bestimmt auch die beste Lösung am Ende.

Arduino Pinnummern gehen nicht, es ist ein ATtiny den ich im nackten AS7 programmiere ohne IDE Plugin.

Ich danke euch für die Unterhaltung.

Vererbung hast du ja explizit ausgeschlossen.

Frei nach dem Motto:

Wasch mich, aber mach mich nicht nass.

Hallo,

ja habe ich, weil ich weiß das sich das für diesen Fall nicht wirklich lohnt. Ich denke das siehst du sicherlich genauso.
Wenn du jedoch Lust hast, können wir das gern durchspielen bzw. kannst du mir das zeigen.
Das Ziel sollte jedoch sein, dass ich danach immer noch mittels Indexdurchlauf alle gleich behandeln kann. Der Name der Methode darf sich nach außen nicht verändern.

Eine Vererbung wurde mir schon einmal gezeigt. Hatte aber keine Idee wie ich das darauf anwenden könnte ...... Während ich diese Zeilen tippe habe ich gerade einen sehr hellen Moment :slight_smile: ich glaube ich weiß was ich machen könnte ... Update kommt ....

Während ich diese Zeilen tippe habe ich gerade einen sehr hellen Moment

:o :o

Fein!

Für deine "(weiß nicht, wie ich es nennen soll)" möchte ich dir die OOP Entwurfsmuster ans Herz legen.
Ja, ich weiß, dass man nicht alle auf µC sinnvoll umsetzen kann. Aber es stecken ca 40 Jahre Erfahrung darin. Von Hunderten, wenn nicht sogar von Tausenden, Programmieren.
Ein paar Fetzen lassen sich bestimmt nutzen.

OOP Design Pattern
Für uns sind wohl mehr die Strukturmuster und Verhaltensmuster von Interesse:

Vielleicht ist ja jetzt an der Zeit, sich aus diesem genialen Pool zu bedienen.

Hallo,

die schwer verdauliche Kost lese ich mir durch. Habe in der Zwischenzeit was zusammengebaut. Kompilieren tut es fehlerfrei in AS7. Ich habe wieder 2 Konstrukturen mit unterschiedlichen Signaturen. Dann habe ich den einen Konstruktor mit 4 Parametern vererbt an “GrayEncoder_v3”, doofe Namensgebung ich weiß, und dann die encode Methode dafür abgeändert. Sodass diese mit gleichen Namen getrennt ist. Allerdings und das stört mich, kann man dennoch eine Instanz erstellen mit dem 4 Parameter Konstruktur von GrayEncoder_v2 der dann bei encode ins leere läuft bzw. der Compiler meckert schon vorher.

Was ist nun falsch im Sinne von geht zwar vielleicht aber totaler Nonens?

// main Initialisierungen
GrayEncoder_v2 Encoder[] = {
							GrayEncoder_v2(&PINA, 3, 4),
							GrayEncoder_v2(&PINA, 5, 6),
							GrayEncoder_v3(&PINA, 7, &PINB, 2),
							GrayEncoder_v2(&PINB, 1, 0),
};


---------------------------------------------------------

// GrayEncoder_v2.h
#pragma once

#include <avr/io.h>


class GrayEncoder_v2				// Klassenname "GrayEncoder_v2"
{
  protected:						// private wird zu protected, nur intern zugängliche Elemente ...   
	volatile uint8_t const *PORT;   // gemeinsamer Port für Phase A&B
	volatile uint8_t const *PORT_a;	// unterschiedlicher Port Phase A
	volatile uint8_t const *PORT_b;	// unterschiedlicher Port Phase B
    uint8_t const PinA;				// Pin Port.Bit erste Phase
    uint8_t const PinB;				// Pin Port.Bit zweite Phase
	int8_t enc_last;
    volatile int32_t enc_counter;	// Zähler
    int8_t direction;				// Richtungsanzeige  +1 / -1
    uint8_t Phase_A;				// erste Phase
    uint8_t Phase_B;				// zweite Phase
	
  public:							// von außen zugängliche Elemente ...
    GrayEncoder_v2 (volatile uint8_t*, uint8_t, uint8_t);   // Port, Port.Bit, Port.Bit
	GrayEncoder_v2 (volatile uint8_t*, uint8_t, volatile uint8_t*, uint8_t);   // Port, Port.Bit, Port, Port.Bit
    void encode(void);
	void delCounter(void);							// nullt den Counter
	void setCounter( int32_t value );				// Counter-Wert ändern
    int32_t getCounter()  { return enc_counter; }	// Getter-Methode, Zählerrückgabe
    int8_t getDirection()  { return direction; }	// Getter-Methode, Richtungsanzeige    	
    uint8_t getA(void)  { return Phase_A; }    		// Getter-Methode, Signal von Phase A
    uint8_t getB(void)  { return Phase_B; }    		// Getter-Methode, Signal von Phase B	
}; 


// Klasse "GrayEncoder_v3" erbt von "GrayEncoder_v2"
class GrayEncoder_v3 : public GrayEncoder_v2
{
	public:	             // von außen zugängliche Elemente ...
	GrayEncoder_v3 (volatile uint8_t*, uint8_t, volatile uint8_t*, uint8_t);   // Port, Port.Bit, Port, Port.Bit
	void encode(void);
};


---------------------------------------------------------

// Definition der Klasse
// GrayEncoder_v2.cpp

#include "GrayEncoder_v2.h"

// Konstruktor ::              // Gesamtprogramm: Flash 3698 Bytes, SRAM 272 Bytes
GrayEncoder_v2::GrayEncoder_v2 (volatile uint8_t *p_Port, uint8_t _A, uint8_t _B):
// Initialisierungsliste
PORT(p_Port),	
PinA(_A),
PinB(_B),
enc_last(0x02),	// Startwert B11, weil TLE4905 low aktiv
enc_counter(0),	// Zähler
direction(0),	// Richtungsanzeige  +1 / -1
Phase_A(1),     // default 1, weil TLE4905 low aktiv
Phase_B(1)		// default 1, weil TLE4905 low aktiv
{ }
	
// Konstruktor ::          
GrayEncoder_v2::GrayEncoder_v2 (volatile uint8_t *p_Port_a, uint8_t _A, volatile uint8_t *p_Port_b,  uint8_t _B):
// Initialisierungsliste
PORT_a(p_Port_a),
PORT_b(p_Port_b),
PinA(_A),
PinB(_B),
enc_last(0x02),	// Startwert B11, weil TLE4905 low aktiv
enc_counter(0),	// Zähler
direction(0),	// Richtungsanzeige  +1 / -1
Phase_A(1),     // default 1, weil TLE4905 low aktiv
Phase_B(1)		// default 1, weil TLE4905 low aktiv
{ }


// ab hier Scopes/Bereichsoperator ::
void GrayEncoder_v2::encode(void)  
{
  int8_t i = 0;
  
  Phase_A = (*PORT >> PinA & 1);	// gemeinsamer Port
  Phase_B = (*PORT >> PinB & 1);
    
  if( Phase_A )  {
    i = 1;
  }
  
  if( Phase_B )  {
    i ^= 3;				// convert gray to binary
  }
  
  i -= enc_last;		// difference new - last

  if( i & 1 )  {							// bit 0 = value (1)
    enc_last += i;							// store new as next last
    enc_counter += (int32_t) (i & 2) - 1;	// bit 1 = direction (+/-), Zähler
    direction = (i & 2) - 1;				// Richtungsanzeige (+1 / -1)
  }
  
}

void GrayEncoder_v2::delCounter(void)
{
  enc_counter = 0;
}


void GrayEncoder_v2::setCounter( int32_t value )
{
  enc_counter = value;
}		


// neue vererbte Klasse // mit der 4 Parameter Signatur, Gesamtprogramm: Flash 3736 Bytes, SRAM 280 Bytes
// Konstruktor Vererbung
GrayEncoder_v3::GrayEncoder_v3 (volatile uint8_t *p_Port_a, uint8_t _A, volatile uint8_t *p_Port_b,  uint8_t _B) : GrayEncoder_v2 (p_Port_a, _A, p_Port_b, _B)
{ }

void GrayEncoder_v3::encode(void)
{
	int8_t i = 0;
	
	Phase_A = (*PORT_a >> PinA & 1);	// getrennte Ports
	Phase_B = (*PORT_b >> PinB & 1);
	
	if( Phase_A )  {
		i = 1;
	}
	
	if( Phase_B )  {
		i ^= 3;				// convert gray to binary
	}
	
	i -= enc_last;			// difference new - last

	if( i & 1 )  {								// bit 0 = value (1)
		enc_last += i;							// store new as next last
		enc_counter += (int32_t) (i & 2) - 1;	// bit 1 = direction (+/-), Zähler
		direction = (i & 2) - 1;				// Richtungsanzeige (+1 / -1)
	}	
}

Geht und ist wohl kein Nonsense.
Musst halt sichergehen, dass auch wenn alle Encoder[ i ] vom Typ _v2 sind, bei Verwendung von Encoder[2] die _v3 Methoden verwendet werden.

Das geht, wenn die verwendeten Methoden der Basisklasse virtual sind.

Oder du hast ein Array aus Elementen einer abstrakten Basisklasse, das aus Elementen verschiedener abgeleiteter Klassen besteht.