Forum: Compiler & IDEs 16bit Timer [MEGA8]


von Adi A. (mateit)


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:
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
#include "lcd_new.h"
4
5
int i;
6
char Buffer[20];
7
8
int main()
9
{
10
  i = 0;
11
12
  /************************************/
13
  /* DataDirectionRegister            */
14
  /************************************/ 
15
  DDRC = 0xFF; // LCD
16
17
18
  /************************************/
19
  /* Timer              */
20
  /************************************/ 
21
  
22
  //Timerregister 1 konfigurieren, prescaler 1024
23
  TCCR1B |= (1<<CS12) | (1<<CS10);
24
  
25
  //interrupt mask, overflow interrupts enable timer 1
26
  TIMSK |= TOIE1;
27
  
28
  //4MHz / prescaler (1024) -> 4kHz Timerfrequenz -> 4.096 -> 65.536 - 4.096 = 61.440 startwert ^= F0 00
29
  TCNT1 = 0xF000;
30
31
  /************************************/
32
  /* LCD                */
33
  /************************************/ 
34
35
    /* initialize display, cursor off */
36
    lcd_init(LCD_DISP_ON);
37
38
  /* loesche das LCD Display und Cursor auf 1 Zeile, 1 Spalte */
39
    lcd_clrscr();
40
    
41
  
42
  //enable interrupts    
43
    sei();
44
  
45
    while(1) //loop
46
    {
47
    }
48
    return 0;
49
 
50
}
51
52
  /****************************/
53
  /* InterruptServiceRoutine  */
54
  /****************************/ 
55
56
ISR(TIMER1_OVF_vect){
57
  cli(); //disable interrupts
58
59
  lcd_clrscr();
60
61
  itoa( i, Buffer, 10 ); //int 2 string
62
  i++;
63
64
  lcd_puts(Buffer);
65
  
66
67
  // Zaehler erneut auf Startwert setzen
68
  TCNT1 = 0xF000;
69
70
    sei(); //enable interrupts
71
}

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

von Stefan B. (Gast)


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.

1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
#include <util/atomic.h> // wg. ATOMIC_BLOCK
4
#include <stdlib.h>      // wg. itoa
5
#include "lcd_new.h"
6
7
volatile int i;
8
char Buffer[20];
9
10
int main(void)
11
{
12
  i = 0;
13
14
  /************************************/
15
  /* DataDirectionRegister            */
16
  /************************************/ 
17
  DDRC = 0xFF; // LCD
18
19
  /************************************/
20
  /* Timer                            */
21
  /************************************/ 
22
  
23
  //Timerregister 1 konfigurieren, prescaler 1024
24
  TCCR1B |= (1<<CS12) | (1<<CS10);
25
  
26
  //interrupt mask, overflow interrupts enable timer 1
27
  TIMSK |= TOIE1;
28
  
29
  //4MHz / prescaler (1024) -> 4kHz Timerfrequenz -> 4.096 -> 65.536 - 4.096 = 61.440 startwert ^= F0 00
30
  TCNT1 = 0xF000;
31
32
  /************************************/
33
  /* LCD                              */
34
  /************************************/ 
35
36
  /* initialize display, cursor off */
37
  lcd_init(LCD_DISP_ON);
38
39
  /* loesche das LCD Display und Cursor auf 1 Zeile, 1 Spalte */
40
  lcd_clrscr();
41
    
42
  
43
  //enable interrupts    
44
  sei();
45
  
46
  while(1) //loop
47
  {
48
    static int letztes_i = -1;
49
    int aktuelles_i;
50
  
51
    // http://www.nongnu.org/avr-libc/user-manual/group__util__atomic.html
52
    // Compiler Option -std=c99 oder -std=gnu99 erforderlich
53
    ATOMIC_BLOCK(ATOMIC_FORCEON)
54
    {
55
       aktuelles_i = i;
56
    }
57
58
    if ( aktuelles_i != letztes_i )     
59
    {
60
      aktuelles_i = letztes_i;
61
      lcd_clrscr();
62
      itoa( i, Buffer, 10 ); //int 2 string
63
      lcd_puts(Buffer);
64
    }
65
  }
66
67
  return 0;
68
}
69
70
  /****************************/
71
  /* InterruptServiceRoutine  */
72
  /****************************/ 
73
74
ISR(TIMER1_OVF_vect)
75
{
76
  i++;
77
  // Zaehler erneut auf Startwert setzen
78
  TCNT1 = 0xF000;
79
}

von Flo (Gast)


Lesenswert?

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

von Stefan B. (Gast)


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.

von Rolf Magnus (Gast)


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.

von Adi A. (mateit)


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:
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
#include "lcd_new.h"
4
5
int i;
6
volatile unsigned int flag;
7
char Buffer[20];
8
9
int main()
10
{
11
  i = 0;
12
13
  /************************************/
14
  /* DataDirectionRegister            */
15
  /************************************/ 
16
  DDRC = 0xFF; // LCD
17
  DDRD = 0xFF; // LED
18
  DDRB = 0x00; // Taster
19
20
  /************************************/
21
  /* Timer | Compare Match Timer 1    */
22
  /************************************/ 
23
  
24
  //Timerregister 1 konfigurieren, prescaler 1024, ClearTimeCounter (wgm12)
25
  TCCR1B |= (1 << CS12) | (1 << CS10) | (1 << WGM12);
26
27
  //interrupt mask, compare match
28
  TIMSK |= (1 << OCIE1A);
29
  
30
  //4MHz / prescaler (1024) -> 4.096 -> 0x1000
31
  OCR1A = 0x1000;
32
33
34
  /************************************/
35
  /* LCD                              */
36
  /************************************/ 
37
38
    /* initialize display, cursor off */
39
    lcd_init(LCD_DISP_ON);
40
41
  /* loesche das LCD Display und Cursor auf 1 Zeile, 1 Spalte */
42
    lcd_clrscr();
43
    
44
  
45
  //enable interrupts    
46
    sei();
47
  
48
    while(1) //loop
49
    {
50
    /****************************/
51
    /* Compare Match Interrupt  */
52
    /****************************/ 
53
54
    if(flag == 1)
55
    {
56
      lcd_clrscr();
57
  
58
      itoa( i, Buffer, 10 ); //int 2 string
59
      i++;
60
61
      lcd_puts(Buffer);
62
63
      flag = 0;
64
    }
65
    
66
    /****************************/
67
    /* Taster                   */
68
    /****************************/ 
69
70
    if( !(PINB & (1 << PB0)) )
71
      PORTD = (1 << PD2);
72
73
    if( !(PINB & (1 << PB1)) )
74
      PORTD = !(1 << PD2);
75
    }
76
    return 0;
77
 
78
}
79
80
81
  /****************************/
82
  /* InterruptServiceRoutine  */
83
  /****************************/ 
84
85
ISR(TIMER1_COMPA_vect)
86
{
87
  flag = 1;
88
}

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

Danke nochmals.


MfG

MatEit

von avr (Gast)


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

von Stefan B. (Gast)


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 ;)

von Gast123 (Gast)


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.

von Stefan B. (Gast)


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.

von Adi A. (mateit)


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

von Karl H. (kbuchegg)


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.

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.