Beitrag befindet sich in Bearbeitung
1. Grundlagen der Timer-Programmierung
Ein Timer ist ein Funktionsbaustein eines Mikrocontrollers und kann verwendet werden, um Zeitereignisse zu messen. Vom Aufbau her ist ein Timer ist im Grunde nichts anderes als ein Register im Mikrocontroller, das hardwaregesteuert fortlaufend um 1 erhöht (oder verringert) wird. Dazu wird üblicherweise der Timer mit dem Systemtakt verbunden, um so die Genauigkeit des Quarzes auszunutzen, um ein regelmäßiges und vor allen Dingen vom restlichen Programmfluss unabhängiges Zählen zu ermöglichen.
Um die verschiedenen Timer, die ein Microcontroller zur Verfügung stellt verwenden zu können, müssen bestimmte Eigenschaften konfiguriert werden. Dazu sind jedem Timer bestimmte Register zugeordnet. Zum Teil sind diese Timer spezifisch, es gibt aber auch Register, die von allen Timern gemeinsam genutzt werden. Im nachfolgender Abbildung sind diese Register am Beispiel des 16-Bit Timers1 dargestellt:
Neben diesen spezifischen Registern die alle Timer haben, gibt es wie gemeinsam genutzte Register. Dabei handelt es sich zum einen um das sogenannte TIMSK-Register. Das TIMSK-Register definiert, bei welchen Ereignissen welche Art Interrupts ausgelöst werden. Das Register hat für den Timer1 den nachfolgend dargestellten Aufbau.
Dazu kommt noch ein weiteres Register das gemeinsam genutzt wird. Im TIFR- Register wird festgelegt, wie reagiert werden soll, wenn ein Zähler überläuft, bzw. sein Zielwert erreicht hat.
Sind die jeweiligen Interrupts aktiviert, können diese dann über die Interrupt Service Routinen (ISRs) benutzt werden. Dazu müssen die entsprechenden Interruptvektoren an die ISR übergeben werden:
- TIMER1_CAPT_vect für Input Capture
- TIMER1_COMPA_vect / TIMER1_COMPB_vect für Compare Match
- TIMER1_OVF_vect für Timer Overflow
Das ist die Theorie der Timer Programmierung. Für unerfahrene Programmierer sicher alles andere als einfach nachzuvollziehen. Aus diesem Grund soll die Arbeit mit Timern nachfolgend Schritt für Schritt erläutert werden. Dazu ist es wichtig, zu wissen, wie die eben beschriebenen Register im Zusammenhang stehen.
2. Kontrollregister
Um die Timer Funktion zu spezifizieren, werden bestimmte Bits in den für die Timer-Konfiguration vorgesehenen Registern gesetzt. Die wesentlichen Register sind :
- TCCRx – Timer/Counter Control Register
- TCNTx – Timer/Counter Register
- OCRx – Output Compare Register
- ICRx – Input Capture Register (only for 16bit timer)
- TIMSKx – Timer/Counter Interrupt Mask Register
- TIFRx – Timer/Counter Interrupt Flag Register
Im folgenden werden diese Register erklärt und aufgezeigt wie deren innere Bit-Struktur aussieht. Um die Timer zu konfigurieren, müssen diese Register mit entsprechenden Werten gesetzt werden. Das mutet auf den ersten Blick vielleicht etwas verwirrend an, wird am Ende aber hoffentlich transparent.
2.1 Das Timer Control Register
In den Timer Control Registern werden die wesentlichen Einstellungen für das Timer-Verhalten vorgenommen. Dazu gehören u.a.:
- Wahl des Wave Form Generation Modes über die WGMx Bits
- Festlegung, was bei einem Compare Match passiert (COM xy Bits)
- Prescaler bzw. external Clock über die Chip Select Bits CS1x
Im folgenden ist eine Übersicht über Anzahl und den Aufbau sämtlicher Timer Kontroll-Register der Timer des Arduino UNO dargestellt.
2.2 Das Counter Register TCNTx
Das Timer-Register zählt im Systemtakt oder verlangsamt über den gewählten Prescaler. Die untere Grenze wird als Bottom (Null) bezeichnet, die obere Grenze als Top. Top ist, je nach Modus, festgelegt oder kann variabel definiert werden. Für alle Timer gibt es je nach Größe ein oder zwei 8Bit Counter Register. Sie haben die Bezeichnungen: TCNT0, TCNT1H und TCNT1L sowie TCNT2.
2.3 Das Output Compare Register OCRx
In den beiden Output Compare Registern OCR1A und OCR1B kann man Werte definieren, die permanent mit dem Timer Daten Register TCNTx verglichen werden. Je nach Einstellung und Modus löst eine Übereinstimmung (Compare Match) bestimmte Aktionen aus. Die entsprechenden Aktionen werden über die Timer/Counter 1 Control und Status Register konfiguriert.
Der Compare Output Mode ist in alle Timer integriert. Die Bits COMx0 und COMx1 steuern das Verhalten des OCx-Pins (der Ausgabepin des CompareOutput Modes). Wenn beide Bits auf 0 gesetzt sind, behält der OCx-Pin seine Standardfunktion im Mikrocontroller.
Ist das COMx1 Register gesetzt wird der OCx-Pin bei einem Compare Match auf 0 gesetzt (bei PWM wird er bei Erreichen vom maximalen Wert wieder auf 1 gesetzt). Sind beide Register gesetzt ist diese Funktion invertiert (also bei jedem Compare Match wird OCx auf 1 gesetzt).
Output-Compare-Pins
Jedem Timer sind im Timer-Kontroll-Register sogenannte Output-Compare-Pins zugeordnet. Dem Timer1 sind beispielsweise beim Arduino UNO die zwei Output Compare Pins OC1A (Pin 9) und OC1B (Pin 10) zugeordnet. Die Tabelle gibt eine Übersicht der Zuordnung für UNO und MEGA:
2.4 Das Input Capture Register ICR1
Dieses Register hat nur der Timer1. Es hat zwei Funktionen: Bei einem Ereignis an ICP1 wird der Zählerstand von TCNT1 in ICR1 geschrieben. Das ICES1 Bit (Bit6 im TCCR1B Kontroll-Register) legt fest, ob dies bei steigender (ICES1 = 1) oder fallender Flanke (ICES1 = 0) passieren soll.
ICR1 dient, wie OCR1A, in einigen WGM1 Modi als Top Wert. In diesen Fällen ist die Input Capture Register Funktion deaktiviert. Im Gegensatz zu OCR1A ist ICR1 nicht gepuffert, sondern wird sofort überschrieben. Welche Folgen das hat, besprechen wir bei den PWM Modi.
2.5 Das TIMSK Register
Dieses Register wird von allen Timern verwendet. Das TIMSK-Register definiert, bei welchen Ereignissen welche Art Interrupts ausgelöst werden. Das Register hat den nachfolgend dargestellten Aufbau. Die Bedeutung der einzelnen Bits wird unten beschrieben:
- OCIE2 (Timer/Counter2 Output Compare Match Interrupt Enable)
Wenn dieses Bit gesetzt wird, wird der Timer/Counter2 Compare Match Interrupt aktiviert (vorrausgesetzt die Interrupts sind global aktiviert). - TOIE2 (Timer/Counter2 Overflow Interrupt Enable)
Wenn dieses Bit gesetzt wird, wird der Timer/Counter2 Overflow Interrupt aktiviert. - TICIE1 (Timer/Counter1, Input Capture Interrupt Enable)
Wenn dieses Bit gesetzt wird, wird der Timer/Counter1 Input Capture Interrupt aktiviert. - OCIE1A (Timer/Counter1 Output Compare A Match Interrupt Enable)
Wenn dieses Bit gesetzt wird, wird der Timer/Counter1 Output Compare A Match Interrupt aktiviert . - OCIE1B (Timer/Counter1 Output Compare B Match Interrupt Enable)
Wenn dieses Bit gesetzt wird, wird der Timer/Counter1 Output Compare B Match Interrupt aktiviert. - TOIE1 (Timer/Counter1 Overflow Interrupt Enable)
- Wenn dieses Bit gesetzt wird, wird der Timer/Counter1 Overflow Interrupt aktiviert.
- OCIE0 (Timer/Counter0 Output Compare Match Interrupt Enable)
Wenn dieses Bit gesetzt wird, so wird der Timer/Counter0 Compare Match Interrupt aktiviert. - TOIE0 (Timer/Counter0 Overflow Interrupt Enable)
Wenn dieses Bit gesetzt wird, so wird der Timer/Counter0 Overflow Interrupt aktiviert.
Sind die jeweiligen Interrupts aktiviert, können diese dann über die Interrupt Service Routinen (ISRs) benutzt werden. Dazu müssen die entsprechenden Interruptvektoren an die ISR übergeben werden:
- TIMER1_CAPT_vect für Input Capture
- TIMER1_COMPA_vect / TIMER1_COMPB_vect für Compare Match
- TIMER1_OVF_vect für Timer Overflow
2.6 Das Timer Interrupt Flag Register TIFRx
Die Timer des ATMega16 können verschiedene Interrupts auslösen um auf verschiedene Timerereignisse zu reagieren. Um die Interrupts zu aktivieren, muss das das entsprechende Bit im TIMSK (Timer/Counter Interrupt Mask Register) gesetzt werden. Bei einem Interrupt Request wird das entsprechende Flag (Bit) im TIFR (Timer Interrupt Flag Register) gesetzt, welches dann den Interrupt auslöst. Man kann also auch durch Setzen des entsprechenden Bits im TIFR einen Interrupt „von Hand“ auslösen.
2.6.1 Output Compare Match Interrupt (OCI)
Erreicht das Register TCNTx den Wert, der im Register OCRx voreingestellt wurde, so tritt ein Timer Compare Match ein. Dabei wird ein Output Compare Interrupt ausgelöst. Auf Wunsch wird der Counter dabei zurückgesetzt. Dazu dient das CTC-Bit (Clear Timer on Compare) im TCCRx-Register.
2.6.2 Timer Overflow Interrupt (TOC)
Ist das Register TCNTx voll, so wird ein Timer/Counter Overflow und damit ein Timer/Counter Overflow Interrupt ausgelo ̈st. Der Timer zählt dabei von 0 aufwärts weiter. So kann periodisch eine Interruptroutine ausgeführt werden.
Bei PWM kann bei Bedarf der Timer Overflow Interrupt aktiviert werden um z.B. das OCRx Register zu ändern, wodurch man einen sinusförmigen Ausgang generieren kann.
2.6.3 Timer Input Capture Interrupt (TICI)
Der TICI wird bei einem Input Capture Event ausgelöst, welches eintritt, wenn am ICP1-Pin eine steigende/fallende Flanke (je nach Einstellung) gemessen wird. Es besteht die Möglichkeit einen Noice Canceler einzuschalten, der dafür sorgt, dass der TICI erst ausgelöst wird, wenn das Signal 4 Takte lang stabil anliegt. Dieser Modus kann genutzt werden um Ereignisse zu zählen oder auf sie zu reagieren.
3. Festlegung der Timer-Betriebsart
Timer können in verschiedenen Modi konfiguriert werden. Man unterscheidet dabei zwischen folgenden Modi:
- Normal-Mode
Der einfachste Betriebsmodus ist der normale Modus. Die Zählrichtung des Timers ist immer aufsteigend, bis zum Überlauf – da fängt der Zähler wieder bei 0 an. Der Überlauf kann einen Interrupt (Timer-Overflow) auslösen. Der Timer kann so konfiguriert werden, dass beim Erreichen dieses Maximums der TIMERn_OVF_vect ausgelöst wird. Die Frequenz, mit der ein Overflow bei Verwendung des Prozessortakts als Taktquelle auftritt ergibt sich mit:
wobei N =256 bei 8Bit Zählern und N=65536 bei 16Bit-Zählern gesetzt wird. - CTC-Mode
Hier zählt der Timer nach oben bis zum Erreichen des OCRn Registers. Das Register TCNTn wird beim Erreichen zurückgesetzt. Der Timer kann so konfiguriert werden, dass beim Erreichen des OCRn Wertes der TIMERn_COMPx_vect ausgelöst wird. Die Frequenz, mit der ein Compare Match bei Verwendung des Prozessortakts als Taktquelle auftritt ergibt sich mit:
- PWM-Mode
Im PWM Modus werden die OCxy Ausgabe-Pins verwendet um PWM Signale zu erzeugen und auszugeben. Die jeweiligen Pins sind genau festgelegt und lassen sich aus der oben stehenden Übersicht entnehmen. - Fast-PWM-Mode
Beim Fast PWM zählt der Timer bis zum Maximum seines Zählberreichs. Das Register OCRn dient als Vergleich und abhängig davon, ob TCNTn kleiner oder größer OCRn ist, kann der OCn Pin auf logisch 0 oder 1 gesetzt werden.
Um die beschriebenen Modi zu setzen sind im Timer-Kontroll-Register die WGMx-Bits vorgesehen. der verschiedenen Timer sogenannte Waveform Bits vorgesehen. Für den Timer2 sind das die Bits WGM20 und WGM21. Diese befinden sich im TCCR2A Register an der Position 0 und 1 siehe Abbildung:
Wenn man den Modus bei Timer0 und Timer1 konfigurieren möchte, würde man analog vorgehen, und die entsprechenden WGM0x- und WGM1x-Bits mit den WaveForm-Bit-Kombinationen belegen. Da wie oben erwähnt jeder Timer sein spezifischer zugeordnetes Kontrollregister besitzt.
4. Erstes Beispiel
Als Beispiel soll der Timer 2 so konfiguriert werden, dass eine LED Leuten soll. Der Timer2 soll dabei im Normal-Modus betreiben werden mit einem Prescaler von 1024. Dazu müssen die Parameter in den zughörigen Kontroll-Rister entsprechend gesetzt werden:
Als erstes wird Kontrollregister A des Timer 2, also TCCR2A auf 0 setzen, damit ist der Pin OC2A inaktiv und der Timer aktiviert:
Anschliessend werden im Kontrollregister TCCR2B alle CS2x Bits auf HIGH gesetzt (Gelb markiert). Der Prescaler ist damit auf 1024 gesetzt.
Da die WGM2x Bits standardmässig auf 0 gesetzt sind, braucht man hier keine weiteren Aktivitäten durchführen, um den Normal-Modus zu konfigurieren.
Als nächstes wird das Bit OIE2 für den Timer Overflow Interrupt gesetzt. Dadurch wird jedesmal, wenn das TCNT2-Register überläuft (> 255), ein Interrupt ausgelöst.
Nun fehlt noch die Festlegung des LED-Pins. Hierzu nutzen wir die Port-Manipulations-Anweisungen, um Pin 7 als Ausgang zu definieren.
Und abschliessend wird noch die Interrupt-Service Routine definiert, die den Timer2 Overflow Interrupt TIMER2_OVF_vect abfängt.
Das Programm ist dann trotz der vielen Vorüberlegungen alles in allem sehr Kompakt. Das liegt natürlich auch daran, dass die ganzen Bezeichnet bereits vordefiniert wurden, und man dadurch sehr effizient auf die Register und die Bits zugreifen kann:
void setup(){ TCCR2A = 0x00; // Set register TCCR2A from Timer2 to LOW; TCCR2B = (1<<CS22) + (1<<CS21) + (1<<CS20); // prescaler = 1024 TIMSK2 = (1<<TOIE2); // if TCNT2 > 255, wird ein Interrupt ausgelöst DDRD |= (1<<PD7); // Portmanipulation: replaces pinMode(7, OUTPUT); } void loop() { } ISR(TIMER2_OVF_vect){ PORTD ^= (1<<PD7); // toggle PD7 }
Nach dem Start des Programms erkennt man, dass die LED sehr schnell leuchtet. Nach der oben genannten Formel lässt sich die Frequenz auch leicht berechnen:
Wenn man einen kleinere Frequenz haben möchte, müsste man weitere Methoden zum verlangsamen, wie beispielsweise einen Scalefaktor, einführen.
4. Timer im CTC Modus
Im Modus Clear-Timer on Compare wird ein Vergleichswert in ein Compare-Register geschrieben. Für den Timer0 wird das Register OCR0 verwendet. Sobald der Timer0 diesen Wert erreicht (TOP-Wert) wird ein Flag gesetzt. Der Timer0 zählt also nicht mehr von 0 bis 255 (MAX-Wert), wie im Normalmodus, sondern kann irgendwo zwischen 0 und 255 zum Zählabbruch gezwungen werden, um dann wieder bei 0 anzufangen. Analog verhält es sich natürlich bei den anderen Timern. Mit dem Unterschied, dass beim 16Bit-Timer1 die Top- und Max-Werte entsprechend größer sind.
Eine zweite Variante im Normalmodus, die noch nicht angesprochen wurde, besteht darin, dass man den Timer nicht bei 0 sondern bei einem Wert k zwischen 0 und 255 starten lässt. Damit lassen sich ebenfalls die Zählzeiten verkürzen.
4. 1 Timer Interrupt Berechnung
Da die Timer wie oben beschrieben, immer an den Systemtakt gekoppelt sind, werden die Zählregister beim Arduino UNO und MEGA mit einer Takt-Frequenz von 16 Mhz erhöht. Dies bedeutet, dass die Incrementierung eines Registers benötigt. Dementsprechend würde ein 8 Bit Register nach 16 Microsekunden überlaufen und ein 16 Bit Register nach 4,9 ms, wie aus den nachfolgenden Rechnungen hervorgeht.
Register Breite | Zeit bis Überlauf |
---|---|
8 Bit Timer Register | 256 * 62,5 ns = 16 µs |
16 Bit Timer Register | 65536 * 62,5 ns = 4,90 ms |
Für die meisten Anwendungen ist das natürlich zu schnell. Deshalb gibt es den so genannten Vorteiler oder auch Prescaler genannt, mit dem die Zeitbasis reduziert werden kann.
Ein Prescaler ist einfach ein binärer Teiler für die Taktfrequenz. Mögliche Werte liegen bei 8, 32, 64, 256 oder 1024. Seine Aufgabe ist es, den Systemtakt um den angegebenen Faktor zu teilen. Steht der Vorteiler also auf 1024, so wird nur bei jedem 1024-ten Impuls vom Systemtakt das Timerregister um 1 erhöht. Entsprechend weniger häufig kommen dann natürlich die Overflows. Die allgemeine Formel für die Frequenz f mit der ein Zähler einen Interrupt auslöst lautet:
(1)
In der folgenden Tabelle sind für den Timer1 (16Bit) und Timer 2 (8Bit) mögliche Prescaler-Werte und die daraus folgenden Zeiten für einen Register Überlauf zusammengestellt. Die Idee hinter der Berechnung ist folgende: Bei 16 MHz benötigt ein Zähl-Impuls 62,5 ns. Dementsprechend benötigt ein 8-Bit Register 256 Zeit für einen Überlauf und somit einen Interrupt.
Prescaler | 1 | 8 | 64 | 256 | 1024 |
---|---|---|---|---|---|
8 Bit Reg | 16,00 µs | 128,00 µs | 1,024 ms | 4,096 ms | 16,38 ms |
16 Bit Reg | 4,90 ms | 32,76 ms | 256,14 ms | 1,048 s | 4,19 s |
4.2 Setzen der Prescalerwerte
Die Prescaler Werte werden über die CSx-Bits im TCCRx Register definiert. Das x in obiger Tabelle steht wie immer für die Timer-Nummer. Für den Timer1 sind folgende Bits definiert: CS12, CS11 und CS10.
Um nun die Zählgeschwindigkeitfestzulegen, ist der jeweilige Pressale-Wert zu wählen und die entsprechende Bit-Kombination in dem Kontrollregister des Timers zu setzen. Ein entsprechender C-Code wie diese Bits konfiguriert werden ist nachfolgend dargestellt:
TCCR1B | = (0 << CS12) | (0 << CS11) | (1 >> CS10); //kein Prescale TCCR1B | = (0 << CS12) | (1 << CS11) | (0 >> CS10); //Prescale auf 8 TCCR1B | = (0 << CS12) | (1 << CS11) | (1 >> CS10); //Prescale auf 64 TCCR1B | = (1 << CS12) | (0 << CS11) | (0 >> CS10); //Prescale auf 256 TCCR1B | = (1 << CS12) | (0 << CS11) | (1 >> CS10); //Prescale auf 1024
Weiteres Beispiel
Das von oben bekannte Beispiel mit dem Timer2, soll nun auf den 16Bit Timer 1 übertragen werden. Nun aber mit der Vorgabe, dass die LED mit einer vorgegebenen Taktzeit von 1,5s aufleuchten soll.
void setup(){ DDRD |= (1<<PD7); // Portmanipulation: replaces pinMode(7, OUTPUT); TCCR1A = 0; // clear ctrl register A TCCR1B = 0; // clear ctrl register B TCCR1B |= (1 << WGM12); // set bit for CTC mode TCCR1B |= (1 << CS12); // set bit 2 of prescaler for 1024x TCCR1B |= (1 << CS10); // set bit 0 of prescaler for 1024x OCR1A = 23437; // set L & H bytes to 23437 (1.5 sec) TIMSK1 |= (1 << OCIE1A); // enable interrupt on OCR1A TCNT1 = 0; // reset counter to zero } void loop() { } ISR(TIMER2_OVF_vect){ PORTD ^= (1<<PD7); // toggle PD7 }
Betrachtet man das Ergebnis des obigen Programms am Oszillographen, so erkennt man dass saubere Flanken in einer Frequenz von rund 30,6 Hz angezeigt werden.
Gemäß obiger Formel
(2)
Möchte man nun genaue Frequenzen einstellen, benötigt man einen weiteren Skalierungsfaktor und einen Startwert, mit denen sich dann folgende Formel ergibt:
(3)
Für eine Wunsch-Frequenz von 1 Hz nutzen wir den Umstand, dass das TCNTx Register auch mit einem Startwert beschrieben werden kann. Es werden dann nur noch Schritte bis zum Überlauf durchgeführt. Wie in der obigen Formel beschrieben, bleibt aber immer noch eine Gleichung mit mehreren Unbekannten:
(4)
(5)
(6)
Wir kennen aber den Systemtakt, die möglichen Prescalewerte und das der Startwert ganzzahlig und kleiner in diesem Beispiel 256 sein muss. Mit nachfolgendem Zusammenhang und etwas rumprobieren erhält man folgende Ergebnisse:
Aus der Tabelle lassen sich 3 gute Kombinationen entnehmen: Mit einem Prescaler von 256 finden sich die Startwerte 250 und 500. Bei einem Presclaewert von 64 würde nur der Startwert 1000 passen. Bei einem Prescaler von 1024 haben wir nur annähernd passende Werte, die man aufrunden müsste.
Mit und einem 8-Bit Timer ergibt sich dann
(7)
Damit folgt folgendes überarbeitete Programm, dass die Diode mit genau 1 Hz zum leuchten bringt:
byte counterStart = 131; unsigned int scaleFactor = 500; void setup(){ TCCR2A = 0x00; TCCR2B = (1<<CS22) + (1<<CS21); // prescaler = 256 TIMSK2 = (1<<TOIE2); // interrupt when TCNT2 is overflowed TCNT2 = counterStart; DDRD |= (1<<PD7); } void loop() { } ISR(TIMER2_OVF_vect){ static int counter = 0; TCNT2 = counterStart; counter++; if(counter==scaleFactor){ PORTD ^= (1<<PD7); counter = 0; } }
Programm-Sequenz um Timer zu programmieren
// Timmer aktivieren TCCR0A |= (1 << WGM01); // enable timer0 CTC mode TIMSK0 |= (1 << OCIE0A); // enable timer0 compare interrupt TCCR1B |= (1 << WGM12); // enable timer1 CTC mode TIMSK1 |= (1 << OCIE1A); // enable timer1 compare interrupt TCCR2A |= (1 << WGM21); // enable timer2 CTC mode TIMSK2 |= (1 << OCIE2A); // enable timer2 compare interrupt // Timmer Rücksetzen // reset a timer unit (replace X by timer number) TCCRXA = 0; // set TCCRXA register to 0 TCCRXB = 0; // set TCCRXB register to 0 TCNTX = 0; // reset counter value //Timer Vergleichswert setzen OCR0A = 124; // set compare match register of timer 0 (max. value: 255 = 2^8 - 1) OCR1A = 20233; // set compare match register of timer 1 (max. value: 65535 = 2^16 - 1) OCR2A = 20; // set compare match register of timer 2 (max. value: 255 = 2^8 - 1) //Prescaler Werte setzen //am Beispiel Timer1 TCCR1B | = (0 << CS12) | (0 << CS11) | (1 >> CS10); //kein Prescale TCCR1B | = (0 << CS12) | (1 << CS11) | (0 >> CS10); //Prescale auf 8 TCCR1B | = (0 << CS12) | (1 << CS11) | (1 >> CS10); //Prescale auf 64 TCCR1B | = (1 << CS12) | (0 << CS11) | (0 >> CS10); //Prescale auf 256 TCCR1B | = (1 << CS12) | (0 << CS11) | (1 >> CS10); //Prescale auf 1024 TCCR0B |= (1 << CS00); // no prescaling for timer0 TCCR2B |= (1 << CS22) | (1 << CS20); //Prescale auf 1024 for timer2
void setup(){ //set pins as outputs pinMode(8, OUTPUT); pinMode(9, OUTPUT); pinMode(13, OUTPUT); cli();//stop interrupts //set timer0 interrupt at 2kHz TCCR0A = 0;// set entire TCCR2A register to 0 TCCR0B = 0;// same for TCCR2B TCNT0 = 0;//initialize counter value to 0 // set compare match register for 2khz increments OCR0A = 124;// = (16*10^6) / (2000*64) - 1 (must be <256) // turn on CTC mode TCCR0A |= (1 << WGM01); // Set CS01 and CS00 bits for 64 prescaler TCCR0B |= (1 << CS01) | (1 << CS00); // enable timer compare interrupt TIMSK0 |= (1 << OCIE0A); //set timer1 interrupt at 1Hz TCCR1A = 0;// set entire TCCR1A register to 0 TCCR1B = 0;// same for TCCR1B TCNT1 = 0;//initialize counter value to 0 // set compare match register for 1hz increments OCR1A = 15624;// = (16*10^6) / (1*1024) - 1 (must be <65536) // turn on CTC mode TCCR1B |= (1 << WGM12); // Set CS12 and CS10 bits for 1024 prescaler TCCR1B |= (1 << CS12) | (1 << CS10); // enable timer compare interrupt TIMSK1 |= (1 << OCIE1A); //set timer2 interrupt at 8kHz TCCR2A = 0;// set entire TCCR2A register to 0 TCCR2B = 0;// same for TCCR2B TCNT2 = 0;//initialize counter value to 0 // set compare match register for 8khz increments OCR2A = 249;// = (16*10^6) / (8000*8) - 1 (must be <256) // turn on CTC mode TCCR2A |= (1 << WGM21); // Set CS21 bit for 8 prescaler TCCR2B |= (1 << CS21); // enable timer compare interrupt TIMSK2 |= (1 << OCIE2A); sei();//allow interrupts }//end setup // Interrupt Service Routine ISR(TIMER0_COMPA_vect){ //timer0 interrupt 2kHz toggles pin 8 } ISR(TIMER1_COMPA_vect){ //timer1 interrupt } ISR(TIMER2_COMPA_vect){ //timer1 interrupt 8kHz toggles pin 9 } void loop(){ //do other things here }
Quellen
- AVR-GCC-Tutorial/Die Timer und Zähler des AVR https://www.mikrocontroller.net/articles/AVR-GCC-Tutorial/Die_Timer_und_Zähler_des_AVR
- Die Timer/Counter des AVR (Uni Regensburg)
- ATMega Datenblatt (deutsche Version)
- Timer Teil1 und Teil2 Wolles Elektronik Kiste https://wolles-elektronikkiste.de/tag/timer
- und die vielen übrigen Beiträge in Foren