Timer und PWM – Teil 2 (16 Bit Timer1)

Über den Beitrag

In meinem letzten Beitrag hatte ich die 8-Bit Timer des Arduino UNO (bzw. ATmega328P), nämlich Timer0 und Timer2, beschrieben. In diesem zweiten Teil möchte ich mich dem 16-Bit Timer1 widmen. Im Prinzip ist er den beiden 8-Bit Timern sehr ähnlich, weist aber eine höhere Flexibilität und zusätzliche Funktionen auf.

Ihr müsst nicht unbedingt den Teil 1 lesen, ich werde aber weniger weit ausholen. Folgendes wird in diesem Beitrag behandelt:

  • Struktur und Komponenten des Timer1
  • Normal Mode
  • CTC Mode
  • PWM Modi
    • Fast PWM
    • Phase Correct PWM
    • Phase and Frequency Correct PWM
  • Verwendung des Input Capture Pins
  • Externer Taktgeber

Struktur und Komponenten des Timer1

Die Register des Timer1

Timer/Counter1 Register TCNT1

Das Timer/Counter1 Register TCNT1 ist 16 Bit breit. Das Register zählt im Systemtakt oder verlangsamt über den gewählten Prescaler. Die untere Grenze ist Bottom (Null), die obere Grenze ist Top. Top ist, je nach Modus, festgelegt oder kann variabel definiert werden.

Output Compare Register OCR1x

In den beiden Output Compare Registern OCR1A und OCR1B könnt ihr Werte definieren, die permanent mit dem TCNT1 Register verglichen werden. Je nach Einstellung und Modus löst eine Übereinstimmung (Compare Match) bestimmte Aktionen aus. In bestimmten WGM1 Modi ist OCR1A Top. Die Register sind natürlich wie TCNT1 auch 16 Bit breit. 

Timer/Counter1 Control Register TCCR1x

In den beiden Timer/Counter1 Control Registern TCCR1A, TCCR1B und TCCR1C werden die wesentlichen Einstellungen vorgenommen. Dazu gehören u.a.:

  • Wahl des Wave Form Generation Modes über die WGM1 Bits
  • Festlegung, was bei einem Compare Match passiert (COM1xy Bits)
  • Prescaler bzw. external Clock über die Chip Select Bits CS1x
Timer / Couter1 Control Register TCCR1A
Timer / Couter1 Control Register TCCR1B
Timer / Couter1 Control Register TCCR1C
Timer/Couter1 Control Register TCCR1A/B/C

ICES1, das Input Capture Edge Select Bit in TCCR1B, wird uns noch bei der Besprechung der Input Capture Funktion begegnen. Für alle weiteren Bits schaut bitte ins Datenblatt der ATmega 48 / 88 / 168 / 328 Familie.

Input Capture Register ICR1

Dieses Timer1 Register hat zwei Funktionen.

  1. Bei einem Ereignis an ICP1 wird der Zählerstand von TCNT1 in ICR1 geschrieben. Das eben erwähnte ICES1 Bit legt fest, ob dies bei steigender (ICES1 = 1) oder fallender Flanke (ICES1 = 0) passieren soll.
  2. 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. 

Timer/Counter1 Interrupt Mask Register TIMSK1

In TIMSK1 aktiviert ihr die Interrupts für die Input Capture Funktion (ICIE1), die Output Compare Matches (OCIE1B, OCIE1A) und den Timer Overflow (TOIE1). Das „IE“ steht dabei für „Interrupt Enable“.

Timer/Counter1 Interrupt Mask Register
Timer/Counter1 Interrupt Mask Register

Sind die jeweiligen Interrupts aktiviert, dann könnt ihr sie in Interrupt Service Routinen (ISRs) nutzen. Dabei übergebt ihr der ISR die jeweiligen Interruptvektoren:

  • TIMER1_CAPT_vect für Input Capture
  • TIMER1_COMPA_vect / TIMER1_COMPB_vect für Compare Match
  • TIMER1_OVF_vect für Timer Overflow

Timer/Counter1 Interrupt Flag Register TIFR1

Sofern die Interrupts aktiviert sind, werden im Falle eines Interrupts die entsprechenden Bits in TIFR1 gesetzt. Die Ausführung eines Interruptvektors löscht das Bit. Alternativ löscht ihr die Bits durch Überschreiben mit 0. 

Output Compare Pins OC1x

Dem Timer1 sind zwei Output Compare Pins zugeordnet, OC1A (PB1, Arduino UNO Pin 9) und OC1B (PB2, Arduino UNO Pin 10). Wenn ich im Folgenden von Arduino Pins spreche, meine ich den Arduino UNO. Außerdem beziehe ich mich immer auf digitale Pins, wenn ich nichts anderes schreibe. 

Pinbezeichnungen ATmega 328P vs. Arduino UNO
Pinbezeichnungen ATmega328P vs. Arduino UNO

Das Verhalten der Output Compare Pins hängt von den WGM1 Bits und den Compare Output Bits in TCCR1A und TCCR1B ab. 

Input Capture Pin ICP1

Der Input Capture Pin entspricht PB0 (Arduino Pin 8). Mithilfe der Input Capture Funktion lassen sich elegant schnelle Ereignisse zeitlich vermessen. Ich komme gegen Ende des Beitrages darauf zurück.

External Clock Pin T1

Alternativ zum internen Taktgeber könnt ihr einen externen Taktgeber an T1 (PD4, Arduino Pin 4) hängen. 

Einstellungen in TCCR1x

Wave Form Generation Modes

Im Gegensatz zu den Timer0 und Timer2 stehen für den Timer1 vier WGM Bits zur Verfügung, d.h. es gibt 16 Einstellungsmöglichkeiten. Die Wahl des WGM ist immer die Grundlage für alle weiteren Einstellungen.

WGM1 Einstellungen an Timer1
Übersicht über die WGM Einstellungen des Timer1

Clock Select Bits / Prescaler

Die Zählfrequenz von TCNT1 ergibt sich aus dem Systemtakt und dem Prescaler:

f_{Wunsch}=\frac{Systemtakt}{prescaler}
Prescalerauswahl über die Clock Select Bits des Timer1
Prescalerauswahl über die Clock Select Bits des Timer1

Compare Output Mode Bits

Die Wirkung der COM1xy Bits hängt davon ab, welcher Mode gewählt wurde:

Compare Output Mode (Timer1) für non-PWM Modes
1. Compare Output Mode für non-PWM Modes
Compare Output Mode (Timer1) für Fast PWM Modes
2. Compare Output Mode für Fast PWM Modes
Compare Output Mode (Timer1) für Phase Correct und Phase and Frequency Correct Modes
3. Compare Output Mode für Phase Correct und Phase and Frequency Correct Modes

Der Normal Mode

Im Normal Mode ist Top immer 0xFFFF (65535). Ein kompletter Durchlauf umfasst 65536 Schritte, da die 0 mitgezählt wird. Nach Erreichen von Top wird TNCT1 wieder auf 0 gesetzt. Die Frequenz für den Timer Overflow wird durch den Systemtakt und den Prescaler bestimmt. Zusätzlich könnt ihr TCNT1 mit einem Startwert versehen und so – im Rahmen der Auflösung und der Limits – beliebige Frequenzen für den Overflow erzeugen. Den Startwert müsst ihr allerdings nach jedem Overflow erneut in TCNT1 schreiben. Die Frequenz fWunsch berechnet sich nach der Formel:

f_{Wunsch}=\frac{Systemtakt}{prescaler\cdot (65536-Startwert)}
Startwert=65536-\frac{Systemtakt}{prescaler\cdot f_{Wunsch}}

