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)