Bascom-AVR

Kleiner Bascom AVR Kurs -
Timer0 als Timer (Codebeispiel: Sirene)

Hallo!

Im letzten Beitrag hatte ich dich schon auf Timer eingestimmt. Jetzt geht es an's Eingemachte. :-) Lass' uns doch mal den einfachsten Timer des ATmega8 ansehen -- Timer0. Dieser Timer kann von 0 bis 255 zählen. Und bei jedem Überlauf (255 -> 0) kann ein Interrupt (OVF0) ausgelöst werden.

Als "Timer" zählt der Timer0 den Systemtakt. Wenn man keinen Prescaler einstellt, dann wird bei jedem Tick des Systemtakts auch der Wert des Timers um 1 erhöht. Würde man 8 als Prescaler einstellen, dann erhöht sich der Wert des Timers nur alle 8 Ticks.

Da der Timer beim 256. Tick wieder nach 0 überläuft, würde der OVF0-Interrupt bei einem Systemtakt von 8 MHz genau 31.250 mal ausgelöst werden. Würde man im OVF0-Handler einen Pin toggeln, dann hätte man am Ausgangspin eine Frequenz von 15.625 Hz anliegen. Das wäre eine Frequenz, die man nicht hören würde, wenn man sie an einem Lautsprecher anlegt.

Würde man das gleiche Spiel mit einem Prescaler von 8 spielen, dann würde der OVF0-Interrupt bei einem Systemtakt von 8 MHz genau 3.906,25 mal ausgelöst werden. Wie man sieht, ist das keine Ganzzahl mehr. Das liegt an dieser Rechnung:

8.000.000 (Systemtakt) / 8 (Prescaler) = 1.000.000
1.000.000 (Counts) / 256 (Überlauf) = 3906,25

Toggelt man auch in diesem Fall im OVF0-Handler einen Pin, dann würde am Ausgangspin eine Frequenz von 1.953,125 Hz. anliegen. Steuert man mit diesem Ausgangspin einen Summer (Buzzer) an, dann erhält man einen ziemlich lauten (fast 2 kHz) Ton.

Mach dich bereit, nach dem Übertragen dieses Programmes den Strom vom Board zu nehmen. :-) Dieser Ton kann ziemlich unangenehm sein (Ohren zuhalten und Kampfhunde anketten). :-)

sirene_atmega8_summer_v03.gif
$regfile = "M8def.dat"
$crystal = 8000000
$hwstack = 100
$swstack = 100
$framesize = 100


'Buzzer des Atmel Evaluationsboard
'http://www.pollin.de/shop/downloads/D810038B.PDF
Buzzer Alias Portd.7
Config Buzzer = Output

'Timer0 konfigurieren
'8000000 (Systemtakt) / 8 (Prescaler) = 1000000 (Counts)
Config Timer0 = Timer , Prescale = 8

'Timer0 Overflow Interrupt
'Dieser Interrupt wird beim Überlauf (255 nach 0) ausgelöst
'1000000 (Counts) / 256 (8-Bit Timer) = 3906,25 (Interrupts die Sekunde)
On OVF0 On_ovf0
Enable OVF0

Enable Interrupts


Do
   !NOP
Loop

End


'Timer0 Overflow Interrupt
'Zwei mal toggeln entspricht einem Impuls. 3906,25 mal toggeln gibt auf dem
'Buzzer einen Ton mit 1953,125 Hertz.
On_ovf0:
   Toggle Buzzer
Return

Hier ein etwas angenehmeres Programm: :-)

$regfile = "M8def.dat"
$crystal = 8000000
$hwstack = 100
$swstack = 100
$framesize = 100


'Taster1 auf Atmel Evaluationsboard (mit eigenem Pulldown auf der Platine)
Taster Alias Pind.2
Config Taster = Input

'Buzzer des Atmel Evaluationsboard
'http://www.pollin.de/shop/downloads/D810038B.PDF
Buzzer Alias Portd.7
Config Buzzer = Output

'Timer0 konfigurieren
'8000000 (Systemtakt) / 64 (Prescaler) = 125000 (Counts)
Config Timer0 = Timer , Prescale = 64

'Timer0 Overflow Interrupt
'Dieser Interrupt wird beim Überlauf (255 nach 0) ausgelöst
'125000 (Counts) / 256 (8-Bit Timer) = 488,28125 (Interrupts die Sekunde)
On Timer0 On_timer0_overflow
Enable Timer0

Enable Interrupts


Do
   !NOP
Loop

End


'Timer0 Overflow Interrupt
'Wenn der Taster gedrückt wurde, dann wird der Buzzer-Pin getoggelt.
'Zwei mal toggeln entspricht einem Impuls. 488 mal toggeln gibt auf dem Buzzer
'einen Ton mit 244 Hertz aus.
On_timer0_overflow:
   If Taster = 1 Then
      Toggle Buzzer
   Else
      Buzzer = 0
   End If
Return

Wie man im Beispiel sieht, kann man statt OVF0 auch das Alias TIMER0 verwenden.

Die oben gezeigten Beispiele demonstrierten schon mal das Grundprinzip aller Timer. Man kann damit zählen und beim Überlauf einen Interrupt auslösen. Man kann den aktuellen Wert des Timers aber auch auslesen und schreiben. Wenn man den Wert des Timers setzt, dann zählt der Timer ab diesem Wert nach oben. Je höher man diesen Timer0-Wert setzt, desto weniger Zeit vergeht bis zum Überlauf des Timers. Das kann man nutzen um die Frequenz genauer einzustellen.

Sirene

