Erstellt: 13. Oktober 2013 (zuletzt geändert: 7. Januar 2018)

Raster-IRQ: Bad Lines

Erstmal ein herzliches Willkommen zum 100. Beitrag auf dieser Seite.
Erstmal ein herzliches Willkommen zum 100. Beitrag auf dieser Seite.

Zur Feier des Tages gibt es den bisher wohl anspruchvollsten Beitrag. Ich hoffe ihr könnt mit den folgenden Ausführungen etwas anfangen, ich kämpfe mich derweil noch weiter durch die Eigenheiten des VIC-II.

Die Bad Lines

C64 Studio, AMCE & TASM

Trotz des im letzten Beitrag ‚Raster-IRQ: Endlich stabil!!!‚ entwickelten stabilen Rasterzeilen-Interrupts, funktioniert (wie bereits häufiger erwähnt) leider nicht immer alles wie erwartet. Der Grund sind u. a. die sog. Bad Lines. Hier geht es jetzt also darum was Bad Lines sind, wann sie auftreten, welche Auswirkungen sie haben und wie man mit ihnen umgehen kann.

Wieder wird das standard PAL-System vorausgesetzt!

Was sind Bad Lines

Um uns das Problem mit den ‚bösen Linien‚ etwas genauer vor Augen zu führen, benutzen wir ein kleines Beispielprogramm.

RASTER          = $03               ;Hier beginnen die Linien

;*** 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 unser Interrupt auftreten
 sta $d012                      

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

 ;*** TEST TEST TEST ***
 and #%11101111                     ;BS-Ausgabe abschalten
 ;*** TEST TEST TEST ***

 sta $D011

 lda #%0111111                      ;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



!align 1023,0
myIRQ
;*** Wenn wir hier landen, sind bereits 38-45 Taktzyklen in der
;*** aktuellen Rasterzeile (ab jetzt als STARTROW bezeichnet) 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 (STARTROW+2) 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 (STARTROW+1)!
;*** Verbraucht wurden dort 1-8 TZ
 inc $D012                          ;(6 TZ) 2. IRQ in der übernächsten Zeile STARTROW+2
                                    ;       $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 immer noch in Rasterzeile STARTROW+1 und 
;*** haben bisher 13-20 Zyklen verbraucht

;*** etwas Zeit verschwenden...
 ldx #$08                           ;            2 TZ
 dex                                ;8 * 2 TZ = 16 TZ
 bne *-1                            ;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 STARTROW+2 und
;*** haben bisher 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)
 lda #$00                           ;(2 TZ) Farbe in den Akku
 ldx $d012                          ;(4 TZ)
 cpx $d012                          ;(4 TZ) sind wir noch in Rasterzeile STARTROW+2?
                                    ;======
                                    ;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 (STARTROW+3)
;*** nach dem 1. Raster-IRQ (den hatten wir ja für Zeile STARTROW festgelegt

myIRQMain
 ;*** Soviel Zeit in der aktuellen Zeile verschwenden,
 ;*** dass die nächste durchgängig eine Farbe erhält
                                    ;s. oben     3TZ
 ldx #$09                           ;            2TZ
 dex
 bne *-1                            ;X*5TZ-1TZ= 44TZ
 ldy #$00                           ;            2TZ
 nop                                ;            2TZ
 nop                                ;            2TZ
 nop                                ;            2TZ
 nop                                ;            2TZ
                                    ;===============
                                    ;           59TZ    
loop
 sta $d020                          ;            4TZ
 ldx #$09                           ;            2TZ
 dex                                        
 bne *-1                            ;X*5TZ-1TZ= 44TZ
 nop                                ;            2TZ
 nop                                ;            2TZ
 nop                                ;            2TZ
 eor #%00001010                     ;            2TZ
 dey                                ;            2TZ
 bne loop                           ;            3TZ
                                    ;===============
                                    ;           63TZ

 sta $d020                          ;Rahmenfarbe setzen

 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

Das Programm sollte euch bekannt vorkommen. Es entspricht fast zu 100% dem im letzten Beitrag entwickelten stabilen Rasterzeileninterrupt. Die wenigen Änderungen sind schnell erklärt. Der Interrupt beginnt diesmal schon in Zeile 3 (s. RASTER), damit wir möglichst viele Linien zu sehen bekommen.

Wichtig ist folgende Zeile bei der Initialisierung:

 ;*** TEST TEST TEST ***
 and #%11101111                 ;BS-Ausgabe abschalten
 ;*** TEST TEST TEST ***

Damit schalten wir den Bildschrim erstmal aus (s. ‚VIC-II: Die Register‚). Somit besteht unsere Anzeige so zu sagen nur aus einem durchgängigen Rahmen.

Am Schluß von doubleIRQ holen wir jetzt schwarz (statt weiß) in den Akku. Ab myIRQMain bereiten wir unsere Hauptschleife vor und verschwenden soviel Zeit, dass die erste Linie linksbündig beginnt. Wir beginnen also mit dem Setzen der Farbe am Schluß der vorherigen Zeile. In der Schleife loop färben wir dann den Rahmen abwechselnd schwarz und hellrot ein. Der Rest sollte dann wieder bekannt sein.

Ohne Bildschirmausgabe sieht alles gut aus. (Bild-1)
Ohne Bildschirmausgabe sieht alles gut aus. (Bild-1)

Die Anzeige sieht doch exakt so aus, wie wir es uns gedacht haben. Ein Problem ist nicht zu erkennen. Da wir noch zur Systemroutine nach $ea31 springen, zeichnen wir die Linien nicht ganz bis zum unteren Rand.

Also alles paletti, oder? Kommentiert nun das AND zwischen ;*** TEST TEST TEST *** aus und startet das Programm erneut.

Kaum wird die Ausgabe aktiviert geht es schief. (Bild-2)
Kaum wird die Ausgabe aktiviert geht es schief. (Bild-2)

Und schon haben wir den Salat!

Wer möchte kann übrigens noch ein kleines BASIC-Programm starten (z. B. „HALLO“ in einer Endlosschleife ausgeben) um dann festzustellen, dass dieses keine Veränderung bei den Linien hervorruft. Es liegt also einzig am aktivierten Ausgabebereich.

Was zeigen uns diese beiden Bilder?

Ohne Bildschirmausgabe (s. Bild-1) funktionierte ja alles, also muss das Problem mit der Ausgabe zusammenhängen. Auf Bild-2 erkennen wir, dass auch dort im oberen und unteren Rahmen alles glatt läuft. Nur im Ausgabebereich kommt das System aus dem Tritt. Es fällt ebenfalls auf, dass die Linien nun weiter nach unten reichen. Wir scheinen mit aktivierter Ausgabe also mehr Zeit zu benötigen.

Betrachten wir den Ausgabebereich mal genauer: Die letzte Linie von oben, bevor der Ausgabebereich beginnt, sieht noch sauber aus, aber direkt die nächste wird schon vermurkst. Hin und wieder werden die Linien (wenn auch versetzt) durchgängig sauber gezeichnet. Mit einigem Aufwand (spart euch die Mühe) würdet ihr feststellen, dass wir ab Zeile 51 alle acht Zeilen ein Timing-Problem haben.

Genau dies sind die Bad Lines!

Der technische Hintergrund

Der Takt des C64 wird in eine erste und zweite Phase unterteilt. Während der ersten Phase darf der VIC-II auf den Adressbus zugreifen und kann seine Arbeit verrichten, der Bus ist dann für den Prozessor gesperrt. Die zweite gehört der CPU und der VIC dürfte dann nicht auf den Adressbus zugreifen. Nun reicht bei aktivierter Ausgabe die Zeit, die dem VIC-II zur Verfügung steht, aber nicht aus, um alle Aufgaben zu erledigen. Er benötigt den Adressbus länger, um die sog. Zeichenzeiger und Pixeldaten für die Ausgabe zu lesen. Diese zusätzliche Zeit benötigt er immer zu Beginn einer Ausgabzeile (Textzeile, aber auch im Bitmap-Modus)! Daher kommt unser Muster direkt mit Beginn der ersten Textzeile aus dem Tritt. Uns stehen in einer Bad Line einfach viel weniger Taktzyklen zur Verfügung als normalerweise (ihr wisst, dass es normal 63TZ beim PAL-System sind).

Was nun?

Ist unser Streifenmuster also unmöglich hinzubekommen? Natürlich nicht! Was liegt also näher, als das Programm so umzubauen, dass es die folgenden Bedingungen beachtet.

  • Rahmen oben
    • es stehen die vollen 63TZ zur Verfügung

  • Im Ausgabebereich für jeweils acht Zeilen
    • in der ersten Zeile (Bad Line!) etwas weniger Zeit verschwenden
    • die nächsten sieben Zeilen wieder volle 63 Taktzyklen abwarten

  • Rahmen unten
    • genau wie im oberen Rahmen sind 63TZ möglich

Schön gesagt, „in der ersten Zeile (Bad Line!) etwas weniger Zeit verschwenden“!

Wieviel Zeit ist denn etwas weniger?

Um dass jetzt zu ermitteln, verwenden wir ein abgewandeltes Testprogramm. Dieses ist mit dem bisherigen größtenteils identisch, die wenigen Unterschiede sind gelb hervorgehoben.

RASTER          = $2f               ;Hier beginnen die Linien

;*** 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 unser Interrupt auftreten
 sta $d012                      

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

 ;*** TEST TEST TEST ***
 ;and #%11101111                     ;BS-Ausgabe abschalten
 ;*** TEST TEST TEST ***

 sta $D011

 lda #%0111111                      ;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



!align 1023,0
myIRQ
;*** Wenn wir hier landen, sind bereits 38-45 Taktzyklen in der
;*** aktuellen Rasterzeile (ab jetzt als STARTROW bezeichnet) 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 (STARTROW+2) 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 (STARTROW+1)!
;*** Verbraucht wurden dort 1-8 TZ
 inc $D012                          ;(6 TZ) 2. IRQ in der übernächsten Zeile STARTROW+2
                                    ;       $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 immer noch in Rasterzeile STARTROW+1 und 
;*** haben bisher 13-20 Zyklen verbraucht

;*** etwas Zeit verschwenden...
 ldx #$08                           ;            2 TZ
 dex                                ;8 * 2 TZ = 16 TZ
 bne *-1                            ;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 STARTROW+2 und
;*** haben bisher 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)
 bit $01                            ;(3 TZ)
 lda #$0c                           ;(2 TZ) grau als 'Hauptfarbe' in den Akku
 ldy #$00                           ;(2 TZ) 'Zeilenfarbe' schwarz ins Y-Reg. laden
 ldx $d012                          ;(4 TZ)
 cpx $d012                          ;(4 TZ) sind wir noch in Rasterzeile STARTROW+2?
                                    ;======
                                    ;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 (STARTROW+3)
;*** nach dem 1. Raster-IRQ (den hatten wir ja für Zeile STARTROW festgelegt

myIRQMain
                                    ; 3TZ (s. oben)
 sty $D020                          ; 4TZ schwarz
 iny                                ; 2TZ 
 sty $D020                          ; 4TZ weiß
 dey                                ; 2TZ
 sty $D020                          ; 4TZ s
 iny                                ; 2TZ
 sty $D020                          ; 4TZ w
 dey                                ; 2TZ
 sty $D020                          ; 4TZ s
 iny                                ; 2TZ
 sty $D020                          ; 4TZ w
 dey                                ; 2TZ
 sty $D020                          ; 4TZ s
 iny                                ; 2TZ
 sty $D020                          ; 4TZ w
 dey                                ; 2TZ
 sty $D020                          ; 4TZ s
 iny                                ; 2TZ 
 sty $D020                          ; 4TZ w
                                    ;====
                                    ;61TZ
 
 sta $D020                          ; 4TZ grau

 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

Wir lassen unseren Raster-IRQ so auftreten, dass wir in der letzten normalen Zeile vor der ersten Bad Line mit myIRQMain beginnen. Dort ändern wir einfach laufend die Rahmenfarbe (immer zwischen schwarz und weiß umschalten), bis die Rasterzeile zu Ende ist. Den restlichen Rahmen setzen wir auf grau. Wie ihr oben seht, verbrauchen wir sogar 65 Taktzyklen und landen somit schon in der Bad Line, was hier aber noch nicht von Interesse ist.

Das Programm sollte zu folgender Ausgabe führen (unbedingt Rahmen debuggen aktivieren!):

Unsere Testausgabe...
Unsere Testausgabe…

Dieses Bild wurde direkt aus WinVICE gespeichert, es ist KEIN Windows-Screenshot! Dass ist wichtig, damit die Pixel wirklich 1:1 erhalten bleiben. Denn gleich werden wir die einzelnen Pixel zählen. Um den nun kommenden Schritten besser zu folgen, solltet ihr euer bevorzugtes Grafikprogramm (ich verwende z. B. Paint.NET) starten. Dann benötigt ihr die Bilder in ihrer original Größe. Klickt zur Sicherheit aufs Bild, kopiert es dann über das Kontextmenü und fügt es ins Grafikprogramm ein.

Wie ihr seht, beginnt unsere normale Zeile erst mit einem grauen Teilstück, danach wechseln sich schwarz und weiß immer ab.

Als nächstes habe ich links beim ersten Pixel begonnen und jeweils 8 Pixel abwechselnd rot und grün eingefärbt. (Dies hat übrigens keinerlei politischen Bezug! 😉 )

Hier seht ihr die 63 Taktzyklen!
Der rote und grüne Block ist je 8 Pixel breit.

Klickt bitte auf die Grafik, um sie als Vollbild anzuzeigen. Wie ihr seht, können wir die gesamte Bildschirmbreite exakt mit den 8 Pixel breiten rot / grün Blöcken füllen. Wie euch bekannt ist, stehen uns im PAL-System die bereits häufiger erwähnten 63 Taktzyklen zur Verfügung. Dann zählt oben mal nach, wieviele rot und grün Blöcke es insgesamt gibt….

… schon fertig? Ihr solltet 63 Stück gezählt haben!!

Hier habt ihr also die Taktzyklen mal direkt vor Augen 😉 . Euch ist bestimmt aufgefallen, dass die schwarzen und weißen Abschnitte exakt auf die Zyklengrenze fallen.

Untersuchen wir den Beginn der Grafik noch etwas genauer.

Anfang der normalen Rasterzeile.
Anfang der normalen Rasterzeile.

Ein roter bzw. grüner Block einspricht also immer einem Taktzyklus. Vergleichen wir den Beginn der Rasterzeile doch einfach mal mit den ersten Befehlen ab myIRQMain.

1:                                ; 3TZ (s. oben)
2: sty $D020                      ; 4TZ schwarz
3: iny                            ; 2TZ 
4: sty $D020                      ; 4TZ weiß
5: dey                            ; 2TZ
6: sty $D020                      ; 4TZ schwarz
  1. Die ersten drei Taktzyklen werden noch vom stabilen Raster-IRQ verbraucht. Ihr erinnert euch: Nach 3TZ in der 3. Zeile?
  2. Jetzt folgt der sty um den Rahmen auf schwarz zu setzen. Dieser Befehl benötigt 4 TZ. Wie ihr oben seht ändert sich die Rahmenfarbe direkt am Ende des Befehls.
  3. Der iny benötigt 2 TZ. Auf die Rahmenfarbe hat das natürlich erstmal keine Auswirkung. Nur der Rasterstrahl läuft weiter.
  4. Wieder ein 4 TZ langer sty, jetzt wird der Rahmen auf weiß gesetzt.
  5. Ein dey braucht auch nur 2 TZ (s. Punkt 3)
  6. Hier wiederholt sich alles ab Punkt 2.

Wir können also unsere Befehle direkt in der Grafik erkennen. So könnt ihr nun direkt abzählen, wo welcher Befehl stattfindet.

Schaut euch als nächstes folgendes Bild einmal an (am besten im Grafikprogramm vergrößern).

Ein TZ = ein Zeichen!
Ein TZ = ein Zeichen!

Ich habe die ersten rot / grün Blöcke im Ausgabebereich mal etwas verlängert, um euch einen wichtigen Zusammenhang zu zeigen. Dass ein Taktzyklus 8 Pixel dauert, haben wir ja schon gesehen. Hier erkennt ihr, dass die TZ synchron mit den einzelnen Zeichen auf dem Bildschirm laufen! Außerdem erkennt man, dass der Ausgabebereich exakt an einer Zyklengrenze beginnt und endet.

Eine letzte Erkenntnis, die wir aus diesen Grafiken ziehen können. Am Schluß von Raster-IRQ: PAL oder NTSC habe ich euch eine Tabelle präsentiert, in der u. a. die Anzahl der sichtbaren Pixel verzeichnet ist. Diese könnt ihr nun um die Anzahl der max. Pixel ergänzen. Da eine Rasterzeile 63 TZ hat und pro TZ 8 Pixel vergehen, kommen wir auf 8 * 63 = 504 Pixel je Zeile bei einem PAL-System.

Jetzt aber endlich zurück zu den Bad Lines:
Wie wir festgestellt haben, tritt eine Bad Line nur dann auf, wenn der Ausgabebereich aktiv ist. Die in einer Bad Line verfügbaren Taktzyklen könnt ihr nun direkt abzählen oder berechnen. Es sind die für den Rahmen verfügbaren Zyklen. Da wir 40 Zeichen darstellen können (was, wie eben gesehen, auch 40 Taktzyklen in der Rasterzeile entspricht), kommen wir auf 63 – 40 = 23 TZ für eine Bad Line. Ein Abzählen in der Grafik, sollte zum selben Ergebnis führen. Wir haben 17TZ für den linken und 6TZ im rechten Rahmen zur Verfügung.

Dann lasst uns dass gleich mal überprüfen, übernehmt bitte die gelb markierten Änderungen in unser kleines Testprogramm.

 iny                                ; 2TZ 
 sty $D020                          ; 4TZ w
                                    ;====
                                    ;61TZ
 dey                                ; 2TZ
                                    ;====
                                    ;63TZ
 
 ;*** Bad Line!
 sty $d020                          ; 4TZ
 ldy #$0d                           ; 2TZ hellgrün ins Y-Reg.
 bit $01                            ; 3TZ
 nop                                ; 2TZ
 nop                                ; 2TZ
 nop                                ; 2TZ
 nop                                ; 2TZ
 nop                                ; 2TZ
 sty $D020                          ; 4TZ hellgrün
                                    ;====
                                    ;23TZ
 ;*** normale Zeile 
 sta $D020                          ; 4TZ grau

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

Wir verbringen durch das abschließende dey jetzt die vollen 63 Taktzyklen in der letzten normalen Zeile vor der Bad Line. In der Bad Line angekommen, setzen wir direkt die Rahmenfarbe und holen uns dann als nächste Farbe hellgrün ins Y-Register (einfach zur besseren Unterscheidung). Anschließend warten wir etwas, setzen den Rahmen auf hellgrün und verbringen den Rest der Bad Line mit warten, bis die von uns ermittelten 23 TZ vergangen sind. Zum Schluß wird wieder grau als Rahmenfarbe gesetzt.

Ein Blick auf die neue Bildschirmausgabe sollte euch stutzig werden lassen.

Da passt was nicht!
Da passt was nicht!

Wenn ihr das Bild vergrößert, seht ihr, dass das hellgrün vom Schluß der Bad Line sich zuweit in die nächste normale Zeile erstreckt, weil er zu spät beginnt. Eigentlich dürfte der hellgrüne Teil nur solang wie der weiße in der darüberliegenden Bad Line sein, da sty $d020 ja nur vier Zyklen benötigt und er müsste ganz links beginnen. Hier vergehen aber noch drei weitere Takte, bevor die Farbe geändert wird.

Was geht denn da schon wieder schief?

Anscheinend stehen uns in der Bad Line doch keine 23 TZ zur Verfügung. Löschen wir den Befehl bit $01, sodass wir auf 20TZ für die Bad Line kommen, dann passt wieder alles.

Also haben wir nur 20 TZ?!?

Ganz so einfach ist es dann aber doch nicht!

Wieviele Taktzyklen tatsächlich vergehen, hängt auch von unserem Programm ab. Bevor der VIC-II dem Prozessor den Bus in einer Bad Line entzieht, fordert er den Bus an. Dazu wird das BA-Signal des VIC-II drei Zyklen, bevor er den Bus benötigt, auf Low gesetzt. BA ist mit der RDY-Leitung des 6510 verbunden. Geht RDY auf low, dann hält die CPU an. Allerdings prüft der Prozessor den RDY-Status nur bei Lesezugriffen, da ein Schreibvorgang nicht unterbrochen werden darf. Beim 6510 können max. drei Schreibzugriffe direkt hintereinander vorkommen, somit ist die Quelle dieser ominösen 😉 Zahl-3 nun auch geklärt. Wir haben also 20-23 TZ in einer Bad Line, da der Prozessor bei einem Lesezugriff innerhalb der eben erwähnten 3 Taktzyklen sofort stoppt. Aber weshalb geschieht das hier überhaupt? Bevor der Ausgabebereich beginnt, haben wir doch nur nop-Befehle?? Also wird doch nichts gelesen und wir sollten 23 TZ haben. Der 6510 führt auch dann Lesezugriffe aus, wenn eigentlich keine notwendig sind. So führt ein nop, der ja 2 TZ benötigt dazu, dass das Read/Write-Signal für beide Taktzyklen auf Read gesetzt wird.

Ändert unser Testprogramm nochmal:

 ;*** Bad Line!
 sty $d020                      ; 4TZ
 ldy #$0d                       ; 2TZ hellgrün
 nop                            ; 2TZ
 sty $d020                      ; 4TZ hellgrün
 nop                            ; 2TZ
 nop                            ; 2TZ
 nop                            ; 2TZ
 nop                            ; 2TZ
                                ;====
                                ;20TZ

Den Befehl bit $01 haben wir oben bereits gelöscht, um auf 20 TZ zu kommen, nun ziehen wir das sty $d020 von ganz unten etwas weiter nach oben. Wenn wir jetzt dass Programm starten, dann sollte die hellgrüne Line doch einfach nur früher beginnen und wie eben wieder nach 4 TZ in der nächsten Zeile enden, oder?

Obwohl sich die Anzahl der Taktzyklen nicht ändert, verschiebt sich die Ausgabe.
Obwohl sich die Anzahl der Taktzyklen nicht ändert, verschiebt sich die Ausgabe!

Wir haben den sty $d020 eben so verschoben, dass sein abschließender Write-Zugiff in die 3 TZ fällt, in denen der VIC den Bus anfordert. Dadurch haben wir in diesem Fall 21 TZ statt 20 in der Bad Line. Verschiebt jetzt mal ein nop von unten vor das eben verschobenen sty $d020 und startet das Programm. Nun sind es wieder 20 TZ, aber das hellgrün beginnt erst nach dem Ausgabebereich! Ihr müsst also ganz genau darauf achten, welche Befehle in einer Bad Line, kurz vor dem Ausgabebereich auftreten und wie ihr Read/Write-Status in dem Moment ist. Diese optische Herangehensweise ist natürlich nicht 100% korrekt! Sie erklärt nicht wirklich was, wann abgeht. Aber ich möchte die genauen Zusammenhänge hier nicht weiter ausführen und euch auf den nächsten Beitrag vertrösten.

Zurück zum Steifenmuster!

Um jetzt das Streifenmuster sauber zu erstellen, müssen wir also nur auf die Bad Lines gesondert reagieren. Setzt die Anforderungen aus obiger Liste doch mal in ein Programm um.

Ich möchte euch empfehlen, das Problem zunächst selbst zu lösen, bevor ihr weiter lest. Ich würde die Aufgabe als nicht allzu schwer erachten. Es geht einzig ums haargenaue Verschwenden von Taktzyklen. Allerdings ist hier eure Einsatzbereitschaft gefragt, bis zur erfolgreichen Lösung kann es schon etwas dauern. Gleich folgen noch einige Tipps und Anregungen, wie man die Sache angehen kann. Um euch nicht in Versuchung zu führen, ist das Listing diesmal eingeklappt.

Hier meine Lösung, wichtig ist der Teil ab myIRQMain:

RASTER          = $03               ;Hier beginnen die Linien

;*** 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 unser Interrupt auftreten
 sta $d012                      

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

 ;*** TEST TEST TEST ***
 ;and #%11101111                     ;BS-Ausgabe abschalten
 ;*** TEST TEST TEST ***

 sta $D011

 lda #%0111111                      ;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



!align 1023,0
myIRQ
;*** Wenn wir hier landen, sind bereits 38-45 Taktzyklen in der
;*** aktuellen Rasterzeile (ab jetzt als STARTROW bezeichnet) 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 (STARTROW+2) 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 (STARTROW+1)!
;*** Verbraucht wurden dort 1-8 TZ
 inc $D012                          ;(6 TZ) 2. IRQ in der übernächsten Zeile STARTROW+2
                                    ;       $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 immer noch in Rasterzeile STARTROW+1 und 
;*** haben bisher 13-20 Zyklen verbraucht

;*** etwas Zeit verschwenden...
 ldx #$08                           ;            2 TZ
 dex                                ;8 * 2 TZ = 16 TZ
 bne *-1                            ;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 STARTROW+2 und
;*** haben bisher 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)
 lda #$00                           ;(2 TZ) Farbe in den Akku
 ldx $d012                          ;(4 TZ)
 cpx $d012                          ;(4 TZ) sind wir noch in Rasterzeile STARTROW+2?
                                    ;======
                                    ;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 (STARTROW+3)
;*** nach dem 1. Raster-IRQ (den hatten wir ja für Zeile STARTROW festgelegt

myIRQMain
 ;*** Soviel Zeit in der aktuellen Zeile verschwenden,
 ;*** dass die nächste durchgängig eine Farbe erhält
                                    ;s. oben     3TZ
 ldx #$09                           ;            2TZ
 dex
 bne *-1                            ;X*5TZ-1TZ= 44TZ
 nop                                ;            2TZ
 ldx #$19                           ;            2TZ
 stx blockCount                     ;            4TZ
 ldy #$2c                           ;            2TZ
                                    ;===============
                                    ;           59TZ

 ;*** Hier die 'normalen' Rasterzeilen verarbeiten
loop1
 sta $d020                          ;            4TZ
 ldx #$08                           ;            2TZ
 dex                                        
 bne *-1                            ;X*5TZ-1TZ= 39TZ
 eor #%00001010                     ;            2TZ
 ;*** letzte Zeile speziell, sonst gibt es einen Versatz
 cpy #$01                           ;            2TZ
 bne skip                           ;      ------3TZ
                                    ;     |
 ;falls KEIN Sprung                       |      2TZ statt 3TZ
 nop                                ;     |      2TZ
 nop                                ;     |      2TZ
 nop                                ;     |      2TZ
 bit $01                            ;     |      3TZ
 jmp badLine                        ;     |      3TZ -------
                                    ;     |   ======        |
                                    ;     |     63TZ        |
skip                                ;     |                 |
 nop                                ; <---       2TZ        |
 nop                                ;            2TZ        |
 nop                                ;            2TZ        |
 dey                                ;            2TZ        |
 bne loop1                          ;            3TZ        |
 ;hier geht es NIE weiter!           ===============        |
                                    ;           63TZ        |
badLine                             ;  <--------------------
 sta $d020                          ;            4TZ
 nop                                ;            2TZ
 nop                                ;            2TZ
 nop                                ;            2TZ
 nop                                ;            2TZ
 nop                                ;            2TZ
 nop                                ;            2TZ
 nop                                ;            2TZ
 eor #%00001010                     ;            2TZ
                                    ;===============
                                    ;           20TZ in einer Bad Line

 ;*** erste normale Line nach der bad line
 sta $d020                          ;            4TZ
 ldx #$06                           ;            2TZ
 dex                                        
 bne *-1                            ;X*5TZ-1TZ= 29TZ
 eor #%00001010                     ;            2TZ
 ldy #$01                           ;            2TZ
 cpy blockCount                     ;            4TZ
 bne skip1                          ;            3TZ

 ;falls KEIN Sprung                              2TZ statt 3TZ
 nop                                ;            2TZ
 nop                                ;            2TZ
 bit $01                            ;            3TZ
 bit $01                            ;            3TZ
 bit $01                            ;            3TZ
 ldy #$20                           ;            2TZ
 jmp loop2                          ;            3TZ
                                    ;===============
                                    ;           63TZ
skip1
 bit $01                            ;            3TZ
 bit $01                            ;            3TZ
 ldy #$06                           ;            2TZ
 dec blockCount                     ;            6TZ
 bne loop1                          ;            3TZ 
 ;hier geht es NIE weiter!           ===============
                                    ;           63TZ

 ;*** ab hier kommen nur noch normale Zeilen
loop2
 sta $d020                          ;            4TZ
 ldx #$08                           ;            2TZ
 dex                                        
 bne *-1                            ;X*5TZ-1TZ= 39TZ
 eor #%00001010                     ;            2TZ
 cpy #$01                           ;            2TZ
 bne skip2                          ;            3TZ

 ;falls kein Sprung                              2TZ statt 3TZ
 nop                                ;            2TZ
 nop                                ;            2TZ
 nop                                ;            2TZ
 bit $01                            ;            3TZ
 jmp exit                           ;            3TZ
                                    ;===============
                                    ;           63TZ
skip2
 nop                                ;            2TZ
 nop                                ;            2TZ
 nop                                ;            2TZ
 dey                                ;            2TZ
 bne loop2                          ;            3TZ
 ;hier geht es NIE weiter!           ===============
                                    ;           63TZ

 ;*** hier wird der Raster-IRQ nun verlassen
exit
 sta $d020
 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

blockCount
 !byte $00                          ;Hilfsvariable

Das sieht nun schlimmer aus, als es wirklich ist. Der Programmabschnitt bedarf eigentlich keiner weiteren Erklärung (alles steht in den Kommentaren), es geht hier, wie bereits erwähnt, einzig und allein um die genaue Verschwendung von Taktzyklen.

Wenn alles geklappt hat, sollte das Ergebnis (Rahmen debuggen ist aktiv) so aussehen:

So solltes es aussehen, wenn die Bad Lines beachtet werden.
So solltes es aussehen, wenn die Bad Lines beachtet werden.

(Rahmen debuggen ist aktiv)

Mein Ansatz

Es gibt eine Schleife, die sich solange um alle normalen Zeilen kümmert, bis eine Bad Line ansteht. Die folgende Bad Line wird dann speziell verarbeitet, da in dieser weniger Taktzyklen zur Verfügung stehen. Die nächste Zeile ist wieder normal und bereitet den Block der weiteren normalen Zeilen vor. Diese Block wird dann wieder mit der ersten Schleife abgearbeitet. Zum Schluß kümmert sich eine letzte Schleife um die Zeilen im unteren Rahmen, da kommen ja nur noch normale Zeilen.

Wer mal ein BASIC-Programm laufen läßt, wird feststellen, dass der C64 plötzlich sehr langsam geworden ist. Das liegt daran, dass wir fast die ganz Rechenleistung für unsere Linien verschwenden, fürs BASIC steht dann natürlich weniger Zeit zur Verfügung.

Ihr könnt die Linien auch bis ganz nach unten zeichnen, dann flackert aber alles, sobald ihr z. B. eine Taste drückt.

Unser Lohn ist die Erkenntnis, dass in einer Bad Line nur 20-23 Taktzyklen, statt der sonst üblichen 63 auf einem PAL-System möglich sind.

Die Probleme reißen nicht ab!

Nehmen wir jetzt mal an, ihr müsst den Bildschirm um einige Zeilen scrollen. Also ändern wir am Beginn des Programms die Zeile and #%01111111 ;für die Rasterzeile löschen so, dass wir auch das unterste BIT durch den AND-Befehl löschen (and #%01111110). Dies bewirkt ein Verschieben des Ausgabebereichs (s. VIC-II: Die Register).

Ein Start läßt uns dann schon wieder verzweifeln:

Kaum wird die Anzeige ‚gescrollt‚, kommt es erneut zu Darstellungsfehlern.
Kaum wird die Anzeige „gescrollt“, kommt es erneut zu Darstellungsfehlern.

Bad Lines & Scrolling

Das Auftreten von Bad Lines scheint also mit der aktuellen Scrollposition zusammen zu hängen.

Dies ist in der Tat so! Da der Ausgabebereich verschoben wurde, verschiebt sich auch die erste (und damit alle folgenden) Bad Lines. Wer den Zusammenhang genauer untersucht wird feststellen, dass eine Bad Line auftritt, wenn im Bereich der Ausgabe, die unteren 3-BITs von $d012 mit dem vertikalen Scrollwert (die unteren 3-BITs von $d011 s. VIC-II: Die Register) übereinstimmt.

Wenn man diesen Zusammenhang genauer überdenkt, dann müssten sich Bad Lines durch eine Veränderung des vertikalen Scrollwertes vermeiden lassen. Das klappt tatsächlich, außerdem ist es dadurch möglich Bad Lines dann zu erzeugen, wann man es möchte. So wird z. B. durch das Auslösen einer Bad Line in jeder Zeile der FLIinfoFlexible Line InterpretationIm „Flexible Line Interpretation“-Modus können, im Gegensatz zum normalen Multicolor-Modus, sogar alle 16 Farben in jeder 4*8 Pixel großen „Zelle“ verwendet werden.-Grafikmodus erst möglich.

Wer die Auswirkung des Scrollens mal testen möchte, der braucht z. B. nur in der Zeile vor einer Bad Line $d011 um eins erhöhen. Somit sind auch in der nächsten Zeile die unteren drei BITs von $d011 und $d012 verschieden. Es gibt also keine Bad Line. In der eigentlichen Bad Line dann nochmal $d011 um eins erhöhen und daran denken, jetzt 63TZ zu warten. In der Zeile nach der Bad Line dann $d011 wieder auf seinen ursprünglichen Wert zurück setzen.

Es gibt keine Bad Lines mehr!
Es gibt keine Bad Lines mehr!

Die Ausgabe sieht zwar merkwürdig aus, aber es gibt jetzt wirklich keine Bad Lines mehr! Die Erklärung, weshalb der Ausgabebereich schwarz ist, wurde eigentlich schon oben gegeben. Der VIC benötigt die Bad Lines, um die Grafikausgabe (s. oben Zeichenzeiger und Pixeldaten) vorzubereiten. Da wir ihm hier aber keine Bad Line zubilligen, kann er auch nichts ausgeben. Was man mit ohne 😉 Bad Lines anstellen kann, schauen wir uns nach dem nächsten Beitrag einmal an.

Wer möchte, kann auch mal blind, folgendes BASIC-Programm eingeben und starten.
10 POKE53280,RND(0)*16:GOTO 10
Wie ihr am Flackern erkennt (sofern ihr euch nicht vertippt habt) arbeitet das BASIC im Hintergrund noch.

Das BASIC ist noch aktiv.
Das BASIC ist noch aktiv.

Hier noch das komplette Listing, um die Bad Lines zu vermeiden. Wie immer gilt, dieses Programm dient der Erklärung, es ist in keinster Weise optimiert.

RASTER          = $03               ;Hier beginnen die Linien

;*** 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 Interrupt auftreten
 sta $d012                      

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

 ;*** TEST TEST TEST ***
;and #%11111110                     ;BS-Ausgabe abschalten
 ;*** TEST TEST TEST ***
 sta $d011

 lda #%0111111                      ;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



!align 1023,0
myIRQ
;*** Wenn wir hier landen, sind bereits 38-45 Taktzyklen in der
;*** aktuellen Rasterzeile (ab jetzt als STARTROW bezeichnet) 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 (STARTROW+2) 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 (STARTROW+1)!
;*** Verbraucht wurden dort 1-8 TZ
 inc $d012                          ;(6 TZ) 2. IRQ in der übernächsten Zeile STARTROW+2
                                    ;       $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 STARTROW+1 und 
;*** haben bisher 13-20 Zyklen verbraucht

;*** etwas Zeit verschwenden...
 ldx #$08                           ;            2 TZ
 dex                                ;8 * 2 TZ = 16 TZ
 bne *-1                            ;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 STARTROW+2 und
;*** haben bisher 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)
 lda #$00                           ;(2 TZ) Farbe in den Akku
 ldx $d012                          ;(4 TZ)
 cpx $d012                          ;(4 TZ) sind wir noch in Rasterzeile STARTROW+2?
                                    ;======
                                    ;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 (STARTROW+3)
