PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Timer auf ATMega8



Lightstorm
18.03.2010, 09:38
Hallo mal wieder zusammen,

nach ersten Schaltversuchen und Spielen mit delay.h (siehe früherer Threas von mir), möchte ich mich jetzt mit Interrupts bzw dem Timer beschäftigen.

Ziel meiner Programmierung: Eine LED soll im Sekunden Takt durch einen Timer geschaltet werden. Leider steige ich die Beschreibung nicht durch:
http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial/Die_Timer_und_Z%C3%A4hler_des_AVR

Gearbeitet wird an dieser Schaltung mit einem ATMega8 bei F_CPU 1000000
http://olimex.com/dev/images/avr-p28-sch.gif

Meine Idee:


ISR(TIMER1_OVF_vect)
{
PORTC ^= (1<<5); //Invertiert den Zustand an der LED (PORTC 5)
}

Damit das klappt muss ich aber noch den Timer1 aktivieren, den Prescaler einstellen und das Vergleichsregister entsprechend einstellen.

Und an dieser Stelle setzt es aus...Was müsste ich da einstellen und wieso?

Lieben Dank im Vorraus.
Georg

askazo
18.03.2010, 10:55
Ok, gehen wir das mal Schritt für Schritt durch:
- bei einer Taktfrequenz von 1MHz und Prescaler 1 zählt der Zähler jede µs einen Schritt hoch.
- Um auf 1 Sekunde zu kommen, bräuchtest Du also 1.000.000 Schritte.
- Der Zähler geht nur bis 65.536. 1.000.000 / 65.536 = 15.259
- Du brauchst also mindestens einen Prescaler von 16, um mit dem Zähler auf 1s zu kommen. 16 gibt's aber nicht, das nächste ist 64. Damit zählt der Counter also alle 64µs einen Schritt hoch. 1s / 64µs = 15.625. Damit hätten wir also schon mal den Vergleichswert.

- Nun gibt es mehrere Möglichkeiten, den Timer für Deine Aufgabe einzustellen. Oft wird es so gemacht, dass der Timer im Normalmodus läuft und auf den Maximalwert - Vergleichswert vorgeladen wird und dann mit dem Overflow-Interrupt die gewünschte Aktion ausgeführt wird. Oder man lässt den Timer von 0 an laufen, setzt das OCR Register auf den Vergleichswert und löst damit den Compare-Interrupt aus. Diese beiden Methoden haben jedoch den Nachteil, dass man im Interrupt das Zählerregister manuell entweder auf den Vorladewert oder auf Null setzten muss.
- Die sinnvollste Alternative ist es, den CTC-Modus zu verweden. Hierbei wird ebenfalls der Compare-Interrupt genutzt, allerdings setzt sich der Timer selbständig wieder auf 0 zurück, wenn der Interrupt ausgelöst wird.

So sähe das Programm für den CTC-Modus aus:


int main(void)
{
//Timer initialisieren
TCCR1A = 0x00;
TCCR1B = 0x0B; //Prescaler 64, CTC-Mode
OCR1A = 15625; //Vergleiswert setzten
TIMSK = (1<<OCIE1A); //OC-Interrupt aktivieren
sei();

while (1)
{
//Hier passiert nix
//geht alles über Interrupts
asm volatile ("nop");
}

return 0;
}

ISR(TIMER0_COMPA_vect)
{
PORTC ^= (1<<5); //Invertiert den Zustand an der LED (PORTC 5)
}

return 0;
}


Gruß,
askazo

021aet04
18.03.2010, 11:03
Hast du das Kapitel im DB gelesen?
Wu musst wie du schon gesagt hast den Timer einstellen und aktivieren. Du musst den Timer einstellen (Prescaler, Startwert,...) und musst. Du brauchst mehrere Register zum Einstellen (TCCRB, TIMSK). Zum Schluss musst du noch die Interrupt freigeben ("sei();").
Wenn du dir noch unsicher bist könntest du nach Interruptbeispielen suchen (Google).

edit: Da war einer schneller.

MfG Hannes

radbruch
18.03.2010, 11:27
Hallo

Am Thema Timer habe ich mich vor ein paar Tagen auch versucht, vielleicht hilft es weiter:
https://www.roboternetz.de/phpBB2/viewtopic.php?p=491141#491141

Gruß

mic

Lightstorm
18.03.2010, 11:49
@askezo: Herzlichen Dank für die Erklärung. Das hat mein Problem direkt auf den Kopf getroffen.

@021aet04: Welches Kapitel im DB meinst du? Was meinst du mit DB? Einfach bei rn-wissen.de den Artikel Timer/Counter (Avr)

Ich hab mir nur die Artikel bei mikrocontroller.net angeschaut und wusste dann nicht mehr welche Bit(s) in welchen Registern ich setzen muss damit es funktioniert. War mir etwas zu viel Input - ahhhh, Overflow!!! ;-)

