Forum: Compiler & IDEs Anfängerfrage zu ISR und dem Timertutorial (GCC)


von Paul (Gast)


Lesenswert?

Hallo, habe mir kürzlich ein STK500 besorgt und spiele jetzt ein 
bisschen damit herum. Ich benutze einen ATMega8, der vom internen RC auf 
8MHz getaktet wird. Jetzt wollte ich erstmal zum Üben eine Uhr 
programmieren. Dazu benutze ich den Timer2 des Mega8 im CTC-Modus. 
Analog zum Tutorial habe ich erstmal folgenden Code (angepasst auf Mega8 
und 8MHz):
#include <avr/io.h>
#include <avr/interrupt.h>

volatile unsigned int  millisekunden=0;
volatile unsigned int  sekunde=0;
volatile unsigned int  minute=0;
volatile unsigned int  stunde=0;
int main(void)
{
  TCCR2 |= (1<<2) | (1<<3);  //CTC-Modus, Prescaler=64 -> Takt=125000
  OCR2 |= (1<<0) | (1<<2) | (1<<3) | (1<<4) | (1<<5) | 
(1<<6);//=125->1ms
  TIMSK |= (1<<7); // Compare Match Interrupt aktiviert
  sei();
  {
  //Hier kann z.B. eine Ausgabe stattfinden
  }
}
ISR (TIMER2_COMP_vect)
{
   millisekunden++;
   if(millisekunden==1000)
   {
      sekunde++;
      millisekunden=0;
      if(sekunde==60)
      {
         minute++;
         sekunde=0;
      }
      if(minute ==60)
      {
        stunde++;
        minute=0;
      }
   }
}
Das funktioniert auch gut, aber jetzt meine Frage: Sollte man nicht 
besser die Berechnung von Sekunden, Minuten und Stunden aus der ISR 
auslagern? Also vielleicht so:
#include <avr/io.h>
#include <avr/interrupt.h>

volatile unsigned int  millisekunden=0;
unsigned int  sekunde=0;
unsigned int  minute=0;
unsigned int  stunde=0;
int main(void)
{
  TCCR2 |= (1<<2) | (1<<3);  //CTC-Modus, Prescaler=64 -> Takt=125000
  OCR2 |= (1<<0) | (1<<2) | (1<<3) | (1<<4) | (1<<5) | (1<<6);//=125 -> 
1ms
  TIMSK |= (1<<7); // Compare Match Interrupt aktiviert
  sei();
  {
   if(millisekunden==1000)
   {
      sekunde++;
      millisekunden=0;
      if(sekunde==60)
      {
         minute++;
         sekunde=0;
      }
      if(minute ==60)
      {
        stunde++;
        minute=0;
      }
   }
   //Hier kann z.B. eine Ausgabe stattfinden
}
}
ISR (TIMER2_COMP_vect)
{
   millisekunden++;
}
Ich habe immer gedacht, dass eine ISR so kurz wie möglich sein sollte. 
Danke im Vorraus.
Gruß
Paul

: Verschoben durch Moderator
von Tom M. (tomm) Benutzerseite


Lesenswert?

Paul schrieb:

> Das funktioniert auch gut, aber jetzt meine Frage: Sollte man nicht
> besser die Berechnung von Sekunden, Minuten und Stunden aus der ISR
> auslagern?

Jein. Die wenigen Vergleichsoperationen erzeugen nur wenig Code und sind 
sehr rasch abgearbeitet.

In deinem Fall hast du sogar das Problem von race conditions. Was ist, 
wenn du in main() den Moment verpasst, in welchem die Variable 
"millisekunden" genau den Wert 1000 hat?


> Ich habe immer gedacht, dass eine ISR so kurz wie möglich sein sollte.

Gute Faustregel.


>  TCCR2 |= (1<<2) | (1<<3);  //CTC-Modus, Prescaler=64 -> Takt=125000
>  OCR2 |= (1<<0) | (1<<2) | (1<<3) | (1<<4) | (1<<5) | (1<<6);//=125 -> 1ms
>  TIMSK |= (1<<7); // Compare Match Interrupt aktiviert

