Kurs: Assembler ist keine Alchimie
C 64/VC 20
Assembler-Kurs

Assembler ist keine Alchimie — Teil 11

Noch einmal geht es um Unterbrechungen. Wie geht unser Computer bei den verschiedenen Möglichkeiten vor? Wo kann man selbst einhaken und wie schreibt man eigene Unterbrechungsprogramme? Anhand von zwei Beispielen lernen Sie in dieser Folge, wie man den Ausstieg aus einem Programm verhindert und wie man die Rasterzeilenunterbrechung für eigene Programme einsetzt.

In der letzten Folge haben Sie erfahren, wie unsere CPU mit Unterbrechungen umgeht, welche Sorten von Unterbrechungen es gibt, welches Instrumentarium die Assembler-Sprache zur Behandlung dieser speziellen Technik bietet, und Sie kennen die »primären« Quellen für eine Unterbrechung. Diesmal wollen wir analysieren, wie die Unterbrechungen softwaremäßig bearbeitet werden, um Wege zu finden, die uns auf möglichst einfache Weise Eingriffe erlauben.

Der normale Verlauf eines IRQ

Neulich hatten wir bereits festgestellt, daß eine IRQ-Anforderung (nach dem Retten des Programmzählers und des Prozessorstatus-Registers, sowie dem Setzen der I-Flagge) den Inhalt des Vektors $FFFE/FFFF in den Programmzähler holt. Dort steht die Adresse $FF48 (dez. 65352) und deshalb startet nun das dort im ROM verankerte Programm, welches wir uns nun im einzelnen ansehen werden (alle Adressen als Dezimalzahlen, in Bild 1 finden Sie das Flußdiagramm dazu).

Bild 1. So bearbeitet der C 64 normale IRQs. Das Programm wird im Text erklärt.
65352PHAZunächst werden der Akku und die Register X und Y auf den Stapel geschoben
TXA
PHA
TYA
PHA

Trickreich sind die beiden folgenden Befehle, mit denen das zu Beginn durch die CPU gerettete Statusregister gelesen wird:

TSXStapelzeiger ins X- Register
LDA 260,XEinladen des Status-Registers

Nun wird geprüft, ob die BRK-Flagge gesetzt ist. Wenn das der Fall ist, dann ist der Auslöser ein BRK gewesen, ansonsten ein IRQ:

AND #16Isolieren der BRK-Flagge
BEQ 65368Wenn keine BRK-Flagge, dann überspringen des nächsten Befehls.
65365JMP (790)Falls BRK
65368JMP (788)Falls IRQ

Den vorletzten Sprungbefehl werden wir bei der BRK-Behandlung verfolgen. Interessant für uns ist jetzt der indirekte Sprung bei 65368. Der Vektor 788/789 ($314/315) liegt im RAM! Damit können wir ihn auf eigene Routinen verstellen. Genau hier ist der Ansatzpunkt für nahezu alle Eingriffe in die Unterbrechungsbehandlung. Der voreingestellte Wert in diesem Vektor ist die Adresse 59953 ($EA31). Das dort angesiedelte Programm wird im Normalfall 60mal in der Sekunde ausgeführt:

59953JSR 65514Das ist ein Kernal-Sprungbefehl zur Routine UDTIM bei 63131.

In diesem Unterprogramm wird zuerst die Uhr TI$ weitergestellt und dann die Tastaturabfrage vorbereitet.

59956 bis 60000In diesem Programmteil erfolgt die Cursorbehandlung.
60001 bis 60026Anschließend wird abgefragt, ob eine Recordertaste gedrückt ist und entsprechende Flaggen bearbeitet.
60027JSR 60039Dieses Unterprogramm dient zur Tastaturabfrage.

Auch in dieser Routine tritt übrigens ein indirekter Sprung nach einem RAM-Vektor auf (655/656 = $28F/290), der normalerweise auf 60232 zeigt, aber auch auf eine eigene Routine verbogen werden könnte.

