Forum: Mikrocontroller und Digitale Elektronik Timer Problem


von Pepe (Gast)


Lesenswert?

Hallo hab da ein kleines Problem,

und zwar will ich meinen Timer in eine Endlosschleife einfügen, aber 
egal wo ich die Schleife setze, irgendwie klappt es nicht.
Entweder werden die Timer nicht abgearbeitet oder aber die LED welche an 
Port B0 angeschlossen ist blinkt im falschen takt und das Programm wird 
nur einmal abgearbeitet. Hoffe jemand kann mir helfen.

Gruß Paul

#include <avr/io.h>
#include <avr/interrupt.h>

//Variablen für die Zeit
volatile unsigned int  millisekunden;
volatile unsigned int sekunde;
volatile unsigned int minute;
volatile unsigned int stunde;

int main(void)
{
DDRB = 0xff;
  // Timer 0 konfigurieren
  TCCR0 = (1<<WGM01); // CTC Modus
  TCCR0 |= (1<<CS01); // Prescaler 8
  // ((1000000/8)/1000) = 125
  OCR0 = 125-1;

  // Compare Interrupt erlauben
  TIMSK |= (1<<OCIE0);

  // Global Interrupts aktivieren
  sei();

  while(1)
  {
    /*Hier kann die aktuelle Zeit
      ausgeben werden*/
  }
}

/*
Der Compare Interrupt Handler
wird aufgerufen, wenn
TCNT0 = OCR0A = 125-1
ist (125 Schritte), d.h. genau alle 1 ms
*/

  ISR (TIMER0_COMP_vect)
  {

      millisekunden++;
      if(millisekunden == 16000)
      {
        sekunde++;
        millisekunden = 0;
        }

    sekunde++;
    if(sekunde == 60)
        {
         minute++;
          sekunde = 0;
        }

    stunde++;
        if(minute == 1)
        {
    PORTB |= (1<<PB0);
        }

    if(minute == 1)
        {
        PORTB &= ~(1<<PB0);
        }

    }
}

von Floh (Gast)


Lesenswert?

Pepe schrieb:
> blinkt im falschen takt und das Programm wird
> nur einmal abgearbeitet.

Wie schnell blinkt sie?
Woran erkennst du, das das Programm nur einmal abgearbeitet wird?
:-)

von Pepe (Gast)


Lesenswert?

Die LED blinkt einmal auf und anstelle einer Minute blinkt sie 
vielleicht 10 sekunden auf. Wenn ich die Schleife nicht drin habe so wie 
oben funktioniert es.

von Frank B. (foobar)


Lesenswert?

"sekunde" nur innerhalb des "if(millisekunden == 16000)" erhöhen, nicht 
unkonditional danach nochmal, und dasselbe für die anderen Variablen.

Je nach Microcontroller musst du auch ein Interrupt-Statusregister 
auslesen und beschreiben, wenn der Interrupt ausgelöst wurde, um den 
Interruptzustand zurückzusetzen, da der sonst nicht nochmal ausgelöst 
wird (siehe Datenblatt).

von Pepe (Gast)


Lesenswert?

Vielen Dank für die schnellen Antworten.

Hast du das etwa so gemeint???

#include <avr/io.h>
#include <avr/interrupt.h>

//Variablen für die Zeit
volatile unsigned int  millisekunden;
volatile unsigned int sekunde;
volatile unsigned int minute;
volatile unsigned int stunde;

int main(void)
{
DDRB = 0xff;
  // Timer 0 konfigurieren
  TCCR0 = (1<<WGM01); // CTC Modus
  TCCR0 |= (1<<CS01); // Prescaler 8
  // ((1000000/8)/1000) = 125
  OCR0 = 125-1;

  // Compare Interrupt erlauben
  TIMSK |= (1<<OCIE0);

  // Global Interrupts aktivieren
  sei();

  while(1)
  {
    /*Hier kann die aktuelle Zeit
      ausgeben werden*/
  }
}

/*
Der Compare Interrupt Handler
wird aufgerufen, wenn
TCNT0 = OCR0A = 125-1
ist (125 Schritte), d.h. genau alle 1 ms
*/

  ISR (TIMER0_COMP_vect)
  {

      millisekunden++;

      if(millisekunden == 16000)
      {
        sekunde++;
        millisekunden = 0;
        }

    if(sekunde == 60)
        {
         minute++;
          sekunde = 0;
        }

        if(minute == 1)
        {
    PORTB |= (1<<PB0);
        }

    if(minute == 1)
        {
        PORTB &= ~(1<<PB0);
        }


}

Und wo müßte jetzt korrekterweise die Endlosschleife rein?

