PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Timer beim ATMega32 in C



tom77?
09.06.2012, 15:17
Hallo Gemeinde,

ich habe mich extra hier angemeldet, da mir dieses Form sehr fachlich erscheint und die Leute relativ human miteinander umgehen^^.

Ich hätte eine Frage zu den Timern - beim ATMega32 sollen es ja drei sein, falls ich richtig gelesen habe. Ich möchte jede Sekunde ein kleines Unterprogramm aufrufen - vereinfacht sagen wir mal ich möchte die Sekunden hochzählen. Getacktet wird mein µC mit 16Mhz (das Funktioniert auch bestens), aber wie genau muss ich die Timer (also den einen) einstellen, dass das Funktioniert und welchen der drei benötige ich überhaupt? Ich habe mich sehr viel eingelesen, verstehe die Technik, jedoch finde ich keinen nützlichen Code in C. Es wäre sehr toll, wenn mir jemand eine Seite nennen könnte, wo ich nicht nur den C-Code, sondern auch eine Erklärung zum Code finden würde - eine eigene Erklärung wäre mir jedoch am liebsten, so dass ich ggf. dazu Fragen stellen kann.

Was ich bis jetzt verstanden habe:
- Die Timer verlaufen parallel zum Code und unterbrechen die eigentlich ablaufende Schleife zur eingestellten Zeit, um ein Unterprogramm aufzurufen und kehren nach Abschluss dieses Programmes wirder zum Unterbrechungszustand zurück.
- Es gibt den sog. Prescaler (Vorteiler), der den Grundtakt - bei mir 16Mhz - aufteilt, um sinnvoll damit umgehen zu können (überlicherweise in Zweierpotenzen)
- üblicherweise Laufen Timer (falls man diese so einstellt) über und beginnen wieder bei Null, je nach Bitzahl (es gibt beim ATMega32 8- oder 16 Bit Timer) sind dies bei 8 Bit 256 Zählschritte und bei 16 Bit 65536 Zählschritte.

Bitte korrigiert mich, falls etwas nicht stimmt.

Wobei ich eure Hilfe benötige (bitte mit Erklärungen):
1. Wie initialisiere ich einen Timer für die "ein sekunden Zählaufgabe"
2. Wie rufe ich nach jeder Sekunde mein Unterprogramm auf?
3. Muss man den Timer stoppen?

Ich programmiere mit AVR Studio 4 und möchte kein externes Uhrenquarz mehr einbauen (Hardwarebedingt).

Vielen Dank für die Hilfe.

021aet04
09.06.2012, 15:48
Wilkommen im Forum,
Grundsätzlich ist es egal welchen Timer du verwendest. Du kannst den Timer aber nur für eine Aufabe verwenden. Wenn du z.B. Timer0 für die PWM Erzeugung nutzt kannst du den Sekundentakt nicht mit dem Timer0 erzeugen. Du nimmst also einfach den ersten Timer den du noch nicht brauchst.

Wenn eine Funktion hardwaremäßig im Controller ist (Timer, ADC, I2C, SPI, RS232,...) läuft dies unabhängig (wenn richtig konfiguriert) vom Programm. Auf Ereignisse wirst du mit Interrupts aufmerksam gemacht. Wenn z.B. der Timer überläuft wird ein Interrupt ausgelöst.

Zum Timer: Du nimmst den größtmöglichen Prescaler. Wenn du 16MHz hast, brauchst du einen Prescaler von 1024. Somit erhältst du 16000000Hz / 1024 = 15625Hz. Das ist die Frequenz um die der Zählerwert des Timers um 1 erhöht wird. Bei 8Bit hättest du dann 256 Schritte (0-255) und bei 16Bit hast du 1024 Schritte (0-1023). Wenn du einen 16Bit Timer nimmst erhälts du eine Frequenz von 15625 / 1024 = ca.15,26Hz. Somit erhältst du 15,26 Überläufe/s (f=1/s). Mit der Wahl des richtigen Zählerwertes kannst du auf genau 16 Überläufe kommen. Jetzt musst du nur noch bis 16 Zählen und hast dann 1 Sekunde.

Vielleicht hilft das weiter http://www.rn-wissen.de/index.php/Timer/Counter_(Avr)

