Hallo!

Im letzten Beitrag habe ich dir den Timer0 als Timer vorgestellt. In diesem Beitrag geht es darum, den Timer0 als Counter einzusetzen. Als Counter kann man den Timer0 dazu verwenden, externe Ereignisse zu zählen. Auch hier kann man wieder nach einem Überlauf den OVF0-Interrupt auslösen lassen. Allerdings kann man, wenn man den Timer0 als Counter verwendet, keinen Prescaler vorschalten.

Als Eingangspin kann nur der T0-Pin (PD4) verwendet werden. Dafür hat man den Vorteil, dass man auf Flanken reagieren kann. Man kann auswählen, ob der Timer bei steigender oder bei fallender Flanke zählen soll.

Üblicherweise zählt man schnelle, digitale Ereignisse und nicht das Drücken eines Tasters. Das wäre Ressourcenverschwendung. Das Drücken eines Tasters ist so langsam, dass man dafür lieber den Befehl DEBOUNCE verwenden sollte. Der Vorteil des Counters ist, dass der Zählvorgang den Programmablauf nicht beeinflusst. Noch ein Vorteil ist, dass man den µC schlafen legen kann, während der Counter im Hintergrund unbeeinflusst weiterzählt. Ein OVF0-Interrupt weckt den µC zum Arbeiten kurz wieder auf und danach kann er wieder schlafen gelegt werden. In so einem Fall ist das Zählen von "langsamen" Ereignissen mit dem Timer0 natürlich keine Ressourcenverschwendung sondern überlegtes Handeln. ;-)

Ich muss zugeben, dass ich mir ziemlich schwer tue, ein gutes Beispiel für den Counter zu finden. Nehmen wir mal an, ich möchte einen Tastendruck zählen. Dann würde ich das mit DEBOUNCE erledigen. Und wenn ich einfach zur zählen möchte, wie oft ein Ereignis auftritt, dann kann ich das über einen Interrupt (INT0 oder INT1) wunderbar erledigen. Dafür brauche ich den Counter nicht.

Der Counter wird oft erst dann interessant, wenn sehr schnelle Ereignisse gezählt werden sollen. Etwas, was das Hauptprogramm ständig unterbrechen bzw. aufhalten würde. Man kann mit dem Counter Ereignisse zählen, die bis zu Systemtakt / 2,5 schnell sind. Wenn der Systemtakt auf 1 Mhz eingestellt ist, dann kann der Counter maximal 400.000 Ereignisse die Sekunde zählen. Ereignisse mit höheren Frequenzen würden verloren gehen. Das Erkennen einer Flanke braucht in etwa 2,5 bis 3,5 Systemticks. Wenn man den ATmega8 voll ausrüstet und mit einem 16 MHz Quarz versieht, dann können in der Sekunde bis zu 6,4 Millionen Ereignisse gezählt werden.

In den folgenden Beispielen handelt es sich nicht um Beispiele aus der realen Welt, sondern um Beispiele die man selber nachbauen und ausprobieren kann. Ich dachte mir, dass man so den Counter und dessen Möglichkeiten besser verstehen kann. Denn je besser man die Möglichkeiten des µC kennen lernt, desto vielseitiger lässt er sich einsetzen. Außerdem habe ich darauf geachtet, auch ein paar Dinge die bereits gezeigt wurden in den Beispielen zu wiederholen. Und wie immer ist die BASCOM-Hilfe der erste Anlaufpunkt, wenn man einen Befehl nicht kennt. Man kann die Problemstellungen der Beispiele auch anders lösen. Aber hier geht es um den Timer0 als Counter. :-)

Counter0 als Frequenzteiler

