Kampis Elektroecke

XMega – Timer

XMega Blockdiagramm
Jeder XMega-Mikrocontroller verfügt über eine Vielzahl von Timern, die sich in drei verschiedene Kategorien unterteilen lassen:

  • Timer/Counter Typ 0
  • Timer/Counter Typ 1
  • Timer/Counter Typ 2

Die verschiedenen Typen von Timern unterscheiden sich minimal voneinander. So haben die Timer/Counter vom Typ 0 insgesamt vier und die Timer/Counter vom Typ 1 nur zwei Compare or Capture (CC)-Kanäle. Wenn ein Timer vom Typ 0 in zwei 8-Bit Timer aufgeteilt wird, entsteht daraus automatisch ein Timer vom Typ 2.

Der Fokus in diesem Tutorial liegt auf den Timern des Typs 0.

Der Timer als Taktgeber:

Im ersten Beispiel soll ein Timer vom Typ 0 (hier TCC0) genutzt werden um einen sekündlichen Interrupt zu erzeugen. Mit dem erzeugten Interrupt soll dann eine LED, die sich an Pin R.0 befindet und Active Low beschaltet ist, ein- und ausgeschaltet werden.

Zuerst wird der Pin für die LED als Ausgang deklariert und die LED eingeschaltet.

PORTR.DIRSET = (0x01 << 0x00);
PORTR.OUTCLR = (0x01 << 0x00);

Die einzelnen Funktionen des Timers werden über die WGMODE-Bits im CTRLB-Register eingestellt. Insgesamt werden sechs verschiedene Modi zur Verfügung gestellt.

Modus Funktion TOP
NORMAL Frequenzerzeugung über PER-Register PER
FRQ Frequenzerzeugung über CCA-Register CCA
SINGLESLOPE
  • Single-slope PWM
  • Reset nach BOTTOM, wenn TOP erreicht
PER
DSTOP
  • Dual-slope PWM
  • Aufwärtszählen von BOTTOM bis TOP und dann abwärts bis BOTTOM
  • Interrupts bei TOP
PER
DSBOTH
  • Dual-slope PWM
  • Aufwärtszählen von BOTTOM bis TOP und dann abwärts bis BOTTOM
  • Interrupts bei TOP und BOTTOM
PER
DSBOTTOM
  • Dual-slope PWM
  • Aufwärtszählen von BOTTOM bis TOP und dann abwärts bis BOTTOM
  • Interrupts bei BOTTOM
PER

Für die Frequenzerzeugung soll der Modus NORMAL genutzt werden.

TCC0.CTRLB = TC_WGMODE_NORMAL_gc;

 Im Normalmodus wird die Ausgangsfrequenz des Timers mit Hilfe der Formel

f_{Out} = \frac{f_{CPU}}{Prescaler \cdot (PER + 1)}

berechnet und ist vom Vorteiler, sowie vom Inhalt des PER-Registers abhängig. Der Vorteiler wird über das CTRLA-Register des Timers eingestellt und dient zusätzlich noch dazu den Timer zu deaktivieren, indem die CLKSEL-Bits gelöscht werden.

Um ein möglichst genaues Ergebnis zu erreichen, habe ich einen Vorteiler von 64 gewählt. Mit diesem Vorteiler ergibt sich ein glatter Wert für das PER-Register. Bei einer Taktfrequenz von 2 MHz ergeben sich somit folgende Werte:

Vorteiler 64
PER-Register 31249

Die berechneten Werte werden nun in die entsprechenden Register geschrieben:

TCC0.CTRLA = TC_CLKSEL_DIV64_gc;
TCC0.PER = 31249;

Jetzt muss noch der Overflow-Interrupt des Timers aktiviert und die ISR programmiert werden. Die Prioritäten für die Timer Interrupts werden über die Register INTCTRLA und INCTRLB gesteuert. Das Register INTCTRLB beinhaltet die Interruptprioritäten für die Compare or Capture-Interrupts und das Register INTCTRLA die Prioritäten für die Error oder Overflow Interrupts.

TCC0.INTCTRLA = TC_OVFINTLVL_LO_gc;

ISR(TCC0_OVF_vect)
{
	PORTR.OUTTGL = (0x01 << 0x00);
}

Zum Schluss werden noch die Interrupts im Interruptcontroller aktiviert.

PMIC.CTRL = PMIC_LOLVLEN_bm;
sei();

Sobald das Programm gestartet wird, fängt die LED an mit einer Frequenz von etwa 0,5 Hz (1 Sekunde an, 1 Sekunde aus) zu blinken.

PWM mittels Timer erzeugen:

Im nächsten Beispiel soll der Timer genutzt werden um ein pulsweitenmoduliertes Signal (PWM) zu erzeugen. Die Timer verfügen über einen OC-Pin pro Compare or Capture Kanal an denen die erzeugte PWM ausgegeben werden kann. Der verwendete Timer 0 verfügt somit über vier verschiedene OC-Pins und diee Belegung lässt sich den Datenblättern der verschiedenen XMega-Mikrocontrollern entnehmen.

Die PWM soll in diesem Bild an Pin C.0, bzw. OC0A (Output Compare Timer 0 Channel A) ausgegeben werden und eine Frequenz von 50 Hz, sowie einen Tastgrad von 20% haben.

Zuerst muss der verwendete OC-Pin als Ausgang deklariert werden:

PORTC.DIRSET = (0x01 << 0x00);

Bei der PWM soll es sich um eine Single-slope PWM handeln. Über das CTRLB-Register wird der Timer in den Single-slope Modus gesetzt und der verwendete Compare or Capture Kanal aktiviert.

TCC0.CTRLB = TC0_CCAEN_bm | TC_WGMODE_SINGLESLOPE_gc;

Die Ausgangsfrequenz wird somit wieder über das PER-Register des Timers bestimmt. Für die Ausgangsfrequenz gilt dabei die folgende Formel:

f_{Out} = \frac{f_{CPU}}{Prescaler \cdot (PER + 1)}

Bei einem Vorteiler von 64 ergibt sich für eine Ausgangsfrequenz von 50 Hz der Wert 624.

TCC0.CTRLA = TC_CLKSEL_DIV64_gc;
TCC0.PER = 624;

Über das CC-Register des verwendeten Kanals wird dann die Pulsweite eingestellt. Für einen Tastgrad von 20% ergibt sich demnach der Wert 125.

TCC0.CCA = 125;

Damit ist der Timer fertig konfiguriert und erzeugt eine PWM mit einer Frequenz von 50 Hz und einem Tastgrad von 20%.

Timer zur Frequenzmessung:

Der Timer kann nicht nur für die Erzeugung von Signalen genutzt werden, sondern auch um externe Signale zu vermessen. Dieses Beispiel beschäftigt sich damit, wie die Capture-Funktion des Timers genutzt werden kann, um die Frequenz, die Pulsbreite oder Frequenz und Pulsbreite eines externen Signals zu vermessen.

Für dieses Beispiel wird der Timer D zur Erzeugung einer PWM genutzt und die Frequenz, sowie der Tastgrad der erzeugten PWM soll mit Hilfe des Timers C vermessen werden. Die PWM von Timer D soll am Pin D.0 ausgegeben und über den Pin C.7 an den Timer C weitergereicht werden.

Im ersten Beispiel soll die Frequenz vermessen werden. Dazu wird der Timer D im PWM-Modus mit einem Tastgrad von 50% initialisiert:

PORTD.DIRSET = (0x01 << 0x00);
TCD0.CTRLA = TC_CLKSEL_DIV64_gc;
TCD0.CTRLB = TC0_CCAEN_bm | TC_WGMODE_SINGLESLOPE_gc;
TCD0.PER = 947;
TCD0.CCA = TCD0.PER >> 0x01;

Damit mit einem Timer die Frequenz oder der Tastgrad eines Signals gemessen werden kann, muss der Capture-Modus verwendet werden. In diesem Modus wird das externe Signal über das Eventnetzwerk zu den einzelnen CC-Kanälen des Timer geleitet und eine fest definierte Flanke startet, bzw. stoppt den Timer. Sobald ein Event eintrifft, wird der aktuelle Inhalt des CNT-Registers des Timers in das CC-Register des verwendeten Kanals kopiert. Das Verhalten des Timers bei verschiedenen Taktflanken hängt vom verwendeten Capture-Modus ab.

Um also die Frequenz eines Signals zu messen, muss erst einmal das Eventnetzwerk konfiguriert werden.

PORTC.DIRCLR = (0x01 << 0x07);
PORTC.PIN7CTRL = PORT_ISC_RISING_gc;
EVSYS.CH0MUX = EVSYS_CHMUX_PORTC_PIN7_gc;

Für die Frequenzmessung wird der Pin C.7 als Eingang geschaltet. Zusätzlich soll der Pin auf steigende Flanken reagieren. Anschließend wird der Pin mit dem Eventkanal 0 verbunden.

Der Timer muss für sämtliche Capture-Operationen im NORMAL-Modus betrieben werden. Zudem soll in diesem Beispiel der Capture-Kanal CCA genutzt werden. Über das CTRLD-Register wird der Timer in den Frequency-Modus gesetzt und der verwendete Eventkanal konfiguriert.

TCC0.CTRLB = TC0_CCAEN_bm | TC_WGMODE_NORMAL_gc;
TCC0.CTRLD = TC_EVACT_FRQ_gc | TC_EVSEL_CH0_gc;

Die Frequenzmessung startet und stoppt automatisch bei einer steigenden Flanke. Bei einer steigenden Flanke wird der aktuelle Inhalt des CNT-Registers kopiert und der Timer neu gestartet. Zwischen beiden Flanken wird das CNT-Register mit jedem Taktzyklus vom Timer inkrementiert.

Abschließend muss noch der Vorteiler für den Takt eingestellt werden, damit der Timer aktiviert wird. Zudem wird der Compare-Interrupt für den verwendeten CC-Kanal aktiviert:

TCC0.CTRLA = TC_CLKSEL_DIV1_gc;
TCC0.INTCTRLB = TC_CCAINTLVL_LO_gc;
PMIC.CTRL = PMIC_LOLVLEN_bm;
sei();

Das PER-Register der Timer wird bei einem Reset automatisch mit 0xFFFF initialisiert, sprich bei der ersten positiven Flanke des Signals wird der Wert 0xFFFF in das CCA-Register kopiert. Anschließend zählt der Timer los und bei der zweiten positiven Flanke wird der aktuelle Wert des PER-Registers in das CCA-Register kopiert. Damit entspricht der Wert des CCA-Registers automatisch der Länge einer Periode des Signals in Taktzyklen des Timers. Damit gilt für die Ausgangsfrequenz:

f = \frac{f_{CPU}}{Prescaler}\cdot \frac{1}{CCA}

Der Wert des CCA-Registers kann dann über den CCA-Interrupt ausgelesen werden.

ISR(TCC0_CCA_vect)
{
	Frequency_Cap = TCC0.CCA;
}

Die erzeugte PWM hat eine Frequenz von 33 Hz und der Timer zählt insgesamt 0xECFF Zyklen. Das entspricht einer gemessenen Frequenz von:

f = \frac{2000000 Hz}{1}\cdot \frac{1}{60671}
f = 32,96 Hz

Damit ist die Frequenzmessung komplett. Schauen wir uns mal an, wie man die Pulsbreite messen kann…

Messen der Pulsbreite mittels Timer:

Die Pulsbreite eines Signals wird, vom Prinzip her, ähnlich gemessen, wie die Frequenz eines Signals. Zuerst erzeuge ich mir wieder eine PWM als Testsignal.

PORTD.DIRSET = (0x01 << 0x00);
TCD0.CTRLB = TC0_CCAEN_bm | TC_WGMODE_SINGLESLOPE_gc;
TCD0.CTRLA = TC_CLKSEL_DIV64_gc;
TCD0.PER = 624;
TCD0.CCA = 312;

Dann wird wieder das Eventnetzwerk konfiguriert.

PORTC.DIRCLR = (0x01 << 0x07);
PORTC.PIN7CTRL = PORT_ISC_RISING_gc;
EVSYS.CH0MUX = EVSYS_CHMUX_PORTC_PIN7_gc;

In diesem Beispiel muss der Eingangspin allerdings für beide Flanken konfiguriert werden, da die Messung mit der steigenden Flanke gestartet und mit der fallenden Flanke gestoppt wird.

Die Konfiguration für den Timer ist nahezu die Selbe wie bei der Frequenzmessung, außer das der Timer über das CTRLD-Register in den Pulse Width-Modus versetzt wird.

TCC0.CTRLB = TC0_CCAEN_bm | TC_WGMODE_NORMAL_gc;
TCC0.CTRLA = TC_CLKSEL_DIV1_gc;
TCC0.CTRLD = TC_EVACT_PW_gc | TC_EVSEL_CH0_gc;
TCC0.INTCTRLB = TC_CCAINTLVL_LO_gc;
PMIC.CTRL = PMIC_LOLVLEN_bm;
sei();

In der ISR des Capture-Kanals wird wieder das CCA-Register ausgelesen.

ISR(TCC0_CCA_vect)
{
	PulseWidth_Cap = TCC0.CCA;
}

Die Funktions- und Zählweise ist komplett identisch wie bei der Frequenzmessung, außer dass die Messung bei der steigenden Flanke gestartet und bei der fallenden Flanke gestoppt wird.

Bei der erzeugten PWM zählt der Timer 0x4DFF Zyklen. Damit folgt für die Pulsbreite eine Zeit von:

T_{On} = \frac{1}{2000000 Hz}\cdot 19967
T_{On} = 9,98 ms

Tastgrad und Frequenz mit Timer messer:

Zum Abschluss möchte ich noch zeigen, wie der Timer genutzt werden kann, um sowohl den Tastgrad, wie auch die Frequenz eines externen Signals zu messen.

Auch in diesem Beispiel soll der Timer D als Signalquelle und das Eventsystem für Timer C genutzt werden:

PORTD.DIRSET = (0x01 << 0x00);
TCD0.CTRLB = TC0_CCAEN_bm | TC_WGMODE_SINGLESLOPE_gc;
TCD0.CTRLA = TC_CLKSEL_DIV64_gc;
TCD0.PER = 624;
TCD0.CCA = 124;

PORTC.DIRCLR = (0x01 << 0x07);
PORTC.PIN7CTRL = PORT_ISC_BOTHEDGES_gc;
EVSYS.CH0MUX = EVSYS_CHMUX_PORTC_PIN7_gc;

Eine direkte Messung des Tastgrades und der Frequenz mit einem Timer ist nicht möglich, da der Timer entweder die Pulsbreite oder die Frequenz messen kann.

Allerdings kann diese Funktion leicht per Software eingebaut werden, indem der Capture-Modus genutzt wird. In diesem Modus wird der Messbereich des Timers halbiert und das MSB im PER-Register genutzt um die Polarität des verwendeten Pins zu erfassen. Zuerst wird der Timer also wieder initialisiert:

TCC0.CTRLB = TC0_CCAEN_bm | TC_WGMODE_NORMAL_gc;
TCC0.CTRLA = TC_CLKSEL_DIV2_gc;
TCC0.CTRLD = TC_EVACT_CAPT_gc | TC_EVSEL_CH0_gc;
TCC0.PER = 0x7FFF;
TCC0.INTCTRLB = TC_CCAINTLVL_LO_gc;
	
PMIC.CTRL = PMIC_LOLVLEN_bm;
sei();

Hier ist jetzt zu beachten, dass ich den Vorteiler auf 2 gestellt habe, weil der Timer im Capture-Modus nur über den halben Messbereich verfügt und dieser Messbereich durch den größeren Vorteiler wieder vergrößert wird. Zudem muss das MSB im PER-Register gelöscht werden, da das MSB in diesem Fall den Status des Eventpins anzeigt.

In der ISR des Capture-Kanals wird dann wieder das CCA-Register ausgelesen.

ISR(TCC0_CCA_vect)
{
	uint16_t Capture = TCC0.CCA;
	if(Capture & 0x8000)
	{
		Frequency_Cap = Capture & 0x7FFF;
		TCC0.CTRLFSET = TC_CMD_RESTART_gc;
	}
	else
	{
		PulseWidth_Cap = Capture;
	}
}

Dieses mal wird allerdings geprüft ob das MSB des CCA-Registers gesetzt ist. Wenn das Bit (und dem entsprechend der Eventpin) gesetzt ist, wird der Wert des CCA-Registers, ohne das MSB, für die Frequenzberechnung gespeichert und der Timer neu gestartet, indem das entsprechende Kommando in das CTRLFSET-Register geschrieben wird.

Wenn das Bit bei einem Capture-Event (also ein Flankenwechsel am Eventpin) nicht gesetzt worden ist, bedeuted dies, dass ein Wechsel von High nach Low stattgefunden hat. Somit wird der Wert des CCA-Registers für die Berechnung der Pulsbreite gespeichert.

Aus diesen beiden Werten kann dann der Tastgrad und die Frequenz berechnet werden. Für die verwendete PWM ergibt sich für Frequency_Cap der Wert 0x4DFF und für PulseWidth_Cap der Wert 0x0F5F. Daraus folgt für die Frequenz und den Tastgrad:

f = \frac{2000000 Hz}{2}\cdot \frac{1}{19967}
f = 50,08 Hz

DutyCycle = \frac{PulseWidth\_Cap}{Frequency\_Cap}
DutyCycle = \frac{3935}{19967}
DutyCycle = 0,197


Achtung:

Bei der Frequenz-, bzw. Pulsweitenmessung ist Vorsicht geboten! Die maximal messbare Periodendauer, bzw. Pulslänge ist von der Taktrate und vom Vorteiler des Timers abhängig. Bei einem 2 MHz Takt und einem Vorteiler von 1 ergibt sich z. B. für die maximal messbare Periodendauer:

T=\frac{Prescaler}{f_{CPU}}\cdot 65536
T=\frac{1}{2000000 Hz}\cdot 65536
T=0,0327 s = 30,51 Hz

Im Capture-Modus, also bei einer gleichzeitigen Messung von Frequenz und Pulsweite, ist die maximal messbare Frequenz noch einmal um die Hälfte reduziert, da der Timer, auf Grund der Funktion des letzten Bits zur Flankenerkennung, nur über den halben Messbereich verfügt.


Das komplette Projekt inkl. Beispielanwendungen für die verschiedenen Betriebsarten steht in meinem GitHub-Repository zum Download bereit.

Schreibe einen Kommentar

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