rollover save timer in c

Gast #4324303
Lesenswert?

In Mikrocontrolleranwendungen gibt es oft das Problem, eine Schleife mit 
konstanter Frequenz durchlaufen zu lassen.

Hier wurde schonmal was in der Art
1
uint8_t stopTime;
2
uint8_t delayTime;
3

4
stopTimer=currentTime()+delayTime;
5
while(currentTime()<topTime);

vorgeschlagen. Bei diesem Verfahren gibt es aber Probleme mit dem 
Überlauf.
Hier werden verschiedenste Techniken vorgeschlagen:
http://stackoverflow.com/questions/61443/rollover-safe-timer-tick-comparisons

Wie löst Ihr das Problem auf einfach Weise?
Gast #4324345
Lesenswert?

Dann bildet man neben dem Hard Timer noch ein Soft Timer.
Alternativ benutzt du für Wartezeiten einfach einen eigenen HardTimer 
und wartes drauf, dass er abgelaufen ist.

Also ich seh bei dem Thema nun kein großes Problem.
Gast #4324361
Lesenswert?

Dr. Emmett Brown (Gast) schrieb
>
1
>uint16_t delay;
2
>uint16_t stop;
3
>
4
>stop = TCNT + delay;
5
>
6
>while(TCNT != stop);
7
>


Das würde funktionieren, wenn sichergestellt ist, dass der Timer nicht 
"full speed" als mit Prozessortakt, läuft. In meinem Anwendungsfall ist 
das aber gegeben.
Gast #4324377
Lesenswert?

also bei UINT Werten kann man doch einfach subtrahieren, das stimmt auch 
bei einem Überlauf:
1
UINT8 startTime;
2
UINT8 delay = 20;
3

4
startTime = currentTime();
5

6
while( (currentTime() - startTime) < delay);

Oder habe ich das bisher immer falsch gemacht?
Gast #4324384
Lesenswert?

ch schrieb:
> Das würde funktionieren, wenn sichergestellt ist, dass der Timer nicht
> "full speed" als mit Prozessortakt, läuft. In meinem Anwendungsfall ist
> das aber gegeben.

Meine Güte, ist denn das so schwierig?

