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 !