Interrupt-Kurs "Die Hardware ausgetrickst..." (Teil 7)
Nachdem wir uns im letzten Kursteil ausgiebig um das IRQ-Timing gekümmert hatten, 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 darstellen 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 Bildschirms. 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 vertikalen Scrollen verwendet, da man bei
einem 38- Spalten-Bildschirm neu hereinlaufende Bildschirmzeichen setzen kann, ohne daß sie vom Betrachter gesehen werden.
Durch das rechtzeitige Setzen und Lö- schen dieses Bits kann man nun auch den
linken und rechten Bildschirmrand abschalten. Hierbei liegt die Betonung
besonders auf dem Wort " rechtzeitig" . Da
der Rasterstrahl in der vertikalen nämlich derart schnell ist, daß der Prozessor kaum nachkommt, müssen wir den Zeitpunkt der Umschaltung sehr genau abpassen, 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 Endet, umgeschaltet werden muß, und der
Rasterstrahl zum Zeichnen von 8 Pixeln
gerade mal 1 .2 Taktzyklen benötigt, haben 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 beginnen abzuarbeiten. Der eigentliche
Schreibzugriff findet dann im dritten Taktzyklus, genau zwischen 38- und 40- Zeichen-Rand statt. Zur Verdeutlichung
sollten Sie sich einmal das Programmbeispiel " 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 Joystickbewegungen nach oben und unten können Sie die
Anzahl der zu öffnenden Zeilen variieren.
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 Interruptquellen, 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, damit der Sideborder-Effekt auch sichtbar
wird. Sie sehen hier zunächst 7 NOPs, mit denen wir vom Ende der Rasterglättung bis zum Beginn des sichbaren rechten Bildschirmrandes verzögern. Hieraufhin 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 effektiv 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 bemerken, daß wir lediglich im oberen, sonst mit dem Rahmen verdeckten Bereich
des Bildschirms den seitlichen Rand
geöffnet haben. Das funktioniert natürlich nur, wenn wir den VIC zuvor mit dem
normalen Bordereffekt überlistet haben, so daß er den oberen und unteren Bildschirmrand 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 unser 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 Kombination 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 entscheidenden 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 Routine vom Timing her immer noch genauso
funktioniert, wobei wir den Bildschirmrand jedoch innerhalb des normalen Bildbereichs 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 Sprites darstellen. Dies gestaltet sich jedoch ebenfalls schwieriger als sonst.
Nämlich so wie der VIC den Prozessor in
jeder Charakterzeile anhält, um ungestö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 dieser Rasterzeile anzuzeigen sind. Hierzu
benötigt er ungefähr 2 .4 Taktzyklen, die
uns von der Prozessorzeit natürlich wieder abgehen! Somit muß die Verzögerung
innerhalb unserer Schleife verkürzt werden. Da wir aber keine Bruchteile von
Taktzyklen verzögern können, wird die
ganze Sache umso haariger! ! ! In der Regel hilft hier nur die alte " Trialand- 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, anstelle von ursprünglich 29, warten, bis
der Rasterstrahl die nächste Zeile erreicht. Es gehen uns also 17 Zyklen verloren! 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 Sideborder- Effekt nur in den Rasterzeilen
funktioniert, in denen die Sprites
sichtbar sind. Das gilt auch dann, wenn
Sie die Sprites in Y-Richtung expandieren ( vor dem Aufruf des Beispiels PO-KE53271,255 eingeben) . Hier funktioniert
der Effekt dann doppelt soviele Zeilen
lang, da der VIC bei Expansion jede Zeile eines Sprites einfach doppelt liest( auf zwei Rasterzeilen verteilt) . Möchten Sie den Rand weiter nach unten öffnen, so ist nur noch ein komisches Linien- 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 sieben Sprites darzustellen sind. Schalten
Sie weniger oder mehr Sprites ein, so
ist der Sidebordereffekt zunichte gemacht. Das gleiche passiert, wenn Sie
nicht alle sieben Sprites Yexpandieren.
Hier Endet der Effekt nach 21 Zeilen, nämlich dann wenn ein nichtexpandiertes
Sprite zu Ende gezeichnet ist, und Sie
sehen im Border nur die Hälfte der expandierten Sprites!
4) HINWEISE ZUM OPTIMALEN TIMING Möchten Sie nun selbst die Verzögerung
ausloten, die zur Darstellung einer bestimmten Anzahl Sprites benötigt wird,
so beachten Sie folgende Regeln:
* Geben Sie allen Sprites, die in Bereichen angezeigt werden, in denen der
Sideborder abgeschaltet ist, ein und
dieselbe Y-Koordinate, so daß sie horizontal in einer Linie liegen. Dadurch 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 Bereich sichtbaren, Sprites expandiert
sein ( es sei denn Sie möchten nur die
Hälfte eines expandierten Sprites sehen) .
* 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
NOPund 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 Anzahl 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 funktioniert. 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 Sprite ist lediglich zur Orientierung gedacht. Da der Prozessor immer nur zu
einem Taktsignal eine Operation beginnen kann, sollte die Verzögerung also immer zu finden sein.
* Zum Testen, wann Register $ D016 geändert 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 Einstellung 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ämlich eine Sideborderroutine, die acht
Sprites im Border darstellt. Das verwunderliche an ihr ist die Funktionsweise.
Denn obwohl die Sideborderschleife genauso viele Taktzyklen verbraucht wie
die Version für sieben Sprites, kann
dennoch ein Sprite mehr dargestellt werden. Ermöglicht wird dies durch eine
trickreichere Variante des Löschen und
Setzen des Bildschirmbreite-Bits aus
Register $ D016 . Wir initialisieren dieses Register vor der eigentlichen Sideborderroutine 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 Befehls schon früher eintritt. Alles in
Allem ist das Funktionieren dieser Methode 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 Assemblerprogrammierung ein, da es an der Funktionsweise des DEC-Befehls liegt, warum
das Bit rechtzeitig gelöscht wird.
Beachten Sie daher bitte auch diese Variation der Sideborder-Routine für eigene 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 Xexpandiert 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 Hilfe 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äutere, zumal es ja eigentich nicht zu unserem Thema gehört.
Öbrigens: vielleicht ist Ihnen schon
aufgefallen, daß beim Üffnen des Sideborders 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 Abschalten des Randes zwar am rechten Ende
einer Rasterzeile geschieht, sich jedoch
auf das linke Ende der folgenden Rasterzeile auswirkt!
In der nächsten Folge dieses Kurses werden wir Ihnen zeigen, wie man den Border
OHNE wegdrücken des Bildschirms durch
FLD abschaltet ( besonders haarige Timingprobleme) . 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 besprochenen Beispiele nocheinmal genauer anschauen, und ein wenig damit herumexperimentieren. Sie ahnen nicht wie vielseitig man mit dem abgeschalteten Border
arbeiten kann. . .
(ub/ih)