Interrupt-Kurs
"Die Hardware ausgetrickst..."
(Teil 7)
----------------------------------------
Nachdem wir uns im letzten Kursteil aus-
giebig um das IRQ-Timing gekümmert hat-
ten, wollen wir in dieser Ausgabe nun
eine Anwendung besprechen, bei der vor
allem das Glätten von Interrupts eine
große Bedeutung einnimmt: es geht um die
Sideborderroutinen.
1) DAS PRINZIP
Im dritten Kursteil hatten wir ja schon
gelernt, wie man den oberen und unteren
Bildschirmrand abschaltet. Wir hatten
dem VIC hierzu zunächst mitgeteilt, daß
er einen 25-Zeilen hohen Bildschirm dar-
stellen soll. Zwei Rasterzeilen bevor er
jedoch den Rand desselben erreichte
schalteten wir ihn auf 24-Zeilen-
Darstellung um, weswegen er glaubte,
schon zwei Rasterzeilen vorher mit dem
Zeichnen des Randes begonnen zu haben.
Da dies aber nicht der Fall war, und der
VIC seine Arbeit normal fortführte,
"vergaß" er sozusagen, den unteren und
den oberen Bildschirmrand zu zeichnen,
was uns ermöglichte, in diesen Bereichen
Sprites darzustellen. Derselbe Trick
funktioniert nun auch mit dem linken und
rechten Bildschirmrand - zumindest vom
Prinzip her. Bit 3 in VIC-Register $D016
steuert die Breite des sichtbaren Bild-
schirms. Ist dieses Bit gesetzt, so
zeichnet der VIC 320 sichtbare Pixel in
der Vertikalen, was einer Darstellung
von 40 Zeichen pro Zeile entspricht.
Löschen wir dieses Bit, so stellt er nur
302 Pixel, bzw. 38 Zeichen pro Zeile
dar. Dieses Bit wird vor allem zum ver-
tikalen Scrollen verwendet, da man bei
einem 38-Spalten-Bildschirm neu herein-
laufende Bildschirmzeichen setzen kann,
ohne daß sie vom Betrachter gesehen wer-
den.
Durch das rechtzeitige Setzen und Lö-
schen dieses Bits kann man nun auch den
linken und rechten Bildschirmrand ab-
schalten. Hierbei liegt die Betonung
besonders auf dem Wort "rechtzeitig". Da
der Rasterstrahl in der vertikalen näm-
lich derart schnell ist, daß der Prozes-
sor kaum nachkommt, müssen wir den Zeit-
punkt der Umschaltung sehr genau abpas-
sen, damit unser Effekt funktioniert.
Bei der 38-Zeichen-Darstellung beginnt
der linke Bildschirmrand nur 8 Pixel
später und endet nur 8 Pixel früher als
sonst. Da aber genau dann, wenn er En-
det, umgeschaltet werden muß, und der
Rasterstrahl zum Zeichnen von 8 Pixeln
gerade mal 1.2 Taktzyklen benötigt, ha-
ben wir eben nur genau diese Zeit zur
Verfügung, um Bit 3 in Register $D016 zu
löschen. Da ein "STA $D016" aber drei
Taktzyklen verbraucht, muß der Prozessor
diesen Befehl also schon zwei Zyklen vor
dem Bildschirmrand erreichen und begin-
nen abzuarbeiten. Der eigentliche
Schreibzugriff findet dann im dritten
Taktzyklus, genau zwischen 38- und 40-
Zeichen-Rand statt. Zur Verdeutlichung
sollten Sie sich einmal das Programmbei-
spiel "SIDEBORDER.0" anschauen. Wie alle
unsere Beispiele ist es absolut (also
mit ",8,1") zu laden und mit SYS4096 zu
starten. Sie verlassen die Routine mit
einem Druck auf den Feuerknopf eines
Joysticks in Port2. Mit Joystickbewegun-
gen nach oben und unten können Sie die
Anzahl der zu öffnenden Zeilen variie-
ren.
Wir möchten Ihnen nun die Kernroutine
des Beispiels zeigen. Die Erklärung der
Interruptinitialisierung werden wir uns
an dieser Stelle sparen, da wir sie ja
schon oft genug besprochen haben. Im
Beispiel selbst haben wir alle Inter-
ruptquellen, außer den Interrupts vom
Rasterstrahl gesperrt. Desweiteren wurde
das Betriebssystems-ROM abgeschaltet,
und die Adresse auf unsere Routine
gleich in den Vektor bei $FFFF/$FFFE
geschrieben, so daß der Prozessor ohne
Zeitverzögerung zu unserer Routine
springt. Selbige "glättet" den Interrupt
zunächst nach der im letzten Kursteil
beschriebenen Art und Weise. Direkt nach
der Glättung folgt nun diese Routine:
...
LDX #00 ;Zeilenzähler löschen
CLC
LOOP NOP ;7 NOPs Verzögerung
NOP ; bis Zeilenende
NOP
NOP
NOP
NOP
NOP
LDA #00 ;EINEN Taktzykl. vor
STA $D016 ;Beg. d.rechten Randes
LDA #08 ;Bit 3 löschen und
STA $D016 ;gleich wieder setzen
NOP ;13 weitere NOPs zum
NOP ; Verzögern von 26
NOP ; Taktzyklen
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
BIT $EA ;3 Zyklen verzögern
INX ;Linienzähler+1
CPX $02 ;Mit Anz.zu öffnender
BCC LOOP ;Zeilen vgl. u. weiter
...
Diese Abfolge von Befehlen beinhaltet
nun haargenau das benötigte Timing, da-
mit der Sideborder-Effekt auch sichtbar
wird. Sie sehen hier zunächst 7 NOPs,
mit denen wir vom Ende der Rasterglät-
tung bis zum Beginn des sichbaren rech-
ten Bildschirmrandes verzögern. Hierauf-
hin wird Bit 3 von Register $D016
gelöscht, um auf 38-Zeilen-Darstellung
umzuschalten, und gleich darauf wieder
gesetzt, damit das Umschalten in der
nächsten Rasterzeile noch genauso effek-
tiv ist. Anschließend wird mit den 13
NOPs und dem BIT-Befehl um insgesamt 29
Taktzyklen verzögert, und geprüft, ob
das X-Register, das als Zähler der schon
geöffneten Rasterzeilen fungiert, den
Wert der zu öffnenden Zeilen enthält,
der in Speicherzelle $02 abgelegt ist.
Wenn nicht, so wird wieder zum Anfang
der Schleife verzweigt und somit eine
weitere Rasterzeile geöffnet.
2) EINE VERFEINERUNG
Wenn Sie das Beispiel einmal gestartet
und angesehen haben, so werden Sie be-
merken, daß wir lediglich im oberen,
sonst mit dem Rahmen verdeckten Bereich
des Bildschirms den seitlichen Rand
geöffnet haben. Das funktioniert natür-
lich nur, wenn wir den VIC zuvor mit dem
normalen Bordereffekt überlistet haben,
so daß er den oberen und unteren Bild-
schirmrand ebenfalls wegfallen lässt.
Desweiteren kann der Rand dort nur bis
zur letzten Zeile, des (unsichtbaren)
oberen Bildschirmrandes geöffnet werden,
was einen besonderen Grund hat: Wie wir
mittlerweile ja wissen, liest der VIC
zum Beginn einer jeden Charakterzeile
(also jede achte Rasterzeile), die 40 in
dieser Zeile darzustellenden Zeichen
ein, und blockiert während dieser Zeit
den Prozessor für eine Zeitspanne von 42
Taktzyklen. Dadurch gerät natürlich un-
ser ganzes Timing durcheinander, weshalb
der Effekt an diesen Stellen nicht mehr
zu sehen ist (obwohl immer noch dieselbe
Schleife läuft!). Um nun innerhalb des
normalen Bildschirms, in dem Zeichen
dargestellt werden können, den Rand zu
öffnen, verkompliziert sich das Timing
natürlich erheblich, da wir jede achte
Rasterzeile 42 Taktzyklen weniger zur
Verfügung haben. Die einfachste Methode,
dieses Problem zu umgehen, ist die Kom-
bination der Sideborderroutine mit einer
FLD-Routine. Dadurch schieben wir ja den
Beginn der nächsten Charakterzeile vor
dem VIC her, so daß er keine Daten liest
und uns somit nicht bei der Arbeit
stört. Diesen Weg geht unser zweites
Programmbeispiel mit dem Namen "SIDEBOR-
DER.1". Hier das Listing der entschei-
denden Schleife. Auch hier wurde der
Interrupt natürlich zuvor geglättet:
...
LOOP LDA $D012 ;FLD-Routine
ADC #$02
AND #$07
ORA #$18
STA $D011
LDA #$00 ;wieder 1 Zykl. vor Beg.
STA $D016 ;d. rechten Randes Bit 3
LDA #$08 ;von $D016 löschen und
STA $D016 ;gleich wieder setzen
NOP ;13 weitere NOPs zum
NOP ; Verzögern von 26
NOP ; Taktzyklen
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
NOP
BIT $EA ;3 Zyklen verzögern
INX ;Linienzähler+1
CPX $02 ;Mit Anz. zu öffnender
BCC LOOP ;Zeilen vgl. u. weiter
...
Im Prinzip hat sich hier nicht viel
geändert. Nur daß die 7 NOPs am Anfang
der Routine den fünf Befehlen des FLD-
Effektes gewichen sind. Diese brauchen
ebenso 14 Taktzyklen, so daß unsere Rou-
tine vom Timing her immer noch genauso
funktioniert, wobei wir den Bildschirm-
rand jedoch innerhalb des normalen Bild-
bereichs abgeschaltet haben!
3) SPRITES IM SIDEBORDER
Wie bei den Bildschirmrändern oben und
unten kann man natürlich auch in den
seitlichen, abgeschalteten Rändern Spri-
tes darstellen. Dies gestaltet sich je-
doch ebenfalls schwieriger als sonst.
Nämlich so wie der VIC den Prozessor in
jeder Charakterzeile anhält, um un-
gestört die Charakterdaten zu lesen, so
hält er ihn auch an, wenn er Spritedaten
zu lesen hat. Hierbei prüft der VIC
zunächst einmal, ob ein Sprite an der
aktuellen Rasterposition überhaupt
sichtbar ist. Wenn ja, so liest er die
drei Datenbytes des Sprites, die in die-
ser Rasterzeile anzuzeigen sind. Hierzu
benötigt er ungefähr 2.4 Taktzyklen, die
uns von der Prozessorzeit natürlich wie-
der abgehen! Somit muß die Verzögerung
innerhalb unserer Schleife verkürzt wer-
den. Da wir aber keine Bruchteile von
Taktzyklen verzögern können, wird die
ganze Sache umso haariger!!! In der Re-
gel hilft hier nur die alte "Trial-and-
Error"-Methode, um das perfekte Timing
genau auszuloten. Zum Einschalten von
sieben Sprites müssen wir nach dem Lö-
schen und Setzen des Anzeigebits von
Register $D016 nur noch 12 Takte, an-
stelle von ursprünglich 29, warten, bis
der Rasterstrahl die nächste Zeile er-
reicht. Es gehen uns also 17 Zyklen ver-
loren! Programmbeispiel "SIDEBORDER.2"
benutzt anstelle der 13 NOPs und dem
BIT-Befehl nur noch 6 NOPs. Sehen Sie
sich dieses Beispiel ruhig einmal mit
einem Disassembler oder Speichermonitor
an. Wenn Sie durch Herunterdrücken des
Joysticks den geöffneten Bereich vergrö-
ßern, so werden Sie merken, daß der Si-
deborder-Effekt nur in den Rasterzeilen
funktioniert, in denen die Sprites
sichtbar sind. Das gilt auch dann, wenn
Sie die Sprites in Y-Richtung expandie-
ren (vor dem Aufruf des Beispiels PO-
KE53271,255 eingeben). Hier funktioniert
der Effekt dann doppelt soviele Zeilen
lang, da der VIC bei Expansion jede Zei-
le eines Sprites einfach doppelt liest
(auf zwei Rasterzeilen verteilt). Möch-
ten Sie den Rand weiter nach unten öff-
nen, so ist nur noch ein komisches Li-
nien-Wirr-Warr zu sehen, da in diesen
Zeilen keine Sprites angezeigt werden,
und somit wieder eine Verzögerung von 29
Taktzyklen von Nöten wäre! Wohlgemerkt
funktioniert dieser Effekt nur, wenn
auch wirklich in jeder Rasterzeile sie-
ben Sprites darzustellen sind. Schalten
Sie weniger oder mehr Sprites ein, so
ist der Sidebordereffekt zunichte ge-
macht. Das gleiche passiert, wenn Sie
nicht alle sieben Sprites Y-expandieren.
Hier Endet der Effekt nach 21 Zeilen,
nämlich dann wenn ein nicht-expandiertes
Sprite zu Ende gezeichnet ist, und Sie
sehen im Border nur die Hälfte der ex-
pandierten Sprites!
4) HINWEISE ZUM OPTIMALEN TIMING
Möchten Sie nun selbst die Verzögerung
ausloten, die zur Darstellung einer be-
stimmten Anzahl Sprites benötigt wird,
so beachten Sie folgende Regeln:
* Geben Sie allen Sprites, die in Berei-
chen angezeigt werden, in denen der
Sideborder abgeschaltet ist, ein und
dieselbe Y-Koordinate, so daß sie ho-
rizontal in einer Linie liegen. Da-
durch werden Ungleichheiten innerhalb
der Rasterzeilen vermieden, so daß
immer gleich viele Spritedaten gelesen
werden. Gleiches gilt für die Y-
Expandierung. Es sollten entweder alle
oder keines der, in einem solchen Be-
reich sichtbaren, Sprites expandiert
sein (es sei denn Sie möchten nur die
Hälfte eines expandierten Sprites se-
hen).
* Achten Sie darauf, daß andere Sprites
nicht in den Bereich ohne Sideborder
hineinragen, da auch sie das Timing
durcheinanderbringen.
* Beim Ausloten der Verzögerung sind
NOP- und BIT-Befehle am Besten zum
schrittweisen Timing-Test geeignet.
Möchten Sie eine ungerade Anzahl Zy-
klen verzögern, so benutzen Sie einen
Zeropage-BIT-Befehl (so wie der oben
benutzte "BIT $EA"), da er drei Zyklen
verbraucht. Bei einer geraden Anzahl
Zyklen reicht eine entsprechende An-
zahl NOPs, die jeweils nur 2 Zyklen
benötigen. So brauchen Sie zur Verzö-
gerung von z.B. 7 Zyklen 2 NOPs und
einen BIT-Befehl. Bei 8 Zyklen sind 4
NOPs ohne BIT-Befehl ausreichend, usw.
* Schätzen Sie in etwa ab, wieviele
Taktzyklen Sie bei einer bestimmten
Anzahl Sprites etwa benötigen, um zu
verzögern (Formel: 29-AnzSprites*2.4)
und testen Sie ob dieser Wert funktio-
niert. Wenn nicht ändern Sie in 1-
Zyklus-Schritten nach oben oder unten.
* Natürlich wird der Prozessor immer nur
eine gerade Anzahl Zyklen angehalten.
Der Richtwert von 2.4 Zyklen pro Spri-
te ist lediglich zur Orientierung ge-
dacht. Da der Prozessor immer nur zu
einem Taktsignal eine Operation begin-
nen kann, sollte die Verzögerung also
immer zu finden sein.
* Zum Testen, wann Register $D016 geän-
dert wird, sollten Sie die Zugriffe
auf dieses Register zu Testzwecken mit
Zugriffen auf Register $D021 ersetzen.
Sie erzielen dadurch eine Farbänderung
des Hintergrunds, und zwar genau zu
dem Zeitpunkt, zu dem normalerweise
auch Register $D016 verändert wird.
Sehen Sie einen schwarzen Strich am
Bildschirmrand, der länger als 8 Pixel
ist, so ist der Zugriff zu früh. Sehen
Sie gar keinen Strich, so war er zu
spät. Wenn Sie die richtige Einstel-
lung gefunden haben können Sie $D021
wieder mit $D016 ersetzen, und der
Sideborder-Effekt sollte funktioieren.
5) NOCH TRICKREICHERE PROGRAMMIERUNG
Zum Abschluß möchte ich Ihnen noch ein
viertes Programmbeispiel erläutern, näm-
lich eine Sideborderroutine, die acht
Sprites im Border darstellt. Das verwun-
derliche an ihr ist die Funktionsweise.
Denn obwohl die Sideborderschleife ge-
nauso viele Taktzyklen verbraucht wie
die Version für sieben Sprites, kann
dennoch ein Sprite mehr dargestellt wer-
den. Ermöglicht wird dies durch eine
trickreichere Variante des Löschen und
Setzen des Bildschirmbreite-Bits aus
Register $D016. Wir initialisieren die-
ses Register vor der eigentlichen Side-
borderroutine nämlich mit dem Wert $C8,
was dem Standardwert dieses Registers
entspricht. In der eigentlichen Routine
wird dann Bit 3 gelöscht, indem wir das
ganze Register mittels "DEC $D016" um
eins herunterzählen (auf $C7 - bin.
%11000111 - Bit 3 gelöscht). Ein direkt
folgendes "INC $D016" setzt das Bit dann
wieder. Hier das Listing der Kernroutine
des Beispiels "SIDEBORDER.3":
...
LOOP LDA $D012 ;FLD-Routine
ADC #$02
AND #$07
ORA #$18
STA $D011
DEC $D016 ;Bit 3 löschen und
INC $D016 ;gleich wieder setzen
NOP ;Wie bei "SIDEBORDER.2"
NOP ; stehen hier trotzdem
NOP ; nur 6 NOPs!!!
NOP
NOP
NOP
INX ;Linienzähler+1
CPX $02 ;Mit Anz. zu öffnender
BCC LOOP ;Zeilen vgl. u. weiter
...
Der Grund, warum die Routine dennoch
funktioniert ist, daß der Prozessor zur
Abarbeitung des DEC-Befehls sechs
Taktzyklen verbraucht, wobei der
Schreibzugriff mit dem dekrementierten
Wert durch den internen Aufbau des Be-
fehls schon früher eintritt. Alles in
Allem ist das Funktionieren dieser Me-
thode umso wunderlicher, da die beiden
Befehle DEC und INC, ebenso wie die bei-
den LDAs und STAs vorher, 12 Taktyklen
benötigen. Wir tauchen hier also schon
in die tiefere Bereiche der Assembler-
programmierung ein, da es an der Funk-
tionsweise des DEC-Befehls liegt, warum
das Bit rechtzeitig gelöscht wird.
Beachten Sie daher bitte auch diese Va-
riation der Sideborder-Routine für eige-
ne Zwecke und benutzen Sie sie, wenn
normales Ausloten nicht ausreicht.
6) EIN LETZTES BEISPIEL
Als zusätzlichen Leckerbissen haben wir
Ihnen noch ein weiteres Beispielprogramm
auf dieser MD untergebracht: "SIDEBOR-
DER.4" basiert auf dem selben Prinzip
wie "SIDEBORDER.3", nur daß die Sprites
zusätzlich X-expandiert wurden und somit
eine lückenlose Laufschrift durch den
Bildschirmrand dargestellt werden kann.
Die Zeichendaten werden dabei direkt aus
dem Zeichensatz-ROM geholt, und mit Hil-
fe des ROL-Befehls bitweise durch die
Sprites rotiert. Wie so etwas funktio-
niert wissen Sie bestimmt, weshalb ich
es hier nicht noch eimal extra erläute-
re, zumal es ja eigentich nicht zu unse-
rem Thema gehört.
Öbrigens: vielleicht ist Ihnen schon
aufgefallen, daß beim Üffnen des Side-
borders der rechte Bildschirmrand immer
eine Rasterzeile höher geöffnet ist, als
der Linke. Und das dieser umgekehrt eine
Rasterzeile länger offen ist, als der
Rechte. Das liegt daran, daß das Ab-
schalten des Randes zwar am rechten Ende
einer Rasterzeile geschieht, sich jedoch
auf das linke Ende der folgenden Raster-
zeile auswirkt!
In der nächsten Folge dieses Kurses wer-
den wir Ihnen zeigen, wie man den Border
OHNE wegdrücken des Bildschirms durch
FLD abschaltet (besonders haarige Ti-
mingprobleme). Desweiteren bleiben wir
dann beim Thema "Sprites", und wollen
uns anschauen, wie man den VIC derart
austrickst, daß er mehr als acht dieser
kleinen Grafikstückchen auf den Bild-
schirm zaubert. Bis dahin sollten Sie
sich die in der heutigen Folge bespro-
chenen Beispiele nocheinmal genauer an-
schauen, und ein wenig damit herumexpe-
rimentieren. Sie ahnen nicht wie viel-
seitig man mit dem abgeschalteten Border
arbeiten kann...
(ub/ih)