Forum: Compiler & IDEs Instanzvariable im Interrupt


von Christoph M. (fakulatus)


Lesenswert?

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
class Timer
2
{
3
4
public:
5
  Timer(void);
6
  virtual ~Timer(void);
7
8
  const int get_msec(void);
9
  const void start(void);
10
  const void stop(void);
11
12
protected:
13
14
private:
15
16
  unsigned long ms;
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
long int globalems=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
13
    TIFR2 |= 1<<TOV0;          //Clear TOV0 / clear
14
    TIMSK2 |= 1<<TOIE0;   //Enable Timer0 Overflow Interrupt
15
    TCNT0 = 6;  // Interval 1 ms
16
17
    sei();  // Enable all interrupt 
18
  }
19
}
20
21
const void Timer::timer_start(void)
22
{
23
  ms=globalems;
24
}
25
26
const int Timer::get_msec(void)
27
{
28
  return globalems-ms;
29
}

Sehr simpel und sehr unschön.

Hat jemand eine bessere Lösung dazu, wie ich eine Instantvariable im 
Interrupt verwenden kann?

von P. S. (Gast)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

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

von Christoph M. (fakulatus)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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
class Timer;
2
3
class TimerClient
4
{
5
  public:
6
    void RegisterWith( Timer* pTimer );
7
8
    virtual void OnTick();
9
};  
10
11
class Timer
12
{
13
  public:
14
    void Register( TimerClient* pClient );
15
    bool IsRegistered( TimerClient* pClient );
16
17
    void OnInterrupt();
18
19
  private:
20
    std::vector< TimerClient* > clients_;
21
};
22
23
void TimerClient::RegisterWith( Timer* pTimer )
24
{
25
  pTimer->Register( this );
26
}
27
28
void Timer::Register( TimerClient* pClient )
29
{
30
  if( ! IsRegistered( pClient ) )
31
    clients_.push_back( pClient );
32
}
33
34
void Timer::OnInterrupt()
35
{
36
  for( size_t i = 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
class MyTimerClient : TimerClient
2
{
3
  public:
4
    MyTimerClient( Timer* theTimer )
5
    {
6
      RegisterWith( theTimer );
7
    }
8
9
    virtual void OnTick()
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
Timer Timer1;
2
3
SIGNAL( .... )    // Du solltest hier wirklich ISR benutzen
4
{
5
  Timer1.OnInterrupt();
6
}
7
8
9
int main()
10
{
11
  MyTimerClient a;
12
  MyTimerClient b;
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.

von Stefan E. (sternst)


Lesenswert?

Karl heinz Buchegger wrote:
1
Timer Timer1;
2
3
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.

von Karl H. (kbuchegg)


Lesenswert?

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?

von Stefan E. (sternst)


Lesenswert?

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?

von Karl H. (kbuchegg)


Lesenswert?

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.

von Christoph M. (fakulatus)


Lesenswert?

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

von Christoph M. (fakulatus)


Lesenswert?

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

von Peter D. (peda)


Lesenswert?

Wichtig!

Nicht die typischen Fallgruben mit Interrupts vergessen, wie volatile, 
atomicity, race condition.


Peter

von P. S. (Gast)


Lesenswert?

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.

von Stefan E. (sternst)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

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
class Timer
2
{
3
  ...
4
};
5
6
inline void Timer::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
MyTimerClient a;
4
MyTimerClient b;
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
int main()
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)
1
class TimerClient
2
{
3
  public:
4
    void OnTick() = 0;
5
};

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.