Forum: Mikrocontroller und Digitale Elektronik Frage zu Timern in C


von Stefan H. (stefan_h143)


Lesenswert?

Hallo zusammen,

Ich hätte da eine Frage, die wahrscheinlich schon tausend mal gestellt 
wurde. Ich bin wahrscheinlich zu doof zum Suchen.

Ich arbeite mich grad in die Timer ein und will eigentlich nur delay 
durch eine Timerabfrage ersetzten. Also ähnlich wie BlinkWithoutDelay 
beim Arduino.

Momentan mache habe ich den Code so:
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
4
uint8_t schritt = 0; //Schrittvariable
5
uint8_t sechzehn_ms = 0; // alle 16ms ein Inkrement
6
uint8_t vergangene_zeit = 0; // vergangene Zeit für schritt
7
8
int main(void)
9
{  
10
  // Timer 0 konfigurieren -> Prescaler auf 1024
11
  TCCR0B |= (1<<CS02) | (1<<CS00);
12
  // Overflow Interrupt von Timer 0 einschalten
13
  TIMSK0 |= (1<<TOIE0);
14
  // Global Interrupts aktivieren
15
  sei();
16
17
18
    while (1) 
19
    {
20
    
21
22
  uint8_t aktuelle_zeit = sechzehn_ms;
23
  if (aktuelle_zeit - vergangene_zeit >= 63)
24
  {
25
    vergangene_zeit = aktuelle_zeit; 
26
27
    //Schritt weiter schalten
28
    if (schritt <2 )
29
    {
30
      schritt++;
31
    } else
32
    {
33
      schritt=0;
34
    }
35
  }
36
      
37
    }
38
}
39
40
ISR (TIMER0_OVF_vect)
41
{
42
  sechzehn_ms++;
43
}

Ich möchte im Prinzip nur ca alle Sekunde den Schritt um eins weiter 
zählen oder eben auf 0 setzten. die Zeit muss nicht genau sein, daher 
hab ich den Prescaler hoch gewählt.
Das ganze läuft einmal durch aber wenn die vergangene_zeit dann bei 
einem zu hohen Wert steht, kann die Bedingung natürlich nicht mehr wahr 
werden.

Wie umgeht man das? Mache ich das grundsätzlich falsch?

Gruß
Stefan

von Fux (Gast)


Lesenswert?

> bei einem zu hohen Wert steht

Da werden die Unixe dann auch stehenbleiben.
Aber erst 2038.
Sei also unbesorgt!

von Stefan H. (stefan_h143)


Lesenswert?

Fux schrieb:
> Sei also unbesorgt!

bin nicht unbesorgt, denn es funktioniert ja nicht...

von N.N. (Gast)


Lesenswert?

volatile uint8_t sechzehn_ms = 0; // alle 16ms ein Inkrement

von Samuel C. (neoexacun)


Lesenswert?

Indem du einen uint64_t wählst. Dann hast du Ruhe.

: Bearbeitet durch User
von Stefan H. (stefan_h143)


Lesenswert?

Samuel C. schrieb:
> Indem du einen uint64_t für vergangene_zeit wählst. Dann hast du Ruhe

wie meinst du das? meine meine sechzehn_ms sind doch auch nur 8bit.
Selbst bei einem so großen wert wäre doch irgendwann das gleiche Problem 
da oder?

von Stefan H. (stefan_h143)


Lesenswert?

N.N. schrieb:
> volatile uint8_t sechzehn_ms = 0; // alle 16ms ein Inkrement

bringt leider nichts.

von Wolfgang (Gast)


Lesenswert?

Auf jeden Fall solltest du sechzehn_ms als volatile deklarieren, 
damit dir der Optimierer da nicht in die Suppe spucken kann.

von Stefan F. (Gast)


Lesenswert?

1.) Mache sechzehn_ms volatile.

2.) Solche Zeitzähler soll man im Laufe des Programmes niemals ändern. 
Die einzige Änderung darf das Inkrementieren in der ISR sein.

3.) Du musst subtrahieren, den Part hast du fast richtig gemacht.

Beispiel:
1
volatile uint8_t sechzehn_ms=0;
2
3
int main(void)
4
{
5
    // Hier den Timer starten
6
7
    uint8_t warte_seit=sechzehn_ms;
8
    while(1)
9
    {
10
        if (sechzehn_ms-warte_seit >= 63)
11
        {
12
            warte_seit=sechzehn_ms;
13
            // Tu etwas
14
        }
15
    }
16
}
17
18
ISR (TIMER0_OVF_vect)
19
{
20
  sechzehn_ms++;
21
}

Dann funktioniert das sogar bei einem Timer-Überlauf korrekt. Wichtig 
ist, dass beide Variablen unsigned Integer mit gleicher Größe sind.

von Samuel C. (neoexacun)


Lesenswert?

Stefan H. schrieb:
> Samuel C. schrieb:
>> Indem du einen uint64_t für vergangene_zeit wählst. Dann hast du Ruhe
>
> wie meinst du das? meine meine sechzehn_ms sind doch auch nur 8bit.
> Selbst bei einem so großen wert wäre doch irgendwann das gleiche Problem
> da oder?

Dann mach auch sechzehn_ms zu uint64_t.

Wieviele Milliarden Jahre willst du das Ding denn betreiben?

von Stefan F. (Gast)


Lesenswert?

Noch ein Nachtrag:

Wenn du mehr als 8 bits verwendest, musst du beim Lesen des Zählers 
verhindern, dass er wöhrend dessen durch die ISR verändert wird. Zum 
Beispiel so:
1
volatile uint32_t sechzehn_ms=0;
2
3
int main(void)
4
{
5
    // Hier den Timer starten
6
7
    cli();
8
    uint32_t warte_seit=sechzehn_ms;
9
    sei();
10
    while(1)
11
    {
12
        cli();
13
        uint32_t jetzt=sechzehn_ms;
14
        sei();
15
        if (jetzt-warte_seit >= 63)
16
        {
17
            warte_seit=jetzt;
18
            // Tu etwas
19
        }
20
    }
21
}
22
23
ISR (TIMER0_OVF_vect)
24
{
25
  sechzehn_ms++;
26
}