Systemtakt und Frequenz sind bekannte Größen. Bleibt eine Gleichung mit zwei Unbekannten. Darauf bin ich in Teil 1 des Beitrages schon ausführlicher eingegangen. Deswegen will ich das hier nicht noch einmal wiederholen. Ich möchte aber noch einmal auf die hilfreichen AVR Timer Kalkulatoren im Netz hinweisen, z.B. hier oder hier.

Normal Mode Beispielsketch

Eine LED soll im Sekundentakt an- und ausgehen. Das bewerkstelligen wir, indem TCNT1 in genau diesem Takt überläuft und wir den Overflow Interrupt zum Invertieren des LED Pin Ausganges nutzen.

Bei Timer0 und Timer2 mussten wir für diese Anwendung noch einen weiteren Scalefaktor einführen (siehe letzter Beitrag). Wegen der 16 Bit Breite von TCNT1 ist das hier nicht notwendig. Der Arduino UNO hat einen Takt von 16 MHz. Daraus ergeben sich zwei mögliche Kombinationen: 1) Prescaler 256 / Startwert 3036 oder 2) Prescaler 1024 / Startwert 49911.

Die in TCCR1A und TCCR1B zu setzenden Bits ergeben sich aus den Tabellen weiter oben. TOIE1 muss gesetzt werden, damit wir einen Overflow Interrupt erhalten. Die LED hängt an PD7 (Arduino Pin 7). In der ISR wird der LED Pin invertiert und der Timer wieder auf den Startwert gesetzt.

unsigned int counterStart = 3036; // alternative: 49911

void setup(){ 
  TCCR1A = 0x00; // OC2A and OC2B disconnected; Wave Form Generator: Normal Mode
  TCCR1B = (1<<CS12); // prescaler = 256; alternative: 1024 (set CS12 and CS10)
  TIMSK1 = (1<<TOIE1); // interrupt when TCNT1 is overflowed
  TCNT1 = counterStart;
  DDRD |= (1<<PD7);
} 

void loop() { 
 // do something else
} 

ISR(TIMER1_OVF_vect){
  TCNT1 = counterStart;
  PORTD ^= (1<<PD7);
}

 

Ein Vorzug am Arbeiten mit Timern ist, dass die Prozesse im Hintergrund laufen. Die loop Schleife des Sketches ist immer noch leer. Normalerweise hättet ihr die blinkende LED wahrscheinlich mit einer delay Konstruktion erzeugt. Da kann es immer eine Herausforderung sein, zusätzlichen Code drum herum zu bauen, besonders, wenn dieser weitere Zeitabhängigkeiten enthält. Im letzten Beitrag hatte ich als Beispiel zwei LEDs asynchron blinken lassen. 

CTC Mode

CTC steht für „Clear Timer on Compare Match“ und genau das macht man in diesem Modus. Anstelle von 0xFFFF ist Top entweder OCR1A (WGM 4) oder ICR1 (WGM 12). Top bestimmt also die Frequenz.

Auch im CTC Modus wird nur aufwärts gezählt. Nach Erreichen von Top wird TCNT1 auf Null zurückgesetzt.

CTC Mode Beispielsketch

Wir machen dasselbe wie im Normal Mode: eine LED soll im Sekundentakt blinken. Für die Berechnung des Prescalers und Top gilt: 

f_{Wunsch}=\frac{Systemtakt}{prescaler\cdot (1+Top)}
Top = \frac{Systemtakt}{prescaler\cdot f_{Wunsch}}-1\;\;\;\;mit\;\;\;\;Top<65536

Auch hier gibt es wieder zwei mögliche Kombinationen für den Prescaler und Top. Wir nehmen den Prescaler 1024 und als Top 15624. Die Alternative wäre Prescaler 256 und Top 62499. Ob wir Mode 4 oder 12 verwenden, macht keinen Unterschied, außer in der Definition von Top. Wir wählen Mode 4, also ist OCR1A Top. Im TCCR1A Register setzen wir COM1A0. Dadurch definieren wir gemäß Tabelle: „Toggle OC1A on Compare Match“. Wir nutzen hier also nicht den Interrupt mittels einer ISR, sondern steuern direkt einen Output Compare Pin. Und in diesem Fall ist das OC1A = PB1 = Arduino Pin 9. OC1A muss auf Output gesetzt werden, denn das passiert nicht automatisch. Dieser Schritt sollte immer nach allen anderen Einstellungen erfolgen.

