Ich bin mir bewusst, dass viele hier nicht unbedingt ein Fan von C++ bei
Kontrollern sind, dennoch hoffe ich, dass jemand hier schon Erfahrungen
damit hat.
Ich habe eine kleine Klasse Timer (Atmega168)
1
classTimer
2
{
3
4
public:
5
Timer(void);
6
virtual~Timer(void);
7
8
constintget_msec(void);
9
constvoidstart(void);
10
constvoidstop(void);
11
12
protected:
13
14
private:
15
16
unsignedlongms;
17
18
};
Um mehrere Objekte vom Typ Timer erzeugen zu können, hätte ich gerne die
Variable ms im Interrupt:
1
SIGNAL(SIG_OVERFLOW2)// Interval 1 ms
2
{
3
ms++;
4
}
Nun, leider ist der Interrupt nicht innerhalb der Klasse, was
logischerweise dazu führt, dass die Variable ms nicht definiert ist ->
Sehr dumm, denn eine in timer.cpp definierte Variable würde für alle
Objekte gelten.
Um das Problem zu umgehen, starte ich beim erzeugen des ersten Objekts
den Timer und zähle die ms von dort an immer hinauf. Beim Starten
speichere ich mir den Zählerstand, bei der Rückgabe subtrahiere ich den
aktuellen vom gespeicherten wert.
1
longintglobalems=0;
2
3
SIGNAL(SIG_OVERFLOW2)// Interval 1 ms
4
{
5
globalems++;
6
}
7
8
Timer::Timer(void)
9
{
10
if(globalems>0)
11
{
12
TCCR2B=(1<<CS01)|(1<<CS00);// Prescaler 64,16 MHz,1 MC = 64/16M = 4us/count
Globale, einmalige Resourcen werden IMO am besten durch globale,
einmalige Objekte repraesentiert. Da du hier mit dem Interrupt und
globalms eine Uhr abbildest, wuerde ich eben eine Klasse Clock
implementieren, die beides abdeckt und zentral (also z.B. in main())
einmal instanziiert und zur Verfuegung gestellt wird. Zur Verfuegung
stellen entweder ueber eine globale Variable oder, je nach Geschmack,
ueber eine statische Methode in Clock. Von den Timern aus wird dann eben
auf diese Clock zugegriffen und von dort die aktuelle Zeit geholt.
Laeuft am Ende natuerlich irgendwie auf's Gleiche raus, ist aber
sauberer aufgeteilt und universeller.
Christoph Mauchle wrote:
> Um mehrere Objekte vom Typ Timer erzeugen zu können, hätte ich gerne die> Variable ms im Interrupt:
Und da hast du schon deinen Denkfehler.
Auf deinem µC gibt es nun mal nur diesen einen Timer und daher solltest
du auch nur 1 Timer Objekt haben.
Aber: Es kann natürlich mehrere 'Timer-Clients' geben, die sich beim
Timer-Objekt registrieren, dass sie benachrichtigt werden wollen, wenn
eine Timertick vorliegt.
Soweit die reine OO-Lehre
Karl heinz Buchegger wrote:
> Aber: Es kann natürlich mehrere 'Timer-Clients' geben, die sich beim> Timer-Objekt registrieren, dass sie benachrichtigt werden wollen, wenn> eine Timertick vorliegt.
jop, und wie geht das? :)
@Peter, das mit der Clock ist eine gute Idee, schlussendlich jedoch das
selbe wie schon implementiert, nur mit ein wenig anderm Namen. Falls es
für mein Problem keine bessere Lösung gibt, werd ich eine Clock-Klasse
erstellen
Christoph Mauchle wrote:
> Karl heinz Buchegger wrote:>> Aber: Es kann natürlich mehrere 'Timer-Clients' geben, die sich beim>> Timer-Objekt registrieren, dass sie benachrichtigt werden wollen, wenn>> eine Timertick vorliegt.>> jop, und wie geht das? :)
Du hast es so gewollt :-)
(Ich skizziere hier nur, Code also als Anregung zu verstehen)
1
classTimer;
2
3
classTimerClient
4
{
5
public:
6
voidRegisterWith(Timer*pTimer);
7
8
virtualvoidOnTick();
9
};
10
11
classTimer
12
{
13
public:
14
voidRegister(TimerClient*pClient);
15
boolIsRegistered(TimerClient*pClient);
16
17
voidOnInterrupt();
18
19
private:
20
std::vector<TimerClient*>clients_;
21
};
22
23
voidTimerClient::RegisterWith(Timer*pTimer)
24
{
25
pTimer->Register(this);
26
}
27
28
voidTimer::Register(TimerClient*pClient)
29
{
30
if(!IsRegistered(pClient))
31
clients_.push_back(pClient);
32
}
33
34
voidTimer::OnInterrupt()
35
{
36
for(size_ti=0;i<clients_.count();++i)
37
clients_[i]->OnTick();
38
}
Und von TimerClient leitest du jetzt deine tatsächliche Klasse her,
lässt den Client sich beim einzigen Timer registrieren und überschreibst
die virtuelle OnTick.
1
classMyTimerClient:TimerClient
2
{
3
public:
4
MyTimerClient(Timer*theTimer)
5
{
6
RegisterWith(theTimer);
7
}
8
9
virtualvoidOnTick()
10
{
11
std::cout<<"OnTick for MyTimerClient called\n";
12
}
13
};
Dazu dann noch das globale Timer Objekt (ich verzichte jetzt auf ein
Singleton), das in der ISR durch Aufruf der OnInterrupt Funktion über
den Interrupt benachrichtigt wird.
1
TimerTimer1;
2
3
SIGNAL(....)// Du solltest hier wirklich ISR benutzen
4
{
5
Timer1.OnInterrupt();
6
}
7
8
9
intmain()
10
{
11
MyTimerClienta;
12
MyTimerClientb;
13
14
a.RegisterWith(Timer1);
15
b.RegisterWith(Timer1);
16
17
// Timer starten, Interrupt freigeben, etc
18
19
// ab sofort wird sowohl a.OnTick() als auch b.OnTick() vom
20
// Timer Objekt aufgerufen
21
22
...
23
}
und klar: auf einem µC wirst du wahrscheinlich nicht std::vector
benutzen sondern ev. ein fix dimensioniertes Feld, etc.
SIGNAL(....)// Du solltest hier wirklich ISR benutzen
4
{
5
Timer1.OnInterrupt();
6
}
Ich glaube nicht, dass das so funktionieren wird.
Das Name-Mangling wird dafür sorgen, dass die ISR von der Toolchain
nicht mehr als solche erkannt wird. Meiner Meinung nach muss die ISR in
einer C-Datei stecken.
Stefan Ernst wrote:
> Ich glaube nicht, dass das so funktionieren wird.> Das Name-Mangling wird dafür sorgen, dass die ISR von der Toolchain> nicht mehr als solche erkannt wird.
Sagte ich schon, dass ich den Code nur kurz hier im Einagbeeditor
skizziert habe?
Karl heinz Buchegger wrote:
> Stefan Ernst wrote:>>> Ich glaube nicht, dass das so funktionieren wird.>> Das Name-Mangling wird dafür sorgen, dass die ISR von der Toolchain>> nicht mehr als solche erkannt wird.>> Sagte ich schon, dass ich den Code nur kurz hier im Einagbeeditor> skizziert habe?
Ja, aber genau das ist doch der entscheidende Knackpunkt.
Wie bekommt man den Event vom Interrupt in den C++-Code?
Stefan Ernst wrote:
> Karl heinz Buchegger wrote:>> Stefan Ernst wrote:>>>>> Ich glaube nicht, dass das so funktionieren wird.>>> Das Name-Mangling wird dafür sorgen, dass die ISR von der Toolchain>>> nicht mehr als solche erkannt wird.>>>> Sagte ich schon, dass ich den Code nur kurz hier im Einagbeeditor>> skizziert habe?>> Ja, aber genau das ist doch der entscheidende Knackpunkt.> Wie bekommt man den Event vom Interrupt in den C++-Code?
Ähm. Indem man das Name Mangling für diese Funktion abschaltet?
1
extern"C"SIGNAL(....)
2
{
3
...
4
}
Ausserdem geh ich schon davon aus, das ein C++ Compiler entsprechende
Makros für Interrupt Funktionen zur Verfügung stellt, die dann auch
richtig funktionieren.
Heho,
hab dank!
Durch das, dass zwei Klassen vorkommen, sieht diese Holzfällermethode
sogar noch einigermassen sinnvoll aus. Das mit dem Registrieren könnte
man ggf noch ein wenig einfacher gestalten. Halte das ganze für
lauffähig.
Ich werde in dem Fall in dieser Richtung einmal weiterforschen, obwohl
ich eigentlich gehoft habe, es gibt eine "inteligentere" Lösung. Bei
jedem Interrupt eine for-Schlaufe für jedes Objekt aus zu führen find
ich nicht so toll.
Danke euch beiden
Stefan Ernst wrote:
> Ja, aber genau das ist doch der entscheidende Knackpunkt.> Wie bekommt man den Event vom Interrupt in den C++-Code?
Der Compiler hat keine Probleme mit Interrupts im cpp-Code, der
interpretiert das einwandfrei (AVR-G++)
Christoph Mauchle wrote:
> Ich werde in dem Fall in dieser Richtung einmal weiterforschen, obwohl> ich eigentlich gehoft habe, es gibt eine "inteligentere" Lösung. Bei> jedem Interrupt eine for-Schlaufe für jedes Objekt aus zu führen find> ich nicht so toll.
Wenn's effizient seni soll, wuerde ich tatsaechlich bei der Methode
bleiben, Im Timer() die Startzeit zu notieren und dann bei Bedarf die
vergangene Zeit zu berechnen. Voellig unabnhaengig davon, ob das auf
einem Mikrokontroller laeuft, oder auf einem Enterprise-Server ;-)
Erst, wenn ich z.B. den Interrupt allgemeiner zwischen mehreren Objekten
sharen will, wuerde ich auf eine Register()/Unregister()-Schematik
wechseln.
Karl heinz Buchegger wrote:
> Ähm. Indem man das Name Mangling für diese Funktion abschaltet?
1
extern"C"SIGNAL(....)
2
{
3
...
4
}
Mir war nicht klar, dass in extern "C" deklarierten Funktionen C++-Code
stehen darf. Ich muss meine C++-Kenntnisse wohl doch mal wieder etwas
auffrischen.
Christoph Mauchle wrote:
> hab dank!> Durch das, dass zwei Klassen vorkommen, sieht diese Holzfällermethode> sogar noch einigermassen sinnvoll aus. Das mit dem Registrieren könnte> man ggf noch ein wenig einfacher gestalten.
Kommt immer drauf an, wie universell man das Ganze braucht.
> ich eigentlich gehoft habe, es gibt eine "inteligentere" Lösung. Bei> jedem Interrupt eine for-Schlaufe für jedes Objekt aus zu führen find> ich nicht so toll.
Ich könnte mir da noch eine Lösung mit einer fixen Obergrenze und
Einsatz des Präproessors vorstellen :-)
So ungefähr für max. 3 Clients
1
classTimer
2
{
3
...
4
};
5
6
inlinevoidTimer::OnInterrupt()
7
{
8
#ifdef CLIENT1
9
CLIENT1.OnTick();
10
#endif
11
12
#ifdef CLIENT2
13
CLIENT2.OnTick();
14
#endif
15
16
#ifdef CLIENT3
17
CLIENT3.OnTick();
18
#endif
19
}
und dann
1
#include"TimerClient.h"
2
3
MyTimerClienta;
4
MyTimerClientb;
5
6
#define CLIENT1 a
7
#define CLIENT2 b
8
9
// fertig, a und b sind bereits durch die #define da oben 'registriert'
10
// keine weiteren Aktionen mehr notwendig
11
12
#include"Timer.h"
13
14
...
15
16
intmain()
17
{
18
}
Wenn der Compiler dann die OnTick() Aufrufe auch noch inlined ...
OnTick braucht dann auch nicht mehr virtual sein. Die Basisklasse könnte
dann so aussehen (so man überhaupt eine machen will)