Das folgende Beispiel demonstriert wie man den Wert des Timer0 bei jedem Überlauf (im Overflow-Interrupt) neu setzen kann. Das Beispiel soll eine Sirene darstellen. Dabei wird der Buzzer des Pollin-Evaluationsboards oder des Testboards von Roland Walter angesteuert. Bitte keinen Lautsprecher direkt anschließen. Das kann den ATmega8 zerstören.

Der Vorwahlwert für den Timer0 wird in der Mainloop in zwei For-Schleifen rauf und runter gezählt. Im Interrupt-Handler wird dieser Wert verwendet um den Timer0-Wert neu zu setzen. Bis der Interrupt-Handler ausgeführt wird, kann unterschiedlich viel Zeit vergehen, je nachdem was der µC gerade machen muss. Weiters sichert der µC vor jedem Interrupt-Handler die AVR-Register. Das benötigt bei jedem Anspringen des Interrupt-Handlers zusätzliche 66 Ticks. Man kann sich also ausrechnen, dass der Timer0 in der Zwischenzeit -- wenn kein Prescaler eingestellt wäre -- bereits mehr als 66 Ticks hochgezählt hat. Würde man dem neuen Wert des Timer0 die bereits vergangenen Ticks nicht anrechnen, dann würde die Frequenz des Überlauf-Interrupts schwanken und zu langsam sein.

Das Setzen des neuen Timer0-Wertes braucht leider auch ein paar Ticks. Diese Ticks muss man dazuzählen wenn die Frequenz genau sein soll. Das Wort "genau" ist vielleicht ein wenig übertrieben. Mit dem Timer0 kann man nur dann eine "sehr genau einstellbare" Frequenz erzeugen, wenn man den fertigen Maschinencode analysieren kann um herauszufinden, wie viel Ticks dafür benötigt werden. Da ich es selten so genau brauche, gehe ich einfach von 9 Ticks aus (so habe ich es in einem Fachbuch gelesen). Diese Ticks zähle ich gleich beim Auslesen des aktuellen Timer0-Wertes mit dazu.

Es gilt also: Mit dem Timer0 kann man nur dann eine "sehr genau einstellbare" Frequenz erzeugen, wenn man den fertigen Maschinencode analysieren kann. Wem das zu umständlich ist, der sollte für "sehr genaue" Frequenzen den Timer1 oder Timer2 verwenden.

$regfile = "M8def.dat"
$crystal = 8000000
$hwstack = 100
$swstack = 100
$framesize = 100


'Buzzer des Atmel Evaluationsboard
Buzzer Alias Portd.7
Config Buzzer = Output

'Timer0 konfigurieren
'8000000 (Systemtakt) / 64 (Prescaler) = 125000 (Counts)
Config Timer0 = Timer , Prescale = 64

'Timer0 Overflow Interrupt
'Dieser Interrupt wird beim Überlauf (255 nach 0) ausgelöst
On Timer0 On_timer0_overflow
Enable Timer0

Enable Interrupts

Dim Timer0_vorwahl As Byte
Dim Current_timervalue As Byte


Do
   'schnell hochzählen
   For Timer0_vorwahl = 80 To 150 Step 1
      Waitms 5
   Next Timer0_vorwahl

   'den hohen Ton etwas herausziehen
   Waitms 1500

   'etwas langsamer runterzählen
   For Timer0_vorwahl = 150 To 80 Step -1
      Waitms 30
   Next Timer0_vorwahl
Loop

End


'Timer0 Overflow Interrupt - den Buzzer-Pin toggeln
'
'Zwei mal toggeln entspricht einem Impuls.
'Vorher wird der Timer auf einen Wert eingestellt, ab dem der Timer0
'nach oben zählen soll. Je höher dieser Wert, desto weniger Zeit braucht der
'Timer0 um von 255 auf 0 überzulaufen.
'Damit die in der Zwischenzeit gezählten Ticks in der Rechnung berücksichtigt werden,
'wird zuerst der aktuelle Timerwert ausgelesen und zum Vorwahlwert dazugezählt.
'Das Setzen des neuen Timer0-Wertes braucht leider auch ein paar Ticks. Es sind
'wahrscheinlich so um die 9 Ticks. Um trotzdem eine ziemlich genaue Frequenz
'zu erzeugen, werden die dafür benötigten 9 Ticks gleich mit dazugezählt.
On_timer0_overflow:
   'aktuellen Stand auslesen und 9 Ticks dazuzählen
   Current_timervalue = Timer0 + 9
   'Timerwert neu setzen
   Timer0 = Timer0_vorwahl + Current_timervalue
   'Pin toggeln
   Toggle Buzzer
Return

Das gleiche Programm läuft auch auf dem ATmega16. Man muss nur die Regfile-Zeile anpassen: $regfile = "M16def.dat"

Im Schaltplan habe ich statt dem Buzzer einen kleinen 0,5 Watt Lautsprecher angeschlossen. Der Lautsprecher hat 8 Ohm. Wenn dein Lautsprecher weniger Widerstand hat, dann musst du den Vorwiderstand (vor dem Lautsprecher) entsprechend erhöhen.

Um die Last des Lautsprechers schalten zu können, habe ich im Schaltplan den Transistor BC548C eingesetzt. Dieser kann kurzfristig bis zu 500 mA schalten. Der ATmega16 wäre zu schwach dafür. Wenn du keinen BC548C hast, dann kannst du auch den 2N2222 dafür verwenden. Nimm für den 2N2222 aber statt dem 4,7k Widerstand einen 1k Widerstand. Sonst ist nicht sichergestellt, dass der Transistor voll durchschaltet. Und ist das nicht der Fall, dann wird er eventuell heiß.

sirene_atmega16_lautsprecher_v01.gif

Viel Vergnügen beim Ausprobieren!


lg Gerold :-)



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