Der gläserne VC 20 — Teil 6
Als Abschluß unseres Kurses beschäftigen wir uns mit Interruptmechanismus und Betriebssystem des Volkscomputers. Damit haben wir in den sechs Teilen den VC 20 aus jeder Perspektive durchleuchtet und wirklich zum »gläsernen« Computer gemacht.
Ein Betriebssystem, so sagt ja bereits der Name, ist für die elementaren Funktionen des Computers wie zum Beispiel Bildschirmverwaltung, Tastaturabfrage, laden und speichern von Daten und vielem mehr verantwortlich. Natürlich ist auch im VC 20 solch ein Systemprogramm eingebaut: Es befindet sich im ROM zwischen Adresse $E429 und $FFFF, der restliche ROM-Bereich (von $C000 bis $E428) wird vom Basic-Interpreter beansprucht. Diese zwei Programmblöcke sind bei diesem Computer so miteinander verzahnt, daß kein Teil für sich alleine arbeiten kann.
Was beim Einschalten alles geschieht
Als erstes möchte ich nun die Vorgänge beschreiben, die sich beim Einschalten des Systems abspielen.
Zunächst wird die Resetleitung der CPU von der Elektronik des Computers auf Low gezogen (genau das Gleiche geschieht übrigens beim Betätigen eines eventuell eingebauten Reset-Tasters), wodurch die wichtigsten Systemkomponenten (CPU, VIC und die Ein-/ Ausgabebausteine) in einen definierten Betriebszustand versetzt werden.
Danach sucht sich die 6502-Zentraleinheit aus den Speicherstellen $FFFC und $FFFD die Startadresse des Betriebssystems heraus und beginnt die Programmausführung an der dort abgespeicherten Stelle (beim VC 20 $FD22). Diese Vektoren sind hardwaremäßig in der CPU festgelegt worden und können daher nicht durch ein Programm geändert werden.
Die Einschaltroutine hat nun die verschiedensten Aufgaben zu bewältigen. Am wichtigsten ist zunächst die Initialisierung des Prozessorstacks, also des Stapelspeichers, der die Rücksprungadressen beim Befehl JSR verwaltet (damit der Computer aus den Unterprogrammen wieder ins Hauptprogramm zurückfindet).
Der nächste Schritt in der Reset-Routine ist die Modulabfrage, in der die Modulidentifikation abgefragt wird. Findet der VC 20 im Bereich $A004 bis $A009 die Zeichenfolge »aOCBM« (die den Bytes 41 30 C3 C2 und CD entspricht), so gibt das Betriebssystem die Kontrolle an das Modulprogramm ab. Auch hierbei spielen wieder Vektoren ein Rolle, denn die Startadresse des Moduls muß in den beiden Speicherstellen $A000 und $A001 abgelegt sein.
Mit Hilfe eines solchen Modulprogramms, das automatisch gestartet wird, kann man nun den Computer ganz nach seinen Wünschen gestalten. Dies kann zum Beispiel in Form einer Basic-Erweiterung geschehen, wie sie an dieser Stelle ja schon besprochen worden ist.
Findet die Einschaltroutine — um wieder zum Reset zurückzukommen — keine Modulmarkierung, so wird die Initialisierung ganz normal fortgesetzt. Diese besteht im wesentlichen aus vier Unterprogrammen, die auch in einem Autostartprogramm nacheinander aufgerufen werden sollten. Da ist als erstes eine Routine ($FD8D), die die Aufgabe hat, in der Zeropage bestimmte Startwerte einzutragen und den verfügbaren Speicher festzustellen. Dieser RAM-Test ist für den VC 20 besonders wichtig, denn auf diese Weise kann er erstens die Funktionsweise des Speichers überprüfen und zweitens die momentane Ausbauversion feststellen. Wird tatsächlich einmal ein Defekt in einer oder mehreren Speicherstellen des Grundversions-RAMs festgestellt, dann »hängt« sich das Betriebssystem in einer Endlosschleife auf, und der Bildschirm bleibt dunkel.
Nach dieser Prüfung, die bei voll ausgebautem Speicher einige Sekunden in Anspruch nimmt, werden in einer weiteren Unterroutine ($FD52) die Kernal-Vektoren im Bereich zwischen $0314 und $0355 installiert (einige dieser Zeiger werden wir später noch genauer kennenlernen). Zum Schluß müssen natürlich auch in den Registern der Peripheriebausteine die benötigten Werte abgespeichert werden. Dafür sind die zwei letzten Unterprogramme der Einschaltroutine zuständig. Das eine ($FDF9) initialisiert die I/O-Register, damit Interrupts und Tastaturoperationen stattfinden können, und das andere ($E518) kümmert sich um die Grundeinstellung des VIC.
Dies waren im wesentlichen die Schritte, die nach dem Einschalten des Computers ablaufen; damit ist nun das Betriebssystem einsatzbereit und gibt nun die Kontrolle an den Basic-Interpreter ($E378) ab. Der wiederum setzt als erstes die in Folge 3 angesprochenen Basic-Vektoren, damit die essentiellen Operationen (wie zum Beispiel die Umwandlung einer Zeile in Interpretertoken) über diese abgewickelt werden können.
Bevor die bekannte Einschaltmeldung (CBM BASIC..) auf dem Bildschirm ausgegeben wird, initialisiert der Interpreter den Basic-Teil der Zeropage und des Speichers nach seinen Bedürfnissen. Dabei werden zum Beispiel verschiedene Zeiger (Adresse 43, 44, 45, …) gesetzt und der erste Verbindungszeiger im Programmspeicher gelöscht (daher kann ein Basic-Programm nach einem Reset nicht mehr gelistet werden).
Unterbrechung? Ja, bitte!
Nachdem die Systemmeldung auf dem Bildschirm ausgegeben wurde, verzweigt die Interpreterroutine in die sogenannte Eingabewarteschleife ($C474), die wirja bereits in Folge 3 näher beleuchtet haben. Damit ist der Computer bereit, Anweisungen des Benutzers zu empfangen.
Soweit die Beschreibung der Resetroutine, die durch ein Signal an einem Prozessoreingang gestartet wurde. Auch die Interrupts, auf die ich genauer eingehen möchte, werden über die Hardware ausgelöst. Der Plural bei Interrupts verrät bereits, daß es mehrere — genauer gesagt sogar 3 — Unterbrechungen von dieser Sorte gibt.
Da ist zunächst der Systeminterrupt, der vom VC 20 alle 60steF Sekunde durchlaufen wird (ein Timer in dem 6522-Ausgabebaustein ist für dessen Auslösung verantwortlich). Durch diese Interruptanfrage — sie wird auch IRQ (Interrupt Request) genannt — wird die Zentraleinheit veranlaßt, das gerade laufende Programm zu unterbrechen, um in eine Abarbeitungsroutine zu verzweigen. Auch hierbei besorgt sich die CPU über den Hardwarevektor ($FFFE und $FFFF) die entsprechende Startadresse ($FF72).
Dort werden als erstes alle CPU-Register auf den Stack gerettet, denn die dort gespeicherten Werte werden ja später bei der Wiederaufnahme des regulären Programmablaufs benötigt. Danach verzweigt die Routine über einen eigenen Vektor (Interrupt-Vektor $0314,$0315) in das eigentliche Abarbeitungsunterprogramm (Adresse $EABF).
Hier wird zunächst die interne Uhr (TI) um eins hochgezählt und bei dieser Gelegenheit auch gleich der Zustand der STOP-Taste abgefragt. Ist diese gedrückt, dann setzt die Unterroutine ein Flag in der Zeropage, und der Interpreter unterbricht — nachdem der Interrupt zu Ende bearbeitet worden ist — das laufende Basic-Programm. Durch »verbiegen« des erwähnten Sprungvektors kann dieses Unterprogramm in der Abarbeitungsroutine so übersprungen werden, daß keine Abfrage der STOPTaste mehr stattfindet, womit bewirkt wird, daß man ein Basic-Programm nicht mehr auf normalem Wege anhalten kann (dieses nur als eine Programmschutzvariante).
Nun aber wieder zurück zu unserer Abarbeitungsroutine. Hier wird als nächstes der sogenannte Cursorblinkzähler, der bei jedem Interrupt um eins dekrementiert wird, abgefragt. Ist dieser auf Null, so wird der momentane Zustand des Cursors geändert; das heißt aus revers wird normal oder umgekehrt (auf diese Weise wird ein Blinken des Zeigers erreicht).
Abschließend wird — und das ist das Wichtigste an dem ganzen Interrupt — die Tastatur des Computers abgefragt. Drückt der Benutzer irgendeine Taste, so wandelt die Routine diesen Tastencode in den entsprechenden ASCII-Wert und speichert das Ergebnis im Tastaturpuffer ab. Dieser bleibt solange dort, bis ein Zeichen von der Tastatur angefordert wird. Das ist entweder im Direktmodus der Fall oder bei den Basic-Befehlen INPUT und GET (oder den entsprechenden Unterprogrammen im Betriebssystem).
Damit hat die Interrupt-Routine ihren Zweck erfüllt, und der VC 20 kann die Arbeit an dem eigentlichen (Basic-) Programm wieder aufnehmen. Vorher werden allerdings noch die CPU-Register wiederhergestellt (deren Inhalte befinden sich ja immer noch auf dem Stack) und dann gibt das Interrupt-Unterprogramm über den Befehl RTI (Return From Interrupt) die Kontrolle wieder an das eigentliche Hauptprogramm ab. Diese Einschnitte mittels Interrupt haben für den Benutzer den Vorteil, daß er sich nicht um die, in einem gewissen Zeitabstand immer wiederkehrenden, Tastaturabfragen zu kümmern braucht. Außerdem kann kein noch so kurzer Tastendruck verloren gehen, wie es der Fall wäre, wenn die Tastatur in unregelmäßigen, längeren Abständen abgefragt werden würde.
Eingriff ins Eingemachte
Wie wir bereits gesehen haben, springt der Computer über einen Vektor ($0314-$0315) in die Bearbeitungsroutine ein. Dadurch können wirjetzt in den Ablauf eingreifen, indem wir dort zusätzliche Unterprogramme einbetten. Als Beispiel für solch einen Eingriff möchte ich nun eine Routine besprechen, die den regelmäßigen Interruptzyklus benützt, um die aktuelle Uhrzeit auf dem Bildschirm anzuzeigen. Dazu bedienen wir uns einer speziellen Eigenschaft des VIC, die es erlaubt, mehr als 23 Zeilen auf dem Bildschirm anzuzeigen. Mit Hilfe des Registers # 4 (Adresse 36867 beziehungsweise $9003) kann man die anzeigbare Zeilenzahl vergrößern oder verkleinern. Für unseren Zweck genügt es, die verfügbaren Zeilen auf 25 zu erhöhen. In diesen Zeilen wird dann später die Uhrzeit angezeigt werden. Mittels POKE 36867,50 wird diese Umschaltung realisiert. Meine Routine kann mit dem Lader (Listing 1) irgendwo im Speicher plaziert werden (das Programm gibt die günstigste Adresse vor). Den wichtigsten Teil der Initialisierungsroutine — also dem Unterprogramm, das den Interrupt-Vektor »verbiegt« — möchte ich kurz anhand von Listing 2 beschreiben. Dort wird gleich als erstes eine Unterroutine aufgerufen, die ein eventuell vorhandenes Basic-Programm im Speicher verschieben soll. Das hat seinen guten Grund, denn die zwei zusätzlichen Bildschirmzeilen nehmen, um ihre Zeichen ablegen zu können, den Anfang des Basic-Speichers in Beschlag, so daß ein dort stehendes Programm teilweise überschrieben werden würde. Diese Verschiebe-Routine ist so universell, daß sie auch für andere Anwendungen nützlich sein kann (um beispielsweise ein Basic-Programm zu verschieben, damit Platz für einen Grafikspeicher ist). Die Anzahl der zu verschiebenden Bytes ist, bei einer anderweitigen Verwendung, in die Additionsroutine in $210E einzusetzen.
Nachdem die 24. und 25. Zeile zugeschaltet worden ist, wird der Interruptvektor auf den Anfang unserer Uhrenroutine gestellt, damit sie beim nächsten Aufruf mit abgearbeitet wird. Bevor man diesen Zeiger jedoch verändert, muß man mittels des Befehls SEI dafür sorgen, daß zeitweise keine Interrupts mehr akzeptiert werden. Sonst könnte es passieren, daß die — zufällig gerade zu dieser Zeit ausgelöste — Interruptroutine ihren Vektor sucht, und wir sind gerade dabei, ihn zu verändern, wodurch der Computer eine falsche Adresse finden und damit abstürzen würde. Das ist auch der Grund, warum man niemals versuchen darf, Interrupt-Vektoren von Basic aus mit POKE-Befehlen zu verändern.
Der Maschinenbefehl SEI (Set Interrupt-disable Flag) setzt das entsprechende Flag im Statusregister der CPU. Wird nun ein IRQ ausgelöst, so testet die Zentraleinheit zuerst dieses Flag; ist es gesetzt, dann wird die Interruptanfrage ignoriert. Das Gegenstück dazu ist der Befehl CLI, der dieses Flag löscht, damit Interrupts wieder möglich werden.
Interrupt auf Tastendruck
Ich habe ja eingangs bereits erwähnt, daß es auch andere Interrupts gibt — diesen wollen wir uns jetzt zuwenden. Wer wird es glauben, auch die RESTORE-Taste löst einen Interrupt aus. Über den Hardwarevektor in $FFFA und $FFFB bezieht die CPU die Anfangsadresse der NMI-Abarbeitungsroutine (NMI bedeutet »nicht maskierbarer Interrupt«). Dieser Interrupt unterscheidet sich von dem IRQ in der Weise, daß er nicht per Spezialbefehl abschaltbar ist. Die Abarbeitungsroutine ($FEAD), die wiederum über einen Vektor in $0318, $0319 angesprungen wird, prüft nun als erstes, ob sie etwa ein Modulprogramm unterbrochen hat. Ist dies der Fall, so verzweigt die Routine in die NMI-Bearbeitung des Modulprogramms (dazu ist in $A002, $A003 die entsprechende Startadresse anzugeben). Dadurch ist es für den Programmierer eines solchen Moduls möglich, alle Unterbrechungen, die durch das Drücken der RESTORE-Taste entstehen, abzublocken, womit natürlich auch ein gewisser Programmschutz gewährleistet ist.
Sollte ein solches Modulprogramm nicht vorhanden sein, so wird die NMI-Routine normal fortgesetzt. Als erstes wird dann die STOP-Taste des Computers abgefragt, denn ein Programmabbruch soll ja nur durch das Drücken der Tastenkombination RUN/STOP-RESTORE möglich sein. Wenn auch diese Klippe genommen ist, beginnt der eigentliche Warmstart des Computers, der die Vektoren neu setzt und die Zeropage ordnet (daher wird auch unser Uhrenprogramm nach dem Drücken dieser Tastenkombination abgeschaltet).
Natürlich kann auch der NMI für unsere Programme mißbraucht werden. Dazu muß nur wieder (wie soll es auch anders sein) der entsprechende Vektor geändert werden, so daß er auf ein eigenes Maschinenprogramm zeigt. In Folge 3 ist eine sehr kurze und effiziente Anwendung der RESTORE-Taste erläutert worden. Damals ging es darum, sich innerhalb von Hochkommas (“) von den lästigen Cursor-Steuerzeichen zu befreien. Durch das Drücken der RESTORE-Taste, die dann quasi als zusätzliche Funktionstaste arbeitet, kann man also auch Aktionen auslösen.
Aller guten Dinge sind drei
Der Vollständigkeit halber möchte ich jetzt auch noch den dritten Interrupttyp anführen, der allerdings nicht durch irgendwelche Signale in der Hardware ausgelöst wird. Ich spreche von dem Maschinenbefehl BRK. Dieser wird in der CPU wie ein Interrupt-Aufruf (IRQ) behandelt, und deshalb benutzt er auch den gleichen Hardwarevektor, nämlich $FFFE. Das Betriebssystem im VC 20 hingegen trifft eine Unterscheidung zwischen diesen beiden Interrupttypen, denn der Befehl BRK soll beim VC 20 einen Warmstart bewirken. Auch diese Abarbeitung wird (ich traue mich es schon nicht mehr zu sagen) über einen Vektor ($0316, $0317) geleitet. Damit bietet sich die Möglichkeit, dieses Kommando dazu zu benutzen, ein Unterprogramm über einen Ein-Byte-Befehl (!) aufzurufen. Durch die Angabe der Startadresse in diesem Vektor kann man jedes beliebige Unterprogramm adressieren und mit BRK aufrufen.
Soweit zu den Interrupts. Ich möchte Ihnen zum Schluß noch, da wir gerade beim Betriebssystem sind, einige sehr nützliche Unterprogramme aus dem Interpreter vorstellen. Diese Routinen können nämlich sehr gut in eigenen Maschinenprogrammen Verwendung finden, und dies spart enormen Platz- und Programmieraufwand.
Nützliche Routinen im Betriebssystem
Verschieberoutine ($C3B8): Genau diese wurde in meinem Beispielprogramm verwendet, um das eventuell im Speicher vorhandene Basic-Programm zu verschieben. Die Benutzung dieser Routine ist verhältnismäßig einfach. Die Anfangsadresse des zu verschiebenden Speicherbereichs muß in den beiden Zeropagespeicherstellen $5F und $60 abgelegt werden. Um das Ende zu markieren, vermerkt man die entsprechende Adresse plus 1 (!) in den Speicherstellen $5A und $5B. Schließlich ist auch noch die neue Anfangsadresse in $58 und $59 abzulegen. Auch hierbei ist darauf zu achten, daß eins dazuaddiert wird, denn die Routine ist dementsprechend konzipiert. Der Aufruf der Routine geschieht schließlich mit »JSR $C3B8«.
Fehlermeldung ausgeben ($C437): Oftmals ist es nötig, eine Basic-Fehlermeldung auszugeben (zum Beispiel in einer Befehlserweiterung, in der ein Komma abzufragen ist). Wenn dieses nicht vorhanden ist oder sonst ein Fehler vorliegt, dann soll eine Fehlermeldung ausgedruckt werden. Alles, was man bei dieser Routine zu tun hat, ist die Eingabe der Fehlernummer ins X-Register. Alle möglichen Meldungen und die dazugehörigen Nummern sind in Tabelle 1 zu finden. Dann wird die Abarbeitungsroutine mit JMP $C437 angesprungen. Zu beachten ist, daß ein Maschinenprogramm auf diese Weise beendet wird (der Interpreter springt nämlich in die Eingabewarteschleife zurück). Möchte man nur eine Meldung ausgeben (ohne das Programm anzuhalten), so ist die Nachricht mit einer anderen Routine, die noch besprochen wird, zu erzeugen. Zwei oft benötigte Fehlermeldungen (Syntax- und Illegal Quantity Error) sind noch einfacher zu bekommen. Um den Syntax Error zu erzeugen springt man mit JMP $CF08 in eine Unterroutine ein, die bereits die Fehlernummer enthält (auf diese Weise spart der Interpreter Speicherplatz). Auch für den Illegal Quantity Error gibt es solch ein Unterprogramm: JMP $D248. Für beide Abkürzungen gilt im übrigen — dies noch als Ergänzung — das oben zum Programmabbruch gesagte.
Nummer | Fehlermeldung | Adresse |
---|---|---|
01 | TOO MANY FILES OPEN | $C19E |
02 | FILE OPEN | $ClAD |
03 | FILE NOT OPEN | $ClB5 |
04 | FILE NOT FOUND | $ClC2 |
05 | DEVICE NOT PRESENT | $ClD0 |
06 | NOT INPUT FILE | $ClE2 |
07 | NOT OUTPUT FILE | $ClF0 |
08 | MISSING FILE NAME | $ClFF |
09 | ILLEGAL DEVICE NUMBER | $C210 |
0A | NEXT WITHOUT FOR | $C225 |
0B | SYNTAX | $C235 |
0C | RETURN WITHOUT GOSUB | $C23B |
0D | OUT OF DATA | $C24F |
0E | ILLEGAL QUANTITY | $C25A |
0F | OVERFLOW | $C26A |
10 | OUT OF MEMORY | $C272 |
11 | UNDEF’D STATEMENT | $C27F |
12 | BAD SUBSCRIPT | $C290 |
13 | REDIM’D ARRAY | $C29D |
14 | DIVISION BY ZERO | $C2AA |
15 | ILLEGAL DIRECT | $C2BA |
16 | TYPE MISMATCH | $C2C8 |
17 | STRING TOO LONG | $C2D5 |
18 | FILE DATA | $C2E4 |
19 | FORMULA TOO COMPLEX | $C2ED |
lA | CAN’T CONTINUE | $C300 |
lB | UNDEF’D FUNCTION | $C30E |
lC | VERIFY | $C31E |
lD | LOAD | $C324 |
Programmzeilen neu ordnen ($C533): Dies ist eine der praktischsten Routinen, die mir im gesamten Basic-Interpreter untergekommen ist. Das kleine unscheinbare Unterprogramm kann mehr, als zunächst zu vermuten ist. Es ist wahrscheinlich jedem schon einmal passiert, daß plötzlich aus irgendeinem Grund ein verstümmeltes Basic-Programm im Speicher steht. Das kann durch falsches Laden vom Band oder auch durch unvorsichtiges POKEn im Basic-Speicher geschehen. Der Grund für diesen »Zeilensalat« ist eine Veränderung der Verbindungszeiger (Koppeladressen), die am Beginn jeder (Basic-) Programmzeile stehen, und die im Normalfall auf die Adresse der jeweils folgenden Zeile zeigen (siehe auch Folge 1 dieses Kurses). Das Unterprogramm ab $C533 hat nun die nützliche Eigenschaft, fast alle auf diese Weise beschädigten Programme wieder zu restaurieren. Man ruft es einfach von Basic aus mit SYS 53483 auf, und schon ist das Programm zumindest wieder LISTbar (jedenfalls in den meisten Fällen). Damit aber noch nicht genug. Bei dieser Gelegenheit stellt die Routine auch noch die Länge des Programmes fest und übergibt die Endadresse den Speicherstellen $22 und $23. Damit ist es möglich, die durch einen Reset oder durch NEW scheinbar verlorenen Programme völlig wiederherzustellen. Dazu ist lediglich der erste Verbindungszeiger zu rekonstruieren (das macht unsere Unterroutine), und das Programmende muß in den Speicherstellen $2D und $2E vermerkt werden. Die nachfolgende Programmzeile ist das kürzeste Rekonstruktionsprogramm, das mir bekannt ist. Ich habe es bereits in Folge 1 besprochen und möchte es in diesem Zusammenhang noch einmal erwähnen.
SYS 50483:POKE 46,PEEK(35): POKE 45,PEEK(781) + 2:CLR
Bevor diese Zeile eingegeben wird, muß man den Anfang des Basic-Programms im Speicher markieren, denn sonst wird es von der Routine nicht gefunden: POKE (Basic-Anfangsadresse) + 1,1.
Natürlich ist diese Routine auch im C 64 zu finden. Dort ist die Startadresse $A533.
String ausgeben ($CB1E): Mit diesem Unterprogramm ist es in einfacher Weise möglich, Texte in einem Maschinenprogramm auszugeben. Dadurch entfallen die sonst nötigen langwierigen Druckschleifen, mit denen man sich sonst herumschlagen muß. Auch bei dieser Unterroutine ist die Handhabung wieder ganz einfach. Der auszugebende Text, der natürlich auch Sonderund Steuerzeichen enthalten darf, muß im ASCII-Format irgendwo im Speicher stehen. Das Textende muß mit einer Null markiert sein. Ist diese Voraussetzung erfüllt, braucht man nur noch den Akku mit dem Low-Byte, das Y-Register mit dem High-Byte der Anfangsadresse zu laden und das Unterprogramm mit JSR $CBlE aufzurufen: Schon steht der Text auf dem Bildschirm. Auch Fehlermeldungen können auf diese einfache Weise ausgegeben werden (die Möglichkeit hatte ich ja oben schon angekündigt). Die Adressen der einzelnen Fehlernachrichten sind Tabelle 1 zu entnehmen.
Es soll nicht verschwiegen werden, daß es in seltenen Fällen zu einem Problem kommen kann: Weil dieses Unterprogramm auf String-Basis arbeitet, wird der Text als Variable zwischengespeichert. Wurden die Zeiger $37 und $38 (dezimal 55, 56) verändert, so kommt es manchmal dazu, daß irgendwelche unsinnigen Zeichen, aber nicht der gewünschte Text ausgegeben werden. In solch einem Fall müssen die Zeichen konventionell, das heißt über die Kernalroutine $FFD2 ausgegeben werden.
Nachlese
Zum Schluß möchte ich nochmals ein Thema aufgreifen, das ich schon einmal angesprochen hatte — ich spreche von dem Autostartprogramm aus Folge 1. Damals wurde ein Programm abgedruckt, das mir viele Leserzuschriften eingebracht hat. Einige berichteten mir, daß es bei ihnen nicht möglich gewesen sei, diese Routine ordnungsgemäß laufen zu lassen. Da dieses Programm bei mir tadellos arbeitet, kann ich mir die geschilderten Fehler nicht erklären. Dennoch möchte ich diese Thematik nochmals aufgreifen, denn es gab eine Reihe von Anfragen nach einer Version für andere Commodore-Computer.
Um möglichst allen Wünschen gerechtzu werden, erläutere ich nun eine neuartige, kürzere Methode, in der auf sämtliche Programmschutzmaßnahmen, wie zum Beispiel das Abschalten der RESTORE-Taste, verzichtet wurde.
Für diese selbststartenden Programme nutzen wir eine besondere Eigenschaft des Kassettenpuffers aus. Der ist nämlich in der Lage, einen Programmnamen mit einer Länge von bis zu 187 Zeichen zu speichern. Davon werden allerdings nur die bekannten 16 Zeichen auf dem Bildschirm angezeigt; der Rest bleibt im Verborgenen. Da der gesamte Inhalt des Kassettenpuffers als Vorspann zu jedem Programm abgespeichert wird, kann man aber tatsächlich einen sehr viel längeren Filenamen mit dem Programm übertragen. Der Trick ist nun folgender: Neben dem Programmnamen mit einer Länge von 16 Zeichen wird auch ein kleines Maschinenprogramm im Bandpuffer generiert. Dieses wird nach der Beendigung des Ladevorganges von dem geänderten Kernal INPUT-Vektor angesprungen (siehe dazu auch Folge 1). Die kleine Maschinenroutine macht nun nichts anderes, als diesen Zeiger auf seinen Ursprungswert zurückzustellen und den Tastaturpuffer mit den Zeichen LOAD (CR) und RUN (CR) zu füllen. Das ist im Prinzip nichts anderes, als wenn man über die Tastatur SHIFT RUN/STOP eingeben würde, nur mit dem Unterschied, daß dies hier automatisch geschieht.
Im einzelnen wird also Folgendes gemacht: Man generiert einen kurzen Vorspann (ohne Programm) auf Band. Dieser enthält lediglich den präparierten Filenamen. Nachdem dieser Vorspann geladen worden ist, verzweigt der Computer über den INPUT-Vektor in das sich im Bandpuffer befindliche Maschinenprogramm, das wiederum einen neuen Ladebefehl erzeugt, mit dem das eigentliche Programm nachgeladen und automatisch gestartet wird.
Dazu jetzt die einzelnen Schritte, mit denen man einen solchen Vorspann generieren kann:
- Eingabe des Ladeprogramms (Listing 3)
- Programm testen und abspeichern
- Lader starten
- Programmnamen eingeben
- Die auf dem Bildschirm ausgedruckten Zeilen nacheinander mit RETURN ausführen
- Vorspann abspeichern
- Wenn auf dem Bildschirm »SEARCHING« erscheint, muß der beginnende Ladevorgang mit der STOP-Taste abgebrochen werden
- Hauptprogramm nachladen und abspeichern
Listing 4 zeigt das kleine Maschinenprogramm aus den DATA-Zeilen 360 bis 380 des Basic-Laders im Assembler-Format. Wer den C 64 dazu bringen will, das Gleiche zu tun, den möchte ich auf die REM-Zeilen am Ende des Programms aufmerksam machen, in denen die nötigen Änderungen beschrieben sind.
Zwei Anmerkungen wären zu dieser Methode noch zu machen:
Erstens: Die demonstrierte Autostartroutine funktioniert nur im Kassettenbetrieb. Die Floppystation nutzt diesen Puffer nicht, folglich funktioniert auch diese Methode nicht.
Zweitens: Da das Maschinenprogramm an einen festen Speicherplatz im Kassettenpuffer gebunden ist, muß auch der Filename eine feste Länge haben, denn an diesen schließt sich die Maschinenroutine an. Daher ergänzt der Lader den Programmnamen auf 16 Zeichen.
Mit diesem Ausflug in Betriebssystem und Interruptmechanismus ist der VC 20, so meine ich, genug durchleuchtet worden. Ich hoffe, daß Sie in den sechs Folgen dieses Kurses einen Eindruck von den Geschehnissen im Inneren Ihres Computers bekommen haben, so daß dieser Kurs seinen Namen auch wirklich verdient hat.
(Christoph Sauer/ev)100 rem ************************ 110 rem * irq-clock by c.sauer * 120 rem ************************ 130 rem 140 rem 150 rem ####################### 160 rem ## nur fuer speicher ## 170 rem ## groesser 8 kbyte ## 180 rem ####################### 190 fort=0to279:readd:s=s+d:next 200 ifs<>32965thenprint"fehler in datas":end 210 print"{clr}{down}{down}wo soll das programm" 220 print"{down}abgespeichert werden?" 230 v=peek(56)*256-512 240 print"{down}vorschlag: "v; 250 input"{left}{left}{left}{left}{left}{left}{left}{left}";v 260 w2=int(v/256):w1=v-w2*256 270 poke55,w1:poke56,w2 280 restore 290 fort=vtov+279 300 readd 310 if d<0thengosub330 320 poket,d:next:sysv:end 330 onabs(d)goto340,350,360,370,380 340 d=w2:return 350 d=w2+1:return 360 d=w1+208:return 370 d=w1+60:return 380 d=w1+10:return 390 data032,-03,-01,173,003 400 data144,041,128,009,050 410 data141,003,144,162,044 420 data169,160,157,249,017 430 data173,015,144,041,007 440 data157,249,149,202,208 450 data240,162,008,173,134 460 data002,157,022,150,202 470 data208,250,120,169,-04 480 data141,020,003,169,-01 490 data141,021,003,088,032 500 data089,198,076,116,196 510 data162,005,181,097,157 520 data073,003,202,208,248 530 data162,011,189,254,000 540 data157,084,003,202,208 550 data247,165,113,141,097 560 data003,165,071,141,098 570 data003,165,093,141,099 580 data003,165,094,141,100 590 data003,165,162,166,161 600 data164,160,032,135,207 610 data132,094,136,132,113 620 data160,006,132,093,160 630 data036,032,104,222,169 640 data023,133,169,169,018 650 data133,170,160,000,162 660 data002,185,255,000,145 670 data169,200,192,006,240 680 data018,202,208,243,169 690 data058,145,169,136,230 700 data169,208,002,230,170 710 data162,003,208,233,162 720 data005,189,073,003,149 730 data097,202,208,248,162 740 data011,189,084,003,157 750 data254,000,202,208,247 760 data173,097,003,133,113 770 data173,098,003,133,071 780 data173,099,003,133,093 790 data173,100,003,133,094 800 data076,191,234,166,045 810 data134,090,164,046,132 820 data091,032,-05,-02,134 830 data088,134,045,132,089 840 data132,046,166,043,134 850 data095,164,044,132,096 860 data032,-05,-02,134,043 870 data134,166,132,044,132 880 data167,032,184,195,032 890 data051,197,165,166,208 900 data002,198,167,198,166 910 data169,000,168,145,166 920 data096,132,168,024,138 930 data105,049,170,165,168 940 data105,000,168,096,000
****************** INITIALISIERUNG ****************** DER UHRENROUTINE 2000 JSR $20D0 ; BASICPGM. VERSCIEBEN 2003 LDA $9003 ; BILDSCHIRMGROESSE 2006 AND #$80 ; LADEN UND 24,25. 2008 ORA #$32 ; ZEILE ZUSCHALTEN 200A STA $9003 ; ABSPEICHERN 200D LDX #$2C ; ZEILEN LOESCHEN 200F LDA #$A0 ; MIT INVERSEN SPACES 2011 STA $11F9,X; ABSPEICHERN 2014 LDA $900F ; AKTUELLE FARBE 2017 AND #$07 ; WIRD ZUR 2019 STA $9FF9,X; ZEICHENFARBE 201C DEX ; SCHLEIFE 201D BNE $200F 201F LDX #$08 ; FARBE DER UHRZEIT 2021 LDA $0286 ; IST AKTUELLE ZEICH- 2024 STA $9616,X; CHENFARBE 2027 DEX 2028 BNE $2024 202A SEI ; INTERRUPTS VERHIND. 202B LDA #$3C ; VEKTOREN INITIAL. 202D STA $0314 2030 LDA #$20 2032 STA $3015 2035 CLI ; INTERRUPTS ZULASSEN 2036 JSR $C659 ; BASIC-BEFEHL CLR 2039 JMP $C474 ; INS BASIC ZURUECK ****************** UHREN ANZEIGE 203C LDX #$05 ; DIVERSE ZEROPAGE- 203E LDA $61,X ; DATEN IN DEN BAND- 2040 STA $0349,X; PUFFER RETTEN 2043 DEX 2044 BNE $203E 2046 LDX #$0B 2048 LDA $00FE,X 204B STA $0354,X 204E DEX 204F BNE $2048 2051 LDA $71 2053 STA $0361 2056 LDA $47 2058 STA $0362 205B LDA $5D 205D STA $0363 2060 LDA $5E 2062 STA $0364 2065 LDA $A2 ; UHRZEIT (A2,A1,A0) 2067 LDX $A1 ; HOLEN 2069 LDY $A0 206B JSR $CF87 ; IN STRING VERWANDELN 206E STY $5E ; UNTERROUTINE VOR- 2070 DEY ; BEREITEN 2071 STY $71 2073 LDY #$06 ; 6 ZIFFERN DARSTELLEN 2075 STY $5D 2077 LDY #$24 2079 JSR $DE68 ; ZIFFERN BERECHNEN 207C LDA #$17 ; BILDSCHIRMSPEICHER- 207E STA $A9 ; ADRESSE DER ZIFFERN 2080 LDA #$12 2082 STA $AA 2084 LDY #$00 ; ZAEHLER FUER ZIFFERN 2086 LDX #$02 ; ZAEHLER FUER ':' 2088 LDA $00FF,X; ZIFFERN HOLEN 208B STA ($A9),X; IN BILDSCHIRMSPEICHER 208D INY ; SCHLEIFE 1 208E CPY #$06 ; ALLE ZIFFERN ? 2090 BEQ $20A4 ; JA, DANN ENDE 2092 DEX 2093 BNE $2088 2095 LDA #$3A ; DOPPELPUNKT LADEN 2097 STA ($A9),Y; UND ABSPEICHERN 2099 DEY ; SCHLEIFE 2 209A INC $A9 ; ZAHLER KORREKTUR 209C BNE $20A0 209E INC $AA ; ZAHLER KORREKTUR 20A0 LDX #$03 ; NEUER ':' ZAHLER 20A2 BNE $208D 20A4 LDX #$05 ; ALLE GERETTETEN 20A6 LDA $0349,X; ZEROPAGE VARIABLEN 20A9 STA $61,X ; WIEDER ZURUECK- 20AB DEX ; SPEICHERN. 20AC BNE $20A6 20AE LDX #$0B 20B0 LDA $0354,X 20BE STA $00FE,X 20B6 DEX 20B7 BNE $20B0 20B9 LDA $0361 20BC STA $71 20BE LDA $0362 20C1 STA $47 20C3 LDA $0363 20C6 STA $5D 20C8 LDA $0364 20CB STA $5E 20CD JMP $EABF ; ZUR INTERRUPTROUTINE ****************** BASICPROGRAMM VER- ****************** SCHIEBEN 20D0 LDX $2D ; ENEADRESSE DES PGMS 20D2 STX $5A ; HOLEN 20D4 LDY $2E 20D6 STY $5B 20D8 JSR $210A ; 49 ADDIEREN 20DB STX $58 ; UND ABSPEICHERN 20DD STX $2D 20DF STY $59 20E1 STY $2E 20E3 LDX $2B ; PGM. ANFANGSADRESSE 20E5 STX $5F ; HOLEN UND ABSP. 20E7 LDY $2C 20E9 STY $60 20EB JSR $210A ; 49 ADDIEREN 20EE STX $2B ; UND ABSPEICHERN 20F0 STX $A6 20F2 STY $2C 20F4 STY $A7 20F6 JSR $C3B8 ; PROGRAMM VERSCHIEBEN 20F9 JSR $C533 ; PGM.ZEILEN BINDEN 20FC LDA $A6 20FE BNE $2102 2100 DEC $A7 ; 0 AN DEN NEUEN PGM.- 2102 DEC $A6 ; ANFANG SPEICHERN 2104 LDA #$00 2106 TAY 2107 STA ($A6), 2109 RTS ****************** ADDITIONSROUTINE 210A STY $A8 210C CLC ; 16-BIT ADDITION 210D TXA ; VORBEREITEN 210E ADC #$31 ; DEZ 49 ADDIEREN 2110 TAX 2111 LDA $A8 ; HIGH-BYTE LADEN 2113 ADC #$00 ; UEBERTRAG ADDIEREN 2115 TAY 2116 RTS
100 print"{clr}**********************" 110 print"* autostart fur c 64 *" 120 print"* und vc 20 (version *" 130 print"* fuer vc 20). * 140 print"*--------------------*" 150 print"* die anweisungen des*" 160 print"* programms muessen *" 170 print"* genau befolgt wer- *" 180 print"* den !!" 190 print"**********************" 200 input"filename:";f$:fort=1to16-f:f$=f$+" ":next 210 fort=1to16:poke929+t,asc(mid$(f$,t,1)):next 220 fort=1to24:readd:poke945+t,d:next 230 print"{clr}{rvon}folgende zeilen nach-" 240 print"{rvon}einander mit <return>" 250 print"{rvon}durchgehen." 260 print"{down}poke43,24:poke44,3:poke45,60:poke46,3:clr" 270 print"{down}{down}fort=930to970:f$=f$+chr$(pE(t)):nE" 280 print"{rvon}{down}** achtung **" 290 print"{rvon}programmcassette be-" 300 print"{rvon}reit halten. unter-" 310 print"{rvon}brechen nach best-" 320 print"{rvon}aetigung der naech-" 330 print"{rvon}sten zeile nicht mit" 340 print"{rvon}<run/stop> !!!" 350 print"{down}pO804,81:pO805,3:savef$,1,1{home}{down}" 360 data162,014,142,036,003,162,242,142,037 370 data003,162,009,134,198,189,244,237,157 380 data119,002,202,016,247,096 1000 rem aenderungen fuer c 64: 1002 rem in zeile 360 sind die 1005 rem daten 014 und 242 durch 1007 rem 087 und 241 zu ersetzen 1010 rem in zeile 370 sind die 1020 rem daten 244,237 durch 1030 rem 231,236 zu ersetzen
0351 LDX #$0E ; INPUT-VEKTOR REKON- 0353 STX $0324 ; STRUIEREN. 0356 LDX #$F2 0358 STX $0325 035B LDX #$09 ; NEUN ZEICHEN 035D STX $C6 ; IN TATATURCOUNTER 035F LDA $EDF4,X ; ZEICHEN AUS ROM HOLEN 0362 STA $0277,X ; UND IN TASTATURPUFFER 0365 DEX 0366 BPL $035F ; ALLE NEUNE ? 0368 RTS ; JA, DANN ENDE