von Stefan H. (stefan_h143)


Lesenswert?

Stefanus F. schrieb:
> Dann funktioniert das sogar bei einem Timer-Überlauf korrekt. Wichtig
> ist, dass beide Variablen unsigned Integer mit gleicher Größe sind.

Danke für deine Antwort!

Leider funktioniert das wieder nicht. es läuft einmal durch, dann ist 
wieder Schluss. Im Prinzip machst du ja fast das gleiche wie ich ganz am 
anfang.

von Stefan F. (Gast)


Lesenswert?

Stefan H. schrieb:
> Im Prinzip machst du ja fast das gleiche wie ich ganz am
> anfang.

Ja stimmt. Meine Aussage "fast richtig" war falsch, ich hatte mich 
diesbezüglich verguckt.

Das müsste laufen. Zeige nochmal deinen aktuellen Quelltext (mit 
volatile).

von Stefan H. (stefan_h143)


Lesenswert?

Stefanus F. schrieb:
> Das müsste laufen. Zeige nochmal deinen aktuellen Quelltext (mit
> volatile).
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
4
//Delay Funktion 
5
#define F_CPU 16000000UL  // 16 MHz
6
#include <util/delay.h>
7
8
//Globale Variablen
9
uint8_t schritt = 0; //Schrittvariable
10
volatile uint8_t sechzehn_ms = 0; // alle 16ms ein Inkrement
11
uint8_t zeit_eins = 0; // vergangene Zeit f�r schritt
12
13
14
int main(void)
15
{
16
  
17
  // Timer 0 konfigurieren -> Prescaler auf 1024
18
  TCCR0B |= (1<<CS02) | (1<<CS00);
19
  // Overflow Interrupt von Timer 0 einschalten
20
  TIMSK0 |= (1<<TOIE0);
21
  // Global Interrupts aktivieren
22
  sei();
23
  
24
  
25
 warte_seit=sechzehn_ms; // müsste ich mir eigentlich sparen können, da sechzehn_ms==0
26
    while (1) 
27
    {
28
    
29
    //uint8_t aktuelle_zeit = sechzehn_ms;
30
    if (sechzehn_ms - zeit_eins >= 63)
31
    {
32
      zeit_eins = sechzehn_ms;
33
      //Schritt weiter schalten
34
      if (schritt <2 )
35
      {
36
        schritt++;
37
      } else
38
      {
39
        schritt=0;
40
      }
41
    }
42
    
43
    
44
    
45
    }
46
}
47
48
ISR (TIMER0_OVF_vect)
49
{
50
  sechzehn_ms++;
51
}

ich verwende dann den schritt um leds leuchten zu lassen... also immer 
drei Farben abwechselnd. das sollte ja aber keinen Einfluss haben, da 
ich eigentlich nur mit einer switch case schleife den schritt abfrage

: Bearbeitet durch User
von Stefan F. (Gast)


Lesenswert?

Ich raff' es nicht. Ich sehe den Fehler nicht!

von Stefan H. (stefan_h143)


Lesenswert?

Stefanus F. schrieb:
> Ich raff' es nicht. Ich sehe den Fehler nicht!

Naja ich denke dass ich den fehler mache:

wenn sechzehn_ms gerade zb 250 ist und ich das dann in zeit_eins 
abspeichere, dann kann bei der nächsten Rechnung ja nie mehr was 
größeres als 5 raus kommen.

Also sechzehn_ms (= 255) - zeit_eins(250) = 5. Damit ist die Bedingung 
nie erfüllt und ich komme nie mehr dazu den schritt weiter zu schalten.


Sehe ich das falsch?

wenn ich nun natürlich alles auf 32bit hoch nehme, dann dauert es ewig 
bis es dazu kommt. aber irgendwann eben schon. das muss man doch 
irgendwie umgehen können oder?


Wenn ich das so abfange, läuft zumindes irgenwie was weiter...
1
if (sechzehn_ms - zeit_eins >= 63)
2
    {
3
      if (sechzehn_ms < 192)
4
      {
5
        zeit_eins = sechzehn_ms;
6
      } else
7
      {
8
        zeit_eins = 50;
9
      }
10
     
11
      //Schritt weiter schalten
12
      if (schritt <2 )
13
      {
14
        schritt++;
15
      } else
16
      {
17
        schritt=0;
18
      }
19
    }

: Bearbeitet durch User
von Wolfgang (Gast)


Lesenswert?

Samuel C. schrieb:
> Indem du einen uint64_t wählst. Dann hast du Ruhe.

Einen kaputten Algorithmus durch größere Variablen etwas länger am Leben 
zu halten, kann nicht die Lösung sein - allenfalls ein schlechter Work 
around.

von Stefan H. (stefan_h143)


Lesenswert?

Wolfgang schrieb:
> Einen kaputten Algorithmus durch größere Variablen etwas länger am Leben
> zu halten, kann nicht die Lösung sein - allenfalls ein schlechter Work
> around.

Deswegen frage ich ja, wie man das besser lösen könnte :)
Hast du da eine Idee?

von Wolfgang (Gast)


Lesenswert?

Stefan H. schrieb:
> Deswegen frage ich ja, wie man das besser lösen könnte :)

Mein Kommentar bezog sich auf den etwas cruden "Tip" von Samuel C.


> Hast du da eine Idee?

Ich würde das in dieser Art angehen:
1
volatil uint8_t sechzehn_ms = 0; // alle 16ms ein Inkrement
2
uint8_t startzeit = 0; //Schrittvariable
3
4
if (sechzehn_ms - startzeit > 63)
5
{
6
   ... tue etwas
7
   startzeit  = sechzehn_ms; 
8
}

von Stefan H. (stefan_h143)


Lesenswert?

Wolfgang schrieb:
> Ich würde das in dieser Art angehen

Ja aber ich mache doch genau das, mit dem beschriebenen Problem.

