Forum: Mikrocontroller und Digitale Elektronik Timer des AVR (GCC-Tutorial)


von Sebastian J. (jackophant)


Lesenswert?

Hallo,
ich habe versucht mich, mit Hilfe des im AVR-GCC-Tutorial angegebenen 
Beispiels, mit den Timern des Atmega8 vertraut zu machen.
Um es für den Anfang einfach zu halten wollte ich lediglich eine an 
PORTC angeschlossene LED blinken lassen.
Dafür habe ich den Code aus dem Tutorial zu dem unten angehängten 
verändert.
Leider leuchtet die LED dauerhaft.

Wenn ich das ganze richtig verstehe (ich hab mir jetzt bereits die ein 
oder andere Stunde damit verbracht...), dann tut das Programm doch 
folgendes:
1)Ich setze die Bits des Timers 0CS00 und CS02 auf 1 und damit den 
Prescaler auf 1024
2)Ich setze OCR des Timers 0 als Vergleichswert auf 125
3)Ich sage dem Timer, dass er immer dann einen Interrupt auslösen soll, 
wenn sein Wert gleich OCR ist
4)Ich aktiviere alle Interrupts

Sofern jetzt der Timer einen Interrupt auslöst, wird ausgeführt was in 
ISR(TIMER0_COMP_vect) definiert ist.
Das heißt die Variable 'millisekunden' wird erhöht und bei erreichen der 
1000 wird der PORTC invertiert.

Wie gesagt, das Problem ist, dass sich einfach nichts tut.
Meine zwei Fragen daher, hab ich den Ablauf richtig verstanden, oder ist 
genau da der Wurm drin?
Falls das Prinzip soweit stimmt, wo kann sonst der Fehler liegen? (Ich 
bin auch nach langer Suche in diesem Forum leider nicht schlauer 
geworden.)

Vielen Dank schonmal im Voraus,
Sebastian

PS.: Ich verwende das Olimex-Board aus diesem Starterkit: 
http://shop.embedded-projects.net/product_info.php?ref=2&info=p67
Der Prozessor ist ein Atmega8 mit Werkseinstellungen (betreibe ihn 
demnach auch mit internen 1MHz). Der Test mit der _delay_ms Funktion ist 
erfolgreich, die LED blinkt im Sekundentakt.
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
#include <util/delay.h>
4
5
 
6
//Variablen für die Zeit
7
volatile unsigned int  millisekunden=0;
8
volatile unsigned int  sekunde=0;
9
10
int main()
11
{
12
   DDRC = 0xff;                    //Alle Pins des Port C als Ausgang 
13
14
   TCCR0 =(1<<WGM01) |(1<<CS01);   //1)Timer 0 arbeitet mit 1/1024 * CPU-Takt
15
   OCR0=125;                       //2)Vergleichswert
16
    
17
   TIMSK|=(1<<OCIE0);              //3)Compare Interrupt aktivieren
18
   sei();                          //4)Interrupts aktivieren
19
20
   while(1)
21
   {
22
   //_delay_ms(1000);              //LED Funktionstest
23
   //PORTC = ~PORTC;
24
   }
25
 
26
   return 0;
27
}
28
 
29
// Der Compare Interrupt Handler
30
// Wird aufgerufen wenn TCNT0 = 125
31
ISR (TIMER0_COMP_vect)
32
{
33
   millisekunden++;
34
   if(millisekunden==1000)
35
   {
36
      sekunde++;
37
      millisekunden=0;
38
39
    PORTC = ~PORTC;              //Zustand des Port C invertieren
40
41
   }
42
}

von Stefan E. (sternst)


Lesenswert?

> Meine zwei Fragen daher, hab ich den Ablauf richtig verstanden, oder ist
> genau da der Wurm drin?

Der Ablauf stimmt soweit, außer dass dort noch der Schritt fehlt:
- Konfiguriere Timer auf CTC-Modus

