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
classTimer{};
2
classCaptureCompare:publicTimer{}
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
classTimer{};
2
3
classCaptureCompare{
4
private:
5
Timer*timer;
6
public:
7
voidstartTimer(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?
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
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.
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.
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...
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.
Ja. Ich habs mal leicht geändert so wie ich es schreiben würde.
1
classX{
2
private:
3
registerstruct*timer_reg;
4
};
5
6
classTimer{
7
public:
8
virtualvoidstartTimer()=0;
9
};
10
11
classCaptureCompare:publicTimer{
12
private:
13
X&manager;
14
15
public:
16
voidstartTimer()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).
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.
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.
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.
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.
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.
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.