in dem Moment wenn die Startzeit zu groß wird, kann die Bedingung ja 
nicht mehr wahr werden oder?
Ich steh auf dem Schlauch

von foobar (Gast)


Lesenswert?

> wenn sechzehn_ms gerade zb 250 ist und ich das dann in zeit_eins
> abspeichere, dann kann bei der nächsten Rechnung ja nie mehr was
> größeres als 5 raus kommen.

Doch, wegen des Überlaufs: "negative" Ergebnisse sind bei unsigned sehr 
groß.

Du hast allerdings zwei Probleme:

1) Alle Ausdrücke in C werden immer mit mindestens int-Größe 
durchgeführt.  Dein uint8_t wird also zu einem signed int.  Bei a-b 
wird, obwohl a und b uint8_t sind, signed gerechnet, evtl negativ und 
damit kleiner als jeder positive Vergleichswert.  Der Vergleich müsste 
"if ((uint8_t)(a-b) > 63)" oder "if ((unsigned)a-b > 63)" lauten[1].

2) Deine Schleife macht nichts, außer die Variable "schritt" zu 
verändern - da kann der Compiler ordentlich optimieren - evtl bleibt 
nichts übrig ...


[1] Unterschied rausfinden "is left as an exercise for the reader" ;-)

von foobar (Gast)


Lesenswert?

> "if ((uint8_t)(a-b) > 63)" oder "if ((unsigned)a-b > 63)"

Noch ne dritte Variante: "if (a-b > 63u)"

von Veit D. (devil-elec)


Lesenswert?

Hallo,

zeige bitte einen kompilierbaren Code.
Woher weißt du das der Code stehen bleibt?
Du hast nirgends eine Ausgabe bzw. Taktausgang.

von Samuel C. (neoexacun)


Lesenswert?

Wolfgang schrieb:
> Mein Kommentar bezog sich auf den etwas cruden "Tip" von Samuel C.

Krude seh ich ein. Aber löst das Problem (fast) ohne weiteres bis zum 
Verlöschen unseres Zentralgestirns.

: Bearbeitet durch User
von Stefan H. (stefan_h143)


Lesenswert?

foobar schrieb:
> 1) Alle Ausdrücke in C werden immer mit mindestens int-Größe
> durchgeführt.  Dein uint8_t wird also zu einem signed int.  Bei a-b
> wird, obwohl a und b uint8_t sind, signed gerechnet, evtl negativ und
> damit kleiner als jeder positive Vergleichswert.  Der Vergleich müsste
> "if ((uint8_t)(a-b) > 63)" oder "if ((unsigned)a-b > 63)" lauten[1].
>

okay. verstehe ich zwar im prinzip, aber mir ist nicht klar, warum dann 
aus meinem uint8_t ein signed int wird. kannst du mir das vielleicht 
noch erklären.
Das mit dem Überlauf verstehe ich. Stimmt eigentlich.
Ich habe die Variablen jetzt als unsigned int deklariert, dann geht es.
sorry, wenn ich das jetzt noch nicht ganz kapiere.



> 2) Deine Schleife macht nichts, außer die Variable "schritt" zu
> verändern - da kann der Compiler ordentlich optimieren - evtl bleibt
> nichts übrig ...

Ja ich mache schon noch was damit, dass habe ich raus geschnitten, weil 
ich es nicht für wichtig hielt. im Prinzip eben nur drei Farben bei RGB 
leds durch schalten. Dabei schreibe ich aber nicht in die verwendeten 
Variablen rein, daher hab ichs weg gelassen.
Wenn du magst, kann ich den ganzen code rein tun, ist halt dann recht 
lang.

von Stefan H. (stefan_h143)


Lesenswert?

Samuel C. schrieb:
> Krude seh ich ein. Aber löst das Problem (fast) ohne weiteres bis zum
> Verlöschen unseres Zentralgestirns.

Danke für den Lachanfall :)

wills halt so lernen, wie es sich gehört ;)

von foobar (Gast)


Lesenswert?

> aber mir ist nicht klar, warum dann aus meinem uint8_t ein signed int wird.

Ist halt eine Regel in C: wenn der kleinere Datentyp in ein "int" passt, 
wird es ein "int".  Google mal nach "C type promotion".

Lustig wird's z.B. bei "uint16_t": je nachdem, was für eine int-Größe 
das System hat, wird es ein "signed int" (32-Bit-ints) oder ein 
"unsigned int" (16-Bit-ints).

> Ja ich mache schon noch was damit, dass habe ich raus geschnitten,

Dann ist's ok - wollte nur drauf hinweisen.

von Viktor B. (coldlogic)


Lesenswert?

Hey Stefan,

soweit ich das verstanden habe, willst du jedes Mal, wenn die Variable 
sechzehn_ms 63 Schritte macht, den flag "Schritt" weiterschalten. Könnte 
man nicht einfach 64 Schritte machen und eine Modulo-Operation nehmen? 
Mit einer Potenz von 2 ist die Operation gar nicht mal so schwer.
1
  while (1) 
2
    {
3
      if (!(sechzehn_ms%64))
4
        {
5
        //Schritt weiter schalten
6
        if (schritt <2 )
7
        {
8
          schritt++;
9
        } else
10
        {
11
          schritt=0;
12
        }
13
      }
14
    }
15
  }

von Veit D. (devil-elec)


Lesenswert?

Hallo,

ich verstehe noch nicht warum sechzehn und zeit_eins zu int wird, wenn 
beide mit uint8_t deklariert sind. Wozu gibt man Datentypen an wenn sie 
ignoriert werden. Zudem sehe ich wie sechzehn im Bytebereich sauber 
überläuft und zeit_eins stehen bleibt.

von Apollo M. (Firma: @home) (majortom)


Lesenswert?

Stefan H. schrieb:
> //Globale Variablen
> uint8_t schritt = 0; //Schrittvariable
> volatile uint8_t sechzehn_ms = 0; // alle 16ms ein Inkrement
> uint8_t zeit_eins = 0; // vergangene Zeit f�r schritt