MfG Hannes

tom77?
09.06.2012, 16:40
danke erst mal für die rasche antwort.

kann es sein dass dir ein kleiner fehler unterlaufen ist. du schreibst:
>>Bei 8Bit hättest du dann 256 Schritte (0-255) und bei 16Bit hast du 1024 Schritte (0-1023)<<
sind es bei 16 bit nicht 0 - 65535?

ansonsten würde mich genau das mit den interrupts interresieren - könntest du mir bitte ein codebeispiel posten?

robo_tom_24
09.06.2012, 17:08
Ja, bei 16bit sinds 65535 Schritte -> ~0.2 Hz

Du kannst auch verschiedene Interrupts Auslösen: Überlauf, Timer hat bestimmten Wert
Und je nachdem wie du das ganze aufbaust kannst du eigentlich jede beliebige Frequenz erzeugen - auch mit einem 8bit Timer
Ich habs sogar geschafft mit einem 8bit Timer ein Servo anzusteuern, mit unterschiedlichen Prescalern usw - sehr interessant das ganze ;)

Gute Veranschaulichung der Register: klick (http://www.mikrocontroller.net/articles/AVR-Tutorial:_Timer)

tom77?
09.06.2012, 18:41
hmm.. danke aber ich such immer noch nach c-source-code....

robo_tom_24
09.06.2012, 19:10
Wie gesagt, schau dir die Tutorials an...
Es ist besser etwas zu können als den ganzen Code zu kopieren, und so schwer istn nun auch nicht ;)

Welche Timer hast du schon in Verwendung?
Bzw. Welche Timer-Pins?

tom77?
09.06.2012, 19:23
benötige ich denn pins um einen timer starten zu können?
timer im allgemeinen verwende ich noch nicht.

robo_tom_24
09.06.2012, 19:27
Neee...aber man kann den Timer direkt nach außen legen auf einen Pin...

Aber ich helf dir mal ein bisschen....
Du willst:
Im 1Hz Takt wird eine Variable inkrementiert

Du brauchst:
Timer
Prescaler
Interrupt
Interrupt-Routine
Variable

Einen fertigen Code wirst du wahrscheinlich nirgends finden - bekommen wirst du von uns auch keinen - nur Denkanstöße ;)

tom77?
10.06.2012, 00:32
hmm.. also wenn ich alles richtig kapiert habe, dann mal eine kleine nachfrage...



TCCR1B |= (1<<CTC1)| (1<<CS02) | (1<<CS00);
OCR1C = 15625;
OCR1A = 0;
TIMSK |= (1<<OCIE1A);


wie ich den code verstehe:
zeile eins ergibt den vorteiler mit wert 1024 (dann wird der interrupt ausgelößt).
nach dem wert in zeile zwei (15625) wird wider bei null (wegen zeile drei) begonnen.
zweile vier schaltet den interrupt wieder frei.

berechnung:
16Mhz / 1024 = 15625Hz
1/15625Hz = 0,000064 Sek.

funktion:
der timer wir alle 0,000064 Sek. um den wert eins erhöht (+1).
bei "OCR1C = 15625;" bedeutet das 0,000064 Sek. * 15625 = 1,00 Sek.
der timer springt ab 15625 auf 0 und dann wird die interrupt routine ausgelößt und so geht es weiter und weiter und weiter....

liege ich soweit richtig mit meiner interpretation?!
und wie um gottes willen lege ich jetzt diese interrupt routine an?!

wkrug
10.06.2012, 08:51
Also.
In deiner ersten Zeile stellst Du den Prescaler ein und sagst dem Timer, das das TCNT Register bei einem Comparematch 0 werden soll.
In der zweiten Zeile gibst Du an, das das Comparematchregister 1C einen Wert von 15625 haben soll.
In der dritten Zeile weist Du dem OCR1A Register den Wert 0 zu, was aber eigentlich keinen Sinn macht.
In der vierten Zeile gibst Du den Interrupt bei Comparematch des 1A Comparematch frei.

Nimm Zeile 2 raus und weise in Zeile 3 dem OCR1A den Wert 15625 zu.

Das Ganze funktioniert nun so 16MHz/1024/15625 = 1Hz. Das passt dann schon mal.