;*** nach dem 1. Raster-IRQ (den hatten wir ja für Zeile STARTROW festgelegt)

myIRQMain
 ;*** Soviel Zeit in der aktuellen Zeile verschwenden,
 ;*** dass die nächste durchgängig eine Farbe erhält
                                    ;s. oben     3TZ
 ldx #$09                           ;            2TZ
 dex
 bne *-1                            ;X*5TZ-1TZ= 44TZ
 nop                                ;            2TZ
 ldy #$2c                           ;            2TZ
 ldx #$19                           ;            2TZ
 stx blockCount                     ;            4TZ
                                    ;===============
                                    ;           59TZ

 ;*** Hier die 'normalen' Rasterzeilen verarbeiten
loop1
 sta $D020                          ;            4TZ
 ldx #$08                           ;            2TZ
 dex                                        
 bne *-1                            ;X*5TZ-1TZ= 39TZ
 ;*** letzte Zeile speziell, sonst gibt es einen Versatz
 cpy #$01                           ;            2TZ
 bne skip0                          ;      ------3TZ
                                    ;     |             
                                    ;     |      2TZ
 eor #%00001010                     ;     |      2TZ
 bit $01                            ;     |      3TZ
 inc $D011                          ;     |      6TZ Bad Line verschieben! Y-Scroll
 jmp skip1                          ;     |      3TZ -------
                                    ;     |   ======        |
                                    ;     |     63TZ        |
