Interrupt-Kurs "Die Hardware ausgetrickst..." (Teil 1)
Wer kennt sie nicht, die tollen Effekte, die die großen Alchimisten der Programmierkunst aus unserem guten alten " Brotkasten" herauszaubern: mehr als 8 Sprites gleichzeitig auf dem Bildschirm, Sideund Topborderroutinen, die die
Grenzen des Bildschirmrahmens sprengen, sich wellenförmig bewegende Logos, Grafikseiten, die sich in atemberaubender
Geschwindigkeit zusammenfalten, sowie
schillernd bunte 256- farbige Bilder. Man
könnte diese Liste noch bis ins Unendliche weiterführen und sie würde dennoch
die vollständig sein. In der Tat - was
manche Programmierer aus den Silizuimmolekülen der Hardware des C64 herauskitzeln hat schon so manch Einen zum Stau- nen gebracht. Hätte Commodore zur Markteinführung des C64 schon gewusst, was
für eine Wahnsinnsmaschine sie da gebaut
hatten, hätten sich die Entwickler wahrscheinlich selbst an den Kopf gefasst.
All diese Effekte sind nämlich absolut
unbeabsichtigt unserer " Dampfmaschine" der Computerwelt eingepflanzt worden, sondern verdanken ihre Existenz einzig
und allein der unermüdlichen Ausdauer
der Freaks und Coder, die in nächtelanger Herumtüftelei die Software dazu entwickelten, die den C64 weitaus länger
leben ließ, als man je geglaubt hätte.
" Rasterinterrupt" heißt das Zauberwort
das, einem " Sesam öffne Dich" gleich, dem Programmierer das Tor zur wunderbaren Welt der Computer-Effekte aufstößt.
Und um genau diese Effekte soll sich in
diesem Kurs alles drehen. Die nächsten
Monate sollen Sie hier erfahren, wie man
sie programmiert, und wir werden versuchen Ihnen das Grundwissen zu vermitteln, neue Routinen selbst entwickeln zu können.
Im ersten Teil dieses Kurses wollen wir
uns nun zunächst um die Grundlagen kümmern, und den Fragen " Was ist ein Interrupt?"," Wo kommt er her?" und " Was passiert während eines Interrupts?" auf den
Grund gehen. Leider müssen wir Sie auch
darauf hinweisen, daß zur Programmierung
von Interrupts die gute Kenntnis der
Maschinensprache sowie des Befehlssatzes
des 6510- Prozessors ( so wie er im 64 er
seinen Dienst tut) Grundbedingung ist.
Desweiteren sollten Sie einen guten
Speichermonitor, bzw. Disassembler zur
Hand haben, da wir der Einfachheit halber alle Programmbeispiele als direkt
ausführbaren Code der Magic Disk beifügen werden. Ein gutes Hilfsmittel dieser
Art ist z. B. der, mittlerweise sehr weit
verbreitete," SMON" .
1) WAS SIND INTERRUPTS?
Das Wort " Interrupt" kommt aus dem Englischen und bedeutet " Unterbrechung" .
Und nichts anderes tut nun ein Interrupt: er unterbricht den Prozessor bei
seiner momentanen Arbeit. Das tut er
natürlich nicht einfach aus heiterem
Himmel. Vielmehr hat der Programmierer
die Möglichkeit bestimmte Bedingungen
anzugeben, die einen Interrupt auslösen
sollen. Auf diese Weise kann man ganz
genau den Zeitpunkt bestimmen, zu dem
ein Interrupt auftreten soll. Nun wird
der Prozessor jedoch nicht nur einfach
angehalten, wenn er eine Interruptanforderung bekommt. Da das Ganze ja auch
einen Nutzen haben soll, kann natürlich
ein ganz bestimmtes Programm von ihm
abgearbeitet werden, das auf das Interruptereignis reagieren soll.
Bevor wir uns jedoch in diese Dinge
stürzen, wollen wir erst einmal klären, welche Interrupts der 6510 versteht. Er
kennt insgesamt vier Unterbrechungstypen, die von ihm jeweils unterschiedlich behandelt werden. Die Unterscheidung
wird ihm schon durch seinen hardwaremäßigen Aufbau ermöglicht. Er verfügt nämlich über drei Eingangsleitungen, die
die entsprechenden Interrupts bezeichnen
( der vierte Interrupt ist ein besonderer, den wir weiter unten besprechen
werden) . Die Chips um den Prozessor herum sind nun mit diesen Interrupt-Leitungen verbunden, und können ihm so
das Eintreten eines Unterbrechungsereignisses mitteilen. Sie sehen also, daß
Interrupts hardwaremäßig ausgelöst werden. Um nun eine Unterbrechung zu einem
bestimmten Zeitpunkt auftreten lassen zu
können, sollte man sich ebenfalls in der
Programmierung der Hardware auskennen.
Und diese wollen wir in diesem Kurs
natürlich auch ansprechen.
Kommen wir nun jedoch zu den vier Interrupttypen. Der Erste von ihnen ist wohl
Einer der einfachsten. Es handelt sich
um den " RESET", der von keinem externen Chip, sondern vielmehr von einem " externen" Menschen ausgelöst wird. Er dient
dazu, den Computer wieder in einen Normalzustand zu versetzen. Da der Prozessor selbst nicht merkt, wann er Mist
gebaut hat und irgendwo hängt, muß ihm
der Benutzer das durch Drücken eines
RESET-Tasters mitteilen. Hieraufhin
springt er dann automatisch in ein spezielles Programm im Betriebssystem-ROM
und stellt den Einschaltzustand des
Rechners wieder her. Ein Reset-Taster
ist demnach also direkt mit dem Reset-Interrupteingang des Prozessors verbunden.
Der zweite Interrupttyp heißt " NMI" und
kann ausschließlich von CIA-B des C64 ausgelöst werden. Dies ist ein spezieller Chip, der die Kommunikation zwischen
externen Geräten und 6510 ermöglicht.
Desweiteren ist die ' RESTORE'- Taste direkt mit dem NMI-Eingang des Prozessors
verbunden. Drückt man sie, so wird eben- falls ein NMI ausgelöst. Auch hier
springt der 6510 automatisch in eine
spezielle Routine des ROMs und führt ein
NMI-Programm aus. Es prüft, ob zusätzlich auch noch die " RUN/ STOP"- Taste
gedrückt wurde und verzweigt bei positiver Abfrage in die Warmstartroutine des
BASIC-Interpreters.
Der dritte Interrupt ist der wohl am
häufigsten benutzte. Er heißt " IRQ" und
wird von CIA-A, dem Zwilling von CIA-B, sowie dem Videochip ( VIC) ausgelöst.
Gerade weil der VIC diesen Interrupt
bedient, ist dies derjenige, mit dem wir
in diesem Kurs am meisten arbeiten werden.
Der vierte und letzte Interrupt, ist
derjenige unter den vieren, der keine
Leitung zum Prozessor hat. Das liegt
daran, daß er ein softwaremäßiger Interrupt ist, mit dem sich der Prozessor
quasi selbst unterbricht. Er wird aus- gelöst, wenn der 6510 die " BRK"- Anweisung ausführen soll. Demnach heißt
er auch " BRK"- Interrupt. Er ist eine
sehr einfache Unterbrechung, die eigentlich sehr selten verwendet wird, da sie
ja viel bequemer durch ein " JMP" oder
" JSR" ersetzt werden kann. Dennoch kann
er von Nutzen sein, z. B. wenn man einen
Debugger programmiert. Ebenso kann über
den BRK z. B. in einen Speichermonitor
verzweigt werden, der so die Register
oder einen bestimmten Speicherbereich zu
einem bestimmten Punkt im Programm anzeigen kann. Er unterscheidet sich von
den anderen Interrupts lediglich darin, daß er softwaremäßig ausgelöst wird.
2) IRQ UND NMI - EIN UNGLEICHES PAAR Diese beiden Interrupttypen sind für uns
die Interresantesten, da mit Ihnen Unterbrechungsereignisse der Hardware abgefangen werden, die wir ja exzessiv
programmieren werden. Außer, daß sie beide verschiende Quellen haben, unterscheiden Sie sich auch noch in einem
weiteren Punkt: während der NMI IMMER
ausgelöst wird, wenn ein Unterbrechungsereignis eintritt, kann der IRQ " maskiert", bzw." abgeschaltet" werden. Dies
geschieht über den Assemblerbefehl
" SEI", mit dem das Interruptflag des
Statusregisters gesetzt wird. Ist dieses
Flag nun gesetzt, und es tritt ein IRQ-Interruptereignis ein, so ignoriert der
Prozessor schlichtweg das Auftreten dieser Unterbrechung. Dies tut er solange, bis er durch den Asseblerbefehl " CLI" die Instruktion erhält, das Interrupt-Flag wieder zu löschen. Erst dann reagiert er wieder auf eintretende Unterbrechungen. Dies ist ein wichtiger Umstand, den wir auch später bei unseren
IRQ-Routinen beachten müssen.
3) INTERRUPTVEKTOREN - WEGWEISER FÖR DEN PROZESSOR
Was geschieht nun, wenn der 6510 eine
der oben genannten Interruptanforderungen erhält? Er soll also seine momentane Arbeit unterbrechen, um in die
Interrupt-Routine zu springen. Damit er
Nach Beendingung des Interrupts wieder
normal fortfahren kann muß er jetzt einige Schitte durchführen:
1) Ist der auftretende Interrupt ein
BRK, so wird zunächst das dazugehörige Flag im Statusregister gesetzt.
2) Anschließend werden Highund Lowbyte
( in dieser Reihenfolge) des Programmzählers, der die Speicheradresse
des nächsten, abzuarbeitenden Befehls
beinhaltet, auf den Stack geschoben.
Dadurch kann der 6510 beim Zurückkehren aus dem Interrupt wieder die
ursprüngliche Programmadresse ermitteln.
3) Nun wird das Statusregister auf den
Stack geschoben, damit es bei Beendingung des Interrupts wiederherge- stellt werden kann, so daß es denselben Inhalt hat, als bei Auftreten der
Unterbrechung.
4) Zuletzt holt sich der Prozessor aus
den letzten sechs Bytes seines Adressierungsbereiches ( Adressen $ FFFA-$ FFFF) einen von drei Sprungvektoren.
Welchen dieser drei Vektoren er auswählt hängt von der Art des Interrupts ab, der ausgeführt werden soll.
Hierzu eine Liste mit den Vektoren:
Adresse Interrupt Sprungadr.
$FFFA/$FFFB NMI $FE43 $FFFC/$FFFD RESET $FCE2 $FFFE/$FFFF IRQ/BRK $FF48
Da diese Vektoren im ROM liegen, sind
sie schon mit bestimmten Adressen vorbelegt, die die Betriebssystem-Routinen
zur Interruptverwaltung anspringen. Wir
wollen nun einmal einen Blick auf diese
Routinen werfen. Da der RESET keine für uns nützliche Unterbrechung darstellt, wollen wir ihn jetzt und im Folgenden
weglassen. Kommen wir zunächst zu der
Routine für die IRQ/ BRK-Interrupts. Wie
Sie sehen haben diese beiden Typen denselben Sprungvektor, werden also von der
selben Service-Routine bedient. Da diese
jedoch auch einen Unterschied zwischen
den beiden machen soll hat sie einen
speziellen Aufbau. Hier einmal ein ROM-Auszug ab der Adresse $ FF48 :
FF48: PHA ;Akku auf Stapel FF49: TXA ;X in Akku und FF4A: PHA ; auf Stapel FF4B: TYA ;Y in Akku und FF4C: PHA ; auf Stapel FF4D: TSX ;Stackpointer nach FF4E: LDA $0104,X ;Statusreg. holen FF51: AND #$10 ;BRK-Flag ausmas- kieren FF53: BEQ $FF58 ;Nicht gesetzt also überspringen FF55: JMP ($0316) ;Gesetzt, also Öber Vektor $0316/$0317 springen FF58: JMP ($0314) ;Öber Vektor $0314/ $0315 springen
Dieses kleine Programm rettet zunächst
einmal alle drei Register, indem Sie sie
einzeln in den Akku holt und von dort
auf den Stack schiebt ($ FF48-$ FF4 C) .
Dies ist notwendig, da in der Interruptroutine die Register je ebenfalls benutzt werden sollen, und so die ursprünglichen Inhalte beim Verlassen des Interrupts wiederhergestellt werden können
( durch umgekehrtes zurücklesen) . Ab
Adresse $ FF4 D wird nun eine Unterscheidung getroffen, ob ein BRKoder IRQ-Interrupt aufgetreten ist. Ist ersteres
nämlich der Fall, so muß das BRK-Flag im
Statusregister, das bei Auftreten der
Unterbrechung vom Prozessor auf den
Stack geschoben wurde, gesetzt sein.
Durch Zugriff auf den Stack, mit dem
Stackpointer als Index in X, wird nun das Statusregister in den Akku geholt.
Dies ist übrigens die Einzige Methode, mit der das Statusregister als Ganzes
abgefragt werden kann. Natürlich geht
das immer nur aus einem Interrupt heraus. Das BRK-Flag ist nun im 4 . Bit des
Statusregisters untergebracht. Durch
eine UND-Verknüfung mit diesem Bit kann
es aus dem Statusregisterwert isoliert
werden. Ist der Akkuinhalt nun gleich
null, so war das BRK-Flag nicht gesetzt
und es muß daher ein IRQ vorliegen. In
dem Fall wird auf den JMP-Befehl an
Adresse $ FF58 verzweigt. Im anderen Fall
wird der JMP-Befehl an Adresse $ FF55 ausgeführt.
Wie Sie sehen springen diese beiden Befehle über einen indirekten Vektor, der
beim IRQ in den Adressen $0314/$0315, beim BRK in $0316/$0317 liegt. Da diese
Vektoren im RAM liegen, können Sie auch
von uns verändert werden. Das ist also
auch der Punkt, an dem wir unsere Interrupts " Einklinken" werden. Durch die Belegung dieser Vektoren mit der Adresse
unser eigenen Interruptroutine können
wir den Prozessor also zu dieser Routine
umleiten.
Werfen wir nun noch einen Blick auf die
NMI-Routine, die durch den Vektor bei
$ FFFA/$ FFFB angesprungen wird. Sie ist
noch einfacher aufgebaut und besteht
lediglich aus zwei Befehlen:
FE43: SEI ;IRQs sperren FE44: JMP ($0318) ;Öber Vektor $0318/ $0319 springen
Hier wird lediglich der IRQ geperrt und
anschließend über den Vektor in $0318/$0319 gesprungen. Die Akku, Xund Y-Regsiter werden NICHT gerettet. Das muß
unsere Interruptroutine selbst tun.
Zusammenfassend kann man also sagen, daß
beim Auslösen eines Interrupts jeweils
über einen der drei Vektoren, die im Bereich von $0314-$0319 stehen, gesprungen wird. Der angesprungene Vektor ist
dabei interruptabhängig. Hier nochmal
eine Öbersicht der Interruptvektoren im
genannten Bereich:
Adressen Interrupt Zieladr.
$0314/$0315 IRQ $EA31 $0316/$0317 BRK $FE66 $0318/$0319 NMI $FE47
Diese Vektoren werden bei einem Reset
mit Standardwerten vorbelegt. Hierbei
wird dann die jeweilige Standardroutine
des Betriebssystems angesprungen, die
den entsprechenden Interrupt bearbeiten
soll. Möchten wir eigene Interrupts einbinden, so müssen wir lediglich die
Zieladresse der Vektoren auf den Anfang
unserer Routine verbiegen.
4) PROGRAMMBEISPIELE
Um das Prinzip des Einbindens eines eigenen Interrupts kennenzulernen, wollen
wir nun einmal einen eigenen Interrupt
programmieren. Es handelt sich dabei um
den einfachsten von allen, den BRK-Interrupt. Hier einmal ein Programmlisting, daß Sie als ausführbares Programm
auch auf der Rückseite dieser MD unter
dem Namen " BRK-DEMO1" finden. Sie müssen
es absolut ( mit ",8,1") in den Speicher
laden, wo es dann ab Adresse $1000( dez.
4096) abgelegt wird. Gestartet wird es
mit " SYS4096" . Sie können es sich auch
mit Hilfe eines Disassemblers gerne einmal ansehen und verändern:
;*** Hauptprogramm 1000: LDX #$1C ;BRK-Vektor auf 1002: LDY #$10 ; $101C verbiegen. 1004: STX $0316 1007: STY $0317 100A: BRK ;BRK auslösen 100B: NOP ;Füll-NOP 100C: LDA #$0E ;Rahmenfarbe auf dez.14 100E: STA $D020 ; zurücksetzen 1011: LDX #$66 ;Normalen BRK-Vektor 1013: LDY #$FE ; ($FE66) wieder 1015: STX $0316 ; einstellen 1018: STY $0317 101B: RTS ;Und ENDE! ;*** Interruptroutine 101C: INC $D020 ;Rahmenfarbe hochzählen 101F: LDA $DC01 ;Port B lesen 1022: CMP #$EF ;SPACE-Taste gedrückt? 1024: BNE LOOP ;Nein, also Schleife 1026: PLA ;Ja, also Y-Register 1027: TAY 1028: PLA ;X-Register 1029: TAX 102A: PLA ;u.Akku v.Stapel holen 102B: RTI ;Interrupt beenden.
In den Adressen $1000-$100 A setzen wir
zunächst einmal Lowund Highbyte des
BRK-Vektors auf Adresse $101 C, wo unsere
BRK-Routine beginnt. Direkt danach wird
mit Hilfe des BRK-Befehls ein Interrupt ausgelöst, der, wie wir ja mittlerweile
wissen, nach Retten der Prozessorregister über den von uns geänderten BRK-Vektor auf die Routine ab $101 C springt.
Selbige tut nun nichts anderes, als die
Rahmenfarbe des Bildschirms um den Wert
1 hochzuzählen, und anschließend zu vergleichen, ob die ' SPACE'- Taste gedrückt
wurde. Ist dies nicht der Fall, so wird
wieder zum Anfang verzweigt, so daß
ununterbrochen die Rahmenfarbe erhöht
wird, was sich durch ein Farbschillern
bemerkbar macht. Wird die ' SPACE'- Taste
nun endlich gedrückt, so kommen die folgenden Befehle zum Zuge. Hier holen wir
die Prozessorregister, die von der Betriebssystem- Routine in der Reihenfolge
Akku, X, Y auf dem Stack abgelegt wurden, wieder umgekehrt zurück. Das abschließende " RTI" beendet den Interrupt.
Diese Answeisung veranlasst den Prozessor dazu, den alten Programmzähler, sowie das Statusregister wieder vom Stapel
zu holen, und an die Stelle im Hauptpro- gramm zurückzuspringen, an der der BRK
ausgelöst wurde. Dies ist logischerweise
der Befehl direkt nach dem BRK-Kommando.
So sollte man normalerweise denken, jedoch nimmt BRK eine Sonderstellung diesbezüglich ein. Bei IRQs und NMIs wird
tatsächlich der Befehl nach dem zuletzt
bearbeiten wieder ausgeführt, jedoch
wird beim BRK der Offset 2 auf den Programmzähler hinzuaddiert, weshalb nach
Beendigung des Interrupts ein Byte hinter den BRK-Befehl verzweigt wird. Demnach dient der NOP-Befehl, der nach dem
BRK kommt, lediglich dem Angleichen an
die tatsächliche Rücksprungadresse. Er
wird nie ausgeführt, da der Prozessor ja
an Adresse $100 C weiterfährt. Hier nun
setzen wir die Rahmenfarbe wieder auf
das gewohnte Hellblau und geben dem
BRK-Vektor wieder seine Ursprüngliche
Adresse zurück. Würden wir das nicht
tun, so würde beim nächsten BRK-Befehl, der irgendwo ausgeführt wird, automatisch wieder zu unserem Bildschirmflak- kern verzweigt werden. Hier jedoch würde
der Computer unter Umständen nicht mehr
aus dem Interrupt zurückkehren können, weil wir ja nicht wissen, welche Befehle
hinter dem auslösenden BRK standen ( wenn
es nicht der unseres Programmes an
Adresse $100 A war) .
LADEN SIE NUN DEN 2. TEIL DES KURSES !
Um bei JEDEM BRK wieder in einen definierten Zustand zu gelangen, müssten wir
das unterbrochene Programm gänzlich
stoppen und anschließend wieder zur BA-SIC- Eingabe zurückkehren. Dies ist eine
sehr einfache Öbung. Wir müssen lediglich den " Müll" den Prozessor und Betriebssystem- BRK-Routine auf dem Stapel
abgelegt haben, mit Hilfe von sechs aufeinanderfolgenden " PLA"- Befehlen von
dort wieder wegholen, und anschließend
einen BASIC-Warmstart durchführen. Noch
einfacher und sauberer geht es jedoch, wenn wir den Stapelzeiger gleich nochmal
neu initialisieren. Hierbei werden nämlich auch nicht mehr benötigte Daten, die evtl. vor Auslösen des Interrupts
auf dem Stack abgelegt wurden, entfernt.
Demnach kann jedes Programm mit folgendem Interrupt abgebrochen werden:
LDX #$ FF ; Stapelzeiger per X-Register TXS ; zurücksetzen JMP $ E386 ; BASIC-Warmstart anspringen.
Es ist ein beliebiges Programm daß immer
am Ende eines Interrupts stehen kann. Es
bricht zusätzlich auch den Interrupt ab
und kehrt zur BASIC-Eingabe zurück.
Folgendes Programmbeispiel nutzt diese
Methode. Zuvor holt es sich jedoch die
vom Interrupt auf dem Stapel abgelegten
Informationen, und gibt Sie in hexadezimaler Schreibweise auf dem Bildschirm
aus. Dadurch haben Sie eine ganz simple
Debugging-Kontrolle, mit der Sie Fehler
in eigenen Programmen abfangen können.
Wird z. B. ein bestimmter Programmteil
angesprungen, der einen BRK-Befehl
enthält, so wird diese Routine angesprungen, die Ihnen die Registerinhalte, sowie Zustand des Programmzählers, des
Statusregisters und des Stapelzeigers
zum Zeitpunkt der Unterbrechung auf dem
Bildschirm ausgibt. Hier das Listing:
;*** Hauptprogramm
1000: LDX #$0B ;BRK-Vektor auf 1002: LDY #$10 ; eigene Routine 1004: STX $0316 ; bei $100B 1007: STY $0317 ; verbiegen 100A: BRK ;Interrupt auslösen ;*** Interruptroutine 100B: LDA #$69 ;Adresse Infotext 100D: LDY #$10 ; ($1069) laden 100F: JSR $AB1E ;Text ausgeben 1012: PLA ;Inhalt Y-Register 1013: JSR $103E ; ausgeben 1016: PLA ;Inhalt X-Register 1017: JSR $103E ; ausgeben 101A: PLA ;Akkuinhalt 101B: JSR $103E ; ausgeben 101E: PLA ;Statusregister 101F: JSR $103E ; ausgeben 1022: PLA ;PC Low-Byte holen 1023: STA $02 ; u. zwischenspeich. 1025: PLA ;PC High-Byte holen 1026: JSR $103E ; u. ausgeben 1029: LDA #$9D ;'CRSR' left 102B: JSR $FFD2 ; ausgeben 102E: LDA $02 ;PC Low-Byte holen 1030: JSR $103E ; u. ausgeben 1033: TSX ;Stapelzähler nach 1034: TXA ; Akku übertragen 1035: JSR $103E ; u. ausgeben 1038: LDX #$FF ;Stapelzähler 103A: TXS ; initialiseren 103B: JMP $E386 ;BASIC-Warmstart
Im Bereich von $1000-$1009 setzen wir, wie gewohnt, den BRK-Interruptvektor auf
den Beginn unserer Routine, der in diesem Beispiel bei $100 B liegt. In $100 A
wird ein BRK ausgelöst, der die Interruptroutine sofort anspringt. Diese gibt
nun, mit Hilfe der Betriebssystemroutine
" STROUT" bei $ AB1 E, einen kleinen Infotext auf dem Bildschirm aus. Hiernach
holen wir uns Yund X-Register, sowie
Akku und Statusregister vom Stapel ( in
dieser Reihenfolge), und zwar in dem
Zustand, den sie zum Zeitpunkt, als die
Unterbrechung eintrat, innehatten. Die
Werte werden dabei einzeln mit der Rou- tine bei $103 E auf dem Bildschirm ausgegeben. Diese Routine wandelt den Akkuinhalt in eine hexadezimale Zahl um, die
auf dem Bildschirm angezeigt wird. Hinter dieser Zahl gibt sie zusätzlich ein
Leezeichen aus, das als optische Trennung zwischen diesem, und dem nächsten
Wert dienen soll. Die genaue Beschreibung erspare ich mir hier, da sie ja
eigentlich nichts mit unseren Interrupts
zu tun hat.
Im Bereich von $1022-$1032 wird nun
zunächst das Low-Byte des alten Programmzählers vom Stapel geholt und in
der Speicherzelle $02 zwischengespeichert. Hieraufhin wird das High-Byte
geladen und auf dem Bildschirm ausgegeben. Abschließend wird das Low-Byte wieder aus $02 herausgeholt und ebenfalls
ausgegeben. Zwischen den beiden Zahlen
schicken wir noch ein ' CRSR links'- Zeichen auf den Bildschirm, da das Leezeichen, das durch unsere Ausgaberoutine
zwischen Highund Lowbyte steht, nicht erscheinen soll. Die beiden Werte sollen
ja zusammenhängend ausgeben werden.
Abschließend wird der Stapelzeiger in
den Akku transferiert und ebenfalls ausgegeben. Da wir zu diesem Zeitpunkt ja
schon alle Daten, die durch den Interrupt auf den Stapel gelegt wurden, wieder von dort entfernt haben, entspricht
der Stapelzeiger genau dem Wert, der zum
Zeitpunkt der Unterbrechung vorhanden
war.
Abschließend wird der Stapelzeiger wie
oben beschrieben zurückgesetzt, und das
Programm verzweigt auf den BASIC-Warm- start, womit wir den Interrupt ohne Verwendung von " RTI", unter Korrektur aller
ggf. erfolgten Änderungen am Stapel, verlassen hätten.
Versuchen Sie doch jetzt einmal BRKs von
anderen Adressen aus ( durch " POKE Adresse,0 : SYS Adresse", oder in eigenen Programmen auszulösen. Sie werden hierbei
immer wieder zu unserer kleinen Registeranzeige gelangen, die das System automatisch wieder ins BASIC zurückführt. Benutzen Sie jedoch nach dem
erstmaligen Initialiseren des Programms
nach Möglichkeit keinen Speichermonitor
mehr, da diese Hilfsprogramme nämlich
ähnlich arbeiten wie unser Programm, und
somit den von uns eingestellten BRK-Vektor auf ihre eigenen Routinen verbiegen.
Experimentieren Sie einmal ein wenig mit
den BRK-Unterbrechungen, um sich die
Eigenarten von Interrupts im Algmeinen
anzueignen. Im nächsten Kursteil werden
wir uns dann um die Ansteuerung der NMIund IRQ-Unterbrechungen kümmern, bevor
wir uns dann im dritten Teil endlich den
interessantesten Interrupts, den Raster-IRQs nämlich, zuwenden werden.
(ub/ih)
Interrupt-Kurs "Die Hardware ausgetrickst..." (Teil 2)
Im zweiten Teil unseres Interruptkurses
wollen wir uns um die Programmierung von
IRQund NMI-Interrupts kümmern. Hierbei
soll es vorrangig um die Auslösung der
Beiden durch die beiden CIA-Chips des
C64 gehen.
1) DER BETRIEBSSYSTEM-IRQ Um einen einfachen Anfang zu machen, möchte ich Ihnen zunächst eine sehr simple Methode aufzeigen, mit der Sie einen
Timer-IRQ programmiereren können. Hierbei machen wir uns zunutze, daß das Betriebssystem selbst schon standardmäßig
einen solchen Interrupt über den Timer
des CIA-A direkt nach dem Einschalten
des Rechners installiert hat. Die Routine die diesen Interrupt bedient, steht bei Adresse $ EA31 und ist vorrangig für
das Cursorblinken und die Tastaturabfrage verantwortlich. Wichtig ist, daß der
Timer der CIA diesen IRQ auslöst. Hierbei handelt es sich um eine Vorrichtung, mit der frei definierbare Zeitintervalle
abgewartet werden können. In Kombination
mit einem Interrupt kann so immer nach
einer bestimmten Zeitspanne ein Interruptprogramm ausgeführt werden. Die
Funktionsweise eines Timers wollen wir
etwas später besprechen. Vorläufig
genügt es zu wissen, daß der Betriebssystem- IRQ von einem solchen Timer im
sechzigstel-Sekunden- Takt ausgelöst
wird. Das heißt, daß 60 Mal pro Sekunde
das Betriebssystem-IRQ- Programm abgearbeitet wird. Hierbei haben wir nun die
Möglichkeit, den Prozessor über den
IRQ-Vektor bei $0314/$0315 auf eine eigene Routine springen zu lassen. Dazu
muß dieser Vektor ledeiglich auf die
Anfangsadresse unseres eigenen Programms
verbogen werden. Hier einmal ein Bei- spielprogramm:
1000: SEI ;IRQs sperren 1001: LDX #$1E ;IRQ-Vektor bei 1003: LDY #$10 ; $0314/$0315 auf eigene 1005: STX $0314 ; Routine bei $101E 1008: STY $0315 ; verbiegen 100B: LDA #00 ;Interruptzähler in Adr. 100D: STA $02 ; $02 auf 0 setzen 100F: CLI ;IRQs wieder erlauben 1010: RTS ;ENDE --- 1011: SEI ;IRQs sperren 1012: LDX #$31 ;IRQ-Vektor bei 1014: LDY #$EA ; $0314/$0315 wieder 1016: STX $0314 ; auf normale IRQ-Rout. 1019: STY $0315 ; zurücksetzen. 101C: CLI ;IRQs wieder erlauben 101D: RTS ;ENDE --- 101E: INC $02 ;Interruptzähler +1 1020: LDA $02 ;Zähler in Akku holen 1022: CMP #30 ;Zähler=30? 1024: BNE 102E ;Nein, also weiter
1026 : LDA #32 ; Ja, also Zeichen in 1028 : STA $0427 ;$0427 löschen 102 B: JMP $ EA31 ; Und SYS-IRQ anspringen ---
102E: CMP #60 ;Zähler=60? 1030: BNE 103B ;Nein, also weiter 1032: LDA #24 ;Ja, also "X"-Zeichen in 1034: STA $0427 ; $0427 schreiben 1037: LDA #00 ;Zähler wieder auf 1039: STA $02 ; Null setzen 103B: JMP $EA31 ;Und SYS-IRQ anspringen
Sie finden dieses Programm übrigens auch
als ausführaren Code auf dieser MD unter
dem Namen " SYSIRQ-DEMO" . Sie müssen es
mit " . . .,8,1" laden und können es sich
mit einem Disassembler anschauen. Gestartet wird es mit SYS4096(=$1000) .
Was Sie daraufhin sehen, ist ein " X", das in der rechten, oberen Bildschirmekke im Sekundentakt vor sich hin blinkt.
Wollen wir nun klären wie wir das zustande gebracht haben:
Bei Adresse $1000-$1011 wird die Inter-
ruptroutine vorbereitet und der Interruptvektor auf selbige verbogen. Dies
geschieht durch Schreiben des Lowund
Highbytes der Startadresse unserer eigenen IRQ-Routine bei $101 E in den IRQ-Vektor bei $0314/$0315 . Beachten Sie
bitte, daß ich vor dieser Initialisierung zunächst einmal alle IRQs mit Hilfe
des SEI-Befehls gesperrt habe. Dies muß
getan werden, um zu verhindern, daß während des Verbiegens des IRQ-Vektors ein
solcher Interrupt auftritt. Hätten wir
nämlich gerade erst das Low-Byte dieser
Adresse geschrieben, wenn der Interrupt
ausgelöst wird, so würde der Prozessor
an eine Adresse springen, die aus dem
High-Byte des alten und dem Low-Byte des
neuen Vektors bestünde. Da dies irgendeine Adresse im Speicher sein kann, würde der Prozessor sich höchstwahrscheinlich zu diesem Zeitpunkt verabschieden, da er nicht unbedingt ein sinnvolles
Programm dort vorfindet. Demnach muß
also unterbunden werden, daß solch ein unkontrollierter Interrupt auftreten
kann, indem der IRQ mittels SEI einfach
gesperrt wird.
Bei $100 B-$100 F setzen wir nun noch die
Zeropageadresse 2 auf Null. Sie soll der
IRQ-Routine später als Interruptzähler
dienen. Anschließend werden die IRQs
wieder mittels CLI-Befehl erlaubt und
das Programm wird beendet.
Durch das Verbiegen des Interruptvektors
und dadurch, daß schon ein Timer-IRQ von
Betriebssystem installiert wurde, wird
unser Programm bei $101 E nun 60 Mal pro
Sekunde aufgerufen. Die Anzahl dieser
Aufrufe sollen nun zunächst mitgezählt
werden. Dies geschieht bei Adresse
$101 E, wo wir die Zähleradresse bei $02 nach jedem Aufruf um 1 erhöhen. Unser
" Sekunden-X" soll nun einmal pro Sekunde
aufblinken, wobei es eine halbe Sekunde
lang sichtbar und eine weitere halbe
Sekunde unsichtbar sein soll. Da wir pro
Sekunde 60 Aufrufe haben, müssen wir
logischerweise nach 30 IRQs das " X"-
Zeichen löschen und es nach 60 IRQs wieder setzen. Dies geschieht nun in den
folgenden Zeilen. Hier holen wir uns den
IRQ-Zählerstand zunächst in den Akku und
vergleichen, ob er schon bei 30 ist.
Wenn ja, so wird ein Leerzeichen ( Code
32) in die Bildschirmspeicheradresse
$0427 geschrieben. Anschließend springt
die Routine an Adresse $ EA31 . Sie liegt
im Betriebssystem-ROM und enthält die
ursprüngliche Betriebssystem-IRQ- Routine, die ja weiterhin arbeiten soll.
Ist die 30 nicht erreicht, so wird nach
$102 E weiterverzweigt, wo wir prüfen, ob
der Wert 60 im Zähler enthalten ist. Ist
dies der Fall, so wird in die obig genannte Bildschirmspeicheradresse der
Bildschirmcode für das " X"(=24) geschrieben. Gleichzeitig wird der Zähler
in Speicherstelle 2 wieder auf 0 zurückgesetzt, damit der Blinkvorgang wieder
von Neuem abgezählt werden kann. Auch
hier wird am Ende auf die Betriebssystem- IRQ-Routine weiterverzweigt.
Ebenso, wenn keiner der beiden Werteim
Zähler stand. Dieser nachträgliche Aufruf des System-IRQs hat zwei Vorteile:
zum Einen werden die Systemfunktionen, die von dieser Routine behandelt werden, weiterhin ausgeführt. Das heißt, daß
obwohl wir einen eigenen Interrupt laufen haben, der Cursor und die Tastaturabfrage weiterhin aktiv sind. Zum Anderen brauchen wir uns dabei auch nicht um
das zurücksetzen der Timerregister ( mehr
dazu weiter unten) oder das Zurückholen
der Prozessorregister ( sie wurden ja
beim Auftreten des IRQs auf dem Stapel
gerettet - sh. Teil1 dieses Kurses) kümmern, da das alles ebenfalls von der
System-Routine abgehandelt wird.
Bleiben nun nur noch die Zeilen von
$1011 bis $101 E zu erläutern. Es handelt
sich hierbei um ein Programm, mit der
wir unseren Interrupt wieder aus dem
System entfernen. Es wird hierbei wie
bei der Initialisierung des Interrupts
vorgegangen. Nach Abschalten der IRQs wird die alte Vektoradresse $ EA31 wieder
in $0314/$0315 geschrieben. Dadurch werden die IRQs wieder direkt zur System-IRQ- Routine geleitet. Sie werden nun
mittels CLI erlaubt und das Programm
wird beendet.
2) DIE PROGRAMMIERUNG DER TIMER Einen Interrupt auf die obig genannte
Weise in das System " einzuklinken" ist
zwar eine ganz angenehme Methode, jedoch
mag es vorkommen, daß Sie für spezielle
Problemstellungen damit garnicht auskommen. Um zum Beispiel einen NMI zu programmieren, kommen Sie um die Initialisierung des Timers nicht herum, da das
Betriebssystem diesen Interrupt nicht
verwendet. Deshalb wollen wir nun einmal
anfangen, in die Eingeweide der Hardware
des C64 vorzustoßen um die Funktionsweise der CIA-Timer zu ergründen.
Zunächst einmal sollte erwähnt werden, daß die beiden CIA-Bausteine einander
gleichen wie ein Ei dem Anderen. Unter- schiedlich ist lediglich die Art und
Weise, wie sie im C64 genutzt werden.
CIA-A ist haupsächlich mit der Tastaturabfrage beschäftigt und übernimmt auch
die Abfrage der Gameports, wo Joystick, Maus und Paddles angeschlossen werden.
Sie kann die IRQ-Leitung des Prozessors
ansprechen, weswegen sie zur Erzeugung
solcher Interrupts harangezogen wird.
CIA-B hingegen steuert die Peripheriegeräte, sowie den Userport. Zusätzlich
hierzu erzeugt sie die Interruptsignale, die einen NMI auslösen. Je nach dem ob
wir nun IRQs oder NMIs erzeugen möchten, müssen wir also entweder auf CIA-A, oder
CIA-B zurückgreifen. Hierbei sei angemerkt, daß wir das natürlich nur dann
tun müssen, wenn wir einen timergesteuerten Interrupt programmieren möchten. Innerhalb der CIAs gibt es zwar
noch eine ganze Reihe weiterer Möglichkeiten einen Interrupt zu erzeugen, jedoch wollen wir diese hier nicht ansprechen. Hier muß ich Sie auf einen schon vor längerer Zeit in der MD erschienenen
CIA-Kurs, in dem alle CIA-Interrupt- quellen ausführlich behandelt wurden, verweisen. Wir wollen uns hier ausschließlich auf die timergesteuerten
CIA-Interrupts konzentrieren.
Beide CIAs haben nun jeweils 16 Register, die aufgrund der Gleichheit, bei
beiden Bausteinen dieselbe Funktion haben. Einziger Unterschied ist, daß die
Register von CIA-A bei $ DC00, und die
von CIA-B bei $ DD00 angesiedelt sind.
Diese Basisadressen müssen Sie also zu
dem entsprechenden, hier genannten, Registeroffset hinzuaddieren, je nach dem
welche CIA Sie ansprechen möchten. Von
den 16 Registern einer CIA sind insgesamt 7 für die Timerprogrammierung
zuständig. Die anderen werden zur Datenein- und - ausgabe, sowie eine Echtzeituhr verwandt und sollen uns hier nicht
interessieren.
In jeder der beiden CIAs befinden sich nun zwei 16- Bit-Timer, die man mit Timer
A und B bezeichnet. Beide können getrennt voneinander laufen, und getrennte
Interrupts erzeugen, oder aber zu einem
einzigen 32- Bit-Timer kombiniert werden.
Was tut nun so ein Timer? Nun, prinzipiell kann man mit ihm bestimmte Ereignisse zählen, und ab einer bestimmten
Anzahl dieser Ereignisse von der dazugehörigen CIA einen Interrupt auslösen
lassen. Hierzu hat jeder der beiden Timer zwei Register, in denen die Anzahl
der zu zählenden Ereignisse in Low/ High-Byte- Darstellung geschrieben wird. Von
diesem Wert aus zählt der Timer bis zu
einem Unterlauf ( Zählerwert=0), und löst
anschließend einen Interrupt aus. Hier
eine Liste mit den 4 Zählerregistern:
Reg. Name Funktion
4 TALO Low-Byte Timerwert A 5 TAHI High-Byte Timerwert A 6 TBLO Low-Byte Timerwert B
7 TBHI High-Byte Timerwert B
Schreibt man nun einen Wert in diese
Timerregister, so wird selbiger in ein
internes " Latch"- Register übertragen und
bleibt dort bis zu nächsten Schreibzugriff auf das Register erhalten. Auf
diese Weise kann der Timer nach einmaligem Herunterzählen, den Zähler wieder
mit dem Anfangswert initialisieren.
Liest man ein solches Register aus, so
erhält man immer den aktuellen Zählerstand. Ist der Timer dabei nicht gestoppt, so bekommt man jedesmal verschiedene Werte.
Zusätzlich gibt es zu jedem Timer auch
noch ein Kontrollregister, in dem festgelegt wird, welche Ereignisse gezählt
werden sollen. Weiterhin sind hier Kontrollfunktionen untergebracht, mit denen
man den Timer z. B. starten und stoppen
kann. Auch hier gibt es einige Bits, die
für uns irrelevant sind, weswegen ich sie hier nicht nenne. Das Kontrollregister für Timer A heißt " CRA" und liegt
an Registeroffset 14, das für Timer B
heißt " CRB" und ist im CIA-Register 15 untergebracht. Hier nun die Bitbelegung
von CRA:
Bit 0( START/ STOP) Mit diesem Bit schalten Sie den Timer an
(=1) oder aus (=0) .
Bit 3( ONE-SHOT/ CONTINOUS) Hiermit wird bestimmt, ob der Timer nur
ein einziges Mal zählen, und dann anhalten soll (=1), oder aber nach jedem Unterlauf wieder mit dem Zählen vom Anfangswert aus beginnen soll.
Bit 4( FORCE LOAD) Ist dieses Bit bei einem Schreibvorgang
auf das Register gesetzt, so wird das
Zählregister, unabhängig, ob es gerade
läuft oder nicht, mit dem Startwert aus
dem Latch-Register initialisiert.
Bit 5( IN MODE) Dieses Bit bestimmt, welche Ereignisse
Timer A zählen soll. Bei gesetztem Bit
werden positive Signale am CNT-Eingang
der CIA gezählt. Da das jedoch nur im
Zusammenhang mit einer Hardwareerweiterung einen Sinn hat, lassen wir das Bit
gelöscht. In dem Fall zählt der Timer
nämlich die Taktzyklen des Rechners.
Dies ist generell auch unsere Arbeitsgrundlage, wie Sie weiter unten sehen
werden.
Kommen wir nun zur Beschreibung von CRB
( Reg.15) . Dieses Register ist weitgehend identisch mit CRA, jedoch unterscheiden sich Bit 5 und 6 voneinander.
Diese beiden Bits bestimmen nämlich ZU-SAMMEN, die Zählerquelle für Timer B ( IN
MODE) . Aus den vier möglichen Kombinationen sind jedoch nur zwei für uns interessant. Setzt man beide Bits auf 0, so zählt Timer B wieder Systemtaktimpul- se. Setzt man Bit 6 auf 1 und Bit 5 auf
0, so werden Unterläufe von Timer A
gezählt. Auf diese Art und Weise kann
man beide Timer miteinander koppeln, und
somit Zählerwerte verwenden, die größer
als $ FFFF sind ( was der Maximalwert für
ein 16- Bit-Wert ist) .
Nun wissen wir also, wie man die beiden
Timer initialisieren kann, und zum Laufen bringt. Es fehlt nun nur noch ein
Register, um die volle Kontrolle über
die CIA-Timer zu haben. Es heißt " Interrupt- Control-Register"(" ICR") und ist
in Register 13 einer CIA untergebracht.
Mit ihm wird angegeben, welche CIA-Ereignisse einen Interrupt erzeugen sollen. Auch hier sind eigentlich nur drei
Bits für uns von Bedeutung. Die Restlichen steuern andere Interruptquellen der
CIA, die uns im Rahmen dieses Kurses
nicht interessieren sollen.
Es sei angemerkt, daß der Schreibzugriff
auf dieses Register etwas anders funk- tioniert als sonst. Will man nämlich
bestimmte Bits setzen, so muß Bit 7 des
Wertes, den wir schreiben möchten, ebenfalls gesetzt sein. Alle anderen Bits
werden dann auch im ICR gesetzt. Die
Bits, die im Schreibwert auf 0 sind, beeinflussen den Registerinhalt nicht.
So kann z. B. Bit 0 im ICR schon gesetzt
sein. Schreibt man nun den Binärwert
10000010(=$81) in das Register, so wird
zusätzlich noch Bit 1 gesetzt. Bit 0 bleibt davon unberührt, und ebenfalls
gesetzt ( obwohl es im Schreibwert
gelöscht ist!) . Umgekehrt, werden bei
gelöschtem 7 . Bit alle gesetzten Bits
des Schreibwertes im ICR gelöscht. Um
also Bit 0 und 1 zu löschen müsste der
Binärwert 00000011 geschrieben werden.
Näheres dazu finden Sie in einem Beispiel weiter unten.
Die nun für uns relevanten Bits sind die
schon angesprochenen Bits 0,1 und 7 .
Die Funktion des 7 . Bits sollte Ihnen
jetzt ja klar sein. Bit 0 und 1 geben an, ob Timer A oder Timer B ( oder beide) einen Interrupt auslösen sollen. Sie
müssen das entsprechende Bit lediglich
auf die oben beschriebene Art setzen, um
einen entsprechenden Interrupt zu erlauben. Um z. B. einen Timer-A- Unterlauf als
Interruptquelle zu definieren, müssen
Sie den Wert $81 in das ICR schreiben.
Für einen Timer-B- Unterlauf $82 . Für
beide Timer als Interruptquelle $83 .
Das ICR hat nun noch eine weitere Funktion. Tritt nämlich ein Interrupt auf, so wissen wir als Programmierer ja noch
nicht, ob es tatsächlich ein CIA-Interrupt war, da es auch moch andere
Interruptquellen als nur die CIA gibt.
Um nun zu überprüfen, ob der Interrupt
von einer CIA stammt, kann das ICR ausgelesen werden. Ist in diesem Wert nun
das 7 . Bit gesetzt, so heißt das, das
eines der erlaubten Interruptereignisse
eingetreten ist. Wenn wir wissen möchten, um welches Ereignis es sich dabei
genau handelt, brauchen wir nur die Bits zu überprüfen, die die Interruptquelle
angeben. Ist Bit 0 gesetzt, so war es
Timer A, der den Interrupt auslöste, ist
Bit 1 gesetzt, so kam die Unterbrechung
von Timer B. Das Auslesen des ICR hat
übrigens noch eine weitere Funktion:
solange in diesem Register ein Interrupt
gemeldet ist, werden weitere Interruptereignisse ignoriert. Erst wenn das Register ausgelesen wird, wird der CIA
signalisiert, daß der Interrupt verarbeitet wurde und neue Unterbrechungen
erlaubt sind. Auf diese Weise kann verhindert werden, daß während der Abarbeitung eines Interrupts noch ein zweiter
ausgelöst wird, was womöglich das gesamte Interruptsystem durcheinander bringen
könnte. Sie müssen also, egal ob Sie
sicher sind, daß der Interrupt von der
CIA kam, oder nicht - das ICR immer einmal pro Interrupt auslesen, damit der
Nächste ausgelöst werden kann. Beachten
Sie dabei auch, daß Sie das Register mit
dem Auslesen gleichzeitig auch löschen!
Sie können den gelesenen Wert also nicht
zweimal über das Register abfragen!
Nach all der trockenen Theorie, wollen
wir einmal in die Praxis übergehen und
uns einem Programmbeispiel widmen. Wir
wollen einmal ein Sprite mittels Joystick in Port 2 über den Bildschirm bewegen. Die Abfrage desselben soll im NMI
geschehen, wobei wir CIA-B 30 Mal einen
Timerinterrupt pro Sekunde auslösen lassen. Timer A soll für diese Aufgabe herhalten. Hier das Programmlisting des
Beispiels, das Sie auf dieser MD auch
unter dem Namen " NMI-SPR- DEMO" finden:
( Anm. d. Red. : Bitte laden Sie jetzt Teil 2 dieses Artikels)
( Fortsetzung Interruptkurs)
--- Initialisierung 1000: LDA #$01 ;Sprite 0 1002: STA $D015 ; einschalten und 1005: STA $D027 ; Farbe auf Weiß 1008: LDA #$3F ;Sprite-Pointer auf 100A: STA $07E8 ; $3F*$40=$0FC0 100D: LDA #$64 ;X-/Y-Position des 100F: STA $D000 ; Sprites auf 1012: STA $D001 ; 100/100 setzen 1015: LDX #$0E ;Rahmen- und Hinter- 1017: LDY #$06 ; grundfarbe auf 1019: STX $D020 ; Hellblau und 101C: STY $D021 ; Dunkelblau 101F: LDA #$7F ;CIA-B-Interrupt-Quellen 1021: STA $DD0D ; sperren 1024: LDA #$00 ;Timer A von CIA-B 1026: STA $DD0E ; anhalten. 1029: LDX #$48 ;Startadresse für neuen 102B: LDY #$10 ; NMI (=$1048) im 102D: STX $0318 ; Pointer bei $0318/0319 1030: STY $0319 ; ablegen. 1033: LDX #$49 ;Timerzählwert=$8049 1035: LDY #$80 ; (30 Mal pro Sekunde) 1037: STX $DD04 ; in Timerzählerregister 103A: STY $DD05 ; (Timer A) schreiben 103D: LDA #$81 ;Timer A als Interrupt- 103F: STA $DD0D ; quelle erlauben. 1042: LDA #$11 ;Timer starten (mit 1044: STA $DD0E ; FORCE-LOAD-Bit) 1047: RTS ;ENDE --- Interruptroutine 1048: PHA ;Akku retten 1049: TXA ;X in Akku 104A: PHA ; und retten 104B: TYA ;Y in Akku 104C: PHA ; und retten 104D: LDA $DD0D ;ICR auslesen 1050: BNE $1058 ;Wenn<>0, dann CIA-NMI 1052: INC $D020 ;Sonst NMI von RESTORE- ;Taste. Rahmenfarbe erh. 1055: JMP $1073 ;Und auf Ende NMI spr. 1058: LDA $DC01 ;CIA-NMI: Joyport2 ausl. 105B: LSR ;Bit in Carry-Flag rot. 105C: BCS $1061 ;Wenn gelöscht Joy hoch 105E: DEC $D001 ;SpriteX-1 1061: LSR ;Bit in Carry-Flag rot. 1062: BCS $1067 ;Wenn gel., Joy runter 1064: INC $D001 ;SpriteX+1 1067: LSR ;Bit in Carry-Flag rot. 1068: BCS $106D ;Wenn gel. Joy rechts 106A: INC $D000 ;SpriteY+1 106D: LSR ;Bit in Carry-Flag rot. 106E: BCS $1073 ;Wenn gel. Joy links 1070: DEC $D000 :SpriteY-1 1073: PLA ;Y-Reg vom Stapel 1074: TAY ; holen 1075: PLA ;X-Reg vom Stapel 1076: TAX ; holen 1077: PLA ;Akku vom Stapel holen 1078: CLI ;IRQs wieder erlauben 1079: RTI ;RETURN FROM INTERRUPT --- Eigenen NMI entfernen 107A: LDA #$7F ;Alle NMI-Quellen von 107C: STA $DD0D ; CIA-B sperren 107F: LDA #$00 ;Timer A von CIA-B 1081: STA $DD0E ; stoppen 1084: LDX #$47 ;Alten NMI-Vektor 1086: LDY #$FE ; (bei $FE47) wieder in 1088: STX $0318 ; $0318/$0319 108B: STY $0319 ; eintragen 108E: LDA #00 ;Sprite 0 1090: STA $D015 ; ausschalten 1093: RTS ;ENDE
Das Programm starten Sie mit SYS4096 .
Hieraufhin sehen Sie einen Sprite-Pfeil
auf dem Bildschirm, der mit einem Joystick in Port 2 über den Bildschirm bewegt werden kann. Gleichzeitig ist jedoch weiterhin die normale Tastaturabfrage des C64 aktiv. Die beiden Prozesse
laufen scheinbar gleichzeitig ab, weil
wir einen zweiten Interrupt generiert
haben, der den Joystick abfragt und entsprechend das Sprite bewegt. Kommen wir
nun zur Programmbeschreibung:
In den ersten Zeilen, von $1000 bis
$101 E wird zunächst einmal das Sprite
initialisiert und die Bildschirmfarben
auf die gängige Kombination hellblau/ dunkelblau gesetzt. Hiernach folgt die
NMIund Timerinitialisierung. Da wir
auch hier verhindern müssen, daß ein NMI
auftritt, während wir den NMI-Vektor verändern, wird der Wert $7 F in das ICR
geschrieben. Dieser Wert löscht alle
Interruptquellen-Bits, womit kein NMI
mehr von CIA-B ausgelöst werden kann. Da
sie der einzige Chip ist, der NMIs
auslöst, können wir sicher sein, daß
tatsächlich keiner dieser Interrupts
aufgerufen wird. Beachten Sie bitte, daß
der SEI-Befehl hier wirkungslos wäre, denn er sperrt lediglich die IRQs. NMIs
sind nicht abschaltbar, weswegen wir sie
auf diese umständliche Art und Weise
unterbinden müssen. Hiernach halten wir
Timer A an, indem wir einen Wert in CRA
schreiben, der das 0 . Bit gelöscht hat, woraufhin die CIA den Timer stoppt. Dies
müssen wir tun, damit der Timer korrekt
mit seinem Anfangszählwert gefüttert
werden kann. Würden wir nämlich das
Low-Byte geschrieben haben, das High-Byte jedoch noch nicht, während ein Unterlauf des Timers stattfindet, so würde
er mit sich mit einem falschen Anfangswert initialisieren.
In den folgenden Zeilen wird nun der
NMI-Vektor bei $0318/$0319 auf unsere
eigene NMI-Routine bei $1048 verbogen.
Hiernach wird der Timer mit seinem Zählwert initialisiert. Er beträgt $8049 .
Wie wir auf diesen Wert kommen, möchte
ich Ihnen nun erläutern: Wir hatten ja
schon festgestellt, daß wir den Timer
vorwiegend die Takzyklen des 64 ers zählen lassen möchten. Selbige sind der
Taktgeber für den Prozessor. Der Quarz
der sie erzeugt generiert zusammen mit
einigen Bausteinen um ihn herum einen
Takt von genau 985248 .4 Impulsen pro
Sekunde, die auch in Herz ( Hz) gemessen
werden. Dieser Wert erntspricht also
annähernd einem MHz. Um nun zu ermitteln, wieviele Taktzyklen gezählt werden
müssen, damit der Timer genau 30 Mal pro
Sekunde einen Interrupt erzeugt, müssen
wir diesen Wert lediglich durch 30 dividieren. Das Ergebnis hiervon ist genau
32841 .6, was $8049 entspricht! Um nun
z. B.50 Interrupts pro Sekunde zu erzeu- gen dividieren Sie einfach durch 50, usw. Auf diese Weise können Sie die Joystickabfrage auch beschleunigen oder
verlangsamen, denn das Sprite wird bei
betätigtem Joystick immer um genau soviele Pixel pro Sekunde verschoben, wie
Interrupts auftreten. Versuchen Sie die
Timerwerte doch einmal auf 100 oder nur 20 Interrupts pro Sekunde verändern!
Nachdem der Timer nun mit seinem Startwert gefüttert wurde, erlauben wir den
Timer-A- Interrupt durch Setzen des 0 .
Bits im ICR. Hiernach muß der Timer nur
noch gestartet werden. Damit er gleich
beim richtigen Wert beginnt zu zählen, ist hier außer dem START/ STOP-Bit auch
noch das FORCE-LOAD- Bit gesetzt. Das 3 .
Bit ist auf 0, damit der Timer im CONTI-NOUS- Modus läuft. Ebenso wie das 5 . Bit, das ihm sagt, daß er Taktzyklen zählen
soll. Alles in allem wird also der Wert
$11 in CRA geschrieben. Von nun an läuft
der Timer und wird bei einem Unterlauf
einen Interrupt erzeugen, der unsere NMI-Routine bei $1048 anspringen wird.
Kommen wir nun zu dieser Routine selbst.
Zu Anfang müssen wir erst einmal die
drei Prozessorregister retten, da wir ja
einen NMI programmiert haben, bei dem
uns die Betriebsystemsvorbeitungsroutine
diese Arbeit noch nicht abgenommen hat
( im Gegensatz zu der IRQ-Routine) . Hiernach wird das ICR von CIA-B ausgelesen.
Dadurch ermöglichen wir es diesem Chip
beim nächsten Timerunterlauf erneut einen Interrupt auslösen zu können.
Gleichzeitig können wir dadurch abfragen, ob der NMI tatsächlich von der CIA
kam. Dies ist zwar sehr wahrscheinlich, da die CIA der einzige Chip ist, der
diese Art von Interrupt erzeugen kann, jedoch ist die RESTORE-Taste auf der
Tastatur des C64 ebenso mit dem NMI-Eingang des Prozessors verbunden. Das
heißt also, daß durch Drücken dieser
Taste ebenfalls ein NMI ausgelöst wird.
In dem Fall erhalten wir beim Auslesen
des ICR den Wert 0, da ja kein CIA- Interrupt auftrat. Dadurch können wir
abfragen, ob es sich um unseren Timer-NMI, oder einen RESTORE-Tastendruck handelt. Ist letzterer der Auslöser, so
wird einfach die Rahmenfarbe um eins
erhöht, und zum Ende des NMIs weitergeprungen. Handelt es sich um einen CIA-NMI, so wird direkt zur Joystickabfrage
weiterverzweigt, in der wir Bitweise die
Portbits von CIA-A auswerten. Öbrigens:
da in dem gelesenen Register auch Datenbits der Tastatur erscheinen, kann es
passieren, daß Sie bei bestimmten Tastendrücken ebenfalls das Sprite über
den Bildschirm zucken sehen. Dies ist
normal und handelt sich nicht um einen
Fehler!
Zum Abschluß der Routine holen wir die
drei Prozessorregister wieder vom Stapel
und erlauben mittels CLI die IRQs wieder. Selbige wurden nämlich in der Betriebsystemsvorbeitung zum NMI abgeschaltet ( sh. Teil1 dieses Kurses) . Das
Programm wird diesmal nicht mit einem Sprung auf die NMI-Behandlungsroutine
des Betriebssystems beendet, da diese
ausschließlich für die Behandlung der
RESTORE-Taste benutzt wird und demnach
bei einfachem Drücken der RUN-STOP- Taste
sofort einen BASIC-Warmstart auslösen
würde ( so wird nämlich die Tastenkombination RUN-STOP/ RESTORE abgefragt) .
Am Ende des Programmbeispiels sehen Sie
noch eine Routine, die unsere NMI-Routine wieder aus dem System entfernt.
Sie sperrt wieder alle NMI-Interrupts
von CIA-B und stoppt ihren Timer A. Anschließend wird der NMI-Vektor bei
$0318/$0319 wieder auf die ursprüngliche
Adresse, nämlich $ FE47, zurückgebogen.
Das war es nun für diesen Monat. Probieren Sie ein wenig mit den Timerinterrupts herum, und versuchen Sie z. B. einen solchen Interrupt von Timer B auslösen zu lassen. Prüfen Sie auch einmal, was passiert, wenn Sie andere Timerwerte
benutzen. Ich empfehle Ihnen, eine Rou- tine zu schreiben, die immer nach genau
$4 CC7 Taktzyklen ausgelöst werden soll
und dann für eine kurze Zeit die Rahmenfarbe verändert. Sie werden hierbei
schon einmal einen Vorgeschmack auf den
nächsten Kursteil bekommen, in dem wir
uns endlich dann den Rasterinterrupts
nähern werden.
( ub)
Interrupt-Kurs "Die Hardware ausgetrickst..." (Teil 3)
Herzlich willkommen zum dritten Teil
unseres Interruptkurses. In diesem Monat
möchten wir endlich in " medias res" gehen und in den interessantesten Teilbereich der Interruptprogrammierung einsteigen: den Raster-Interrupt.
1) WAS IST EIN RASTER-INTERRUPT Raster-Interrupts sind Interrupts die
vom Videochip des C64 ausgelöst werden.
Er ist die dritte hardwaremäßige Interrupptquelle und bietet uns vielfältige
Möglichkeiten um besonders interessante
und schöne Interrupt-Effekte erzeugen zu
können. Zunächst jedoch wollen wir klären, was so ein Raster-Interrupt nun eigentlich ist. Hierzu wollen wir zuerst
einmal die Funktionsweise eines Computermonitors oder Fernsehers in groben
Zügen besprechen:
Der Bildschirm, so wie Sie ihn jetzt vor
sich haben, ist an seiner Innenseite mit
einem Stoff beschichtet, der, wenn er
von Elektronen getroffen wird, zum
Leuchten angeregt wird. Innerhalb des
Monitors befindet sich nun eine Elektronenquelle, die, durch Magnetfelder
gebündelt, einen hauchdünnen Strahl von
Elektronen erzeugt. Zusätzlich gibt es
noch zwei Ablenkmagnete, die den Strahl
in der Horizontalen und Vertikalen
ablenken können, so daß jede Position
auf dem Bildschirm erreicht werden kann.
Der Elektronenstrahl wird nun durch ein
bestimmtes Bewegungsschema zeilenweise
über den Bildschirm bewegt und bringt
selbigen zum Leuchten. Hierbei ist der
gesamte Bildschirm in 313 sogenannte
Rasterzeilen aufgeteilt. Jedesmal, wenn
eine Zeile von links nach rechts aufge- baut wurde, wird der Stahl kurzfristig
abgeschaltet und am linken Bildschirmrand wieder angesetzt, um die nächste
Zeile zu zeichnen. Dies geschieht mit
einer unglaublichen Geschwindigkeit, so
daß für das sehr träge Auge ein Gesamtbild aus unzählligen Leuchtpunkten auf
dem Bidschirm entsteht. Insgesamt 25 Mal
pro Sekunde ' huscht' der Elektronenstahl
auf diese Weise über den Bildschirm, wodurch 25 Bilder pro Sekunde aufgebaut
werden. Damit nun ein ganz spezielles
Videobild auf dem Bildschirm zu sehen
ist, z. B. der Text, den Sie gerade lesen, benötigt man eine Schaltlogik, die
die Textoder Grafikzeichen aus dem
Speicher des Computers in sichtbare
Bildpunkte umwandelt. Diese Umwandlung
geschieht durch den Video-Chip des C64, dem sogenannten VIC. Er erzeugt Steuersignale für den Rasterstrahl, die angeben in welcher Rasterzeile er gerade
arbeiten soll, und mit welcher Intensität die einzelnen Bildpunkte auf dem Bildschirm leuchten sollen. Helle Bildpunkte werden dabei mit mehr, dunklere
mit weniger Elektronen ' beschossen' . Da
der VIC nun auch die Steuersignale für
den Rasterstrahl erzeugt, weiß er auch
immer, wo sich selbiger gerade befindet.
Und das ist der Punkt an dem wir einsetzen können, denn der VIC bietet uns nun
die Möglichkeit, diese Strahlposition in
einem seiner Register abzufragen. Damit
wir nun aber nicht ständig prüfen müssen, in welcher der 313 Rasterzeilen
sich der Strahl nun befindet, können wir
ihm auch gleich mitteilen, daß er einen
Interrupt bei Erreichen einer bestimmten
Rasterzeile auslösen soll. Dies geschieht über spezielle Interrupt-Register, deren Belegung ich Ihnen nun
aufführen möchte.
2) DIE RASTER-IRQ- PROGRAMMIERUNG Zunächst einmal brauchen wir die Möglichkeit, die gewünschte Rasterzeile festlegen zu können. Dies wird in den
Registern 17 und 18( Adressen $ D011 und
$ D012) des VICs angegeben. Da es ja insgesamt 313 Rasterzeilen gibt reicht nämlich ein 8- Bit-Wert nicht aus, um alle
Zeilen angeben zu können. Aus diesem
Grund ist ein zusätzliches neuntes Bit
in VIC-Register 17 untergebracht. Bit 7 dieses Registers gibt zusammen mit den
acht Bits aus VIC-Register 18 nun die
gewünschte Rasterzeile an. Da die anderen Bits dieses Registers ebenfalls einige Aufgaben des VICs ausfüllen, dürfen
wir nur durch logische Verknüpfungen
darauf zugreifen. Möchten wir also eine
Rasterzeile von 0 bis 255 als Interruptauslöser definieren, so muß das 7 . Bit
mit den folgenden drei Befehlen gelöscht
werden. Die Rasterzeilen-Nummer wird
dann ganz normal in Register 18($ D012) geschrieben:
LDA $D011 ;Reg.17 lesen AND #$7F ;7. Bit löschen
STA $ D011 ; Reg.17 zurückschreiben
Wenn nun eine Rasterzeile größer als 255 in die beiden VIC-Register geschrieben
werden soll, so müssen wir mit den folgenden drei Befehlen das 7 . Bit in Register $ D011 setzen, und die Nummer der
Rasterzeile minus dem Wert 256 in Register $ D012 schreiben:
LDA $D011 ;Reg.17 lesen ORA $80 ;7. Bit setzen STA $D011 ;Reg.17 zurückschreiben.
Um z. B. Rasterzeile 300 als Interruptauslöser einzustellen, müssen wir wie
eben gezeigt das 7 . Bit in $ D011 setzen
und anschließend den Wert 300-256=44 in
$ D012 ablegen.
Das alleinige Festlegen einer Rasterzeile als Interruptauslöser genügt jedoch
noch nicht, den VIC dazu zu bringen den
Prozessor beim Erreichen dieser Zeile zu unterbrechen. Wir müssen ihm zusätzlich
noch mitteilen, daß er überhaupt einen
Interrupt auslösen soll. Dies wird in
Register 26( Adresse $ D01 A) festgelegt.
Es ist das Interrupt-Control- Register
( ICR) des VIC und hat eine ähnliche
Funktion wie das ICR der CIA, das wir im
letzten Kursteil schon kennengelernt
hatten.
Das VIC-ICR kennt vier verschiedene
Ereignisse, die den VIC zum Auslösen
eines Interrupts bewegen. Jedes der
Ereignisse wird durch ein zugehöriges
Bit im ICR repräsentiert, das lediglich
gesetzt werden muß, um das entsprechende
Ereignis als Interruptquelle zu definieren. Hier die Belegung:
Bit Interruptquelle ist
0 Rasterstrahl 1 Kollision Sprites und Hintergrund 2 Kollision zwischen Sprites 3 Lightpen sendet Signal
Wie Sie sehen ist für uns hauptsächlich
Bit 0 von Bedeutung. Setzen wir es, so
löst der VIC bei Erreichen der zuvor
festgelegten Rasterzeile einen Interrupt
aus. Sie sehen sicherlich auch, daß
Sprite-Kollisionen mit Hintergrundgrafik
und/ oder mit anderen Sprites ebenso einen Interrupt auslösen können. Sie können auf diese Weise also auch sehr komfortabel eine Kollision über den Interrupt abfragen. Bit 3 wird nur sehr selten benutzt und bringt eigentlich nur
etwas in Zusammenhang mit einem Lightpen. Selbiger sendet nämlich immer dann
ein Signal, wenn der Rasterstrahl gerade
an ihm vorübergezogen ist. Wird so nun
ein Interrupt ausgelöst, muß das entsprechende Lightpen-Programm nur noch
auswerten, an welcher Rasterposition der
Strahl sich gerade befindet, um herauszufinden, an welcher Stelle des Bildschirms sich der Lightpen befindet. Dies
soll uns hier jedoch nicht interessie- ren.
Wir müssen also lediglich Bit 0 von $ D0-1 A durch Schreiben des Wertes 1 setzen, um Raster-Interrupts auslösen zu können.
3) DAS HANDLING DER RASTER-INTERRUPTS Hardwaremäßig haben wir nun alles festgelegt und den VIC auf die Interrupt-Erzeugung vorbereitet. Was nun noch
fehlt ist die Software die den Interrupt
bedient. Auch dies ist für uns im Prinzip ein alter Hut, denn der VIC ist mit
der IRQ-Leitung des Prozessors verbunden, weswegen wir einen Raster-Interrupt
im Prinzip wie den CIA1- Interrupt des
Betriebssystems abfragen können. Hierzu
brauchen wir natürlich erst einmal eine
Initialisierungsroutine, die den IRQ-Vektor bei $0314/$0315 auf unsere eigene
Interrupt-Routine verbiegt. Hierbei müssen wir jedoch beachten, daß der Betriebssystem- Interrupt, der von Timer A
der CIA1 ausgelöst wird, ebenfalls über diesen Vektor springt. Das bedeutet, daß
unsere Routine zwischen diesen beiden
Interrupts unterscheiden muß, da ein
Raster-IRQ anders bedient werden muß, als ein CIA-IRQ. Würden wir keinen Unterschied machen, so würde uns die CIA
ungehemmt ' dazwischenfunken' und der
Rasterinterrupt würde außer Kontrolle
geraten. Bevor ich mich nun aber in theroretischen Fällen verliere, sehen Sie
sich einmal folgendes Beispielprogramm
an, anhand dessen wir die Lösung dieses
Problems besprechen möchten:
;*** Initialisierung Init: sei ;IRQs sperren lda #$00 ;Rasterzeile 0 als Inter- sta $d012 ; ruptauslöser festlegen lda $d011 ; (höchstwertiges Bit and #$7f ; der Rasterzeile sta $d011 ; löschen) lda #$01 ;Rasterstrahl als Inter- sta $d01a ; ruptquelle festlegen) lda #3 ;Zähler in $02 mit dem sta $02 ; Wert 3 initialisieren) ldx #<(irq) ;IRQ-Vektor bei $0314/ ldy #>(irq) ; $0315 auf eigene stx $0314 ; Routine mit dem Namen sty $0315 ; "IRQ" verbiegen. cli ;IRQs wieder erlauben rts ;ENDE! ;*** Interrupt-Routine irq: lda $d019 ;VIC-IRR lesen sta $d019 ; und zurückschreiben bmi raster ;Wenn 7.Bit gesetzt, war ; es ein Raster-IRQ jmp $ea31 ;Sonst auf Betriebssystem ; Routine springen
raster:
dec $02 ;Zähler runterzählen lda $02 ;Zähler lesen cmp #2 ;Zähler=2? bne rast70 ;Nein, also weiter lda #$70 ;Sonst Zeile $70 als neu- sta $d012 ; en Auslöser festlegen lda #$00 ;Rahmen- und Hintergrund- sta $d020 ; farbe auf 'schwarz' sta $d021 ; setzen jmp $febc ;IRQ beenden
rast70 :
cmp #1 ;Zähler=1? bne rastc0 ;Nein, also weiter lda #$c0 ;Sonst Zeile $c0 als neu- sta $d012 ; en Auslöser festlegen lda #$02 ;Rahmen- und Hintergrund- sta $d020 ; farbe auf 'rot' sta $d021 ; setzen jmp $febc ;IRQ beenden
rastc0 :
lda #$00 ; Wieder Zeile 0 als Aussta $ d012 ; löser festlegen lda #$07 ; Rahmenund Hintergrundsta $ d020 ; farbe auf ' gelb' sta $ d021 ; setzen
lda #3 ;Zähler wieder auf 3 sta $02 ; zurücksetzen jmp $febc ;IRQ beenden
Sie finden das Programm auch als ausführbaren Code unter dem Namen
" RASTERDEMO1" auf dieser MD. Es muß
absolut ( mit ",8,1") geladen und mit
" SYS4096" gestartet werden. Möchten Sie
es sich ansehen, so disassemblieren Sie
mit einem Monitor ab Adresse $1000 .
Unser erster Raster-Interrupt soll nun
auf dem Bildschirm eine Schwarz-Rot- Gold-Flagge darstellen. Zu diesem Zweck
lassen wir den VIC in den drei Rasterzeilen $00,$70 und $ C0 einen Interrupt
auslösen, woraufhin wir die Rahmenund
Hintergrundfarbe in eine der drei Farben
ändern. Gehen wir jedoch Schritt für
Schritt vor und schauen wir uns zunächst
einmal die Initialisierungsroutine an:
Hier schalten wir zunächst einmal die
IRQs mittels des SEI-Befehls ab, und
setzen anschließend auf die schon beschriebene Art und Weise die Rasterzeile 0 als Interruptauslösende Zeile
fest. Hieraufhin wird der Wert $01 in
Register $ D01 A geschrieben, womit wir
die Rasterinterrupts erlauben. Danach
wird noch ein Zähler in Adresse $02 initialisert, den wir zum Abzählen der
Raster-Interrupts benötigen, damit wir
auch immer die richtige Farbe an der
richtigen Rasterposition setzen. Zuletzt
wird der IRQ-Vektor bei $0314/$0315 auf
unsere Routine verbogen und nach einem
abschließenden Freigeben der IRQs wird
die Initialisierung beendet. Von nun
an wird bei jedem IRQ, stammt er nun vom
VIC oder der CIA, die den Betriebssystem
IRQ erzeugt, auf unsere Routine " IRQ" gesprungen.
Damit der Raster-Interrupt nun jedoch
nicht, wie oben schon angesprochen,
außer Kontrolle gerät, müssen wir hier
nun als erstes herausfinden, von welchem
der beiden Chips der IRQ ausgelöst
wurde. Dies geschieht durch Auslesen des
Registers 25 des VICs ( Adresse $ D019) .
Es das Interrupt-Request- Register
dieses Chips, in dem das 7 . Bit, sowie
das entsprechende Bit des ICR, immer
dann gesetzt werden, wenn eines der vier
möglichen Interrupt-Ereignisse des VICs
eingetreten ist. Durch das Lesen dieses
Registers holen wir uns also seinen Wert
in den Akku. Das Anschließende zurückschreiben löscht das Register wieder.
Dies ist notwendig, da der VIC den
soeben in diesem Register erzeugten Wert
solange hält, bis ein Schreibzugriff
darauf durchgeführt wird. Dadurch wird
sichergestellt, daß die Interruptanforderung auch tatsächlich bei einem
behandelnden Programm angelangt ist.
Würden wir nicht schreiben, so würde der
VIC den nächsten Interrupt schlichtweg
ignorieren, da er den alten ja noch für nicht abgearbeitet interpretiert. Der
Schreibzugriff auf das IRR des VICs entspricht im Prinzip also derselben
Funktion, wie der Lesezugriff des ICR
einer CIA, wenn von dort ein Interrupt
ausgelöst wurde.
Im Akku befindet sich nun also der Wert
aus dem IRR. Gleichzeitig mit dem Lesevorgang wurden die dem Wert entsprechenden Flags im Statusregister des
Prozessors gesetzt. Wir müssen nun also
lediglich prüfen, ob das 7 . Bit dieses
Wertes gesetzt ist, um herauszufinden, ob es sich um einen vom VIC stammenden
Raster-IRQ handelt, oder nicht. Dies
geschieht durch den folgenden BMI-Befehl, der nur dann verzweigt, wenn ein
negativer Wert das Ergebnis der letzten
Operation war. Da ein negativer Wert
aber immer das 7 . Bit gesetzt hat, wird
also nur in diesem Fall auf die Unterroutine " RASTER" verzweigt. Im anderen
Fall springen wir mit einem " JMP $ EA31",
die Betriebssystem-IRQ- Routine an.
War das 7 . Bit nun also gesetzt, so wird
ab dem Label " RASTER" fortgefahren, wo
wir zunächst einmal unseren Zähler in
$02 um 1 erniedrigen, und ihn dann in
den Akku holen. Steht dort nun der Wert
2, so wurde der Interrupt von Rasterzeile 0 ausgelöst, und wir setzen Zeile
$70 als nächsten Auslöser fest. Hierbei
müssen wir Bit 7 von $ D011 nicht extra
löschen, da es von der Initialisierungsroutine her ja noch auf 0 ist. Danach
werden Rahmenund Hintergrundfarbe
auf ' schwarz' gesetzt und der Interrupt
durch einen Sprung auf Adresse $ FEBC
beendet. Hier befindet sich das Ende des
Betriebssystems-IRQ- Programms, in dem
die Prozessorregister wieder zurückgeholt werden, und mit Hilfe des
RTI-Befehls aus dem Interrupt
zurückgekehrt wird.
Steht in unserem Zähler bei $02 nun der Wert 1, so wurde der Interrupt von Zeile
$70 ausgelöst. Hier setzen wir die Bildschirmfarben nun auf ' rot' und legen
Zeile $ C0 als Auslöser des nächsten
Raster-Interrupts fest. Tritt selbiger
auf, so steht weder 1 noch 2 in unserem
Zählregister, weswegen zur Routine
" RASTC0" verzweigt wird, wo wir die
Rahmenfarbe auf ' gelb' setzen, den
Zähler wieder mit seinem Startwert
initialisieren und Rasterzeile 0 als
nächsten Interruptauslöser festlegen, womit sich der ganze Prozeß wieder von
vorne wiederholt.
( Bitte laden Sie nun den 2 . Teil dieses
Artikels aus dem Textmenu)
4) DIE PROBLEME BEI RASTER-IRQS
Wenn Sie unser eben beschriebenes Beispielprogramm einmal gestartet und angeschaut haben, so wird Ihnen sicher
aufgefallen sein, daß die Farbbalken
nicht ganz sauber auf dem Bildschirm zu
sehen waren. Bei jedem Farbübergang
flackerte der Bildschirm am linken Rand.
Dies ist eines der größten Probleme bei
der Raster-Interrupt- Programmierung.
Dadurch nämlich, daß der Rasterstrahl
mit einer Wahnsinnsgeschwindigkeit über
den Bildschirm huscht können minimale
Verzögerungen im Programm, wie zum
Beispiel ein weiterer Befehl, bevor die
Farbänderung geschrieben wird, verheerende Folgen haben. In unserem
Beispiel werden die beiden Farben
nämlich genau dann geändert, wenn sich
der Rasterstrahl gerade am Übergang vom
Bildschirmrahmen zum Bildschirmhintergrund befindet. Dadurch entstehen die
kleinen Farbsteifen am linken Rand, wo die Rahmenfarbe schon auf den neuen
Wert, die Hintergrundfarbe jedoch noch
auf dem alten Wert steht. Erst wenn
letztere ebenfalls geändert wurde, ist
das Bild so wie gewünscht. Zusätzlich
benötigt unsere Routine immer
Unterschiedlich viel Rechenzeit, da sie
einige Branch-Befehle enthält, die je
nach Zutreffen der abgefragten Bedingung
2 oder 3 Taktzyklen dauern, weswegen die
Farbstreifen unruhig hinund herspringen. Zusätzlich kann es passieren, daß kurzfristig einer der drei Farbbereiche nicht richtig eingeschaltet
wird, und dann der ganze Balken
flackert. Dieser Fall tritt dann ein, wenn Rasterund CIA-Interrupt kurz
hintereinander auftreten und ein gerade
bearbeiteter Raster-IRQ durch den
CIA-IRQ nochmals unterbrochen wird. Die
Routine kann also noch erheblich
verbessert werden! Stellen Sie doch
einmal die Farbänderung jeweils an den
Anfang der entsprechenden Unterroutine,
und sperren Sie gleichzeitig mittels
" SEI" das Auftreten weiterer IRQs. Die
Flackereffekte sollten sich dadurch
schon um einiges vermindern, aber
dennoch werden Sie nicht ganz
verschwinden. Das liegt hauptsächlich an
unserer Verzweigungsroutine, die
zwischen VICund CIA-IRQ unterscheidet.
Da sie für den Rasterstrahl nicht
unerheblich Zeit verbraucht wird immer
irgendwo eine Assynchronität
festzustellen sein.
Sauberer soll das nun mit unserem
nächsten Programmbeispiel gelöst werden.
Hier wollen wir ganz einfach den
Betriebssystems-IRQ von CIA1 ganz
unterbinden, und ihn quasi über den
Raster-IRQ ' simulieren' . Zusätzlich soll
die Abfrage nach der Rasterzeile, die
den Interrupt auslöste wegfallen, so daß
wenn unsere Routine angesprungen wird, immer gleich die gewünschte Farbe
eingestellt werden kann. Da wir daraufhin auch keine Abfragen mittels
Branch-Befehlen in unserem
Interrupt-Programm haben, wird die
Routine immer mit der gleichen Laufzeit
ablaufen, weswegen unruhiges Zittern
wegfallen wird. Hier nun zunächst das
Beispielprogramm. Sie finden es auf
dieser MD als ausführbaren Code unter
dem Namen " RASTERDEMO2" . Es wird ebenso
geladen und gestartet wie unser erstes
Beispiel:
;*** Initialisierung Init: sei ;IRQs sperren lda #$7f ;Alle Bits im CIA-ICR sta $dc0d ;löschen (CIA-IRQs) lda $dc0d ;CIA-ICR löschen lda #$00 ;Rasterzeile 0 als Inter- sta $d012 ; ruptauslöser festlegen lda $d011 ;Bit 7 in $D011 and #$7f ; löschen. sta $d011 lda #$01 ;Rasterstrahl als Inter- sta $d01a ; rupt-quelle definieren ldx #<(irq1) ;IRQ-Vektor bei $0314/ ldy #>(irq1) ; $0315 auf erste stx $0314 ; Interrupt- sty $0315 ; Routine verbiegen cli ;IRQs wieder freigeben rts ;ENDE!
;*** Erster Interrupt
irq1: lda #$70 ;Zeile $70 als Auslöser sta $d012 ; für nächsten IRQ festl. dec $d019 ;VIC-IRR löschen ldx #<(irq2) ;IRQ-Vektor auf ldy #>(irq2) ; zweite Interrupt- stx $0314 ; Routine sty $0315 ; verbiegen lda #$00 ;Rahmen u. Hintergrund sta $d020 ; auf 'schwarz' sta $d021 ; setzen jmp $febc ;IRQ beenden ;*** Zweiter Interrupt irq2: lda #$C0 ;Zeile $C0 als Auslöser sta $d012 ; für nächsten IRQ festl. dec $d019 ;VIC-IRR löschen ldx #<(irq3) ;IRQ-Vektor auf ldy #>(irq3) ; dritte Interrupt- stx $0314 ; Routine sty $0315 ; verbiegen lda #$00 ;Rahmen u. Hintergrund sta $d020 ; auf 'rot' sta $d021 ; setzen jmp $febc ;IRQ beenden ;*** Dritter Interrupt irq2: lda #$C0 ;Wieder Zeile 0 als Aus- sta $d012 ; löser für IRQ festl. dec $d019 ;VIC-IRR löschen ldx #<(irq1) ;IRQ-Vektor wieder auf ldy #>(irq) ; erste Interrupt- stx $0314 ; Routine sty $0315 ; verbiegen lda #$00 ;Rahmen u. Hintergrund sta $d020 ; auf 'gelb' sta $d021 ; setzen jmp $ea31 ;Betr.sys.-IRQ anspringen
Die Initialisierungsroutine dieses Beispiels unterscheidet sich kaum von dem
des ersten Programms. Wir setzen auch
hier Zeile 0 als Auslöser für den ersten
IRQ und verbiegen den IRQ-Vektor auf die
erste IRQ-Routine mit dem Namen " IRQ1" .
Ganz am Anfang jedoch unterbinden wir
alle CIA1- IRQs, indem wir durch
Schreiben des Wertes $7 F alle Bits des
CIA-ICRs löschen, womit wir alle von
CIA1 auslösbaren IRQs sperren. Das
anschließende Auslesen des ICRs dient
dem ' freimachen' der CIA, falls dort
noch eine Interruptanforderung vorliegen
sollte, die nach Freigeben der IRQs ja
sofort ausgeführt und so in unsere
Raster-IRQ- Routine1 springen würde.
Die Interrupt-Routine selbst wird nun
wirklich nur dann aufgerufen, wenn sich
der Rasterstrahl in Zeile 0 befindet.
Deshalb können wir hier gleich, ohne
große Abfragen durchführen zu müssen, Zeile $70 als nächsten Interruptauslöser
festlegen und die Bildschirmfarben
ändern. Damit hier nun ebenfalls die
richtige Routine angesprungen wird, verbiegen wir den IRQ-Vektor noch auf
die Routine " IRQ2", die speziell den
Interrupt bei Zeile $70 behandeln soll.
Sie verfährt genauso mit dem dritten
Interruptauslöser, Rasterzeile $ C0, für
die die Routine " IRQ3" zuständig ist.
Sie biegt den IRQ-Vektor nun wieder
zurück auf " IRQ1", womit das ganze von
vorne beginnt. Eine Neuheit ist hier
übrigens das Löschen des VIC-IRRs. Um
selbiges zu tun, hatten wir in letztem
Beispiel ja einen Schreibzuriff auf das
Register durchführen müssen. Dies tun
wir hier mit dem Befehl " DEC $ D019" . Er
hat den Vorteil daß er kürzer und schneller ist als einzelnes ein Lesen
und Schreiben des Registers und zudem
kein Prozessorregister in Anspruch
nimmt, in das wir lesen müssen.
Wie Sie nun sehen werden ist unsere
Flagge nun nicht mehr von
Flackererffekten gebeutelt. Die
Farbbalken sind sauber durch horizontale
Linien voneinander getrennt. Versuchen
Sie jetzt doch einmal ein paar andere
Farbkombinationen, oder vielleicht auch
mehr Rasterbalken zu erzeugen. Oder
kombinieren Sie doch einmal Hiresgrafik
mit Text. Die Möglichkeiten solch recht
einfacherer Raster-IRQs sind sehr
vielseitig, und warten Sie erst einmal
ab, wenn wir zu komplizierteren Routinen
kommen. Dies wird im nächsten Monat dann
schon der Fall sein, wenn wir per
Rasterstrahl die Bildschirmränder
abschalten werden. Bis dahin viel Spaß
beim Rasterprogrammieren!
( ub)
Interrupt-Kurs - Teil 4" Die Hardware ausgetrickst. . ."
Herzlich Willkommen zum vierten Teil
unseres IRQ-Kurses. In dieser Ausgabe
möchten wir uns mit sehr zeitkritischen
Rastereffekten beschäftigen und kurz
zeigen, wie man den oberen und unteren
Bildschirmrand mit Hilfe von Rasterinterrupts verschwinden lassen kann.
1) DIE TOP-UND BOTTOM-BORDER- ROUTINE Wie Sie ja sicherlich wissen, ist der
Bildschirm des C64 auf einen sichbaren
Bereich von 320 x200 Pixeln, oder auch
40 x25 Textzeichen beschränkt. Der Rest
des Bildschirms ist von dem meist hellblauen Bildschirmrahmen verdeckt und
kann in der Regel nicht genutzt werden.
Werfen Sie jetzt jedoch einen Blick auf
diese Zeilen, so werden Sie feststellen, daß das Magic-Disk- Hauptprogramm genau
25 Textzeilen, die maximale vertikale
Anzahl also, darstellt und trotzdem am oberen Bildschirmrand die Seitennummer, sowie am unteren Bildschirmrand das MD-Logo zu sehen sind. Ganz offensichtlich
haben wir es geschafft, den Bildschirm
auf wundersame Weise zu vergrößern!
Tatsächlich schaltet das MD-Haupt- programm den oberen und unteren Bildschirmrand schlichtweg ab, so daß wir
auch hier noch etwas darstellen können
und auf diese Weise mehr Informationen
auf einer Bildschirmseite abzulesen
sind. Dies ist wieder einmal nichts anderes als ein Rastertrick. Noch dazu
einer der simpelsten die es gibt. Wie
einfach er zu programmieren ist soll
folgendes kleines Rasterprogramm verdeutlichen. Sie finden es auf dieser MD
unter dem Namen " BORDERDEMO" und müssen
es wie immer mit ",8,1" laden und mittels " SYS4096" starten:
INIT: SEI ;IRQ sperren LDA #$7F ;Timer-IRQ STA $DC0D ; abschalten
LDA $ DC0 D ; ICR löschen
LDA #$F8 ;Rasterzeile $F8 als STA $D012 ; Interrupt-Auslöser LDA $D011 ; festlegen (incl. dem AND #$7F ; Löschen des HI-Bits) STA $D011 LDA #$01 ;Raster als IRQ- STA $D01A ; Quelle wählen LDX #<(IRQ) ;IRQ-Vektoren auf LDY #>(IRQ) ; eigene Routine STX $0314 ; verbiegen STY $0315 ; LDA #$00 ;Letzte VIC-Adr. auf STA $3FFF ; 0 setzen LDA #$0E ;Rahmen- u. Hinter- STA $D020 ; grundfarben auf LDA #$06 ; hellblau/blau STA $D021 ; setzen LDY #$3F ;Sprite-Block 13 LDA #$FF ; ($0340) mit $FF LOOP2 STA $0340,Y ; füllen DEY BPL LOOP2 LDA #$01 ;Sprite 0 STA $D015 ; einschalten STA $D027 ; Farbe="Weiß" LDA #$0D ;Spritezeiger auf STA $07F8 ; Block 13 setzen LDA #$64 ;X- und Y-Pos. STA $D000 ; auf 100/100 STA $D001 ; setzen CLI ;IRQs freigeben RTS ;ENDE IRQ LDA $D011 ;Bildschirm AND #$F7 ; auf 24 Zeilen STA $D011 ; Umschalten DEC $D019 ;VIC-ICR löschen LDX #$C0 ;Verzögerungs- LOOP1 INX ; schleife BNE LOOP1 LDA $D011 ;Bildschirm ORA #$08 ; auf 25 Zeilen STA $D011 ; zurückschalten
INC $ D001 ; Sprite bewegen END JMP $ EA31 ; Weiter zum SYS-IRQ
Wie Sie sehen, besteht die eigentliche
IRQ-Routine, die den Border abschaltet
nur aus einer handvoll Befehlen. Die
Initialisierung der Routine sollte Ihnen
noch aus dem letzten Kursteil bekannt
sein. Wir sperren hier zunächst alle
IRQs und verhindern, daß CIA-A ebenfalls
IRQs auslöst, damit unser Rasterinterrupt nicht gestört wird. Als nächstes
wird Rasterzeile $ F8 als Interruptauslöser festgelegt, was auch einen ganz
bestimmten Grund hat, wie wir weiter
unten sehen werden. Nun sagen wir noch
dem VIC, daß er Rasterinterrupts auslösen soll, und verbiegen den IRQ-Vektor
bei $0314/$0315 auf unsere eigene Routine namens " IRQ" . Die nun folgenden Zeilen dienen lediglich " kosmetischen" Zwecken. Wir setzen hier Rahmenund
Hintergrundfarben auf die Standardwerte
und schalten Sprite 0 ein, das von
unserer Interruptroutine in der Vertikalen pro IRQ um einen Pixel weiterbewegt werden soll. Zudem wird der Spriteblock, der dieses Sprite darstellt, mit
$ FF gefüllt, damit wir ein schönes Quadrat auf dem Bildschirm sehen und keinen
Datenmüll. Nach Freigabe der IRQs
mittels " CLI" wird dann wieder aus dem
Programm zurückgekehrt. Von nun an
arbeitet unsere kleine, aber feine
Raster-IRQ- Routine. Damit Sie sie
verstehen, müssen wir nun ein wenig in
die Funktionsweise des VIC einsteigen:
Normalerweise zeigt uns der Videochip
des C64, wie oben schon erwähnt, ein 25 Text-, bzw.200 Grafikzeilen hohes Bild.
Nun können wir die Bildhöhe mit Hilfe
von Bit 3 in Register 17 des VICs auf 24 Textzeilen reduzieren. Setzen wir es auf"1", so werden 25 Textzeilen dargestellt, setzen wir es auf "0", so sehen
wir lediglich 24 Textzeilen. Im letzteren Fall werden dann jeweils vier
Grafikzeilen des oberen und unteren
Bildschirmrandes vom Bildschirmrahmen
überdeckt. Diese Einschränkung ist vor
allem bei der Programmierung eines
vertikalen Soft-Scrollers von Bedeutung.
Effektiv zeichnet der VIC nun also den
oberen Bildschirmrand vier Rasterzeilen
länger und den unteren vier Rasterzeilen früher. Um nun den Rahmen zu
zeichnen kennt die Schaltlogik des VIC
zwei Rasterzeilen, die er besonders
behandeln muß. Erreicht er nämlich
Rasterzeile $ F7, ab der der Bildschirm
endet, wenn die 24 Textzeilen-Darstel- lung aktiv ist, so prüft er, ob Bit 3 von Register 17 gelöscht ist. Wenn ja, so beginnt er den Rand zu zeichnen, wenn
nein, so fährt er normal fort. Erreicht
er dann Rasterzeile $ FB, die das Ende
eines 25- zeiligen Bildschirms darstellt,
wird nochmals geprüft, ob das obige Bit
auf 0 ist. Wenn ja, so weiß der VIC, daß
er mit dem Zeichnen des Rahmens schon
begonnen hat. Wenn nein, so beginnt er
erst jetzt damit. Mit unserem Interrupt
tricksen wir den armen Siliziumchip nun
aus. Unsere Routine wird immer in Rasterzeile $ F8 angesprungen, also genau
dann, wenn der VIC die 24- Zeilen-Prüfung
schon vorgenommen hat. Da die Darstellung auf 25 Zeilen war, hat er noch
keinen Rand gezeichnet. Unsere Interruptroutine schaltet nun aber auf 24 Zeilen um und gaukelt dem VIC auf
diese Weise vor, er hätte schon mit dem
Zeichnen des Randes begonnen, weshalb er
nicht nocheinmal beginnen muß, und somit
ohne zu zeichnen weitermacht. Dadurch
erscheinen unterer und oberer Bildschimrand in der Hintergrundfarbe, und
es ist kein Rahmen mehr sichtbar. In
diesen Bereichen kann man nun zwar
keinen Text oder Grafik darstellen, jedoch sind Sprites, die sich hier befinden durchaus sichtbar! Sie werden
normalerweise nämlich einfach vom Rahmen
überdeckt, sind aber dennoch vorhanden.
Da der Rahmen nun aber weg ist, sieht
man auch die Sprites, wie das sich
bewegende Sprite 0 unserer Interruptroutine beweist!
Wichtig an unserer Routine ist nun noch, daß wir vor Erreichen des oberen Bildrandes die Darstellung nocheinmal auf 25 Zeilen zurückschalten, damit der Trick
beim nächsten Rasterdurchlauf nocheinmal
klappt. Hierbei darf natürlich frühestens dann umgeschaltet werden, wenn der
Rasterstrahl an der zweiten Prüf-Posi- tion, Rasterzeile $ FB, schon vorbei ist.
Dies wird durch die kleine Verzögerungsschleife bewirkt, die genau 4 Rasterzeilen wartet, bevor mit dem anschließenden
ORA-Befehl Bit 3 in Register 17 des VIC
wieder gesetzt wird. Am Ende unseres
Interrupts bewegen wir das Sprite noch
um eine Y-Position weiter und verzweigen zum Betriebssystem-IRQ, damit die
Systemaufgaben trotz abgeschalteter CIA
dennoch ausgeführt werden. Die interruptauslösende Rasterzeile muß nicht
nochmal neu eingestellt werden, da wir
diesmal nur eine Rasterzeile haben, die
jedesmal wenn sie erreicht wird einen
Interrupt auslöst.
Wollen wir nun noch klären, warum wir
bei der Initialisierung eine 0 in
Adresse $3 FFF geschrieben haben. Wie Sie
vielleicht wissen, kann der VIC Speicherbereiche von lediglich 16 KB adressieren, aus denen er sich seine Daten
holt. Im Normalfall ist das der Bereich
von $0000-$3 FFF. Die letzte Speicherzelle seines Adressbereichs hat nun eine
besondere Funktion. Der Bit-Wert, der in
ihr steht, wird nämlich in allen Spalten
der Zeilen des nun nicht mehr überdekkten Bildschirmrandes dargestellt - und
zwar immer in schwarzer Farbe. Durch das
Setzen dieser Zelle auf 0 ist hier also gar nichts sichtbar. Schreiben wir
jedoch bei aktivierter Borderroutine
mittels " POKE16383, X" andere Werte
hinein, so werden je nach Wert mehr oder
weniger dicke, vertikale Linien in
diesem Bereich sichtbar. Durch Setzen
aller Bits mit Hilfe des Wertes 255( mit
Rahmenfarbe= schwarz), können wir sogar
einen scheinbar vorhandenen Bildschirmrand simulieren!
Vielleicht fällt Ihnen nun auch noch ein
interessanter Nebeneffekt auf: nachdem
wir die oberen und unteren Bildschirmgrenzen abgeschaltet haben, gibt es
Spritepositionen, an denen das Sprite
zweimal zu sehen ist. Nämlich sowohl im
oberen, als auch im unteren Teil des
Bildschirms. Das liegt daran, daß das
PAL-Signal, welches der VIC erzeugt
313 Rasterzeilen kennt, wir aber die
Y-Position eines Sprites nur mit 256 verschiedenen Werten angeben können.
Dadurch stellt der VIC das Sprite an den Y-Positionen zwischen 0 und 30 sowohl am
unteren, als auch am oberen Rand dar.
Bei eingeschalteten Rändern fiel dieser
Nebeneffekt nie auf, da diese Spritepositionen normalerweise im unsichtbaren
Bereich des Bildschirms liegen, wo sie
vom Bildschirmrahmen überdeckt werden.
Bleibt noch zu erwähnen, daß wir mit einem ähnlichen Trick auch die seitlichen
Ränder des Bildschirms verschwinden lassen können, nur ist das hier viel
schwieriger, da es auf ein sehr
genaues Timing ankommt. Wie man damit
umgeht müssen wir jetzt erst noch
lernen, jedoch werde ich in den nächsten
Kursteilen auf dieses Problem nocheinmal
zu sprechen kommen.
2) EINZEILEN-RASTER- EFFEKTE Kommen wir nun zu dem oben schon erwähnten Timing-Problem. Vielleicht haben Sie
nach Studieren des letzten Kursteils
einmal versucht einzeilige Farbraster- effekte zu programmieren. Das heißt also
daß Sie gerade eine Zeile lang, die
Bildschirmfarben wechseln, und sie dann
wieder auf die Normalfarbe schalten.
Hierzu wären dann zwei Raster-Interrupts
notwendig, die genau aufeinander zu
folgen haben ( z. B. in Zeile $50 und
$51) . Wenn Sie verschucht haben ein
solches Rasterprogramm zu schreiben, so
werden Sie bestimmt eine Menge Probleme
dabei gehabt haben, da die Farben nie
genau eine Rasterzeile lang den
gewünschten Wert enthielten, sondern
mindestens eineinhalb Zeilen lang
sichtbar waren. Dieses Problem hat
mehrere Ursachen, die hauptsächlich
durch die extrem schnelle Gesschwindigkeit des Rasterstahls entstehen.
Selbiger bewegt sich nämlich in genau 63 Taktzyklen einmal von links nach rechts.
Da innerhalb von 63 Taktzyklen nicht
allzu viele Instruktionen vom Prozessor
ausgeführt werden können, kann jeder
Befehl zuviel eine zeitliche Verzögerung verursachen, die eine Farbänderung um
mehrere Pixel nach rechts verschiebt, so
daß die Farbe nicht am Anfang der Zeile, sondern erst in ihrer Mitte sichtbar
wird. Da ein IRQ nun aber verhältnismäßig viel Rechenzeit benötigt, bis er
abgearbeitet ist, tritt ein Raster-IRQ
in der nächsten Zeile meist zu früh
auf, nämlich noch bevor der erste IRQ
beendet wurde! Dadurch gerät das
Programm natürlich vollkommen aus dem
Takt und kann ggf. sogar abstürzen!
Noch dazu muß ein weiterer, hardwaremäßiger Umstand beachtet werden: hat man
normale Textdarstellung auf dem Bildschirm eingeschaltet, so muß der VIC
nämlich jedes mal zu Beginn einer
Charakterzeile die 40 Zeichen aus dem
Video-RAM lesen, die er in den folgenden
acht Rasterzeilen darzustellen hat, und
sie entsprechend in ein Videosignal umwandeln. Um diesen Vorgang durchzuführen
hält er den Prozessor für eine Zeit von genau 42 Taktzyklen an, damit er einen
ungestörten Speicherzugriff machen kann.
Eine Charakterzeile ist übrigens eine
der 25 Textzeilen. Da der Bildschirm in
der Regel bei Rasterzeile $32 beginnt, und jede achte Rasterzeile ein solcher
Zugriff durchgeführt werden muß, sind
all diese Zeilen besonders schwierig
über einen Raster-IRQ programmierbar, da
erst nach dem VIC-Zugriff ein Raster-IRQ
bearbeitet werden kann, der jedoch durch
den Zugriff schon viel zu spät eintritt, da die Zeile in der Zwischenzeit ja
schon zu zwei Dritteln aufgebaut wurde.
Hier muß man sich eines speziellen
Tricks behelfen. Um selbigen besser
erläutern zu können, wollen wir uns das
folgende Beispielprogramm einmal etwas
näher anschauen:
;************************************** INIT SEI ;IRQs sperren LDA #$7F ;CIA-A-IRQs STA $DC0D ; unterbinden
LDA $ DC0 D
LDA #$82 ;Rasterzeile $82 als STA $D012 ; Interruptauslöser LDA $D011 ; festlegen (incl. AND #$7F ; Löschen des STA $D011 ; HI-Bits LDA #$01 ;Rasterstrahl ist STA $D01A ; IRQ-Auslöser LDX #<(IRQ) ;IRQ-Vektor auf eigene LDY #>(IRQ) ; Routine verbiegen STX $0314 STY $0315 CLI ;IRQs freigeben VERZ RTS ;ENDE ;************************************** IRQ DEC $D019 ;VIC-IRQ freigeben JSR VERZ ;Verzögern... JSR VERZ NOP LDY #$00 ;Farb-Index init. LOOP1 LDX #$08 ;Char-Index init. LOOP2 LDA $1100,Y ;Farbe holen STA $D020 ; und im Rahmen-und STA $D021 ; Hintergrund setzen INY ;Farb-Index+1 DEX ;Char-Index-1 BEQ LOOP1 ;Wenn Char=0 verzweig. LDA VERZ ;Sonst verzögern... JSR VERZ JSR VERZ JSR VERZ
CPY #$48 ; Farb-Index am Ende?
BCC LOOP2 ; Nein also weiter
LDA #$0E ;Sonst Rahmen/Hinter- STA $D020 ; grund auf LDA #$06 ; Standardfarben STA $D021 ; zurücksetzen JMP $EA31 ;IRQ mit SYS-IRQ beend
Besten laden Sie das Programm einmal und starten es mit " SYS4096" . Sie sehen nun
einen schönen Rasterfarbeneffekt auf dem
Bildschirm, wo wir ab Rasterzeile $83 in
jeder Zeile die Rahmenund Hintergrundfarbe ändern. Ich habe hierbei Farbabstufungen benutzt, die schöne Balkeneffekte erzeugen. Die entsprechende
Farbtabelle liegt ab Adresse $1100 im
Speicher und kann natürlich von Ihnen
auch verändert werden. Kommen wir nun
zur Programmbeschreibung. Die Initialisierungsroutine sollte Ihnen keine Probleme bereiten. Wir schalten wie immer
die CIA ab, sagen dem VIC, daß er einen
Raster-IRQ generieren soll, legen eine
Rasterzeile ( hier Zeile $82) als IRQ-Auslöser fest, und verbiegen die IRQ-Vektoren auf unser eigenes Programm.
Etwas seltsam mag Ihnen nun die eigentliche IRQ-Routine vorkommen. Nachdem wir
mit dem DEC-Befehl dem VIC bestätigt
haben, daß der IRQ bei uns angekommen
ist, folgen nun drei, scheinbar
sinnlose, Befehle. Wir springen nämlich
das Unterprogramm " VERZ" an, das
lediglich aus einem RTS-Befehl besteht, und somit direkt zu unserem Programm
zurückverzweigt. Zusätzlich dazu folgt
noch ein NOP-Befehl der ebensowenig tut, wie die beiden JSRs zuvor. Der Sinn
dieser Instruktionen liegt lediglich in
einer Zeitverzögerung, mit der wir
abwarten, bis der Rasterstrahl am Ende
der Zeile $82 angelangt ist. Wir hätten
hier auch jeden anderen Befehl verwenden
können, jedoch ist es mit JSRs am einfachsten zu verzögern, da ein solcher
Befehl, ebenso wie der folgende RTS-Befehl, jeweils 6 Taktzyklen verbraucht.
Durch einen JSR-Befehl vergehen also
genau 12 Taktzyklen, bis der nächste
Befehl abgearbeitet wird. Da ein NOP-Befehl, obwohl er nichts macht, zwei
Taktzyklen zur Bearbeitung benötigt, und
wir zwei JSRs verwenden, verzögern wir
also um insgesamt 26 Taktzyklen. Genau
diese Verzögerung ist dem DEC-Befehl
zuvor und den folgenden LDXund LDY- Befehlen notwendig, um soviel zu
verzögern, daß sich der Rasterstrahl bis
ans Ende der Rasterzeile bewegt hat.
Hinzu kommt daß wir die 42 Taktzyklen
hinzurechnen müssen, die der VIC den
Prozessor sowieso schon gestoppt hat, da
Rasterzeile $82 eine der schon oben
angesprochenen Charakterzeilen darstellt
($82-$32=$50/8=10- ohne Rest!) .
Laden Sie nun bitte Teil 2 des Kurses!
Ich hoffe, Sie nun nicht unnötig mit dem
Gerede von Taktzyklenzahlen verwirrt zu
haben. Im Endeffekt kommt es darauf an, das Ende der entsprechenden Rasterzeile
abgewartet zu haben. Wieviel Verzögerung
dazu notwendig ist, muß nicht groß
berechnet werden, sondern wird in der
Regel einfach ausprobiert. Sie fügen der
IRQ-Routine einfach so viele Verzögerungen hinzu, bis eine Farbänderung genau
in einer Zeile liegt, und nicht irgendwo
mitten in der Rasterzeile anfängt.
Beachten Sie bitte, daß Sie die Verzögeung für eine Nicht-Charakterzeile
erweitern müssen, da in diesen Zeilen
dem Prozessor ja 42 zusätzliche
Taktzyklen zur Verfügung stehen!
Kommen wir nun zu den folgenden Instruktionen. Auch hier haben wir es nicht
einfach mit irgendeinem Programm zu tun, sondern mit einer sorgfältigen Folge von
Befehlen, die genau darauf abgestimmt ist, immer solange zu dauern, bis genau
eine Rasterzeile beendet ist. Wie ich
zuvor erwähnte sind das immer genau 63 Taktzyklen pro Rasterzeile, in denen der
Prozessor irgendwie beschäftigt sein
muß, damit die nächste Farbänderung zum
richtigen Zeitpunkt eintritt. Wie immer
funkt uns jede achte Rasterzeile der VIC
dazwischen, der den Prozessor dann
wieder für 42 Takte anhält, weswegen
unsere Routine jede achte Rasterzeile nicht mehr und nicht weniger als 63-42=21 Taktzyklen dauern darf! Da die nun folgende Beschreibung etwas haarig wird, und schnell in arithmetisches Taktzyklenjonglieren ausartet, hier nocheinmal die Farbänderungsschleife aus unserem Beispielprogramm, wobei ich hier
die Kommentare durch die Zyklenzahlen je
Befehl ersetzt habe:
LDY #$00 ;2 LOOP1 LDX #$08 ;2 LOOP2 LDA $1100,Y ;4 STA $D020 ;4 STA $D021 ;4 INY ;2 DEX ;2 BEQ LOOP1 ;2 oder 3 LDA VERZ ;4 JSR VERZ ;12 JSR VERZ ;12 JSR VERZ ;12 CPY #$48 ;2 BCC LOOP2 ;2 oder 3
Der LDY-Befehl am Anfang ist eigentlich
weniger wichtig, ich habe ihn nur der
Vollständigkeit halber aufgeführt. Wir
haben hier zwei verschachtelte Schleifen
vor uns. Die eine, mit dem Namen " LOOP1" wird immer nur jede achte Rasterzeile
aufgerufen, nämlich dann, wenn eine
Charakterzeile beginnt. Diese Schleife
wird über das X-Register indiziert. Die
zweite Schleife wird vom Y-Register gesteuert, das gleichzeitig Indexregister
für unsere Farbtabelle bei $1100 ist.
Wichtig ist nun der zeitliche Ablauf der
beiden Schleifen. Wie wir ja wissen, müssen wir in einer Charakterzeile mit
unserem Programm 21 und in einer
normalen Rasterzeile 63 Taktzyklen
verbrauchen. Da wir uns beim ersten
Schleifendurchlauf genau in Rasterzeile
$83 befinden, beginnt die Schleife also
zunächst in einer normalen Rasterzeile
( eine Zeile nach einer Charakterzeile) .
Hier wird die Schleife ab dem Label
" LOOP2" bis zum Ende (" BCC LOOP2") abgearbeitet. Wenn Sie jetzt die Taktzyklen
am Rand innerhalb dieses Bereichs aufaddieren, so vergehen bis zum BCC-Befehl
genau 60 Zyklen. Der BCC-Befehl hat nun
eine ganz besondere Funktion. Alle
Branch-Befehle verbrauchen nämlich bei
nicht zutreffender Abfragebedingung nur
zwei Taktzyklen ( so auch beim zuvorigen
BEQ-Befehl der das X-Register abfrägt) .
Trifft die Bedingung zu, so wie auch
beim abschließenden BCC, so muß verzweigt werden, was einen weiteren,
dritten Taktzyklus in Anspruch nimmt.
Dadurch sind also genau 60+3=63 Taktzyklen verstrichen, wenn die Schleife
das nächste Mal durchlaufen wird. Und
das ist genau die Zeit die vergehen muß, bis der Rasterstrahl in der nächsten
Zeile ist, wo die Farbe erneut geändert
werden kann. Kommt der Strahl nun wieder
in eine Chakterzeile, so ist das X-Register auf Null heruntergezählt. Durch
die zutreffende Abfragebedingung im BEQ-Befehl dauert die Verzweigung nun drei
Takte. Vom Label " LOOP2" bis zu dem BEQ-Befehl verbrauchen wir also nach Adam
Riese nun 19 Taktzyklen. Da der Branch-Befehl zum Label " LOOP1" verzweigt, und
der dortige LDX-Befehl wiederum 2 Zyklen
benötigt, sind genau 21 Takte verstrichen, wenn sich der Prozessor wieder am
Startpunkt," LOOP2" nämlich, befindet.
Und das ist wieder genau die Zeit die
verstreichen musste, damit in der
Charakterzeile der Rasterstrahl wieder
am Anfang der folgenden Zeile steht! Sie sehen also, wie sehr es auf genaues
Timing hier ankommt! Fügen Sie dieser
Kerschleife auch nur einen Befehl hinzu, oder entfernen Sie einen, so gerät das
gesamte Timing ausser Kontrolle und
unsere Farbbalken erscheinen verzerrt
auf dem Bildschirm. Probieren Sie es
ruhig einmal aus!
Zum Abschluß des Raster-IRQs schalten
wir nun wieder die normalen
Bildschirmfarben ein und verzweigen zum
Betriebssystems-IRQ.
3) WEITERE PROGRAMMBEISPIELE
Außer den beiden bisher besprochenen
Programmen finden Sie auf dieser MD noch
drei weitere Beispiele, die lediglich
Variationen des letzten Programms
darstellen. Alle drei werden wie immer
mit ",8,1" geladen und mit " SYS4096" gestartet." RASTCOLOR2" entspricht haargenau " RASTCOLOR1", nur daß ich hier
am Ende eine Routine hinzugefügt habe, die die Farbtabelle um jeweils eine
Zeile weiterrotiert. Das Ergebnis des
Ganzen sind rollende und nicht stehende
Farbbalken.
Die Programme " RASTSINUS1" und "-2" funktionieren nach einem ähnlichen
Prinzip. Hier wird jedoch nicht die
Farbe in den angegebenen Rasterzeilen
verändert, sondern der horizontale
Verschiebeoffset. Dadurch kann der
entsprechende Bildbereich effektvoll
verzerrt werden. Starten Sie
" RASTSINUS1" und fahren Sie mit dem
Cursor in die untere Bildschirmhälfte, so werden dort alle Buchstaben in Form
einer Sinuskurve verzerrt." RASTSINUS2" geht noch einen Schritt weiter. Hier
werden die Werte der Sinustabelle, wie
auch schon bei " RASTCOLOR2" am Ende der
Interruptroutine gerollt, weswegen der
gerasterte Bereich, wasserwellenähnlich hinund her" schlabbert" . Schauen Sie
sich die Programme ruhig einmal mit
Hilfe eines Speichermonitors an, und
versuchen Sie ein paar Änderungen daran
vorzunehmen. Im nächsten Kursteil werden
wir noch ein wenig mehr mit Taktzyklen
herumjonglieren und uns mit FLDund
Sideborder-Routinen beschäftigen.
(ub)
Interrupt-Kurs "Die Hardware ausgetrickst..." (Teil 5)
Nachdem wir im letzten Monat ja schon
kräftig mit schillernden Farbund Sinuswellenrasterroutinen um uns geworfen
haben, möchten wir uns auch in dieser
Ausgabe der MD einem sehr trickreichen
Beispiel eines Raster-IRQs zuwenden: der
FLD-Routine.
1) FLD - EIN ZAUBERWORT FÖR RASTERFREAKS
Die Abkürzung " FLD" steht für " Flexible
Line Distance", was übersetzt soviel
bedeutet wie " beliebig verschiebbarer
Zeilenunterschied" . Diese, zunächst
vielleicht etwas verwirrende, Bezeichnung steht für einen Rastereffekt, der
vom Prinzip und der Programmierung her
extrem simpel ist, jedoch ungeahnte Möglichkeiten in sich birgt. Um zu wissen, welcher Effekt damit gemeint ist, brauchen Sie sich lediglich einmal anzuschauen, was passiert, wenn Sie im MD-Hauptmenu einen neuen Text laden, oder
einen gelesenen Text wieder verlassen:
der Textbildschirm scheint hier von unten her hochgezogen, bzw. nach unten hin
weggedrückt zu werden. Und genau das tut
nun eine FLD-Routine. Hierbei sei darauf
hingewiesen, daß es sich dabei nicht um
irgendeine Programmierakrobatik handelt, bei der aufwendig hinund herkopiert
und rumgescrollt werden muß, sondern um
eine einfache, ca.150 Byte große, Rasterroutine! Der Trick des Ganzen liegt
wie so oft bei der Hardware des 64 ers, die wieder einmal beispielhaft von uns
" veräppelt" wird. Denn eigentlich sollte
sie nicht dazu in der Lage sein, einen
solchen Effekt zu erzeugen!
Wie funktioniert nun diese Routine? Wie
Sie vielleicht wissen, kann in den unteren drei Bits von Register 17 des VICs($ D011), ein vertikaler Verschiebeoffset
für die Bildschirmdarstellung eingetragen werden. In der Regel benutzt man
diese Bits um ein vertikales Softscrolling zu realisieren. Je nach dem welcher
Wert dort eingetragen wird ( von 0 bis
7), kann die Darstellung des sichtbaren
Bildschirms um 0 bis 7 Rasterzeilen nach
unten verschoben werden. Lässt man diese
Werte nacheinander durch das Register
laufen, und kopiert man daraufhin den
Inhalt des Bildschirms von der 2 . Textzeile in die 1 . Textzeile, so entsteht
ein Softscrolling nach unten. Der Wert, der dabei in den unteren drei Bits von
$ D011 steht gibt dem VIC an, ab welchem
vertikalen Bildschirmoffset er damit
anfangen soll, die nächste Textzeile
aufzubauen. Wie wir aus dem letzten Kursteil noch wissen, geschieht dies ab
Rasterzeile 41 und jeweils in jeder achten, folgenden Zeile. Wird nun ein vertkaler Verschiebeoffset angegeben, so
verzögert der VIC diesen Zeitpunkt um die angegebene Anzahl Rasterzeilen ( maximal 7) . Steht in der Vertikalverschiebung z. B. der Wert 1, so muß der VIC
also noch eine Rasterzeile warten, bis
er die nächste Charakterzeile aufzubauen
hat.
Der Trick der FLD-Routine liegt nun darin, daß Sie in jeder Rasterzeile, diesen Charakterzeilenanfang vor dem Rasterstrahl " herschiebt", so daß dieser
eigentlich nie die gesuchte Anfangszeile
erreichen kann - zumindest nicht solange, wie unsere FLD-Routine ihm vortäuscht, noch nicht den Anfang dieser Zeile
erreicht zu haben! Wie einfach das alles
geht, soll Ihnen folgendes Programmbeispiel verdeutlichen. Sie finden es auf
dieser MD unter dem Namen " FLD-DEMO1" und müssen es absolut ( mit ",8,1") laden
und wie alle unsere Programmbeispiele
mit " SYS4096" starten:
init: sei ;IRQs sperren lda #$7f ;CIA-Timer abschalten sta $dc0d ; (SYS-IRQ) lda $dc0d ;Und CIA-ICR löschen lda #$f8 ;Zeile $f8 ist IRQ- sta $d012 ; Auslöser lda $d011 ;7.Bit Rasterzeile and #$7f ; löschen sta $d011 ; u. zurückschr. lda #$01 ;VIC löst Raster-IRQs sta $d01a ; aus ldx #<(IRQ2);IRQ-Vektor auf ldy #>(IRQ2); eigene Routine stx $0314 ; verbiegen sty $0315 lda #$00 ;Zählregister sta $02 ; löschen lda #$ff ;Leerbereich auf sta $3fff ; schwarz setzen cli ;IRQs freigeben verz: rts ;Und Ende --- irq1: lda #$10 ;Vert. Verschiebung sta $d011 ;gleich 0 lda $02 ;Zähler laden beq lab2 ;Wenn 0, überspringen ldx #$00 ;Zählreg. löschen lab1: clc ;Carry f.Add.löschen (!) lda $d011 ;Verschiebung holen (!) adc #$01 ; +1 (!) and #$07 ; unt. Bits ausmask. (!) ora #$10 ; Bit 4 setzen (!) sta $d011 ; u. zurückschr. dec $d019 ;VIC-IRQ freigeben jsr verz ;Verzögern... jsr verz lda $d012 ;Strahlpos < Bild- cmp #$f6 ; schrimende? beq lab2 ;Ja, also überspr. inx ;Zähler+1 cpx $0002 ;Zähler=Endwert? bne lab1 ;Nein, also weiter lab2: lda #$f8 ;Rasterz. $f8 ist sta $d012 ; nächster IRQ-Ausl. dec $d019 ;VIC-IRQs freigeb. lda #$78 ;IRQ-Vektor auf IRQ2- sta $0314 ; Routine verbiegen ldx #$0e ;Bildschirmfarben ldy #$06 ; zurücksetzen stx $d020 sty $d021 jmp $febc ;IRQ beenden --- irq2: lda #$10 ;Vertikal-Versch. sta $d011 ; init. lda #$71 ;Rasterz. $71 ist sta $d012 ; IRQ-Auslöser dec $d019 ;VIC-IRQs freigeben lda #$30 ;IRQ-Vektor auf IRQ1- sta $0314 ; routine verbiegen lda $dc00 ;Portreg lesen lsr ;Akku in Carry rot. bcs lab3 ;C=1? Wenn ja, weiter dec $02 ;Sonst Joystick hoch lab3: lsr ;Akku in Carry rot. bcs lab4 ;C=1? Ja, also weiter inc $02 ;Sonst Joyst. runter lab4: jmp $ea31 ;SYS-IRQ und Ende
Die Beschreibung der Initialisierungsroutine können wir uns sparen, da wir
ihren Aufbau ja schon von anderen Programmbeispielen her kennen. Wichtig ist
nur, daß wir hier Rasterzeile $ F8 als IRQ-Auslöser festlegen, und die zweite
IRQ-Routine (" IRQ2") in den IRQ-Vektor
eintragen. Ansonsten wird hier auch noch
der FLD-Zeilenzähler in Speicherzelle
$02 gelöscht, sowie der Wert $ FF in
letzte Adresse des VIC-Bereichs geschrieben. Die Bedeutung dieser Adresse
kennen wir noch von unserer Borderroutine aus dem letzten Kursteil: ihr Inhalt
wird in schwarzer Farbe immer an allen
Stellen auf dem Bildschirm angezeigt, an
denen wir den Rasterstrahl mit unseren
Interrupts austricksen, was ja auch hier
der Fall ist. Möchten wir an solchen
Stellen die Hintergrundfarbe sehen, so
müssen wir den Wert $00 hineinschreiben.
Die Routine " IRQ2" wird nun immer einmal
pro Bildschirmaufbau aufgerufen. Sie
bereitet die eigentliche FLD-Routine
vor, die ab der Rasterzeile $71 ausgelöst werden soll. Gleichzeitig beinhaltet diese Routine eine Joystickabfrage, mit der wir das Zählregister in Adresse $02 ändern können. Auf diese
Weise kann mit dem Joystick die FLD-Lücke ab Rasterzeile $71, je nach
Wunsch, vergrößert oder verkleinert werden. Abschließend biegt diese IRQ-Routine den IRQ-Vektor auf die eigentliche FLD-IRQ- Routine (" IRQ1") und ruft
den System-IRQ auf, den wir in der Init-Routine ja abgeschaltet hatten und nun
" von Hand" ausführen müssen.
Hiernach ist nun " IRQ1" am Zug. Kern der
Routine ist die Schleife zwischen den
beiden Labels " LAB1" und " LAB2" . Am
wichtigsten sind hierbei die fünf Befehle die ich Ihnen mit Ausrufungszeichen
markiert habe. Hier wird zunächst der
Inhalt des Registers $ D011 gelesen, in
dem der vertikale Verschiebeoffset zu
finden ist, und 1 auf diesen Wert hinzuaddiert. Da dabei auch ein Öberlauf in
das 3 . Bit des Registers stattfinden
kann, das ja nicht mehr zur vertikalen
Verschiebung herangezogen wird, müssen
wir mit dem folgenden AND-Befehl alle Bits außer den unteren dreien ausmaskieren, und mittels ORA, das 3 . Bit wieder
setzen, da es steuert, ob der Bildschirm
ein, oder ausgeschaltet sein soll, und
deshalb immer gesetzt sein muß. Anschließend wird der neue Wert für $ D011 wieder zurückgeschrieben. Da diese Verschiebungsänderung nun auch in jeder
folgenden Zeile auftreten soll, solange
bis der Zeilenzähler abgelaufen ist, müssen mit dem Rest der Routine die 63 Taktzyklen, die der Rasterstrahl zum
Aufbau einer Rasterzeile braucht, verzögert werden. Eine Unterscheidung in normale Rasterzeilen und Charakterzeilen, in denen der Prozessor vom VIC ja für 42 Taktzyklen angehalten wird, und die
Schleife deshalb weniger verzögern muß, braucht diesmal nicht durchgeführt werden, da wir durch das " voruns- Herschieben" der nächsten Charakterzeile
deren Aufbau ja solange verhindern, bis
die Schleife der FLD-Routine beendet
ist. Dies ist dann der Fall, wenn entwe- der der Zähler im X-Register bis auf 0 gezählt wurde, oder aber die Rasterzeile
$ F6 erreicht wurde, ab der der untere
Bildschirmrand beginnt.
Ab dem Label " LAB2", wird nun wieder
Rasterzeile $ F8 für " IRQ2" als Interruptauslöser festgelegt. Zusätzlich verbiegen wir den IRQ-Vektor auf diese Routine
zurück. Dabei wird in unserem Beispiel
lediglich das Low-Byte geändert, da beide Routinen ja an einer Adresse mit
$10 xx anfangen, und somit die High-Bytes
der beiden Routinenadressen immer den
Wert $10 haben. Zum Schluß wird wieder
auf den Teil der Betriebssystemroutine
($ FEBC) gesprungen, der die Prozessorregister vom Stack zurückholt und den Interrupt beendet.
Die Art und Weise, wie wir hier die Vertikalverschiebung vor dem Rasterstrahl
herschieben mag etwas umständlich anmuten. Tatsächlich gibt es hier auch noch
andere Möglichkeiten, die in den Bei- spielprogrammen " FLD-DEMO2", und " FLD-DEMO3" benutzt wurden. Sauberer ist die
Lösung des Zeilenproblems, wenn man das
Register, das die aktuelle Rasterzeile
enthält ($ D012), als Zähler verwendet.
Wir müssen hier lediglich die Rasterposition auslesen, ihren Wert um 1 erhöhen, die unteren drei Bits ausmaskieren
und das 4 . Bit in diesem Register wieder
setzen. Selbiges wird durch die folgende
Befehlsfolge durchgeführt:
clc lda $d012 adc #$01 and #$07 ora #$10 sta $d011
Noch schneller geht das, wenn man den
illegalen Opcode " ORQ" verwendet. Er
addiert 1 auf den Akku hinzu und verodert gleichzeitig das Ergebnis mit dem
Operandenwert. Die Befehlsfolge ist dann nur noch vier Zeilen lang:
lda $d012 and #$07 orq #$10 sta $d011
Selbst wenn diese Methode kürzer ist, als die zuvorgenannte, ist es dennoch
nicht ratsam sie zu verwenden, da " ORQ" wie gesagt ein illegaler, also inoffizieller, Assemblerbefehl ist, und deshalb von den meisten Assemblern und Disassemblern nicht erkannt wird. Zudem
können Laufzeitunterschiede oder gar
Fehlfunktionen bei verschiedenen Produktionsversionen des 6510- Prozessors vorkommen, so daß ein Programm mit einem
solchen illegalen Opcode nicht auf jedem
C64 lauffähig sein muß. Wer es wirklich
kurz will, der sollte über eine Tabelle
die benötigten Zeilendaten holen, wie
das im Beispiel " FLD-DEMO3" der Fall
ist. Hier wurde eine Tabelle bei Adresse$1200 abgelegt, die den jeweils entsprechenden Wert für jede einzelne Rasterzeile enthält. Die eigentlichen FLD-Befehle verkürzen sich damit auf die
beiden folgenden Zeilen:
lda $1200,x sta $d011
Die Lösung des Problems über eine Tabelle beinhaltet gleichzeitig auch noch den
Vorteil, daß wir viel flexibler die FLD-Effekte einsetzen können. So ist es damit sehr einfach möglich, mehrere Charakterzeilen zu verschieben, wie das im
" FLD-DEMO3" der Fall ist. Dieses Beispielprogramm beginnt übrigens ausnahmsweise an Adresse $1100, weswegen es
nicht wie sonst mit " SYS4096", sondern
durch ein " SYS4352" aufgerufen werden
muß.
Alles in allem sollten Sie sich die drei
Beispiele ruhig einmal mit einem Disassembler oder Speichermonitor anschauen um ihre Funktionsweise zu verstehen. Mit
FLD erzielbare Effekte sind sehr vielseitig und sie sollten schon ein wenig
damit herumexperimentieren. Weiterhin
gibt es einige Rastereffekte die durch
eine FLD-Routine stark vereinfacht programmiert werden können, oder sogar ohne
sie gar nicht möglich wären, weswegen
ein gründliches Verständnis der Materie
sehr von Vorteil bei anderen Rastereffekten sein kann.
2) TIMINGPROBLEME UND TAKZYKLENMESSER Wie wir wieder einmal bewiesen haben, ist die Rasterprogrammierung eine Sache, bei der es auf absolut exaktes Timing
ankommt. Noch haariger wird das im nächsten Kursteil ersichtlich, wo wir Ihnen
eine Sideborder-Routine vorstellen werden. Wird diese Routine auch nur einen
Taktzyklus zu früh oder zu spät ausgeführt, so funktioniert sie schon nicht
mehr. Deswahlb wollen wir uns nun erst wieder ein wenig in die Theorie stürzen
und Verfahrensweisen zur Ermittlung der
Laufzeit eines Programms vorstellen.
Wie Sie mittlerweile nun oft genug mitbekommen haben, braucht der Rasterstrahl
zum Aufbau einer Rasterzeile genau 63 Taktzyklen. Innerhalb dieser Zeit müssen
wir den Prozessor immer irgendwie
beschäftigen, damit wir rechtzeitig zum
Beginn der nächsten Rasterzeile eine
weitere Änderung vornehmen können. Hinzu
kommt, daß wir bei eigeschaltemem Textmodus und Rastereffekten im sichtbaren
Bildschirmbereich beachten müssen, daß
jede achte Rasterzeile, jeweils am Beginn einer Charakterzeile, der VIC den
Prozessor für 42 Taktzyklen anhält, damit er die, in den folgenden acht Rasterzeilen darzustellenden, Zeichen generieren kann. Somit bleiben für den
Prozessor für solch eine Rasterzeile nur
noch 21 Taktzyklen Rechenzeit. Um nun
ein exaktes Timing zu erreichen müssten
wir eigentlich die Laufzeiten eines je- den einzelnen Befehls einer Raster-Routine zusammenaddieren um herauszufinden, ob eine Routine schnell, bzw. langsam genug, abgearbeitet wird. Das kann
unter Umständen eine sehr aufwendige
Sache werden, da hierbei ewig lang Befehlstabellen mit Zyklenangaben gewälzt
werden müssten, und bei jeder kleinen
Änderung neue Verzögerungsbefehle in die
Routine eingefügt, oder aus ihr entfernt
werden müssten.
Damit Sie die Zyklenzahlen selbst zur
Hand haben, habe ich Ihnen am Ende dieses Kurses in einer Tabelle alle Prozessor- Befehle in allen möglichen Adressierungsarten aufgelistet. Um also von Hand
die Laufzeit einer Routine zu berechnen
können Sie dort nachschlagen.
Noch einfach geht das Abwägen der Laufzeit jedoch mit einem Programm. Wir können uns hier die Möglichkeit zunutze
machen, daß mit den Timern der CIAs einzelne Zyklen gezählt werden können. Ich habe Ihnen hierzu ein Zyklenmessprogramm
geschrieben, das es Ihnen ermöglicht, eine eigene Routine bezüglich ihrer
Laufzeit zu testen. Es heißt " Cyclecount" und ist ebenfalls auf dieser MD
zu finden. Das Programm ist in der Lage, Routinen mit einer Dauer von maximal
65490 Taktzyklen zu stoppen. Laden Sie
es hierzu mit LOAD" CYCLECOUNT",8,1 in
den Speicher und schreiben Sie Lowund
High-Byte der zu testenden Routine in
die Adressen 828/829($033 c/$033 d) . Die
zu messende Routine muß mit einem
" BRK"- Befehl beendet werden. Rufen Sie
nun das Programm mit einem " SYS49152" auf. Cyclecount gibt Ihnen daraufhin den
ermittelten Zyklen-Wert auf dem Bildschirm aus. Das Programm benutzt dabei
den Timer A von CIA-B zum Messen der
Zyklen. Es initialisert diesen Timer mit
dem Wert $ FFFF, startet ihn und ruft
daraufhin die zu testende Routine auf.
Zuvor wird der BRK-Vektor bei Adresse
$0316/$0317 auf eine eigene Routine ver- bogen. Wird die zu testende Routine nun
mit einem BRK-Befehl beendet, so wird
sofort zur Auswertungsroutine von Cyclecount verzweigt, die den Timer wieder
anhält und den in den Timerregistern
enhaltenen Wert von $ FFFF subtrahiert.
Zudem müssen 45 Zyklen abgezogen werden, die hauptsächlich zur Ausführung des
JMP-Befehls auf die zu testende Routine
und durch den beendenden BRK-Befehl verbraucht wurden, und nicht mitgezählt
werden dürfen.
( Anm. d. Red. : Bitte wählen Sie jetzt den
2 . Teil des Kurses aus dem Textmenu.)
3) DIE ZYKLENTABELLE
Kommen wir nun noch zu der Tabelle mit
den Zyklendauern der einzelnen Befehle.
Da viele darunter mit unterschiedlichen
Adressierungsarten verwendbar sind, habe
ich Ihnen zwei Einzeltabellen aufgeführt, in denen ähnliche Befehle zu
Gruppen zusammengefasst wurden. Kommen
wir hierbei zunächst zu den impliziten
Befehlen. Sie bestehen lediglich aus
einem Befehlswort und benötigen keinen
Operanden:
Befehl Zyklen
ASL 2 LSR 2 ROL 2 ROR 2 CLC 2 SEC 2 CLD 2 SED 2 CLI 2 SEI 2 CLV 2 NOP 2 RTS 6 RTI 6 BRK 7 TAX 2 TAY 2 TXA 2 TYA 2 TXS 2 TSX 2 PLA 4 PHA 3 PLP 4 PHP 3 INX 2 DEX 2 INY 2 DEY 2
Es folgen nun die Befehle, die entweder
direkt, oder über Speicheradressen eine Operation mit den Prozessorregistern
durchführen. Die Bitschiebebefehle kommen hier nochmals vor, da sie auch mit
Adressierung verwendbar sind. Die Spalten der Tabelle stehen ( von links nach
rechts) für:" IMMediate", wenn der Operand ein konstanter Wert ist (" LDA
#00")," ABSolut", für dirkete Speicheradressierung (" LDA $1000")," ABSolut, X"(" LDA $1000, X")," ABSolut, Y"," ZeroPage"(" LDA $02")," ZeroPage, X"," ZeroPage, Y", Zeropage indirektimplizit "( zp, X)" und
Zeropage implizitidirekt "( zp), Y" . Alle
Zyklenangaben, die mit einem "*" markiert sind verlängern sich um einen
Taktzyklus, wenn bei dieser Adressierung
eine Bereichsüberschreitung stattfindet, was bedeutet, daß wenn die Summe des
Offsetregisters und des Basiswertes das
High-Byte überschreitet, ein Takt mehr
benötigt wird, als angegeben. Dies ist
z. B. bei dem Befehl " LDA $10 FF, X" der
Fall. Dann, wenn nämlich im X-Register
ein Wert größer oder gleich 1 steht:
Bef Imm Abs Abs Abs ZP ZP ZP (,X) (,Y) ,X ,Y ,X ,Y
BIT - 4 - - 3 - - - - CPX 2 4 - - 3 - - - - CPY 2 4 - - 3 - - - - CMP 2 4 4* 4* 3 4 - 6 5* ADC 2 4 4* 4* 3 4 - 6 5* SBC 2 4 4* 4* 3 4 - 6 5* AND 2 4 4* 4* 3 4 - 6 5* ORA 2 4 4* 4* 3 4 - 6 5* EOR 2 4 4* 4* 3 4 - 6 5* INC - 6 7 - 5 6 - - - DEC - 6 7 - 5 6 - - - LDA 2 4 4* 4* 3 4 - 6 5* LDX 2 4 - 4* 3 - 4 - - LDY 2 4 4 - 3 4* - - - STA - 4 5 5 3 4 - 6 6 STX - 4 - - 3 - 4 - - STY - 4 - - 3 4 - - - ASL - 6 7 - 5 6 - - - LSR - 6 7 - 5 6 - - - ROL - 6 7 - 5 6 - - - ROR - 6 7 - 5 6 - - - JMP - 3 - - - - - - - JSR - 6 - - - - - - -
Zudem kennt der JMP-Befehl auch noch die
indirekte Adressierung, über einen Low-/ High-Bytezeiger ( z. B." JMP ($ A000)"), der 5 Taktzyklen verbraucht.
Es fehlen jetzt nur noch die Branch-Befehle, die jedoch nicht in einer eigenen Tabelle erscheinen müssen, da sie
immer 2 Taktzyklen verbrauchen. Es sei
denn, die vom Befehl abgefragte Bedingung trifft zu. In diesem Fall wird ein
weiterer, dritter Takt in Anspruch genommen.
Das war es dann wieder für diesen Monat.
Im nächsten Kursteil werden wir uns weiterhin ein wenig mit Timingproblemen
beschäftigen müssen, und uns ansehen, wie man IRQs " glättet" . Dies soll uns dann als Grundlage für den nächsten Raster- Effekt dienen: einer Routine zum
Abschalten der seitlichen Ränder des
Bildschirms.
(ub)
Interrupt-Kurs " Die Hardware ausgetrickst. . ."
(Teil 6)
Anhand der FLD-Routine des letzen Kursteils hatten wir gesehen, wie einfach
man die Hardware unseres kleinen Brotkastens austricksen kann, und sie dazu
bewegt Dinge zu tun, zu denen sie eigentlich nicht in der Lage ist. So soll
es auch in den folgenden Teilen des
IRQ-Kurses sein, jedoch müssen wir uns
zuvor um ein Problem kümmern, mit dessen
Lösung wir noch trickreichere Rastereffekte programmieren und den Copperund
Blittermagiern des Amigas das Fürchten
lehren werden. . .
1) AUF GUTES TIMING KOMMT ES AN. . .
Wie auch schon am Ende des letzten Teils angesprochen, ist der Schlüssel zu tollen Rasterinterrupts ein besonders exaktes Timing. Wie schon am Beispiel der
FLD-Routine unschwer erkennbar war, besteht das eigentliche " Austricksen" des
Video-Chips meist aus gerade einer handvoll Befehlen. Wichtig ist nur, daß diese Befehle zum richtigen Zeitpunkt ausgeführt werden. Wie wichtig das ist, werden wir später am Beispiel einer Rasterroutine sehen, die in der Lage ist, den linken und rechten Rand des Bildschirms abzuschalten. Wird sie auch nur
einen Taktzyklus zu früh oder zu spät
ausgeführt, so bewirkt sie absoult garnichts. Nur wenn zu einem ganz bestimmten Zeitpunkt der Wert eines Registers
verändert wird, funktioniert sie auch
wie sie soll! Damit wir solche Effekte
also auch realisieren können, werden wir
uns nun zwei Problemen widmen, die immer
noch Ungenauigkeitsfaktoren in unseren
Routinen darstellen und eliminiert werden müssen:
2) SYSTEMVEKTOREN SIND SCHNELLER
Der erste Ungenauigkeitsfaktor ist das
Betriebssystem des C64 . Wie Sie ja aus
den ersten Kursteilen wissen, holt sich
der 6510- Prozessor bei einer Interruptanfrage zunächst einmal eine der drei
Interrupt-Sprungadressen am Ende seines
Adressierungsbereichs ( von $ FFFA-$ FFFF) in den Programmzähler. Hier stehen die
jeweiligen Adressen der Betriebssystemsroutinen, die die entsprechende Art von
Interrupt ( IRQ, NMI oder Reset) bedienen. Gerade beim IRQ ist diese Routine
etwas aufwendiger aufgebaut, da derselbe
Vektor auch für softwaremäßige BRK-Interrupts benutzt wird, und die Routine
deshalb eine Unterscheidung treffen muß.
Dadurch stiehlt sie uns quasi Prozessorzeit, wenn wir davon ausgehen, daß der
BRK-Interrupt in der Regel nicht verwendet wird, da er ja einfacher durch ein
JMP programmiert werden kann. Erst nach der Unterscheidung verzweigt die Routine
dann über den Vektor $0314/$0315 auf die
eigentliche IRQ-Routine ( bzw. über
$0316/$0317 zur BRK-Routine) . Und erst
an dieser Stelle " klinken" wir unsere
eigenen Raster-IRQs in das Interruptsystem ein. Um nun die Verzögerung durch
das Betriebssystem zu eliminieren, müssen wir es umgehen. Es sollte eigentlich
also reichen, wenn wir die Startadresse
unserer Interruptroutine im Vektor
$ FFFE/$ FFFF eintragen, da er der IRQ-Vektor ist. Hierbei stellt sich uns jedoch ein weiteres Problem in den Weg:
diese Adressen gehören ja zum Betriebssystem- ROM und können nicht verändert werden, da sie für alle Zeiten in
dem ROM-Chip eingebrannt sind. Aber
nicht verzagen, denn " unter" diesem ROM
befindet sich auch noch echtes RAM, und
das können wir verändern. Damit der Prozessor sich dann den IRQ-Vektor auch von
dort holt, müssen wir das darüberliegende ROM sozusagen " ausblenden", was über das Prozessoradressregister geschieht, das in Speicherzelle 1 zu finden ist. Im
Normalfall steht hier der Wert 55($37), der das Basicund Betriebssystem-ROM in
den Adressbereichen $ A000-$ BFFF ( Basic), sowie $ E000-$ FFFF ( System) einblendet.
Führen wir Schreibzugriffe auf diese
Bereiche aus, so landen diese, selbst
bei eingeschaltetem ROM im darunterlegenden RAM. Das Problem dabei ist nur, daß wir dann die geschriebenen Werte
nicht auslesen können, da wir immer nur
den Inhalt der ROM-Adresse lesen können.
Um das zu ändern, muß der Wert 53($35) in das Prozessoradressregister geschrieben werden. Dadurch werden nämlich die
beiden ROM-Bausteine deaktiviert und das
darunterliegende RAM kommt zum Vorschein, worauf der Prozessor dann auch
Zugriff hat.
Ändern wir nun den IRQ-Vektor bei
$ FFFE/$ FFFF, und tritt dann ein IRQ auf, so wird direkt auf unsere IRQ-Routine verzweigt, ohne daß der Umweg über das
Betriebssystem gegangen wird. Beachten
Sie hierbei jedoch, daß durch das Abschalten des ROMs weder Basicnoch Betriebssystemsroutinen verfügbar sind, da
wir sie ja weggeschaltet haben. Benutzt
Ihr eigenes Programm solche Routinen, so
müssen Sie das ROM zuvor ins RAM kopieren. Dies tun Sie, indem Sie einfach bei
eingeschaltetem ROM eine Adresse auslesen und gleich wieder in sie zurückschreiben. Beim Lesen erhalten Sie dann
den Wert des ROMs, beim Schreiben schikken Sie ihn ins RAM darunter. Jetzt können Sie getrost das ROM abschalten, ohne
daß Ihr Rechner abstürzt. Als Beispiel
zum Kopieren der beiden ROMs ins darunterliegende RAM können Sie sich das Programm " COPYSYS" auf dieser MD anschauen, das wie all unsere Beispielprogramme
absolut ( mit ",8,1") geladen werden muß, und mit SYS4096( JMP $1000) gestartet
wird.
In unseren Beispielen werden wir allerdings keinerlei Betriebssystemsroutinen
verwenden, weswegen wir uns hier das
Kopieren einsparen. Wichtig ist dabei, daß wir die Interruptquellen von CIA-A
sperren, damit sie uns mit ihren IRQs
nicht zwischen die Raster-IRQs " funkt" .
3) DAS "GLÄTTEN" VON INTERRUPTS
Wenn Sie schon ein wenig mit Raster-IRQs
" herumgespielt" haben, so wird Ihnen
vielleicht schon einmal folgendes Problem aufgefallen sein: Möchten Sie einen
Interrupt programmieren, der z. B. einfach nur die Hintergrundfarbe ändert, so
passiert es manchmal, daß gerade an der
Stelle, an der die Farbe geändert wird, ein unruhiges Flackern zu sehen ist. Man
hat den Eindruck, als würde die Farbe
mal ein paar Pixel früher oder später
geändert werden, so daß an der selben
Stelle des Bildschirms manchmal die al- te, manchmal die neue Farbe zu sehen
ist. Irgendwie scheint es also nicht
möglich zu sein, den Interrupt immer zur
selben Zeit auftreten zu lassen - obwohl
die Interruptroutine immer einen konstanten Wert an Taktzyklen verbraucht, und deshalb der Flackereffekt gar nicht
auftreten dürfte!
Tatsächlich liegt die Ursache allen
Öbels nicht beim Interrupt, sondern am
Hauptprogramm: Tritt nämlich eine Interruptanforderung am Prozessor auf, so muß
dieser zunächst einmal den aktuell bearbeiteten Befehl zu Ende führen, bevor er
den Interruptvektor anspringen kann.
Angenommen, er wäre gerade dabei den
Befehl " LDA #$00" auszuführen. Dieser
Befehl benötigt 2 Taktzyklen. Einen zum
Lesen und Dekodieren des Befehlsbytes, und einen zum Lesen des Operanden und
Laden des Akkus. Hat der Prozessor nun
gerade das Befehlsbyte gelesen und tritt
in genau diesem Moment der Interrupt auf, so muß er zunächst noch den Operanden lesen, um anschließend in die IRQ-Routine verzweigen zu können. Selbige
wird dadurch aber erst einen Taktzyklus
später, als eigentlich erforderlich gewesen wäre, ausgeführt. Noch größer wird
die Verzögerung, wenn gerade z. B. ein
" STA $ D021"(4 Taktzyklen!) oder gar ein
" ROR $1000, X"(7 Taktzyklen!) ausgeführt
wurde.
Das Programm " FLACKER" auf dieser MD
veranschaulicht dieses Problem. Die Routine ist eine Kombination aus FLDund
Borderroutine. Mit dem FLD-Teil drücken
wir einen Teil des Bildschirms nach unten und stellen im Zwischenraum einen
Rasterbalken dar. Der Border-Teil schaltet einfach den oberen und unteren Bildschirmrand weg und dient mehr als Beispiel zur Kombination der beiden Rastereffekte. Der Rasterbalken ist nun auch
in der X-Richtung verschiedenfarbig, so
daß eine Zeile in der einen Hälfte eine Farbe und in der anderen Hälfte eine
zweite Farbe enthält. Dieser " Rastersplit" ist übrigens nur durch die FLD-Routine möglich, da diese ja verhindert, daß eine Charakterzeile gelesen wird, die den Prozessor für 42 Taktzyklen
( zwei Drittel der gesamten Zeile also) anhält. Ohne FLD könnte zum Beginn jeder
Charakterzeile die Farbänderung gar
nicht rechtzeitig ( nämlich nach der
Hälfte) stattfinden, da der Prozessor
zum erforderlichen Zeitpunkt ja immer
noch durch den VIC angehalten wäre. Außerdem hat die FLD-Routine den Vorteil, daß wir keinen Unterschied zwischen Charakter- und normalen Rasterzeilen machen
müssen, weshalb wir uns die verschachtelten Schleifen sparen. Sie starten das
Programm wie immer mit " SYS4096" und
verlassen es durch einen Druck auf den
Feuerknopf. Mit Joystickbewegungen nach
oben und unten können Sie übrigens die
Größe der FLD-Öffnung variieren.
Kommen wir nun zu unserem Problem
zurück: sieht man sich das Beispielprogramm einmal an, so wird man ein erhebliches Flackern bemerken. Das liegt
daran, daß das Programm zur Demonstration gerade in den zeitkritischen Momenten, besonders zeitintensive Befehle
ausführt, weswegen der IRQ mit bis zu 7 Taktzyklen Verzögerung auftreten kann.
Da nun aber für die weiteren Beispiele
dieses Kurses eine höhere Präzision erforderlich ist, müssen wir uns eine Methode angewöhnen, mit der wir einen Interrupt " glätten" können. Selbiges tut
nämlich das dritte Beispiel dieses Kursteils, mit dem Namen " LOESUNG" . Hier
das dokumentierte Listing:
;*** Initialiserung ($1000) Init: sei ;IRQ sperren lda #$7f ;Timer IRQ sta $dc0d ;abschalten bit $dc0d ;ICR löschen lda #$f8 ;Rasterzeile $f8 als sta $d012 ; IRQ-Auslöser festlegen lda $d011 ;Bit 7 löschen and #$7f sta $d011 lda #$01 ;Raster als IRQ sta $d01a ; wählen ldx #<Bord;Hard-IRQ-Vektoren ldy #>Bord; auf eigene stx $fffe ; Routine sty $ffff ; umstellen
lda #$33 ; Zeilenabstand für FLD sta $02 ; initialisieren lda #$00 ; VIC-Byte löschen sta $3 fff
lda #$35 ;ROM ausblenden sta $01 cli ;Interrupts erlauben
Hier haben wir den Initialisierungsteil
vor uns. Wie üblich sperren wir zunächst
die IRQs mittels SEI-Befehl, schalten
alle von CIA-A möglichen Interruptquellen ab, und löschen mit Hilfe des BIT-Befehsl eine evtl. noch gemeldete Interruptanfrage. Anschließend wird Rasterzeie $ F8 als Interruptauslöser festgelegt ( mit Löschen des Hi-Bits in $ D011) und dem VIC mitgeteilt, daß er Raster-IRQs erzeugen soll. Nun erst wird der
IRQ-Vektor ( wohlgemerkt der bei $ FFFE/$ FFFF und nicht der bei $0314/$0315) mit
der Adresse der Border-Routine gefüttert. Zum Schluß löschen wir dann noch
die letze VIC-Adresse, deren Inhalt ja
im abgeschalteten Border und dem FLD-Bereich angezeigt wird, und legen den
FLD-Zähler fest ( Speicherzelle $02), so
daß er $33( dez.51) Zeilen öffnet. Nun
erst wird das ROM durch Schreiben von
$35 in die Speicherzelle 1 ausgeblendet, damit der Prozessor bei einem IRQ auch unseren Vektor bei $ FFFE/$ FFFF anspringt, und die Interrupts werden wieder
erlaubt. Die Init-Routine kehrt nun
nicht wieder zur normalen Eingabe
zurück, da wir damit ja in eine Routine
des Basics zurückspringen würden, die
nach abschalten des ROMs nicht mehr vorhanden ist. Stattdessen folgt nun eine
Hauptschleife, mit der wir ständig den
Joystickknopf abfragen. Hierbei erfüllen
die ersten sieben Befehle eigentlich
keinen Zweck. Es sind nur besonders zeitintenive Befehle, die wir zur Öberprüfung, ob unser Glätten auch funktioniert, im Programm haben. Sie sind gefolgt von einer simplen Abfrage des
Feuerknopf-Bits von Joyport 2 :
;*** Hauptprogramm wfire inc $03 ;Dummy-Befehle, die inc $2000 ; absichtlich besonders ror $03 ; viel Rechenzeit ror $2000 ; verbrauchen. bit $03 ldx #$00 ror $2000,x
lda $ dc00 ; Joyport laden and #$10 ; Firebutton-Bit isol.
bne wfire ; Nicht gedr.-> weiter
Wird der Feuerknopf nun gedrückt, so
müssen wir die ROMs wieder einschalten, dem VIC die IRQs verbieten und sie der
CIA wieder erlauben, um das Programm
verlassen zu können. Dies tut folgende
Endroutine:
sei ;IRQs sperren lda #$37 ;ROMs einschalten sta $01 lda #$f0 ;VIC-IRQs sperren sta $d01a dec $d019 ;ggf.VIC-IRQ-Anf.lösch. lda #$1b ;Normaler Darstellungs- sta $d011 ; modus (wg. Border) lda #$81 ;CIA-A darf Timer-IRQs sta $dc0d ; auslösen bit $dc0d ;ggf.CIA-IRQ-Anf.lösch. cli ;IRQs freigeben rts ;und ENDE
Kommen wir nun zur Borderroutine. Sie
ist die erste IRQ-Routine, die nach der
Initialisierung ( bei Rasterzeile $ F8) aufgerufen wird:
Bord pha ;Akku, X- u. Y-Reg. txa ; auf Stapel retten pha tya pha lda #$10 ;24-Zeilen-Darst. an sta $d011 ; (=Bordereffekt) lda #$3d ;nächsten IRQ bei 2. sta $d012 ; Charakterzeile ausl. dec $d019 ;VIC-ICR löschen ldx #<FLD1;IRQ-Vektoren auf ldy #>FLD1; erste stx $fffe ; FLD-Routine sty $ffff ; verbiegen
jsr JoyCk ; Joystickabfrage
pla ;Akku, X- u. Y-Reg. tay ; wieder vom Stapel pla ; holen tax pla rti ;IRQ beenden.
Die Routine macht eigentlich nichts weiter, als die 24- Zeilen-Darstellung zu
aktivieren, die in Rasterzeile $ F8 ja
das Abschalten des Borders bewirkt, die
Rasterzeile des nächsten IRQs festzulegen ($3 D= Startposition der 2 . Charakterzeile-2), den Interrupt-Vektor auf die
Routine ( FLD) für diese Zeile zu verbiegen, und den Joystick abzufragen ( Unterroutine, die hier nicht aufgeführt ist) .
Beachten Sie, daß wir hier die Prozessorregister mit Hilfe der Transferund
Stapelbefehle von Hand retten und wiederherstellen müssen. Gerade das Retten
war nämlich eine Aufgabe, die uns das Betriebssystem freundlicherweise schon
abgenommen hatte. Da es jetzt ja abgeschaltet ist, müssen wir uns natürlich
selbst darum kümmern.
(Anm. d. Red.: Bitte wählen Sie jetzt den 2. Teil des Kurses aus dem MD-Menu!)
Interruptkurs (Teil6 - 2.Hälfte)
Kommen wir nun zur ersten FLD-Routine.
In ihr wird der Interrupt geglättet, was
eine besonders trickreiche Angelegenheit
ist. Sehen Sie sich hierzu einmal den
Sourcecode an:
;*** FLD-Routine mit Glättung ($1100) FLD1 pha ;Akku, X- u. Y-Reg. txa ; retten pha tya pha dec $d019 ;neue IRQs erlauben inc $d012 ;nächte Raster.=Ausl. lda #<FLD2 ;Lo-Byte von FLD-IRQ2- sta $fffe ;Routine setzen cli ;IRQs erlauben WIRQ nop ;13 NOPs nop ; (innerhalb dieser nop ; Schleife wird der nop ; Interrupt ausge- nop ; löst werden!!) nop nop nop nop nop nop nop nop jmp QIRQ FLD2 pla ;Programmzähler und pla ; Statusreg. gleich pla ; wieder v. Stack holen nop ;19 NOPs zum Verzögern nop ; bis zum tatsächlichen nop ; Charakterzeilen- nop ; anfang nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop lda $d012 ;Den letzten Zyklus cmp $d012 ; korrigieren bne cycle cycle ... ;eigentlicher IRQ
Hier werden Ihnen einige Dinge etwas
merkwürdig vorkommen ( vor allem die vielen NOPs) . Beginnen wir von Anfang an:
In der Borderroutine hatten wir die Rasterzeile festgelegt, in der die FLD1- Routine angesprungen werden soll. Dies
war Zeile 61($3 D), die genau zwei Rasterzeilen vor der eigentlichen IRQ- Rasterzeile liegt. In diesen zwei Zeilen, die wir den IRQ früher ausgelöst
haben, werden wir ihn jetzt glätten. Wie
in jedem Interrupt retten wir zunächst
die Prozessorregister. Daran anschließend wird das Low-Byte der Routine
" FLD2" in das Low-Byte des IRQ-Vektors
geschrieben, und die nächste Rasterzeile
( durch den INC-Befehl) als nächster Interruptauslöser festgelegt. Beachten Sie
hierbei, daß die diese Routine dasselbe
High-Byte in der Adresse haben muß, wie
die erste FLD-Routine. Das kann man dadurch erzielen, daß " FLD1" an einer
Adresse mit 0- Lowbyte ablegt wird und
sofort danach die Routine " FLD2" folgt
( im Beispiel ist FLD1 an Adresse $1100) .
Um einen neuen Interrupt zu ermöglichen, müssen noch das ICR des VIC und das Interrupt- Flag des Prozessors gelöscht
werden ( beachten Sie, daß letzteres vom
Prozessor automatisch bei Auftreten des
IRQs gesetzt wurde) .
Es folgt nun der eigentliche " Glät- tungsteil" . Hierzu lassen wir den Prozessor ständig durch eine Endlos-Schleife mit NOP-Befehlen laufen. Dadurch wird sichergestellt, daß der Raster- IRQ der nächsten Rasterzeile in
jedem Fall während der Ausführung eines
NOP-Befehls auftritt. Da dieser Befehl
nur 2 Taktzyklen verbraucht, kann die
Verzögerung des Interupts nur 0 oder 1 Taktzyklen lang sein. Diesen einen Zyklus zu korrigieren ist nun die Aufgabe
des zweiten FLD-IRQs. Nachdem er angesprungen wurde holen wir gleich wieder
die, vom Prozessor automatisch gerettete, Programmzähleradresse und das Statusregister vom Stapel, da sie nur zum
ersten FLD-Interrupt gehören, in den
nicht mehr zurückgekehrt werden soll.
Danach folgen 19 NOP-Befehle, die nur
der Verzögerung dienen, um das Ende der
Rasterzeile zu erreichen. Die letzten
drei Befehle sind die Trickreichsten!
Sie korrigieren den einen möglichen
Verzögerungs-Zyklus. Zum besseren Verständnis sind sie hier nochmal aufgelistet:
lda $d012 ;Den letzten Zyklus cmp $d012 ; korrigieren bne cycle cycle ... ;eigentlicher IRQ
Obwohl diese Folge recht unsinnig erscheint, hat sie es ganz schön in sich:
Wir laden hier zunächst den Akku mit dem
Inhalt von Register $ D012, das die Nummer der aktuell bearbeiteten Rasterzeile
beinhaltet, und vergleichen ihn sofort
wieder mit diesem Register. Danach wird
mit Hilfe des BNE-Befehls auf die Folgeadresse verzweigt, was noch unsinniger
erscheint.
Der LDA-Befehl befindet sich nun durch
die NOP-Verzögerung genau an der Kippe
zur nächsten Rasterzeile, nämlich einen
Taktzyklus bevor diese Zeile beginnt.
Sollte nun der FLD2- IRQ ohne den einen
Taktzyklus Zeitverzögerung ausgeführt worden sein, so enthält der Akku z. B.
den Wert 100 . Der CMP-Befehl ist dann
ein Vergleich mit dem Wert 101, da der
Rasterstahl nach dem LDA-Befehl schon in
die nächste Zeile gesprungen ist. Dadurch sind die beiden Werte ungleich, womit das Zero-Flag gelöscht ist, und
der Branch tatsächlich ausgeführt wird.
Beachten Sie nun, daß ein Branch-Befehl
bei zutreffender Bedingung durch den
Sprung einen Taktzyklus mehr Zeit verbraucht, als bei nicht zutreffender Bedingung (3 Zyklen!) . War der FLD2- IRQ
allerdings mit dem einem Taktzyklus
Verzögerung aufgetreten, so wird der
LDA-Befehl genau dann ausgeführt, wenn
Register $ D012 schon die Nummer der
nächsten Rasterzeile enthält, womit der
Akku den Wert 101 beinhaltet. Durch den
Vergleich mit dem Register, das dann
immer noch den Wert 101 enthält, wird
das Zero-Flag gesetzt, da die beiden
Werte identisch sind. Dadurch trifft die
Bedingung des BNE-Befehls nicht zu, und er verbraucht nur 2 Taktzyklen! Dies
gewährleistet, daß in beiden Fällen immer die gleiche Zyklenzahl verbraucht
wird! War der FLD2- IRQ ohne Verzögerung, so verbracht die Routine einen Zyklus
mehr, als wenn er mit einem Zyklus
Verspätung auftrat!
Hiermit hätten wir den IRQ also geglättet und können die eigentliche FLDund
Farbsetz-Routine ausführen. Beachten Sie
für Folgebeispiele, daß wir in Zukunft
auf diese Weise die IRQs immer glätten
werden müssen, um saubere Ergebnisse zu
erzielen. Hierzu wird immer wieder diese
Routine verwandt, wobei das eigentliche
IRQ-Programm dann nach dem Branch-Befehl
eingesetzt wird. Gleichmäßiger kann man
Raster-IRQ nun wirklich nicht mehr
ausführen!
Nach dem Glätten folgt die eigentliche
Interruptroutine, und zwar direkt nach
dem Label " Cycle" . Sie setzt Rasterzeile
$ F8 als Interruptauslöser fest und ver- biegt den IRQ-Vektor wieder auf die Borderroutine, womit der Kreislauf von
Neuem beginnt. Gleichzeitig setzt Sie
die Darstellung auf 25 Zeilen zurück, damit der Bordereffekt auch funktioniert. Anschließend wird der FLD-Effekt
durchgeführt, indem der Zeilenanfang vor
dem Rasterstrahl hergeschoben wird. Währenddessen werden die Vorderund Hintergrundfarbe nach zwei Farbtabellen bei
$1200 und $1300 verändert. Der Rastersplit wird durch ausreichende Verzögerung bis zur Mitte einer Rastezeile erzeugt. Zum Schluß des IRQs wird noch bis
zum Ende der Rasterzeile verzögert, und
die Standardfarben zurückgesetzt, bevor
die ursprünglichen Inhalte der Prozessorregister, wie sie vor dem Auftreten
des FLD1- IRQs vorhanden waren, zurückgeholt werden und der IRQ beendet wird:
Cycle dec $ d019 ; VIC-ICR löschen lda #$18 ;25- Zeilen-Darst. einsta $ d011 ; schlaten ( Bordereff.)
lda #$f8 ;Rasterz. $F8 ist näch- sta $d012 ; ster IRQ-Auslöser ldx #<Bord ;IRQ-Vektor ldy #>Bord ; auf stx $fffe ; Border-Routine sty $ffff ; verbiegen nop ;Verzögern lda $02 ;FLD-Zähler laden beq FDEnd ; Wenn 0, kein FLD! ldx #$00 ;Zählreg.f.Farben init. clc FDLop lda $d012 ;FLD-Sequenz (Zeilen- adc #$02 ; anfang vor Raster- and #$07 ; strahl herschieben ora #$18 sta $d011 lda $1200,x;Farbe links holen sta $d020 ; und setzen sta $d021 nop ;Verzögern bis Mitte nop nop nop nop nop nop lda $1300,x;Farbe rechts holen sta $d020 ; und setzen sta $d021 bit $ea ;Verzögern inx ;Farb-Zähler+1 cpx $02 ;Mit FLD-Zähler vgl. bcc FDLop ;ungl., also weiter FDEnd nop ;Verz. bis Zeilenende nop nop nop nop lda #$0e ;Vorder-/Hintergrund- sta $d020 ; farben auf lda #$06 ; hellblau u. dunkel- sta $d021 ; blau setzen pla ;Akku, X- und Y-Reg. tay ; zurückholen pla tax pla rti ;IRQ beenden
Das war es dann wieder für diesen Monat.
Im nächsten Kursteil werden wir eine
weitere Anwendung besprechen, die eine
IRQ-Glättung benötigt: die Sideborderroutinen zum Abschalten des linken und
rechten Bildschirmrands, nämlich.
(ub)
Interrupt-Kurs "Die Hardware ausgetrickst..." (Teil 7)
Nachdem wir uns im letzten Kursteil ausgiebig um das IRQ-Timing gekümmert hatten, wollen wir in dieser Ausgabe nun
eine Anwendung besprechen, bei der vor
allem das Glätten von Interrupts eine
große Bedeutung einnimmt: es geht um die
Sideborderroutinen.
1) DAS PRINZIP Im dritten Kursteil hatten wir ja schon
gelernt, wie man den oberen und unteren
Bildschirmrand abschaltet. Wir hatten
dem VIC hierzu zunächst mitgeteilt, daß
er einen 25- Zeilen hohen Bildschirm darstellen soll. Zwei Rasterzeilen bevor er
jedoch den Rand desselben erreichte
schalteten wir ihn auf 24- Zeilen-Darstellung um, weswegen er glaubte, schon zwei Rasterzeilen vorher mit dem Zeichnen des Randes begonnen zu haben.
Da dies aber nicht der Fall war, und der
VIC seine Arbeit normal fortführte," vergaß" er sozusagen, den unteren und
den oberen Bildschirmrand zu zeichnen, was uns ermöglichte, in diesen Bereichen
Sprites darzustellen. Derselbe Trick
funktioniert nun auch mit dem linken und
rechten Bildschirmrand - zumindest vom
Prinzip her. Bit 3 in VIC-Register $ D016 steuert die Breite des sichtbaren Bildschirms. Ist dieses Bit gesetzt, so
zeichnet der VIC 320 sichtbare Pixel in
der Vertikalen, was einer Darstellung
von 40 Zeichen pro Zeile entspricht.
Löschen wir dieses Bit, so stellt er nur
302 Pixel, bzw.38 Zeichen pro Zeile
dar. Dieses Bit wird vor allem zum vertikalen Scrollen verwendet, da man bei
einem 38- Spalten-Bildschirm neu hereinlaufende Bildschirmzeichen setzen kann, ohne daß sie vom Betrachter gesehen werden.
Durch das rechtzeitige Setzen und Lö- schen dieses Bits kann man nun auch den
linken und rechten Bildschirmrand abschalten. Hierbei liegt die Betonung
besonders auf dem Wort " rechtzeitig" . Da
der Rasterstrahl in der vertikalen nämlich derart schnell ist, daß der Prozessor kaum nachkommt, müssen wir den Zeitpunkt der Umschaltung sehr genau abpassen, damit unser Effekt funktioniert.
Bei der 38- Zeichen-Darstellung beginnt
der linke Bildschirmrand nur 8 Pixel
später und endet nur 8 Pixel früher als
sonst. Da aber genau dann, wenn er Endet, umgeschaltet werden muß, und der
Rasterstrahl zum Zeichnen von 8 Pixeln
gerade mal 1 .2 Taktzyklen benötigt, haben wir eben nur genau diese Zeit zur
Verfügung, um Bit 3 in Register $ D016 zu
löschen. Da ein " STA $ D016" aber drei
Taktzyklen verbraucht, muß der Prozessor
diesen Befehl also schon zwei Zyklen vor
dem Bildschirmrand erreichen und beginnen abzuarbeiten. Der eigentliche
Schreibzugriff findet dann im dritten Taktzyklus, genau zwischen 38- und 40- Zeichen-Rand statt. Zur Verdeutlichung
sollten Sie sich einmal das Programmbeispiel " SIDEBORDER.0" anschauen. Wie alle
unsere Beispiele ist es absolut ( also
mit ",8,1") zu laden und mit SYS4096 zu
starten. Sie verlassen die Routine mit
einem Druck auf den Feuerknopf eines
Joysticks in Port2 . Mit Joystickbewegungen nach oben und unten können Sie die
Anzahl der zu öffnenden Zeilen variieren.
Wir möchten Ihnen nun die Kernroutine
des Beispiels zeigen. Die Erklärung der
Interruptinitialisierung werden wir uns
an dieser Stelle sparen, da wir sie ja
schon oft genug besprochen haben. Im
Beispiel selbst haben wir alle Interruptquellen, außer den Interrupts vom
Rasterstrahl gesperrt. Desweiteren wurde
das Betriebssystems-ROM abgeschaltet, und die Adresse auf unsere Routine
gleich in den Vektor bei $ FFFF/$ FFFE
geschrieben, so daß der Prozessor ohne Zeitverzögerung zu unserer Routine
springt. Selbige " glättet" den Interrupt
zunächst nach der im letzten Kursteil
beschriebenen Art und Weise. Direkt nach
der Glättung folgt nun diese Routine:
... LDX #00 ;Zeilenzähler löschen CLC LOOP NOP ;7 NOPs Verzögerung NOP ; bis Zeilenende NOP NOP NOP NOP NOP LDA #00 ;EINEN Taktzykl. vor STA $D016 ;Beg. d.rechten Randes LDA #08 ;Bit 3 löschen und STA $D016 ;gleich wieder setzen NOP ;13 weitere NOPs zum NOP ; Verzögern von 26 NOP ; Taktzyklen NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP BIT $EA ;3 Zyklen verzögern INX ;Linienzähler+1 CPX $02 ;Mit Anz.zu öffnender BCC LOOP ;Zeilen vgl. u. weiter
. . .
Diese Abfolge von Befehlen beinhaltet
nun haargenau das benötigte Timing, damit der Sideborder-Effekt auch sichtbar
wird. Sie sehen hier zunächst 7 NOPs, mit denen wir vom Ende der Rasterglättung bis zum Beginn des sichbaren rechten Bildschirmrandes verzögern. Hieraufhin wird Bit 3 von Register $ D016 gelöscht, um auf 38- Zeilen-Darstellung
umzuschalten, und gleich darauf wieder gesetzt, damit das Umschalten in der
nächsten Rasterzeile noch genauso effektiv ist. Anschließend wird mit den 13 NOPs und dem BIT-Befehl um insgesamt 29 Taktzyklen verzögert, und geprüft, ob
das X-Register, das als Zähler der schon
geöffneten Rasterzeilen fungiert, den
Wert der zu öffnenden Zeilen enthält, der in Speicherzelle $02 abgelegt ist.
Wenn nicht, so wird wieder zum Anfang
der Schleife verzweigt und somit eine
weitere Rasterzeile geöffnet.
2) EINE VERFEINERUNG Wenn Sie das Beispiel einmal gestartet
und angesehen haben, so werden Sie bemerken, daß wir lediglich im oberen, sonst mit dem Rahmen verdeckten Bereich
des Bildschirms den seitlichen Rand
geöffnet haben. Das funktioniert natürlich nur, wenn wir den VIC zuvor mit dem
normalen Bordereffekt überlistet haben, so daß er den oberen und unteren Bildschirmrand ebenfalls wegfallen lässt.
Desweiteren kann der Rand dort nur bis
zur letzten Zeile, des ( unsichtbaren) oberen Bildschirmrandes geöffnet werden, was einen besonderen Grund hat: Wie wir
mittlerweile ja wissen, liest der VIC
zum Beginn einer jeden Charakterzeile
( also jede achte Rasterzeile), die 40 in
dieser Zeile darzustellenden Zeichen
ein, und blockiert während dieser Zeit
den Prozessor für eine Zeitspanne von 42 Taktzyklen. Dadurch gerät natürlich unser ganzes Timing durcheinander, weshalb
der Effekt an diesen Stellen nicht mehr
zu sehen ist ( obwohl immer noch dieselbe
Schleife läuft!) . Um nun innerhalb des
normalen Bildschirms, in dem Zeichen
dargestellt werden können, den Rand zu
öffnen, verkompliziert sich das Timing
natürlich erheblich, da wir jede achte
Rasterzeile 42 Taktzyklen weniger zur
Verfügung haben. Die einfachste Methode, dieses Problem zu umgehen, ist die Kombination der Sideborderroutine mit einer
FLD-Routine. Dadurch schieben wir ja den Beginn der nächsten Charakterzeile vor
dem VIC her, so daß er keine Daten liest
und uns somit nicht bei der Arbeit
stört. Diesen Weg geht unser zweites
Programmbeispiel mit dem Namen " SIDEBOR-DER.1" . Hier das Listing der entscheidenden Schleife. Auch hier wurde der
Interrupt natürlich zuvor geglättet:
... LOOP LDA $D012 ;FLD-Routine ADC #$02 AND #$07 ORA #$18 STA $D011 LDA #$00 ;wieder 1 Zykl. vor Beg. STA $D016 ;d. rechten Randes Bit 3 LDA #$08 ;von $D016 löschen und STA $D016 ;gleich wieder setzen NOP ;13 weitere NOPs zum NOP ; Verzögern von 26 NOP ; Taktzyklen NOP NOP NOP NOP NOP NOP NOP NOP NOP NOP BIT $EA ;3 Zyklen verzögern INX ;Linienzähler+1 CPX $02 ;Mit Anz. zu öffnender BCC LOOP ;Zeilen vgl. u. weiter
. . .
Im Prinzip hat sich hier nicht viel
geändert. Nur daß die 7 NOPs am Anfang
der Routine den fünf Befehlen des FLD-Effektes gewichen sind. Diese brauchen
ebenso 14 Taktzyklen, so daß unsere Routine vom Timing her immer noch genauso
funktioniert, wobei wir den Bildschirmrand jedoch innerhalb des normalen Bildbereichs abgeschaltet haben!
3) SPRITES IM SIDEBORDER
Wie bei den Bildschirmrändern oben und
unten kann man natürlich auch in den
seitlichen, abgeschalteten Rändern Sprites darstellen. Dies gestaltet sich jedoch ebenfalls schwieriger als sonst.
Nämlich so wie der VIC den Prozessor in
jeder Charakterzeile anhält, um ungestört die Charakterdaten zu lesen, so
hält er ihn auch an, wenn er Spritedaten
zu lesen hat. Hierbei prüft der VIC
zunächst einmal, ob ein Sprite an der
aktuellen Rasterposition überhaupt
sichtbar ist. Wenn ja, so liest er die
drei Datenbytes des Sprites, die in dieser Rasterzeile anzuzeigen sind. Hierzu
benötigt er ungefähr 2 .4 Taktzyklen, die
uns von der Prozessorzeit natürlich wieder abgehen! Somit muß die Verzögerung
innerhalb unserer Schleife verkürzt werden. Da wir aber keine Bruchteile von
Taktzyklen verzögern können, wird die
ganze Sache umso haariger! ! ! In der Regel hilft hier nur die alte " Trialand- Error"- Methode, um das perfekte Timing genau auszuloten. Zum Einschalten von
sieben Sprites müssen wir nach dem Löschen und Setzen des Anzeigebits von
Register $ D016 nur noch 12 Takte, anstelle von ursprünglich 29, warten, bis
der Rasterstrahl die nächste Zeile erreicht. Es gehen uns also 17 Zyklen verloren! Programmbeispiel " SIDEBORDER.2" benutzt anstelle der 13 NOPs und dem
BIT-Befehl nur noch 6 NOPs. Sehen Sie
sich dieses Beispiel ruhig einmal mit
einem Disassembler oder Speichermonitor
an. Wenn Sie durch Herunterdrücken des
Joysticks den geöffneten Bereich vergrößern, so werden Sie merken, daß der Sideborder- Effekt nur in den Rasterzeilen
funktioniert, in denen die Sprites
sichtbar sind. Das gilt auch dann, wenn
Sie die Sprites in Y-Richtung expandieren ( vor dem Aufruf des Beispiels PO-KE53271,255 eingeben) . Hier funktioniert
der Effekt dann doppelt soviele Zeilen
lang, da der VIC bei Expansion jede Zeile eines Sprites einfach doppelt liest( auf zwei Rasterzeilen verteilt) . Möchten Sie den Rand weiter nach unten öffnen, so ist nur noch ein komisches Linien- Wirr-Warr zu sehen, da in diesen
Zeilen keine Sprites angezeigt werden, und somit wieder eine Verzögerung von 29 Taktzyklen von Nöten wäre! Wohlgemerkt
funktioniert dieser Effekt nur, wenn
auch wirklich in jeder Rasterzeile sieben Sprites darzustellen sind. Schalten
Sie weniger oder mehr Sprites ein, so
ist der Sidebordereffekt zunichte gemacht. Das gleiche passiert, wenn Sie
nicht alle sieben Sprites Yexpandieren.
Hier Endet der Effekt nach 21 Zeilen, nämlich dann wenn ein nichtexpandiertes
Sprite zu Ende gezeichnet ist, und Sie
sehen im Border nur die Hälfte der expandierten Sprites!
4) HINWEISE ZUM OPTIMALEN TIMING Möchten Sie nun selbst die Verzögerung
ausloten, die zur Darstellung einer bestimmten Anzahl Sprites benötigt wird,
so beachten Sie folgende Regeln:
* Geben Sie allen Sprites, die in Bereichen angezeigt werden, in denen der
Sideborder abgeschaltet ist, ein und
dieselbe Y-Koordinate, so daß sie horizontal in einer Linie liegen. Dadurch werden Ungleichheiten innerhalb
der Rasterzeilen vermieden, so daß
immer gleich viele Spritedaten gelesen
werden. Gleiches gilt für die Y-Expandierung. Es sollten entweder alle
oder keines der, in einem solchen Bereich sichtbaren, Sprites expandiert
sein ( es sei denn Sie möchten nur die
Hälfte eines expandierten Sprites sehen) .
* Achten Sie darauf, daß andere Sprites
nicht in den Bereich ohne Sideborder
hineinragen, da auch sie das Timing
durcheinanderbringen.
* Beim Ausloten der Verzögerung sind
NOPund BIT-Befehle am Besten zum
schrittweisen Timing-Test geeignet.
Möchten Sie eine ungerade Anzahl Zy- klen verzögern, so benutzen Sie einen
Zeropage-BIT- Befehl ( so wie der oben
benutzte " BIT $ EA"), da er drei Zyklen
verbraucht. Bei einer geraden Anzahl
Zyklen reicht eine entsprechende Anzahl NOPs, die jeweils nur 2 Zyklen
benötigen. So brauchen Sie zur Verzögerung von z. B.7 Zyklen 2 NOPs und
einen BIT-Befehl. Bei 8 Zyklen sind 4 NOPs ohne BIT-Befehl ausreichend, usw.
* Schätzen Sie in etwa ab, wieviele
Taktzyklen Sie bei einer bestimmten
Anzahl Sprites etwa benötigen, um zu
verzögern ( Formel:29- AnzSprites*2 .4) und testen Sie ob dieser Wert funktioniert. Wenn nicht ändern Sie in 1- Zyklus-Schritten nach oben oder unten.
* Natürlich wird der Prozessor immer nur
eine gerade Anzahl Zyklen angehalten.
Der Richtwert von 2 .4 Zyklen pro Sprite ist lediglich zur Orientierung gedacht. Da der Prozessor immer nur zu
einem Taktsignal eine Operation beginnen kann, sollte die Verzögerung also immer zu finden sein.
* Zum Testen, wann Register $ D016 geändert wird, sollten Sie die Zugriffe
auf dieses Register zu Testzwecken mit
Zugriffen auf Register $ D021 ersetzen.
Sie erzielen dadurch eine Farbänderung
des Hintergrunds, und zwar genau zu
dem Zeitpunkt, zu dem normalerweise
auch Register $ D016 verändert wird.
Sehen Sie einen schwarzen Strich am
Bildschirmrand, der länger als 8 Pixel
ist, so ist der Zugriff zu früh. Sehen
Sie gar keinen Strich, so war er zu
spät. Wenn Sie die richtige Einstellung gefunden haben können Sie $ D021 wieder mit $ D016 ersetzen, und der
Sideborder-Effekt sollte funktioieren.
5) NOCH TRICKREICHERE PROGRAMMIERUNG Zum Abschluß möchte ich Ihnen noch ein
viertes Programmbeispiel erläutern, nämlich eine Sideborderroutine, die acht
Sprites im Border darstellt. Das verwunderliche an ihr ist die Funktionsweise.
Denn obwohl die Sideborderschleife genauso viele Taktzyklen verbraucht wie
die Version für sieben Sprites, kann
dennoch ein Sprite mehr dargestellt werden. Ermöglicht wird dies durch eine
trickreichere Variante des Löschen und
Setzen des Bildschirmbreite-Bits aus
Register $ D016 . Wir initialisieren dieses Register vor der eigentlichen Sideborderroutine nämlich mit dem Wert $ C8, was dem Standardwert dieses Registers
entspricht. In der eigentlichen Routine
wird dann Bit 3 gelöscht, indem wir das
ganze Register mittels " DEC $ D016" um
eins herunterzählen ( auf $ C7- bin.
%11000111- Bit 3 gelöscht) . Ein direkt
folgendes " INC $ D016" setzt das Bit dann
wieder. Hier das Listing der Kernroutine
des Beispiels " SIDEBORDER.3" :
... LOOP LDA $D012 ;FLD-Routine ADC #$02 AND #$07 ORA #$18 STA $D011 DEC $D016 ;Bit 3 löschen und INC $D016 ;gleich wieder setzen NOP ;Wie bei "SIDEBORDER.2" NOP ; stehen hier trotzdem NOP ; nur 6 NOPs!!! NOP NOP NOP INX ;Linienzähler+1 CPX $02 ;Mit Anz. zu öffnender BCC LOOP ;Zeilen vgl. u. weiter
. . .
Der Grund, warum die Routine dennoch
funktioniert ist, daß der Prozessor zur
Abarbeitung des DEC-Befehls sechs
Taktzyklen verbraucht, wobei der
Schreibzugriff mit dem dekrementierten
Wert durch den internen Aufbau des Befehls schon früher eintritt. Alles in
Allem ist das Funktionieren dieser Methode umso wunderlicher, da die beiden
Befehle DEC und INC, ebenso wie die bei- den LDAs und STAs vorher,12 Taktyklen
benötigen. Wir tauchen hier also schon
in die tiefere Bereiche der Assemblerprogrammierung ein, da es an der Funktionsweise des DEC-Befehls liegt, warum
das Bit rechtzeitig gelöscht wird.
Beachten Sie daher bitte auch diese Variation der Sideborder-Routine für eigene Zwecke und benutzen Sie sie, wenn
normales Ausloten nicht ausreicht.
6) EIN LETZTES BEISPIEL Als zusätzlichen Leckerbissen haben wir Ihnen noch ein weiteres Beispielprogramm
auf dieser MD untergebracht:" SIDEBOR-DER.4" basiert auf dem selben Prinzip
wie " SIDEBORDER.3", nur daß die Sprites
zusätzlich Xexpandiert wurden und somit
eine lückenlose Laufschrift durch den
Bildschirmrand dargestellt werden kann.
Die Zeichendaten werden dabei direkt aus
dem Zeichensatz-ROM geholt, und mit Hilfe des ROL-Befehls bitweise durch die
Sprites rotiert. Wie so etwas funktio- niert wissen Sie bestimmt, weshalb ich
es hier nicht noch eimal extra erläutere, zumal es ja eigentich nicht zu unserem Thema gehört.
Öbrigens: vielleicht ist Ihnen schon
aufgefallen, daß beim Üffnen des Sideborders der rechte Bildschirmrand immer
eine Rasterzeile höher geöffnet ist, als
der Linke. Und das dieser umgekehrt eine
Rasterzeile länger offen ist, als der
Rechte. Das liegt daran, daß das Abschalten des Randes zwar am rechten Ende
einer Rasterzeile geschieht, sich jedoch
auf das linke Ende der folgenden Rasterzeile auswirkt!
In der nächsten Folge dieses Kurses werden wir Ihnen zeigen, wie man den Border
OHNE wegdrücken des Bildschirms durch
FLD abschaltet ( besonders haarige Timingprobleme) . Desweiteren bleiben wir
dann beim Thema " Sprites", und wollen
uns anschauen, wie man den VIC derart
austrickst, daß er mehr als acht dieser
kleinen Grafikstückchen auf den Bild- schirm zaubert. Bis dahin sollten Sie
sich die in der heutigen Folge besprochenen Beispiele nocheinmal genauer anschauen, und ein wenig damit herumexperimentieren. Sie ahnen nicht wie vielseitig man mit dem abgeschalteten Border
arbeiten kann. . .
(ub/ih)
IRQ-KURS "Die Hardware ausgetrickst..." (Teil 8)
Herzlich willkommen zum achten Teil unseres Raster-IRQ- Kurses. Heute wollen
wir uns mit einem besonders interessanten Rastertrick beschäftigen: der FLI-Routine, die es uns ermöglicht, Grafiken
mit mehr als 2 Farben im Hires-Modus, bzw. mehr als 4 Farben im Multicolor-Modus, jeweils pro 8 x8- Pixel-Block, darzustellen. Durch die dabei entstehende
Farbvielfalt können sehr eindrucksvolle
Bilder angezeigt werden, die bei geschickten Grafikern schon an die Qualität von Amiga-Bildern reichen können!
1) GRUNDLAGEN Wollen wir uns zunächst einmal anschauen, wie der VIC vorgeht, um eine Bitmap-Grafik auf den Bildschirm zu zaubern:
Zunächst einmal muß der Grafikmodus eingeschaltet werden, wobei die Lage der
Bitmap im Speicher mitangegeben wird.
Nun zeigt der VIC also die Bits des angegegebenen Speicherbereichs als einzelne Pixel auf dem Bildschirm an. Um diesen Pixeln nun auch noch Farbe zu verleihen, muß in zwei Fälle unterschieden
werden:
a) DER HIRES-MODUS Hier bestimmt ein Farbcode im Video-RAM, das im Textmodus zur Darstellung der
Zeichen benutzt wird und sich normalerweise bei $0400( dez.1024) befindet, die Farbe der Pixel innerhalb eines
8 x8- Pixel-Blocks. So legt das erste Byte
des Video-RAMs ( als Zeichen ganz links
oben), die Farbe für die 8 x8, im Grafikmodus quasi " über" ihm liegenden, Pixel
fest. Das zweite Byte ist für den nächsten 8 x8- Pixel-Block zuständig, und so weiter. Da der VIC insgesamt nur 16 Farben kennt, sind dabei jeweils nur die
unteren vier Bits eines Video-RAM- Bytes
von Bedeutung. Die oberen vier sind unbenutzt und werden ignoriert.
b) DER MULTICOLOR-MODUS In diesem Modus werden zwei nebeneinander liegende Pixel jeweils zu einem
Farbcode zusammengefasst. Sind beide 0, so erscheint an ihrer Stelle die Hintergrundfarbe, sind beide auf 1, so wird
für beide die Farbe aus einem äquvalenten Byte des Color-RAMs geholt ( bei
Adresse $ D800- dez.55296), das normalerweise zur Farbgebung der einzelnen
Zeichen im Textmodus verwendet wird. Bei
der Bitkombination "10" wird wieder das
Lownibble ( die unteren vier Bits) des
Video-RAMs zur Farbgebung ausgewertet.
Bei der Bitkombination "10" sind die, im
Hires-Modus unbenutzten, oberen vier
Bits des Video-RAMs für die Pixelfarbe
zuständig. Sie sehen also, daß das Vi- deo-RAM im Multicolormodus gleich zwei
Farbwerte für Pixelkombinationen festlegt.
2) DAS FLI-PRINZIP Soviel zur Darstellung einer Grafik.
Sicherlich ist Ihnen bei den obigen
Ausführungen das häufige Auftreten des
Begriffs " Video-RAM", bzw." Video-Map" aufgefallen. Und genau dieser Begriff
ist der Schlüssel zu unserer FLI-Routine. Wie Sie vielleicht wissen, kann
man die Lage der Video-Map, innerhalb
des Adressierungsbereichs des VICs ( im
Normalfall von $0000-$3 FFF) in 1 KB-Schritten verschieben. Hierfür ist Register 24 des VICs ( Adresse $ D018- dez.
53272) zuständig. Seine oberen 4 Bits
geben die Lage der Video-Map an, also
des RAM-Bereichs, der für die Darstellung der Textzeichen, bzw. im Grafikmodus der Farbzeichen, zuständig ist. Die
unteren Bits von 0-3 bestimmen die Lage des Zeichengenerators, also des Speicherbereichs, in dem die Daten für den
Zeichensatz ausgelesen werden. Dies soll
uns hier jedoch nicht interessieren und
sei nur nebenbei angemerkt.
Konzentrieren wir uns auf die Bits 4-7 :
Sie bestimmen die Lage des Video-RAMs
innerhalb des 16 KB-Bereichs des VICs.
Die im Folgenden angegebenen Adressen
verstehen sich also als Offsets, die auf
die Startadresse des 16 KB-Bereichs aufaddiert werden müssen:
Wert Bits Bereich (Hex) Bereich (Dez)
0: 0000 $0000-$03FF 0- 1023 1: 0001 $0400-$07FF 1024- 2047 2: 0010 $0800-$0BFF 2048- 3071 3: 0011 $0CFF-$0FFF 3072- 4095 4: 0100 $1000-$13FF 4096- 5119 5: 0101 $1400-$17FF 5120- 6143 6: 0110 $1800-$1BFF 6144- 7167 7: 0111 $1CFF-$1FFF 7168- 8191 8: 1000 $2000-$23FF 8192- 9215 9: 1001 $2400-$27FF 9216-10239 10: 1010 $2800-$2BFF 10240-11263 11: 1011 $2CFF-$2FFF 11264-12287 12: 1100 $3000-$33FF 12288-13311 13: 1101 $3400-$37FF 13312-14335 14: 1110 $3800-$3BFF 14336-15359 15: 1111 $3CFF-$3FFF 15360-16383
Obwohl die Video-Map nur 1000 Bytes lang
ist, habe ich hier dennoch 1024- Byte-Bereiche angegeben. Das hat nämlich auch
eine Bedeutung für den nächsten Kursteil.
Desweiteren wollen wir noch schnell klären, wie man den 16 KB-Bereich des VICs
verschiebt, da die FLI-Routine eine Menge Video-Speicher benötigt ( nämlich die
vollen 16 KB), sollten wir den VIC in
einem Bereich arbeiten lassen, in dem
wir nicht auf Zeropage-Adressen und
Sprungvektoren ( die im ersten VIC-Bereich - von $0000-$3 FFF - eine Menge Platz wegnehmen) Rücksicht nehmen zu
müssen.
Der Adressbereich des VIC wird nun mit
den untersten zwei Bits der Adresse
$ DD00( dez.56576) angegeben ( richtig:
dies ist ein CIA-B Register, das dem VIC
seinen Adressbereich vorschreibt) . Hier
eine Auflistung der möglichen Bitkombinationen und der Adressbereiche die sie
aktivieren:
3: 11 $0000-$3FFF 0-16383 2: 10 $4000-$7FFF 16384-32767 1: 01 $8000-$BFFF 32768-49151 0: 00 $C000-$FFFF 49152-65535
In unserem Programmbeispielen werden wir
den VIC-Bereich nach $4000 verschieben, womit der Wert 2 in die Adresse $ DD00 geschrieben werden muß. Die tatsächliche
Adresse der Video-Map ist dann immer
$4000 plus der oben angegebenen Offseta- dresse.
Nachdem wir nun also die Grundlagen
gelkärt hätten, ist die Erklärung des
Prinzips der FLI-Routine höchst simpel:
Zum Einen wissen wir, daß die Video-Map
zur Farbgebung der Grafik verwendet
wird. Zum Anderen haben wir gesehen, wie
die Startadresse, aus der der VIC sich
die Video-Map- Daten holt, verschoben
werden kann. Was liegt nun also näher
als zwei und zwei zusammenzuzählen, und
eine Raster-IRQ- Routine zu schreiben, die in JEDER Rasterzeile eine andere
Video-Map aktiviert? Die Folge dieser
Operation wäre dann nämlich die Möglichkeit, einem 8 x8- Pixel Block der Grafik
in jeder Zeile eine neue Farbe zuzuteilen ( im Multicolormodus sogar 2), so daß
wir die 2-, bzw.4- Farbeinschränkung auf
einen Bereich von 1 x8 Pixeln reduzieren!
3) DIE UMSETZUNG
Vom Prinzip her klingt das natürlich
sehr einfach, jedoch stellt sich uns
noch ein kleines Problem in den Weg:
Wie wir bei der Beschreibung des FLD-Effektes schon gelernt hatten, liest der
VIC ja nur in jeder Charakterzeile ( also
jede achte Rasterzeile), die 40 Bytes, die er in den folgenden acht Rasterzeilen darzustellen hat. Somit würde ein
einfaches Umschalten auf eine neue Video- Map nichts bewirken, da der VIC ja
immer noch mit den 40 Zeichen, die er zu
Beginn der Charakterzeile gelesen hat, die folgenden Zeilen darstellen würde - und das selbst bei umgeschalteter Video-Map. Also müssen wir ihn auch hier mit
einem kleinen Raster-Trick " veräppeln" .
Man kann den VIC nämlich auch dazu zwingen, eine Charakterzeile NOCHMALS zu
lesen. Der anzuwendende Trick ist uns
dabei gar nicht mal so unbekannt, denn
er funktioniert ähnlich wie der FLD-Effekt. Bei diesem schoben wir den Anfang der nächsten Charakterzeile durch Hochsetzen der vertikalen Bildschirmverschiebung vor dem Rasterstrahl her, so
daß die Charakterzeile für den VIC erst
begann, als wir mit unserer Manipulation
aufhörten. Nun arbeiten wir im Prinzip
genauso, nur daß wir die Chrakterzeile
nicht VOR dem Rasterstrahl wegschieben, sondern MIT ihm. Verschieben wir den
Vertikal-Offset nämlich so, daß er immer
mit dem Anfang einer Charakterzeile zusammenfällt, so meint der VIC, er müsse
jetzt die neuen Charakterdaten lesen, selbst wenn er das in der Rasterzeile
zuvor auch schon getan hat. Schalten wir
nun gleichzeitig auch noch auf eine andere Video-Map um, so liest der VIC, so
als wäre alles in Ordnung, die 40 Charakter der neuen Map!
Bevor ich mich nun aber in theoretischen
Erklärungen verliere, möchte Ich Ihnen
ein Programmbeispiel zeigen, das alles
einfacher zu erklären vermag. Sie finden
es auf dieser MD unter dem Namen " GO-FLI- CODE", und müssen es mit der Endung",8,1" laden. Gleichzeitig ( und ebenfalls mit ",8,1") sollte auch noch die
lange Grafik " GO-FLIPIC" geladen werden, damit Sie beim Start der Routine mittels
" SYS4096", auch etwas auf dem Bildschirm
sehen. Es handelt sich dabei um das Logo
unseres Schwestermagazins " Game On", und
zwar in schillernd bunten Farbabstufungen!
Doch kommen wir nun zum Sourcecode dieser FLI-Routine. Zunächst einmal werde
ich Ihnen die Initialisierung hier auflisten, die größenteils identisch mit
den zuvorigen Init-Routinen ist, jedoch
auch einige FLIspezifische Einstellungen vornimmt:
Init:sei ;IRQs sperren lda $7F ;CIA-IRQs und NMIs sta $dc0d ; sperren sta $dd0d bit $dc0d ;ggf. vorhandene IRQ- bit $dd0d ;oder NMI-Anfr. löschen lda #$0b ;Bildschirm sta $d011 ; abschalten ldy #$00 ;Daten für Color-Map lda $3c00,y ; von $3C00 sta $d800,y ; nach $D800 lda $3d00,y ; kopieren sta $d900,y lda $3e00,y sta $da00,y lda $3f00,y sta $db00,y iny bne color lda #$00 ;Hardvektor füt IRQ bei sta $fffe ; $FFFE/$FFFF auf lda #$11 ; eigene Routine bei sta $ffff ; $1100 setzen ldx #$38 ;Basisw. Vert.Versch. stx $02 ; in Zeropage ablegen inx ;Pro Zeile +1 und stx $03 ; ebenfalls in ZP abl. inx ;usw. bis X=$3F stx $04 inx stx $05 inx stx $06 inx stx $07 inx stx $08 inx stx $09 lda #$5C ;Rasterz. $d012 ist sta $d012 ; IRQ-Auslöser lda #$81 ;Raster als IRQ-Quelle sta $d01a ; einschalten dec $d019 ;ggf. VIC-IRQ freigeben lda #$35 ;Basic- u. Betr.Sys.- sta $01 : ROM abschalten cli ;IRQs freigeben
lda #$7 f ; Tastaturport init.
sta $ dc00
spc: lda $dc01 ;Auf 'SPACE'-Taste cmp #$ef ; warten bne spc lda #$37 ;ROMs wieder sta $01 ; einschalten jmp $fce2 ;und RESET auslösen
Hier schalten wir zunächst alle CIA-IRQs
ab, tragen die Startadresse unserer FLI-Routine bei $1100 in den Hard-IRQ- Vektor
$ FFFE/$ FFFF ein, schalten das Betriebssystem ab, damit der Vektor auch
angesprungen wird, und erlauben dem VIC
in Rasterzeile $5 C einen Raster-IRQ auszulösen. Zudem wird eine Tabelle in den
Zeropageadressen von $02 bis $09 initialisiert, die in Folge die Werte von $38 bis $3 F enthält. Diese müssen wir später, je nach Rasterzeile, in Register
$ D011 schreiben, damit der Anfang der
Charakterzeile immer in der aktuellen
Rasterzeile ist. Durch den Basiswert $38 legen wir zunächst nur fest, daß der Bildschirm eingeschaltet ist, und sich
der VIC im Grafikmodus und in 25- Charakterzeilen-Darstellung befindet.
Durch das jeweilige aufaddieren von 1 wird dann legiglich der vertikale Verschiebeoffset um 1 erhöht, was dann immer dem jeweiligen Wert für den Beginn
einer Charakterzeile entspricht ( Startwert $38-> Verschiebung=0 für tatsächliche Charakterzeile; nächster Wert=$39-> Verschiebung=1 Zeile, wehalb die erste Rasterzeile hinter der normalen Charakterzeile vom VIC wieder als Charakterzeile aufgefasst wird; usw.) . Die
Werte werden übrigens deshalb in eine
Tabelle innerhalb der Zeropage eingetragen, damit das Timing später besser
klappt.
Desweiteren wird hier der Inhalt der
Color-Map initialisiert. Hierzu sollte
ich vielleicht noch erläutern, wie das
68 Blocks lange FLIPIC-File aufgebaut
ist: zunächst einmal wird es an Adresse
$3 C00 geladen, wo sich auch die $0400
Bytes für die Color-Map befinden. Ab
Adresse $4000 beginnen nun die acht Video- Maps, die später von der FLI-Routine
durchgeschaltet werden. Auch sie sind
jeweils $0400 Bytes lang ( was einer Gesamtlänge von $0400*8=$2000 Bytes
gleichkommt) . Hiernach, im Bereich von
$6000-$8000 befindet sich nun die darzustellende Grafik-Bitmap. Was die Init-Routine nun macht, ist lediglich die
Color-Map- Daten zwischen $3 C00 und $4000 nach $ D800( Basisadresse des Color-RAMs), zu kopieren. Die Video-RAM- Daten
liegen in den Speicherbereichen, die mit
den Werten von 0-7 für die Highbits von
Register $ D018, angewählt werden können
( was gleichzeitig auch der Anordnung in
Rasterzeilen entspricht - Video-RAM Nr.0($4000) für die 0 . Rasterzeile innerhalb
Charakterzeile; Video-RAM Nr.1($4400) für die 1 . Rasterzeile innerhalb der
Charakterzeile; usw) .
Kommen wir nun zur Interruptroutine selbst. Sie beginnt wie all unsere zeitkritischen Raster-IRQs zunächst mit der
Glättungsroutine, wobei ein weiterer
Interrupt festgelegt und ausgelöst wird, bevor die eigentliche FLI-Routine folgt.
Sie beginnt wieder ab dem Label " Onecycle", ab dem der IRQ " geglättet" ist.
Hier nun der eigentliche Kern der Routine. Da der Interrupt bei Rasterzeile $5 C
ausgelöst wurde, und die Glättung zwei
Rasterzeilen verbrauchte, befinden wir
uns nun also in Zeile $5 E, womit wir
also zwei Rasterzeilen vor Beginn der
achten Charakterzeile stehen:
dec $d019 ;VIC-ICR löschen lda #$5C ;Neue IRQ-Rasterzeile sta $d012 ; festlegen ($5C) lda #$00 ;IRQ-Vektor wieder auf sta $fffe ; die erste IRQ-Routine lda #$11 ; verbiegen (für den sta $ffff ; nächsten Aufruf)
lda #$00 ; Bildschirmrahmenund sta $ d020 ; Hintergrundfarbe auf sta $ d021 ;' schwarz' setzen
NOP ;Hier folgen nun 40 NOPs ... ; um bis zum richtigen NOP ; Zeitpunkt zu verzögern ldy #$08 ;Rasterzeilenzähler init. jsr fliline ;8 FLI-Rasterzeilen jsr fliline ; für 12 Charakterzeilen jsr fliline ; durchführen (gesamte jsr fliline ; Größe des FLI-Bereichs: jsr fliline ; 8x12=96 Rasterzeilen) jsr fliline jsr fliline jsr fliline jsr fliline jsr fliline jsr fliline jsr fliline
lda #$02 ; Bildschirmrahmenund sta $ d020 ; Hintergrundfarbe auf sta $ d021 ;' rot' setzen
lda #$38 ;Vertikalverschiebung sta $d011 ; zurücksetzen (=0) lda #$88 ;VideoMap 8 sta $d018 ; einschalten pla ;Prozessorregister vom tay ; Stapel zurückholen pla ; und IRQ beenden. tax pla rti
Hier werden erst einmal die Vorbereitungen für den nächsten Interrupt getroffen
( also für den nächsten Bildaufbau) . Dies
ist das Rücksetzen des IRQ-Vektors, auf
die Glättung-IRQ- Routine, das neue festlegen der Rasterzeile $5 C als IRQ-Auslöser, sowie das Löschen des VIC-ICR- Registers durch Zugriff auf Adresse
$ D019 . Nun wird das Y-Register mit dem
Wert 8 initialisiert, der in der nun
folgenden " FLILINE"- Routine gebraucht wird. Selbige führt den eigentlichen
FLI-Effekt aus, wobei Sie immer acht
Rasterzeilen lang, seit Beginn einer
Rasterzeile, den Effekt erzeugt. Durch
den 12- maligen Aufruf lassen wir ihn
also ab Rasterzeile $5 E für 96 Rasterzeilen aktiv sein. Hier nun der Sourcecode zur FLILINE-Routine:
( Anm. d. Red. : Bitte wählen Sie nun den zweiten Teil des Kurses aus dem Textmenu)
fliline sty $ d018 ; Video-Map0 einschalt.
ldx $02 ; Vert. Versch.=0($38) stx $ d011 ; schreiben
lda #$18 ;VideoMap1-Wert laden ldx $03 ;Vert.Versch.=1 ($39) nop ;Verzögern... nop nop nop sta $d018 ;Werte zeitgenau in nop ; $d018 und $d011 stx $d011 ; eintragen lda #$28 ;VideoMap2-Wert laden ldx $04 ;Ver.Versch.=1 ($3A) nop ;Verzögern... nop nop nop sta $d018 ;Werte zeitgenau in nop ; $d018 und $d011 stx $d011 ; eintragen lda #$38 ;Dito f. die folgenden ldx $05 ; 6 Rasterzeilen nop nop nop nop sta $d018 nop stx $d011 lda #$48 ldx $06 nop nop nop nop sta $d018 nop stx $d011 lda #$58 ldx $07 nop nop nop nop sta $d018 nop stx $d011 lda #$68 ldx $08 nop nop nop nop sta $d018 nop stx $d011 lda #$78 ldx $09 nop nop nop nop sta $d018 ldy #$08 stx $d011 rts
Dieser etwas merkwürdige Aufbau der Routine ist mal wieder absolut unerläßlich
für die richtige Funktionsweise des FLI-Effektes. Wie immer muß hier extrem
zeitgenau gearbeitet werden, damit die
einzelnen Befehle zum richtigen Zeitpunkt ausgeführt werden. Durch die NOPs
innerhalb der eigentlichen IRQ-Routine
wird die FLILINE-Routine zum ersten Mal
genau dann angesprungen, wenn sich der
Rasterstrahl kurz vor dem Beginn einer
neuen Charakterzeile befindet. Durch den
" STY $ D018"- Befehl am Anfang der Routine
wird nun gleich auf Video-Map 0 geschaltet, deren Ihnhalt für die Farbgebung
der Grafikpixel in dieser Rasterzeile
zuständig ist. Gleichzeitig setzen wir
den vertikalen Verschiebeoffset auf
Null, wobei mit dem Wert von $38, der
sich in der Zeropageadresse $02 befin- det, gleichzeitig die Grafikdarstellung
eingeschaltet wird. Genau nachdem diese
beiden Werte geschrieben wurden beginnt
nun die Charakterzeile, in der der VIC, wie wir ja wissen, den Prozessor, und
damit unser Programm, für 42 Taktzyklen
anhält. Hiernach haben wir noch 21 Taktzyklen Zeit, die Werte für die nächste Rasterzeile einzustellen, wobei auch
dies exakt vor Beginn derselben geschehen muß. Hierzu wird zunächst der Akku
mit dem Wert $18 geladen ( Wert für Register $ D018), wobei die oberen vier Bits
die Lage der Video-Map bestimmen. Sie
enthalten den Wert 1 und bezeichnen damit Video-Map1 bei Adresse $4400 . Die
unteren vier Bits bestimmen die Lage des
Zeichensatzes und könnten eigentlich
jeden beliebigen Wert enthalten. Da Sie
normalerweise den Wert 8 enthalten, benutzen wir ihn ebenfalls. Der LDA-Befehl
verbraucht nun 2 Taktzyklen. Als Nächstes wird das X-Register mit dem Wert
für Register $ D011 inititalisiert. Er ist diesmal $39, was der Vertikal-Verschiebung des Bildschirms um eine
Rasterzeile entspricht. Hierbei wird der
Wert aus Speicherzelle $03 der Zeropage
ausgelesen. Vielleicht wird Ihnen jetzt
auch klar, warum wir die Wertetabelle
überhaupt, und dann ausgerechnet in der
Zeropage angelegt haben: Durch die ZP-Adressierung verbraucht der LDX-Befehl
nämlich 3 Taktzyklen, anstelle von nur
zweien ( bei direktem Laden mit " LDX
#$39"), was für unser Timing besonders
wichtig ist! Es folgen nun 4 NOP-Befehle, die einfach nur 8 Taktzyklen
verbrauchen sollen, damit wir zum richtigen Zeitpunkt in die VIC-Register
schreiben. Was dann auch tatsächlich
geschieht, wobei wir mit den Befehlen
" STA"," STX" und " NOP" nochmals 10 Takte
" verbraten" und uns wieder genau am Beginn der nächsten Rasterzeile befinden.
Durch die im letzten Moment vorgegaukelte Vertikal-Verschiebung um eine Rasterzeile, meint der VIC nun, daß er sich wieder am Anfang einer Charakterzeile
befindet, weswegen er sie auch prompt
einliest. Sinnigerweise jedoch aus der
neu eingeschalteten Video-Map Nr.1 ! ! !
Dies setzt sich so fort, bis auch die
letzte der acht Rasterzeilen, nach Beginn der eigentlichen Charakterzeile, abgearbeitet wurde. Nun wird zum Haupt-IRQ zurückverzweigt, wo " FLILINE" sogleich zur Bearbeitung der nächsten Charakterzeile aufgerufen wird.
Wenn Sie übrigens einmal die Summe der
verbrauchten Taktzyklen pro Rasterzeile
berechnen, so wird Ihnen auffallen, daß
wir 23(=2+3+10+8), anstelle von 21 Taktzyklen verbraucht haben, so daß unser Programm also 2 Zyklen länger
dauert, als es eigentlich sollte. Dies
liegt an einem kleinen " Nebeneffekt" von
FLI. Dadurch, daß wir den VIC austricksen, scheint er etwas " verwirrt" zu
sein, weswegen er nicht gleich die 40 Zeichen der neuen Video-Map einliest,
sondern 24 Pixel lang erstmal gar nicht
weiß, was er machen soll ( vermutlich
liest er sie zu spät, weswegen er nichts
darstellen kann) . Aus diesem Grund können auch die ersten 24 Pixel einer FLI-Grafik nicht dargestellt werden. Wenn
Sie sich das Beispielbild einmal genauer
ansehen, so werden Sie merken, daß es
nur 296 Pixel breit ist (3 Charakter
fehlen auf der linken Seite) . Erst danach kann der VIC die Grafik mit aktiviertem FLI-Effekt darstellen. In den 3 Charaktern davor ist dann wieder das
Bitmuster der letzten Speicherzelle seines Adressierungsbereiches zu sehen
( normalerweise $3 FFF - im Beispiel aber
durch die Bereichsverschiebung $7 FFF) .
Gleichzeitig scheint der Prozessor dadurch aber wieder 2 Zyklen mehr zu haben, was die einzige Erklärung für die
oben aufgezeigte Diskkrepanz sein kann
( Sie sehen: selbst wenn man einen solchen Effekt programmieren kann - bleiben
manche Verhaltensweisen der Hardware selbst dem Programmierer ein Rätsel) .
4) WEITERE PROGRAMMBEISPIELE Das war es dann wieder einmal für diesen
Monat. Ich hoffe, daß Sie meinen, manchmal etwas komplizierten, Ausführungen
folgen konnten. Mit der Erfahrung aus
den letzten Kursteilen sollte das jedoch
kein Problem für Sie gewesen sein. Wenn
Sie sich ein paar FLI-Bilder anschauen
möchten, so werfen Sie einen Blick auf
diese Ausgabe der MD. Außer dem oben
schon angesprochenen " GO-FLIPIC", haben
wir Ihnen noch zwei weitere FLI-Bilder
mit auf die Diskette kopiert." FULL-FLIPIC", ist ein Multicolor-FLI- Bild, daß über den gesamten Bildschirm geht.
Viel mehr als es anzuschauen können Sie
nicht damit machen, da sich der Prozessor während des annähernd gesamten Bildaufbaus im FLI-Interrupt befindet, und
dadurch wenig Rechenzeit für andere Effekte übrigbleibt. Das dritte Beispiel
ist ein FLI-Bild in HIRES-Darstellung(" HIRES-FLIPIC") . Hier sehen Sie wie
Eindrucksvoll der FLI-Effekt sein kann, da durch die hohe Auflösung noch bessere
Farbeffekte erzielt werden. Zu jedem der
drei Bilder müssen Sie das gleichnamige
Programm laden ( erkennbar an der Endung
"- CODE"), das immer mit " SYS4096" gestartet und durch einen ' SPACE'- Tastendruck beendet wird. Das Prinzip
der einzelnen Routinen ist immer ähnlich, nur daß bei " FULL" noch mehr Rasterzeilen in FLI-Darstellung erscheinen, und bei " HIRES" zusätzlich der Hires- Modus zur Darstellung verwendet
wird. Am Besten Sie disassemblieren sich
die drei Programme mit Hilfe eines Speichermonitors und manipulieren sie ein
wenig, um die Vielfalt und Funktionsweise von FLI-Effekten besser zu erlernen.
Übrigens: Der FLI-Effekt funktioniert
auch im normalen Textmodus. Hierbei werden dann immer nur die oberen acht Pixel
eines Zeichens in allen acht Rasterzei- len wiederholt. Man könnte damit einen
recht eindrucksvollen Texteinund ausblend- Effekt programmieren, bei dem die
einzelnen Zeichen quasi auf den Bildschirm " fließen"( oder von ihm " wegschmelzen") . Dies als Anregung für ein
eigenes FLI-Projekt. . .
(ih/ub)
IRQ-KURS "Die Hardware ausgetrickst..." (Teil 9)
Herzlich Willkommen zum neunten Teil
unseres Raster-IRQ- Kurses. In dieser
Ausgabe soll es um die trickreiche Programmierung von Sprites gehen, die wir
bisher ja nur am Rande angeschnitten
hatten. Durch Raster-IRQs ist es nämlich
möglich, mehr als acht Sprites auf den
Bildschirm zu zaubern! Wir wollen hierzu
eine sogenannte Sprite-Multiplexer- Routine kennenlernen, mit der wir bis zu
16 Sprites ( fast) frei über den Bildschirm bewegen können!
1) DAS FUNKTIONSPRINZIP Zunächst wollen wir klären, wie man im
Allgemeinen die Anzahl der Sprites
erhöht. Das Prinzip ist höchst simpel:
dadurch nämlich, daß man einen Rasterinterrupt z. B. in der Mitte des sichtbaren Bildschirms auslöst, hat man die Möglichkeit die Sprite-Register des VIC
neu zu beschreiben, um z. B. neue Spritepositionen und Spritepointer zu setzen.
Bevor der Rasterstrahl nun wieder die
obere Bildschirmhälfte erreicht, können
dann wieder die Daten der ersten acht
Sprites in den VIC geschrieben werden.
Auf diese Weise kann man in der oberen, sowie in der unteren Bildschirmhälfte je
acht Sprites darstellen. Wie einfach
dieses Prinzip funktioniert soll folgendes Programmbeispiel verdeutlichen, das
Sie auf dieser MD auch unter dem Namen
"16 SPRITES" finden, und wie immer mit
",8,1" laden und durch ein " SYS4096" starten. Nach dem Start werden Sie 16( !) Sprites auf dem Bildschirm sehen, acht in der oberen und acht in der unteren Bildschirmhäfte, jeweils in einer
Reihe. Kommen wir zunächst zur Init-Routine unsres kleinen Beispiels, deren
Funktionsprinzip uns mittlerweile bekannt sein sollte:
Init:sei ;IRQs sperren lda #6 ;Farbe auf 'blau' sta $d020 sta $d021 lda #$7f ;Alle Interruptquellen sta $dc0d ; von IRQ- und NMI-CIA sta $dd0d ; sperren ggf. aufge- bit $dc0d ; tretene IRQs frei- bit $dd0d ; geben lda #$94 ;Rasterzeile $94 als sta $d012 ; IRQ-Auslöser lda $d011 ; festlegen and #$7f sta $d011 lda #$01 ;Rasterstrahl ist sta $d01a ; Interruptquelle lda #<irq1 ;Vektor auf erste IRQ- sta $fffe ; Routine verbiegen lda #>irq1 sta $ffff ldy #19 ;Pseudo-Sprite-Register lo1: lda vic1,y ; in Zeropage von sta $80,y ; von $80 bis $C0 lda vic2,y ; kopieren sta $a0,y dey bpl lo1 ldy #62 ;Spriteblock Nr. 13 lda #$ff ; mit $FF auffüllen lo2: sta 832,y dey bpl lo2 ldy #7 ;Spritepointer aller lo3: lda #13 ; Sprites auf Block 13, sta 2040,y ; sowie Farbe aller lda #1 ; Sprites auf 'weiß' sta $d027,y ; setzen dey bpl lo3 lda #$35 ;ROM abschalten sta $01 cli ;IRQs freigeben lo4: jsr $1200 ;Bewegunsroutine aufr. lda $dc01 ;SPACE-Taste abfragen cmp #$ef bne lo4 lda #$37 ;Wenn SPACE, dann ROM
sta $01 ; wieder einschalten jmp $ fce2 ; und RESET auslösen.
Hier schalten wir nun also wie gewohnt
alle Interruptquellen der CIA ab, und
aktivieren den Raster-IRQ, wobei dieser
das erste Mal in Rasterzeile $94 auftreten soll, was in etwa die Mitte des
sichtbaren Bildbereichs ist. Dort soll
dann die Routine " IRQ1" aufgerufen werden, deren Adresse in den Hard-IRQ- Vektor bei $ FFFE/$ FFFF geschrieben wird.
Damit der Prozessor beim Auftreten des
IRQs auch tatsächlich unsere Routine
anspringt, wird zuvor noch das ROM abgeschaltet, und anschließend in eine
Schleife gesprungen, die auf die SPACE-Taste wartet, und in dem Fall einen Reset auslöst. Wichtig sind nun noch die
drei Kopierschleifen innerhalb der Initialisierung. Die erste davon (" LO1") kopiert nun zunächst eine Tabelle mit
Sprite-Register- Werten, die am Ende des
Programms stehen, in die Zeropage ab Adresse $80 . Was es damit auf sich hat, sehen wir später. Die zweite und dritte
Schleife füllen dann noch den Spriteblock 13 mit gesetzten Pixeln, setzen
die Spritepointer aller Sprites auf diesen Block, sowie die Farbe Weiß als
Spritefarbe.
Sehen wir nun, was die Interruptroutine
" IRQ1" macht:
IRQ1:pha ;Prozessorregister txa ;retten pha tya pha lda #0 ;Farbe auf 'schwarz' sta $d020 sta $d021
lda #$ fc ; Rasterzeile $ FC soll sta $ d012 ; nächster IRQ-Auslöser lda #< irq2 ; sein, wobei Routine sta $ fffe ;" IRQ2" angesprungen
lda #>irq2 ; werden soll sta $ffff dec $d019 ;VIC-ICR freigeben lda $a0 ;X-Pos. Sprite 0 sta $d000 ; setzen lda $a1 ;Y-Pos. Sprite 0 sta $d001 ; setzen lda $a2 ;Ebenfalls für Sprite 1 sta $d002 lda $a3 sta $d003 lda $a4 ;Sprite 2 sta $d004 lda $a5 sta $d005 lda $a6 ;Sprite 3 sta $d006 lda $a7 sta $d007 lda $a8 ;Sprite 4 sta $d008 lda $a9 sta $d009 lda $aa ;Sprite 5 sta $d00a lda $ab sta $d00b lda $ac ;Sprite 6 sta $d00c lda $ad sta $d00d lda $ae ;Sprite 7 sta $d00e lda $af sta $d00f lda $b0 ;Hi-Bits der X-Pos. sta $d010 ; aller Sprites setzen lda $b1 ;Sprite-Enable setzen sta $d015 ; (welche sind an/aus) lda $b2 ;X-Expansion setzen sta $d017 lda $b3 ;Y-Expansion setzen sta $d01d
lda #6 ; Farbe wieder ' blau' sta $ d020 sta $ d021
pla ;Prozessor-Regs. tya ; zurückholen pla txa pla rti ;Und IRQ beenden...
Wie Sie sehen, tut die Routine eigentlich nichts anderes, als Werte aus den
Zeropageadressen von $ A0 bis $ B3 in einzelne VIC-Register zu kopieren. Damit
geben wir dem VIC wir ab der Rasterposition $94 also einfach neue Spritewerte.
Gleiches macht nun auch die Routine
" IRQ2", die an Rasterzeile $ FC ausgelöst
wird, nur daß sie die Werte aus den Zeropageadressen von $80 bis $93 in den
VIC-Kopiert. In den beiden genannten
Zeropage-Bereichen haben wir also quasi
eine Kopie der wichtigsten Sprite-Register für jeweils zwei Bildschirmbereiche untergebracht, deren Inhalte je- weils an Rasterzeile $94 und $ FC in den
VIC übertragen werden. Verändern wir
diese Werte nun innerhalb der Zeropage, so können wir jedes der 16 sichtbaren
Sprites einzeln bewegen, einoder auschalten, sowie X-, bzw. Y-Expandieren.
Wir haben also quasi zwei " virtuelle", oder " softwaremäßige" Sprite-VICs erschaffen, deren Register wie die des
normalen VICs beschrieben werden können.
Dies können Sie übrigens mit einer Routine ab Adresse $1200 machen. Sie wird
im Hauprogramm ( sh. INIT-Listing) ständig aufgerufen, womit ich Ihnen die Möglichkeit der Spritebewegung offenhalten
wollte. Im Beispiel steht an dieser
Adresse nur ein " RTS", das Sie jedoch
mit einer eigenen Routine ersetzen können.
Wir haben nun also 16 Sprites auf dem
Bildschirm, jedoch mit der Einschränkung, daß immer nur jeweils acht im oberen und unteren Bildschirmbereich er- scheinen dürfen. Setzen wir die Y-Position eines Sprites aus dem unteren
Bereich auf eine Zahl kleiner als $94, so wird es nicht mehr sichtbar sein, da
diese Position ja erst dann in den VIC
gelangt, wenn der Rasterstrahl schon an
ihr vorbei ist. Umgekehrt darf ein Sprite im oberen Bildbereich nie eine Position größer als $94 haben. Ausserdem ist
noch ein weiterer Nachteil in Kauf zu
nehmen: das Umkopieren der Register ist
zwar schnell, da wir absichtlich mit
Zeropageadressen arbeiten, auf die der
Zugriff schneller ist, als auf Low-/ High-Byte- Adressen (2 Taktzyklen, anstelle von 3), jedoch dauert es immer
noch knappe 4 Rasterzeilen, in denen gar
keine Sprites dargestellt werden können, da es dort zu Problemen kommen kann, wenn der VIC teilweise schon die Werte
der oberen und der unteren Sprites
enthält.
2) DIE OPTIMIEREUNG Wie Sie in obigem Beispiel sahen, ist
die Programmierung von mehr als 8 Sprites recht problemlos, solange alle weiteren Sprites in der Horizontalen voneinander getrennt dargestellt werden
können. Was nun aber, wenn Sie z. B. ein
Action-Spiel programmieren möchten, in
dem mehr als acht Sprites auf dem Bildschirm darstellbar sein sollen, und zudem auch noch möglichst kreuz und quer
beweglich sein müssen? Für diesen Fall
brauchen wir eine etwas intelligentere
Routine, die es uns ermöglicht, flexibler mit den horizontalen Sprite-Positionen umzugehen. Solch eine Routine
werden wir nun realisieren. Sie ist allgemeinhin unter dem Namen " Sprite-Multiplexer" bekannt. Wer sich nichts
darunter Vorstellen kann, der sollte
sich auf dieser MD einmal das Double-Density- Demo anschauen, in dem die Hintergrundsterne, sowie die Meteore, die
über den Bildschirm huschen auf diese Art und Weise dargestellt werden.
Kommen wir zunächst zum Grundprinzip der
Multiplex-Routine. Mit ihr soll es uns
möglich sein,16 Sprites auf dem Bildschirm darzustellen, wobei wir uns möglichst wenig Sorgen über die Darstellung machen wollen. Diese Arbeit soll
unsere Routine übernehmen, und automatisch die richtige Darstellung wählen.
Damit es keine Bereiche gibt, in denen
gar keine Sprites dargestellt werden
können, weil gerade irgendwelche Pseudo- VIC-Daten kopiert werden, sollte Sie
zusätzlich auch noch möglichst schnell
sein, bzw. den Zeitpunkt der anfallenden
Werteänderungen im VIC sorgfältig
auswählen können.
Um nun all diesen Anforderungen zu genügen, legen wir uns wieder einen " virtuellen" VIC an, den wir so behandeln, als könne er tatsächlich 16 Sprites darstellen. Seine Register sollen wieder in
der Zeropage zu finden sein, damit die Zugriffe darauf schneller ausgeführt
werden können. Hierzu belegen wir die
obere Hälfte der Zeropage mit den benötigten Registerfunktionen. Beachten Sie
bitte, daß in dem Fall keine Betriebssystemroutinen mehr verwendet werden können, da diese nämlich ihre Parameter in
der Zeropage zwischenspeichern und somit
unseren VIC verändern würden! Hier nun
zunächst eine Registerbelegung unseres
Pseudo-VICs:
$80-$8 F 16 Bytes, die bestimmen, welche Sprites
einoder ausgeschaltet sind. Eine 0 in
einem dieser Bytes schaltet das entsprechende Sprite aus. Der Wert 1 schaltet
es ein ($80 ist für Sprite0,$8 F für
Sprite15 zuständig) .
$90-$9 F Diese 16 Bytes halten das Low-Byte der
X-Koordinaten der 16 Sprites.
$ A0-$ AF In diesen 16 Bytes sind die High-Bytes
der X-Koordinaten der 16 Sprites untergebracht.
$B0-$BF Hier sind nacheinander alle Y- Koordinaten der 16 Sprites zu finden.
$ C0-$ CF Diese Register legen die Farben der 16 Sprites fest.
$ D0-$ DF Hier werden die Spritepointer untergebracht, die angeben, welcher Grafikblock
durch ein Sprite dargestellt wird.
$ E0-$ EF Dies ist ein Puffer für die Y-Positionen
der 16 Sprites. Er wird für die Darstellung später benötigt.
$ F0-$ FF
Innerhalb dieses Bereichs werden Zeiger
auf die Reihenfolge der Sprites angelegt. Mehr dazu später.
Die hier angegebenen Register können nun
von uns genauso verwendet werden, als
könne der VIC tatsächlich 16 Sprites
darstellen. Möchten wir also z. B. Sprite
Nr.9 benutzen, so müssen wir zunächst Xund Y-Position durch Beschreiben der
Register $99,$ A9, sowie $ B9 setzen, Farbe und Spritepointer in $ C9 und $ D9 unterbringen, und anschließend das Sprite durch Schreiben des Wertes 1 in Register $89 einschalten. Analog wird mit
allen anderen Sprites verfahren, wobei
die Sprite-Nummer immer der zweiten Ziffer des passenden Registers entspricht.
Wie muß unsere Multiplex-Routine nun
vorgehen, um dem VIC tatsächlich 16 unabhängige Sprites zu entlocken? Man
greift hier, wie bei allen Raster-IRQ- Programmen, zu einem Trick: Wie wir bis- her gesehen hatten, ist die einzige
hardwaremäßige Beschränkung, die uns
daran hindert, mehr als acht Sprites
darzustellen, die X-Koordinate. Es können also immer maximal acht Sprites mit
derselben Y-Koordinate nebeneinander
stehen. Daran können wir auch mit den
besten Rastertricks nichts ändern. In
der Horizontalen, können wir aber sehr
wohl mehr als acht Sprites darstellen, und das eigentlich beliebig oft ( das
erste Beispielprogramm hätte auch problemlos 24, oder gar 32 Sprites auf den
Bildschirm bringen können) . Rein theoretisch kann ja ein Sprite, das weiter
oben am Bildschirm schon benutzt wurde, weiter unten ein weiteres Mal verwendet
werden. Einzige Bedingung hierzu ist, daß das zweite, virtuelle, Sprite mindestens 22 Rasterzeilen ( die Höhe eines
ganzen Sprites plus eine Zeile " Sicherheitsabstand") unterhalb des ersten virtuellen Sprites liegt. Damit diese Bedingung so oft wie nur möglich erfüllt ist, verwendet man ein echtes VIC-Sprite
immer zur Darstellung des nten, sowie
des < n+8>- ten virtuellen Sprites. Das
echte Sprite0 stellt also das virtuelle
Sprite0, sowie das virtuelle Sprite8 dar. Da die Y-Koordinaten beider Sprites
jedoch beliebig sein können, müssen wir
zuvor eine interne Sortierung der Y-Koordinaten vornehmen, so daß der
größtmögliche Abstand zwischen den beiden Sprites erreicht wird. Dies sollte
unsere Routine in einem Bildschirmbereich tun, in dem keine Sprites angezeigt werden können, also dann, wenn der
Rasterstrahl gerade dabei ist, den oberen und unteren Bildschirmrand zu zeichnen ( selbst wenn man diesen auch abschalten kann) . Nach der Sortierung können nun die Werte der ersten acht virtuellen Sprites im VIC eingetragen werden, wobei gleichzeitig ermittelt wird, in welcher Rasterzeile Sprite0 zu Ende
gezeichnet ist. Für diese Rasterzeile
wird ein Raster-Interrupt festgelegt,
der die Werte für Sprite8 in den VIC
eintragen soll, und zwar in die Register
des " echten" Sprite0 . Ebenso wird mit
den Sprites von 9-15 verfahren. Jedesmal, wenn das korrespondierende Sprite
( n-8) zu Ende gezeichnet wurde, muß ein
Rasterinterrupt auftreten, der die Werte
des neuen Sprites schreibt.
Kommen wir nun jedoch zu der Routine
selbst. Sie finden Sie übrigens auch in
den beiden Programmbeispielen " MULTI-PLEX1" und " MULTIPLEX2" auf dieser MD.
Beide wierden wie üblich mit ",8,1" geladen um mittels " SYS4096" gestartet.
Die Initialierung möchte ich Ihnen diesmal ersparen, da Sie fast identisch mit
der obigen ist. Wichtig ist, daß durch
sie zunächst ein Rasterinterrupt an der
Rasterposition $ F8 ausgelöst wird, an
der unsere Multiplex-Routine ihre Organisationsarbeit durchführen soll. Hierbei wird in die folgende IRQ-Routine
verzweigt, die in den Programmbeispielen
an Adresse $1100 zu finden ist:
BORD:pha ;Prozessor-Regs. txa ; retten pha tya pha lda #$e0 ;Neuen Raster-IRQ sta $d012 ; für Zeile $E0 lda #$00 ; und Routine "BORD" sta $fffe ; festlegen lda #$11 sta $ffff dec $d019 ;VIC-IRQs freigeben jsr PLEX ;Multiplexen jsr SETSPR ;Sprites setzen pla ;Prozessorregs. wieder tay ; zurückholen und pla ; IRQ beenden tax pla rti
( Bitte wählen Sie nun den 2 . Teil des IRQ-Kurses aus dem Textmenu!)
Fortsetzung IRQ-Kurs (Teil9)
Wie Sie sehen, wird hier lediglich der
Rasterinterrupt auf Zeile $ E0 gesetzt, sowie die Bord-Routine als IRQ-Routine
eingestellt. Die eigentliche Arbeit wird
von den Routinen " PLEX" und " SETSPR" durchgeführt, wovon wir uns die Erstere
nun genauer anschauen möchten. Sie steht
ab Adresse $1300 :
PLEX:clc ;Sprites zaehlen, indem lda $80 ; die Inhalte der Ein-/ adc $81 ; Ausschaltregister adc $82 ; aller Sprites einfach adc $83 ; im Akku aufaddiert adc $84 ; werden. adc $85 adc $86 adc $87 adc $88 adc $89 adc $8a adc $8b adc $8c adc $8d adc $8e adc $8f sta $7e ;Anzahl merken tax ;Aus Tabelle ONTAB lda ONTAB,x; den VIC-Wert zum Ein- sta $7f ; schalten holen und in ; $7F ablegen cpx #$00 ;Keine Sprites an? bne clry ;Nein, also weiter rts ;Sonst Prg. beenden
Diese Routine ermittelt zunächst einmal, wieviele Sprites überhaupt eingeschaltet
sind. Dies tut sie, indem Sie die Inhalte der Einschalt-Register des Pseudo-VICs aufaddiert, wobei das Ergebnis die
Anzahl eingeschalteter Sprites ergibt
( wenn an, dann Wert=1, sonst 0) . Anschließend wird aus der Tabelle " ONTAB" der Wert ausgelesen, der in das VIC- Register zum Einschalten der Sprites
kommen muß, um die gefundene Anzahl
Sprites zu aktivieren. Die Liste enthält
folgende Werte:
$00,$01,$03,$07,$0F,$1F,$3F,$7F $FF,$FF,$FF,$FF,$FF,$FF,$FF,$FF
Sind nun weniger als acht Sprites eingeschaltet, so wird auch nur diese Anzahl
aktiviert werden. Sind es mehr, so müssen immer alle acht eingeschaltet werden. Der so ermittelte Wert wird dann in
der Speicherzelle $7 F zwischengespeichert. Für den weiteren Verlauf der Routine ist auch die ermittelte Anzahl notwendig, die in der Zeropageadresse $7 E
untergebracht wird. Zum Schluß prüft die
Routine noch, ob überhaupt ein Sprite
eingeschaltet werden soll, und kehrt bei
einer Anzahl von 0 unverrichteter Dinge
zur IRQ-Routine zurück.
Wenn mindestens ein Sprite eingeschaltet ist, so wird die eigentliche Multiplex-Routine aktiv. Sie muß nun die Y-Koordinaten der Sprites sortieren, und
die Verteilung der 16 virtuellen Sprites
auf die echten VIC-Sprites übernehmen.
Zur Sortierung benötigen wir noch zwei
weitere, jeweils 16 Byte große, Felder.
Im ersten, von Zeropageadresse $ E0 bis
$ EF, wird eine Kopie der Y-Koordinaten
angelegt, welche dann sortiert wird. Da
die Sortierroutine die Werte der einzelnen Koordinaten manipulieren muß, ist
diese Maßnahme notwendig. Desweiteren
darf sie die Spritedaten in den Registern von $80 bis $ DF nicht verändern, bzw. vertauschen, da ein Programm, das
die viruellen VIC-Register mit Daten
füttert, ja immer davon ausgehen muß, daß es bei Sprite0 immer das ein und
selbe Sprite anspricht, und nicht eines, das von einer Position weiter hinten
hierhin sortiert wurde. Deshalb sortiert
die Multiplex-Routine nicht die eigent- lichen Sprites in die benötigte Reihenfolge, sondern legt eine Tabelle mit
Zeigern auf die Reihenfolge der Sprites
an. Selbige wird in den Zeropageadressen
von $ F0 bis $ FF abgelegt. Sie enthält
nach der Sortierung Werte zwischen 0 und
15, die als Index auf die dem virtuellen
Sprite entsprechenden Register dienen.
Zur eigentlichen Sortierung verwenden
wir nun einen ganz einfachen Bubblesort-Algorithmus, der so oft über dem zu sortierenden Feld angewandt wird, wie Sprites eingeschaltet sind. Er ermittelt
durch eine Reihe von Vergleichen immer
den kleinsten Wert innerhalb der Kopie
der Y-Positionen, legt ihn in der Zeiger- Liste bei $ F0 ab, und setzt die entsprechende Koordinate auf $ FF, damit sie
im nächsten Sortierdurchlauf den
größtmöglichen Wert enthält und somit
nicht noch einmal das Minimum sein kann.
Ebenso müssen wir mit den Y-Koordinaten von abgeschalteten Sprites verfahren, die vor dem eigentlichen Sortieren ebenfalls auf $ FF gesetzt werden. Dadurch
stehen sie immer am Ende der Liste. Nach
der Sortierung kann die Routine dann mit
Hilfe der Zeiger die Daten zu den Sprites aus den entsprechenden Registern
holen, und in den VIC schreiben.
Bevor wir nun zur Beschreibung der Routine kommen, noch einige technische Hinweise: damit die Routine möglichst
schnell arbeitet, wurde auf die Verwendung von Schleifen so weit wie möglich
verzichtet. Das heißt, daß z. B jeder der
Y-Werte über einen eigenen CMP-Befehl
verfügt, der ihn mit dem aktuellen Minimum vergleicht. Analog ist es bei anderen Vorgängen ( so auch bei der Aufaddierung der Sprite-Einschalt- Register oben, wo eigentlich auch eine Schleife hätte
benutzt werden können) . Dadurch verlängert sich das Programm natürlich ein
wenig, jedoch ist der damit verbundene Speicheraufwand noch erträglich und wir
erreichen zudem eine hohe Verarbeitungsgeschwindigkeit. Da sich viele Vorgänge
oft wiederholen, werde ich an diesen
Stellen mit " . . ." eine Folge andeuten, die analog auch für alle weiteren Sprites ausgeführt wird.
Kommen wir nun also zur eigentlichen
Multiplex-Routine, die mit dem Label
" CLRY" beginnt, wohin auch die letzte
Routine verzweigt, wenn Sprites eingeschaltet sind. Hier wird nun geprüft, ob
ein Sprite eingeschaltet ist, oder
nicht, und in letzterem Fall die Y-Koordinate mit dem Wert $ FF überschrieben, damit das abgeschaltete Sprite bei
der Sortierung später nicht mehr berücksichtigt wird:
CLRY:ldy #$ff ;Y-Reg mit $FF laden sx0: lda $80 ;Sprite0 an? bne sx1 ;Ja, also weiter sty $B0 ;Nein, also $FF in Y-Pos sx1: lda $81 ;Sprite 1 an? bne sx2 ;Ja, also weozer sty $B1 ;Nein, also $FF in Y-Pos
sx2 : . . . Analog für Sprites 3-14
sx15 : lda $8 F ; Sprite 15 an?
bne YCOPY ; Ja, also zu YCOPY spr.
sty $ BF ; Nein, also $ FF in Y-Pos Damit hätten wir nun also alle Y-Posi- tionen abgeschalteter Sprites gelöscht.
Da die Routine die Y-Positionen der eingeschalteten Sprites nicht verändern
darf, wird nun eine Kopie dieser Werte
in $ E0 bis $ EF angelegt:
YCOPY: lda $ B0 ; Y-Wert Sprite 0 sta $ E0 ; kopieren
. . . Analog für Sprites 1-14
lda $ BF ; Y-Wert Sprite 15
sta $ EF ; kopieren
Nachdem nun auch das Sorierfeld angelegt
wurde, können wir endlich mit der Sortierung beginnen. Hierbei benutzen wir
das Y-Register um den momentan kleinsten
Y-Wert zu speichern. Der Akku wird beim
Auffinden eines minimalen Wertes dann
immer mit einem Zeiger auf das entsprechende Sprite geladen, der der Spritenummer entspricht. Das X-Register
enthält den aktuellen Index auf die Zeigertabelle und wird pro Durchlauf um
eins erhöht. Die Sortierung ist beendet, wenn die Vergleichsroutine insgesamt so
oft durchlaufen wurde, wie Sprites eingeschaltet sind:
SORT: ldx #$00 ;Index init. loop: ldy #$ff ;YMin. init. cpy $E0 ;YSpr0 < YMin? bcc s1 ;Nein, also weiter ldy $E0 ;Ja, also YMin=YSpr0
lda #$00 ; Zeiger Spr0=0 laden
s1: cpy $E1 ;YSpr1 < YMin? bcc s2 ;Nein, also weiter ldy $E1 ;Ja, also YMin=YSpr1 lda #$01 ;Zeiger Spr1=1 laden
s2 : . . . Analog für Sprites 3-14
s15: cpy $EF ;YSpr15 < YMin? bcc s16 ;Nein, also weiter ldy $EF ;Ja, also YMin=YSpr15 lda #$0F ;Zeiger Spr15=15 s16: sta $F0,x ;Zeiger ablegen tay ;Zgr. als Index in Y-Reg lda #$ff ;YSpr mit YMin auf $FF sta $E0,y ; setzen. inx ;Zeiger-Index+1 cpx $7E ;mit Anzahl Sprites vgl. beq end ;Gleich, also Ende jmp loop ;Sonst nochmal sortieren
end: rts
Wie Sie sehen, wird nach jedem ermittelten Minimum der entsprechende Y-Wert auf
$ FF gesetzt, damit er im nächsten Vergleich nicht mehr herangezogen wird. Auf
diese Weise wird nun nach und nach immer
wieder der kleinste Wert ermittelt, solange, bis der Puffer nur noch $ FF-Werte
enthält, und die Schleife " Anzahl-Sprites"- Mal durchlaufen wurde. Die Sortierung ist damit beendet, und die Multiplex- Routine kehrt wieder zur IRQ-Routine zurück.
Hier nun wird als Nächstes die " SETSPR"- Routine aufgerufen, die anhand der ermittelten Werte zunächst die Daten der
ersten acht virtuellen Sprites in den
VIC überträgt. Gleichzeitig berechnet
sie mit Hilfe der Y-Position dieser
Sprites die Rasterzeile, an der ein IRQ
ausgelöst werden muß, um das jeweils
achte Sprite nach dem aktuellen anzuzeigen, und setzt den nächsten IRQ-Auslöser
entsprechend. Zunächst einmal wollen wir uns den ersten Teil dieser Routine anschauen. Er beginnt ab Adresse $1500 :
SETSPR:
lda $7F ;VIC-Wert für eingesch. sta $d015 ; Sprites setzen lda #$00 ;High-Bits der X-Pos sta $d010 ; löschen lda $7E ;Anzahl Sprites holen cmp #$01 ;Wenn mind. 1 Spr. ein- bcs spr00 ;gesch., dann weiter rts ;Sonst Ende spr00:clc ;C-Bit für Add. löschen ldx $E0 ;Zgr. aus Tabelle holen lda $90,x ;X-Pos. holen und für sta $d000 ; VIC-Spr.0 setzen lda $B0,x ;Y-Pos. holen und für sta $d001 ; VIC-Spr.0 setzen adc #22 ;Raster für Spr.Ende= sta ras8+1 ; YPos+22 setzen lda $D0,x ;Spr.Zeiger holen und sta $07f8 ; für VIC-Spr.0 setzen lda $C0,x ;Spr.Farbe holen und sta $d027 ; für VIC-Spr.0 setzen ldy $a0,x ;X-Pos-High holen, lda $d010 ;X-High-Bit-Reg. holen ora high0,y;Wert f. VIC-Spr.0 ein- sta $d010 ; odern und zurückschr. lda $7E ;Anzahl holen cmp #$02 ; Mehr als 1 Spr. an? bcs spr01 ; Ja, also weiter. rts ;Sonst Ende
spr01 : Analog für Sprite 1-7
. . .
spr07:... ;Werte f. Spr.7 setzen lda $7E ;Falls mehr als acht cmp #$09 ; Sprites, dann bcs acht ; neuen IRQ setzen rts ; Sonst Ende
acht: lda #< spr08 ; Adr. Routine " Spr8" sta $ fffe ; in IRQ-Vektor bei lda #> spr08 ;$ FFFE/$ FFF einsta $ ffff ; tragen ras8 : lda #$00 ; Rasterz. Spr0+22 als
sta $d012 ; IRQ-Quelle setzen dec $d019 ;VIC-IRQs freigeben rts ;Ende
Wie Sie sehen, holt die SETSPR-Routine
nun nacheinander alle Zeiger aus der
sortierten Tabelle bei $ F0 und überträgt
die Werte der ersten acht Sprites in den
VIC. Auf die Register des Pseudo-VICs
wird dabei über X-Register- indizierte
Adressierung zugegriffen. Zwei Dinge
sollten nun noch erläutert werden: Zum
Einen wird nach Setzen der Y-Position
eines Sprites die Rasterzeile berechnet, an der es zu Ende gezeichnet ist. Der so
ermittelte Wert wird nun an dem Label
" RAS8" plus 1 eingetragen, womit wir den
Operanden des LDA-Befehls am Ende der
Routine modifizieren. Er lädt nun den
Akku mit der besagten Rasterzeilennummer
und schreibt ihn in das Raster-IRQ- Register, womit der VIC nachdem er Sprite0 auf dem Bildschirm dargestellt hat, einen Raster-IRQ erzeugt. Hierbei wird dann zur Routine " SPR8" verzweigt, die
ich Ihnen gleich erläutern werde. Analog
wird mit den Sprites von 1-7 verfahren, wobei nach jedem Sprite geprüft wird, ob
noch ein weiteres Sprite eingeschaltet
ist, und demnach initialisiert werden
muß. Ist das nicht der Fall, so wird
direkt zum IRQ zurückgekehrt. Dadurch
wird ebenfalls nur dann ein IRQ auf die
Routine " SPR8" eingestellt, wenn
tatsächlich mehr als acht virtuelle
Sprites eingeschaltet sind. Im anderen
Fall brauchen wir ja keine Manipulation
vorzunehmen, weswegen der nächste Raster- IRQ, wieder auf " BORD" springt, wo
wir die Multiplex-Routine ein weiteres
Mal durchlaufen. Sollen nun aber mehr
als acht Sprites dargestellt werden, so
wird ein IRQ erzeugt, der auf die Routine " SPR08" springt, die wir uns gleich
näher anschauen werden.
Die zweite, etwas undurchsichtige Stelle
ist das Setzen der High-Bits für die X-Position eines Sprites. Hier gehen wir
wiefolgt vor: Zunächst wird der High-Wert der X-Position geholt, der nur 0 oder 1 sein, je nach dem ob die X-Position kleiner oder größer/ gleich 256 ist. Dieser Wert wird nun als Zeiger auf
eine Tabelle mit X-High- Bit-Werten für
das jeweilige Sprite benutzt. Sie steht
ganz am Ende des Programms und sieht
folgendermaßen aus:
high0: $00,$01 high1: $00,$02 high2: $00,$04 high3: $00,$08 high4: $00,$10 high5: $00,$20 high6: $00,$40 high7: $00,$80
Wie Sie sehen, enthält sie für jedes
Sprite einmal ein Nullbyte, das durch
die SETSPR-Routine geladen wird, wenn
der X-High- Wert Null ist, sowie einen Wert, in dem das Bit gesetzt ist, das im
X-High- Bit-Register für das entsprechende Sprite zuständig ist. Durch das Einodern des ermittelten Wertes in dieses
Register setzen wir nun letztendlich das
High-Bit der X-Position eines Sprites.
Hierbei wird bei den Sprites von 1-7 jeweils auf ein eigenes " High"- Label
zugegriffen, bei Sprite 1 also auf
" High1", bei Sprite 2 auf " High2" und so
weiter.
Kommen wir nun jedoch zur " SPR08"- Routine, die als IRQ-Routine aufgerufen
wird, und zwar nachdem Sprite0 auf dem
Bildschirm dargestellt wurde:
SPR08:pha ;Prozessor-Regs. retten txa pha tya pha
ldx $ F8 ; Zgr. aus Sort-Liste
lda $90,x ;X-Pos. in VIC-Spr0 sta $d000 ; eintragen lda $B0,x ;Y-Pos. in VIC-Spr0 sta $d001 ; eintragen lda $D0,x ;Spr.Zgr. in VIC-Spr0 sta $07f8 ; eintragen lda $C0,x ;Spr.Farbe in VIC-Spr0 sta $d027 ; eintragen ldy $90,x ;X-High-Wert holen lda $d010 ;VIC-High-Reg. lesen and #$FE ;Bit f. Spr0 ausmask. ora high0,y;Mit Wert f. X-High sta $d010 ; odern u. zurückschr. lda #<spr09;Adresse f. Raster-IRQ sta $fffe ; des nächsten Sprite lda #>spr09; in IRQ-Vektoren sta $ffff ; schreiben dec $d019 ;VIC-IRQs freigeben lda $7E ;Anzahl holen, cmp #$0a ;Spr9 benutzt? bcs ras9 ;Ja, also weiter jmp bordirq;Sonst IRQ rücksetzen ras9: lda #$00 ;Rasterz. f. Spr9 laden cmp $d012 ;m. akt. Strahlpos vgl. bmi direct9;Wenn größer o. gleich beq direct9; Spr.9 sofort zeichnen sta $d012 ;Sonst n. IRQ festlegen pla ;Prozessor-Regs zurück- tay ; holen und IRQ pla ; beenden tax pla rti
Wie Sie sehen, werden zunächst wieder
wie schon bei den anderen Sprite-Routinen, die vituellen VIC-Werte in den
echten VIC übertragen. Hiernach wird
verglichen, ob mehr als neun Sprites
dargestellt werden sollen, und wenn ja
zur Routine " RAS9" verzweigt, die den
IRQ für Sprite9 vorbeitet. An diesem
Label befindet sich wieder der LDA-Befehl, der von der Darstellungroutine
für Sprite1 so abgeändert wurde, daß er
die Rasterzeile des Endes dieses Sprites in den Akku lädt. Bevor nun der Interrupt gesetzt wird, prüft das Programm
durch einen Vergleich des Akkuinhalts
mit der aktuellen Rasterstrahlposition, ob der Rasterstrahl an besagter Zeile
nicht schon vorüber ist. In dem Fall
wird kein IRQ vorbereitet, sondern direkt auf die Routine zur Darstellung des
nächsten Sprites verzweigt ( sh. BEQbzw. BMI-Befehle) . Diese beginnt dann
folgendermaßen:
( Bitte wählen Sie nun den 3 . Teil des IRQ-Kurses aus dem Textmenu!)
Forsetzung IRQ-Kurs ( Teil9)
---------------------------------------- SPR09: pha ;Prozessor-Regs. txa ; retten (dieser Teil pha ; wird bei einem IRQ tya ; abgearbeitet!) pha direct9:ldx $F9 ;Setzen der Werte für lda $99,x ;Spr9 in VIC-Spr1
. . .
Wie Sie sehen, wird durch den Sprung auf
" direct9" lediglich der IRQ-Teil der
Routine übersprungen. Ansonsten ist die
Routine identisch mit " SPR8" . Ebenso
existieren jeweils eigene Routinen für
die Sprites von 9 bis 15 . Sollten weniger als 15 Sprites dargestellt werden, so wird, wie bei SPR8 auch schon ersichtlich, auf die Routine " BORDIRQ" gesprungen. Sie steht ganz am Ende des
Programms und setzt " BORD" wieder als nächste IRQ-Routine und beendet den aktuellen IRQ. Sie wird ebenfalls abgearbeitet, wenn das fünfzehnte, virtuelle
Sprite initialisiert wurde, so daß der
gesamte Multiplex-Vorgang nocheinmal von
vorne beginnen kann.
Dies wäre es dann wieder einmal für diesen Monat. Ich möchte Sie noch dazu animieren, ein wenig mit den Multiplex-Routinen herumzuexperimentieren. Versuchen Sie doch einmal 24 Sprites auf den
Bildschirm zu bringen! Im übrigen sind
nicht immer alle 16 Sprites auf dem
Bildschirm darstellbar. Ist der Abstand
zwischen zwei virtuellen Sprites, die
durch das selbe " echte" Sprite dargestellt werden, kleiner als 22 Rasterzeilen, so erscheint das zweite Sprite
nicht auf dem Bildschirm. Desweiteren
ist aus dem Multiplex-Demo2 ersichtlich, daß es auch Probleme mit der Sprite-Priorität gibt. Dadurch, daß die Sprites
umsortiert werden müssen, kann es pas- sieren, daß manche zunächst Sprites vor, und beim nächsten Bildaufbau hinter anderen Sprites liegen. Da man die Sprite-Priorität nicht beeinflussen kann, und
Sprites mit kleinen Spritenummern Prioriät vor Sprites mit großen Spritenummern haben, kommt es hier zu kleinen
Darstellungsfehlern, die leider nicht
behoben, sondern lediglich unsichtbar
gemacht werden können, indem man allen
Sprites dieselbe Farbe gibt. In einem
Spiel, in dem es wichtig ist, daß die
Spielfigur immer vor allen anderen Objekten zu sehen ist, kann man aber auch
nur die Sprites von 1-7 multiplexen, und
Sprite 0 unberührt von den restlichen
lassen. Die Routine kann dann zwar nur
15 Sprites darstellen, korrigiert jedoch
den obigen Fehler. Wie Sie sehen ist
auch dieser Raster-Effekt in vielfachen
anwendeungsspezifischen Kombinationsmöglichkeiten umsetzbar.
( ub)
IRQ-KURS "Die Hardware ausgetrickst..." (Teil 10)
Herzlich Willkommen zum zehnten Teil
unseres Raster-IRQ- Kurses. In dieser
Ausgabe wollen wir uns weiterhin mit der
trickreichen Spriteprogrammierung befassen. Im letzten Kursteil hatten Sie ja
schon gelernt, wie man mit Hilfe eines
Sprite-Multiplexers mehr als 8 Sprites
auf den Bildschirm zaubert. Mit einem
ähnlichen Trick werden wir heute einen
sogenannten " Movie-Scroller" programmieren, der es uns ermöglicht, einen
Scrolltext, so wie in Abspännen von Filmen, von unten nach oben über den gesamten Bildschirm zu scrollen. Hierbei werden wir wieder durch den Multiplex-Effekt insgesamt 104( !) Sprites aus dem
VIC locken!
1) DAS PRINZIP DES MOVIESCROLLERS Das Funktionsprinzip des Movie-Scrollers
ist recht einfach und sollte nach Kenntnis der Multiplex-Routinen kein Problem
für Sie sein: Jede Zeile unseres Movie-Scrollers soll aus 8 Sprites aufgebaut
sein, in denen wir einen Text darstellen. Um nun mehrere Zeilen zu generieren, müssen wir in regelmäßigen Abständen einen Raster-IRQ erzeugen, der jedesmal die Y-Position, sowie die Sprite- Pointer der acht Sprites neu setzt, um somit die nächste Scroller-Zeile darzustellen. Unsere Routine tut dies alle
24 Rasterzeilen, womit sich zwischen
zwei Spritezeilen immer 3 Rasterzeilen
Freiraum befinden. Um nun einen Scrolleffekt nach oben zu erzeugen, benutzen
wir zusätzlich ein Zählregister, daß
einmal pro Rasterdurchlauf um 1 erniedrigt, und so von $17 bis $00 heruntergezählt wird. Es dient als zusätzlicher
Offset auf die Rasterzeilen, in denen
ein IRQ ausgelöst werden muß. Ist dieses Zählregister auf 0 heruntergezählt, so
wird es wieder auf $17 zurückgesetzt und
die Spritepointer für jede einzelne
Spritezeile werden alle um je eine Zeile
höher kopiert. In die, am Ende der Pointerliste, freigewordenen Sprites werden
dann die Buchstaben der neuen Zeile einkopiert, die sich dann von unten wieder
in den Scroller einreihen können.
2) DER PROGRAMMABLAUF Um den Movie-Scroll- Effekt besser zu
erläutern haben wir natürlich wieder
einige Beispielprogramme für Sie parat.
Sie heißen " MOVIE.1" und " MOVIE.2" und
befinden sich ebenfalls auf dieser MD.
Wie immer müssen beide Beispiele mit
",8,1" geladen und durch ein " SYS4096" gestartet werden." MOVIE.2" unterscheidet sich von " MOVIE.1" nur darin, daß
zusätzlich zum Scroller auch der obere
und untere Bildschirand geöffnet wurden.
Dies ist nur durch einen ganz besonderen Trick möglich, den wir später noch erläutern werden. Ich möchte Ihnen nun
zunächst eine Speicheraufteilung der
beiden Routinen geben, damit Sie sich
die einzelnen Unterroutinen, die ich
nicht alle in diesem Kurs erläutern werde, mit Hilfe eines Disassemblers einmal
selbst anschauen können:
Adr. Funktion
$0800 Zeichensatz $1000 IRQ-Init, incl. aller Sprite-Initialiserungen $1100 Movie-IRQ- Routine. Dies ist die
eigentliche IRQ-Routine, die alle
24 Rasterzeilen die Sprites neu
setzt.
$1200 BORDERNMI ( nur in " MOVIE.2"), zum
Üffnen des oberen und unteren
Bildschirmrandes.
$1300 MOVESPR-Routine. Sie bewegt die
13 Spritezeilen pro Rasterdurlauf
um eine Rasterzeile nach oben.
$1400 MAKETEXT-Routine. Diese Routine
schreibt den Text in die acht
Sprites, die sich gerade am unteren Bildschirmrand befinden.
$1500 Spritepointer-Liste, die auf die
Sprites im Bereich von $2600-$3 FFF zeigt.
$1600 Der Scroll-Text im ASCII-Format.
Die Initialisierungsroutine unseres Movie- IRQs schaltet wie üblich das Betriebssystem- ROM ab, und setzt im Hardware- IRQ-Zeiger bei $ FFFE/$ FFFF die
Startadresse der MOVIEIRQ-Routine
($1200) ein. Zudem werden alle CIA-Interrupts gesperrt und die Spriteregister des VICs initialisiert. Hierbei
tragen wir lediglich alle X-Positionen
der Sprites ein, die bei $58 beginnen, und in 24- Pixel-Schritten pro Sprite
erhöht werden. Natürlich muß auch das
X-Position- High-Bit des achten Sprites, daß sich ganz rechts auf dem Bildschirm befindet, auf 1 gesetzt werden. Desweiteren wird die Farbe aller Sprites auf
" weiß" geschaltet. Zusätzlich wird in
einer eigenen Unterroutine der Speicherbereich von $2600-$3 FFF, in dem die 104 Sprites unterbringen, gelöscht. Zuletzt
legt die Init-Routine Rasterzeile $17 als ersten IRQ-Auslöser fest und erlaubt
dem VIC IRQs zu erzeugen.
Die MOVIEIRQ-Routine stellt nun den Kern
unseres Movie-Scrollers dar. Es handelt
sich hierbei um die IRQ-Routine, die
alle 24 Rasterzeilen die Y-Positionen
der Sprites ändert. Sie wird zum ersten
Mal an Rasterzeile $17 angesprungen und
setzt dann die folgenden IRQ-Rasterzeilen von selbst. Hier nun der
kommentierte Sourcecode:
MOVIEIRQ:
pha ;Prozessorregs. retten txa pha tya pha lda #$ff ;Alle Sprites sta $d015 ; einschalten inc $d020 ;Rahmenfarbe erhöhen
Nach Einsprung in die IRQ-Routine werden, nach dem obligatorischen Retten
aller Prozessorregister, zunächst alle
Sprites eingeschaltet. Anschließend
erhöhen wir die Rahmenfarbe um 1, damit
Sie sehen können, wie lange es dauert, bis alle Sprites neu gesetzt wurden. Nun
beginnt der eigentliche Hauptteil der
Routine:
clc ; C-Bit für Add. löschen ldy counter ; Rasterzähler als Y-Index
lda softroll; Akt. Scrolloffs. holen. .
adc ypos, y ; . . Y-Wert addieren. .
sta $ d001 ; . . und selbigen in alle sta $ d003 ; acht Y-Positionen sta $ d005 ; der Sprites eintragen
sta $ d007
sta $d009 sta $d00b sta $d00d sta $d00f
Wir setzen hier die Y-Positionen der
Sprites. Hierbei hilft uns eine Tabelle
namens " YPOS", in der alle Basis-Y- Positionen der insgesamt 13 Spritezeilen
untergebracht sind. Die Y-Positionen der
Sprites in der ersten Zeile sind dabei
auf den Wert 26 festgelegt. Alle weiteren Positionen resultieren dann aus dem
jeweils letzten Positionswert plus dem
Offset 24 .
Desweiteren erscheinen in diesem Programmteil noch zwei Labels, mit den Namen " SOFTROLL" und " COUNTER" . Sie stehen
für die Zeropageadressen $ F6 und $ F7, in
denen wir Zwischenwerte unterbringen.
" SOFTROLL"($ F6) ist der oben schon
erwähnte Rasterzeilenzähler, der von $17 auf 0 heruntergezählt wird. In " COUNTER" ist vermerkt, wie oft die IRQ-Routine während des aktuellen Rasterdurchlaufs
schon aufgerufen wurde. Dies dient
gleichzeitig als Zähler dafür, welche
Spritezeile wir momentan zu bearbeiten
haben. Beim ersten Aufruf enthält " COUN-TER" den Wert 0 . Auf diese Weise können
wir ihn als Index auf die YPOS-Tabelle
verwenden. Nachdem der Akku nun also mit
den Scrolloffset " SOFTROLL" geladen wurde, kann so der Basis-Y- Wert der entsprechenden Spritezeile ( im ersten
Durchlauf Zeile 0, Y-Pos 26) auf den
Akkuinhalt aufaddiert werden. Der resultierende Wert entspricht nun der Y-Position aller Sprites dieser Zeile, die
wir sogleich in die VIC-Register eintragen.
Nun müssen noch die Spritepointer der
neuen Spritezeile neu gesetzt werden, da
diese ja einen anderen Text enthält als
die vorherige:
ldy isline ; Zgr.- Index in Y holen
ldx pointer,y;Zgr.-Basiswert aus Tab. stx $07f8 ;für Sprite0 setzen.. inx ; ..um 1 erhöhen und stx $07f9 ; für Sprite1 setzen inx ;Ebenso für Sprites2-7 stx $07fa inx stx $07fb inx stx $07fc inx stx $07fd inx stx $07fe inx stx $07ff
Auch hier verwenden wir ein Label um auf
eine Zeropageadresse zuzugreifen." ISLI-NE" steht für Adresse $ F9, die uns als
Zwischenspeicher für einen Index auf die
Spritezeigerliste dient. In letzterer
sind nun alle Spritepointerwerte für das
jeweils 0 . Sprite einer jeden Zeile un- tergebracht. Die Zeiger für die Sprites
von 1 bis 7 resultieren aus dem aufaddieren von 1 auf den jeweils letzten
Wert, was in unserer Routine durch die
INX-Befehle durchgeführt wird. Die Zeigertabelle enthählt nun die Werte von
$98 bis $ F8, als Zeiger auf die Sprites, die im Speicherbereich von $2600-$3 FFF
liegen, jeweils in Achterschritten. Hier
eine Auflistung der kompletten Tabelle:
pointer .byte $98,$a0,$a8,$b0 .byte $b8,$c0,$c8,$d0 .byte $d8,$e0,$e8,$f0,$f8 .byte $98,$a0,$a8,$b0 .byte $b8,$c0,$c8,$d0 .byte $d8,$e0,$e8,$f0,$f8
Wie Sie sehen, liegen hier die angesprochenen Pointer-Werte zweimal vor. Das
ist notwendig, um das Umschalten der
Spritezeilen, wenn diese aus dem oberen
Bildschirmrand herausgescrollt werden,
zu vereinfachen. Verschwindet nämlich
die erste Spritezeile, deren Spritepointer von $98-$9 F gehen, womit ihre Sprites von im Bereich von $2600 bis $2800 untergebracht sind, aus dem oberen Bildschirmrand, so muß die zweite Spritezeile ( Pointer von $ A0-$ A7) von nun an die
erste, auf dem Bildschirm darzustellende, Zeile sein, wobei wir die gerade
herausgescrollte Zeile als unterste Zeile verwenden müssen. Nachdem ihr Textinhalt in die Sprites im Speicherbereich
von $2600 bis $2800 eingetragen wurde, müssen nur noch die Spritezeiger zum
richtigen Zeitpunkt auf diese Zeile umgeschaltet werden. Wir haben nun noch
einen Weiteren Index, namens " SHOWLINE", der in Zeropageadresse $ F8 untergebracht
ist, und uns angibt, welche der Spritezeilen als Erstes auf dem Bildschirm
dargestellt werden muß. Zu Beginn eines
neuen Rasterdurchlaufs wird " ISLINE" mit
diesem Index initialisert. Dadurch, daß
unsere Tabelle nun nach dem Wert $ F8 ein zweites Mal von vorne beginnt, kann " IS-LINE" während des Programmablaufs problemlos inkementiert werden, ohne dabei
einen Zeilenüberlauf beachten zu müssen!
Kommen wir jedoch wieder zurück zu unserer IRQ-Routine. Nachdem die Y-Positionen, sowie die Spritezeiger gesetzt wurden müssen noch einige verwaltungstechnische Aufgaben durchgeführt
werden:
clc ; C-Bit für Add. löschen ldy counter ; Spr-Zeilen- Index holen lda ad012+1, y; LO-Byte f. nächst. IRQ adc softroll ; Scroll-Offs. add.
sta $ d012 ; u. als nächst. IRQ setzen
ror ; C-Bit in Akku rotieren and #$80 ; und isolieren ora ad011+1, y; HI-Bit f. nächst. IRQ ora $ d011 ; sowie $ D011 einodern sta $ d011 ; und setzen dec $ d019 ; VIC-IRQs freigeben dec $ d020 ; Rahmenfarbe zurücksetz.
In diesem Teil der Routine wird nun der
folgende Raster-IRQ vorbereitet. Hierzu
muß zunächst die Rasterzeile ermittelt
werden, in der dieser auftreten soll.
Dafür existieren zwei weitere Tabellen, die die Basis-Rasterstrahlpositionen für
die 13 Interrupts enthalten. Hierbei
wird es wieder etwas kompliziert, da
nämlich auch IRQs an Strahlpositionen
größer als $ FF ausgelöst werden müssen, und wir deshalb das High-Bit der auslösenden Rasterstrahlposition, das in Bit
7 von $ D011 eingetragen werden muß, mitberücksichtigen müssen. Auch hierfür
müssen wir sehr trickreich vorgehen:
Zunächst einmal ermitteln wir das Low-Byte der nächsten Rasterposition, indem
wir es, mit " COUNTER" als Index im Y-Register, aus der Tabelle " AD012" auslesen. Auf diesen Wert muß nun noch der
momentane Scrolloffset aus " SOFTROLL" addiert werden, um die tatsächliche Rasterzeile zu erhalten. Der daraus resul- tierende Wert kann zunächst einmal in
$ D012 eingetragen werden. Sollte bei der
Addition ein Öberlauf stattgefunden haben, also ein Wert größer $ FF herausgekommen sein, so wurde dies im Carry-Flag
vermerkt. Selbiges rotieren wir mit dem
ROR-Befehl in den Akku hinein, und zwar
an Bitposition 7, wo auch das High-Bit
der IRQ-Rasterstrahls in $ D011 untegebracht wird. Nachdem nun dieses High-Bit
durch ein " AND $80" isoliert wurde, odern wir aus der Tabelle " AD011" das
High-Bit der Standard-Rasterposition
( ohne Offset) in den Akku ein. Danach
müssen natürlich auch noch alle weiteren
Bits von $ D011 in den Akku eingeknüpft
werden, damit wir beim folgenden
Schreibzugriff keine Einstellungen ändern.
Nun muß nur noch das ICR des VIC
gelöscht werden, damit der nächste IRQ
auch auftreten kann. Gleichzeitig wird
die Rahmenfarbe wieder heruntergezählt
( dadurch entstehen die grauen Rechen- zeit-Anzeigen im Bildschirmrahmen) .
Nun noch der letzte Teil der IRQ-Routine, der prüft, ob schon alle Spritezeilen aufgebaut wurden:
inc isline ;Akt. Zgr.-Zähler. +1 inc counter ;Y-Pos-Zähler +1 lda counter ;Y-Pos-Zähler holen cmp #$0c ; und mit 14 vgl. bne endirq ;Wenn ungl., dann weiter jsr movespr lda #$00 sta $d015 mt:jsr maketext
ENDIRQ:
pla tay pla tax pla rti
Hier werden jetzt die Zähler und Indizes
für den nächsten IRQ voreingestellt, sowie geprüft, ob schon alle Spritezeilen dargestellt wurden. Ist dies nicht
der Fall, so wird der IRQ durch Zurückholen der Prozessorregister, gefolgt von
einem RTI, beendet. Befinden wir uns
allerdings schon im letzen der 13 IRQs, die pro Rasterdurchlauf auftreten sollen, so fällt der Vergleich von " COUN-TER" mit dem Wert 14 negativ aus, womit
die Routine " MOVESPR" angesprungen wird.
Sie sorgt für das korrekten Herabzählen
des Scrolloffsets, und erkennt, wenn die
oberste Spritezeile gerade aus dem Bildschirm gescrollt wurde:
MOVESPR:
lda #$00 ;IRQ-Zähler init. sta counter sec ;C-Bit für Subtr. setzen lda softroll;Scroll-Offs. holen sbc #$01 ;Und 1 subtrahieren
sta softroll; neuen Scroll-Offs. abl.
bpl rollon ; Wenn >0, dann weiter
Wie Sie sehen, wird hier zunächst der
IRQ-Zähler für den nächsten Rasterdurchlauf auf 0 zurückgesetzt. Anschließend
subtrahieren wir den Wert 1 vom Scroll-Offset, wodurch die Sprites im nächsten
Durchlauf eine Y-Position höher dargestellt werden. Durch Ändern des hier
subtrahierten Wertes in 2 oder 3 können
Sie übrigens auch die Scrollgeschwindigkeit erhöhen. Gab es bei der Subtraktion
keinen Unterlauf, so wird zum Label
" ROLLON"( s. u.) verzweigt. Im anderen
Fall wurde durch den Scroll soeben eine
ganze Spritezeile aus dem Bildschirm
gescrollt, weswegen wir die Zeile unten, mit einem neuen Text belegt, wieder
einfügen müssen. Zusätzlich müssen die
Spritepointer in anderer Reihenfolge
ausgelesen werden, was wir durch das
Hochzählen von " SHOWLINE" bewirken.
Diese Aufgaben werden in folgendem Pro- grammteil ausgeführt:
inc showline ;1 . Spr-Zeilen- Ind. erh.
sec ; C-Bit f. Subtr. setzen lda showline ; Showline holen sbc #$0 d ; und 13 subtr.
bmi noloop ; Bei Unterlauf weiter sta showline ; Sonst Wert abl.
Da unsere Pointertabelle zwar doppelt, aber nicht ewig lang ist, muß sie natürlich alle 13 Spritezeilen wieder zurückgesetzt werden, was durch den SBC-Befehl
geschieht. Erzeugte die Subtraktion ein
negatives Ergebnis, so sind wir noch in
einer Zeile kleiner als 13, und der erhaltene Wert wird ignoriert. Im andern
Fall haben wir soeben die Mitte der
Pointerliste erreicht, ab der ja die
selben Werte stehen wie am Anfang, und
wir können " SHOWLINE" wieder auf den
erhaltenen Wert ( immer 0) zurücksetzen.
Den nun folgenden Routinenteil, der ab
dem Label " NOLOOP" beginnt, möchte ich Ihnen nur der Vollständigkeit halber
hier auflisten. Er prüft, ob die Laufschrift, die an dem Label " ESTEXT" abgelegt ist, schon zu Ende gescrollt wurde.
Wenn ja, so wird in diesem Fall der Zeiger " TPOINT", der auf das erste Zeichen
der als nächstes darzustellenden Spritezeile zeigt, wieder mit der Startadradresse des Textes (" ESTEXT") initialisiert:
NOLOOP:
lda tpoint cmp #<estext+($34*$18) bne continue lda tpoint+1 cmp #<estext+($34*$18) bne continue lda #$00 sta showline lda #<estext sta tpoint+0 lda #>estext sta tpoint+1
( Anm. d. Red. : Bitte wählen Sie nun den zweiten Teil des IRQ-Kurses aus dem Textmenu aus.)
Fortsetzung IRQ-Kurs, Teil 10
Es folgt nun der Programmteil mit dem
Label " CONTINUE", der von der obigen
Routine angesprungen wird. Hier kümmern
wir uns wieder um den Scrolloffset, dessen Wert ja noch negativ ist, da wir im
ersten Teil der MOVESPR-Routine durch
die Subtraktion einen Unterlauf des Zählers erzeugt hatten. Damit Sie hier nun
auch Werte größer 1 einsetzen können, womit der Scroll schneller durchläuft, wird " SOFTROLL" nicht wieder mit $17 vorinitialisiert, sondern wir addieren
auf das Ergebnis der Subtraktion den
Offset, der zwischen zwei Rasterinterrupts liegt,$18(= dez.24), auf. Der
resultierende Wert wird dann wieder in
Softroll abgelegt. Auf diese Weise werden also auch Scrollwerte größer 1 berücksichtigt. War das Ergebnis der
Subtraktion z. B.-2, so wird " SOFTROLL" auf 22 zurückgesetzt, womit der Öberlauf
abgefangen wird, und der Scrolleffekt flüssig weiterläuft:
CONTINUE:
clc ;C-Bit f.Add. löschen lda softroll;SOFTROLL laden adc #$18 ;24 addieren sta softroll;und wieder ablegen lda #$20 ;Op-Code für "JSR" sta mt ;in MT eintragen
Besonders trickreich ist die LDA-STA- Folge am Ende dieses Programmteils. Wir
tragen hier den Wert $20, der dem Assembler- Opcode des " JSR"- Befehls entspricht, in das Label " MT" ein. Letzteres befindet sich innerhalb der
MOVIEIRQ-Routine, und zwar vor dem Befehl " JSR MAKETEXT" . Die MAKETEXT-Routine baut eine Spritezeile auf, indem
Sie die Zeichendaten dieser Zeile von
dem Zeichensatz bei $0800 in die zu benutzenden Sprites einkopiert. Da dies
nicht direkt zu unserem Kursthema
gehört, möchte ich auch nicht weiter auf diese Routine eingehen. Wichtig zu wissen ist nur, daß die MOVESPR-Routine, nachdem sie erkannt hat, daß eine neue
Spritezeile aufgebaut werden muß, die
MOVIEIRQ-Routine derart modifiziert, daß
im nächsten IRQ die MAKETEXT-Routine
angesprungen wird. Innerhalb selbiger
existiert dann eine weitere Befehlsfolge, die den Wert $2 C in das Label " MT" schreibt. Selbiger Wert ist der Opcode
für den Assemlerbefehl " BIT" . In allen
folgenden IRQs arbeitet der Prozessor an
diesem Label also immer den Befehl " BIT
MAKETEXT" ab, der eigentlich keine Funktion beinhaltet, sondern lediglich verhindern soll, daß die Maketext-Routine
angesprungen wird. Erst, wenn MOVESPR
erkannt hat, daß eine neue Spritezeile
aufgebaut werden muß, ändert sie den
Befehl wieder in " JSR MAKETEXT" um, so
daß die Zeile im nächsten IRQ wieder
automatisch neu berechnet wird. Dieser, zugegebenermaßen etwas umständliche, Weg
des Routinenaufrufs wurde gewählt, da MAKETEXT recht lange ( insgesamt etwa
einen Rasterdurchlauf) braucht, um die
Spritezeile aufzubauen. Damit Sie in
dieser Zeit die Raster-IRQs nicht blokkiert, muß sie auf diesem Weg benutzt
werden. Während ihres Ablaufs erlaubt
sie auch weitere IRQs, so daß Sie während der Darstellung des nächsten Rasterdurchlaufs, immer zwischen den Spritepositionierungen durch den IRQ, ablaufen kann.
Kommen wir nun zum letzten Teil der MO-VESPR- Routine, dem Label " ROLLON" . Selbiges wird ja ganz am Anfang der Routine
angesprungen, wenn kein Unterlauf von
" SOFTROLL" stattfand, und somit ohne
jegliche Änderung weitergescrollt wird.
Sie setzt den Spritepointerindex " ISLI-NE" zurück auf den Wert in " SHOWLINE" und bereitet den ersten Raster-IRQ vor, der ja immer in der Rasterzeile " SOFT-ROLL" aufzutreten hat.
ROLLON:
lda showline;ISLINE mit Inhalt von sta isline ; SHOWLINE init. lda softroll;Scroll-Offset holen sta $d012 ;und als nächsten IRQ- lda $d011 ;Auslöser setzen, dabei and #$7f ;Hi-Bit löschen u. ora #$08 ;gleichz. 24-Zeilen- sta $d011 ;Darst. einschalten rts
Beim Festlegen des Wertes " SOFTROLL" als
nächste IRQ-Rasterzeile, muß die Routine
auch das High-Bit, dieser Rasterposition, in $ D011 löschen. Gleichzeitig
schaltet sie die 25- Zeilen-Darstellung
durch Setzen des 3 . Bits dieses Registers wieder ein. Dies ist eigentlich
eine Aufgabe, die für das Beispielprogramm " MOVIE.1" irrelevant ist. Wohl
aber für " MOVIE.2", in dem wir den Movie- Scroller über einen Bildschirm mit
abgeschaltetem oberen und unteren Rand
laufen lassen. Wie Sie wissen, muß dazu in Rasterzeile $ FA die Darstellung von
25 auf 24 Zeichen-Zeilen heruntergeschaltet werden, und danach, vor nochmaligem Erreichen von $ FA, wieder auf 25 Zeilen zurück, was hiermit durchgeführt
wird. Da MOVESPR immer im 13 . Interrupt
aufgerufen wird, und dieser immer nur
innerhalb der Rasterzeilen $10 C-$126 auftreten kann, befinden wir uns
tatsächlich schon unterhalb der Rasterzeile $ FA, womit die Änderung zum korrekten Zeitpunkt durchgeführt wird.
3) GLEICHZEITIGES ÜFFNEN DES BILDSCHIRMS
Wie schon angesprochen, öffnet das Programmbeispiel " MOVIE.2" zusätzlich noch
den oberen und unteren Bildschirmrand, damit die Sprites in voller Bildhöhe
über den Bildschirm laufen. Vom Prinzip
her ist dies ein recht einfaches Unterfangen, das wir auch schon ausgiebig in
diesem Kurs besprochen und angewandt
haben. Durch unsere Scrollroutine stellt sich uns jedoch ein kleines Problem in
den Weg: da unsere Raster-IRQs durch den
Scrolleffekt immer in verschiedenen Rasterzeilen aufzutreten haben, und wir
nicht immer genau sagen können, ob nun
der 11 . oder 12 . Rasterinterrupt gerade
ausgelöst wurde, bevor der Rasterstrahl
die Position $ FA erreicht hat, wird es
schwierig die Scroll-Routine so abzutimen, daß sie genau an dieser Position
die Bildschirmdarstellung ändert. Würde
man versuchen durch Verzögerungsschleifen die richtige Position abzutimen, so
wäre das mit einem erheblichen Programmieraufwand verbunden. Deshalb greifen
wir zu einem kleinen Trick: Die INIT-Routine von " MOVIE.2" wurde um eine
kleine Änderung erweitert. Zunächst lassen wir hier den Prozessor, durch ständiges Auslesen und Vergleichen der aktuellen Rasterposition, auf Rasterzeile
$ FA warten. Ist diese erreicht, so initialiseren wir Timer A von CIA-2 mit dem
Wert $4 CC7 und starten ihn. Da der Ra- sterstrahl immer exakt so viele Taktzyklen braucht, um einmal über den ganzen
Bildschirm zu laufen, erzeugt diese CIA
immer genau in Rasterzeile $ FA, in der
sie gestartet wurde, einen NMI. Weil
dieser Vorrang vor dem IRQ hat, wird er
selbst dann ausgelöst, wenn gerade ein
Raster-IRQ auftritt oder in Bearbeitung
ist. Die NMI-Routine nimmt nun die erforderliche Änderung von $ D011 vor, um
den Rand abzuschalten ( Bit 3 löschen) und kehrt anschließend sofort wieder
zurück, ggf. sogar in einen vom NMI unterbrochenen Raster-IRQ, der dann ganz
normal zu Ende bearbeitet wird. Das
Zurücksetzen von $ D011 auf die alte 25- Zeilen-Darstellung, wird dann wieder von
der MOVESPR-Routine durchgeführt, wie
wir oben ja schon gesehen hatten.
Um die NMIs zu erzeugen springt die
INIT-Routine von " MOVIE.2" auf eine Unterrountine zum Initialiseren des Timer- NMIs. Wie das funktioniert hatten wir schon ganz zu Anfang des Interrupt-Kurses bespochen. Hier die Routine, um
Ihnen den Vorgang wieder ins Gedächtnis
zu rufen. Wie auch schon für den IRQ
springen wir diesmal nicht über den
Soft-NMI- Vektor bei $0318/$0319, sondern
über den Hardvektor bei $ FFFA/$ FFFB in
den NMI ein:
NMIINIT:
lda #<nmi ;Startadresse NMI-Routine sta $fffa ; in die Hardvektoren bei lda #>nmi ;$FFFA/$FFFB eintragen sta $fffb lda #$c7 ;Timer A mit dem Zählwert sta $dd04 ; $4CC7 initialiseren lda #$4c sta $dd05 lda #$fa ;Rasterz. $FA in Akku WAIT: cmp $d012 ;m. akt. Raster vgl. bne wait ;ungl. also weiter lda #$11 ;Gleich, also Timer A sta $dd0e ; starten lda #$81 ;Timer-A-NMIs in ICR sta $dd0d ; erlauben rts
Das war eigentlich schon alles. Von nun
an löst Timer A von CIA-B alle $4 CC7 Taktzyklen einen NMI aus. Da der Rasterstrahl immer genau diese Anzahl Zyklen
benötigt, um ein ganzes Mal über den
Bildschirm zu laufen, tritt der NMI also
immer in Rasterzeile $ FA ein, auf die
wir vor Starten des Timers gewartet haben. Die NMI-Routine selbst ist recht
kurz und sieht folgendermaßen aus:
NMI: pha ;Akku retten lda $d011 ;$D011 holen HILO: ora #$00 ;7.Bit Rasterpos. setzen and #$F7 ;3.Bit löschen sta $d011 ;$D011 zurückschreiben bit $dd0d ;NMIs wieder erlauben pla ;Akku zurückholen rti ; und Ende
Da die NMI-Routine lediglich den Akku
benutzt, brauchen wir auch ausschlißelich nur diesen auf dem Stapel zu retten. Danach wird der Inhalt von $ D011 gelesen. Der nun folgende ORA-Befehl hat
die Aufgabe eine ggf. gesetztes High-Bit
der Rasterstrahlposition des nächsten
Raster-IRQs zu setzen, damit wir durch
unsere Manipulation von $ D011 nicht versehentlich die, schon gesetzte, nächste
Raster-IRQ- Position verändern. Hierzu
wurde die MOVIEIRQ-Routine von " MOVIE.2" derart abgeändert, daß Sie die High-Position für den nächsten Raster-IRQ
nicht nur in $ D011 schreibt, sondern
auch im Label " HILO+1" ablegt, so daß
das Argument des ORA-Befehls zwischen
$00 und $80 variiert und immer den richtigen ODER-Wert enthält. Anschließend
folgt nun ein AND-Befehl zum Löschen des
3 . Bits, womit wir mit dem Ablegen des
Wertes die 24- Zeilen-Darstellung einschalten. Durch den BIT-Befehl führen
wir einen Lesezugriff auf das ICR- Register von CIA-2 aus, womit wir selbiges Löschen, und dadurch das Auftreten
und Melden des nächsten NMIs ermöglichen. Hiernach wird dann nur noch der
gerettete Akkuinhalt zurückgeholt, bevor
wir den NMI mittels " RTI" beenden. Da
die MOVESPR-Routine nun automatisch zu
einer späteren Position Bit 3 in $ D011 wieder setzt, funktioniert der NMI-Border- Trick also auch wieder im folgenden Rasterdurchlauf! Mit Hilfe des hier
benutzen NMI-Tricks können Sie quasi
zwei Raster-Intterrupts gleichzeitig
ablaufen lassen, was auch für andere
Rasterroutinen sehr hilfreich sein kann.
(ih/ub)
IRQ-KURS "Die Hardware ausgetrickst..." (Teil 11)
Herzlich Willkommen zum elften Teil unseres IRQ-Kurses. Wie schon in den letzten Kursteilen, werden wir uns auch diesen Monat mit der Spriteprogrammierung
der besonderen Art beschäftigen. Es soll
um einige Tricks gehen, mit denen man
den Bildschirm auch über alle Ränder
hinaus mit Gafikdaten füllen kann. Diesen Effekt nennt man " ESCOS", der Drehund Angelpunkt für die Rastertricks in
diesem Kursteil sein wird.
1) UNSER ZIEL In früheren Kursteilen hatten wir ja
schon einmal besprochen, auf welche Art
und Weise oberer und unterer, sowie linker und rechter Bildschirmrand abgeschaltet werden. Wir hatten weiterhin gelernt, daß in diesen Bereichen ausschließlich Sprites auftauchen können, die durch die abgeschalteten Ränder
sichtbar sind, wo sie sonst von letzteren überdeckt werden. Wir hatten ebenso
eine Möglichkeit kennengelernt, beide
Ränder, die horizontalen und vertikalen, gleichzeitig abzuschalten, wobei wir auf
sehr exaktes Timing achten mussten, da
das Abschalten der linken und rechten
Bildschirmbegrenzung eine hohe Genauigkeit erforderte. Desweiteren wird Ihnen
aus dem letzten Kursteilen bestimmt noch
die Sprite-Multiplexer- Routine in Kombination mit einem Moviescroller im Kopf
sein, mit der wir 104 Sprites gleichzeitig auf den Bildschirm brachten. Wie Sie
sich vielleicht erinnern, waren wir dabei an gewisse Grenzen gebunden. So war
z. B. zwischen zwei Spritezeilen immer
ein Abstand von ca.2 Rasterzeilen erforderlich, die wir benötigten, um die
Spritepointer sowie die neuen Y-Positionen der Sprites zu setzen. Des- weiteren mussten wir ein komplizierte
Timingroutine benutzen, die zwischen
Charakterzeilen ( jede achte Rasterzeile, in der der VIC den Prozessor für eine
Dauer von 42 Taktzyklen anhält) und normalen Rasterzeilen zu unterscheiden hatte. In dieser Folge unseres Kurses wollen wir nun all diese Komponenten miteinander verbinden und eine Möglichkeit
kennenlernen, Timingprobleme durch Charakterzeilenberücksichtigung, zu umgehen. Das Endergebnis wird ein flächendeckend ( !) mit Sprites belegter Bildschirm sein, wobei weder Leerräume zwischen den Sprites, noch im gesamten
Bildschirmrahmen zu sehen sein werden!
Wir werden also über eine Grafik verfügen, die, ähnlich einem Fernsehbild, die
gesamte Bildröhrenfläche ausfüllt!
2) ERSTES PROBLEM: DAS TIMING Kommen wir gleich zum Kern dieses Kursteils, dem Timing-Problem, das sich uns entgegenstellt. Möchten wir nämlich alle
Bildschirmränder abschalten und gleichzeitig auch noch die Sprites multiplexen, so können wir programmtechnisch in
" Teufels Küche" gelangen. Wir müssten
berücksichtigen, daß im sichtbaren Bildschirmfenster alle acht Rasterzeilen
eine Chakaterzeile auftritt, gleichzeitig müsste in jeder Rasterzeile der linke und rechte Bildschirmrand abgeschaltet, sowie in jeder 21 . Rasterzeile die
Sprites neu positioniert werden. Dabei
stellen vor allem die Charakterzeilen
ein großes Problem dar. Wir müssten unterscheiden zwischen Rasterstrahlpositionen im oberen und unteren Bildschirmrand und innerhalb des sichtbaren Bildschirmfensters, und zudem noch für letzteren Fall berücksichtigen, wann sich
der VIC gerade in einer Chakaterzeile
befindet und wann in einer normalen Rasterzeile. Dieses Problem stellte sich
bei unseren Raster-Effekten nun schon
häufiger in den Weg, wobei wir es meist durch einen einfachen Trick umgingen: in
der Regel benutzten wir in solchen Fällen eine FLD-Routine, die die Charakterzeile im fraglichen Bildschirmbereich
einfach nach unten " wegdrückte", so daß
wir in jeder Rasterzeile 63 Taktzyklen
zur Verfügung hatten und somit ein einheitliches Timing programmieren konnten.
Wir könnten diesen Effekt hier nun auch
anwenden, jedoch gibt es speziell für
diese Anwendung einen weiteren, viel
einfacherern Trick, die Charakterzeilen
zu umgehen: da wir ja den gesamten Bildschirm mit Sprites füllen möchten, können wir davon ausgehen, daß wir keinerlei Hintergrundgrafik, bzw. Textzeichen
benötigen. Es gibt nun einen Trick, den
wir noch nicht kennengelernt haben, mit
dem wir die Charakterzeilen ganz abschalten können, so daß nur noch die
Sprites dargestellt werden. Wie fast
immer ist Register $ D011, ein Drehund
Angelpunkt der VIC-Trickeffekt- Kiste, für diesen Trick verantwortlich. Mit Bit4 dieses Registers können wir nämlich
den gesamten Bildschirm abschalten, was
einer Deaktivierung des VICs gleichkommt. Er wird durch das Löschen dieses
Bits veranlasst, auf dem gesamten Bildschirm nur noch die Rahmenfarbe darzustellen, und keine Charakterzeilen mehr
zu lesen. Die Sprites bleiben jedoch
weiterhin aktiv, obwohl sie jetzt unsichbar sind, da sie jetzt auf dem gesamten Bildschirm mit dem Rahmen überdeckt werden. Man könnte das Setzen und
Löschen des 4 . Bits von Register $ D011 quasi mit dem Üffnen und Schließen eines
Vorhangs vor einem Fenster vergleichen:
Eine Fliege ( oder unser Sprite), die
sich auf der Fensterscheibe befindet, ist bei geschlossenem Vorhang nicht
sichtbar. Ist letzterer jedoch geöffnet, so sieht man die Fliege, solange sie
sich im sichtbaren Bereich des Fenster
bewegt, und nicht unter den seitlich
aufgerafften Vorhängen verschwindet.
Nun, selbst wenn die Sprites noch aktiv sind, so nutzen Sie uns herzlich wenig
wenn sie unsichtbar sind. Deshalb gilt
es mal wieder, den VIC auszutricksen, um
das gewünschte Ergebnis zu erhalten.
Dies gestaltet sich in diesem Fall recht
einfach: Zunächst einmal schalten wir an
einer Rasterposition, an der der VIC
normalerweise den oberen oder unteren
Bildschirmrand zeichnet, den gesamten
Bildschirm durch Löschen von Bit 4 aus
Register $ D011, ab. Erreicht der Rasterstrahl nun Rasterzeile $30, an der eigentlich das sichtbare Bildschirmfenster
beginnt, so prüft der VIC, ob der Bildschirm nun einoder ausgeschaltet ist.
In letzterem Fall deaktiviert er seine
Zeichenaufbau-Schaltkreise bis zum nächsten Erreichen dieser Rasterposition, womit er keine einzige Charakterzeile
mehr liest. Anstelle dessen zeigt er in
den folgenden Rasterzeilen nur noch die
Farbe des Bildschirmrahmens an. Wenn wir
diesen jedoch mit Hilfe einer Border-Routine bei $ FA abschalten, so - oh Wun- der - zeigt uns der VIC den Bildschirmhintergrund, auf dem sich auch die Sprites herumtollen dürfen! Wie bei jedem
Effekt, bei dem der VIC etwas tut, was
er sonst nicht tun kann, erscheint hier
dann wieder der Inhalt der letzten
Adresse des VIC-Speichers ( normalerweise
$3 FFF) in Schwarz auf Hintergrundfarbe.
Durch Schreiben des Wertes 0 in diese
Speicherzelle können wir natürlich die
Schwarzen Streifen auch abschalten und
damit nur die Hintergrundfarbe anzeigen
lassen.
Um diese Vorgehensweise nun besser zu
erläutern haben wir natürlich wieder ein
Programmbeispiel auf Lager, das ich Ihnen nun auflisten möchte. Sie finden es
auf dieser MD auch als fertig ausführbares File mit dem Namen " SPRITES-ONLY", daß Sie wie immer mit ",8,1" laden und
durch ein " SYS4096" starten müssen.
Kommen wir also zur Initialiserungsroutine des Beispiels, die bei Adresse$1000 beginnt:
INIT: sei ;IRQs sperren lda #$7f ;Alle CIA-IRQs abschalten sta $dc0d ; (CIA1) sta $dd0d ; (CIA2) bit $dc0d ;CIA1-ICR löschen bit $dd0d ;CIA2-ICR löschen
lda #$01 ; VIC-Hintergrundstriche sta $3 fff ; auf 1 setzen jsr sprinit; Sprites initialiseren jsr irqinit; IRQ initialiseren
lda #$35 ;ROMs abschalte sta $01 cli ;IRQs erlauben spc:lda #$7f ;Auf SPACE-Taste sta $dc00 ; warten... lda $dc01 cmp #$ef bne spc sei ;IRQs sperren lda #$37 ;ROM wieder einschalten sta $01 jmp $fce2 ;und RESET auslösen
Alles in allem für uns keine besondere
Initialisierung. Wichtig sind noch die
Routinen " SPRINIT" und " IRQINIT" . In
ersterer initialisieren wir lediglich
die Sprite-Positionen, sowie die Sprite- Pointer und schalten alle acht Sprites ein. Letzere Routine ist für das
Korrekte initialisieren unseres IRQs
zurständig und sieht folgendermaßen aus:
IRQINIT:
lda #< bordirq ; IRQ-Vektor bei sta $ fffe ;$ FFFE/$ FFFF lda #> bordirq ; auf " BORDIRQ" verbiegen
sta $ ffff lda #$1 b ; Wert für $ D011 mit gel.
sta $ d011 ; High-Bit f. Rasterpos.
lda #$ fa ; Ersten Raster-IRQ bei
sta $d012 ; Zeile $FA auslösen lda #$81 ;VIC-Raster-IRQs sta $d01a ; erlauben dec $d019 ;VIC-ICR ggf. löschen rts
Wie Sie sehen, aktivieren wir hier einen
Raster-IRQ für Rasterzeile $ FA, der bei
Auftreten die Routine " BORDIRQ" anspringt, wo sich eine ganz gewöhnliche
Routine zum Abschalten des oberen und
unteren Bildschirmrandes befindet. Hier
das Listing dieser Routine:
BORDIRQ:
pha ;Prozessorregs. retten txa pha tya pha lda $d011 ;24-Zeilen-Darstellung and #$77 ; einschalten sta $d011 lda #$28 ;nächster IRQ bei Zeile $28 sta $d012 dec $d019 ;VIC-ICR löschen lda #<soff;Routine für nächsten IRQ sta $fffe ; "SOFF" in IRQ-Vektor lda #>soff; eintragen sta $ffff pla ;Prozessorregs. wieder vom tay ; Stapel holen und IRQ pla ; beenden tax pla rti
Wir schalten hier also an der üblichen
Position auf 24- Zeilen-Darstellung herunter, damit der VIC vergisst, den oberen und unteren Bildschirmrand zu zeichnen. Gleichzeitig wird ein neuer IRQ
initialisiert, der bei Erreichen von
Rasterzeile $28( acht Rasterzeilen vor
Beginn des sichtbaren Bildschirmfens- ters) die Routine " SOFF" anspringen
soll. Diese Routine übernimmt nun die
Aufgabe, die Darstellung der Charakterzeilen zu verhindern:
soff: pha ;Prozessorregs. retten txa pha tya pha
lda $ d011 ; Bild ausschalten ( durch and #$6 F ; ausmaskieren von Bit4) sta $ d011
lda #$32 ;nächster IRQ bei Raster- sta $d012 ; zeile $32 dec $d019 ;VIC-ICR löschen lda #<son ;Nächster IRQ soll auf sta $fffe ; Routine "SON" springen lda #>son sta $ffff pla ;Prozessorregs. wieder vom tay ; Stapel holen und IRQ pla ; beenden tax pla rti
Wie Sie sehen eine recht einfache Aufgabe: durch eine AND-Verknüpfung des
$ D011- Inhalts mit dem Wert $6 F wird einfach das 4 . Bit dieses Registers
gelöscht. Gleichzeitig löschen wir dabei
Bit 7, das ja das High-Bit der Rasterstrahlposition angibt, womit wir also
auch dieses Bit für den folgenden Raster- IRQ bei Zeile $32 vorbereitet hättem. Die Routine, die hier abgearbeitet
werden soll, heisst " SON" und ist für
das Wiedereinschalten des Bildschirms
verantwortlich. Da sich Rasterzeile $32 im sichtbaren Fenster befindet wäre somit der VIC überlistet und soweit gebracht, daß er keine Charakterzeilen mehr liest, geschweige denn darstellt.
Gleichzeitig schaltet diese Routine wieder auf die 25- Zeilen-Darstellung zurück
( Bit 3 von $ D011 muß gesetzt werden), damit das Abschalten des Borders auch im
nächsten Rasterstrahldurchlauf funktioniert:
SON: pha ;Prozessorregs. retten txa pha tya pha
lda $ d011 ; Bild und 25- Zeilenora #$18 ; Darstellung einschalten sta $ d011
lda #$fa ;nächster IRQ wieder bei sta $d012 ; Zeile $FA dec $d019 ;VIC-ICR löschen lda #<bordirq;Wieder "BORDIRQ" in sta $fffe ; IRQ-Vektor eintragen lda #>bordirq sta $ffff pla ;Prozessorregs. wieder vom tay ; Stapel holen und IRQ pla ; beenden tax pla rti
Nachdem Register $ D011 auf den gewünschten Wert zurückgesetzt wurde, wird der
IRQ wieder für die Routine " BORDIRQ" bei
Rasterzeile $ FA vorbereitet, womit sich
der Kreis schließt und unser Programmbeispiel komplett ist.
3)" ESCOS"- EINEN SCHRITT WEITER Nachdem wir nun unser Timing-Problem
gelöst, und die störenden Charakter-Zeilen aus dem Weg geräumt haben, möchten wir wieder zu unserer eigentlichen
Aufgabenstellung zurückkehren: dem bild- schirmfüllenden Darstellen von Sprites.
Hierzu müssen wir, nachdem oberer und
unterer Bildschirmrand, sowie die Charakterzeilen abgeschaltet wurden, zusätzlich auch noch die Bildschirmränder links und rechts deaktivieren. Das
Funktionsprinzip einer solchen Sideborderroutine sollte Ihnen noch aus einem
der ersten Kursteile im Kopf sein: durch
rechtzeitiges Umstellen von 40- auf 38- Spaltendarstellung und zurück tricksen
wir den VIC nach dem selben Prinzip aus, wie wir es bei den horizontalen Bildschirmrändern tun. Dadurch aber, daß
sich der Rasterstrahl horizontal recht
schnell bewegt, kommt es dabei auf ein
höchst exaktes Timing an. Einen Taktzyklus zu früh oder zu spät funktioniert
die Routine schon nicht mehr. Wenn man
das Ganze nun zusätzlich noch mit dem
Üffnen des oberen und unteren Randes, sowie dem Abschalten der Charakter-Zeilen und einem Sprite-Multiplexer kombinieren muß, so könnte man meinen, daß dies eine recht programmieraufwendige
Sache werden kann. Doch keine Panik, die
Umsetzung ist einfacher als Sie glauben.
Am Besten sehen Sie sich erst einmal das
Demoprogramm " ESCOS1" an. Es wird wie
üblich geladen und mit SYS4096 gestartet, und zeigt dann einen vollends mit
Sprites gefüllten Bildschirm - und zwar
über alle Ränder hinaus! Um so etwas nun
sebst zu programmieren, werden wir
zunächst auf die organisatirischen Probleme und deren Lösung eingehen:
Als Erstes müssen Sie davon ausgehen, daß wir keine IRQ-Routine im eigentlichen Sinne programmieren werden. Aufgrund des exakten Timings, das zum Abschalten des linken und rechten Randes
notwendig ist, muß der Prozessor nahezu
während des gesamten Rasterdurchlaufs
damit beschäftigt sein, die richtige
Rasterposition abzuwarten, um zwischen
der 38- und 40- Zeilen-Darstellung hinund herzuschalten. Dadurch wird ledi- glich ein einziger IRQ pro Rasterdurchlauf aufgerufen, nämlich direkt zu Anfang desselben, in Rasterzeile 0 . Dieser
IRQ muß nun, auf die uns schon bekannte
Art und Weise," geglättet" werden, so
daß kein einziger Taktzyklus Unterschied
zum vorherigen Rasterdurchlauf besteht.
Ab dann ( durch das Glätten, das 2 Rasterzeilen dauert also ab Zeile 2) beginnt der Prozessor damit in jeder einzelnen Rasterzeile den Rand abzuschalten. Dies geschieht dadurch, daß wir
nach jedem Umschalten der Spaltendarstellung genau am Öbergangspunkt zwischen sichtbarem Bildschirmfenster und
- rahmen, exakt 63 Taktzyklen verzögern, bevor dieser Vorgang wiederholt wird. So
zumindest sähe es aus, wenn wir keine
Sprites darzustellen hätten. Da wir dies
jedoch tun, müssen wir weiterhin berücksichtigen, daß der VIC zum Darstellen
der Sprites den Prozessor ebenso anhalten muß, wie beim Lesen der Charakterzeilen, um sich die Spritedaten aus dem Speicher zu holen. Hierbei braucht er 3 Taktzyklen für das erste Sprite, und
dann jeweils 2 Zyklen für alle weiteren, eingeschalteten Sprites. Da wir alle 8 Sprites benutzen, müssen wir also noch
den Betrag 3+7*2=17 von den 63 Zyklen
abziehen und erhalten somit exakt 46 Taktzyklen, die der Prozessor pro Rasterzeile " verbrauchen" muß.
( Anm. d. Red. : Bitte wählen Sie jetzt den zweiten Zeil des IRQ-Kurses aus dem
Textmenu.)
Gleichzeitig müssen wir berücksichtigen, daß alle 21 Rasterzeilen die Y-Position
der Sprites um diesen Betrag hochgezählt
werden muß, damit sie auch untereinander
auf dem Bildschirm erscheinen. Dies ist
glücklicherweise eine nicht allzu schwere Aufgabe, da der VIC uns diesbezüglich
ein wenig entgegenkommt. Ändern wir nämlich die X-Position eines Sprites innerhalb einer Rasterzeile, so hat dies eine
sofortige eine Wirkung auf die Spriteposition: das Sprite erscheint ab dieser
Rasterzeile an der neuen X-Position. Mit
der Y-Position verhält es sich anders:
wird sie noch während das Sprite gezeichnet wird geändert, so hat das keine
direkte Auswirkung auf die restlichen
Spritezeilen. Der VIC zeichnet das Sprite stur zu Ende, bevor er die Y-Position
nocheinmal prüft. Das gibt uns die Möglichkeit, die Y-Position schon im Vorraus zu ändern, nämlich irgendwann innerhalb der 21 Rasterzeilen, die das
Sprite hoch ist. Setzen wir die neue Y-Position nun also genau auf die Rasterzeile nach der letzen Spritezeile, so kümmert sich der VIC darum erst einmal gar nicht. Er zeichnet zunächst sein
aktuelles Sprite zu Ende, und wirft dann
erst einen Blick in das Y-Positions- Register des Sprites. Da er dort dann
eine Position vorfindet, die er noch
nicht überlaufen hat, glaubt er, das
Sprite wäre noch nicht gezeichnet worden, woraufhin er unverzüglich mit dem
Zeichnen wiederanfängt - und schon wäre
dasselbe Sprite zweimal untereinander
auf dem Bildschirm zu sehen! Dadurch
können wir uns in der Rasterroutine mit
der Neupositionierung Zeit lassen, und
selbige über zwei Rasterzeilen verteilt
durchführen ( innerhalb einer Zeile wäre
auch gar nicht genug Zeit dafür) .
Um die Bildschirmränder unsichtbar zu
machen muß sich ein Teil unserer IRQ-Routine um das Abschalten des oberen und
unteren Bildschirmrandes, sowie der Cha- rakterzeilen kümmern. Wie Sie im Programmbeispiel zuvor gesehen haben, ist
das eigentlich eine recht einfache Aufgabe. Da wir zum Abschalten der Ränder
links und rechts jedoch ein hypergenaues
Timing benötigen, wäre es recht aufwendig für die Rasterzeilen $ FA,$28 und
$32 eigene IRQ-Routinen zu schreiben, ohne dabei das Timing für die Sideborderabschaltung damit durcheinander zu
bringen. Aus diesem Grund haben wir uns
einen Trick ausgedacht, mit dem wir beide Aufgaben quasi " in einem Aufwasch" bewältigen: Wie Sie zuvor vielleicht
schon bemerkt haben, hatten alle IRQs
unserer Beispielroutine eins gemeinsam:
Sie erzielten den gewünschten Effekt
durch irgendeine Manipulation des Registers $ D011 . Warum sollten wir also
nicht aus zwei eins machen, und generell
in jeder Rasterzeile einen Wert in dieses Register schreiben? Das hilft uns
zum Einen das Verzögern des Rasterstrahls bis zum Ende der Rasterzeile,
und hat zudem den angenehmen " Nebeneffekt" die horizontalen Bildschirmränder, sowie die Charakterzeilendarstellung
quasi " automatisch" abzuschalten.
Nach all dieser trockenen Theorie möchte
ich Ihnen den Source-Code zu unserer
ESCOS-Routine nun nicht mehr länger vorenthalten. Die Init-Routine werden wir
uns diesmal sparen, da sie nahezu identlisch mit der des letzten Programmbeispiels ist. Wichtig für uns ist, daß Sie
den VIC darauf vorbereitet, einen IRQ in
Rasterzeile 0 auszulösen, bei dessen
Auftreten der Prozessor in die Routine
" SIDEBORD" zu springen hat. Selbige Routine befindet sich an Adresse $1200 und
sieht folgendermaßen aus:
SIDEBORD:
pha ;Prozessorregs. retten txa pha tya pha dec $d019 ;VIC-ICR löschen inc $d012 ;nächste Rasterz. neuer lda #<irq2 ; IRQ-Auslöser, mit Sprung sta $fffe ; auf "IRQ2" cli ;IRQs erlauben ch:nop ;13 NOPs während der der ... ;IRQ irgendwann auftritt nop jmp ch IRQ2: lda #$ff ;Alle Sprites, sowie sta $d015 ; X-Expansion sta $d01d ; einschalten clc lda $00FB ;"SOFTROLL" lesen adc #$02 ;Die Y-Position sta $d001 ; der Sprites sta $d003 ; befindet sich sta $d005 ; 2 Rasterzeilen sta $d007 ; nach RasterIRQ sta $d009 ; und muß demnach sta $d00b ; gesetzt werden. sta $d00d sta $d00f lda $d012 ;den letzen Zyklus cmp $d012 ;korrigieren bne onecycle
ONECYCLE:
pla ;Vom 2. IRQ erzeugte pla ;Rückspr.adr, u. CPU- pla ;Status v.Stapel entf. lda #<sidebord;IRQ-Ptr. wieder auf sta $fffe ; "SIDEBORD" zurück dec $d019 ;VIC-ICR löschen lda $FB ;Index für $D011- lsr ; Tabelle vorbereiten tay
Soweit also keine uns besonders unbekannte Routine. Der hier aufgezeigte
Auszug dient lediglich dem Glätten des
IRQs und sollte Ihnen, wenn auch leicht modifiziert, schon aus anderen Beispielen bekannt sein. Einzig zu erwähnender
Punkt wäre die Adresse $ FB. In dieser
Zeropageadresse steht die Nummer der
Rasterzeile, in der der IRQ aufgerufen
werden soll. Obwohl das bei uns zwar
immer der Wert 0 ist ( womit also immer 0 in diesem Register steht), hat dies einen entscheidenden Vorteil: durch einfaches Hochzählen dieses Registers um 1, nach jedem Rasterdurchlauf, wird der
Raster-IRQ jeweils eine Zeile später
erst auftreten, womit wir einen ganz
simplen Scrolleffekt erzielen. Zu sehen
ist dies auch im Beispiel " ESCOS2", das
absolut identisch zu " ESCOS1" ist, jedoch mit der oben genannten Änderung.
Gleichzeitig hat die Adresse $ FB noch
eine zweite Aufgabe: sie wird zusätzlich
als Index auf eine Liste " mißbraucht", in der die Werte stehen, die in $ D011 eingetragen werden müssen, um oberen und
unteren Bildschirmrand, sowie Charakterzeilen abzuschalten. Da im Prinzip nur drei verschiedene Werte in dieser Tabelle stehen, und zudem die Änderungen der
Werte nicht hundertprozentig genau in
einer speziellen Rasterzeile auftreten
müssen, sondern ruhig auch einmal eine
Rasterzeile früher oder später, haben
wir die Tabelle nur halb solang gemacht
und tragen nur jede zweite Rasterzeile
einen Wert von ihr in $ D011 ein. Dies
hat den zusätzlichen Vorteil, daß sie
nicht länger als 256 Byte wird und somit
keine High-Byte- Adressen berücksichtigt
werden müssen. Aus diesem Grund wird
also auch der Index-Wert, der ins Y-Register geladen wird, durch einen
" LSR"- Befehl halbiert, damit bei einer
Verschiebung automatsich die ersten Tabellenwerte übersprungen und somit auf
den " richtigen" ersten Tabelleneintrag
zugegriffen wird. Wichtig ist nur noch, daß die Tabelle unbedingt innerhalb eines 256- Byte-Blocks steht und an einer
Adresse mit Low-Byte- Wert 0 beginnt.
Durch die Yindizierte Adressierung, mit der wir auf sie zugreifen, könnte es im
anderen Fall zu Timing-Problemen kommen.
Greifen wir nämlich mit dem Befehl " LDA
$11 FF, Y" auf eine Speicherzelle zu, und
enthält in diesem Fall das Y-Register
einen Wert größer 1, so tritt ein Öberlauf bei der Adressierung auf
($11 FF+1=$1200- High-Byte muß hochgezählt werden!) . Ein solcher Befehl
benötigt dann nicht mehr 4 Taktzyklen, sondern einen Taktzyklus mehr, in dem
das High-Byte korrigiert wird! Dadurch
würden wir also unser Timing durcheinanderbringen, weswegen die Tabelle ab
Adresse $1500 abgelegt wurde und mit
Ihren 156 Einträgen keinen solchen Öberlauf erzeugt. Die Tabelle selbst enthält
nun, jeweils blockweise, die Werte $10,$00 und $18, die an den richtigen Rasterpositionen untergebracht wurden, so
daß die Ränder und Charakterzeilen beim
Schreiben des entsprechenden Wertes abgeschaltet werden. Sie können ja einmal
mit Hilfe eines Speichermonitors einen Blick hineinwerfen.
Wir werden uns nun um den Rest unserer
Routine kümmern, in der wir in 294 Rasterzeilen die seitlichen Ränder abschalten und zudem jede 21 . Rasterzeile
die Sprites neu positionieren, so daß
insgesamt 14 Zeilen zu je 8 Sprites auf
dem Bildschirm erscheinen. Dies ist die
Fortsetzung der SIDEBORD-IRQ- Routine:
nop ; Diese Befehlsfolge wird jsr open21 ; insgesamt 14 x aufgerufen
... ; um je 21 Zeilen zu öffnen ... nop ;Sprite Line 14 jsr open21 lda #$00 ;Sprites aus sta $d015
lda #$ c8 ;40- Spalten-Modus zurücksta $ d016 ; setzen lda $ FB ; Zeile aus $ FB für nächsten sta $ d012 ; Raster-IRQ setzen
pla ;Prozessorregs. zurückholen tay ; und IRQ beenden0 pla tax pla rti
Wie Sie sehen besteht der Kern unserer
Routine aus den 14 Mal aufeinander folgenden Befehlen:" NOP" und " JSR OPEN21" .
Der NOP-Befehl dient dabei lediglich dem
Verzögern. Die Unterroutine " OPEN21" ist
nun dafür zuständig, in den folgenden 21 Rasterzeilen ( genau die Höhe eines Sprites) den Rand zu öffnen, sowie die neuen
Y-Spritepositionen zu setzen. Sie sieht
folgendermaßen aus:
;line 00 OPEN21:
nop ; Verzögern bis rechter
nop ; Rand erreicht dec $d016 ;38 Spalten inc $d016 ;40 Spalten lda $1500,y ;$D011-Wert aus Tabelle sta $d011 ; lesen und eintragen iny ;Tabellen-Index+1 jsr cycles+5;24 Zyklen verzögern ;line 01 dec $d016 ;38 Spalten inc $d016 ;40 Spalten jsr cycles ;34 Zyklen verzögern
. . . ; dito für 2-15
Wie Sie sehen können, verzögern wir
zunächst mit zwei NOPs bis zum Ende des
sichtbaren Bildschirmfensters, wo wir
durch Herunterzählen von Register $ D016 die 38- Spalten-Darstellung einschalten, und gleich darauf durch Hochzählen desselben Registers wieder auf 40 Zeilen
zurückgehen. Damit wäre der Rand geöffnet worden. Als nächstes wird der Wert
für $ D011 aus der besagten Tabelle aus- gelesen und in dieses Register eingetragen, sowie der Index-Zähler im Y-Register für die Öbernächste Zeile um 1 erhöht. Danach wird die Routine " CYCLES" aufgerufen, jedoch nicht an ihrer eigentlichen Adresse, sondern 5 Bytes weiter. Diese Routine besteht aus insgesamt
11 NOPs, die lediglich die Zeit verzögern sollen, bis die nächste Rand-Abschaltung fällig wird. Da ein NOP 2 Taktzyklen verbraucht, verzögert sie 22 Taktyklen. Hier muß man zusätzlich noch
die Zeit hinzurechnen, die für den JSRund RTS-Befehl draufgehen. Beide verbrauchen je 6 Taktzyklen, womit die
" CYCLES"- Routine insgesamt 34 Zyklen in
Anspruch nimmt. Durch den Offset von 5 Bytes, den wir beim ersten Einsprung in
die Routine machen," übergehen" wir einfach die ersten 5 NOPs, womit nur 24 Zyklen verbraucht werden. Nach Rückkehr
aus dieser Routine befindet sich der
Rasterstrahl nun genau an der Position, an der wir den Rand für die neue Raster- zeile öffnen müssen, was sogleich auch
durch die INC-DEC- Befehlsfolge getan
wird. Anschließend wird wieder verzögert, wobei wir diesmal die " CYCLES"- Routine komplett durchlaufen, da in dieser Zeile kein neuer Wert in $ D011 eingetragen wird, und wir somit 10 Zyklen
mehr zu verzögern haben!
Dieser Programm-Auszug wiederholt sich
nun insgesamt noch 7 Mal, bis wir in die
16 . Spritezeile gelangt sind. Hier nun
beginnen wir mit dem Neupositionieren
der Sprites:
;line 16 dec $d016 ;Altbekannte Methode inc $d016 ; um Zeile 16 zu öffnen lda $1500,y ; u. $D011 neu zu setzen sta $d011 iny jsr cycles+5 ;line 17 dec $d016 ;38 Spalten inc $d016 ;40 Spalten clc ;Neue Y-Pos. für nächste lda $d001 ; Spr.-Reihe= Alte Y-Pos. adc #$15 ; plus 21 sta $d001 ;Und für Sprites 0-3 sta $d003 ; eintragen sta $d005 sta $d007 nop ;Nur 10 Zyklen verzögern nop nop nop nop ;line 18 dec $d016 ;Altbekannte Methode zum inc $d016 ; öffnen der 17. Zeile... lda $1500,y sta $d011 iny jsr cycles+5 ;line 19 dec $d016 ;38 Spalten inc $d016 ;40 Spalten clc ;Neue Y-Pos. für nächste lda $d009 ; Sprite-Reihe berechnen adc #$15 sta $d009 ;und für Sprites 4-7 sta $d00b ; setzen sta $d00d sta $d00f nop ;Nur 10 Zyklen verzögern nop nop nop nop ;line 20 dec $d016 ;38 Spalten inc $d016 ;40 Spalten lda $1500,y;Neuen Wert für $D011 aus sta $d011 ; Tab. holen u. eintragen iny ;Tab-Index+1 nop ;6 Zyklen verzögern nop nop rts ;Und fertig!
Wie Sie sehen, benutzen wir für die geraden Zeilennummern die alte Befehlsfolge zum Üffnen des Randes und Setzen des
neuen $ D011- Wertes. In den Spritezeilen
17 und 19 jedoch wird nach dem Üffnen
des Randes die Y-Position um 21 erhöht
und in je vier Y-Sprite- Register eingetragen, womit also die Y-Positionen der
nächsten Spritereihe festgelegt wären.
In Spritezeile 20 wird nun ein letztes
Mal der $ D011- Wert neu gesetzt und nach
einer kurzen Verzögerung wieder zum
Hauptprogramm zurückgekehrt, von wo die
Routine für alle 14 Spritereihen nochmal
aufgerufen wird, und womit unser ESCOS-Effekt fertig programmiert ist!
4) ABSCHLIESSENDE HINWEISE Wie schon erwähnt, ist der Prozessor zur
Anzeige dieser insgesamt 114 Sprites, sowie dem Abschalten der Bildschirmränder, fast den gesamten Rasterdurchlauf lang voll beschäftigt. Viel Rechenzeit
bleibt uns nicht mehr übrig, um weitere
Effekte zu programmieren. Dennoch sind
am Ende des Rasterdurchlaufs noch 16 Rasterzeilen zur freien Verfügung, und
man muß noch die Zeit hinzurechnen, in
der der Rasterstrahl vom unteren Bildschirmrand wieder zum oberen Bildschirmrand zu wandern hat, in der wir ebenfalls noch mit dem Prozessor arbeiten
können. Diese Zeit wurde im Beispiel
" ESCOS2" benutzt, um die Spritereihen
zusätzlich noch zu scrollen. Jedoch ist
auch noch ein Moviescroller problemlos
machbar. Dieses Thema werden wir jedoch
erst nächsten Monat ansprechen, und uns
auch weiterhin mit den Sprites befassen.
Sie werden dabei eine Möglichkeit kennenlernen, wie man diese kleinen Grafikwinzlinge hardwaremässig dehnen und verbiegen kann. Wie immer gibt es dabei
einen Trick, den VIC zu überreden das
Unmögliche möglich zu machen. . .
( ih/ ub)
IRQ-KURS "Die Hardware ausgetrickst..." (Teil 12)
Herzlich Willkommen zu einer neuen Folge
unseres IRQ-Kurses. In dieser Ausgabe
werden wir uns weiter mit der ESCOS-Routine des letzten Kursteils beschäftigen. Wir werden hierbei die Routine mit
einem Movie-Scroller verbinden, um so
einen bildschirmübergreifenden Text
scrollen zu lassen. Dies gestaltet sich
ohne einen speziellen Trick gar nicht
mal so einfach. . .
1) EINLEITUNG Wie Sie sich vielleicht noch erinnern, so hatten wir in der letzten Folge des
IRQ-Kurses zuletzt das Beispielprogramm
" ESCOS2" besprochen. Diese Raster-IRQ- Routine öffnete uns den linken und rechten, sowie den oberen und unteren Bild- schirmrand und stellte dann alle 21 Rasterzeilen eine neue Reihe mit je acht
Sprites auf dem Bildschirm dar. Dadurch
hatten wir insgesamt vierzehn Zeilen zu
je acht Sprites auf dem Bildschirm, die
alle nahtlos aneinandergereiht fast den
gesamten Bildschirm überdeckten. Der
einzige ungenutzte Bereich war der von
Rasterzeile 294 bis 312, der zu klein
war um eine weitere Spritereihe darin
unterzubringen, aber sowieso schon unterhalb der für normale Monitore darstellbaren Grenze liegt.
Der einzige Unterschied der Routine " ES-COS2" zu " ESCOS1" bestand nun darin, daß
erstere Routine lediglich ein Zeilenoffset- Register für den ersten auszulösenden Raster-IRQ herunterzählte, um so
einen Scroll-Effekt der Spritereihen zu
erzielen." ESCOS2" ist also schon eine
Art " Moviescroller" . Der Grund, warum
sie es nicht wirklich ist, liegt darin, daß in jeder Spritezeile dieselben Sprites auf dem Bildschirm zu sehen waren.
Wie wir aber von den Moviescroll-Routinen wissen, müssen wir für einen
Scrolltext das Aussehen einer Spritezeile ja individuell bestimmen können, damit auch wirklich ein Text, und nicht
immer dieslbe Zeile, über den Bildschirm
läuft.
2) DAS PROBLEM - DIE LÜSUNG " Kein Problem" werden Sie nun sagen," einfach die Textausgaberoutine der Moviescroller- Routinen vom IRQ-Kurs einbauen, und schon haben wir einen Scrolltext" . Diese Feststellung stimmt schon, wie sollte es auch anders gehen, jedoch
stellt sich uns dabei noch ein kleines
Problem in den Weg: dadurch nämlich, daß
wir in den ESCOS-Routinen, keine Zwischenräume mehr zwischen den einzelnen
Spritezeilen haben, gestaltet es sich
als schwierig, die Sprite-Pointer zeitgenau zu setzen. Setzen wir diese nämlich auf die neu einzuscrollende Sprite- zeile, noch bevor die letzte Spritezeile
abgearbeitet wurde, so erscheinen die
alten Sprites schon im Aussehen der
Neuen. Umgekehrt können die Spritepointer auch nicht nach Abarbeiten der letzten Spritezeile gesetzt werden da hier
ja schon die neuen Sprites erscheinen
müssen. Man könnte nun versuchen, ein
megagenaues Timing zu programmieren, das innerhalb der ersten Rasterzeile
einer neuen Spritezeile exakt vor Darstellen eines neuen Sprites dessen Pointer neu setzt. Dies gestaltet sich jedoch umso umständlicher, wenn wir beachten müssen, daß gleichzeitig auch noch
der linke und rechte Rand geöffnet werden soll. Da dieser ebenfalls ein exaktes Timing verlangt, würden wir mit dem
Doppeltiming in des Teufels Küche kommen. Aber keine Sorge: glücklicherweise
können wir als IRQ-Magier wieder in die
Raster-Trickkiste greifen, und eine
weitaus einfachere Lösung des Problems
anwenden.
Wie Sie z. B. schon von unseren FLI-Routinen wissen, hat man mit dem VIC die
Möglichkeit, das Video-RAM innerhalb
seines Adressbereiches von 16 KB zu verschieben. Der Speicherbereich, den der
Grafikchip dazu verwendet, um den Inhalt
des Textbildschirms aufzubauen muß also
nicht zwingenderweise bei $0400 liegen, wo er sonst nach dem Einschalten des
Rechners untergebracht ist. Durch die
Bits 4-7 des Registers $ D018 können insgesamt 16 verschiedene Speicherbereiche
gewählt werden, deren Basisadressen in
$0400- Schritten von $0000-$3 C00 gehen.
Nun werden Sie fragen, was denn das Video- RAM mit unserer ESCOS-Routine zu tun
hat, zumal wir die Bildschirmdarstellung
doch sowieso abgeschaltet haben, und
keinen Text auf dem Bildschirm haben?
Nun, ganz einfach: wo befinden sich denn
die Register der Spritepointer normalerweise? Natürlich in den Adressen von
$07 F8-$07 FF. Und genau diese Adresse liegen am Ende des Video-RAMs. Verschiebt man nun das Video-RAM an eine
andere Adresse, so verschiebt man automatisch auch die Registeradressen der
Spritepointer! Wird das Video-RAM also
beispielsweise um $0400 Bytes nach $0800 verschoben, so bewegen sich die Spritepointer- Register ebenfalls um $0400- Bytes nach vorne. Um nun also das Aussehen der acht Sprites zu definieren, müssen die Adressen $0 BF8-$0 BFF beschrieben
werden. Und genau das ist die Lösung
unseres Problems. Da es während des
Spriteaufbaus zu lange dauert, alle acht
Spritepointer in den Akku zu laden und
in die Register zu schreiben, soll das
die Initialisierungsroutine des Moviescrollers übernehmen. Hierbei schreibt
letztere die benötigten Werte für eine
Spritezeile in die Pointerregister eines
verschobenen Video-RAMs. Während der
Darstellung brauchen wir nun nur noch
durch Schreiben eines einzigen Bytes, nämlich des ensprechenden Wertes für ein neues Video-RAM in $ D018, auf selbiges
umzuschalten. Dadurch, daß der VIC für
die neue Spritezeile nun in einem ganz
anderen Video-RAM arbeitet, holt er sich
auch die Spritepointer aus den neuen
Adressen, womit wir alle acht Spritepointer mit nur einem Schreibzugriff
umgeschaltet hätten!
Die Umsetzung dieser Lösung in die Praxis gestaltet sich für uns sehr einfach.
Sinnigerweise haben wir nämlich in den
Routinen " ESCOS1" und " ESCOS2" schon
Platz für diese Änderung gelassen. Wie
Sie sich vielleicht erinnern, hatten wir
zur Darstellung der Sprites innerhalb
der IRQ-Routine dieser beiden Beispielprogramme die Unterroutine " OPEN21" vierzehnmal aufgerufen. Sie wird immer
in der ersten Rasterzeile einer neuen
Spritezeile angesprungen, und übernimmt
das Neusetzen der Y-Koordinaten aller
Sprites, sowie das Üffnen des Sideborders in den folgenden 21 Rasterzeilen.
So sah der Aufruf in den beiden Bei- spielprogrammen aus:
nop ; Diese Befehlsfolge wird jsr open21 ; insgesamt 14 x aufgerufen
... ; um je 21 Zeilen zu öffnen nop ;Sprite Line 14 jsr open21
Die " NOP"- Befehle dienten dabei nur der
Verzögerung um zwei Taktzyklen, damit
die Änderung zum richtigen Zeitpunkt
eintritt. Wir können diese Befehle mit
jedem anderen Befehl ersetzen, der nur 2 Takte in Anspruch nimmt. Diese Tatsache
machen wir uns für den Moviescroller
zunutze. Wir ersetzen die NOPs mit LDA-Befehlen, wobei der Wert, der in den
Akku geladen wird, dem Wert entspricht, der in Register $ D018 geschrieben werden
muß, um auf das neue Video-RAM umzuschalten. Er dient quasi als Parameter
für die " OPEN21"- Routine. Demnach sieht
der Aufruf dieser Routine, aus dem Raster- IRQ heraus, nun folgendermaßen aus:
v00 : lda #$00*$10 ; Code für Scr0($0000) jsr open21 ;21 Rasterzeilen öffnen
v01 : lda #$01*$10 ; Code für Scr0($0400) jsr open21 ;21 Rasterzeilen öffnen
v02 : lda #$02*$10 ; Code für Scr0($0800) jsr open21 ;21 Rasterzeilen öffnen
... v0d:lda #$0d*$10 ;Code für Scr13 ($3400) jsr open21 ;21 Rasterzeilen öffnen
Wie schon in den ESCOS-Beispielen, so
wiederholt sich auch hier die LDA-JSR- Befehlsfolge 14 Mal, wobei jeweils der
nächste Video-RAM- Bereich als Parameter
im Akku übergeben wird. Timingmässig hat
sich nichts geändert, da der NOP-Befehl
genausolange braucht wie der LDA-Befehl.
Eine ähnliche Modifikation haben wir nun
für das Schreiben dieses Akku-Wertes in
Register $ D018 vorgenommen. Diese Aufgabe soll von der Routine " OPEN21" durchgeführt werden. Hier ein Auszug der er- sten Zeilen dieser Routine aus dem Beispiel " ESCOS2" :
open21 :
nop ;Verzögern bis rechter nop ; Rand erreicht dec $d016 ;38 Spalten (Rand inc $d016 ;40 Spalten öffnen) lda $1500,y ;$D011-Wert aus Tabelle sta $d011 ; lesen und eintragen iny ;Tabellen-Index+1 jsr cycles+5;24 Zyklen verzögern
. . .
Wie Sie sehen, stehen hier ebenfalls
zwei NOP-Befehle am Anfang der Routine.
Sie benötigen 4 Taktzyklen, was für einen " STA $ XXXX"- Befehl ebenfalls zutrifft. Die zwei NOPs wurden für den
Moviescroller nun mit einem " STA $ D018" ersetzt:
open21 :
sta $ d018 ; VRAM f. SprPtrs. versch.
dec $d016 ;38 Spalten (Rand inc $d016 ;40 Spalten öffnen) lda $1500,y ;$D011-Wert aus Tabelle sta $d011 ; lesen und eintragen iny ;Tabellen-Index+1 jsr cycles+5;24 Zyklen verzögern
. . .
Damit hätten wir also die ESCOS-Routine
so umprogrammiert, daß Sie uns in jeder
der 14 Spritezeilen auch neue Spritepointer setzt. Es müssen nun noch zwei
weitere Änderungen gemacht werden, damit
der Moviescroller auch voll funktionstüchtig ist.
Zunächst einmal muß die Initialierungsroutine erweitert werden. Sie soll uns
die Spritepointer der benutzten Video-RAM- Adressen auf bestimmte Sprites vorinitialisieren, so daß später zur Darstellung einer bestimmten Spritereihe
nur noch die Nummer des zu benutzenden
Video-RAMs in den Labeln " V00" bis " V0 D" der Raster-IRQ- Routine ( siehe oben) ein- getragen werden muß, und die Routine
somit automatisch den richtigen Bildschirm zur korrekten Darstellung der
entsprechenden Spritezeile wählt.
Zunächst einmal wollen wir vereinbaren, daß wir den 16 K-Adressbereich des VICs
von $0000-$3 FFF um eins nach oben in den
Bereich von $4000-$7 FFF verschieben.
Dadurch stört uns die Zeropage, die normalerweise ja auch im Bereich des VICs
liegt, nicht mehr, und wir haben volle
16 KB zur Speicherung von Sprites und
Spritepointer-Video- RAM zur Verfügung.
Die Verschiebung wird durch Schreiben
des Wertes 2 in das Portregister A von
CIA-B erreicht. Innerhalb der Initialsierung wurde also folgende Befehlssequenz hinzugefügt:
LDA #$02 STA $DD00
Damit befindet sich der Datenbereich für
den VIC nun im Bereich von $4000-$7 FFF.
Beachten Sie bitte auch, daß nun der
Bytewert, den der VIC in " ausgetricksten" Rasterzeilen darstellt, nicht mehr
in $3 FFF, sondern in $7 FFF abgelegt werden muß ( im Beispielprogramm enthält er
den Wert $81, womit vertikale Lininen
hinter dem Scrolltext erscheinen) . Nun
folgt der Teil, der die Spritepointer
setzt. Hier treffen wir die Konvention, daß die Sprites, die durch die Pointer
einer Video-RAM- Bereichs dargestellt
werden auch innerhalb dieses Video-RAMs
unterbracht werden sollen. Liegt dieses
also Beispielsweise bei $4400, so soll
Spritepointer 0 auf das Sprite bei $4400( Blocknr.16), Spritepointer 1 auf das
Sprite bei $4440( Blocknr.17), usw., zeigen. Dies bewerkstelligt nun der folgende Teil der Init-Routine:
lda #$f8 ;ZP-Zgr. $02/03 mit sta $02 ; $43F8 init. lda #$43 sta $03
lda #($0000/64) ; Ptr. f. Sprite-Line01 jsr setpoint ; setzen lda #($0400/64) ; Ptr. f. Sprite-Line02 jsr setpoint ; setzen lda #($0800/64) ; Ptr. f. Sprite-Line03 jsr setpoint ; setzen
. . .
lda #($3000/64) ; Ptr. f. Sprite Line13 jsr setpoint ; setzen lda #($3400/64) ; Ptr. f. Sprite Line14 jsr setpoint ; setzen
Wie Sie sehen wird lediglich ein Adressierungszeiger in der Zeropage initialisiert, und dann 14 Mal die Unterroutine
" SETPOINT" aufgerufen, wobei im Akku der
Inhalt für den jeweils ersten Spritepointer übergeben wird. Hier nun die
Routine " SETPOINT", die die eigentlichen
Werte in die Pointerregister schreibt:
SETPOINT:
ldy #$00 ; Index-Reg. init.
sta ($02), y; SprPtr0 ablegen
clc ;Akku=Akku+1 adc #$01 iny ;Index=Index+1 sta ($02),y;SprPtr1 ablegen clc ;Akku=Akku+1 adc #$01 iny ;Index=Index+1 sta ($02),y;SprPtr2 ablegen ... clc ;Akku=Akku+1 adc #$01 iny ;Index=Index+1 sta ($02),y;SprPtr7 ablegen clc ;Auf Hi-Byte des $02/$03 lda $03 ; Zeigers den Wert 4 add. adc #$04 ; um auf nächstes VRAM zu sta $03 ; positonieren rts
Wie Sie sehen, so wird vom Basiswert des
ersten Spritepointers an, acht Mal jeweils um eins hochgezählt und das Ergebnis über den Vektor bei $02/$03 in die entsprechende Sprite-Pointer- Adresse
geschrieben. Beim ersten Durchlauf zeigt
dieser Vektor auf Adresse $43 F8, wo sich
die Spritepointer des ersten Video-RAM- Bereichs befinden. Am Ende der Pointerinitialisierung wird die Vektoradresse
nun um $0400 erhöht ( auf Hi-Byte wird
$04 addiert), damit beim nächsten Durchlauf die Zeiger des nächsten Video-RAM- Bereichs gesetzt werden.
( Anm. d. Red. : Bitte wählen Sie jetzt den
zweiten Teil des IRQ-Kurses aus dem Textmenu)
Fortsetzung IRQ-Kurs 12 - 2. Teil
Nachdem nun die Spritepointer vorinitialisiert wurden, muß die " MOVESPR"- Routine, die Sie bestimmt noch von unseren alten Moviescroller-Routinen her
kennen, so modifiziert werden, daß sie
die IRQ-Routine derart ändert, daß in
jeder Spritezeile auch der richtige Video- RAM-Bereich eingeschaltet wird. Da
die " MOVESPR"- Routine schon im 10 . Teil
des IRQ-Kurses ausfühlich beschrieben
wurde soll hier nur das kommentierte
Listing die Funktionsweise der Routine
wieder ins Gedächtnis rufen. Der eigentliche, für uns wichtige Teil der Routine
befindet sich am Ende und heißt " ROL-LON" . Er wurde der ESCOS-Routine angepasst und soll anschließend aufgeführt
werden. Zunächst jedoch die MOVESPR-Routine:
movespr:
lda softroll ; Scroll-Offs. holen sbc #$01 ; Und 1 subtrahieren sta softroll ; neuen Scroll-Offs. abl.
bpl rollon ; Wenn >0, dann weiter
newline:
inc showline ;1 . Spr-Zeilen- Ind. erh.
sec ; C-Bit f. Subtr. setzen lda showline ; Showline holen sbc #$0 d ; und 13 subtr.
bmi noloop ; Bei Unterlauf weiter sta showline ; Sonst Wert abl.
noloop:
lda tpoint cmp #<estext+($34*$18) bne continue lda tpoint+1 cmp #<estext+($34*$18) bne continue lda #$00 sta showline lda #<estext sta tpoint+0 lda #>estext sta tpoint+1
continue:
clc ;C-Bit f.Add. löschen lda softroll;SOFTROLL laden adc #$18 ;24 addieren sta softroll;und wieder ablegen lda #$20 ;Op-Code für "JSR" sta mt ;in MT eintragen
Ab dem Label " NEWLINE" ist die Routine
damit beschäftigt, die neue Spritezeile
aufzubauen, sowie die Zeiger für diese
Zeile neu zu setzen. Nach ihrer Abarbeitung gelangt sie automatisch zum Teil
" ROLLON", zu dem auch am Anfang verzweigt wird, wenn keine neue Spritezeile
eingefügt werden muß. Hier geschehen nun
die für uns wesentlichen Dinge, nämlich
das Voreinstellen der richtigen Video-RAM- Werte innerhalb der IRQ-Routine.
Zunächst das Listing:
rollon:
lda softroll ; nächsten Rastersta $ d012 ; IRQ vorbereiten
ldy showline ; SprZeilen-Ind. holen
lda pointer+$00, y; Wert f.1 . SprZeile
sta v00,+1 ; in IRQ setzen lda pointer+$01, y; Wert f.2 . SprZeile
sta v01,+1 ; in IRQ setzen
. . .
lda pointer+$0 D, y; Wert f.14 . SprZeile
sta v0 D,+1 ; in IRQ setzen rts
Alles in Allem also keine schwierige
Aufgabe. Zur Erinnerung sollte ich noch
erwähnen, daß die beiden Labels " SOFT-ROLL" und " SHOWLINE" für die Zeropageadressen $ F8 und $ F9 stehen." SOFTROLL" ist dabei ein Zähler für den Scrolloffset, der pro Bildschirmdurchlauf einmal
erniedrigt wird. Dieser Zähler gibt
gleichzeitig auch die Rasterzeile an, an
der der nächste Raster-IRQ auftreten muß. Dadurch, daß dieser Wert am Anfang
der " MOVESPR"- Routine um eins erniedrigt
wurde, und er hier nun als Auslöser für
die nächste Rasterzeile eingetragen
wird, erreichen wir den Scrolleffekt, da
die Spritezeilen pro Rasterdurchlauf
immer 1 Rasterzeile früher dargestellt
werden, solange bis " SOFTROLL" als Zähler einen Unterlauf meldet, und wieder
auf 21 zurückgesetzt wird. Gleichzeitig
muß dann die soeben weggescrollte Spritezeile am unteren Bildschirmrand, mit
neuem Text wiedereingefügt werden, was
vom Abschnitt " NEWLINE" schon durchgeführt wurde." SHOWLINE" ist ein Zeiger
auf die Tabelle " POINTER" . Er gibt an, welche der 14 Spritezeilen momentan an
der obersten Position steht. Er wird bei
einem " SOFTROLL"- Unterlauf um eins hochgezählt, da nun ja die ehemals zweite
Spritezeile zur Ersten wird. Damit tut
unser neuer " ROLLON"- Teil nichts anderes, als für jede Spritezeile einen Wert
aus der Tabelle zu lesen und in eins der Labels von " V00" bis " V0 D" einzutragen.
Wie aus dem obigen Auszug der IRQ-Routine ersichtlich, so ist dies jeweils
der LDA-Befehl, der vor jedem " JSR
OPEN21" steht. Da wir auf die Labeladressen den Wert 1 addieren, schreiben
wir also einfach einen neuen Operanden
für den LDA-Befehl in das Programm, so
daß die IRQ-Routine automatisch den
richtigen Pointerwert übergibt. Die
Pointertabelle sieht nun wiefolgt aus:
POINTER:
.by $00,$10,$20,$30 .by $40,$50,$60,$70 .by $80,$90,$A0,$B0 .by $C0,$D0 .by $00,$10,$20,$30 .by $40,$50,$60,$70 .by $80,$90,$A0,$B0 .by $C0,$D0
Wie Sie sehen, enthält sie einfach die
benötigten Werte für das VIC-Register$ D018, um der Reihe nach die Video-RAM- Bereiche zwischen $4000 und $7400 einzuschalten. Die Tabelle enthält, wie auch
schon bei den alten Moviescroller-Routinen, alle Pointerdaten doppelt, damit " SHOWLINE" auch bis zum Wert 13 hochgezählt werden kann, und dennoch die
Werte korrekt gesetzt werden.
Damit wären nun alle relevanten Teile
der Änderung einer ESCOS-Routine zum
Moviescroller erklärt worden. Wie Sie
sehen, haben wir auf recht einfach Art
und Weise zwei verschiedene Rastereffekte miteinander kombiniert, ohne große
Änderungen vornehmen zu müssen. Das Ergebnis ist quasi ein Moviescroller mit
ESCOS-IRQ- Routine. Auf diese Weise ist
es auch möglich ganz andere Effekte miteinander zu kombinieren, was Ihre Phantasie bezüglich der Raster-IRQ- Programmierung ein wenig beflügeln soll.
Das hier besprochene Programmbeispiel
befindet sich wie immer auch auf dieser
MD und kann von Ihnen begutachtet werden. Es heißt " MOVIE V1 .2" und muß mit
",8,1" geladen und durch ein " SYS4096" gestartet werden. Im nächsten Kursteil
werden wir dann die, schon versprochenen, Routinen zum Dehnen von Sprites
besprechen.
(ih/ub)
IRQ-KURS "Die Hardware ausgetrickst..." (Teil 13)
Herzlich Willkommen zum dreizehnten Teil
unseres IRQ-Kurses. Auch diesen Monat
soll es um das trickreiche manipulieren
von Sprites gehen. Wir werden uns einige
Routinen zum Dehnen von Sprites anschauen, und dabei lernen, daß es auch
Sprites gibt, die mehr als 21 Rasterzeilen hoch sind. . .
1) DAS PRINZIP Wie immer kommen wir zu Beginn zum Funktionsprinzip des Rastereffektes dieses
Kursteils: Wie Sie vielleicht wissen, so
ist die Größe eines Sprites prinzipiell
auf 24 x21 Pixel begrenzt. Diese Größe
ist starr und eigentlich nicht veränderbar. Es existieren jedoch 3 Sonderfälle, in denen das Sprite auch größer sein kann, nämlich dann, wenn wir mit Hilfe
der Register $ D017 und $ D01 D die Xund
Y-Expansion eines Sprites einschalten.
In diesem Fall kann sowohl die Xals
auch die Y-Ausdehnung verdoppelt werden, so daß wir ein Sprite mit einer maximalen Größe von 48 x42 Pixeln erhalten, in
dem die normalen 24 x21 Pixel einfach
doppelt dargestellt werden. Wie Sie also
sehen ist der VIC rein theoretisch doch
in der Lage größere Sprites auf den
Bildschirm zu zaubern. Jedoch auch dies
nur in höchst beschränkter Form, da wir
nun ebenfalls an eine fixe Auflösung
gebunden sind. Wir werden allerdings
gleich ein Verfahren kennenlernen, mit
dem es uns möglich sein wird, zumindest
die Y-Auflösung eines Sprites variabel
festzulegen.
Drehund Angelpunkt dieses Effektes
wird das eben schon angesprochene Register zur Y-Expansion der Sprites ($ D017) sein. Wollen wir zunächst einmal klären, wie der VIC die Sprites überhaupt dar- stellt: Nehmen wir also an, daß wir ein
Sprite auf dem Bildschirm darstellen
möchten. Zunächst einmal nicht expandiert. Der VIC vergleicht nun jede Rasterzeilennummer mit der Y-Position des
Sprites. Sind beide Werte gleich, so hat
er die Rasterzeile erreicht, in der die
oberste Linie des Sprites zu sehen sein
soll. Es wird nun eine interne Schaltlogik aktiviert, die dem VIC zu Beginn
einer jeden Rasterzeile die Adresse der
nächsten drei Datenbytes an den Datenbus
legt und ihn somit jedesmal mit den in
dieser Zeile für dieses Sprite relevanten Daten füttert. Die Schaltlogik
verfügt nun für jedes Sprite über ein
1- Bit-Zähl-, sowie ein 1- Bit-Latch- Register, die beide bei einem Schreibzugriff auf das Register $ D017, mit dem
Zustand des Bits zum zugehörigen Sprite
neu initialisiert werden. Das Latch-Register dient dabei als " Merkhilfe" für
den Zustand der Y-Expansion des Sprites.
Enthält es den Bitwert 1, so soll das Sprite in Y-Richtung verdoppelt werden, enthält es den Wert 0, so soll es normal
dargestellt werden. Ab der Rasterzeile, ab der das Sprite nun gezeichnet werden
soll legt die Schaltlogik nun zunächst
die aktuelle Spritedatenadresse an den
Bus und füttert den VIC so mit den Spritedaten für die erste Rasterzeile.
Gleichzeitig wird bei Erreichen der
nächsten Rasterzeile der 1- Bit-Zähler um
eins erniedrigt. Tritt dabei ein Unterlauf auf, so reinitialisiert die Schaltlogik den Zähler wieder mit dem Inhalt
des Latch-Registers und erhöht die Quelladresse für die Speitedaten um 3 Bytes, so daß Sie anschließend dem VIC die Daten der nächsten Spritezeile zukommenlässt. Tritt kein Unterlauf auf, weil
der Zähler auf 1 stand und beim Herunterzählen auf 0 sprang, so bleibt die
Quelladresse der Spritedaten unverändert, so daß der VIC auch in dieser Rasterzeile dieselben Spritedaten liest, die er auch eine Rasterzeile vorher schon darstellte. Ist die Spriteexpansion nun abgeschaltet, so wurden Latch
und Zähler zu Beginn mit dem Wert 0 gefüttert. In dem Fall läuft der Zähler
in jeder Rasterzeile unter und somit
bekommt der VIC in jeder Rasterzeile
neue Spritedaten zugewiesen. Ist die
Y-Expansion eingeschaltet, so enthalten
Zähler und Latch den Wert 1 und es wird
fortlaufend nur in jeder zweiten Rasterzeile eine neue Adresse an den Datenbus
angelegt, womit der VIC das Sprite in
der Vertikalen doppelt so hoch darstellt.
Wie nun eingangs schon erwähnt so werden
sowohl Latch als auch Zähler neu initialisiert, sobald ein Schreibzugriff auf
Register $ D017 erfolgt. Dadurch haben
wir also auch direkten Einfluß auf den
Y-Expansions- Zähler. Was sollte uns nun
also davon abhalten, diesen Zähler in
JEDER Rasterzeile durch Setzen des Y-Expansionsbits des gewünschten Sprites
wieder auf 1 zurückzusetzen, so daß die Schaltlogik nie einen Unterlauf erzeugen
kann, und somit immer wieder dieselben
Spritedaten angezeigt werden? Und ganz
genau so können wir ein Sprite länger
strecken als es eigentlich ist und z. B.
3-,4-, oder 5- fache Expansion des Sprites bewirken ( indem jede Spritezeile 3-,4- oder 5- mal hintereinander dargestellt
wird) ! Wir haben sogar die Möglichkeit
jede Spritezeile beliebig, und voneinander unabhängig oft, zu wiederholen, so
daß man z. B. auch eine Sinuswelle über
das Sprite laufen lassen kann! Hierzu
muß lediglich exakt zu Beginn einer Rasterzeile entweder das Expansionsbit
gesetzt werden, wenn die Rasterzeile
dieselben Spritedaten enthalten soll wie
die letzte Rasterzeile, oder wir Löschen
das Expansionsbit des gewünschten Sprites, um die Daten der nächsten Spritezeile in den VIC zu holen!
2) PROGRAMMBEISPIELE 1 UND 2
Um einen Eindruck von den Möglichkeiten
zu bekommen, die uns dieser Effekt bietet, sollten Sie sich einmal die Beispielprogramme " STRETCHER.1" bis " STRET-CHER.4" auf dieser MD anschauen. Sie
werden alle wie immer mit LOAD" Name",8,1 geladen und durch ein " SYS4096" gestartet. Die ersten beiden Beispiele stellen
ein Sprite dar, das normalerweise nur
eine diagonale Line von der linken oberen Ecke zur rechten unteren Ecke des
Sprites enthält. Im ersten Beispiel haben wir lediglich einige dieser Zeilen
mehrfach dargestellt. Das zweite Beispiel enthält eine Streckungstabelle, mit der wir jede Rasterzeile in Folge
1-,2-,3-,4-,5-, und 6- Mal darstellen, womit die Linie in etwa die Rundungen einer Sinuskurve bekommt!
Wollen wir uns nun einmal den Programmcode anschauen, den wir zur Erzeugung
der Verzerrung in Beispiel " STRETCHER.1" verwenden. Die Initialisierung und den
Beginn der IRQ-Routine möchte ich wie immer aussparen, da beides absolut identisch mit unseren anderen IRQ-Beispielen
ist. Wir legen hier den Raster-IRQ auf
Rasterzeile $82 fest und schalten Betriebssystem- ROM ab, um direkt über den
IRQ-Vektor bei $ FFFE/$ FFFF zu springen.
Die IRQ-Routine selbst beginnt nun ab
Adresse $1100, wo zunächst unser altbekannter Trick zum Glätten des IRQs aufgeführt ist. Der für uns wesentliche
Teil beginnt wie immer ab dem Label
" ONECYCLE", ab den der IRQ geglättet
wurde, und die für uns relevanten Routinenteile stehen. Zusätzlich sei erwähnt, daß wir gleichzeitig, um Timingprobleme
zu vermeiden, eine FLD-Routine benutzen
um die Charakterzeilen wegzudrücken und
gleichzeitig den linken und rechten
Bildschirmrand öffnen, damit wir in späteren Beispielen auch Sprites in diesen
Bereichen darstellen und sehen können.
Hier nun jedoch zunächst der Source-Code:
onecycle:
lda #$18 ;1. Wert für FLD sta $d011 ; in $D011 schreiben lda #$f8 ;Nächsten IRQ bei Raster- sta $d012 ; zeile $f8 auslösen dec $d019 ;VIC-ICR löschen lda #21*5 ;Zähler f. FLD init. (21*5= sta $02 ; 5-fache Spritehöhe) nop ;Verzögern.. ldx #$00 ;Tabellenindex init.
fldloop:
lda ad017,x;Wert aus Stretch-Tab lesen sta $d017 ; und in Y-Exp. eintragen lda ad011,x;Wert aus FLD-Tab lesen sta $d011 ; und in $D011 eintragen dec $d016 ;38 Spalten (Rand inc $d016 ;40 Spalten öffnen) nop ;Bis zum Anfang der nop ; nächsten Rasterzeile nop ; verzögern... nop nop nop nop nop nop lda #$00 ;Y-Exp. auf 0 sta $d017 ; zurücksetzen inx ;Tab-Index+1 cpx $02 ;Mit FLD-Zähler vgl. bcc fldloop;Kleiner, also weiter lda #$82 ;Nächster IRQ bei Rasterz. sta $d012 ; $82 ldx #$00 ;IRQ-Vektoren ldy #$11 ; auf eigene stx $fffe ; Routine sty $ffff ; umstellen lda #$0e ;Farben auf hellblau/ sta $d020 ; schwarz setzen lda #$00 sta $d021 pla ;Prozessorregs. zurück- tay ; holen pla tax pla rti ;IRQ beenden
Ab dem Label ONECYCLE setzen wir
zunächst einige Basiswerte für die Sprite- Stretch, FLDund Sideborderroutinen.
Hierzu schreiben wir erst einmal die
Anzahl der Rasterzeilen, in denen diese
drei Effekte aktiv sein sollen als Vergleichszähler in Adresse $02 und löschen
das X-Register, das als Index auf die
FLDund Sprite-Stretch- Tabellen dienen
soll ( dazu später mehr) . Das Setzen des
nächsten IRQ-Auslösers auf Rasterzeile
$ F8 ist lediglich ein Relikt aus älteren
IRQ-Routinen, in denen wir den unteren
und oberen Rand des Bildschirms öffneten. Da wir später jedoch wieder Rasterzeile $82 als IRQ-Auslöser festlegen, ist diese Befehlsfolge eigentlich unnötig. Dennoch haben wir sie beibehalten, da das Entfernen der beiden Befehle zum
Einen das Timing, das zum exakten synchronisieren zwischen Programm und Ra- sterstrahl notwendig ist, durcheinanderbrächte und wir dadurch andere Befehle zum Verzögern einfügen müssten, und
zum Anderen um die Flexibilität der Routine dadurch nicht einzuschränken. Auf
diese Weise wird es z. B. für Sie sehr
einfach, die Routine mit einer Topund
Bottom-Border- Funktion " nachzurüsten", indem Sie lediglich die IRQ-Vektoren
weiter unten auf eine solche Routine
verbiegen und das Festlegen des nächsten
IRQs bei Rasterzeile $82 auf diese Routine verlagern. Dies ist übrigens eine
saubere Möglichkeit timingkritische
IRQ-Routinen zu schreiben, und sie
zusätzlich zu anderen Raster-Effekten
erweiterbar zu halten. Da wir sowieso
die meiste Zeit verzögern müssen tun uns
die beiden Befehle auch nicht weiter
weh.
Es folgt nun der eigentliche Kern der
IRQ-Routine; eine Schleife namens " FLD-LOOP" . Hier lesen wir zunächst einmal
einen Wert aus der Tabelle " AD017" aus und tragen ihn in das Y-Expansions- Register ein. Die besagte Tabelle befindet sich im Code ab Adresse $1600 und
enthält die Werte, die nötig sind, um
das Sprite wie gewünscht zu dehnen. Da
wir uns in den Beispielen 1 und 2 nur
auf ein Sprite beschränken ( Sprite 0 nämlich), enthält die Tabelle natürlich
nur $00- und $01- Werte. Bei $00 wird
ganz normal die nächste Spritezeile gelesen und angezeigt, bei $01 wird immer
wieder die zuletzt dargestellte Spritezeile auf den Bildschirm gebracht. Folgen mehrere $01- Werte aufeinander, so
wird eine einzige Spritezeile so oft
wiederholt, wie $01- Werte in der Tabelle
stehen. Um die nächste Spritezeile darzustellen muß jetzt mindestens einmal
ein $00- Wert folgen. Diese Zeile kann
nun ebenfalls beliebig oft wiederholt
werden, usw. Zur besseren Öbersicht hier
ein Auszug aus der Tabelle mit Ihren
Werten für das Beispiel " STRETCHER.1" :
ad017 :
.byte $01,$01,$01,$00,$01,$00,$00,$00 .byte $00,$00,$00,$00,$00,$00,$00,$00 .byte $01,$01,$01,$00,$01,$00,$00,$00 .byte $00,$00,$00,$00,$00,$00,$00,$00 .byte $01,$01,$01,$01,$01,$01,$01,$01 .byte $01,$01,$01,$01,$01,$01,$01,$01 .byte $01,$01,$01,$01,$01,$01,$01,$01 .byte $01,$01,$01,$01,$01,$01,$01,$01 .byte $01,$01,$01,$01,$01,$01,$01,$01 .byte $01,$01,$01,$01,$01,$01,$01,$01 .byte $01,$01,$01,$01,$01,$01,$01,$01 .byte $01,$01,$01,$01,$01,$01,$01,$01 .byte $01,$01,$01,$01,$01,$01,$01,$01 .byte $01,$01,$01,$01,$00,$00,$00,$00
Wie Sie sehen verzerren wir hier
zunächst die ersten Spritezeilen verschieden oft. Hiernach wird die letzte
Spritezeile so oft wiederholt, bis das
Ende unseres, durch FLDund Sideborder
behandelten, Bildschirmbereichs erreicht
wurde.
Kommen wir jedoch wieder zu unserer FLD-LOOP zurück. Nach dem Setzen des Y-Expansions- Registers wird abermals ein
Tabellenwert gelesen und diesmal in Register $ D011 übertragen. Diese Befehlsfolge ist für den FLD-Effekt notwendig, mit dem wir den Beginn der nächsten Charakterzeile vor dem Rasterstrahl herschieben. Die Tabelle enthält immer wieder die Werte $19,$1 A,$1 B,$1 C,$1 D,$1 E,$1 F,$18, usw., womit wir in jeder
Rasterzeile die Horizontalverschiebung
um eine Rasterzeile versetzt vor dem
Rasterstrahl herdrücken.
Als Nächstes folgt das Üffnen des linken
und rechten Bildschirmrandes durch die
altbekannte Befehlsfolge zum schnellen
Runterund wieder Hochschalten zwischen
38- und 40- Spalten-Darstellung.
Durch die nun folgenden 9 NOP-Befehle
verzögern wir solange, bis der Rasterstrahl eine Position erreicht hat, zu
der die VIC-Schaltlogik schon die
gewünschte Spritezeilenadresse an den Datenbus angelegt hat, und somit der VIC
mit den gewünschten Spritedaten für diese Zeile gefüttert wurde. Jetzt schalten
wir die Y-Expansion wiederum ganz ab, um
das Register für den nächsten Wert vorzubereiten. Gleichzeitig stellen wir
damit sicher, daß der Rest des Sprites, der ggf. über unseren, vom Raster-IRQ
behandelten, Bereich hinausragt ( z. B.
wenn wir das Sprite zu lang gedehnt haben), in normaler Darstellung auf den
Bildschirm gelangt. Es wird nun nur noch
der Index-Zähler im X-Register für den
nächsten Schleifendurchlauf um 1 erhöht
und mit der Anzahl der Raster-Effekt- Zeilen in Register $02 verglichen. Ist
er noch kleiner als dieser Wert, so können wir die Schleife wiederholen, um die
nächste Rasterzeile zu bearbeiten. Im
anderen Fall sind wir am Ende angelangt, wo der nächste Interrupt vorbereitet
wird, bevor wir die IRQ-Routine wie gewohnt beenden.
Damit hätten wir auch schon den Kern unserer Routine besprochen. Experimentieren Sie doch ein wenig mit der Verzerrung, indem Sie die Tabelle " AD017" ab Adresse $1600 mit Hilfe eines Speichermoditors abändern. Sie werden sehen
welch lustige Deformierungen dabei entstehen können! Beachten Sie dabei, daß
Sie die Form des Sprites im Speicher
niemals ändern, sonder daß die Änderung
hardwaremäßig eintritt!
( Anm. d. Red. : Bitte wählen Sie nun den 2 .
Teil des IRQ-Kurs aus dem Textmenu)
Fortsetzung IRQ-Kurs (Teil 13)
Kommen wir nun zum Beispiel " STRET-CHER.2" . Rein äußerlich unterscheidet
sich diese Routine nicht von " STRET-CHER.1" . Auch hier wird dasselbe Sprite
wieder verzerrt dargestellt, wobei die
Verzerrung jedoch etwas anders ausfällt.
Rein theroretisch haben wir nur die Tabelle " AD017" verändert, so daß dasselbe
Sprite ein anderes Aussehen erlangt.
Technisch gesehen wurde dieses Beispiel
jedoch um ein zusätzliches Feature erweitert: Am Ende unserer IRQ-Routine, genau bevor wir die Prozessorregister
wieder zurückholen und den IRQ mittels
RTI verlassen, haben wir den Befehl " JSR
$1300" hinzugefügt. An dieser Adresse
befindet sich nun eine kleine Unterroutine, die es uns ermöglicht, flexiblere
Dehnungen zu programmieren, ohne, daß
wir uns Gedanken über den Aufbau der
Tabelle " AD017" machen müssen. Sie greift auf eine Tabelle namens
" LSTRETCH" zu, die 21 Bytes enthält, die
jeweils die Anzahl der Rasterzeilen angeben, die eine jede der 21 Spritezeilen
wiederholt werden soll. Die Tabelle
liegt ab Adresse $1700 und sieht folgendermaßen aus:
lstretch:
.byte $01 ;Spriteline01 1x Wdh. .byte $02 ;Spriteline02 2x Wdh. .byte $03 ;Spriteline03 3x Wdh. .byte $04 ;Spriteline04 4x Wdh. .byte $05 ;Spriteline05 5x Wdh. .byte $06 ;Spriteline06 6x Wdh. .byte $06 ;Spriteline07 6x Wdh. .byte $05 ;Spriteline08 5x Wdh. .byte $04 ;Spriteline09 4x Wdh. .byte $03 ;Spriteline10 3x Wdh. .byte $02 ;Spriteline11 2x Wdh. .byte $01 ;Spriteline12 1x Wdh. .byte $01 ;spriteline13 1x Wdh. .byte $01 ;Spriteline14 1x Wdh. .byte $01 ;Spriteline15 1x Wdh. .byte $01 ;Spriteline16 1x Wdh. .byte $01 ;Spriteline17 1x Wdh. .byte $01 ;Spriteline18 1x Wdh. .byte $01 ;Spriteline19 1x Wdh. .byte $01 ;Spriteline20 1x Wdh. .byte $00 ;Spriteline21 0x Wdh.
Die Routine bei $1300(" STCHART" ist ihr
Name) soll nun diese Werte in Folgen von
$00/$01- Bytes umrechnen, und in der Tabelle " AD017" ablegen, so daß wir lediglich angeben müssen, welche Spritezeile
wie oft wiederholt werden soll. Hier nun
der Sourcecode der STCHART-Routine:
stchart ldx #20 ;Zähler in X-Reg. init. lda #$ff ;Akku mit $FF init. fill: sta ad017,x ;AD017-Tab mit $FF inx ; auffüllen (=letzte cpx #21*5 ; Spritezeile immer bis bne fill ; Ende wiederholen)
ldy #$00 ; Index f. LSTRETCH-Tab ldx #$00 ; Index f. AD017- Tab
nextline:
lda lstretch, y;1 . Wert lesen und in sta $ FF ;$ FF abl.
double:
dec $ FF ; Wert-1 beq normal ; Wert=0-> nächst. Zeile lda #$01 ; Sonst 1 x wiederholen in
sta ad017, x ; AD017- Tab eintr.
inx ; AD017- Index+1 jmp double ; Und nochmal durchlaufen normal:
lda #$00 ;Code für "nächste Zeile sta ad017,x ; lesen" in AD017-Tab inx ;AD017-Index+1 iny ;LSTRETCH-Index+1 cpy #20 ;Mit Ende vgl. bne nextline ;Nein, also nochmal rts ;Sonst Ende
Wie Sie sehen füllt diese Routine lediglich die AD017- Tabelle so oft mit $01- Werten, wie der Bytewert einer Spritezeile aus LSTRETCH groß ist. Auf diese
Weise können wir die Verzerrung einfacher handhaben, was uns in Beispiel 2 noch nichts nutzt, da hier immer nur
dieselbe LSTRETCH-Tabelle benutzt wird, was uns aber bei den Beispielen 3 und 4 sehr zugute kommt.
3) PROGRAMMBEISPIELE 3 UND 4 Diese beiden Beispiele bauen nun auf den
Grundstein, den wir mit den ersten beiden Programmen legten, auf." STRET-CHER.3" ist ein Programm, in dem Sie
einen kleinen Flugsaurier auf dem Bildschirm flattern sehen. Diesen können Sie
nun mit Hilfe eines Joysticks in Port 2 nach links und rechts über den Bildschirm bewegen. Drücken Sie den Joystick
jedoch nach oben und unten, so können
Sie unseren kleinen Freund wachsen oder wieder schrumpfen lassen, also fließend
auf 5- fache Größe dehnen und wieder auf
seine Ursprungsgröße zusammenschrumpfen
lassen. Hierzu haben wir lediglich eine
Joystickabfrage miteingebaut, die bei
nach unten gedrücktem Joystick einen der
Werte aus der Tabelle LSTRETCH um 1 erhöht, bei nach oben gedrücktem Joystick einen dieser Werte erniedrigt.
Dadurch, daß nun gleichzeitig auch nach
jedem Rasterdurchlauf die Routine ST-CHART aufgerufen wird, wird somit die
kleine Änderung durch die Joystickroutine direkt in die AD017- Tabelle übertragen, womit ein quasi stufenloser Dehnund Staucheffekt entsteht. Hierbei ist
es übrigens wichtig, die richtige Spritezeile für eine Vergrößerung zu wählen.
In der Regel nimmt man dazu ein iteratives Verfahren, das die Anzahl der Spritezeilen, die eine Rasterzeile mehr dargestellt werden als Andere, so gleichmäßig verteilt, daß der Dehneffekt besonders flüssig erscheint. Dies sind jedoch Feinheiten, die wir hier nicht weiter
besprechen möchten, da sie nicht zum
Thema gehören.
Das Beispiel " STRETCHER.4" ist nun eine
weitere Kombination aus den Beispielen 2 und 3 . Wir haben hier acht Mal den kleinen Flugsaurier auf dem Bildschirm, der
in Sinuswellenform quasi über den Bildschirm " schwabbelt" . Hierbei haben wir
uns eines einfachen Tricks bedient: Wir
nahmen zunächst die LSTRETCH-Tabelle aus
" STRETCHER.2", die eine Art runde Verzerrung in Vertikalrichtung erzeugt.
Diese Tabelle wird nun zyklisch durchgerollt. Pro Rasterdurchlauf kopieren wir
die Bytes 0-19 um ein Byte nach vorne
und setzen den Wert von Byte 20 wieder
bei 0 ein. Dadurch entsteht der Effekt, als würden die kleinen Saurier als Reflektion auf einer gewellten Wasseroberfläche sichtbar sein!
Diese einfache Variation zeigt, mit welch simplen Methoden man eindrucksvolle Effekte durch Spritedehnung erzielen
kann. Sie soll Sie wieder anregen, eigene Experimente mit unserer neuen Raster-Effekt- Routine durchzuführen.
(ih/ub)
IRQ-KURS "Die Hardware ausgetrickst..." (Teil 14)
Unser Kurs neigt sich langsam dem Ende
zu, und als wahre " Raster-Feinschmecker" haben wir uns das beste Stückchen Code
ganz für den Schluß aufgehoben. In diesem und den nächsten beiden ( letzten) Kursteilen werden wir uns mit Raster-Tricks beschäftigen, die es uns ermöglichen, den Bildschirm des C64 HARDWA-REMÄSSIG in alle Richtungen zu scrollen.
" Hardwaremässig" heißt, daß wir nicht
etwa die Softscroll-Register des VICs
beschreiben, und dann alle 8 Scollzeilen/- spalten den gesamten Bildschirm um
8 Pixel ( oder einen Charakter) weiterkopieren, sondern daß wir vielmehr den VIC
derart austricksen, daß er uns diese
Arbeit von alleine abnimmt, und zwar
ohne, daß wir auch nur einen einzigen
Taktzyklus zum Kopieren von Grafikdaten
verschwenden müssen! Die hierzu notwen- digen Routinen heißen " VSP", zum vertikalen Scrollen," HSP", zum horizontalen
Scrollen, sowie " AGSP", die eine Kombination Kombination aus den beiden ersteren dartsellt. Mit ihnen können wir den
gesamten Bildschirm ganz problemlos
( auch Hires-Grafiken!) in alle Richtungen verschieben. Im heutigen Kursteil
wollen wir mit der VSP-Routine beginnen:
1) FLD UND VSP - DIE ( UN) GLEICHEN BRÜDER
Sie werden sich jetzt sicher wundern, warum hier der Begriff " FLD", auftaucht, war das doch eine der " einfacheren" Routinen, die wir schon zu Anfang dieses
Kurses besprochen hatten. Doch wie ich
schon öfter erwähnte, ist die FLD-Routine meist der Schlüssel zu den kompexeren Rastertricks, und leistete uns
auch schon manchen guten Dienst um Timingprobleme extrem zu vereinfachen.
Diesmal jedoch dreht sich alles direkt
um unseren kleinen Helfershelfer, da die
VSP-Routine sehr stark mit ihm verwandt ist, wenn die Beiden nicht sogar identisch sind." VSP" steht für " Vertical
Screen Position", was der einfach Ausdruck für das ist, was die Routine bewirkt: durch sie wird es uns ermöglicht, den gesamten Bildschirm ohne jeglichen
Kopieraufwand vollkommen frei nach oben
oder unten zu scrollen. Und jetzt der
Clou an der ganzen Sache: FLD und VSP
unterscheiden sich lediglich durch einen
einzigen NOP-Befehl voneinander. Fügt
man Letzterern den Verzögerungs-NOPs
nach der IRQ-Glättung der FLD-Routine
hinzu, so erhält man eine voll funktionstüchtige VSP-Routine, die gleichzeitig noch einen FLD-Effekt miteingebaut hat. Damit Sie genau wissen, wovon
wir hier sprechen, sollten Sie sich auf
dieser MD einmal die Programmbeispiele
" FLD" und " VSP1" anschauen. Ersteres ist
die FLD-Routine, so wie wir sie zu Beginn dieses Kurses kennengelernt hatten.
Durch Bewegen des Joysticks nach oben
und unten können wir mit ihr den Bild- schirm nach unten " wegdrücken" . Das
Prinzip, das dabei verfolgt wurde war, daß die FLD-Routine den Beginn der nächsten Charakterzeile vor dem Rasterstrahl
herschob, indem sie ihn ständig mit
neuen vertikalen Verschiebeoffsets in
Register $ D011 fütterte. Da er deshalb
glaubte, sich noch nicht in der richtigen Rasterzeile zu befinden, in der er
die nächste Charakterzeile zu lesen hatte," vergaß" er solange sie zu zeichnen, bis wir ihm durch Beenden der FLD-Schleife die Möglichkeit dazu gaben, endlich die Rasterzeile zu erreichen, in
der er nun tatsächlich die versäumte
Charakterzeile lesen und anzeigen durfte. Egal, wieviele Rasterzeilen wir ihn
dadurch " vertrödeln" ließen, er begann
dann immer bei der Charakterzeile, die
er eigentlich als nächstes aufzubauen
gehabt hätte, so als wenn der FLD-Effekt
nie aufgetreten wäre. War das die erste
Charakterzeile des Bildschirms, so konnte man auch problemlos den Bildschirm erst in der Mitte oder am unteren Bildschirmrand beginnen lassen ( so arbeitet
übrigens auch der Effekt, mit dem Sie
die Seiten, die Sie gerade lesen umblättern) .
2) DAS PROGRAMMBEISPIEL VSP1 Wollen wir uns nun einmal die IRQ-Routine des Programmbeispiels " VSP1" anschauen. Im Prinzip nichts besonderes, da sie, wie schon erwähnt, fast identisch mit der FLD-Routine ist. Sie ist
ab Adresse $1100 zu finden und wird wie
die meisten unserer IRQ-Routinen von der
Border-IRQ- Routine, die wir immer zum
Abschalten des unteren und oberen Bildrandes benutzen initialisiert:
fld pha ;Prozessorregs. txa ; auf Stapel retten pha tya pha dec $d019 ;VIC-ICR löschen inc $d012 ;Glättungs-IRQ lda #<irq2 ; vorbereitem sta $fffe cli ;IRQs freigeben ch nop ;Insgesamt 13 NOPs ... ; zum Verzögern bis nop ; zum nächsten jmp ch ; Raster-IRQ irq2 pla ;Statusreg. u. pla ; IRQ-Adr. vom pla ; Stapel werfen dec $d019 ;VIC-ICR freigeben lda #$f8 ;Rasterpos. f. Bor- sta $d012 ; der IRQ festlegen ldx #<bord ;IRQ-Vektoren ldy #>bord ; auf Border-IRQ stx $fffe ; zurücksetzen (für sty $ffff ; nächst. Durchlauf) nop ;Verzögern bis nop ; zum Raster- nop ; zeilenende nop nop nop nop lda $d012 ;den letzen Cyclus cmp $d012 ;korrigieren bne onecycle onecycle lda #$18 ;25-Zeilen-Bildschirm sta $d011; einschalten nop ;Insgesamt 16 NOPs zum ... ;Verzögern für FLD
Es folgt nun der alles entscheindende
siebzehnte NOP-Befehl, der den VSP-Effekt auslöst:
nop ;NOP für VSP
Nun gehts weiter mit dem normalen FLD-Code. Entfernen Sie das obige NOP aus
dem Code, so erhalten Sie wieder eine
ganz normale FLD-Routine!
lda $02 ;Zeilenz. holen beq fldend ;Wenn 0 -> Ende ldx #$00 ;Zeiger init. fldloop lda rollvsp,x ;FLD-Offs. zum ora #$18 ;Verschieben d. sta $d011 ; Charakterz. lda colors1,x ;Blaue Raster sta $d020 ; im FLD-Be- sta $d021 ; reich darst. nop ;Bis Zeilen- nop ; mitte verzö- nop ; gern nop nop nop nop lda colors2,x ;Rote Raster sta $d020 ; Im FLD-Be- sta $d021 ; reich darst. nop ;Verzögern bis nop ; Zeilenende bit $ea inx ;Zeilenz.+1 cpx $02 ;Mit $02 vgl. bcc fldloop ;ungl.->weiter fldend nop ;Verz. bis nop ; Zeilenende nop nop nop lda #$0e ;Normale sta $d020 ; Bildschirm- lda #$06 ; farben ein- sta $d021 ; schalten pla ;Prozessorregs. tay ; zurückholen pla tax pla rti ; und ENDE
Die Tabelle " ROLLVSP" enthält Softscroll- Werte für Register $ D011( nur die
untersten 3 Bit sind genutzt), die die
vertikale Verschiebung immer um eine
Rasterzeile vor dem Rasterstrahl herschieben, so daß der FLD-Effekt entstehen kann.
Die Tabellen " Color1" und " Color2" geben
die Farben an, die die FLD-Routine im weggedrückten FLD-Bereich darstellt.
Dies ist nur als " Verschönerung" nebenbei gedacht.
3) DAS FUNKTIONPRINZIP VON VSP Intern kann man sich die Vorgehensweise
des VIC beim Aufbauen des Bildschirms
mit all seinen Rasterzeilen folgendermaßen vorstellen: Trifft unser armer Grafikchip auf eine Rasterzeile, in der
eigentlich die nächste Charakterzeile
erscheinen sollte, so bereitet er sich
auf den gleich folgenden Zugriff auf das
Video-RAM vor, und zählt schon einmal
einen internen Adresszeiger auf die
gewünschte Adresse um 40 Zeichen nach
oben, um die folgenden 40 Bytes rechtzeitig lesen zu können. Nun vergleicht
er die Rasterstrahlposition ständig mit
seiner Startposition für den Lesevorgang
und hält beim Erreichen von Selbiger den
Prozessor für 42 Taktzyklen an, um seinen Lesezugriff durchzuführen. Durch die
FLD-Routine wurde nun jedoch der Beginn der gesamten Charakterzeile ständig vor
dem VIC hergeschoben, weswegen er sie
auch schön brav erst später zeichnete.
Unsere VSP-Routine verfügt nun über einen einzigen NOP mehr, der hinter der
IRQ-Glättung eingefügt wurde. Das bedeutet, daß der FLD-Schreibzugriff auf Register $ D011, mit dem der vertikale Verschiebeoffset um eins erhöht wird, exakt
2 Taktzyklen später eintritt als sonst.
In genau diesen beiden Taktzyklen aber
hat der VIC schon die interne Adresse
auf das Video-RAM um 40 Zeichen erhöht
und wartet jetzt auf das Erreichen der
Startposition für den Lesevorgang. Da
der in unserem Programm folgende
Schreibzugriff auf $ D011 diese Position
für den VIC nun aber eine Rasterzeile
weiterschiebt, kann er diese Position
nie erreichen. Das Endergebnis, das wir
dadurch erhalten ist Folgendes:
* Der FLD-Effekt greift, und wir lassen
den VIC die nächste Charakterzeile
eine Rasterzeile später zeichnen.
* Der interne Adresszeiger des VIC auf
die Daten der nächsten Charakterzeile
wurde um 40 Zeichen ( also eine Charakterzeile) erhöht.
* Da der VIC beim Erreichen der nächsten
Rasterzeile wieder glaubt, er müsse
sich auf die Charakterzeile vorbereiten, zählt er den internen Adresszeiger um weitere 40 Bytes nach oben und
wartet auf die richtige Startposition
zum Lesen der nächsten 40 Bytes.
* Lassen wir ihn nun tatsächlich die
Charakterzeile lesen, indem wir die
FLD( VSP)- Routine beenden, so stellt er
zwar, wie beim normalen FLD-Effekt, wieder Charakterzeilen dar, jedoch
wurde durch das zweimalige Erhöhen des
Adresszeigers um 40 Bytes eine gesamte
Charakterzeile übersprungen! Hatten
wir den FLD-Effekt also z. B. vor Erreichen der ersten Charakterzeile einsetzen lassen, so zeichnet der VIC nun
nicht die erste, sondern die ZWEITE
Charakterzeile, da er sich ja 40 Bytes zu weit nach vorne bewegte! ! ! Die erste Charakterzeile wurde somit ÖBER-SPRUNGEN!
Im Klartext bedeutet das: für jede Rasterzeile, die wir die FLD( VSP)- Routine
länger laufen lassen, überspringt der
VIC eine Charakterzeile. Auf diese Weise
können wir also auch die Daten, die in
der Mitte des Video-RAMs liegen am Anfang des Bildschirms anzeigen lassen.
Der VIC liest nun aber pro Rasterdurchlauf immer 25 Charakterzeilen. Lassen
wir ihn also durch unseren VSP-Trick 40 Bytes zu spät auf die vermeintliche 1 .
Charakterzeile zugreifen ( es handelt
sich um die 1 . Charakterzeile die auf
dem Bildschirm zu sehen ist, jedoch mit
den Daten, die eigentlich erst in der
zweiten Charakterzeile des Bildschirms
erscheinen sollten), so hört für ihn der
Bildschirm auch 40 Bytes zu spät auf.
Das Ergebnis: in der 25 . Bildschirmzeile
liest und stellt der VIC die Daten des
sonst ungenutzten Bereichs des Video- RAMs im Adressbereich von $07 E8 bis
$07 FF dar ( wenn wir von der Stanard-Basisadresse des Video-RAMs bei $0400 ausgehen) . Dies sind 24 Zeichen, die
somit am Anfang der letzten Bildschirmzeile zu sehen sind. Hieraufhin folgen
16 weitere Zeichen, die sich der VIC
wieder aus der Anfangsadresse des Video-RAMs holt.
Zum besseren Verständnis sollten wir
vielleicht noch klären, wie der VIC auf
das Video-RAM zugreift. Selbiges ist
immer 1024 Bytes ( also exakt 1 KB) lang, obwohl der Bildschirm nur 1000 Bytes
groß ist und somit die letzten 24 Bytes
nie sichtbar werden. Wie Sie nun wissen, kann der VIC immer nur 16 KB der 64 KB des
C64 adresssieren, wobei die Lage dieses
Bereichs allerdings auch verschoben werden kann. Das heißt also, daß der VIC
insgesamt zwar Adressen in einem 64 KB-Raum (16 Adressbits sind hierzu notwendig) adressieren, aber gleichzeitig immer nur auf 16 KB für Grafikdaten zugrei- fen kann. Möchte man diesen 16 KB-Bereich
verschieben, so kann das mit den untersten zwei Bits von Adresse $ DD00 getan
werden. Im Normalfall stehen beide auf 0 und lassen den VIC deshalb im Bereich
von $0000 bis $3 FFF arbeiten. Diese
Adressen dienen als Basis für den VIC
die ihm von der NMI-CIA in die obersten
zwei Adressbits für seine RAM-Zugriffe
eingeblendet werden. Die Zugriffsadresse
ist also 16 Bit groß, wobei die beiden
Bits 0 und 1 aus $ DD00 immer in den
obersten zwei Bits (14 und 15) erscheinen. Die Bits 10-13 dieser Adresse bestimmen die Basisadresse des Video-RAMs.
Sie werden von den Bits 4-7 der VIC-Adresse $ D018 geliefert. Steht hier der
Bitcode $0001( so wie nach Einschalten
des Computers), so wird also zusammen
mit den Bits aus $ DD00 die Adresse $0400 angesprochen, die im Normalfall die Basisadresse für das Video-RAM ist. Die
restlichen, zur Adressierung benötigten
10 Bits kommen aus dem oben schon erwähnten, VICinternen Adresszähler. Er
ist exakt 10 Bits lang, womit maximal
1 KB-Bereiche adressiert werden können.
Dadurch erklärt sich auch, warum der VIC
durch austricksen mit der VSP-Routine, in der letzten Zeile die 24 sonst ungenutzten Zeichen des Video-RAMs darstellt: Da der 10- Bit-Zähler nun auf den
Offset $03 E8 zeigt, werden von dort auch
die Daten gelesen. Hierbei findet dann
aber nach dem 24 . Byte ein Öberlauf des
10- Bit-Zählers statt, womit selbiger
wieder auf 0 zurückspringt und wieder
den Anfang des Video-RAMs adressiert.
Dadurch erklärt sich auch, warum in der
letzten Bildschirmzeile nach den Zeichen
aus $07 E8-$07 FF wieder die Zeichen ab
Adresse $0400 erscheinen.
Bleibt noch zu erwähnen, daß dasselbe
für das Color-RAM bei $ D800 gilt. Dies
ist immer an einer Fixadresse, wobei die
untersten 10 Bit ebenfalls aus dem Charakterzeilen- Adresszähler kommen. Das
heißt also, daß die 24 sonst unsichtba- ren Zeichen ihre Farbe aus dem ebenfalls
sonst ungenutzten Color-RAM- Bereich von
$ DBE8-$ DBFF erhalten.
5) PROBLEME DIE BEI VSP ENTSTEHEN KÖNNEN
Die Nachteile, die durch die Charakterverschiebung entstehen, sollten auch
nicht unerwähnt bleiben:
Durch die Verschiebung der Video-RAM- Daten am Ende des Bildschirms um 24 Zeichen nach rechts, ist natürlich auch die
gesamte Grafik in zwei Teile zerlegt
worden, weswegen ein Scroller nicht ganz
so einfach durchzuführen ist. Um diesen
Fehler auszugleichen müssen wir einen
zweiten Video-RAM- Bereich verwalten, in
dem die gesamte Grafik um 24 Zeichen
versetzt abgelegt sein muß. In diesem
Video-RAM sind dann die ersten 24 Zeichen ungenutzt. Durch einen stinknormalen Rasterinterrupt können wir dann problemlos vor Beginn der Charakterzeile, an der der VIC-Adresszeiger überläuft, auf das zweite Video-RAM umschalten,
wobei dieses dann zwar an der überlaufenden Adresse ausgelesen wird, was jedoch durch unsere Verschiebung wieder
ausgeglichen wird.
Ein weiterer Nachteil, der bei VSP entsteht, liegt auf der Hand: Die letzten
acht Bytes des jetzt sichtbaren Video-RAMs sind die Adressen, in denen die
Spritepointer abgelegt werden. Das heißt
also, daß wir beim gleichzeitigen Anzeigen von Grafikzeichen in diesen acht
Bytes mit der Spriteanzeige Probleme
bekomen. Jedoch auch dies ist zu bewältigen. Entweder, indem man so geschickt
arbeitet, daß in diesen Bereichen auf
dem Bildschirm nur Zeichen in der Hintergrundfarbe zu sehen sind ( bei schwarzem Hintergrund einfach das Color-RAM in
den Adressen von $ DBF0-$ DBFF ebenfalls
auf schwarz setzen) . Oder aber indem wir
in diesen Zeichen immer nur dann die
eigentlichen Video-RAM- Werte eintragen, wenn sie ausgerechnet vom VIC benötigt
werden ( also auch exakt vor dem Lesen der Öberlauf-Charakterzeile), und ansonsten die Spritepointer hineinschreiben. Da der VIC schon 42 Taktzyklen nach
Beginn der Rasterzeile, in der die Charakterzeile beginnen soll, die Video-RAM- Daten gelesen hat, können wir also
getrost wieder die Spritepointer zurückschreiben und haben so lediglich eine
einzige Rasterzeile, in der die Sprites
garnicht ( oder nur verstümmelt) dargestellt werden können.
Soll der gesamte Bildschirm einmal
durchgescrollt werden können, so muß
davon ausgegangen werden, daß maximal 25 Rasterzeilen am Beginn des Bildschirms
ungenutzt bleiben müssen, da in Ihnen
das Timing für den VSP-Effekt unterbracht werden muß. Da pro Rasterzeile
der interne Adresspointer um eine Charakterzeile weitergezählt wird, muß also
im Maximalfall in 25 Rasterzeilen der
VSP-Effekt eingesetzt werden. Will man
den Bildschirm nun konstant an eine bestimmten Position beginnen lassen, so muß nach der Anzahl der zu überspringenden Charakterzeilen wieder eine normale
FLD-Routine ( ohne das zusätzlich NOP) benutzt werden, um bis zur 25 . Rasterzeile nach Bildschirmanfang zu verzögern, OHNE daß gleich alle 25 Charakterzeilen übersprungen werden.
6) WEITERE PROGRAMMBEISPIELE Ausser der oben schon erwähnten " VSP1"- Routine finden Sie auf dieser MD auch
noch zwei weitere Beispielprogramme, mit
den Namen " VSP2" und " VSP3"( wie alle
unserer Beispiele werden auch sie mit
" SYS4096" startet) . In Ersterem haben
wir zusätzlich zum VSP-Effekt die unteren drei Bits von $ D011 zum Softscrollen des Bildschirms verwendet, so
daß der Bildschirm weich nach oben und
unten geschoben werden kann. Die Routine
" VSP3" scrollt das gesamte Video-RAM
ununterbrochen durch, wodurch quasi ein
unendlicher Horizontalscroller durchgeführt werden kann. Die 25 unbenutzten Rasterzeilen am Bildschirmanfang, die
für das VSP-Timing benötigt werden, sind
mit Sprites hinterlegt, in denen man
z. B. in einem Spiel auch ein Scoreboard
unterbringen kann.
In der nächsten Folge des IRQ-Kurses
werden wir uns den horizonalen Hardwarescroller " HSP" anschauen, bei dem das
VSP-Prinzip auf die Horizontale Bildschirmposition umgesetzt wurde.( ih/ ub)
IRQ-KURS "Die Hardware ausgetrickst..." (Teil 15)
Im letzten Kursteil hatten wir uns einen
ganz besonderen Rastertrick angeschaut.
Anhand einer VSP-Routine hatten wir gelernt, wie einfach es ist, den Bildschirm des C64 HARDWAREMÄSSIG, also ohne
den Prozessor mit großen Bildschirm-Verschiebe- Aktionen zu belasten, nach
oben und unten zu scrollen. Dabei unterschied sich die VSP-Routine von einer
FLD-Routine in nur einem NOP-Befehl, der
das nötige Timing erzeugte, um den
gewünschten Effekt auszulösen. Der FLD-Effekt war, wie wir gesehen hatten maßgeblich daran beteiligt, daß der VIC das
Lesen von einigen Charakterzeilen vergaß, weswegen wir in der Lage waren, einzelne Bereiche im Video-RAM zu überspringen, und so den Bildschirm beliebig
nach oben und unten zu scrollen. In die- sem Kursteil soll es nun um einen nahen
Verwandten von VSP gehen. Wir werden den
HSP-Effekt besprechen." HSP" steht für
" Horizontal Screen Position" und setzt
den VSP-Effekt quasi auf die Horizontale
um. Mit ihm können wir DEN GESAMTEN
BILDSCHIRM problemlos um bis zu 320 Pixel nach rechts verschieben, wobei wir
den Prozessor nur läppische 3 Rasterzeilen lang in Anspruch nehmen müssen. Damit werden schnell scrollende Balleroder Jump' n Run Spiele, wie auf dem Amiga oder dem Super-NES von Nintendo auch
auf dem C64 möglich!
1) ZUM PRINZIP VON HSP Wir erinnern uns: Um die VSP-Routine
funktionsfähig zu machen, mussten wir
den FLD-Effekt so anwenden, daß wir dem
VIC vorgaukelten, sich auf das Lesen der
nächsten Rasterzeile vorzubereiten und
seinen internen Lesezähler hochzuzählen, um die richtige Charakterzeilen-Adresse anzusprechen. Als er nun seinen Lesevorgang durchführen wollte machten wir ihn
durch FLD-Wegdrücken der Charakterzeilen
glauben, daß er doch noch nicht die entsprechende Zeile erreicht hatte. Somit
übersprang er mehrere Adressen und somit
auch Charakterzeilen. Wir ließen ihn
also die Charakterzeilen zu spät lesen, was zu dem gewünschten Effekt führte.
Einen ähnlichen Trick können wir nun
auch für die HSP-Routine anwenden. Wie
wir ja wissen, so liest der VIC ab der
Rasterposition $30 alle 8 Rasterzeilen
die 40 Zeichen, die in den nächsten 8 Rasterzeilen zu sehen sein sollen, aus
dem Video-RAM, um sie anschließend anzuzeigen. Durch den FLD-Effekt haben wir
nun schon oft genung die erste Charakterzeile auf dem Bildschim vor ihm hergeschoben, so daß der VIC diese Zeile
verspätet erreichte, und somit der Bildschirm nach unten weggedrückt wurde. Der
Witz ist, daß dieser Trick nun auch in
her Horizontalen funktioniert! Denn so- bald der VIC merkt, daß er sich in einer
Charakterzeile befindet, in der er Zeichendaten zu Lesen und Anzuzeigen hat, beginnt er auch unverzüglich mit dieser
Arbeit, und das ohne noch auf die horizontale Rasterposition zu achten, um
ggf. festzustellen, daß er sich garnicht
am Zeilenanfang befindet! Wenn wir nun
also eine Art FLD-Routine einsetzen, die
nur für einen Teil der aktuellen Rasterzeile vorschreibt, daß selbige noch keine Charakterzeile ist, und dann mitten
innerhalb dieser Zeile von uns wieder
auf normale Darstellung zurückgeschaltet
wird, so fängt der VIC auch prompt mittendrin damit an die Charakterdaten zu
lesen und sofort auf den Bildschirm zu
bringen. Verzögern wir also nach einem
FLD bis zur Mitte der Rasterzeile, und
schalten dann wieder zurück, so wird der
Video-RAM Inhalt um exakt 20 Zeichen
nach rechts versetzt auf dem Bildschirm
dargestellt, wobei die 20 letzten Zeichen, die ja nicht mehr in diese Text- zeile passen, automatisch erst in der
nächsten Zeile erscheinen. Es kommt sogar noch besser: aufgrund eines internen
Timers des VIC, der das Lesen der Charakterzeilen mitbeeinflusst ( es sei den
wir tricksen ihn aus) führt der VIC den
Lesefehler in JEDER WEITEREN Charakterzeile ebenso durch, so daß es genügt, lediglich am Bildschirmanfang einmal um
einen bestimmten Wert zu verzögern, um
DEN GESAMTEN Bildschirm wie gewünscht
nach rechts versetzt darzustellen! ! !
Um den Trick nun umzusetzen müssen wir
wiefolgt vorgehen: zunächst schalten wir
in $ D011 den vertikalen Verschiebe-Offset ( wird in den untersten 3 Bits
festgelegt) auf 1, so daß für den VIC
die erste Charakterzeile erst eine Rasterzeile nach Beginn des sichtbaren
Bildschirmfensters folgt. Dieser Beginn
liegt normalerweise in Rasterzeile $30 .
Durch die Verschiebung legen wir die
erste vom VIC zu lesende Charakterzeile
jedoch in Rasterzeile $31 . Verzögern wir nun jedoch in Rasterzeile $30 um eine
bestimmte Anzahl Taktzyklen, und schalten wir dann die horizontale Verschiebung mittendrin wieder auf 0 zurück, so
merkt der VIC plötzlich, daß er sich
doch schon in einer Charakterzeile befindet und fängt einfrig damit an die
Charakterdaten zu lesen und auf dem
Bildschirm darzustellen. Wohlgemerkt
obwohl er sich schon nicht mehr am Zeilenanfang befindet, sondern mitten innerhalb dieser Rasterzeile! Für jeden
Taktzyklus, den wir mehr verzögern, stellt der VIC die Charakterdaten um
jeweils ein Zeichen ( also 8 Pixel) weiter rechts dar. Schalten wir also 10 Takte nach Beginn des linken Bildrandes
die vertikale Verschiebung ab, so wird
die Charakterzeile exakt 10 Zeichen nach
rechts versetzt gezeichnet. Dies setzt
sich, wie oben schon erwähnt, über den
gesamten Bildschirm, also auch für die
folgenden 24 weiteren Charakterzeilen, fort, womit auch der gesamte Bildschirm um 10 Zeichen versetzt angezeigt wird!
2) DIE UMSETZUNG Wie das Ganze dann aussieht, können Sie
sich in den Programmbeispielen " HSP1" und " HSP2" anschauen. Sie werden beide
mit ",8,1" geladen und durch SYS4096 gestartet. HSP2 unterscheidet sich von
HSP1 nur darin, daß das Zeichenwirrwarr, das durch den HSP-Effekt in der ersten
Rasterzeile zu sehen ist, mit einem
schwarzen Rasterbalken unsichtbar gemacht wurde.
Kommen wir nun also zu der Routine, die
uns diesen Effekt erzeugt. Sie ist vom
Aufbau eigentlich recht einfach.
Zunächst einmal befindet sich eine Initialisierungsroutine ab Adresse $1000, die wie die meisten unserer IRQ-Routinen
das ROM abschaltet, um den IRQ-Vektor am
Speicherende verwenden zu können, und
anschließend einen Border-Raster- IRQ
initialisiert, der uns zunächst einmal,
wie immer, den unteren und oberen Bildschirmrand abschaltet. Sie steht ab
Adresse $1200 im Speicher und ist gefolgt von einer Routine zum Auslesen des
Joysticks, sowie der Routine " Control", die die Joystick-Daten auswertet, den
Bildschirmverschiebeoffset berechnet und
damit die HSP-IRQ- Routine beeinflusst.
Selbige ist ab Adresse $1100 zu finden.
Dir Border-IRQ- Routine legt diese Adresse in den IRQ-Vektoren bei $ FFFE/$ FFFF
ab, und gibt dem VIC vor, den nächsten
Raster-IRQ in Rasterzeile $2 D auszulösen.
Wird unsere HSP-Routine nun in dieser
Rasterzeile aufgerufen, so glätten wir
zunächst den IRQ, nach der uns mittlerweile altbekannten Methode. Ab dem Label
" Onecycle" steht nun unsere eigentliche
Routine, die wiefolgt aussieht:
onecycle:
lda #$19 ; Bildsch.1 Zeile nach sta $ d011 ; unten scrollen
ldy #$08 ;Verzögerungsschleife wy dey ; um den richtigen bne wy ; Moment abzupassen jsr cycles ;Verzögerung 12 Takte lda #$18 ;Wert f. 1 Zeile zurück redu1 beq redu2 ;2 oder 3 Take verz. redu2 bne tt ;ans Ende verzweigen nop ;20 NOPs die für das nop ; Timing später SEHR nop ; WICHTIG sind, ob- nop ; wohl sie hier nop ; übersprungen werden!! nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop tt sta $d011 ;Wert schreiben
lda #$0 e ; Bildschirmfarben setsta $ d020 ; zen ( innerhalb dieser
lda #$06 ; Befehle wird die Chasta $ d021 ; rakterz. gelesen!)
Zu Beginn unserer IRQ-Routine wird
zunächst also eine vertikale Verschiebung um eine Rasterzeile in $ D011 eingetragen ( Bits 0-2 enthalten den Wert
%001=$01) . Dadurch, daß der HSP-IRQ in
Rasterzeile $2 D aufgerufen wurde, und
daß die IRQ-Glättung 2 Rasterzeilen verbraucht, befinden wir uns nun also in
Rasterzeile $2 F. Wir verzögern nun noch
mit der folgenden Zählschleife und dem
JSR-Befehl um eine knappe weitere Zeile, wobei exakt die Position abgepasst wird,
an der sich der Rasterstrahl genau am
Anfang des linken Randes des sichtbaren
Bildschirms befindet ( so wie bei der
Routine zum Abschalten der seitlichen
Bildschirmränder) . Hierbei gehöhrt der
" LDA #$18"- Befehl ebenfalls zur Verzögerung. Er initialisirt den Akku jedoch
gleichzeitig schon mit dem Wert vor, den
wir in $ D011 schreiben müssen, um die
vertikale Verschiebung wieder abzuschalten ( die unstersten drei Bits enthalten
den Wert 0) . Nun folgt wieder eine sehr
ausgeklügelte Verzögerung, um Taktgenau
eine ganz bestimmte Rasterposition abzupassen. Beachten Sie bitte, daß die Routine an dieser Stelle von der oben schon
erwähnten " Control"- Routine modifiziert
wird, um die jeweils gewünschte Bildschirmverschiebung zu erreichen. Wir
haben hier nun zwei Branch-Befehle, die
jeweils mit Labels versehen sind, und
denen 20 NOP-Befehle folgen. Zum Schluß
steht dann der STA-Befehl, mit dem der
Wert des Akkus in $ D011 eingetragen wird, um nach der gewünschten Verzögerung den VIC zum Lesen der Charakterzeile zu bewegen. Schauen wir uns nun
zunächst die beiden Branches und deren
Bedeutung an:
lda #$18 ; Wert f.1 Zeile zurück
redu1 beq redu2 ;2 oder 3 Take verz.
redu2 bne tt ; ans Ende verzweigen
Durch den vorherigen LDA-Befehl, der
einen Wert ungleich Null lädt, wissen
wir, daß das Zero-Flag in jedem Fall
gelöscht ist. Ausserdem scheint der " BEQ
REDU2"- Befehl unsinnig zu sein, zumal er
exakt zum nächsten Befehl weiterspringt.
Wie aber auch schon bei unserer IRQ-Glättungsroutine hat dieser Branch-Befehl die Aufgabe, exakt einen Taktzyklus lang zu verzögern. Ein Branch-Befehl benötigt mindestens zwei Takte um
ausgeführt zu werden. Trifft die abgefragte Bedingung nicht zu, so wird
gleich beim nächsten Befehl fortgefahren( so wie das hier auch der Fall ist) .
Trifft die Bedingung jedoch zu, so
dauert der Branch-Befehl einen Taktzyklus länger, in dem der Prozessor den
Sprung-Offset auf den Programmzähler
aufaddieren muß. Soll nun um eine gerade
Anzahl Zyklen verzögert werden, weil der
Bildschirm um eine gerade Anzahl Zeichen
verschoben werden soll, so steht hier
ein BEQ-Befehl, der nur 2 Zyklen verbraucht. Soll eine ungerade Anzahl
verzögert werden, so wird von der Routine " Control", im Label " REDU1" der Opcode für einen BNE-Befehl eingetragen, womit die Routine einen Taktzyklus länger dauert, und somit auch ungerade
verzögert. Der nun folgende BNE-Befehl
ist immer wahr und verzögert somit immer
3 Taktzyklen ( da dies eine ungerade Zahl
ist, verhält es sich also eigentlich
umgekehrt mit der Änderung des BEQ-Befehls für gerade und ungerade Verzögerung) . Bei diesem Befehl wird von " Control" die Sprungadresse modifiziert. Je nach dem, wieviele weitere Zyklen verzögert werden müssen, trägt die Routine
einen Offset auf die folgenden NOP-Befehle ein. Der Befehl verzweigt dann
nicht mehr auf das Label " TT", wo der
Akkuinhalt nach $ D011 geschrieben wird, sondern auf einen NOP-Befehl davor. Einer dieser Befehle verzögert dann immer
um 2 Taktzyklen. Hierzu sollten wir einen Blick auf die Routine " Contol" werfen:
control:
ldx #$d0 ;Opcode für BNE in stx redu1 ; REDU1 ablegen lda xposhi ;X-Verschiebungs- sta xposhib ; zähler kopieren lda xposlo ; (Low- und High- sta xposlob ; byte) and #$08 ;Bit 3 v. XLo ausmask. bne co1 ;<>0, dann ldx #$f0 ;Opcode für BEQ in stx redu1 ; REDU1 ablegen co1 lsr xposhib ;X-Verschiebung ror xposlob ; (16 Bit) durch 4X lsr xposhib ; Rotieren nach rechts ror xposlob ; mit 16 dividieren lsr xposhib ror xposlob lsr xposhib ror xposlob sec ;Subtraktion vorber. lda #$14 ;20 NOPS sbc xposlob ; minus XPos/16 sta redu2+1 ;als Sprungoffset für ; BNE bei REDU2
lda xposlo ; Alte X-Versch. laden and #$07 ; unterste 3 Bits isol.
ora #%00011000 ; Standard-Bits setzen sta softmove+1 ; in hor. Scroll eintr.
rts
Zu Allererst trägt die Control-Routine, die innerhalb des Border-IRQs aufgerufen
wird, in das Label " REDU1" den Wert $ D0
ein, der dem Opcode für den BNE-Befehl
entspricht. Hieran anschließend werden
die Inhalte der Labels " XPOSLO" und " X-POSHI" in die Labels " XPOSLOB" und " X-POSHIB" kopiert, was für die Offset-Berechnung später notwendig ist. Diese
Labels sind im Source-Code des Programms
auf die Zeropage-Adressen $02,$03,$04 und $05 vordefiniert." XPOSLO" und " X-POSHI" enthalten einen 16- Bit Zähler für
die horziontale Bildschirmverschiebung, die von der Joystickabfrage, die ebenfalls während des Border-IRQs aufgerufen
wird, den Joystickbewegungen entsprechend hoch oder runter gezählt wird.
Dieser Zähler kann also einen Wert zwischen 0 und 320 enthalten. Da mit der
HSP-Routine der Bildschirm nur in
Schritten von einzelnen Zeichen ( also 8 Pixeln) versetzt werden kann, muß unsere
Routine zum weichen Scollen des Bildschirms auch noch den horizontalen Verschiebeoffset des Bildschirms verändern.
Diese Verschiebung wird in Register$ D016 des VICs festgelegt, wobei die
untersten 3 Bits unseres XPOS-Wertes in
die untersten 3 Bits dieses Registers
gelangen müssen. Die Bits 3-9 des XPOS-Wertes enthalten nun ( dreimal nach
rechts verschoben) die Anzahl Zeichen, und somit auch Taktzyklen, die die HSP-Routine verzögern muß, damit der VIC die
Charakterzeile erst an der gewünschten
Position liest. Ist also das 3 . Bit ( das
das 0 . Bit der Anzahl ist) gesetzt, so
muß eine ungerade Anzahl Zeichen verzögert werden. In dem Fall enthält das
Label " REDU1" schon den richtigen Wert, nämlich den Opcode für den BNE-Befehl, der zusammen mit dem BNE-Befehl bei " RE-DU2" sechs ( also eine gerade Anzahl) Taktzyklen verbraucht. Ist Bit 3 gelöscht, so enthält der Akku nach dem
" AND #$08"- Befehl den Wert 0 und es wird
vor dem Weitergehen im Programm der Opcode für den BEQ-Befehl in " REDU1" eingetragen. Damit wird in der HSP-Routine
um 2+3=5( also eine ungerade Anzahl)
Taktzyklen verzögert. Nun muß noch ermittelt werden, wieviele zusätzliche
NOPs zur Verzögerung notwendig sind. Da
ein NOP-Befehl immer 2 Taktzyklen
braucht, wird nur die halbe Anzahl NOPs
benötigt, um enstsprechend viele Zeichen
( Taktzyklen) lang zu verzögern. Da zudem
noch die 3 Bit Verschiebeoffset in XPOS
stehen, muß dieser Wert durch 16 dividiert werden, um den gewünschten Wert zu
erhalten. Diese Berechnung wird an der
Kopie von XPOS, also in den Labels " X-POSLOB" und " XPOSHIB" durchgeführt.
Nachdem im Low-Byte dieser Register
hiernach die Anzahl der benötigten NOPs
stehen, kann nun die Sprungadresse für
den BNE-Befehl bei " REDU2" berechnet
werden. Da Branch-Befehle immer nur ein
Byte mit dem relativen Offset zum aktuellen Programmzähler enthalten, müssen
wir also lediglich angeben, wieviele
NOPs übersprungen werden müssen. Dies
wird durch die Gesamtanzahl NOPs minus
der benötigten Anzahl NOPs errechnet,
und in Adresse " REDU2"+1 eingetragen.
Die Verzögerung sollte nun also sauber
funktionieren.
Zu guter Letzt wird noch der horizontale
Verschiebeoffset für das horzontale
Softscrolling ermittelt, indem aus
" XPOSLO" die untersten drei Bits ausmaskiert werden. Da diese Bits im Register
$ D016 landen müssen, das auch für die
38/40- Zeichendarstellung und den Multicolormodus zuständig ist, setzen wir die
entsprechenden Bits mit dem folgenden
OR-Befehl. Der resultierende Wert wird
in das Label " SOFTMOVE"+1 eingetragen, das in der HSP-Routine kurz vor dem oben
gezeigten Codestück steht:
softmove lda #$00 sta $d016
Hier wird also lediglich der Verschiebeoffset in den Operanden des LDA-Befehls eingetragen, der dann in der
HSP-Routine die Bildschirmverschiebung jeweils wie benötigt in $ D016 einträgt.
Damit hätten wir alle Einzelheiten der
HSP-Routine besprochen. Wie Sie sehen
funktioniert sie sogar noch einfacher
als die VSP-Routine. Vielleicht experimentieren Sie einmal ein wenig mit den
Programmbeispielen und versuchen einen
horizontalen Endlosscroller daraus zu
machen. Der HSP-Effekt funktioniert
übrigens genauso wie VSP auch mit HI-RES- Grafik. Im nächsten Kursteil werden
wir auch das noch sehen, und die erstaunlichste IRQ-Raster- Routine kennenlernen die je entdeckt wurde: die AGSP-Routine nämlich, die eine Kombination
aus HSP und VSP darstellt, und mit der
es problemlos möglich ist den kompletten
Bildschirm in ALLE Richtungen zu scrollen, ohne große Kopieraktionen mit dem
Prozessor durchführen zu müssen! ! !
(ih/ub)
IRQ-KURS "Die Hardware ausgetrickst..." (Teil 16)
Herzlich Willkommen zum 16 . Teil unseres
Kurses über die Rasterstrahlprogrammierung. In den letzten beiden Kursteilen
hatten wir uns mit zwei IRQ-Routinen
beschäftigt, die das hardwaremässige
Scrollen des Bildschirms jeweils in der
Horizontalen ( HSP-Routine) und Vertikalen ( VSP-Routine) ermöglichten. Der Clou
an diesen beiden Routinen war, daß des
GESAMTE Bildschirm vom VIC verschoben
wurde, und wir keinen einzigen Taktzyklus zum Kopieren von Bildschirmdaten
verschwenden mussten. In diesem Kursteil
wollen wir nun das " Sahnestückchen" der
Raster-IRQ- Programmierung besprechen:
die Kombinattion aus HSPund VSP-Routine, genannt " AGSP" . Diese Abkürzung
steht für " Any Given Screen Position" und bedeutet, daß der Bildschirm in jede Richtung verschoben werden kann, und das
natürlich HARDWAREMÄSSIG, also wie schon
bei HSP und VSP ohne auch nur einen Zyklus zum Kopieren von Bildschirmdaten zu
verschwenden!
Stellen Sie sich vor, was für Möglichkeiten sich hierdurch für Spiele ergeben: so kann man z. B. problemlos Spiele
programmieren, die sich über einer beliebig großen Oberfläche abspielen, die
in alle Richtungen flüssig gescrollt
werden kann, ohne dabei mehr als 30 Rasterzeilen pro Frame zu verbrauchen.25 Rasterzeilen lang ist der Prozessor eigentlich nur mit dem Abpassen der optimalen Rasterstrahlpostion für den HSP-Effekt beschäftigt, der, wie wir schon
im 14 . Kursteil sehen konnten, pro Charakterzeile, die der Bildschirm horizontal gescrollt werden soll, eine Rasterzeile für das Timing verbraucht.
Damit Sie sehen, was mit der AGSP-Routine alles möglich ist, haben wir
Ihnen natürlich wieder zwei Beispielpro- gramme vorbereitet. Sie heissen " AGSP1" und " AGSP2" und werden wie immer mit
",8,1" geladen und durch ein " SYS4096" gestartet. Desweiteren finden Sie auf
dieser MD eine Demo mit Namen " AGSP-Demo", in der am eindrucksvollsten die
Möglichkeiten der AGSP-Routine demonstriert werden. Sie sehen hier einige
Hiresgrafiken, die in Sinuswellenform
aufund abschwingen und gleichzeitig
von links nach rechts scrollen. Hierbei
werden jeweils neue Grafiken in die Grafik einkopiert, so daß der Eindruck entsteht, daß der Bildschirm weitaus größer
sei, als eigentlich sichtbar. Das Demo
laden Sie mit LOAD" AGSP-DEMO",8 und
starten es durch " RUN" .
1) DAS AGSP-PRINZIP Kommen wir nun also zum Arbeitsprinzip
von AGSP, das eigentlich recht schnell
erläutert ist: Zunächst einmal passen
wir die erste Rasterzeile des Bild- schirms ab, an der die erste Charakterzeile gelesen werden muß. Hier nun lassen wir eine VSP-Routine, so wie wir Sie
im letzten Kurteil schon kennenlernten, zunächst dem VIC vorgaugeln, daß er auf
den Beginn der Charakterzeile noch zu
warten hat, indem wir eine horizontale
Bildschirmverschiebung um 1 Pixel in
$ D011 angeben. Inmitten dieser Zeit soll
nun die VSP-Routine diese Verschiebung
wieder Aufheben, um so den VIC sofort
die Charakterdaten lesen zu lassen, und
ihn somit dazu zu zwingen, den Bildschirm nach rechts versetzt darzustellen. Die Anzahl Taktzyklen, die bis zum
Zurückschalten verzögert werden müssen, muß dabei der Anzahl Zeichen entsprechen, die der Bildschirm verschoben werden soll. Hieran anschließend lasen wir
eine HSP-Routine folgen, die den Beginn
der nächsten Charakterzeilen vor dem VIC
herdrückt, um so auch die zeilenweise
Verschiebung des Bildschirmanfangs zu
erreichen. Da diese Routine maximal 25
Rasterzeilen benötigt ( nämlich dann, wenn die Darstellung erst 25 Charakterzeilen später beginnen soll, müssen die
ersten 25 Rasterzeilen des sichtbaren
Bildschirms immer für das Timing der
AGSP-Routine verwendet werden, auch wenn
stellenweise weniger Rasterzeilen dazu
benötigt werden ( z. B. wenn nur eine Zeile Verschiebung erzeugt werden muß) . Für
diese Fälle müssen wir mit Hilfe einer
normalen FLD-Routine den Bildschirm um
die fehlende Anzahl Rasterzeilen nach
unten drücken, damit der Bildschirm auch
immer in ein und derselben Rasterzeile
beginnt. Diese FLD-Routine wird in unserem Programmbeispiel den VSPund HSP-Routinen vorgeschaltet. Sie sorgt also
gleichzeitig für immer gleiches Timing
und zudem für die richtige Positionierung in der Horzontalen. Aus diesem
Grund sind dann die ersten 25 Rasterzeilen des Bildschirms auch nicht zum Darstellen von Bildschirmdaten verwendbar.
In unserem Programmbeispielen haben wir hier jedoch Sprites untergebracht, in
denen die Scrollrichtung angezeigt wird.
In einem Spiel könnte sich hier z. B.
auch eine Score-Anzeige befinden.
2) DAS PROGRAMM Wollen wir uns nun die Funktionsweise
von AGSP anhand des Programmbeispiels
" AGSP1" anschauen. Die Routinen zur Joystickabfrage und Scrollgeschwindigkeitsverzögerung sollen hier nicht Thema unseres Kurses sein, sondern lediglich die
Routine, die uns den Bildschirm beliebig
im sichtbaren Fenster positioniert. Zur
angesprochenen Scrollgeschwindigkeitsverzögerung sei erwähnt, daß wir hier
beim Drücken und Loslassen des Joysticks
die Scrollrichtung verzögern, bzw. nachlaufen lassen, so daß der Scrolleffekt
zunächst träge anfängt und beschleunigt
und beim Loslassen erst abgebremst werden muß, bis er zum Stillstand kommt.
Die Initialisierungsroutine zu AGSP1 bei$1000 legt nun zunächst einen Border-IRQ
in Zeile $ FB fest, wobei, wie schon so
oft in unseren Beispielen, der Hard-IRQ- Vektor bei $ FFFE/$ FFFF benutzt wird.
Diese Border-Routine ( bei $1400) schaltet nun den oberen und unteren Bildschirmrand ab und initialisiert einen
Raster-IRQ für Rasterzeile $18, wobei
dann unsere eigentliche IRQ-Routine
" AGSP" angesprungen wird. Sie befindet
sich an Adresse $1200 und soll im nun
Folgenden ausführlich beschrieben werden. Die AGSP-Routine soll nun der Reihe
nach folgende Arbeiten verrichten:
* Verwaltung des Sprite-Scoreboards, das
aus zwei Spritereihen zu je acht Sprites
bestehen soll.
* Glätten des IRQs, um ein möglichst
exaktes Timing zu bewirken.
* FLD-Routine benutzen, um die fehlenden
Rasterzeilen, die von VSP ggf. nicht
benötigt werden, auszugleichen
* VSP-Routine charakterweisen zum hoch- und runterscrollen des Bildschirms einsetzen
* HSP-Routine zum charakterweisen linksund rechtsscrollen des Bildschirms einsetzen
* horizontales und vertikales Softscrolling zur exakten Bildschirmverschiebung
durchführen
a) DIE SPRITELEISTE Kommen wir nun also zum ersten Teil der
AGSP-Routine, die zunächst das Sprite-Scoreboard verwaltet. Wir verwenden hier
zwei Sätze zu je acht Sprites, deren
Spritedaten in den Adressen von $0 C00-$1000 zu finden sein sollen ( Sprite-Pointer $30-$3 f) . Um die Pointer zwischen beiden leichter umschalten zu können benutzen wir zwei Video-RAM- Adressen. Für die erste Zeile, die eh in
einem unsichtbaren Bildbereich liegt, wird das Video-RAM an Adresse $0000 verschoben, so daß wir die Spritepointer in
den Adressen $03 F8-$03 FF liegen haben.
Für die zweite Spritezeile wird das Video- RAM an die ( normale) Adresse $0400 gelegt, womit wir die Spritepointer an
den Adressen $07 F8-$07 FF vorfinden. Dadurch, daß durch den VSP-Effekt auch
diese Spritepointer als einzelne Zeichen
auf dem Bildschirm auftauchen können, müssen wir die Pointer in jedem Rasterdurchlauf ( Frame) wieder neu setzen, da
die Pointer auf dem Bildschirm ja durchaus auch Bildschirmzeichen enthalten
können. Doch kommen wir nun endlich zum
Source-Code der AGSP-Routine, der in
seinem ersten Teil zunächst einmal die
Spriteinitialisierungen vornimmt und
eigentlich einfach zu verstehen sein
sollte:
agsp: pha ;IRQ-Beginn -> also txa ; Prozessorregs. pha ; retten tya pha lda #$1e ;Y-Position aller sta $d001 ; Sprites auf Raster- sta $d003 ; zeile $1E (dez. 30) sta $d005 ; setzen sta $d007 sta $d009 sta $d00b sta $d00d sta $d00f lda #$01 ;Die Farbe aller sta $d027 ; Sprites auf 1, also sta $d028 ; 'weiß' setzen sta $d029 sta $d02a sta $d02b sta $d02c sta $d02d sta $d02e
lda #$ ff ; Alle Sprites einsta $ d015 ; schalten lda #$80 ; Hi-Bit d. X-Pos. von sta $ d010 ; Sprite 8 setzen lda #$00 ; Sprite-Multicolor sta $ d01 c ; ausschalten
clc ;C-Bit f.Add. löschen lda #$58 ;XPos Sprite 0 sta $d000 ; setzen adc #$18 ;$18 (dez.24) add. sta $d002 ; u. XPos Spr.1 setz. adc #$18 ;$18 (dez.24) add. sta $d004 ; u. XPos Spr.2 setz. adc #$18 ;usw. sta $d006 adc #$18 sta $d008 adc #$18 sta $d00a adc #$18 sta $d00c adc #$18 ;$18 (dez.24) add. sta $d00e ; u. XPos Spr7 setz.
Bis hierhin hätten wir nun die wichtigsten Spritedaten initialisiert. Dadurch, daß diese in jedem Frame neu gesetzt werden, können Sie im AGSP-Bereich problemlos auch noch acht weitere Sprites
sich bewegen lassen. Sie müssen deren
Spritedaten dann nur am Ende der AGSP-Routine wieder in die VIC-Register einkopieren. Nun folgt noch die Initialisierung der Spritepointer. Zunächst die
für die Sprites der ersten Spritezeile, deren Pointer ja im Video-RAM ab Adresse
$0000 untergebracht sind, und sich somit
in den Adressen von $03 f8-$03 ff befinden. Anschließend werden die Spritepointer für die zweite Spritereihe eingetragen, die sich im normalen Bildschirmspeicher bei $0400, in den Adressen
$07 f8-$07 ff befinden. Das abschließende
schreiben des Wertes 3 in Register $ DD00 stellt sicher, daß der 16 K-Adressbereich
des VICs auf den unteren Bereich, von
$0000 bis $3 FFF, gelegt ist:
ldx #$30 ;Spr.Pointer Spr0=$30 stx $03f8 ; bis Spr7=$37 in inx ; die Pointeradressen stx $03f9 ; $03F8-$03FF ein- inx ; tragen stx $03fa inx stx $03fb inx stx $03fc inx stx $03fd inx stx $03fe inx stx $03ff inx ;Spr.Pointer Spr0=$38 stx $07f8 ; bis Spr8=$3F in inx ; die Pointeradressen stx $07f9 ; $07F8-$07FF ein- inx ; tragen stx $07fa inx stx $07fb inx stx $07fc inx stx $07fd inx stx $07fe inx stx $07ff
lda #$03 ; VIC-Adressraum auf sta $ dd00 ;$0000-$3 FFF schalt.
b) DAS GLÄTTEN DES IRQS Der nun folgende Teil der AGSP-Routine
ist für das Glätten des IRQs verantwortlich. Ausserdem wird hier der Border-IRQ
wieder als nächste IRQ-Quelle festgelegt, womit diese Arbeit nun auch erledigt wäre:
dec $d019 ;IRQ-ICR freigeben lda #$1d ;Nächster Raster-IRQ sta $d012 ; bei Zeile $1D lda #<irq2 ;Adresse Glättungs- sta $fffe ; IRQ in $FFFE/$FFFF lda #>irq2 ; eintragen sta $ffff cli ;IRQs erlauben
ch: nop ; Insgesamt 23 NOPs, in
... ; denen der Glättungs- nop ; IRQ ausgelöst wird bne ch
Beachten Sie bitte, daß das Programm
hier nicht mehr normal fortgesetzt wird, sondern daß innerhalb der 23 NOPs auf
den Glättungs-IRQ bei " IRQ2" gewartet
wird. Selbiger folgt gleich diesem
Stückchen Source-Code, wobei ihm ein
RTS-Befehl vorangestellt ist, der mit
dem Label " Cycles" assoziiert wird. Dieser RTS-Befehl wird später im AGSP-IRQ
desöfteren zum Verzögern angesprungen.
Zusammen mit dem aufrufenden JSR-Befehl
werden so 12 Taktzyklen verbraucht ( je
Befehl 6 Zyklen) :
cycles:
rts ; Verzög.- Unterroutine
irq2:pla ;(IRQ-) Prozessorstatus pla ; und Rücksprungadresse pla ; vom Stapel werfen dec $d019 ;VIC-ICR freigeben lda #$f8 ;Nächster Raster-IRQ sta $d012 ;bei Zeile $F8 lda #<border;Adresse der Border-IRQ sta $fffe ; -Routine in Vektor lda #>border; bei $FFFE/$FFFF ein- sta $ffff ; tragen lda #$c8 ;40-Zeichen-Darst. sta $d016 ; einschalten lda #$06 ;Video-RAM nach $0000 sta $d018 ; verschieben (für Spr. Pointer 1.Spr.Zeile, sh. oberste 4 Bits!) nop ;verzögern
lda $ d012 ; Und den letzten Taktcmp #$1 d ; Zyklus korrigieren beq line ;( IRQ-Glättung)
line: lda #$00
. . .
c) DIE FLD-ROUTINE Nun folgt der wichtige Teil der AGSP-Routine. Zunächst einmal noch ein wenig
Verwaltungsarbeit. Wir setzen hier den
Bildschirmbereich, in dem das VSP-Timing
unergebracht ist auf schwarz und setzen
schon einmal die Y-Positionen der Sprites für die zweite Spritezeile des Scoreboards ( wie Sie aus unseren Kursteilen
über die Spriteprogrammierung wissen, so
wird die neue Y-Koordinate erst dann vom
VIC übernommen, wenn die ersten Sprites
fertig gezeichnet sind - somit können
wir die neuen Koordinaten irgendwann
setzen, wenn der VIC die alten Sprites
noch zeichnet - was hier der Fall ist) :
line lda #$00 ; Bildschirm und Rahsta $ d020 ; mein auf ' schwarz' sta $ d021 ; schalten
jsr cycles ;3*12=36 Zyklen jsr cycles ; verzögern jsr cycles bit $ea ;3 Zyklen verz. ldy #$1e+21;YPos=YPos+21 sty $d001 ; und für die Spr0- sty $d003 ; Spr7 setzen sty $d005 sty $d007 sty $d009 sty $d00b sty $d00d sty $d00f
Nach diesen mehr organisatorisch wichtigen Aufgaben folgt nun die FLD-Routine, die das Timing zur VSP-Routine ausgleichen soll:
fld: ldx #$27 ; Max. Anz. FLD-Durchl.
ldy #$01 ; Index d011/ d018- Tab.
fldlp: jsr cycles ;12 Zyklen verz.
lda field1,y;Wert für $d011 holen sta $d011 ; und setzen lda field2,y;Wert für $d018 holen sta $d018 ; und setzen nop ;6 Zyklen verz. nop nop iny ;Tab-Index+1 dex ;FLD-Durchläufe-1 cpx <fldcnt ;=erforderliche Anz. bne fldlp ; Durchl.?Nein->Weiter nop ;Sonst 14 Zyklen jsr cycles ; verzögern lda field2,y;und letzten Wert für iny ; $d018 setzen sta $d018
( Anm. d. Red. : Bitte wählen Sie nun den 2 . Teil des Kurses aus dem Textmenu)
IRQ-Kurs - 2.Teil
Die FLD-Schleife benötigt für einen
Durchlauf exakt eine Rasterzeile. In
jedem Durchlaub wird vor Beginn der Charakterzeile durch die horizonale Verschiebung in $ D011 der Charakterzeilenanfang vor dem Rasterstrahl hergedrückt. Dies bewähltigen wir mit Hilfe
einer Liste namens " Field1" . Sie wurde
im Hauptprogramm der AGSP-Routine vorinitialisiert und befindet sich an Adresse $0100 . Es stehen dort Werte für
$ D011, die die zur normalen Bildschirmdarstellung notwenidigen Bits gesetzt
haben, und deren Bits für der horizontale Bitverschiebung jeweils um eine Rasterzeile erhöht werden, und bei acht
Rasterzeilen Verschiebung wieder auf 0 zurückgesetzt sind. Die Tabelle ist 64 Einträge lang und enthält somit für jede
Rasterzeile ab dem Beginn der FLD-Routine einen passenden Wert für die$ D011- Verschiebung. Sie wird ebenso in
der VSP-Routine verwendet. Die Tabelle
" Field2" erfüllt einen ähnlichen Zweck:
Sie enthält Werte für Register $ D018, befindet sich an Adresse $0140 und
enthält ebenfalls 64 Werte. Auch ihre
Werte gelangen in jeder Rasterzeile ( also auch jedem FLD-Durchlauf) in das Register $ D018, womit wir den Adressbereich des Video-RAMs verschieben. Die
Tabelle enthält 20 Einträge, die das
Video-RAM an Adresse $0000 legen, gefolgt von 44 Einträgen, die es an Adresse $0400 verschieben. Auf diese Weise
schaltet Sie also exakt in der 21 . Rasterzeile nach Beginn der FLD-Routine
auf den Bildschirm bei $0400 um, womit
wir die Spritepointer auch auf diesen
Bereich umschalten. Nach dieser 21 . Rasterzeile hat der VIC nämlich gerade die
erste Spritereihe fertiggezeichnet und
wir bringen ihn so also dazu die auch
noch die zweite Reihe mit anderen Pointern zu zeichnen. Der Grund, warum dies über eine Tabelle geschehen muß, und
nicht etwa durch Abpassen der entsprechenden Position und dem dann folgenden
Umschalten liegt auf der Hand: Braucht
die der FLD-Routine folgende VSP-Routine
z. B.25 Rasterzeilen, um den Bildschirm
25 Charakterzeilen tiefer darzustellen, so läuft unsere FLD-Routine nur einmal
durch und endet, wenn die Sprites noch
längst nicht ganz fertig gezeichnet
sind. Umgekehrt kann es auch passieren, daß die VSP-Routine keine Zeit benötigt, weil keine Verschiebung notwendig ist, und deshalb die FLD-Routine 25 Rasterzeilen lang laufen muß, damit der Bildschirm an derselben Position wie im
letzten Frame erscheint. In dem Fall muß
das Umschalten von der FLD-Routine
durchgeführt werden. Benutzen allerdings
beide Routinen ein und dieselbe Tabelle
und denselben Index darauf, so übernimmt
automatisch die Routine die Umschaltungsaufgabe, die gerade an der Reihe ist, ohne, daß wir etwas dazutun müssen! Dies mag verwirrender klingen als es ist: Im
Endeffekt stellt die Tabelle sicher, daß
immer in der 21 . Rasterzeile seit FLD-Beginn, die Spritepointer umgeschaltet
werden - unabhängig davon, ob sich der
Prozessor zu diesem Zeitpunkt noch in
der FLDoder schon in der VSP-Routine
befindet!
d) DIE VSP-ROUTINE Nach FLD folgt die VSP-Routine, die sich
von der ersteren nur darin unterscheidet, daß sie mit zwei Zyklen Verzögerung
die Änderungen in $ D011 einträgt und
somit nicht nur den Beginn der nächsten
Charakterzeile, sondern auch den VICinternen Adresszeiger selbiger erhöht
und somit eine Charakterzeile überspringt:
vsp: inx ;Alter FLD-Zähler= stx <vspcnt ; VSP-Zähler+1 nop ;8 Takte verzögern nop nop nop vsplp:nop ;8 Takte verzögern nop nop nop ldx field1+3,y;Wert aus d011-Tab+3 stx $d011 ; nach $D011 kop. nop ;Nochmals 6 Takte nop ; verz. (Ausgleich nop ; zu FLD) lda field2,y ;Wert aus d018-Tab sta $d018 ; nach $D018 kop. nop ;4 Zyklen bis Ende nop ; verz. iny ;Tab-Index+1 dec <vspcnt ;VSP-Zähler-1 bne vsplp ;<>0 -> Weiter bit $ea ;Sonst 7 Takte nop ; verzögern nop
Wie Sie sehen, ist dies quasi unsere
FLD-Routine. Einziger Unterschied liegt in der Art, wie die beiden Tabellen ausgelesen und geschreiben werden. Um den
VSP-Effekt zu erzielen kann dies hier
nicht mehr direkt aufeinanderfolgen.
Ausserdem wird hier nicht mehr der nächste Tabellenwert von " Field1" gelesen, sondern der dritte Wert danach. Dies tun
wir, um in jedem Fall eine $ D011- Wert zu
schreiben, der die Charakterzeile mindestens 1 Rasterzeile vor den Rasterstrahl
drückt. Durch die Zeit die zwischen FLD
und VSP vergeht haben wir nämlich auch
schon eine Charakterzeile verloren, und
damit der Y-Index nicht unnötig erhöht
werden muß greifen wir einfach auf einen
weiter entfernten Tabellenwert zu ( wieviele Rasterzeilen wir die Charakterzeile vor uns herschieben ist ja egal - es
zählt nur, daß sie vor uns hergeschoben
wird) ! Die Anzahl der VSP-Schleifendurchläufe wird durch den FLD-Zähler ermittelt. In der FLD-Routine
wurde das X-Register mit dem Wert $27 initialisiert. Nach Abzug der FLD- Durchläufe enthält das X-Register nun
noch die erforderliche Anzahl VSP-Durchläufe, die in dem Label " VSPCNT"( Zeropageadresse $069) bgelegt wird und
von nun an als Zähler dient.
e) DIE HSP-ROUTINE Nun folgt dem ganzen noch die HSP-Routine, die Sie ja noch aus dem letzten
Kursteil kennen. Wir schreiben hier
zunächst den nächsten Wert der Field1- Tabelle in $ D011 und verzögern dann bis
zum gewünschten Punkt um den geschriebenen $ D011- Wert-1 zu schreiben, mit dem
die horizontale Bildverschiebung erzielt
wird:
HSP: ldx field1+3,y;nächst. d011-Wert stx $d011 ;schreiben jsr cycles ;Anfang sichtb. ; Bildschirm abwart. dex ;d011-Wert-1 redu1:beq redu2 ;Ausgleich für unge- redu2:bne tt ; rade Zyklen nop ;Insgesamt 20 ... ; NOPs für das nop ; HSP-Timing tt stx $d011 ;akt.Z. einschalt.
Hier also drücken wir zunächst den Charakterzeilenbeginn vor den Rasterstrahl
und warten bis zum benötigten Zeitpunkt
um die vertikale Bildschirmverschiebung
um eine Rasterzeile herunterzustellen
und so den HSP-Effekt zu erzielen. Die
merkwürdige BEQ/ BNE-Folge dient dem Ausgleichen eines ggf. ungeraden Verzögerungszeitraums. Das Label " tt" wird von
der Timingroutine verändert um so verschiedene Zeitverzögerungen innerhalb
der 20 NOPs zu erreichen.
f) DAS SOFTSCROLLING Dies ist die leichteste Aufgabe unserer
IRQ-Routine. Es werden hier lediglich
die Softscrollingwerte in der Horizonalen und Vertikalen in die Register $ D016 und $ D011 eingetragen. Die zu schreiben- den Werte wurden von der Timing-Routine
berechnet und in den Operanden der LDA-Befehle bei " HORIZO" und " VERTIC" eingetragen:
horizo lda #$00 ; Versch. vom linken sta $ d016 ; Bildrand vertic lda #$00 ; Versch. vom oberen sta $ d011 ; Bildrand
ldx #$57 ;Verzögerungsschleife w1 dex ;bis Bildanfang bne w1 ldy #$17 ;Video-RAM bei $0400 sty $d018; einsch. lda $d011;$D011 laden and #$1F ; relev. Bits ausmask. sta $d011; und schreiben lda #$0e ;Bildschirmfarben sta $d020; zurücksetzen lda #$06 sta $d021 pla ;Prozessorregs. vom tay ; Stapel holen und IRQ pla ; beenden. tax pla rti
Soviel nun also zu unserer IRQ-Routine.
Sie eredigt für uns die entsprechenden
Aufgaben zur Bildverschiebung. Allerdings muß sie auch irgendwie mit Basiswerten gefüttert werden, um das Timing
für alle Fälle genau aufeinander anzustimmen. Dies übernehmen weitere Steuerroutinen, die wir im nächsten, und dann
auch letzten Teil dieses Kurses besprechen werden.
(ub)
IRQ-KURS "Die Hardware ausgetrickst..." (Teil 17)
Herzlich Willkommen zum 17 . und letzten
Teil unseres IRQ-Kurses. In dieser Ausgabe möchten wir Ihre Ausbildung abschließen und Ihnen die letzten Geheimnisse der AGSP-Routine erläutern, mit
deren Besprechung wir ja schon im letzten Monat begannen.
Dort hatten wir zunächst die IRQ-Routine
besprochen, die es uns ermöglicht, den
AGSP-Effekt durchzuführen. Sie bestand
aus einer geschickten Kombination der
Routinen FLD, VSP und HSP, sowie einigen
Befehlen zum Softscrollen des Bildschirms. Zudem hatten wir noch einen
einfachen Sprite-Multiplexer mit integriert. Wie Sie also sehen, ist die
AGSP-Routine auch ein gutes Beispiel
dafür, wie die Raster-IRQ- Effekte in
Kombination miteinander ein perfektes Zusammenspiel ergeben können.
Prinzipiell ist die im letzten Monat
besprochene AGSP-IRQ- Routine also in der
Lage, uns den Bildschirm um jeden beliebigen Offset in Xund Y-Richtung zu
verschieben ( in X-Richtung um 0-319, in
Y-Richtung um 0-255 Pixel) . Damit wir
jedoch eine SPEZIELLE Verschiebung umsetzen können, muß die AGSP-Routine
natürlich auch mit speziellen Parametern
gefüttert werden, die von einer anderen
Routine in der Hauptschleife des Prozessors vorberechnet werden müssen. Diese
Routine exisitiert natürlich auch in
unseren Programmbeispielen " AGSP1" und
" AGSP2", sie heißt " Controll" und soll
das Thema dieses Kursteils sein.
1) DIE PARAMETER DER AGSP-ROUTINE Wollen wir zunächst einmal klären, welche Parameter unsere AGSP-Routine benötigt. Da es eine IRQ-Routine ist, können
wir selbige natürlich nicht so einfach übergeben. Sie müssen teilweise in Zeropageadressen oder sogar direkt im Code
der IRQ-Routine eingetragen werden. Insgesamt gibt es 5 Stellen, die wir ändern
müssen, damit die AGSP-Routine auch die
Parameter bekommt, die sie benötigt, um
eine ganz bestimmte Verschiebung durchzuführen. Die Verschiebungen in Xund
Y-Richtungen werden wir im folenden mit
XPos und YPos bezeichnen. Unsere Beispielprogramme verwalten auch gleichnamige Variablen in der Zeropage. Sie werden von der Joystickabfrage automatisch
auf die zwei benötigten Werte gesetzt, um die die AGSP-Routine den Bildschirm
in beide Richtungen verschieben soll. Da
dies in jedem Frame erneut geschieht
wird so der Eindruck einer flüssigen
Verschiebung erzielt. Da die X-Verschiebung Werte größer als 256 aufnehmen muß, benötigen wir für den Wert
XPos also zwei Speicherstellen, die von
unserem Beispielprogramm in den Zeropageadressen $02/$03 in Lo/ Hi-Byte-
Darstellung verwaltet werden. Für YPos
genügt uns ein Byte, das in der Zeropageadresse $04 zu finden ist.
Wollen wir nun jedoch zunächst herausstellen, welche Parameter aus diesen
beiden Werten berechnet werden müssen, damit die AGSP-Routine die korrekte
Bildschirmverschiebung durchführt:
a) FLDCNT FÜR FLD UND VSP Wie Sie sich sicherlich noch erinnern, so benutzte unsere AGSP-Routine eine
FLD-Routine, um das Timing zur VSP-Routine auszugleichen, für den Fall, daß
letztere weniger als 25 Charakterzeilen
vertikale Bildschirmverschiebung
durchführen sollte. Um nun die korrekte
Anzahl Zeilen zu verschieben, hatte sich
die FLD-Routine eines Zählers bedient, der ihr angab, wieviel Mal sie durchzulaufen hatte. Zur besseren Erläuterung
des Zählers hier noch einmal der Kern
der FLD-Routine:
fld: ldx #$27 ;Max. Anz. FLD-Durchl. ldy #$01 ;Index d011/d018-Tab. fldlp:jsr cycles ;12 Zyklen verz. lda field1,y;Wert für $d011 holen sta $d011 ; und setzen lda field2,y;Wert für $d018 holen sta $d018 ; und setzen nop ;6 Zyklen verz. nop nop iny ;Tab-Index+1 dex ;FLD-Durchläufe-1 cpx <fldcnt ;=erforderliche Anz. bne fldlp ; Durchl.?Nein->Weiter
Der besagte FLD-Zähler befindet sich nun
im X-Register, das mit dem Wert $27 vorinitialisiert wird. Dieser Wert ergibt
sich aus der Anzahl Rasterzeilen, die
für das VSP-Timing benötigt werden. Normalerweise sollten dies 25 Rasterzeilen
sein. Da wir jedoch ein Spritescoreboard
mit einer Gesamthöhe von 42(=$2 a) Ra- sterzeilen darstellen, muß auch diese
Anzahl Zeilen übersprungen werden. Der
FLD-Zähler enthält diesen Wert minus 3, da ja auch schon durch die IRQ-Glättung
2 Rasterzeilen verbraucht wurden und
nach der FLD-Routine der Zähler wieder
für die VSP-Routine um 1 erhöht wird.
Dieser Zähler im X-Register wird nun pro
Rasterzeile einmal herabgezählt, solange, bis er dem Wert in FLDCNT entspricht, womit die FLD-Schleife verlassen wird. Aus dem verbleibenden Wert im
X-Register, der dann genau dem FLDCNT-Wert entspricht, ergibt sich der VSPCNT, der angibt, wie oft die VSP-Routine
durchlaufen werden muß. Dieser Zähler
muß nicht eigens berechnet werden. Somit
ist VSPCNT also einer der 5 Parameter, die die AGSP-Routine benötigt. Er berechnet sich aus YPos dividiert durch 8 .
b) REDU1 UND REDU2 FÜR HSP Nachdem also der Parameter für die VSP- Routine und somit der Chrakterzeilen-Verschiebung geklärt ist, wollen wir zu
den Parametern für die HSP-Routine kommen. Dies sind keine Parameter im eigentlichen Sinne, sondern Änderungen im
Code der HSP-Routine, die eine Verzögerung der gewünschten Dauer durchführen.
Wie Sie sich vielleicht erinnern, so
wird mit jedem Taktzyklus, den die HSP-Routine das Zurücksetzen des Bildschirms
auf " Charakterzeile zeichnen" verzögert, die Darstellung desselben um ein Zeichen
( also 8 Pixel) nach rechts verschoben.
Das heißt also, daß wir XPOS/8 Taktzyklen verzögern müssen, um die gewünschte
Verschiebung zu erzielen. Auch hier zeige ich Ihnen wieder einen Auszug aus der
HSP-Routine, der der besseren Erläuterung dienen soll:
HSP: ldx field1+3,y;nächst. d011-Wert stx $d011 ;schreiben jsr cycles ;Anfang sichtb. ; Bildschirm abwart. dex ;d011-Wert-1 redu1:beq redu2 ;Ausgleich für unge- redu2:bne tt ; rade Zyklen nop ;Insgesamt 20 ... ; NOPs für das nop ; HSP-Timing tt stx $d011 ;akt.Z. einschalt.
Die HSP-Routine setzt die Y-Verschiebung
des Bildschirms nun zunächst hinter den
Rasterstrahl, um so dem VIC vorzugaukeln, er befände sich noch nicht in einer Rasterzeile. Anschließend wird mit
dem JSR-, dem DEX-, und den BEQ-/ BNE-Befehlen auf den Anfang des linken Bildschirmrandes gewartet, wobei die letzen
beiden Befehle ausschließlich für das
taktgenaue HSP-Timing herangezogen werden. An den Labels " REDU1" und " REDU2", wird unsere Parameterberechnungsroutine
später den Code modifizieren, so daß das
Timing exakt dem gewünschten XPos/8- Offset entspricht. Hierzu dienen auch
die 20 NOP-Befehle, die genau 40 Zyklen,
bzw. Zeichen verzögern, wodurch im extremfall alle Zeichen übersprungen werden können. Der anschließende STX-Befehl
setzt dann die Darstellung wieder auf
Charakterzeilenbeginn, womit der VIC
dazu gezwungen wird, sofort eine Charakterzeile zu lesen und anzuzeigen. Also
erst hier wird der eigentliche Effekt
ausgeführt. Um nun das Einsetzen der
Charakterzeile genau abzutimen, müssen
wir also um XPOS/8 Taktzyklen verzögern.
Diese Verzögerung kann recht haarig umzusetzen sein, wenn es sich dabei um
eine ungerade Anzahl Zyklen handelt.
Diese Aufgabe soll der Branch-Befehl, beim Label REDU1 lösen. Zunächst einmal
sollte erwähnt werden, daß das X-Register nach dem DEX-Befehl IMMER einen
Wert ungleich null enthält, da dort ja
der benötigte Wert für $ d011 steht, der
immer größer als 1 ist, womit durch den
DEX-Befehl nie auf 0 herabgezählt werden
kann. Das Zeroflag ist beim Erreichen
des Labels REDU1 also immer gelöscht.
Wie auch schon beim Glätten des IRQs
benutzen wir nun also den Trick mit den
Branch-Befehlen, um ggf. eine ungerade
Verzögerung zu erzielen. Wie Sie von
dort vielleicht noch wissen, benötigt
ein Branch-Befehl immer mindestens 2 Taktzyklen. Trifft die abgefragte Bedingung nun zu, so dauert die Abarbeitung
des Branchbefehls immer einen Zyklus
mehr, also 3 Zyklen. Soll der Bildschirm
nun um eine gerade Anzahl Zeichen nach
rechts verschoben werden, so trägt die
Parameterberechnungsroutine bei REDU1 den Assembler-Opcode für einen BEQ-Befehl ein. Da diese Bedingung nie
erfüllt ist, werden nur 2 Zyklen verbraucht und direkt mit dem folgenden
Befehl bei REDU2 fortgefahren. Muß eine
ungerade Anzahl Taktzyklen verzögert
werden, so setzt die Parameterberechnung
bei REDU1 den Opcode für einen BNE-Befehl ein. Da diesmal die Bedingung
zutrifft, dauert die Abarbeitung des
Befehls 3 Taktzyklen, wobei jedoch eben- falls mit REDU2 fortgefahren wird. Dadurch dauert die Routine nun einen
Taktzyklus mehr, womit wir eine ungerade
Verzögerung erzielt haben.
Der Branch-Befehl bei REDU2 muß nun
ebenfalls modifiziert werden. Hierbei
bleibt der Opcode jedoch immer derselbe, also ein BNE-Befehl, der immer wahr ist.
Es wird lediglich der Operand dieses
Befehls geändert, so daß nicht mehr auf
das Label " TT" gesprungen wird, sondern
auf einen der zuvor stehenden NOP-Befehle, wodurch die entsprechende HSP-Verzögerung sichergestellt wird. Der
Operand eines Branch-Befehles kann nun
ein Bytewert zwischen -127 und +128 sein, der den Offset angibt, um den der
Branch-Befehl den Programmzähler des
Prozessors erhöhen, bzw. erniedigen
soll. Steht hier also der Wert $05, so
werden die nächsten 5 Bytes im Code
übersprungen. Da ein NOP-Befehl immer
ein Byte lang ist, werden also in unserem Fall exakt 5 NOPs übersprungen, wo- mit noch 15 NOPs ausgeführt werden, die 15*2=30 Taktzyklen verzögern, und somit
den Bildschirm in der Horzontalen erst
ab dem dreißigsten Charakter beginnen
lassen. Der Sprungoffset für den BNE-Befehl berechnet sich danach also aus
der Formel:' Anzahl NOPs'- XPOS/8/2=20- XPOS/16 .
Mit Hilfe dieser beiden Branch-Befehle
hätten wir nun also das entsprechende
Timing für die HSP-Routine korrekt umgesetzt. Beachten Sie nur bitte noch folgenden Umstand: Dadurch, daß der
Branch-Befehl bei REDU2 immer ausgeführt
werden muß, also daß seine Bedingung
immer wahr sein muß, müssen wir hier
einen BNE-Befehl verwenden. Gerade aber
WEIL dessen Bedigung immer wahr ist, benötigt er auch immer 3 Taktzyklen, also eine ungerade Anzahl, womit er den
Ausgleich des Branch-Befehls bei REDU1 wieder zunichte machen würde. Damit dies
nicht geschieht, muß die Parameterumrechnungsroutine die Opcodes genau umge- kehrt einsetzen, wie oben beschrieben:
also einen BNE-Befehl, wenn eine gerade
Anzahl Zyklen verzögert werden soll, und
einen BEQ-Befehl, wenn die Anzahl der zu
verzögernden Zyklen ungerade ist! Ich
erläuterte dies im obigen Fall zunächst
anders, um Sie nicht noch zusätzlich zu
verwirren!
c) PARAMETER FÜR DAS SOFTSCROLLING Damit wären also alle Parameterberechnungen, die Raster-IRQ- Effekte betreffend, abgehandelt. Da HSPund VSP-Routine den Bildschirm jedoch immer nur
in 8- Pixel-Schritten verschieben, müssen
wir diesen nun noch mit den ganz normalen Softscroll-Registern um die fehlende
Anzahl Einzelpixel verschieben. Auch
dies wird noch innerhalb des AGSP-Interrupts durchgeführt, nämlich genau
am Ende desselben. Hier hatten wir zwei
Labels mit den Namen " HORIZO" und " VER-TIC" untergebracht, die jeweils auf eine LDA-STA- Befehlsfolge zeigten, und den
entsprechenden Verschiebeoffset in die
VIC-Register $ D011 und $ D016 eintrugen.
Hier nochmal ein Codeauszug aus dem
AGSP-IRQ:
horizo: lda #$00 ; Versch. vom linken sta $ d016 ; Bildrand vertic: lda #$00 ; Versch. vom oberen sta $ d011 ; Bildrand
Um nun die Werte dieser Verschiebungen
zu ermitteln, müssen wir lediglich jeweils die untersten 3 Bits aus XPos und
YPos ausmaskieren und in die Oparanden-Bytes der LDA-Opcodes eintragen. Da die
Register $ D011 und $ D016 jedoch auch
noch andere Aufgaben erfüllen, als lediglich das Softscrolling des Bildschirms zu setzen, müssen auch zur korrekten Bildschirmdarstellung notwendige
Bits in diesen Registern mitgesetzt werden. Auch dies wird unsere Parameterberechnungsroutine übernehmen.
2) DIE AGSP-PARAMETER- ROUTINE " CONTROLL" Kommen wir nun endlich zu der Unterroutine, die die benötigten Parameter in
umgerechneter Form in die AGSP-Routine
einbettet, und letztere zu einer korrekten Anzeige des Bildschirms bewegt.
Nachdem wir die Funktionprinzipien der
Parameterübergabe nun ausführlich besprochen haben, besteht die Controll-Routine nur noch aus ein paar " Rechenaufgaben" . Sie wird von der Border-IRQ- Routine aufgerufen und wertet die
Einträge in XPos sowie YPos aus. Diese
Werte werden, wie schon erwähnt, von der
Joystickabfrage in der Hauptschleife
unseres Programmbeispiels entsprechend
gesetzt und verwaltet.
Dadurch, daß Controll während des Border- IRQs aufgerufen wird, stellen wir
gleichzeitig auch sicher, daß die AGSP-Routine zu einem Zeitpunkt modifiziert
wird, zu dem sie nicht ausgeführt wird,
und vermeiden so ihr Fehlverhalten.
Hier nun also die Controll-Routine, die
als erstes die erforderlichen Werte für
die Y-Verschiebung berechnet. Dies ist
zunächst der Wert für FLDCNT, gefolgt
von dem entsprechenden Eintrag für die
vertikale Softscrolling-Verschiebung, die in VERTIC+1 eingetragen werden muß.
FLDCNT berechnet sich einfach aus
YPos/8 . Der Softscrollwert entspricht
den untersten 3 Bits von YPos, also
( YPos AND $07) . Hierbei muß jedoch durch
das HSP-Timing der benötigte Wert minus
1 ins Softscrollregister eingetragen
werden. Das hängt damit zusammen, daß
die Softscrollveränderung ja ebenfalls
in Register $ D011 eingetragen werden
muß, was ja zugleich Drehund Angelpunkt aller anderen Raster-IRQ- Routinen
ist. Die gewünschte Änderung führen wir
mit Hilfe einer speziellen Befehlsfolge
durch, um uns Überlaufsvergleiche zu
sparen. Zum Schluß steht in jedem Fall der richtige Wert im Akku, in den wir
dann mittels ORA-Befehl noch die Bits 3 und 4 setzen, die den 25 Zeilen-Schirm, sowie den Bildschirm selbst einschalten, was bei $ D011 ja ebenfalls noch berücksichtigt werden muß:
Controll:
lda <ypos ;YPOS holen lsr ; durch 8 lsr ; dividieren lsr sta <fldcnt ;und in FLDCNT ablegen clc ;C-Bit f. Add. löschen lda <ypos ;YPos holen and #$07 ;Unterste Bits ausmask. eor #$07 ; umkehren adc #$1a ; Ausgleichswert add. and #$07 ; wieder maskieren ora #$18 ; Bilschirmbits setzen sta vertic+1 ; und in AGSP ablegen
Der ORA-Befehl am Ende ist übrigens von wichtiger Bedeutung. Wie Sie ja wissen, unterscheiden sich die beiden Beispiel
" AGSP1" und " AGSP2" nur darin, daß die
erste Version einen Textbildschirm, die
zweite Version einen Multicolor-Grafik- Schirm scrollt. Der programmtechnische
Unterschied zwischen diesen beiden
Routinen besteht nun lediglich in der
Änderung des oben aufgeführten ORA-Befehls. In AGSP2 wird hier mit dem Wert
$78 verknüft, womit zusätzlich auch noch
Hiresund Extended-Background- Color-Mode eingeschaltet werden. Dies ist der
EINZIGE Unterschied zwischen den beiden
Beispielen, der eine große Auswirkung
auf das Erscheinen hat! Die AGSP-Routine
selbst ändert sich durch die Grafikdarstellung nicht!
( Anm. d. Red. : Bitte wählen Sie nun den 2 . Teil der IRQ-Routine aus dem Menu)
IRQ-KURS (2.Teil)
Als nächstes behandelt die Routine den
XPos-Wert. Hierzu legt sie sich zunächst
eine Kopie von Lowund Highbyte in den
Labels AFA und AFB an, die den Zeropageadressen $07 und $08 zugewiesen sind.
Anhand des 3 . Bits aus dem Low-Byte von
XPos kann ermittelt werden, ob eine
gerade ( Bit gelöscht) oder ungerade ( Bit
gesetzt) Anzahl Taktzyklen bei HSP
verzögert werden muß. In letzterem Fall
muß in REDU1 der Opcode für einen
BEQ-Befehl eingesetzt werden. Im anderen
Fall steht dort ein BNE-Opcode, der ganz
am Anfang des folgenden Sourcode-Segmentes dort eingetragen wird:
ldx #$d0 ;Opcode für "BNE" in stx redu1 ; REDU1 eintragen lda <xpos+1 ;Hi-Byte XPos holen sta <afb ; und nach AFB kop. lda <xpos ;Lo-Byte XPos holen sta <afa ; nach AFA kop.
and #$08 ;3 . Bit ausmask.
bne co1 ;<>0-> alles ok, weiter
ldx #$ f0 ; Sonst Opcode für " BEQ" stx redu1 ; in REDU1 eintr.
Nachdem der einzelne Taktzyklus korrigiert wurde, müssen wir nun noch den
Sprungoffset für den BNE-Befehl bei
REDU1 ermitteln. Hierzu wird die Kopie
des 16- Bit-Wertes von XPos zunächst
durch 16 dividiert, das daraus
resultierende 8- Bit Ergebnis muß nun
noch von dem Wert 20( der Anzahl der
NOPs in der HSP-Routine) subtrahiert
werden, bevor es in REDU2+1 abgelegt
werden kann:
co1:lsr <afb ;Wert in AFA/AFB 4 Mal ror <afa ; nach rechts rotieren lsr <afb ; (=Division durch 16) ror <afa lsr <afb ror <afa lsr <afb ror <afa sec ;C-Bit f. Sub. setzen lda #$14 ;Akku=20 NOPS sbc <afa ;Ergebnis subtr. sta redu2+1 ;u. abl.
Folgt nun nur noch die Berechnung des
horizontalen Softscrollwertes. Dieser
entspricht den untersten 3 Bits von
XPos. Da wir auch hier ein Register
($ D016) beschreiben, das auch noch
andere Aufgaben erfüllt, müssen wir in
Ihm ebenfalls noch ein Bit seten, nämlich das dritte, daß die 40 Zeichen/ Zeile-Darstellung einschaltet. In AGSP2 setzen wir hier zusätzlich auch noch das
vierte Bit, daß den Multicolor-Modus
einschaltet:
lda <xpos ;XPos holen and #$07 ;unterste 3Bits ausmask. ora #$08 ;40 Zeichen/Zeile sta horizo+1 ;und ablegen rts ;ENDE
Das ist nun alles wichtige gewesen, was
noch zu AGSP zu sagen war. Wir sind
damit auch wirklich beim allerletzten
Teil unseres Kurses über die Raster-IRQ- Programmierung angelangt. Ich hoffe es
hat Ihnen die letzten 17 Monate viel
Spaß bereitet, in die tiefsten Tiefen
unseres " Brotkasten" vorzudringen," where ( almost) no man has gone before" !
Einen besonderen Dank möchte ich auch
meinem Kollegen Ivo Herzeg ( ih) aussprechen, ohne dessen unermüdlichen Bemühungen um extaktes Rastertiming dieser Kurs
bestimmt nicht zustande gekommen wäre.
(ih/ub