... ich "hasse" redundanz!

- globale variables werden immer automatisch initialisiert mit zero.

- volatile ist hier auch redundant und der sinn dazu unsinnig erklärt

und die weit verbreite "unsitte" singel anweisungen e.g. bei if/else zu 
klammern ist zu mindest hässlich/unästhetisch.


weg damit!


mt

von Irgend W. (Firma: egal) (irgendwer)


Lesenswert?

Irgendwie würde ich das ganze andersherum aufziehen damit man diese 
ganze merkwürdige Rechnerei garnicht erst benötigt.
1
uint8_t schritt = 0; //Schrittvariable
2
uint8_t sechzehn_ms; // alle 16ms ein Inkrement
3
volatile uint8_t tu_was; // vergangene Zeit für schritt
4
5
int main(void)
6
{  
7
  tu_was = false;
8
  sechzehn_ms = 0;
9
10
  Timer init...
11
12
  while (true) 
13
  {
14
    if (tu_was)
15
    {
16
      tu_was = false;
17
18
      //Schritt weiter schalten
19
      if (schritt < 2)
20
      {
21
        ++schritt;
22
      }
23
      else
24
      {
25
        schritt = 0;
26
      }
27
    }
28
29
    GehSchlafenBisZumNaechstenEvent();
30
  }
31
}
32
33
ISR (TIMER0_OVF_vect)
34
{
35
  ++sechzehn_ms;
36
  if (sechzehn_ms >= 63)
37
  {
38
    sechzehn_ms = 0;
39
    tu_was = true;
40
  }
41
}

Denn ganzen Block mit "schritt" wird dir der Compiler/Linker natürlich 
entsorgen wenn du damit nichts vernünftiges Anfängst.

von E. H. (emax)


Lesenswert?

In die etwas verzwickte Logik habe ich mich jetzt nicht rein gedacht. 
Aber ginge das nicht:


#include <util/atomic.h>

// ...

while (1)
{
    ATOMIC_BLOCK(ATOMIC_FORCEON)
    {
        if (sechzehn_ms >= 63)
        {
            sechzehn_ms=0;
            ++schritt;
        }
    }

    if (schritt == 2)
        schritt=0;

    // .. schritt auswertung

} // end while

von Apollo M. (Firma: @home) (majortom)


Lesenswert?

Stefan H. schrieb:
> verstehe ich zwar im prinzip, aber mir ist nicht klar, warum dann
> aus meinem uint8_t ein signed int wird. kannst du mir das vielleicht
> noch erklären.

google mal int promotion rules!

von guest (Gast)


Lesenswert?

foobar schrieb:
>> "if ((uint8_t)(a-b) > 63)" oder "if ((unsigned)a-b > 63)"
>
> Noch ne dritte Variante: "if (a-b > 63u)"

Und noch eine:
1
uint8_t t = a - b;
2
if( t > 63 )

von foobar (Gast)


Lesenswert?

> ich verstehe noch nicht warum sechzehn und zeit_eins zu int wird, wenn
> beide mit uint8_t deklariert sind. Wozu gibt man Datentypen an wenn sie
> ignoriert werden.

Weil in C immer mit mindestens int-Größe gerechnet wird.  Die 
abstrakte Maschine, die C beschreibt, kann keine Arithmethik mit Zahlen 
kleiner als int.  Datentypen kleiner als int (char/short) dienen nur als 
"Speichergröße" - nur da existieren sie in der reduzierten Form.  Dass 
meist doch 8-Bit-Arithmetik rauskommt, ist dem Optimizer zu verdanken.

Das gleiche passiert übrigens auch bei Fließkommazahlen (immer double).

Ein weitere Grund für Datentypen ist die Fehlererkennung - man gibt dem 
Compiler mehr Infos, mit denen er arbeiten kann.  Wenn du das nicht 
brauchst, geht zurück nach B oder gar BCPL ;-)

von Teo D. (teoderix)


Lesenswert?

Stefan H. schrieb:
> will eigentlich nur delay
> durch eine Timerabfrage ersetzten.

So?
1
volatile uint8_t sechzehn_ms_flag;
2
3
while (1) {
4
5
if(sechzehn_ms_flag){
6
   sechzehn_ms_flag=0;
7
   ....
8
   ...
9
  }
10
}
11
12
ISR (TIMER0_OVF_vect)
13
static uint8_t sechzehn_ms=63;
14
{
15
  if (!sechzehn_ms--){
16
     sechzehn_ms=63;
17
     sechzehn_ms_flag=1;
18
}

von Apollo M. (Firma: @home) (majortom)


Lesenswert?

Teo D. schrieb:
> ISR (TIMER0_OVF_vect)
> static uint8_t sechzehn_ms=63;
> {

ISR (TIMER0_OVF_vect) {
  static uint8_t sechzehn_ms=63;
  ...
}

ansonsten alles viel besser als der mist vorher!

von Veit D. (devil-elec)


Lesenswert?

Hallo,

aha. Danke. Das Problem war mir bis jetzt in der Form nicht bewußt das 
trotz Byte Angabe in int gewandelt wird. Das Literale wie 55+77 in int 
gerechnet werden wußte ich dagegen. Nur hier gibt man keine Datentypen 
an. Deswegen meine Verwunderung.

Auf jedenfall gut zu wissen und nochmal darauf hingewiesen zu werden.

von Nop (Gast)


Lesenswert?

Apollo M. schrieb:

> - volatile ist hier auch redundant

Ist es nicht. Wann immer man eine Variable zwischen ISR und 
Hauptschleife teilt, MUSS sie volatile sein, weil der Compiler sie sonst 
wegoptimieren kann.

von Veit D. (devil-elec)


Lesenswert?

Teo und Apollo,

wenn man nur ein Einziges Timerintervall benötigt mag das so mehr Sinn 
machen. Benötigt man mehrere verschiedene Zeitintervalle kommt man 
glaube ich mit dem allgemeinen globalen Counter besser weg. Ansonsten 
hätte man dann gefühlt Tausende globale Flags. Oder nicht?

von Apollo M. (Firma: @home) (majortom)


Lesenswert?

Nop schrieb:
> Ist es nicht. Wann immer man eine Variable zwischen ISR und
> Hauptschleife teilt, MUSS sie volatile sein, weil der Compiler sie sonst
> wegoptimieren kann.

so so  ... dann google und lese nochmal.

use of volatile ...
wenn und nur wenn die variable sich zwischen zwei zugriffen ändern kann, 
daher durch hw geändert wird, was ja der compiler sonst nicht wissen 
kann und diese dann als konstante ansieht und e.g. in ein register 
ablegt.

daher volatile erzwingt, dass die variable vor jeden zugriff immer neu 
eingelesen wird!

... und wegoptimieren wäre auch unsinn, weil nicht weg sondern wird nur 
einmal eingelesen.


mt

von Stefan H. (stefan_h143)


Lesenswert?

foobar schrieb:
> Google mal nach "C type promotion".

Apollo M. schrieb:
> google mal int promotion rules!

Hab ich und ich wurde erleuchtet :) Danke für euren Hinweiß! Damit hat 
sich meine eigentliche Frage erledigt.

Vielen Dank für eure Hilfe :) Immer wieder gut, wenn man dann weiter 
kommt!