Verwende unbedingt die vordefinierten Symbole, wenn du Bits/Flags setzt! 
Das macht den Code besser lesbar und portabler. Oder weisst du in 10 
Tagen noch, welche Funktion Bit 5 des OCR2 auf deinem MP Variante XY 
hat? ;)


> Danke im Vorraus.

Ist das eigentlich die neue Rechtschreibung, "vorraus"? Lese ich immer 
wieder mal...

Viel Spass mit deinem neuen Hobby. :)

von Falk B. (falk)


Lesenswert?

@  Paul (Gast)

>  OCR2 |= (1<<0) | (1<<2) | (1<<3) | (1<<4) | (1<<5) |
>(1<<6);//=125->1ms

Was soll das denn? Solche Zahlen schreibt man direkt hin. Die (1<<n) 
Schreibweise nimmt man nur für Steuerregister, in denn die einzelnen 
Bits einzelne Funktionen haben.

>  TIMSK |= (1<<7); // Compare Match Interrupt aktiviert

Ausserdem nimmt man die symbolischen Bitnamen, das erleichtet das Lesen 
deutlich. Also eher (1 << OCIE2).

>Das funktioniert auch gut, aber jetzt meine Frage: Sollte man nicht
>besser die Berechnung von Sekunden, Minuten und Stunden aus der ISR
>auslagern? Also vielleicht so:

Kann man machen, ist hier aber nicht nötig. Die paar Vergleiche und 
Rechenoperationen sind OK.

>Ich habe immer gedacht, dass eine ISR so kurz wie möglich sein sollte.

Ja, man muss es aber nicht übertreiben.

MfG
Falk

von Paul (Gast)


Lesenswert?

Danke Tom für die schnelle Hilfe,
das Stichwort "race conditions" war was mir gefehlt hat. Ich glaube ich 
habs jetzt kapiert.
Gruß
Paul

von Stefan B. (Gast)


Lesenswert?

Grundsätzlich hast du Recht mit den möglichst kurzen ISRs. Im Detail hat 
deine 2. Lösung mehrere Haken, die man berücksichtigen muss:

1. Du musst in main() noch für atomaren Zugriff auf millisekunden 
sorgen. siehe Artikel Interrupt

2. Das Abprüfen auf exakt 1000 kann schief gehen. Du kannst nicht davon 
ausgehen, dass du pro Schleifendurchlauf in while immer eine Erhöhung 
von millisekunden um max. 1 hast.

3. Was machst du mit den millisekunden, die vielleicht von der ISR 
zwischen der Abfrage (if(millisekunden==1000)) und dem Reset 
(millisekunden=0) schon gezählt wurden?

4. Die erste Lösung ist praktischer, wenn im Restprogramm stunde, 
minute, sekunde ebenfalls benötigt werden. Die Routinen dort können dann 
direkt darauf zugreifen und brauchen nicht auf eine Bereitstellung durch 
main() zu hoffen.

von Paul (Gast)


Lesenswert?

Falk Brunner schrieb:
> Was soll das denn? Solche Zahlen schreibt man direkt hin. Die (1<<n)
> Schreibweise nimmt man nur für Steuerregister, in denn die einzelnen
> Bits einzelne Funktionen haben.

Hallo Falk,
also einfach den hexadezimalen bzw. binären Wert da rein schreiben? Ok 
werd ich in Zukunft so machen.

Falk Brunner schrieb:
> Ausserdem nimmt man die symbolischen Bitnamen, das erleichtet das Lesen
> deutlich. Also eher (1 << OCIE2).

Das meinte Dein Vorredner auch schon, auch das werde ich in Zukunft 
beherzigen.

Danke
Paul

von Paul (Gast)


Lesenswert?

Hallo Stefan,
auch dir vielen Dank. Jetzt ist die Sache schon klarer.
Gruß
Paul

von Stefan B. (Gast)


Lesenswert?

Bei der Berechnung des Wertes für OCR2 solltest du noch mal im aktuellen 
Tutorial nachsehen (AVR-GCC-Tutorial/Die Timer und Zähler des AVR). 
Dort ist letztens ein Bugfix reingekommen. Um 125 Zählerschritte 
abzupassen, muss man den OCR2 Wert auf 125-1 setzen.

