Lernpaket Mikrocontroller Kap 5       

Elektronik-Labor  Lernpakete  AVR  LPmikros                  

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



Elektronik-Labor  Lernpakete  AVR  LPmikros