von Nop (Gast)


Lesenswert?

Apollo M. schrieb:

> wenn und nur wenn die variable sich zwischen zwei zugriffen ändern kann

Das ist der Fall, nämlich zwischen zwei Durchläufen der while-Schleife 
in Stefans main().

Da ISRs nie im Callpath stehen, kann der Compiler annehmen, die Variable 
werde mit 0 initialisiert, nie verändert, und die ganze Auswertung in 
main kann wegoptimiert werden.

von Stefan H. (stefan_h143)


Lesenswert?

Veit D. schrieb:
> Teo und Apollo,
>
> wenn man nur ein Einziges Timerintervall benötigt mag das so mehr Sinn
> machen. Benötigt man mehrere verschiedene Zeitintervalle kommt man
> glaube ich mit dem allgemeinen globalen Counter besser weg. Ansonsten
> hätte man dann gefühlt Tausende globale Flags. Oder nicht?

Ja genau. Ich will dann halt mehrere Zeiten abfragen, daher wollte ich 
das eben so machen.

von Apollo M. (Firma: @home) (majortom)


Lesenswert?

Nop schrieb:
>> - volatile ist hier auch redundant
>
> Ist es nicht. Wann immer man eine Variable zwischen ISR und
> Hauptschleife teilt, MUSS sie volatile sein, weil der Compiler sie sonst
> wegoptimieren kann.

zur erleuchtung ...
https://barrgroup.com/Embedded-Systems/How-To/C-Volatile-Keyword

: Bearbeitet durch User
von Nop (Gast)


Lesenswert?

Apollo M. schrieb:

> zur erleuchtung ...
> https://barrgroup.com/Embedded-Systems/How-To/C-Volatile-Keyword

Hast Du den Link eigentlich selber sinnerfassend gelesen?

Im Abschnitt "Proper Use of C's volatile Keyword" findet sich:
2. Global variables modified by an interrupt service routine

Das ist exakt die Situation in Stefans Code.

von Apollo M. (Firma: @home) (majortom)


Lesenswert?

Nop schrieb:
> Da ISRs nie im Callpath stehen, kann der Compiler annehmen, die Variable
> werde mit 0 initialisiert, nie verändert, und die ganze Auswertung in
> main kann wegoptimiert werden.

korrekt! und rückwärtsrolle ...

von Veit D. (devil-elec)


Lesenswert?

Hallo,

ich weiß auch nicht was es am "volatile sechzehn_ms" auszusetzen gibt.

Es ist absolut notwendig und korrekt. Wird eine Variable außerhalb des 
eigentlichen Programmablaufes geändert, was in einer ISR der Fall ist, 
dann muss diese volatile sein. Ist diese Variable zudem größer ein Byte, 
dann muss der Zugriff im Programmablauf darauf atomar erfolgen. 
Ansonsten könnte es passieren das low und high Byte nicht mehr 
zusammenpassen, weil zwischendurch ein neuer Interrupt den Wert 
verändert hat.

von Stefan F. (Gast)


Lesenswert?

foobar schrieb:
> Weil in C immer mit mindestens int-Größe gerechnet wird.

Gerade mal auf dem PC ausprobiert:
1
#include <stdint.h>
2
#include <stdio.h>
3
4
int main()
5
{
6
    uint8_t jetzt=1;
7
    uint8_t warte_seit=192;
8
    uint8_t diff=jetzt-warte_seit;
9
    printf("1) %d - %d = %d\n",jetzt,warte_seit,diff);
10
    printf("2) %d - %d = %d\n",jetzt,warte_seit,jetzt-warte_seit);
11
    printf("3) %d - %d >= 63 is %d\n",jetzt,warte_seit, jetzt-warte_seit >= 63);
12
    printf("4) %d - %d >= 63 is %d\n",jetzt,warte_seit, jetzt-warte_seit >= (uint8_t)63);
13
    printf("5) %d - %d >= 63 is %d\n",jetzt,warte_seit, (uint8_t)(jetzt-warte_seit) >= 63);
14
}

Erzeugt unter Linux diesen Output:
1
sfrings@stefanpc:~/Test$ gcc test.c 
2
sfrings@stefanpc:~/Test$ ./a.out 
3
1) 1 - 192 = 65
4
2) 1 - 192 = -191
5
3) 1 - 192 >= 63 is 0
6
4) 1 - 192 >= 63 is 0
7
5) 1 - 192 >= 63 is 1