Das anlegen eines Interrupts ist von deinem Compiler abhängig.
Soweit ich weiß muss man bei AVR GCC die interrupt.h Library includen #include <avr/interrupt.h> und kann dann mit der Funktion.
ISR (Vektorname)
{
}

Nach der Initialisierung und vor der Hauptschleife deines Programmes musst Du mit sei(); noch global die Interrupts frei geben.
Sonst wird niemals ein Interrupt ausgeführt.

Den Funktionsaufruf der Interruptroutine starten.
Da gibts aber so viele Tutorials dafür, guck da einfach mal nach.

In diesem Interrupt würde ich nur ein Flag setzen ( Bit Variable ) und im Hauptprogramm dieses Flag wieder löschen, sobald es verarbeiet wurde.
Dadurch bleibt die Interruptroutine sehr kurz, was ja auch erwünscht ist.

Klebwax
10.06.2012, 09:09
In diesem Interrupt würde ich nur ein Flag setzen ( Bit Variable ) und im Hauptprogramm dieses Flag wieder löschen, sobald es verarbeiet wurde.

Wozu dann überhaupt eine ISR? Dann kannst du ja gleich das Interrupt Status Bit in der Mainloop pollen.

MfG Klebwax

tom77?
10.06.2012, 14:02
auch nach vielen versuchen und änderungen will der code nicht. jetzt wird zu langsam gezählt :(



#include <avr/interrupt.h>
...
unsigned long int timerLauf= 0;
...
int main (void)
{
TCCR1B |= (1<<CS02) | (1<<CS00);
OCR1A = 15625;
TIMSK |= (1<<OCIE1A);
sei();
...
}

ISR (TIMER1_COMPA_vect)
{
timerLauf++;
}


was mache ich nur falsch?

robo_tom_24
10.06.2012, 16:53
Hast du den Quarz richtig eingestellt?

Setzt du den Timer auch wieder zurück nachdem der ISR aufgerufen wurde?
Du musst noch ins Register TCCR1B das Bit CTC aktivieren -> Clear Timer on Compare Match -> Rücksetzen nach Abgleich ;)

Wahrscheinlich zählt der Timer 1 mal richtig und dann zählt er immer die vollen 65535 Schritte durch
Also ca so:
Timerwert: 0...........15625 *INTERRUPT* ..............65535 - 0 ........ 15625 *INTERRUPT* ...........65535 - 0 .........15625 *INTERRUPT*.... ->
Zeiterlauf: 0s.................1s............................. ...................................4,2s........... ............................................8,4s.. ................. ->
Variable:...0....................1................ .................................................. 2................................................. ...........3.................... ->

tom77?
10.06.2012, 18:42
wenn ich das CTC aktiviere lässt sich der code nicht kompilieren :(


TCCR1B |= (1<<CTC) | (1<<CS02) | (1<<CS00);

error: 'CTC' undeclared (first use in this function)

achja: quarz ist richtig eingestellt (getestet mit _delay_ms(xx) -> funktioniert exakt, auch gemessen mit dem oszi)

Wie genau meinst du das?
Setzt du den Timer auch wieder zurück nachdem der ISR aufgerufen wurde?

robo_tom_24
10.06.2012, 21:06
Ok...
CTC heißt das der Timer nach dem Match - also der Übereinstimmung - zurückgesetzt wird

Das kannst du aber auch händisch machen inem du in deiner ISR den Timer wieder auf 0 zurück setzt:

TCNT1=0; //Timer-Wert-Register auf 0 setzen

wkrug
10.06.2012, 21:07
Ohne CTC läuft der Timer nicht richtig.
Das CTC bedeutet, das das TCNT1 Register bei einem Comparematch auf 0 gesetzt wird.
Wenn das nicht eingestellt ist wird der Timer über den Overflow springen und dann erst wieder beim Zählerstand von 15625 wieder einen Interrupt auslösen.
Das bedeutet der Interrupt wird nur alle ca 4 Sekunden angesprungen.
Da gibt es mehrere Lösungen.
Entweder du zählst bei einem aufgetretenen Comparematch Interrupt ( in diesem natürlich ) einfach die 15625 zum Comparematchwert dazu.
OCR1A+=15625;
Das bewirkt, das beim Zählerstand von 31250 wieder ein Interrupt ausgelöst wird.
Keine Angst bei den Überläufen ( >65535 ) Werden die Überlaufenden Bits gekillt, weil das ja nur ein 16Bit Register ist.
Oder du aktivierst das CTC Bit im Register, das anscheinend in der Library anders heisst, oder nicht definiert ist.
Guck auch mal ins Datenblatt unter Timer 1 WGM10 bis WGM13 bzw. CTC1.

Das mit dem TCNT1=0 würde ich nicht machen, weil dabei ein kompletter Zählersprung "vergessen" werden könnte und deine Uhr damit auf Dauer falsch geht.
Das ist halt eine Sache wie lange deine Controller sich in anderen Interupts aufhält, wie lange diese Interrupts sind und welche Prescaler man benutzt.

tom77?
11.06.2012, 22:29
danke für eure antworten. kann im moment leider nichts testen - werde den code aber noch versuchen und mich dann nochmals melden ;).

