Magic Disk 64

home to index to text: MD9404-KURSE-IRQ-KURS_6.1.txt

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

Valid HTML 4.0 Transitional Valid CSS!