mikrocontroller.net

Forum: Mikrocontroller und Digitale Elektronik Timer Problem


Autor: Pepe (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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);
        }

    }
}

Autor: Floh (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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?
:-)

Autor: Pepe (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Frank Buss (foobar)
Datum:

Bewertung
0 lesenswert
nicht 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).

Autor: Pepe (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Stefan B. (stefan) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht lesenswert
#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 = (1<<PB0); // PB0 Ausgang
  // PB0 anfangs (1. Minute) default LOW

  // 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 bei 1 MHz
*/

ISR (TIMER0_COMP_vect)
{
  millisekunden++;
  if(millisekunden == 16000) // F_CPU 16 MHz statt 1 MHz
  {
    millisekunden = 0;
    sekunde++;
    if(sekunde == 60)
    {
      sekunde = 0;
      minute++;
      PORTB ^= (1<<PB0); // PB0 beim Minutenwechsel toggeln
    }
    if(minute == 60)
    {
      minute = 0;
      stunde++;
    }
    if (stunde == 24)
    {
      stunde = 0;
    }
  }
}

Autor: pepe (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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:
{
  DDRB = (1<<PB0); // PB0 Ausgang
  // PB0 anfangs (1. Minute) default LOW

  // 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 bei 1 MHz
*/

ISR (TIMER0_COMP_vect)
{
  while(1)
  {
    while(1)
    {
    millisekunden++;
    
  if(millisekunden == 16000) // F_CPU 16 MHz statt 1 MHz
    {
      millisekunden = 0;
  sekunde++;
  }

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

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

      if (stunde == 24)
      {
        stunde = 0;
      }

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

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

    break;

  }

  }

}

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

Autor: pepe (Gast)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: Stefan B. (stefan) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: pepe (Gast)
Datum:

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

Autor: Frank Buss (foobar)
Datum:

Bewertung
0 lesenswert
nicht 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.

Autor: pepe (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
So jetz noch einmal das ganze:
So müßte doch das Problem mit dem zählen gelöst sein.
#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 = (1<<PB0); // PB0 Ausgang
  // PB0 anfangs (1. Minute) default LOW

  // 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 bei 1 MHz
*/

ISR (TIMER0_COMP_vect)
{
  while(1)
  {
    while(1)
    {
    millisekunden++;
    
    if(millisekunden == 16000) // F_CPU 16 MHz statt 1 MHz
      {
      millisekunden = 0;
    sekunde++;
    }

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

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

      if (stunde == 24)
      {
        stunde = 0;
      }

    if(stunde == 4)
      {
    PORTB |= (1<<PB0);
      
      if(minute == 2)
        {
      PORTB &= ~(1<<PB0);
      }

      }



    break;

  }

  }

}


Autor: Frank Buss (foobar)
Datum:

Bewertung
0 lesenswert
nicht 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 :-)

Autor: Stefan B. (stefan) Benutzerseite
Datum:

Bewertung
0 lesenswert
nicht 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.

// Eine neue Hilfsvariable um die Wartezeit in Minuten zu zählen
volatile unsigned char gesamtminuten; // max. 255 Minuten!

// Eine Funktion um nach der Messung die Wartezeit zu resetten
void timer_neustart(void)
{
  TIMSK &= ~(1<<OCIE0); // Timer aus
  millisekunden = 0; sekunde = 0; minute = 0; 
  gesamtminuten = 0; stunde = 0;  TCNT0  = 0;
  TIMSK |= (1<<OCIE0); // Timer an
}

// ... dann in main() die Arbeitsroutine

  while(1)
  {
    // ggf. stunde:minute:sekunde anzeigen

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

    if (gesamtminuten == 241)
    {
      PORTB &= ~(1<<PB0);
      timer_neustart();
    }
  }

// ... und in der ISR() neu das Zählen der Wartezeit

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

Autor: Karl Heinz (kbuchegg) (Moderator)
Datum:

Bewertung
0 lesenswert
nicht 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.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [avrasm]AVR-Assembler-Code[/avrasm]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.
Hinweis: der ursprüngliche Beitrag ist mehr als 6 Monate alt.
Bitte hier nur auf die ursprüngliche Frage antworten,
für neue Fragen einen neuen Beitrag erstellen.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.