Forum: Mikrocontroller und Digitale Elektronik Knoten im Kopf, Klassen und Vererbung


von A. Z. (donvido)


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?

von Timm R. (Firma: privatfrickler.de) (treinisch)


Lesenswert?

A. Z. schrieb:
> 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.

Das "trotzdem" verstehe ich nicht.

Viele liebe Grüße
timm

von A. Z. (donvido)


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.

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


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.

von Vka (Gast)


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...

von A. Z. (donvido)


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
}

: Bearbeitet durch User
von A. Z. (donvido)


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.

von M.K. B. (mkbit)


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).

von A. Z. (donvido)


Lesenswert?

M.K. B. schrieb:
> Ja. Ich habs mal leicht geändert so wie ich es schreiben würde.

Vielen Dank für die Mühe, ich gucke morgen mal, ob ich das gescheit 
umgesetzt bekomme.

von Maier (Gast)


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.

von MitLeserin (Gast)


Angehängte Dateien:

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.

von Rolf M. (rmagnus)


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.

: Bearbeitet durch User
von donvido (Gast)


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.

von Vincent H. (vinci)


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.

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.