( Fortsetzung Interruptkurs)
--- Initialisierung 1000: LDA #$01 ;Sprite 0 1002: STA $D015 ; einschalten und 1005: STA $D027 ; Farbe auf Weiß 1008: LDA #$3F ;Sprite-Pointer auf 100A: STA $07E8 ; $3F*$40=$0FC0 100D: LDA #$64 ;X-/Y-Position des 100F: STA $D000 ; Sprites auf 1012: STA $D001 ; 100/100 setzen 1015: LDX #$0E ;Rahmen- und Hinter- 1017: LDY #$06 ; grundfarbe auf 1019: STX $D020 ; Hellblau und 101C: STY $D021 ; Dunkelblau 101F: LDA #$7F ;CIA-B-Interrupt-Quellen 1021: STA $DD0D ; sperren 1024: LDA #$00 ;Timer A von CIA-B 1026: STA $DD0E ; anhalten. 1029: LDX #$48 ;Startadresse für neuen 102B: LDY #$10 ; NMI (=$1048) im 102D: STX $0318 ; Pointer bei $0318/0319 1030: STY $0319 ; ablegen. 1033: LDX #$49 ;Timerzählwert=$8049 1035: LDY #$80 ; (30 Mal pro Sekunde) 1037: STX $DD04 ; in Timerzählerregister 103A: STY $DD05 ; (Timer A) schreiben 103D: LDA #$81 ;Timer A als Interrupt- 103F: STA $DD0D ; quelle erlauben. 1042: LDA #$11 ;Timer starten (mit 1044: STA $DD0E ; FORCE-LOAD-Bit) 1047: RTS ;ENDE --- Interruptroutine 1048: PHA ;Akku retten 1049: TXA ;X in Akku 104A: PHA ; und retten 104B: TYA ;Y in Akku 104C: PHA ; und retten 104D: LDA $DD0D ;ICR auslesen 1050: BNE $1058 ;Wenn<>0, dann CIA-NMI 1052: INC $D020 ;Sonst NMI von RESTORE- ;Taste. Rahmenfarbe erh. 1055: JMP $1073 ;Und auf Ende NMI spr. 1058: LDA $DC01 ;CIA-NMI: Joyport2 ausl. 105B: LSR ;Bit in Carry-Flag rot. 105C: BCS $1061 ;Wenn gelöscht Joy hoch 105E: DEC $D001 ;SpriteX-1 1061: LSR ;Bit in Carry-Flag rot. 1062: BCS $1067 ;Wenn gel., Joy runter 1064: INC $D001 ;SpriteX+1 1067: LSR ;Bit in Carry-Flag rot. 1068: BCS $106D ;Wenn gel. Joy rechts 106A: INC $D000 ;SpriteY+1 106D: LSR ;Bit in Carry-Flag rot. 106E: BCS $1073 ;Wenn gel. Joy links 1070: DEC $D000 :SpriteY-1 1073: PLA ;Y-Reg vom Stapel 1074: TAY ; holen 1075: PLA ;X-Reg vom Stapel 1076: TAX ; holen 1077: PLA ;Akku vom Stapel holen 1078: CLI ;IRQs wieder erlauben 1079: RTI ;RETURN FROM INTERRUPT --- Eigenen NMI entfernen 107A: LDA #$7F ;Alle NMI-Quellen von 107C: STA $DD0D ; CIA-B sperren 107F: LDA #$00 ;Timer A von CIA-B 1081: STA $DD0E ; stoppen 1084: LDX #$47 ;Alten NMI-Vektor 1086: LDY #$FE ; (bei $FE47) wieder in 1088: STX $0318 ; $0318/$0319 108B: STY $0319 ; eintragen 108E: LDA #00 ;Sprite 0 1090: STA $D015 ; ausschalten 1093: RTS ;ENDE
Das Programm starten Sie mit SYS4096 .
Hieraufhin sehen Sie einen Sprite-Pfeil
auf dem Bildschirm, der mit einem Joystick in Port 2 über den Bildschirm bewegt werden kann. Gleichzeitig ist jedoch weiterhin die normale Tastaturabfrage des C64 aktiv. Die beiden Prozesse
laufen scheinbar gleichzeitig ab, weil
wir einen zweiten Interrupt generiert
haben, der den Joystick abfragt und entsprechend das Sprite bewegt. Kommen wir
nun zur Programmbeschreibung:
In den ersten Zeilen, von $1000 bis
$101 E wird zunächst einmal das Sprite
initialisiert und die Bildschirmfarben
auf die gängige Kombination hellblau/ dunkelblau gesetzt. Hiernach folgt die
NMIund Timerinitialisierung. Da wir
auch hier verhindern müssen, daß ein NMI
auftritt, während wir den NMI-Vektor verändern, wird der Wert $7 F in das ICR
geschrieben. Dieser Wert löscht alle
Interruptquellen-Bits, womit kein NMI
mehr von CIA-B ausgelöst werden kann. Da
sie der einzige Chip ist, der NMIs
auslöst, können wir sicher sein, daß
tatsächlich keiner dieser Interrupts
aufgerufen wird. Beachten Sie bitte, daß
der SEI-Befehl hier wirkungslos wäre, denn er sperrt lediglich die IRQs. NMIs
sind nicht abschaltbar, weswegen wir sie
auf diese umständliche Art und Weise
unterbinden müssen. Hiernach halten wir
Timer A an, indem wir einen Wert in CRA
schreiben, der das 0 . Bit gelöscht hat, woraufhin die CIA den Timer stoppt. Dies
müssen wir tun, damit der Timer korrekt
mit seinem Anfangszählwert gefüttert
werden kann. Würden wir nämlich das
Low-Byte geschrieben haben, das High-Byte jedoch noch nicht, während ein Unterlauf des Timers stattfindet, so würde
er mit sich mit einem falschen Anfangswert initialisieren.
In den folgenden Zeilen wird nun der
NMI-Vektor bei $0318/$0319 auf unsere
eigene NMI-Routine bei $1048 verbogen.
Hiernach wird der Timer mit seinem Zählwert initialisiert. Er beträgt $8049 .
Wie wir auf diesen Wert kommen, möchte
ich Ihnen nun erläutern: Wir hatten ja
schon festgestellt, daß wir den Timer
vorwiegend die Takzyklen des 64 ers zählen lassen möchten. Selbige sind der
Taktgeber für den Prozessor. Der Quarz
der sie erzeugt generiert zusammen mit
einigen Bausteinen um ihn herum einen
Takt von genau 985248 .4 Impulsen pro
Sekunde, die auch in Herz ( Hz) gemessen
werden. Dieser Wert erntspricht also
annähernd einem MHz. Um nun zu ermitteln, wieviele Taktzyklen gezählt werden
müssen, damit der Timer genau 30 Mal pro
Sekunde einen Interrupt erzeugt, müssen
wir diesen Wert lediglich durch 30 dividieren. Das Ergebnis hiervon ist genau
32841 .6, was $8049 entspricht! Um nun
z. B.50 Interrupts pro Sekunde zu erzeu- gen dividieren Sie einfach durch 50, usw. Auf diese Weise können Sie die Joystickabfrage auch beschleunigen oder
verlangsamen, denn das Sprite wird bei
betätigtem Joystick immer um genau soviele Pixel pro Sekunde verschoben, wie
Interrupts auftreten. Versuchen Sie die
Timerwerte doch einmal auf 100 oder nur 20 Interrupts pro Sekunde verändern!
Nachdem der Timer nun mit seinem Startwert gefüttert wurde, erlauben wir den
Timer-A- Interrupt durch Setzen des 0 .
Bits im ICR. Hiernach muß der Timer nur
noch gestartet werden. Damit er gleich
beim richtigen Wert beginnt zu zählen, ist hier außer dem START/ STOP-Bit auch
noch das FORCE-LOAD- Bit gesetzt. Das 3 .
Bit ist auf 0, damit der Timer im CONTI-NOUS- Modus läuft. Ebenso wie das 5 . Bit, das ihm sagt, daß er Taktzyklen zählen
soll. Alles in allem wird also der Wert
$11 in CRA geschrieben. Von nun an läuft
der Timer und wird bei einem Unterlauf
einen Interrupt erzeugen, der unsere NMI-Routine bei $1048 anspringen wird.
Kommen wir nun zu dieser Routine selbst.
Zu Anfang müssen wir erst einmal die
drei Prozessorregister retten, da wir ja
einen NMI programmiert haben, bei dem
uns die Betriebsystemsvorbeitungsroutine
diese Arbeit noch nicht abgenommen hat
( im Gegensatz zu der IRQ-Routine) . Hiernach wird das ICR von CIA-B ausgelesen.
Dadurch ermöglichen wir es diesem Chip
beim nächsten Timerunterlauf erneut einen Interrupt auslösen zu können.
Gleichzeitig können wir dadurch abfragen, ob der NMI tatsächlich von der CIA
kam. Dies ist zwar sehr wahrscheinlich, da die CIA der einzige Chip ist, der
diese Art von Interrupt erzeugen kann, jedoch ist die RESTORE-Taste auf der
Tastatur des C64 ebenso mit dem NMI-Eingang des Prozessors verbunden. Das
heißt also, daß durch Drücken dieser
Taste ebenfalls ein NMI ausgelöst wird.
In dem Fall erhalten wir beim Auslesen
des ICR den Wert 0, da ja kein CIA- Interrupt auftrat. Dadurch können wir
abfragen, ob es sich um unseren Timer-NMI, oder einen RESTORE-Tastendruck handelt. Ist letzterer der Auslöser, so
wird einfach die Rahmenfarbe um eins
erhöht, und zum Ende des NMIs weitergeprungen. Handelt es sich um einen CIA-NMI, so wird direkt zur Joystickabfrage
weiterverzweigt, in der wir Bitweise die
Portbits von CIA-A auswerten. Öbrigens:
da in dem gelesenen Register auch Datenbits der Tastatur erscheinen, kann es
passieren, daß Sie bei bestimmten Tastendrücken ebenfalls das Sprite über
den Bildschirm zucken sehen. Dies ist
normal und handelt sich nicht um einen
Fehler!
Zum Abschluß der Routine holen wir die
drei Prozessorregister wieder vom Stapel
und erlauben mittels CLI die IRQs wieder. Selbige wurden nämlich in der Betriebsystemsvorbeitung zum NMI abgeschaltet ( sh. Teil1 dieses Kurses) . Das
Programm wird diesmal nicht mit einem Sprung auf die NMI-Behandlungsroutine
des Betriebssystems beendet, da diese
ausschließlich für die Behandlung der
RESTORE-Taste benutzt wird und demnach
bei einfachem Drücken der RUN-STOP- Taste
sofort einen BASIC-Warmstart auslösen
würde ( so wird nämlich die Tastenkombination RUN-STOP/ RESTORE abgefragt) .
Am Ende des Programmbeispiels sehen Sie
noch eine Routine, die unsere NMI-Routine wieder aus dem System entfernt.
Sie sperrt wieder alle NMI-Interrupts
von CIA-B und stoppt ihren Timer A. Anschließend wird der NMI-Vektor bei
$0318/$0319 wieder auf die ursprüngliche
Adresse, nämlich $ FE47, zurückgebogen.
Das war es nun für diesen Monat. Probieren Sie ein wenig mit den Timerinterrupts herum, und versuchen Sie z. B. einen solchen Interrupt von Timer B auslösen zu lassen. Prüfen Sie auch einmal, was passiert, wenn Sie andere Timerwerte
benutzen. Ich empfehle Ihnen, eine Rou- tine zu schreiben, die immer nach genau
$4 CC7 Taktzyklen ausgelöst werden soll
und dann für eine kurze Zeit die Rahmenfarbe verändert. Sie werden hierbei
schon einmal einen Vorgeschmack auf den
nächsten Kursteil bekommen, in dem wir
uns endlich dann den Rasterinterrupts
nähern werden.
( ub)