Enthalten in der Tastaturabfrage ist auch die Überprüfung der RUN/STOP-Taste, die aber nur zusammen mit den in dem UDTIM-Aufruf voreingestellten Flaggen funktioniert. Deshalb wird das Abschalten der RUN/STOP-Taste im allgemeinen dadurch durchgeführt, daß man den IRQ-Vektor auf 59956 stellt und damit den ersten JSR-Befehl überspringt. Allerdings wird auf diese Weise auch die TI$-Uhr nicht weitergestellt.

60030LDA 56333Das ist das Unterbrechungs-Kontrollregister des IRQ-CIA, das hier durch Auslesen gelöscht wird.

Den Abschluß der IRQ-Routine bildet nun noch das Zurückschreiben der Register:

60033PLAZurückholen des
TAYY- und
PLA
TAXdes X-Registers
PLAsowie des Akku.
60038RTIDamit kehrt der Computer zu dem durch den IRQ unterbrochenen Programm zurück.

Somit hätten wir's. Nun können wir je nach Bedarf entscheiden, welche von diesen Servicetätigkeiten wir bei einem eigenen IRQ-Programm brauchen: Die Uhr TI$, die Cursorbehandlung, die Abfrage der Recordertasten und die Tastaturabfrage.

Sehen wir uns nun an, was geschieht, wenn ein BRK-Kommando der Auslöser war.

BRK-Unterbrechung

Wir hatten vorhin am Scheideweg zwischen IRQ und BRK den letzteren links liegen gelassen. Normalerweise verwendet man beim Programmieren in Assembler ja ein Software-Instrument wie zum Beispiel den SMON, der so gebaut ist, daß der BRK-Vektor, welchen wir vorhin kennengelernt haben ($316/317 = 790/791) auf die Registeranzeige weist. Was geschieht eigentlich, wenn der BRK-Vektor unverändert bleibt, so also, wie er im Einschaltzustand des Computers vorliegt?

Dann zeigt er auf die Adresse 65126 ($FE66), wo ein Teil der NMI-Routine zu finden ist (Siehe auch das Flußdiagramm in Bild 2):

Bild 2. Auf diese Weise verläuft ein unvorhergesehener BRK im Sande
65126JSR 64789Sprung ins Programm RESTOR, in dem alle Vektoren (788-819) gemäß einer ROM-Liste auf ihre Ausgangswerte gesetzt werden.
JSR 64931Sprung in das Programm I/O-RESET.

In diesem Programm werden die beiden CIAs auf die Anfangswerte gestellt.

JSR 58648Sprung in ein Programm, welches zuerst den VIC-II-Chip initialisiert, dann einen Bildschirmeditor-RESET durchführt. Nach Beenden dieser Routine ist der Bildschirm gelöscht.
JMP (40962)

Mit diesem indirekten Sprung ist die BRK-Unterbrechung beendet. Man sieht aber jetzt schon deutlich, daß es sich hier nicht um eine Unterbrechung im eigentlichen Sinn handelt, vielmehr um einen Abbruch. In 40962/40963 steht die Adresse des Basic-Warmstarts (58235). Danach befindet sich der Computer im READY-Zustand in der Eingabe-Warteschleife.

Das Zurückholen der Register und ein RTI erübrigt sich hier, weil ohnehin viele Werte aus dem unterbrochenen Programm inzwischen weitgehend zerstört sind und alle Unterbrechungskontrollregister (CIAs und VIC-II-Chip) neu belegt wurden. Ein unkontrollierter BRK hat also recht fatale Folgen!

Was macht ein NMI?

Wenden wir uns nun der Firmware zu, die zur Bearbeitung eines NMI vorgesehen ist (Dazu sehen Sie sich bitte in Bild 3 das Flußdiagramm an).

Bild 3. Flußdiagramm zum Ablauf einer NMI-Unterbrechung

