IRQ-KURS "Die Hardware ausgetrickst..." (Teil 15) ----------------------------------------
Im letzten Kursteil hatten wir uns einen ganz besonderen Rastertrick angeschaut. Anhand einer VSP-Routine hatten wir ge- lernt, wie einfach es ist, den Bild- schirm des C64 HARDWAREMÄSSIG, also ohne den Prozessor mit großen Bildschirm- Verschiebe-Aktionen zu belasten, nach oben und unten zu scrollen. Dabei unter- schied sich die VSP-Routine von einer FLD-Routine in nur einem NOP-Befehl, der das nötige Timing erzeugte, um den gewünschten Effekt auszulösen. Der FLD- Effekt war, wie wir gesehen hatten maß- geblich daran beteiligt, daß der VIC das Lesen von einigen Charakterzeilen ver- gaß, weswegen wir in der Lage waren, einzelne Bereiche im Video-RAM zu über- springen, und so den Bildschirm beliebig nach oben und unten zu scrollen. In die- sem Kursteil soll es nun um einen nahen Verwandten von VSP gehen. Wir werden den HSP-Effekt besprechen. "HSP" steht für "Horizontal Screen Position" und setzt den VSP-Effekt quasi auf die Horizontale um. Mit ihm können wir DEN GESAMTEN BILDSCHIRM problemlos um bis zu 320 Pi- xel nach rechts verschieben, wobei wir den Prozessor nur läppische 3 Rasterzei- len lang in Anspruch nehmen müssen. Da- mit werden schnell scrollende Baller- oder Jump'n Run Spiele, wie auf dem Ami- ga oder dem Super-NES von Nintendo auch auf dem C64 möglich! 1) ZUM PRINZIP VON HSP Wir erinnern uns: Um die VSP-Routine funktionsfähig zu machen, mussten wir den FLD-Effekt so anwenden, daß wir dem VIC vorgaukelten, sich auf das Lesen der nächsten Rasterzeile vorzubereiten und seinen internen Lesezähler hochzuzählen, um die richtige Charakterzeilen-Adresse anzusprechen. Als er nun seinen Lesevor- gang durchführen wollte machten wir ihn durch FLD-Wegdrücken der Charakterzeilen glauben, daß er doch noch nicht die ent- sprechende Zeile erreicht hatte. Somit übersprang er mehrere Adressen und somit auch Charakterzeilen. Wir ließen ihn also die Charakterzeilen zu spät lesen, was zu dem gewünschten Effekt führte. Einen ähnlichen Trick können wir nun auch für die HSP-Routine anwenden. Wie wir ja wissen, so liest der VIC ab der Rasterposition $30 alle 8 Rasterzeilen die 40 Zeichen, die in den nächsten 8 Rasterzeilen zu sehen sein sollen, aus dem Video-RAM, um sie anschließend anzu- zeigen. Durch den FLD-Effekt haben wir nun schon oft genung die erste Charak- terzeile auf dem Bildschim vor ihm her- geschoben, so daß der VIC diese Zeile verspätet erreichte, und somit der Bild- schirm nach unten weggedrückt wurde. Der Witz ist, daß dieser Trick nun auch in her Horizontalen funktioniert! Denn so- bald der VIC merkt, daß er sich in einer Charakterzeile befindet, in der er Zei- chendaten zu Lesen und Anzuzeigen hat, beginnt er auch unverzüglich mit dieser Arbeit, und das ohne noch auf die hori- zontale Rasterposition zu achten, um ggf. festzustellen, daß er sich garnicht am Zeilenanfang befindet! Wenn wir nun also eine Art FLD-Routine einsetzen, die nur für einen Teil der aktuellen Raster- zeile vorschreibt, daß selbige noch kei- ne Charakterzeile ist, und dann mitten innerhalb dieser Zeile von uns wieder auf normale Darstellung zurückgeschaltet wird, so fängt der VIC auch prompt mit- tendrin damit an die Charakterdaten zu lesen und sofort auf den Bildschirm zu bringen. Verzögern wir also nach einem FLD bis zur Mitte der Rasterzeile, und schalten dann wieder zurück, so wird der Video-RAM Inhalt um exakt 20 Zeichen nach rechts versetzt auf dem Bildschirm dargestellt, wobei die 20 letzten Zei- chen, die ja nicht mehr in diese Text- zeile passen, automatisch erst in der nächsten Zeile erscheinen. Es kommt so- gar noch besser: aufgrund eines internen Timers des VIC, der das Lesen der Cha- rakterzeilen mitbeeinflusst (es sei den wir tricksen ihn aus) führt der VIC den Lesefehler in JEDER WEITEREN Charakter- zeile ebenso durch, so daß es genügt, lediglich am Bildschirmanfang einmal um einen bestimmten Wert zu verzögern, um DEN GESAMTEN Bildschirm wie gewünscht nach rechts versetzt darzustellen!!! Um den Trick nun umzusetzen müssen wir wiefolgt vorgehen: zunächst schalten wir in $D011 den vertikalen Verschiebe- Offset (wird in den untersten 3 Bits festgelegt) auf 1, so daß für den VIC die erste Charakterzeile erst eine Ra- sterzeile nach Beginn des sichtbaren Bildschirmfensters folgt. Dieser Beginn liegt normalerweise in Rasterzeile $30. Durch die Verschiebung legen wir die erste vom VIC zu lesende Charakterzeile jedoch in Rasterzeile $31. Verzögern wir nun jedoch in Rasterzeile $30 um eine bestimmte Anzahl Taktzyklen, und schal- ten wir dann die horizontale Verschie- bung mittendrin wieder auf 0 zurück, so merkt der VIC plötzlich, daß er sich doch schon in einer Charakterzeile be- findet und fängt einfrig damit an die Charakterdaten zu lesen und auf dem Bildschirm darzustellen. Wohlgemerkt obwohl er sich schon nicht mehr am Zei- lenanfang befindet, sondern mitten in- nerhalb dieser Rasterzeile! Für jeden Taktzyklus, den wir mehr verzögern, stellt der VIC die Charakterdaten um jeweils ein Zeichen (also 8 Pixel) wei- ter rechts dar. Schalten wir also 10 Takte nach Beginn des linken Bildrandes die vertikale Verschiebung ab, so wird die Charakterzeile exakt 10 Zeichen nach rechts versetzt gezeichnet. Dies setzt sich, wie oben schon erwähnt, über den gesamten Bildschirm, also auch für die folgenden 24 weiteren Charakterzeilen, fort, womit auch der gesamte Bildschirm um 10 Zeichen versetzt angezeigt wird! 2) DIE UMSETZUNG Wie das Ganze dann aussieht, können Sie sich in den Programmbeispielen "HSP1" und "HSP2" anschauen. Sie werden beide mit ",8,1" geladen und durch SYS4096 gestartet. HSP2 unterscheidet sich von HSP1 nur darin, daß das Zeichenwirrwarr, das durch den HSP-Effekt in der ersten Rasterzeile zu sehen ist, mit einem schwarzen Rasterbalken unsichtbar ge- macht wurde. Kommen wir nun also zu der Routine, die uns diesen Effekt erzeugt. Sie ist vom Aufbau eigentlich recht einfach. Zunächst einmal befindet sich eine Ini- tialisierungsroutine ab Adresse $1000, die wie die meisten unserer IRQ-Routinen das ROM abschaltet, um den IRQ-Vektor am Speicherende verwenden zu können, und anschließend einen Border-Raster-IRQ initialisiert, der uns zunächst einmal, wie immer, den unteren und oberen Bild- schirmrand abschaltet. Sie steht ab Adresse $1200 im Speicher und ist ge- folgt von einer Routine zum Auslesen des Joysticks, sowie der Routine "Control", die die Joystick-Daten auswertet, den Bildschirmverschiebeoffset berechnet und damit die HSP-IRQ-Routine beeinflusst. Selbige ist ab Adresse $1100 zu finden. Dir Border-IRQ-Routine legt diese Adres- se in den IRQ-Vektoren bei $FFFE/$FFFF ab, und gibt dem VIC vor, den nächsten Raster-IRQ in Rasterzeile $2D auszulö- sen. Wird unsere HSP-Routine nun in dieser Rasterzeile aufgerufen, so glätten wir zunächst den IRQ, nach der uns mittler- weile altbekannten Methode. Ab dem Label "Onecycle" steht nun unsere eigentliche Routine, die wiefolgt aussieht: onecycle: lda #$19 ;Bildsch. 1 Zeile nach sta $d011 ; unten scrollen
ldy #$08 ;Verzögerungsschleife wy dey ; um den richtigen bne wy ; Moment abzupassen jsr cycles ;Verzögerung 12 Takte lda #$18 ;Wert f. 1 Zeile zurück redu1 beq redu2 ;2 oder 3 Take verz. redu2 bne tt ;ans Ende verzweigen nop ;20 NOPs die für das nop ; Timing später SEHR nop ; WICHTIG sind, ob- nop ; wohl sie hier nop ; übersprungen werden!! nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop tt sta $d011 ;Wert schreiben
lda #$0e ;Bildschirmfarben set- sta $d020 ; zen (innerhalb dieser lda #$06 ; Befehle wird die Cha- sta $d021 ; rakterz. gelesen!) Zu Beginn unserer IRQ-Routine wird zunächst also eine vertikale Verschie- bung um eine Rasterzeile in $D011 einge- tragen (Bits 0-2 enthalten den Wert %001=$01). Dadurch, daß der HSP-IRQ in Rasterzeile $2D aufgerufen wurde, und daß die IRQ-Glättung 2 Rasterzeilen ver- braucht, befinden wir uns nun also in Rasterzeile $2F. Wir verzögern nun noch mit der folgenden Zählschleife und dem JSR-Befehl um eine knappe weitere Zeile, wobei exakt die Position abgepasst wird, an der sich der Rasterstrahl genau am Anfang des linken Randes des sichtbaren Bildschirms befindet (so wie bei der Routine zum Abschalten der seitlichen Bildschirmränder). Hierbei gehöhrt der "LDA #$18"-Befehl ebenfalls zur Verzöge- rung. Er initialisirt den Akku jedoch gleichzeitig schon mit dem Wert vor, den wir in $D011 schreiben müssen, um die vertikale Verschiebung wieder abzuschal- ten (die unstersten drei Bits enthalten den Wert 0). Nun folgt wieder eine sehr ausgeklügelte Verzögerung, um Taktgenau eine ganz bestimmte Rasterposition abzu- passen. Beachten Sie bitte, daß die Rou- tine an dieser Stelle von der oben schon erwähnten "Control"-Routine modifiziert wird, um die jeweils gewünschte Bild- schirmverschiebung zu erreichen. Wir haben hier nun zwei Branch-Befehle, die jeweils mit Labels versehen sind, und denen 20 NOP-Befehle folgen. Zum Schluß steht dann der STA-Befehl, mit dem der Wert des Akkus in $D011 eingetragen wird, um nach der gewünschten Verzöge- rung den VIC zum Lesen der Charakterzei- le zu bewegen. Schauen wir uns nun zunächst die beiden Branches und deren Bedeutung an: lda #$18 ;Wert f. 1 Zeile zurück redu1 beq redu2 ;2 oder 3 Take verz. redu2 bne tt ;ans Ende verzweigen Durch den vorherigen LDA-Befehl, der einen Wert ungleich Null lädt, wissen wir, daß das Zero-Flag in jedem Fall gelöscht ist. Ausserdem scheint der "BEQ REDU2"-Befehl unsinnig zu sein, zumal er exakt zum nächsten Befehl weiterspringt. Wie aber auch schon bei unserer IRQ- Glättungsroutine hat dieser Branch- Befehl die Aufgabe, exakt einen Taktzy- klus lang zu verzögern. Ein Branch- Befehl benötigt mindestens zwei Takte um ausgeführt zu werden. Trifft die abge- fragte Bedingung nicht zu, so wird gleich beim nächsten Befehl fortgefahren (so wie das hier auch der Fall ist). Trifft die Bedingung jedoch zu, so dauert der Branch-Befehl einen Taktzy- klus länger, in dem der Prozessor den Sprung-Offset auf den Programmzähler aufaddieren muß. Soll nun um eine gerade Anzahl Zyklen verzögert werden, weil der Bildschirm um eine gerade Anzahl Zeichen verschoben werden soll, so steht hier ein BEQ-Befehl, der nur 2 Zyklen ver- braucht. Soll eine ungerade Anzahl verzögert werden, so wird von der Routi- ne "Control", im Label "REDU1" der Opco- de für einen BNE-Befehl eingetragen, womit die Routine einen Taktzyklus län- ger dauert, und somit auch ungerade verzögert. Der nun folgende BNE-Befehl ist immer wahr und verzögert somit immer 3 Taktzyklen (da dies eine ungerade Zahl ist, verhält es sich also eigentlich umgekehrt mit der Änderung des BEQ- Befehls für gerade und ungerade Verzöge- rung). Bei diesem Befehl wird von "Con- trol" die Sprungadresse modifiziert. Je nach dem, wieviele weitere Zyklen verzö- gert werden müssen, trägt die Routine einen Offset auf die folgenden NOP- Befehle ein. Der Befehl verzweigt dann nicht mehr auf das Label "TT", wo der Akkuinhalt nach $D011 geschrieben wird, sondern auf einen NOP-Befehl davor. Ei- ner dieser Befehle verzögert dann immer um 2 Taktzyklen. Hierzu sollten wir ei- nen Blick auf die Routine "Contol" wer- fen: control:
ldx #$d0 ;Opcode für BNE in stx redu1 ; REDU1 ablegen lda xposhi ;X-Verschiebungs- sta xposhib ; zähler kopieren lda xposlo ; (Low- und High- sta xposlob ; byte) and #$08 ;Bit 3 v. XLo ausmask. bne co1 ;<>0, dann ldx #$f0 ;Opcode für BEQ in stx redu1 ; REDU1 ablegen co1 lsr xposhib ;X-Verschiebung ror xposlob ; (16 Bit) durch 4X lsr xposhib ; Rotieren nach rechts ror xposlob ; mit 16 dividieren lsr xposhib ror xposlob lsr xposhib ror xposlob sec ;Subtraktion vorber. lda #$14 ;20 NOPS sbc xposlob ; minus XPos/16 sta redu2+1 ;als Sprungoffset für ; BNE bei REDU2
lda xposlo ;Alte X-Versch. laden and #$07 ;unterste 3 Bits isol. ora #%00011000;Standard-Bits setzen sta softmove+1;in hor.Scroll eintr. rts Zu Allererst trägt die Control-Routine, die innerhalb des Border-IRQs aufgerufen wird, in das Label "REDU1" den Wert $D0 ein, der dem Opcode für den BNE-Befehl entspricht. Hieran anschließend werden die Inhalte der Labels "XPOSLO" und "X- POSHI" in die Labels "XPOSLOB" und "X- POSHIB" kopiert, was für die Offset- Berechnung später notwendig ist. Diese Labels sind im Source-Code des Programms auf die Zeropage-Adressen $02, $03, $04 und $05 vordefiniert. "XPOSLO" und "X- POSHI" enthalten einen 16-Bit Zähler für die horziontale Bildschirmverschiebung, die von der Joystickabfrage, die eben- falls während des Border-IRQs aufgerufen wird, den Joystickbewegungen entspre- chend hoch oder runter gezählt wird. Dieser Zähler kann also einen Wert zwi- schen 0 und 320 enthalten. Da mit der HSP-Routine der Bildschirm nur in Schritten von einzelnen Zeichen (also 8 Pixeln) versetzt werden kann, muß unsere Routine zum weichen Scollen des Bild- schirms auch noch den horizontalen Ver- schiebeoffset des Bildschirms verändern. Diese Verschiebung wird in Register $D016 des VICs festgelegt, wobei die untersten 3 Bits unseres XPOS-Wertes in die untersten 3 Bits dieses Registers gelangen müssen. Die Bits 3-9 des XPOS- Wertes enthalten nun (dreimal nach rechts verschoben) die Anzahl Zeichen, und somit auch Taktzyklen, die die HSP- Routine verzögern muß, damit der VIC die Charakterzeile erst an der gewünschten Position liest. Ist also das 3. Bit (das das 0. Bit der Anzahl ist) gesetzt, so muß eine ungerade Anzahl Zeichen verzö- gert werden. In dem Fall enthält das Label "REDU1" schon den richtigen Wert, nämlich den Opcode für den BNE-Befehl, der zusammen mit dem BNE-Befehl bei "RE- DU2" sechs (also eine gerade Anzahl) Taktzyklen verbraucht. Ist Bit 3 gelöscht, so enthält der Akku nach dem "AND #$08"-Befehl den Wert 0 und es wird vor dem Weitergehen im Programm der Op- code für den BEQ-Befehl in "REDU1" ein- getragen. Damit wird in der HSP-Routine um 2+3=5 (also eine ungerade Anzahl) Taktzyklen verzögert. Nun muß noch er- mittelt werden, wieviele zusätzliche NOPs zur Verzögerung notwendig sind. Da ein NOP-Befehl immer 2 Taktzyklen braucht, wird nur die halbe Anzahl NOPs benötigt, um enstsprechend viele Zeichen (Taktzyklen) lang zu verzögern. Da zudem noch die 3 Bit Verschiebeoffset in XPOS stehen, muß dieser Wert durch 16 divi- diert werden, um den gewünschten Wert zu erhalten. Diese Berechnung wird an der Kopie von XPOS, also in den Labels "X- POSLOB" und "XPOSHIB" durchgeführt. Nachdem im Low-Byte dieser Register hiernach die Anzahl der benötigten NOPs stehen, kann nun die Sprungadresse für den BNE-Befehl bei "REDU2" berechnet werden. Da Branch-Befehle immer nur ein Byte mit dem relativen Offset zum ak- tuellen Programmzähler enthalten, müssen wir also lediglich angeben, wieviele NOPs übersprungen werden müssen. Dies wird durch die Gesamtanzahl NOPs minus der benötigten Anzahl NOPs errechnet, und in Adresse "REDU2"+1 eingetragen. Die Verzögerung sollte nun also sauber funktionieren. Zu guter Letzt wird noch der horizontale Verschiebeoffset für das horzontale Softscrolling ermittelt, indem aus "XPOSLO" die untersten drei Bits ausmas- kiert werden. Da diese Bits im Register $D016 landen müssen, das auch für die 38/40-Zeichendarstellung und den Multi- colormodus zuständig ist, setzen wir die entsprechenden Bits mit dem folgenden OR-Befehl. Der resultierende Wert wird in das Label "SOFTMOVE"+1 eingetragen, das in der HSP-Routine kurz vor dem oben gezeigten Codestück steht:
softmove lda #$00 sta $d016
Hier wird also lediglich der Verschie- beoffset in den Operanden des LDA- Befehls eingetragen, der dann in der HSP-Routine die Bildschirmverschiebung jeweils wie benötigt in $D016 einträgt. Damit hätten wir alle Einzelheiten der HSP-Routine besprochen. Wie Sie sehen funktioniert sie sogar noch einfacher als die VSP-Routine. Vielleicht experi- mentieren Sie einmal ein wenig mit den Programmbeispielen und versuchen einen horizontalen Endlosscroller daraus zu machen. Der HSP-Effekt funktioniert übrigens genauso wie VSP auch mit HI- RES-Grafik. Im nächsten Kursteil werden wir auch das noch sehen, und die er- staunlichste IRQ-Raster-Routine kennen- lernen die je entdeckt wurde: die AGSP- Routine nämlich, die eine Kombination aus HSP und VSP darstellt, und mit der es problemlos möglich ist den kompletten Bildschirm in ALLE Richtungen zu scrol- len, ohne große Kopieraktionen mit dem Prozessor durchführen zu müssen!!!
(ih/ub)