1) bestätigt, dass die Zeit-Differenz durch die Subtraktion trotz 
Überlauf korrekt berechnet wird.
2) bestätigt, dass der Compiler automatisch mit mehr als 8bit rechnet, 
wenn wir es ihm nicht anders vorgeben. (-191 ist größer als ein Byte).
3) bestätigt das eigentliche Problem des TO: Der if-Ausdruck 
funktioniert nicht wie erwartet.
4) zeigt, dass es nichts nützt, das integer Literal 63 auf unit8_t zu 
casten.
5) zeigt, dass es hilft, die Subtraktion auf uint8_t zu casten.

Veit D. schrieb:
> Das Problem war mir bis jetzt in der Form nicht bewußt
> Auf jedenfall gut zu wissen und nochmal darauf hingewiesen zu werden.

Geht mir genau so.

Ich habe das bisher immer nur mit mindestens 16bit Integer gemacht, 
daher war ich mit diesem Problem bisher noch nie konfrontiert.

von Arno (Gast)


Lesenswert?

Stefanus F. schrieb:
> Gerade mal auf dem PC ausprobiert:
> #include <stdint.h>
> #include <stdio.h>
>
> int main()
> {
>     uint8_t jetzt=1;
>     uint8_t warte_seit=192;
>     uint8_t diff=jetzt-warte_seit;
>     printf("1) %d - %d = %d\n",jetzt,warte_seit,diff);
>     printf("2) %d - %d = %d\n",jetzt,warte_seit,jetzt-warte_seit);
>     printf("3) %d - %d >= 63 is %d\n",jetzt,warte_seit, jetzt-warte_seit
>>= 63);
>     printf("4) %d - %d >= 63 is %d\n",jetzt,warte_seit, jetzt-warte_seit
>>= (uint8_t)63);
>     printf("5) %d - %d >= 63 is %d\n",jetzt,warte_seit,
> (uint8_t)(jetzt-warte_seit) >= 63);
> }

Die nächste Fehlerquelle lauert dann im Format-String von printf - wenn 
der nicht zum tatsächlichen (int), aber zum gewünschten (uint8_t) 
Datentyp passt, kann die richtige Ausgabe herauskommen. Könnte zum 
Beispiel passieren, wenn du %d durch %hhu ersetzt.

MfG, Arno

von Apollo M. (Firma: @home) (majortom)


Lesenswert?

Stefanus F. schrieb:
> Ich habe das bisher immer nur mit mindestens 16bit Integer gemacht,
> daher war ich mit diesem Problem bisher noch nie konfrontiert.

... interessant, das steht fast in jeder docu über c oder specific 
compiler drin, im sinne von vorsicht falle mit int promotion - wohl auch 
so definiert im c-standard.

ist das bekannt ... struct/array init with zero? hatte ich noch nicht 
auf meinen schirm oder wo im code gesehen.


void test() {
   struct_t struct_test = {}; //initialize with zero
   int array_test[42] = {};

   ...
}

attached gutes buch dazu aus meiner bibo, ... schnell zugreifen bevor 
wieder weg!

von W.S. (Gast)


Lesenswert?

Stefan H. schrieb:
> ISR (TIMER0_OVF_vect)
> {
>   sechzehn_ms++;
> }
>
> Ich möchte im Prinzip nur ca alle Sekunde den Schritt um eins weiter
> zählen

Also erst einmal: Was hat ein Timer (d.h. ein Hardware-Peripheriecore) 
mit C zu tun? Antwort: garnichts.

Und dann:
Anstelle deiner wirklich extrem minimalistischen ISR solltest du dir 
eine Art Systemuhr einrichten.

Also wäre es gut, wenn deine o.g. ISR in einem Takt aufgerufen würde, 
der ein ganzzahliger Teil von 1 Sekunde ist. Zum Beispiel 10 ms oder 1 
ms (für ganz Eilige), oder 1/2 1/4..1/128 Sekunde.

Dann zählst du in deiner ISR einfach eine globale Variable hoch - und 
schon hast du die Systemzeit in Sekunden oder eben deinem gewählten 
Takt. Und diese globale Variable darf jeder lesen, aber nicht 
beschreiben.

Und wenn das Ganze mehr als nur 1 Tag laufen soll, dann mußt du eben in 
der ISR auch zur Mitternacht den Tick nullen und den Datumzähler um 1 
erhöhen.

Du scheinst mir nen AVR zu benutzen, also sollte deine ISR auch eine 
Zeitausgabe beinhalten, etwa so:
1
globale Vars:
2
unsigned long Time;
3
bool bitte;
4
unsigned long da_isses;
5
6
ISR: ...
7
     ++Time;
8
     if (bitte)
9
     { da_isses = Time;
10
       bitte = false;
11
     }
12
   ... ISR ende

So, damit können andere Teile deiner Firmware die aktuelle Uhrzeit (in 
Ticks) abholen, indem sie bitte = true setzen und warten, bis bitte 
wieder false ist.

W.S.

von foobar (Gast)


Lesenswert?

Wer noch etwas üben möchte: Wo ist der Unterschied zwischen den drei 
folgenden Varianten (sie erreichen das gewünschte Ziel auf 
unterschiedliche Weise) und welche ist zu bevorzugen:

// uint8_t a, b;
1) if ((uint8_t)(a-b) > 63)
2) if ((unsigned)a-b > 63)
3) if (a-b > 63u)

von Stefan F. (Gast)


Lesenswert?

Apollo M. schrieb:
> attached gutes buch dazu

Cool, danke

von Stefan F. (Gast)


Lesenswert?

W.S. schrieb:
> Und wenn das Ganze mehr als nur 1 Tag laufen soll, dann mußt du eben in
> der ISR auch zur Mitternacht den Tick nullen und den Datumzähler um 1
> erhöhen.

Er hat das mit der Subtraktion prinzipiell richtig gemacht. Einfach die 
Variable breiter machen halte ich für Pfusch, denn

a) Größere Variablen sind langsamer im Zugriff
b) Laufen auch irgendwann über

von foobar (Gast)


Lesenswert?

> indem sie bitte = true setzen und warten, bis bitte wieder false ist

Dann muss "bitte" und "da_isses" aber volatile sein ...