von Oliver (Gast)


Lesenswert?

Wenn es um eine Uhr geht, die im Sekundentakt hochzählt, würde man, um 
die ISR und die CPU-Belastung möglichst kurz zu halten, etwas anders 
vorgehen. Voraussetzung ist, daß die genaue Millisekunde nicht benötigt 
wird.

In der ISR läuft ein Zähler hoch, und nach Ablauf einer Sekunde wird ein 
Flag gesetzt, welches dann im Hauptprogramm die Uhr eine Sekunde 
weiterzählen lässt (Stunde, Minute und Sekunde). Die ISR muß dabei gar 
nicht im Millisekundentakt laufen, der langsamste Takt, der restfrei 
eine Sekunde ergeben kann, reicht völlig aus.

Falls an mehreren Stellen im Hauptprogramm eine (fast) 
millisekundengenaue Uhrzeit benötigt wird, muß die Uhrzeit vor direkt 
diesen Stellen halt aktualisiert werden.

Oliver

von Paul (Gast)


Lesenswert?

Stefan B. schrieb:
> Dort ist letztens ein Bugfix reingekommen. Um 125 Zählerschritte
> abzupassen, muss man den OCR2 Wert auf 125-1 setzen.

Hallo Stefan,
gilt der Bugfix für alle Atmel 8-bit MCs oder nur bei Verwendung eines 
bestimmten Prescalers oder generell für AVR-GCC oder ... ???
Das hat mich schon beim lesen etwas verwirrt, daher hab ich das mal 
einfach ignoriert.
Gruß
Paul

von Paul (Gast)


Lesenswert?

Hallo Oliver,
hier wird man ja mit Antworten geradezu überschüttet! Auch deinen 
Beitrag finde ich sehr hilfreich! Für meine Anwendung (Fahrradcomputer) 
sollte eine 1/100 Sekunde locker ausreichen. Allerdings weiß ich noch 
nicht welche Frequenz mein Nabendynamo so erzeugt. Ich stecke eben noch 
in den Anfängen. Und ja, ich weiß, dass es so ein Projekt schon gibt. 
Ich will eben etwas lernen, und nicht einfach fremden Code übernehmen.
Dank und Gruß
Paul

von Michael U. (amiga)


Lesenswert?

Hallo,

schau ins Datenblatt des AVR nach der Formel. Der Fehler war im 
Tutorial.

Zu den Zeiten: bei läuft oft ein 10ms Interrupt, der dann auch 
Tastenentprellung/DCF77-Dekodierung/Timeouts o.ä. übernimmt, irgendwas 
wird da ohnehin immer gebraucht.

Da läuft dann auch gleich die Uhr mit, zählt eben bis 100 für eine 
Sekunde.

Gruß aus Berlin
Michael

von Karl H. (kbuchegg)


Lesenswert?

Paul schrieb:
> Stefan B. schrieb:
>> Dort ist letztens ein Bugfix reingekommen. Um 125 Zählerschritte
>> abzupassen, muss man den OCR2 Wert auf 125-1 setzen.
>
> Hallo Stefan,
> gilt der Bugfix für alle Atmel 8-bit MCs oder nur bei Verwendung eines
> bestimmten Prescalers oder generell für AVR-GCC oder ... ???
> Das hat mich schon beim lesen etwas verwirrt, daher hab ich das mal
> einfach ignoriert.

Er gilt für alle.
Der Timer soll nach jeweils 125 Takt-Zählungen einen Interrupt auslösen. 
Da er aber bei 0 zu zählen anfängt, muss der Vergleich logischerweise 
auf 124 erfolgen. Denn von 0 bis 124 sind es 125 Zählungen.
Wenn du zählst 0, 1, 2, 3  dann hast du 4 Zahlen gezählt.

von Paul (Gast)


Lesenswert?

Michael U. schrieb:
> schau ins Datenblatt des AVR nach der Formel. Der Fehler war im
> Tutorial.

