www.mikrocontroller.net

Forum: Compiler & IDEs Instanzvariable im Interrupt


Autor: Christoph Mauchle (fakulatus)
Datum:

Bewertung
0 lesenswert
nicht 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)
class Timer
{

public:
  Timer(void);
  virtual ~Timer(void);

  const int get_msec(void);
  const void start(void);
  const void stop(void);

protected:

private:

  unsigned long ms;

};

Um mehrere Objekte vom Typ Timer erzeugen zu können, hätte ich gerne die 
Variable ms im Interrupt:
SIGNAL (SIG_OVERFLOW2) // Interval 1 ms
{
  ms++;
}

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.

long int globalems=0;

SIGNAL (SIG_OVERFLOW2) // Interval 1 ms
{
  globalems++;
}

Timer::Timer(void)
{
  if(globalems>0)
  {
    TCCR2B = (1<<CS01)|(1<<CS00);  // Prescaler 64,16 MHz,1 MC = 64/16M = 4us/count
    TIFR2 |= 1<<TOV0;          //Clear TOV0 / clear
    TIMSK2 |= 1<<TOIE0;   //Enable Timer0 Overflow Interrupt
    TCNT0 = 6;  // Interval 1 ms

    sei();  // Enable all interrupt 
  }
}

const void Timer::timer_start(void)
{
  ms=globalems;
}

const int Timer::get_msec(void)
{
  return globalems-ms;
}

Sehr simpel und sehr unschön.

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

Autor: P. S. (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Christoph Mauchle (fakulatus)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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)

class Timer;

class TimerClient
{
  public:
    void RegisterWith( Timer* pTimer );

    virtual void OnTick();
};  

class Timer
{
  public:
    void Register( TimerClient* pClient );
    bool IsRegistered( TimerClient* pClient );

    void OnInterrupt();

  private:
    std::vector< TimerClient* > clients_;
};

void TimerClient::RegisterWith( Timer* pTimer )
{
  pTimer->Register( this );
}

void Timer::Register( TimerClient* pClient )
{
  if( ! IsRegistered( pClient ) )
    clients_.push_back( pClient );
}

void Timer::OnInterrupt()
{
  for( size_t i = 0; i < clients_.count(); ++i )
    clients_[i]->OnTick();
}

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.
class MyTimerClient : TimerClient
{
  public:
    MyTimerClient( Timer* theTimer )
    {
      RegisterWith( theTimer );
    }

    virtual void OnTick()
    {
      std::cout << "OnTick for MyTimerClient called\n";
    }
};

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.

Timer Timer1;

SIGNAL( .... )    // Du solltest hier wirklich ISR benutzen
{
  Timer1.OnInterrupt();
}


int main()
{
  MyTimerClient a;
  MyTimerClient b;

  a.RegisterWith( Timer1 );
  b.RegisterWith( Timer1 );

  // Timer starten, Interrupt freigeben, etc

  // ab sofort wird sowohl a.OnTick() als auch b.OnTick() vom
  // Timer Objekt aufgerufen

   ...
}

und klar: auf einem µC wirst du wahrscheinlich nicht std::vector 
benutzen sondern ev. ein fix dimensioniertes Feld, etc.

Autor: Stefan Ernst (sternst)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Karl heinz Buchegger wrote:
Timer Timer1;

SIGNAL( .... )    // Du solltest hier wirklich ISR benutzen
{
  Timer1.OnInterrupt();
}

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.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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?

Autor: Stefan Ernst (sternst)
Datum:

Bewertung
0 lesenswert
nicht 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?

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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?
extern "C" SIGNAL( .... )
{
  ...
}

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.

Autor: Christoph Mauchle (fakulatus)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Christoph Mauchle (fakulatus)
Datum:

Bewertung
0 lesenswert
nicht 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++)

Autor: Peter Dannegger (peda)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Wichtig!

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


Peter

Autor: P. S. (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Stefan Ernst (sternst)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Karl heinz Buchegger wrote:

> Ähm. Indem man das Name Mangling für diese Funktion abschaltet?
extern "C" SIGNAL( .... )
{
  ...
}

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.

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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
class Timer
{
  ...
};

inline void Timer::OnInterrupt()
{
#ifdef CLIENT1
  CLIENT1.OnTick();
#endif

#ifdef CLIENT2
  CLIENT2.OnTick();
#endif

#ifdef CLIENT3
  CLIENT3.OnTick();
#endif
}

und dann
#include "TimerClient.h"

MyTimerClient a;
MyTimerClient b;

#define CLIENT1 a
#define CLIENT2 b

// fertig, a und b sind bereits durch die #define da oben 'registriert'
// keine weiteren Aktionen mehr notwendig

#include "Timer.h"

...

int main()
{
}

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)
class TimerClient
{
  public:
    void OnTick() = 0;
};

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.