Timer und PWM – Teil 1 (8 Bit Timer0/2)

Über diesen Beitrag

In diesem Beitrag über Timer und Pulsweitenmodulation (PWM) tauche ich in die Tiefen des Arduino UNO bzw. des ATmega 328P ein. Der Arduino ist eine tolle Erfindung, die es einem leicht macht, in die Welt der Microcontroller einzutreten. Allerdings ist der Preis für die Vereinfachung, dass nicht alle Fähigkeiten des zugrunde liegenden Microcontrollers in die Arduino Welt übernommen wurden. Timer und PWM gehören definitiv dazu. Diese Features sind (mit wenigen Einschränkungen) immer noch zugänglich, aber nur über die etwas kryptisch anmutenden Binäroperationen.

Ich werde versuchen, das Thema mit vielen kleinen Beispielsketchen verständlich zu erklären. Dadurch ist der Beitrag ausgesprochen lang geworden – eher ein Buchkapitel als ein Blogbeitrag. Aber ich möchte gerade auch unerfahrenere Arduinoisten „abholen“. Dabei setze ich allerdings die Kenntnis von Binärlogik und Portmanipulation voraus. Falls ihr da noch Nachholbedarf habt, schaut in meinen letzten Beitrag

Wegen des Umfanges beschränke ich mich in diesem Beitrag auf die 8 Bit Timer. Den 16 Bit Timer behandele ich in Teil 2

Inhalt

Also, das steht euch bevor:

  • Was sind Timer und PWM
  • Welche Timer besitzt der Arduino UNO (bzw. ATmega 328 P)
  • Die vier Timer Modi:
    • Normal
    • CTC (clear timer on compare match)
    • Fast PWM
    • Phasenkorrekte PWM
  • C – Sketche in Atmel Studio

Was ist ein Timer, was ist PWM?

Ich fange ganz einfach an: zunächst einmal gehört zu einem Timer ein Counter. Und der macht, was sich für einen Counter gehört – nämlich zählen. Er zählt wiederholend bis zu einem Limit hoch oder runter oder bis zu einstellbaren Grenzen. Ein Timer wird daraus, wenn er zeitabhängig zählt. Und einen Sinn erhält der Timer schließlich erst dann, wenn das Erreichen des Limits oder eines Zwischenwertes ein Ereignis auslösen kann.

PWM ist einfach ausgedrückt die Technik, die digitalen Ausgänge eures Microcontrollers periodisch HIGH und LOW zu schalten, also Rechtecksignale mit bestimmten Mustern zu erzeugen. Und da das zum Teil sehr schnell und im Hintergrund passieren soll, verwendet man dafür Timer. Ihr habt bestimmt schon PWM Signale erzeugt, z.B. über analogWrite, Servosteuerung oder die tone Funktion. Das ist Fertigkost, sozusagen. Hier lernt ihr nun, wie ihr euch PWM Signale selber zubereiten könnt.

Die Timer des Arduino UNO

In diesem Beitrag (und dem Teil 2) behandele ich die folgenden Timer:

  • Timer0: 8 Bit
  • Timer1: 16 Bit (Teil 2)
  • Timer2: 8 Bit

Es gibt noch mehr spezialisierte Timer (z.B. Watchdog Timer). Die hier genannten sind aber diejenigen, die für PWM und ähnlich Zwecke genutzt werden.

Die korrespondierenden Counter, Kontrollregister und I/O Pins

Die Timer/Counter Register TCNTx

Jetzt wird es erst einmal trocken und theoretisch. Aber keine Sorge, mit den Beispielen wird das Ganze klarer. Nehmt die Dinge erst einmal hin.

Für die Timer gibt es je nach Größe ein oder zwei Counter Register, nämlich TCNT0 (Timer/Counter 0), TCNT1L, TCNT1H und TCNT2. Da der Timer1 16 Bit breit ist, ist er auf zwei Register verteilt. Im Folgenden beschränke ich mich nun aber auf die beiden 8 Bit Timer.

Die Kontrollregister TCCRxy

Die wesentlichen Einstellungen für die Timer werden in den Timer/Counter Control Registern vorgenommen. Zum Timer0 gehören TCCR0A und TCCR0B, zum Timer2 gehören TCCR2A und TCCR2B. Hier als Beispiel die Timer2 Kontrollregister:

TCCR2A Kontrollregister für Timer 2
TCCR2A Kontrollregister für Timer2
TCCR2B Kontrollregister für Timer2
TCCR2B Kontrollregister für Timer2

Ihr findet die Register und ihre Beschreibung auch im recht länglichen Datenblatt zur ATmega 48 / 88 / 168 / 328 Familie.

Die Bezeichnungen für die Register und die Bits sind in den AVR Bibliotheken über #define Anweisungen hinterlegt. Und da diese Bibliotheken ein fester Bestandteil der Arduino IDE sind, macht das den Zugriff sehr einfach.

Im Grunde sind die Bits der Register Schalter: gesetztes Bit = 1 =  Schalter an, nicht gesetztes Bit = 0 = Schalter aus. Wie man die Schalter sinnvoll kombiniert, ist Gegenstand dieses Beitrages.

Die Timer/Counter Interrupt Mask Register TIMSKx

Wenn ein Timer/Counterüberlauf oder das Erreichen eines Vergleichswertes (Compare Match) einen Interrupt auslösen soll, stellt ihr das in den entsprechenden Registern TIMSK0 bzw. TIMSK2 ein. Hier als Beispiel TIMSK2:

Das Setzen von TOIE2 (Timer/Counter2 Overflow Interrupt Enable) bewirkt, dass ein Registerüberlauf von TCNT2 (bei 8 Bit ist das nach 255) einen Interrupt auslöst. Gesetzte OCIE2A und OCIE2B Bits (= Timer/Counter2 Output Compare Match Interrupt Enable A bzw. B) bewirken, dass ein Interrupt ausgelöst wird, wenn TCNT2 den Vergleichswerten in den Output Compare Registern entspricht. 

TIMSK0 ist entsprechend organisiert. Einfach jeweils 2 durch 0 ersetzen.

Die Output Compare Register OCRxy

Für den Timer0 und den Timer2 gibt es die Output Compare Register OCR0A und OCR0B bzw. OCR2A und OCR2B. Wenn ihr diese nutzt, wird der Inhalt in TCNT0 bzw. TCNT2 ständig mit den OCR0A/OCR0B bzw OCR2A/OCR2B Registerwerten verglichen. Was bei einer Übereinstimmung (Compare Match) passiert, wird in den Kontrollregistern (TCCRxy) festgelegt.

Die Output Compare Pins OCxy

Zu den Timer/Countern gehören jeweils zwei Pins, OCxA und OCxB. In den Kontrollregistern (TCCRxy) legt ihr fest, was an den Pins im Falle eines Compare Match oder Timerüberlaufes passieren soll.

Die folgende Grafik zeigt euch, wo diese Pins am ATmega 328P liegen und welches die entsprechenden Arduino Pins sind.

Pinout des Atmega 328P vs. Arduino UNO

Übersicht über die Einstellungen

Haltet durch – ich muss euch noch ein bisschen mit weiteren Informationen quälen bis wir zu den Beispielen kommen. 

Zunächst legt ihr in den Kontrollregistern (TCCRxy) den Wave Form Generation Mode fest. Dafür sind die drei Bits WGMx0, WGMx1 und WGMx2 zuständig. Warum man diese Bits unbedingt auf zwei Register verteilen musste, ist mir ein Rätsel. Hier die Übersicht für den Timer2:

Wave Form Generation Mode Beschreibung für Timer 2
Wave Form Generation Mode Beschreibung für Timer2

Die Output Compare Modi

Der Compare Output Mode, also die Wirkung der Bits COMxyz (mit x = 0 oder 2, y = A oder B, z = 0 oder 1) hängt vom gewählten WGM Modus ab.

Compare Output Mode für non-PWM (Normal Mode 0), Timer 2, COM2Ax
1a) Compare Output Mode für non-PWM (Normal Mode 0), Timer2, COM2Ax
Compare Output Mode für non-PWM (Normal Mode 0), Timer 2, COM2Bx
1b) Compare Output Mode für non-PWM (Normal Mode 0), Timer2, COM2Bx
Compare Output Mode für Fast PWM (WGM Modi 3 und 7), Timer 2, COM2Ax
2a) Compare Output Mode für Fast PWM (WGM Modi 3 und 7), Timer2, COM2Ax
Compare Output Mode für Fast PWM (WGM Modi 3 und 7), Timer 2, COM2Bx
2b) Compare Output Mode für Fast PWM (WGM Modi 3 und 7), Timer2, COM2Bx
Compare Output Mode für Phase Correct PWM (WGM Modi 1 und 5), Timer 2, COM2Ax
3a) Compare Output Mode für Phase Correct PWM (WGM Modi 1 und 5), Timer2, COM2Ax
 Compare Output Mode für Phase Correct PWM (WGM Modi 1 und 5), Timer 2, COM2Bx
3b) Compare Output Mode für Phase Correct PWM (WGM Modi 1 und 5), Timer2, COM2Bx

Das waren die Einstellungen für Timer2. Die gute Nachricht: alle bisherigen Tabellen des Timer2 entsprechen denen des Timer0. Ihr müsst lediglich dort, wo eine 2 den Timer repräsentiert, die 2 durch eine 0 ersetzen.

Prescaler Einstellungen

Die Geschwindigkeit mit der das Timer-/Counterregister TCNTx zählt, orientiert sich am Systemtakt. Beim Arduino UNO sind das 16 MHz. Für viele Anwendungen ist das viel zu schnell. Deshalb gibt es den Prescaler, der bewirkt, dass der Zähler nur jeden n-ten Takt inkrementiert wird. Die Einstellungen dafür nehmt ihr im Timer/Counter Control Register B (TCCRxB) mit den Clock Select Bits (CSx0, CSx1,CSx2) vor. Für den Timer2 gilt:

Einstellung des Prescalers mit den Clock Select Bits an Timer 2
Einstellung des Prescalers mit den Clock Select Bits an Timer2

Für den Timer0 ist die Tabelle unterschiedlich, dazu später mehr. Dass ich mit dem Timer2 beginne, hat seinen Grund. Auch dazu später mehr.

Der Timer im Normal Mode

Geschafft, jetzt geht es in die Praxis. So komplex die Einstellungen, so einfach die Sketche. Die Verwirrung wird sich legen.

Wir starten mit dem Normal Mode. Dieser ist für eher langsame Anwendungen geeignet.

Der erste Sketch im Normal Mode

Für den folgenden Sketch schließt ihr eine LED an den Arduino Pin 7 (PD7) an. Ihr setzt das Kontrollregister A des Timer 2, also TCCR2A auf 0. Damit ist der Pin OC2A inaktiv. Im Kontrollregister B (TCCR2B) werden alle CS2x Bits gesetzt. Der Prescaler beträgt damit 1024. Es ist kein WGM2x Bit gesetzt, damit ist der Normal Mode aktiv. Im Timer/Counter2 Interrupt Mask Register wird das Bit für den Timer Overflow Interrupt gesetzt. Das heißt, dass jedesmal, wenn der TCNT2 überläuft (> 255), ein Interrupt ausgelöst wird. Was Ihr mit dem Interrupt anfangt, legt ihr in der ISR (Interrupt Service Routine) fest. Die ISR behandelt TIMER2_OVF_vect, also den Timer2 Overflow Interrupt. Eine Tabelle mit verfügbaren Interrupts gibt es im Datenblatt.

In diesem Fall führt der Interrupt dazu, dass der Pin 7 invertiert wird. Hinweis: mit DDRD |= (1<<PD7) wurde Pin 7 als Output definiert. Diese Anweisung sollte immer nach den anderen Registeranweisungen erfolgen.

void setup(){ 
  TCCR2A = 0x00; // Wave Form Generation Mode 0: Normal Mode; OC2A disconnected
  TCCR2B = (1<<CS22) + (1<<CS21) + (1<<CS20); // prescaler = 1024
  TIMSK2 = (1<<TOIE2); // interrupt when TCNT2 is overflowed
  DDRD |= (1<<PD7);  // Portmanipulation: replaces pinMode(7, OUTPUT);
} 

void loop() { 
 // do something else
}

ISR(TIMER2_OVF_vect){
    PORTD ^= (1<<PD7); // toggle PD7
}

 

Ladet den Sketch hoch und schaut was passiert. So sieht das Ergebnis am Oszilloskop aus:

Minimale Frequenz im Normal Mode. Die Frequenz bezieht sich auf ein HIGH/LOW Paar.
Minimale Frequenz im Normal Mode. Die Frequenz bezieht sich auf ein HIGH/LOW Paar.

Wenn ihr den Sketch auf den Timer0 übertragt, bekommt ihr eine Fehlermeldung:

Das Problem ist, dass in der Arduino Umgebung der Timer0_OVF_vect für andere Dinge eingesetzt wird. Das ist der Grund, weswegen ich mit dem Timer2 begonnen habe. In anderen Umgebungen, wie z.B. in Atmel Studio, gibt es das Problem nicht. Am Ende des Beitrages komme ich darauf zurück.

Berechnung der Frequenz

Ihr seht, dass die LED bei Verwendung des letzten Sketches sehr schnell blinkt. Der Arduino taktet 16 Millionen mal pro Sekunde. Aufgrund der Prescaler Einstellung wird TCNT2 jeden 1024sten Takt erhöht. TCNT2 beginnt bei 0 und läuft nach 255 über. Das sind 256 Schritte (wie bei einem for(i = 0; i<256; i++)). Die allgemeine Formel für die Frequenz f lautet deshalb:

f=\frac{Systemtakt}{prescaler\cdot256}=\frac{16000000}{1024\cdot 256}=61.035...[\text{s}^{-1}]

Grafisch sieht das ganze so aus:

Grafik 1: TNTC vs. Pin Level
Grafik 1: TNTC vs. Pin Level

TCNT beginnt bei Bottom (hier 0) und zählt bis Top. In der WGM Tabelle seht ihr für den Normal Mode: Top = 0xFF und TOV Flag set on MAX (=TOP). Bei jedem Overflow invertiert PD7.

Weiteres Herunterregeln der Frequenz

Selbst mit dem maximalen Prescaler ist die Frequenz für einen Blinksketch also immer noch sehr hoch. Um den Vorgang weiter zu verlangsamen, führen wir einen Scalefaktor in die ISR ein (counter = 60). Damit erhalten wir eine Frequenz von ungefähr 1. Genau genommen 61,035… / 60 [s-1].

void setup(){ 
  TCCR2A = 0x00; // Wave Form Generation Mode 0: Normal Mode, OC2A disconnected
  TCCR2B = (1<<CS22) + (1<<CS21) + (1<<CS20); // prescaler = 1024
  TIMSK2 = (1<<TOIE2); // interrupt when TCNT2 is overflowed
  DDRD |= (1<<PD7);
} 

void loop() { 
 // do something else
}

ISR(TIMER2_OVF_vect){  // Interrupt Service Routine 
  static int counter = 0;
  counter++;
  if(counter==60){
    PORTD ^= (1<<PD7); 
    counter = 0; 
  }
}

 

Einstellen einer genauen Frequenz

Im nächsten Schritt soll eine Frequenz von genau 1 Hz eingestellt werden. Dafür nutzen wir den Umstand, dass das TCNTx Register auch mit einem Startwert beschrieben werden kann. Es werden dann nur noch 256 - Startwert Schritte bis zum Überlauf durchgeführt. Die allgemeine Formel lautet:

f_{W\!unsch}\cdot Scalefaktor = \frac{Systemtakt}{prescaler\cdot(256-Startwert)}

Mit fWunsch = 1 ergibt sich umgestellt:

Startwert=256-\frac{Systemtakt}{prescaler\cdot Scalefaktor}

Der Systemtakt ist 16 MHz. Bleibt aber immer noch eine Gleichung mit drei Unbekannten. Was wir aber wissen ist, dass der Prescaler nur bestimmte Werte annehmen kann, dass der Startwert kleiner 256 sein muss und er muss ganzzahlig sein. Also probieren wir ein bisschen:

Startwert = 256-\frac{15625}{Scalefaktor}\;\;\;\;\;mit\;prescaler=1024
Startwert = 256-\frac{62500}{Scalefaktor}\;\;\;\;\;mit\;prescaler=256
Startwert = 256-\frac{250000}{Scalefaktor}\;\;\;\;\;mit\;prescaler=64

Der Prescaler 1024 gibt zu „krumme“ Werte. Mit einem Prescaler von 256 sieht es besser aus. Nehmen wir einen Scalefaktor von 500 dazu, kommen wir auf einen Startwert von 131. Mit einem Prescaler von 64 und einem Scalefaktor von 1000 ergibt sich ein Startwert von 6. Beides gut. 

Wenn ihr keine Lust habt zu rechnen, dann gibt es im Netz Kalkulatoren dafür, z.B. hier oder hier. Zwar berücksichtigen diese Kalkulatoren keine Scalefaktoren, aber eine Hilfe sind sie trotzdem.

Der vorherige Sketch wird geringfügig verändert. TCNT2 muss zu Beginn und nach jedem Überlauf wieder auf den Startwert eingestellt werden.

byte counterStart = 131;  // alternative: 6
unsigned int scaleFactor = 500; // alternative: 1000

void setup(){ 
  TCCR2A = 0x00; // OC2A and OC2B disconnected; Wave Form Generation Mode 0: Normal Mode
  TCCR2B = (1<<CS22) + (1<<CS21); // prescaler = 256
  // TCCR2B = (1<<CS22); // prescaler = 64; 
  TIMSK2 = (1<<TOIE2); // interrupt when TCNT2 is overflowed
  TCNT2 = counterStart;
  DDRD |= (1<<PD7);
} 

void loop() { 
 // do something else
}

ISR(TIMER2_OVF_vect){
  static int counter = 0;
  TCNT2 = counterStart;
  counter++;
  if(counter==scaleFactor){
    PORTD ^= (1<<PD7);
    counter = 0; 
  }
}

 

Eine Anwendung im Normal Mode: asynchrone LEDs

Vielleicht ist euch aufgefallen, dass bei den bisherigen Sketchen die loop Schleife leer war. Das LED Blinken passiert automatisch im Hintergrund. Stünde der Blink Code mit einer delay Funktion in der loop Schleife, müsste jeder zusätzliche Code „drumherum“ gebastelt werden.

Als ganz simples Beispiel lassen wir im nächsten Sketch zwei LEDs asynchron blinken. Versucht einmal, das ohne Timer zu programmieren.

byte counterStart = 131;  // alternative: 6
unsigned int scaleFactor = 500; // alternative: 1000

void setup(){ 
  TCCR2A = 0x00; // OC2A and OC2B disconnected; Wave Form Generator: Normal Mode
  TCCR2B = (1<<CS22) + (1<<CS21); // prescaler = 256
  // TCCR2B = (1<<CS22); // prescaler = 64; 
  TIMSK2 = (1<<TOIE2); // interrupt when TCNT2 is overflowed
  TCNT2 = counterStart;
  DDRD |= (1<<PD7) + (1<<PD6); // Pin 6 und Pin 7 als Ausgang
} 

void loop() { 
 PORTD ^= (1<<PD6);
 delay(723);
}

ISR(TIMER2_OVF_vect){
  static int counter = 0;
  TCNT2 = counterStart;
  counter++;
  if(counter==scaleFactor){
    PORTD ^= (1<<PD7);
    counter = 0; 
  }
}

 

Der Timer im CTC Mode

Im „Clear Timer on Compare Match“ Modus, kurz CTC, wird TCNTx nicht nach 256 Schritten, sondern nach Erreichen des in OCRxA hinterlegten Wertes auf Null zurückgesetzt. Wieder wollen wir eine LED im Sekundentakt blinken lassen. Die Formel für die Frequenzberechnung lautet:

f_{Wunsch}\cdot Scalefaktor=\frac{Systemtakt}{prescaler\cdot(1+Top)}

Und wieso (1 + Top) und nicht nur Top? Ganz einfach: weil die 0 mitzählt! Aufgelöst nach Top und mit fWunsch = 1:

Top=\frac{Systemtakt}{prescaler\cdot Scalefaktor}-1

Ein Systemtakt von 16 MHz, ein Prescaler von 256 und ein Scalefaktor von 500 ergibt einen Top-Wert von 124. Diesen Wert tragen wir in das OCRxA Register ein, hier: OCR2A = 124.

Gemäß der WGM Tabelle müssen wir für den CTC Mode das WGM21 Bit setzen. Für den Prescaler 256 setzen wir CS22 und CS21. Im Timer/Counter Interrupt Mask Register setzen wir OCIE2A (Output Compare Interrupt Enable A, Timer 2), da diesmal kein Timer Overflow stattfindet, sondern ein Compare Match. Entsprechend müssen wir auch noch die ISR Routine abändern und als Argument TIMER2_COMPA_vect übergeben.

unsigned int scaleFactor = 500;

void setup(){ 
  TCCR2A = (1<<WGM21); // Wave Form Generation Mode 2: CTC, OC2A disconnected
  TCCR2B = (1<<CS22) + (1<<CS21) ; // prescaler = 256
  TIMSK2 = (1<<OCIE2A); // interrupt when Compare Match with OCR2A
  OCR2A = 124;
  DDRD |= (1<<PD7);
  
} 

void loop() { 
 // do something else
}

ISR (TIMER2_COMPA_vect){  // Interrupt Service Routine 
  static int counter = 0;
  counter++;
  if(counter==scaleFactor){
    PORTD ^= (1<<PD7); // 
    counter = 0; 
  }
}

 

Wenn wir den Normal Mode mit dem CTC Mode vergleichen, dann haben wir einmal von einem bestimmten TCNT Wert bis 255 gezählt, im anderen Fall von 0 bis zu dem in OCRA hinterlegten Wert.

Der Timer im Fast PWM Mode

Im Fast PWM Modus arbeitet man für gewöhnlich mit den Pins, die dem Timer zugeordnet sind. Das ist für den

  • Timer0: OC0A (=PD6, Arduino Pin 6) / OC0B (=PD5, Arduino Pin 5)
  • Timer2: OC2A (=PB3, Arduino Pin 11) / OC2B (=PD3, Arduino Pin 3)

Der PWM Modus arbeitet im Mode 3 mit einem Timer Overflow nach 255 (0xFF). Im Mode 7 arbeitet der PWM Modus mit einem Compare Match. Dabei ist Top der in OCRxA hinterlegte Wert.

Fast PWM an OC2B

Als Beispiel nehmen wir den Mode 7. Ziel ist ein Rechtecksignal mit einer Frequenz von 1 kHz an OC2B. In einer Periode soll das Signal 20% der Zeit HIGH sein und 80% LOW. Man sagt auch: der Duty Cycle ist 20%.

Wir setzen dazu das COM2B1 Bit. Das bedeutet laut Tabelle: „Clear OC2B at Compare Match, Set OC2B at BOTTOM“. Der Compare Match bezieht sich dabei auf den in OCR2B hinterlegten Wert. Grafisch sieht das so aus:

Grafik 2: PWM Signal an OC2B, Timer 2
Grafik 2: PWM Signal an OC2B

Erstmal müssen wir aber wieder rechnen. Wir brauchen den Top-Wert für die Frequenz und den Wert für OCR2B für den Duty Cycle. Wegen der kleinen Frequenz brauchen wir keinen Scalefaktor. Es gilt:

f_{Wunsch}=\frac{Systemtakt}{prescaler\cdot (1+Top)}
Top=\frac{16000}{prescaler}-1\;\;\;\;mit\;\;\;\;Takt=16000000\;\;\;\; und \;\;\;\;f=1000

Ein Prescaler von 64 ergibt 249 für Top. Das sind 250 Schritte. Ein Fünftel davon sind 50. Da der Zähler bei 0 beginnt, ist OCR2B 49, jedenfalls theoretisch. Praktisch müsst ihr das mal ausprobieren. Ich habe das gewünschte Signal besser mit der Paarung 249 / 50 getroffen. Es kommt natürlich auch darauf an, wie genau der Microcontroller taktet.

So sieht der Sketch aus:

// Period = 1 ms => Frequenz = 1kHz
void setup(){ 
  // WGM22/WGM21/WGM20 all set -> Mode 7, fast PWM
  TCCR2A = (1<<COM2B1) + (1<<WGM21) + (1<<WGM20); // Set OC2B at bottom, clear OC2B at compare match
  TCCR2B = (1<<CS22) + (1<<WGM22); // prescaler = 64; 
  OCR2A = 249;
  OCR2B = 49;
  DDRD |= (1<<PD3);
} 

void loop() {}

 

… und so sieht es dann am Oszilloskop aus:

PWM Signal an OC2B
PWM Signal an OC2B

Fast PWM an OC2A

Für ein PWM Signal an OC2A ist Mode 7 nur bedingt geeignet, da der Compare Match gleich Top ist. Es fehlt die Möglichkeit das Signal in einen HIGH und einen LOW Teil zu unterteilen. Schaut in die Tabellen, um das nachzuvollziehen. In Mode 3 zählt der Timer bis 255, deshalb lassen sich dort beliebige Duty Cycles realisieren. Der Nachteil ist jedoch, dass sich nicht jede beliebige Frequenz einstellen lässt, da Top fix ist.

Was sich hingegen an OC2A gut realisieren lässt, sind PWM Signale mit 50% Duty Cycle. Das macht ihr mit der Kombi: Mode 7 / gesetztes COM2A0. Gemäß Tabelle heißt das: Toggle O2CA on Compare Match. Dabei verdoppelt sich die Periode bzw. die Frequenz halbiert sich.

// Period = 2 ms / Frequenz = 500 Hz. 
void setup(){ 
  // WGM22/WGM21/WGM20 all set -> Mode 7, fast PWM, TOP = OCR2A
  TCCR2A = (1<<COM2A0) + (1<<WGM21) + (1<<WGM20); // Toggle OC2A at compare match
  TCCR2B = (1<<CS22) + (1<<WGM22); // prescaler = 64; 
  OCR2A = 249;
  DDRB |= (1<<PB3); // PB3 = OC2A = Arduino Pin 11
} 

void loop() { 
}

 

AnalogWrite – eine PWM Anwendung

Dass ein analogWrite kein analoges, sondern ein PWM Signal ist, erkennt ihr am Oszilloskop:

Ein analogWrite(pin, 50) am Oszilloskop

Ihr habt kein Oszilloskop?

Den einen oder anderen mag es frustrieren, dass er das Ergebnis der Fast PWM Sketche nicht überprüfen kann, da er kein Oszilloskop besitzt. Drei Vorschläge dazu:

  • auch im Fast PWM Modus könnt ihr mit Compare Match Interrupts arbeiten, in der ISR einen Counter einfügen und damit quasi in Zeitlupe das PWM Signal beobachten. Ihr könnt sogar OCIE2A und OCIE2B setzen und zwei ISR Routinen nutzen (TIMER2_COMPA_vect / TIMER2_COMPB_vect).
  • nutzt die Technik aus meinem Beitrag über IR Fernbedienungen, um schnelle Signale zu analysieren
  • kauft euch ein DSO 138 Oszilloskop für unter 30 Euro. Sucht danach bei Amazon oder eBay. Die Teile funktionieren erstaunlich gut:
DSO 138 Oszilloskop in Aktion

Der Timer im Phase Correct PWM Mode

Im phasenkorrekten PWM Modus (Phase Correct PWM Mode) zählt der Counter von BOTTOM bis TOP hoch und dann wieder herunter bis BOTTOM. Als Beispiel nehmen wir den Modus 5 /  gesetztes COM2B1, also: „Clear OC2B when up-counting, set OC2B when down-counting“. Grafisch sieht das so aus:

Phase Correct PWM mit dem Timer 2 an OC2B.
Grafik 3: Phase Correct PWM mit dem Timer 2 an OC2B

Damit wird die Frequenz gegenüber der Fast PWM Methode halbiert.

Phasenkorrekt bedeutet, dass die Umkehrpunkte von HIGH nach LOW (bzw. umgekehrt) im gleichen Abstand vor und hinter Bottom bzw. Top des Timers liegen. Dadurch ergibt sich ein symmetrisches Signal, auch wenn der OCR2B Compare Wert dynamisch verändert wird. Eine sehr hilfreiche Animation, die den Unterschied zum Fast PWM deutlich macht, findet ihr hier. Die phasenkorrekte PWM wird vor allem bei Motorsteuerungen angewendet. Wer mehr über die PWM Modi wissen möchte, schaut z.B. hier. Es gibt noch einen weiteren PWM Modus und zwar den „phasen- und frequenzkorrekten PWM“ Modus. Dieser wird uns bei dem 16 Bit Timer in Teil 2 begegnen. Dort werde ich auch etwas detaillierter auf die Unterschiede der PWM Modi eingehen.

Und hier nun ein Sketch Beispiel:

// Period = 2 ms 
void setup(){ 
  // WGM22/WGM20 all set -> Mode 5, phase correct PWM
  TCCR2A = (1<<COM2B1) + (1<<WGM20); // Set OC2A at bottom, clear OC2B at compare match
  TCCR2B = (1<<CS22) + (1<<WGM22); // prescaler = 64; 
  OCR2A = 249;
  OCR2B = 49;
  DDRD |= (1<<PD3);
} 

void loop() { }

 

Timer 0 vs. Timer 2

Wie schon weiter oben erwähnt, sind die Strukturen des Timer0 und Timer2 sehr ähnlich. Den wesentlichen Unterschied seht ihr in der Clock Select Bit Tabelle für den Timer0:

Prescaler / Clock Select mit den Clock Select Bits an Timer 0
Prescaler / Clock Select mit den Clock Select Bits an Timer0

Es stehen weniger Prescaler zur Auswahl, stattdessen könnt ihr hier auch einen externen Taktgeber verwenden. Diesen schließt ihr an T0 (PD4, Arduino Pin 4) an. Darüber hinaus könnt ihr auswählen, ob auf die steigende oder die fallende Kante gezählt wird.

Einfaches Beispiel für externen Taktgeber

Als Taktgeber verwenden wir einen schlichten Taster. An den Ausgabepin OC0A (PD6, Arduino Pin 6) kommt eine LED.

Timer 0 mit Taster als externem Taktgeber