Nach:
  ISR (TIMER0_COMP_vect)
  {
???

Wie kann man einen Interruptzustand zurücksetzen?

Gruß
Pepe

von Karl H. (kbuchegg)


Lesenswert?

Pepe schrieb:
> Vielen Dank für die schnellen Antworten.
>
> Hast du das etwa so gemeint???

Im Grunde ja.

Aber überleg mal:


>   ISR (TIMER0_COMP_vect)
>   {
>
>       millisekunden++;
>
>       if(millisekunden == 16000)
>       {
>         sekunde++;
>         millisekunden = 0;
>         }
>
>     if(sekunde == 60)

Wann kann den sekunde überhaupt 60 werden?
Doch wohl nur dann, wenn vorher millisekunden 16000 war und daher 
sekunde um 1 hochgezählt wurde. Wenn das nicht der Fall war, dann kann 
sekunde auch nicht 60 sein.

>         {
>          minute++;
>           sekunde = 0;
>         }
>
>         if(minute == 1)
>         {
>     PORTB |= (1<<PB0);
>         }

wenn minute gleich 1 ist, dann schaltest du die LED EIN

>
>     if(minute == 1)
>         {
>         PORTB &= ~(1<<PB0);
>         }

wenn minute gleich 1 ist, dann schaltest du die LED AUS


In summe ergibt das:
möglichst komplizert: wenn Minute gleich 1 ist, dann die LED einschalten 
und gleich darauf wieder ausschalten. Und du denkst ernsthaft, dass du 
das sehen kannst, wenn die LED ein paar Nanosekunden leuchtet?


Der Rest stimmt schon.
Die Endlosschleife ist immer in main
Und den Interrupt brauchst du auch nicht zurücksetzen. Durch den Aufruf 
der ISR wird der automatisch zurückgesetzt.

von Stefan B. (stefan) Benutzerseite


Lesenswert?

1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
4
//Variablen für die Zeit
5
volatile unsigned int  millisekunden;
6
volatile unsigned int sekunde;
7
volatile unsigned int minute;
8
volatile unsigned int stunde;
9
10
int main(void)
11
{
12
  DDRB = (1<<PB0); // PB0 Ausgang
13
  // PB0 anfangs (1. Minute) default LOW
14
15
  // Timer 0 konfigurieren
16
  TCCR0 = (1<<WGM01); // CTC Modus
17
  TCCR0 |= (1<<CS01); // Prescaler 8
18
  // ((1000000/8)/1000) = 125
19
  OCR0 = 125-1;
20
21
  // Compare Interrupt erlauben
22
  TIMSK |= (1<<OCIE0);
23
24
  // Global Interrupts aktivieren
25
  sei();
26
27
  while(1)
28
  {
29
    /*Hier kann die aktuelle Zeit
30
      ausgeben werden*/
31
  }
32
}
33
34
/*
35
Der Compare Interrupt Handler wird aufgerufen, 
36
wenn TCNT0 = OCR0A = 125-1 ist (125 Schritte), 
37
d.h. genau alle 1 ms bei 1 MHz
38
*/
39
40
ISR (TIMER0_COMP_vect)
41
{
42
  millisekunden++;
43
  if(millisekunden == 16000) // F_CPU 16 MHz statt 1 MHz
44
  {
45
    millisekunden = 0;
46
    sekunde++;
47
    if(sekunde == 60)
48
    {
49
      sekunde = 0;
50
      minute++;
51
      PORTB ^= (1<<PB0); // PB0 beim Minutenwechsel toggeln
52
    }
53
    if(minute == 60)
54
    {
55
      minute = 0;
56
      stunde++;
57
    }
58
    if (stunde == 24)
59
    {
60
      stunde = 0;
61
    }
62
  }
63
}

von pepe (Gast)


Lesenswert?

So hab es jetzt noch einmal ein bisschen verändert:

Im Grunde genommen soll das ganze später eine Messung ergeben. Soll 4h 
messen und das für 1 min. Danach soll das ganze wieder von vorne 
beginnen.

Aber der Counter zählt ja weiter oder? Kann man den irgendwie wieder 
reseten?

Hier mal der Code:
1
{
2
  DDRB = (1<<PB0); // PB0 Ausgang
3
  // PB0 anfangs (1. Minute) default LOW
4
5
  // Timer 0 konfigurieren
6
  TCCR0 = (1<<WGM01); // CTC Modus
7
  TCCR0 |= (1<<CS01); // Prescaler 8
8
  // ((1000000/8)/1000) = 125
9
  OCR0 = 125-1;
10
11
  // Compare Interrupt erlauben
12
  TIMSK |= (1<<OCIE0);
13
14
  // Global Interrupts aktivieren
15
  sei();
16
17
  while(1)
18
  {
19
    /*Hier kann die aktuelle Zeit
20
      ausgeben werden*/
21
  }
22
}
23
24
/*
25
Der Compare Interrupt Handler wird aufgerufen, 
26
wenn TCNT0 = OCR0A = 125-1 ist (125 Schritte), 
27
d.h. genau alle 1 ms bei 1 MHz
28
*/
29
30
ISR (TIMER0_COMP_vect)
31
{
32
  while(1)
33
  {
34
    while(1)
35
    {
36
    millisekunden++;
37
    
38
  if(millisekunden == 16000) // F_CPU 16 MHz statt 1 MHz
39
    {
40
      millisekunden = 0;
41
  sekunde++;
42
  }
43
44
  if(sekunde == 60)
45
      {
46
      sekunde = 0;
47
        minute++;
48
      }
49
50
      if(minute == 60)
51
      {
52
        minute = 0;
53
        stunde++;
54
      }
55
56
      if (stunde == 24)
57
      {
58
        stunde = 0;
59
      }
60
61
    if(minute == 240)
62
      {
63
    PORTB |= (1<<PB0);
64
      }
65
66
    if(minute == 241)
67
      {
68
    PORTB &= ~(1<<PB0);
69
      }
70
71
    break;
72
73
  }
74
75
  }
76
77
}

Hab jetzt mal nen break drin in der Hoffnung das wemnn die zweite 
Schleife unterbrochen wird die erste wieder von 0 beginnt.
Aber wahrscheinlich muß erst mal der Counter genullt werden oder???

Gruß Andi

von pepe (Gast)


Lesenswert?

Das Problem ist ich kann den ISR (TIMER0_COMP_vect) nicht in die 
Schleife mit einfügen dann bekomme ich vom AVR Studio ne Fehlermeldung 
bei der Schleife.

von Stefan B. (stefan) Benutzerseite


Lesenswert?

Wie soll das funktionieren, wenn die Minuten von 0 bis 59 laufen

      if(minute == 60)
      {
        minute = 0;
        stunde++;
      }

und du sie hier auf 240 testen willst?

      if(minute == 240)
      {
        PORTB |= (1<<PB0);
      }

Das geht so nicht. Wenn du so arbeiten willst, brauchst du durchlaufende 
Minuten, keine "klassischen" Minuten wie bei der Uhr (0..59).

Logisch kannst du die vier Zeitvariablen nach der Messung zurücksetzen. 
Dabei daran denken, dass du auch den Hardware-Timercounter nullst.

von pepe (Gast)


Lesenswert?

Ok das ist einleuchtend. Und wie kann man diese Zeitvariablen 
zurücksetzen und den Counter?

von Frank B. (foobar)


Lesenswert?

Das macht so keinen Sinn mit der Schleife im Interrupt. Sinn des 
Interrupts ist es ja, daß der automatisch periodisch aufgerufen wird und 
du den möglichst schnell wieder verlässt, damit der in der nächsten 
Millisekunde wieder aufgerufen werden kann. Bei jedem Aufruf zählst du 
die nächste Millisekunde, falls der Kommentar stimmt, daß der Interrupt 
jede Millisekunde aufgerufen wird. In dem Fall auch "millisekunden == 
1000" testen.

Interruptzustand zurücksetzen hängt vom Chip und der 
Entwicklungsumgebung ab. Wo ich letztens einen Freescale-Chip getestet 
hatte, musst Bit 7 im RTCSC-Register setzen, damit der Interrupt erneut 
ausgelöst wurde. Kann aber sein, daß mit "ISR (TIMER0_COMP_vect)" das 
alles schon von alleine gemacht wird oder dein Microcontroller sowas 
nicht braucht. Daher Datenblatt zum Microcontroller lesen und die 
Erklärung zu "ISR" in der Compiler-Anleitung lesen.

von pepe (Gast)


Lesenswert?

So jetz noch einmal das ganze:
So müßte doch das Problem mit dem zählen gelöst sein.
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
4
//Variablen für die Zeit
5
volatile unsigned int  millisekunden;
6
volatile unsigned int sekunde;
7
volatile unsigned int minute;
8
volatile unsigned int stunde;
9
10
int main(void)
11
{
12
  DDRB = (1<<PB0); // PB0 Ausgang
13
  // PB0 anfangs (1. Minute) default LOW
14
15
  // Timer 0 konfigurieren
16
  TCCR0 = (1<<WGM01); // CTC Modus
17
  TCCR0 |= (1<<CS01); // Prescaler 8
18
  // ((1000000/8)/1000) = 125
19
  OCR0 = 125-1;
20
21
  // Compare Interrupt erlauben
22
  TIMSK |= (1<<OCIE0);
23
24
  // Global Interrupts aktivieren
25
  sei();
26
27
  while(1)
28
  {
29
    /*Hier kann die aktuelle Zeit
30
      ausgeben werden*/
31
  }
32
}
33
34
/*
35
Der Compare Interrupt Handler wird aufgerufen, 
36
wenn TCNT0 = OCR0A = 125-1 ist (125 Schritte), 
37
d.h. genau alle 1 ms bei 1 MHz
38
*/
39
40
ISR (TIMER0_COMP_vect)
41
{
42
  while(1)
43
  {
44
    while(1)
45
    {
46
    millisekunden++;
47
    
48
    if(millisekunden == 16000) // F_CPU 16 MHz statt 1 MHz
49
      {
50
      millisekunden = 0;
51
    sekunde++;
52
    }
53
54
      if(sekunde == 60)
55
      {
56
      sekunde = 0;
57
        minute++;
58
      }
59
60
      if(minute == 60)
61
      {
62
        minute = 0;
63
        stunde++;
64
      }
65
66
      if (stunde == 24)
67
      {
68
        stunde = 0;
69
      }
70
71
    if(stunde == 4)
72
      {
73
    PORTB |= (1<<PB0);
74
      
75
      if(minute == 2)
76
        {
77
      PORTB &= ~(1<<PB0);
78
      }
79
80
      }
81
82
83
84
    break;
85
86
  }
87
88
  }
89
90
}

von Frank B. (foobar)


Lesenswert?

Das mit der äußeren while-Schleife und dem break ist übrigens identisch, 
wie wenn du die äußere while-Schleife und das break einfach weglassen 
würdest. Man sollte es auch besser formatieren, also nach geschweifter 
Klammer eine Ebene einrücken und wieder zurück bei geschweifter Klammer 
zu, sodaß man die Struktur besser sehen kann.

Bist du sicher, daß C-Programmierung das Richtige für dich ist? Hat 
normalerweise nicht was mit Versuch und Irrtum zu tun, sondern mit 
überlegtem und planvollem vorgehen :-)

von Stefan B. (stefan) Benutzerseite


Lesenswert?

Pepe, schmeiss alle while() in der ISR raus. Du musst und kannst 
ohne auskommen. Du hast ein while in der main(). Schreib dein Programm 
so, dass kein while in der ISR ist und dafür das while in der main() 
benutzt wird.

Deine Zeitvariablen (minute&stunde) kannst du auch in main() benutzen! 
Die ISR ist dann nur für das Hochzählen der Zeitvariablen 
verantwortlich. Lies dir den Artikel Interrupt durch und beachte das 
Thema atomaren Datenzugriff mit der cli/sei Klammer.

1
// Eine neue Hilfsvariable um die Wartezeit in Minuten zu zählen
2
volatile unsigned char gesamtminuten; // max. 255 Minuten!
3
4
// Eine Funktion um nach der Messung die Wartezeit zu resetten
5
void timer_neustart(void)
6
{
7
  TIMSK &= ~(1<<OCIE0); // Timer aus
8
  millisekunden = 0; sekunde = 0; minute = 0; 
9
  gesamtminuten = 0; stunde = 0;  TCNT0  = 0;
10
  TIMSK |= (1<<OCIE0); // Timer an
11
}
12
13
// ... dann in main() die Arbeitsroutine
14
15
  while(1)
16
  {
17
    // ggf. stunde:minute:sekunde anzeigen
18
19
    if (gesamtminuten == 240)
20
    {
21
      PORTB |= (1<<PB0);
22
    }
23
24
    if (gesamtminuten == 241)
25
    {
26
      PORTB &= ~(1<<PB0);
27
      timer_neustart();
28
    }
29
  }
30
31
// ... und in der ISR() neu das Zählen der Wartezeit
32
33
    if(sekunde == 60)
34
    {
35
      sekunde = 0;
36
      minute++;
37
      gesamtminuten++;
38
    }

von Karl H. (kbuchegg)


Lesenswert?

Ich würde an deiner Stelle auch darüber nachdenken, die Minuten nicht 
raufzuzählen sondern runterzuzählen, so wie bei einem Countdowntimer.

In main() setzt du die minuten auf die Zeit, die es abzuwarten gilt. UNd 
dann wird einfach in main() ständig überprüft, ob die Minuten wieder auf 
0 heruntergezählt wurden. Die Stunden brauchst du nicht wirklich. 4 
Stunden sind 240 Minuten und das geht mit einer 8 Bit Variablen noch 
gut.

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.