Forum: Compiler & IDEs Probleme Int Vergleich Timer


von Michael K. (mad_axe)


Lesenswert?

Hallo,

heute mal wieder ein Problem. Ich habe einen Timer, der genau alle 1ms 
einen Interupt auslöst. Dort wird eine Zahl, die vorher einen Wert 
bekommen hat heruntergezählt.
Hier die Codeausschnitte dazu :
1
//Timer1 Interupt Compare
2
3
int timerCounter = 0;
4
5
ISR (SIG_OUTPUT_COMPARE1A)
6
{
7
  if(timerCounter > 0)
8
    timerCounter--;
9
10
}
11
12
 ....
13
14
void waitMs(int ms)
15
{
16
  timerCounter = ms;
17
  printf("WarteZeit = %i\r\n", timerCounter);
18
  while(timerCounter > 0)  {}
19
  printf("Ende = %i\r\n", timerCounter);
20
}

Folgendes Problem. Wenn ich die Funktion waitMs() aufrufe, kommt es alle 
3-4 mal dazu, das die letzte printf-Ausgabe in der Funktion(also "Ende=" 
) den Wert 255 ausgibt, die Variable timerCounter also nicht bis 0 
heruntergezählt ist, was ja aber durch den Vergleich (timerCounter > 0) 
eigentlich nicht sein kann.

Vielleicht einer eine Idee ...

Gruß
Micha

von Andreas K. (a-k)


Lesenswert?

Erstens: volatile int timerCounter = 0;

Zweitens: Wenn das ein AVR ist, dann kann das auch dann nicht sauber 
funktionieren. Weil in waitMs die Variable als 2 getrennte Bytes 
verarbeitet wird, und wenn dazwischen der Interrupt reinläuft, ist hat 
das eine schon geladene Byte den Zustand von vorher, das andere danach 
geladene den Zustand nachher.

von Fred S. (Gast)


Lesenswert?

Hallo Micha,

die Variable, die ja von einer Funktion und der ISR benutzt wird, sollte 
als "volatile" deklariert werden:
1
volatile int timerCounter = 0;

Gruß

Fred

PS: Andreas war schneller!

von Andreas K. (a-k)


Lesenswert?

Aus 
http://www.mikrocontroller.net/articles/Interrupt#Atomarer_Datenzugriff: 
"Ein ähnliches Problem entsteht bei Variablen, deren Grösse die 
Wortbreite der Maschine übersteigt. Bei 8-Bit-Prozessoren wie AVR oder 
i51 also bereits bei normalen "int" Variablen. Diese Variablen werden 
zwangsläufig byteweise verarbeitet. Wenn genau dazwischen ein Interrupt 
erfolgt, wird ein falscher Wert gelesen. Wenn beispielsweise eine 
Interrupt-Routine einen 16-Bit-Zähler verwendet und von 0x00FF auf 
0x0100 hochzählt, dann kann das Hauptprogramm auch schon mal 
versehentlich die Werte 0x01FF oder 0x0000 lesen."

von Michael K. (mad_axe)


Lesenswert?

Hallo,

Danke erstmal für die schnellen Antworten !!

das mit dem volatile hatte ich schon drin, nur gestern Abend beim 
herumprobieren irgendwie wieder draußen gelassen.

Ich kann mir schon vorstellen das es genau das ist, was ihr mir hier 
beschreibt, nur wie mache ich es dann am besten.. na mal sehen ob mir 
auch was einfällt.

Gruß
Micha

von mo (Gast)


Lesenswert?

Moin,

du musst den Vergleich timerCounter > 0 einkapseln mit cli(); und 
sei();, damit der Interrupt nicht dazwischenfunken kann (siehe 
Beschreibung von Andreas)

Gruß
mo

von Michael K. (mad_axe)


Lesenswert?

Aber damit sollte doch dann mein Zeitzähler ziemlich ungenau werden oder 
sehe ich das falsch. Brauche schon ziemlich genaue Zeit, da das ganze 
von einem Kameraauslöser ist ...

