Home ==> Mikrobeginner ==> 6. LED mit Interrupt
ATtiny13

Lektion 6: Eine LED blinkt mit dem Interrupt


Mit dieser Lektion treten wir schon in die Interrupt-Programmierung ein und beenden die lineare Programmierung. Nur sehr einfache Aufgaben lassen sich ohne Interrupts erledigen. Am häufigsten sind mehrere Aufgaben (scheinbar) gleichzeitig zu erledigen, was nur mit Interrupts vernünftig geht. Und genau das soll hier als grundlegende Programmiertechnik erlernt werden.

6.0 Übersicht

  1. Einführung in die Interrupt-Programmierung
  2. Hardware, Bauteile, Aufbau
  3. Timer mit Overflow-Interrupt
  4. Timer mit CTC-Interrupt

6.1 Einführung in die Interrupt-Programmierung

6.1.1 Interrupts

Der Vorteil der Interrupt-Programmierung ist, dass mehrere zeitkritische Vorgänge gleichzeitig ablaufen und trotzdem alle eintretenden Ereignisse korrekt ablaufen und bearbeitet werden. Spätestens dies ist das Ende aller Warte- und Verzögerungsschleifen.

Interrupts sind automatisierte Unterbrechungen des Programms. Läuft beispielsweise der Timer über (von FF auf 00), können wir mittels eines Bits im Timer veranlassen, dass bei diesem Ereignis ein Interrupt ausgelöst wird. Ist das Bit gesetzt, dann bewirkt jeder Überlauf Was jetzt weiter passiert, ist Sache der selbstgeschriebenen Interrupt-Service-Routine. Dort wird erledigt, was beim Eintreten des Interrupts zu erledigen ist. Nach Erledigung kehrt der Programmablauf wieder dahin zurück, wo er zum Zeitpunkt der Unterbrechung war.

Bei diesem Konzept müssen zwei Bedingungen sichergestellt werden. Erstens darf während der Ausführung der Interrupt-Service-Routine nicht irgend ein anderer Interrupt dessen Ausfürung unterbrechen (verschachtelter Interrupt). Daraus resultiert zweitens, dass bei gleichzeitig eintretenden Unterbrechungen eine Reihenfolge festgelegt werden muss, wer zuerst mit der Bearbeitung dran ist (Priorität).

I-Bit Die erste Bedingung wird durch ein Flaggenbit erfüllt, das im Statusregister SREG, einem Port, lokalisiert ist. Es heißt "I" und liegt im Bit 7 des Ports. Mit einer Null in diesem Bit wird kein Interrupt ausgeführt, welche Hardware auch immer nach Unterbrechung ruft. Es muss daher nach dem Initiieren der Hardware auf Eins gesetzt werden, damit überhaupt ein Interrupt ernstgenommen wird. Das Eins-Setzen dieses Bits geht mit der Instruktion "SEI" (SEt I), sein Löschen mit "CLI" (CLear I).

Genau dieses Bit wird automatisch auf Null gesetzt, sobald der entsprechende Interrupt in Bearbeitung geht (und der Programmablauf verzweigt). Ist die Service-Routine beendet, muss sie dieses Bit wieder auf Eins setzen, um weitere anstehende oder neu eintretende Unterbrechungen wieder zuzulassen. Ohne dieses Setzen würden gar keine weiteren Unterbrechungen mehr zugelassen, die einsamen Rufer nach Unterbrechung würden verhungern.

Damit wird auch klar, weshalb im obigen Ablauf jeder Unterbrecher ein Bit setzen muss, wenn der Fall eintritt: es kann durchaus sein, dass er erst später dran ist, wenn andere Interrupts noch in Bearbeitung sind. Und so lange signalisiert dieses Bit eben Unterbrechungsbedarf. Damit ist auch die Priorisierung von Interrupts klar: sie ist notwendig um zu bestimmen, wer als erstes dran ist, wenn mehrere dieser Bits gleichzeitig Unterbrechungsbedarf signalisieren. Die Priorität ist fest (siehe unten) und nicht beeinflussbar. Je höher der Interrupt in der Vektorenliste steht (siehe weiter unten), desto höher die Priorität.

Weil jede Interrupt-Service-Routine den weiteren Fortgang der Bearbeitung erst mal blockiert, gilt als wichtigste Regel, diese Service-Routine so kurz und knapp wie möglich zu halten. Keine Zeit hier, um langwierige Operationen oder Verzögerungsschleifen unterzubringen. Optimalerweise beschränkt sich diese Routine auf 10 bis 15 Instruktionen. Langwierige Bearbeitungen werden nach außerhalb dieser Routine zu verlegen sein. Das bedeutet, dass die Interrupt-Service-Routine mit "außerhalb" über Flaggen kommunizieren muss. Mehr dazu später.

6.1.2 Stackspeicher

Um die Bearbeitung von Interrupts zu ermöglichen, braucht es einen Speicher, wo die Unterbrechungsadresse zeitweise abgelegt werden kann. Dazu verwendet der AVR den sogenannten Stack (Stapel) im statischen RAM. Der ATtiny13 hat 64 Bytes SRAM, reichlich um die zwei Byte lange Adresse abzulegen. Der Stapel wird immer am Ende des Speichers angelegt, indem der Stapelzeiger SPL (Stack Pointer Low) auf die Adresse RAMEND eingestellt wird. Ablegen auf den Stapel erfolgt immer so, dass die Stapeladresse niedriger wird, Entnahme vom Stapel erhöht die Adresse entsprechend wieder.

Ein einmal angelegter Stack kann im Programm auch zum zeitweisen Ablegen von Registerinhalten verwendet werden. Mit "PUSH R0" wird der Inhalt des Registers auf den Stapel abgelegt, mit "POP R0" wieder gelesen.

Der Stapel kann ferner dazu verwendet werden, um zeitweilig zu einer anderen Programmadresse zu verzeigen und später wieder zurückzukehren. Mit "RCALL Label" wird die aktuelle Ausführungsadresse auf den Stapel gelegt und dann an die Adresse "Label"verzweigt. Von dort wird mit der Instruktion "RET" wieder an die aufrufende Stelle zurückgekehrt.

Dasselbe passiert bei einem Interrupt. Nur dass hier die Adressablage auf dem Stapel und die Verzweigung vollautomatisch geschieht. Anstelle "RET" wird das angesprungene Programm mit der Instruktion "RETI" beendet, die auch gleich noch das I-Bit im Statusregister auf Eins setzt und die Interruptbearbeitung wieder einschaltet.

6.1.3 Interruptvektoren

Wo springt der Prozessor nun bei einem Interrupt hin? An die ersten Adressen im Flashspeicher. Dort befinden sich die sogenannten Interruptvektoren. Beim ATtiny13 sind das folgende:
#AdresseNameBeschreibung
00000RESETWird beim Reset, beim Anlegen der Betriebsspannung, bei Brown-Out und beim Watchdog-Reset angesprungen
10001INT0Signalflanke am Eingang INT0
20002PCINT0Signalflanke an einem Eingang
30003TIM0_OVFTimer 0 Überlauf
40004EE_RDYSchreibzyklus beim EEPROM abgeschlossen
50005ANA_COMPPolaritätswechsel am Analogvergleicher
60006TIM0_COMPATimer 0 Vergleicher A
70007TIM0_COMPBTimer 0 Vergleicher B
80008WDTWatchdog-Ereignis
90009ADCAD-Wandlung beendet


Die ersten 10 Programmworte in jedem interruptgesteuerten Programm sehen daher so aus:

.CSEG ; Assemblieren in den Flashspeicher (Code Segment)
.ORG 0 ; Adresse auf Null (Reset- und Interruptvektoren beginnen bei Null)
	rjmp Start ; Reset Vektor, Sprung zur Initiierung
	reti ; INT0-Int, nicht aktiv
	reti ; PCINT-Int, nicht aktiv
	reti ; TIM0_OVF, nicht aktiv
	reti ; EE_RDY-Int, nicht aktiv
	reti ; ANA_COMP-Int, nicht aktiv
	reti ; TIM0_COMPA-Int, nicht aktiv
	reti ; TIM0_COMPB-Int, nicht aktiv
	reti ; WDT-Int, nicht aktiv
	reti ; ADC-Int, nicht aktiv
;
; Programmstart beim Reset
;
Start:
	; [Hier beginnt das Programm]

Das "RETI" bei allen nicht verwendeten Vektoren stellt sicher, dass bei einem versehentlich aktivierten Interrupt ein geordneter Rückweg ausgeführt wird.

Hier hat sich bei vielen Programmierern eine Unart entwickelt. Sie setzen die gewünschte Adresse eines verwendeten Vektors mit der Assembler-Direktive „.ORG Vektoradresse“. Daraus macht der Assembler in Verbindung mit dem Programmiergerät dann eine Vektortabelle, die mit lauter NOP-Instruktionen (mit unprogrammierten FFFF) durchsetzt ist. Für die hintersten Vektoren gibt es gar keine Entsprechung, da steht dann schon weiterer Programmcode oder irgendeine andere Interrupt-Service-Routine. Hat man dann einen Interrupt zugelassen, aber vergessen, den Interrupt mit einem Vektor zu versehen, dann wird statt Nichtausführung des fehlenden Vektors (dort stünde bei ordentlichem Programmieren noch ein RETI herum) nach einigen NOP's der nächste programmierte Vektor oder eben das, was auf die Vektortabelle folgt, ausgeführt. Fehlt diesem Code ein RETI, werden fürderhin einfach alle Interrupts blockiert. Das gibt lustige Folgefehler, die zu finden und zu beseitigen einen ziemlichen Aufwand verursachen kann. So kann man sich selbst lustige Beine stellen und das Leben erschweren, ohne groß nachzudenken.

6.1.4 Der Timerüberlauf-Interrupt

Overflow-Int-Enable Um diesen Interrupt zu nutzen, reicht es aus, das Overflow-Interrupt-Enable-Flag TOIE0 im Timer Interrupt Mask Register TIMSK0 zu setzen, z. B. mit "ldi R16 1<<TOIE0" und "out TIMSK0 R16".

Damit der Interrupt angenommen wird, muss natürlich das I-Flag im Statusregister auf 1 gesetzt werden. Damit etwas passiert, muss natürlich noch eine Interrupt-Service-Routine her. Dazu muss in der Interruptvektortabelle in der Zeile mit dem Overflow-Int ein "rjmp ovflw_isr" hin und die Interrupt-Service-Routine ovflw_isr ist zu schreiben. Die kann z. B. zwischen die Vektortabelle und die Start-Routine. So eine typische Routine ist hier dargestellt:

ovflw_isr:
	in R15,SREG ; Statusregister in Register sichern
	dec R17 ; ein Register abwärts zählen
	brne ovflw_isr1 ; springe wenn noch nicht Null
	sbr R18,0b00000001 ; setze Bit 0 im Register R18
	ldi R17,10 ; zaehle ab 10 abwärts
ovflw_isr1:
	out SREG,R15 ; Statusregister wiederherstellen
	reti ; Rückkehr vom Interrupt, I-Flagge setzen

Typisch ist, dass zu Beginn das Statusregister gerettet werden muss. Da viele Instruktionen der Service-Routine die dortigen Flaggen beeinflussen (z. B. die Null-Flagge Z oder die Überlaufflagge C) können, muss dessen Originalzustand zu Beginn gesichert und am Ende wieder hergestellt werden. Schließlich kann die Routine jederzeit zuschlagen, auch wenn woanders mit diesen Flaggen gerade gearbeitet wird. Einhaltung dieser Grundregel "Sichern/Wiederherstellen von SREG in ISRs" ist daher reiner Selbstschutz.

6.1.5 Der Compare-Match-Interrupt

Setzt man eine oder beide Compare-Match-Flaggen OCIE0A oder OCIE0B, dann wird im Anschluss an jede Übereinstimmung mit dem Vergleichswert ein Interrupt ausgelöst. Bei einem Timer im CTC-Modus mit Compare-A tritt nach jedem Rücksetzen des Timers ein Interrupt auf, der zum Vektor TIM0_COMPA führt. Entsprechendes gilt für den Vergleicher B. Selbiges gilt für den PWM-Mode.

6.1.6 Interrupts und Schlafmodus

Im Schlafmodus Idle, den wir bereits verwendet haben, wecken alle Interrupts den Prozessor wieder auf. Damit dieser wieder schlafen gelegt werden kann, muss er in einer Schleife wieder die Instruktion "SLEEP" kriegen. Vor dem Schlafenlegen kann noch geprüft werden, ob eine Interrupt-Service-Routine noch eine Flagge hinterlassen hat, irgendetwas zu tun. Dann wird das eben jetzt erledigt, die Flagge wieder gelöscht und dann erst schlafen gelegt.

