mikrocontroller.net

Forum: Compiler & IDEs 16bit Timer [MEGA8]


Autor: Adi A. (mateit)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo und Frohes Neues Jahr,

ich versuche mich gerade am Timer1, dem 16bit Timer vom Atmega8. Ich 
möchte, dass der Timer ab einem bestimmten Startwert anfängt zu zählen 
und dann einen Overflow Interrupt auslöst, dann wieder vom Startwert 
hochzählt usw. . Sinn des Ganzen soll - wie ihr bestimmt schon erwartet 
habt ;) - das Hochzählen um je eine Sekunde sein.

Ich verwende einen Atmel Atmega8 bei einem Takt von 4MHz.

Das ist mein Code:
#include <avr/io.h>
#include <avr/interrupt.h>
#include "lcd_new.h"

int i;
char Buffer[20];

int main()
{
  i = 0;

  /************************************/
  /* DataDirectionRegister            */
  /************************************/ 
  DDRC = 0xFF; // LCD


  /************************************/
  /* Timer              */
  /************************************/ 
  
  //Timerregister 1 konfigurieren, prescaler 1024
  TCCR1B |= (1<<CS12) | (1<<CS10);
  
  //interrupt mask, overflow interrupts enable timer 1
  TIMSK |= TOIE1;
  
  //4MHz / prescaler (1024) -> 4kHz Timerfrequenz -> 4.096 -> 65.536 - 4.096 = 61.440 startwert ^= F0 00
  TCNT1 = 0xF000;

  /************************************/
  /* LCD                */
  /************************************/ 

    /* initialize display, cursor off */
    lcd_init(LCD_DISP_ON);

  /* loesche das LCD Display und Cursor auf 1 Zeile, 1 Spalte */
    lcd_clrscr();
    
  
  //enable interrupts    
    sei();
  
    while(1) //loop
    {
    }
    return 0;
 
}

  /****************************/
  /* InterruptServiceRoutine  */
  /****************************/ 

ISR(TIMER1_OVF_vect){
  cli(); //disable interrupts

  lcd_clrscr();

  itoa( i, Buffer, 10 ); //int 2 string
  i++;

  lcd_puts(Buffer);
  

  // Zaehler erneut auf Startwert setzen
  TCNT1 = 0xF000;

    sei(); //enable interrupts
}


Ich habe bereits, die hier geschriebenen Tutorials sowie das Datenblatt 
durchforstet. Allerdings wird auf dem LCD einfach nichts angezeigt.

Ich hoffe ihr könnt mir helfen.
Schönen Sonntag noch.


MfG

matEit

Autor: Stefan B. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Das macht man anders.

Schau dir den CTC-Modus an. Dort geht es von einem Startwert 0 zu einem 
von dir vorgegebenen Endwert. Ist der erreicht, wird der ...COMP... 
Interrupt ausgelöst und automatisch der Zähler wieder auf den 
Startwert 0 gesetzt.

cli()/sei() in der ISR ist seltsame Programmierung. Ich weiss nicht, 
woher die Anfänger diese Sache immer finden. Gibt es da ein Tutorial in 
dem das so drin steht?

Die Arbeitsanweisung in der ISR ist auch nicht toll.

>  lcd_clrscr();
>  itoa( i, Buffer, 10 ); //int 2 string
>  i++;
>  lcd_puts(Buffer);

Wenn die zu lange dauert, gehen je nach Einstellung des Timers 
Interrupts verloren und der Timer wird ungenau.

Üblich ist es in der ISR nur ein Flag (eine besondere Variable) zu 
setzen und das Flag im Anwendungsprogramm auszuwerten. Das Flag muss, da 
es in der ISR und dem Anwendungsprogramm benutzt wird volatile sein 
und man muss auf atomaren Datenzugriff achten. Beide Begriffe sind im 
Artikel Interrupt erklärt.


#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/atomic.h> // wg. ATOMIC_BLOCK
#include <stdlib.h>      // wg. itoa
#include "lcd_new.h"

volatile int i;
char Buffer[20];

int main(void)
{
  i = 0;

  /************************************/
  /* DataDirectionRegister            */
  /************************************/ 
  DDRC = 0xFF; // LCD

  /************************************/
  /* Timer                            */
  /************************************/ 
  
  //Timerregister 1 konfigurieren, prescaler 1024
  TCCR1B |= (1<<CS12) | (1<<CS10);
  
  //interrupt mask, overflow interrupts enable timer 1
  TIMSK |= TOIE1;
  
  //4MHz / prescaler (1024) -> 4kHz Timerfrequenz -> 4.096 -> 65.536 - 4.096 = 61.440 startwert ^= F0 00
  TCNT1 = 0xF000;

  /************************************/
  /* LCD                              */
  /************************************/ 

  /* initialize display, cursor off */
  lcd_init(LCD_DISP_ON);

  /* loesche das LCD Display und Cursor auf 1 Zeile, 1 Spalte */
  lcd_clrscr();
    
  
  //enable interrupts    
  sei();
  
  while(1) //loop
  {
    static int letztes_i = -1;
    int aktuelles_i;
  
    // http://www.nongnu.org/avr-libc/user-manual/group__util__atomic.html
    // Compiler Option -std=c99 oder -std=gnu99 erforderlich
    ATOMIC_BLOCK(ATOMIC_FORCEON)
    {
       aktuelles_i = i;
    }

    if ( aktuelles_i != letztes_i )     
    {
      aktuelles_i = letztes_i;
      lcd_clrscr();
      itoa( i, Buffer, 10 ); //int 2 string
      lcd_puts(Buffer);
    }
  }

  return 0;
}

  /****************************/
  /* InterruptServiceRoutine  */
  /****************************/ 