tom77?
15.06.2012, 13:27
so. nach einigen tests von beiden beschriebenen beiträgen muss ich leider wider einen misserfolg vermelden. es will und will nicht :(.

Kampi
15.06.2012, 13:47
Wie macht sich den der Fehler bemerkbar?
Wird die Timer-ISR nicht ausgeführt oder was?

tom77?
15.06.2012, 13:56
Weder ein syntaktisch noch ein semantisch Fehler wird vom Compiler angezeigt, folgedessen muss es sich um einen Logikfehler handeln.
Bemerkbar macht sich das dadurch, dass der Couter jetzt gar nicht mehr hochzählt.

folgenden code verwende ich im moment:


//Variablen initialisierung
long int Laufvariable = 0;

//HAUPTPROGRAMM
int main (void)
{
//Timer1 (16Bit) Initialisieren
TCCR1A |= (1<<CS02) | (1<<CS00) | (1<<WGM10);
OCR1A = 15625;
TIMSK |= (1<<OCIE1A);
sei();
...
...
//Ausgabe der Variablen "Laufvariable" auf dem Display
//(Displayfehler habe ich 100%ig ausgeschlossen)
...
...
return 0;
}

//INTERRUPT von Timer1 (16Bit)
ISR (TIMER1_COMPA_vect)
{
if (++Laufvariable > 100)
Laufvariable = 0;
}


laut meinem verständnis sollte der code jede sekunde unterbrechen und den zähler "Laufvariable" durch die isr um eins erhöhen. ist der zähler über 100 gekommen, so wird er reseted.

Bumbum
15.06.2012, 14:28
Hallo,

drei Dinge fallen mir spontan auf:

- die Laufvariable muss volatile sein (volatile long int Laufvariable = 0;) Warum ist die überhaupt Long? Wenn die nur bis 100 zählt reicht ein Smallint bis 127 oder zumindest ein Byte. Controller-Speicher ist kostbar!
- in die ISR müsste noch ein OCR1A = 15625; oder?
- ist die Display-Ausgabe in der Main auch in einer Endlosschleife?

Viele Grüße
Andreas

Bumbum
15.06.2012, 14:35
Nachtrag: Ich habe gerade das Register OCR1A nachgeschlagen. Das ist ja der Compare-Vergleichswert. Da reicht es, wenn er einmal gesetzt wird. In der ISR kannst du ihn weglasen.

Die Initialisierung des Timers habe ich nicht geprüft. Ich hoffe die Bits stimmen alle, und auch der Interrupt wird richtig aktiviert. Wenn es immer noch nicht läuft, würde ich eine LED anschalten, wenn die Interrupt-Routine ausgeführt wird. (Vor dem sei natürlich ausschalten)

tom77?
15.06.2012, 14:44
hmm.. danke dir erst mal für deine mühen. code hab ich versucht --> erfolglos :(

zum datentyp. in der praxis zählt er höher als bis 100 (100 war nur zur vereinfachten darstellung im forum - aber auch mit 100 funktioniert es nicht)

led werde ich testen.

tom77?
15.06.2012, 14:52
led wird nicht angesteuert :( - ergo wird die routine gar nicht angesprochen. aber warum :(?

wkrug
15.06.2012, 16:24
Warum rätselst Du da rum?
Nimm den Simulator vom AVR Studio und simuliere das Prog Schritt für Schritt.
Dann siehst Du schon wo es hakt.
Du kannst ja das OCRA1 Comparematch Flag auch händisch setzten und musst dann nicht ewig im Code warten.

Bumbum
15.06.2012, 17:33
Hallo,

wenn die ISR gar nicht angesteuert wird würde ich sagen ist die Initialisierung des Timers falsch. Das sind nur 3 Zeilen, da muss man halt ein bisschen spielen, wenn man nicht weiß was man da tut. ;-)

Nach lesen des Datenblattes frage ich mich, warum du z.B.WGM10 setzt? Das hat mit Timer 1 gar nichts zu tun. Wenn überhaupt heißt das Bit WGM12. Aber wäre nicht Normal Operation besser? Du müsstest dann in der ISR den Timer nur manuell wieder auf 0 setzen.

Viele Grüße
Andreas

wkrug
16.06.2012, 07:45
Du müsstest dann in der ISR den Timer nur manuell wieder auf 0 setzen
Das Thema hatten wir schon ein paar Antworten weiter oben.

Bumbum
16.06.2012, 09:32
Guten Morgen,


Das Thema hatten wir schon ein paar Antworten weiter oben.

jetzt habe ich es auch gelesen. :-)

Trotzdem sollte das entsprechende Bit laut Datenblatt WGM12 heißen. CTC gibt es nicht, CTC1 auch nicht, deswegen hat das kompilieren in den Antworten weiter oben nicht funktioniert. Aber WGM10 ist laut Datenblatt eindeutig falsch! In der Tabelle für die WGM-Bits des Registers TCCR1A sieht man, dass nur WGM12 gesetzt sein muss für CTC.

Viele Grüße
Andreas

MagicWSmoke
16.06.2012, 10:28
Weder ein syntaktisch noch ein semantisch Fehler wird vom Compiler angezeigt
Ist auch ein wildes Durcheinander.

Im letzten geposteten Code:

//Timer1 (16Bit) Initialisieren
TCCR1A |= (1<<CS02) | (1<<CS00) | (1<<WGM10);
OCR1A = 15625;
TIMSK |= (1<<OCIE1A);
sei();
TCCR1A enthält keine Prescalerbits, ein paar Posts vorher war das noch ein bisserl besser:

TCCR1B |= (1<<CS02) | (1<<CS00);
Lies das Datenblatt statt programming-by-guessing.
Prescalerbits und auch einige andere Bitnamen haben bei Atmel die Timernummer im Namen, ein CS02 ist ein Timer0-Bit, hat hier nur keinen Einfluss, da das Register sowieso das Falsche ist und die Bits für Timer0 und Timer1 ander gleichen Position stehen.
Außerdem ver-odert man bei der Initialisierung nicht:

TCCR1A |= ...
sondern weist direkt zu, damit die Register einen definierten Zustand erhalten.

Mach das alles richtig und es wird gehen.

tom77?
16.06.2012, 13:21
ich danke euch allen für die posts. werde mich wohl selbst etwas mehr einlesen müssen und hoffen dass ich etwas mehr finde.

Osterhofera
16.06.2012, 22:16
Hi
du machst den Fehler das du Timer1 mit parameter von Timer0 lädst
so gehts - nur Quartz anpassen
#include <avr/io.h>
#include <avr/interrupt.h>
#define F_CPU 5120000
#include <util/delay.h>
#include <avr/pgmspace.h>
#include <stdio.h>
#include <inttypes.h>

ISR(TIMER1_COMPA_vect)
{
PORTA ^= (1<<PA1);
}

int main(void)
{
TCCR1B |= (1<<WGM12)| (1<<CS12) | (1<<CS10);
OCR1A = 5000;
TIMSK |= (1 << TOIE1);
TIMSK |= (1<<OCIE1A);
sei();

while(1);
}

tom77?
20.06.2012, 14:29
danke. inzwischen habe ich aber schon die lösung für mein problem gefunden.

radbruch
20.06.2012, 18:59
inzwischen habe ich aber schon die lösung für mein problem gefunden.Dann schreib das doch und zeig uns noch deine Lösung. Danke.

Gruß

mic