In der Overflow-ISR deines mit 3,5GHz getakteten Timers:
1
static uint8_t ms_timer;
2
ms_timer++;
3
if(ms_timer == (3500000000UL / 65536 / 1000))
4
{
5
  millis++;
6
  ms_timer = 0;

millis ist eine globale Variable und mit den Rundungsfehlern mußt du 
leben.
Gast #4324390
Lesenswert?

Hallo,

die meisten in Prozessoren verwendeten Timer können das ohne weiteres 
und ohne langes Nachdenken des Programmierers: vom eingestellten 
Startwert wird heruntergezählt auf 0, ein IRQ ausgelöst und der 
Startwert wieder geladen. Die IRQs folgen also äquidistant aufeinander 
ohne jeden Programmeingriff.

Das Problem existiert also eigentlich garnicht.

Georg
Gast #4324396
Lesenswert?

Georg schrieb:
> Hallo,
>
> die meisten in Prozessoren verwendeten Timer können das ohne weiteres
> und ohne langes Nachdenken des Programmierers: vom eingestellten
> Startwert wird heruntergezählt auf 0, ein IRQ ausgelöst und der
> Startwert wieder geladen. Die IRQs folgen also äquidistant aufeinander
> ohne jeden Programmeingriff.
>
> Das Problem existiert also eigentlich garnicht.
>
> Georg

Jepp, was wiederum heißt, dass "while(delay);" schlechter 
Programmierstil ist.
Es sei denn, man bezweckt was ganz spezielles damit.
#4324398
Lesenswert?

ch schrieb:
> In Mikrocontrolleranwendungen gibt es oft das Problem, eine Schleife mit
> konstanter Frequenz durchlaufen zu lassen.

Die bessere Variante ist es allerdings, nicht solange Däumchen zu 
drehen, bis eine bestimmte Zeit (voraussichtlich) um ist. Wobei es auch 
Ausnahmen gibt, wenn die Zeiten sehr kurz sind und nicht mit 
UNterbrechungen zu rechnen ist.

Die weit aus bessere Variante ist es, den Timer so aufzusetzen und so zu 
verwenden, dass er in den gewünschten regelmässige Zeitabständen das 
Startsignal für die nächste Abarbeitung von Funktionalitäten gibt.
#4324415
Lesenswert?

Georg schrieb:
> die meisten in Prozessoren verwendeten Timer können das ohne weiteres
> und ohne langes Nachdenken des Programmierers: vom eingestellten
> Startwert wird heruntergezählt auf 0, ein IRQ ausgelöst und der
> Startwert wieder geladen. Die IRQs folgen also äquidistant aufeinander
> ohne jeden Programmeingriff.
>
> Das Problem existiert also eigentlich garnicht.

Genau so ist es. Man muss in der Software keine Probleme lösen, die man 
schon von der Hardware erschlagen bekommt.
Gast #4324423
Lesenswert?

danvet schrieb:
> Jepp, was wiederum heißt, dass "while(delay);" schlechter
> Programmierstil ist.
> Es sei denn, man bezweckt was ganz spezielles damit.

Meistens macht das nur Probleme.

1
#define SYSTICK 1000
2

3
static unsigned char intervaldo_ = 0;
4

5
#define INTERVAL(intervalvalue_){\
6
static unsigned int intervalct_ = 0;\
7
if(!intervalct_) intervalct_ = (unsigned int)((long)intervalvalue_ *SYSTICK / 1000);\
8
intervalct_--;\
9
intervaldo_ = !intervalct_;\
10
}\
11
if(intervaldo_)
12

13