Eine recht nützliche Sache ist, dass man nicht auf jedes Ereignis reagieren muss. Man kann den Counter eine gewisse Menge an Ereignissen zählen lassen und z.B. nur auf jedes hundertste Ereigniss reagieren. Das entlastet den µC bei hohen Frequenzen enorm. Als Counter kann man den Timer0, genau so wie als Timer, nach 256 Ticks überlaufen lassen. Und dabei kann der OVF0-Interrupt ausgelöst werden. Und wenn man den Timer0 vorher z.B. auf 156 stellt, dann wird der OVF0-Interrupt (alias TIMER0) nach exakt 100 Zählereignissen ausgelöst. So kann der Timer0 z.B. zum Teilen von Frequenzen verwendet werden. Wenn man die Eingangsfrequenz zum Beispiel durch 100 teilen und an einem Ausgangspin zur Verfügung stellen möchte, dann wird dafür das Hauptprogramm des Mikrocontrollers nur alle 50 Zählereignisse unterbrochen um den Ausgangspin zu toggeln. Man kann sich sicher vorstellen, dass so etwas bei einer Eingangsfrequenz von angenommen 1 Mhz, das Hauptprogramm merkbar entlastet.

timer_0_als_frequenzteiler_v01.gif
$regfile = "m8def.dat"
$crystal = 8000000
$hwstack = 100
$swstack = 100
$framesize = 100

'Diese Konstante gibt an, nach wievielen Zählereignissen der Counter überlaufen soll.
'   Presetvalue = gewünschter Teiler / 2
'Bei 1 würde die Frequenz halbiert werden. Bei 2 würde die Frequenz geviertelt
'werten. Und bei 50 wäre am Ausgang immer ein Hundertstel der Eingangsfrequenz.
'   1/2 = 1
'   1/4 = 2
'   1/6 = 3
'   1/100 = 50
'   1/512 = 256
Const Presetvalue = 1

'Eingang
Config Pind.4 = Input       '=T0

'Ausgang
Config Portc.0 = Output

'Timer0/Counter0 einstellen und Überlauf-Interrupt aktivieren
Config Timer0 = Counter , Edge = Rising
On Timer0 On_timer0

Load Timer0 , Presetvalue

Enable Timer0
Enable Interrupts


Do
   !nop
Loop

END


'Wenn dieser Interrupt-Handler aufgerufen wird, dann wird zuerst der
'Wert des Timer0 so weit hoch gesetzt, dass der Timer0/Counter0
'erst wieder nach nach <Presetvalue> Zählereignissen ausgelöst wird.
On_timer0:
   'Mit dem Befehl LOAD wird der Wert des Timer0 gesetzt.
   Load Timer0 , Presetvalue
   'Ausgangspin toggeln
   Toggle Portc.0
Return

So funktioniert das aber nur, wenn man ein schönes Eingangssignal hat. Damit meine ich ein Signal, das bei LOW nach GND zieht und bei HIGH nach VCC. Ist das nicht der Fall, dann kann man mit einem PullUp- oder PullDown-Widerstand am Eingang für klare Verhältnisse sorgen (je schneller das Signal, desto kleiner muss der Widerstand sein).

Wenn man dieses Beispiel mit einem Taster ausprobieren möchte, dann muss man bedenken, dass ein Taster prellt. In so einem Fall empfehle ich, den Eingang mit einem Kondensator zu entprellen, den Taster gegen GND schalten zu lassen und den eingebauten PullUp-Widerstand des Pins zu aktivieren.

timer_0_als_frequenzteiler_mit_taster_v01.gif
$regfile = "m8def.dat"
$crystal = 8000000
$hwstack = 100
$swstack = 100
$framesize = 100


'Diese Konstante gibt an, nach wievielen Zählereignissen der Counter überlaufen soll.
'   Presetvalue = gewünschter Teiler / 2
'Bei 1 würde die Frequenz halbiert werden. Bei 2 würde die Frequenz geviertelt
'werten. Und bei 50 wäre am Ausgang immer ein Hundertstel der Eingangsfrequenz.
'   1/2 = 1
'   1/4 = 2
'   1/6 = 3
'   1/100 = 50
'   1/512 = 256
Const Presetvalue = 1

'Eingang
Config Pind.4 = Input       '=T0
Pind.4 = 1       'Pullup-Widerstand aktivieren
'kurz warten, um dem Entprellkondensator des Tasters genug Zeit zum Laden zu geben
Waitms 1

'Ausgang
Config Portc.0 = Output

'Timer0/Counter0 einstellen und Überlauf-Interrupt aktivieren
Config Timer0 = Counter , Edge = Falling
On Timer0 On_timer0

Load Timer0 , Presetvalue

Enable Timer0
Enable Interrupts


Do
   !nop
Loop

END


'Wenn dieser Interrupt-Handler aufgerufen wird, dann wird zuerst der
'Wert des Timer0 so weit hoch gesetzt, dass der Timer0/Counter0
'erst wieder nach nach <Presetvalue> Zählereignissen ausgelöst wird.
On_timer0:
   'Mit dem Befehl LOAD wird der Wert des Timer0 gesetzt.
   Load Timer0 , Presetvalue
   'Ausgangspin toggeln
   Toggle Portc.0
Return

Da der Taster nach GND zieht wenn er gedrückt wird, habe ich im Code den Timer0 so eingestellt, dass dieser bei "fallender" Flanke zählt.

Zählen von (schnellen) externen Ereignissen

Abschließend möchte ich noch mit einem Beispiel zeigen, wie der Counter den µC beim Zählen von schnellen Ereignissen entlasten kann. Damit man es nachvollziehen kann, bleibe ich bei der Schaltung mit dem Taster (PullUp; Taster nach GND). Um den gezählten Wert anzeigen zu können, schicke ich ihn über die UART zum Computer. Und damit man sieht wieviele Ereignisse in den letzten fünf Sekunden gezählt wurden, wird dieser Wert auch noch ermittelt und in Klammern mit angezeigt.

timer0_externe_ereignisse_zaehlen_mit_taster_und_max232_v01.gif
$regfile = "m8def.dat"
$crystal = 8000000
$hwstack = 100
$swstack = 100
$framesize = 100
$baud = 4800

'Diese Konstante gibt an, nach wievielen Zählereignissen der Counter überlaufen soll.
'Gibt man hier ``1`` an, dann wird jedes Ereignis angezeigt. Gibt man hier ``10`` an,
'dann wird zwar jedes Ereignis gezählt, aber nur jedes 10. Ereignis führt dazu, dass
'es in den angezeigten Wert (counted_value) übernommen wird.
Const Interval = 10

'Eingang
Config Pind.4 = Input       '=T0
Pind.4 = 1       'Pullup-Widerstand aktivieren

'Ausgang
Config Portc.0 = Output

'Zählervariable
Dim Counted_value As Long       'LONG: bis 2147483647
Dim Counted_value_saved As Long
Dim Delta As Long

'Timer0/Counter0 einstellen und Überlauf-Interrupt aktivieren
Config Timer0 = Counter , Edge = Falling
On Timer0 On_timer0

Load Timer0 , Interval

Enable Timer0
Enable Interrupts


Do
   'Mit ``DISABLE INTERRUTPS`` wird sichergestellt, dass kein Interrupt-Handler
   'den Wert der Variable COUNTED_VALUE verändert, während damit gerechnet wird.
   Disable Interrupts

   'herausfinden, wieviele Zählereignisse seit der letzten Berechnung auftraten
   'und den neuen Zählstand sichern
   Delta = Counted_value - Counted_value_saved
   Counted_value_saved = Counted_value

   'Der Befehl ``ENABLE INTERRUPTS`` gibt die Interruptroutinen wieder frei.
   'Sollte in der Zwischenzeit ein Interrupt ausgelöst worden sein, wird dieser
   'Interrupt jetzt nachgeholt.
   Enable Interrupts

   Print Counted_value_saved ; " (" ; Delta ; ")"

   Wait 5
Loop

END


On_timer0:
   'Mit dem Befehl LOAD wird der Wert des Timer0 gesetzt, damit der Timer
   'nach <Interval> Zählereignissen wieder überläuft.
   Load Timer0 , Interval

   'Zählervariable um <Interval> erhöhen
   Counted_value = Counted_value + Interval
Return

Je höher man den Wert der Konstante Interval einstellt (1-255), desto mehr wird der µC entlastet.


Und abschließend möchte ich noch betonen, dass es sich hier nur um Beispiele handelt, die die Arbeit mit dem Counter verdeutlichen sollen. Natürlich wäre es super, wenn du die Beispiele selbst nachbaust und nachvollziehen kannst. Die "Best Praxis"-Lösung gibt es nicht. Jedes Problem kann auf verschiedene Arten gelöst werden. Der "Timer0 als Counter" ist eine davon.


lg Gerold :-)



Den zugehörigen Original-Beitrag findest du im Loetstelle-Forum.