Timer und Counter
Worum geht's?
BearbeitenMit der Funktionseinheit Timer und Controller verfügen AVR Mikrocontroller über eine Komponente, die vielseitig verwendet werden kann.
Als einfache Zähler eingesetzt können sie für die Zeitmessung verwendet werden. Über für diesen Zweck vorgesehene Ausgabepins können sie für die Erzeugung elektrischer Signale herangezogen werden. Schließlich können Timer und Controller auch so konfiguriert werden, dass sie periodisch Interrupts auslösen und so für die Ausführung nebenläufiger Tätigkeiten herangezogen werden.
Bei einem so großen Leistungsspektrum ist es kein Wunder, dass die Beschreibung von Timern einen beinahe einschüchternden Umfang von Seiten im Datenblatt verschlingt. Ziel dieses Kapitels ist es, Dir zu helfen Dich in diesem dichten Papierdschungel ein wenig schneller zurecht zu finden. Ein Ersatz für die Lektüre des Datenblatts ist es nicht.
In diesem Kapitel kannst die folgenden Dinge lernen:
- Ein/Ausgabepins von Timern lokalisieren
- Zählerstände von Timern auslesen und vorgeben
- Die Betriebsart von Timern vorgeben
- Zählintervalle und Taktquellen von Timern konfigurieren
- Das Verhalten der Ausgabepins von Timern festlegen
- Timer für die Erzeugung periodischer Interrupts verwenden
Der Streifzug in die Welt der Timer und Counter in diesem Kapitel orientiert sich am Funktionsumfang eines AtMega328p Microcontrollers. Angaben zu Seitenzahl und Sektion beziehen sich auf das zugehörige Datenblatt [M328p].
Anzahl und Funktionsumfang verfügbarer Timer können bei anderen AVR Modellen abweichen. In diesem Kapitel beschriebene Grundprinzipien und Funktionsweisen sollten aber übertragbar sein. Unterschiede in den Details müssen in diesem Fall mit dem Datenblatt abgeglichen werden.
Grundlagen
BearbeitenDer AtMega328p verfügt über 3 Timer/Counter. Die Timer haben die Namen TC0 TC1 und TC2.
Jeder der Timer kann in einer Reihe verschiedener Betriebsarten verwendet werden. Nach einem Reset befinden sich alle Timer in einer Betriebsart, die im Handbuch Normal Mode genannt wird.
In diesem Abschnitt wird es ausschließlich um diese Betriebsart gehen. Eigenschaften und Konzepte, die Du beim Studium dieser Betriebsart in diesem Abschnitt kennen lernst, werden in folgenden Abschnitten auf andere Betriebsarten übertragen und verallgemeinert werden.
Stromsparmodus
BearbeitenNach einem Reset befinden sich alle Timer im Stromsparmodus und sind daher deaktiviert.
Bevor es daran gehen kann, mit einem der Timer zu arbeiten, muss er also zunächst erst einmal aktiviert werden. Um die Timer TC0, TC1 bzw. TC2 zu aktivieren, muss das zugehörige Bit PRTIM0, PRTIM1 bzw. PRTIM2 im PRR (power reduction) Register auf den Wert 0 geschrieben werden.[1]
Achtung! Die Information im Handbuch [M328p] ist leider widersprüchlich formuliert:
Zu Timer TC0 heißt es: [2]
“The TC0 is enabled by writing the PRTIM0 bit in ”Minimizing Power Consumption” to '0'.
The TC0 is enabled when the PRTIM0 bit in the Power Reduction Register (PRR.PRTIM0) is written to '1'.”
Die erste Angabe ist die richtige.
Zu Timer TC1 heißt es: [3]
“The Power Reduction TC1 bit in the Power Reduction Register (PRRPRR.PRTIM1) must be written to zero to enable the TC1 module.”
Die Abkürzung für das Power Reduction Register ist PRR nicht PRRPRR
, ansonsten ist die Angabe korrekt.
Zu Timer TC2 heißt es: [4]
“The TC2 is enabled when the PRTIM2 bit in the Power Reduction Register (PRR.PRTIM2) is written to '1'.”
Das zugehörige Bit PRTIMn muss also stets auf den Wert 0 geschrieben werden.
Im C Quellcode kann das Aktivieren und Deaktivieren der einzelnen Timer wie folgt implementiert werden.
void t0_power(uint8_t on) {
if (on) cbi(PRR, PRTIM0);
else sbi(PRR, PRTIM0);
}
void t1_power(uint8_t on) {
if (on) cbi(PRR, PRTIM1);
else sbi(PRR, PRTIM1);
}
void t2_power(uint8_t on) {
if (on) cbi(PRR, PRTIM2);
else sbi(PRR, PRTIM2);
}
Zählregister
BearbeitenJeder Timer verfügt über einen Zähler, der von einer Taktquelle gespeist wird. Bei jedem Eintreffen eines Taktsignals schreitet der Zählerstand um einen Schritt voran.
Die Zählrichtung (aufwärts oder abwärts) und der maximale Stand eines Zählers sind dabei von der gewählten Betriebsart abhängig.
Im Normal Mode, mit dem wir uns in diesem Abschnitt befassen, ist die Zählrichtung immer aufwärts. Bei jedem eintreffen eines Taktsignals wird der aktuelle Zählerstand um eins erhöht. Für die Timer TC0 und TC2 liegt der maximale Zählerstand bei 0xFF, für den Timer TC1 beträgt der maximale Zählerstand 0xFFFF. Nach Erreichen des maximalen Zählerstands wird die Zählung beim Eintreffen des nächsten Taktsignals mit Zählerstand 0 fortgesetzt.
Der aktuelle Stand eines Zählers kann über zugehörige Zählregister sowohl gelesen, als auch geschrieben werden. Die Timer TC0 und TC2 verfügen je über einem 8-bit Zähler, dessen Stand über das Register TCNT0, bzw. TCNT2 manipuliert werden kann. Der Timer TC1 verfügt über einen 16-bit Zähler. Zugriff auf den Zählerstand des Timers TC1 ist über die Register TCNT1L und TCNT1H möglich, über die hochwertiges und niederwertiges Byte manipuliert werden können.
Achtung! Beim Zugriff auf 16-bit Register ist eine feste Reihenfolge einzuhalten:[5]
- Beim Schreiben muss das high Byte vor dem low Byte geschrieben werden.
- Beim Lesen muss das low Byte vor dem high Byte gelesen werden.
Abschließend sind die Eigenschaften der Zählregister in folgender Tabelle wiedergegeben.
Timer | Bitlänge | Zählregister | max |
---|---|---|---|
TC0 | 8-bit | TCNT0 | 0xFF |
TC1 | 16-bit | TCNT1H TCNT1L | 0xFFFF |
TC2 | 8-bit | TCNT2 | 0xFF |
Taktquelle
BearbeitenDas Tempo, mit dem der Stand eines Zählers voranschreitet, wird durch eine Taktquelle vorgegeben, mit der der zugehörige Timer versorgt wird. Nach einem Reset ist keiner der Timer mit einer Taktquelle verbunden. Bevor es mit dem Zählen los gehen kann, muss der betreffende Timer also zunächst mit einer Taktquelle versorgt werden.
Die Timer TC0 und TC1 können sowohl von einer externen Taktquelle, als auch von einer internen Taktquelle mit einem Takt versorgt werden. Der Timer TC2 kann nur von einer internen Taktquelle versorgt werden. In diesem Abschnitt wird es ausschließlich um die intern zur Verfügung stehenden Taktquellen gehen. Die genaue Lage der Pins, mit denen die Timer TC0 und TC1 extern versorgt werden können, werden wir uns zu einem späteren Zeitpunkt ansehen.
Alle internen Taktquellen werden von zwei prescalern bereit gestellt. Über sie können fest vorgegebene Bruchteile des Systemtakts abgegriffen werden. Ein prescaler Wert von N gibt an, dass der prescaler nur für jeden N-ten CPU Takt ein eigenes Taktsignal erzeugt. Die Timer TC0 und TC1 teilen sich den selben prescaler, an dem sie unabhängig von einander Taktwerte abgreifen können. Der Timer TC2 verfügt über einen eigenen prescaler.
Die Taktquellen der Timer können mit einem 3-bit Wert vorgegeben werden. Die Taktquelle des Timers TCn kann mit den Bits CSn[0:2] (clock source) im zugehörigen TCCRnB (timer counter control register B) Register festgelegt werden.
Über zur Verfügung stehende Wahlmöglichkeiten gibt folgende Tabelle Auskunft. .[6]
CSn[0:2] | TC0 / TC1 | TC2 | |
---|---|---|---|
0 | 000 | deaktiviert | deaktiviert |
1 | 001 | clk / 1 | clk / 1 |
2 | 010 | clk / 8 | clk / 8 |
3 | 011 | clk / 64 | clk / 32 |
4 | 100 | clk / 256 | clk / 64 |
5 | 101 | clk / 1024 | clk / 128 |
6 | 110 | fallende Flanke an TO/T1 | clk / 256 |
7 | 111 | steigende Flanke an TO / T1 | clk / 1024 |
Die Wahl der Taktquelle kann im C Quellcode mit folgendem Code Schnipsel implementiert werden.
void t0_cs(uint8_t cs) {
uint8_t tccr_b = TCCR0B & 0b11111000;
TCCR0B = tccr_b | cs;
}
void t1_cs(uint8_t cs) {
uint8_t tccr_b = TCCR1B & 0b11111000;
TCCR1B = tccr_b | cs;
}
void t2_cs(uint8_t cs) {
uint8_t tccr_b = TCCR2B & 0b11111000;
TCCR2B = tccr_b | cs;
}
Codebeispiel
BearbeitenMit Aktivierung eines Timers und Vorgabe der zugehörigen Taktquelle haben wir bereits alle Schritte durchgeführt, die für den Betrieb eines Timers zwingend erforderlich sind. Davon, dass wir einen Timer mit diesen Schritten tatsächlich in Gang setzen können, können wir uns mit einem kurzen Programm überzeugen. Für die Ausgabe machen wir dabei Gebrauch von der im Kapitel Tipps Tricks und kleine Helferlein vorgestellten Möglichkeit Ausgaben über die serielle Schnittstelle zu versenden und am PC zu empfangen.
void setup() {
stdout = log_init();
// TIMER 0
t0_power(1); // power on
t0_cs(1); // clock source: clk/1
}
void loop() {
printf("%d ", TCNT0);
_delay_ms(500); // delay ~ 500 ms
}
Nachdem Du das Programm übersetzt und auf den Microcontroller übertragen hast, sollte der Microcontroller über die serielle Schnittstelle nun kontinuierlich neue Zahlen ausgeben:
8 204 91 95 104 232 109 237 109 242 114 247 119 247 124 252 129 1 197 84 88 97 101 229 106 234 111
Glückwunsch! Du hast den Timer 0 erfolgreich zum laufen gebracht.
Noch ist das Programm zwar nicht sehr nützlich. Aber mit ihm hast Du bereits den wichtigsten Grundstein für die Erstellung umfangreicherer Programme mit Timern gelegt. Die Schritte, die Du bis hier hin durchgeführt hast, müssen in jedem Programm, das mit Timern arbeitet, ausgeführt werden.
In den folgenden Abschnitten wirst Du weitere Möglichkeiten kennen lernen, Timer zu nutzen und mit ihrer Hilfe komplexere Programme zu erstellen.
Timer und Interrupts
BearbeitenÜberlauf
BearbeitenWie Du bereits erfahren hast, setzt ein Zähler, der seinen maximalen Zählerstand erreicht hat, die Zählung bei erneutem Eintreffen eines Taktsignal bei 0 fort. Ein weiteres Detail ist dabei bisher außer Acht geblieben. Zusätzlich zur bekannten Arbeitsweise wird bei einem solchen Überlauf des Zählers ein Statusflag gesetzt.
Der aktuelle Zustand des Overflow Flags des Timers TC0 kann über das Bit TOV0 (timer overflow) im Register TIFR0 (timer interrupt flag) ausgelesen werden. In analoger Art und Weise sind die Bits TOV1 und TOV2 in den Registern TIFR1 bzw. TIFR2 für die Timer TC1 und TC2 zuständig. Einmal gesetzt bleibt das Overflow Flag eines Timers TCn so lange gesetzt, bis es durch Schreiben einer 1 in das zugehörige TOCn Bit manuell zurückgesetzt wird.
Interrupt
BearbeitenEine besondere Bedeutung gewinnt das Overflow Flag TOVn eines Timers TCn erst dann, wenn es zum Auslösen von Interrupts verwendet wird. Um diese zu erreichen muss sowohl das zugehörige Bit TOIEn (timer overflow interrupt enable) Bit im TIMSKn (timer interrupt mask) Register gesetzt werden, als auch die Zustellung von Interrupts global aktiviert werden.
Bei Ansprung der zugehörigen Service Routine wird das Overflow Flag automatisch gelöscht. Ein manuelles Rücksetzen ist in diesem Fall also nicht nötig.
Zum Aktivieren und Deaktivieren des Interruptbetriebs kann der folgende Code Schnipsel verwendet werden.
void t0_interrupt_ov(uint8_t on) {
if (on) sbi(TIMSK0, TOIE0);
else cbi(TIMSK0, TOIE0);
}
void t2_interrupt_ov(uint8_t on) {
if (on) sbi(TIMSK2, TOIE2);
else cbi(TIMSK2, TOIE2);
}
void t1_interrupt_ov(uint8_t on) {
if (on) sbi(TIMSK1, TOIE1);
else cbi(TIMSK1, TOIE1);
}
Die zugehörigen Interrupt Service Routinen können mit dem ISR() Makro der Header Datei avr/interrupt.h definiert werden. Die Indices der zugehörigen Einträge in der Interruptvektortabelle können mit den Präprozessormakros TIMER0_OVF_vect, TIMER1_OVF_vect bzw. TIMER2_OVF_vect angegeben werden.
Codebeispiel
BearbeitenDas Arbeiten mit dem Overflow Interrupt können wir mit einem kleinen Programm ausprobieren. Durch zählen der zwischen zwei Überläufen des Zählers verstrichenen Taktschritte soll es die seit dem Systemstart verstrichene Zeit in Sekunden ausgeben. Die Anzahl der Taktschritte, die pro Sekunde ausgeführt werden, kann dem Makro F_CPU entnommen werden.
void setup() {
stdout = log_init();
// TIMER 0
t0_power(1); // power on
t0_cs(5); // clock source: clk/1024
t0_interrupt_ov(1); // enable overflow interrupt
sei(); // global interrupt enable
}
volatile uint32_t uptime = 0;
void loop() {
printf("\r%5ld s", uptime);
_delay_ms(500); // delay ~ 500 ms
}
ISR(TIMER0_OVF_vect) {
static uint32_t clocks = 0;
clocks += 1024L * 256;
if (clocks > F_CPU) {
uptime += 1;
clocks -= F_CPU;
}
}
Nachdem Du das Programm übersetzt und auf den Microcontroller übertragen hast, sollte der Microcontroller über die serielle Schnittstelle nun kontinuierlich die aktuelle Anzahl Sekunden seit dem Systemstart anzeigen.
Vergleichsregister
BearbeitenZusätzlich zu den Registern, die Du bereits kennen gelernt hast, verfügt jeder Timer über zwei Vergleichsregister OCRnA (output compare A) und OCRnB (output compare B).
Der Inhalt dieser beiden Vergleichsregister wird permanent mit dem aktuellen Zählerstand des zugehörigen Timers verglichen. Bei einer Übereinstimmung werden die Flags OCRFnA (output compare flag A) bzw. OCRFnB (output compare flag B) im TIFRn (timer interrupt flag register) Register gesetzt.
Die Timer TC0 und TC2 verfügen jeweils über zwei 8-bit Vergleichsregister OCR0A und OCR0B bzw. OCR2A und OCR2B. Der Timer TC1 verfügt über zwei 16-bit Vergleichsregister, mit zughörigen Werten in den Registern OCR1AL und OCR1AH sowie OCR1BL und OCR1BH.
Achtung! Bei der Arbeit mit den Vergleichsregistern sind folgende Besonderheiten zu beachten:
- Bei schreibendem Zugriff auf das Zählregister eines Timers wird der Vergleich für einen Taktzyklus ausgesetzt.
- Bei Zugriff auf die 16-bit Register OCR1A und OCR1B ist dieselbe Reihenfolge, wie bei Zugriff auf die 16-bit Zählerstände einzuhalten.
Zum Setzen der Werte der Vergleichsregister kann folgender Code Schnipsel verwendet werden
void t0_ocra(uint8_t ocra) {
OCR0A = ocra;
}
void t0_ocrb(uint8_t ocrb) {
OCR0B = ocrb;
}
void t2_ocra(uint8_t ocra) {
OCR2A = ocra;
}
void t2_ocrb(uint8_t ocrb) {
OCR2B = ocrb;
}
void t1_ocra(uint16_t ocra) {
OCR1AH = (uint8_t) (ocra >> 8);
OCR1AL = (uint8_t) (ocra & 0xFF);
}
void t1_ocrb(uint16_t ocrb) {
OCR1BH = (uint8_t) (ocrb >> 8);
OCR1BL = (uint8_t) (ocrb & 0xFF);
}
Interrupts
BearbeitenAnalog zur Arbeitsweise des Overflow Flags können auch diese beiden Flags zum Auslösen von Interrupts verwendet werden. Damit ein Interrupt ausgelöst wird müssen die Bits OCIEnA (output compare interrupt enable A) bzw. OCIEnB (output compare interrupt enable B) im TIMSKn (timer interrupt mask) Register auf den Wert 1 gesetzt werden.
Um den Interruptbetrieb zu aktivieren / deaktivieren kann der folgende Code Schnipsel verwendet werden:
void t0_interrupt_ocra(uint8_t on) {
if (on) sbi(TIMSK0, OCIE0A);
else cbi(TIMSK0, OCIE0A);
}
void t0_interrupt_ocrb(uint8_t on) {
if (on) sbi(TIMSK0, OCIE0B);
else cbi(TIMSK0, OCIE0B);
}
void t2_interrupt_ocra(uint8_t on) {
if (on) sbi(TIMSK2, OCIE2A);
else cbi(TIMSK2, OCIE2A);
}
void t2_interrupt_ocrb(uint8_t on) {
if (on) sbi(TIMSK2, OCIE2B);
else cbi(TIMSK2, OCIE2B);
}
void t1_interrupt_ocra(uint8_t on) {
if (on) sbi(TIMSK1, OCIE1A);
else cbi(TIMSK1, OCIE1A);
}
void t1_interrupt_ocrb(uint8_t on) {
if (on) sbi(TIMSK1, OCIE1B);
else cbi(TIMSK1, OCIE1B);
}
Die Indices der zugehörigen Interruptvektor Einträge können mit den Präprozessormakros TIMER0_COMPA_vect, TIMER0_COMPB_vect, TIMER1_COMPA_vect, TIMER1_COMPB_vect, TIMER2_COMPA_vect bzw. TIMER2_COMPB_vect angegeben werden.
Codebeispiel
BearbeitenAuch am Ende dieses Abschnitts soll ein Teil der neu gewonnen Möglichkeiten anhand eines kleinen Beispiels demonstriert und überprüft werden.
In diesem Programm soll die Leutdiode L in schnellem Wechsel ein- und ausgeschaltet und somit gedimmt werden. Um das zu bewerkstelligen, können wir zwei Interrupts verwenden. Wenn wir sowohl den Overflow Interrupt, als auch den Compare Match A Interrupt verwenden, können wir die zugehörigen Service Routinen so implementieren, dass die Leuchtdiode bei jedem Überlauf eingeschaltet und bei jedem Compare Match wieder ausgeschaltet wird.
Über das zugehörige Vergleichsregister kann dann die Leuchtdauer festgelegt werden.
void setup() {
stdout = log_init();
sbi(DDRB, DDB5); // LED: PORTB5 out
// TIMER 0
t0_power(1); // power on
t0_cs(5); // clock source: clk/1024
t0_interrupt_ocra(1); // enable compare match A interrupt
t0_interrupt_ov(1); // enable overflow interrupt
sei(); // global interrupt enable
}
void loop() {
static uint8_t i;
i += 1;
if (i == 128)
i = 0;
t0_ocra(i);
_delay_ms(10);
}
ISR(TIMER0_OVF_vect) {
sbi(PORTB, PORTB5); // high LED
}
ISR(TIMER0_COMPA_vect) {
cbi(PORTB, PORTB5); // low LED
}
Glückwunsch! Du hast nun alle Möglichkeiten kennen gelernt, die Timer im 'normal mode zur Verfügung stellen.
Im nächsten Abschnitt wird es darum gehen, dieses Wissen auch auf die anderen Betriebsarten von Timern zu übertragen.
Betriebsarten
BearbeitenBisher wurde ausschließlich die Arbeitsweise im normal mode betrachtet. In diesem Abschnitt wird es darum gehen einen Überblick über die verschiedenen anderen Modi zu erhalten, in denen die Timer betrieben werden können. Die verschiedenen Betriebsarten von Timern werden im Handbuch unter der Bezeichnung Wave Generation Modes geführt, da sie vor allem für die Signalerzeugung von besonderer Bedeutung sind.
Eigenschaften
BearbeitenDie Timer TC0 und TC2 verfügen über den gleichen Satz an Betriebsarten. Der gewünschte Wave Generation Mode kann für jeden der beiden Timer mit einem 3-bit Wert konfiguriert werden. Der Timer1 verfügt über einen umfangreicheren Satz von Betriebsarten. Für ihn kann der gewünschte Wave Generation Mode mit einem 4-bit Wert vorgegeben werden.
Die verschiedenen Wave Generation Modes unterscheiden sich in den folgenden Eigenschaften:
- maximaler Zählerstand
- Zählintervall
- Pufferung der Vergleichsregister
Der maximale Zählerstand kann, so wie Du es bereits vom Normal Mode kennst, durch einen für den jeweiligen Modus spezifischen festen Wert vorgegeben sein. In einigen Modi kann der maximale Stand eines Zählers stattdessen durch Vorgabe des gewünschten Werts im OCRA bzw. dem ICR Register vorgegeben werden.
Das Zählintervall eines Timers durchläuft entweder das Intervall von 0 bis zum Maximalwert, oder es durchläuft zunächst aufsteigend die Werte von 1 bis zum Maximalwert um danach schrittweise bis auf den Wert 0 abzusinken.
Im Normal Mode fand keine Pufferung der Vergleichsregister statt. In die Register OCRnA und OCRnB geschriebene Werte wurden sofort übernommen und haben sich somit sofort auf das Verhalten der Timer ausgewirkt. In anderen Modi können die die OCRnx Register stattdessen gepuffert sein. Werte in diesen Registern werden dann nur zu bestimmten, durch den jeweiligen Modus bestimmten Zeitpunkten übernommen.
Überblick
BearbeitenAchtung! Die Benennung der Betriebsarten im Handbuch [M328p] folgt keinem einheitlichen Schema:
- Bei Modi mit gepufferten OCRx Registern erfolgt die Benennung anhand des Zählintervalls und des Zeitpunkts der Übernahme der gepufferten Werte. Bei diesen Modi wird zwischen
Fast PWM Mode
,Phase Correct PWM Mode
undPhase and Frequency Correct PWM Mode
unterschieden. - Bei ungepufferten OCRx Registern wird abhängig vom maximalen Zählerstand zwischen
Normal Mode
undCTC Mode
unterschieden.
In der folgenden Übersicht wurden Normal Mode und Clear Timer on Compare Match Mode zur Kategorie Non-PWM Mode zusammengefasst. Die folgenden Kombinationen aus Zählintervall und Pufferung der Vergleichsregister sind möglich. Das Overflow Flag wird dabei stets beim Überschreiten des in der Spalte Zählintervall ganz rechts angegebenen Werts gesetzt.
Kategorie | Zählintervall | OCRx update | |
---|---|---|---|
n/ctc | Non-PWM Mode | 0...max | sofort |
f | Fast PWM Mode | 0...max | 0 |
pc | Phase Correct PWM Mode | 1...max...0 | max |
pfc | Phase and Frequency Correct PWM Mode | 1...max...0 | 0 |
Durch die Kategorie ist das Verhalten eines Timers bereits bis auf den maximalen Zählerstand bestimmt. Abhängig davon, um welchen Timer es sich handelt, sind folgende Kombinationen aus Kategorie und maximalem Zählerstand möglich.
Timer 0 / 2
BearbeitenWGM | 0 0000 | 1 0001 | 2 0010 | 3 0011 | 4 0100 | 5 0101 | 6 0110 | 7 0111 |
---|---|---|---|---|---|---|---|---|
Typ | n | pc | ctc | f | - | pc | - | f |
max | 0xFF | 0xFF | OCRA | 0xFF | - | OCRA | - | OCRA |
Timer 1
BearbeitenWGM | 0 00000 | 1 0001 | 2 0010 | 3 0011 | 4 0100 | 5 0101 | 6 0110 | 7 0111 |
---|---|---|---|---|---|---|---|---|
Typ | n | pc | pc | pc | ctc | f | f | f |
max | 0xFFFF | 0x00FF | 0x01FF | 0x03FF | OCRA | 0x00FF | 0x01FF | 0x03FF |
WGM | 8 10000 | 9 1001 | 10 1010 | 11 1011 | 12 1100 | 13 1101 | 14 1110 | 15 1111 |
Typ | pfc | pfc | pc | pc | ctc | - | f | f |
max | ICR | OCRA | ICR | OCRA | ICR | n/a | ICR | OCRA |
Konfiguration
BearbeitenDie Modi der Timer TC0 und TC2 können mit einem 3-bit Wert konfiguriert werden. Die zugehörigen Bits liegen über zwei Register verteilt. Die Bits WGMn0, WGMn1 finden sich im Register TCCRnA, das Bit WGMn2 findet sich im TCCRnB
Der Modus von Timer1 kann mit einem 4-bit Wert konfiguriert werden. Auch hier sind die Bits über zwei Register verteilt. Die Bits WGM10 und WGM11 finden sich im Register TCCR1A, die Bits WGM12 und WGM13 im Register TCCR1B.
Um die Modi der Timer vorgeben zu können kann folgender Code Schnipsel verwendet werden.
void t0_wgm(uint8_t wgm) {
uint8_t wgm_01 = wgm & 0b00000011;
uint8_t wgm_2 = (uint8_t) ( (wgm & 0b00000100) << 1 );
uint8_t tccr_a = TCCR0A & 0b11111100;
uint8_t tccr_b = TCCR0B & 0b11110111;
TCCR0A = tccr_a | wgm_01;
TCCR0B = tccr_b | wgm_2;
}
void t2_wgm(uint8_t wgm) {
uint8_t wgm_01 = wgm & 0b00000011;
uint8_t wgm_2 = (uint8_t) ( (wgm & 0b00000100) << 1 );
uint8_t tccr_a = TCCR2A & 0b11111100;
uint8_t tccr_b = TCCR2B & 0b11110111;
TCCR2A = tccr_a | wgm_01;
TCCR2B = tccr_b | wgm_2;
}
void t1_wgm(uint8_t wgm) {
uint8_t wgm_01 = wgm & 0b00000011;
uint8_t wgm_23 = (uint8_t) ( (wgm & 0b00001100) << 1 );
uint8_t tccr_a = TCCR1A & 0b11111100;
uint8_t tccr_b = TCCR1B & 0b11100111;
TCCR1A = tccr_a | wgm_01;
TCCR1B = tccr_b | wgm_23;
}
Codebeispiel
BearbeitenIm Codebeispiel am Ende dieses Abschnitts geht es noch einmal darum die LED L zu dimmen. Im Normal Mode, in dem wir den Timer im letzten Codebeispiel betrieben hatten, mussten wir dafür zwei Interrupt Routinen verwendet. Wenn wir den Timer im Phase Correct PWM Mode betreiben, genügt eine Interrupt Routine. Sowohl beim Aufwärtszählen, als auch beim Abwärtszählen wird ein Compare Match mit dem Vergleichsregister OCRA ausgelöst. Es genügt also bei einem Compare Match den Zustand der LED zu toggeln.
void setup() {
stdout = log_init();
sbi(DDRB, DDB5); // LED: PORTB5 out
// TIMER 0
t0_power(1); // power on
t0_cs(5); // clock source: clk/1024
t0_wgm(1);
t0_interrupt_ocra(1); // enable overflow interrupt
sei(); // global interrupt enable
}
void loop() {
static uint8_t i;
++i;
t0_ocra(i);
_delay_ms(10);
}
ISR(TIMER0_COMPA_vect) {
sbi(PINB, PINB5); // toggle LED
}
Signalerzeugung
BearbeitenDie verschiedenen Betriebsarten von Timern sind vor allem dann von Nutzen, wenn sie zum Erzeugen periodischer Signale eingesetzt werden. Zeitgesteuerte Aufgaben wie diese können, wie Du bereits gesehen hast, mit Interrupts in Software ausgeführt werden. Kosten in diesem Fall aber auch Rechnzeit des Microcontrollers.
Eine elegante Möglichkeit, mit der Aufgaben wie diese erledigt werden können, auch ohne die CPU des Micorcontrollers zu belasten, kannst Du in diesem Abschnitt kennen lernen.
Ausgabepins konfigurieren
BearbeitenFür jeden Timer TCn sind zwei Ausgabepins OCnA und OCnB vorgesehen. Wenn ein Timer nur zum zählen verwendet wird, können die zugehörigen Ausgabepins für einen anderen Zweck benutzt werden.
Die genaue Lage der Pins kann im Datenblatt nachgesehen werden.[7]
Timer | Taktquelle (Tn) | output A (OCRnA) | output B (OCRnB) | capture (ICP1) |
---|---|---|---|---|
TC0 | PD4 | PD6 | PD5 | - |
TC1 | PD5 | PB1 | PB2 | PB0 |
TC2 | - | PB3 | PD3 | - |
Damit die entsprechenden Pins als Ausgabepins verwendet werden können, muss zunächst die Datenrichtung auf Ausgabe gestellt werden.
Das kann mit folgendem Code Schnipsel realisiert werden.
void t0_a_out() { // OC0A = PD6
sbi(DDRD, DDB6);
};
void t0_b_out() { // OC0B = PD5
sbi(DDRD, DDB5);
};
void t2_a_out() { // OC2A = PB3
sbi(DDRB, DDB3);
};
void t2_b_out() { // OC2B = PD3
sbi(DDRD, DDD3);
};
void t1_a_out() { // OC1A = PB1
sbi(DDRB, DDB1);
};
void t1_b_out() { // OC1B = PB2
sbi(DDRB, DDB2);
};
Ausgabemodus
BearbeitenDas Verhalten der Ausgabepins von Timer TC0 kann kann über die Bits COM0A[1:0] (compare output mode A), bzw. COM0B[1:0] (compare output mode B) im TCCR0A (timer counter control register A) Register vorgegeben werden. Analog kann das Verhalten der Ausgabepins der Timer TC1 und TC2 über die Bits COM1A[1:0] und COM1B[1:0] im TCCR1A Register, bzw. die Bits COM2A[1:0] und COM2B[1:0] im TCCR2A Register vorgegeben werden.
Die Ausgabepins können so konfiguriert werden, dass sie abhängig vom Zählerstand des zugehörigen Timers gesetzt, gelöscht oder getoggelt werden. Die Konfigurationsmöglichkeiten hängen dabei von dem Modus ab, in dem der jeweilige Timer betrieben wird.
M:match B:bottom U:match when up-counting D: match when down counting
COMxA/B | normal/ctc | fast PWM | phase (and frequency) correct PWM | |||
---|---|---|---|---|---|---|
0 | 00 | disconnected | disconnected | disconnected | ||
1 | 01 | M:toggle | siehe Tabelle X | siehe Tabelle Y | ||
2 | 10 | M:clear | M:clear | B:set | U:clear | D:set |
3 | 11 | M:set | M:set | B:clear | U:set | D:clear |
Der Wahl des Ausgabemodus kann im C Quellcode mit folgendem Code Schnippsel implementiert werden.
void t0_coma(uint8_t coma) {
uint8_t tccr_a = TCCR0A & 0b00111111;
TCCR0A = tccr_a | (uint8_t) (coma << COM0A0);
}
void t0_comb(uint8_t comb) {
uint8_t tccr_a = TCCR0A & 0b11001111;
TCCR0A = tccr_a | (uint8_t) (comb << COM0B0);
}
void t2_coma(uint8_t coma) {
uint8_t tccr_a = TCCR2A & 0b00111111;
TCCR2A = tccr_a | (uint8_t) (coma << COM0A0);
}
void t2_comb(uint8_t comb) {
uint8_t tccr_a = TCCR2A & 0b11001111;
TCCR2A = tccr_a | (uint8_t) (comb << COM0B0);
}
void t1_coma(uint8_t coma) {
uint8_t tccr_a = TCCR1A & 0b00111111;
TCCR1A = tccr_a | (uint8_t) (coma << COM0A0);
}
void t1_comb(uint8_t comb) {
uint8_t tccr_a = TCCR1A & 0b11001111;
TCCR1A = tccr_a | (uint8_t) (comb << COM0B0);
}
Frequenz und Tastgrad
BearbeitenAllen Signalen, die mit die Ausgabepins generiert werden können, ist gemein, dass der Ausgabewert nur zwischen den beiden Pegeln low und high wechseln kann. Es können also immer nur Rechtecksignale erzeugt werden.
Typische Kenngrößen eines solchen Rechtecksignals sind:
- Frequenz bzw. Periodendauer des Signals
- Tastgrad
Bei der Betrachtung dieser Kenngrößen stellt der Vergleichsmodus 1 einen Sonderfall dar. Aus diesem Grund wird er erst zu einem späteren Zeitpunkt betrachtet werden.
Vergleichsmodus 2 und 3
BearbeitenNon-PWM Mode
BearbeitenHier wird gar kein Rechtecksignal erzeugt. Der Ausgabepegel wird einmalig auf high, bzw. low gesetzt und verbleibt auf diesem Stand.
Fast PWM Mode
BearbeitenDer Zähler durchläuft das Intervall in Schritten. Nach einem weiteren Schritt des Zählers beginnt die Zählung von vorn. Eine Periode ist somit genau nach Schitten des Zählers durchlaufen. Die Periodendauer beträgt somit Taktschritte. Um aus diesem Wert die Periodendauer in Sekunden zu erhalten, muss er mit dem CPU Takt verrechnet werden. Die Frequenz kann dann als Kehrwert der Periodenlänge berechnet werden.
s
Hz
Phase Correct / Phase and Frequency Correct PWM Mode
BearbeitenDer Zähler durchläuft das Intervall in Schritten. Nach einem weiteren Zählschritt, also insgesamt Schritten, ist eine Periode beendet. Die Periodendauer beträgt somit Taktschritte. Für die Umrechnung in Sekunden muss dieser Wert mit dem CPU Takt verrechnet werden. Die Frequenz kann dann als Kehrwert der Periodenlänge in Sekunden berechnet werden.
s
Hz
Vergleichsmodus 1
BearbeitenNon-PWM Mode
BearbeitenDer Zustand des Ausgabepins wird in diesem Vergleichsmodus bei jedem Erreichen des maximalen Zählerstands invertiert. In zwei aufeinander folgenden Durchläufen des Zählintervalls steht der Ausgabepin so einmal low und einmal high und erzeugt auf diese Weise ein Rechtecksignal.
Das Intervall, das für die Erzeugung einer Periode des Rechtecksignals durchlaufen wird, besteht aus Zählerständen . Erst bei Eintreffen des nächsten Taktsignals beginnt die Generierung von vorn. In dieser Zeit verstreichen Zählschritte. Die Periodendauer beträgt somit Taktschritte. Um hieraus die Periodendauer in Sekunden und die Frequenz zu berechnen, muss der CPU Takt verrechnet und der Kehrwert gebildet werden.
s
Hz
Fast PWM Mode
BearbeitenDer Zustand des Ausgabepins verhält sich in diesem Vergleichsmodus im Fast PWM Mode genauso wie im Non-PWM Mode. Für Periodenlänge und Frequenz ergeben sich somit dieselben Werte wie zuvor.
Phase Correct / Phase and Frequency Correct PWM Mode
BearbeitenTODO
Zusammenfassung
BearbeitenEine Zusammenfassung findest Du in den folgenden Tabellen.
Output Compare Mode 1
BearbeitenTyp | Frequenz | Tastgrad |
---|---|---|
n/ctc | 50 % | |
f | 50 % | |
pc/pfc |
Output Compare Mode 2 / 3
BearbeitenTyp | Frequenz | Tastgrad |
---|---|---|
n/ctc | kein Rechtecksignal | - |
f | ||
pc/pfc |
Rückschau und Ausblick
BearbeitenAn dieser Stelle hast den Rundgang durch die Welt der Timer und Counter absolviert. Du hast nun alle Möglichkeiten von Timern kennen gelernt, die Dir bei der Realisierung eigener Projekte zur Verfügung stehen.
Gegebenenfalls hast Du die Informationen dieses Kapitels mit dem Datenblatt Deines Microcontrollers abgeglichen. Du kannst jetzt:
- die Ein- und Ausgabepins von Timern lokalisieren
- Zählerstände von Timern auslesen und vorgeben
Gegebenenfalls hast Du die Code Schnippsel dieses Kapitels an Deinen Microcontroller angepasst. Mit den Funktionen, die Du Dir in diesem Kapitel erarbeitet hast, kannst Du:
- Timer aktivieren und deaktivieren
Du kannst das Zählintervall von Timern vorgeben, indem Du:
- dem jeweiligen Timer eine Taktquelle zuweist
- die Betriebsart des Timers festlegst
- gegebenenfalls den maximalen Zählerstand vorgibst
Du kannst dafür sorgen, dass Timer periodisch Interrupts auslösen, indem Du:
- den Overflow Interrupt von Timern aktivierst
- den Compare Match Interrupt von Timern aktivierst
- gewünschte Zeitpunkt über die zughörigen Vergleichsregister festlegst
- die Zustellung von Interrupts global aktivierst
Du kannst die Ausgabepins von Timern für die Signalerzeugung verwenden, indem Du:
- die Datenrichtung der Pins als Ausgabepin festlegst
- den Ausgabemodus des zugehörigen Vergleichsregisters vorgibst
- gewünschte Werte in das Vergleichsregister schreibst
In folgenden Kapiteln findest Du Beispiele und Anregungen zu Aufgaben, die mit Hilfe von Timern realisiert werden können.
Register Übersicht
BearbeitenFür Timer 0 und Timer 2
Kontroll Register | ||||||||
---|---|---|---|---|---|---|---|---|
TCCRnA | compare output mode A | compare output mode B | waveform generation mode | |||||
COMnA1 | COMnA0 | COMnB1 | COMnB0 | x | x | WGMn1 | WGMn0 | |
TCCRnB | force output A | force output B | waveform gen mode | clock select | ||||
FOCnA | FOCnB | x | x | WGMn2 | CSn2 | CSn1 | CSn0 | |
Interrupt Register | ||||||||
TIFRn | compare B | compare A | overflow | |||||
x | x | x | x | x | OCFnB | OCFnA | TOVn | |
TIMSKn | compare A interrupt enable | compare B interrupt enable | overflow interrupt enable | |||||
x | x | x | x | x | OCIEnB | OCIEnA | TOIEn | |
Vergleichsregister | ||||||||
OCRnA | output compare A | |||||||
OCRnA_7 | OCRnA_6 | OCRnA_5 | OCRnA_4 | OCRnA_3 | OCRnA_2 | OCRnA_1 | OCRnA_0 | |
OCRnB | output compare B | |||||||
OCRnB_7 | OCRnB_6 | OCRnB_5 | OCRnB_4 | OCRnB_3 | OCRnB_2 | OCRnB_1 | OCRnB_0 | |
Zählregister | ||||||||
TCNTn | counter value | |||||||
TCNTn_7 | TCNTn_6 | TCNTn_5 | TCNTn_4 | TCNTn_3 | TCNTn_2 | TCNTn_1 | TCNTn_0 |
Kontroll Register | ||||||||
---|---|---|---|---|---|---|---|---|
TCCR1A | compare output mode A | compare output mode B | waveform generation mode | |||||
COM1A1 | COM1A0 | COM1B1 | COM1B0 | x | x | WGM11 | WGM10 | |
TCCR1B | input capture noise cancel | input capture edge select | waveform generation mode | clock select | ||||
ICNC1 | ICES1 | x | WGM13 | WGM12 | CS12 | CS11 | CS10 | |
TCCR1C | force output A | force output B | ||||||
FOC1A | FOC1B | x | x | x | x | x | x | |
Interrupt Register | ||||||||
TIFR1 | input capture | compare B | compare A | overflow | ||||
x | x | ICF | x | x | OCF0B | OCF0A | TOV0 | |
TIMSK1 | input capture interrupt enable | compare B interrupt enable | compare A interrupt enable | overflow | ||||
x | x | ICIE | x | x | OCIE1B | OCIE1A | TOIE1 | |
Vergleichsregister | ||||||||
OCR1AH | output compare A high byte | |||||||
OCR1AH7 | OCR1AH6 | OCR1AH5 | OCR1AH4 | OCR1AH3 | OCR1AH2 | OCR1AH1 | OCR1AH0 | |
OCR1AL | output compare A low byte | |||||||
OCR1AL7 | OCR1AL6 | OCR1AL5 | OCR1AL4 | OCR1AL3 | OCR1AL2 | OCR1AL1 | OCR1AL0 | |
OCR1BH | output compare B high byte | |||||||
OCR1BH7 | OCR1BH6 | OCR1BH5 | OCR1BH4 | OCR1BH3 | OCR1BH2 | OCR1BH1 | OCR1BH0 | |
OCR1BL | output compare B low byte | |||||||
OCR1BL7 | OCR1BL6 | OCR1BL5 | OCR1BL4 | OCR1BL3 | OCR1BL2 | OCR1BL1 | OCR1BL0 | |
Capture Register | ||||||||
ICR1H | input capture high byte | |||||||
ICR1H7 | ICR1H6 | ICR1H5 | ICR1H4 | ICR1H3 | ICR1H2 | ICR1H1 | ICR1H0 | |
OCR1AL | input capture low byte | |||||||
ICR1L7 | ICR1L6 | ICR1L5 | ICR1L4 | ICR1L3 | ICR1L2 | ICR1L1 | ICR1L0 | |
Zählregister | ||||||||
TCNT1H | counter high byte | |||||||
TCNT1H7 | TCNT1H6 | TCNT1H5 | TCNT1H4 | TCNT1H3 | TCNT1H2 | TCNT1H1 | TCNT1H0 | |
TCNT1L | counter low byte | |||||||
TCNT1L7 | TCNT1L6 | TCNT1L5 | TCNT1L4 | TCNT1L3 | TCNT1L2 | TCNT1L1 | TCNT1L0 |
Fußnoten
Bearbeiten- ↑ (siehe [M328p]: 14.12.3. Power Reduction Register, S.71)
- ↑ (siehe [M328p]: Sektion 19.2 Overview S.125)
- ↑ (siehe [M328p]: Sektion 20.3 Block Diagram S.149)
- ↑ (siehe [M328p]: Sektion 22.2 Overview S.189)
- ↑ (siehe [M328p]: Sektion 20.6. Accessing 16-bit Registers, S.152)
- ↑ (siehe [M328p]: Table 19-10. Clock Select Bit Description, S. 142, Table 20-7. Clock Select Bit Description, S. 173 sowie Table 22-10. Clock Select Bit Description, S. 206 )
- ↑ (siehe [M328p]: Sektion 5. Pin Configurations, S.14)