Interrupt-Kurs "Die Hardware ausgetrickst..." (Teil 3)
Herzlich willkommen zum dritten Teil
unseres Interruptkurses. In diesem Monat
möchten wir endlich in " medias res" gehen und in den interessantesten Teilbereich der Interruptprogrammierung einsteigen: den Raster-Interrupt.
1) WAS IST EIN RASTER-INTERRUPT Raster-Interrupts sind Interrupts die
vom Videochip des C64 ausgelöst werden.
Er ist die dritte hardwaremäßige Interrupptquelle und bietet uns vielfältige
Möglichkeiten um besonders interessante
und schöne Interrupt-Effekte erzeugen zu
können. Zunächst jedoch wollen wir klären, was so ein Raster-Interrupt nun eigentlich ist. Hierzu wollen wir zuerst
einmal die Funktionsweise eines Computermonitors oder Fernsehers in groben
Zügen besprechen:
Der Bildschirm, so wie Sie ihn jetzt vor
sich haben, ist an seiner Innenseite mit
einem Stoff beschichtet, der, wenn er
von Elektronen getroffen wird, zum
Leuchten angeregt wird. Innerhalb des
Monitors befindet sich nun eine Elektronenquelle, die, durch Magnetfelder
gebündelt, einen hauchdünnen Strahl von
Elektronen erzeugt. Zusätzlich gibt es
noch zwei Ablenkmagnete, die den Strahl
in der Horizontalen und Vertikalen
ablenken können, so daß jede Position
auf dem Bildschirm erreicht werden kann.
Der Elektronenstrahl wird nun durch ein
bestimmtes Bewegungsschema zeilenweise
über den Bildschirm bewegt und bringt
selbigen zum Leuchten. Hierbei ist der
gesamte Bildschirm in 313 sogenannte
Rasterzeilen aufgeteilt. Jedesmal, wenn
eine Zeile von links nach rechts aufge- baut wurde, wird der Stahl kurzfristig
abgeschaltet und am linken Bildschirmrand wieder angesetzt, um die nächste
Zeile zu zeichnen. Dies geschieht mit
einer unglaublichen Geschwindigkeit, so
daß für das sehr träge Auge ein Gesamtbild aus unzählligen Leuchtpunkten auf
dem Bidschirm entsteht. Insgesamt 25 Mal
pro Sekunde ' huscht' der Elektronenstahl
auf diese Weise über den Bildschirm, wodurch 25 Bilder pro Sekunde aufgebaut
werden. Damit nun ein ganz spezielles
Videobild auf dem Bildschirm zu sehen
ist, z. B. der Text, den Sie gerade lesen, benötigt man eine Schaltlogik, die
die Textoder Grafikzeichen aus dem
Speicher des Computers in sichtbare
Bildpunkte umwandelt. Diese Umwandlung
geschieht durch den Video-Chip des C64, dem sogenannten VIC. Er erzeugt Steuersignale für den Rasterstrahl, die angeben in welcher Rasterzeile er gerade
arbeiten soll, und mit welcher Intensität die einzelnen Bildpunkte auf dem Bildschirm leuchten sollen. Helle Bildpunkte werden dabei mit mehr, dunklere
mit weniger Elektronen ' beschossen' . Da
der VIC nun auch die Steuersignale für
den Rasterstrahl erzeugt, weiß er auch
immer, wo sich selbiger gerade befindet.
Und das ist der Punkt an dem wir einsetzen können, denn der VIC bietet uns nun
die Möglichkeit, diese Strahlposition in
einem seiner Register abzufragen. Damit
wir nun aber nicht ständig prüfen müssen, in welcher der 313 Rasterzeilen
sich der Strahl nun befindet, können wir
ihm auch gleich mitteilen, daß er einen
Interrupt bei Erreichen einer bestimmten
Rasterzeile auslösen soll. Dies geschieht über spezielle Interrupt-Register, deren Belegung ich Ihnen nun
aufführen möchte.
2) DIE RASTER-IRQ- PROGRAMMIERUNG Zunächst einmal brauchen wir die Möglichkeit, die gewünschte Rasterzeile festlegen zu können. Dies wird in den
Registern 17 und 18( Adressen $ D011 und
$ D012) des VICs angegeben. Da es ja insgesamt 313 Rasterzeilen gibt reicht nämlich ein 8- Bit-Wert nicht aus, um alle
Zeilen angeben zu können. Aus diesem
Grund ist ein zusätzliches neuntes Bit
in VIC-Register 17 untergebracht. Bit 7 dieses Registers gibt zusammen mit den
acht Bits aus VIC-Register 18 nun die
gewünschte Rasterzeile an. Da die anderen Bits dieses Registers ebenfalls einige Aufgaben des VICs ausfüllen, dürfen
wir nur durch logische Verknüpfungen
darauf zugreifen. Möchten wir also eine
Rasterzeile von 0 bis 255 als Interruptauslöser definieren, so muß das 7 . Bit
mit den folgenden drei Befehlen gelöscht
werden. Die Rasterzeilen-Nummer wird
dann ganz normal in Register 18($ D012) geschrieben:
LDA $D011 ;Reg.17 lesen AND #$7F ;7. Bit löschen
STA $ D011 ; Reg.17 zurückschreiben
Wenn nun eine Rasterzeile größer als 255 in die beiden VIC-Register geschrieben
werden soll, so müssen wir mit den folgenden drei Befehlen das 7 . Bit in Register $ D011 setzen, und die Nummer der
Rasterzeile minus dem Wert 256 in Register $ D012 schreiben:
LDA $D011 ;Reg.17 lesen ORA $80 ;7. Bit setzen STA $D011 ;Reg.17 zurückschreiben.
Um z. B. Rasterzeile 300 als Interruptauslöser einzustellen, müssen wir wie
eben gezeigt das 7 . Bit in $ D011 setzen
und anschließend den Wert 300-256=44 in
$ D012 ablegen.
Das alleinige Festlegen einer Rasterzeile als Interruptauslöser genügt jedoch
noch nicht, den VIC dazu zu bringen den
Prozessor beim Erreichen dieser Zeile zu unterbrechen. Wir müssen ihm zusätzlich
noch mitteilen, daß er überhaupt einen
Interrupt auslösen soll. Dies wird in
Register 26( Adresse $ D01 A) festgelegt.
Es ist das Interrupt-Control- Register
( ICR) des VIC und hat eine ähnliche
Funktion wie das ICR der CIA, das wir im
letzten Kursteil schon kennengelernt
hatten.
Das VIC-ICR kennt vier verschiedene
Ereignisse, die den VIC zum Auslösen
eines Interrupts bewegen. Jedes der
Ereignisse wird durch ein zugehöriges
Bit im ICR repräsentiert, das lediglich
gesetzt werden muß, um das entsprechende
Ereignis als Interruptquelle zu definieren. Hier die Belegung:
Bit Interruptquelle ist
0 Rasterstrahl 1 Kollision Sprites und Hintergrund 2 Kollision zwischen Sprites 3 Lightpen sendet Signal
Wie Sie sehen ist für uns hauptsächlich
Bit 0 von Bedeutung. Setzen wir es, so
löst der VIC bei Erreichen der zuvor
festgelegten Rasterzeile einen Interrupt
aus. Sie sehen sicherlich auch, daß
Sprite-Kollisionen mit Hintergrundgrafik
und/ oder mit anderen Sprites ebenso einen Interrupt auslösen können. Sie können auf diese Weise also auch sehr komfortabel eine Kollision über den Interrupt abfragen. Bit 3 wird nur sehr selten benutzt und bringt eigentlich nur
etwas in Zusammenhang mit einem Lightpen. Selbiger sendet nämlich immer dann
ein Signal, wenn der Rasterstrahl gerade
an ihm vorübergezogen ist. Wird so nun
ein Interrupt ausgelöst, muß das entsprechende Lightpen-Programm nur noch
auswerten, an welcher Rasterposition der
Strahl sich gerade befindet, um herauszufinden, an welcher Stelle des Bildschirms sich der Lightpen befindet. Dies
soll uns hier jedoch nicht interessie- ren.
Wir müssen also lediglich Bit 0 von $ D0-1 A durch Schreiben des Wertes 1 setzen, um Raster-Interrupts auslösen zu können.
3) DAS HANDLING DER RASTER-INTERRUPTS Hardwaremäßig haben wir nun alles festgelegt und den VIC auf die Interrupt-Erzeugung vorbereitet. Was nun noch
fehlt ist die Software die den Interrupt
bedient. Auch dies ist für uns im Prinzip ein alter Hut, denn der VIC ist mit
der IRQ-Leitung des Prozessors verbunden, weswegen wir einen Raster-Interrupt
im Prinzip wie den CIA1- Interrupt des
Betriebssystems abfragen können. Hierzu
brauchen wir natürlich erst einmal eine
Initialisierungsroutine, die den IRQ-Vektor bei $0314/$0315 auf unsere eigene
Interrupt-Routine verbiegt. Hierbei müssen wir jedoch beachten, daß der Betriebssystem- Interrupt, der von Timer A
der CIA1 ausgelöst wird, ebenfalls über diesen Vektor springt. Das bedeutet, daß
unsere Routine zwischen diesen beiden
Interrupts unterscheiden muß, da ein
Raster-IRQ anders bedient werden muß, als ein CIA-IRQ. Würden wir keinen Unterschied machen, so würde uns die CIA
ungehemmt ' dazwischenfunken' und der
Rasterinterrupt würde außer Kontrolle
geraten. Bevor ich mich nun aber in theroretischen Fällen verliere, sehen Sie
sich einmal folgendes Beispielprogramm
an, anhand dessen wir die Lösung dieses
Problems besprechen möchten:
;*** Initialisierung Init: sei ;IRQs sperren lda #$00 ;Rasterzeile 0 als Inter- sta $d012 ; ruptauslöser festlegen lda $d011 ; (höchstwertiges Bit and #$7f ; der Rasterzeile sta $d011 ; löschen) lda #$01 ;Rasterstrahl als Inter- sta $d01a ; ruptquelle festlegen) lda #3 ;Zähler in $02 mit dem sta $02 ; Wert 3 initialisieren) ldx #<(irq) ;IRQ-Vektor bei $0314/ ldy #>(irq) ; $0315 auf eigene stx $0314 ; Routine mit dem Namen sty $0315 ; "IRQ" verbiegen. cli ;IRQs wieder erlauben rts ;ENDE! ;*** Interrupt-Routine irq: lda $d019 ;VIC-IRR lesen sta $d019 ; und zurückschreiben bmi raster ;Wenn 7.Bit gesetzt, war ; es ein Raster-IRQ jmp $ea31 ;Sonst auf Betriebssystem ; Routine springen
raster:
dec $02 ;Zähler runterzählen lda $02 ;Zähler lesen cmp #2 ;Zähler=2? bne rast70 ;Nein, also weiter lda #$70 ;Sonst Zeile $70 als neu- sta $d012 ; en Auslöser festlegen lda #$00 ;Rahmen- und Hintergrund- sta $d020 ; farbe auf 'schwarz' sta $d021 ; setzen jmp $febc ;IRQ beenden
rast70 :
cmp #1 ;Zähler=1? bne rastc0 ;Nein, also weiter lda #$c0 ;Sonst Zeile $c0 als neu- sta $d012 ; en Auslöser festlegen lda #$02 ;Rahmen- und Hintergrund- sta $d020 ; farbe auf 'rot' sta $d021 ; setzen jmp $febc ;IRQ beenden
rastc0 :
lda #$00 ; Wieder Zeile 0 als Aussta $ d012 ; löser festlegen lda #$07 ; Rahmenund Hintergrundsta $ d020 ; farbe auf ' gelb' sta $ d021 ; setzen
lda #3 ;Zähler wieder auf 3 sta $02 ; zurücksetzen jmp $febc ;IRQ beenden
Sie finden das Programm auch als ausführbaren Code unter dem Namen
" RASTERDEMO1" auf dieser MD. Es muß
absolut ( mit ",8,1") geladen und mit
" SYS4096" gestartet werden. Möchten Sie
es sich ansehen, so disassemblieren Sie
mit einem Monitor ab Adresse $1000 .
Unser erster Raster-Interrupt soll nun
auf dem Bildschirm eine Schwarz-Rot- Gold-Flagge darstellen. Zu diesem Zweck
lassen wir den VIC in den drei Rasterzeilen $00,$70 und $ C0 einen Interrupt
auslösen, woraufhin wir die Rahmenund
Hintergrundfarbe in eine der drei Farben
ändern. Gehen wir jedoch Schritt für
Schritt vor und schauen wir uns zunächst
einmal die Initialisierungsroutine an:
Hier schalten wir zunächst einmal die
IRQs mittels des SEI-Befehls ab, und
setzen anschließend auf die schon beschriebene Art und Weise die Rasterzeile 0 als Interruptauslösende Zeile
fest. Hieraufhin wird der Wert $01 in
Register $ D01 A geschrieben, womit wir
die Rasterinterrupts erlauben. Danach
wird noch ein Zähler in Adresse $02 initialisert, den wir zum Abzählen der
Raster-Interrupts benötigen, damit wir
auch immer die richtige Farbe an der
richtigen Rasterposition setzen. Zuletzt
wird der IRQ-Vektor bei $0314/$0315 auf
unsere Routine verbogen und nach einem
abschließenden Freigeben der IRQs wird
die Initialisierung beendet. Von nun
an wird bei jedem IRQ, stammt er nun vom
VIC oder der CIA, die den Betriebssystem
IRQ erzeugt, auf unsere Routine " IRQ" gesprungen.
Damit der Raster-Interrupt nun jedoch
nicht, wie oben schon angesprochen,
außer Kontrolle gerät, müssen wir hier
nun als erstes herausfinden, von welchem
der beiden Chips der IRQ ausgelöst
wurde. Dies geschieht durch Auslesen des
Registers 25 des VICs ( Adresse $ D019) .
Es das Interrupt-Request- Register
dieses Chips, in dem das 7 . Bit, sowie
das entsprechende Bit des ICR, immer
dann gesetzt werden, wenn eines der vier
möglichen Interrupt-Ereignisse des VICs
eingetreten ist. Durch das Lesen dieses
Registers holen wir uns also seinen Wert
in den Akku. Das Anschließende zurückschreiben löscht das Register wieder.
Dies ist notwendig, da der VIC den
soeben in diesem Register erzeugten Wert
solange hält, bis ein Schreibzugriff
darauf durchgeführt wird. Dadurch wird
sichergestellt, daß die Interruptanforderung auch tatsächlich bei einem
behandelnden Programm angelangt ist.
Würden wir nicht schreiben, so würde der
VIC den nächsten Interrupt schlichtweg
ignorieren, da er den alten ja noch für nicht abgearbeitet interpretiert. Der
Schreibzugriff auf das IRR des VICs entspricht im Prinzip also derselben
Funktion, wie der Lesezugriff des ICR
einer CIA, wenn von dort ein Interrupt
ausgelöst wurde.
Im Akku befindet sich nun also der Wert
aus dem IRR. Gleichzeitig mit dem Lesevorgang wurden die dem Wert entsprechenden Flags im Statusregister des
Prozessors gesetzt. Wir müssen nun also
lediglich prüfen, ob das 7 . Bit dieses
Wertes gesetzt ist, um herauszufinden, ob es sich um einen vom VIC stammenden
Raster-IRQ handelt, oder nicht. Dies
geschieht durch den folgenden BMI-Befehl, der nur dann verzweigt, wenn ein
negativer Wert das Ergebnis der letzten
Operation war. Da ein negativer Wert
aber immer das 7 . Bit gesetzt hat, wird
also nur in diesem Fall auf die Unterroutine " RASTER" verzweigt. Im anderen
Fall springen wir mit einem " JMP $ EA31",
die Betriebssystem-IRQ- Routine an.
War das 7 . Bit nun also gesetzt, so wird
ab dem Label " RASTER" fortgefahren, wo
wir zunächst einmal unseren Zähler in
$02 um 1 erniedrigen, und ihn dann in
den Akku holen. Steht dort nun der Wert
2, so wurde der Interrupt von Rasterzeile 0 ausgelöst, und wir setzen Zeile
$70 als nächsten Auslöser fest. Hierbei
müssen wir Bit 7 von $ D011 nicht extra
löschen, da es von der Initialisierungsroutine her ja noch auf 0 ist. Danach
werden Rahmenund Hintergrundfarbe
auf ' schwarz' gesetzt und der Interrupt
durch einen Sprung auf Adresse $ FEBC
beendet. Hier befindet sich das Ende des
Betriebssystems-IRQ- Programms, in dem
die Prozessorregister wieder zurückgeholt werden, und mit Hilfe des
RTI-Befehls aus dem Interrupt
zurückgekehrt wird.
Steht in unserem Zähler bei $02 nun der Wert 1, so wurde der Interrupt von Zeile
$70 ausgelöst. Hier setzen wir die Bildschirmfarben nun auf ' rot' und legen
Zeile $ C0 als Auslöser des nächsten
Raster-Interrupts fest. Tritt selbiger
auf, so steht weder 1 noch 2 in unserem
Zählregister, weswegen zur Routine
" RASTC0" verzweigt wird, wo wir die
Rahmenfarbe auf ' gelb' setzen, den
Zähler wieder mit seinem Startwert
initialisieren und Rasterzeile 0 als
nächsten Interruptauslöser festlegen, womit sich der ganze Prozeß wieder von
vorne wiederholt.
( Bitte laden Sie nun den 2 . Teil dieses
Artikels aus dem Textmenu)