Hallo Michael,
im Datenblatt zum Mega8 hab ich nach kurzer Durchsicht nichts dazu 
gefunden. Was meinst du mit:"Der Fehler war im Tutorial."? a): Das 
Tutorial war fehlerhaft oder b): Der Fehler wurde im Tutorial erwähnt.?
Paul

von Paul (Gast)


Lesenswert?

Danke Karl,
jetzt bin ich schlauer. Da hätte ich eigentlich auch selbst drauf kommen 
können.
Gruß
Paul
P.S. @ Michael: hat sich erledigt, Danke euch allen!

von Stefan B. (Gast)


Lesenswert?

Bugfix heisst, das ist eine Korrektur des Beispielcodes, der vorher dort 
im Tutorial gestanden hat (ohne -1).

Die Logik wie man OCRx im CTC-Modus belegt, gilt für alle 8-Bit AVRs. 
Schau dir die Timing Diagramme im Datenblatt an und achte darauf, wann 
der Interrupt ausgelöst wird und welche Werte der Zähler TCNTx dann hat. 
Spiele Mit Papier und Bleistift µC.

Wenn du z.B. OCR auf 4 setzt, dann wird der Interrupt beim Übergang von 
TCNT=4 auf TCNT=4+1 ausgelöst. Dabei wird TCNT im CTC-Modus auf 0 
gesetzt. An Schritten sind - in dem Moment in dem das Interruptflag 
gesetzt wird - abgearbeitet: Die Schritte bei denen TCNT die Werte 
0,1,2,3,4 hatte. Das sind 5 Schritte. Wenn du vom Programm her also X 
Schritte brauchst, musst du OCR mit X-1 belegen.

von Falk B. (falk)


Lesenswert?

@  Paul (Gast)

>also einfach den hexadezimalen bzw. binären Wert da rein schreiben? Ok
>werd ich in Zukunft so machen.

Auch dezimale Zahlen sind in dem Fall vollkommen OK.

MfG
Falk

von I. E. (anfaenger69)


Lesenswert?

>   if(millisekunden==1000)
>   {
>      sekunde++;
>      millisekunden=0;

Kann man das nicht besser so schreiben?

>   if(millisekunden>1000)
>   {
>      sekunde++;
>      millisekunden %=1000;  //auf 999 bregrenzen

Wenn die Main Schleife erst bei Wert 1004 zum abfragen kommen sollte, 
dann wird die Sekunde trotzdem hochgezählt, und als Rest bleibt der Wert 
"5" in der Variable millisekunden.

von Karl H. (kbuchegg)


Lesenswert?

Igor Ebner schrieb:

> Kann man das nicht besser so schreiben?

Im Prinzip ja.
Kommt drauf an an welcher Stelle.
Wenn das Weiterzählen der Millisekunden in der main Schleife passiert: 
Ja dann kann es natürlich sein, dass Millisekunden über 1000 drüber 
geht.
In einer ISR, unmittelbar nach Erhöhen von Millisekunden kann das aber 
nicht passieren.

>>   if(millisekunden>1000)
>>   {
>>      sekunde++;
>>      millisekunden %=1000;  //auf 999 bregrenzen

Besser

        while( millisekunden > 999 )
          millisekunden -= 1000;

Divisionen (und damit auch Restbildung) sind auf einem AVR ziemlich 
teuer. Die willst du nicht wirklich haben, wenn es eine andere 
Möglichkeit gibt. Und im Regelfall wird praktisch immer eine Subtraktion 
reichen.

Ich würde die Erhöhung der Uhr in der ISR lassen. Das vereinfacht die 
Situation indem die Funktionseinheit 'Uhr' als Ganzes im restlichen 
Programm immer als in sich konsistente Einheit angesehen werden kann, 
die keiner weiteren Betreuung bedarf. Die Uhr tickt ganz von alleine vor 
sich hin ohne den Prozessor in der ISR großartig zu belasten. Vor 
Auslesen der Uhr, wie bei allen Datentypen die aus mehreren Bytes 
bestehen, die Interrupts sperren, danach wieder aktivieren.

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.