skip0                               ;     |                 |
 nop                                ; <---       2TZ        |
 nop                                ;            2TZ        |
 nop                                ;            2TZ        |
 eor #%00001010                     ;            2TZ        |
 dey                                ;            2TZ        |
 bne loop1                          ;            3TZ        |
                                    ;===============        |
                                    ;           63TZ        |
                                    ;                       |
;*** bad line (jetzt nicht mehr!)                           |
skip1                               ;  <--------------------
 sta $d020                          ;            4TZ
 eor #%00001010                     ;            2TZ
 nop                                ;            2TZ
 nop                                ;            2TZ 

 nop                                ;            2TZ
 nop                                ;            2TZ
 nop                                ;            2TZ
 ldx $d011                          ;            4TZ akt. Y-Scroll merken

 dex                                ;            2TZ Y-Scroll zurücksetzen
 inc $d011                          ;            6TZ Bad Line nochmal verschieben
 nop                                ;            2TZ

 nop                                ;            2TZ
 nop                                ;            2TZ
 nop                                ;            2TZ
 nop                                ;            2TZ
 nop                                ;            2TZ

 nop                                ;            2TZ
 nop                                ;            2TZ
 nop                                ;            2TZ
 nop                                ;            2TZ
 nop                                ;            2TZ

 nop                                ;            2TZ
 nop                                ;            2TZ
 nop                                ;            2TZ
 nop                                ;            2TZ
 nop                                ;            2TZ

 bit $01                            ;            3TZ
                                    ;===============
                                    ;           63TZ

