Interrupt-Kurs "Die Hardware ausgetrickst..." (Teil 5) ----------------------------------------
Nachdem wir im letzten Monat ja schon kräftig mit schillernden Farb- und Si- nuswellenrasterroutinen um uns geworfen haben, möchten wir uns auch in dieser Ausgabe der MD einem sehr trickreichen Beispiel eines Raster-IRQs zuwenden: der FLD-Routine. 1) FLD - EIN ZAUBERWORT FÖR RASTERFREAKS Die Abkürzung "FLD" steht für "Flexible Line Distance", was übersetzt soviel bedeutet wie "beliebig verschiebbarer Zeilenunterschied". Diese, zunächst vielleicht etwas verwirrende, Bezeich- nung steht für einen Rastereffekt, der vom Prinzip und der Programmierung her extrem simpel ist, jedoch ungeahnte Mög-lichkeiten in sich birgt. Um zu wis- sen, welcher Effekt damit gemeint ist, brauchen Sie sich lediglich einmal anzu- schauen, was passiert, wenn Sie im MD- Hauptmenu einen neuen Text laden, oder einen gelesenen Text wieder verlassen: der Textbildschirm scheint hier von un- ten her hochgezogen, bzw. nach unten hin weggedrückt zu werden. Und genau das tut nun eine FLD-Routine. Hierbei sei darauf hingewiesen, daß es sich dabei nicht um irgendeine Programmierakrobatik handelt, bei der aufwendig hin- und herkopiert und rumgescrollt werden muß, sondern um eine einfache, ca. 150 Byte große, Ra- sterroutine! Der Trick des Ganzen liegt wie so oft bei der Hardware des 64ers, die wieder einmal beispielhaft von uns "veräppelt" wird. Denn eigentlich sollte sie nicht dazu in der Lage sein, einen solchen Effekt zu erzeugen! Wie funktioniert nun diese Routine? Wie Sie vielleicht wissen, kann in den unte- ren drei Bits von Register 17 des VICs ($D011), ein vertikaler Verschiebeoffset für die Bildschirmdarstellung eingetra- gen werden. In der Regel benutzt man diese Bits um ein vertikales Softscrol- ling zu realisieren. Je nach dem welcher Wert dort eingetragen wird (von 0 bis 7), kann die Darstellung des sichtbaren Bildschirms um 0 bis 7 Rasterzeilen nach unten verschoben werden. Lässt man diese Werte nacheinander durch das Register laufen, und kopiert man daraufhin den Inhalt des Bildschirms von der 2. Text- zeile in die 1. Textzeile, so entsteht ein Softscrolling nach unten. Der Wert, der dabei in den unteren drei Bits von $D011 steht gibt dem VIC an, ab welchem vertikalen Bildschirmoffset er damit anfangen soll, die nächste Textzeile aufzubauen. Wie wir aus dem letzten Kur- steil noch wissen, geschieht dies ab Rasterzeile 41 und jeweils in jeder ach- ten, folgenden Zeile. Wird nun ein vert- kaler Verschiebeoffset angegeben, so verzögert der VIC diesen Zeitpunkt um die angegebene Anzahl Rasterzeilen (ma- ximal 7). Steht in der Vertikalverschie- bung z.B. der Wert 1, so muß der VIC also noch eine Rasterzeile warten, bis er die nächste Charakterzeile aufzubauen hat. Der Trick der FLD-Routine liegt nun da- rin, daß Sie in jeder Rasterzeile, die- sen Charakterzeilenanfang vor dem Ra- sterstrahl "herschiebt", so daß dieser eigentlich nie die gesuchte Anfangszeile erreichen kann - zumindest nicht solan- ge, wie unsere FLD-Routine ihm vortäu- scht, noch nicht den Anfang dieser Zeile erreicht zu haben! Wie einfach das alles geht, soll Ihnen folgendes Programmbei- spiel verdeutlichen. Sie finden es auf dieser MD unter dem Namen "FLD-DEMO1" und müssen es absolut (mit ",8,1") laden und wie alle unsere Programmbeispiele mit "SYS4096" starten:
init: sei ;IRQs sperren lda #$7f ;CIA-Timer abschalten sta $dc0d ; (SYS-IRQ) lda $dc0d ;Und CIA-ICR löschen lda #$f8 ;Zeile $f8 ist IRQ- sta $d012 ; Auslöser lda $d011 ;7.Bit Rasterzeile and #$7f ; löschen sta $d011 ; u. zurückschr. lda #$01 ;VIC löst Raster-IRQs sta $d01a ; aus ldx #<(IRQ2);IRQ-Vektor auf ldy #>(IRQ2); eigene Routine stx $0314 ; verbiegen sty $0315 lda #$00 ;Zählregister sta $02 ; löschen lda #$ff ;Leerbereich auf sta $3fff ; schwarz setzen cli ;IRQs freigeben verz: rts ;Und Ende --- irq1: lda #$10 ;Vert. Verschiebung sta $d011 ;gleich 0 lda $02 ;Zähler laden beq lab2 ;Wenn 0, überspringen ldx #$00 ;Zählreg. löschen lab1: clc ;Carry f.Add.löschen (!) lda $d011 ;Verschiebung holen (!) adc #$01 ; +1 (!) and #$07 ; unt. Bits ausmask. (!) ora #$10 ; Bit 4 setzen (!) sta $d011 ; u. zurückschr. dec $d019 ;VIC-IRQ freigeben jsr verz ;Verzögern... jsr verz lda $d012 ;Strahlpos < Bild- cmp #$f6 ; schrimende? beq lab2 ;Ja, also überspr. inx ;Zähler+1 cpx $0002 ;Zähler=Endwert? bne lab1 ;Nein, also weiter lab2: lda #$f8 ;Rasterz. $f8 ist sta $d012 ; nächster IRQ-Ausl. dec $d019 ;VIC-IRQs freigeb. lda #$78 ;IRQ-Vektor auf IRQ2- sta $0314 ; Routine verbiegen ldx #$0e ;Bildschirmfarben ldy #$06 ; zurücksetzen stx $d020 sty $d021 jmp $febc ;IRQ beenden --- irq2: lda #$10 ;Vertikal-Versch. sta $d011 ; init. lda #$71 ;Rasterz. $71 ist sta $d012 ; IRQ-Auslöser dec $d019 ;VIC-IRQs freigeben lda #$30 ;IRQ-Vektor auf IRQ1- sta $0314 ; routine verbiegen lda $dc00 ;Portreg lesen lsr ;Akku in Carry rot. bcs lab3 ;C=1? Wenn ja, weiter dec $02 ;Sonst Joystick hoch lab3: lsr ;Akku in Carry rot. bcs lab4 ;C=1? Ja, also weiter inc $02 ;Sonst Joyst. runter lab4: jmp $ea31 ;SYS-IRQ und Ende
Die Beschreibung der Initialisierungs- routine können wir uns sparen, da wir ihren Aufbau ja schon von anderen Pro- grammbeispielen her kennen. Wichtig ist nur, daß wir hier Rasterzeile $F8 als IRQ-Auslöser festlegen, und die zweite IRQ-Routine ("IRQ2") in den IRQ-Vektor eintragen. Ansonsten wird hier auch noch der FLD-Zeilenzähler in Speicherzelle $02 gelöscht, sowie der Wert $FF in letzte Adresse des VIC-Bereichs ge- schrieben. Die Bedeutung dieser Adresse kennen wir noch von unserer Borderrouti- ne aus dem letzten Kursteil: ihr Inhalt wird in schwarzer Farbe immer an allen Stellen auf dem Bildschirm angezeigt, an denen wir den Rasterstrahl mit unseren Interrupts austricksen, was ja auch hier der Fall ist. Möchten wir an solchen Stellen die Hintergrundfarbe sehen, so müssen wir den Wert $00 hineinschreiben. Die Routine "IRQ2" wird nun immer einmal pro Bildschirmaufbau aufgerufen. Sie bereitet die eigentliche FLD-Routine vor, die ab der Rasterzeile $71 aus- gelöst werden soll. Gleichzeitig bein- haltet diese Routine eine Joystickabfra- ge, mit der wir das Zählregister in Adresse $02 ändern können. Auf diese Weise kann mit dem Joystick die FLD- Lücke ab Rasterzeile $71, je nach Wunsch, vergrößert oder verkleinert wer- den. Abschließend biegt diese IRQ- Routine den IRQ-Vektor auf die eigentli- che FLD-IRQ-Routine ("IRQ1") und ruft den System-IRQ auf, den wir in der Init- Routine ja abgeschaltet hatten und nun "von Hand" ausführen müssen. Hiernach ist nun "IRQ1" am Zug. Kern der Routine ist die Schleife zwischen den beiden Labels "LAB1" und "LAB2". Am wichtigsten sind hierbei die fünf Befeh- le die ich Ihnen mit Ausrufungszeichen markiert habe. Hier wird zunächst der Inhalt des Registers $D011 gelesen, in dem der vertikale Verschiebeoffset zu finden ist, und 1 auf diesen Wert hin- zuaddiert. Da dabei auch ein Öberlauf in das 3. Bit des Registers stattfinden kann, das ja nicht mehr zur vertikalen Verschiebung herangezogen wird, müssen wir mit dem folgenden AND-Befehl alle Bits außer den unteren dreien ausmaskie- ren, und mittels ORA, das 3. Bit wieder setzen, da es steuert, ob der Bildschirm ein, oder ausgeschaltet sein soll, und deshalb immer gesetzt sein muß. An- schließend wird der neue Wert für $D011 wieder zurückgeschrieben. Da diese Ver- schiebungsänderung nun auch in jeder folgenden Zeile auftreten soll, solange bis der Zeilenzähler abgelaufen ist, müssen mit dem Rest der Routine die 63 Taktzyklen, die der Rasterstrahl zum Aufbau einer Rasterzeile braucht, verzö- gert werden. Eine Unterscheidung in nor- male Rasterzeilen und Charakterzeilen, in denen der Prozessor vom VIC ja für 42 Taktzyklen angehalten wird, und die Schleife deshalb weniger verzögern muß, braucht diesmal nicht durchgeführt wer- den, da wir durch das "vor-uns-Her- schieben" der nächsten Charakterzeile deren Aufbau ja solange verhindern, bis die Schleife der FLD-Routine beendet ist. Dies ist dann der Fall, wenn entwe- der der Zähler im X-Register bis auf 0 gezählt wurde, oder aber die Rasterzeile $F6 erreicht wurde, ab der der untere Bildschirmrand beginnt. Ab dem Label "LAB2", wird nun wieder Rasterzeile $F8 für "IRQ2" als Interrup- tauslöser festgelegt. Zusätzlich verbie- gen wir den IRQ-Vektor auf diese Routine zurück. Dabei wird in unserem Beispiel lediglich das Low-Byte geändert, da bei- de Routinen ja an einer Adresse mit $10xx anfangen, und somit die High-Bytes der beiden Routinenadressen immer den Wert $10 haben. Zum Schluß wird wieder auf den Teil der Betriebssystemroutine ($FEBC) gesprungen, der die Prozessorre- gister vom Stack zurückholt und den In- terrupt beendet. Die Art und Weise, wie wir hier die Ver- tikalverschiebung vor dem Rasterstrahl herschieben mag etwas umständlich anmu- ten. Tatsächlich gibt es hier auch noch andere Möglichkeiten, die in den Bei- spielprogrammen "FLD-DEMO2", und "FLD- DEMO3" benutzt wurden. Sauberer ist die Lösung des Zeilenproblems, wenn man das Register, das die aktuelle Rasterzeile enthält ($D012), als Zähler verwendet. Wir müssen hier lediglich die Rasterpo- sition auslesen, ihren Wert um 1 erhö- hen, die unteren drei Bits ausmaskieren und das 4. Bit in diesem Register wieder setzen. Selbiges wird durch die folgende Befehlsfolge durchgeführt:
clc lda $d012 adc #$01 and #$07 ora #$10 sta $d011
Noch schneller geht das, wenn man den illegalen Opcode "ORQ" verwendet. Er addiert 1 auf den Akku hinzu und vero- dert gleichzeitig das Ergebnis mit dem Operandenwert. Die Befehlsfolge ist dann nur noch vier Zeilen lang:
lda $d012 and #$07 orq #$10 sta $d011
Selbst wenn diese Methode kürzer ist, als die zuvorgenannte, ist es dennoch nicht ratsam sie zu verwenden, da "ORQ" wie gesagt ein illegaler, also inoffi- zieller, Assemblerbefehl ist, und des- halb von den meisten Assemblern und Dis- assemblern nicht erkannt wird. Zudem können Laufzeitunterschiede oder gar Fehlfunktionen bei verschiedenen Produk- tionsversionen des 6510-Prozessors vor- kommen, so daß ein Programm mit einem solchen illegalen Opcode nicht auf jedem C64 lauffähig sein muß. Wer es wirklich kurz will, der sollte über eine Tabelle die benötigten Zeilendaten holen, wie das im Beispiel "FLD-DEMO3" der Fall ist. Hier wurde eine Tabelle bei Adresse $1200 abgelegt, die den jeweils entspre- chenden Wert für jede einzelne Raster- zeile enthält. Die eigentlichen FLD- Befehle verkürzen sich damit auf die beiden folgenden Zeilen:
lda $1200,x sta $d011
Die Lösung des Problems über eine Tabel- le beinhaltet gleichzeitig auch noch den Vorteil, daß wir viel flexibler die FLD- Effekte einsetzen können. So ist es da- mit sehr einfach möglich, mehrere Cha- rakterzeilen zu verschieben, wie das im "FLD-DEMO3" der Fall ist. Dieses Bei- spielprogramm beginnt übrigens ausnahms- weise an Adresse $1100, weswegen es nicht wie sonst mit "SYS4096", sondern durch ein "SYS4352" aufgerufen werden muß. Alles in allem sollten Sie sich die drei Beispiele ruhig einmal mit einem Disas- sembler oder Speichermonitor anschauen um ihre Funktionsweise zu verstehen. Mit FLD erzielbare Effekte sind sehr viel- seitig und sie sollten schon ein wenig damit herumexperimentieren. Weiterhin gibt es einige Rastereffekte die durch eine FLD-Routine stark vereinfacht pro- grammiert werden können, oder sogar ohne sie gar nicht möglich wären, weswegen ein gründliches Verständnis der Materie sehr von Vorteil bei anderen Rasteref- fekten sein kann. 2) TIMINGPROBLEME UND TAKZYKLENMESSER Wie wir wieder einmal bewiesen haben, ist die Rasterprogrammierung eine Sache, bei der es auf absolut exaktes Timing ankommt. Noch haariger wird das im näch- sten Kursteil ersichtlich, wo wir Ihnen eine Sideborder-Routine vorstellen wer- den. Wird diese Routine auch nur einen Taktzyklus zu früh oder zu spät aus- geführt, so funktioniert sie schon nicht mehr. Deswahlb wollen wir uns nun erst wieder ein wenig in die Theorie stürzen und Verfahrensweisen zur Ermittlung der Laufzeit eines Programms vorstellen. Wie Sie mittlerweile nun oft genug mit- bekommen haben, braucht der Rasterstrahl zum Aufbau einer Rasterzeile genau 63 Taktzyklen. Innerhalb dieser Zeit müssen wir den Prozessor immer irgendwie beschäftigen, damit wir rechtzeitig zum Beginn der nächsten Rasterzeile eine weitere Änderung vornehmen können. Hinzu kommt, daß wir bei eigeschaltemem Text- modus und Rastereffekten im sichtbaren Bildschirmbereich beachten müssen, daß jede achte Rasterzeile, jeweils am Be- ginn einer Charakterzeile, der VIC den Prozessor für 42 Taktzyklen anhält, da- mit er die, in den folgenden acht Ra- sterzeilen darzustellenden, Zeichen ge- nerieren kann. Somit bleiben für den Prozessor für solch eine Rasterzeile nur noch 21 Taktzyklen Rechenzeit. Um nun ein exaktes Timing zu erreichen müssten wir eigentlich die Laufzeiten eines je- den einzelnen Befehls einer Raster- Routine zusammenaddieren um herauszufin- den, ob eine Routine schnell, bzw. lang- sam genug, abgearbeitet wird. Das kann unter Umständen eine sehr aufwendige Sache werden, da hierbei ewig lang Be- fehlstabellen mit Zyklenangaben gewälzt werden müssten, und bei jeder kleinen Änderung neue Verzögerungsbefehle in die Routine eingefügt, oder aus ihr entfernt werden müssten. Damit Sie die Zyklenzahlen selbst zur Hand haben, habe ich Ihnen am Ende die- ses Kurses in einer Tabelle alle Prozes- sor-Befehle in allen möglichen Adressie- rungsarten aufgelistet. Um also von Hand die Laufzeit einer Routine zu berechnen können Sie dort nachschlagen. Noch einfach geht das Abwägen der Lauf- zeit jedoch mit einem Programm. Wir kön- nen uns hier die Möglichkeit zunutze machen, daß mit den Timern der CIAs ein- zelne Zyklen gezählt werden können. Ich habe Ihnen hierzu ein Zyklenmessprogramm geschrieben, das es Ihnen ermöglicht, eine eigene Routine bezüglich ihrer Laufzeit zu testen. Es heißt "Cycle- count" und ist ebenfalls auf dieser MD zu finden. Das Programm ist in der Lage, Routinen mit einer Dauer von maximal 65490 Taktzyklen zu stoppen. Laden Sie es hierzu mit LOAD"CYCLECOUNT",8,1 in den Speicher und schreiben Sie Low- und High-Byte der zu testenden Routine in die Adressen 828/829 ($033c/$033d). Die zu messende Routine muß mit einem "BRK"-Befehl beendet werden. Rufen Sie nun das Programm mit einem "SYS49152" auf. Cyclecount gibt Ihnen daraufhin den ermittelten Zyklen-Wert auf dem Bild- schirm aus. Das Programm benutzt dabei den Timer A von CIA-B zum Messen der Zyklen. Es initialisert diesen Timer mit dem Wert $FFFF, startet ihn und ruft daraufhin die zu testende Routine auf. Zuvor wird der BRK-Vektor bei Adresse $0316/$0317 auf eine eigene Routine ver- bogen. Wird die zu testende Routine nun mit einem BRK-Befehl beendet, so wird sofort zur Auswertungsroutine von Cycle- count verzweigt, die den Timer wieder anhält und den in den Timerregistern enhaltenen Wert von $FFFF subtrahiert. Zudem müssen 45 Zyklen abgezogen werden, die hauptsächlich zur Ausführung des JMP-Befehls auf die zu testende Routine und durch den beendenden BRK-Befehl ver- braucht wurden, und nicht mitgezählt werden dürfen. (Anm.d.Red.: Bitte wählen Sie jetzt den 2.Teil des Kurses aus dem Textmenu.)