In der letzten Folge erfuhren wir, daß auch für diese Unterbrechung am Ende des Speichers ein Vektor vorhanden ist, nämlich $FFFA/FFFB (65530/65531). Dort steht die Adresse 65091 ($FE43), die nun in den Programmzähler gelangt und damit startet das folgende Programm:

65091SEIUnterbrechungen niedrigerer Priorität werden gesperrt.
JMP (792)

Das ist nun wieder ein für uns sehr interessanter Vektor 792/793 ($318/319), der — weil er im RAM-Bereich liegt — verstellbar ist. Genau das haben wir am Ende der letzten Folge getan mittels des M-Kommandos von SMON um den NMI zu testen, den wir mit der RESTORE-Taste ausgelöst haben. Der voreingestellte Wert in diesem Vektor ist die Adresse 65095 ($FE47), also direkt der nächste Befehl nach dem indirekten Sprungbefehl.

65095PHAEbenso wie vorhin beim IRQ werden hier die Inhalte des Akku und der Register auf den Stapel geschoben.
TXA
PHA
TYA
PHA
LDA #127das ist binär 01111111.
STA 56589Sperrt alle weiteren NMI-Anforderungen
LDY 56589NMI-CIA Kontrollregister laden.
BMI 65138Wenn der NMI von der RESTORE-Taste kam, ist Bit 7 des Registers = 0, sonst = 1 (bei NMI-Anforderung durch NMI-CIA). Wenn also nicht durch die RESTORE-Taste, erfolgt Sprung.

An dieser Stelle läuft nun das Programm weiter, wenn die RESTORE-Taste der NMI-Auslöser war:

65110JSR 64770Das ist ein Unterprogramm, welches prüft, ob ein Modul ab $8000 vorhanden ist.

