Interrupt-Kurs " Die Hardware ausgetrickst. . ."
(Teil 6)
Anhand der FLD-Routine des letzen Kursteils hatten wir gesehen, wie einfach
man die Hardware unseres kleinen Brotkastens austricksen kann, und sie dazu
bewegt Dinge zu tun, zu denen sie eigentlich 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 Rastereffekte programmieren und den Copperund
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 tollen Rasterinterrupts ein besonders exaktes Timing. Wie schon am Beispiel der
FLD-Routine unschwer erkennbar war, besteht das eigentliche " Austricksen" des
Video-Chips meist aus gerade einer handvoll Befehlen. Wichtig ist nur, daß diese Befehle zum richtigen Zeitpunkt ausgeführt werden. Wie wichtig das ist, werden wir später am Beispiel einer Rasterroutine sehen, die in der Lage ist, den linken und rechten Rand des Bildschirms abzuschalten. Wird sie auch nur
einen Taktzyklus zu früh oder zu spät
ausgeführt, so bewirkt sie absoult garnichts. Nur wenn zu einem ganz bestimmten 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 werden 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 Interruptanfrage 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 Betriebssystemsroutinen, die die entsprechende Art von
Interrupt ( IRQ, NMI oder Reset) bedienen. 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 Prozessorzeit, wenn wir davon ausgehen, daß der
BRK-Interrupt in der Regel nicht verwendet 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 Interruptsystem ein. Um nun die Verzögerung durch
das Betriebssystem zu eliminieren, müssen 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 jedoch ein weiteres Problem in den Weg:
diese Adressen gehören ja zum Betriebssystem- ROM und können nicht verändert 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 Prozessor sich dann den IRQ-Vektor auch von
dort holt, müssen wir das darüberliegende 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 Basicund 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 darunterlegenden 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 geschrieben werden. Dadurch werden nämlich die
beiden ROM-Bausteine deaktiviert und das
darunterliegende RAM kommt zum Vorschein, 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 Abschalten des ROMs weder Basicnoch Betriebssystemsroutinen verfügbar sind, da
wir sie ja weggeschaltet haben. Benutzt
Ihr eigenes Programm solche Routinen, so
müssen Sie das ROM zuvor ins RAM kopieren. Dies tun Sie, indem Sie einfach bei
eingeschaltetem ROM eine Adresse auslesen und gleich wieder in sie zurückschreiben. Beim Lesen erhalten Sie dann
den Wert des ROMs, beim Schreiben schikken Sie ihn ins RAM darunter. Jetzt können Sie getrost das ROM abschalten, ohne
daß Ihr Rechner abstürzt. Als Beispiel
zum Kopieren der beiden ROMs ins darunterliegende RAM können Sie sich das Programm " 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 allerdings 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 Problem aufgefallen sein: Möchten Sie einen
Interrupt programmieren, der z. B. einfach 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 konstanten 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 Interruptanforderung am Prozessor auf, so muß
dieser zunächst einmal den aktuell bearbeiteten 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 Operanden lesen, um anschließend in die IRQ-Routine verzweigen zu können. Selbige
wird dadurch aber erst einen Taktzyklus
später, als eigentlich erforderlich gewesen 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 Routine ist eine Kombination aus FLDund
Borderroutine. Mit dem FLD-Teil drücken
wir einen Teil des Bildschirms nach unten und stellen im Zwischenraum einen
Rasterbalken dar. Der Border-Teil schaltet einfach den oberen und unteren Bildschirmrand weg und dient mehr als Beispiel zur Kombination der beiden Rastereffekte. 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 " Rastersplit" 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 Charakter- und normalen Rasterzeilen machen
müssen, weshalb wir uns die verschachtelten 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 Beispielprogramm einmal an, so wird man ein erhebliches Flackern bemerken. Das liegt
daran, daß das Programm zur Demonstration gerade in den zeitkritischen Momenten, 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 erforderlich ist, müssen wir uns eine Methode angewöhnen, mit der wir einen Interrupt " glätten" können. Selbiges tut
nämlich das dritte Beispiel dieses Kursteils, 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 $3 fff
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 Interruptquellen ab, und löschen mit Hilfe des BIT-Befehsl eine evtl. noch gemeldete Interruptanfrage. Anschließend wird Rasterzeie $ F8 als Interruptauslöser festgelegt ( 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üttert. 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 anspringt, 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 vorhanden 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 zeitintenive Befehle, die wir zur Öberprüfung, ob unser Glätten auch funktioniert, im Programm haben. Sie sind gefolgt 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 weiter, als die 24- Zeilen-Darstellung zu
aktivieren, die in Rasterzeile $ F8 ja
das Abschalten des Borders bewirkt, die
Rasterzeile des nächsten IRQs festzulegen ($3 D= Startposition der 2 . Charakterzeile-2), den Interrupt-Vektor auf die
Routine ( FLD) für diese Zeile zu verbiegen, und den Joystick abzufragen ( Unterroutine, die hier nicht aufgeführt ist) .
Beachten Sie, daß wir hier die Prozessorregister mit Hilfe der Transferund
Stapelbefehle von Hand retten und wiederherstellen müssen. Gerade das Retten
war nämlich eine Aufgabe, die uns das Betriebssystem freundlicherweise schon
abgenommen hatte. Da es jetzt ja abgeschaltet 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!)