Jetzt wählen wir Mode 7 und setzen alle CS0x Bits (Clock on rising edge). OCR0A setzen wir auf 10.

void setup(){
  TCCR0A = (1<<COM0A0) + (1<<WGM01) + (1<<WGM00); // WGM 7: fast PWM; since WGM02 = 1 --> Toggle OC0A on Compare Match;
  TCCR0B = (1<<CS02) + (1<<CS01) + (1<<CS00) + (1<<WGM02);  // External clock source on T0 (rising edge)
  OCR0A = 10;
  TCNT0 = 0;
  DDRD |= (1<<PD6);
} 

void loop() { 
}

Theoretisch sollte die LED bei jedem elften Tasterdruck an- bzw. ausgehen. Durch das Tasterprellen schaltet die LED entsprechend früher um.

Das Zählen externer Ereignisse im Hintergrund kann für bestimmte Anwendungen sehr nützlich sein. Dafür gibt es übrigens auch spezielle Counter ICs, die ich hier beschrieben habe.

Wenn ihr einen Uhrenquarz als Taktgeber verwendet, dann könnt ihr euch eine Quarzuhr bauen. Im Detail ist das hier beschrieben. Ziemlich cool.

Hilfe für die Timer- / PWM-Programmierung

Die Timer- und PWM-Programmierung kann schon etwas nervig und verwirrend sein. Eine großartige Hilfe ist das Tool „Arduino Web Timers“ von David Buezas, das ihr hier findet. Ich kann es wirklich wärmstens empfehlen.

Verwendung von Atmel Studio

Ganz zu Beginn hatte ich erwähnt, dass der Timer0 Overflow Interrupt in der Arduino Umgebung nicht zugänglich ist. Verwendet ihr z.B. die (kostenlose) Software Atmel Studio, dann habt ihr diese Einschränkung nicht. Eine Einführung in Atmel Studio findet ihr hier. Das ist nichts, was man mal so eben nebenbei macht, aber es lohnt sich aus meiner Sicht.

Der Sketch für die zwei asynchron blinkenden LEDs sieht, übertragen auf den Timer0, in Atmel Studio so aus:

#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
uint8_t counterStart = 131;
uint16_t scaleFactor = 500;

int main(void)
{
  TCCR0A = 0x00; // OC0A and OC0B disconnected; Wave Form Generation Mode 0: Normal Mode
  TCCR0B = (1<<CS02); // prescaler = 256
  TIMSK0 = (1<<TOIE0); // interrupt when TCNT0 is overflowed
  TCNT0 = counterStart;
  DDRB |= (1<<PB1) + (1<<PB0);
  sei();  // activate interrupts
      
  while (1)
  {	
    PORTB ^= (1<<PB0);
    _delay_ms(723);	
  }
}


ISR(TIMER0_OVF_vect)
{
  static int counter = 0;
  TCNT0 = counterStart;
  counter++;
  if(counter==scaleFactor)
  {
    PORTB ^= (1<<PB1);
    counter = 0;
  }
}

 

Abschließende Worte

So, ich hoffe der eine oder andere hat bis hier durchgehalten. Ich persönlich fand es jedenfalls sehr spannend als ich die ersten Erfahrungen mit den Timern gemacht habe und dadurch einen tieferen Einblick in die innere Struktur des ATmega 328P bekam.

Was folgt ist, wie oben schon angekündigt, Teil 2, der sich mit dem 16 Bit breiten Timer1 beschäftigt.

Danksagung

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

