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.)