Interrupt-Kurs - Teil 4 "Die Hardware ausgetrickst..." Herzlich Willkommen zum vierten Teil unseres IRQ-Kurses. In dieser Ausgabe möchten wir uns mit sehr zeitkritischen Rastereffekten beschäftigen und kurz zeigen, wie man den oberen und unteren Bildschirmrand mit Hilfe von Rasterin- terrupts verschwinden lassen kann. 1) DIE TOP- UND BOTTOM-BORDER-ROUTINE Wie Sie ja sicherlich wissen, ist der Bildschirm des C64 auf einen sichbaren Bereich von 320x200 Pixeln, oder auch 40x25 Textzeichen beschränkt. Der Rest des Bildschirms ist von dem meist hell- blauen Bildschirmrahmen verdeckt und kann in der Regel nicht genutzt werden. Werfen Sie jetzt jedoch einen Blick auf diese Zeilen, so werden Sie feststellen, daß das Magic-Disk-Hauptprogramm genau 25 Textzeilen, die maximale vertikale Anzahl also, darstellt und trotzdem am oberen Bildschirmrand die Seitennummer, sowie am unteren Bildschirmrand das MD- Logo zu sehen sind. Ganz offensichtlich haben wir es geschafft, den Bildschirm auf wundersame Weise zu vergrößern! Tatsächlich schaltet das MD-Haupt- programm den oberen und unteren Bild- schirmrand schlichtweg ab, so daß wir auch hier noch etwas darstellen können und auf diese Weise mehr Informationen auf einer Bildschirmseite abzulesen sind. Dies ist wieder einmal nichts an- deres als ein Rastertrick. Noch dazu einer der simpelsten die es gibt. Wie einfach er zu programmieren ist soll folgendes kleines Rasterprogramm ver- deutlichen. Sie finden es auf dieser MD unter dem Namen "BORDERDEMO" und müssen es wie immer mit ",8,1" laden und mit- tels "SYS4096" starten:
INIT: SEI ;IRQ sperren LDA #$7F ;Timer-IRQ STA $DC0D ; abschalten
LDA $DC0D ;ICR löschen
LDA #$F8 ;Rasterzeile $F8 als STA $D012 ; Interrupt-Auslöser LDA $D011 ; festlegen (incl. dem AND #$7F ; Löschen des HI-Bits) STA $D011 LDA #$01 ;Raster als IRQ- STA $D01A ; Quelle wählen LDX #<(IRQ) ;IRQ-Vektoren auf LDY #>(IRQ) ; eigene Routine STX $0314 ; verbiegen STY $0315 ; LDA #$00 ;Letzte VIC-Adr. auf STA $3FFF ; 0 setzen LDA #$0E ;Rahmen- u. Hinter- STA $D020 ; grundfarben auf LDA #$06 ; hellblau/blau STA $D021 ; setzen LDY #$3F ;Sprite-Block 13 LDA #$FF ; ($0340) mit $FF LOOP2 STA $0340,Y ; füllen DEY BPL LOOP2 LDA #$01 ;Sprite 0 STA $D015 ; einschalten STA $D027 ; Farbe="Weiß" LDA #$0D ;Spritezeiger auf STA $07F8 ; Block 13 setzen LDA #$64 ;X- und Y-Pos. STA $D000 ; auf 100/100 STA $D001 ; setzen CLI ;IRQs freigeben RTS ;ENDE IRQ LDA $D011 ;Bildschirm AND #$F7 ; auf 24 Zeilen STA $D011 ; Umschalten DEC $D019 ;VIC-ICR löschen LDX #$C0 ;Verzögerungs- LOOP1 INX ; schleife BNE LOOP1 LDA $D011 ;Bildschirm ORA #$08 ; auf 25 Zeilen STA $D011 ; zurückschalten
INC $D001 ;Sprite bewegen END JMP $EA31 ;Weiter zum SYS-IRQ Wie Sie sehen, besteht die eigentliche IRQ-Routine, die den Border abschaltet nur aus einer handvoll Befehlen. Die Initialisierung der Routine sollte Ihnen noch aus dem letzten Kursteil bekannt sein. Wir sperren hier zunächst alle IRQs und verhindern, daß CIA-A ebenfalls IRQs auslöst, damit unser Rasterinter- rupt nicht gestört wird. Als nächstes wird Rasterzeile $F8 als Interruptaus- löser festgelegt, was auch einen ganz bestimmten Grund hat, wie wir weiter unten sehen werden. Nun sagen wir noch dem VIC, daß er Rasterinterrupts aus- lösen soll, und verbiegen den IRQ-Vektor bei $0314/$0315 auf unsere eigene Rou- tine namens "IRQ". Die nun folgenden Zeilen dienen lediglich "kosmetischen" Zwecken. Wir setzen hier Rahmen- und Hintergrundfarben auf die Standardwerte und schalten Sprite 0 ein, das von unserer Interruptroutine in der Verti- kalen pro IRQ um einen Pixel weiterbe- wegt werden soll. Zudem wird der Sprite- block, der dieses Sprite darstellt, mit $FF gefüllt, damit wir ein schönes Qua- drat auf dem Bildschirm sehen und keinen Datenmüll. Nach Freigabe der IRQs mittels "CLI" wird dann wieder aus dem Programm zurückgekehrt. Von nun an arbeitet unsere kleine, aber feine Raster-IRQ-Routine. Damit Sie sie verstehen, müssen wir nun ein wenig in die Funktionsweise des VIC einsteigen: Normalerweise zeigt uns der Videochip des C64, wie oben schon erwähnt, ein 25 Text-, bzw. 200 Grafikzeilen hohes Bild. Nun können wir die Bildhöhe mit Hilfe von Bit 3 in Register 17 des VICs auf 24 Textzeilen reduzieren. Setzen wir es auf "1", so werden 25 Textzeilen darge- stellt, setzen wir es auf "0", so sehen wir lediglich 24 Textzeilen. Im letzte- ren Fall werden dann jeweils vier Grafikzeilen des oberen und unteren Bildschirmrandes vom Bildschirmrahmen überdeckt. Diese Einschränkung ist vor allem bei der Programmierung eines vertikalen Soft-Scrollers von Bedeutung. Effektiv zeichnet der VIC nun also den oberen Bildschirmrand vier Rasterzeilen länger und den unteren vier Rasterzei- len früher. Um nun den Rahmen zu zeichnen kennt die Schaltlogik des VIC zwei Rasterzeilen, die er besonders behandeln muß. Erreicht er nämlich Rasterzeile $F7, ab der der Bildschirm endet, wenn die 24 Textzeilen-Darstel- lung aktiv ist, so prüft er, ob Bit 3 von Register 17 gelöscht ist. Wenn ja, so beginnt er den Rand zu zeichnen, wenn nein, so fährt er normal fort. Erreicht er dann Rasterzeile $FB, die das Ende eines 25-zeiligen Bildschirms darstellt, wird nochmals geprüft, ob das obige Bit auf 0 ist. Wenn ja, so weiß der VIC, daß er mit dem Zeichnen des Rahmens schon begonnen hat. Wenn nein, so beginnt er erst jetzt damit. Mit unserem Interrupt tricksen wir den armen Siliziumchip nun aus. Unsere Routine wird immer in Ras- terzeile $F8 angesprungen, also genau dann, wenn der VIC die 24-Zeilen-Prüfung schon vorgenommen hat. Da die Darstel- lung auf 25 Zeilen war, hat er noch keinen Rand gezeichnet. Unsere Inter- ruptroutine schaltet nun aber auf 24 Zeilen um und gaukelt dem VIC auf diese Weise vor, er hätte schon mit dem Zeichnen des Randes begonnen, weshalb er nicht nocheinmal beginnen muß, und somit ohne zu zeichnen weitermacht. Dadurch erscheinen unterer und oberer Bild- schimrand in der Hintergrundfarbe, und es ist kein Rahmen mehr sichtbar. In diesen Bereichen kann man nun zwar keinen Text oder Grafik darstellen, jedoch sind Sprites, die sich hier befinden durchaus sichtbar! Sie werden normalerweise nämlich einfach vom Rahmen überdeckt, sind aber dennoch vorhanden. Da der Rahmen nun aber weg ist, sieht man auch die Sprites, wie das sich bewegende Sprite 0 unserer Interrupt- routine beweist! Wichtig an unserer Routine ist nun noch, daß wir vor Erreichen des oberen Bild- randes die Darstellung nocheinmal auf 25 Zeilen zurückschalten, damit der Trick beim nächsten Rasterdurchlauf nocheinmal klappt. Hierbei darf natürlich frühes- tens dann umgeschaltet werden, wenn der Rasterstrahl an der zweiten Prüf-Posi- tion, Rasterzeile $FB, schon vorbei ist. Dies wird durch die kleine Verzögerungs- schleife bewirkt, die genau 4 Rasterzei- len wartet, bevor mit dem anschließenden ORA-Befehl Bit 3 in Register 17 des VIC wieder gesetzt wird. Am Ende unseres Interrupts bewegen wir das Sprite noch um eine Y-Position weiter und verzweigen zum Betriebssystem-IRQ, damit die Systemaufgaben trotz abgeschalteter CIA dennoch ausgeführt werden. Die inter- ruptauslösende Rasterzeile muß nicht nochmal neu eingestellt werden, da wir diesmal nur eine Rasterzeile haben, die jedesmal wenn sie erreicht wird einen Interrupt auslöst. Wollen wir nun noch klären, warum wir bei der Initialisierung eine 0 in Adresse $3FFF geschrieben haben. Wie Sie vielleicht wissen, kann der VIC Spei- cherbereiche von lediglich 16KB adres- sieren, aus denen er sich seine Daten holt. Im Normalfall ist das der Bereich von $0000-$3FFF. Die letzte Speicher- zelle seines Adressbereichs hat nun eine besondere Funktion. Der Bit-Wert, der in ihr steht, wird nämlich in allen Spalten der Zeilen des nun nicht mehr überdek- kten Bildschirmrandes dargestellt - und zwar immer in schwarzer Farbe. Durch das Setzen dieser Zelle auf 0 ist hier also gar nichts sichtbar. Schreiben wir jedoch bei aktivierter Borderroutine mittels "POKE16383,X" andere Werte hinein, so werden je nach Wert mehr oder weniger dicke, vertikale Linien in diesem Bereich sichtbar. Durch Setzen aller Bits mit Hilfe des Wertes 255 (mit Rahmenfarbe=schwarz), können wir sogar einen scheinbar vorhandenen Bildschirm- rand simulieren! Vielleicht fällt Ihnen nun auch noch ein interessanter Nebeneffekt auf: nachdem wir die oberen und unteren Bildschirm- grenzen abgeschaltet haben, gibt es Spritepositionen, an denen das Sprite zweimal zu sehen ist. Nämlich sowohl im oberen, als auch im unteren Teil des Bildschirms. Das liegt daran, daß das PAL-Signal, welches der VIC erzeugt 313 Rasterzeilen kennt, wir aber die Y-Position eines Sprites nur mit 256 verschiedenen Werten angeben können. Dadurch stellt der VIC das Sprite an den Y-Positionen zwischen 0 und 30 sowohl am unteren, als auch am oberen Rand dar. Bei eingeschalteten Rändern fiel dieser Nebeneffekt nie auf, da diese Sprite- positionen normalerweise im unsichtbaren Bereich des Bildschirms liegen, wo sie vom Bildschirmrahmen überdeckt werden. Bleibt noch zu erwähnen, daß wir mit ei- nem ähnlichen Trick auch die seitlichen Ränder des Bildschirms verschwinden las- sen können, nur ist das hier viel schwie- riger, da es auf ein sehr genaues Timing ankommt. Wie man damit umgeht müssen wir jetzt erst noch lernen, jedoch werde ich in den nächsten Kursteilen auf dieses Problem nocheinmal zu sprechen kommen. 2) EINZEILEN-RASTER-EFFEKTE Kommen wir nun zu dem oben schon erwähn- ten Timing-Problem. Vielleicht haben Sie nach Studieren des letzten Kursteils einmal versucht einzeilige Farbraster- effekte zu programmieren. Das heißt also daß Sie gerade eine Zeile lang, die Bildschirmfarben wechseln, und sie dann wieder auf die Normalfarbe schalten. Hierzu wären dann zwei Raster-Interrupts notwendig, die genau aufeinander zu folgen haben (z.B. in Zeile $50 und $51). Wenn Sie verschucht haben ein solches Rasterprogramm zu schreiben, so werden Sie bestimmt eine Menge Probleme dabei gehabt haben, da die Farben nie genau eine Rasterzeile lang den gewünschten Wert enthielten, sondern mindestens eineinhalb Zeilen lang sichtbar waren. Dieses Problem hat mehrere Ursachen, die hauptsächlich durch die extrem schnelle Gesschwin- digkeit des Rasterstahls entstehen. Selbiger bewegt sich nämlich in genau 63 Taktzyklen einmal von links nach rechts. Da innerhalb von 63 Taktzyklen nicht allzu viele Instruktionen vom Prozessor ausgeführt werden können, kann jeder Befehl zuviel eine zeitliche Verzögerung verursachen, die eine Farbänderung um mehrere Pixel nach rechts verschiebt, so daß die Farbe nicht am Anfang der Zeile, sondern erst in ihrer Mitte sichtbar wird. Da ein IRQ nun aber verhältnis- mäßig viel Rechenzeit benötigt, bis er abgearbeitet ist, tritt ein Raster-IRQ in der nächsten Zei- le meist zu früh auf, nämlich noch bevor der erste IRQ beendet wurde! Dadurch gerät das Programm natürlich vollkommen aus dem Takt und kann ggf. sogar abstürzen! Noch dazu muß ein weiterer, hardware- mäßiger Umstand beachtet werden: hat man normale Textdarstellung auf dem Bild- schirm eingeschaltet, so muß der VIC nämlich jedes mal zu Beginn einer Charakterzeile die 40 Zeichen aus dem Video-RAM lesen, die er in den folgenden acht Rasterzeilen darzustellen hat, und sie entsprechend in ein Videosignal um- wandeln. Um diesen Vorgang durchzuführen hält er den Prozessor für eine Zeit von genau 42 Taktzyklen an, damit er einen ungestörten Speicherzugriff machen kann. Eine Charakterzeile ist übrigens eine der 25 Textzeilen. Da der Bildschirm in der Regel bei Rasterzeile $32 beginnt, und jede achte Rasterzeile ein solcher Zugriff durchgeführt werden muß, sind all diese Zeilen besonders schwierig über einen Raster-IRQ programmierbar, da erst nach dem VIC-Zugriff ein Raster-IRQ bearbeitet werden kann, der jedoch durch den Zugriff schon viel zu spät eintritt, da die Zeile in der Zwischenzeit ja schon zu zwei Dritteln aufgebaut wurde. Hier muß man sich eines speziellen Tricks behelfen. Um selbigen besser erläutern zu können, wollen wir uns das folgende Beispielprogramm einmal etwas näher anschauen:
;************************************** INIT SEI ;IRQs sperren LDA #$7F ;CIA-A-IRQs STA $DC0D ; unterbinden
LDA $DC0D
LDA #$82 ;Rasterzeile $82 als STA $D012 ; Interruptauslöser LDA $D011 ; festlegen (incl. AND #$7F ; Löschen des STA $D011 ; HI-Bits LDA #$01 ;Rasterstrahl ist STA $D01A ; IRQ-Auslöser LDX #<(IRQ) ;IRQ-Vektor auf eigene LDY #>(IRQ) ; Routine verbiegen STX $0314 STY $0315 CLI ;IRQs freigeben VERZ RTS ;ENDE ;************************************** IRQ DEC $D019 ;VIC-IRQ freigeben JSR VERZ ;Verzögern... JSR VERZ NOP LDY #$00 ;Farb-Index init. LOOP1 LDX #$08 ;Char-Index init. LOOP2 LDA $1100,Y ;Farbe holen STA $D020 ; und im Rahmen-und STA $D021 ; Hintergrund setzen INY ;Farb-Index+1 DEX ;Char-Index-1 BEQ LOOP1 ;Wenn Char=0 verzweig. LDA VERZ ;Sonst verzögern... JSR VERZ JSR VERZ JSR VERZ
CPY #$48 ;Farb-Index am Ende? BCC LOOP2 ;Nein also weiter
LDA #$0E ;Sonst Rahmen/Hinter- STA $D020 ; grund auf LDA #$06 ; Standardfarben STA $D021 ; zurücksetzen JMP $EA31 ;IRQ mit SYS-IRQ beend
Besten laden Sie das Programm einmal und starten es mit "SYS4096". Sie sehen nun einen schönen Rasterfarbeneffekt auf dem Bildschirm, wo wir ab Rasterzeile $83 in jeder Zeile die Rahmen- und Hintergrund- farbe ändern. Ich habe hierbei Farbab- stufungen benutzt, die schöne Balken- effekte erzeugen. Die entsprechende Farbtabelle liegt ab Adresse $1100 im Speicher und kann natürlich von Ihnen auch verändert werden. Kommen wir nun zur Programmbeschreibung. Die Initiali- sierungsroutine sollte Ihnen keine Pro- bleme bereiten. Wir schalten wie immer die CIA ab, sagen dem VIC, daß er einen Raster-IRQ generieren soll, legen eine Rasterzeile (hier Zeile $82) als IRQ- Auslöser fest, und verbiegen die IRQ- Vektoren auf unser eigenes Programm. Etwas seltsam mag Ihnen nun die eigent- liche IRQ-Routine vorkommen. Nachdem wir mit dem DEC-Befehl dem VIC bestätigt haben, daß der IRQ bei uns angekommen ist, folgen nun drei, scheinbar sinnlose, Befehle. Wir springen nämlich das Unterprogramm "VERZ" an, das lediglich aus einem RTS-Befehl besteht, und somit direkt zu unserem Programm zurückverzweigt. Zusätzlich dazu folgt noch ein NOP-Befehl der ebensowenig tut, wie die beiden JSRs zuvor. Der Sinn dieser Instruktionen liegt lediglich in einer Zeitverzögerung, mit der wir abwarten, bis der Rasterstrahl am Ende der Zeile $82 angelangt ist. Wir hätten hier auch jeden anderen Befehl verwenden können, jedoch ist es mit JSRs am ein- fachsten zu verzögern, da ein solcher Befehl, ebenso wie der folgende RTS- Befehl, jeweils 6 Taktzyklen verbraucht. Durch einen JSR-Befehl vergehen also genau 12 Taktzyklen, bis der nächste Befehl abgearbeitet wird. Da ein NOP- Befehl, obwohl er nichts macht, zwei Taktzyklen zur Bearbeitung benötigt, und wir zwei JSRs verwenden, verzögern wir also um insgesamt 26 Taktzyklen. Genau diese Verzögerung ist dem DEC-Befehl zuvor und den folgenden LDX- und LDY- Befehlen notwendig, um soviel zu verzögern, daß sich der Rasterstrahl bis ans Ende der Rasterzeile bewegt hat. Hinzu kommt daß wir die 42 Taktzyklen hinzurechnen müssen, die der VIC den Prozessor sowieso schon gestoppt hat, da Rasterzeile $82 eine der schon oben angesprochenen Charakterzeilen darstellt ($82-$32=$50/8=10 - ohne Rest!). Laden Sie nun bitte Teil 2 des Kurses!