14
int main(void
15
{
16
 while(1)
17
 {
18
  if(millisekundentakt)
19
  {
20
   INTERVAL(1000)
21
   {
22
     //Tu was alle 1000ms
23
   }
24

25
   INTERVAL(143)
26
   {
27
     //Tu was alle 143ms
28
   }
29

30
   INTERVAL(5000)
31
   {
32
     //Tu was alle 5000ms
33
   }
34

35
   INTERVAL(10)
36
   {
37
     //Tu was alle 10ms
38
   }
39
  }
40
 }
41
}
Gast #4324428
Lesenswert?

1
> UINT8 startTime;
2
> UINT8 delay = 20;
3
> 
4
> startTime = currentTime();
5
> 
6
> while( (currentTime() - startTime) < delay);
7
>

Das hört sich ziemlich gut an.
Hier mal der Vorschlag umgesetzt auf einem Arduino Due. Es läuft ca. 
eine Sekunde und bleibt dann hängen.
Sieht jemand den Fehler?
1
void setup() 
2
{
3
  pinMode(13, OUTPUT);
4
}
5

6
void toogle()
7
{
8
  static uint8_t flag;
9
  flag ^= 1;
10
  digitalWrite(13, flag);
11
}
12

13
uint8_t systemTime()
14
{
15
  return millis();
16
}
17

18
uint8_t Start;
19
uint8_t Delay;
20

21
void setDelayTimer(uint8_t t)
22
{
23
  Start=systemTime();
24
  Delay=t;
25
}
26

27
void wait()
28
{
29
  while( (systemTime()-Start ) < Delay );
30
}

void loop()
{
  toogle();
  //delay(5);
  setDelayTimer(5);
  wait();

}
#4324436
Lesenswert?

ch schrieb:

> uint8_t Start;
> uint8_t Delay;

Vergessen.

uint8_t ist keine gute Idee.

Der springende Punkt ist das unsigned. Machst du aber Rechnungen mit 
uint8_t so wird das int (und zwar signed int) gerechnet. Genau das 
kannst du aber nicht brauchen! Du willst und musst die Berechnung 
unsigned halten.

Wozu das ganze? millis liefert dir einen unsigned long. Ob du den jetzt 
wie ein wilder runter castest oder gleich alles in unsigned long machst, 
schenkt sich nichts.
#4324458
Lesenswert?

ch schrieb:
>>Der springende Punkt ist das unsigned. Machst du aber Rechnungen
> mit
>>uint8_t so wird das int (und zwar signed int) gerechnet
>
> Danke für die Antwort. Bist Du Dir aber sicher?
>
> int8_t   Signed
> uint8_t Unsigned
>
> https://en.wikibooks.org/wiki/C_Programming/C_Refe...

Das Problem ist nicht der Typ sondern die Berechnung. Siehe hier: 
http://en.cppreference.com/w/cpp/language/implicit_cast

Entsprechender Auszug:
unsigned char or unsigned short can be converted to int if it can hold 
its entire value range, and unsigned int otherwise.
#4324460
Lesenswert?

ch schrieb:
> Danke für die Antwort. Bist Du Dir aber sicher?

Ich bin mir sicher, dass sich Karl Heinz sicher ist. Die Datentypen 
selbst sind zwar unsigned, die Berechnung wird in diesem Fall jedoch als 
signed int durchgeführt. Kompiliere ich folgenden Code,
1
uint8_t Value =3;
2
Value = 2 * Value;
so erhalte ich eine Warnung:
1
warning: conversion to ‘uint8_t {aka unsigned char}’ from ‘int’ may alter its value [-Wconversion]|

Das ganze nennt sich integral promotion.

Konkret geht es um diese Zeile:
1
while( (systemTime()-Start ) < Delay );
Alle Operanden sind ein uint8_t und werden somit zu einem signed int 
gecastet. Dementsprechend wird die Berechnung als signed durchgeführt.
#4324468
Lesenswert?

be s. schrieb:

> Alle Operanden sind ein uint8_t und werden somit zu einem signed int
> gecastet. Dementsprechend wird die Berechnung als signed durchgeführt.


Und damit natürlich auch der Vergleich.

> Bist Du Dir aber sicher?

Ja, ich bin mir absolut sicher. So sind nun mal die C Regeln.
In einer Operation werden alle beteiligten Operanden immer auf den 
höchsten Types eines der beiden Operanden hochgehoben, mindestens aber 
int.
Gast #4324480
Lesenswert?

> UINT8 startTime;
> UINT8 delay = 20;
>
> startTime = currentTime();
>
> while( (currentTime() - startTime) < delay);
>

Der oben gepostete Code war mit UINT8.
Mein Code ist mit uint8_t.
Seht Ihr da einen Unterschied?
Gast #4324553
Lesenswert?

be stucki (bestucki) schrieb
>Konkret geht es um diese Zeile:
>
>while( (systemTime()-Start ) < Delay );
>
>Alle Operanden sind ein uint8_t und werden somit zu einem signed int
>gecastet. Dementsprechend wird die Berechnung als signed durchgeführt.

Hmm, ich hätte gedacht, dass die Berechnung folgendermaßen durchgeführt 
wird:

(uint8_t)((uint8_t)systemTime()-(uint8_t)Start ) < (uint8_t)Delay

Dazu folgender Test:
1
void setup()
2
{
3
  Serial.begin(115200);
4
  
5
  uint8_t a;
6
  uint8_t b;
7
 
8
  a = 250;
9
  b = 255;
10
  Serial.print("a= ");Serial.println(a);
11
  Serial.print("b= ");Serial.println(b);
12
  Serial.println("Is (b-a < 6) true or false?");
13
  
14
  if (b-a < 6)Serial.println("  it is true");
15
  Serial.println("");
16
  Serial.println("");
17
  
18
  a = 255;
19
  b = 4;
20
  Serial.print("a= ");Serial.println(a);
21
  Serial.print("b= ");Serial.println(b);
22
  Serial.println("Is (b-a < 6) true or false?");
23
  
24
  if (b-a < 6)Serial.println("  it is true");
25
  Serial.println("");
26
}
27

28
void loop()
29
{
30

31
}

Das Ergebnis ist


a= 250
b= 255
Is (b-a < 6) true or false?
  it is true


a= 255
b= 4
Is (b-a < 6) true or false?
  it is true
Gast #4324573
Lesenswert?

Arduino?

Dann sind die Zeiten alle unsigned long zu definieren. Und auch so damit 
zu rechnen. uint8_t macht da wenig Sinn.



Hier mal ein Vorschlag in "Arduino", etwas Ressourcen verplempernd, aber 
das spielt ja hier erstmal keine Rolex.
1
class TimerTick
2
{
3
  private:
4
  unsigned long lastHit;
5
  unsigned long interval;
6
  
7
  public:
8
  TimerTick(unsigned long interval):interval(interval){}
9
  
10
  bool abgelaufen()
11
  {
12
    if(millis() - lastHit >= interval)
13
    {
14
        lastHit += interval; // mit aufholjagt
15
        return true;  
16
    }
17
    return false;
18
  }  
19
};
20

21
const byte led = 13;
22
TimerTick timer(500); // alle 0,5 sec
23

24

25
void tuwas()
26
{
27
    digitalWrite(led,!digitalRead(led)); // toggle LED
28
}
29

30
void setup() 
31
{
32
    pinMode(led,OUTPUT);
33
}
34

35
void loop() 
36
{
37
  if(timer.abgelaufen()) tuwas();   
38
}
#4324605
Lesenswert?

ch schrieb:

> Hmm, ich hätte gedacht, dass die Berechnung folgendermaßen durchgeführt
> wird:

Nein.
Genau deswegen gibt es Literatur, in der die Regeln genau und 
ausführlich vermittelt werden.

Kurz gesagt: Nein, C funktioniert nicht so.

> a= 255
> b= 4
> Is (b-a < 6) true or false?
>   it is true

4 - 255 ergibt -251. Und das ist selbstverständlich kleiner 6.

Und genau das willst du nicht haben. Du willst nicht haben, dass das 
Ergebnis ein Vorzeichen besitzt. Dazu muss aber die ganze Berechnung und 
der ganze Vergleich unsigned durchgeführt werden.

Das ist prinzipiell kein Problem, solange man berücksichtigt, dass in C 
niemals in einem kleineren Datentyp als int gerechnet wird. In diesem 
Zusammenhang spielt es daher keine Rolle, dass a und b als unsigned char 
definiert sind. Denn beide werden zuerst auf den Datentyp int 
hochgehoben, ehe die Berechnung überhaupt startet. Wichtig: int(!) - 
nicht unsigned int! Denn die Regeln sagen auch, dass in so einem Fall 
der erste Datentyp genomen wird, der alle Werte aufnehmen kann. Und das 
ist nun mal ein int. Ein unsigned char hat einen Wertebereich von 0 bis 
255 - passt perfekt in einen int hinein, womit sich über die Hintertür 
wieder ein Vorzeichen einschmuggelt, welches selbstverständlich dann in 
weiterer Folge berücksichtigt und ausgewertet wird.
Gast #4324612
Lesenswert?

Stucki, Du hast recht.
Das oben von  danvet gepostete Beispiel
1
> UINT8 startTime;
2
> UINT8 delay = 20;
3
> 
4
> startTime = currentTime();
5
> 
6
> while( (currentTime() - startTime) < delay);
7
>
scheint dann zu funktionieren, wenn man in die Zeile mit dem while einen 
typecast einfügt:

while( (UINT8)(currentTime() - startTime) < delay);
#4324614
Lesenswert?

ch schrieb:

> scheint dann zu funktionieren, wenn man in die Zeile mit dem while einen
> typecast einfügt:
>
> while( (UINT8)(currentTime() - startTime) < delay);

Und jetzt lehnst du dich zurück, denkst noch mal über alles nach, was 
dir heute erzählt wurde und lässt dir durch den Kopf gehen, WARUM in 
diesem Fall das richtige passiert.

Es ist wichtig das du verstehst, was da warum passiert.
Gast #4324643
Lesenswert?

>Und jetzt lehnst du dich zurück, denkst noch mal über alles nach, was
>dir heute erzählt wurde und lässt dir durch den Kopf gehen, WARUM in
>diesem Fall das richtige passiert

Ich weis, warum das richtige passiert. Nur leider habe ich eine Weile 
gebraucht, den Fehler zu finden. Hättest Du weiter oben nicht Folgendes 
geschrieben

 Karl Heinz (kbuchegg) (Moderator) schrieb
>>danvet schrieb:
>> UINT ist doch unsigned ???

>ja. Sag ich doch, dass es richtig ist.
>Es gibt aber auch Zeitgenossen, die mitlesen und nicht erkennen, wo der
>springende Punkt liegt. Viele sehen in Datentypen nicht mehr als eine
>Art 'schmückendes Beiwerk'.

und erwähnt dass in Beispiel von davnet der typecast fehlt, wäre auch 
alles etwas schneller gegangen.
Gast #4324660
Lesenswert?

Karl H. schrieb:
> while( (currentTime() - startTime) < delay);

Ähemm.. sind ja alles vorzeichenlose Bytes, also beispielsweise:

currentTime = 254;
starttime = 250;
delay = 150;

Hab ich heut schon zuviel Rotwein intus, oder..?


Also wenn es bei mir auf kleinere, aber präzise Delays ankommt, dann 
reserviere ich dafür einen Timer, den ich geeignet programmiere und 
starte.

Wenn es lediglich größere Wartezeiten sind, dann setze ich einen 
"delayed_Event", der vom Timertick des Systems verwaltet wird. Dieser 
zählt die Millisekunden per long, also 32 Bit - UND!! er zieht am 
Tagesende einen ganzen Tag ab (sowohl bei der Uhrzeit als auch bei den 
verwalteten delayed Events). Da klappt immer, ist sauber und einfach zu 
benutzen.

W.S.
#4324794
Lesenswert?

W.S. schrieb:
> Karl H. schrieb:
>> while( (currentTime() - startTime) < delay);
>
> Ähemm.. sind ja alles vorzeichenlose Bytes, also beispielsweise:
>
> currentTime = 254;
> starttime = 250;
> delay = 150;
>
> Hab ich heut schon zuviel Rotwein intus, oder..?

Mag sein. Aber bei einem unsigned char soll es auch schon mal 
vorgekommen sein, dass ein Überlauf zwischendurch passiert und 
currentTime dann effektiv kleiner als startTime ist.

Was an und für sich keine Rolle spielt, solange die Arithmetik unsigned 
durchgeführt wird. Was sie hier allerdings nicht wird, da da noch die 
"es wird nicht kleiner als int gerechnet" Regel mit ins Spiel kommt.
Sobald die uint8_t auf mindestens uint16_t geändert werden, ist dieses 
Problem keines mehr und das ganze läuft auch ohne unsigned cast des 
Subtraktionsergebnisses völlig richtig ab.
Gast #4325127
Lesenswert?

Karl Heinz (kbuchegg) (Moderator) schrieb:
>Sobald die uint8_t auf mindestens uint16_t geändert werden, ist dieses
>Problem keines mehr und das ganze läuft auch ohne unsigned cast des
>Subtraktionsergebnisses völlig richtig ab.

Naja, der Arduino Due ist ein ARM-Controller mit 32 bit, da führt das 
weglassen des typcast zur Fehlfunktion.
Damit die Routine portabel auf verschiedenen Architekturen funktioniert, 
sollter der typecast auf jeden Fall drinn bleiben.
Meine Routinen laufen auf mindestens 5 verschiedenen 
Controller-Architekturen.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren