Interrupt-Kurs "Die Hardware ausgetrickst..." (Teil 3) ----------------------------------------
Herzlich willkommen zum dritten Teil unseres Interruptkurses. In diesem Monat möchten wir endlich in "medias res" ge- hen und in den interessantesten Teilbe- reich der Interruptprogrammierung ein- steigen: 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 Inter- rupptquelle 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 Compu- termonitors 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 Elektro- nenquelle, 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 Bildschirm- rand 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 Gesamt- bild 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 le- sen, benötigt man eine Schaltlogik, die die Text- oder Grafikzeichen aus dem Speicher des Computers in sichtbare Bildpunkte umwandelt. Diese Umwandlung geschieht durch den Video-Chip des C64, dem sogenannten VIC. Er erzeugt Steuer- signale für den Rasterstrahl, die ange- ben in welcher Rasterzeile er gerade arbeiten soll, und mit welcher Inten- sität die einzelnen Bildpunkte auf dem Bildschirm leuchten sollen. Helle Bild- punkte 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 einset- zen 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üs- sen, 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 ge- schieht ü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ög- lichkeit, 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 ins- gesamt 313 Rasterzeilen gibt reicht näm- lich 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 ande- ren Bits dieses Registers ebenfalls ei- nige 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 Interrupt- auslö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 fol- genden drei Befehlen das 7. Bit in Regi- ster $D011 setzen, und die Nummer der Rasterzeile minus dem Wert 256 in Regi- ster $D012 schreiben:
LDA $D011 ;Reg.17 lesen ORA $80 ;7. Bit setzen STA $D011 ;Reg.17 zurückschreiben.
Um z.B. Rasterzeile 300 als Interrupt- auslö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 Rasterzei- le 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 $D01A) 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 definie- ren. 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 ei- nen Interrupt auslösen können. Sie kön- nen auf diese Weise also auch sehr kom- fortabel eine Kollision über den Inter- rupt abfragen. Bit 3 wird nur sehr sel- ten benutzt und bringt eigentlich nur etwas in Zusammenhang mit einem Light- pen. 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 ent- sprechende Lightpen-Programm nur noch auswerten, an welcher Rasterposition der Strahl sich gerade befindet, um heraus- zufinden, an welcher Stelle des Bild- schirms sich der Lightpen befindet. Dies soll uns hier jedoch nicht interessie- ren. Wir müssen also lediglich Bit 0 von $D0- 1A 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 fest- gelegt 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 Prin- zip ein alter Hut, denn der VIC ist mit der IRQ-Leitung des Prozessors verbun- den, 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üs- sen wir jedoch beachten, daß der Be- triebssystem-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 Un- terschied machen, so würde uns die CIA ungehemmt 'dazwischenfunken' und der Rasterinterrupt würde außer Kontrolle geraten. Bevor ich mich nun aber in the- roretischen 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 Aus- sta $d012 ; löser festlegen lda #$07 ;Rahmen- und Hintergrund- sta $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 aus- fü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 Raster- zeilen $00, $70 und $C0 einen Interrupt auslösen, woraufhin wir die Rahmen- und 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 be- schriebene Art und Weise die Raster- zeile 0 als Inter- ruptauslösende Zeile fest. Hieraufhin wird der Wert $01 in Register $D01A 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ück- schreiben 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 Interruptanfor- derung 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 ent- spricht 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 Lese- vorgang wurden die dem Wert entsprechen- den 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 Unter- routine "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 Raster- zeile 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 Initialisierungs- routine her ja noch auf 0 ist. Danach werden Rahmen- und Hinter- grundfarbe 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ückge- holt 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 Bild- schirmfarben 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)