Knoten im Kopf, Klassen und Vererbung

OP #5562667
Lesenswert?

Guten Abend miteinander,

Vielleicht könnt ihr mir helfen, ich komme nichtmehr weiter.

Ich möchte gerne in C++ eine Klasse für einen Hardwaretimer schreiben.
Dieser Timer hat einen Counter und eine Interrupt Unit. Soweit so gut, 
dass lässt sich ja relativ einfach umsetzen. Zusätzlich hat der Timer 
nun aber 4 CaptureCompare-Kanäle. Diese möchte ich gerne als separate 
Objekte ansprechen und trotzdem sollen sie sich wie ein eigenständiger 
Timer verhalten. Wenn ich jetzt die Timer-Klasse als Basis-Klasse nehme 
und davon die CaptureCompare-Kanäle ableite
1
class Timer {};
2
class CaptureCompare:public Timer{}
dann habe ich das Problem, dass ich bei 4 Kanälen quasi auch 4 Instanzen 
der Basisklasse erstelle und das erschwert das Interrupt Handling.
Wenn ich nun die CaptureCompare Klasse nicht ableite
1
class Timer {};
2

3
class CaptureCompare{
4
private:
5
Timer *timer;
6
public:
7
void startTimer(void){
8
timer->startTimer();
9
}
10
}
kann ich sie nur dann wie die Basisklasse behandeln, wenn ich alle 
Funktionen der Basisklasse auch dort implementiere.
Ich glaube ich sehe den Wald vor lauter Bäumen nicht mehr.
Könnt ihr mir bitte helfen?
OP #5562728
Lesenswert?

Damit meine ich, dass ich gerne über die CaptureCompare Instanz die 
Grundfunktionen des Timers bedienen möchte.
Am Ende müsste es dann also möglich sein über jede Instanz der 
CaptureCompare Klasse zum Beispiel den Prescaler zu verstellen. Das 
hätte natürlich auswirkungen auf die jeweiligen anderen Instanzen.

edit: ist wohl ein bisschen doof formuliert.

vlt ist so besser:

> Diese möchte ich gerne als separate
> Objekte ansprechen und trotzdem sollen sie sich bezüglich der
> Grundfunktionen wie ein Timer (eine Instanz der Klasse Timer) verhalten.
#5562747
Lesenswert?

Mal ein Vorschlag.

Die class Timer ist ein Interface, das die Timermethoden vorgibt.
Davon abgeleitet ist dann class CaptureCompare, die diese Funktionen 
implementiert.
Eine class X die deine Hardware ansteuert. Die CaptureCompare Instanzen 
haben alle eine Referenz auf die selbe Instanz von X, die dann die 
Abhängigkeiten verwaltet.
Gast #5562769
Lesenswert?

A. Z. schrieb:
> Am Ende müsste es dann also möglich sein über jede Instanz der
> CaptureCompare Klasse zum Beispiel den Prescaler zu verstellen. Das
> hätte natürlich auswirkungen auf die jeweiligen anderen Instanzen.

Das ist IMHO eine blöde Architektur. Dann brauchst du irgend eine 
Abstraktion vom eigentlichen Timer, damit sich nicht alles gegenseitig 
verstellt.
Dann kommt Jitter rein, oder irgend ein Glitch den du behandeln musst. 
Schon wird aus dem ollen Timer eine ganze Bibliothek...
OP #5562774
Lesenswert?

> Mal ein Vorschlag.
Das wäre dann sowas in der Art?
1
class X{
2
private:
3
registerstruct* timer_reg;
4
};
5

6
class Timer{
7
public:
8
virtual void startTimer(void);
9
};
10

11
class CaptureCompare:public Timer{
12
private:
13
X* manager;
14

15
public:
16
void startTimer(void){
17
manager->timer_reg->register_A|=startbit;
18
};
19
}
OP #5562777
Lesenswert?

Vka schrieb:
> Das ist IMHO eine blöde Architektur. Dann brauchst du irgend eine
> Abstraktion vom eigentlichen Timer, damit sich nicht alles gegenseitig
> verstellt.

Das mag sein, mir ist leider noch keine bessere Idee gekommen. Ich weiß 
aber, dass ich am Ende ganz gerne die einzelnen CaptureCompare Channel 
als separate Objekte übergeben möchte.
#5562781
Lesenswert?

Ja. Ich habs mal leicht geändert so wie ich es schreiben würde.
1
class X{
2
private:
3
registerstruct* timer_reg;
4
};
5

6
class Timer{
7
public:
8
virtual void startTimer() = 0;
9
};
10

11
class CaptureCompare:public Timer{
12
private:
13
X& manager;
14

15
public:
16
void startTimer() override {
17
manager.timer_reg->register_A|=startbit;
18
};
19
}

void als Funktionsparameter ist in C++ nicht nötig (in C schon, weil 
sonst beliebige Parameter möglich wären).
Die Funktion im Interface ist pure virtual und das override im 
CaptureCompare gibt einen Fehler, wenn man damit irgendwann nichts mehr 
aus der Basis überschreibt. Das ist vorallem dann wichtig, wenn die 
überschrieben Funktion nicht pure virtual wäre.
Manager als Referenz, dann bekommt man einen Fehler, wenn man es nicht 
initialisiert. Allerdings kann man gegenüber einem Pointer die Referenz 
nachher nicht mehr tauschen (aber in diesem Fall ändert sich die HW ja 
auch nicht zur Leufzeit).
Gast #5562803
Lesenswert?