36 thoughts on “Timer und PWM – Teil 1 (8 Bit Timer0/2)

  1. Hallo, inwiefern kann man die Codierung auch beim Arduino Mega 2560 anwenden? Welche Unterschiede muss man beachten?
    Grüße

    1. Hallo Max,
      die gute Nachricht ist, dass das Prinzip der Timerprogrammierung beim zugrundeliegenden ATmega2560 dasselbe ist. Die Register sind nach demselben Schema benannt, und die verschiedenen Waveform Modi und Output Compare Modi sind weitestgehend identisch. Die schlechte Nachricht ist, dass man die Sketche nicht 1:1 übertragen kann.

      Der ATmega2560 hat die zwei 8-Bit Timer 0 und 2 und die 4 16-Bit Timer 1, 3, 4, 5. Den Timer0 solltest du wie beim ATmega328P nicht anrühren, da millis() und die delay() darüber laufen. Der Timer2 steht zu deiner freien Verfügung. Die Timerausgänge liegen allerdings an anderen Pins. Wenn du beispielsweise den Sketch timer2_fast_pwm_mode_80_20_an_OC2B.ino übertragen willst, dann musst du lediglich beachten, dass OC2B an D9 = PH6 liegt. Also so:

      / Period = 1 ms => Frequenz = 1kHz
      void setup(){ 
        // WGM22/WGM21/WGM20 all set -> Mode 7, fast PWM
        TCCR2A = (1<<COM2B1) + (1<<WGM21) + (1<<WGM20); // Set OC2B at bottom, clear OC2B at compare match
        TCCR2B = (1<<CS22) + (1<<WGM22); // prescaler = 64; 
        OCR2A = 249;
        OCR2B = 49;
        DDRH |= (1<<PH6); // OC2B = PH6 = D9
      } 
      
      void loop() {}
      

      Für den 16-Bit Timer1 habe ich auch einmal einen Sketch übertragen. Hier wandert die Ausgabe an OC1A von PB1 (D9) auf dem ATmega328P zu PB5 (D11) auf dem ATmega2560:

      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<<PB5); // OC1A = PB5 = D11
      } 
      
      void loop() { 
       // do something else
      } 
      

      Es lohnt sich auch mal ins Datenblatt zu schauen, insbesondere in die Kapitel über die Timer:
      https://ww1.microchip.com/downloads/en/devicedoc/atmel-2549-8-bit-avr-microcontroller-atmega640-1280-1281-2560-2561_datasheet.pdf

      VG, Wolfgang

  2. Hallo Wolfgang,
    ich versuche mit dem Timer 2 eine PWM von 45%-85% mit prescale = 1 zu generieren.
    (Der Timer 1 wird leider vom Modbus-Treiber #include „MgsModbus.h“ genutzt.)

    TCCR2A = (1<<COM2B1)+ (1<<WGM22) + (1<<WGM21) + (1<<WGM20);
    TCCR2B = (1<<CS20) + (1<<WGM22) + (1<<WGM21) + (1<<WGM20) ; // prescaler = 1;
    OCR2A = 695 ;
    OCR2B = 313 ;
    DDRD |= (1<<PD3);

    Bei einem prescaler = 8 funktioniert alles, jedoch schwankt die Duty zwischen 0,82-0,89.

    Bei enem prescale von 1 erhalte ich eine Frequenz von 1360 Hz und ein Duty von 0,315 ?
    Was mache ich falsch?

    VG
    Joachim

    1. Hallo Joachim,
      da sind zwei Fehler:
      1) der Timer 2 ist ein 8-Bit Timer, d. h. er zählt nur bis 255 und entsprechen dürfen OCR2A und OCR2B auch nicht größer als 255 sein.
      2) WGM20 und WGM21 werden in TCCR2A geschrieben, WGM22 in TCCR2B. Du hast WGM20, WGM21 und WGM22 in beide Register geschrieben.

      Ich habe interessehalber mal nachvollzogen, was bei deinen Einstellungen passiert. Dabei ist zu beachten, dass diese ganzen Kürzel wie beispielsweise WGM22 lediglich Zahlen sind, die über define Direktiven in den AVR Bibliotheken definiert sind. WGM22 wäre z.B. 3 und (1<1<WGM22) ist entsprechend (1<1<3) = 0b00001000.

      In TCCR2A ist das Bit 3 nicht definiert. Insofern stört es nicht, dass du versucht hast das, das Bit zu setzen. Wenn man die Bits in TCCR2B zusammenrechnet, erhält man:
      TCCR2B = (1<1<CS20) + (1<1<WGM22) + (1<1<WGM21) + (1<1<WGM20) ;
      TCCR2B = 0b00000001 + 0b00001000 + 0b00000010 + 0b00000001 = 0b00001100;
      Das heißt, du hast das Bit 3 (WGM22) und das Bit 2 (CS22) gesetzt. Damit hast du tatsächlich den Fast PWM Modus eingestellt (was du wolltest) und einen Prescaler von 64 (was du nicht wolltest).

      OCR2A hast du als 695 definiert, das ist 0b1010110111. Alle Bits oberhalb von 255 werden ignoriert, d.h. relevant ist 0b10110111 = 183. OCR2B ist 313 = 0b100111001. Davon bleibt 0b00111001 = 57.

      OCR2A ist dein TOP und gibt die PWM Frequenz vor:
      f = 16000000 / (64 * (1 + 183)) = ca. 1359 Hz. Passt perfekt zu deinen Werten.

      Das Verhältnis von OCR2B + 1 zu OCR2A + 1 gibt den Duty Cycle vor. 58/184 = ca. 0,315. Passt ebenso perfekt!!! Ist nur eben nicht, was du wolltest. Wenn du lediglich die beiden o.g. Punkte beachtest, wird es funktionieren.

      Um sich das Leben hinsichtlich der Bits etwas zu erleichtern, empfehle ich das Arduino Web Timer Tool (hier schon mit den Einstellungen, die du gewählt hattest):
      https://dbuezas.github.io/arduino-web-timers/#mcu=ATMEGA328P&timer=2&CompareOutputModeB=set-on-match%2C+clear-at-max&OCR2B=128&clockPrescalerOrSource=64&topValue=OCR2A&OCR2A=85

      VG, Wolfgang

  3. Hi Wolfgang,
    erstmal vielen Dank für die Anleitung. Die hat mir schon sehr geholfen.
    Eine Frage hätte ich dazu:
    Du hast geschrieben, dass sich der der OC2A Ausgang nicht für den Fast PWM Mode 7 anbietet.
    Leider erschließt sich mir nicht ganz in den Tabellen warum dort der Compare Match gleich dem Top sein soll.
    In den Tabellen steht bei COM2A1=1 und COM2A0=0 das gleiche drin wie eine Tabelle darunter für den OC2B Ausgang.

    Könntest du das nochmal etwas genauer erläutern?

    Ich brächte für einen B6C Gleichrichter 3 PWM Ausgänge die ich zu unterschiedlichen Zeiten mit einer PWM beaufschlagen kann. Um dort die Frequenz besser kontrollieren zu können wäre Fast PWM Mode 7 hilfreich, sonst wäre ich auf die nicht so schönen Einstellungen vom Prescaler angewiesen.
    Vielen Dank!

    1. Hi Florian,
      lang ist es her, dass ich den Beitrag geschrieben habe. Ich muss mich selbst erst mal wieder reindenken.

      Ich hatte ja zunächst beschrieben, wie der Fast PWM Modus 7 an OC2B funktioniert. Bei OC2B ist die Welt in Ordnung: Bei Botton ist O2CB je nach Einstellung HIGH oder LOW. TNTC zählt hoch, bei einem Compare Match mit OCR2B wechselt OC2B und dann wird weitergezählt bis OCR2A. Dann wird heruntergezählt und bei OCR2B gibt es wieder den Wechsel. Also wird der Duty Cycle über das Verhältnis von OCR2B zu OCR2A eingestellt.
      Bei OC2A gibt es nun aber ein Problem. Das zuständige Compare Match Register ist OCR2A. Der in OCR2A hinterlegte Wert ist aber auch der TOP Wert. Und wie soll man dann einen Duty Cycle einstellen? Wäre der TOP Wert für OC2A OCR2B, dann könnte man genauso wie an OC2B vorgehen. Ich hoffe, jetzt ist es klarer.
      VG, Wolfgang

  4. hallo Wolfgang,
    toller blogeintrag – wie immer!
    Folgende frage:
    Ich versuche mit einem clone des motortreibers DRV8871 von adafruit die versorgungsspannung von 12V per PWM (arduino nano, pin 10 als PWM-pin) in stufen runterzuregeln. Versorgt werden damit vier 12V encodermotoren (da sind nochmals zwei dual-motortreiber mit drinn). Es sind sechs stufen für die spannungsdrosselung vorgesehen, zwei würden mir aber auch reichen, wenn ich damit die drehzal der vier getriebemotoren und damit die geschwindigkeit des roboters halbieren könnte. Es funktioniert aber nicht so, wie ich es mir in meiner naivität vorgestellt habe:
    Alle motoren drehen mit einer kaum merkbar langsameren geschwindigkeit bis zu dem zeitpunkt, an dem nur noch einer der vier motoren langsam weiterdreht und das passiert ziemlich abrupt. Ich dachte nun daran, ob es möglich ist, den bereich in dem die versorgungsspannung quasi zusammenbricht irgendwie zu „strecken“. Wäre da der prescaler des PWM eine lösung? Ein ja/nein würde mir schon reichen, damit ich da nicht zu lange einer idee nachrenne…
    dank & gruss
    georg

    1. Hallo Georg, mir ist der Aufbau deiner Schaltung noch nicht klar. Der DRV8871 ist ja schon ein Motortreiber. Und dann verwendest du Motoren, die auch nochmal Motortreiber besitzen. Wieso zwei Motortreiber hintereinander? Der DRV8871 wird über ein PWM Signal gesteuert. Und dann willst du zusätzlich noch die Versorgungsspannung über ein weiteres PWM Signal steuern? Ich kann mir das noch nicht so ganz vorstellen. Tut mir leid – mit ja oder nein kann ich die Frage nicht beantworten.
      VG, Wolfgang

      1. hallo Wolfgang,
        der nano steuert zwei dual-motortreiber ( HG7881 /800mA) mit jeweils zwei 12V-DC getriebemotoren dran. Das funktioniert alles soweit, mit dem nano kann ich die vier motoren ein und ausschalten, die drehrichtung wechseln und auch die vier encoder auslesen. Was nicht geht, ist das steuern der drehzahlen, die dafür notwendigen PWM-pins gibt der nano nicht mehr her (vielleicht auch deshalb weil ich am anfang nicht daran dachte die PWM pins dafür zu reservieren). Die schaltung ist nun fertig (eine änderung der pinbelegung ist nicht mehr möglich, dann müsste ich die komplette leiterplatte völlig neu aufbauen) und ich hatte nun die idee den letzten freien PWM-pin dafür zu nutzen per PWM die versorgungsspannung der motoren mit dem DRV8871 eben auf z.b. 9V zu reduzieren um die getriebemotoren (als alternativ geschwindigkeit) mit halben drehzahlen drehen zu lassen. Der versuch ist nur halb erfolreich, weil – so meine laienhafte erklärung – die spannung nun bei einem PWM wert um die 130 zusammenbricht und die motoren bleiben (bis auf einen) stehen. Ich weiss natürlich nicht, ob sich ein motortreiber auch für so etwas eignet, vielleicht gibts auch andere möglichkeiten…

        gruss georg

        1. Hallo Georg,
          das müsste ich selbst probieren. Aus der Ferne kann ich dir leider nicht wirklich weiterhelfen.
          VG, Wolfgang

          1. hallo Wolfgang,
            ich habe das problem an einer anderen stelle nochmals beschrieben, ich denke ich komme der lösung näher. Folgendes wurde dort diskutiert:
            ——————–
            Der Motor braucht 12V, um genügend Kraft aufzubauen, damit er sich bewegt. Wenn man die Spannung senkt, geht er irgendwann in die Knie. Ich vermute, man muss mit der Frequenz des PWM-Signals genauer regeln. Eigentlich über die Spannung den Strom regeln
            ——————–
            es reichen ihm auch vielleicht 11.8V. Aber deshalb ja auch die frage nach der streckung des regelbereichs: 11.8V bis 12V auf den PWM-bereich von 0 bis 255…
            ——————–
            Der Atmega hat einen 16 Bit Timer/Counter mit PWM. Den müsstest du aber wohl mit Registern ansteuern, wenn die Library keine 16 Bit unterstützt.
            Damit könntest du 65k Abstufungen erreichen, das wären dann fast 1100 Stufen zwischen 11,8 und 12 Volt.
            ——————–
            und nun ist die frage, was denkst du dazu? Wie könnte ich das mit meiner hardware (die ja an sich schon läuft) ausprobieren?

            gruss georg

            1. Hallo Georg,
              ja, warum nicht, probieren geht über studieren. Wenn deine Teile verbaut sind, dann kannst du nur eine Ersatzschaltung bauen (falls du noch Ersatzteile hat) oder du musst halt provisorisch etwas dranbasteln. Wie der 16 Bit Timer (Timer1) funktioniert steht im Folgebeitrag zu diesem Beitrag. Am einfachsten wäre die Erzeugung eines PWM Signals an OC1A (= PB1 = Arduino Pin 9) oder OC1B (= PB2 = Arduino Pin 10). Da ich keine Erfahrung mit den von dir verwendeten Motortreibern habe, kann ich dir nichts über die Erfolgsaussichten sagen.
              VG, Wolfgang

  5. Hallo Wolfgang,
    Vielen Dank für deine ausführliche Beschreibung.
    Ich hab folgendes Problem:
    Ich verwende die Serielle Schnittstelle des Arduino, und benötige Timer1 und Timer0 wegen des externen Clockeingangs. Wenn ich jetzt schreibe:
    // Timer 0 = BurstCounter
    TCCR0A |= (1<<WGM01); // CTC Mode
    TCCR0B |= ((1<<CS02) + (1<<CS01)); // External clock source on T0
    OCR0A = BurstCnt;
    TCNT0 = 0;
    hängt die Serielle Schnittstelle, obwohl der Clock Eingang T0 eigentlich bei asynchroner Übertragung (RS232) gar nicht benutzt wird.
    Hast du ne Idee?

    1. Hallo Walter,

      die ehrliche, schlichte Antwort ist: nein. Das bringe ich nicht zusammen. Vielleicht hat ein anderer Leser eine Idee?

      VG, Wolfgang

      1. Eigentlich hat meines Wissens die Serielle Schnittstelle im asynchronen Modus (RS232) gar nichts mit Timer0 zu tun. (im synchronen Modus teilt sie den externen Clock Eingang XCK mit T0)
        Vielleicht hat es mit der Implementierung der „Serial“ Funktion in der Arduino IDE zu tun…

  6. Hallo Wolfgang
    komme mit TIMSK0 und OCIE0B und OCIE0A mit OCRxy und OCxy nicht klar. Wenn ich TOIE0 setze kann ich mit OCIE0A festlegen ob auf ..A der Interrupt kommt. Doch was mache ich mit OCRxy bzw. OCxy? Wird der Interrupt auf bestimmte Pins ausgegeben oder kann ich festlegen was damit gemacht wird? Leider finde ich im Netz auch kaum was konkretes dazu.
    achim

    1. Hallo Achim,
      ich muss mich da auch immer wieder neu reindenken. Atmel (oder jetzt Microchip) macht es einem nicht leicht.

      Ich hatte geschrieben:
      Das Setzen von TOIE2 (Timer/Counter2 Overflow Interrupt Enable) bewirkt, dass ein Registerüberlauf von TCNT2 (bei 8 Bit ist das nach 255) einen Interrupt auslöst. Gesetzte OCIE2A und OCIE2B Bits (= Timer/Counter2 Output Compare Match Interrupt Enable A bzw. B) bewirken, dass ein Interrupt ausgelöst wird, wenn TCNT2 den Vergleichswerten in den Output Compare Registern entspricht.

      Mit anderen Worten: TIMSK0 ist das das Register, in dem sich die Bits TOIE0, OCIE0A und OCIE0B befinden. Durch Setzen der Bits legst du fest, unter welchen Bedingungen ein Interrupt ausgelöst wird, nämlich wenn das Timer Register (TNTC0) überläuft oder wenn ein Vergleichswert in den Output Compare Registern erreicht wird.

      Du kannst die Interrupts über eine ISR verwenden. TIMER0_OVF_vect für einen Timer Overflow und TIMER0_COMPA_vect/TIMER0_COMPB_vect für einen Compare Match. Diese Interrupts kannst du z.B. nutzen, um einen Pin zu schalten oder was auch immer. Bei einem Compare Match (nicht für den Overflow) kannst du aber auch einstellen, dass der verbundene OC0A bzw OC0B Pin HIGH geht (set), LOW geht (clear) oder wechselt (toggle). Dieses Verhalten (den Output Compare Modus) stellst du im Register TCCR0A / TCCR0B über die COM0… und tw. über die WGM… Bits ein (siehe Abschnitt Output Compare Modi). Das ist dann im Grunde die Ausgabe des Compare Match Interrupts.

      Ich hoffe, das hilft und verwirrt dich nicht noch mehr. Sonst müssen wir das konkret an einem Beispiel durchgehen.
      VG, Wolfgang

      1. Morgen Wolfgang
        Danke für deine Info. Leider ist es manchmal komisch Sachen kompliziert darzustellen wenn es nicht auch einfach geht. Werde es mit dem was ich bisher verstanden habe (nach mehrmaligen lesen) noch mal angehen und es probieren. Da ich gerade mit den tiefen des 8 Bit Timers arbeite, gibt es genügend Möglichkeiten für Fehler. Schönes Wochende
        achim

        1. Wenn du Fragen zu einem konkreten Sketch hast, kannst du ihn mir auch schicken. Ich musste mich auch wirklich lange mit dem Thema beschäftigen, bis ich es irgendwann verstanden hatte. Schwer verdauliche Kost!

          1. Hallo Wolfgang
            Bin noch am suchen der Ausgangspin für OC..A und..B. Wenn ich richtig verstanden habe kann mit TIMSK und OC.. einen Interrupt auslösen. Dieser kann mir wiederrum einen IO toggeln oder anderes damit machen. Wie ist das bei PWM, wenn ich z.B. einen Servo zum testen in 3 verschiedene Positionen setzen will?

            1. Hallo Achim,
              wo die Ausgänge sind, findest du oben im Pinout Schema, z.B. ist OC0A der Pin PD6, bzw. die Nr. 12, wenn du die Beinchen abzählst.
              Bei PWM kommt es zunächst darauf an welchen PWM Modus du anwenden möchtest: du kannst ein PWM im Normalen Modus erzeugen, du kannst Fast PWM oder phasenkorrekten PWM wählen. Und davon hängt der genaue Weg ab. Als erstes musst du die Grundfrequenz einstellen. Systemtakt und Prescaler musst du so einstellen, dass die Frequenz höher ist als deine Ziel-Grundfrequenz. Das Finetuning machst du entweder über einen Startwert bei den Modi die bis 0xFF laufen oder über den OCRxy Wert, bei den Modi die bis zu diesem Wert laufen.
              Einen DutyCyle kannst du über unterschiedliche Startwerte einstellen oder über das Verhältnis von OCRxA zu OCRxB im Fast PWM Modus.
              Tut mir leid, aber ich kann das nicht im Detail alles noch einmal in einem Kommentar erklären – der Artikel ist nicht umsonst so lang wie er ist. Nimm einfach die Beispielsketch und ändere sie gezielt ab – und schaue was passiert. Wenn du ein Oszilloskop hast, kann man das damit gut beobachten. Oder du greifst die Interrupts ab und führst da einen Zähler ein um die Geschichte zu verlangsamen (wie bei der LED die im Sekundentakt leuchtet).

  7. Hallo Wolfgang.
    Danke für die ausführliche Beschreibung.
    Leider finde ich nicht das richtige im Netz, wie man RTC-Millis von RTCLib.h. Ich möchte die RTC millis anstelle von arduino millis().
    Ich möchte es für einen einstellbaren sleep Timer für OLED Display verwenden. Die arduino millis sind leider etwas ungenau.
    Momentan sieht der Befehl ungefähr so aus:

    void loop() {

    ….
    unsigned long letzteMessung = millis();
    unsigned long previousMillis = 0;
    If (letzteMessung – previousMillis > interval){
    previousMillis = letzteMessung;
    displayoff;}

    }

    Ich kommen irgendwie nicht auf die Idee es auf die RTC-Millis.

    Ich währe für Ihre Hilfe sehr dankbar.

    1. Hallo Sergej,

      die RTCLib arbeitet mit DateTime Objekten. Die DateTime Objekte kennen nur Sekunden, aber keine Millisekunden. Mir ist kein Weg bekannt an die Millisekunden zu kommen.

      Die RTCLib kann mit verschiedenen RTCs wie dem DS3231, dem DS1307 oder dem PCF8523 arbeiten. Ich weiß ja nicht, was du verwendest. Falls es z.B. ein DS3231 ist müsstest du erstmal ein RTC Objekt so erzeugen:
      RTC_DS3231 rtc;
      und dann „die Uhr stellen“. In meinem Beitrag über den DS3231 erkläre ich das alles:
      https://wolles-elektronikkiste.de/ds3231-echtzeituhr

      Die aktuelle Zeit fragst du so ab:
      DateTime now = rtc.now();
      Das DateTime Objekt now kannst du in die Unixzeit, also Sekunden, umwandeln:
      unsigned int timeInSeconds = now.unixtime();
      und damit kannst du dann rechnen. Aber nur in Sekundenauflösung. Und dann wäre es wahrscheinlich besser mit Alarmen zu arbeiten, als die Zeit immer wieder abzufragen (siehe auch wieder mein DS3231 Beitrag.

      Mit der RTCLib lassen sich auch Software RTC Objekte erzeugen (RTC_Millis / RTC_Micros), also ohne externe RTC, z.B.:
      RTC_Micros rtc;
      Die Software RTCs basieren auf der millis() bzw. micros() Funktion. Zunächst einmal hat man damit also dieselbe (Un-)Genauigkeit. Die RTC_Micros Klasse hat aber eine Funktion adjustDrift(int ppm);. Das heißt, wenn du weißt, um wieviel die Uhr vor oder nachgeht, kannst du das kompensieren. Bei Verwendung von RTC_Micros ist allerdings zu beachten, dass spätestens alle 71.6 min now() aufgerufen werden muss, weil dann micros() überläuft und bei Null beginnt.
      Mit RTC_Micros kommst du auch nicht an die Millisekunden, aber immerhin sind die Sekunden dann genauer.

      Sonst gäbe es noch die Möglichkeit mit einem DS3231 zu arbeiten und direkt das 32 kHz oder den SQW (Squarewave) Pin anzuzapfen und so die Zeit genau zu messen.

      Hoffe das hilft ein wenig. VG, Wolfgang

  8. Hallo, erstmal sehr guter Bericht, hat mir echt weitergeholfen. Aber beträgt die Frequenz bei „Einstellen einer genauen Frequenz“ nicht eher 0,5 Hz, da eine Periodendauer 1 Sekunde an und 1 Sekunde aus ja insgesamt 2 Sekunden ergibt? Bei einer Frequenz von 1 Hz müsste die Phasen jeweils 500 ms dauern.

    LG

    1. Was ich hier meine ist die Frequenz des Überlaufs, die Toggle Frequenz. Bezogen auf eine „An-Aus“ Frequenz hast du Recht. VG, Wolfgang

  9. Hallo,
    Ich habe nach ihren Beispiel 2 Timer mit Fast PWM erstellt. Gibt es die Möglichkeit die PWM Signale zu verodern oder zu verunden?
    Vielen Dank Benni
    void setup() {
    //Timer 1 fast PWM
    TCCR2A = (1<<COM2B1) + (1<<WGM21) + (1<<WGM20); // Set OC2B at bottom, clear OC2B at compare match
    TCCR2B = (1<<CS22) + (1<<WGM22); // prescaler = 64;
    OCR2A = 249; //duty cycle
    OCR2B = 49;
    DDRD |= (1<<PD3);

    //timer 0 fast PWM
    TCCR0A = (1<<COM0B1) + (1<<WGM01) + (1<<WGM00); // Set OC2B at bottom, clear OC2B at compare match
    TCCR0B = (1<<CS01) + (1<<CS00)+ (1<<WGM02); // prescaler = 64;
    OCR0A = 249; // duty cycle
    OCR0B = 100;
    DDRD |= (1<<PD5);
    }
    void loop() {

    }

    1. Hi Benny,

      ich sage mal „du“. Ich bin mir nicht sicher was du meinst. Möchtest Du entscheiden können, ob das eine Signal an PD3(OC2B) kommt und das andere gleichzeitig an PD5(OC0B) oder das eine oder das andere? Also so wie Du es oben geschrieben hast sollten sie gleichzeitig laufen. Wenn du jetzt gezielt einen der Timer ausschalten willst, musst du einfach nur die WGM Bits auf null setzen, also z.B. für den Timer 2:
      TCCR2A &= ~((1<

  10. Hi, vielen Dank für die ausführliche Beschreibung! Ich habe eine Frage – und hoffe, ich habe die Antwort nicht bereits überlesen weiter oben.

    Ich möchte die Grundfrequenz eines PWM-Signals genau(-er) einstellen als mit den Prescalern der Counter möglich. Auch gerne auf Kosten der PWM-Auflösung. Eine Lösung wurde bereits genannt: nach einem Counter-Overflow mit einer ISR den Startwert des Zählers beliebig setzen (unter Beachtung, dass der „PWM-Umschaltpunkt“ innerhalb des verbleibenden Zählerbereichs liegt – ich hoffe, das soweit begriffen zu haben).

    Frage: gibt es einen Modus (den ich im Atmel-Datenblatt noch nicht gefunden habe), bei dem der TOP- bzw. BOTTOM-Wert aus einem Register gelesen wird, ohne nach einem Overflow diesen mit einer ISR jedesmal neu setzen zu müssen?

    Vielen Dank, Grüße, Michael

    1. Was das genaue Einstellen angeht, da würde ich dich erstmal fragen ob dich schon mit dem Timer1 beschäftigt hast. Der ist wesentlich genauer einstellbar, wegen der 16 Bit. Wegen des Ansonsten gibt es doch einige Modi in denen der Top Wert OCR0A bzw. OCR2A ist. Da musst du dann keinen neuen Startwert setzen. Und wie gesagt, wenn das zu ungenau ist, dann nimm den Timer1. Ich hoffe, ich habe deine Frage richtig verstanden, so dass die Antwort auch einen Sinn ergibt….

      1. Perfekt, Danke, und wer lesen kann… ich hätte einfach Deinen Teil 2 lesen sollen, da steht die Lösung. Super Seite, Ciao

Schreibe einen Kommentar

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