void setup(){ 
  TCCR1A = (1<<COM1A0); // Toggle OC1A on Compare Match; Wave Form Generator: CTC Mode 4, Top = OCR1A
  TCCR1B = (1<<WGM12) + (1<<CS12) + (1<<CS10); // prescaler = 1024; 
  OCR1A = 15624;
  DDRB |= (1<<PB1);
} 

void loop() { 
 // do something else
} 

 

Graphisch sieht das dann so aus:

Timer1: CTC - "Toggle Modus"
Grafik 1: CTC – „Toggle Modus“ mit Timer1

Fast PWM

Im Fast PWM Modus, wie auch allen anderen PWM Modi, bestimmt der Top Wert die Frequenz. TCNT1 zählt von Bottom bis Top und wird dann auf null zurückgesetzt (eine Flanke pro Periode). Für den Timer1 gibt es ggü. dem Timer0 und dem Timer2 eine erweiterte Auswahl für Top. Je nach WGM1 Bitkombination ist das:

  • 0x00FF, 0x01FF, 0x03FF – Modus 5 bis 7
  • ICR1 (Input Capture Register 1) – Modus 14
  • OCR1A – Modus 15

Normalerweise steuert ihr in den PWM Modi die dem jeweiligen Timer zugehörigen Output Compare Pins OC1A (PB1, Arduino Pin 9) und OC1B (PB2, Arduino Pin 10). Alternativ könnt ihr natürlich auch die Compare Match Interrupts verwerten.

In der Compare Output Mode Tabelle für Fast PWM wählt ihr das gewünschte Verhalten der Output Compare Pins. In der nächsten Grafik ist beispielhaft OCR1A als Top gewählt. Für OCR1B wurde die Option „Clear OC1B at Compare Match, set OC1B at Bottom“ gewählt. OCR1B bestimmt hier deshalb den Duty Cycle, OCR1A die Frequenz. Im Modus 5 bis 7 ist die Frequenz nur durch festen Top Wert und den Prescaler festgelegt. Dem entsprechend könnt ihr also keine beliebigen Frequenzen in Modus 5 bis 7 wählen. 

Timer1: TNTC1 vs. OC1x bei Fast PWM
Grafik 2: TNTC1 vs. OC1x bei Fast PWM

Verändert ihr im Programm OCR1A, dann erfolgt die Aktualisierung des Top Wertes erst nachdem der vorherige Top Wert erreicht wurde. Diese verzögerte Aktualisierung ist möglich, da die OCR1x Register gepuffert sind, d.h. es gibt sozusagen eine Zwischenablage für die zu aktualisierenden Werte.

ICR1 besitzt keinen Puffer und das hat eine entscheidende Konsequenz. Stellt euch vor, ihr verwendet ICR1 als Top und weist ihm einen neuen Wert zu, der kleiner ist als der aktuelle TCNT1 Zählerstand. TCNT1 kann in diesem Fall im aktuellen Zyklus keine Übereinstimmung mehr mit Top finden und zählt bis 0xFFFF. Und das kann je nach Prescaler und Zählerstand ziemlich lange dauern. Deswegen gilt: Wollt ihr die Frequenz während des Programmablaufs verändern, dann verwendet OCR1A als Top (Modus 15). Oder noch besser: wählt den Phase- and Frequency Correct Mode 9.

Fast PWM Beispielsketch

Als Beispiel wollen wir ein 1 kHz Signal mit 25% DutyCycle an OC1A erzeugen. Dabei soll ICR1 als Top dienen. Erstmal müssen wir rechnen:

f_{Wunsch}=\frac{Systemtakt}{prescaler\cdot(1+Top)}
Top=\frac{Systemtakt}{prescaler\cdot f_{Wunsch}}-1
Top=\frac{1600000}{1\cdot 1000}-1=15999\;\;\;\;mit\;\;\;\;prescaler=1

Top ist 15999 und das sind 16000 Schritte. 25 % sind 4000 Schritte, die mit Vollendung von 3999 erreicht sind. Wir wählen die WGM1 Bits für den Mode 14, da dieser ICR1 als Top definiert. Durch Setzen von CS10 wählen wir den Prescaler 1, also keinen Prescaler. OC1A ist PB1 (Arduino Pin 9) und muss auf Ausgang gesetzt werden. 

void setup(){ 
  // Clear OC1A on Compare Match / Set OC1A at Bottom; Wave Form Generator: Fast PWM 14, Top = ICR1
  TCCR1A = (1<<COM1A1) + (1<<WGM11); 
  TCCR1B = (1<<WGM13) + (1<<WGM12) + (1<<CS10); // prescaler = none; 
  ICR1 = 15999;
  OCR1A = 3999;
  DDRB |= (1<<PB1);
} 

void loop() { 
 // do something else
} 

 

Das Schöne daran ist, dass der Timer1 ICR1 als Top zulässt ist, dass wir sowohl OCR1A wie auch OCR1B als Compare Match für die Steuerung der Output Pins zur Verfügung haben. Wir erweitern den Sketch um ein Signal an OC1B mit 75% Duty Cycle:

void setup(){ 
  // Clear OC1A and OC1B on Compare Match / Set OC1A and OC1B at Bottom; 
  // Wave Form Generator: Fast PWM 14, Top = ICR1
  TCCR1A = (1<<COM1A1) + (1<<COM1B1) + (1<<WGM11); 
  TCCR1B = (1<<WGM13) + (1<<WGM12) + (1<<CS10); // prescaler = 1 (none) 
  ICR1 = 15999;
  OCR1A = 3999;
  OCR1B = 11999;
  DDRB |= (1<<PB1)|(1<<PB2);
} 

void loop() { 
 // do something else
} 

 

So sieht es dann am Oszilloskop aus:

Als Übung könnt ihr mal die Einstellungen so verlangsamen, dass ihr das PWM Signal an zwei LEDs sichtbar macht. Falls ihr kein Oszilloskop habt, ist das ja auch befriedigender. Wie schon im letzten Beitrag möchte ich aber noch mal auf das DSO 138, ein Oszilloskop für unter 30 Euro, hinweisen. Als Einstieg kann ich das sehr empfehlen. Bei Amazon, ebay und Co bekommt ihr es bei vielen Anbietern.

Verwendung von Fast PWM

Der Name weist schon darauf hin, dass Fast PWM schnell ist. Deshalb wird dieser Modus vor allem dort eingesetzt, wo es auf diese Eigenschaft ankommt. Warum er doppelt so schnell ist wie die anderen PWM Modi, erfahrt ihr gleich. Auch sind die Einschränkungen von Fast PWM gegenüber den anderen PWM Modi besser verständlich, wenn man sie sich im Vergleich anschaut.

Phase Correct PWM

Bei der phasenkorrekten PWM zählt TCNT1 erst von Bottom bis Top hoch und dann wieder herunter (zwei Flanken pro Periode). Als Top Werte stehen wieder

  • 0x00FF, 0x01FF, 0x03FF – Modus 1 bis 3
  • ICR1 – Modus 10
  • oder OCR1A – Modus 11

zur Auswahl. Im Folgenden möchte ich die Besonderheiten dieses Modus an einem konkreten Beispiel erklären. Dabei ist OCR1A als Top und OCR1B als Compare Match definiert:

Timer1: TNTC1 vs. OC1x bei der Phase Correct PWM
Grafik 3: TNTC1 vs. OC1x bei Phase Correct PWM

