Raster-IRQ: Endlich stabil!!!

weitersagen ...
Tweet about this on TwitterShare on FacebookShare on Google+Share on LinkedIn

CBM prg StudioEin stabiler Rasterzeileninterrupt

Nach dem etwas abrupten Ende von ‚Raster-IRQ: Timing-Probleme‚, wollen wir jetzt endlich unser kleines Problem (die 1 Pixel hohe Linie im oberen Rahmen) lösen. Aber so, dass wir weiterhin das BASIC nutzen können!

Wie gehabt, es wird ein PAL-System vorausgesetzt!

Auch hier ist es ratsam unter VICE „Rahmen debuggen„ zu aktivieren.

Wir haben gesehen, dass wir einfach nicht genügend Taktzyklen einsparen können, um direkt links mit unserer Linie zu beginnen. Ein Hauptgrund dafür ist die Zeit, die der Prozessor ‚intern‚ beim Eintreten des Rasterzeileninterrupts (bzw. bei jedem IRQ) benötigt. Diesen Punkt haben wir bisher vollkommen außer Acht gelassen. Wenn ein IRQ eintritt, dann werden die Rücksprungadresse und die Statusregister (SR) auf dem Stack gesichert und schließlich zur Adresse von $FFFE/FF gesprungen. Dafür benötigt die CPU 7 Taktzyklen und die können wir definitiv nicht verringern!

 
Wir wissen jetzt ziemlich genau, was passiert, sobald ein Raster-IRQ auftritt und wieviel Zeit (also Taktzyklen) vergeht bis wir in unserer Interrupt-Funktion landen. Einen weiteren Punkt habe ich euch bisher verschwiegen. Werfen wir also nochmal einen Blick auf den Ablauf und wieviele Taktzyklen für den jeweiligen Schritt benötigt werden.

  • 2-9 Taktzyklen fürs Beenden des aktuellen Befehls
    Wie jetzt 2-9?

    Unsere bisherige Annahme, dass der Prozessor den aktuellen Befehl beendet und dann den Interrupt ausführt, ist nicht ganz korrekt! Tatsächlich wird der IRQ nur dann direkt nach dem aktuellen Befehl ausgeführt, wenn der Interrupt spätestens 2 Taktzyklen vor dem Ende des aktuellen Befehls eintritt. Anderenfalls wird auch noch der nächste Befehl ausgeführt, bevor es weiter zu unserer IRQ-Routine geht. Daher kommen wir auf mind. 2 und max. 9 Zyklen (einer als Rest + max. 8 für den folgenden Befehl).
     
  • 7 Zyklen fürs Sichern der Rücksprungadresse und des Statusregisters auf dem Stack.
     
  • 29 Zyklen für das Verarbeiten der Funktion an  $FF48  und Sprung zu unserer Routine. Wir müssen das ROM ja aktiviert lassen, damit wir zum BASIC zurückkehren können.

Bis wir also endlich in unserer Interrupt-Routine landen, vergehen 38-45 Taktzyklen! Nur zur Erinnerung, bei einem PAL-System stehen uns lediglich 63 Zyklen (und die auch nicht immer) je Zeile zur Verfügung!


Die rettende Idee
Wenn es uns nur gelänge die Zeit des letzten Befehls, den die CPU ausführt, bevor unser Rasterzeileninterrupt eintritt zu ermitteln, dann könnten wir einfach den Rest der Zeile verstreichen lassen und so in der nächsten Zeile Zyklen genau beginnen. Nichts anderes ist die Aufgabe des stabilen Rasterzeileninterrupts (im Englischen ‘stable raster‚).

Aber wie erreichen wir nun dieses Ziel???

 

Doppelter Rasterzeileninterrupt
Richten wir zunächst (fast) wie gewohnt unseren Rasterzeileninterrupt ein.

