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)