Dies wird dadurch angezeigt, daß von $8004 bis $8008 die Werte stehen: 195, 194, 205, 56, 48 (das ist "CBM80).

BNE 65118Wenn kein Modulprogramm ab $8000 vorliegt, erfolgt ein Sprung.
JMP (32770)Falls Modul.

Wenn ein Modul angezeigt wurde, erfolgt der indirekte Sprung nach den Vektor $8002/8003, der vom Modul vorgegeben wird. Das kann man auch nutzen, um eigene Maschinenprogramme zu starten durch einen Druck auf die RESTORE-Taste. Man muß dann nur in die Speicherstellen $8002 bis 8008 die geforderte Zieladresse beziehungsweise »CBM80« schreiben.

Der nun folgende Abschnitt wird nur angesprungen, wenn die RESTORE-Taste der NMI-Auslöser war:

65118JSR 63164Das ist ein Programmteil, der auch schon von der IRQ-Routine (nach dem Weiterstellen von TI$) durchlaufen wird. Hier werden einige Voreinstellungen für die Tastaturabfrage erledigt, die insbesonders die RUN/STOP-Taste betreffen.
JSR 65505Kernalroutine STOP

Dort befindet sich ein indirekter Sprung über den Vektor 808/809 ($328/329), also auch ein verstellbarer RAM-Vektor. Im Normalfall zeigt dieser Vektor auf 63213 ($F6ED). Dort wird geprüft, ob die RUN/STOP-Taste gedrückt ist. Eine andere Methode zum Ausschalten des RUN/STOP bietet sich hier an, die die Uhr TI$ ungeschoren läßt.

BNE 65138Falls nur die RESTORE-Taste (also ohne RUN/STOP) gedrückt ist, erfolgt nun ein Sprung.

Waren aber sowohl die RUN/STOP- als auch die RESTORE-Taste gedrückt, dann folgt nun ein Programmabbruch, der uns schon von BRK her bekannt ist. Hier wie dort endet das Ganze dann mit dem Reset der I/O-Bausteine, des VIC-II-Chips, der Vektoren, des Bildschirmeditors und das Ergebnis ist ein Basic-Warmstart.

Ab 65138 befindet sich der Rest der NMI-Routine, auf die das Programm läuft, wenn

  1. die NMI-Anforderung nicht von der RESTORE-Taste kommt oder
  2. zwar von dieser Taste kommt, aber die RUN/STOP-Taste nicht gedrückt ist.

65138 bis 65211Dieser ganze Abschnitt ist zur Behandlung der RS232C-Schnittstelle eingerichtet.
65212PLAAbschluß des NMI durch Rückschreiben des Akku und der Register vom Stapel
TAY
PLA
TAX
PLA
65217RTI

Wenn Sie sich nun mal unser kleines Demo-Programm aus der letzten Folge ansehen, dann werden Sie feststellen, daß der Programmteil bis $600E lediglich den ersten Teil der normalen NMI-Routine kopiert. Die Prüfung auf das Modul und die RUN/STOP-Taste werden übersprungen. Statt dessen erfolgt nach der Abarbeitung des für die RESTORE-Taste gebauten Programmes das Ende der NMI-Routine ($FEBC = 65212). Im anderen Fall, wenn also die RESTORE-Taste nicht der Auslöser des NMI war, wird in die normale Routine ab 65138 eingemündet.

Eigentlich keine Unterbrechung: RESET

Weil wir alle Unterbrechungen hier bearbeiten wollen, soll auch der RESET angesprochen werden. Es handelt sich dabei aber nicht um eine Unterbrechung im bisher definierten Sinn. Mir fällt allerdings kein Platz ein in dieser Serie, wo der RESET besser hinpassen würde. Ähnlich wie bei NMI und IRQ wird auch hier ein Vektorinhalt in den Programmzähler geladen, der in den höchsten Speicheradressen zu finden ist (Auch hierzu wieder ein Flußdiagramm in Bild 4).

Bild 4. Diesem Weg folgt ein RESET

Dieser Vektor liegt in $FFFC/FFFD. Der Inhalt ist die Adresse 64738 ($FCE2) und genau dort geht das Programm dann weiter:

64738LDX #255Im ersten Teil wird der Stapelspeicher initialisiert.
SEIVerhindern von IRQ
TXSStapelzeiger auf $FF
CLDDezimal-Modus ausschalten (falls er eingeschaltet war).
JSR 64770Das ist wieder das Unterprogramm, das auf ein Modul prüft.

Hier ergibt sich die Möglichkeit, auch beim RESET einzugreifen, indem man die Kennung CBM80 an die abgefragten Orte packt.

BNE 64751Falls kein Modul, erfolgt Sprung.
64748JMP(32768)

Dieser indirekte Sprung erfolgt nach dem Vektorinhalt von $8000/8001 = 32768/32769. Das ist ein anderer Vektor als wir ihn vorhin beim NMI hatten (dort war es $8002/8003 = 32770/32771). So kann ein anderer Programmteil angesteuert werden als durch den NMI, was übrigens auch dringend erforderlich ist, weil der Stapelzeiger zerstört wurde.

64751Hier läuft das Programm weiter, falls keine Modulkennung erkannt wurde.

Der ganze Rest dient dem Versetzen des Computers in den Einschaltzustand. Allerdings bin ich davon überzeugt, daß noch irgendein Unterschied bestehen muß zwischen dem einfachen Aus- und wieder Anschalten des Computers und einem RESET. Es hat sich nämlich bei einigen Programmen gezeigt, daß sie nach einem RESET fehlerhafte Verläufe nehmen können, was nach einem totalen Aus- und wieder Anschalten nicht zu beobachten war. Der Grund für diesen Unterschied liegt (für mich) noch im Dunkel. Vielleicht weiß das ja jemand von Ihnen. Dann schreiben Sie doch mal!

In der nächsten Folge werden wir der Sache mit dem Modulstart noch etwas weiter auf den Grund gehen und auch ein interessantes Programm dazu entwickeln.

(Heimo Ponnath/gk)
Kurs: Assembler ist keine Alchimie
PDF Diesen Artikel als PDF herunterladen
Mastodon Diesen Artikel auf Mastodon teilen
← Vorheriger ArtikelNächster Artikel →