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