Interrupt-Kurs "Die Hardware ausgetrickst..." (Teil 5)
Nachdem wir im letzten Monat ja schon
kräftig mit schillernden Farbund Sinuswellenrasterroutinen 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, Bezeichnung steht für einen Rastereffekt, der
vom Prinzip und der Programmierung her
extrem simpel ist, jedoch ungeahnte Möglichkeiten in sich birgt. Um zu wissen, welcher Effekt damit gemeint ist, brauchen Sie sich lediglich einmal anzuschauen, was passiert, wenn Sie im MD-Hauptmenu einen neuen Text laden, oder
einen gelesenen Text wieder verlassen:
der Textbildschirm scheint hier von unten 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 hinund herkopiert
und rumgescrollt werden muß, sondern um
eine einfache, ca.150 Byte große, Rasterroutine! Der Trick des Ganzen liegt
wie so oft bei der Hardware des 64 ers, 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 unteren drei Bits von Register 17 des VICs($ D011), ein vertikaler Verschiebeoffset
für die Bildschirmdarstellung eingetragen werden. In der Regel benutzt man
diese Bits um ein vertikales Softscrolling 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 . Textzeile 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 Kursteil noch wissen, geschieht dies ab
Rasterzeile 41 und jeweils in jeder achten, folgenden Zeile. Wird nun ein vertkaler Verschiebeoffset angegeben, so
verzögert der VIC diesen Zeitpunkt um die angegebene Anzahl Rasterzeilen ( maximal 7) . Steht in der Vertikalverschiebung 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 darin, daß Sie in jeder Rasterzeile, diesen Charakterzeilenanfang vor dem Rasterstrahl " herschiebt", so daß dieser
eigentlich nie die gesuchte Anfangszeile
erreichen kann - zumindest nicht solange, wie unsere FLD-Routine ihm vortäuscht, noch nicht den Anfang dieser Zeile
erreicht zu haben! Wie einfach das alles
geht, soll Ihnen folgendes Programmbeispiel 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 Initialisierungsroutine können wir uns sparen, da wir
ihren Aufbau ja schon von anderen Programmbeispielen 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 geschrieben. Die Bedeutung dieser Adresse
kennen wir noch von unserer Borderroutine 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 ausgelöst werden soll. Gleichzeitig beinhaltet diese Routine eine Joystickabfrage, 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 werden. Abschließend biegt diese IRQ-Routine den IRQ-Vektor auf die eigentliche 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 Befehle 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 hinzuaddiert. 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 ausmaskieren, 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ß. Anschließend wird der neue Wert für $ D011 wieder zurückgeschrieben. Da diese Verschiebungsä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 normale 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 werden, da wir durch das " voruns- Herschieben" 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 Interruptauslöser festgelegt. Zusätzlich verbiegen wir den IRQ-Vektor auf diese Routine
zurück. Dabei wird in unserem Beispiel
lediglich das Low-Byte geändert, da beide Routinen ja an einer Adresse mit
$10 xx 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 Prozessorregister vom Stack zurückholt und den Interrupt beendet.
Die Art und Weise, wie wir hier die Vertikalverschiebung vor dem Rasterstrahl
herschieben mag etwas umständlich anmuten. 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 Rasterposition 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 verodert 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 inoffizieller, Assemblerbefehl ist, und deshalb von den meisten Assemblern und Disassemblern nicht erkannt wird. Zudem
können Laufzeitunterschiede oder gar
Fehlfunktionen bei verschiedenen Produktionsversionen des 6510- Prozessors vorkommen, 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 entsprechenden Wert für jede einzelne Rasterzeile 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 Tabelle beinhaltet gleichzeitig auch noch den
Vorteil, daß wir viel flexibler die FLD-Effekte einsetzen können. So ist es damit sehr einfach möglich, mehrere Charakterzeilen zu verschieben, wie das im
" FLD-DEMO3" der Fall ist. Dieses Beispielprogramm beginnt übrigens ausnahmsweise 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 Disassembler oder Speichermonitor anschauen um ihre Funktionsweise zu verstehen. Mit
FLD erzielbare Effekte sind sehr vielseitig und sie sollten schon ein wenig
damit herumexperimentieren. Weiterhin
gibt es einige Rastereffekte die durch
eine FLD-Routine stark vereinfacht programmiert 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 Rastereffekten 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ächsten Kursteil ersichtlich, wo wir Ihnen
eine Sideborder-Routine vorstellen werden. Wird diese Routine auch nur einen
Taktzyklus zu früh oder zu spät ausgefü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 mitbekommen 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 Textmodus und Rastereffekten im sichtbaren
Bildschirmbereich beachten müssen, daß
jede achte Rasterzeile, jeweils am Beginn einer Charakterzeile, der VIC den
Prozessor für 42 Taktzyklen anhält, damit er die, in den folgenden acht Rasterzeilen darzustellenden, Zeichen generieren 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 herauszufinden, ob eine Routine schnell, bzw. langsam genug, abgearbeitet wird. Das kann
unter Umständen eine sehr aufwendige
Sache werden, da hierbei ewig lang Befehlstabellen 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 dieses Kurses in einer Tabelle alle Prozessor- Befehle in allen möglichen Adressierungsarten aufgelistet. Um also von Hand
die Laufzeit einer Routine zu berechnen
können Sie dort nachschlagen.
Noch einfach geht das Abwägen der Laufzeit jedoch mit einem Programm. Wir können uns hier die Möglichkeit zunutze
machen, daß mit den Timern der CIAs einzelne 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 " Cyclecount" 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 Lowund
High-Byte der zu testenden Routine in
die Adressen 828/829($033 c/$033 d) . 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 Bildschirm 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 Cyclecount 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 verbraucht wurden, und nicht mitgezählt
werden dürfen.
( Anm. d. Red. : Bitte wählen Sie jetzt den
2 . Teil des Kurses aus dem Textmenu.)