Magic Disk 64

home to index to text: MD9401-KURSE-IRQ-KURS_3.1.txt
             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)

Valid HTML 4.0 Transitional Valid CSS!