Soweit, so bekannt, bis auf den abschließenden Sprung nach $EA31.
Da wir auf den Timer-IRQ verzichten, damit er uns nicht in die Quere kommt, müssen wir noch irgendwie dafür sorgen, dass der Cursor blinkt, die Uhr weitergestellt und die Tastatur abgefragt wird. Sonst können wir das BASIC nicht mehr verwenden. Warum also nicht einfach aus dem Raster-IRQ zur entsprechenden Funktion springen? Der IRQ wird schließlich auch regelmäßig aufgerufen. Das Ende der Funktion bietet sich dazu an. Somit werden auch ‚automatisch‚ die Register wiederhergestellt.
Dass es dadurch auf PAL-Systemen zu einem kleinen Problem kommt, sollte jedem, der den Interrupt-Beiträgen bis hier her gefolgt ist, selbst auffallen.

Starten wir das Programm einmal, um zu sehen, ob alles läuft.

Raster-IRQ
Raster-IRQ

Bis jetzt sieht es doch ganz gut aus. Die Linie ist da (mit Umbruch natürlich) und wir können das BASIC verwenden. Allerdings flackert die Linie noch und sobald wir ein BASIC-Programm laufen lassen nimmt das Flackern zu.

 

Schritt für Schritt zum stabilen Rasterzeileninterrupt
Jetzt geht es ans Eingemachte 😉 . Wir werden nun Schrittweise das obige Programm verändern, so dass wir einen stabilen Raster-IRQ erhalten. Dazu benutzen wir einen Doppelten-Rasterzeileninterrupt (es gibt aber auch noch andere Weg dieses Ziel zu erreichen)! Nach dem Auftreten unseres ‚Haupt-Raster-IRQs‚, werden wir einen zweiten IRQ einrichten, der genau dann eintritt, wann wir es wollen. Dadurch können wir den ‚letzten Befehl‚ der CPU bestimmen und entsprechend reagieren.
Nochmal der Hinweis: Jeder Taktzyklus ist wichtig! Übernehmt die Beispiele so wie sie hier erklärt werden. Erst wenn ihr am Schluß (hoffentlich) das gesamte Prinzip verstanden habt, solltet ihr anfangen die Routine für eure Zwecke zu verändern.

Wenn die CPU bei MyIRQ: landet, dann sind bereits 38-45 Taktzyklen in unserer Rasterzeile (hier 20) vergangen, dass haben wir oben erfahren. Uns bleiben im günstigsten Fall also noch 25, im ‚worst case‚ nur noch 18 Zyklen in der aktuellen Zeilen. Da dass zuwenig Zeit ist, können wir erst in der übernächsten (also der 22. Zeile) unseren ‚Doppelten-Raster-IRQ‚ bekommen.

Fangt mit der Einrichtung direkt hinter MyIRQ: an:

Wie ihr seht, richten wir einfach einen weiteren IRQ ein. Als erstes wird die Adresse für den Doppelten-IRQ  DoubleIRQ: im RAM-Vektor hinterlegt. Dann ist es wichtig, dass wir uns den Stackpointer merken! Den braucht der Haupt-IRQ nachher wieder. Wir holen uns diesen also mit TSX ins X-Register und schreiben den Wert direkt in eine später folgende LDX-Anweisung. Dann verschwenden wir etwas Zeit durch die NOP-Befehle. Statt sinnlosem Zeitverschwenden kann man natürlich auch etwas sinnvolles machen, mit dem letzten Befehl laden wir z. B. schonmal den Akku mit dem richtigen Wert, um gleich den ersten Raster-IRQ zu bestätigen. Das kostet auch Zeit, diese wird aber ‘sinnvoll‚ genutzt.
Wie ihr nun seht haben wir 26 weitere Taktzyklen verbraucht. Somit sind wir auf jeden Fall in der nächsten Rasterzeile (38 + 26 = 64) gelandet (Rasterzeile 21).

 

