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