A. Z. schrieb:
> Vka schrieb:
> Das ist IMHO eine blöde Architektur. Dann brauchst du irgend eine
> Abstraktion vom eigentlichen Timer, damit sich nicht alles gegenseitig
> verstellt.
> Das mag sein, mir ist leider noch keine bessere Idee gekommen. Ich weiß
> aber, dass ich am Ende ganz gerne die einzelnen CaptureCompare Channel
> als separate Objekte übergeben möchte.
Ware die natuerliche Struktur nicht zwei Klassen, eine fuer den Timer 
mit seinen Einstellungen und eine fuer die Captures. Instanzen der 
Captureklasse sind dann Eigenschaften der Timerklasse. So koenntest du 
auch an einen anderen Timer die gleichen 
CaptureCompare-Channel-Einstellungen uebergeben. Also keine Ableitung, 
sondern Hirarchie.
Gast #5562923
Lesenswert?

Ein aus Mcucpp abgeleitetes Konzept für Timer mit hardware spezifischen 
Register-Definitionen in Timer.h und den Timer-Funktionen in 
BaseTimerX.h.

Die OutputCompare Kanäle sind eine Eigenschaft des jeweiligen Timers und 
sollten meiner Meinung nach in der Klasse des Timers enthalten sein.

Expl:  in Timer.h wird Timer0 als
typedef BaseTimerD<Timer0Regs>Timer0; definiert.

als Dateianhang: Timer.h für ATmega-164-324-644-1284 mit Timer0, Timer2 
und Timer1, Timer3
als Dateianhang: BaseTimerD und BaseTimerH mit den Timer-Funktionen.
Angehängte Dateien:
#5563007
Lesenswert?

A. Z. schrieb:
> Am Ende müsste es dann also möglich sein über jede Instanz der
> CaptureCompare Klasse zum Beispiel den Prescaler zu verstellen. Das
> hätte natürlich auswirkungen auf die jeweiligen anderen Instanzen.

Schon alleine deshalb würde ich das nicht machen.

> vlt ist so besser:
>
>> Diese möchte ich gerne als separate
>> Objekte ansprechen und trotzdem sollen sie sich bezüglich der
>> Grundfunktionen wie ein Timer (eine Instanz der Klasse Timer) verhalten.

Ableitung drückt eine ist-ein-Beziehung aus. Also musst du dir die Frage 
stellen: Ist eine Capture-Compare-Unit ein eigener Timer? Wenn nicht, 
sollte sie auch nicht davon abgeleitet sein.
Das Problem ist, dass du im ersten Schritt nicht darüber nachdenkst, wie 
die Struktur deiner Ressourcen aussieht, sondern wie du auf die Elemente 
nachher gerne zugreifen würdest.

Maier schrieb:
> Ware die natuerliche Struktur nicht zwei Klassen, eine fuer den Timer
> mit seinen Einstellungen und eine fuer die Captures.

Ja.

> Instanzen der Captureklasse sind dann Eigenschaften der Timerklasse. So
> koenntest du auch an einen anderen Timer die gleichen
> CaptureCompare-Channel-Einstellungen uebergeben. Also keine Ableitung,
> sondern Hirarchie.

Aggregation nennt sich das. Die Wikipedia-Seite für Vererbung 
https://de.wikipedia.org/wiki/Vererbung_(Programmierung) fasst es in 
einem Satz gut zusammen:
"Eine weitere fragwürdige Verwendung ist, wenn die erbende Klasse nicht 
in einer „ist-ein“-, sondern in einer „hat“-Beziehung zur Basisklasse 
steht, und eine Aggregation angebracht wäre."

Und ein Timer hat Capture-Compare-Einheiten.
Gast #5563019
Lesenswert?

Rolf M. schrieb:
> Und ein Timer hat Capture-Compare-Einheiten.

Da stimme ich euch zu. Das klingt alles sehr plausibel.
Und ich glaube ich habe das sogar schonmal so versucht.

Aber glaub mich, ich habe mir schon jede Menge Gedanken gemacht, daher 
auch der Knoten im Kopf ;).

Vielen Dank für eure Ratschläge.
#5563041
Lesenswert?

Ich bin zwar normalerweise ein Freund des "single responsibility 
principle", aber in dem Fall lohnt sich der zusätzliche 
Verwaltungsaufwand in meinen Augen nicht.

Das größte Problem bei 2x getrennten Klassen wäre vermutlich die 
Interrupts vernünftig unter einen Hut zu bringen. Selbst bei den größten 
Cortex M4/7 Prozessoren hat nur ein kleiner Teil der Timer mehr als 
einen Interrupt Vektor. Die überwiegende Mehrheit teilt sich 
Update/CaptureCompare/etc. Interrupts auf einem einzigen Vektor.

Wo landet also die Funktionalität des Interrupts? In der Timer Klasse? 
Oder im CaptureCompare? Und was wenn du aus versehen einen Update 
Interrupt bekommst, aber im CaptureCompare tatsächlich nur 
CaptureCompare Interrupts behandelst... Dann ist dein Prozessor defakto 
tot.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren