5 Timer/Counter und Interrupts
Der ATtiny85 besitzt zwei
unabhängige 8 Bit breite Vorwärtszähler als Timer oder Counter mit dem Namen
Timer0 und Timer1, die sich vielseitig einsetzen lassen. Der Zählereingang von
Timer0 liegt wahlweise am Anschluss PB2 oder am internen Taktoszillator, mit oder
ohne Vorteiler. Das Kapitel über die Timer im Datenblatt des ATtiny85 ist recht
umfangreich und beschreibt sehr viele Möglichkeiten. Hier sollen die
wichtigsten Fälle erprobt werden.
5.1 Zeitmessung
Der Timer0 soll zunächst als
Zeitmesser verwendet werden und erhält seinen Takt aus dem durch 1024
heruntergeteilten Prozessortakt. Das eigentliche Zählerregister TCNT0 kann zu
jeder Zeit gelesen und beschrieben werden. Entsprechend der Zählerauflösung von
8 Bit beträgt der maximale Zählerstand 255. Danach folgt ein Übertrag auf den
Wert Null.
'Timer0.bas
Messung von Laufzeiten
$regfile
= "attiny85.dat"
$crystal
= 8000000
$hwstack
= 8
$swstack
= 4
$framesize
= 4
Dim
Dat As Byte
Dim
N As Byte
Open
"comb.1:9600,8,n,1" For Output As #1
Config
Timer0 = Timer , Prescale = 1024
Timer0
= 0
For
N = 1 To 20
Dat = Timer0
Print #1 , Dat
Next
N
End
Listing 5.1: Zeitmessung mit
Timer0
Im Terminal kann man die
Ausgabe der Zählerstände sehen: 0 27 63 99 134 178 222 (Überlauf) 10 46 81 117
161 205 249 (Überlauf) 36 72 108 152 196 240. Mit dem eingestellten Vorteiler
1024 erhält der Timer einen Takt von etwa 8 kHz (8 MHz / 1024), und damit eine Taktlänge
ist 128 µs. Die erste Ausgabe ist Null, weil der Timer gerade zurückgesetzt
wurde. Die zweite Ausgabe lautet 27 und steht für 27 * 128 µs, also etwa 3,5 ms.
Gesendet wurden drei Zeichen, 0, CR und LF. Jedes Byte benötigt etwa eine
Millisekunde. Man sieht also, dass die Ausführungszeit des Programms im
Wesentlichen auf die Printausgaben zurückgeht. Da die folgenden Ausgaben mehr Zeichen
enthalten, sind die gemessenen Laufzeiten entsprechend länger. Für die Ausgabe
der Zahl 134 werden offensichtlich 178-134=44 Zeiteinheiten, also etwa 5,6 ms
benötigt.
Exakt die gleichen Ergebnisse
liefert eine Messung mit dem Timer1. Timer0 und Timer1 lassen sich also gleich
gut als Zeitgeber verwenden.
'Timer1.bas
Messung von Laufzeiten
$regfile
= "attiny85.dat"
$crystal
= 8000000
$hwstack
= 8
$swstack
= 4
$framesize
= 4
Dim
Dat As Byte
Dim
N As Byte
Open
"comb.1:9600,8,n,1" For Output As #1
Config
Timer1 = Timer , Prescale = 1024
Timer1
= 0
For
N = 1 To 20
Dat = Timer1
Print #1 , Dat
Next
N
End
Listing 5.2: Zeitmessung mit
Timer1
5.2 Impulse zählen
Das Programm Counter0.bas
richtet den Timer0 als Impulszähler ein. Impulse am Eingang T0 (Pin B2)
gelangen an den Eingang des Zählers, wobei hier negative Flanken gezählt werden,
also Wechsel von High nach Low. B2 ist gleichzeitig der RXD-Eingang und kann
über TXD-Signale der Schnittstelle angesprochen werden. Auch externe Signale
können angeschlossen werden, weil B2 hochohmig über 10 kΩ mit dem
USB-Seriell-Wandler verbunden ist.
'Counter0.bas
Input B2/RXD
$regfile
= "attiny85.dat"
$crystal
= 8000000
$hwstack
= 8
$swstack
= 4
$framesize
= 4
Dim
Dat As Byte
Dim
N As Byte
Open
"comb.1:9600,8,n,1" For Output As #1
Config
Timer0 = Counter , Edge = Falling
Timer0
= 0
Do
Dat = Timer0
Print #1 , Dat
Waitms 1000
Loop
End
Listing 5.3: Ein Impulszähler
Starten Sie das
Bascom-Terminal und senden Sie z.B. eine Folge von Großbuchstaben U. Wie
erwartet erhöht sich der Zähler jeweils um fünf. Bei einem Zählerstand über 255
erfolgt ein Übertrag auf 4, denn 260 = 256 + 4.
Abb. 5.1: Zählen serieller
Datenimpulse
5.3 Timer-Interrupt
Ein Interrupt ist eine
automatische Unterbrechung des Programmlaufs, wobei ein vorbereitetes Interrupt-Unterprogramm
ausgeführt wird. Hier soll der Timer bei jedem Überlauf das Unterprogramm Tim0_isr
(Timer0 Interrupt Service Routine) aufrufen. Während ein normales
Unterprogramm mit End Sub abgeschlossen wird, steht am Ende einer
Interrupt-Routine ein Return.
Damit der Interrupt
tatsächlich ausgeführt wird, muss sowohl der Counter-Interrupt (Enable Timer0)
als auch der globale Interrupt (Enable Interrups) freigegeben werden.
Das Hauptprogramm enthält nur
eine leere Do-Loop-Schleife. Bei jedem Zählerüberlauf verzweigt das Programm
jedoch einmal in die Interrupt-Routine. Dort wird B3 getoggelt, sodass man ein
schnelles Blinken sieht.
Der Timer kann wahlweise mit
dem vollen Prozessortakt von 8 MHz oder mit einem Vorteiler betreiben werden.
Die erlaubten Vorteiler-Einstellungen sind 1, 8, 64, 256 und 1024. Hier wird
der größte Vorteiler 1024 verwendet. Ein Überlauf erfolgt nach jeweils 256
Takten. Daraus ergibt sich eine Interrupt-Frequenz von 30,5 Hz und eine
Blinkfrequenz von etwa 15 Hz. Das Blinken ist also gerade noch klar sichtbar.
'TimerInterrupt0.bas
$regfile
= "attiny85.dat"
$crystal
= 8000000
$hwstack
= 8
$swstack
= 4
$framesize
= 4
Config
Portb.3 = Output
Config
Timer0 = Timer , Prescale = 1024
Enable
Interrupts
Enable
Timer0
On
Timer0 Tim0isr
Do
Loop
'8
MHz / 1024 / 256 = 30,5 Hz
Tim0isr:
Toggle Portb.3
Return
End
Listing 5.4: Ein
LED-Blinker mit Timer-Interrupt
Man kann nun im Hauptprogramm
etwas ganz anderes tun, wobei dann zwei Prozesse quasi gleichzeitig ablaufen.
Das funktioniert sehr gut, solange die Interrupt-Routine nur wenig Rechenzeit
beansprucht. Das Programm TimerInterrupt1.bas führt im Hauptprogramm
Printausgaben durch. Das Blinken der LED an B3 wird diesmal durch den Timer1
gesteuert und ändert sich nicht. Auch die Printausgaben werden nicht durch die
Interrupts gestört, obwohl ein serielles Bit nur ca. 100 µs dauert und sich um
die Dauer der Interrupt-Verarbeitung verlängern kann. Eventuelle Störungen des
zeitlichen Ablaufs könnte man im Terminal an falschen Zeichen erkennen.
'TimerInterrupt1.bas
$regfile
= "attiny85.dat"
$crystal
= 8000000
$hwstack
= 8
$swstack
= 4
$framesize
= 4
Open
"comb.1:9600,8,n,1" For Output As #1
Config
Portb.3 = Output
Config
Timer1 = Timer , Prescale = 1024
Enable
Interrupts
Enable
Timer1
On
Timer1 Tim1isr
Do
Waitms
1000
Print
#1 , ".";
Loop
'8
MHz / 1024 / 256 = 30,5 Hz
Tim1isr:
Toggle Portb.3
Return
End
Listing 5.5: Print-Ausgaben
und Timer-Interrupt
5.4 Sekunden-Timer
Die geringste direkt
erzeugbare Interrupt-Frequenz beträgt 30,5 Hz. Um genaue Sekunden zu erzeugen,
muss ein zusätzlicher Software-Zähler gebildet werden. Damit man mit Ganzzahlen
auskommt, soll ein von 256 abweichender Überlauf erzeugt werden. Beliebige Teiler
lassen sich einstellen, indem der Timer am Anfang der Timer-Prozedur
vorgestellt wird. Hier wird das Timer0-Register jeweils mit 6 geladen, sodass
nur noch 250 Takte bis zum nächsten Überlauf vergehen.
Mit einem Vorteiler von 256
kommt man damit auf eine Interrupt-Frequenz von 125 Hz (8000000 Hz / 256 / 250
= 125 Hz). Ein Zähler in der Variablen Ticks1 zählt diese Zeiteinheiten und
wird jeweils bei einem Stand von 125 zurückgesetzt, wobei gleichzeitig Ticks2
erhöht wird und der Ausgang B3 umgeschaltet wird. Eine angeschlossene LED
blinkt nun im Sekundentakt, sodass man die Genauigkeit des internen
RC-Oszillators direkt mit einer Uhr überprüfen kann. Üblicherweise findet man
dabei Abweichungen in der Größenordnung von einer Sekunde pro Minute.
'TimerInterrupt2.bas
Sekunden
$regfile
= "attiny85.dat"
$crystal
= 8000000
$hwstack
= 8
$swstack
= 4
$framesize
= 4
Dim
Ticks1 As Byte
Dim
Ticks2 As Byte
Config
Portb.3 = Output
Open
"comb.1:9600,8,n,1" For Output As #1
Config
Timer0 = Timer , Prescale = 256
Enable
Interrupts
Enable
Timer0
On
Timer0 Tim0isr
Do
Disable Interrupts
Print #1 , " ";
Print #1 , Ticks2
Enable Interrupts
Waitms 5000
Loop
'8
MHz / 256 / 250 = 125 Hz
Tim0isr:
Timer0 = 6
Ticks1 = Ticks1 + 1
If Ticks1 = 125 Then
Toggle Portb.3
Ticks1 = 0
Ticks2 = Ticks2 + 1
Print #1 , Ticks2
End If
Return
End
Listing 5.6: Der Sekunden-Timer
In der Interrupt-Routine wird
zusätzlich zu jeder neuen Sekunde eine Print-Ausgabe durchgeführt. Im Terminal
sieht man die fortlaufenden Sekunden. Eine zweite Printausgabe erfolgt im
Hauptprogramm nach jeweils 5 s. Jede fünfte Sekunde wird daher zweimal
ausgegeben. Damit sich beide Ausgaben nicht gegenseitig stören, wird im
Hauptprogramm kurzzeitig der Interrupt gesperrt und nach der Ausgabe wieder
freigegeben.
5.5 PWM-Ausgang
PWM-Ausgaben (vgl. Kap. 2.10)
werden ebenfalls mit einem Timer gesteuert. Hier wird Timer0 für zwei
unabhängige PWM-Kanäle mit den Ausgängen OC0A (B0) und OC0B (B1) eingerichtet.
Der Vorteiler ist in weiten Grenzen wählbar, sodass die PWM-Frequenz von 15,625
kHz (Prescale = 1) über 1,95 kHz (Prescale = 8) bis herunter auf 15,3 Hz (Prescale
= 1024) reicht. Das jeweilige PWM-Tastverhältnis wird in die Register Pwm0a
und Pwm0b geschrieben.
'PWM1.bas
PWM0A B0, PWM0B B1
$regfile
= "attiny85.dat"
$crystal
= 8000000
$hwstack
= 8
$swstack
= 4
$framesize
= 4
Dim
Dat As Byte
Dim
Ticks2 As Byte
Config
Portb.3 = Output
Config
Timer0 = Pwm , Prescale = 8 , Compare A Pwm =
Clear_up , Compare B Pwm = Clear_up
Do
Dat = Dat + 1
Pwm0a = Dat
Pwm0b = Dat
Waitms 10
Loop
End
Listing 5.7: PWM-Ausgaben an
zwei Kanälen
Eine an B0 oder an B1
angeschlossene LED zeigt einen periodisch ansteigenden Helligkeitsverlauf.
Testen Sie auch einmal andere Vorteiler. Mit Prescale = 1024 sieht man ein
deutliches Flackern und kann die PWM-Ausgaben anschaulich verfolgen.
5.6 Der weiche Blinker
Alle bisherigen
Blinkprogramme erzeugten harte Übergänge zwischen An und Aus. Mit dem
PWM-Ausgang lassen sich auch weiche Übergänge erzeugen. Die LED-Helligkeit wird
kontinuierlich erhöht und verringert. PWM0A und PWM0B werden gegenphasig
angesteuert. Weil das Helligkeitsempfinden des menschlichen Auges nicht linear
ist, erzeugt das Programm einen quadratischen Verlauf.
'PWM2.bas
Weicher Blinker B0/B1
$regfile
= "attiny85.dat"
$crystal
= 8000000
$hwstack
= 8
$swstack
= 4
$framesize
= 4
Dim
I As Byte
Dim
D As Integer
Dim
D2 As Integer
Config
Timer0 = Pwm , Prescale = 8 , Compare A Pwm = Clear Up , Compare B Pwm = Clear
Up
Pwm0a
= 0
Do
For I = 40 To 215
If I < 128 Then
D = I
D = D * D
D2 = 167 - I
D2 = D2 And 127
D2 = D2 * D2
End If
If I > 127 Then
D = 255 - I
D = D * D
D2 = 167 + I
D2 = D2 And 127
D2 = D2 * D2
End If
D = D / 64
Pwm0a = D
D2 = D2 / 64
Pwm0b = D2
Waitms 15
Next I
Loop
End
Listing 5.8: An- und
Abschwellen der Helligkeit
Schließen Sie die LED mit einem
Vorwiderstand von 1 kΩ an B0 an. Mit dem Start des Programms erhalten Sie
ein „weiches“ Blinken. Eine zweite LED an B1zeigt den gegenphasigen Verlauf.
5.7 Frequenzmessung
Die Frequenz eines
Rechtecksignals ist definiert als die Anzahl der Schwingungen dividiert durch
die Zeit. Deshalb müssen bei einer Frequenzmessung zwei Aufgaben gleichzeitig
erledigt werden, nämlich das Zählen der Impulse und eine Zeitmessung. Wenn die
Auflösung der Messung ein Hertz betragen soll, muss eine Torzeit von einer
Sekunde gewählt werden, d.h. der Impulszähler muss nach genau einer Sekunde
angehalten werden.
Das Programm Freq1.bas löst
die Aufgabe der Zeitmessung mit einer Interruptroutine, die in ähnlicher Form
schon aus Kap. 5.4 bekannt ist. Die Interruptroutine wird im Millisekundentakt
aufgerufen. Dazu wird der Timer0 am Anfang auf 6 voreingestellt, damit nach
genau 250 Takten der nächste Überlauf erfolgt. Die Millisekunden werden in t
gezählt. Zur Zeit t = 1000 ist die Messung beendet. Der Ausgang B3 wird zu
Kontrollzwecken getoggelt. Hier erscheint damit ein symmetrisches
Rechtecksignal mit einer Pulslänge von 1 ms und einer Frequenz von 500 Hz.
Das Zählen der Impulse
übernimmt Timer1, der dazu also Counter mit fallender Flanke eingerichtet wird.
Der Eingang T1 liegt an B2, also an dem Eingang, der sonst als RXD verwendet
wird. Er ist hochohmig an den TXD-Ausgang des USB-Wandlers angeschlossen. Man
kann serielle Signale zum Test verwenden oder ein externes Messsignal an B2
anschließen.
Da Timer1 ebenfalls eine Breite
von 8 Bit hat, kann er nur 255 Impulse zählen. Das Programm verwendet eine
zweite Interrupt-Routine zum Zählen der Überläufe von Timer1, damit auch
Frequenzen über 255 Hz gemessen werden können. Bei jedem Überlauf wird f um
Eins erhöht.
Sobald in der Timer0-Routine
das Ende der Torzeit erkannt wurde, werden alle Interrupts gestoppt, sodass
auch kein weiterer Überlauf an Timer1 mehr sattfinden kann. Dann liest das
Programm den aktuellen Inhalt von Timer1. Er wird im Hauptprogramm als Lowbyte
der Frequenz f verwendet. Der höherwertige Anteil wird mit f * 256 aus den
gezählten Zählerüberläufen berechnet. F ist als Long deklariert und kann daher
eine 32-Bit-Zahl aufnehmen. Die tatsächliche Grenze der Frequenzmessung ist
aber durch die Grenzfrequenz des Zählers Timer1 festgelegt und liegt bei etwa 4
MHz.
'Freq1.bas
Input B2/RXD
$regfile
= "attiny85.dat"
$crystal
= 8000000
$hwstack
= 8
$swstack
= 4
$framesize
= 4
Dim
T As Word
Dim
N As Byte
Dim
F As Long
Config
Portb.3 = Output
Open
"comb.1:9600,8,n,1" For Output As #1
Config
Timer0 = Counter , Edge = Falling
Enable
Timer0
On
Timer0 Tim0isr
Config
Timer1 = Timer , Prescale = 32
Enable
Timer1
On
Timer1 Tim1isr
Do
F = 0
T = 0
Timer0 = 0
Timer1 = 0
Enable Interrupts
Do
Loop Until T = 1000
Disable Interrupts
F = F * 256
F = F + N
Print #1 , Chr(13);
Print #1 , F;
Print #1 , " Hz ";
'Waitms 1000
Loop
Tim0isr:
F = F + 1
Return
'8
MHz / 32 / 250 = 1 kHz
Tim1isr:
Timer1 = 6
Toggle Portb.3
T
= T + 1
If T = 1000 Then
N = Timer0
Disable Interrupts
End If
Return
End
Listing 5.9: Frequenzmessung
mit serieller Ausgabe
Die Frequenzanzeige im
Bascom-Terminal überschreibt immer dieselbe Zeile, sodass eine fest stehende
Ausgabe entsteht. Dazu wird zwar CR gesendet, nicht aber LF, der Cursor springt
also jeweils zurück auf den Zeilenanfang.
Ein erster Test des Frequenzmessers
kann durch Senden von Textzeichen durchgeführt werden. Das Ergebnis hängt vom
gesendeten Zeichen und von der Wiederholgeschwindigkeit der Tastatur ab. Ein
Dauerdruck auf die Leertaste erzeugt etwa 56 Hz. Mit dem kleinen u erreicht man
112 Hz, mit dem großen U die maximale Frequenz von 140 Hz. Das U erzeugt mit Chr(85)
ein regelmäßiges Rechtecksignal mit der halben Baudrate, also mit 4800 Hz,
allerdings wird es mit Pausen gesendet, sodass im Mittel eine wesentlich
kleinere Frequenz gemessen wird.
Eine weitere Methode zum Test
des Zählers bietet das Toggle-Signal der Timer-Interruptroutine an B3. Eine
direkte Verbindung von B3 nach B2 führt zur Anzeige der korrekten Frequenz 500
Hz (Abb. 5.3). Der Zähler wurde bis zu Frequenzen von 1 MHz getestet. Die
Genauigkeit ist durch den 8-MHz-RC-Oszillator des ATtiny85 begrenzt und liegt
in der Größenordnung von 1%. Für wesentlich genauere Messungen müsste man einen
Quarzoszillator verwenden.
Abb. 5.2: Die Frequenzanzeige
Abb. 5.3: Anschluss des
Frequenzmessers
Über einen Schutzwiderstand
von 1 kΩ dürfen nun auch andere Signalquellen angeschlossen werden. Für
eine Messung muss das Eingangssignal eine ausreichende Amplitude über 2,5 V
aufweisen.
5.8 Interrupt-Eingang 0
Der Interrupt-Eingang 0 liegt
ebenfalls an B2 und dient dazu, auf externe Ereignisse zu reagieren. Mit Config
Int0 = Falling wird der Interrupt auf negative Flanken angewandt. Auch hier
muss zusätzlich der globale Interrupt freigegeben werden. Jeder Interrupt ruft
dann das Interrupt-Unterprogramm Isr_int0 auf, das einen Impuls an B3 erzeugt.
'Int0.bas
Interrupt 0 an B2 = Rxd
$regfile
= "attiny85.dat"
$crystal
= 8000000
$hwstack
= 8
$swstack
= 4
$framesize
= 4
Dim
I As Byte
Dim
D As Integer
Dim
D2 As Integer
Ddrb.3
= 1
Config
Int0 = Falling
On
Int0 Isr_int0
Enable
Int0
Enable
Interrupts
Do
Waitms 20
Loop
Isr_int0:
Portb.3 = 1
Waitms 10
Portb.3 = 0
Return
End
Listing 5.10: Fallende
Pegel an Int0
Zum Test sollte wieder eine
LED mit passendem Vorwiderstand an B3 angeschlossen sein. Negative Impulse an
B2 können mit einer Drahtbrücke an GND erzeugt werden. Alternativ lassen sich
auch serielle Daten aus dem Terminal nutzen. Jeder Tastendruck erzeugt nur
einen Impuls an der LED, weil die Interrupt-Routine eine Wartezeit von 10 ms
enthält. Erst nach dem Return wird der nächste Interrupt möglich. Wenn ein
serielles Zeichen aus mehreren Impulsen besteht, löst nur die erste fallende
Flanke den Interrupt aus.
5.9 Pin-Change-Interrupt
Ein PCINT0-Interrupt funktioniert
ähnlich wie der Interrupt 0, kann aber auf jeden Port des ATtiny85 angewandt
werden. Jede Pegeländerung eines festgelegten Eingangs löst den Interrupt aus.
Im Beispiel sollen die beiden Eingänge B2 und B4 überwacht werden. Dazu müssen
die entsprechenden Bits im Register PCMSK gesetzt werden (Pcmsk =
&B00010100).
Die Interrupt-Routine Isr_pcint0
wird nun sowohl bei Änderungen an B2 als auch bei Änderungen an B4 aufgerufen,
und zwar sowohl bei steigenden als auch bei fallenden Flanken. Daher muss
jeweils genauer abgefragt werden, um welches Ereignis es sich handelt. Bei
einem Low-Pegel an B2 wird ein kurzer Impuls an B3 erzeugt, bei einem Low-Pegel
an B4 ein längerer.
'PCint0.bas
Pin Change B2 und B4
$regfile
= "attiny85.dat"
$crystal
= 8000000
$hwstack
= 8
$swstack
= 4
$framesize
= 4
Dim
I As Byte
Dim
D As Integer
Dim
D2 As Integer
Ddrb.3
= 1
Ddrb.0
= 1
Portb.4
= 1
Enable
Pcint0
On
Pcint0 Isr_pcint0
Pcmsk
= &B00010100
Enable
Interrupts
Do
Portb.0 = 1
Waitms 500
Portb.0 = 0
Waitms 500
Loop
Isr_pcint0:
If Pinb.2 = 0 Then
Portb.3 = 1
Waitms 10
Portb.3 = 0
Waitms 100
End If
If Pinb.4 = 0 Then
Portb.3 = 1
Waitms 50
Portb.3 = 0
Waitms 100
End If
Return
End
Listing 5.11: Pin-Change-Interrupt
Zum Test kann man serielle
Daten über das Terminal an B2 senden. Jedes Zeichen löst dann einen kurzen
Impuls an B3 aus. B4 kann manuell auf GND gelegt werden, was jeweils einen
längeren Puls an B3 erzeugt. Meist ist es aber schwierig, einen präzisen Impuls
ohne Prellen zu erzeugen. Das Hauptprogramm erzeugt deshalb zusätzlich ein
Testsignal an B0. Eine direkte Verbindung B0-B4 triggert daher regelmäßige
Impulse an B3.
5.10 Watchdog und Power-Down
Der Watchdog-Timer ist ein
unabhängiger Zähler mit einem eigenen RC-Oszillator mit 128 kHz. Er wird oft
zur Überwachung eines Controllers eingesetzt, wobei die Firmware in
regelmäßigen Abständen einen Watchdog-Reset ausführen muss. Wenn dies
unterbleibt und der Watchdog-Timer überläuft, wird ein Hardware-Reset
ausgeführt und damit der Controller neu gestartet. Auf diese Weise verhindert
man Programmabstürze, bei denen der Controller in einem falschen Status hängen
bleibt.
Der Tiny85 verfügt über
verschiedene Stromspar-Modi, darunter den Sleep-Modus mit reduziertem
Stromverbrauch und dem Power-Down-Modus, bei dem fast alle internen Baugruppen
abgeschaltet werden. Der Controller lässt sich mit einem Hardware-Interrupt
wieder aufwecken. Alternativ kann auch der Watchdog-Timer dazu verwendet
werden,
Das Programm erzeugt einen
einzelnen Lichtblitz und geht dann in den Power-Down-Modus. Da vorher der
Watchdog gestartet wurde, ist dies der einzige noch aktive Verbraucher. Er
läuft etwa eine Sekunde lang und startet den Controller dann neu. Dies ist eine
Methode, einen Controller extrem sparsam zu betreiben, weil der Watchdog-Timer
des Tiny85 nur wenige Mikroampere benötigt.
'Watchdog.bas
Blitzer B3
$regfile
= "attiny85.dat"
$crystal
= 8000000
$hwstack
= 8
$swstack
= 4
$framesize
= 4
Config
Portb.3 = 1
Config
Watchdog = 1024
Start
Watchdog
Portb.3
= 1
Waitms
5
Portb.3
= 0
Waitms
1
Powerdown
End
Listing 5.12: Ein
stromsparender LED-Blitzer
Tiny85 Timer-Interrupts von Christian Meilinger
Der ATTiny85 hat nicht so bekannte Funktionen (im Datenblatt
ersichtlich), mittels derer sich die BASCOM-AVR-Programme
TimerInterrupt0 und TimerInterrupt2 erweitern bzw. verfeinern lassen.
Zu 5.3 TimerInterrupt0.bas
Mittels des Original-Programms aus dem Lernpaket kann man eine
minimale Frequenz von etwa 30.5 Hz per Timer0 erreichen. Darüber
hinaus muss mittels einer Softwareschleife gearbeitet werden.
Was in der BASCOM-Hilfe jedoch nicht steht, ist, dass der Timer1 einen
Prescalerwert von bis zu 16384 erlaubt. Mittels Verwendung dieses
Timers und des höchsten Prescalers kann man eine minimale
Blink-Frequenz von 1 Hz ohne Softwareschleife erreichen.
'5.3 TimerInterrupt0_erw.bas
$regfile = "attiny85.dat"
$crystal = 8000000
$hwstack = 8
$swstack = 4
$framesize = 4
Config Portb.3 = Output
Config Timer1 = Timer , Prescale = 16384
Enable Interrupts
Enable Timer1
On Timer1 Tim1isr
Do
Loop
'8 MHz / 16384 / 256 = 1 Hz
Tim1isr:
Toggle Portb.3
Return
End
Zu 5.4 TimerInterrupt2.bas
In diesem Kapitel des Lernpaketes wird eine Wiederholfrequenz von
125 Hz im Timer0 eingestellt, indem ein Prescaler von 256 und zu Beginn
des Interrupts der Timer0 auf den Wert 6 gestellt wird (Zyklisch
zählen von 6 .. 255 und Interrupt bei 255). Neben der Ungenauigkeit
des internen 8 MHz-Oszillators führt aber auch das Stellen des Timers
per Software zu Abweichungen durch die Programmlaufzeit zw.
Interrupt-Start und dem Stellen des Timers.
(Anmerkung B.K.: Wenn der Prescaler groß genug eingestellt
ist, muss es nicht zu Ungenauigkeiten führen. In diesem Fall war
Prescale = 256 eingestellt. Damit hat man 256 Zyklen für den
Interrupt-Aufruf und das Ändern des Zählerstands. Das reicht, ich habe
es bei andern Systemen mit Quarz schon verwendet und nachgemessen. Aber
für viele Aufgaben ist diese neue Methode sehr wertvoll.)
Es
gibt jedoch die Möglichkeit, den Timer1 ausschließlich per
Einstellungen dazu zu bringen, in der Frequenz von 125 Hz eine
Interrupt-Routine aufzurufen. Dabei macht man sich die Register Ocr1c
(Clear Timer1) und Ocr1a (Compare1A) zu Nutze und setzt beide auf den
gleichen Wert 250. Der Timer setzt sich dann nach 250 Counts zurück.
Das Register Ocr1c kann jedoch leider nicht auch einen Interrupt
auslösen, das ist jedoch mittels eines anderen Registers möglich. Beim
Erreichen des Werts des Registers Compare1A kann ein Match-Interrupt
ausgelöst werden.
Beide Register-Einstellungen zusammen ergeben
einen Interrupt im Zyklus von z.B. 250 Counts (Compare1A kann einen
Wert zwischen 0 .. Ocr1c enthalten, ist der Wert nämlich höher, wird
der Interrupt nie aufgerufen, da der Timer1 davor bereits auf 0
zurückgesetzt wird). Es ist somit nicht mehr notwendig, dass man beim
Start des Interrupts den Timer per Software auf 256-250=6 setzt,
wodurch die Genauigkeit nur mehr vom internen 8 MHz-Oszillator abhängt.
Der 8 MHz-Oszillator des ATTiny wird vom Werk bei 25 Grad und 3
V kalibriert und der fix eingestellte Kalibrierwert automatisch nach
jedem Reset in das Register OSCCAL eingetragen. Man kann diesen per
BASCOM auslesen und temporär bis zum nächsten Reset verändern, um die
Frequenz des Oszillators feinzutunen. Da der ATTiny auf der Platine des
Lernpakets mit 5 V betrieben wird und die Zimmertemperatur ebenfalls
abweichen kann, könnte der Oszillator vom Idealwert von 8 MHz
abweichen. Man verstellt dazu das Register OSCCAL in engen Grenzen, um
den Oszillator in der Schaltung für die aktuelle Temperatur zu
kalibrieren.
Mein ATTiny85 des LP hat einen Factory-Wert von
155. Ich konnte ihn in engen Grenzen ±5 verändern, um den Oszillator
feinzutunen, wobei ein kleinerer Wert den Oszillator verlangsamt hat.
Wird der Wert um mehr als 5 verändert, merkt man dies durch starke
Störzeichen im Terminal, wenn man im Programm Werte an das Terminal
sendet.
Das angehängte Programm ist auch insofern verändert,
als dass es die Zeit in Minuten und Sekunden seit dem letzten Reset im
Terminal anzeigt, damit man den Timer besser mit einer Stoppuhr
vergleichen kann. Durch Ausprobieren über die Zeit von 1-2 Minuten oder
mehr kann man den genauesten Wert für OSCCAL ermitteln.
Auch
das Programm Freq1.bas des Kapitels 5.7 kann mittels der gleichen
Technik genauer gemacht werden. Dort wird an der betroffenen Stelle
bereits Timer1 verwendet, was die Anpassung vereinfacht.
'5.4 TimerInterrupt2_erw.bas
$regfile = "attiny85.dat"
$crystal = 8000000
$hwstack = 8
$swstack = 4
$framesize = 4
Dim Ticks1 As Long
Dim Ticks2 As Long
Dim Ticks3 As Long
Open "comb.1:9600,8,n,1" For Output As #1
Config Portb.3 = Output
Print #1 , "Factory Oszillator Calibration Value: ";
Print #1 , Osccal
Osccal = Osccal + 0
Print #1 , "New Oszillator Calibration Value: ";
Print #1 , Osccal
'Configure Timer1 for Interrupt every 1ms using CTC - 8 MHz / 32 / 250 = 1 kHz
'Timer1 has OCR1C for automatically resetting the timer when reaching its value.
'Though the OVF1 interrupt is only working on normal overflow from FF to 00,
' not on clear by OCR1C.
'The Compare1A-Register helps by hitting an interrupt when reaching its value.
'When set to the same value as OCR1C, it will start the interrupt routine every
'OCR1C/Compare1A counts. Lower values allow the interrupt to be hit earlier than
'the clear for shifting the phase.
'Prescaling and counts between timer cycles
Config Timer1 = Timer , Prescale = 256 , Clear_timer = 1
'Count from 0 to 250 then clear
Ocr1c = 250
'Interrupt of Timer1 with Compare1a register comparison hits 4 counts earlier than clear
Ocr1a = 246
On Oc1a Tim1isr
Enable Oc1a
Enable Interrupts
'Reset Timer1clockphase, otherwise the prescaled clock could hit at any time
'between 1 and 31 cycles of the system clock (8 MHz)
Gtccr.psr1 = 1
Ticks1 = 0
Ticks2 = 0
Ticks3 = 0
Do
'Disable Interrupts
'Print #1 , " ";
'Print #1 , Ticks2
'Enable Interrupts
'Waitms 5000
Loop
'8 MHz / 256 / 250 = 125 Hz
Tim1isr:
Ticks1 = Ticks1 + 1
If Ticks1 = 125 Then
Toggle Portb.3
Ticks1 = 0
Ticks2 = Ticks2 + 1
If Ticks2 = 60 Then
Ticks2 = 0
Ticks3 = Ticks3 + 1
End If
Print #1 , Ticks3;
Print #1 , ":";
Print #1 , Ticks2
End If
Return
End