Hab den Code von askazo noch ein wenig umgeschrieben. Für mich etwas übersichtlicher, weil ich die Bits konkret benenne und nicht hiner 0x0b verstecke (ist nicht böse gemeint, bin nur nicht so schnell im Umrechnen auf Binär). Sind das Codes gleicher Wirkung?


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

#define LEDon PORTC &= ~(1<<5);
#define LEDoff PORTC |= (1<<5);
#define LEDchange PORTC ^= (1<<5);

void initialize();

int main(void)
{
initialize();

while(1)
{

}
return 0;
}

void initialize()
{
DDRC = (1<<5); //Ausgang an Port C, Pin 5
DDRD = 0x00; //Eingang an Port D

PORTD = (1<<2); //Port (PullUp) für Port D, Pin 2

TCCR1A = 0x00; //Timer steuert nicht den Pin an und benutzt kein PWM
TCCR1B = (1<<CS11)|(1<<CS10)|(1<<CTC1); //Prescaler mit CPU/64 und CTC (max-Wert für OCR=65.536)

TIMSK |= (1<<OCIE1A); //Vergleichsregister aktivieren

OCIE1A = 15625; //Vergleichswert für CTC => (Prescaler*OCR)/F_CPU=1s

sei();

LEDoff;
}

ISR(TIMER0_COMPA_vect)
{
LEDchange;
}
[Müsste es nicht ISR(TIMER1_COMPA-vect){} heißen, weil ich benutze ja Timer1]


Und zum Abschluss - herzlichen Dank für eure Antworten !!

021aet04
18.03.2010, 12:22
DB => Datenblatt
Kapitel über Timer/Counter. Dort werden alle Register beschrieben (auf Englisch). Es sind auch kleine Beispiele in ASM und C drinnen.

"TIMSK |= (1<<OCIE1A)" => da würde ich nur = schreiben, da das Register einen beliebigen Zustand haben kann.

Bei "sei();" würde ich als Kommentar dazuschreiben was dieser Befehl macht (zumindestens für den Anfang)
sei(); => generelle Interruptfreigabe => Interrupt einschalten
cli(); => generelle Interruptsperre => Interrupt ausschalten

cli hast du aber in deinem Programm nicht.

Bei "while(1) { }" kannst du auch so schreiben "while(1);". Die Klammer nimmt man, wenn man mehrere Dinge macht.

Sonst sieht das Programm gut aus (Übersichtlich,...)

MfG Hannes

Lightstorm
18.03.2010, 12:37
Wenn das TIMSK einen beliebigen Zustand haben kann, wäre es dann nicht besser mit |= zu arbeiten, um gegebebenenfalls früher gesetzte Flags nicht zu überschreiben? Vielleicht habe ich ja an einer anderen Stelle des Programms schon den TOIE0 für Timer0 aktiviert - so theoretisch gesehen. Ist klar, dass das hier nicht der Fall ist.

Danke für das "Übersichtlich" - ich nehm das mal als Kompliment, wobei es hier ja in der Größe nicht so schwer ist, oder?

askazo
18.03.2010, 12:57
"TIMSK |= (1<<OCIE1A)" => da würde ich nur = schreiben, da das Register einen beliebigen Zustand haben kann.
Nein, das TIMSK-Register wird nach einem Reset des Controllers immer mit 0x00 initialisiert, kann also keinen beliebigen Zusatnd haben. Von daher ist das mit dem |= schon ganz ok. Vor allem, wenn man die Timer-Initialisierung mal in eine eigene Funktion auslagert und noch weitere Timer-Interrupts einsetzt, kann man sich damit evtl. eine lästige Fehlersuche ersparen.

Ansonsten ist das Programm ok so. Nur der Kommentar
//Vergleichsregister aktivieren
passt nicht. Du aktivierst dort kein Register, sondern einen Interrupt.

Gruß,
askazo

021aet04
18.03.2010, 12:58
Wenn das TIMSK einen beliebigen Zustand haben kann, wäre es dann nicht besser mit |= zu arbeiten, um gegebebenenfalls früher gesetzte Flags nicht zu überschreiben?
Stimmt. Wenn man es am Anfang schreibt (Programmanfang) sollte man "=" schreiben. Sonst könnte es sein, dass wenn z.B. das TOIE0 gesetzt ist und man es nicht haben will (weil man es z.B. nicht braucht) trotzdem eine ISR (Interrupt Service Routine = Interrupt) auslöst. Wenn man nachher etwas verändert sollte man "|=" bzw "&= ~" schreiben.


Danke für das "Übersichtlich" - ich nehm das mal als Kompliment, wobei es hier ja in der Größe nicht so schwer ist, oder?
Ist ein Kompliment. Entweder man kann es übersichtlich schreiben oder nicht. Es ist egal, ob das Programm 3 oder 1000 Zeilen hat.

MfG Hannes