; Test 4: Board kennenlernen, Timer im Interupt mode ; Was hier Neues zu lernen ist: ; - Timer im Interrupt modus ; - Interrupts, Interrupt-Vektoren ; - BCD-Arithmetik .NOLIST .INCLUDE "C:\avrtools\appnotes\8515def.inc" .LIST ; Universalregister definieren .DEF mp = R16 ; Zähler Anzahl Nulldurchgänge, MSB Zähler, Software-Zähler .DEF z1 = R0 ; Arbeitsregister für die Interrupt-Service-Routine .DEF ri = R1 ; Register zum Zählen der Sekunden, gepackte BCD-Ziffern .DEF sec = R2 ; Reset-Vektor auf Adresse 0000 rjmp main ; Interrupt-Vektoren, fast alle blindgesetzt ausser dem Timer-Overflow ; RETI ist Rückkehr vom Interrupt mit Wiederherstellung des Interrupt- ; Flags im Status-Register. Wichtig: Sprung zur Interrupt-Service- ; Routine tc0i muss an der Adresse 0007 stehen. ; Mechanismus des Interrupts: Ist der Timer von 255 auf Null überge- ; laufen, dann wird das Programm unterbrochen, der Programmzähler auf ; dem Stapel abgelegt, der Befehl an der Adresse 0007 ausgeführt. Nach ; Beendigung des Interrupts wird der Programmzähler wieder hergestellt ; und mit dem unterbrochenen Programm fortgefahren. ; Die Interrupt-Vektoren zu je 1 Byte: reti ; Int0-Interrupt reti ; Int1-Interrupt reti ; TC1-Capture reti ; TC1-Compare A reti ; TC1-Compare B reti ; TC1-Overflow rjmp tc0i ; Timer/Counter 0 Overflow, mein Sprung-Vektor reti ; Serial Transfer complete reti ; UART Rx complete reti ; UART Data register empty reti ; UART Tx complete reti ; Analog Comparator ; Interrupt-Service-Routine für den Zähler tc0i: in ri,SREG ; Rette den Inhalt des Flag-Registers inc z1 ; Erhöhe Software-Zähler mit Bit 8-15 out SREG,ri ; Stelle Flag-Register wieder her reti ; Kehre vom Interrupt zurück ; Hauptprogramm beginnt hier main: ldi mp,LOW(RAMEND) ;Initiate Stackpointer out SPL,mp ; wegen Interrupts und Unterprogr. ldi mp,HIGH(RAMEND) out SPH,mp ; Software-Zähler-Register auf Null setzen ldi mp,0 ; z1 kann nicht direkt gesetzt werden, da R0 mov z1,mp ; Kopieren von 0 in den Software-Zähler mov sec,mp ; und Sekundenzähler auf Null ; Vorteiler des Zählers = 256, 4 MHz/256 = 15625 Hz = $3D09 ldi mp,0x04 ;Initiate Timer/Counter 0 Vorteiler out TCCR0,mp ; an Timer 0 Control Register ; Port B ist LED-Port ldi mp,0xFF ; alle Bits als Ausgang out DDRB,mp ; in Datenrichtungsregister ; Interrupts bei Timer 0 einschalten ldi mp,$02 ; Bit 1 setzen out TIMSK,mp ; in Timer Interupt Mask Register ; Eigentlich könnte dieser Befehl folgendermassen aussehen: ; SBI TIMSK,TOIE0 ; Timer Interrupt Mask Flag setzen ; Das geht aber nicht, weil SBI nur bei Portnummern bis 0x1F ; geht, TIMSK liegt aber darüber. Deshalb der Umweg. ; Alle Interrupts allgemein freigeben sei ; Gib Interrupts im Status-Register frei ; Hauptprogramm-Loop fragt oberes Byte des Zählers ab, bis dieser ; hex 3D erreicht hat. Dann den Timer, bis dieser 09 erreicht hat ; (eine Sekunde = dez 15625 = hex 3D09 Zählerimpulse). Die Zähler ; werden auf Null gesetzt und eine Sekunde weitergezählt. Die Se- ; kunden werden als gepackte BCD-Zahl behandelt (eine Ziffer zu ; vier Bit, 1 Byte entspricht zwei Ziffern), Die Sekunden werden ; bei Erreichen von 60 wieder auf Null gesetzt. Der Sekundenstand ; wird auf den LEDs ausgegeben. loop: ldi mp,$3D ; Vergleichswert MSB loop1: cp z1,mp ; Vergleiche mit MSB Zählerstand brlt loop1 ; z1 < mp, weiter warten loop2: in mp,TCNT0 ; Zähler LSB lesen cpi mp,$09 ; mit LSB vergleichen brlt loop2 ; TCNT0 < 09, weiter warten ldi mp,0 ; Null out TCNT0,mp ; in Hardware-Zähler LSB mov z1,mp ; und in Software-Zähler MSB rcall IncSec ; Unterprogramm Sekunden-Zähler erhöhen rcall Display ; Unterprogramm Sekunden an LED ausgeben rjmp loop ; Das Ganze von Vorne ; Unterprogramm eine Sekunde zum Sekundenzähler ; in BCD-Arithmetik! Unteres Nibble = Bit 0..3, Oberes N. = 4..7 IncSec: sec ; Setze Carry-Flag für Addition ldi mp,6 ; Provoziere Überlauf unteres Nibble adc sec,mp ; beim Addieren von 6 + 1 (Carry) brhs Chk60 ; Springe zum 60-Check, wenn Überlauf sub sec,mp ; Ziehe die 6 wieder ab Chk60: ldi mp,$60 ; Vergleiche mit 60 cp sec,mp brlt SecRet ; Springe, wenn kleiner ldi mp,256-$60 ; Lade Rest auf Null add sec,mp ; Addiere auf Null SecRet: ret ; Kehre zum Hauptprogramm zurück ; Unterprogramm zur Ausgabe des Sekundenzählers an die LEDs Display: mov mp,sec ; Zählerstand nach mp kopieren com mp ; Einer-Komplement = XOR(FF) wg. Lampen out PORTB,mp ; Software-Zählerstand an LEDs ret ; Zurück zum Hauptprogramm