Die Periode verdoppelt sich gegenüber Fast PWM, bzw. die Frequenz halbiert sich. Ein Compare Match mit OCR1B findet sowohl beim Herauf- wie beim Herunterzählen statt.

Da ihr nun die Einstellungen selbst vornehmen können solltet, und weil der Beitrag nicht noch länger werden soll, habe ich auf weitere Beispielsketche verzichtet.

Phasenkorrekte PWM bei konstanter Frequenz

OCR1A und OCR1B werden aktualisiert, wenn TCNT1 Top erreicht. Dadurch ist, solange nur OCR1B variiert wird, sichergestellt, dass die Mitte des Pulses immer in der Mitte der Periode liegt. Bei Fast PWM wandert die Mitte des Pulses bei Änderung der Pulsweite durch die Periode. Klingt komplex, ist es aber nicht:

Fast PWM vs. Phase Correct PWM bei Änderung der Pulsweite und konstanter Frequenz
Grafik 4: Fast PWM vs. Phase Correct PWM bei Änderung der Pulsweite und konstanter Frequenz

Eine schöne Animation dazu gibt es hier.

Eine typische Anwendung für phasenkorrekte PWM ist die Steuerung von Servomotoren, da diese ein symmetrisches Signal mögen. Über Servomotoren werde ich vielleicht noch einmal separat etwas schreiben.

Phasenkorrekte PWM bei variierender Frequenz

Bei Änderung des Top Wertes, also einer Frequenzänderung, haben wir immer noch das Aktualisierungsproblem, wenn ICR1 als Top verwendet wird. Wird ICR1 als Top beim Heraufzählen aktualisiert und ist dieser Wert kleiner als der aktuelle TCNT1 Wert, findet im aktuellen Zyklus kein „Top Match“ mehr statt.

Mit OCR1A als Top gibt es ein anderes Problem, wenn ihr diesen Wert im laufenden Programm verändert. Stellt euch vor, ihr aktualisiert Top während TCNT1 herunterzählt. Die Länge der abfallenden Flanke ist dann immer noch durch den alten Top Wert bestimmt. Die Länge der steigenden Flanke hingegen wird durch den neuen Top Wert bestimmt. Dadurch bekommt das Signal wieder eine Asymmetrie. Verhindern könnt ihr das, indem ihr auf den phasen- und frequenzkorrekten PWM Modus wechselt.

Phase- and Frequency correct PWM

Auch bei der phasen- und frequenzkorrekten PWM zählt TCNT1 von Bottom bis Top und wieder runter bis Bottom. Es gilt dasselbe Schema wie bei der phasenkorrekten PWM (Grafik 3). Der Unterschied besteht darin, dass OCR1A und OCR1B bei Bottom aktualisiert werden. Dadurch ist sichergestellt, dass immer zwei symmetrische Flanken vorliegen. Im Datenblatt der ATmega 48 / 88 / 168 / 328 Familie gibt es dazu noch weitere Grafiken. 

Zusammenfassung: welcher PWM Modus ist geeignet?

  • Fast PWM: bei konstanter Frequenz und Pulsweite oder Anwendungen die nicht allergisch auf die mit der Pulsweitenänderung verbundenen Wanderung der Pulsmitte reagieren
  • Phase Correct PWM: bei Variation der Pulsweite und konstanter Frequenz
  • Phase and Frequency Correct PWM: bei Variation der Frequenz

Den Input Capture Pin verwenden

Wie oben angekündigt, möchte ich nochmal auf die Input Capture Funktion zurückkommen. Sie einfach nur darauf zu reduzieren, dass man mit ICR1 einen weiteren Top Wert zur Verfügung hat, wäre schade.

Mithilfe des Input Capture Pins lassen sich Ereignisse hinsichtlich ihrer zeitlichen Abfolge bequem im Hintergrund messen. Das folgende Beispiel soll das Prinzip verdeutlichen. Es ist nicht besonders schön programmiert mit seinen vielen globalen Variablen, aber darauf soll es hier nicht ankommen.

