4.1  Timer A/B

Mit dem Timer A und dem Timer B steht uns ein sehr vielseitiger Funktionsblock zur Verfügung, mit dem viele Funktionen rund um das Zählen und das Messen von Zeit realisiert werden können. Beide Funktionsblöcke sind relativ ähnlich aufgebaut, weshalb sie in einem Kapitel beschrieben sind. Timer B besitzt noch einige Zusatzfunktionen, die im Anschluss daran erläutert werden. Die wesentlichen Eigenschaften des Timerbausteins können wie folgt zusammengefasst werden.

Je nach Variante des Prozessors steht uns eventuell nur der Timer A oder, bei besserer Ausstattung, auch der Timer B zur Verfügung. Auch die Anzahl der Capture/Compare-Register ist unterschiedlich. So hat beispielsweise ein MSP430F1232 nur einen Timer A mit drei Capture/Compare-Registern, ein MSP430F1612 hat einen Timer A mit drei und einen Timer B mit sieben Capture/Compare-Registern. Der grundsätzliche Aufbau ist in beiden Fällen jedoch sehr ähnlich. Der Einfachheit halber wird im Folgenden nur der Timer A beschrieben. Für den Timer B gilt alles analog, lediglich die Registernamen sind entsprechend anzupassen. Das Funktionsdiagramm des Timers A ist in folgendem Bild dargestellt.


PIC

Bild 4.1.: Blockschaltbild des Timers A der Modellreihe MSP430x1xx [Tex06c].


Wie man erkennt, kann die Funktionalität des Timers prinzipiell in zwei Funktionsblöcke unterteilt werden. Im oberen Teil ist das eigentliche Zählmodul (”Timer Block”) untergebracht. Zentrales Element ist das Zählregister, das den aktuellen Zählerstand enthält. Umgeben ist es mit etwas Logik, die Zählquelle und Zählmodalität festlegt. Im unteren Teil sieht man die komplexe Funktionalität des Capture/Compare-Funktionsblocks, mit der das Ausgangsverhalten des Timers definiert werden kann.

4.1.1  Taktquellen und Initialisierung

Bevor man einen Timer benutzen kann, muss er den eigenen Funktionsanforderungen entsprechend konfiguriert werden. Dazu steht ein 16 Bit breites Register, das Timer-A-Control- Register, kurz TACTL zur Verfügung. Die Aufteilung der zugehörigen Steuerbits des TACTL-Registers ist in Bild 4.2 dargestellt.


PIC

Bild 4.2.: Bitbelegung des Timer-A-Control-Registers TACTL.


Wie man erkennen kann, sind die oberen sechs Bits reserviert und haben keine Auswirkung auf die Konfiguration des Timers.

In einem ersten Schritt ist die Taktquelle des Timers festzulegen. Er kann wahlweise aus der Auxiliary-Clock ACLK oder der Sub-Main-Clock SMCLK des Basic-Clock-Moduls (siehe Kap. 3) betrieben werden. Alternativ lässt sich der Timer A auch mit einer externen Taktquelle TACLK, angeschlossen an Port P1.0 oder der Taktquelle INCLK, die an Port P2.1 angeschlossen wird, verknüpfen. Mit den TASELx-Flags legt man die Taktquelle des Timers A fest. Die Zuordnung für die Auswahl der Taktquellen ist in Tabelle 4.1 gelistet.


TASSEL1TASSEL0Taktquelle
0 0 TACLK (Port P1.0)
0 1 ACLK
1 0 SMCLK
1 1 INCLK (Port P2.1)

Tabelle 4.1.: Auswahl der Taktquelle für den Timer A

Das Taktsignal kann nach Auswahl der Taktquelle noch zusätzlich um einen Faktor geteilt werden. Dazu dienen die IDx-Flags (Tabelle 4.2).


ID1ID0Teiler
0 0 1
0 1 2
1 0 4
1 1 8

Tabelle 4.2.: IDx-Flags: Software-Teiler für den Timer A

Auf die Bits und Flags MCx, TAIE und TAIFG kommen wir später noch zu sprechen.

Zählregister TAR

Der Timer A verfügt über ein 16 Bit breites Zählregister TAR, das den derzeitig aktuellen Zählerstand enthält. Auf das Register kann sowohl lesend als auch schreibend zugegriffen werden.

Mit Hilfe des TACLR-Flags im TACTL-Steuerregister wird das Zählregister des Timers (TAR) sowie der Teiler und damit die IDx-Bits gelöscht. Auf dieses Flag kann nur schreibend zugegriffen werden. Liest man den Inhalt von TACLR, erhält man immer den Wert 0. Das TACLR-Flag ist damit eine Art Taster, mit dem man einen Reset für den Timer A ausführen kann. Es wird nach Setzen durch den Anwender automatisch vom Controller wieder gelöscht.

4.1.2  Betriebsarten

Der Timer A kann in seiner Arbeitsweise flexibel eingestellt werden. Er besitzt insgesamt vier Betriebsarten:

Continuous-Mode
Das Zählregister wird kontinuierlich bis zum höchsten Wert 0xFFFF hochgezählt, beginnt dann wieder bei 0x0000
Up-Mode
Das Zählregister wird bis zum Wert des TACCR0-Registers hochgezählt, beginnt dann wieder bei 0x0000
Up/Down-Mode
Das Zählregister wird bis zum Wert des TACCR0-Registers hochgezählt, wo es dann seine Zählrichtung ändert und bis 0x0000 heruntergezählt wird. Dort ändert es wieder seine Zählrichtung
Stop-Mode
Der Timer ist angehalten

Modus-Auswahl

Die gewünschten Betriebsarten Continuous-Mode, Up-Mode, Up/Down-Mode oder Stop-Mode werden durch eine entsprechende Bit-Auswahl der MCx-Flags im TACTL-Register gemäß Tabelle 4.3 ausgewählt.


MC1MC0Modus
0 0 Stop-Mode
0 1 Up-Mode
1 0 Continuous-Mode
1 1 Up/Down-Mode

Tabelle 4.3.: Auswahl der Betriebsart im TACTL-Register

Timer starten

Der Timer startet mit dem Zählvorgang, sobald eine andere Betriebsart als der Stop-Mode ausgewählt wurde. Dabei wird der Wert des Registers TAR inkrementiert oder dekrementiert. Beim Up- (Seite §) sowie Up/Down-Mode (Seite §) muss darüber hinaus das Register TACCRO einen von 0 verschiedenen Wert haben. Es wird dringend empfohlen, sämtliche Konfigurationen des Timers nur bei gestopptem Timer durchzuführen. Andernfalls läuft man Gefahr, dass ein sehr merkwürdiges Verhalten des Timers während dieser Zeit entstehen kann.

Continuous-Mode

Beim Continuous-Mode inkrementiert der MSP430 das Register TAR bis zum Wert 65535 (entspricht 0xFFFF in hexadezimaler Schreibweise). Bei eben diesem Maximalwert führt die nächste Taktflanke zum Überlauf und so zu einem Zählerstand 0 des TAR-Zählregisters. Darüber hinaus wird das Timer-A-Interrupt-Flag TAIFG gesetzt.


PIC

Bild 4.3.: Wert des Zählregisters TAR des Timers A im Continous-Mode


Nachdem der Zählerstand wieder 0 ist, beginnt der Timer wieder mit der Inkrementierung des Zählregisters. Der gesamte Prozess wiederholt sich also fortlaufend, was in Bild 4.3 beispielhaft für vier Zyklen skizziert ist.

Das Register TAR kann mit Hilfe des TACLR-Flags manuell auf 0 gesetzt werden. Dabei gehen allerdings auch die Einstellungen des Vorteilers (IDx-Flags) verloren. Beim Auslesen des Timer-Wertes aus dem TAR-Register müssen wir zusätzlich berücksichtigen, dass der Timer während der Abarbeitung des Befehls weiterläuft. Für eine sehr genaue Zeitmessung müssten wir die Ausführungszeit des Lesebefehls bei der Interpretation des Ergebnisses berücksichtigen. Wird der Timer mit der gleichen Taktgeschwindigkeit wie der Prozessortakt MCLK betrieben, sollte vor dem Auslesen des TAR-Registers der Timer gestoppt werden. Andernfalls können Zahlüberläufe zu fehlerhaften Leseergebnissen führen.

Das Beispiel in Listing 4.1 zeigt, wie man mit dem Timer A die Laufzeit einer Funktion messen kann. Die Ausführung muss allerdings weniger als 65535 Schritte betragen, da ja sonst ein Überlauf des Timers eintreten würde. Bei der exakten Berechnung der Laufzeit muss ein OFFSET durch die Dauer der Abarbeitung des Befehls zum Starten und Stoppen des Timers abgezogen werden. Bei diesem Beispiel wurde dieser Offset experimentell ermittelt, indem in einem Vorexperiment der Funktionsaufruf von meine_Funktion() einfach ausgeklammert wurde.

#include <msp430x16x.h> 
 
#define OFFSET 5           // Berechnungsoffset Taktzyklen 
void meine_Funktion(void) 
{ 
    // berechne etwas sehr kompliziertes 
} 
int main(void) { 
  int laufzeit;            // Variable für Ergebnis 
 
  WDTCTL = WDTPW + WDTHOLD;// Watchdog ausschalten 
  BCSCTL2 = 0;             // wähle DCO für MCLK und SMCLK 
  TACTL = TASSEL_2;        // Auswahl der SMCLK als Quelle 
  TACTL |= TACLR;          // Rücksetzen des Zählers 
  TACTL |= MC_2;           // los gehts im Continuous-Mode 
  meine_Funktion();        // Funktion, die gemessen wird 
  TACTL &=~ MC_2;          // stopp 
  laufzeit = TAR-OFFSET;   // gemessene Laufzeit der Funktion 
                           // korrigiert um Offset 
}
Listing 4.1: Verwendung des Timers A zur Laufzeitmessung
Up-Mode

Der Up-Mode ist vergleichbar mit dem Continuous-Mode, nur dass der Programmierer hier den Maximalwert des Zählers mit dem Register TACCR0 einstellen kann. Bild 4.4 skizziert den zeitlichen Verlauf des Timers im Up-Mode über vier Zyklen.


PIC

Bild 4.4.: Zählregister TAR des Timers A im Up-Mode


Erreicht der Zähler den Wert TACCR0, so wird das Capture/Compare-Interrupt-Flag CCIFG0-Flag im TACCTL0-Register gesetzt (dazu später mehr im Kapitel zu den Capture/Compare-Registern). Ein erneuter Taktimpuls setzt das Zählregister wieder auf 0 und das Timer-A- Interrupt-Flag (TAIFG) wird gesetzt.

In Abhängigkeit von der gewählten Taktquelle ergibt sich für jeden Zählerstand ein zugehöriges Zeitintervall. Analog dazu kann man für einen gesamten Zyklus, also für den Zählerstand 65535 ebenfalls einen Zeitintervall berechnen. Bei Verwendung der Auxiliary-Clock mit einer Frequenz von 32.768kHz und einem Software-Teiler von 1 (ID0 = 0 und ID1 = 0) entspricht das Zählinkrement eins einem Zeitintervall von rund 31μs. Der Zählerstand 200 entspricht analog dazu einem Zeitinvertvall von etwa 6.12ms. Gleichung 4.1 beschreibt, wie das Zeitintervall T in Abhängigkeit von der Taktquellen-Frequenz fCLK, dem Software-Teiler ID0 und ID1 und dem Zählerstand TAR berechnet werden kann.

     (ID0+2⋅ID1 )
T = 2---------- ⋅T AR                           (4.1)
       fCLK

Im folgenden Programm soll der Timer A jetzt so initialisiert werden, dass mit einem extern angeschlossenen Uhrenquarz mit einer Frequenz von 32.768kHz der Timer-Interrupt einmal pro Sekunde auftritt. In der Interrupt-Service-Routine implementieren wir eine kleine Echtzeituhr.

#include  <msp430x16x.h> 
int stunde, minute, sekunde; 
int led; 
 
#pragma vector=TIMERA0_VECTOR 
__interrupt void TIMERA0(void){ 
 
 led = ~led & BIT0;         // LED toggeln 
 P1OUT = led; 
 if(sekunde >= 59) {        // Überlauf der Sekunden 
   sekunde = 0; 
   if(minute >= 59) {       // Überlauf der Minuten 
      minute = 0; 
      if(stunde >= 23) {    // Überlauf der Stunden 
         stunde = 0; 
      } 
      else {stunde++;} 
   } 
   else {minute++;} 
 } 
 else {sekunde++;} 
} 
 
void main(void) { 
  int i; 
 
  WDTCTL = WDTPW + WDTHOLD; // Watchdog ausschalten 
 
  led = 0x00;               // LED an P1.0 als Sekunden-Anzeige 
  P1DIR = BIT0; 
  P1OUT = led; 
 
  BCSCTL1 &= ~XTS;          // LF-Mode 
  _BIC_SR(OSCOFF);          // XT1 anschalten 
 
  TACTL    = TASSEL_1;      // wähle ACLK, DIV = 1 
  TACTL   |= MC_1;          // Up-Mode wählen 
  TACCTL0  = CCIE;          // Cap/Comp-Interrupt aktivieren 
  TACCR0   = 32767;         // von 0 bis 32767 zählen 
 
  stunde   = 0; 
  minute   = 0; 
  sekunde  = 0; 
 
  _enable_interrupts ();    // Interrupts global aktivieren 
  while(1) { }              // Endlosschleife 
}
Listing 4.2: Eine Echtzeituhr auf Basis des Timers A
Up/Down-Mode

Bisher haben wir mit dem Continuous- und Up-Mode zwei Betriebsarten kennengelernt, bei denen der Mikroprozessor von 0 bis zu einem bestimmten Maximalwert vorwärts zählt. Der Up/Down-Mode kombiniert einen Vorwärts- und Rückwärtszähler. In einem Zyklus inkrementiert der Prozessor das Zählerregister zunächst bis zum Maximalwert TACCR0. Unmittelbar beim Erreichen des Maximalwerts wird das CCIFG0-Flag gesetzt.


PIC

Bild 4.5.: Zählregister TAR des Timers A im Up/Down-Mode


Anders als beim Up-Mode ändert sich bei Erreichen von TACCR0 nun die Zählrichtung. Der Prozessor zählt vom Maximalwert rückwärts bis zum Wert 0 und setzt dann das TAIFG-Flag. Bild 4.5 skizziert den zeitlichen Verlauf des Zählregisters über drei Zyklen. Die gesamte Periodendauer T eines Zyklus’ lässt sich mit Gleichung 4.2 berechnen. Sie entspricht gerade dem Doppelten der Periodendauer, wenn der Timer A im Up-Mode betrieben wird, bei gleichem TACCR0.

    2(1+ID0+2⋅ID1 )
T = ------------- ⋅(TACCRO    + 1)                    (4.2)
        fCLK
Dabei ist fCLK die Frequenz der Taktquelle, die durch den Software-Teiler ID0 und ID1 für den Timer A konfiguriert werden kann.

Der Zählvorgang kann während des Up/Down-Modus unterbrochen werden. Dabei merkt sich der Mikroprozessor den Wert des Zählregisters und die Zählrichtung (vorwärts oder rückwärts). Setzt man das TACLR-Flag, so wird der Timer neu initialisiert. Damit wird das Zählregister auf 0 gesetzt und die Zählrichtung auf vorwärts festgelegt.

Der Up/Down-Modus eignet sich für die Erzeugung von symmetrischen pulsweitenmodulierten Signalen, die wir im Kapitel 7.4.1 noch näher kennenlernen werden.

Stop-Mode

Um den Timer A in den Stop-Mode zu schalten, müssen die Bits MC0 und MC1 gelöscht werden. Dabei wird der Zählvorgang angehalten, ohne den Zählstand oder die Zählrichtung zu ändern. Will man den Wert des Zählregisters TAR auslesen, so sollte vorher der Timer gestoppt werden. Ansonsten geht in den Zählstand auch die für das Lesen des Registers TAR benötigte Zeit ein.

#include  <msp430x16x.h> 
void main(void) { 
  int c=0; 
 
  TACTL    = TASSEL_2; // Select SMCLK, DIV = 1 
  TACCR0   = 100;      // von 0 bis 100 zählen 
  TACTL   |= MC_3;     // Up/Down-Mode wählen und starten 
 
  // Schleife kann mit dem Debugger ausgewertet werden 
  while(c<100) 
   { 
     c= TAR; 
   } 
  TACTL   &= ~(MC_3);  // Timer anhalten 
}
Listing 4.3: Anhalten des Timers A

4.1.3  Capture/Compare-Timer-Kanäle

Bisher haben wir uns auf das Zählregister sowie dessen Konfiguration konzentriert. Im jetzt folgenden Abschnitt soll es um die erweiterten Auswertemöglichkeiten mit Hilfe der so genannten Capture/Compare-Kanäle (CCRx) gehen. Je nach Ausstattung des Prozessors gibt es von diesen Kanälen pro Timer drei, fünf oder sieben Stück. Alle Kanäle eines Timers teilen sich das gleiche Zählregister (TAR). Jeder Capture/Compare-Kanal hat als zentrale Komponente ein Register sowie eine zugehörige Steuerlogik, die das Ein- und Ausgangsverhalten des Funktionsblocks definiert. Ein Blockschaltbild der Capture/Compare-Funktionseinheit, diesmal des Timers B, zeigt Bild 4.6 (vergleiche dazu auch den Timer A, Bild 4.1).


PIC

Bild 4.6.: Blockschaltbild des Timer-B-Capture/Compare-Funktionsblocks [Tex06c].


Der Kanal 0 (CCR0) ist den anderen Kanälen übergeordnet. Diesen Kanal haben wir im vorigen Kapitel benutzt, um das Zählverhalten des Timers zu beeinflussen (z.B. zum Einstellen der Zählgrenze im Up/Down-Mode).

Im Capture-Modus lassen sich Zeitmessungen bestimmten Ereignissen zuordnen. Das heißt, dass man sich z.B. den Zählerstand automatisch merken kann, wenn der Status eines externen Pins wechselt. Im Compare-Modus wird der Wert des Timer-Registers ständig mit dem Compare-Register verglichen und abhängig davon wird z.B. ein Interrupt ausgelöst. Mit Hilfe der Timer-Kanäle lassen sich also Zeitpunkte und Zeitstempel zuordnen sowie Ereignisse auslösen, ohne dass dazu Ressourcen der CPU benötigt werden.

Capture/Compare-Control-Register

Die Konfiguration der Capture- und Compare-Funktion wird mit Hilfe des Capture/Compare-Control-Registers TACCTLx durchgeführt. Jeder Timer-Kanal verfügt dabei über ein eigenes TACCTLx-Register. Auf dem MSP430F1612 sind beispielsweise drei Timer-Kanäle für Timer A implementiert. Als Konfigurationsregister stehen damit TACCTL0, TACCTL1 und TACCTL2 zur Verfügung.

Die Bitbelegung des 16 Bit breiten Capture/Compare-Control-Registers ist in Bild 4.2 dargestellt und bei allen Timer-Kanälen identisch aufgebaut.


PIC

Bild 4.7.: Capture/Compare-Control-Register TACCTLx


Zu Beginn des Kapitels 4.1.3 wurde darauf verwiesen, dass der MSP430 mit Hilfe der Capture/Compare-Funktionalität Ereignissen Zeitmarken und Zeitstempel zuweisen kann. Ereignisse sind Flanken- bzw. Pegelwechsel an bestimmten Portpins oder Taktquellen. Prinzipiell kann man unterscheiden, ob man auf eine steigende, eine fallende oder sowohl auf eine steigende als auch eine fallende Flanke reagiert.


CMxFunktion
00 Keine Funktion
01 Reaktion auf steigende Flanke
10 Reaktion auf fallende Flanke
11 Reaktion auf steigende und fallende Flanke

Tabelle 4.4.: Flankengetriggerte Reaktion für die Capture/Compare-Funktion

Diese Einstellung kann mit den CMx-Flags (Tabelle 4.4) konfiguriert werden. Jedem Timer-Kanal kann eine eigene Flankentriggerung zugewiesen werden.

Die Auswahl der Eingangsgröße des Ereignisses kann gleichermaßen für jeden Kanal individuell vorgenommen werden. Dazu dienen die Capture/Compare-Input-Select-Bits (CCISx). Wie wir in Tabelle 4.5 sehen, können insgesamt vier Eingangsereignisse ausgewählt werden.


CCISxFunktion
00 CCIxA
01 CCIxB
10 GND
11 VCC

Tabelle 4.5.: Auswahl der Eingänge für die Capture/Compare-Funktion

Die Signale CCIxA und CCIxB sind symbolische Platzhalter. Deren Zuordnung bzw. Anschluss ist unterschiedlich für jeden Timer-Kanal und variiert für die verschiedenen MSP430-Typen. Die genaue Zuordnung ist dem jeweiligen Datenblatt zu entnehmen. Als weitere Signal-Anschlüsse können auch die konstanten Signale GND und VCC verwendet werden. Sinn ergeben diese Zuweisungen erst, wenn man durch Software zwischen beiden Zuständen wechselt. So kann man dann per Software die Capture/Compare-Funktionalität bedienen und zum Beispiel die Laufzeiten von Programmteilen messen.

Beispielhaft für die MSP430F16xx-Baureihe ist in Tabelle 4.6 die Zuordnung der Eingangsereignisse zu CCIxA und CCIxB für den Timer A (oben) und Timer B (unten) zu sehen. Die Eingänge können zum Beispiel ein externer Portpin (z.B. P1.1) sein, aber auch interne Signale, wie die ACLK oder der Ausgang des Comparator-A-Moduls (Kapitel 6).


SignalSignal-AnschlussTimer-Kanal
CCI0AP1.1 TACCR0
CCI0B P2.2 TACCR0
CCI1A P1.2 TACCR1
CCI1B CAOUT (intern)TACCR1
CCI2A P1.3 TACCR2
CCI2B ACLK (intern) TACCR2
     
     
SignalSignal-AnschlussTimer-Kanal
CCI0A+BP4.0 TBCCR0
CCI1A+B P4.1 TBCCR1
CCI2A+B P4.2 TBCCR2
CCI3A+B P4.3 TBCCR3
CCI4A+B P4.4 TBCCR4
CCI5A+B P4.5 TBCCR5
CCI6A P4.6 TBCCR6
CCI6B ACLK (intern) TBCCR6

Tabelle 4.6.: Signale und deren Signal-Anschlüsse für die Capture/Compare-Funktion

Wenn das CIE-Flag gesetzt ist, triggert der MSP430 im Capture- bzw. Compare-Fall einen Interrupt. Ist das Flag gelöscht, so wird kein Interrupt generiert. Der Interrupt kann mit Hilfe des CCIFG-Flags ausgewertet werden. Zur Dekodierung der Interrupts der Timer-Kanäle gibt es ein weiteres Register, das Timer-A- Interrupt-Vector (TAIV)-Register, Bild 4.8.


PIC

Bild 4.8.: Timer-A-Interrupt-Vector-Register (TAIV)


Mit den TAIV-Bits erfolgt eine Codierung der verschiedenen Interrupts der Capture/Compare-Kanäle gemäß Tabelle 4.7. Auf den ersten Blick mag die Position der TAIV-Bits komisch erscheinen, aber auf diese Art und Weise kann der Vektor einfach als Eingang einer Sprungtabelle genutzt werden. Eine entsprechende Beispielimplementierung für den Timer A ist in Listing 4.4 zu finden.


TAIVQuelle Interrupt Flag
00hkein Interrupt  
02h Cap/Comp. 1 TACCR1 CCIFG
04h Cap/Comp. 2 TACCR2 CCIFG
06h reserviert  
08h reserviert  
0Ah Timer overflowTAIFG
0Ch reserviert  
0Eh reserviert  
TBIV Quelle Interrupt Flag
00h kein Interrupt  
02h Cap/Comp. 1 TBCCR1 CCIFG
04h Cap/Comp. 2 TBCCR2 CCIFG
06h Cap/Comp. 3 TBCCR3 CCIFG
08h Cap/Comp. 4 TBCCR4 CCIFG
0Ah Cap/Comp. 5 TBCCR5 CCIFG
0Ch Cap/Comp. 6 TBCCR6 CCIFG
0Eh Timer overflowTBIFG

Tabelle 4.7.: Dekodierung der Interrupts der Capture/Compare-Kanäle für Timer A und Timer B

#pragma vector=TIMERA1_VECTOR 
__interrupt void TIMERA1(void){ 
{ 
switch( TAIV ) { 
    case 0:  break;  // tue nichts 
    case 2:  break;  // Capture/Compare 1 
    case 4:  break;  // Capture/Compare 2 
    case 6:  break;  // reserviert 
    case 8:  break;  // reserviert 
    case 10: break;  // Timer-Overflow 
    case 12: break;  // reserviert 
    case 14: break;  // reserviert 
    default: _never_executed(); 
   } 
}
Listing 4.4: Sprungtabelle zur Dekodierung des Timer A Interrupts, nach [Tex06b]

Am Anfang dieses Kapitels (siehe z.B. Listing 4.2) haben wir auch schon eine Interrupt-Funktion des Timers verwendet. Dort war es allerdings die Interrupt-Funktion des Capture/Compare-Kanals 0, die einen eigenen Interrupt-Vektor (TIMERA0_ISR respektive TMERB0_ISR) besitzt. Dem Interrupt-Vektor TIMERA1_ISR sind alle anderen Interrupt-Ereignisse des Timers zugeordnet. Durch die Struktur der Switch-Case-Anweisung in Listing 4.4 gelingt es dem Compiler, diese in einer einfachen Sprungtabelle abzubilden. Vor dem jeweiligen Break können entweder direkt die zum Interrupt gehörigen Befehle eingefügt oder, alternativ, eine eigene Funktion aufgerufen werden. Die Verwendung der compilereigenen _never_executed()-Funktion führt dazu, dass die Switch-Anweisung ohne Bereichsüberprüfung und nur für die genannten Werte implementiert wird.

Mit dem Timer A/B können an den Digitalports Signale, beispielsweise ein pulsweitenmoduliertes Signal, ausgegeben werden. Für diese Funktionalität stehen die Bits OUTMODx zur Verfügung. Die logischen Pegel an den Digitalports hängen dann je nach Einstellung der OUTMODx-Bits vom Zählregister TACCR0 und/oder TACCRx vom jeweiligen Timer-Kanal ab und sind dem Datenblatt zu entnehmen. Am Beispiel des pulsweitenmodulierten Signals wird diese Funktionalität noch einmal in Kapitel 7.4 verdeutlicht.

Capture-Funktion

Mit der Capture-Funktion kann man Ereignissen eine Zeitmarke zuordnen. Aus zwei Zeitmarken lassen sich Zeitdifferenzen bestimmen, ohne auf Software und damit auf Rechenleistung der CPU zurückgreifen zu müssen. Erkennt der MSP430 ein Ereignis über ein Signal (vgl. vorherige Abschnitte), so speichert er den Wert des Timers TAR im Register TACCRx des jeweiligen Timer-Kanals (zum Beispiel TACCR2 für Timer-Kanal 2).

Um die Capture-Funktion auszuwählen, muss das CAP-Flag im TACCTLx-Register gesetzt werden. Wenn das Flag gelöscht ist, arbeitet der Kanal mit der Compare-Funktion.

Das SCS-Flag im TACCTLx-Register sollte insbesondere bei schnellen Anwendungen gesetzt werden. Wird beispielsweise ein Ereignis erkannt, während der Timer TAR sich gerade im Inkrementierungsvorgang befindet, so kann es zu fehlerhaften Zeitstempeln im TACCRx-Register kommen. Das SCS-Flag synchronisiert den Timer mit der Capture-Funktion, um solche Probleme zu vermeiden.

Wenn zwei Ereignisse in einer kurzen Zeitabfolge hintereinander erkannt werden, kann es zu einem Überschreiben des TACCRx-Registers kommen, ohne dass dieses vorher gelesen werden konnte. Die Berechnung einer Zeitdifferenz würde damit unmöglich werden. Der MSP430 ist zwar nicht in der Lage dieses Problem zu beheben, allerdings bietet der Timer A mit Hilfe des COV-Flags im TACCTLx-Register die Möglichkeit einen Überlauf, also das Überschreiben des Wertes im TACCRx-Registers, zu erkennen. Das Flag kann im Programm ausgelesen und ausgewertet werden. Es muss aber manuell zurückgesetzt werden.

Im folgenden Beispiel schließen wir an den Pin P1.2, dem Eingangspin des Timer-A-Capture/Compare-Kanals 1, ein Rechtecksignal an. Wir wollen nun dessen Periodendauer mit Hilfe des Timers A bestimmen. Der Einfachheit halber gehen wir davon aus, dass eine Periode maximal 65535 Taktzyklen der SMCLK und minimal die Laufzeit der Interruptfunktion dauert.

#include  <msp430x16x.h> 
unsigned int periodendauer = 0; 
unsigned int countStart, countEnd; 
char error = 0; 
 
#pragma vector=TIMERA1_VECTOR 
__interrupt void TIMERA1 (void) { 
  long  hilf;               // Hilfsvariable 
 
  TACCTL1 &= ~(CCIFG);      // lösche Interrupt-Flag 
  countEnd = TACCR1;        // lese Ergebnis aus 
  if (TACCTL1 & COV) { 
    error++;                // Periodendauer zu kurz 
    TACCTL1 &= ~COV;        // Rücksetzen 
    } 
 
  if (countEnd >= countStart){ 
    periodendauer = countEnd - countStart; 
    } 
  else{                     // es gab einen Überlauf 
    periodendauer = (unsigned int)((long)(countEnd)+ 0xFFFF 
                                 - (long)(countStart)); 
    } 
  countStart = countEnd;    // bereite neue Messung vor 
} 
 
void main(void) { 
  WDTCTL = WDTPW + WDTHOLD; // Watchdog ausschalten 
  BCSCTL2 = 0;              // wähle DCO für MCLK und SMCLK 
  TACTL = TASSEL_2;         // Auswahl der SMCLK als Quelle 
  TACCTL1 = CM_1 + SCS + CAP + CCIE; 
                            // Auswahl Pin-Input, Capture-Mode 
                            // und Interrupt-Enable 
  TACTL |= MC_2;            // los gehts im Continuous-Mode 
  _enable_interrupts();     // Interrupts aktivieren 
  while (1) { }             // Endlosschleife 
}
Listing 4.5: Bestimmung der Periodendauer eines Rechtecksignals an P1.2 mit Hilfe des Timers A im Capture-Mode

Compare-Funktion

Die Compare-Funktionalität wird dafür verwendet, den Status des Timerausgangs abhängig von einem Vergleich mit einem Referenzregister (TACCRx-Register) zu schalten. Dies kann man dazu verwenden z.B. zeitgesteuerte Ausgangssignale (PWM, siehe Kapitel 7.4) oder verschieden schnelle Zeitscheiben eines Echtzeitbetriebssystems zu erzeugen.

Um die Compare-Funktion auszuwählen, muss im TACCTLx-Register (vgl. Bild 4.7) das CAP-Flag gelöscht werden. Nun wird das Timer-Zählregister TAR kontinuierlich, ohne weiteres Zutun des Prozessors, über eine Komparatorlogik mit dem TACCRx-Register verglichen. Entspricht der Wert im TACCRx-Register dem Zählerstand, so wird das interne EQUx-Signal gesetzt. Mit ihm werden verschiedene Output-Modi des Timers ausgelöst. Gleichzeitig wird auch das entsprechende CCIFG-Flag gesetzt und, bei gesetztem CIE-Flag, ein Interrupt generiert. Als weitere Reaktion (dies gilt nur für Timer A) übernimmt das SCCI-Flag den Status des Capture/Compare-Input-Blocks (CCI, siehe Seite §). Timer B hat stattdessen eine zusätzliche Auswertlogik, auf die wir aber erst in Kapitel 4.1.4 eingehen werden.

Jeder Capture/Compare-Kanal hat einen eigenen Ausgangspin. Dieser kann natürlich erst als Hardware-Ausgang genutzt werden, wenn die entsprechenden Port-Register (siehe Kapitel 2, PxSEL und PxDIR) richtig gesetzt sind. Eine komplexe Schaltlogik (”Output-Unit”) definiert das Ausgangsverhalten. Insgesamt sind acht verschiedene Modi verfügbar, die durch die OUTMODx-Bytes im TACCTLx-Register definiert werden. Tabelle 4.8 zeigt eine Übersicht.

Das folgende Listing zeigt beispielhaft, wie der Timer A zur Erzeugung eines periodischen Rechtecksignals mit einstellbarer Frequenz, das an P1.1 abzugreifen ist, verwendet werden kann.

#include  <msp430x16x.h> 
uint periodendauer = 12000;     // gewählte Periodendauer 
 
void main(void) { 
  WDTCTL = WDTPW + WDTHOLD;     // Watchdog ausschalten 
 
  P1DIR = BIT1;                 // konfiguriere Port 
  P1SEL = BIT1; 
 
  TACCR0 = periodendauer;       // Timer A konfigurieren 
  TACCTL0 = OUTMOD_4;           // Toggle-Modus 
  TACTL = TASSEL_2 + MC_1;      // wähle SMCLK und Up-Mode 
  _BIS_SR(LPM0);                // Low-Power-Modus 0 
                                // wir brauchen die CPU nicht 
}
Listing 4.6: Erzeugen eines Rechtecksignals mit dem Timer A


OUTMODxMode

Beschreibung

000Output

Der Ausgang entspricht direkt dem Wert des OUT-Bits im TACCTLx-Register

001 Set

Der Ausgang wird gesetzt, wenn der Timer bis zum Wert des TACCRx-Registers gezählt hat. Er behält seinen Zustand bis zu einem Reset des Timers.

010 Toggle/Reset

Der Ausgang wechselt seinen Zustand, wenn der TACCRx-Wert erreicht wird. Erreicht der Timer den TACCR0-Wert, erfolgt ein Reset.

011 Set/Reset

Der Ausgang wird gesetzt, wenn der TACCRx-Wert erreicht wird. Erreicht der Timer den TACCR0-Wert, erfolgt ein Reset.

100 Toggle

Der Ausgang wechselt seinen Zustand, wenn der TACCRx-Wert erreicht wird.

101 Reset

Der Ausgang wird gelöscht, wenn der Timer bis zum Wert des TACCRx-Registers gezählt hat. Er behält seinen Zustand bis zu einem Reset des Timers.

110 Toggle/Set

Der Ausgang wechselt seinen Zustand, wenn der TACCRx-Wert erreicht wird. Erreicht der Timer den TACCR0-Wert, wird der Ausgang gesetzt.

111 Reset/Set

Der Ausgang wird gelöscht, wenn der TACCRx-Wert erreicht wird. Erreicht der Timer den TACCR0-Wert, wird er gesetzt.


Tabelle 4.8.: Konfiguration des Timer-Ausgangsverhaltens

Besseres Verständnis für das Ausgangsverhalten des Timers bekommt man, wenn man sich das Verhalten der verschiedenen Modi bei den unterschiedlichen Timer-Betriebsarten (siehe Kapitel 4.1.2) betrachtet. Bild 4.9 zeigt das Ausgangsverhalten für den Continuous-Mode (links) und den Up-Mode (rechts) für jeweils zwei Perioden.


PIC PIC

Bild 4.9.: Ausgangsverhalten im Continuous-Mode (links) und Up-Mode (rechts) [Tex06c].


Das Ausgangsverhalten des Up/Down-Modes über zwei Perioden ist in Bild 4.10 exemplarisch skizziert.


PIC

Bild 4.10.: Ausgangsverhalten im Up/Down-Mode [Tex06c].


Eine weitere Anwendung des Timers, bei der die Mehrkanaligkeit voll ausgenutzt werden kann, ist die Ableitung verschiedener Systemtakte eines Echtzeitbetriebssystems aus einem Timerbaustein. Wenn man sich näher mit dem Thema Echtzeitbetriebssysteme und Datenkonsistenz beschäftigen möchte, sei z.B. das Buch von David E. Simon [Sim99] empfohlen. Im Folgenden sei hier nur ein kleines Beispiel dafür erklärt. In einem Echtzeitbetriebssystem sollen drei Tasks implementiert werden:

Die Tasks FAST und SLOW werden in Interrupt-Routinen implementiert. Da der Task FAST höchste Priorität hat, muss er alle weniger prioren Tasks unterbrechen können. Dies wirft für den Fall des SLOW-Tasks ein gewisses Problem auf. In allen bisherigen Interrupt-Beispielen wurden Interrupts exklusiv verarbeitet und konnten nicht unterbrochen werden. Soll aber nun ein Interrupt durch einen weiteren Interrupt unterbrochen werden, muss man in der entsprechenden Interrupt-Routine einfach nochmals das Global-Interrupt-Enable-Bit setzen. Eine Grundvoraussetzung für das korrekte Funktionieren ist natürlich, dass die Interrupt-Funktionen auch in der Summe der Zeit abgearbeitet werden können.

#define CHRYSTAL 32768         // Frequenz 
#define FAST_CNT CHRYSTAL/1024 // Counts für die FAST-Task 
#define SLOW_CNT CHRYSTAL      // Counts für die SLOW-Task 
#include  <msp430x16x.h> 
 
void main(void) 
{ 
  WDTCTL = WDTPW + WDTHOLD;  // Watchdog abschalten 
  BCSCTL1 = 0;               // ACLK = 32 kHz 
  P1DIR |= BIT1+BIT0;        // P1.0 und P1.1 als Ausgang 
 
  CCTL1 = CCIE;              // Interrupt für Kanal 1 
  CCTL2 = CCIE;              // Interrupt für Kanal 2 
  CCR1 = SLOW_CNT;           // Lege Task-Counter fest 
  CCR2 = FAST_CNT; 
  TACTL = TASSEL_1 + MC_2;   // ACLK und Continuos-Modus 
 
  _enable_interrupts();      // Interrupts aktivieren 
  while(1){ 
  IdleTask();                // hier ist der IDLE-Task 
  } 
} 
 
#pragma vector=TIMERA1_VECTOR 
__interrupt void TIMERA1 (void) { 
{ // Prüfe, ob Kanal 1 oder Kanal 2 den Interrupt 
  // aufgerufen haben 
  switch( TAIV ) 
  { 
  case  2:                   // SLOW-Task hier 
    { 
      P1OUT ^= BIT0;         // Toggle P1.0 
      CCR1 += SLOW_CNT;      // Zeitmarke für nächsten Aufruf 
      _enable_interrupts();  // Fast-Task-Interrupt möglich 
      SlowTask(); 
    } break; 
  case  4:                   // FAST-Task hier 
    { 
     P1OUT ^= BIT1;          // Toggle P1.1 
     CCR2 += FAST_CNT;       // Zeitmarke für nächsten Aufruf 
     FastTask(); 
    } break; 
  case 10: break;            // Timer-Overflow nicht verwendet 
 } 
}
Listing 4.7: Ein kleines Echtzeit-Tasksystem mit dem Timer A

4.1.4  Timer-B-Besonderheiten

In allen wesentlichen Funktionen gleichen sich Timer A und Timer B, einige wenige Unterschiede gibt es jedoch, auf die in diesem Kapitel eingegangen werden soll: Das Bild 4.11 zeigt das Timer-B-Kontrollregister. Die Bits 11-14, die beim Timer A noch unbenutzt waren, haben jetzt eine Funktion.


PIC

Bild 4.11.: Bitbelegung des Timer-B-Control-Registers TBCTL.


Einstellbare Registerlänge

Die Länge des Timers B ist auf 8, 10, 12 oder 16 Bit einstellbar. Dies erfolgt mit den CNTLx-Bits im Steuerregister. Tabelle 4.9 zeigt die entsprechende Zuordnung.


CNTL1CNTL0Länge Registermaximaler Wert
0 0 16-bit TBR(max) = 0FFFFh
0 1 12-bit TBR(max) = 0FFFh
1 0 10-bit TBR(max) = 03FFh
1 1 8-bit TBR(max) = 0FFh

Tabelle 4.9.: Einstellbare Registerlänge für Timer B

Doppelt gepufferte Register

Die Timer-B-Capture/Compare-Register sind alle doppelt gepuffert. Somit können Schreib- und Lesekonflikte vermieden werden.

Gruppierung von Capture/Compare-Kanälen

Mit den TBCLGRP-Bits kann man mehrere Capture/Compare-Register des Timers zu gleicher Funktion zusammenfassen. Damit lassen sich z.B. PWM-Ausgaben mehrerer Kanäle exakt synchronisieren, siehe Tabelle 4.10.


TBCL1TBCL0

Beschreibung

00

Jedes TBCLx schaltet unabhängig

0 1

TBCL1+TBCL2 (TBCCR1 CLLDx Bits )

TBCL3+TBCL4 (TBCCR3 CLLDx Bits )

TBCL5+TBCL6 (TBCCR5 CLLDx Bits )

TBCL0 unabhängig

1 0

TBCL1+TBCL2+TBCL3 (TBCCR1 CLLDx Bits )

TBCL4+TBCL5+TBCL6 (TBCCR4 CLLDx Bits )

TBCL0 unabhängig

1 1

TBCL0+TBCL1+TBCL2+TBCL3+TBCL4+TBCL5+TBCL6

(TBCCR1 CLLDx Bits bestimmen Verhalten)


Tabelle 4.10.: Gruppierung der Timer-B-Capture/Compare-Kanäle

Hochohmig schaltbare Ausgänge

Mit dem TBOUTH-Pin, der z.B. beim MSP430F1612 dem Pin 5.7 entspricht, können die Ausgangspins des Timers B in einen hochohmigen Zustand gesteuert werden. Hierzu muss ein HIGH-Signal an den Pin angelegt werden. Natürlich muss vorher in der Konfiguration des Pins 5.7 die entsprechende Funktion ausgewählt sein.

SCCI Bit-Funktion

Die SCCI-Bit-Funktion existiert nicht. Somit kann das CCI-Capture/Compare-Input-Signal nicht mehr intern synchronisiert ausgelesen werden. Dies ist aber Dank der schon erwähnten doppelt-gepufferten Register auch nicht mehr nötig