Und da sind wir dann schon beim Thema, denn der Timer 0 des ATmega8 
kennt keinen CTC-Modus. Darf ich fragen, wie du das Programm überhaupt 
compiliert bekommst? Der ATmega8 hat kein WGM01-Bit, kein OCR0-Register, 
kein OCIE0-Bit und damit natürlich auch keinen TIMER0_COMP-Vector. Das 
wird alles vom Compiler mit Fehlermeldungen quittiert. Wenn das bei dir 
compiliert, dann hast du einen falschen Controller eingestellt.

von Sebastian J. (jackophant)


Lesenswert?

Vielen Dank für die sehr schnelle Antwort, die mich in die richtige 
Richtung geschubst hat!
Zuerst einmal habe ich im AVR-Studio den Controller auf einem Mega128 
stehen gehabt, daher konnte ich das ganze ohne weiteres kompilieren. =/
Nachdem ich das korrigiert habe habe ich auch prompt die von dir 
angesprochenen Fehler gemeldet bekommen.
Nachdem ich dann nochmal Datenblatt, Tutorial und die iom8.h 
durchforstet habe, habe ich meinen Code wie unten angehängt verändert.
Das Beste zuerst: Es funktioniert! =)

Nun hab ich aber nichts davon, wenn ich mir nicht sicher bin, dass ich 
den Spaß auch verstanden habe.
Daher nochmal die Gleiche Frage wie zum ersten Code, stimmt meine 
Vorstellung hier mit der Realität überein?
1)Ich aktiviere den Overflow Interrupt Modus des Timers 0
  Wenn ich das richtig sehe, ist das der einzige Modus, den der Timer 0 
des
  Mega8 kennt. Dieser sorgt dafür, dass bei jedem Überlauf der 8bit
  'Variable' TCNT0 ein Interrupt ausgelöst wird.
2)Ich stelle den Presclaer auf 1024
  Stand im oberen Code auf 8
3)Ich aktiviere alle Interrupts
4)Der zugehörige Vektor in der Interrupt-Routine legt fest durch welchen
  Interrupt diese ausgelöst wird.

Das heißt, das Ergebnis ist, dass die Routine mit der Frequenz
 ausgeführt wird.
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
#include <util/delay.h>
4
5
 
6
//Variablen für die Zeit
7
volatile unsigned int  millisekunden=0;
8
volatile unsigned int  sekunde=0;
9
10
int main()
11
{
12
   DDRC = 0xff;                    //Alle Pins des Port C als Ausgang 
13
   TIMSK |= (1 << TOIE0);          //1)Timer 0 mit "Overflow Itterrupt" aktivieren
14
   TCCR0 =(1<<CS00)|(1<<CS02);     //2)Prescaler auf 1024 stellen
15
    
16
   sei();                          //3)Interrupts aktivieren
17
18
   while(1)
19
   {
20
   //_delay_ms(1000);              //LED Funktionstest
21
   //PORTC = ~PORTC;
22
   }
23
 
24
   return 0;
25
}
26
 
27
ISR (TIMER0_OVF_vect)              //4)Führe diese ISR bei Overflow von TCNT0 aus
28
{
29
   millisekunden++;
30
   if(millisekunden==4)
31
   {
32
      sekunde++;
33
      millisekunden=0;
34
35
    PORTC = ~PORTC;              //Zustand des Port C invertieren
36
37
   }
38
}

von Stefan E. (sternst)


Lesenswert?

> Daher nochmal die Gleiche Frage wie zum ersten Code, stimmt meine
> Vorstellung hier mit der Realität überein?

Ja.

Wenn man allerdings Interrupts mit einer bestimmten vorgegebenen 
Häufigkeit haben will, ist der CTC-Modus besser geeignet. Als nächste 
Übung würde ich dir daher empfehlen, nochmal deinen ersten Versuch 
aufzugreifen (1ms-Interrupts mittels CTC). Nur halt mit Timer 1 oder 2 
statt 0, denn die haben einen CTC-Modus.

