Erstellt: 1. September 2013 (zuletzt geändert: 1. Januar 2018)

Raster-IRQ: Endlich stabil!!!

Ein stabiler Rasterzeileninterrupt

C64 Studio, AMCE & TASM

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 wieder ratsam, sofern ihr VICE nutzt, „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 / $ffff 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 den 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.

RASTER = $20

;*** Startadresse 
*=$0801
;** BASIC-Zeile: 2018 SYS 2064:NEW
 !word main-2, 2018 
 !byte $9e
 !text " 2064:"
 !byte $a2,$00,$00,$00

main
 sei                                ;IRQs sperren

 lda #<myIRQ                        ;Adresse unserer Routine in
 sta $0314                          ;den RAM-Vektor
 lda #>myIRQ
 sta $0315

 lda #%00000001                     ;Raster-IRQs vom VIC-II aktivieren
 sta $d01a

 lda #RASTER                        ;Hier soll unsere Linie erscheinen
 sta $d012                      

 lda $d011                          ;Zur Sicherheit höchstes BIT
 and #%01111111                     ;für die Rasterzeile löschen
 sta $d011

 lda #%01111111                     ;Timer-IRQs abschalten
 sta $dc0d
 lda $dc0d                          ;zur Sicherheit bestätigen

 lda #%00000001                     ;Sicherheitshalber auch den
 sta $d019                          ;Raster-IRQ bestätigen

 cli                                ;Interrupts erlauben

 rts                                ;zurück zum BASIC



;*** Platz für ein kleines BASIC-Programm lassen: 
;*** z. B. 10 PRINT "HALLO ";:GOTO 10 
!align 1023,0
myIRQ

myIRQMain 
 lda #$01                           ;weiß in den Akku 
 sta $d020                          ;Rahmenfarbe setzen 
 ldx #$0a                           ;            2TZ
loop2 
 dex                                ;10 * 2TZ = 20TZ 
 bne loop2                          ; 9 * 3TZ = 27TZ 
                                    ; 1 * 2TZ =  2TZ 
 nop                                ;            2TZ 
 nop                                ;            2TZ 
 nop                                ;            2TZ 
 lda #$00                           ;            2TZ ;schwarz in den Akku 
 sta $d020                          ;            4TZ ;Rahmenfarbe setzen 
                                    ;=============== 
                                    ;           63TZ 
 lda #%00000001                     ;IRQ bestätigen 
 sta $d019 
 jmp $ea31                          ;zum Schluß zum 'Timer-Interrupt' springen

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.

Es läuft alles, wie erwartet.
Es läuft alles, wie erwartet.

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, sodass 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 Zeile 20) vergangen, das 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 dies 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:

myIRQ
;*** Wenn wir hier landen, sind bereits 38-45 Taktzyklen
;*** in der aktuellen Rasterzeile (20) vergangen!

;*** Zweiten IRQ einrichten.
;*** Da die Zeit bei aktivierten ROM nicht reicht,
;*** können wir den 2. Raster-IRQ erst in der übernächsten Zeile bekommen!
 lda #<doubleIRQ                    ;(2 TZ) 2. Raster-IRQ einrichten
 sta $0314                          ;(4 TZ)
 lda #>doubleIRQ                    ;(2 TZ)
 sta $0315                          ;(4 TZ)
 tsx                                ;(2 TZ) Stackpointer im X-Reg. retten
 stx doubleIRQ+1                    ;(4 TZ) und fürs zurückholen sichern!
 nop                                ;(2 TZ)
 nop                                ;(2 TZ)
 nop                                ;(2 TZ)
 lda #%00000001                     ;(2 TZ) 1. Raster-IRQ später bestätigen
                                    ;------
                                    ;26 TZ

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

;*** Jetzt sind 64-71 Taktzyklen vergangen und wir sind
;*** auf jeden Fall in nächsten Rasterzeile (21)!
;*** Verbraucht wurden dort 1-8 TZ
 inc $d012                          ;(6 TZ) 2. IRQ in der übernächsten Zeile (22)!!!
                                    ;       $d012 wurde bereits automatisch erhöht
 sta $d019                          ;(4 TZ) IRQ bestätigen
 cli                                ;(2 TZ) Interrupts für den 2. Raster-IRQ
                                    ;       wieder freigeben

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.

;*** Wir befinden uns in Rasterzeile 21 und 
;*** haben bisher 13-20 Zyklen verbraucht

;*** etwas Zeit verschwenden...
 ldx #$08                           ;            2 TZ
loop
 dex                                ;8 * 2 TZ = 16 TZ
 bne loop                           ;7 * 3 TZ = 21 TZ
                                    ;1 * 2 TZ =  2 TZ
                                    ;          ------
                                    ;           41 TZ

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

