MezData-Logo

Blinklicht mit Timer und Interrupt

Prinzip des Timer-Interrupts

Präsentation

Problem: Ein Z-Eye läuft mit 1 MHz Systemtakt und soll LED0 mit 2 Hz blinken lassen.

Ein Timer ist ein Zähler der mit einer bestimmten (möglichst genauen) Frequenz betrieben wird und dadurch eine Zeitspanne erzeugen kann. Bsp:

Taktzahl = Zeitspanne * Frequenz

Nach 250000 Takten sind 250 ms Zeit vergangen.

Taktzahl = Zeitspanne / Periodendauer

Bsp: 250000 = 250ms/1µs = 250 * (E-3/E-6) = 250 E3

 

Präsentation mit Timer-Overflow-Interrupt

(Mit den Pfeiltasten steuern)

8 Bit Timer0 Overflow-Interrup

Blockschaltbild TimerInterruptDie Interrupt Service Routine (ISR) soll zu bestimmten Zeiten aufgerufen werden, hier alle 250ms. Ein Overflow-Interrupt wird beim Überlauf des Zählers vom höchsten Wert -hier 0xFF nach 0x00 ausgelöst.

Interruptzeit = (Werteanzahl-Vorsteller)*Vorteiler/Systemtakt

Zahnraddarstellung

Zahnradmodell: Der Systemtakt wird wie bei einem Getriebe "heruntertransformiert". Hier ein zweistufiges Getriebe mit den Zahnrädern Vorteiler und Vorsteller.

Ziel ist es, eine möglichst genaue Teilung zu erhalten, wobei die Stufen nur begrenzte Einstellmöglichkeiten bieten.

Der Werteanzahl ist der grösste Wert des Zählers, bei 8Bit sind dies 256.

Durch Vorstellen des Zählers können kleinere Zähl-Werte bis zum Overflow-Interrupt erreicht werden.

Sei Systemtakt clkI/O hier 1Mhz

  1. Taktzahl: (Systemtakte bis ISR-Aufruf): Taktzahl = clkI/O*Sollzeit = 1Mhz*250ms = 250.000
  2. Vorteiler: Taktzahl/Werteanzahl = 250.000/256 = 976,6 gewählt wird der nächst höhere Wert aus {1,8,64,256,1024} also 1024
  3. Vorsteller: Werteanzahl - Taktzahl/Vorteiler = 256 - 250.000/1024 = 11,9 gewählt 12
  4. Probe der Genauigkeit: (Werteanzahl-Vorsteller)*Vorteiler/clkI/O =( 256-12)*1024/1MHz = 249,9 ms

Hier ist nur der Vorteilerwert 1024 möglich, kleinere Werte würden mehr Zählimpulse erfordern als mit 8Bit zählbar wären. Bei einer Vorstellung des Zählers mit dem Wert 12 wird nach 256-12=244 Takten der Overflow-Interrupt ausgelöst.

Initialisierung

TCCR0B |= 5; // TimerCounterControllRegister0B 1: /1; 2: /8; 3: /64; 4: /256; 5: /1024
TIMSK |= 1<<TOIE0; // TimerOverflowInterruptEnable0

Quellcode für Version mit 8Bit Timer0 Overflow-Interrupt

TImer0

Quellcode [blinker_overflowinterrupt.c]
#include <avr/io.h>
 
int main(){          // Hauptprogramm
  PORTB = 0xe0;
  DDRB = 0xff;
  TCCR0B = 5;        // Systemtakt durch 1024
  TIMSK |= 1<<TOIE0; // TimerOverflowInterruptEnable0
  sei();             // globale Interruptfreigabe
  while(1);          // Endlosschleife
}
 
ISR(TIMER0_OVF_vect){ // Interrupt Service Routine
  TCNT0 = 12;         // wieder vorstellen
  PORTB ^= 1;         // Ausgang PB0 invertieren
}

Grössere Untersetzungen realisieren

Die LED soll bei 1MHz Systemtakt nun mit 1Hz blinken, somit muss alle 500ms ihr Zustand invertiert werden.

8Bit Timer0 Overflow-Interupt und Zähler in ISR

ZahnraddarstellungZahnradmodell: Das Transformationsverhältnis ist dabei 500.000 : 1.
Mit den Zahnrädern Vorteiler und Vorsteller lassen sich maximal
1024*256 = 262.144 : 1 erreichen.

Mittels eines weiteren Zahnrads in der ISR, einem Zähler der beim Aufruf inkrementiert wird und bei Erreichen eines bestimmten Wertes erst die Aktion auslöst, lassen sich erheblich längere Zeiten realisierten.

Das Zahnrad in der ISR wird folgend als ISR-Teilfaktor bezeichnet.

 

  1. Taktzahl: (Systemtakte bis ISR-Aufruf): Taktzahl = clkI/O*Sollzeit = 1Mhz*500ms = 500.000
  2. Vorteiler: Taktzahl/Werteanzahl = 500.000/256 = 1953! Der Wert für den Vorteiler ist zu gross! Gewählt wird nun der höchste Wert aus {1,8,64,256,1024} also 1024. In der ISR muss nun weiter gezählt werden:
  3. ISRTeilfaktor: Taktzahl/Werteanzahl/Vorteiler = 500.000/256/1024 = 1,9 Aufrunden: 2 Beim 2. Mal erst Aktion.
  4. Vorsteller berechnen: Werteanzahl - Taktzahl/Vorteiler/ISRTeilfaktor = 256 - 500.000/1024/2 = 11,9 gewählt 12
  5. Probe der Genauigkeit: (Werteanzahl-Vorsteller)*Vorteiler/clkI/Oi*ISRTeilfaktor =( 256-12)*1024/1MHz*2 = 499,7 ms

In der ISR wird zusätzlich hoch gezählt, dazu dient eine lokale statische (persistente, also nicht nach dem Ende der ISR wieder gelöschte) Variable teiler, die beim ersten Aufruf der ISR mit 0 initialisiert wird: teiler merkt sich den Wert bis zum nächsten Mal.

Quellcode [blinker-1s_over8bit.c]
#include <avr/io.h>
 
int main(){          // Hauptprogramm
  PORTB = 0xe0;      // LED aus
  DDRB = 0xff;       // PB als Ausgang
  TCCR0B = 5;        // Systemtakt durch 1024
  TIMSK |= 1<<TOIE0; // TimerOverflowInterruptEnable0
  sei();             // globale Interruptfreigabe
  while(1);          // Endlosschleife
}
 
ISR(TIMER0_OVF_vect){ // Interrupt Service Routine
  static unsigned char teiler=0; // persistente lokale Variable mit 0 initialisiert
  TCNT0 = 12;        // wieder vorstellen
  if(teiler<1){      // bis (Teilfaktor-2) hoch zaehlen, hier bei 0
    teiler++;
  }
  else{              // bei Teilfaktor-1 Aktion ausfuehren, hier bei 1
    teiler=0;
    PINB = 1;        // Ausgang PB0 invertieren (PORTB ^= 1)
  }
}

16Bit Timer1Overflow-Interrupt

Neben dem 8Bit Timer0 gibt es noch einen 16Bit Timer1, dessen Maximalwert ist 65536 und ermöglicht dadurch längere und genauere Zeiten.

  1. Taktzahl: (Systemtakte bis ISR-Aufruf): Taktzahl = clkI/O*Sollzeit = 1Mhz*500ms = 500.000
  2. Vorteiler: Taktzahl/Werteanzahl = 500.000/65536 = 7,6 gewählt wird der nächst höhere Wert aus {1,8,64,256,1024} also 8
  3. Vorsteller berechnen: Werteanzahl - Taktzahl/Vorteiler = 65536 - 500.000/8 = 3036
  4. Probe der Genauigkeit: (Werteanzahl-Vorsteller)*Vorteiler/clkI/O =(65536-3036)*8/1MHz = 500 ms

Timer1

Quellcode [blinker-1s_over16bit.c]
#include <avr/io.h>
 
int main(){          // Hauptprogramm
  PORTB = 0xe0;
  DDRB = 0xff;
  TCCR1B = 2;        // Systemtakt durch 8
  TIMSK |= 1<<TOIE1; // TimerOverflowInterruptEnable0
  sei();             // globale Interruptfreigabe
  while(1);          // Endlosschleife
}
 
ISR(TIMER1_OVF_vect){ // Interrupt Service Routine
  TCNT1 = 3036;       // wieder vorstellen
  PORTB ^= 1;         // Ausgang PB0 invertieren
}

8 Bit Timer / Counter0 Clear Timer on Compare Match (CTC) Mode (Auto Reload)

CTC InterruptIm CTC Modus wird der Wert des TCNT0 mit dem Register OCR0A verglichen. Bei Gleichheit wird der Zähler wieder auf 0 gesetzt und es kann dabei ein OC1A Interrupt ausgelöst werden.

Systemtakt clkI/O hier 1Mhz

Interruptzeit = (1+OCR0A)*Vorteiler/Systemtakt

 

  1. Taktzahl: (Systemtakte bis ISR-Aufruf): Taktzahl = clkI/O*Sollzeit = 1Mhz*250ms = 250.000
  2. Vorteiler: Taktzahl/Werteanzahl = 250.000/256 = 976,6 gewählt wird der nächst höhere Wert aus {1,8,64,256,1024} also 1024
  3. OCR0A: Taktzahl/Vorteiler-1 = 250.000/1024 -1 = 243,1 gewählt 243
  4. Probe der Genauigkeit: (1+OCR0A)*Vorteiler/clkI/O =( 1+243)*1024/1MHz = 249,9 ms

Initalisierung

 TCCR0A |= 1<<WGM01; // Timer im CTC-Mode
 TCCR0B |= 5; // Vorteiler 1: /1; 2: /8; 3: /64; 4: /256; 5: /1024
 TIMSK |= 1<<OCIE0A; // Timer/Counter0 Output Compare Match A Interrupt Enable
 OCR0A = 243; // Wert bei dem der Timer wieder auf 0 gesetzt wird

Quellcode [blinker_ctc.c]
#include <avr/io.h>
 
int main(){          // Hauptprogramm
  PORTB = 0xe0;
  DDRB = 0xff;
  TCCR0A |= 1<<WGM01; // Timer im CTC-Mode
  TCCR0B |= 5; // Vorteiler 1: /1; 2: /8; 3: /64; 4: /256; 5: /1024
  TIMSK |= 1<<OCIE0A; // Timer/Counter0 Output Compare Match A Interrupt Enable
  OCR0A = 243; // Wert bei dem der Timer wieder auf 0 gesetzt wird
  sei();             // globale Interruptfreigabe
  while(1);          // Endlosschleife
}
 
ISR(TIMER0_COMPA_vect){ // Interrupt Service Routine
  PORTB ^= 1;           // Ausgang PB0 invertieren (PINB = 1)
}

16Bit Timer1 Clear Timer on Compare Match (CTC) Mode (Auto Reload)

  1. Taktzahl: (Systemtakte bis ISR-Aufruf): Taktzahl = clkI/O*Sollzeit = 1Mhz*500ms = 500.000
  2. Vorteiler: Taktzahl/Werteanzahl = 500.000/65536 = 7,6 gewählt wird der nächst höhere Wert aus {1,8,64,256,1024} also 8
  3. OCR1A: Taktzahl/Vorteiler-1 = 500.000/8-1= 62499
  4. Probe der Genauigkeit: (1+OCR1A)*Vorteiler/clkI/O =(1+62499)*8/1MHz = 500 ms

Initalisierung

 TCCR1B |= 1<<WGM12; // Timer im CTC-Mode
 TCCR1B |= 2; // Vorteiler 1: /1; 2: /8; 3: /64; 4: /256; 5: /1024
 TIMSK |= 1<<OCIE1A; // Timer/Counter0 Output Compare Match A Interrupt Enable
 OCR1A = 62499; // Wert bei dem der Timer wieder auf 0 gesetzt wird

Quellcode [blinker-1s_ctc16bit.c]
#include <avr/io.h>
 
int main(){          // Hauptprogramm
  PORTB = 0xe0;
  DDRB = 0xff;
  TCCR1B |= 1<<WGM12;// Timer im CTC-Mode mit OCR1A als Top
  TCCR1B |= 2;        // Systemtakt durch 8
  TIMSK |= 1<<OCIE1A; // Timer/Counter1 Output Compare Match A Interrupt Enable
  OCR1A = 62499;      // Vergleichswert Timer wieder auf 0
  sei();             // globale Interruptfreigabe
  while(1);          // Endlosschleife
}
 
ISR(TIMER1_COMPA_vect){ // Interrupt Service Routine
  PORTB ^= 1;           // Ausgang PB0 invertieren (PINB = 1)
}

 

Übungsaufgaben

Blinken 1Hz an PB0 mit 4 MHz Systemtakt, 8Bit-Timer0, Overflow-Interrupt

Lösung anzeigen..

Blinken 1Hz an PB0 mit 8 MHz Systemtakt, 16Bit-Timer1, Overflow-Interrupt

Lösung anzeigen..

Interrupt alle 30ms, 1 MHz Systemtakt, 8Bit-Timer0, Overflow-Interrupt

Lösung anzeigen..

Interrupt alle 250µs, 1 MHz Systemtakt, 8Bit-Timer0, Overflow-Interrupt

Lösung anzeigen..

Interrupt alle 50ms, 4 MHz Systemtakt, 8Bit-Timer0, Overflow-Interrupt

Lösung anzeigen..

Interrupt alle 100ms, 4 MHz Systemtakt, 8Bit-Timer0, Overflow-Interrupt

Lösung anzeigen..

Interrupt alle 30ms, 1 MHz Systemtakt, 8Bit-Timer0, CTC-Interrupt

Lösung anzeigen..

Interrupt alle 1000ms, 1 MHz Systemtakt, 16Bit-Timer1, CTC-Interrupt

Lösung anzeigen..

Interrupt alle 250µs, 1 MHz Systemtakt, 8Bit-Timer0, CTC-Interrupt

Lösung anzeigen..