Ich habe einen Taster an ICP1 (PB0, Arduino Pin8) gehängt. Gedrückt ist er HIGH, sonst LOW. Nach zehnmaligem Drücken wird ausgegeben, zu welchen Zeitpunkten der Taster gedrückt wurde. 

Über den Timer1 im Normal Mode registriert der Sketch die Sekunden. Dazu habe ich den Prescaler 256 gewählt und den Startwert 3036. Die Frequenz ist damit 1 Hz und 1 Sekunde entspricht 62500 Schritten. Bei jedem Timer Overflow werden die Sekunden inkrementiert. Durch das Setzen von ICES1 wird die steigende Kante des Signals an ICP1 ausgewählt. ICIE1 und TOIE1 aktivieren die benötigten Interrupts. 

Bei einem Tasterdruck wird der TCNT1 Zählerstand in ICR1 gespeichert. Abziehen des Startwertes und teilen durch 62.5 ergibt die Millisekunden. Die Tasterdrücke werden in einem array gespeichert.

unsigned int counterStart = 3036;
unsigned int eventTime[10][2];
volatile unsigned int seconds = 0;
unsigned int ms = 0;
unsigned int eventCounter = 0;
volatile bool event = false;
const float ms_const = 62.5;

void setup(){ 
  Serial.begin(9600);
  TCCR1A = 0; // Normal Mode
  TCCR1B = (1<<ICES1) + (1<<CS12);  
  TIMSK1 = (1<<ICIE1) + (1<<TOIE1);
  TCNT1 = counterStart;
} 

void loop() { 
  if(event){
    eventTime[eventCounter][1] = seconds;
    eventTime[eventCounter][2] = round((ICR1 - 3036) / ms_const);
    eventCounter++;
    if(eventCounter==10){
      ausgabeZeiten();
      eventCounter=0;
    }
    event=false;
  }   
} 

void ausgabeZeiten(){
  Serial.println("Tastendruck nach: ");
  for(int i=0; i<10; i++){
    Serial.print(i);
    Serial.print(": ");
    Serial.print(eventTime[i][1]);
    Serial.print(" Sekunden, ");
    Serial.print(eventTime[i][2]);
    Serial.println(" Millisekunden");
  }
}

ISR(TIMER1_OVF_vect){
  TCNT1 = counterStart;
  seconds++;
}

ISR(TIMER1_CAPT_vect){
  event = true;
}

 

Ausgabe des Input Capture Beispielsketches

Ich habe den Taster alle 2-3 Sekunden gedrückt. In der Ausgabe erkennt man schön das Tasterprellen. Natürlich hätte man dieses Beispiel auch einfach mit einer digitalRead / millis Kombination programmieren können. Das Schöne an der Input Capture Methode ist aber, dass man den Zeitpunkt des Events nicht sofort abfragen muss. Er ist in ICR1 gespeichert und läuft einem erst einmal nicht weg. Außerdem lässt sich der Zeitpunkt des Events in sehr hoher Auflösung, nämlich taktgenau ermitteln. 

Externe Taktgeber an T1

Wie der Clock Select Bit Tabelle zu entnehmen ist, könnt ihr an T1 (PD5, Arduino Pin 5) einen externen Taktgeber hängen. Ihr habt dabei die Wahl, ob die steigende oder die fallende Flanke das Taktsignal gibt. In Teil 1 dieses Beitrages hatte ich als Taktgeber einen Taster an T0 gehängt und habe damit eine LED „getoggled“. Umso erstaunter war ich, dass das mit T1 am Arduino nicht funktionierte. Dabei kam folgender Sketch zum Einsatz:

// geht nicht!!!!
void setup(){ 
  TCCR1A |= (1<<COM1A0) + (1<<WGM11) + (1<<WGM10); 
  TCCR1B |= (1<<WGM13) + (1<<WGM12) + (1<<CS12) + (1<<CS11) + (1<<CS10); 
  OCR1A = 10;
  DDRB |= (1<<PB1);
}  

void loop() { 
 // do something else
} 

 

Eigentlich müsste die LED and OC1A (PB1, Arduino Pin9) nach jedem zehnten Tasterdruck an- bzw. ausgehen. Warum das nicht passiert, ist mir nicht klar. Hat jemand eine Idee? Aber es scheint am Setup des Arduino zu liegen, denn derselbe Sketch, übersetzt in C funktionierte mit Atmel Studio am „nackten“ ATmega328P ohne Probleme:

#include <avr/io.h>
#include <util/delay.h>
    
int main(void)
{
    TCCR1A |= (1<<COM1A0) + (1<<WGM11) + (1<<WGM10); 
    TCCR1B |= (1<<WGM13) + (1<<WGM12) + (1<<CS12) + (1<<CS11) + (1<<CS10); 
    OCR1A = 10;
    DDRB |= (1<<PB1);
  
    while (1)
    {
    }
}

 

Danksagung

Die Sanduhr im Beitragsbild habe ich „derGestalterCottbus“ auf Pixabay zu verdanken.

8 thoughts on “Timer und PWM – Teil 2 (16 Bit Timer1)

  1. Nach 4 Tagen Counter-Frust hat mir diese Seite endlich geholfen. Ich sitze an einem ATtiny841 und konnte ums Verrecken die 16-Bit-Timer nicht benutzen. Stellte sich heraus, dass ich nur an OCR1A herumgedreht habe, aber nicht an ICR1. Jetzt funktioniert mein Code endlich. Vermutlich hat der µC vorher tatsächlich bis 0xFFFF gezählt.

    Großen Dank und einen extra Karma-Punkt von mir!

    1. Hallo Christoph,

      schön wenn mein Beitrag hilfreich war. Jedesmal, wenn ich mit Timern programmiere, muss mich auch wieder ein bisschen „hineindenken“. Eigentlich ist es nicht schwierig, aber man kann doch viele (Denk-)Fehler einbauen.

      VG, Wolle

  2. Einen ganzen Tag habe ich Google und Foren umgegraben … bis ich endlich hier gelandet bin!
    DANKE für die übersichtliche Darstellung der Timer und der PWM-Modes.

    Danke auch für die sonst fehlenden Erklärung woher die CSxy und andere merkwürdige Konstanten kommen … als Arduino-Anfänger war ich bei blahblub |= (1 <<CS12) ziemlich überfordert anfangs … Ich finde das Konstrukt zwar immer noch schräg, aber jetzt verstehe ich es!

  3. Hallo, liest sich ganz gut, du solltest aber den Text nochmal überarbeiten bezüglich der Namensgebung der Register und Bitnamen. xy kommt im Datenblatt nicht vor. Im Datenblatt gibt es nur nx, bspw. ICRn oder TCCRnx. n steht für die Timernummer und x für den Compare Channel.

    1. Danke für das Feedback – ich meine das „xy“ habe ich nur in Verbindung mit den „COMnxy“ Bits getan, wobei x für A oder B und y für 0 oder 1 steht.

  4. Hallo, das Wichtigste hätte ich beinahe vergessen. Wenn du auf den Arduino eingehst und dafür Bsp. Code zeigst, dann musst du auch daraufhinweisen das erst alle Timerregister gelöscht werden müssen bevor man eigene Settings einstellt. Ansonsten macht der Timer nicht das was man möchte, weil die Timer für PWM analogWrite voreingestellt sind. Auch sollte man Timer0 in Ruhe lassen.

    1. Das ist ein guter Punkt, da muss ich mal sehen an welcher Stelle ich das noch einfließen lasse – und hoffe das so lange der Kommentar noch mitgelesen wird – vielen Dank dafür!

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.