IRQ-KURS "Die Hardware ausgetrickst..." (Teil 15)
Im letzten Kursteil hatten wir uns einen
ganz besonderen Rastertrick angeschaut.
Anhand einer VSP-Routine hatten wir gelernt, wie einfach es ist, den Bildschirm des C64 HARDWAREMÄSSIG, also ohne
den Prozessor mit großen Bildschirm-Verschiebe- Aktionen zu belasten, nach
oben und unten zu scrollen. Dabei unterschied 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 vergaß, weswegen wir in der Lage waren, einzelne Bereiche im Video-RAM zu überspringen, 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 Pixel nach rechts verschieben, wobei wir
den Prozessor nur läppische 3 Rasterzeilen lang in Anspruch nehmen müssen. Damit werden schnell scrollende Balleroder Jump' n Run Spiele, wie auf dem Amiga 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 Lesevorgang durchführen wollte machten wir ihn
durch FLD-Wegdrücken der Charakterzeilen
glauben, daß er doch noch nicht die entsprechende 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 anzuzeigen. Durch den FLD-Effekt haben wir
nun schon oft genung die erste Charakterzeile auf dem Bildschim vor ihm hergeschoben, so daß der VIC diese Zeile
verspätet erreichte, und somit der Bildschirm 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 Zeichendaten zu Lesen und Anzuzeigen hat, beginnt er auch unverzüglich mit dieser
Arbeit, und das ohne noch auf die horizontale 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 Rasterzeile vorschreibt, daß selbige noch keine Charakterzeile ist, und dann mitten
innerhalb dieser Zeile von uns wieder
auf normale Darstellung zurückgeschaltet
wird, so fängt der VIC auch prompt mittendrin 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 Zeichen, die ja nicht mehr in diese Text- zeile passen, automatisch erst in der
nächsten Zeile erscheinen. Es kommt sogar noch besser: aufgrund eines internen
Timers des VIC, der das Lesen der Charakterzeilen mitbeeinflusst ( es sei den
wir tricksen ihn aus) führt der VIC den
Lesefehler in JEDER WEITEREN Charakterzeile 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 Rasterzeile 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 schalten wir dann die horizontale Verschiebung mittendrin wieder auf 0 zurück, so
merkt der VIC plötzlich, daß er sich
doch schon in einer Charakterzeile befindet und fängt einfrig damit an die
Charakterdaten zu lesen und auf dem
Bildschirm darzustellen. Wohlgemerkt
obwohl er sich schon nicht mehr am Zeilenanfang befindet, sondern mitten innerhalb dieser Rasterzeile! Für jeden
Taktzyklus, den wir mehr verzögern, stellt der VIC die Charakterdaten um
jeweils ein Zeichen ( also 8 Pixel) weiter 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 gemacht 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 Initialisierungsroutine 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 Bildschirmrand abschaltet. Sie steht ab
Adresse $1200 im Speicher und ist gefolgt 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 Adresse in den IRQ-Vektoren bei $ FFFE/$ FFFF
ab, und gibt dem VIC vor, den nächsten
Raster-IRQ in Rasterzeile $2 D auszulösen.
Wird unsere HSP-Routine nun in dieser
Rasterzeile aufgerufen, so glätten wir
zunächst den IRQ, nach der uns mittlerweile 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 #$0 e ; Bildschirmfarben setsta $ d020 ; zen ( innerhalb dieser
lda #$06 ; Befehle wird die Chasta $ d021 ; rakterz. gelesen!)
Zu Beginn unserer IRQ-Routine wird
zunächst also eine vertikale Verschiebung um eine Rasterzeile in $ D011 eingetragen ( Bits 0-2 enthalten den Wert
%001=$01) . Dadurch, daß der HSP-IRQ in
Rasterzeile $2 D aufgerufen wurde, und
daß die IRQ-Glättung 2 Rasterzeilen verbraucht, befinden wir uns nun also in
Rasterzeile $2 F. 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ögerung. Er initialisirt den Akku jedoch
gleichzeitig schon mit dem Wert vor, den
wir in $ D011 schreiben müssen, um die
vertikale Verschiebung wieder abzuschalten ( die unstersten drei Bits enthalten
den Wert 0) . Nun folgt wieder eine sehr
ausgeklügelte Verzögerung, um Taktgenau
eine ganz bestimmte Rasterposition abzupassen. Beachten Sie bitte, daß die Routine an dieser Stelle von der oben schon
erwähnten " Control"- Routine modifiziert
wird, um die jeweils gewünschte Bildschirmverschiebung 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ögerung den VIC zum Lesen der Charakterzeile 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 Taktzyklus lang zu verzögern. Ein Branch-Befehl benötigt mindestens zwei Takte um
ausgeführt zu werden. Trifft die abgefragte 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 Taktzyklus 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 verbraucht. Soll eine ungerade Anzahl
verzögert werden, so wird von der Routine " Control", im Label " REDU1" der Opcode für einen BNE-Befehl eingetragen, womit die Routine einen Taktzyklus länger 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ögerung) . Bei diesem Befehl wird von " Control" 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. Einer dieser Befehle verzögert dann immer
um 2 Taktzyklen. Hierzu sollten wir einen Blick auf die Routine " Contol" werfen:
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 ebenfalls während des Border-IRQs aufgerufen
wird, den Joystickbewegungen entsprechend hoch oder runter gezählt wird.
Dieser Zähler kann also einen Wert zwischen 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 Bildschirms auch noch den horizontalen Verschiebeoffset 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 Opcode für den BEQ-Befehl in " REDU1" eingetragen. Damit wird in der HSP-Routine
um 2+3=5( also eine ungerade Anzahl)
Taktzyklen verzögert. Nun muß noch ermittelt 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 dividiert 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 aktuellen 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 ausmaskiert werden. Da diese Bits im Register
$ D016 landen müssen, das auch für die
38/40- Zeichendarstellung und den Multicolormodus 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 Verschiebeoffset 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 experimentieren 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 erstaunlichste IRQ-Raster- Routine kennenlernen 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 scrollen, ohne große Kopieraktionen mit dem
Prozessor durchführen zu müssen! ! !
(ih/ub)