Timer

XMega Blockdiagramm
Der XMega128 besitzt acht Timer, wovon vier Timer jeweils vier Output und Input Compare Channels besitzen und die anderen vier Timer jeweils zwei.
Die Ports C, D, E und F besitzen jeweils zwei Timer, die mit TCC, TCD, TCE und TCF angesprochen werden. Bevor die Timer verwendet werden können, müssen alle dafür notwendigen Interrupts aktiviert werden.

-> Timer als Interrupt:

Um mit einem Timer jede Sekunde einen Interrupt auszulösen muss der Timer erstmal dementsprechend konfiguriert werden.
In meinem Beispiel verwende ich den Timer C0 und als erstes wird in das Control A-Register von Timer C0 der Prescaler festgelegt. Um einen Prescaler von 1024 zu nutzen schreibt man folgendes:

Anschließend setzt man den Timer in den Normal Mode. Hierfür wird das Control B-Register wie folgt beschrieben:

Nun muss noch die Priorität des Interrupts festgelegt werden. Dies wird im Interrupt Enable-Register A  gemacht. Um die Interrupts auf die höchste Priorität zu setzen schreib man folgendes:

Da der Timer nun fertig konfiguriert ist, muss nur noch der Timer-Topwert in das Period-Register geschrieben werden. Der Timer-Topwert (TTW) legt, im Normalmode fest bis wohin der Timer zählt und wann der Interrupt ausgelöst wird.
Wenn der Timer nun jede Sekunde einen Interrupt auslösen soll muss dieser Wert erst berechnet werden. Hierfür ist die Taktfrequenz des Controllers wichtig.
Der Wert berechnet sich wie folgt:

Latex formula

Das heißt bei 32MHz Takt und einem Prescaler von 1024 muss das Periodenregister mit 34285 bzw. 0x7A12 geladen werden:

Nun ist der Timer fertig eingestellt und es muss nur noch die ISR für den Timer programmiert werden. Die ISR wird so programmiert:

Wenn das Programm nun auf den Controller übertragen wird, wird der Pin C.1 jede Sekunde getoggled.

-> Timer zum erzeugen einer PWM:

Mit den Timern kann außerdem noch (wie bei allen anderen Atmel Controllern) eine PWM generiert werden.
Mit diesen Codezeilen wird Timer D0 so eingestellt das er eine PWM generiert:

Mit der ersten Zeile wird die Frequenz für die PWM eingestellt. Die Berechnung erfolgt auf die selbe Weise wie oben beschrieben.
Mit der Zeile

wird der Prescaler auf 4 eingestellt und die nächste Zeile legt die Eigenschaften der PWM fest. In meinem Beispiel ist sie auf Single Slope eingestellt.
Mit der Zeile

lege ich fest, dass die PWM an Pin28(OC0D) ausgegeben werden soll. Dies wird durch die vier höchsten Bits im Register eingestellt.
Soll die PWM nun an OC0A (Pin 25) ausgegeben werden, muss anstelle des 8. Bits das 5. Bit auf High gesetzt werden.
Die Codezeile dafür lautet also:

Genauere Infos findet man hier auf Seite 165:

-> XMega A Manual

Nun ist der Timer fertig konfiguriert und kann verwendet werden. Um ihn zu verwenden muss nur noch das Compare-Register beschrieben werden.
Dies wird so gemacht:

Nun wird der Wert 100 in das Compare-Register geschrieben. Das Compare-Register ist 16-Bit groß (kein Kunststück bei einem 16-Bit Timer ;) ) und dem entsprechend können Werte von 0-65.535 verwendet werden.

Bei 65.535 bzw. 0xFFFE sieht die PWM dann so aus:

pwmmax

Bei einem Dezimalwert von 32.635 bzw. 0x7F17 dann so:

pwmhalb

Wie man sieht ist es nicht schwer eine PWM mit einem XMega zu generieren ;)

 

-> Zurück zum XMega Tutorial

14 thoughts on “Timer
  1. Erstmal, super tutorial. Sehr einfach und gut erklärt.
    Leider kriege ich den TCC0-Interrupt nicht zum laufen, obwohl ich alle codezeilen wie in deinem Tutorial eingestellt habe. Ansonsten funktioniert jedoch alles auf meinem Board (Habe einen LCD angesteuert und Taster / LEDs funktionieren auch, jedoch nicht mit interrupt).

    Kannst du mir bitte einmal dein c-file senden, damit ich schauen kann ob ich einen Überlegungsfehler gemacht habe?

    Danke und Gruss

    • Habe den Fehler schon herausgefunden:
      Natürlich habe ich vergessen, neben den globalen Interrupts auch die einzelnen Interrupt-levels freizuschalten:

      PMIC.CTRL |= PMIC_LOLVLEN_bm | PMIC_MEDLVLEN_bm | PMIC_HILVLEN_bm;

      Nun funktioniert der interrupt einwandfrei. Vielen Dank, dein Tutorial hat mir wirklich sehr geholfen.

      Falls du Hilfe benötigst, um weitere Tutorials zu schreiben (habe gesehen, dass die Seite noch nicht vollständig ist), gehe ich dir gerne zur Hand.

      • Hey,

        schön das du den Fehler gefunden hast :)
        Ja die Hilfe nehme ich gerne an, wenn ich wieder am Tutorial weiter arbeite.
        Im Moment mache ich ein paar andere Dinge (wenn ich Zeit habe).
        Aber ich hoffe mal das ich ab nächsten Monat wenn ich frei habe und keine Schule habe bisl was dazu schreiben kann.

  2. Hallo! Ich habe da mal eine Frage/Anregung:
    Der Wert, der ins PER-REgister geschrieben wird ist doch falsch berechnet, oder? Denn da wir das DIR Bit im Control Register F Clear/Set auf dem Initial Value belassen haben, zählt der Counter ganz normal hoch. Normal bedeutet laut Control Register B aber Update bei TOP, also fange von Null an zu zählen, bis zum TOP Wert, der im PER Register steht. Danach wird auf Null resettet. Demnach muss der Wert für das oben genannte Beispiel doch einfach nur Takt/Prescaler (32M/1024 = 31250 = 0x7A12) lauten?
    In diesem speziellen Beispiel liegen falsche und richtige Werte relativ nah besammen, daher ist der Fehler nicht so leicht zu sehen.

    Ich fange gerade erst an überhaupt auf µCs zu programmieren, also falls ich was falsches geschrieben habe bitte korrigieren. Tolles Tutorial übrigens! :)

    • Hey,

      nein der Timer wird auf einen bestimmten Wert gesetzt und von diesem Wert fängt er dann an zu zählen, sprich normal zählt er von 0 bis zu einem Überlauf aber wenn du ihn mit 0x01 lädst, fängt er von 1 an bis zum Überlauf zu zählen :).
      Ich hoffe das beantwortet deine Frage :)

      Gruß
      Daniel

      • D.h. du lädst ihn in deinem Beispiel mit 0x85ED und er zählt dann bis zum Überlauf (65535), richtig? Demnach müsste für ein halbsekündiges Blinken der Überlauf doppelt so häufig erfolgen. Ergo muss ein größerer Wert geladen werden:
        Takt_neu = (Taktfrequenz/Prescaler)/2
        Timer_Topwert = 65535 – Takt_neu
        Im Beispiel bedeutet das dann statt 34285 nun 49910 (C2F6). Wenn ich das so bei mir ausprobiere toggelt der PIN bei mir aber langsamer statt schneller. (die restlichen Codezeilen sind bei mir identisch zu deinen, außer dass ich an den PIN eine LED gehangen habe, um das Toggeln sichtbar zu machen)
        Also entweder hab ich irgendwo einen Gedankenfehler, oder aber die Funktionsweise ist anders als von dir beschrieben. ;)
        Ps: Lade ich mit 15625 (3D09) bekomme ich ein präzises halbsekündiges Toggeln. Das kann man mit einer LED und einer Quarzuhr sehr gut beobachten, weil die LED im Sekundentakt blinkt.

        • Hey,

          kann natürlich auch sein das ich einen Denkfehler habe :)
          Ich probiere es die Tage nochmal selber aus. Ich habe das Beispiel auf Grundlage eines 1 Sekunden Impulses geschrieben, sprich ich habe jetzt nicht geprüft ob er bis zum vorgeladenen Wert zählt oder vom vorgeladenen Wert zum Überlauf (ist beides gleich hoch bei 1 Sekunde ^.^).
          Aber dank dir vielmals für den Hinweis!
          Ich überprüfe es und korrigiere es. Kann gut sein das es bei den XMegas anders ist als bei den Atmel AVRs. Weil ich meine bei den Atmel AVRs wäre es so das er vom vorgeladenen Wert bis zum Überlauf zählt.
          Sicher bin ich mir hierbei aber auch nicht ganz :)

          Edit:
          Ich habe mal im Datenblatt auf Seite 166 (http://www.atmel.com/Images/doc8077.pdf) nachgeschaut.
          Im Normalmode, sprich so wie der Timer im Interruptbetrieb verwendet wird, ist der Top-Wert = dem Per-Register (siehst du an der Tabelle).
          Du kannst außerdem noch sagen ob er von 0 bis zu dem Timer_Topwert zählen soll oder vom Timer_Topwert bis hin zu 0. Dies kannst du mit dem „DIR“ Bit im „CTRLF-Register“ bestimmen.
          Da habe ich nicht drauf geachtet :( aber nochmals danke für den Hinweis!
          Ist mir persönlich viel wert wenn die Leute mich auf Fehler aufmerksam machen, da ich ja möchte das auch andere Leute von den Sachen profitieren :)

          • Im Datenblatt schau ich auch immer nach. ^^ Auf Seite 155 unter „Normal Mode“ kann man auch nachlesen, dass
            „When TOP is reached when up-counting, the counter will be set to zero when the next clock is given.“
            Zusammen mit der Erkenntnis dass wir den TOP Wert im PER Register eintragen (Seite 172 „PER contains the 16-bit TOP value in the Timer/Counter.“) und im Normal Mode arbeiten (Seite 166), bleibt für mich nur die Schlussforgerung, dass wir von Null anfangen zu zählen, bis zum TOP Wert und danach auf Null resettet wird (durch das sogenannte „Update“).

            Ich gebe aber zu dass ich auch 10 mal nachlesen musste weil ich mir die zeitlichen Abweichungen nicht erklären konnte. :D

          • Ja das mit den Datenblättern ist immer bisl tricky :)
            Ich habe den Artikel auch nur auf Grundlage meiner bisherigen Erfahrungen geschrieben, ohne das ich ins Datenblatt geschaut habe.
            Das es beim XMega offenbar anders ist als bei den AVRs ist doof gelaufen aber gut :D

  3. Überlaufzeit=>∆T=(65535-N0)X T0。 T0 ist nach dem Prescaler=31250Hz=0.00032s
    N0 ist Ladenwert in PER register . bei ∆T = 1s ergibt No=34285 als0`= 0x85ED

  4. wie sind die Bezielungen vonPWM mit register CCxL and CCxH. Wenn ich 50Kz PWM hábe möchte .wie größe wert 0x???? soll in CCD BZW CCDL und CCDH laden

  5. Das Tutorial ist echt super. Und ich hätt da auch ne Frage. Und zwar wird der PIN1 von PORTD alle 80 us getoggelt, das mir auch auf dem Oszilloskop so angezeigt wird. Ich benötige aber viel schnellere toggel Zeiten im ns-bereich, deswegen stell ich meine prescaler für meinen timer auf 1 und ändere nur die nachfolgende zeile in meinem code

    TCE0.CTRLA = TC_CLKSEL_DIV1_gc;

    und rein theoretisch müsse mein port dann alle 312ns (1/32000000*10) getoggelt werden, aber das tut er nicht, laut Oszilloskop er nur ca. alle 1,5us. Aber ich versteh nicht weshalb und ich brauch unbedingt die ns. Kann mir vll. jemand einen tipp geben?
    Code:

    void configure_CLOCK(void)
    {
    //Oszillator auf 32Mhz stellen
    OSC.CTRL |= OSC_RC32MEN_bm;

    // Warten bis der Oszillator bereit ist
    while(!(OSC.STATUS & OSC_RC32MRDY_bm));

    //Schützt I/O Register, Interrupts werden ignoriert
    CCP = CCP_IOREG_gc;

    //aktiviert den internen Oszillator
    CLK.CTRL = CLK_SCLKSEL_RC32M_gc;
    }

    int main (void)
    {
    configure_CLOCK();

    PMIC.CTRL |= PMIC_HILVLEN_bm;
    sei();

    TCE0.CNT = 0x00;
    TCE0.PER = 10; // Interrupt alle 80us

    TCE0.INTCTRLA = TC_OVFINTLVL_HI_gc;

    TCE0.CTRLA = TC_CLKSEL_DIV256_gc;

    while(1){};
    return 0;
    }

    ISR(TCE0_OVF_vect)
    { PORTD.DIRSET= (1<<PIN1);
    PORTD.OUTTGL =(1<<PIN1);
    }

  6. Hallo,
    ich lese gerne auf deiner Seite doch nun ist mir glaube ich ein Fehler aufgefallen.

    Du schreibst …….“dass die PWM an Pin28(OC0D) ausgegeben werden soll.“
    im Datasheet ist PIN28 aber OC0A.

    Gruß

Schreibe einen Kommentar

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

Time limit is exhausted. Please reload CAPTCHA.