;*** 1. normale Line nach der bad line
 sta $d020                          ;            4TZ
 stx $d011                          ;            4TZ Y-Scroll zurücksetzen
 bit $01                            ;            3TZ
 bit $01                            ;            3TZ
 ldx #$04                           ;            2TZ
 dex                                        
 bne *-1                            ;X*5TZ-1TZ= 19TZ
 ldy #$01                           ;            2TZ
 cpy blockCount                     ;            4TZ
 bne skip2                          ;            3TZ
                                    ;            2TZ
 nop                                ;            2TZ
 nop                                ;            2TZ
 bit $01                            ;            3TZ
 bit $01                            ;            3TZ
 bit $01                            ;            3TZ
 eor #%00001010                     ;            2TZ
 ldy #$20                           ;            2TZ
 jmp loop2                          ;            3TZ

skip2
 bit $01                            ;            3TZ
 bit $01                            ;            3TZ
 eor #%00001010                     ;            2TZ
 ldy #$06                           ;            2TZ
 dec blockCount                     ;            6TZ
 bne loop1                          ;            3TZ
                                    ;===============
                                    ;           63TZ

                                    ;            2TZ
loop2
 sta $d020                          ;            4TZ
 ldx #$08                           ;            2TZ
 dex                                        
 bne *-1                            ;X*5TZ-1TZ= 39TZ
 cpy #$01                           ;            2TZ
 bne skip3                          ;            3TZ
                                    ;            2TZ
 nop                                ;            2TZ
 nop                                ;            2TZ
 nop                                ;            2TZ
 bit $01                            ;            3TZ
 eor #%00001010                     ;            2TZ
 jmp skip4                          ;            3TZ