Da wir jetzt in Rasterzeile 21 sind, wurde $D012 vom VIC schon automatisch erhöht. Wir müssen also nur noch eins addieren, damit unser Doppelter-IRQ in Zeile 22 auftritt. Danach bestätigen wir den ersten Raster-IRQ (der Akku wurde eben ja bereits gefüllt) und geben die IRQs mit CLI wieder frei.
Jetzt müssen wir auf den nächsten IRQ warten.  Da dieser erst am Ende der aktuellen Rasterzeile auftritt, ‚verschwenden‚ wir wieder etwas Zeit.

Hier einfach nur warten, damit wir gleich den Befehl, der beim Doppelten-Rasterzeileninterrupt ausgeführt wird, bestimmen können.

 

Wer mitgezählt hat (und dass solltet ihr!!!) stellt fest, dass bis hierhin 54-61 Taktzyklen in der 21. Rasterzeile vergangen sind. Der nächste IRQ steht also kurz bevor. Sorgen wir jetzt dafür, dass er nur bei einem ganz bestimmten Befehl auftreten kann und zwar bei einem NOP:

Der letzte NOP ist, wie bereits erwähnt, wichtig. Er sieht zwar überflüssig aus, da die Rasterzeile nur 63 Taktzyklen lang ist, aber tritt der IRQ nach einem TZ beim vorletzten Befehl auf, dann wird noch der folgende Befehl verarbeitet, bevor der IRQ wirklich ausgeführt wird. Dass können wir später nochmal überprüfen, sobald alles läuft.

 

Jetzt ist unser IRQ (auf einem PAL-System!!!) eingetreten, wir sind in Rasterzeile 22 und landen bei DoubleIRQ:. Sobald wir dort angekommen sind, wissen wir, auf einen Taktzyklus genau, wieviele Zyklen bereits vergangen sind. Es können nur 38 oder 39 Taktzyklen gewesen sein, da der letzte Befehl vor dem Interrupt, zu 100% ein NOP war! Warten wir also diese Rasterzeile noch ab, um dann immer beim selben Taktzyklus in der darauffolgenden Rasterzeile zu beginnen:

Hinter DoubleIRQ: wird als erstes der Stackpointer zurückgeholt (den haben wir weiter oben direkt hinter das LDX geschrieben. #$00 ist also nur ein Platzhalter, für den Stackpointer! Danach wird das X-Register wieder in den Stackpointer kopiert. Dies ist wichtig, da wir den zweiten IRQ einfach ignorieren (eigentlich würden wir die Register vom Stack holen und mit  RTI den Interrupt verlassen). Durchs zurückholen des ‚alten‚ Stackpointers findet der erste IRQ seine gemerkten Register und die korrekte Rücksprungadresse wieder! Dann warten wir etwas, auch hier kann man die Zeit für sinnvolle Dinge nutzen (z. B. merken wir uns die Rasterzeile im X-Reg. und laden schon mal die Farbe weiß in den Akku). Es ist aber unheimlich wichtig immer auf die exakte Anzahl der Taktzyklen zu achten, die verbraucht werden! Da wir eine ungerade Anzahl an TZ verstreichen lassen wollen, verwenden wir den BIT-Befehl. Dieser ändert zwar die Flags (das stört hier allerdings nicht weiter), richtet sonst aber keinen ‚Schaden‚ an.

Jetzt wird es etwas tricky 😉 . Wir könnten uns evtl. damit zufriedengeben, dass wir nur noch eine Abweichung von einem Taktzyklus haben, aber machen wir es doch gleich richtig. Um diese letzte Abweichung zu beseitigen. Vergleichen wir nun das X-Register mit $D012.

Wir wollen so feststellen, ob wir noch in Rasterzeile 22 oder schon in 23 sind! Wichtig ist, dass das cpx $D012 haargenau nach den bisher vergangenen 59 oder 60 Taktzyklen ausgeführt wird. Der Befehl benötigt selbst vier Taktzyklen und der Vergleich findet nicht direkt beim ersten TZ statt. Tatsächlich wird erst nach exakt 63 oder 64 Taktzyklen mit $D012 verglichen. Das BEQ verbraucht dann nochmal 2 TZ (falls wir schon in Zeile 23 sind) oder 3 Taktzyklen (sofern es sich noch um Zeile 22 handelt führen wir einen eigentlich unnötigen Sprung nach  MyIRQ_Main: aus, um 3 TZ zu verbrauchen). Wir beginnen somit immer in der dritten Rasterzeile nach unserem ursprünglichen Rasterzeileninterrupt und haben dort bereits 3 Zyklen verbraucht.

Zurück in der Hauptinterrupt-Routine können wir nun unseren gewünschten ‚Effekt‚ (mir ist klar, dass es sich hier nur um eine weiße Linie handelt) ausführen und müssen natürlich noch daran denken, in den RAM-Vektor wieder die richtige Adresse einzutragen und die von uns gewünschte Rasterzeile zu setzen. Ändert den Rest der bisherigen Routine bitte wie hier zusehen ab:

Neu ist nur, dass wir weiß schon weiter oben in den Akku geladen haben und zum Schluß, wie eben erwähnt, den RAM-Vektor und die Rasterzeile zurücksetzen.

Wie ihr euch nach dem CPX denken könnt, finden Änderungen ‚mitten‚ im Befehl statt. So wird unsere Farbe während der Ausführung von sta $D020 auf weiß geändert und nicht etwa direkt zu Beginn des Befehls. Beim Setzen von schwarz ist es natürlich genau so. Ihr solltet auf jeden Fall beachten, dass eine Änderung am Register nicht direkt mit Beginn des Befehls stattfindet.

 

Starten wir das Programm nun und lassen ein BASIC-Programm laufen, dann sehen wir, dass die Linie absolut stabil ist!

RasterIRQ_009
Unsere Linie beginnt immer an exakt der selben Position. Der Versatz ist nicht so wild, da dieser im normalen Betrieb nicht sichtbar ist. (Rahmen debuggen ist hier noch aktiv!)

 

 

Im Java-Emulator starten…

 

Schlußbemerkung
Wer sich jetzt daran stört, dass unsere Linie immer noch einen Umbruch hat, der muss einfach noch eine komplette Zeile abwarten und erst dann die Farbe setzen.

Das könnt ihr z. B. durch folgende Änderung erreichen:


RasterIRQ_010
Der Befehl zum Setzen der Farbe wird direkt mit dem ersten Taktzyklus der Rasterzeile gestartet.

 

Weshalb die Linie trotzdem nicht ganz links beginnt, sollte jetzt jedem klar sein. Wir beginnen mit dem ersten Befehl auf jeden Fall direkt am Anfang der Rasterzeile.

 

Wir können nun auch noch prüfen, ob das letzte NOP (beim Warten auf den 2. IRQ) wirklich notwendig ist. Ersetzt das nop ;2 TZ (66) einfach mal durch ein dec $D021 und startet das Programm! Schon flackert der Hintergrund und die Linie springt wieder. Daher am besten schnell wieder rückgängig machen. Der DEC-Befehl wird ausgeführt, also wird auch das  NOP verarbeitet und wir brauchen es unbedingt!

 

Wir haben die IRQ-Routine mit align 1024 etwas nach hinten geschoben, damit wir Platz für ein kleines BASIC-Programm haben. Aber dies erfüllt noch einen weiteren Zweck. Wir verwenden ja einige Schleifen um zu warten. Die Wartezeit muss auf den Zyklus (ja schon wieder der Hinweis) genau sein, damit alles klappt. Wir ihr wisst, benötigt ein Bedingter-Sprung aber einen TZ mehr, wenn er über die Pagegrenze geht. Dies verhindern wir durch das align ebenfalls. 


Damit haben wir uns die Basis für weitere Effekte (z. B. das Öffnen des seitlichen Rahmens) erschlossen. Allerdings fehlt noch etwas, unsere Routine kommt aus dem Tritt, sobald ihr sie in bestimmte Bereiche des ‚Hauptbildschirms‚ verschiebt! Ändert in unserem urspünglichen Programm (also dem vor der Schlußbemerkung) den Wert der Variablen RASTER = $20 einfach mal in $30 und startet das Programm.

Wieso, weshalb, warum es hier schon wieder nicht passt, wird später geklärt...
Wieso, weshalb, warum es hier schon wieder nicht passt, wird später geklärt…

 

Noch einge Worte zur SuperCPU und Rasterzeileninterrupts
Wie schon mehrfach angesprochen, kommt es beim Einsatz einer SuperCPU im Turbo-Modus zu Problemen bei zyklengenauen Raster-Funktionen, da die Befehle dann weniger Taktzyklen benötigen. Das Ergebnis könnt ihr jetzt selbst mit unserem kleinen Beispiel austesten. Startet die SuperCPU Version von VICE ohne Turbo (also mit 1MHz) und lasst unser Programm laufen. Dann sieht alles ‘normal‚ aus. Schaltet ihr jetzt den Turbo ein, wird unsere Linie nur noch zerstückelt dargestellt.

Raster-Probleme bei 20MHz mit der SuperCPU. Es sollte eine durchgängige Linie sein!
Raster-Probleme bei 20MHz mit der SuperCPU. Es sollte eine durchgängige Linie sein!

 


Schrott!!Naja...Geht so...Ganz gut...SUPER! (5 Bewertungen | Ø 5,00 von 5 | 100,00%)

Loading...


 

<<< zurück | weiter >>>

 

weitersagen ...
Tweet about this on TwitterShare on FacebookShare on Google+Share on LinkedIn

4 Gedanken zu „Raster-IRQ: Endlich stabil!!!“

  1. Hallo Jörn,

    super und klasse verständlich beschrieben. Daumen doppelt hoch!
    Ein kleiner Hinweis zum Source. Im Text redest du von Zeile 20. Du initialisierst aber die Variable RASTER mit $20 (Zeile 32). Da wir mit der Zeile 32 noch im oberen Rahmen sind gibt es natürlich keine Probleme. 🙂

    Gruß,
    Jörg

    1. Hallo Jörg,
      schön, dass dir der Text gefallen hat.
      Mit $20 / 20 hast du natürlich recht. Keine Ahnung, was mir da durch den Kopf ging. Ich werde mir das die Tage nochmal ansehen und korrigieren.

      Danke für den Hinweis und viel Spass weiterhin,
      Jörn

  2. Erstmal großes Lob für die Seite. Wirklich sehr gut erklärt und einfach zu verstehen.

    Die Stable Raster Routine funktioniert einwandfrei aber ich scheitere daran, den Raster stabil zu halten wenn ich zb. dazu eine Musik abspielen möchte. Woran kann das liegen?

    1. Hallo,
      danke fürs Lob.

      Ich würde vermuten, dass die Musik-Routine zu einem ungünstigen Zeitpunkt aufgerufen wird und so den Raster-IRQ aus dem Takt bringt. Um das genauer zu untersuchen, müsste ich aber einen Blick in den Source oder aufs ‚fertige‘ Programm werfen. Wenn du möchtest kannst du mir den Source und / oder das Programm an die Mailadresse aus dem Impressum schicken.

Schreibe einen Kommentar


Beachtet bitte, dass ich eure Kommentare erst manuell freigegeben muß, bevor sie auf der Seite erscheinen! Da ich nicht pausenlos am Rechner sitze, kann es schon mal etwas dauern, bis ein Kommentar für alle sichtbar ist.

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Protected by WP Anti Spam