ISR(TIMER1_OVF_vect)
{
  i++;
  // Zaehler erneut auf Startwert setzen
  TCNT1 = 0xF000;
}

Autor: Flo (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
deklarier i erst mal als "volatile" da es außerhalb des Programms in der 
ISR verändert wird.

Autor: Stefan B. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Argh, ich habe Mist geschrieben!

Statt:
      aktuelles_i = letztes_i;
      lcd_clrscr();
      itoa( i, Buffer, 10 ); //int 2 string

Schreibe:
      letztes_i = aktuelles_i; // ###
      lcd_clrscr();
      itoa( aktuelles_i, Buffer, 10 ); //int 2 string // ###

Beim Simulieren wäre es aufgefallen. Simulieren ging aber nicht, weil 
mir die Datei lcd_new.h fehlt.

Autor: Rolf Magnus (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
> Ich möchte, dass der Timer ab einem bestimmten Startwert anfängt zu
> zählen und dann einen Overflow Interrupt auslöst, dann wieder vom
> Startwert hochzählt usw. .

Eigentlich macht man das aber anders.  Man nimmt den CTC-Modus. Da 
startet der Timer immer bei 0, und man wählt über ein 
Timer-Compare-Register aus, bis wo er zählen soll. Das ist genauer und 
man kann sich ein manuelles Rücksetzen des Registers sparen, weil der 
Timer das alles schon automatisch macht. Es wird dann einfach regelmäßig 
die entsprechende Output-Compare-ISR aufgerufen.

> int i;

Wenn du die Variable in der ISR benutzt, sollte sie volatile sein.

>  //interrupt mask, overflow interrupts enable timer 1
>  TIMSK |= TOIE1;

  TIMSK |= (1 << TOIE1);

> ISR(TIMER1_OVF_vect){
>   cli(); //disable interrupts

cli() ist in einer ISR überflüssig. Die Interrupts werden automatisch 
deaktiviert. Ein sei() ist in einer ISR meistens keine gute Idee.

>   lcd_clrscr();

>  itoa( i, Buffer, 10 ); //int 2 string

>  lcd_puts(Buffer);

Solche Funktionen gehören nicht in eine ISR. Die ISR sollte man so kurz 
wie möglich halten und stattdessen die eigentliche Arbeit in der Haupt

>  // Zaehler erneut auf Startwert setzen
>   TCNT1 = 0xF000;

Das machst du ganz am Schluß. Damit verlierst du unter Umständen 
Timer-Ticks.

Autor: Adi A. (mateit)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo
und vielen Dank an Alle, die mir hier geholfen haben.

Die Idee mit dem Setzen eines Startwertes habe ich hier: 
Beitrag "Re: atmega8 uhr"
her.
Ich habe es jetzt mit eurem Vorschlag dem Compare Match Interrupt 
gelöst. Schaut auch eleganter aus ;)

Hier mein Code:
#include <avr/io.h>
#include <avr/interrupt.h>
#include "lcd_new.h"

int i;
volatile unsigned int flag;
char Buffer[20];

int main()
{
  i = 0;

  /************************************/
  /* DataDirectionRegister            */
  /************************************/ 
  DDRC = 0xFF; // LCD
  DDRD = 0xFF; // LED
  DDRB = 0x00; // Taster

  /************************************/
  /* Timer | Compare Match Timer 1    */
  /************************************/ 
  
  //Timerregister 1 konfigurieren, prescaler 1024, ClearTimeCounter (wgm12)
  TCCR1B |= (1 << CS12) | (1 << CS10) | (1 << WGM12);

  //interrupt mask, compare match
  TIMSK |= (1 << OCIE1A);
  
  //4MHz / prescaler (1024) -> 4.096 -> 0x1000
  OCR1A = 0x1000;


  /************************************/
  /* LCD                              */
  /************************************/ 

    /* initialize display, cursor off */
    lcd_init(LCD_DISP_ON);

  /* loesche das LCD Display und Cursor auf 1 Zeile, 1 Spalte */
    lcd_clrscr();
    
  
  //enable interrupts    
    sei();
  
    while(1) //loop
    {
    /****************************/
    /* Compare Match Interrupt  */
    /****************************/ 

    if(flag == 1)
    {
      lcd_clrscr();
  
      itoa( i, Buffer, 10 ); //int 2 string
      i++;

      lcd_puts(Buffer);

      flag = 0;
    }
    
    /****************************/
    /* Taster                   */
    /****************************/ 

    if( !(PINB & (1 << PB0)) )
      PORTD = (1 << PD2);

    if( !(PINB & (1 << PB1)) )
      PORTD = !(1 << PD2);
    }
    return 0;
 
}


  /****************************/
  /* InterruptServiceRoutine  */
  /****************************/ 

ISR(TIMER1_COMPA_vect)
{
  flag = 1;
}


Hat noch jemand etwas an meinem Code auszusetzen? Sollte man gewisse 
Dinge nicht tun?

Danke nochmals.


MfG

MatEit

Autor: avr (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Ja, ich habe was auszusetzen.

Deine Sekunde dauert 1,048576 Sekunden.

Dein Wert von 4096 past nicht zu 4 MHz sondern ist für 4,194306 MHz.
Wenn du den Quarz tauschen kannst -> gut
Wenn Quarz bei 4 MHz bleiben muß -> OCR1A = 0x0f61;
Wenn du den internen Oszillator nimmst -> sowieso egal!

Wenn der externe Quarz etwas daneben liegt den OCR1A-Wert
evtl. anpassen; dabei Temperaturen und Versorgungsspannung
bedenken.

avr

Autor: Stefan B. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Willst du das wirklich?

      PORTD = (1 << PD2);
      ...
      PORTD = !(1 << PD2);

oder eher

      PORTD |= (1 << PD2);
      ...
      PORTD &= ~(1 << PD2);

s. Artikel Bitmanipulation

Auch in der ersten Fassung war schon ein Bug drin, der den Timer um ca. 
50ms zu lahm gemacht hat.

  //4MHz / prescaler (1024) -> 4.096 -> 0x1000
  OCR1A = 0x1000;

Du bräuchtest

4000000 / 1024 = 3906,25

für ein exaktes OCR1A. Leider kannst du nur mit Ganzzahlen rechnen.

Diese Kombinationen passen genau:

Prescaler 256 und OCR1A = 4e6/256 - 1;
Prescaler 64 und OCR1A = 4e6/64 - 1;

Bei der Berechnung von OCR1A daran denken, dass der Timer ab 0 los 
läuft. Für 10 (4e6/64) Schritte, muss er von 0 bis 9 (4e6/64-1) laufen, 
d.h. der Vergleichswert ist 9 (4e6/64-1) nicht 10 (4e6/64).

IMHO lässt sich die Berechnung (die zur Compilezeit aufgelöst wird und 
zur Laufzeit keine Rechenzeit verbraucht) angenehmer lesen und warten 
als der absolute Wert 0xF423 ;)

Autor: Gast123 (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Stefan B. schrieb:
> Für 10 (4e6/64) Schritte, muss er von 0 bis 9 (4e6/64-1) laufen,

Das ist so auch missverstaendlich: fuer 10 Schritte muss er von 0 bis 0 
laufen. Der Uebergang von 9 zurueck auf 0 ist der 10. schritt.

Autor: Stefan B. (Gast)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Jo. Ich weiss nicht, wie man es besser/anschaulicher erklären kann. Im 
Wiki ist das IMHO auch nicht so prall erklärt.

Jedenfalls in die Falle mit dem um 1 zu großen OCR1A bin ich ja letztens 
selbst reingetappt. Deshalb habe ich das noch so frisch als 
diesenbugnichtmehrmachen im Gedächnis.

Autor: Adi A. (mateit)
Datum:

Bewertung
0 lesenswert
nicht lesenswert
Hallo,

>Willst du das wirklich?
>
>      PORTD = (1 << PD2);
>      ...
>      PORTD = !(1 << PD2);
>
>oder eher
>
>      PORTD |= (1 << PD2);
>      ...
>      PORTD &= ~(1 << PD2);

das spielt für mich keine Rolle, da ich das nur fix hingeklatscht habe, 
um zu sehen, ob Tastendrücke erkannt werden. Aber du hast natürlich 
Recht, man sollte es so wie du machen.


Vielen Dank für den Hinweis mit OCR1A! Ich habe jetzt einen Prescaler 
von 256 und damit als Vergleichswert 0x3D08.

Den Code brauche ich sicher nicht nochmal posten.

Vielen Dank.


MfG

MatEit

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

Bewertung
0 lesenswert
nicht lesenswert
M. Q. schrieb:

>     if(flag == 1)
>     {
>       lcd_clrscr();
>
>       itoa( i, Buffer, 10 ); //int 2 string
>       i++;

Das ist auch keine so gute Idee.
Erhöhe das i (welches ja die Sekunden enthalten soll, warum heisst die 
Variable eigentlich i?) in der ISR.
Wenn die Ausgabe dann etwas zu lange dauert, oder der µC gerade mit 
etwas anderem beschäftigt ist, engeht dir keine Erhöhung. Machst du die 
Erhöhung aber hier, so kann es theoretisch passieren, dass eine Erhöhung 
übersehen wird.

Auch wenn die Regel lautet, dass in einer ISR keine lang andauernden 
Operationen ablaufen sollen, so geht das Hochzählen einer Uhr schnell 
genug, dass man es in einer ISR durchführen kann. Und auch soll! Denn 
auf die Art geht die Uhr nie falsch, solange die Interrupts enabled 
sind.

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.