Home Top Einführung Hardware Overflow-Int CTC-Int


6.2 Hardware, Bauteile und Aufbau

Zwei LEDs>
Für die Interrupt-Experimente kommt die gleiche Hardware zum Einsatz, wie wir sie schon
in Lektion 2 aufgebaut haben. Jetzt kommt aber eine zweite LED mit Vorwiderstand am Portausgang
OC0B (an Pin 6) hinzu. Diese Bauteile kennen wir schon aus Lektion 2.<br clear=ALL>

<H2><a name=6.3 Timer mit Overflow-Interrupt

6.3.1 Aufgabenstellung

Die folgende Aufgabenstellung ist zu lösen:

6.3.2 Lösungsschritte

Es ist klar, dass sich diese Aufgabe nicht mit den bisherigen Blinkmechanismen lösen lässt, weil wir für diesen fünften Impuls keine geeigneten Mittel haben und das gleichzeitige Wechseln der Helligkeit der zweiten LED ein arg komplexes Geflecht erfordern würde. Es muss ein Mechanismus her, diesen Aussetzer und die Helligkeitssteuerung zu realisieren. Die Lösung ist ein interruptgesteuerter Zählmechanismus.

So ein Programm sieht grundlegend anders aus als ein Linearprogramm, wie wir es bislang verwendet haben. Weil fast alle Programme Interrupts benutzen, ist es wichtig, diese völlig andere Struktur zu verstehen.

Wir verwenden dazu den Timer-Overflow-Interrupt. Um damit den Sekundentakt hinzukriegen, muss die LED mit zwei Hertz (0,5 Sekunden an + 0,5 Sekunden aus) angesteuert werden. Von 1.200.000 Hz geteilt durch 256 (Timer-Overflow-Takte) = 4.687,5 Hz herab müssten wir einen Interruptzähler bis 4.687,5 / 2 = 2.344 (aufgerundet) zählen lassen. Da das grösser als 255 ist, brauchen wir dafür einen 16-Bit-Zähler.

Weil das Ausblenden der fünften Sekunde etwas aufwändigere Operationen bedingt, verlegen wir diesen Teil nach außerhalb der Interrupt-Service-Routine. Das wäre hier zwar nicht nötig, weil wir nur einen einzigen Interrupt bedienen müssen und bis zum nächsten Interrupt 256 Takte hin sind. Aber wir machen das jetzt hier mal um das Prinzip zu lernen, das bei komplexeren Abläufen ohnehin erforderlich ist.

6.3.3 Programm

Das hier ist eine der denkbaren Lösungen.Hier ist der Quellcode. Die gestrenge Auflistung aller verwendeten Register, Ports und Portbits, die eindeutige Benennung aller verwendeten Konstanten und die vollständige Auflistung aller Interruptvektoren ist übrigens nicht pädagogisch motiviert sondern schlichte Notwehr gegen schlampiges Chaos, das aus leidvoller Erfahrung zu elendig langen Fehlersuch-Sitzungen führt. Das empfiehlt sich nicht nur für Anfänger in Sachen Programmierung, auch Fortgeschrittene haben sich eine solche Struktur längst angeeignet, weil sie dem Ergrauen entgegenwirkt. Dies und die üppige Kommentierung hilft, nach einigen Wochen noch zu verstehen, was der geniale Programmierer sich dabei so gedacht hat.

;
; ***************************************
; * Timer mit Overflow-Interrupt        *
; * (C)2016 by www.gsc-elektronik.net   *
; ***************************************
;
.NOLIST
.INCLUDE "tn13def.inc"
.LIST
;
; ---------- Register -------------------
; frei: R0 .. R14
.def rSreg = R15 ; SREG-Zwischenspeicher
.def rmp = R16 ; Vielzweckregister
.def rimp = R17 ; Vielzweckregister Interrupts
.def rFlag = R18 ; Flaggenregister
	.equ bPol = 0 ; Flagge Polaritaetswechsel
.def rZaehl= R19 ; Blinkzaehler
.def rPwm = R20 ; PWM-Zaehler
; frei: R20 .. R23
.def rCntL = R24 ; Zaehler, LSB
.def rCntH = R25 ; dto., MSB
;frei: R26 .. R31
;
; ---------- Ports, Portbits ------------
.equ pLedOut = PORTB ; LED-Ausgabeport
.equ bLedOut = PORTB0
.equ pLedDdr = DDRB ; LED-Richtungsport
.equ bLedDdr = DDB0
.equ pLedIn  = PINB ; LED-Eingabeport
.equ bLedIn  = PINB0
;
.equ bPwmOut = PORTB1 ; Ausgabeport PWM
.equ bPwmDdr = DDB1 ; Richtungsport PWM
;
; ---------- Timing ---------------------
.equ cClock  = 1200000 ; Takt
.equ cPolwechsel = 2 ; Taktfrequenz Led An/Aus
.equ cPresc  = 1 ; Vorteiler Timer
.equ cCount  = cClock / 256 / cPresc / cPolwechsel + 1
.equ cBlink  = 5 ; Blinkzaehler
;
; ---------- Reset- und Interrupt-Vektoren -------
.CSEG ; Programmcode
.ORG 0 ; Reset- und Vektorenadresse
	rjmp Start ; Reset Vektor, Sprung zur Initiierung
	reti ; INT0-Int, nicht aktiv
	reti ; PCINT-Int, nicht aktiv
	rjmp Tc0IsrO ; TIM0_OVF, aktiv
	reti ; EE_RDY-Int, nicht aktiv
	reti ; ANA_COMP-Int, nicht aktiv
	reti ; TIM0_COMPA-Int, nicht aktiv
	reti ; TIM0_COMPB-Int, nicht aktiv
	reti ; WDT-Int, nicht aktiv
	reti ; ADC-Int, nicht aktiv
;
; ---------- Interrupt-Service-Routinen ----------
Tc0IsrO: ; Timer 0 Overflow ISR
	in rSreg,SREG ; Statusregister sichern
	sbiw rCntL,1 ; Zaehler um Eins verringern
	brne Tc0IsrO1 ; springe wenn nicht Null
	sbr rFlag,1<<bPol ; Flagge Polaritaetswechsel setzen
	ldi rCntH,HIGH(cCount) ; Zaehler neu starten
	ldi rCntL,LOW(cCount)
Tc0IsrO1:
	mov rimp,rCntL ; Low Byte Zaehler kopieren
	andi rimp,0b00011111 ; die unteren fuenf Bit isolieren
	brne Tc0IsrO2 ; springe wenn nicht Null
	dec rPwm ; PWM-Wert abwaerts
	out OCR0B,rPwm ; in Vergleichsregister B
Tc0IsrO2:
	out SREG,rSreg ; Statusregister wieder herstellen
	reti ; Ende ISR, I-Bit setzen
;
; ---------- Initiieren und Programmschleife -----
Start:
	; Stapel initiieren
	ldi rmp,LOW(RAMEND) ; Stapelzeiger auf SRAM-Ende
	out SPL,rmp ; Stapelzeiger setzen
	; LED-Ausgang aktivieren
	ldi rmp,(1<<bLedDdr)|(1<<bPwmDdr) ; LED-Ausgaenge an, Treiber an
	out pLedDdr,rmp ; in Port-Richtungsregister
	; Zaehler initiieren
	ldi rZaehl,cBlink ; Blinkzaehler initiieren
	ldi rCntH,HIGH(cCount) ; Zaehler auf Startwert
	ldi rCntL,LOW(cCount)
	; Vergleichsregister initiieren
	ldi rmp,0xFF ; Compare A auf 255
	out OCR0A,rmp ; in Vergleichsregister A
	clr rPwm ; Startwert PWM
	out OCR0B,rPwm ; in Vergleichsregister B
	; Zaehler 0 als starten mit Overflow-Interrupt
	ldi rmp,(1<<COM0B1)|(1<<WGM01)|(1<<WGM00) ; Fast PWM, COM0B
	out TCCR0A,rmp ; in Timer Kontrollregister A
	ldi rmp,1<<CS00 ; Vorteiler durch 1
	out TCCR0B,rmp ; in Timer Kontrollregister B
	ldi rmp,1<<TOIE0 ; Overflow-Interrupt
	out TIMSK0,rmp ; in Timer-Interrupt-Maske
	; Schlafmodus
	ldi rmp,1<<SE ; Sleep-Enable, Mode Idle
	out MCUCR,rmp ; in Universal-Kontrollregister
	; Interrupts ermoeglichen
	sei ; setze Interrupt-Flagge im Statusregister