skip3
 nop                                ;            2TZ
 nop                                ;            2TZ
 nop                                ;            2TZ
 eor #%00001010                     ;            2TZ
 dey                                ;            2TZ
 bne loop2                          ;            3TZ
                                    ;===============
                                    ;           63TZ
skip4
 sta $d020
 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

blockCount
 !byte $00                          ;Hilfsvariable

Eure Ausgabe könnte etwas anders aussehen. Auf einem Röhrenmonitor erhalten wir evtl. kein Streifenmuster im Rahmen. Dies liegt an der Technik der Röhrenmonitore.

Ausgabe auf dem SX64 (Röhrenmonitor).
Ausgabe auf dem SX64 (Röhrenmonitor).

Statt einem schwarzen Hintergrund kann auch ein anderes Muster (s. Foto vom SX64) erscheinen. Wie bei den Demo-Effekten Oberen & unteren Rand öffnen erklärt, nimmt der VIC-II das BYTE von $3FFF für die Ausgabe, falls ihm nichts Gültiges zur Verfügung steht. Schreibt doch einfach mal verschiedene Werte an diese Adresse und schaut, was dann passiert.


Da der Beitrag die Grenze des Erträglichen bereits deutlich überschritten hat, soll nun erstmal Schluß sein. Allerdings müssen wir im nächsten Beitrag noch einige Punkte klären, z. B. die Auswirkung von Sprites auf das Raster-Timing.


Schrott!!Naja...Geht so...Ganz gut...SUPER! (11 Bewertungen | Ø 4,91 von 5 | 98,18%)

Loading...


ZurückWeiter

3 Gedanken zu „Raster-IRQ: Bad Lines“

  1. Toll gemacht. Das hat mir die Augen geöffnet. Möchte gar nicht wissen wir lange du daran gesessen bist das herauszufinden !!
    Danke für diese tolle Webseite !!!

Schreibe einen Kommentar

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

Protected by WP Anti Spam