Natürlich kann man auch einen Soundrecorder-Chip oder gar einen MP3-Player als Türklingel benutzen, die sind inzwischen recht billig zu haben. Aber hier geht es um die Programmierung, nicht um das Endergebnis.
.equ sndein=(1<<ctc1)|(1<<com1a0)+7 ;siehe Datenblatt Seite 29..30) .equ sndaus=0 ;Bitmuster: Timer1 ausschalten .def tonein=r7 ;Wert zum Timer1 einschalten .def tonaus=r8 ;Wert zum Timer1 ausschalten ldi wl,sndein ;Einschalt-Bitmuster Timer1 (für tccr1) mov tonein,wl ;zuweisen ldi wl,sndaus ;Ausschalt-Bitmuster mov tonaus,wl ;zuweisenDer Timer kann nun an jeder Stelle des Programms durch
out tccr1,toneineingeschaltet werden und durch
out tccr1,tonauswieder aus. Dabei wird am Pin OC1A die Tonfrequenz erzeugt, die dem Inhalt des Registers OCR1A entspricht. Da das Löschen des Timers (TCNT1) und das Toggeln des Pins OCR1 automatisch durch Hardware geschieht, muss nichtmal ein Interrupt ausgelöst werden. Demzufolge wird für Timer1 auch keine ISR (Interrupt-Service-Routine) erforderlich.
Wie überredet man nun Timer0 dazu, alle 10ms einen Interrupt austzulösen? Timer0 ist recht einfach gehalten und hat nur den Überlauf-Interrupt, der dann auftritt, wenn der Timer von 255 auf 0 springt. Da der Tiny15 mit 1,6MHz taktet, die ISR des Timer0 mit 100Hz takten soll, muss der Timer0 alle 16000 Takte (1600000Hz / 100Hz) überlaufen. Da der eigentliche Zählumfang nur 256 (von 0 bis 255) beträgt, muss ein Vorteiler vorgeschaltet werden. Dies wird erreicht, indem man in das Steuerregister des Timer0 TCCR0:
den Wert hineinschreibt, den man der Tabelle 9 des Datenblatts:
entnehmen kann. Teilt man nun die benötigten 16000 Takte durch den max. Zählumfang von 256, dann erhält man 62,5. Der nächstliegende Vorteiler in Tabelle 9 beträgt 64. Teilt man die 16000 Takte nun durch den Vorteiler von 64, dann erhält man den erforderlichen Zählumfang des Timer0 von 250 (vorgeteilten) "Ticks".
Um nun den Vorteiler 64 einzustellen, ist der Wert 3 in das Register TCCR0 zu schreiben. Dies wird in der Initialisierung erledigt, denn der Timer0 soll ja ständig durchlaufen und die ISR zyklisch aufrufen. Da nun Timer0 nur aufwärts zählen kann, muss der Startwert des Timers manipuliert werden. Für der Umfang von 250 beträgt dieser 6, nämlich 256-250. Dieser Wert muss in jedem Interrupt neu gesetzt werden. Um dieses zu erleichtern (ISRs sollen möglichst kurz sein), wird ein (unteres) Register mit diesem Wert vorbelegt. Somit braucht man in der ISR nur noch dieses Register nach TCNT0 auszugeben.
Bei den Deklarationen:
.equ zaehlumfang=250 ;Timer0-ZählumfangIn der Reset-Routine:
ldi wl,256-zaehlumfang ;Startwert (Reload) für Timer0 mov tsw,wl ;einrichten und out tcnt0,tsw ;setzen ldi wl,3 ;Vorteiler 64 (DB Seite 27) out tccr0,wl ;für Timer0Damit nun der Timer auch einen Interrupt auslöst, muss noch das Bit TOIE0 im Timer-Interrupt-Maskenregister TIMSK gesetzt werden:
ldi wl,1<<toie0 ;Timer0-Überlauf-Int out timsk,wl ;aktivierenUnd damit Interrupts überhaupt zugelassen werden, muss noch das I-Flag im Statusregister gesetzt werden:
sei ;Interrupts global freigebenDamit nun im Interrupt in die richtige Routine verzweigt wird, müssen die Interrupt-Vektoren eingerichtet werden. Dies sind Sprungbefehle, die zu Beginn des Adressbereiches im Programmspeicher (Flash) angeordnet sind. Anzahl und Zuordnung der Sprungbefehle zu den Adressen ist bei jedem AVR-Typ in Abhängigkeit von der Hardwareausstattung anders und muss dem jeweiligen Datenblatt entnommen werden. Beim Tiny15 sind das:
Um nun die Adressen nicht einzeln definieren zu müssen, fügt man für unbenutzte Interrupts Dummy-Befehle als Platzhalter ein. Nimmt man dafür den Befehl RETI (RETurn from Interrupt), dann wird auch ein versehentlich (durch Programmierfehler) aufgerufener Interrupt sauber und ohne Stackfehler beendet. Die Interrupt-Sprungtabelle für dieses Programm auf dem Tiny15 sieht dann so aus:
.cseg ;Codesegment (Programmcode, Flash) .org 0 ;Startadresse 0 rjmp RESET ;Reset handler reti;rjmp EXT_INT0 ;IRQ0 handler reti;rjmp PIN_CHANGE ;Pin change handler reti;rjmp TIM1_CMP ;Timer1 compare match reti;rjmp TIM1_OVF ;Timer1 overflow handler rjmp TIM0_OVF ;Timer0 overflow handler reti;rjmp EE_RDY ;EEPROM Ready handler reti;rjmp ANA_COMP ;Analog Comparator handler reti;rjmp ADC ;ADC Conversion Handler RESET:Da nur Reset und Timer0-Überlauf genutzt werden, sind alle anderen Sprünge auskommentiert und durch "reti" ersetzt.
In der ISR ist nun der Timer0 wieder auf Startwert zu setzen. Ganz wichtig ist in der ISR auch, dass das SREG (Statusregister mit den Bedingungsflags) gesichert wird und am Ende der ISR wiederhergestellt wird. Beginn und Ende der Timer0-ISR sehen also so aus:
TIM0_OVF: ;ISR Timer0-Überlauf in srsk,sreg ;Statusregister sichern out tcnt0,tsw ;Timer Reload (Startwert setzen) ;...hier folgt die eigentliche Arbeit der ISR, ; also Tasten-Entprellung und Tondauer- und Melodiesteuerung... out sreg,srsk ;Statusregister wiederherstellen reti ;fertig...
Da mechanische Taster sehr stark prellen, ist es erforderlich, diese per Software zu entprellen. Dies geht am einfachsten, indem man die Tasten-Eingänge zyklisch abfragt und neue Werte nur dann übernimmt, wenn sie mehrere Abfragen hintereinander stabil angelegen haben. In der Codesammlung im Mikrocontroller-Forum hat
Peter Dannegger eine
kugelsichere Tastenentprellung für 8 Taster (ein kompletter Port) mit Vierfach-Abfrage veröffentlicht, die sehr effizient arbeitet. Diese Routine läuft in einem Timer-Interrupt im Abstand von 4...20ms. Sie stellt dem Hauptprogramm zwei Register mit Einzelbits für jeden Taster zur Verfügung, von denen das eine den entprellten Status jedes Tasters darstellt, das andere ein "Betätigungsflag" für jeden Taster. Also ein Flag, dass von der Entprellroutine gesetzt wird, wenn ein Taster neu betätigt wurde. Gelöscht wird das Flag erst, wenn auf den Tastendruck reagiert wurde.
Damit die Tastenentprellung zyklisch ausgeführt wird, wird ein Timer-Interrupt benötigt. Damit der Timer-Interrupt auch zur Tondauersteuerung genutzt werden kann, wird sein Abstand auf 10ms (Begründung im nächsten Absatz) festgelegt. Die Erklärung der Entprellroutine findet man hier . Die Entprellung erfordert zwei untere Register als Prellzähler (pz0 und pz1), von denen das Eine die Bit0 aller Bits umfasst und das Andere die Bit1 aller Bits. Ein weiteres unteres Register (tas) enthält den (entprellten) Tastenstatus aller Taster. Die Flags für neu betätigte Tastendrücke liegen in einem oberen Register (tfl), um sie einzeln rücksetzen zu können (Operation mit Konstanten). Da beim Tiny15 nicht alle 8 Bits am Port existieren, bietet es sich an, im Register tfl noch das Busy-Flag unterzubringen, welches gesetzt wird, solange eine Melodieausgabe erfolgt und deswegen keine neue Melodieausgabe begonnen werden darf.
Die (entprellten) Tasten werden in der Hauptschleife des Programms abgefragt, falls das Busy-Flag nicht gesetzt ist. Ist ein Tastendruck erkannt worden, dann wird
Beim Herunterzählen der Tondauer wird zusätzlich auf Übereinstimmung mit der Konstante "stumm" geprüft und bei Übereinstimmung die Tonausgabe deaktiviert. Dies sorgt dafür, dass jeder Ton etwas eher ("stumm" hundertstel Sekunden) abgeschaltet wird, als der nächste Ton aufgerufen wird. Damit wird eine kleine Pause zwischen den Tönen realisiert, die es ermöglicht, innerhalb einer Melodie mehrmals hintereinander den gleichen Ton zu spielen. Ohne diese Pause würde man nur einen langen Ton statt mehrerer gleicher kurzen Tönen hören.
Die Anschlussbelegung des Tiny15 ergibt sich aus den Sonderfunktionen der Pins.
Widerstand zu Vcc Reset Pin1 Pin8 Vcc Versorgungsspannung +5V Taster 4 PB4 Pin2 Pin7 PB2 Taster 2 Taster 3 PB3 Pin3 Pin6 PB1 / OC1A Anschluss Lautsprecher über 10µF GND GND Pin4 Pin5 PB0 Taster 1 Alle Taster und der Lautsprecher werden gegen GND geschaltet...Die Versorgungsspannung ist selbstverständlich mit einem Keramik-Kondensator 100nF abzublocken.
Das Programm hat noch den entscheidenden Nachteil, dass die Ruhestromaufnahme um die 4mA liegt und die Schaltung daher nicht für Batteriebetrieb geeignet ist. Deshalb habe ich eine weitere Variante entwickelt, die zwar nur einen Klingeltaster unterstützt, aber für Batteriebetrieb geeignet ist und über 4 Jumper oder DIP-Schalter einige Einstellungen zulässt.