Interrupt-Kurs
"Die Hardware ausgetrickst..."
(Teil 1)
----------------------------------------
Wer kennt sie nicht, die tollen Effekte,
die die großen Alchimisten der Program-
mierkunst aus unserem guten alten "Brot-
kasten" herauszaubern: mehr als 8 Spri-
tes gleichzeitig auf dem Bildschirm,
Side- und Topborderroutinen, die die
Grenzen des Bildschirmrahmens sprengen,
sich wellenförmig bewegende Logos, Gra-
fikseiten, die sich in atemberaubender
Geschwindigkeit zusammenfalten, sowie
schillernd bunte 256-farbige Bilder. Man
könnte diese Liste noch bis ins Unendli-
che weiterführen und sie würde dennoch
die vollständig sein. In der Tat - was
manche Programmierer aus den Silizuimmo-
lekülen der Hardware des C64 herauskit-
zeln hat schon so manch Einen zum Stau-
nen gebracht. Hätte Commodore zur Mark-
teinführung des C64 schon gewusst, was
für eine Wahnsinnsmaschine sie da gebaut
hatten, hätten sich die Entwickler wahr-
scheinlich 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ächtelan-
ger Herumtüftelei die Software dazu ent-
wickelten, 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 wunderba-
ren 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 versu-
chen Ihnen das Grundwissen zu vermit-
teln, neue Routinen selbst entwickeln zu
können.
Im ersten Teil dieses Kurses wollen wir
uns nun zunächst um die Grundlagen küm-
mern, und den Fragen "Was ist ein Inter-
rupt?", "Wo kommt er her?" und "Was pas-
siert 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 64er
seinen Dienst tut) Grundbedingung ist.
Desweiteren sollten Sie einen guten
Speichermonitor, bzw. Disassembler zur
Hand haben, da wir der Einfachheit hal-
ber 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 En-
glischen und bedeutet "Unterbrechung".
Und nichts anderes tut nun ein Inter-
rupt: 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 Interruptanfor-
derung bekommt. Da das Ganze ja auch
einen Nutzen haben soll, kann natürlich
ein ganz bestimmtes Programm von ihm
abgearbeitet werden, das auf das Inter-
ruptereignis 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 Unterbrechungsty-
pen, die von ihm jeweils unterschiedlich
behandelt werden. Die Unterscheidung
wird ihm schon durch seinen hardwaremä-
ßigen Aufbau ermöglicht. Er verfügt näm-
lich über drei Eingangsleitungen, die
die entsprechenden Interrupts bezeichnen
(der vierte Interrupt ist ein besonde-
rer, den wir weiter unten besprechen
werden). Die Chips um den Prozessor he-
rum sind nun mit diesen Interrupt-
Leitungen verbunden, und können ihm so
das Eintreten eines Unterbrechungsereig-
nisses mitteilen. Sie sehen also, daß
Interrupts hardwaremäßig ausgelöst wer-
den. 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 Inter-
rupttypen. Der Erste von ihnen ist wohl
Einer der einfachsten. Es handelt sich
um den "RESET", der von keinem externen
Chip, sondern vielmehr von einem "exter-
nen" Menschen ausgelöst wird. Er dient
dazu, den Computer wieder in einen Nor-
malzustand zu versetzen. Da der Prozes-
sor 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 spe-
zielles 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 verbun-
den.
Der zweite Interrupttyp heißt "NMI" und
kann ausschließlich von CIA-B des C64
ausgelöst werden. Dies ist ein speziel-
ler Chip, der die Kommunikation zwischen
externen Geräten und 6510 ermöglicht.
Desweiteren ist die 'RESTORE'-Taste di-
rekt 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ätz-
lich auch noch die "RUN/STOP"-Taste
gedrückt wurde und verzweigt bei positi-
ver 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 wer-
den.
Der vierte und letzte Interrupt, ist
derjenige unter den vieren, der keine
Leitung zum Prozessor hat. Das liegt
daran, daß er ein softwaremäßiger Inter-
rupt 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 eigent-
lich 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 an-
zeigen 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 Un-
terbrechungsereignisse der Hardware ab-
gefangen werden, die wir ja exzessiv
programmieren werden. Außer, daß sie
beide verschiende Quellen haben, unter-
scheiden Sie sich auch noch in einem
weiteren Punkt: während der NMI IMMER
ausgelöst wird, wenn ein Unterbrechung-
sereignis eintritt, kann der IRQ "mas-
kiert", 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 die-
ser Unterbrechung. Dies tut er solange,
bis er durch den Asseblerbefehl "CLI"
die Instruktion erhält, das Interrupt-
Flag wieder zu löschen. Erst dann rea-
giert er wieder auf eintretende Unter-
brechungen. Dies ist ein wichtiger Um-
stand, 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 Interruptanforder-
ungen erhält? Er soll also seine mo-
mentane Arbeit unterbrechen, um in die
Interrupt-Routine zu springen. Damit er
Nach Beendingung des Interrupts wieder
normal fortfahren kann muß er jetzt ei-
nige 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 High- und Lowbyte
(in dieser Reihenfolge) des Pro-
grammzählers, der die Speicheradresse
des nächsten, abzuarbeitenden Befehls
beinhaltet, auf den Stack geschoben.
Dadurch kann der 6510 beim Zurückkeh-
ren aus dem Interrupt wieder die
ursprüngliche Programmadresse ermit-
teln.
3) Nun wird das Statusregister auf den
Stack geschoben, damit es bei Been-
dingung des Interrupts wiederherge-
stellt werden kann, so daß es densel-
ben Inhalt hat, als bei Auftreten der
Unterbrechung.
4) Zuletzt holt sich der Prozessor aus
den letzten sechs Bytes seines Adres-
sierungsbereiches (Adressen $FFFA-
$FFFF) einen von drei Sprungvektoren.
Welchen dieser drei Vektoren er aus-
wählt hängt von der Art des Inter-
rupts 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 vorbe-
legt, 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 den-
selben 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-$FF4C).
Dies ist notwendig, da in der Interrup-
troutine die Register je ebenfalls be-
nutzt werden sollen, und so die ursprün-
glichen Inhalte beim Verlassen des In-
terrupts wiederhergestellt werden können
(durch umgekehrtes zurücklesen). Ab
Adresse $FF4D wird nun eine Unterschei-
dung getroffen, ob ein BRK- oder 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 he-
raus. 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 Be-
fehle ü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 Inter-
rupts "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, X- und 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, gesprun-
gen 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 ein-
binden, so müssen wir lediglich die
Zieladresse der Vektoren auf den Anfang
unserer Routine verbiegen.
4) PROGRAMMBEISPIELE
Um das Prinzip des Einbindens eines ei-
genen 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 Programmli-
sting, 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 ein-
mal 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-$100A setzen wir
zunächst einmal Low- und Highbyte des
BRK-Vektors auf Adresse $101C, 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 Prozessorregi-
ster über den von uns geänderten BRK-
Vektor auf die Routine ab $101C springt.
Selbige tut nun nichts anderes, als die
Rahmenfarbe des Bildschirms um den Wert
1 hochzuzählen, und anschließend zu ver-
gleichen, 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 fol-
genden Befehle zum Zuge. Hier holen wir
die Prozessorregister, die von der Be-
triebssystem-Routine in der Reihenfolge
Akku, X, Y auf dem Stack abgelegt wur-
den, wieder umgekehrt zurück. Das ab-
schließende "RTI" beendet den Interrupt.
Diese Answeisung veranlasst den Prozes-
sor dazu, den alten Programmzähler, so-
wie 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, je-
doch nimmt BRK eine Sonderstellung dies-
bezü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 Pro-
grammzähler hinzuaddiert, weshalb nach
Beendigung des Interrupts ein Byte hin-
ter den BRK-Befehl verzweigt wird. Dem-
nach 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 $100C 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, automa-
tisch 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 $100A war).
LADEN SIE NUN DEN 2. TEIL DES KURSES !
Um bei JEDEM BRK wieder in einen defi-
nierten 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 ledig-
lich den "Müll" den Prozessor und Be-
triebssystem-BRK-Routine auf dem Stapel
abgelegt haben, mit Hilfe von sechs auf-
einanderfolgenden "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äm-
lich auch nicht mehr benötigte Daten,
die evtl. vor Auslösen des Interrupts
auf dem Stack abgelegt wurden, entfernt.
Demnach kann jedes Programm mit folgen-
dem 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 hexadezi-
maler 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 ange-
sprungen, 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 die-
sem Beispiel bei $100B liegt. In $100A
wird ein BRK ausgelöst, der die Inter-
ruptroutine sofort anspringt. Diese gibt
nun, mit Hilfe der Betriebssystemroutine
"STROUT" bei $AB1E, einen kleinen Info-
text auf dem Bildschirm aus. Hiernach
holen wir uns Y- und 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 $103E auf dem Bildschirm ausge-
geben. Diese Routine wandelt den Akkuin-
halt in eine hexadezimale Zahl um, die
auf dem Bildschirm angezeigt wird. Hin-
ter dieser Zahl gibt sie zusätzlich ein
Leezeichen aus, das als optische Tren-
nung zwischen diesem, und dem nächsten
Wert dienen soll. Die genaue Beschrei-
bung 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 Pro-
grammzählers vom Stapel geholt und in
der Speicherzelle $02 zwischengespei-
chert. Hieraufhin wird das High-Byte
geladen und auf dem Bildschirm ausgege-
ben. Abschließend wird das Low-Byte wie-
der aus $02 herausgeholt und ebenfalls
ausgegeben. Zwischen den beiden Zahlen
schicken wir noch ein 'CRSR links'-
Zeichen auf den Bildschirm, da das Lee-
zeichen, das durch unsere Ausgaberoutine
zwischen High- und 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 aus-
gegeben. Da wir zu diesem Zeitpunkt ja
schon alle Daten, die durch den Inter-
rupt auf den Stapel gelegt wurden, wie-
der 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 Ver-
wendung 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 Adres-
se,0:SYS Adresse", oder in eigenen Pro-
grammen auszulösen. Sie werden hierbei
immer wieder zu unserer kleinen Regi-
steranzeige gelangen, die das System
automatisch wieder ins BASIC zurück-
fü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 verbie-
gen.
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 NMI-
und 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
IRQ- und 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 sim-
ple Methode aufzeigen, mit der Sie einen
Timer-IRQ programmiereren können. Hier-
bei machen wir uns zunutze, daß das Be-
triebssystem selbst schon standardmäßig
einen solchen Interrupt über den Timer
des CIA-A direkt nach dem Einschalten
des Rechners installiert hat. Die Routi-
ne die diesen Interrupt bedient, steht
bei Adresse $EA31 und ist vorrangig für
das Cursorblinken und die Tastaturabfra-
ge verantwortlich. Wichtig ist, daß der
Timer der CIA diesen IRQ auslöst. Hier-
bei 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 Inter-
ruptprogramm ausgeführt werden. Die
Funktionsweise eines Timers wollen wir
etwas später besprechen. Vorläufig
genügt es zu wissen, daß der Betriebs-
system-IRQ von einem solchen Timer im
sechzigstel-Sekunden-Takt ausgelöst
wird. Das heißt, daß 60 Mal pro Sekunde
das Betriebssystem-IRQ-Programm abgear-
beitet wird. Hierbei haben wir nun die
Möglichkeit, den Prozessor über den
IRQ-Vektor bei $0314/$0315 auf eine ei-
gene 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
102B: 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. Ge-
startet wird es mit SYS4096 (=$1000).
Was Sie daraufhin sehen, ist ein "X",
das in der rechten, oberen Bildschirmek-
ke im Sekundentakt vor sich hin blinkt.
Wollen wir nun klären wie wir das zu-
stande gebracht haben:
Bei Adresse $1000-$1011 wird die Inter-
ruptroutine vorbereitet und der Inter-
ruptvektor auf selbige verbogen. Dies
geschieht durch Schreiben des Low- und
Highbytes der Startadresse unserer eige-
nen IRQ-Routine bei $101E in den IRQ-
Vektor bei $0314/$0315. Beachten Sie
bitte, daß ich vor dieser Initialisie-
rung zunächst einmal alle IRQs mit Hilfe
des SEI-Befehls gesperrt habe. Dies muß
getan werden, um zu verhindern, daß wäh-
rend 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 irgend-
eine Adresse im Speicher sein kann, wür-
de der Prozessor sich höchstwahrschein-
lich 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 $100B-$100F 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 $101E nun 60 Mal pro
Sekunde aufgerufen. Die Anzahl dieser
Aufrufe sollen nun zunächst mitgezählt
werden. Dies geschieht bei Adresse
$101E, 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 wie-
der 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
$102E weiterverzweigt, wo wir prüfen, ob
der Wert 60 im Zähler enthalten ist. Ist
dies der Fall, so wird in die obig ge-
nannte Bildschirmspeicheradresse der
Bildschirmcode für das "X" (=24) ge-
schrieben. Gleichzeitig wird der Zähler
in Speicherstelle 2 wieder auf 0 zurück-
gesetzt, damit der Blinkvorgang wieder
von Neuem abgezählt werden kann. Auch
hier wird am Ende auf die Betriebs-
system-IRQ-Routine weiterverzweigt.
Ebenso, wenn keiner der beiden Werteim
Zähler stand. Dieser nachträgliche Auf-
ruf 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 lau-
fen haben, der Cursor und die Tastatu-
rabfrage weiterhin aktiv sind. Zum Ande-
ren 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üm-
mern, da das alles ebenfalls von der
System-Routine abgehandelt wird.
Bleiben nun nur noch die Zeilen von
$1011 bis $101E 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 wer-
den 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 auskom-
men. Um zum Beispiel einen NMI zu pro-
grammieren, kommen Sie um die Initiali-
sierung 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 Funktionswei-
se 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 Tastatur-
abfrage 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 Peripherie-
gerä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 ange-
merkt, daß wir das natürlich nur dann
tun müssen, wenn wir einen timerge-
steuerten Interrupt programmieren möch-
ten. Innerhalb der CIAs gibt es zwar
noch eine ganze Reihe weiterer Möglich-
keiten einen Interrupt zu erzeugen, je-
doch wollen wir diese hier nicht anspre-
chen. 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 aus-
schließlich auf die timergesteuerten
CIA-Interrupts konzentrieren.
Beide CIAs haben nun jeweils 16 Regi-
ster, die aufgrund der Gleichheit, bei
beiden Bausteinen dieselbe Funktion ha-
ben. 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, Re-
gisteroffset hinzuaddieren, je nach dem
welche CIA Sie ansprechen möchten. Von
den 16 Registern einer CIA sind insge-
samt 7 für die Timerprogrammierung
zuständig. Die anderen werden zur Daten-
ein- und -ausgabe, sowie eine Echtzeit-
uhr 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 ge-
trennt voneinander laufen, und getrennte
Interrupts erzeugen, oder aber zu einem
einzigen 32-Bit-Timer kombiniert werden.
Was tut nun so ein Timer? Nun, prinzi-
piell kann man mit ihm bestimmte Ereig-
nisse zählen, und ab einer bestimmten
Anzahl dieser Ereignisse von der dazu-
gehörigen CIA einen Interrupt auslösen
lassen. Hierzu hat jeder der beiden Ti-
mer 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 Schreibzu-
griff auf das Register erhalten. Auf
diese Weise kann der Timer nach einmali-
gem Herunterzählen, den Zähler wieder
mit dem Anfangswert initialisieren.
Liest man ein solches Register aus, so
erhält man immer den aktuellen Zähler-
stand. Ist der Timer dabei nicht ge-
stoppt, so bekommt man jedesmal ver-
schiedene Werte.
Zusätzlich gibt es zu jedem Timer auch
noch ein Kontrollregister, in dem fest-
gelegt wird, welche Ereignisse gezählt
werden sollen. Weiterhin sind hier Kon-
trollfunktionen 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 Kontrollregi-
ster 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 anhal-
ten soll (=1), oder aber nach jedem Un-
terlauf wieder mit dem Zählen vom An-
fangswert 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 Hardwareerweite-
rung 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 Arbeits-
grundlage, wie Sie weiter unten sehen
werden.
Kommen wir nun zur Beschreibung von CRB
(Reg. 15). Dieses Register ist weitge-
hend identisch mit CRA, jedoch unter-
scheiden 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 Kombina-
tionen sind jedoch nur zwei für uns in-
teressant. 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 Lau-
fen bringt. Es fehlt nun nur noch ein
Register, um die volle Kontrolle über
die CIA-Timer zu haben. Es heißt "Inter-
rupt-Control-Register" ("ICR") und ist
in Register 13 einer CIA untergebracht.
Mit ihm wird angegeben, welche CIA-
Ereignisse einen Interrupt erzeugen sol-
len. Auch hier sind eigentlich nur drei
Bits für uns von Bedeutung. Die Restli-
chen 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, eben-
falls 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 Bei-
spiel 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 erlau-
ben. 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 Funk-
tion. 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 aus-
gelesen 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öch-
ten, 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 Interrupt-
ereignisse ignoriert. Erst wenn das Re-
gister ausgelesen wird, wird der CIA
signalisiert, daß der Interrupt verar-
beitet wurde und neue Unterbrechungen
erlaubt sind. Auf diese Weise kann ver-
hindert werden, daß während der Abarbei-
tung eines Interrupts noch ein zweiter
ausgelöst wird, was womöglich das gesam-
te 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 ein-
mal 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 Joy-
stick in Port 2 über den Bildschirm be-
wegen. Die Abfrage desselben soll im NMI
geschehen, wobei wir CIA-B 30 Mal einen
Timerinterrupt pro Sekunde auslösen las-
sen. Timer A soll für diese Aufgabe her-
halten. 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 Joy-
stick in Port 2 über den Bildschirm be-
wegt werden kann. Gleichzeitig ist je-
doch weiterhin die normale Tastaturab-
frage des C64 aktiv. Die beiden Prozesse
laufen scheinbar gleichzeitig ab, weil
wir einen zweiten Interrupt generiert
haben, der den Joystick abfragt und ent-
sprechend das Sprite bewegt. Kommen wir
nun zur Programmbeschreibung:
In den ersten Zeilen, von $1000 bis
$101E wird zunächst einmal das Sprite
initialisiert und die Bildschirmfarben
auf die gängige Kombination hellblau/
dunkelblau gesetzt. Hiernach folgt die
NMI- und Timerinitialisierung. Da wir
auch hier verhindern müssen, daß ein NMI
auftritt, während wir den NMI-Vektor
verändern, wird der Wert $7F 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 Un-
terlauf des Timers stattfindet, so würde
er mit sich mit einem falschen Anfangs-
wert 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ähl-
wert 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 64ers zäh-
len 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 ermit-
teln, 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 divi-
dieren. 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 Joy-
stickabfrage auch beschleunigen oder
verlangsamen, denn das Sprite wird bei
betätigtem Joystick immer um genau so-
viele 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 Start-
wert 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). Hier-
nach wird das ICR von CIA-B ausgelesen.
Dadurch ermöglichen wir es diesem Chip
beim nächsten Timerunterlauf erneut ei-
nen Interrupt auslösen zu können.
Gleichzeitig können wir dadurch abfra-
gen, 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 han-
delt. Ist letzterer der Auslöser, so
wird einfach die Rahmenfarbe um eins
erhöht, und zum Ende des NMIs weiterge-
prungen. 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 Daten-
bits der Tastatur erscheinen, kann es
passieren, daß Sie bei bestimmten Ta-
stendrü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 wie-
der. Selbige wurden nämlich in der Be-
triebsystemsvorbeitung zum NMI abge-
schaltet (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 Tastenkombi-
nation 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. An-
schließ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. Probie-
ren Sie ein wenig mit den Timerinter-
rupts herum, und versuchen Sie z.B. ei-
nen 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
$4CC7 Taktzyklen ausgelöst werden soll
und dann für eine kurze Zeit die Rahmen-
farbe 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" ge-
hen und in den interessantesten Teilbe-
reich der Interruptprogrammierung ein-
steigen: 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 Inter-
rupptquelle 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 Compu-
termonitors 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 Elektro-
nenquelle, 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 Bildschirm-
rand 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 Gesamt-
bild 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 le-
sen, benötigt man eine Schaltlogik, die
die Text- oder Grafikzeichen aus dem
Speicher des Computers in sichtbare
Bildpunkte umwandelt. Diese Umwandlung
geschieht durch den Video-Chip des C64,
dem sogenannten VIC. Er erzeugt Steuer-
signale für den Rasterstrahl, die ange-
ben in welcher Rasterzeile er gerade
arbeiten soll, und mit welcher Inten-
sität die einzelnen Bildpunkte auf dem
Bildschirm leuchten sollen. Helle Bild-
punkte 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 einset-
zen 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üs-
sen, 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 ge-
schieht ü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ög-
lichkeit, 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 ins-
gesamt 313 Rasterzeilen gibt reicht näm-
lich 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 ande-
ren Bits dieses Registers ebenfalls ei-
nige 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 Interrupt-
auslö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 fol-
genden drei Befehlen das 7. Bit in Regi-
ster $D011 setzen, und die Nummer der
Rasterzeile minus dem Wert 256 in Regi-
ster $D012 schreiben:
LDA $D011 ;Reg.17 lesen
ORA $80 ;7. Bit setzen
STA $D011 ;Reg.17 zurückschreiben.
Um z.B. Rasterzeile 300 als Interrupt-
auslö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 Rasterzei-
le 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 $D01A) 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 definie-
ren. 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 ei-
nen Interrupt auslösen können. Sie kön-
nen auf diese Weise also auch sehr kom-
fortabel eine Kollision über den Inter-
rupt abfragen. Bit 3 wird nur sehr sel-
ten benutzt und bringt eigentlich nur
etwas in Zusammenhang mit einem Light-
pen. 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 ent-
sprechende Lightpen-Programm nur noch
auswerten, an welcher Rasterposition der
Strahl sich gerade befindet, um heraus-
zufinden, an welcher Stelle des Bild-
schirms sich der Lightpen befindet. Dies
soll uns hier jedoch nicht interessie-
ren.
Wir müssen also lediglich Bit 0 von $D0-
1A 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 fest-
gelegt 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 Prin-
zip ein alter Hut, denn der VIC ist mit
der IRQ-Leitung des Prozessors verbun-
den, 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üs-
sen wir jedoch beachten, daß der Be-
triebssystem-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 Un-
terschied machen, so würde uns die CIA
ungehemmt 'dazwischenfunken' und der
Rasterinterrupt würde außer Kontrolle
geraten. Bevor ich mich nun aber in the-
roretischen 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 Aus-
sta $d012 ; löser festlegen
lda #$07 ;Rahmen- und Hintergrund-
sta $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 aus-
fü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 Raster-
zeilen $00, $70 und $C0 einen Interrupt
auslösen, woraufhin wir die Rahmen- und
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 be-
schriebene Art und Weise die Raster-
zeile 0 als Inter- ruptauslösende Zeile
fest. Hieraufhin wird der Wert $01 in
Register $D01A 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ück-
schreiben 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 Interruptanfor-
derung 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 ent-
spricht 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 Lese-
vorgang wurden die dem Wert entsprechen-
den 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 Unter-
routine "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 Raster-
zeile 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 Initialisierungs-
routine her ja noch auf 0 ist. Danach
werden Rahmen- und Hinter- grundfarbe
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ückge-
holt 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 Bild-
schirmfarben 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 Bei-
spielprogramm einmal gestartet und ange-
schaut 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 Bildschirmhinter-
grund 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 hin- und her-
springen. Zusätzlich kann es passieren,
daß kurzfristig einer der drei Farb-
bereiche nicht richtig eingeschaltet
wird, und dann der ganze Balken
flackert. Dieser Fall tritt dann ein,
wenn Raster- und 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 VIC- und 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 Bei-
spiels 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 $7F 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 Rasterin-
terrupts 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 320x200 Pixeln, oder auch
40x25 Textzeichen beschränkt. Der Rest
des Bildschirms ist von dem meist hell-
blauen 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 Bild-
schirmrand 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 an-
deres als ein Rastertrick. Noch dazu
einer der simpelsten die es gibt. Wie
einfach er zu programmieren ist soll
folgendes kleines Rasterprogramm ver-
deutlichen. Sie finden es auf dieser MD
unter dem Namen "BORDERDEMO" und müssen
es wie immer mit ",8,1" laden und mit-
tels "SYS4096" starten:
INIT: SEI ;IRQ sperren
LDA #$7F ;Timer-IRQ
STA $DC0D ; abschalten
LDA $DC0D ;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 Rasterinter-
rupt nicht gestört wird. Als nächstes
wird Rasterzeile $F8 als Interruptaus-
löser festgelegt, was auch einen ganz
bestimmten Grund hat, wie wir weiter
unten sehen werden. Nun sagen wir noch
dem VIC, daß er Rasterinterrupts aus-
lösen soll, und verbiegen den IRQ-Vektor
bei $0314/$0315 auf unsere eigene Rou-
tine namens "IRQ". Die nun folgenden
Zeilen dienen lediglich "kosmetischen"
Zwecken. Wir setzen hier Rahmen- und
Hintergrundfarben auf die Standardwerte
und schalten Sprite 0 ein, das von
unserer Interruptroutine in der Verti-
kalen pro IRQ um einen Pixel weiterbe-
wegt werden soll. Zudem wird der Sprite-
block, der dieses Sprite darstellt, mit
$FF gefüllt, damit wir ein schönes Qua-
drat 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 darge-
stellt, setzen wir es auf "0", so sehen
wir lediglich 24 Textzeilen. Im letzte-
ren 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 Rasterzei-
len 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 Ras-
terzeile $F8 angesprungen, also genau
dann, wenn der VIC die 24-Zeilen-Prüfung
schon vorgenommen hat. Da die Darstel-
lung auf 25 Zeilen war, hat er noch
keinen Rand gezeichnet. Unsere Inter-
ruptroutine 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 Bild-
schimrand 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 Interrupt-
routine beweist!
Wichtig an unserer Routine ist nun noch,
daß wir vor Erreichen des oberen Bild-
randes die Darstellung nocheinmal auf 25
Zeilen zurückschalten, damit der Trick
beim nächsten Rasterdurchlauf nocheinmal
klappt. Hierbei darf natürlich frühes-
tens dann umgeschaltet werden, wenn der
Rasterstrahl an der zweiten Prüf-Posi-
tion, Rasterzeile $FB, schon vorbei ist.
Dies wird durch die kleine Verzögerungs-
schleife bewirkt, die genau 4 Rasterzei-
len 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 inter-
ruptauslö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 $3FFF geschrieben haben. Wie Sie
vielleicht wissen, kann der VIC Spei-
cherbereiche von lediglich 16KB adres-
sieren, aus denen er sich seine Daten
holt. Im Normalfall ist das der Bereich
von $0000-$3FFF. Die letzte Speicher-
zelle 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 überdek-
kten 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 Bildschirm-
rand simulieren!
Vielleicht fällt Ihnen nun auch noch ein
interessanter Nebeneffekt auf: nachdem
wir die oberen und unteren Bildschirm-
grenzen 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 Sprite-
positionen normalerweise im unsichtbaren
Bereich des Bildschirms liegen, wo sie
vom Bildschirmrahmen überdeckt werden.
Bleibt noch zu erwähnen, daß wir mit ei-
nem ähnlichen Trick auch die seitlichen
Ränder des Bildschirms verschwinden las-
sen können, nur ist das hier viel
schwie- riger, 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ähn-
ten 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 Gesschwin-
digkeit 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ältnis-
mäßig viel Rechenzeit benötigt, bis er
abgearbeitet ist, tritt ein Raster-IRQ
in der nächsten Zei- le 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, hardware-
mäßiger Umstand beachtet werden: hat man
normale Textdarstellung auf dem Bild-
schirm 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 um-
wandeln. 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 $DC0D
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 Rahmen- und Hintergrund-
farbe ändern. Ich habe hierbei Farbab-
stufungen benutzt, die schöne Balken-
effekte 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 Initiali-
sierungsroutine sollte Ihnen keine Pro-
bleme 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 eigent-
liche 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 ein-
fachsten 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 LDX- und 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ögerun-
gen 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öge-
ung 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 Instruk-
tionen. 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 no-
cheinmal 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 ge-
steuert, 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") abge-
arbeitet. Wenn Sie jetzt die Taktzyklen
am Rand innerhalb dieses Bereichs auf-
addieren, 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ß ver-
zweigt werden, was einen weiteren,
dritten Taktzyklus in Anspruch nimmt.
Dadurch sind also genau 60+3=63 Takt-
zyklen 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 verstri-
chen, 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
hin- und 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 FLD- und
Sideborder-Routinen beschäftigen.
(ub)
Interrupt-Kurs
"Die Hardware ausgetrickst..."
(Teil 5)
----------------------------------------
Nachdem wir im letzten Monat ja schon
kräftig mit schillernden Farb- und Si-
nuswellenrasterroutinen 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, Bezeich-
nung steht für einen Rastereffekt, der
vom Prinzip und der Programmierung her
extrem simpel ist, jedoch ungeahnte
Mög-lichkeiten in sich birgt. Um zu wis-
sen, welcher Effekt damit gemeint ist,
brauchen Sie sich lediglich einmal anzu-
schauen, was passiert, wenn Sie im MD-
Hauptmenu einen neuen Text laden, oder
einen gelesenen Text wieder verlassen:
der Textbildschirm scheint hier von un-
ten 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 hin- und herkopiert
und rumgescrollt werden muß, sondern um
eine einfache, ca. 150 Byte große, Ra-
sterroutine! Der Trick des Ganzen liegt
wie so oft bei der Hardware des 64ers,
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 unte-
ren drei Bits von Register 17 des VICs
($D011), ein vertikaler Verschiebeoffset
für die Bildschirmdarstellung eingetra-
gen werden. In der Regel benutzt man
diese Bits um ein vertikales Softscrol-
ling 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. Text-
zeile 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 Kur-
steil noch wissen, geschieht dies ab
Rasterzeile 41 und jeweils in jeder ach-
ten, folgenden Zeile. Wird nun ein vert-
kaler Verschiebeoffset angegeben, so
verzögert der VIC diesen Zeitpunkt um
die angegebene Anzahl Rasterzeilen (ma-
ximal 7). Steht in der Vertikalverschie-
bung 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 da-
rin, daß Sie in jeder Rasterzeile, die-
sen Charakterzeilenanfang vor dem Ra-
sterstrahl "herschiebt", so daß dieser
eigentlich nie die gesuchte Anfangszeile
erreichen kann - zumindest nicht solan-
ge, wie unsere FLD-Routine ihm vortäu-
scht, noch nicht den Anfang dieser Zeile
erreicht zu haben! Wie einfach das alles
geht, soll Ihnen folgendes Programmbei-
spiel 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 Initialisierungs-
routine können wir uns sparen, da wir
ihren Aufbau ja schon von anderen Pro-
grammbeispielen 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 ge-
schrieben. Die Bedeutung dieser Adresse
kennen wir noch von unserer Borderrouti-
ne 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 aus-
gelöst werden soll. Gleichzeitig bein-
haltet diese Routine eine Joystickabfra-
ge, 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 wer-
den. Abschließend biegt diese IRQ-
Routine den IRQ-Vektor auf die eigentli-
che 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 Befeh-
le 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 hin-
zuaddiert. 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 ausmaskie-
ren, 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ß. An-
schließend wird der neue Wert für $D011
wieder zurückgeschrieben. Da diese Ver-
schiebungsä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 nor-
male 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 wer-
den, da wir durch das "vor-uns-Her-
schieben" 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 Interrup-
tauslöser festgelegt. Zusätzlich verbie-
gen wir den IRQ-Vektor auf diese Routine
zurück. Dabei wird in unserem Beispiel
lediglich das Low-Byte geändert, da bei-
de Routinen ja an einer Adresse mit
$10xx 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 Prozessorre-
gister vom Stack zurückholt und den In-
terrupt beendet.
Die Art und Weise, wie wir hier die Ver-
tikalverschiebung vor dem Rasterstrahl
herschieben mag etwas umständlich anmu-
ten. 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 Rasterpo-
sition 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 vero-
dert 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 inoffi-
zieller, Assemblerbefehl ist, und des-
halb von den meisten Assemblern und Dis-
assemblern nicht erkannt wird. Zudem
können Laufzeitunterschiede oder gar
Fehlfunktionen bei verschiedenen Produk-
tionsversionen des 6510-Prozessors vor-
kommen, 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 entspre-
chenden Wert für jede einzelne Raster-
zeile 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 Tabel-
le beinhaltet gleichzeitig auch noch den
Vorteil, daß wir viel flexibler die FLD-
Effekte einsetzen können. So ist es da-
mit sehr einfach möglich, mehrere Cha-
rakterzeilen zu verschieben, wie das im
"FLD-DEMO3" der Fall ist. Dieses Bei-
spielprogramm beginnt übrigens ausnahms-
weise 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 Disas-
sembler oder Speichermonitor anschauen
um ihre Funktionsweise zu verstehen. Mit
FLD erzielbare Effekte sind sehr viel-
seitig und sie sollten schon ein wenig
damit herumexperimentieren. Weiterhin
gibt es einige Rastereffekte die durch
eine FLD-Routine stark vereinfacht pro-
grammiert 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 Rasteref-
fekten 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äch-
sten Kursteil ersichtlich, wo wir Ihnen
eine Sideborder-Routine vorstellen wer-
den. Wird diese Routine auch nur einen
Taktzyklus zu früh oder zu spät aus-
gefü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 mit-
bekommen 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 Text-
modus und Rastereffekten im sichtbaren
Bildschirmbereich beachten müssen, daß
jede achte Rasterzeile, jeweils am Be-
ginn einer Charakterzeile, der VIC den
Prozessor für 42 Taktzyklen anhält, da-
mit er die, in den folgenden acht Ra-
sterzeilen darzustellenden, Zeichen ge-
nerieren 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 herauszufin-
den, ob eine Routine schnell, bzw. lang-
sam genug, abgearbeitet wird. Das kann
unter Umständen eine sehr aufwendige
Sache werden, da hierbei ewig lang Be-
fehlstabellen 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 die-
ses Kurses in einer Tabelle alle Prozes-
sor-Befehle in allen möglichen Adressie-
rungsarten aufgelistet. Um also von Hand
die Laufzeit einer Routine zu berechnen
können Sie dort nachschlagen.
Noch einfach geht das Abwägen der Lauf-
zeit jedoch mit einem Programm. Wir kön-
nen uns hier die Möglichkeit zunutze
machen, daß mit den Timern der CIAs ein-
zelne 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 "Cycle-
count" 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 Low- und
High-Byte der zu testenden Routine in
die Adressen 828/829 ($033c/$033d). 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 Bild-
schirm 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 Cycle-
count 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 ver-
braucht 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 auf-
gefü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 kom-
men hier nochmals vor, da sie auch mit
Adressierung verwendbar sind. Die Spal-
ten der Tabelle stehen (von links nach
rechts) für: "IMMediate", wenn der Ope-
rand ein konstanter Wert ist ("LDA
#00"), "ABSolut", für dirkete Speicher-
adressierung ("LDA $1000"), "ABSolut,X"
("LDA $1000,X"), "ABSolut,Y", "ZeroPage"
("LDA $02"), "ZeroPage,X", "ZeroPage,Y",
Zeropage indirekt-implizit "(zp,X)" und
Zeropage implizit-idirekt "(zp),Y". Alle
Zyklenangaben, die mit einem "*" mar-
kiert 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 $10FF,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 eige-
nen Tabelle erscheinen müssen, da sie
immer 2 Taktzyklen verbrauchen. Es sei
denn, die vom Befehl abgefragte Beding-
ung trifft zu. In diesem Fall wird ein
weiterer, dritter Takt in Anspruch ge-
nommen.
Das war es dann wieder für diesen Monat.
Im nächsten Kursteil werden wir uns wei-
terhin 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 Ra-
ster-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 Kurs-
teils hatten wir gesehen, wie einfach
man die Hardware unseres kleinen Brot-
kastens austricksen kann, und sie dazu
bewegt Dinge zu tun, zu denen sie ei-
gentlich 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 Rasteref-
fekte programmieren und den Copper- und
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 tol-
len Rasterinterrupts ein besonders exak-
tes Timing. Wie schon am Beispiel der
FLD-Routine unschwer erkennbar war, be-
steht das eigentliche "Austricksen" des
Video-Chips meist aus gerade einer hand-
voll Befehlen. Wichtig ist nur, daß die-
se Befehle zum richtigen Zeitpunkt aus-
geführt werden. Wie wichtig das ist,
werden wir später am Beispiel einer Ra-
sterroutine sehen, die in der Lage ist,
den linken und rechten Rand des Bild-
schirms abzuschalten. Wird sie auch nur
einen Taktzyklus zu früh oder zu spät
ausgeführt, so bewirkt sie absoult gar-
nichts. Nur wenn zu einem ganz bestimm-
ten 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 wer-
den 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 Interrupt-
anfrage 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 Betriebssystems-
routinen, die die entsprechende Art von
Interrupt (IRQ, NMI oder Reset) bedie-
nen. 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 Prozessor-
zeit, wenn wir davon ausgehen, daß der
BRK-Interrupt in der Regel nicht verwen-
det 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 Interrupt-
system ein. Um nun die Verzögerung durch
das Betriebssystem zu eliminieren, müs-
sen 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 je-
doch ein weiteres Problem in den Weg:
diese Adressen gehören ja zum Be-
triebssystem-ROM und können nicht verän-
dert 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 Pro-
zessor sich dann den IRQ-Vektor auch von
dort holt, müssen wir das darüberliegen-
de 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 Basic- und 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 darunterle-
genden 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 geschrie-
ben werden. Dadurch werden nämlich die
beiden ROM-Bausteine deaktiviert und das
darunterliegende RAM kommt zum Vor-
schein, 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 Ab-
schalten des ROMs weder Basic- noch Be-
triebssystemsroutinen verfügbar sind, da
wir sie ja weggeschaltet haben. Benutzt
Ihr eigenes Programm solche Routinen, so
müssen Sie das ROM zuvor ins RAM kopie-
ren. Dies tun Sie, indem Sie einfach bei
eingeschaltetem ROM eine Adresse ausle-
sen und gleich wieder in sie zurück-
schreiben. Beim Lesen erhalten Sie dann
den Wert des ROMs, beim Schreiben schik-
ken Sie ihn ins RAM darunter. Jetzt kön-
nen Sie getrost das ROM abschalten, ohne
daß Ihr Rechner abstürzt. Als Beispiel
zum Kopieren der beiden ROMs ins darun-
terliegende RAM können Sie sich das Pro-
gramm "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 aller-
dings 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 Pro-
blem aufgefallen sein: Möchten Sie einen
Interrupt programmieren, der z.B. ein-
fach 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 kon-
stanten 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 Inter-
ruptanforderung am Prozessor auf, so muß
dieser zunächst einmal den aktuell bear-
beiteten 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 Operan-
den lesen, um anschließend in die IRQ-
Routine verzweigen zu können. Selbige
wird dadurch aber erst einen Taktzyklus
später, als eigentlich erforderlich ge-
wesen 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 Rou-
tine ist eine Kombination aus FLD- und
Borderroutine. Mit dem FLD-Teil drücken
wir einen Teil des Bildschirms nach un-
ten und stellen im Zwischenraum einen
Rasterbalken dar. Der Border-Teil schal-
tet einfach den oberen und unteren Bild-
schirmrand weg und dient mehr als Bei-
spiel zur Kombination der beiden Raster-
effekte. 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 "Raster-
split" 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 Cha-
rakter- und normalen Rasterzeilen machen
müssen, weshalb wir uns die verschach-
telten 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 Beispielpro-
gramm einmal an, so wird man ein erhe-
bliches Flackern bemerken. Das liegt
daran, daß das Programm zur Demonstra-
tion gerade in den zeitkritischen Momen-
ten, 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 er-
forderlich ist, müssen wir uns eine Me-
thode angewöhnen, mit der wir einen In-
terrupt "glätten" können. Selbiges tut
nämlich das dritte Beispiel dieses Kur-
steils, 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 $3fff
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 Interruptquel-
len ab, und löschen mit Hilfe des BIT-
Befehsl eine evtl. noch gemeldete Inter-
ruptanfrage. Anschließend wird Raster-
zeie $F8 als Interruptauslöser festge-
legt (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üt-
tert. 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 ans-
pringt, 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 vor-
handen 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 zei-
tintenive Befehle, die wir zur Öberprü-
fung, ob unser Glätten auch funktio-
niert,im Programm haben. Sie sind ge-
folgt 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 wei-
ter, als die 24-Zeilen-Darstellung zu
aktivieren, die in Rasterzeile $F8 ja
das Abschalten des Borders bewirkt, die
Rasterzeile des nächsten IRQs festzule-
gen ($3D=Startposition der 2. Charakter-
zeile-2), den Interrupt-Vektor auf die
Routine (FLD) für diese Zeile zu verbie-
gen, und den Joystick abzufragen (Unter-
routine, die hier nicht aufgeführt ist).
Beachten Sie, daß wir hier die Prozes-
sorregister mit Hilfe der Transfer- und
Stapelbefehle von Hand retten und wie-
derherstellen müssen. Gerade das Retten
war nämlich eine Aufgabe, die uns das
Betriebssystem freundlicherweise schon
abgenommen hatte. Da es jetzt ja abge-
schaltet 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 vie-
len NOPs). Beginnen wir von Anfang an:
In der Borderroutine hatten wir die Ra-
sterzeile festgelegt, in der die FLD1-
Routine angesprungen werden soll. Dies
war Zeile 61 ($3D), die genau zwei Ra-
sterzeilen vor der eigentlichen IRQ-
Rasterzeile liegt. In diesen zwei Zei-
len, 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 In-
terruptauslö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 da-
durch 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 In-
terrupt-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-
tungs-teil". Hierzu lassen wir den Pro-
zessor ständig durch eine Endlos-
Schleife mit NOP-Befehlen laufen. Da-
durch wird sichergestellt, daß der Ra-
ster-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 Zy-
klus zu korrigieren ist nun die Aufgabe
des zweiten FLD-IRQs. Nachdem er ange-
sprungen wurde holen wir gleich wieder
die, vom Prozessor automatisch gerette-
te, Programmzähleradresse und das Sta-
tusregister 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 aufge-
listet:
lda $d012 ;Den letzten Zyklus
cmp $d012 ; korrigieren
bne cycle
cycle ... ;eigentlicher IRQ
Obwohl diese Folge recht unsinnig er-
scheint, hat sie es ganz schön in sich:
Wir laden hier zunächst den Akku mit dem
Inhalt von Register $D012, das die Num-
mer der aktuell bearbeiteten Rasterzeile
beinhaltet, und vergleichen ihn sofort
wieder mit diesem Register. Danach wird
mit Hilfe des BNE-Befehls auf die Fol-
geadresse 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. Da-
durch 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 ver-
braucht, als bei nicht zutreffender Be-
dingung (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 im-
mer 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ät-
tet und können die eigentliche FLD- und
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 Bor-
derroutine, womit der Kreislauf von
Neuem beginnt. Gleichzeitig setzt Sie
die Darstellung auf 25 Zeilen zurück,
damit der Bordereffekt auch funktio-
niert. Anschließend wird der FLD-Effekt
durchgeführt, indem der Zeilenanfang vor
dem Rasterstrahl hergeschoben wird. Wäh-
renddessen werden die Vorder- und Hin-
tergrundfarbe nach zwei Farbtabellen bei
$1200 und $1300 verändert. Der Raster-
split wird durch ausreichende Verzöge-
rung bis zur Mitte einer Rastezeile er-
zeugt. Zum Schluß des IRQs wird noch bis
zum Ende der Rasterzeile verzögert, und
die Standardfarben zurückgesetzt, bevor
die ursprünglichen Inhalte der Prozes-
sorregister, wie sie vor dem Auftreten
des FLD1-IRQs vorhanden waren, zurückge-
holt werden und der IRQ beendet wird:
Cycle dec $d019 ;VIC-ICR löschen
lda #$18 ;25-Zeilen-Darst. ein-
sta $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 Sideborder-
routinen zum Abschalten des linken und
rechten Bildschirmrands, nämlich.
(ub)
Interrupt-Kurs
"Die Hardware ausgetrickst..."
(Teil 7)
----------------------------------------
Nachdem wir uns im letzten Kursteil aus-
giebig um das IRQ-Timing gekümmert hat-
ten, 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 dar-
stellen 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 Bild-
schirms. 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 ver-
tikalen Scrollen verwendet, da man bei
einem 38-Spalten-Bildschirm neu herein-
laufende Bildschirmzeichen setzen kann,
ohne daß sie vom Betrachter gesehen wer-
den.
Durch das rechtzeitige Setzen und Lö-
schen dieses Bits kann man nun auch den
linken und rechten Bildschirmrand ab-
schalten. Hierbei liegt die Betonung
besonders auf dem Wort "rechtzeitig". Da
der Rasterstrahl in der vertikalen näm-
lich derart schnell ist, daß der Prozes-
sor kaum nachkommt, müssen wir den Zeit-
punkt der Umschaltung sehr genau abpas-
sen, 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 En-
det, umgeschaltet werden muß, und der
Rasterstrahl zum Zeichnen von 8 Pixeln
gerade mal 1.2 Taktzyklen benötigt, ha-
ben 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 begin-
nen abzuarbeiten. Der eigentliche
Schreibzugriff findet dann im dritten
Taktzyklus, genau zwischen 38- und 40-
Zeichen-Rand statt. Zur Verdeutlichung
sollten Sie sich einmal das Programmbei-
spiel "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 Joystickbewegun-
gen nach oben und unten können Sie die
Anzahl der zu öffnenden Zeilen variie-
ren.
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 Inter-
ruptquellen, 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, da-
mit der Sideborder-Effekt auch sichtbar
wird. Sie sehen hier zunächst 7 NOPs,
mit denen wir vom Ende der Rasterglät-
tung bis zum Beginn des sichbaren rech-
ten Bildschirmrandes verzögern. Hierauf-
hin 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 effek-
tiv 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 be-
merken, daß wir lediglich im oberen,
sonst mit dem Rahmen verdeckten Bereich
des Bildschirms den seitlichen Rand
geöffnet haben. Das funktioniert natür-
lich nur, wenn wir den VIC zuvor mit dem
normalen Bordereffekt überlistet haben,
so daß er den oberen und unteren Bild-
schirmrand 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 un-
ser 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 Kom-
bination 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 entschei-
denden 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 Rou-
tine vom Timing her immer noch genauso
funktioniert, wobei wir den Bildschirm-
rand jedoch innerhalb des normalen Bild-
bereichs 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 Spri-
tes darstellen. Dies gestaltet sich je-
doch ebenfalls schwieriger als sonst.
Nämlich so wie der VIC den Prozessor in
jeder Charakterzeile anhält, um un-
gestö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 die-
ser Rasterzeile anzuzeigen sind. Hierzu
benötigt er ungefähr 2.4 Taktzyklen, die
uns von der Prozessorzeit natürlich wie-
der abgehen! Somit muß die Verzögerung
innerhalb unserer Schleife verkürzt wer-
den. Da wir aber keine Bruchteile von
Taktzyklen verzögern können, wird die
ganze Sache umso haariger!!! In der Re-
gel hilft hier nur die alte "Trial-and-
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, an-
stelle von ursprünglich 29, warten, bis
der Rasterstrahl die nächste Zeile er-
reicht. Es gehen uns also 17 Zyklen ver-
loren! 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 Si-
deborder-Effekt nur in den Rasterzeilen
funktioniert, in denen die Sprites
sichtbar sind. Das gilt auch dann, wenn
Sie die Sprites in Y-Richtung expandie-
ren (vor dem Aufruf des Beispiels PO-
KE53271,255 eingeben). Hier funktioniert
der Effekt dann doppelt soviele Zeilen
lang, da der VIC bei Expansion jede Zei-
le eines Sprites einfach doppelt liest
(auf zwei Rasterzeilen verteilt). Möch-
ten Sie den Rand weiter nach unten öff-
nen, so ist nur noch ein komisches Li-
nien-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 sie-
ben Sprites darzustellen sind. Schalten
Sie weniger oder mehr Sprites ein, so
ist der Sidebordereffekt zunichte ge-
macht. Das gleiche passiert, wenn Sie
nicht alle sieben Sprites Y-expandieren.
Hier Endet der Effekt nach 21 Zeilen,
nämlich dann wenn ein nicht-expandiertes
Sprite zu Ende gezeichnet ist, und Sie
sehen im Border nur die Hälfte der ex-
pandierten Sprites!
4) HINWEISE ZUM OPTIMALEN TIMING
Möchten Sie nun selbst die Verzögerung
ausloten, die zur Darstellung einer be-
stimmten Anzahl Sprites benötigt wird,
so beachten Sie folgende Regeln:
* Geben Sie allen Sprites, die in Berei-
chen angezeigt werden, in denen der
Sideborder abgeschaltet ist, ein und
dieselbe Y-Koordinate, so daß sie ho-
rizontal in einer Linie liegen. Da-
durch 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 Be-
reich sichtbaren, Sprites expandiert
sein (es sei denn Sie möchten nur die
Hälfte eines expandierten Sprites se-
hen).
* 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
NOP- und 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 An-
zahl 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 funktio-
niert. 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 Spri-
te ist lediglich zur Orientierung ge-
dacht. Da der Prozessor immer nur zu
einem Taktsignal eine Operation begin-
nen kann, sollte die Verzögerung also
immer zu finden sein.
* Zum Testen, wann Register $D016 geän-
dert 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 Einstel-
lung 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äm-
lich eine Sideborderroutine, die acht
Sprites im Border darstellt. Das verwun-
derliche an ihr ist die Funktionsweise.
Denn obwohl die Sideborderschleife ge-
nauso viele Taktzyklen verbraucht wie
die Version für sieben Sprites, kann
dennoch ein Sprite mehr dargestellt wer-
den. Ermöglicht wird dies durch eine
trickreichere Variante des Löschen und
Setzen des Bildschirmbreite-Bits aus
Register $D016. Wir initialisieren die-
ses Register vor der eigentlichen Side-
borderroutine 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 Be-
fehls schon früher eintritt. Alles in
Allem ist das Funktionieren dieser Me-
thode 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 Assembler-
programmierung ein, da es an der Funk-
tionsweise des DEC-Befehls liegt, warum
das Bit rechtzeitig gelöscht wird.
Beachten Sie daher bitte auch diese Va-
riation der Sideborder-Routine für eige-
ne 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 X-expandiert 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 Hil-
fe 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äute-
re, zumal es ja eigentich nicht zu unse-
rem Thema gehört.
Öbrigens: vielleicht ist Ihnen schon
aufgefallen, daß beim Üffnen des Side-
borders 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 Ab-
schalten des Randes zwar am rechten Ende
einer Rasterzeile geschieht, sich jedoch
auf das linke Ende der folgenden Raster-
zeile auswirkt!
In der nächsten Folge dieses Kurses wer-
den wir Ihnen zeigen, wie man den Border
OHNE wegdrücken des Bildschirms durch
FLD abschaltet (besonders haarige Ti-
mingprobleme). 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 bespro-
chenen Beispiele nocheinmal genauer an-
schauen, und ein wenig damit herumexpe-
rimentieren. Sie ahnen nicht wie viel-
seitig man mit dem abgeschalteten Border
arbeiten kann...
(ub/ih)
IRQ-KURS
"Die Hardware ausgetrickst..."
(Teil 8)
----------------------------------------
Herzlich willkommen zum achten Teil un-
seres Raster-IRQ-Kurses. Heute wollen
wir uns mit einem besonders interessan-
ten 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 8x8-Pixel-Block, dar-
zustellen. Durch die dabei entstehende
Farbvielfalt können sehr eindrucksvolle
Bilder angezeigt werden, die bei ge-
schickten Grafikern schon an die Qua-
lität von Amiga-Bildern reichen können!
1) GRUNDLAGEN
Wollen wir uns zunächst einmal an-
schauen, wie der VIC vorgeht, um eine
Bitmap-Grafik auf den Bildschirm zu zau-
bern:
Zunächst einmal muß der Grafikmodus ein-
geschaltet werden, wobei die Lage der
Bitmap im Speicher mitangegeben wird.
Nun zeigt der VIC also die Bits des an-
gegegebenen Speicherbereichs als einzel-
ne Pixel auf dem Bildschirm an. Um die-
sen Pixeln nun auch noch Farbe zu ver-
leihen, 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 normaler-
weise bei $0400 (dez. 1024) befindet,
die Farbe der Pixel innerhalb eines
8x8-Pixel-Blocks. So legt das erste Byte
des Video-RAMs (als Zeichen ganz links
oben), die Farbe für die 8x8, im Grafik-
modus quasi "über" ihm liegenden, Pixel
fest. Das zweite Byte ist für den näch-
sten 8x8-Pixel-Block zuständig, und so
weiter. Da der VIC insgesamt nur 16 Far-
ben kennt, sind dabei jeweils nur die
unteren vier Bits eines Video-RAM-Bytes
von Bedeutung. Die oberen vier sind un-
benutzt und werden ignoriert.
b) DER MULTICOLOR-MODUS
In diesem Modus werden zwei nebeneinan-
der liegende Pixel jeweils zu einem
Farbcode zusammengefasst. Sind beide 0,
so erscheint an ihrer Stelle die Hinter-
grundfarbe, sind beide auf 1, so wird
für beide die Farbe aus einem äquvalen-
ten Byte des Color-RAMs geholt (bei
Adresse $D800 - dez. 55296), das norma-
lerweise 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 fest-
legt.
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-$3FFF) in 1KB-
Schritten verschieben. Hierfür ist Regi-
ster 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 Darstel-
lung der Textzeichen, bzw. im Grafikmo-
dus der Farbzeichen, zuständig ist. Die
unteren Bits von 0-3 bestimmen die Lage
des Zeichengenerators, also des Spei-
cherbereichs, 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 16KB-Bereichs des VICs.
Die im Folgenden angegebenen Adressen
verstehen sich also als Offsets, die auf
die Startadresse des 16KB-Bereichs auf-
addiert 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 Kurs-
teil.
Desweiteren wollen wir noch schnell kl-
ären, wie man den 16KB-Bereich des VICs
verschiebt, da die FLI-Routine eine Men-
ge Video-Speicher benötigt (nämlich die
vollen 16KB), 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-$3FFF - 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 Bitkombi-
nationen 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öglich-
keit, einem 8x8-Pixel Block der Grafik
in jeder Zeile eine neue Farbe zuzutei-
len (im Multicolormodus sogar 2), so daß
wir die 2-, bzw. 4-Farbeinschränkung auf
einen Bereich von 1x8 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 Rasterzei-
len darzustellen hat. Somit würde ein
einfaches Umschalten auf eine neue Vi-
deo-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 zwin-
gen, 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 An-
fang der nächsten Charakterzeile durch
Hochsetzen der vertikalen Bildschirmver-
schiebung 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 zu-
sammenfä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 an-
dere Video-Map um, so liest der VIC, so
als wäre alles in Ordnung, die 40 Cha-
rakter 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 eben-
falls 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 Farbabstufun-
gen!
Doch kommen wir nun zum Sourcecode die-
ser FLI-Routine. Zunächst einmal werde
ich Ihnen die Initialisierung hier auf-
listen, die größenteils identisch mit
den zuvorigen Init-Routinen ist, jedoch
auch einige FLI-spezifische Einstellun-
gen 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 #$7f ;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 Be-
triebssystem ab, damit der Vektor auch
angesprungen wird, und erlauben dem VIC
in Rasterzeile $5C einen Raster-IRQ aus-
zulösen. Zudem wird eine Tabelle in den
Zeropageadressen von $02 bis $09 initia-
lisiert, die in Folge die Werte von $38
bis $3F 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 Ver-
schiebeoffset um 1 erhöht, was dann im-
mer dem jeweiligen Wert für den Beginn
einer Charakterzeile entspricht (Start-
wert $38 -> Verschiebung=0 für tatsäch-
liche Charakterzeile; nächster Wert=$39
-> Verschiebung=1 Zeile, wehalb die er-
ste Rasterzeile hinter der normalen Cha-
rakterzeile vom VIC wieder als Charak-
terzeile aufgefasst wird; usw.). Die
Werte werden übrigens deshalb in eine
Tabelle innerhalb der Zeropage eingetra-
gen, 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
$3C00 geladen, wo sich auch die $0400
Bytes für die Color-Map befinden. Ab
Adresse $4000 beginnen nun die acht Vi-
deo-Maps, die später von der FLI-Routine
durchgeschaltet werden. Auch sie sind
jeweils $0400 Bytes lang (was einer Ge-
samtlänge von $0400*8=$2000 Bytes
gleichkommt). Hiernach, im Bereich von
$6000-$8000 befindet sich nun die darzu-
stellende Grafik-Bitmap. Was die Init-
Routine nun macht, ist lediglich die
Color-Map-Daten zwischen $3C00 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 zeit-
kritischen 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 "Onecyc-
le", ab dem der IRQ "geglättet" ist.
Hier nun der eigentliche Kern der Routi-
ne. Da der Interrupt bei Rasterzeile $5C
ausgelöst wurde, und die Glättung zwei
Rasterzeilen verbrauchte, befinden wir
uns nun also in Zeile $5E, 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 ;Bildschirmrahmen- und
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 ;Bildschirmrahmen- und
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 Vorbereitun-
gen 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 fest-
legen der Rasterzeile $5C 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 $5E für 96 Raster-
zeilen aktiv sein. Hier nun der Source-
code 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 Rou-
tine 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 Zeit-
punkt 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 geschal-
tet, 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äch-
ste Rasterzeile einzustellen, wobei auch
dies exakt vor Beginn derselben gesche-
hen muß. Hierzu wird zunächst der Akku
mit dem Wert $18 geladen (Wert für Regi-
ster $D018), wobei die oberen vier Bits
die Lage der Video-Map bestimmen. Sie
enthalten den Wert 1 und bezeichnen da-
mit 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, be-
nutzen wir ihn ebenfalls. Der LDA-Befehl
verbraucht nun 2 Taktzyklen. Als Näch-
stes 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 rich-
tigen 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 Be-
ginn der nächsten Rasterzeile befinden.
Durch die im letzten Moment vorgegaukel-
te Vertikal-Verschiebung um eine Raster-
zeile, 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 Be-
ginn der eigentlichen Charakterzeile,
abgearbeitet wurde. Nun wird zum Haupt-
IRQ zurückverzweigt, wo "FLILINE" so-
gleich zur Bearbeitung der nächsten Cha-
rakterzeile 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ß un-
ser Programm also 2 Zyklen länger
dauert, als es eigentlich sollte. Dies
liegt an einem kleinen "Nebeneffekt" von
FLI. Dadurch, daß wir den VIC austrick-
sen, 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ön-
nen 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 da-
nach kann der VIC die Grafik mit akti-
viertem FLI-Effekt darstellen. In den 3
Charaktern davor ist dann wieder das
Bitmuster der letzten Speicherzelle sei-
nes Adressierungsbereiches zu sehen
(normalerweise $3FFF - im Beispiel aber
durch die Bereichsverschiebung $7FFF).
Gleichzeitig scheint der Prozessor da-
durch aber wieder 2 Zyklen mehr zu ha-
ben, was die einzige Erklärung für die
oben aufgezeigte Diskkrepanz sein kann
(Sie sehen: selbst wenn man einen sol-
chen 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, manch-
mal 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 Prozes-
sor während des annähernd gesamten Bil-
daufbaus im FLI-Interrupt befindet, und
dadurch wenig Rechenzeit für andere Ef-
fekte ü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" ge-
startet und durch einen 'SPACE'-
Tastendruck beendet wird. Das Prinzip
der einzelnen Routinen ist immer ähn-
lich, nur daß bei "FULL" noch mehr Ra-
sterzeilen in FLI-Darstellung erschei-
nen, und bei "HIRES" zusätzlich der Hi-
res-Modus zur Darstellung verwendet
wird. Am Besten Sie disassemblieren sich
die drei Programme mit Hilfe eines Spei-
chermonitors und manipulieren sie ein
wenig, um die Vielfalt und Funktionswei-
se von FLI-Effekten besser zu erlernen.
Übrigens: Der FLI-Effekt funktioniert
auch im normalen Textmodus. Hierbei wer-
den dann immer nur die oberen acht Pixel
eines Zeichens in allen acht Rasterzei-
len wiederholt. Man könnte damit einen
recht eindrucksvollen Textein- und aus-
blend-Effekt programmieren, bei dem die
einzelnen Zeichen quasi auf den Bild-
schirm "fließen" (oder von ihm "weg-
schmelzen"). 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 Pro-
grammierung 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 Bild-
schirm 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 Rasterin-
terrupt 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 Sprite-
positionen 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 folgen-
des Programmbeispiel verdeutlichen, das
Sie auf dieser MD auch unter dem Namen
"16SPRITES" 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 unte-
ren Bildschirmhäfte, jeweils in einer
Reihe. Kommen wir zunächst zur Init-
Routine unsres kleinen Beispiels, deren
Funktionsprinzip uns mittlerweile be-
kannt 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 auftre-
ten soll, was in etwa die Mitte des
sichtbaren Bildbereichs ist. Dort soll
dann die Routine "IRQ1" aufgerufen wer-
den, 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 abge-
schaltet, und anschließend in eine
Schleife gesprungen, die auf die SPACE-
Taste wartet, und in dem Fall einen Re-
set auslöst. Wichtig sind nun noch die
drei Kopierschleifen innerhalb der Ini-
tialisierung. 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 Sprite-
block 13 mit gesetzten Pixeln, setzen
die Spritepointer aller Sprites auf die-
sen 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 eigent-
lich nichts anderes, als Werte aus den
Zeropageadressen von $A0 bis $B3 in ein-
zelne VIC-Register zu kopieren. Damit
geben wir dem VIC wir ab der Rasterposi-
tion $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 Ze-
ropageadressen 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 Bildschirmbe-
reiche 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, ein- oder au-
schalten, sowie X-, bzw. Y-Expandieren.
Wir haben also quasi zwei "virtuelle",
oder "softwaremäßige" Sprite-VICs er-
schaffen, deren Register wie die des
normalen VICs beschrieben werden können.
Dies können Sie übrigens mit einer Rou-
tine ab Adresse $1200 machen. Sie wird
im Hauprogramm (sh. INIT-Listing) stän-
dig 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ön-
nen.
Wir haben nun also 16 Sprites auf dem
Bildschirm, jedoch mit der Einschrän-
kung, daß immer nur jeweils acht im obe-
ren 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 Spri-
te im oberen Bildbereich nie eine Posi-
tion 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, ans-
telle 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 Spri-
tes recht problemlos, solange alle wei-
teren Sprites in der Horizontalen von-
einander 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 Bild-
schirm darstellbar sein sollen, und zu-
dem 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, flexi-
bler mit den horizontalen Sprite-
Positionen umzugehen. Solch eine Routine
werden wir nun realisieren. Sie ist all-
gemeinhin 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 Hin-
tergrundsterne, 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 Bild-
schirm darzustellen, wobei wir uns mö-
glichst wenig Sorgen über die Darstel-
lung machen wollen. Diese Arbeit soll
unsere Routine übernehmen, und automa-
tisch die richtige Darstellung wählen.
Damit es keine Bereiche gibt, in denen
gar keine Sprites dargestellt werden
können, weil gerade irgendwelche Pseu-
do-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 "vir-
tuellen" VIC an, den wir so behandeln,
als könne er tatsächlich 16 Sprites dar-
stellen. 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 Betriebssy-
stemroutinen mehr verwendet werden kön-
nen, 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-$8F
16 Bytes, die bestimmen, welche Sprites
ein- oder ausgeschaltet sind. Eine 0 in
einem dieser Bytes schaltet das entspre-
chende Sprite aus. Der Wert 1 schaltet
es ein ($80 ist für Sprite0, $8F für
Sprite15 zuständig).
$90-$9F
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 unter-
gebracht.
$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 unterge-
bracht, 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 Darstel-
lung später benötigt.
$F0-$FF
Innerhalb dieses Bereichs werden Zeiger
auf die Reihenfolge der Sprites ange-
legt. 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 X-
und Y-Position durch Beschreiben der
Register $99, $A9, sowie $B9 setzen,
Farbe und Spritepointer in $C9 und $D9
unterbringen, und anschließend das Spri-
te durch Schreiben des Wertes 1 in Regi-
ster $89 einschalten. Analog wird mit
allen anderen Sprites verfahren, wobei
die Sprite-Nummer immer der zweiten Zif-
fer 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ön-
nen 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 pro-
blemlos 24, oder gar 32 Sprites auf den
Bildschirm bringen können). Rein theore-
tisch 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 min-
destens 22 Rasterzeilen (die Höhe eines
ganzen Sprites plus eine Zeile "Sicher-
heitsabstand") unterhalb des ersten vir-
tuellen Sprites liegt. Damit diese Be-
dingung so oft wie nur möglich erfüllt
ist, verwendet man ein echtes VIC-Sprite
immer zur Darstellung des n-ten, 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 bei-
den Sprites erreicht wird. Dies sollte
unsere Routine in einem Bildschirmbe-
reich tun, in dem keine Sprites ange-
zeigt werden können, also dann, wenn der
Rasterstrahl gerade dabei ist, den obe-
ren und unteren Bildschirmrand zu zeich-
nen (selbst wenn man diesen auch ab-
schalten kann). Nach der Sortierung kön-
nen nun die Werte der ersten acht vir-
tuellen Sprites im VIC eingetragen wer-
den, 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. Jedes-
mal, 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" ge-
laden um mittels "SYS4096" gestartet.
Die Initialierung möchte ich Ihnen dies-
mal 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 Orga-
nisationsarbeit durchführen soll. Hier-
bei 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 Inhal-
te der Einschalt-Register des Pseudo-
VICs aufaddiert, wobei das Ergebnis die
Anzahl eingeschalteter Sprites ergibt
(wenn an, dann Wert=1, sonst 0). An-
schließ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 einge-
schaltet, so wird auch nur diese Anzahl
aktiviert werden. Sind es mehr, so müs-
sen immer alle acht eingeschaltet wer-
den. Der so ermittelte Wert wird dann in
der Speicherzelle $7F zwischengespei-
chert. Für den weiteren Verlauf der Rou-
tine ist auch die ermittelte Anzahl not-
wendig, die in der Zeropageadresse $7E
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 einzel-
nen Koordinaten manipulieren muß, ist
diese Maßnahme notwendig. Desweiteren
darf sie die Spritedaten in den Regi-
stern 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 Reihen-
folge, 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 sor-
tierenden Feld angewandt wird, wie Spri-
tes eingeschaltet sind. Er ermittelt
durch eine Reihe von Vergleichen immer
den kleinsten Wert innerhalb der Kopie
der Y-Positionen, legt ihn in der Zei-
ger-Liste bei $F0 ab, und setzt die ent-
sprechende 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 eben-
falls 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 Spri-
tes aus den entsprechenden Registern
holen, und in den VIC schreiben.
Bevor wir nun zur Beschreibung der Rou-
tine kommen, noch einige technische Hin-
weise: damit die Routine möglichst
schnell arbeitet, wurde auf die Verwen-
dung 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 Mini-
mum vergleicht. Analog ist es bei ande-
ren Vorgängen (so auch bei der Aufaddie-
rung der Sprite-Einschalt-Register oben,
wo eigentlich auch eine Schleife hätte
benutzt werden können). Dadurch verlän-
gert sich das Programm natürlich ein
wenig, jedoch ist der damit verbundene
Speicheraufwand noch erträglich und wir
erreichen zudem eine hohe Verarbeitungs-
geschwindigkeit. Da sich viele Vorgänge
oft wiederholen, werde ich an diesen
Stellen mit "..." eine Folge andeuten,
die analog auch für alle weiteren Spri-
tes 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 einge-
schaltet sind. Hier wird nun geprüft, ob
ein Sprite eingeschaltet ist, oder
nicht, und in letzterem Fall die Y-
Koordinate mit dem Wert $FF überschrie-
ben, damit das abgeschaltete Sprite bei
der Sortierung später nicht mehr berück-
sichtigt 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 $8F ;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 ein-
geschalteten 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 Sor-
tierung 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 entspre-
chende Sprite geladen, der der Sprite-
nummer entspricht. Das X-Register
enthält den aktuellen Index auf die Zei-
gertabelle und wird pro Durchlauf um
eins erhöht. Die Sortierung ist beendet,
wenn die Vergleichsroutine insgesamt so
oft durchlaufen wurde, wie Sprites ein-
geschaltet 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 ermittel-
ten Minimum der entsprechende Y-Wert auf
$FF gesetzt, damit er im nächsten Ver-
gleich nicht mehr herangezogen wird. Auf
diese Weise wird nun nach und nach immer
wieder der kleinste Wert ermittelt, so-
lange, bis der Puffer nur noch $FF-Werte
enthält, und die Schleife "Anzahl-
Sprites"-Mal durchlaufen wurde. Die Sor-
tierung ist damit beendet, und die Mul-
tiplex-Routine kehrt wieder zur IRQ-
Routine zurück.
Hier nun wird als Nächstes die "SETSPR"-
Routine aufgerufen, die anhand der er-
mittelten 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 anzuzei-
gen, und setzt den nächsten IRQ-Auslöser
entsprechend. Zunächst einmal wollen wir
uns den ersten Teil dieser Routine an-
schauen. 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 ein-
sta $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 Spri-
te0 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 Ra-
ster-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 Routi-
ne "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 entsprechen-
de Sprite zuständig ist. Durch das Ein-
odern 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 Inter-
rupt 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 di-
rekt auf die Routine zur Darstellung des
nächsten Sprites verzweigt (sh. BEQ-
bzw. 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 weni-
ger als 15 Sprites dargestellt werden,
so wird, wie bei SPR8 auch schon er-
sichtlich, auf die Routine "BORDIRQ"
gesprungen. Sie steht ganz am Ende des
Programms und setzt "BORD" wieder als
nächste IRQ-Routine und beendet den ak-
tuellen IRQ. Sie wird ebenfalls abgear-
beitet, 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 die-
sen Monat. Ich möchte Sie noch dazu ani-
mieren, ein wenig mit den Multiplex-
Routinen herumzuexperimentieren. Versu-
chen 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 darge-
stellt werden, kleiner als 22 Rasterzei-
len, 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 an-
deren Sprites liegen. Da man die Sprite-
Priorität nicht beeinflussen kann, und
Sprites mit kleinen Spritenummern Prio-
riät vor Sprites mit großen Spritenum-
mern 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 Ob-
jekten 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 befas-
sen. 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" programmie-
ren, der es uns ermöglicht, einen
Scrolltext, so wie in Abspännen von Fil-
men, von unten nach oben über den gesam-
ten Bildschirm zu scrollen. Hierbei wer-
den 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 Kennt-
nis 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 darstel-
len. Um nun mehrere Zeilen zu generie-
ren, müssen wir in regelmäßigen Abstän-
den einen Raster-IRQ erzeugen, der je-
desmal die Y-Position, sowie die Spri-
te-Pointer der acht Sprites neu setzt,
um somit die nächste Scroller-Zeile dar-
zustellen. Unsere Routine tut dies alle
24 Rasterzeilen, womit sich zwischen
zwei Spritezeilen immer 3 Rasterzeilen
Freiraum befinden. Um nun einen Scroll-
effekt nach oben zu erzeugen, benutzen
wir zusätzlich ein Zählregister, daß
einmal pro Rasterdurchlauf um 1 ernie-
drigt, und so von $17 bis $00 herunter-
gezä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 Poin-
terliste, freigewordenen Sprites werden
dann die Buchstaben der neuen Zeile ein-
kopiert, 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" unterschei-
det 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 wer-
de, 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 unte-
ren Bildschirmrand befinden.
$1500 Spritepointer-Liste, die auf die
Sprites im Bereich von $2600-
$3FFF zeigt.
$1600 Der Scroll-Text im ASCII-Format.
Die Initialisierungsroutine unseres Mo-
vie-IRQs schaltet wie üblich das Be-
triebssystem-ROM ab, und setzt im Hard-
ware-IRQ-Zeiger bei $FFFE/$FFFF die
Startadresse der MOVIEIRQ-Routine
($1200) ein. Zudem werden alle CIA-
Interrupts gesperrt und die Spriteregi-
ster 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. Deswei-
teren wird die Farbe aller Sprites auf
"weiß" geschaltet. Zusätzlich wird in
einer eigenen Unterroutine der Speicher-
bereich von $2600-$3FFF, 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 wer-
den, 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 weite-
ren Positionen resultieren dann aus dem
jeweils letzten Positionswert plus dem
Offset 24.
Desweiteren erscheinen in diesem Pro-
grammteil noch zwei Labels, mit den Na-
men "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 wur-
de, kann so der Basis-Y-Wert der ent-
sprechenden Spritezeile (im ersten
Durchlauf Zeile 0, Y-Pos 26) auf den
Akkuinhalt aufaddiert werden. Der resul-
tierende Wert entspricht nun der Y-
Position aller Sprites dieser Zeile, die
wir sogleich in die VIC-Register eintra-
gen.
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 aufad-
dieren von 1 auf den jeweils letzten
Wert, was in unserer Routine durch die
INX-Befehle durchgeführt wird. Die Zei-
gertabelle enthählt nun die Werte von
$98 bis $F8, als Zeiger auf die Sprites,
die im Speicherbereich von $2600-$3FFF
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 angespro-
chenen 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 Spritepoin-
ter von $98-$9F gehen, womit ihre Spri-
tes von im Bereich von $2600 bis $2800
untergebracht sind, aus dem oberen Bild-
schirmrand, so muß die zweite Spritezei-
le (Pointer von $A0-$A7) von nun an die
erste, auf dem Bildschirm darzustellen-
de, Zeile sein, wobei wir die gerade
herausgescrollte Zeile als unterste Zei-
le verwenden müssen. Nachdem ihr Textin-
halt in die Sprites im Speicherbereich
von $2600 bis $2800 eingetragen wurde,
müssen nur noch die Spritezeiger zum
richtigen Zeitpunkt auf diese Zeile um-
geschaltet werden. Wir haben nun noch
einen Weiteren Index, namens "SHOWLINE",
der in Zeropageadresse $F8 untergebracht
ist, und uns angibt, welche der Sprite-
zeilen 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 pro-
blemlos inkementiert werden, ohne dabei
einen Zeilenüberlauf beachten zu müssen!
Kommen wir jedoch wieder zurück zu unse-
rer IRQ-Routine. Nachdem die Y-
Positionen, sowie die Spritezeiger ge-
setzt wurden müssen noch einige verwal-
tungstechnische 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ß, mit-
berü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" ausle-
sen. Auf diesen Wert muß nun noch der
momentane Scrolloffset aus "SOFTROLL"
addiert werden, um die tatsächliche Ra-
sterzeile zu erhalten. Der daraus resul-
tierende Wert kann zunächst einmal in
$D012 eingetragen werden. Sollte bei der
Addition ein Öberlauf stattgefunden ha-
ben, also ein Wert größer $FF herausge-
kommen 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 untege-
bracht 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 än-
dern.
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 Spri-
tezeilen 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 Spritezei-
len dargestellt wurden. Ist dies nicht
der Fall, so wird der IRQ durch Zurück-
holen der Prozessorregister, gefolgt von
einem RTI, beendet. Befinden wir uns
allerdings schon im letzen der 13 IRQs,
die pro Rasterdurchlauf auftreten sol-
len, 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 Bild-
schirm 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 Rasterdurch-
lauf 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 darge-
stellt werden. Durch Ändern des hier
subtrahierten Wertes in 2 oder 3 können
Sie übrigens auch die Scrollgeschwindig-
keit 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 #$0d ; 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ür-
lich alle 13 Spritezeilen wieder zurück-
gesetzt 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 er-
haltene 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 Lauf-
schrift, die an dem Label "ESTEXT" abge-
legt ist, schon zu Ende gescrollt wurde.
Wenn ja, so wird in diesem Fall der Zei-
ger "TPOINT", der auf das erste Zeichen
der als nächstes darzustellenden Sprite-
zeile zeigt, wieder mit der Startadra-
dresse des Textes ("ESTEXT") initiali-
siert:
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, des-
sen Wert ja noch negativ ist, da wir im
ersten Teil der MOVESPR-Routine durch
die Subtraktion einen Unterlauf des Zäh-
lers 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 Rasterinter-
rupts liegt, $18 (=dez. 24), auf. Der
resultierende Wert wird dann wieder in
Softroll abgelegt. Auf diese Weise wer-
den 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 Assem-
bler-Opcode des "JSR"-Befehls ent-
spricht, in das Label "MT" ein. Letzte-
res befindet sich innerhalb der
MOVIEIRQ-Routine, und zwar vor dem Be-
fehl "JSR MAKETEXT". Die MAKETEXT-
Routine baut eine Spritezeile auf, indem
Sie die Zeichendaten dieser Zeile von
dem Zeichensatz bei $0800 in die zu be-
nutzenden Sprites einkopiert. Da dies
nicht direkt zu unserem Kursthema
gehört, möchte ich auch nicht weiter auf
diese Routine eingehen. Wichtig zu wis-
sen 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 Befehlsfol-
ge, die den Wert $2C 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 Funk-
tion beinhaltet, sondern lediglich ver-
hindern 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 blok-
kiert, muß sie auf diesem Weg benutzt
werden. Während ihres Ablaufs erlaubt
sie auch weitere IRQs, so daß Sie wäh-
rend der Darstellung des nächsten Ra-
sterdurchlaufs, immer zwischen den Spri-
tepositionierungen durch den IRQ, ablau-
fen kann.
Kommen wir nun zum letzten Teil der MO-
VESPR-Routine, dem Label "ROLLON". Sel-
biges 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 Rasterposi-
tion, in $D011 löschen. Gleichzeitig
schaltet sie die 25-Zeilen-Darstellung
durch Setzen des 3. Bits dieses Regi-
sters wieder ein. Dies ist eigentlich
eine Aufgabe, die für das Beispielpro-
gramm "MOVIE.1" irrelevant ist. Wohl
aber für "MOVIE.2", in dem wir den Mo-
vie-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 herunterge-
schaltet werden, und danach, vor nochma-
ligem 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 $10C-$126
auftreten kann, befinden wir uns
tatsächlich schon unterhalb der Raster-
zeile $FA, womit die Änderung zum kor-
rekten Zeitpunkt durchgeführt wird.
3) GLEICHZEITIGES ÜFFNEN DES BILDSCHIRMS
Wie schon angesprochen, öffnet das Pro-
grammbeispiel "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 Unter-
fangen, 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 Ra-
sterzeilen 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 abzuti-
men, daß sie genau an dieser Position
die Bildschirmdarstellung ändert. Würde
man versuchen durch Verzögerungsschlei-
fen die richtige Position abzutimen, so
wäre das mit einem erheblichen Program-
mieraufwand verbunden. Deshalb greifen
wir zu einem kleinen Trick: Die INIT-
Routine von "MOVIE.2" wurde um eine
kleine Änderung erweitert. Zunächst las-
sen wir hier den Prozessor, durch stän-
diges Auslesen und Vergleichen der ak-
tuellen Rasterposition, auf Rasterzeile
$FA warten. Ist diese erreicht, so ini-
tialiseren wir Timer A von CIA-2 mit dem
Wert $4CC7 und starten ihn. Da der Ra-
sterstrahl immer exakt so viele Taktzy-
klen 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 er-
forderliche Ä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 un-
terbrochenen 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 Un-
terrountine zum Initialiseren des Ti-
mer-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 $4CC7
Taktzyklen einen NMI aus. Da der Raster-
strahl 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 ha-
ben. 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ße-
lich nur diesen auf dem Stapel zu ret-
ten. 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 ver-
sehentlich 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 rich-
tigen 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 ein-
schalten. Durch den BIT-Befehl führen
wir einen Lesezugriff auf das ICR-
Register von CIA-2 aus, womit wir selbi-
ges Löschen, und dadurch das Auftreten
und Melden des nächsten NMIs ermögli-
chen. 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 folgen-
den 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 un-
seres IRQ-Kurses. Wie schon in den letz-
ten Kursteilen, werden wir uns auch die-
sen 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. Die-
sen Effekt nennt man "ESCOS", der Dreh-
und 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 lin-
ker und rechter Bildschirmrand abge-
schaltet werden. Wir hatten weiterhin
gelernt, daß in diesen Bereichen aus-
schließlich Sprites auftauchen können,
die durch die abgeschalteten Ränder
sichtbar sind, wo sie sonst von letzte-
ren ü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 Genauig-
keit erforderte. Desweiteren wird Ihnen
aus dem letzten Kursteilen bestimmt noch
die Sprite-Multiplexer-Routine in Kombi-
nation mit einem Moviescroller im Kopf
sein, mit der wir 104 Sprites gleichzei-
tig auf den Bildschirm brachten. Wie Sie
sich vielleicht erinnern, waren wir da-
bei an gewisse Grenzen gebunden. So war
z.B. zwischen zwei Spritezeilen immer
ein Abstand von ca. 2 Rasterzeilen er-
forderlich, 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 nor-
malen Rasterzeilen zu unterscheiden hat-
te. In dieser Folge unseres Kurses wol-
len wir nun all diese Komponenten mitei-
nander verbinden und eine Möglichkeit
kennenlernen, Timingprobleme durch Cha-
rakterzeilenberücksichtigung, zu umge-
hen. Das Endergebnis wird ein flächen-
deckend (!) mit Sprites belegter Bild-
schirm sein, wobei weder Leerräume zwi-
schen 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 Kur-
steils, dem Timing-Problem, das sich uns
entgegenstellt. Möchten wir nämlich alle
Bildschirmränder abschalten und gleich-
zeitig auch noch die Sprites multiple-
xen, so können wir programmtechnisch in
"Teufels Küche" gelangen. Wir müssten
berücksichtigen, daß im sichtbaren Bild-
schirmfenster alle acht Rasterzeilen
eine Chakaterzeile auftritt, gleichzei-
tig müsste in jeder Rasterzeile der lin-
ke und rechte Bildschirmrand abgeschal-
tet, sowie in jeder 21. Rasterzeile die
Sprites neu positioniert werden. Dabei
stellen vor allem die Charakterzeilen
ein großes Problem dar. Wir müssten un-
terscheiden zwischen Rasterstrahlposi-
tionen im oberen und unteren Bildschirm-
rand und innerhalb des sichtbaren Bild-
schirmfensters, und zudem noch für letz-
teren Fall berücksichtigen, wann sich
der VIC gerade in einer Chakaterzeile
befindet und wann in einer normalen Ra-
sterzeile. 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äl-
len eine FLD-Routine, die die Charakter-
zeile im fraglichen Bildschirmbereich
einfach nach unten "wegdrückte", so daß
wir in jeder Rasterzeile 63 Taktzyklen
zur Verfügung hatten und somit ein ein-
heitliches 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 Bild-
schirm mit Sprites füllen möchten, kön-
nen wir davon ausgehen, daß wir keiner-
lei Hintergrundgrafik, bzw. Textzeichen
benötigen. Es gibt nun einen Trick, den
wir noch nicht kennengelernt haben, mit
dem wir die Charakterzeilen ganz ab-
schalten können, so daß nur noch die
Sprites dargestellt werden. Wie fast
immer ist Register $D011, ein Dreh- und
Angelpunkt der VIC-Trickeffekt-Kiste,
für diesen Trick verantwortlich. Mit Bit
4 dieses Registers können wir nämlich
den gesamten Bildschirm abschalten, was
einer Deaktivierung des VICs gleich-
kommt. Er wird durch das Löschen dieses
Bits veranlasst, auf dem gesamten Bild-
schirm nur noch die Rahmenfarbe darzu-
stellen, und keine Charakterzeilen mehr
zu lesen. Die Sprites bleiben jedoch
weiterhin aktiv, obwohl sie jetzt un-
sichbar sind, da sie jetzt auf dem ge-
samten Bildschirm mit dem Rahmen über-
deckt 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 Raster-
strahl nun Rasterzeile $30, an der ei-
gentlich das sichtbare Bildschirmfenster
beginnt, so prüft der VIC, ob der Bild-
schirm nun ein- oder ausgeschaltet ist.
In letzterem Fall deaktiviert er seine
Zeichenaufbau-Schaltkreise bis zum näch-
sten 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 Bildschirm-
hintergrund, auf dem sich auch die Spri-
tes 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
$3FFF) 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 Ih-
nen nun auflisten möchte. Sie finden es
auf dieser MD auch als fertig ausführba-
res 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 Initialiserungsrou-
tine 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 $3fff ; 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 Spri-
te-Pointer und schalten alle acht Spri-
tes 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 #$1b ;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" an-
springt, 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 he-
runter, damit der VIC vergisst, den obe-
ren und unteren Bildschirmrand zu zeich-
nen. 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 Charakter-
zeilen zu verhindern:
soff:
pha ;Prozessorregs. retten
txa
pha
tya
pha
lda $d011 ;Bild ausschalten (durch
and #$6F ; 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 Aufga-
be: durch eine AND-Verknüpfung des
$D011-Inhalts mit dem Wert $6F wird ein-
fach das 4.Bit dieses Registers
gelöscht. Gleichzeitig löschen wir dabei
Bit 7, das ja das High-Bit der Raster-
strahlposition angibt, womit wir also
auch dieses Bit für den folgenden Ra-
ster-IRQ bei Zeile $32 vorbereitet hät-
tem. 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 so-
mit der VIC überlistet und soweit ge-
bracht, daß er keine Charakterzeilen
mehr liest, geschweige denn darstellt.
Gleichzeitig schaltet diese Routine wie-
der auf die 25-Zeilen-Darstellung zurück
(Bit 3 von $D011 muß gesetzt werden),
damit das Abschalten des Borders auch im
nächsten Rasterstrahldurchlauf funktio-
niert:
SON:
pha ;Prozessorregs. retten
txa
pha
tya
pha
lda $d011 ;Bild und 25-Zeilen-
ora #$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ünsch-
ten 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 Programm-
beispiel 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öch-
ten 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 Cha-
rakterzeilen abgeschaltet wurden,
zusätzlich auch noch die Bildschirmrän-
der links und rechts deaktivieren. Das
Funktionsprinzip einer solchen Sidebor-
derroutine 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 Bild-
schirmrändern tun. Dadurch aber, daß
sich der Rasterstrahl horizontal recht
schnell bewegt, kommt es dabei auf ein
höchst exaktes Timing an. Einen Taktzy-
klus 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 kom-
binieren 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 gestar-
tet, 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 Pro-
bleme und deren Lösung eingehen:
Als Erstes müssen Sie davon ausgehen,
daß wir keine IRQ-Routine im eigentli-
chen Sinne programmieren werden. Auf-
grund des exakten Timings, das zum Ab-
schalten 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 hin-
und herzuschalten. Dadurch wird ledi-
glich ein einziger IRQ pro Rasterdurch-
lauf aufgerufen, nämlich direkt zu An-
fang 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 Ra-
sterzeilen dauert also ab Zeile 2) be-
ginnt der Prozessor damit in jeder ein-
zelnen Rasterzeile den Rand abzuschal-
ten. Dies geschieht dadurch, daß wir
nach jedem Umschalten der Spaltendar-
stellung genau am Öbergangspunkt zwi-
schen 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ück-
sichtigen, daß der VIC zum Darstellen
der Sprites den Prozessor ebenso anhal-
ten muß, wie beim Lesen der Charakter-
zeilen, 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 Ra-
sterzeile "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 schwe-
re Aufgabe, da der VIC uns diesbezüglich
ein wenig entgegenkommt. Ändern wir näm-
lich die X-Position eines Sprites inner-
halb einer Rasterzeile, so hat dies eine
sofortige eine Wirkung auf die Spritepo-
sition: 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 ge-
zeichnet wird geändert, so hat das keine
direkte Auswirkung auf die restlichen
Spritezeilen. Der VIC zeichnet das Spri-
te stur zu Ende, bevor er die Y-Position
nocheinmal prüft. Das gibt uns die Mö-
glichkeit, die Y-Position schon im Vor-
raus zu ändern, nämlich irgendwann in-
nerhalb der 21 Rasterzeilen, die das
Sprite hoch ist. Setzen wir die neue
Y-Position nun also genau auf die Ra-
sterzeile nach der letzen Spritezeile,
so kümmert sich der VIC darum erst ein-
mal 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 wor-
den, 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 Pro-
grammbeispiel zuvor gesehen haben, ist
das eigentlich eine recht einfache Auf-
gabe. Da wir zum Abschalten der Ränder
links und rechts jedoch ein hypergenaues
Timing benötigen, wäre es recht aufwen-
dig für die Rasterzeilen $FA, $28 und
$32 eigene IRQ-Routinen zu schreiben,
ohne dabei das Timing für die Sidebor-
derabschaltung damit durcheinander zu
bringen. Aus diesem Grund haben wir uns
einen Trick ausgedacht, mit dem wir bei-
de 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 Regi-
sters $D011. Warum sollten wir also
nicht aus zwei eins machen, und generell
in jeder Rasterzeile einen Wert in die-
ses Register schreiben? Das hilft uns
zum Einen das Verzögern des Raster-
strahls bis zum Ende der Rasterzeile,
und hat zudem den angenehmen "Nebenef-
fekt" 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 vo-
renthalten. Die Init-Routine werden wir
uns diesmal sparen, da sie nahezu ident-
lisch mit der des letzten Programmbei-
spiels 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 Rou-
tine 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 unbe-
kannte Routine. Der hier aufgezeigte
Auszug dient lediglich dem Glätten des
IRQs und sollte Ihnen, wenn auch leicht
modifiziert, schon aus anderen Beispie-
len 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 ei-
nen entscheidenden Vorteil: durch einfa-
ches 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, je-
doch 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 Charakter-
zeilen abzuschalten. Da im Prinzip nur
drei verschiedene Werte in dieser Tabel-
le 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 Ta-
bellenwerte übersprungen und somit auf
den "richtigen" ersten Tabelleneintrag
zugegriffen wird. Wichtig ist nur noch,
daß die Tabelle unbedingt innerhalb ei-
nes 256-Byte-Blocks steht und an einer
Adresse mit Low-Byte-Wert 0 beginnt.
Durch die Y-indizierte 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
$11FF,Y" auf eine Speicherzelle zu, und
enthält in diesem Fall das Y-Register
einen Wert größer 1, so tritt ein Öber-
lauf bei der Adressierung auf
($11FF+1=$1200 - High-Byte muß hoch-
gezä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 durcheinan-
derbringen, weswegen die Tabelle ab
Adresse $1500 abgelegt wurde und mit
Ihren 156 Einträgen keinen solchen Öber-
lauf erzeugt. Die Tabelle selbst enthält
nun, jeweils blockweise, die Werte $10,
$00 und $18, die an den richtigen Ra-
sterpositionen untergebracht wurden, so
daß die Ränder und Charakterzeilen beim
Schreiben des entsprechenden Wertes ab-
geschaltet 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 Ra-
sterzeilen die seitlichen Ränder ab-
schalten 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 14x 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ück-
sta $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 fol-
genden 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 Spri-
tes) 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 des-
selben Registers wieder auf 40 Zeilen
zurückgehen. Damit wäre der Rand geöff-
net worden. Als nächstes wird der Wert
für $D011 aus der besagten Tabelle aus-
gelesen und in dieses Register eingetra-
gen, 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 ei-
gentlichen Adresse, sondern 5 Bytes wei-
ter. 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 JSR-
und RTS-Befehl draufgehen. Beide ver-
brauchen 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 ein-
fach 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 die-
ser Zeile kein neuer Wert in $D011 ein-
getragen 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 ge-
raden Zeilennummern die alte Befehlsfol-
ge 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 einge-
tragen, 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än-
der, 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 Bild-
schirmrand wieder zum oberen Bildschirm-
rand zu wandern hat, in der wir eben-
falls 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 ken-
nenlernen, wie man diese kleinen Grafik-
winzlinge hardwaremässig dehnen und ver-
biegen 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äfti-
gen. 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 rech-
ten, sowie den oberen und unteren Bild-
schirmrand und stellte dann alle 21 Ra-
sterzeilen 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 un-
terhalb der für normale Monitore dar-
stellbaren Grenze liegt.
Der einzige Unterschied der Routine "ES-
COS2" zu "ESCOS1" bestand nun darin, daß
erstere Routine lediglich ein Zeilenoff-
set-Register für den ersten auszulösen-
den 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 Spri-
tes auf dem Bildschirm zu sehen waren.
Wie wir aber von den Moviescroll-
Routinen wissen, müssen wir für einen
Scrolltext das Aussehen einer Spritezei-
le ja individuell bestimmen können, da-
mit 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 Mo-
viescroller-Routinen vom IRQ-Kurs ein-
bauen, und schon haben wir einen Scroll-
text". 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 Zwi-
schenräume mehr zwischen den einzelnen
Spritezeilen haben, gestaltet es sich
als schwierig, die Sprite-Pointer zeit-
genau zu setzen. Setzen wir diese näm-
lich 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 Spritepoin-
ter auch nicht nach Abarbeiten der letz-
ten Spritezeile gesetzt werden da hier
ja schon die neuen Sprites erscheinen
müssen. Man könnte nun versuchen, ein
mega-genaues Timing zu programmieren,
das innerhalb der ersten Rasterzeile
einer neuen Spritezeile exakt vor Dar-
stellen eines neuen Sprites dessen Poin-
ter neu setzt. Dies gestaltet sich je-
doch umso umständlicher, wenn wir beach-
ten müssen, daß gleichzeitig auch noch
der linke und rechte Rand geöffnet wer-
den soll. Da dieser ebenfalls ein exak-
tes Timing verlangt, würden wir mit dem
Doppeltiming in des Teufels Küche kom-
men. 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 16KB zu ver-
schieben. 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 ins-
gesamt 16 verschiedene Speicherbereiche
gewählt werden, deren Basisadressen in
$0400-Schritten von $0000-$3C00 gehen.
Nun werden Sie fragen, was denn das Vi-
deo-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 normaler-
weise? Natürlich in den Adressen von
$07F8-$07FF. Und genau diese Adresse
liegen am Ende des Video-RAMs. Ver-
schiebt man nun das Video-RAM an eine
andere Adresse, so verschiebt man auto-
matisch auch die Registeradressen der
Spritepointer! Wird das Video-RAM also
beispielsweise um $0400 Bytes nach $0800
verschoben, so bewegen sich die Sprite-
pointer-Register ebenfalls um $0400-
Bytes nach vorne. Um nun also das Ausse-
hen der acht Sprites zu definieren, müs-
sen die Adressen $0BF8-$0BFF 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 Movies-
crollers ü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 Sprite-
pointer mit nur einem Schreibzugriff
umgeschaltet hätten!
Die Umsetzung dieser Lösung in die Pra-
xis 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 Beispiel-
programme 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 Sidebor-
ders in den folgenden 21 Rasterzeilen.
So sah der Aufruf in den beiden Bei-
spielprogrammen aus:
nop ;Diese Befehlsfolge wird
jsr open21; insgesamt 14x 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 umzu-
schalten. Er dient quasi als Parameter
für die "OPEN21"-Routine. Demnach sieht
der Aufruf dieser Routine, aus dem Ra-
ster-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 Aufga-
be soll von der Routine "OPEN21" durch-
geführt werden. Hier ein Auszug der er-
sten Zeilen dieser Routine aus dem Bei-
spiel "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 ei-
nen "STA $XXXX"-Befehl ebenfalls zu-
trifft. 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 Sprite-
pointer setzt. Es müssen nun noch zwei
weitere Änderungen gemacht werden, damit
der Movie-scroller auch voll funk-
tionstüchtig ist.
Zunächst einmal muß die Initialierungs-
routine erweitert werden. Sie soll uns
die Spritepointer der benutzten Video-
RAM-Adressen auf bestimmte Sprites vor-
initialisieren, so daß später zur Dar-
stellung einer bestimmten Spritereihe
nur noch die Nummer des zu benutzenden
Video-RAMs in den Labeln "V00" bis "V0D"
der Raster-IRQ-Routine (siehe oben) ein-
getragen werden muß, und die Routine
somit automatisch den richtigen Bild-
schirm zur korrekten Darstellung der
entsprechenden Spritezeile wählt.
Zunächst einmal wollen wir vereinbaren,
daß wir den 16K-Adressbereich des VICs
von $0000-$3FFF um eins nach oben in den
Bereich von $4000-$7FFF verschieben.
Dadurch stört uns die Zeropage, die nor-
malerweise ja auch im Bereich des VICs
liegt, nicht mehr, und wir haben volle
16KB 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 Initial-
sierung wurde also folgende Befehlsse-
quenz hinzugefügt:
LDA #$02
STA $DD00
Damit befindet sich der Datenbereich für
den VIC nun im Bereich von $4000-$7FFF.
Beachten Sie bitte auch, daß nun der
Bytewert, den der VIC in "ausgetrick-
sten" Rasterzeilen darstellt, nicht mehr
in $3FFF, sondern in $7FFF abgelegt wer-
den 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 fol-
gende 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 Adres-
sierungszeiger in der Zeropage initiali-
siert, und dann 14 Mal die Unterroutine
"SETPOINT" aufgerufen, wobei im Akku der
Inhalt für den jeweils ersten Sprite-
pointer ü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 je-
weils um eins hochgezählt und das Ergeb-
nis über den Vektor bei $02/$03 in die
entsprechende Sprite-Pointer-Adresse
geschrieben. Beim ersten Durchlauf zeigt
dieser Vektor auf Adresse $43F8, wo sich
die Spritepointer des ersten Video-RAM-
Bereichs befinden. Am Ende der Pointer-
initialisierung wird die Vektoradresse
nun um $0400 erhöht (auf Hi-Byte wird
$04 addiert), damit beim nächsten Durch-
lauf 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 vorinitia-
lisiert wurden, muß die "MOVESPR"-
Routine, die Sie bestimmt noch von unse-
ren alten Moviescroller-Routinen her
kennen, so modifiziert werden, daß sie
die IRQ-Routine derart ändert, daß in
jeder Spritezeile auch der richtige Vi-
deo-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 eigent-
liche, für uns wichtige Teil der Routine
befindet sich am Ende und heißt "ROL-
LON". Er wurde der ESCOS-Routine ange-
passt 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 #$0d ; 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 Abarbei-
tung gelangt sie automatisch zum Teil
"ROLLON", zu dem auch am Anfang verz-
weigt 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 Raster-
sta $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+$0D,y;Wert f. 14.SprZeile
sta v0D,+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 Zeropage-
adressen $F8 und $F9 stehen. "SOFTROLL"
ist dabei ein Zähler für den Scrolloff-
set, 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äh-
ler einen Unterlauf meldet, und wieder
auf 21 zurückgesetzt wird. Gleichzeitig
muß dann die soeben weggescrollte Spri-
tezeile am unteren Bildschirmrand, mit
neuem Text wiedereingefügt werden, was
vom Abschnitt "NEWLINE" schon durch-
gefü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 hoch-
gezählt, da nun ja die ehemals zweite
Spritezeile zur Ersten wird. Damit tut
unser neuer "ROLLON"-Teil nichts ande-
res, als für jede Spritezeile einen Wert
aus der Tabelle zu lesen und in eins der
Labels von "V00" bis "V0D" 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 Label-
adressen 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 einzu-
schalten. 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 Rastereffek-
te miteinander kombiniert, ohne große
Änderungen vornehmen zu müssen. Das Er-
gebnis ist quasi ein Moviescroller mit
ESCOS-IRQ-Routine. Auf diese Weise ist
es auch möglich ganz andere Effekte mit-
einander zu kombinieren, was Ihre Phan-
tasie 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 wer-
den. 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 versproche-
nen, 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 an-
schauen, und dabei lernen, daß es auch
Sprites gibt, die mehr als 21 Rasterzei-
len hoch sind...
1) DAS PRINZIP
Wie immer kommen wir zu Beginn zum Funk-
tionsprinzip des Rastereffektes dieses
Kursteils: Wie Sie vielleicht wissen, so
ist die Größe eines Sprites prinzipiell
auf 24x21 Pixel begrenzt. Diese Größe
ist starr und eigentlich nicht veränder-
bar. 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 $D01D die X- und
Y-Expansion eines Sprites einschalten.
In diesem Fall kann sowohl die X- als
auch die Y-Ausdehnung verdoppelt werden,
so daß wir ein Sprite mit einer maxima-
len Größe von 48x42 Pixeln erhalten, in
dem die normalen 24x21 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.
Dreh- und Angelpunkt dieses Effektes
wird das eben schon angesprochene Regi-
ster 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 expan-
diert. Der VIC vergleicht nun jede Ra-
sterzeilennummer 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 Schaltlo-
gik 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 relevan-
ten 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 Schreibzu-
griff 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 Spri-
tedaten für die erste Rasterzeile.
Gleichzeitig wird bei Erreichen der
nächsten Rasterzeile der 1-Bit-Zähler um
eins erniedrigt. Tritt dabei ein Unter-
lauf auf, so reinitialisiert die Schalt-
logik den Zähler wieder mit dem Inhalt
des Latch-Registers und erhöht die Quel-
ladresse für die Speitedaten um 3 Bytes,
so daß Sie anschließend dem VIC die Da-
ten der nächsten Spritezeile zukom-
menlässt. Tritt kein Unterlauf auf, weil
der Zähler auf 1 stand und beim Herun-
terzählen auf 0 sprang, so bleibt die
Quelladresse der Spritedaten unverän-
dert, so daß der VIC auch in dieser Ra-
sterzeile dieselben Spritedaten liest,
die er auch eine Rasterzeile vorher
schon darstellte. Ist die Spriteexpan-
sion 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 Raster-
zeile eine neue Adresse an den Datenbus
angelegt, womit der VIC das Sprite in
der Vertikalen doppelt so hoch dar-
stellt.
Wie nun eingangs schon erwähnt so werden
sowohl Latch als auch Zähler neu initia-
lisiert, 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 Spri-
tes bewirken (indem jede Spritezeile 3-,
4- oder 5-mal hintereinander dargestellt
wird)! Wir haben sogar die Möglichkeit
jede Spritezeile beliebig, und voneinan-
der 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 Ra-
sterzeile 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 Spri-
tes, um die Daten der nächsten Sprite-
zeile in den VIC zu holen!
2) PROGRAMMBEISPIELE 1 UND 2
Um einen Eindruck von den Möglichkeiten
zu bekommen, die uns dieser Effekt bie-
tet, sollten Sie sich einmal die Bei-
spielprogramme "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" gestar-
tet. Die ersten beiden Beispiele stellen
ein Sprite dar, das normalerweise nur
eine diagonale Line von der linken obe-
ren Ecke zur rechten unteren Ecke des
Sprites enthält. Im ersten Beispiel ha-
ben wir lediglich einige dieser Zeilen
mehrfach dargestellt. Das zweite Bei-
spiel enthält eine Streckungstabelle,
mit der wir jede Rasterzeile in Folge
1-, 2-, 3-, 4-, 5-, und 6-Mal darstel-
len, womit die Linie in etwa die Rundun-
gen einer Sinuskurve bekommt!
Wollen wir uns nun einmal den Programm-
code 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 iden-
tisch mit unseren anderen IRQ-Beispielen
ist. Wir legen hier den Raster-IRQ auf
Rasterzeile $82 fest und schalten Be-
triebssystem-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 altbe-
kannter Trick zum Glätten des IRQs auf-
gefü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 Routi-
nenteile 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 Spri-
te-Stretch, FLD- und Sideborderroutinen.
Hierzu schreiben wir erst einmal die
Anzahl der Rasterzeilen, in denen diese
drei Effekte aktiv sein sollen als Ver-
gleichszähler in Adresse $02 und löschen
das X-Register, das als Index auf die
FLD- und 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 öffne-
ten. Da wir später jedoch wieder Raster-
zeile $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 syn-
chronisieren zwischen Programm und Ra-
sterstrahl notwendig ist, durcheinan-
derbrächte und wir dadurch andere Befeh-
le zum Verzögern einfügen müssten, und
zum Anderen um die Flexibilität der Rou-
tine dadurch nicht einzuschränken. Auf
diese Weise wird es z.B. für Sie sehr
einfach, die Routine mit einer Top- und
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 Rou-
tine verlagern. Dies ist übrigens eine
saubere Möglichkeit timing-kritische
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 befin-
det 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 ge-
lesen und angezeigt, bei $01 wird immer
wieder die zuletzt dargestellte Sprite-
zeile auf den Bildschirm gebracht. Fol-
gen mehrere $01-Werte aufeinander, so
wird eine einzige Spritezeile so oft
wiederholt, wie $01-Werte in der Tabelle
stehen. Um die nächste Spritezeile dar-
zustellen 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 ver-
schieden oft. Hiernach wird die letzte
Spritezeile so oft wiederholt, bis das
Ende unseres, durch FLD- und 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 Re-
gister $D011 übertragen. Diese Befehls-
folge ist für den FLD-Effekt notwendig,
mit dem wir den Beginn der nächsten Cha-
rakterzeile vor dem Rasterstrahl her-
schieben. Die Tabelle enthält immer wie-
der die Werte $19, $1A, $1B, $1C, $1D,
$1E, $1F, $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
Runter- und wieder Hochschalten zwischen
38- und 40-Spalten-Darstellung.
Durch die nun folgenden 9 NOP-Befehle
verzögern wir solange, bis der Raster-
strahl 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 die-
se Zeile gefüttert wurde. Jetzt schalten
wir die Y-Expansion wiederum ganz ab, um
das Register für den nächsten Wert vor-
zubereiten. 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 ha-
ben), 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ön-
nen 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 ge-
wohnt beenden.
Damit hätten wir auch schon den Kern
unserer Routine besprochen. Experimen-
tieren Sie doch ein wenig mit der Ver-
zerrung, indem Sie die Tabelle "AD017"
ab Adresse $1600 mit Hilfe eines Spei-
chermoditors abändern. Sie werden sehen
welch lustige Deformierungen dabei ent-
stehen 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 Ta-
belle "AD017" verändert, so daß dasselbe
Sprite ein anderes Aussehen erlangt.
Technisch gesehen wurde dieses Beispiel
jedoch um ein zusätzliches Feature er-
weitert: 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 Unterrou-
tine, 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 an-
geben, die eine jede der 21 Spritezeilen
wiederholt werden soll. Die Tabelle
liegt ab Adresse $1700 und sieht folgen-
dermaß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 Ta-
belle "AD017" ablegen, so daß wir ledi-
glich 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 1x 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 ledi-
glich die AD017-Tabelle so oft mit $01-
Werten, wie der Bytewert einer Sprite-
zeile aus LSTRETCH groß ist. Auf diese
Weise können wir die Verzerrung einfa-
cher 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 bei-
den Programmen legten, auf. "STRET-
CHER.3" ist ein Programm, in dem Sie
einen kleinen Flugsaurier auf dem Bild-
schirm flattern sehen. Diesen können Sie
nun mit Hilfe eines Joysticks in Port 2
nach links und rechts über den Bild-
schirm 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 Joy-
stick 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 Joystickrouti-
ne direkt in die AD017-Tabelle übertra-
gen, womit ein quasi stufenloser Dehn-
und Staucheffekt entsteht. Hierbei ist
es übrigens wichtig, die richtige Spri-
tezeile für eine Vergrößerung zu wählen.
In der Regel nimmt man dazu ein iterati-
ves Verfahren, das die Anzahl der Spri-
tezeilen, die eine Rasterzeile mehr dar-
gestellt werden als Andere, so gleichmä-
ßig verteilt, daß der Dehneffekt beson-
ders 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 klei-
nen Flugsaurier auf dem Bildschirm, der
in Sinuswellenform quasi über den Bild-
schirm "schwabbelt". Hierbei haben wir
uns eines einfachen Tricks bedient: Wir
nahmen zunächst die LSTRETCH-Tabelle aus
"STRETCHER.2", die eine Art runde Ver-
zerrung in Vertikalrichtung erzeugt.
Diese Tabelle wird nun zyklisch durchge-
rollt. 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 Re-
flektion auf einer gewellten Wassero-
berfläche sichtbar sein!
Diese einfache Variation zeigt, mit
welch simplen Methoden man eindrucksvol-
le Effekte durch Spritedehnung erzielen
kann. Sie soll Sie wieder anregen, eige-
ne 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 die-
sem und den nächsten beiden (letzten)
Kursteilen werden wir uns mit Raster-
Tricks beschäftigen, die es uns ermögli-
chen, 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 Scollzei-
len/-spalten den gesamten Bildschirm um
8 Pixel (oder einen Charakter) weiterko-
pieren, 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 verti-
kalen Scrollen, "HSP", zum horizontalen
Scrollen, sowie "AGSP", die eine Kombi-
nation Kombination aus den beiden erste-
ren dartsellt. Mit ihnen können wir den
gesamten Bildschirm ganz problemlos
(auch Hires-Grafiken!) in alle Richtun-
gen 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" Rou-
tinen, 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 kom-
pexeren Rastertricks, und leistete uns
auch schon manchen guten Dienst um Ti-
mingprobleme 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 iden-
tisch sind. "VSP" steht für "Vertical
Screen Position", was der einfach Aus-
druck für das ist, was die Routine be-
wirkt: 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 funk-
tionstüchtige VSP-Routine, die gleich-
zeitig noch einen FLD-Effekt miteinge-
baut 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 Be-
ginn 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äch-
sten 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 richti-
gen Rasterzeile zu befinden, in der er
die nächste Charakterzeile zu lesen hat-
te, "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 durf-
te. 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 konn-
te man auch problemlos den Bildschirm
erst in der Mitte oder am unteren Bild-
schirmrand beginnen lassen (so arbeitet
übrigens auch der Effekt, mit dem Sie
die Seiten, die Sie gerade lesen umblät-
tern).
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 iden-
tisch 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 Bild-
randes 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 Soft-
scroll-Werte für Register $D011 (nur die
untersten 3 Bit sind genutzt), die die
vertikale Verschiebung immer um eine
Rasterzeile vor dem Rasterstrahl her-
schieben, so daß der FLD-Effekt entste-
hen 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" neben-
bei 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 Gra-
fikchip 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 recht-
zeitig 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 sei-
nen 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 ei-
nen einzigen NOP mehr, der hinter der
IRQ-Glättung eingefügt wurde. Das bedeu-
tet, daß der FLD-Schreibzugriff auf Re-
gister $D011, mit dem der vertikale Ver-
schiebeoffset 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 Charak-
terzeile) erhöht.
* Da der VIC beim Erreichen der nächsten
Rasterzeile wieder glaubt, er müsse
sich auf die Charakterzeile vorberei-
ten, zählt er den internen Adresszei-
ger 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 Er-
reichen der ersten Charakterzeile ein-
setzen 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 er-
ste Charakterzeile wurde somit ÖBER-
SPRUNGEN!
Im Klartext bedeutet das: für jede Ra-
sterzeile, 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 An-
fang des Bildschirms anzeigen lassen.
Der VIC liest nun aber pro Rasterdurch-
lauf 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 $07E8 bis
$07FF dar (wenn wir von der Stanard-
Basisadresse des Video-RAMs bei $0400
ausgehen). Dies sind 24 Zeichen, die
somit am Anfang der letzten Bildschirm-
zeile 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 1KB) 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 16KB der 64KB des
C64 adresssieren, wobei die Lage dieses
Bereichs allerdings auch verschoben wer-
den kann. Das heißt also, daß der VIC
insgesamt zwar Adressen in einem 64KB-
Raum (16 Adressbits sind hierzu notwen-
dig) adressieren, aber gleichzeitig im-
mer nur auf 16KB für Grafikdaten zugrei-
fen kann. Möchte man diesen 16KB-Bereich
verschieben, so kann das mit den unter-
sten zwei Bits von Adresse $DD00 getan
werden. Im Normalfall stehen beide auf 0
und lassen den VIC deshalb im Bereich
von $0000 bis $3FFF 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) erschei-
nen. Die Bits 10-13 dieser Adresse be-
stimmen 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 Ba-
sisadresse für das Video-RAM ist. Die
restlichen, zur Adressierung benötigten
10 Bits kommen aus dem oben schon
erwähnten, VIC-internen Adresszähler. Er
ist exakt 10 Bits lang, womit maximal
1KB-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 unge-
nutzten Zeichen des Video-RAMs dar-
stellt: Da der 10-Bit-Zähler nun auf den
Offset $03E8 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 $07E8-$07FF 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 Cha-
rakterzeilen-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 Charakter-
verschiebung entstehen, sollten auch
nicht unerwähnt bleiben:
Durch die Verschiebung der Video-RAM-
Daten am Ende des Bildschirms um 24 Zei-
chen 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 Zei-
chen ungenutzt. Durch einen stinknorma-
len Rasterinterrupt können wir dann pro-
blemlos vor Beginn der Charakterzeile,
an der der VIC-Adresszeiger überläuft,
auf das zweite Video-RAM umschalten,
wobei dieses dann zwar an der überlau-
fenden Adresse ausgelesen wird, was je-
doch durch unsere Verschiebung wieder
ausgeglichen wird.
Ein weiterer Nachteil, der bei VSP ent-
steht, 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 Anzei-
gen von Grafikzeichen in diesen acht
Bytes mit der Spriteanzeige Probleme
bekomen. Jedoch auch dies ist zu bewäl-
tigen. Entweder, indem man so geschickt
arbeitet, daß in diesen Bereichen auf
dem Bildschirm nur Zeichen in der Hin-
tergrundfarbe zu sehen sind (bei schwar-
zem 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 an-
sonsten die Spritepointer hineinschrei-
ben. Da der VIC schon 42 Taktzyklen nach
Beginn der Rasterzeile, in der die Cha-
rakterzeile beginnen soll, die Video-
RAM-Daten gelesen hat, können wir also
getrost wieder die Spritepointer zurück-
schreiben und haben so lediglich eine
einzige Rasterzeile, in der die Sprites
garnicht (oder nur verstümmelt) darge-
stellt 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 unter-
bracht werden muß. Da pro Rasterzeile
der interne Adresspointer um eine Cha-
rakterzeile weitergezählt wird, muß also
im Maximalfall in 25 Rasterzeilen der
VSP-Effekt eingesetzt werden. Will man
den Bildschirm nun konstant an eine be-
stimmten Position beginnen lassen, so
muß nach der Anzahl der zu überspringen-
den Charakterzeilen wieder eine normale
FLD-Routine (ohne das zusätzlich NOP)
benutzt werden, um bis zur 25. Raster-
zeile nach Bildschirmanfang zu verzö-
gern, OHNE daß gleich alle 25 Charakter-
zeilen ü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 unte-
ren drei Bits von $D011 zum Soft-
scrollen 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 durch-
gefü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 Hardwa-
rescroller "HSP" anschauen, bei dem das
VSP-Prinzip auf die Horizontale Bild-
schirmposition 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 ge-
lernt, wie einfach es ist, den Bild-
schirm des C64 HARDWAREMÄSSIG, also ohne
den Prozessor mit großen Bildschirm-
Verschiebe-Aktionen zu belasten, nach
oben und unten zu scrollen. Dabei unter-
schied 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 ver-
gaß, weswegen wir in der Lage waren,
einzelne Bereiche im Video-RAM zu über-
springen, 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 Pi-
xel nach rechts verschieben, wobei wir
den Prozessor nur läppische 3 Rasterzei-
len lang in Anspruch nehmen müssen. Da-
mit werden schnell scrollende Baller-
oder Jump'n Run Spiele, wie auf dem Ami-
ga 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 Lesevor-
gang durchführen wollte machten wir ihn
durch FLD-Wegdrücken der Charakterzeilen
glauben, daß er doch noch nicht die ent-
sprechende 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 anzu-
zeigen. Durch den FLD-Effekt haben wir
nun schon oft genung die erste Charak-
terzeile auf dem Bildschim vor ihm her-
geschoben, so daß der VIC diese Zeile
verspätet erreichte, und somit der Bild-
schirm 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 Zei-
chendaten zu Lesen und Anzuzeigen hat,
beginnt er auch unverzüglich mit dieser
Arbeit, und das ohne noch auf die hori-
zontale 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 Raster-
zeile vorschreibt, daß selbige noch kei-
ne Charakterzeile ist, und dann mitten
innerhalb dieser Zeile von uns wieder
auf normale Darstellung zurückgeschaltet
wird, so fängt der VIC auch prompt mit-
tendrin 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 Zei-
chen, die ja nicht mehr in diese Text-
zeile passen, automatisch erst in der
nächsten Zeile erscheinen. Es kommt so-
gar noch besser: aufgrund eines internen
Timers des VIC, der das Lesen der Cha-
rakterzeilen mitbeeinflusst (es sei den
wir tricksen ihn aus) führt der VIC den
Lesefehler in JEDER WEITEREN Charakter-
zeile 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 Ra-
sterzeile 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 schal-
ten wir dann die horizontale Verschie-
bung mittendrin wieder auf 0 zurück, so
merkt der VIC plötzlich, daß er sich
doch schon in einer Charakterzeile be-
findet und fängt einfrig damit an die
Charakterdaten zu lesen und auf dem
Bildschirm darzustellen. Wohlgemerkt
obwohl er sich schon nicht mehr am Zei-
lenanfang befindet, sondern mitten in-
nerhalb dieser Rasterzeile! Für jeden
Taktzyklus, den wir mehr verzögern,
stellt der VIC die Charakterdaten um
jeweils ein Zeichen (also 8 Pixel) wei-
ter 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 ge-
macht 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 Ini-
tialisierungsroutine 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 Bild-
schirmrand abschaltet. Sie steht ab
Adresse $1200 im Speicher und ist ge-
folgt 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 Adres-
se in den IRQ-Vektoren bei $FFFE/$FFFF
ab, und gibt dem VIC vor, den nächsten
Raster-IRQ in Rasterzeile $2D auszulö-
sen.
Wird unsere HSP-Routine nun in dieser
Rasterzeile aufgerufen, so glätten wir
zunächst den IRQ, nach der uns mittler-
weile 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 #$0e ;Bildschirmfarben set-
sta $d020 ; zen (innerhalb dieser
lda #$06 ; Befehle wird die Cha-
sta $d021 ; rakterz. gelesen!)
Zu Beginn unserer IRQ-Routine wird
zunächst also eine vertikale Verschie-
bung um eine Rasterzeile in $D011 einge-
tragen (Bits 0-2 enthalten den Wert
%001=$01). Dadurch, daß der HSP-IRQ in
Rasterzeile $2D aufgerufen wurde, und
daß die IRQ-Glättung 2 Rasterzeilen ver-
braucht, befinden wir uns nun also in
Rasterzeile $2F. 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öge-
rung. Er initialisirt den Akku jedoch
gleichzeitig schon mit dem Wert vor, den
wir in $D011 schreiben müssen, um die
vertikale Verschiebung wieder abzuschal-
ten (die unstersten drei Bits enthalten
den Wert 0). Nun folgt wieder eine sehr
ausgeklügelte Verzögerung, um Taktgenau
eine ganz bestimmte Rasterposition abzu-
passen. Beachten Sie bitte, daß die Rou-
tine an dieser Stelle von der oben schon
erwähnten "Control"-Routine modifiziert
wird, um die jeweils gewünschte Bild-
schirmverschiebung 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öge-
rung den VIC zum Lesen der Charakterzei-
le 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 Taktzy-
klus lang zu verzögern. Ein Branch-
Befehl benötigt mindestens zwei Takte um
ausgeführt zu werden. Trifft die abge-
fragte 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 Taktzy-
klus 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 ver-
braucht. Soll eine ungerade Anzahl
verzögert werden, so wird von der Routi-
ne "Control", im Label "REDU1" der Opco-
de für einen BNE-Befehl eingetragen,
womit die Routine einen Taktzyklus län-
ger 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öge-
rung). Bei diesem Befehl wird von "Con-
trol" 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. Ei-
ner dieser Befehle verzögert dann immer
um 2 Taktzyklen. Hierzu sollten wir ei-
nen Blick auf die Routine "Contol" wer-
fen:
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 eben-
falls während des Border-IRQs aufgerufen
wird, den Joystickbewegungen entspre-
chend hoch oder runter gezählt wird.
Dieser Zähler kann also einen Wert zwi-
schen 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 Bild-
schirms auch noch den horizontalen Ver-
schiebeoffset 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 Op-
code für den BEQ-Befehl in "REDU1" ein-
getragen. Damit wird in der HSP-Routine
um 2+3=5 (also eine ungerade Anzahl)
Taktzyklen verzögert. Nun muß noch er-
mittelt 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 divi-
diert 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 ak-
tuellen 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 ausmas-
kiert werden. Da diese Bits im Register
$D016 landen müssen, das auch für die
38/40-Zeichendarstellung und den Multi-
colormodus 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 Verschie-
beoffset 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 experi-
mentieren 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 er-
staunlichste IRQ-Raster-Routine kennen-
lernen 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 scrol-
len, 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 Rasterstrahlprogrammie-
rung. 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 Vertika-
len (VSP-Routine) ermöglichten. Der Clou
an diesen beiden Routinen war, daß des
GESAMTE Bildschirm vom VIC verschoben
wurde, und wir keinen einzigen Taktzy-
klus zum Kopieren von Bildschirmdaten
verschwenden mussten. In diesem Kursteil
wollen wir nun das "Sahnestückchen" der
Raster-IRQ-Programmierung besprechen:
die Kombinattion aus HSP- und 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 Zy-
klus zum Kopieren von Bildschirmdaten zu
verschwenden!
Stellen Sie sich vor, was für Möglich-
keiten sich hierdurch für Spiele erge-
ben: so kann man z.B. problemlos Spiele
programmieren, die sich über einer be-
liebig großen Oberfläche abspielen, die
in alle Richtungen flüssig gescrollt
werden kann, ohne dabei mehr als 30 Ra-
sterzeilen pro Frame zu verbrauchen. 25
Rasterzeilen lang ist der Prozessor ei-
gentlich nur mit dem Abpassen der opti-
malen Rasterstrahlpostion für den HSP-
Effekt beschäftigt, der, wie wir schon
im 14. Kursteil sehen konnten, pro Cha-
rakterzeile, die der Bildschirm horizon-
tal gescrollt werden soll, eine Raster-
zeile 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 demon-
striert werden. Sie sehen hier einige
Hiresgrafiken, die in Sinuswellenform
auf- und abschwingen und gleichzeitig
von links nach rechts scrollen. Hierbei
werden jeweils neue Grafiken in die Gra-
fik einkopiert, so daß der Eindruck ent-
steht, 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 Charakter-
zeile gelesen werden muß. Hier nun las-
sen 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 Bild-
schirm nach rechts versetzt darzustel-
len. Die Anzahl Taktzyklen, die bis zum
Zurückschalten verzögert werden müssen,
muß dabei der Anzahl Zeichen entspre-
chen, die der Bildschirm verschoben wer-
den 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 Charakter-
zeilen 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 Zei-
le 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 unse-
rem Programmbeispiel den VSP- und HSP-
Routinen vorgeschaltet. Sie sorgt also
gleichzeitig für immer gleiches Timing
und zudem für die richtige Positionie-
rung in der Horzontalen. Aus diesem
Grund sind dann die ersten 25 Rasterzei-
len des Bildschirms auch nicht zum Dar-
stellen 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 Joy-
stickabfrage und Scrollgeschwindigkeits-
verzögerung sollen hier nicht Thema un-
seres Kurses sein, sondern lediglich die
Routine, die uns den Bildschirm beliebig
im sichtbaren Fenster positioniert. Zur
angesprochenen Scrollgeschwindigkeits-
verzögerung sei erwähnt, daß wir hier
beim Drücken und Loslassen des Joysticks
die Scrollrichtung verzögern, bzw. nach-
laufen lassen, so daß der Scrolleffekt
zunächst träge anfängt und beschleunigt
und beim Loslassen erst abgebremst wer-
den 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) schal-
tet nun den oberen und unteren Bild-
schirmrand 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 wer-
den. 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 ein-
setzen
* HSP-Routine zum charakterweisen links-
und rechtsscrollen des Bildschirms ein-
setzen
* horizontales und vertikales Softscrol-
ling 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 $0C00-
$1000 zu finden sein sollen (Sprite-
Pointer $30-$3f). Um die Pointer zwi-
schen beiden leichter umschalten zu kön-
nen 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 ver-
schoben, so daß wir die Spritepointer in
den Adressen $03F8-$03FF liegen haben.
Für die zweite Spritezeile wird das Vi-
deo-RAM an die (normale) Adresse $0400
gelegt, womit wir die Spritepointer an
den Adressen $07F8-$07FF vorfinden. Da-
durch, daß durch den VSP-Effekt auch
diese Spritepointer als einzelne Zeichen
auf dem Bildschirm auftauchen können,
müssen wir die Pointer in jedem Raster-
durchlauf (Frame) wieder neu setzen, da
die Pointer auf dem Bildschirm ja dur-
chaus 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 ein-
sta $d015 ; schalten
lda #$80 ;Hi-Bit d. X-Pos. von
sta $d010 ; Sprite 8 setzen
lda #$00 ;Sprite-Multicolor
sta $d01c ; 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 wichtig-
sten Spritedaten initialisiert. Dadurch,
daß diese in jedem Frame neu gesetzt
werden, können Sie im AGSP-Bereich pro-
blemlos 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 ein-
kopieren. Nun folgt noch die Initiali-
sierung 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 $03f8-$03ff befin-
den. Anschließend werden die Spritepoin-
ter für die zweite Spritereihe eingetra-
gen, die sich im normalen Bildschirm-
speicher bei $0400, in den Adressen
$07f8-$07ff befinden. Das abschließende
schreiben des Wertes 3 in Register $DD00
stellt sicher, daß der 16K-Adressbereich
des VICs auf den unteren Bereich, von
$0000 bis $3FFF, 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-$3FFF schalt.
b) DAS GLÄTTEN DES IRQS
Der nun folgende Teil der AGSP-Routine
ist für das Glätten des IRQs verantwort-
lich. Ausserdem wird hier der Border-IRQ
wieder als nächste IRQ-Quelle festge-
legt, womit diese Arbeit nun auch erle-
digt 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. Die-
ser 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 Takt-
cmp #$1d ;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 Spri-
tes für die zweite Spritezeile des Sco-
reboards (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 Rah-
sta $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 wichti-
gen Aufgaben folgt nun die FLD-Routine,
die das Timing zur VSP-Routine ausglei-
chen 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 Cha-
rakterzeile durch die horizonale Ver-
schiebung in $D011 der Charakterzeile-
nanfang vor dem Rasterstrahl her-
gedrückt. Dies bewähltigen wir mit Hilfe
einer Liste namens "Field1". Sie wurde
im Hauptprogramm der AGSP-Routine vori-
nitialisiert und befindet sich an Adres-
se $0100. Es stehen dort Werte für
$D011, die die zur normalen Bildschirm-
darstellung notwenidigen Bits gesetzt
haben, und deren Bits für der horizonta-
le Bitverschiebung jeweils um eine Ra-
sterzeile 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 (al-
so auch jedem FLD-Durchlauf) in das Re-
gister $D018, womit wir den Adressbe-
reich des Video-RAMs verschieben. Die
Tabelle enthält 20 Einträge, die das
Video-RAM an Adresse $0000 legen, ge-
folgt von 44 Einträgen, die es an Adres-
se $0400 verschieben. Auf diese Weise
schaltet Sie also exakt in der 21. Ra-
sterzeile nach Beginn der FLD-Routine
auf den Bildschirm bei $0400 um, womit
wir die Spritepointer auch auf diesen
Bereich umschalten. Nach dieser 21. Ra-
sterzeile 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 Poin-
tern zu zeichnen. Der Grund, warum dies
über eine Tabelle geschehen muß, und
nicht etwa durch Abpassen der entspre-
chenden 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 Raster-
zeilen lang laufen muß, damit der Bild-
schirm 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 Umschaltung-
saufgabe, 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 FLD- oder schon in der VSP-Routine
befindet!
d) DIE VSP-ROUTINE
Nach FLD folgt die VSP-Routine, die sich
von der ersteren nur darin unterschei-
det, 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 VIC-
internen Adresszeiger selbiger erhöht
und somit eine Charakterzeile über-
springt:
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 aus-
gelesen und geschreiben werden. Um den
VSP-Effekt zu erzielen kann dies hier
nicht mehr direkt aufeinanderfolgen.
Ausserdem wird hier nicht mehr der näch-
ste Tabellenwert von "Field1" gelesen,
sondern der dritte Wert danach. Dies tun
wir, um in jedem Fall eine $D011-Wert zu
schreiben, der die Charakterzeile minde-
stens 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 (wie-
viele Rasterzeilen wir die Charakterzei-
le 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 geschriebe-
nen $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 Cha-
rakterzeilenbeginn 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 Aus-
gleichen eines ggf. ungeraden Verzöge-
rungszeitraums. Das Label "tt" wird von
der Timingroutine verändert um so ver-
schiedene 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 Horizona-
len 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" einge-
tragen:
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. Aller-
dings muß sie auch irgendwie mit Basis-
werten gefüttert werden, um das Timing
für alle Fälle genau aufeinander anzu-
stimmen. Dies übernehmen weitere Steuer-
routinen, die wir im nächsten, und dann
auch letzten Teil dieses Kurses bespre-
chen werden.
(ub)
IRQ-KURS
"Die Hardware ausgetrickst..."
(Teil 17)
----------------------------------------
Herzlich Willkommen zum 17. und letzten
Teil unseres IRQ-Kurses. In dieser Aus-
gabe möchten wir Ihre Ausbildung ab-
schließen und Ihnen die letzten Geheim-
nisse der AGSP-Routine erläutern, mit
deren Besprechung wir ja schon im letz-
ten 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 Bild-
schirms. Zudem hatten wir noch einen
einfachen Sprite-Multiplexer mit inte-
griert. 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 belie-
bigen Offset in X- und Y-Richtung zu
verschieben (in X-Richtung um 0-319, in
Y-Richtung um 0-255 Pixel). Damit wir
jedoch eine SPEZIELLE Verschiebung um-
setzen können, muß die AGSP-Routine
natürlich auch mit speziellen Parametern
gefüttert werden, die von einer anderen
Routine in der Hauptschleife des Prozes-
sors 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, wel-
che 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 Zero-
pageadressen oder sogar direkt im Code
der IRQ-Routine eingetragen werden. Ins-
gesamt 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 durch-
zuführen. Die Verschiebungen in X- und
Y-Richtungen werden wir im folenden mit
XPos und YPos bezeichnen. Unsere Bei-
spielprogramme verwalten auch gleichna-
mige Variablen in der Zeropage. Sie wer-
den 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 auf-
nehmen muß, benötigen wir für den Wert
XPos also zwei Speicherstellen, die von
unserem Beispielprogramm in den Zeropa-
geadressen $02/$03 in Lo/Hi-Byte-
Darstellung verwaltet werden. Für YPos
genügt uns ein Byte, das in der Zeropa-
geadresse $04 zu finden ist.
Wollen wir nun jedoch zunächst heraus-
stellen, 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 durchzu-
laufen 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 vor-
initialisiert wird. Dieser Wert ergibt
sich aus der Anzahl Rasterzeilen, die
für das VSP-Timing benötigt werden. Nor-
malerweise sollten dies 25 Rasterzeilen
sein. Da wir jedoch ein Spritescoreboard
mit einer Gesamthöhe von 42 (=$2a) 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, solan-
ge, bis er dem Wert in FLDCNT ent-
spricht, womit die FLD-Schleife verlas-
sen 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 be-
rechnet 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 kom-
men. Dies sind keine Parameter im ei-
gentlichen Sinne, sondern Änderungen im
Code der HSP-Routine, die eine Verzöge-
rung 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 Taktzy-
klen verzögern müssen, um die gewünschte
Verschiebung zu erzielen. Auch hier zei-
ge ich Ihnen wieder einen Auszug aus der
HSP-Routine, der der besseren Erläute-
rung 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 vorzugau-
keln, er befände sich noch nicht in ei-
ner Rasterzeile. Anschließend wird mit
dem JSR-, dem DEX-, und den BEQ-/BNE-
Befehlen auf den Anfang des linken Bild-
schirmrandes gewartet, wobei die letzen
beiden Befehle ausschließlich für das
taktgenaue HSP-Timing herangezogen wer-
den. 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 ex-
tremfall alle Zeichen übersprungen wer-
den können. Der anschließende STX-Befehl
setzt dann die Darstellung wieder auf
Charakterzeilenbeginn, womit der VIC
dazu gezwungen wird, sofort eine Charak-
terzeile 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 um-
zusetzen 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 Bedin-
gung 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 ver-
braucht 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. Da-
durch 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 unse-
rem 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 umge-
setzt. Beachten Sie nur bitte noch fol-
genden 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 Parameterum-
rechnungsroutine 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 Parameterberech-
nungen, die Raster-IRQ-Effekte betref-
fend, abgehandelt. Da HSP- und VSP-
Routine den Bildschirm jedoch immer nur
in 8-Pixel-Schritten verschieben, müssen
wir diesen nun noch mit den ganz norma-
len 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 je-
weils 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 le-
diglich das Softscrolling des Bild-
schirms zu setzen, müssen auch zur kor-
rekten Bildschirmdarstellung notwendige
Bits in diesen Registern mitgesetzt wer-
den. Auch dies wird unsere Parameterbe-
rechnungsroutine übernehmen.
2) DIE AGSP-PARAMETER-ROUTINE "CONTROLL"
Kommen wir nun endlich zu der Unterrou-
tine, die die benötigten Parameter in
umgerechneter Form in die AGSP-Routine
einbettet, und letztere zu einer korrek-
ten Anzeige des Bildschirms bewegt.
Nachdem wir die Funktionprinzipien der
Parameterübergabe nun ausführlich be-
sprochen haben, besteht die Controll-
Routine nur noch aus ein paar "Reche-
naufgaben". 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 Bor-
der-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 Dreh- und Angel-
punkt 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ück-
sichtigt 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
Hires- und 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 Grafik-
darstellung 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 Low- und Highbyte in den
Labels AFA und AFB an, die den Zeropage-
adressen $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 korri-
giert 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 Aufga- ben erfüllt, müssen wir in
Ihm ebenfalls noch ein Bit seten, näm-
lich 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) ausspre-
chen, ohne dessen unermüdlichen Bemühun-
gen um extaktes Rastertiming dieser Kurs
bestimmt nicht zustande gekommen wäre.
(ih/ub