Gruß
Micha

von Karl H. (kbuchegg)


Lesenswert?

Michael Kentschke wrote:
> Aber damit sollte doch dann mein Zeitzähler ziemlich ungenau werden

definiere 'ungenau'.

Spielt es wirklich eine Rolle, wenn der Vergleich ein paar
Nanosekunden zu spät  kommt, weil der Interrupt warten muss
bis der int einmal auf 0 geprüft wurde?

> oder
> sehe ich das falsch. Brauche schon ziemlich genaue Zeit, da das ganze
> von einem Kameraauslöser ist ...

Ich denke nicht, dass da ein paar Nanosekunden eine Rolle spielen.

von Oliver (Gast)


Lesenswert?

>du musst den Vergleich timerCounter > 0 einkapseln mit cli(); und
>sei();

was aber bei einem while-Konstrukt nicht geht.

Am kleinsten wird die "Ungenauigkeit", wenn du die volatile-Variable in 
eine lokale Variable umkopierst, und nur das mit cli()/sei() kapselst.

Aus
1
cli();
2
int a = timerCounter ;
3
sei();

wird
1
cli
2
lds  r24, 0x0060
3
lds  r25, 0x0061
4
sei

mit maximal 5 Taktzyklen Verzögerung. Macht selbst bei langsamen 1MHz 
nur 5 Mikrosekunden Ungenauigkeit.

Oliver

von Michael K. (mad_axe)


Lesenswert?

Ok, vielleicht verstehe ich das falsch, aber ich versuches es mal zu 
Re-Konstruieren.

Ich könnte ja jetzt folgendes Machen:
1
int a = timerCounter;  
2
while(a> 0)  
3
{
4
cli();
5
a = timerCounter ;
6
sei();
7
}

Dann habe ich doch aber jedesmal eine Verzögerung. z.B. 1000ms warten, 
da wird der TimerInterrupt doch ziemlich oft ab und angeschaltet werden, 
oder verstehe ich das falsch.

Gruß
Micha

von Andreas K. (a-k)


Lesenswert?

Da steckt der gleiche Fehler ja immer noch drin (in a = ...). Besser:
1
int a;
2
do {
3
  cli();
4
  a = timerCounter;
5
  sei();
6
} while(a > 0)

> da wird der TimerInterrupt doch ziemlich oft ab und angeschaltet werden,
> oder verstehe ich das falsch.

Nö, das verstehts du richtig. Und was stört dich daran? Der geht dadurch 
nicht verloren und wird nur unmerklich verzögert (wenige CPU-Takte).

von Karl H. (kbuchegg)


Lesenswert?

Andreas Kaiser wrote:

> Nö, das verstehts du richtig. Und was stört dich daran? Der geht dadurch
> nicht verloren.


Ich denke Michael hat Angst, dass ihm hier irgendwie Zeit verloren
geht und sich das aufsummiert. Dem ist aber nicht so. Der Interrupt
wird trotzdem immer zeitgerecht ausgelöst, weil dieses Auslösen ja von
der Hardware gemacht wird. Lediglich die Abarbeitung der Interrupt
Funktion kann sich unter Umständen ein klein wenig verzögern.
Das ist so, wie wenn du eine Uhr hast, bei der der Sekundenzeiger
jedesmal wenn er bei 0 ist einen Summton auslöst. Wenn du zufällig
mal bei einem Summton keine Zeit hast und 5 Sekunden später auf
die Uhr schaust, dann siehst du das eine mal etwas zu spät auf die
Uhr. Aber der nächste Summton kommt wieder pünktlich zur nächsten
vollen Minute.

von Michael K. (mad_axe)


Lesenswert?

Danke @Karl heinz Buchegger, du hattest schon recht mit der Vermutung 
das ich dachte, es summiert sich auf.
Aber ich denke nun habe ich es verstanden und baue dann mal mein 
Programm um.

Danke @all

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.