Wer mitgezählt hat (und das 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:

;*** Bis hier sind 54-61 Taktzyklen vergangen, jetzt auf den IRQ warten...
;*** Der nächste Rasterinterrupt wird während dieser NOPs auftreten!
 nop                                ;2 TZ (56)
 nop                                ;2 TZ (58)
 nop                                ;2 TZ (60)
 nop                                ;2 TZ (62)
 nop                                ;2 TZ (64)
 nop                                ;2 TZ (66)

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. Dies 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:

doubleIRQ
;*** Wir sind nun in Rasterzeile 22 und
;*** haben in dieser genau 38 oder 39 Taktzyklen benötigt!!
;*** Wir können so sicher sein, da der IRQ während der NOPs auftrat.

;*** Jetzt exakt soviele Taktzyklen 'verschwenden', wie in 
;*** dieser Zeile noch zu verarbeiten sind (also 24 oder 25).
 ldx #$00                           ;(2 TZ) Platzhalter für 1. Stackpointer
 txs                                ;(2 TZ) Stackpointer vom 1. IRQ wiederherstellen
 nop                                ;(2 TZ)
 nop                                ;(2 TZ)
 nop                                ;(2 TZ)
 nop                                ;(2 TZ)
 bit $01                            ;(3 TZ)
 ldx $d012                          ;(4 TZ)
 lda #$01                           ;(2 TZ) weiß schonmal in den Akku

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 Wert des Stackpointers! 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.

 cpx $d012                          ;(4 TZ) sind wir noch in Rasterzeile 22?
                                    ;------
                                    ;25 TZ = 63 oder 64 TZ!!!

 beq myIRQ_Main                     ;(3 TZ) wenn JA einen letzten Takt 'verschwenden'
                                    ;(2 TZ) sonst einfach weiterlaufen...

;*** Wir beginnen also immer exakt nach 3 TZ in der dritten Rasterzeile (23)
;*** nach dem 1. Raster-IRQ (den hatten wir ja in für Zeile 20 festgelegt)

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:

MyIRQ_Main:
 sta $D020                      ;Rahmenfarbe setzen

 ldx #$0A                       ;            2TZ
@loop:
 dex                            ;10 * 2TZ = 20TZ
 bne @loop:                     ; 9 * 3TZ = 27TZ
                                ; 1 * 2TZ =  2TZ
 nop                            ;            2TZ
 nop                            ;            2TZ
 nop                            ;            2TZ
 lda #$00                       ;            2TZ ;schwarz in den Akku
 sta $D020                      ;            4TZ ;Rahmenfarbe setzen
                                ;===============
                                ;           63TZ

 lda #<MyIRQ:                   ;Original IRQ-Vektor setzen
 sta $0314
 lda #>MyIRQ:
 sta $0315

 lda #RASTER                    ;ursprüngliche Rasterzeile zurücksetzen
 sta $D012

 lda #%00000001                 ;IRQ bestätigen
 sta $D019

 jmp $EA31                      ;zum Schluß zum 'Timer-Interrupt' springen

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!

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

RASTER = $20

;*** Startadresse 
*=$0801
;** BASIC-Zeile: 2018 SYS 2064:NEW
 !word main-2, 2018 
 !byte $9e
 !text " 2064:"
 !byte $a2,$00,$00,$00

main
 sei                                ;IRQs sperren

 lda #<myIRQ                        ;Adresse unserer Routine in
 sta $0314                          ;den RAM-Vektor
 lda #>myIRQ
 sta $0315

 lda #%00000001                     ;Raster-IRQs vom VIC-II aktivieren
 sta $d01a

 lda #RASTER                        ;Hier soll unsere Linie erscheinen
 sta $d012                      

 lda $d011                          ;Zur Sicherheit höchstes BIT
 and #%01111111                     ;für die Rasterzeile löschen
 sta $d011

 lda #%01111111                     ;Timer-IRQs abschalten
 sta $dc0d
 lda $dc0d                          ;zur Sicherheit bestätigen

 lda #%00000001                     ;Sicherheitshalber auch den
 sta $d019                          ;Raster-IRQ bestätigen

 cli                                ;Interrupts erlauben

 rts                                ;zurück zum BASIC



;*** Platz für ein kleines BASIC-Programm lassen: 
;*** z. B. 10 PRINT "HALLO ";:GOTO 10 
!align 1023,0
myIRQ
;*** Wenn wir hier landen, sind bereits 38-45 Taktzyklen
;*** in der aktuellen Rasterzeile (20) vergangen!

;*** Zweiten IRQ einrichten.
;*** Da die Zeit bei aktivierten ROM nicht reicht,
;*** können wir den 2. Raster-IRQ erst in der übernächsten Zeile bekommen!
 lda #<doubleIRQ                    ;(2 TZ) 2. Raster-IRQ einrichten
 sta $0314                          ;(4 TZ)
 lda #>doubleIRQ                    ;(2 TZ)
 sta $0315                          ;(4 TZ)
 tsx                                ;(2 TZ) Stackpointer im X-Reg. retten
 stx doubleIRQ+1                    ;(4 TZ) und fürs zurückholen sichern!
 nop                                ;(2 TZ)
 nop                                ;(2 TZ)
 nop                                ;(2 TZ)
 lda #%00000001                     ;(2 TZ) 1. Raster-IRQ später bestätigen
                                    ;------
                                    ;26 TZ

;*** Jetzt sind 64-71 Taktzyklen vergangen und wir sind
;*** auf jeden Fall in nächsten Rasterzeile (21)!
;*** Verbraucht wurden dort 1-8 TZ
 inc $d012                          ;(6 TZ) 2. IRQ in der übernächsten Zeile (22)!!!
                                    ;       $d012 wurde bereits automatisch erhöht
 sta $d019                          ;(4 TZ) IRQ bestätigen
 cli                                ;(2 TZ) Interrupts für den 2. Raster-IRQ
                                    ;       wieder freigeben
                                    
;*** Wir befinden uns in Rasterzeile 21 und 
;*** haben bisher 13-20 Zyklen verbraucht

;*** etwas Zeit verschwenden...
 ldx #$08                           ;            2 TZ
loop
 dex                                ;8 * 2 TZ = 16 TZ
 bne loop                           ;7 * 3 TZ = 21 TZ
                                    ;1 * 2 TZ =  2 TZ
                                    ;          ------
                                    ;           41 TZ

;*** Bis hier sind 54-61 Taktzyklen vergangen, jetzt auf den IRQ warten...
;*** Der nächste Rasterinterrupt wird während dieser NOPs auftreten!
 nop                                ;2 TZ (56)
 nop                                ;2 TZ (58)
 nop                                ;2 TZ (60)
 nop                                ;2 TZ (62)
 nop                                ;2 TZ (64)
 nop                                ;2 TZ (66)

doubleIRQ
;*** Wir sind nun in Rasterzeile 22 und
;*** haben in dieser genau 38 oder 39 Taktzyklen benötigt!!
;*** Wir können so sicher sein, da der IRQ während der NOPs auftrat.

;*** Jetzt exakt soviele Taktzyklen 'verschwenden', wie in 
;*** dieser Zeile noch zu verarbeiten sind (also 24 oder 25).
 ldx #$00                           ;(2 TZ) Platzhalter für 1. Stackpointer
 txs                                ;(2 TZ) Stackpointer vom 1. IRQ wiederherstellen
 nop                                ;(2 TZ)
 nop                                ;(2 TZ)
 nop                                ;(2 TZ)
 nop                                ;(2 TZ)
 bit $01                            ;(3 TZ)
 ldx $d012                          ;(4 TZ)
 lda #$01                           ;(2 TZ) weiß schonmal in den Akku

 cpx $d012                          ;(4 TZ) sind wir noch in Rasterzeile 22?
                                    ;------
                                    ;25 TZ = 63 oder 64 TZ!!!

 beq myIRQMain                      ;(3 TZ) wenn JA einen letzten Takt 'verschwenden'
                                    ;(2 TZ) sonst einfach weiterlaufen...

;*** Wir beginnen also immer exakt nach 3 TZ in der dritten Rasterzeile (23)
;*** nach dem 1. Raster-IRQ (den hatten wir ja in für Zeile 20 festgelegt)

myIRQMain
 sta $d020                          ;Rahmenfarbe setzen

 ldx #$0a                           ;            2TZ
loop2
 dex                                ;10 * 2TZ = 20TZ
 bne loop2                          ; 9 * 3TZ = 27TZ
                                    ; 1 * 2TZ =  2TZ
 nop                                ;            2TZ
 nop                                ;            2TZ
 nop                                ;            2TZ
 lda #$00                           ;            2TZ ;schwarz in den Akku
 sta $D020                          ;            4TZ ;Rahmenfarbe setzen
                                    ;===============
                                    ;           63TZ

 lda #<myIRQ                        ;Original IRQ-Vektor setzen
 sta $0314
 lda #>myIRQ
 sta $0315

 lda #RASTER                        ;ursprüngliche Rasterzeile zurücksetzen
 sta $d012

 lda #%00000001                     ;IRQ bestätigen
 sta $d019

 jmp $ea31                          ;zum Schluß zum 'Timer-Interrupt' springen

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.

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

rasterIRQMain
                                    ;Start nach  3TZ
 ldx #$0B                           ;            2TZ
 dex                                ;11 * 2TZ = 22TZ
 bne *-1                            ;10 * 3TZ = 30TZ
                                    ; 1 * 2TZ =  2TZ
 nop                                ;            2TZ
 nop                                ;            2TZ
                                    ;===============
                                    ;           63TZ

 sta $d020                          ;            4TZ Rahmenfarbe setzen
 ldx #$0a                           ;            2TZ
 dex                                ;10 * 2TZ = 20TZ
 bne *-1                            ; 9 * 3TZ = 27TZ
                                    ; 1 * 2TZ =  2TZ
 nop                                ;            2TZ
 lda #$00                           ;            2TZ ;schwarz in den Akku
 sta $d020                          ;            4TZ ;Rahmenfarbe setzen
                                    ;===============
                                    ;           63TZ
Der Befehl zum Setzen der Farbe wird direkt mit dem ersten Taktzyklus der Rasterzeile gestartet.
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 1023,0 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! (10 Bewertungen | Ø 5,00 von 5 | 100,00%)

Loading...


ZurückWeiter

6 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

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

Protected by WP Anti Spam