Die ursprüngliche Variante von Stefan ist schon OK und üblich - das 
Linux-Kernel z.B. macht es genauso (der globale Zähler heißt da 
"jiffies") und einige Systeme haben so einen Counter direkt in der 
Hardware.  Ja, man kann bei der Benutzung Fehler machen, insb wenn man 
wrap-around und atomic-access berücksichtigen muß - im Linux-Kernel 
haben sie deshalb irgendwann Macros eingeführt (time_after, 
get_jiffies_64, etc).

von Stefan F. (Gast)


Lesenswert?

foobar schrieb:
> 1) if ((uint8_t)(a-b) > 63)
> 2) if ((unsigned)a-b > 63)
> 3) if (a-b > 63u)
> Wo ist der Unterschied?

Werfen wir dazu einen Blick in den Assembler-Code, den der Compiler 
generiert. Ich musste ein bisschen tricksen, damit der Optimizer den 
wirkungslosen Code nicht komplett weg optimiert:
1
#include <stdint.h>
2
#include <stdio.h>
3
#include <stdbool.h>
4
#include <avr/io.h>
5
6
__attribute__((noinline)) bool test1(uint8_t a, uint8_t b)
7
{
8
   return (uint8_t)(a-b) > 63;
9
}
10
11
__attribute__((noinline)) bool test2(uint8_t a, uint8_t b)
12
{
13
    return (unsigned)a-b > 63;
14
}
15
16
__attribute__((noinline)) bool test3(uint8_t a, uint8_t b)
17
{
18
    return a-b > 63u;
19
}
20
21
int main()
22
{
23
    PORTB=test1(1,192);
24
    PORTB=test2(2,193);
25
    PORTB=test3(3,194);
26
}

Ergibt folgendes Listing:
1
...
2
00000080 <test1>:
3
  80:  98 2f         mov  r25, r24
4
  82:  96 1b         sub  r25, r22
5
  84:  81 e0         ldi  r24, 0x01  ; 1
6
  86:  90 34         cpi  r25, 0x40  ; 64
7
  88:  08 f4         brcc  .+2        ; 0x8c <test1+0xc>
8
  8a:  80 e0         ldi  r24, 0x00  ; 0
9
  8c:  08 95         ret
10
11
0000008e <test2>:
12
  8e:  28 2f         mov  r18, r24
13
  90:  30 e0         ldi  r19, 0x00  ; 0
14
  92:  26 1b         sub  r18, r22
15
  94:  31 09         sbc  r19, r1
16
  96:  81 e0         ldi  r24, 0x01  ; 1
17
  98:  20 34         cpi  r18, 0x40  ; 64
18
  9a:  31 05         cpc  r19, r1
19
  9c:  08 f4         brcc  .+2        ; 0xa0 <test2+0x12>
20
  9e:  80 e0         ldi  r24, 0x00  ; 0
21
  a0:  08 95         ret
22
23
000000a2 <test3>:
24
  a2:  28 2f         mov  r18, r24
25
  a4:  30 e0         ldi  r19, 0x00  ; 0
26
  a6:  26 1b         sub  r18, r22
27
  a8:  31 09         sbc  r19, r1
28
  aa:  81 e0         ldi  r24, 0x01  ; 1
29
  ac:  20 34         cpi  r18, 0x40  ; 64
30
  ae:  31 05         cpc  r19, r1
31
  b0:  08 f4         brcc  .+2        ; 0xb4 <test3+0x12>
32
  b2:  80 e0         ldi  r24, 0x00  ; 0
33
  b4:  08 95         ret
34
35
000000b6 <main>:
36
  b6:  60 ec         ldi  r22, 0xC0  ; 192
37
  b8:  81 e0         ldi  r24, 0x01  ; 1
38
  ba:  0e 94 40 00   call  0x80  ; 0x80 <test1>
39
  be:  85 b9         out  0x05, r24  ; 5
40
  c0:  61 ec         ldi  r22, 0xC1  ; 193
41
  c2:  82 e0         ldi  r24, 0x02  ; 2
42
  c4:  0e 94 47 00   call  0x8e  ; 0x8e <test2>
43
  c8:  85 b9         out  0x05, r24  ; 5
44
  ca:  62 ec         ldi  r22, 0xC2  ; 194
45
  cc:  83 e0         ldi  r24, 0x03  ; 3
46
  ce:  0e 94 51 00   call  0xa2  ; 0xa2 <test3>
47
  d2:  85 b9         out  0x05, r24  ; 5
48
  d4:  80 e0         ldi  r24, 0x00  ; 0
49
  d6:  90 e0         ldi  r25, 0x00  ; 0
50
  d8:  08 95         ret
51
...

Wie wir sehen ist Variante 1 deutlich kürzer, da auf 8bit Berechnung 
reduziert.

Die anderen beiden rechnen exakt gleich mit 16bit unsgined Integer. 
Offenbar spielt es keine Rolle, ob die linke oder rechte Seite vom ">" 
Operator den unsigned Integer vorgibt.

von volatile (Gast)


Lesenswert?

Apollo M. schrieb:
>
> attached gutes buch dazu aus meiner bibo, ... schnell zugreifen bevor
> wieder weg!

Scheiß Buch wenn es im Index nicht einmal volatile gibt.

von foobar (Gast)


Lesenswert?

> Werfen wir dazu einen Blick in den Assembler-Code, den der Compiler
> generiert.

Mir ging es eher um den konzeptuellen Unterschied: warum liefern diese 
Varianten das gewünschte Ergebnis, wo findet welche Konvertierung statt, 
mit welchem Typ werden die beiden Operationen durchgeführt, entstehen da 
möglicherweise Probleme?

Welcher Kode daraus generiert wird, steht auf einem anderen Blatt - im 
Optimalfall kommt bei allen drei Varianten das gleiche raus.

> Offenbar spielt es keine Rolle, ob die linke oder rechte Seite vom ">"
> Operator den unsigned Integer vorgibt.

Korrekt.  Der kleinere Operand wird immer zum größeren konvertiert 
(wobei signed_int < unsigned_int < signed_long < unsigned_long).

