Assembler für Einsteiger - Teil 5 ----------------------------------- von Tim-Philipp Müller Bevor wir wiedermal zur Tat schreiten, erst ein paar neue Assembler-Befehle: PLA - PulL Akku - Hole Akku vom Stack PHA - PusH Akku - Bringe Akku in Stack So, nun sind wir alle wieder schlauer. Für viele wird der Begriff 'STACK' wohl noch etwas ungewohnt sein. Im Grunde ist 'Stack' nur das englische Wort für 'Stapel' und ist auch nicht mehr als ein 256 Byte großer Stapel ($100-$1FF). Er funktioniert eigentlich wie ein Bücherstapel: Was man als letztes oben drauf gelegt hat, holt man auch als erstes wieder herunter. Haben Sie sich schon einmal gefragt, wo sich der Compy bei einem JSR-Befehl merkt, wohin er bei einem RTS zurück- kehren muß? Jetzt wissen Sie es! Er bringt die Adresse auf den Stapel, und zwar erst das HI-Byte der ADRESSE-1 (!!!), dann das LO-Byte der ADRESSE-1. Warum die Adresse-1? Das liegt daran, daß der Prozessor die vom Stack geholte Adresse automatisch um ein Byte erhöht. Nun kann man den Stack natürlich auch benutzen, um selbst ein paar Werte ohne viel Aufwand zwischenzuspeichern. WICHTIG! Dies muß immer auf der selben 'Programm-Ebene' liegen! FALSCH: HALLO LDA #10 ------- PHA JSR SUB JMP HALLO SUB LDA #32 ... PLA ... RTS Warum? Ganz einfach, weil nach dem JSR zwei weitere Bytes auf den Stack geschoben werden. Der PLA im Unterprogramm würde jetzt ja das LO-Byte der Rücksprungadresse-1 holen! Nach einem RTS wäre ja eine falsche Adresse auf dem Stack. Folge: Der Compy stürzt höchstwahrscheinlich ab! RICHTIG: HALLO LDA #10 -------- PHA JSR SUB PLA ... JMP HALLO SUB ... LDA #32 ... RTS Weiterhin ist es möglich, ein JSR quasi unwirksam zu machen. Einfach zwei Bytes vom Stapel holen.... START JSR SUB JMP START SUB JSR CHECK RTS CHECK ... PLA Bei'JMP START' PLA weitermachen RTS Übrigens: ein 00001 JSR SUB 00002 RTS kann durch ein JMP SUB ersetzt werden! --------------------------------------- So, nun aber zum eigentlichen Thema: Statt Player und Missiles beschäftigen uns diesmal Interrupts. to interrupt (engl.) - unterbrechen Wird der CPU ein Interrupt gemeldet, hält sie das eigentliche Programm an, und verzweigt zu einem kurzen Zwischen- Programm, das die Kontrolle kurz darauf wieder an das Hauptprogramm zurückgibt. Nun stellen Sie sich folgendes vor: Im Hauptprogramm wird .1 LDA 712 vor dem CMP ein Interrupt CMP #231 ausgelöst, dessen Routine BEQ .1 den Akku verändert. Nach Rückkehr befindet sich im Akku ein anderer Wert, also verzweigt unser Beispiel falsch! Am Anfang der Interrupt-Routine müssen also alle benutzten Register gerettet werden. Dafür ist der Stack natürlich prädestiniert: DLI PHA Akku auf den Stack retten TYA PHA auf den Stack retten TXA PHA retten ... PLA TAX zurückholen PLA TAY zurückholen PLA Akku holen RTI ReTurn from Interrupt Man unterscheidet zwischen zwei Arten von Interrupts: - NMI (non-maskable-interrupts) nicht maskierbare interrupts: Vertikal-Blank-Interrupt (VBI) Display-List-Interrupt (DLI) RESET-Interrupt - IRQ (interrupt request) maskierbare interrupts: Tastatur-Interrupt Breaktasten-Interrupt (nur XL/XE) Pokey-Timer Interrupt BRK-Interrupt (es gibt noch ein paar Interrupts, die aber nicht wichtig sind) Maskierbar bedeutet, der Interrupt kann durch die Befehle SEI - SEt Interrupt (Int. verhindern) CLI - CLear Interrupt (Int. freigeben) aus- und eingeschaltet werden. Sollte nach einem SEI also z.B. eine Taste gedrückt werden, unterbricht die CPU ihr Programm nicht! Mit Ausnahme des RESET-Interrupts können aber auch die NMI's 'maskiert' werden, und zwar über das Register $D40E NMIEN Bit 7 =1:DLI erlaubt Bit 6 =1:VBI erlaubt Bit 5 Reset-Interrupt(immer gesetzt) Bit 0-4 unbenutzt Beim Einschalten setzt das OS Bit 6 und installiert eine eigene VBI-Routine. Für jeden Interrupt existiert ein Vektor, der anzeigt, an welche Adresse gesprungen werden soll, wenn dieser Interrupt auftritt: $200,$201 VDSLST DLI $206,$207 VBREAK BRK-Befehl $208,$209 VKEYBD Tastaturinterrupt $210,$211 VTIMR1 Pokey-Timer 1 $212,$213 VTIMR2 Pokey-Timer 2 $214,$215 VTIMR4 Pokey-Timer 4 XL/XE! $222,$223 VVBLKI Immediate VBI $224,$225 VVBLKD Deferred VBI $236,$237 BRKKY Breaktasten-interrupt nur XL/XE! Das OS stellt eine Routine, die es ermöglicht, einige Interrupts ohne das Problem zu setzen, daß vielleicht gerade auf den 'halbveränderten' Vektor zugegriffen wird, und der Computer abstürzt: $E45C SETVBV =6: Immediate VBI =7: Deferred VBI +: Adresse LO+HI Ansonsten muß man die Interrupts über die Register verbieten, neu setzen, und wieder erlauben. Nun zu den einzelnen Interrupts: VERTIKAL-BLANK-INTERRUPT: ------------------------- Der VBI wird immer dann ausgelöst, wenn der ANTIC in der Display List auf den Befehl $41 stößt,also alle 1/50 Sekunde und immer am Ende des Bildschirm- aufbaus. Damit ist garantiert, daß sich während seines Ablaufs der Bildschirm nicht im Aufbau befindet. Somit ist der VBI prädestiniert dafür, Aktionen wie Playerbewegung, Feinscrolling, Farben- Änderung etc. ohne Flackern und Ruckeln durchzuführen. Die Programme 'MASIC' und 'The Soundmachine' benutzen den VBI dazu, ein Musikstück 'im Hintergrund' abzuspielen. Man unterscheidet zwischen dem - Immediate VBI, der auch bei zeit- kritischen Aktionen wie I/O abläuft (wenn $42 CRITIC =1) und dem - Deferred VBI, der nur abläuft, wenn $42=0 ist. Beim VBI muß man die Register übrigens nicht selbst retten. Eine Immediate Routine muß mit einem JMP SETVBV ($E45F), eine Deferred Routine mit JMP XITVBV ($E462) beendet werden. HARDWARE-REGISTER UND SCHATTEN-REGISTER --------------------------------------- Haben Sie schon einmal ausprobiert, was passiert, wenn Sie das Register $D01A ändern (z.B. im Monitor)? Nichts, allerhöchstens ein kurzes Flackern, das passiert! Aber hier steht doch "$D01A COLBK Farbregister für den Hintergrund". Die Erklärung ist relativ simpel: Würde das Hardware-Register (das, nach dem sich der Chip GTIA beim Aufbau des Bildes richtet) während des Bildaufbaus plötzlich geändert, würde ein unschönes Flackern entstehen. Um dies zu verhindern, richtet das OS s.g. Schattenregister ein, aus denen es im VBI die Werte in das entsprechende Hardware- Register überträgt. Das ganze hat noch einen Vorteil: Aus den Schattenregistern kann man den Aktuellen Wert, z.B. eine Farbe, auch wieder auslesen. Die Hardware- Register erfüllen meist zwei Funktionen,z.B. $D011 (Lesen) TRIG1 Trigger Stick 1 $D011 (Write) GRAFM Bitmuster für GTIA-Missiles Wenn während des Bildaufbaus Werte verändert werden sollen, müssen die Hardwareregister benutzt werden (und der Interrupt während des Aufbaus stattfinden, ein VBI scheidet also aus). Damit auch dabei kein Flackern entsteht, ist es durch Schreiben eines Wertes in $D40A WSYNC möglich, den Prozessor so lange anzuhalten, bis das Ende der Bildschirmzeile(Scanline) erreicht ist. Schattenregister Hardware-Register $2C0-$2C3 PCOLR0-3 > $D012-$D015 COLPM $22F SDMCTL > $D400 DMACTL $2F3 CHACT > $D401 CHACTL $230,$231 SDLST > $D402,$D403 DLIST $2F4 CHBAS > $D409 CHBASE $234+$235 LPENH/V > $D40C+$D40D PENH/V $278+$279 STICK0/1 < $D300 PORTA $27C-$27F PTRIG0-3 < $D300 PORTA $232 SSKCTL > $D20F SKCTL $10 POKMSK > $D20E IRQEN $2FC CH < $D209 KBCODE $270-$278 PADDL0-7 < $D200-$D207 POT0-7 $26F GPRIOR > $D01B PRIOR $284+$285 STRIG0/1 < $D010+$D011 TRIG0 $2FA GINTLK < $D013 TRIG3 DISPLAY-LIST-INTERRUPT (DLI): ----------------------------- Ein DLI wird ausgelöst, wenn in der DL des ANTIC das Bit 7 ($80) gesetzt ist. Dies dient dazu, an bestimmter Stelle (z.B. in der 5. Zeile) des Bildaufbaus einen Interrupt auslösen zu können. Im DLI werden meist Farben oder Player- Positionen geändert. Um einen DLI zu ermöglichen, muß Bit 7 von NMIEN gesetzt sein, und der Vektor aus die eigene Routine umgebogen werden. Die Register müssen selbst gerettet werden, der DLI endet mit RTI. Da der DLI- wie der VBI- jeden Bild- aufbau aufgerufen wird, kommt er jede 1/50 Sekunde zur Auslösung. Der DLI kann aber im Gegensatz zum VBI erhebliche Timing-Probleme bereiten! BREAK-TASTEN-INTERRUPT: ----------------------- Der BTI existiert nur auf den XL/XE- Geräten. Damit er dort wirksam werden kann, muß Bit 7 ($80) von POKMSK ($10) gesetzt sein und der Vektor auf eine eigene Routine zeigen, die alle Register rettet und mit RTI endet. ACHTUNG: Das OS rettet vor Aufruf des Breaktasten-Interrupts den Akku auf den Stapel. Zurück- holen müssen SIE ihn aber am Ende der Routine (mit PHA). BRK-INTERRUPT: -------------- Dieser Interrupt tritt auf, wenn der Prozessor auf den Befehl BRK stößt. Er wird zur Fehler- Suche in Assembler- Programmern benutzt. Der BIBO-Assembler setzt den Vektor auf eine Routine, die sämtliche Register sowie die Adresse, an der das Programm unterbrochen wurde, zeigt. ACHTUNG: Das OS rettet vor Aufruf des BRK-Interrupts den Akku auf den Stapel. Zurückholen müssen SIE ihn aber am Ende der Routine (mit PHA). TASTATUR-INTERRUPT: ------------------- Er wird ausgelöst, wenn eine andere Taste als Reset,Option,Select,Start, Control und Shift gedrückt wurde. Natürlich kann auch dieser Vektor auf eine eigene Routine verbogen werden. Wie immer, muß man dann Register retten, und mit RTI aufhören. ACHTUNG: Das OS rettet vor Aufruf des Tastatur-Interrupts den Akku auf den Stapel. Zurückholen müssen SIE ihn aber am Ende der Routine (mit PHA). POKEY-TIMER-INTERRUPT: ---------------------- Es ist auch möglichh, Interrupts periodisch in wesentlich kürzeren Zeitabständen als 1/50 Sekunde auszulösen. Und das funktioniert folgendermaßen: Mit einer BASIS-Frequenz von 64kHz bzw. 15kHz getaktet, werden nach Schreiben eines Wertes ungleich null in das Register $D209 STIMER die drei Pokey- Zähler erniedrigt. Ein Interrupt könnte also 64000 mal pro Sekunde aufgerufen werden! $D200 AUDF1 Zählregister Timer 1 $D202 AUDF2 Zählregister Timer 2 $D204 AUDF4 Zählregister Timer 4 Hat ein Zähler 0 erreicht, wird die Routine aufgerufen, auf die der entsprechende Vektor zeigt. Setzt man im Register $D208 AUDCTL das Bit 0 ($1), wird die BASIS-Frequenz der Zähler von 64kHz auf 15kHz verringert. Theoretisch könnte durch Setzen der Bits 5 ($20) und 6 ($40) die Frequenz auf 1.79MHz erhöht werden, was der Taktfrequenz der CPU entspräche. Deshalb würde diese Frequenz auch zum Absturz führen! Nochmal der Reihe nach: - Vektor auf Routine setzen - Frequenz setzen - Zählregister setzen - Wert ungleich 0 in STIMER schreiben - entsprechende Timer erlauben,über $10 POKMSK und $D20E IRQEN Bit 7: Breaktaste wirksam Bit 6: Tastaturinterrupt möglich Bit 5-3: für serielle Datentransfer- Interrupts Bit 2: Pokey-Timer-Int. 4 möglich Bit 1: Pokey-Timer-Int. 2 möglich Bit 0: Pokey-Timer-Int. 1 möglich Vor Aufruf des Interrupts wird vom OS automatisch das entsprechende Zähler- Register zurückgesetzt, das brauchen Sie also nicht zu übernehmen. ACHTUNG: Das OS rettet vor Aufruf eines Pokey-Timer-Interrupts den Akku auf den Stapel. Zurück- holen müssen SIE ihn aber am Ende der Routine (mit PHA). So, das wär's eigentlich auch schon wieder. Auf der Disk sind wie immer einige spärliche Beispiel-Programme für den BIBO-Assembler und ATMAS-II. Noch eines zum Schluß: Es ist zwar ganz toll, wenn man mit Interrupts furchtbar tolle Farbeffekte erzielen kannn, aber umso öfter das Hauptprogramm unterbrochen wird, desto langsamer arbeitet es logischerweise. Der Vorteil ist eben, daß man das Hauptprogramm gezielt und/oder regel- mäßig unterbrechen kann. Damit dieser Text nicht zu lang wird, habe ich in einem weiterem Text zusammengefaßt, was die einzelnen Interrupts des OS erledigen. Good Byte (Nächstes Mal: Display-List und DLI)