; Programmschleife
Schleife:
	sleep ; schlafen legen
	nop ; nach Aufwachen
	sbrc rFlag,bPol ; ueberspringe naechste Instruktion wenn Flagge Null
	rcall Polaritaet ; Flagge bearbeiten: Polaritaetswechsel
	rjmp Schleife ; wieder schlafen legen
;
; --------- Polaritaet der LED wechseln ----------
Polaritaet:
	cbr rFlag,1<<bPol ; Flagge loeschen
	sbis pLedOut,bLedOut ; ueberspringe naechste Instruktion wenn Led An
	rjmp Polaritaet3 ;  Led ist an, springe zu LED aus
	; Led ist Aus
	cpi rZaehl,0 ; ist Blinkzaehler Null?
	breq Polaritaet1 ; ja, ist Null, starte Zaehler neu
	; Led Aus, Zaehler nicht Null
	dec rZaehl ; nein, vermindere Blinkzaehler
	brne Polaritaet2 ; Led einschalten
	ret ; kein Polaritaetswechsel
Polaritaet1: ; Neustart Zaehler
	ldi rZaehl,cBlink ; starte Zaehler neu
	ret ; kein Polaritaetswechsel
Polaritaet2: ; Led einschalten
	cbi pLedOut,bLedOut ; Led an
	ret
Polaritaet3: ; Led ausschalten
	sbi pLedOut,bLedOut ; Led aus
	ret
;
; Ende Quellcode
;

Neu sind die Instruktionen

6.3.4 Das Ergebnis

Led-Kombination So leuchten zwei LEDs in unterschiedlichem Modus: die eine abrupt mit fehlendem fünftem Impuls, die andere mit sanft veränderter Helligkeit. Und das alles mit minimiertem Strombedarf, weil der Prozessor überwiegend im Schlafmodus herumschnarcht und nur bei Bedarf geweckt wird und danach in der Regel wieder schlafen gelegt wird.

6.3.5 Programmgliederung

Die erkennbare Gliederung des Programmes in
  1. Registerdefinitionen, Portfestlegungen und Konstanten zu Beginn,
  2. Reset- und Interruptvektoren,
  3. Interrupt-Service-Routinen,
  4. Hauptprogramm-Init mit Hardwarekonfiguration und Startwerten,
  5. Schleifenbearbeitung, und
  6. Bearbeitungsroutinen außerhalb von Interrupt-Service-Routinen
macht bei interruptgesteuerten Programmen immer Sinn, um den Überblick auch bei komplizierteren Abläufen zu behalten. Es hilft immens bei der Fehlersuche, weil man potenzielle Fehlerverursacher schneller findet.

6.3.6 Vor- und Nachteile

Der Vorteil bei dieser Lösung ist die hohe Flexibiltät (leichte Modifizierbarkeit). So kann die Blinkfrequenz, der zu überspringende Blinkimpuls oder der LED-Port einfach durch Änderung von Konstanten im Kopf auf anderen Bedarf umgestellt werden.

Der Nachteil der Lösung ist, dass die Blinkdauer nur ungefähr stimmt. Das ist darauf zurückzuführen, dass das Teilen durch 256 nicht zu passenden Ganzzahlen führt. Diesen Nachteil behebt das nächste Lösung.

Home Top Einführung Hardware Overflow-Int CTC-Int


6.4 Timer mit CTC-Interrupt

Damit es exakt zugeht, z. B. bei einer Uhr, ist eine exaktere Lösung zwingend. Die geht wie hier gezeigt.

6.4.1 CTC-Auswahl

Um exakte Teilerverhältnisse zu erhalten, muss die Kombination von Taktfrequenz, Vorteiler, CTC-Wert und Software-Zähler exakt den Zielwert ergeben. In der Tabelle sind für alle Taktfrequenzen des ATtiny13 und für alle Vorteiler diejenigen CTC-Teiler angegeben, die sich mit einem 8- oder 16-Bit-Zähler realisieren lassen, um 1 Hz zu erhalten. Die Verhältnisse sind farblich gekennzeichnet.
Farben:16-Bit-Zähler 8-Bit-ZählerCTC=256 CTC=256/8-Bit-Zähler

Daraus ergeben sich folgende Ergebnisse (nur CTC-Werte > 100 sind aufgelistet).
Takt (Hz)Prescaler Ganzzahlige CTC-Teiler
9.600.0001256 (37500) 250 (38400)240 (40000) 200 (48000)192 (50000) 160 (60000)150 (64000)
8250 (4800)240 (5000) 200 (6000)192 (6250) 160 (7500)150 (8000) 128 (9375)
64250 (600)240 (625) 200 (750)150 (1000)
256250 (150)150 (250)
1024
4.800.0001256 (18750) 250 (19200)240 (20000) 200 (24000)192 (25000) 160 (30000)150 (32000) 128 (37500)
8250 (2400)240 (2500) 200 (3000)192 (3125) 160 (3750)150 (4000)
64250 (300)200 (375) 150 (500)
256250 (75)150 (125)
1024
1.200.0001250 (4800) 240 (5000)200 (6000) 192 (6250)160 (7500) 150 (8000)128 (9375)
8250 (600)240 (625) 200 (750)150 (1000)
64250 (75)150 (125)
256
1024
128.0001256 (500) 250 (512)200 (640) 160 (800)128 (1000)
8250 (64)200 (80) 160 (100)128 (125)
64250 (8)200 (10)
256250 (2)
1024
Für 1,2 MHz Takt bietet sich eine CTC-Teilung durch 250 (125 für 2 Hz) bei einem Vorteiler von 64 an. Da die zweite LED ein PWM-Signal erzeugen muss, muss der Timer im PWM-Modus betrieben werden.

6.4.2 Programm

Für das Zählen reicht nun ein 8-Bit-Register. Hier ist der Quellcode.

;
; ***************************************
; * Timer mit COMP-A-Interrupt          *
; * (C)2016 by www.gsc-elektronik.net   *
; ***************************************
;
.NOLIST
.INCLUDE "tn13def.inc"
.LIST
;
; ---------- Register -------------------
; frei: R0 .. R14
.def rSreg = R15 ; SREG-Zwischenspeicher
.def rmp = R16 ; Vielzweckregister
.def rimp = R17 ; Vielzweckregister bei Interrupts
.def rFlag = R18 ; Flaggenregister
	.equ bPol = 0 ; Flagge Polaritaetswechsel
.def rZaehl= R19 ; Blinkzaehler
.def rCtcInt = R20 ; CTC-Interrupt-Zaehler
.def rPwm = R21 ; PWM-Zaehler
; frei: R22 .. R31
;
; ---------- Ports, Portbits ------------
.equ pLedOut = PORTB ; LED-Ausgabeport
.equ bLedOut = PORTB0
.equ pLedDdr = DDRB ; LED-Richtungsport
.equ bLedDdr = DDB0
.equ pLedIn  = PINB ; LED-Eingabeport
.equ bLedIn  = PINB0
;
.equ bPwmOut = PORTB1 ; zweite LED im PwmMode
.equ bPwmDdr = DDB1
;
; ---------- Timing ---------------------
.equ cClock  = 1200000 ; Takt
.equ cPolwechsel = 2 ; Taktfrequenz Led An/Aus
.equ cPresc  = 64 ; Vorteiler Timer
.equ cCtcInt = 125 - 1 ; CTC-Int-Zaehler
.equ cCtcCnt = cClock / cPresc / cPolwechsel / (cCtcInt +1)
.equ cBlink  = 5 ; Blinkzaehler
;
; ---------- Reset- und Interrupt-Vektoren -------
.CSEG ; Programmcode
.ORG 0 ; Reset- und Vektorenadresse
	rjmp Start ; Reset Vektor, Sprung zur Initiierung
	reti ; INT0-Int, nicht aktiv
	reti ; PCINT-Int, nicht aktiv
	reti ; TIM0_OVF, nicht aktiv
	reti ; EE_RDY-Int, nicht aktiv
	reti ; ANA_COMP-Int, nicht aktiv
	rjmp Tc0IsrA ; TIM0_COMPA-Int, nicht aktiv
	reti ; TIM0_COMPB-Int, nicht aktiv
	reti ; WDT-Int, nicht aktiv
	reti ; ADC-Int, nicht aktiv
;
; ---------- Interrupt-Service-Routinen ----------
Tc0IsrA: ; Timer 0 Compare A Interrupt
	in rSreg,SREG ; Statusregister sichern
	dec rPwm ; PWM-Zaehler erniedrigen
	brne Tc0IsrA1 ; springen wenn nicht Null
	ldi rPwm,cCtcInt ; Neustart PWM-Zaehler
Tc0IsrA1:
	out OCR0B,rPwm ; PWM aktualisieren
	dec rCtcInt ; Zaehler um Eins verringern
	brne Tc0IsrA2 ; springe wenn nicht Null
	sbr rFlag,1<<bPol ; Flagge Polaritaetswechsel setzen
	ldi rCtcInt,cCtcCnt ; Zaehler neu starten
Tc0IsrA2:
	out SREG,rSreg ; Statusregister wieder herstellen
	reti ; Ende ISR, I-Bit setzen
;
; ---------- Initiieren und Programmschleife -----
Start:
	; Stapel initiieren
	ldi rmp,LOW(RAMEND) ; Stapelzeiger auf SRAM-Ende
	out SPL,rmp ; Stapelzeiger setzen
	; LED-Ausgang aktivieren
	ldi rmp,(1<<bPwmDdr)|(1<<bLedDdr) ; LED-Ausgaenge, Treiber an
	out pLedDdr,rmp ; in Richtungsregister
	; Zaehler initiieren
	ldi rZaehl,cBlink ; Blinkzaehler initiieren
	ldi rCtcInt,cCtcCnt ; Zaehlerstartwert
	ldi rPwm,cCtcInt ; PWM initiieren
	; Compare A-Wert fuer CTC setzen
	ldi rmp,cCtcInt ; CTC-Teiler durch 125
	out OCR0A,rmp ; in Vergleicher A
	; Zaehler 0 normal starten mit Overflow-Interrupt
	ldi rmp,(1<<COM0B1)|(1<<WGM01)|(1<<WGM00) ; PWM am Port OC0B, Fast PWM
	out TCCR0A,rmp ; in Timer Kontrollregister A
	ldi rmp,(1<<WGM02)|(1<<CS01)|(1<<CS00) ; Fast PWM, Vorteiler durch 64
	out TCCR0B,rmp ; in Timer Kontrollregister B
	ldi rmp,1<<OCIE0A ; Compare A-Interrupt
	out TIMSK0,rmp ; in Timer-Interrupt-Maske
	; Schlafmodus
	ldi rmp,1<<SE ; Sleep-Enable, Mode Idle
	out MCUCR,rmp ; in Universal-Kontrollregister
	; Interrupts ermoeglichen
	sei ; setze Interrupt-Flagge im Statusregister
; Programmschleife
Schleife:
	sleep ; schlafen legen
	nop ; nach Aufwachen
	sbrc rFlag,bPol ; ueberspringe naechste Instruktion wenn Flagge Null
	rcall Polaritaet ; Flagge bearbeiten: Polaritaetswechsel
	rjmp Schleife ; wieder schlafen legen
;
; --------- Polaritaet der LED wechseln ----------
Polaritaet:
	cbr rFlag,1<<bPol ; Flagge loeschen
	sbis pLedOut,bLedOut ; ueberspringe naechste Instruktion wenn Led An
	rjmp Polaritaet3 ;  Led ist an, springe zu LED aus
	; Led ist Aus
	cpi rZaehl,0 ; ist Blinkzaehler Null?
	breq Polaritaet1 ; ja, ist Null, starte Zaehler neu
	; Led Aus, Zaehler nicht Null
	dec rZaehl ; nein, vermindere Blinkzaehler
	brne Polaritaet2 ; Led einschalten
	ret ; kein Polaritaetswechsel
Polaritaet1: ; Neustart Zaehler
	ldi rZaehl,cBlink ; starte Zaehler neu
	ret ; kein Polaritaetswechsel
Polaritaet2: ; Led einschalten
	cbi pLedOut,bLedOut ; Led an
	ret
Polaritaet3: ; Led ausschalten
	sbi pLedOut,bLedOut ; Led aus
	ret
;
; Ende Quellcode
;

Natürlich sehen wir den Taktunterschied bei der LED gegenüber der vorherigen Lösung nicht, weil das menschliche Auge unempfindlich ist für solche minimalen Unterschiede. Wir wissen aber, dass das jetzt genau ist, weil wir es so programmiert haben.

Home Top Einführung Hardware Overflow-Int CTC-Int


©2016 by http://www.gsc-elektronic.net