von guest (Gast)


Lesenswert?

foobar schrieb:
> (wobei signed_int < unsigned_int < signed_long < unsigned_long).

Vorsicht. Wenn int und long die gleiche Breite haben stimmt das u.U. 
nicht mehr.

von Apollo M. (Firma: @home) (majortom)


Lesenswert?

volatile schrieb:
> Scheiß Buch wenn es im Index nicht einmal volatile gibt.

für dich newbie noch eine "lesehilfe" und keinen mucks mehr!

von Nop (Gast)


Lesenswert?

Zum "atomic access" bei 16bit-Variablen auf einer 8-Bit-CPU kann man 
auch ganz einfach ein sequential lock bei der Abfrage nutzen. Dann muß 
man keine Interrupts abschalten:
1
volatile uint16_t systime;
2
...
3
4
uint16_t Get_Systime(void) {
5
    uint16_t prev, curr;
6
    
7
    curr = systime;
8
    do {
9
        prev = curr;
10
        curr = systime;
11
    } while (prev != curr);
12
    
13
    return curr;
14
}

von Codix (Gast)


Lesenswert?

Mal ein Beispiel aus der Praxis:
1
/*
2
#define F_CPU  3686411UL -> Compiler Option!
3
*/
4
5
volatile uint_fast64_t systick;
6
extern uint8_t TimeOut;
7
8
// Sekundentakt
9
ISR(TIMER1_COMPA_vect)
10
{
11
    PORTD ^= ( 1 << PD5); /* Toggle LED */
12
13
    if ( systick )
14
        systick++;
15
16
    if ( TimeOut )
17
        TimeOut--;  
18
19
    
20
21
22
}
23
void Init_Timer(void)
24
{
25
  
26
 /* Init Timer0 and Timer1*/
27
   tick2 = 0;
28
29
   TIMSK  |= (1 << OCIE0)| (1 << OCIE1A) | ( 1 << OCIE2);  // OCIE0 and
30
                              // OCIE1A int
31
                              // OCIE2 int
32
   TCCR0 |= PRESCALER0| (1 << WGM01);  // Prescaler and CTC Mode WGM01 
33
    TCNT0  = 0;         
34
  OCR0 = 4;              // 3,6 => 1 ms
35
// This is the central clock with 1 Hz 
36
  TCCR1B = PRESCALER1 | (1 << WGM12);// Prescaler 1024 and CTC Mode
37
  TCNT1 = 0;                // Set to zero
38
  OCR1A = (F_CPU/1024);        // calculate the 1 second value = 3600
39
40
// Timer2 is used to debounce the keys
41
  TCCR2 |= PRESCALER2 | (1 << WGM21); // Prescaler 1024 and CTC Mode
42
  TCNT2 = 0;
43
  OCR2 = (F_CPU/1024)/20;        // 20 ms
44
45
    sei();
46
}
Ist für einen ATmega32

von volatile (Gast)


Lesenswert?

Apollo M. schrieb:
> volatile schrieb:
>> Scheiß Buch wenn es im Index nicht einmal volatile gibt.
>
> für dich newbie noch eine "lesehilfe" und keinen mucks mehr!

Sie sollten die Bücher selbst lesen um volatile zu verstehen.

von Stefan F. (Gast)


Lesenswert?

foobar schrieb:
> warum liefern diese
> Varianten das gewünschte Ergebnis

Weil sie einen Algorithmus erzwingen, der ohne Vorzeichen arbeitet.

von Bernhard R. (bernhard_r874)


Lesenswert?

Codix schrieb:
> if ( systick )
>         systick++;
>
>     if ( TimeOut )
>         TimeOut--;


Kleiner Tipp:
1
    systick++;
2
3
    if ( TimeOut && (TimeOut > 1))
4
        TimeOut--;

Die Abfrage von systick kann man sich sparen.
TimeOut kann man zwar machen wie vorgeschlagen, ich bevorzuge meine 
Lösung :)

Wenn TimeOut == 1, dann ist er abgelaufen. TimeOut == 0 ist 
abgeschaltet.
So kann man in der Mainloop eine Aktion bei Timeout entweder permanent 
ausführen oder, wenn TimeOut auf 0 gesetzt wird, nur ein Mal.

: Bearbeitet durch User
von Apollo M. (Firma: @home) (majortom)


Lesenswert?

Bernhard R. schrieb:
> if ( TimeOut && (TimeOut > 1))
>         TimeOut--;

... auch meine bevorzugte lsg!
ich würde nur noch die "klammerorgie" weglassen, da der rang der 
operatoren das regelt, bei langen bedingungen wird das sonst schnell 
unübersichtlich

if (TimeOut && TimeOut>1) TimeOut--;

von Frank M. (ukw) (Moderator) Benutzerseite


Lesenswert?

Apollo M. schrieb:
> attached gutes buch dazu aus meiner bibo, ... schnell zugreifen bevor
> wieder weg!

Apollo M. schrieb:
> für dich newbie noch eine "lesehilfe" und keinen mucks mehr!

Es ist eine Urheberrechtsverletzung, so etwas einfach hier reinzusetzen. 
Ich ermahne eindringlich, dieses zukünftig zu unterlassen. Nicht nur, 
dass Andreas durch solche Aktionen rechtliche Schwierigkeiten bekommen 
könnte, sondern auch Du. Hinzu kommt, dass solche "Aktionen" auch den 
Ruf dieses Forums schädigen. Das ist hier keine Raubkopierer-Plattforum.

Bedenke auch, dass Du mit solchen Weitergaben von urheberrechtlich 
geschütztem Material den Autor des Werkes nicht nur schädigst, sondern 
auch den nötigen Respekt gegenüber seiner Arbeit vermissen lässt.

Ich appelliere daher eindringlich, so etwas grundsätzlich zu 
unterlassen. Bei Wiederholung wird es zwangsläufig zu Gegenmaßnahmen 
kommen müssen.

: Bearbeitet durch Moderator
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.