von Sebastian J. (jackophant)


Lesenswert?

Ok, besten Dank.
Ich denke das erste Beispiel hab ich verstanden, daher dachte ich mir, 
kann das nächste nicht so schwer sein...
Mein Ziel war es diesmal, wie von dir vorgeschlagen, den Timer2 (die 
zwei Register des 16bit Timers 1 haben mich vorerst abgeschreckt, da 
unübersichtlich) im CTC Modus für die gleiche Aufgabe zu benutzen.
Bei einem Prescaler von 1, einer CPU Frequenz von einem Megahertz und 
einem Vergleichswert von 99 (weil 999 ja leider nicht mehr in die 8bit 
passen) sollte also ein Interrupt 1/10 Millisekunde entsprechen. Den 
Namen des Vektors habe ich wieder aus der iom8.h übernommen.
Leider bleibt die LED wieder mal dauerhaft eingeschaltet.
Stimmt die Initialisierung des Timers nicht?
Habe da im Datenblatt nur die Angabe gefunden, dass für den CTC Modus 
WGM21:0 = 2 gesetzt werden muss. Ich habe das so interpretiert, dass 
WGM21=1 und WGM20=0 gesetzt wird, damit ich die Binärzahl 10 also 2 
habe.
Ist das falsch? Ich habe leider keine genauere Angabe finden können.
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
4
 
5
//Variablen für die Zeit
6
volatile unsigned int  zehntel_millisekunden=0;
7
volatile unsigned int  sekunde=0;
8
9
int main()
10
{
11
   DDRC = 0xff;               //Alle Pins des Port C als Ausgang 
12
    
13
   TCCR2 |=(1<<WGM21);        //1)Timer 2 mit CTC Modus aktivieren
14
   TCCR2 |=(1<<CS20);         //2)Prescaler auf 1 stellen
15
   OCR2=99; 
16
   sei();                     //3)Interrupts aktivieren
17
18
   while(1)
19
   {
20
   }
21
 
22
   return 0;
23
}
24
 
25
ISR (TIMER2_COMP_vect)       //4)Führe diese ISR aus wenn TCNT2==OCR2
26
{
27
   zehntel_millisekunden++;
28
   if(zehntel_millisekunden==10000)
29
   {
30
      sekunde++;
31
      zehntel_millisekunden=0;
32
      
33
      PORTC = ~PORTC;        //Zustand des Port C invertieren
34
   }
35
}

von Stefan E. (sternst)


Lesenswert?

Du hast nur vergessen, den entsprechenden Interrupt freizuschalten.

Noch 'ne Zusatzbemerkung:
Ein Interrupt alle 99 Takte ist schon dicht an der Grenze des Möglichen. 
Geh lieber auf die ursprünglich angedachte 1ms. Das erreichst du, indem 
du zusätzlich den Vorteiler einsetzt.

von Sebastian J. (jackophant)


Angehängte Dateien:

Lesenswert?

Humpf, ok, das hätte ich anhand meines ersten Versuchs auch selbst sehen 
können... =/
Um so besser, dass es kein grundsätzliches Problem war.
Jetzt läuft alles so wie ich mir das vorgestellt habe. (Ich habe das 
korrigierte Programm der Vollständigkeit halber mal angehängt.)

Nochmal vielen Dank für die enorm schnelle und kompetente Hilfe!
Diese Seite inklusive dem Forum, mit seinen Usern, ist wirklich Gold 
wert!
Man trifft im Internet leider selten auf so viele hilfsbereite und 
fachkundige Menschen wie das hier der Fall ist!

Beste Grüße, und ein schönes Wochenende, wünsche ich dir!
  Sebastian

Edit:
Hab den Prescaler geändert. Es besteh ja kein Grund am Limit zu 
arbeiten!

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.