Interrupt-Kurs
"Die Hardware ausgetrickst..."
(Teil 6)
----------------------------------------
Anhand der FLD-Routine des letzen Kurs-
teils hatten wir gesehen, wie einfach
man die Hardware unseres kleinen Brot-
kastens austricksen kann, und sie dazu
bewegt Dinge zu tun, zu denen sie ei-
gentlich nicht in der Lage ist. So soll
es auch in den folgenden Teilen des
IRQ-Kurses sein, jedoch müssen wir uns
zuvor um ein Problem kümmern, mit dessen
Lösung wir noch trickreichere Rasteref-
fekte programmieren und den Copper- und
Blittermagiern des Amigas das Fürchten
lehren werden...
1) AUF GUTES TIMING KOMMT ES AN...
Wie auch schon am Ende des letzten Teils
angesprochen, ist der Schlüssel zu tol-
len Rasterinterrupts ein besonders exak-
tes Timing. Wie schon am Beispiel der
FLD-Routine unschwer erkennbar war, be-
steht das eigentliche "Austricksen" des
Video-Chips meist aus gerade einer hand-
voll Befehlen. Wichtig ist nur, daß die-
se Befehle zum richtigen Zeitpunkt aus-
geführt werden. Wie wichtig das ist,
werden wir später am Beispiel einer Ra-
sterroutine sehen, die in der Lage ist,
den linken und rechten Rand des Bild-
schirms abzuschalten. Wird sie auch nur
einen Taktzyklus zu früh oder zu spät
ausgeführt, so bewirkt sie absoult gar-
nichts. Nur wenn zu einem ganz bestimm-
ten Zeitpunkt der Wert eines Registers
verändert wird, funktioniert sie auch
wie sie soll! Damit wir solche Effekte
also auch realisieren können, werden wir
uns nun zwei Problemen widmen, die immer
noch Ungenauigkeitsfaktoren in unseren
Routinen darstellen und eliminiert wer-
den müssen:
2) SYSTEMVEKTOREN SIND SCHNELLER
Der erste Ungenauigkeitsfaktor ist das
Betriebssystem des C64. Wie Sie ja aus
den ersten Kursteilen wissen, holt sich
der 6510-Prozessor bei einer Interrupt-
anfrage zunächst einmal eine der drei
Interrupt-Sprungadressen am Ende seines
Adressierungsbereichs (von $FFFA-$FFFF)
in den Programmzähler. Hier stehen die
jeweiligen Adressen der Betriebssystems-
routinen, die die entsprechende Art von
Interrupt (IRQ, NMI oder Reset) bedie-
nen. Gerade beim IRQ ist diese Routine
etwas aufwendiger aufgebaut, da derselbe
Vektor auch für softwaremäßige BRK-
Interrupts benutzt wird, und die Routine
deshalb eine Unterscheidung treffen muß.
Dadurch stiehlt sie uns quasi Prozessor-
zeit, wenn wir davon ausgehen, daß der
BRK-Interrupt in der Regel nicht verwen-
det wird, da er ja einfacher durch ein
JMP programmiert werden kann. Erst nach
der Unterscheidung verzweigt die Routine
dann über den Vektor $0314/$0315 auf die
eigentliche IRQ-Routine (bzw. über
$0316/$0317 zur BRK-Routine). Und erst
an dieser Stelle "klinken" wir unsere
eigenen Raster-IRQs in das Interrupt-
system ein. Um nun die Verzögerung durch
das Betriebssystem zu eliminieren, müs-
sen wir es umgehen. Es sollte eigentlich
also reichen, wenn wir die Startadresse
unserer Interruptroutine im Vektor
$FFFE/$FFFF eintragen, da er der IRQ-
Vektor ist. Hierbei stellt sich uns je-
doch ein weiteres Problem in den Weg:
diese Adressen gehören ja zum Be-
triebssystem-ROM und können nicht verän-
dert werden, da sie für alle Zeiten in
dem ROM-Chip eingebrannt sind. Aber
nicht verzagen, denn "unter" diesem ROM
befindet sich auch noch echtes RAM, und
das können wir verändern. Damit der Pro-
zessor sich dann den IRQ-Vektor auch von
dort holt, müssen wir das darüberliegen-
de ROM sozusagen "ausblenden", was über
das Prozessoradressregister geschieht,
das in Speicherzelle 1 zu finden ist. Im
Normalfall steht hier der Wert 55 ($37),
der das Basic- und Betriebssystem-ROM in
den Adressbereichen $A000-$BFFF (Basic),
sowie $E000-$FFFF (System) einblendet.
Führen wir Schreibzugriffe auf diese
Bereiche aus, so landen diese, selbst
bei eingeschaltetem ROM im darunterle-
genden RAM. Das Problem dabei ist nur,
daß wir dann die geschriebenen Werte
nicht auslesen können, da wir immer nur
den Inhalt der ROM-Adresse lesen können.
Um das zu ändern, muß der Wert 53 ($35)
in das Prozessoradressregister geschrie-
ben werden. Dadurch werden nämlich die
beiden ROM-Bausteine deaktiviert und das
darunterliegende RAM kommt zum Vor-
schein, worauf der Prozessor dann auch
Zugriff hat.
Ändern wir nun den IRQ-Vektor bei
$FFFE/$FFFF, und tritt dann ein IRQ auf,
so wird direkt auf unsere IRQ-Routine
verzweigt, ohne daß der Umweg über das
Betriebssystem gegangen wird. Beachten
Sie hierbei jedoch, daß durch das Ab-
schalten des ROMs weder Basic- noch Be-
triebssystemsroutinen verfügbar sind, da
wir sie ja weggeschaltet haben. Benutzt
Ihr eigenes Programm solche Routinen, so
müssen Sie das ROM zuvor ins RAM kopie-
ren. Dies tun Sie, indem Sie einfach bei
eingeschaltetem ROM eine Adresse ausle-
sen und gleich wieder in sie zurück-
schreiben. Beim Lesen erhalten Sie dann
den Wert des ROMs, beim Schreiben schik-
ken Sie ihn ins RAM darunter. Jetzt kön-
nen Sie getrost das ROM abschalten, ohne
daß Ihr Rechner abstürzt. Als Beispiel
zum Kopieren der beiden ROMs ins darun-
terliegende RAM können Sie sich das Pro-
gramm "COPYSYS" auf dieser MD anschauen,
das wie all unsere Beispielprogramme
absolut (mit ",8,1") geladen werden muß,
und mit SYS4096 (JMP $1000) gestartet
wird.
In unseren Beispielen werden wir aller-
dings keinerlei Betriebssystemsroutinen
verwenden, weswegen wir uns hier das
Kopieren einsparen. Wichtig ist dabei,
daß wir die Interruptquellen von CIA-A
sperren, damit sie uns mit ihren IRQs
nicht zwischen die Raster-IRQs "funkt".
3) DAS "GLÄTTEN" VON INTERRUPTS
Wenn Sie schon ein wenig mit Raster-IRQs
"herumgespielt" haben, so wird Ihnen
vielleicht schon einmal folgendes Pro-
blem aufgefallen sein: Möchten Sie einen
Interrupt programmieren, der z.B. ein-
fach nur die Hintergrundfarbe ändert, so
passiert es manchmal, daß gerade an der
Stelle, an der die Farbe geändert wird,
ein unruhiges Flackern zu sehen ist. Man
hat den Eindruck, als würde die Farbe
mal ein paar Pixel früher oder später
geändert werden, so daß an der selben
Stelle des Bildschirms manchmal die al-
te, manchmal die neue Farbe zu sehen
ist. Irgendwie scheint es also nicht
möglich zu sein, den Interrupt immer zur
selben Zeit auftreten zu lassen - obwohl
die Interruptroutine immer einen kon-
stanten Wert an Taktzyklen verbraucht,
und deshalb der Flackereffekt gar nicht
auftreten dürfte!
Tatsächlich liegt die Ursache allen
Öbels nicht beim Interrupt, sondern am
Hauptprogramm: Tritt nämlich eine Inter-
ruptanforderung am Prozessor auf, so muß
dieser zunächst einmal den aktuell bear-
beiteten Befehl zu Ende führen, bevor er
den Interruptvektor anspringen kann.
Angenommen, er wäre gerade dabei den
Befehl "LDA #$00" auszuführen. Dieser
Befehl benötigt 2 Taktzyklen. Einen zum
Lesen und Dekodieren des Befehlsbytes,
und einen zum Lesen des Operanden und
Laden des Akkus. Hat der Prozessor nun
gerade das Befehlsbyte gelesen und tritt
in genau diesem Moment der Interrupt
auf, so muß er zunächst noch den Operan-
den lesen, um anschließend in die IRQ-
Routine verzweigen zu können. Selbige
wird dadurch aber erst einen Taktzyklus
später, als eigentlich erforderlich ge-
wesen wäre, ausgeführt. Noch größer wird
die Verzögerung, wenn gerade z.B. ein
"STA $D021" (4 Taktzyklen!) oder gar ein
"ROR $1000,X" (7 Taktzyklen!) ausgeführt
wurde.
Das Programm "FLACKER" auf dieser MD
veranschaulicht dieses Problem. Die Rou-
tine ist eine Kombination aus FLD- und
Borderroutine. Mit dem FLD-Teil drücken
wir einen Teil des Bildschirms nach un-
ten und stellen im Zwischenraum einen
Rasterbalken dar. Der Border-Teil schal-
tet einfach den oberen und unteren Bild-
schirmrand weg und dient mehr als Bei-
spiel zur Kombination der beiden Raster-
effekte. Der Rasterbalken ist nun auch
in der X-Richtung verschiedenfarbig, so
daß eine Zeile in der einen Hälfte eine
Farbe und in der anderen Hälfte eine
zweite Farbe enthält. Dieser "Raster-
split" ist übrigens nur durch die FLD-
Routine möglich, da diese ja verhindert,
daß eine Charakterzeile gelesen wird,
die den Prozessor für 42 Taktzyklen
(zwei Drittel der gesamten Zeile also)
anhält. Ohne FLD könnte zum Beginn jeder
Charakterzeile die Farbänderung gar
nicht rechtzeitig (nämlich nach der
Hälfte) stattfinden, da der Prozessor
zum erforderlichen Zeitpunkt ja immer
noch durch den VIC angehalten wäre. Au-
ßerdem hat die FLD-Routine den Vorteil,
daß wir keinen Unterschied zwischen Cha-
rakter- und normalen Rasterzeilen machen
müssen, weshalb wir uns die verschach-
telten Schleifen sparen. Sie starten das
Programm wie immer mit "SYS4096" und
verlassen es durch einen Druck auf den
Feuerknopf. Mit Joystickbewegungen nach
oben und unten können Sie übrigens die
Größe der FLD-Öffnung variieren.
Kommen wir nun zu unserem Problem
zurück: sieht man sich das Beispielpro-
gramm einmal an, so wird man ein erhe-
bliches Flackern bemerken. Das liegt
daran, daß das Programm zur Demonstra-
tion gerade in den zeitkritischen Momen-
ten, besonders zeitintensive Befehle
ausführt, weswegen der IRQ mit bis zu 7
Taktzyklen Verzögerung auftreten kann.
Da nun aber für die weiteren Beispiele
dieses Kurses eine höhere Präzision er-
forderlich ist, müssen wir uns eine Me-
thode angewöhnen, mit der wir einen In-
terrupt "glätten" können. Selbiges tut
nämlich das dritte Beispiel dieses Kur-
steils, mit dem Namen "LOESUNG". Hier
das dokumentierte Listing:
;*** Initialiserung ($1000)
Init: sei ;IRQ sperren
lda #$7f ;Timer IRQ
sta $dc0d ;abschalten
bit $dc0d ;ICR löschen
lda #$f8 ;Rasterzeile $f8 als
sta $d012 ; IRQ-Auslöser festlegen
lda $d011 ;Bit 7 löschen
and #$7f
sta $d011
lda #$01 ;Raster als IRQ
sta $d01a ; wählen
ldx #<Bord;Hard-IRQ-Vektoren
ldy #>Bord; auf eigene
stx $fffe ; Routine
sty $ffff ; umstellen
lda #$33 ;Zeilenabstand für FLD
sta $02 ; initialisieren
lda #$00 ;VIC-Byte löschen
sta $3fff
lda #$35 ;ROM ausblenden
sta $01
cli ;Interrupts erlauben
Hier haben wir den Initialisierungsteil
vor uns. Wie üblich sperren wir zunächst
die IRQs mittels SEI-Befehl, schalten
alle von CIA-A möglichen Interruptquel-
len ab, und löschen mit Hilfe des BIT-
Befehsl eine evtl. noch gemeldete Inter-
ruptanfrage. Anschließend wird Raster-
zeie $F8 als Interruptauslöser festge-
legt (mit Löschen des Hi-Bits in $D011)
und dem VIC mitgeteilt, daß er Raster-
IRQs erzeugen soll. Nun erst wird der
IRQ-Vektor (wohlgemerkt der bei $FFFE/
$FFFF und nicht der bei $0314/$0315) mit
der Adresse der Border-Routine gefüt-
tert. Zum Schluß löschen wir dann noch
die letze VIC-Adresse, deren Inhalt ja
im abgeschalteten Border und dem FLD-
Bereich angezeigt wird, und legen den
FLD-Zähler fest (Speicherzelle $02), so
daß er $33 (dez. 51) Zeilen öffnet. Nun
erst wird das ROM durch Schreiben von
$35 in die Speicherzelle 1 ausgeblendet,
damit der Prozessor bei einem IRQ auch
unseren Vektor bei $FFFE/$FFFF ans-
pringt, und die Interrupts werden wieder
erlaubt. Die Init-Routine kehrt nun
nicht wieder zur normalen Eingabe
zurück, da wir damit ja in eine Routine
des Basics zurückspringen würden, die
nach abschalten des ROMs nicht mehr vor-
handen ist. Stattdessen folgt nun eine
Hauptschleife, mit der wir ständig den
Joystickknopf abfragen. Hierbei erfüllen
die ersten sieben Befehle eigentlich
keinen Zweck. Es sind nur besonders zei-
tintenive Befehle, die wir zur Öberprü-
fung, ob unser Glätten auch funktio-
niert,im Programm haben. Sie sind ge-
folgt von einer simplen Abfrage des
Feuerknopf-Bits von Joyport 2:
;*** Hauptprogramm
wfire inc $03 ;Dummy-Befehle, die
inc $2000 ; absichtlich besonders
ror $03 ; viel Rechenzeit
ror $2000 ; verbrauchen.
bit $03
ldx #$00
ror $2000,x
lda $dc00 ;Joyport laden
and #$10 ;Firebutton-Bit isol.
bne wfire ;Nicht gedr. -> weiter
Wird der Feuerknopf nun gedrückt, so
müssen wir die ROMs wieder einschalten,
dem VIC die IRQs verbieten und sie der
CIA wieder erlauben, um das Programm
verlassen zu können. Dies tut folgende
Endroutine:
sei ;IRQs sperren
lda #$37 ;ROMs einschalten
sta $01
lda #$f0 ;VIC-IRQs sperren
sta $d01a
dec $d019 ;ggf.VIC-IRQ-Anf.lösch.
lda #$1b ;Normaler Darstellungs-
sta $d011 ; modus (wg. Border)
lda #$81 ;CIA-A darf Timer-IRQs
sta $dc0d ; auslösen
bit $dc0d ;ggf.CIA-IRQ-Anf.lösch.
cli ;IRQs freigeben
rts ;und ENDE
Kommen wir nun zur Borderroutine. Sie
ist die erste IRQ-Routine, die nach der
Initialisierung (bei Rasterzeile $F8)
aufgerufen wird:
Bord pha ;Akku, X- u. Y-Reg.
txa ; auf Stapel retten
pha
tya
pha
lda #$10 ;24-Zeilen-Darst. an
sta $d011 ; (=Bordereffekt)
lda #$3d ;nächsten IRQ bei 2.
sta $d012 ; Charakterzeile ausl.
dec $d019 ;VIC-ICR löschen
ldx #<FLD1;IRQ-Vektoren auf
ldy #>FLD1; erste
stx $fffe ; FLD-Routine
sty $ffff ; verbiegen
jsr JoyCk ;Joystickabfrage
pla ;Akku, X- u. Y-Reg.
tay ; wieder vom Stapel
pla ; holen
tax
pla
rti ;IRQ beenden.
Die Routine macht eigentlich nichts wei-
ter, als die 24-Zeilen-Darstellung zu
aktivieren, die in Rasterzeile $F8 ja
das Abschalten des Borders bewirkt, die
Rasterzeile des nächsten IRQs festzule-
gen ($3D=Startposition der 2. Charakter-
zeile-2), den Interrupt-Vektor auf die
Routine (FLD) für diese Zeile zu verbie-
gen, und den Joystick abzufragen (Unter-
routine, die hier nicht aufgeführt ist).
Beachten Sie, daß wir hier die Prozes-
sorregister mit Hilfe der Transfer- und
Stapelbefehle von Hand retten und wie-
derherstellen müssen. Gerade das Retten
war nämlich eine Aufgabe, die uns das
Betriebssystem freundlicherweise schon
abgenommen hatte. Da es jetzt ja abge-
schaltet ist, müssen wir uns natürlich
selbst darum kümmern.
(Anm. d. Red.:
Bitte wählen Sie jetzt den 2. Teil
des Kurses aus dem MD-Menu!)