VC 20
Kurs

Der gläserne VC 20 – Teil 2

In der ersten Folge haben wir uns hauptsächlich mit der Basic-Verwaltung befaßt. Dabei tauchte immer wieder der Begriff Zeropage auf, mit dem wir uns heute beschäftigen wollen.

Die Zeropage — oder zu Deutsch die Seite Null — ist in Maschinensprache besonders einfach zu handhaben. Als Seite bezeichnet man im übrigen immer ein Paket von jeweils 256 Byte. So entspricht Seite 0 den Adressen 0-255, Seite 1 den Adressen 256-511 und so weiter.

Eine Zeropageadressierung, zum Beispiel LDA $42, benötigt nur zwei Byte, eine absolute Adressierung, zum Beispiel LDA $1234, hingegen drei Bytes im Speicher. Damit verbunden ist auch die Bearbeitungsgeschwindigkeit eines Maschinenprogramms. Denn die Zeropageadressierung ist schneller als die entsprechende Drei-Byte-Methode. Bei kleineren Programmen in Assembler fällt dieser Aspekt zwar nicht so sehr ins Gewicht, bei sehr umfangreichen Routinen, (wie zum Beispiel beim Basic-Interpreter) spielt die Adressierungsart jedoch eine größere Rolle.

In der Seite 0 legt der Computer also insbesondere die Daten ab, die er oft benötigt, wie zum Beispiel Vektoren, Parameter etc. Die komplette Liste der Adreßbelegung zeigt Tabelle 1.

Wir wollen es jedoch nicht nur mit der Aufstellung alleine bewenden lassen. Die interessantesten Lokationen möchte ich hier herausgreifen und besprechen. Beginnen wir also mit den ersten drei Bytes im Speicher:

Der USR-Vektor

Adresse 0,1,2: Über die USR-Funktion kann der Benutzer eigene mathematische Routinen, die nicht im Basic implementiert sind, in seine Programme einbinden. Dieses Kommando ruft ein Maschinenprogramm auf, dessen Startadresse vorher in den Speicherzellen 1 und 2 abgelegt wurde. Wer sich dieses Befehls nicht bedient, hat drei Zeropagespeicherzellen zur freien Verfügung, die er für eigene Maschinenprogramme nutzen kann. Die Syntax der USR-Funktion entspricht derjenigen normaler Basic-Funktionen wie zum Beispiel FRE, POS, SIN, COS, TAN etc.: A= USR (B) oder PRINT USR (B).

Der Vorteil gegenüber dem SYS-Befehl liegt in der Möglichkeit Parameter zu übergeben. In unserem Beispiel wird der Benutzerroutine die Variable B übergeben. Der errechnete Zahlenwert kann dann seinerseits wieder einer Variablen zugewiesen werden.

Wie kann man sich in einem Maschinenprogramm die eingegebene Zahl beschaffen? Nun, alle Basic-Variablen werden im Fließkommaformat abgespeichert, eine Ausnahme bilden nur die Integer-Variablen. Dabei werden die Zahlen vom Computer nach einem bestimmten Verfahren binär verschlüsselt, so daß eine Zahl mit acht Nachkommastellen und einem Exponenten in nur fünf Bytes Platz findet.

Zwischengespeichert wird das Ergebnis in besonderen Fließkomma-Akkumulatoren (Adresse 97-104). Mit Hilfe eines Unterprogramms aus dem Basic-Interpreter kann es von dort aus abgerufen werden, wobei es in eine Integerzahl (also ein Wert zwischen 0 und 65536) umgewandelt wird.

Aufgerufen wird die Routine mit JSR $D7F7. Der 2-Byte-Wert steht dann in den Zeropagestellen $14 und $15 (Hexadezimal) zur Verfügung. Da diese Adressen vom Interpreter oft benutzt werden, ist es ratsam die Zahlen in andere Register zu übertragen (in welche, darauf kommen wir später noch zu sprechen).

Das Gegenstück zu dem eben gezeigten Unterprogramm stellt die Routine »2-Byte in Fließkomma« dar. Das Low-Byte wird in das y-Register geladen, das höherwertige Byte muß in den Akku. Dann wird die Routine mit JSR $D391 gestartet, wobei die USR-Variable (in unserem Beispiel A) die Daten erhält.

Speicher ausgedeutet — die Basic-Zeiger

Adresse 43 — 56: Diese Adressen spielen, wie wir schon im ersten Teil gesehen haben, bei der Verwaltung von Basic-Programmen und -Variablen eine zentrale Rolle. Über sie erfolgt die Trennung zwischen Programm und den einzelnen Variablentypen. Auch für das Speichern und Laden von Programmen liefern sie die Basisdaten. Die einzelnen Funktionen können sie Tabelle 1 entnehmen.

Dezimal Hexadezimal Bemerkung
0-2 00 - 02 USR-Sprungvektor (Normal: JMP $D248)
3-4 03 - 04 Vektor für Unterprogramm ’Fließkomma nach Integer’
5-6 05 - 06 Vektor für Unterprogramm ’Integer nach Fließkomma’
7 07 Suchzeichen (sucht ’:’ oder Zeilenende)
8 08 Hochkommaflag
9 09 Spaltenspeicher beim TAB-Befehl
10 0A LOAD/VERIFY Flag (0 = LOAD/1 = VERIFY)
11 OB Eingabepufferzeiger (Anzahl der Elemente)
12 0C Flag für Dim (enthält die laufende Variable)
13 0D Variablentyp (0 = Numerisch/128 = String)
14 0E Numerische Variable (0 = Fließkomma/128 Integer)
15 0F Flag bei DATA und LIST
16 10 Flag für FN
17 11 Eingabeflag (0 = INPUT/64 = GET/152 = READ)
18 12 Vorzeichen bei ATN
19 13 Aktuelles Ein-/Ausgabegerät
20-21 14 - 15 Integerwert (zum Beispiel Zeilennummer oder Zwischenerg.)
22 16 Zeiger im Stringstapel
23-24 17 - 18 Zeiger auf den zuletzt verwendeten String
25-33 19 - 21 Stringstapel
34-37 22 - 25 Speicher für div. Hilfszeiger
38-42 26 - 2A Speicherbereich bei einer Multiplikation
43-44 2B - 2C Zeiger auf Beginn des Basic-Speicherbereichs
45-46 2D- 2E Beginn der Variablen (= Ende des Programms)
47-48 2F - 30 Beginn der Arrays (= Ende der Variablen)
49-50 31 - 32 Zeiger auf Ende der Arrays
51-52 33 - 34 Zeiger auf Beginn der Strings
53-54 35 - 36 Hilfszeiger für Strings
55-56 37 - 38 Zeiger auf Basic-Ende
57-58 39 - 3A Momentane Basic-Zeilennummer
59-60 3B - 3C Vorherige Basic-Zeilennummer
61-62 3D- 3E Zeiger auf lfd. Basic-Befehl (für CONT)
63-64 3F - 40 Momentane Zeilennummer für DATA
65-66 41 - 42 Momentane Adresse einer DATA-Zeile
67-68 43 - 44 Zeiger auf Element in DATA-Zeile und INPUT-Vektor
69-70 45 - 46 Momentaner Variablenname
71-72 47 - 48 Adresse der momentanen Variablen
73-74 49 - 4A Zeiger für FOR/NEXT Variable
75-76 4B - 4C Zwischenspeicher für Programmzeiger (bei SAVE)
77 4D Speicher für Vergleichssymbole
78-79 4E - 4F Zeiger für FN
80-83 50 - 53 Verschieden genutzter Speicherbereich (Hauptsächlich für Strings)
84-86 54 - 56 FN-Sprungvektor (ähnlich USR)
87-96 57 - 60 Feld für diverse arithmetische Zwecke (Arithmetik Akku #3 und #4)
97 61 Fließkommaakku #1 : Exponent
98 - 101 62 - 65 Fließkommaakku #1 : Matisse
102 66 Fließkommaakku #1 : Vorzeichen
103 67 Zeiger für Polynomauswertung
104 68 Überlauf von Akku #1
105 69 Fließkommaakku #2 : Exponent
106 - 109 6A - 6D Fließkommaakku #2 : Mantisse
110 6E Fließkommaakku #2 : Vorzeichen
111 6F Vergleichsbyte der Vorzeichen von Akku #1 und Akku #2
112 70 Rundung für Akku #1
113 - 114 71 - 72 Länge des Kassettenpuffers
115 - 138 73 - 8A CHRGET-Routine
139 - 143 8B - 8F RND-Wert als Fließkommazahl
144 90 Statusflag ST
145 9)
150 96 Kassetten EOT (End of Tape) erhalten
151 97 Zwischenspeicher für Register
152 98 Anzahl der offenen Dateien
153 99 Eingabegerät (Normal = 0 : Tastatur)
154 9A Ausgabegerät (Normal = 3 : Bildschirm)
155 9B Paritätsbyte)
150 96 Kassetten EOT (End of Tape) erhalten
151 97 Zwischenspeicher für Register
152 98 Anzahl der offenen Dateien
153 99 Eingabegerät (Normal = 0 : Tastatur)
154 9A Ausgabegerät (Normal = 3 : Bildschirm)
155 9B Paritätsbyte vom Band (Prüfsumme)
156 9C Flag für Byte erhalten (von Band)
157 9D Flag für Direktmodus (= 128) oder Programm (= 0)
158 9E Band: erster Teil - Prüfsumme
159 9F Band: zweiter Durchlauf - Prüfsumme
160 - 162 A0- A2 Interne Uhr (Stunde, Minute, Sekunde)
163 A3 Bitzähler für serielle Ausgabe
164 A4 Zähler für Band
165 A5 Startsynchronisation bei Kassetten
166 A6 Zeiger im Bandpuffer
167 - 171 A7- AB Flags für Schreiben/Lesen bei Band
172 - 173 AC - AD Zeiger auf Kassettenpuffer und für scrolling
174 - 175 Ae- AF Zeiger auf Programmende bei LOAD
176 - 177 B0 - Bl Bandzeitkonstanten
178 - 179 B2 - B3 Startadresse des Bandpuffers
180 B4 Bitzähler für Band
181 B5 Band oder RS232: nächstes zu sendendes Bit
182 B6 Übertragungsspeicher für RS232
183 B7 Länge des Filenamens
184 B8 Logische Filenummer
185 B9 Sekundäradresse
186 Ba Gerätenummer
187 - 188 BB- BC Zeiger auf Filenamen
189 BD Ein-/Ausgabespeicher (für seriell)
190 BE Blockzähler für Band
191 BF Puffer für serielle Ausgabe
192 CO !Bandmotor Flag
193 - 194 Cl - C2 Startadresse für Ein-/Ausgabe
195 - 196 C3- C4 Endadresse für Ein-/Ausgabe
197 C5 gedrückte Taste (momentaner Wert im Tastaturmatrixcode)
198 C6 Anzahl der gedrückten Tasten (im Tastaturpuffer)
199 C7 Bildschirm: negative Anzeige
200 C8 Zeiger auf Zeilenende bei Eingabe
201 C9 Cursorzeile
202 CA Cursorspalte
203 CB Welche Taste? (64 = keine)
204 CC Cursorblinken
205 CD Cursor Blinkzähler
206 CE Zeichen unter dem Cursor
207 CF Flag für Cursorblinken (0 = An/1 = Aus)
208 DO Eingabe von Bildschirm/oder Tastatur
209 - 210 D1 - D2 Start der aktuellen Bildschirmzeile
211 D3 Position des Cursors in der aktuellen Bildschirmzeile
212 D4 Flag für Cursor (0 = Direkt, sonst programmiert)
213 D5 Länge der Bildschirmzeile (21 od. 42 od. 63 od. 84)
214 D6 Cursorzeile
215 D7 Zeiger für diverse Zwecke
216 D8 Anzahl der Inserts
217 - 242 D9- F2 High Bytes der Bildschirmzeilenanfänge
243 - 244 F3 - F4 Zeiger im Farbspeicher
245 - 246 F5 - F6 Zeiger in der Tastaturdekodiertabelle
247 - 248 F7 - F8 Zeiger auf RS232 Eingabepuffer
249 - 250 F9 - FA Zeiger auf RS232 Ausgabepuffer
251 - 254 FA - FE Freie Zeropageadressen
255 (- 266) FF - 10A Zwischenspeicher für Fließkomma nach ASCII
Tabelle 1. Die Adreßbelegung der Zeropage beim VC 20

Eine interessante Gebrauchsmöglichkeit ergibt sich durch die Memoryroutine. Dies ist ein kurzes Basic-Programm, welches die Aufgabe hat, den von Programmen, Variablen, Strings und Arrays belegten Speicherplatz festzustellen (Listing 1):

Wie man nach dem Starten des Programms sehen kann, erhält man die Werte, indem man die Zeiger — nachdem sie in Dezimalzahlen umgewandelt worden sind — voneinander subtrahiert. Den Speicherplatz, der durch Variablen belegt ist, erhält man beispielsweise durch Subtraktion des Hilfszeigers, »Beginn der Variablen« (45/46) von dem Zeigerpaar »Beginn der Arrays« (47/ 48).

Gerade bei stark limitiertem Speicherplatz (wie zum Beispiel in der Grundversion) kann dieses Hilfsprogramm Aufschluß über die momentane Speicherverteilung geben.

Eine weitere Verwendung ergibt sich durch geschicktes Manipulieren der Speicherzellen, so daß es möglich wird, mehrere Programme gleichzeitig um Speicher unterzubringen.

Diese Aufgabe erfüllt das recht komfortable Programm »Basic-Switch« (Listing 2); zunächst aber die Grundlagen: Die besagten Zeiger stecken, wie bereits im ersten Teil unseres Kurses beschrieben, den Adreßbereich für Basic-Programme ab. Die ersten vier Bytes aus dem Zeropagekomplex (Adresse 43 bis 46) grenzen das Programm nach oben und unten ab. Die daran folgenden Variablen sind in ihrer Ausdehung ebenfalls durch ein Zeigerpaar (Adresse 55/56) limitiert. Der Bereich jenseits dieser Markierung ist für Basic tabu. Dort ist also Platz für Maschinenprogramme oder ähnliches, denn ein Überschreiben durch Basic-Variable ist nicht möglich.

Will man nun mehrere Basic-Programme im Speicher versammeln, so muß man sie nur durch verstellen der Zeiger eindeutig voneinander trennen.

Wie in Bild 1 zu sehen ist, grenzen die Programmblöcke direkt aneinander. Unter Programmblock ist hier die Einheit von Programm und einem sich daran anschließenden Variablenbereich zu verstehen. Durch Umschalten der Zeiger wird dann immer der gewünschte Programmblock eingeblendet.

Bild 1. Durch π S1 wird der Programmblock Nr. 1 eingeblendet

Nützlich kann diese Art der Speicheraufteilung sein, wenn man umfangreiche Programme erstellen möchte und es nötig wird, andauernd Hilfsprogramme (zum Beispiel Grafikeditor oder ähnliches) nachzuladen. Durch Basic-Switch kann hier viel Arbeit eingespart werden.

Diese Routine liegt wieder in zweierlei Form ausgedruckt vor. Zum einen als Assemblerlisting für solche, die tiefer in das Programm einsteigen möchten (Listing 3). Und zum anderen als Basic-Lader. Das Programm ist grundsätzlich auf jeder Ausbaustufe lauffähig. Sinnvoll wird es jedoch erst bei größerem Speicher. Es ist in bewährter Weise über das Befehlswort PI in den Interpreter eingebunden.

Nachdem die Routine per SYS gestartet worden ist, (der Lader übernimmt auch diese Arbeit), muß der erste Programmbereich initialisiert werden, das heißt der Benutzer nimmt die größenmäßige Einteilung des Speichers vor. Man muß sich jedoch schon im voraus darüber im klaren sein, wie lang das Programm inklusive Variablen sein soll, denn eine nachträgliche Änderung ist nicht möglich.

Die Syntax für die Initialisierung lautet: π E »Länge«. Danach steht der Speicherbereich abzüglich zwei Bytes für Basic zur Verfügung. Der erste so eingerichtete Programmblock hat die Nummer 1, der zweite 2 und so fort. Nachdem ein zweiter Programmbereich eingerichtet wurde, kann mit π S »Nummer« zwischen den Blöcken umgeschaltet werden, wobei die Routine jedoch jedesmal die Variable löscht.

Um dem Benutzer die Orientierung zu erleichtern, druckt das Maschinenprogramm nach jedem Umschalten die ersten REM-Zeilen aus. Daher ist es ratsam, jedem Programm eine Kopfzeile mit den wichtigsten Informationen zu geben.

Nach dieser etwas umfangreichen Erläuterung kehren wir nun zur Beschreibung der Zeropage zurück.

Adresse 59,60: Diese Speicherzellen enthalten die laufende Zeilennummer eines Basic-Programms. Sollte es im Programmlauf unterbrochen werden, hat man hier die Möglichkeit, die Zeilennummer nachzulesen.

Adresse 63 — 66: Mit Hilfe dieser Zeiger läßt sich ein zeilennummerngesteuertes RESTORE realisieren. Die ersten zwei Bytes enthalten die momentane Zeilennummer für die DATA-Abfrage. Dies ist sehr praktisch, denn damit kann man jederzeit feststellen, in welcher DATA-Zeile man sich befindet. Das zweite Doppelbyte bildet die Adresse für die nächste DATA-Zeile. Man hat also zwischen Zeilennummer und der absoluten Adresse einer DATA-Zeile zu unterscheiden.

Das nun folgende kurze Maschinenprogramm (Listing 4 und 5) ermöglicht ein Zeilennummern-RESTORE. Man kann damit zwar nicht gezielt auf ein Element in einer DATA-Zeile zugreifen, aber zumindest auf eine bestimmte Zeile.

Was auch im Zusammenhang mit dem Maschinensprachkurs von Interesse sein kann, ist der Aufbau dieser kurzen Routine. Denn durch geschicktes Nutzen von Unterprogrammen aus dem ROM kann man nämlich viel Speicherplatz sparen. Aus diesem Grunde werde ich in einer späteren Folge Interpreter- und Betriebssystemunterroutinen näher beleuchten.

Das vorliegende Maschinenprogramm benötigt genau 43 Byte und liegt am Ende des verfügbaren Basic-Speichers. Die Start- und Endadresse wird vom Lader angegeben. Aufgerufen wird der Zeilenrestore mit: SYS »Startadresse«, »Zeilennumer«.

Die DATA-Zeiger werden dann auf die angegebene Zeile zurückgestellt, was sowohl im Direktmodus, als auch vom Programm aus erfolgen kann.

Zustandsbeschreibung: Das Statusflag

Adresse 144: Dieses Statusflag ist auch von Basic aus über die STATUS beziehungsweise ST-Anweisung abfragbar. Es liefert das Computerstatus-Byte, dessen Inhalt aufgrund der letzten Input-Output-Operation gesetzt wurde. Bezogen auf den Kassettenport liefert es nach Bild 2 bestimmte Meldungen. Die Informationen über die geladenen Programme werden binär wiedergegeben. Die Null signalisiert ein ordnungsgemäß geladenes Programm. 32 hingegen bedeutet, daß ein Prüfsummenfehler vorliegt. Es ist aber auch möglich, daß der Computer mehrere Meldungen in dieses Byte packt, beispielsweise 52 = 32 + 16 + 4 : Hier wurde ein kurzer Block geladen, jedoch ist die Prüfsumme falsch und ein fataler Ladefehler liegt vor.

Bild 2. Darstellung des Statusbytes, das verschiedene Fehlermeldungen enthalten kann. Die Bits 0, 1 und 6 werden bei Bandbetrieb nicht genutzt. Daher sind sie auf Null.

An dieser Stelle ist es angebracht, sich näher mit dem Aufzeichnungsverfahren zu beschäftigen (Bild 3).

Bild 3. Darstellung eines auf Band abgespeicherten Programms

So kommen Programme aufs Band

Jeder Abspeichervorgang beginnt mit dem Header. Dieser Kopf besteht aus dem Vorspann (das ist ein etwa acht Sekunden langer Pfeifton) und dem eigentlichen Programmkopf. Dieser enthält vier wichtige Informationen, nämlich über Programmtyp, Startadresse, Endadresse und Programmname. Diese Daten sind ebenfalls im Bandpuffer zu finden und können von dort abgerufen werden.

Das erste Byte (Adresse 828) gibt Auskunft über den Headertyp. Eine 1 zeigt an, daß es sich um ein Programm handelt, das verschoben geladen werden kann, also auch an eine andere Stelle, als die, von der aus es abgespeichert wurde. Das Gegenstück dazu ist die absolute Lademethode (Headertyp 3), die bereits in der ersten Folge meiner Abhandlung vorgestellt wurde. Gemeint ist LOAD”..”,1,1. Die Sekundäradresse 1 signalisiert dem Computer, daß er das Programm (unabhängig von den Zeigern 43, 44) wieder in den gleichen Adreßbereich laden soll. Um Verwechslungen vorzubeugen, ist es wichtig, Headertyp und Sekundäradresse zu unterscheiden. Für den Headertyp gibt es drei Möglichkeiten:
1: Laden mit Verschiebelader (die Anfangsadresse wird durch den Zeiger 43 und 44 bestimmt).
2: Ein File — also Daten aus Variablen — wurde abgespeichert. Die Unterscheidung ist wichtig, denn Daten können nicht mit LOAD geladen werden.
3: Ein Programm ist absolut zu laden.

Die nächsten vier Bytes geben die Anfangs- und Endadresse des geladenen Files an. Mit der Endadresse hat es eine besondere Bewandtnis. Sie wird nämlich nach korrektem Laden der Zeropage (Adresse 45, 46) übergeben. Bei Load Error geschieht dies nicht; PRINT FRE(0) zeigt dann die volle Bytezahl an, obwohl sich ein Programm im Speicher befindet. Man darf das Programm — das vielleicht nur einen geringfügigen Fehler hat —, dann nicht starten, weil die Variablen dieses überschreiben würden. Abhilfe schafft in diesem Fall
POKE 45,PEEK(831):POKE 46,PEEK (832):CLR.
Die Werte werden damit von »Hand« übertragen und ein normaler Programmablauf ist in den meisten Fällen wieder möglich.

Jetzt aber wieder zurück zu Bild 3: Nachdem der Vorspann und der Programmkopf vor einem Trennzeichen (dies ist ein ganz kurzer Pieps) auf Band geschrieben worden ist, wird der Header gleich noch einmal abgespeichert.

Dies ist eine Eigenheit des Commodore-Systems, das der Datensicherheit dient. Denn nachdem der Programmkopf 1 geladen hat, vergleicht er in einem zweiten Durchgang das bisher geladene (welches sich ja schon im Speicher befindet) mit Programmkopf 2. Eine Abweichung veranlaßt den Computer eine Fehlermeldung auszugeben, in ganz schweren Fällen wird der Ladevorgang gleich ganz unterbrochen. Diese Verfahrensweise gilt nicht nur für den Programmkopf, sondern auch für Programme und Daten — sie alle werden doppelt abgespeichert.

Nachdem also der Programmkopf erkannt worden ist, zeigt der Computer den Filenamen an, und das eigentliche Programm (oder die Daten) wird geladen. Ihnen ist wiederum ein kurzer Vorspann vorangestellt. Der Endblock besteht aus Endmarkierung und einem Nachspann. Er zeigt dem VC 20 das Ende des geladenen Programms an, womit der Ladevorgang beendet ist.

Nach diesem Exkurs zum Kassettenaufzeichnungsformat nun wieder zur Zeropage.

Die STOP-Taste mit Sonderfunktion

Adresse 145: Mit Hilfe dieser Speicherstelle kann man den Zustand der STOP- und der linken SHIFT-Taste abfragen:
Wert =
253: Linke SHIFT-Taste gedrückt
254: STOP-Taste gedrückt
255: Keine der beiden gedrückt

Wenn man das Low-Byte des STOP-Vektors (Adresse 808) auf 114 — statt ursprünglich 112 — setzt, so ist man in der Lage, diese Taste von Basic aus abzufragen, ohne das laufende Programm anzuhalten. Ihr kann dann in der Routine eine besondere Funktion zugewiesen werden, beispielsweise Anhalten des Programmablaufs für eine bestimmte Zeit.

Programmiert oder direkt? Das ist hier die Frage

Adresse 157: Dieses Flag kann nur zwei Zustände annehmen: Entweder 0 oder 128. Ist der Inhalt der Speicherstelle Null, dann arbeitet der Computer gerade ein Programm ab; bei 128 befindet er sich im Direktmodus.

Diese Zeropageadresse kann dann von Bedeutung sein, wenn man eigene Basic-Kommandos definieren will (vergleiche Teil 1).

Zum Beenden einer solchen Routine gibt es zwei Möglichkeiten: Entweder kam der Programmbefehl aus dem Direktmodus. In diesem Fall springt man in die Interpreterschleife (JMP $C474) zurück, damit der Computer READY meldet. Kam das Kommando aber aus einem Basic-Programm, so muß man mit JMP $0079 in die CHRGOT-Routine zurückspringen, damit kein Bereitschaftszeichen ausgegeben wird. Mit Hilfe dieses Flags trifft man hier die Unterscheidung.

Nun wenden wir uns einem anderen Kapitel zu, der Cursorverwaltung. Über die Zeropage kann jederzeit die Cursorposition festgestellt werden. Die Speicherstelle 211 zeigt die Spalte, 214 die Zeile, in der sich der Blinker derzeit befindet. Korrekte Werte liefern die zwei Adressen aber nur innerhalb eines Programms.

Interessanter ist die umgekehrte Verfahrensweise, nämlich das Setzen des Cursors an eine beliebige Bildschirmposition. Dazu müssen allerdings vier Adressen geändert werden. Glücklicherweise nimmt uns eine Unterroutine aus dem Betriebssystem diese Arbeit ab.

Gewußt wo — der Cursor

In Maschinensprache wird das X-Register mit dem Zeilenwert, das Y-Register mit dem Spaltenwert geladen. Danach ruft man mit JSR $E50C das Unterprogramm auf.

Von Basic aus werden die zwei CPU-Register über die Adressen 781 und 782 geladen (warum das so ist, sehen wir in der nächsten Folge): POKE 781,»Zeile«: POKE 782,»Spalte«: SYS 58636.

Diese Verfahrensweise ist, wie man sieht, wesentlich komfortabler als das Hantieren mit den Steuerzeichen.

Eine andere Zeropageadresse kann uns zu einer weiteren Erleichterung verhelfen. Jeder kennt das Problem, den Cursor innerhalb von Anführungszeichen (»Gänsefüßchen«) zu bewegen. Es werden nur Steuerzeichen ausgedruckt, eine Bewegung des Zeigers findet nicht statt.

Dieses Manko läßt sich durch eine kurze Maschinenroutine beheben:
LDA #$00 ; lösche Hochkommaflag
STA $D4 ; speichere es ab
JMP $FEAD ; springe in die NMI-Routine

Ausgelöst wird diese Routine durch Drücken der RESTORE-Taste. Dazu muß vorher der NMI-Vektor (eine nähere Beschreibung nächstes Mal) auf den Beginn unserer Routine gestellt werden. Da die sehr kurz ist, legen wir sie im Kassettenpuffer ab. Dort ist sie solange sicher verwahrt, bis man etwas laden oder abspeichern will. Hier zunächst der Einzeiler, der die Routine in den Bandpuffer generiert:
10 FOR T=828 TO 834: READD: POKE T,D:NEXT: DATA 169, 0, 133, 212, 76, 173, 254: POKE 792, 60: POKE 793, 3

Arbeitet man im Hochkommamodus, so kann man durch Drücken der RESTORE-Taste diesen Betriebszustand abschalten, der Cursor kann dann wieder ganz normal bewegt werden.

Abschließend möchte ich noch sagen, welche Zeropageadressen ohne Komplikationen von eigenen Maschinenroutinen genutzt werden können, was im hohen Maße vom Verwendungszweck abhängt.

Nicht verändert werden dürfen solche Speicherstellen, die vom Betriebssystem gebraucht werden, also die Adressen 192 bis 244 und 160 bis 162. Hat das Maschinenprogramm keine Verbindung zu Basic, so können alle Speicherstellen zwischen 0 und 138 überschrieben werden, weil sich hier nur Basic-Parameter befinden. Eng wird der Platz für die sogenannten Utilities — also für Basic-Ergänzungen oder Hilfsprogramme. Will man Daten nur zwischenspeichem, kann man sie in den Bereich zwischen Adresse 147 bis 159 packen. Dort sind sie bis zum nächsten Lade- oder Abspeichervorgang sicher.

Dauerhaft können Daten nur am Anfang und am Ende der Zeropage abgelegt werden. Dazu gehören Adresse 0 bis 2 und 245 bis 254.

Genau diese werden aber auch von käuflichen Basic-Erweiterungen gebraucht, wodurch es zu Komplikationen kommen kann. Also Vorsicht, erst durch vorheriges Probieren testen, ob man sie benutzen darf!

Damit möchte ich für heute schließen. In der nächsten Folge werden schwerpunktmäßig der Tastaturpuffer, die Basic-Vektoren und die Kernalvektoren behandelt.

(Christoph Sauer/ev)
10000 rem *** memory dump ***
10005 rem
10010 a=46:b=44:gosub10080:print"programm  :";x
10020 a=48:b=46:gosub10080:print"variablen :";x
10030 a=50:b=48:gosub10080:print"arrays    :";x
10040 a=56:b=52:gosub10080:print"string    :";x
10050 a=56:b=44:gosub10080:print"speicher  :";x
10060 print"bytes free:";fre(0)
10070 end
10080 x=peek(a)*256+peek(a-1)-peek(b)*256-peek(b-1)
10090 return
Listing 1. »Memory Dump«
10 rem *************************
20 rem ****                 ****
30 rem ****   basicswitch   ****
40 rem ****                 ****
50 rem ****   by c. sauer   ****
60 rem ****                 ****
70 rem *************************
80 fort=0to286:readd:p=p+d:next:ifp<>38021thenprint"{down}{down}fehler !!":end
90 poke55,0:poke56,peek(56)-2:clr:a=peek(56)
100 restore:fort=a*256toa*256+286
110 readd
120 ifd=-1thend=a
130 ifd=-2thend=a+1
140 poket,d
150 next
160 print"{clr}{down}{down}fertig. start mit":print"{down}sys"a*256+259
170 print"{down}{down}aktivierung mit 'a'"
180 geta$:ifa$=""then180
190 ifa$<>"a"thenend
200 sysa*256+259
210 data230,122,208,002,230
220 data123,032,121,000,201
230 data255,240,003,076,121
240 data000,032,115,000,201
250 data069,208,003,076,168
260 data-01,201,083,240,003
270 data076,008,207,032,155
280 data215,165,101,240,004
290 data228,251,144,003,076
300 data072,210,134,252,032
310 data133,-01,166,252,202
320 data138,010,010,010,168
330 data162,000,185,032,-02
340 data149,043,200,232,224
350 data004,208,245,162,000
360 data185,032,-02,149,055
370 data200,232,224,002,208
380 data245,165,252,133,250
390 data032,096,198,032,142
400 data198,032,115,000,240
410 data006,201,143,240,016
420 data208,245,160,001,177
430 data122,208,239,200,192
440 data002,208,247,076,116
450 data196,032,215,202,165
460 data122,164,123,032,030
470 data203,240,241,166,250
480 data202,138,010,010,010
490 data168,162,000,181,043
500 data153,032,-02,200,232
510 data224,004,208,245,162
520 data000,181,055,153,032
530 data-02,200,232,224,002
540 data208,245,096,032,133
550 data-01,032,115,000,032
560 data138,205,032,184,209
570 data165,253,133,043,165
580 data254,133,044,165,101
590 data024,101,253,133,253
600 data165,100,101,254,133
610 data254,165,251,133,252
620 data230,251,165,043,208
630 data002,198,044,198,043
640 data032,248,227,165,254
650 data205,132,002,144,016
660 data208,007,165,253,205
670 data131,002,144,007,169
680 data012,160,205,032,030
690 data203,165,253,133,055
700 data165,254,133,056,032
710 data068,198,165,252,133
720 data250,076,049,-01,162
730 data000,134,250,134,116
740 data232,134,251,134,252
750 data165,043,133,253,165
760 data044,133,254,169,076
770 data133,115,169,-01,133
780 data117,096
Listing 2. »Basic-Switch« (Basic-Lader)
BASICSWITCH

***************** CHRGET ERGAENZUNG
1C00 INC $7A    ; CHRGET FORTFUEHREN
1C02 BNE $1C06
1C04 INC $7B
1C06 JSR $0079
1C09 CMP #$FF   ; TOKEN = PI ?
1C0B BEQ $1C10  ; JA, DANN VERZWEIGEN
1C0D JMP $0079  ; SONST ZURUECK INS BASIC
1C10 JSR $0073  ; CHRGET, NAECHSTES ZEICHEN
1C13 CMP #$45   ; 'E' FUER ERGAENZUNG
1C15 BNE $1C1A  ; NEIN/ DANN WEITER
1C17 JMP $1CA8  ; SONST IN ERGAENZUNGSROUTINE SPRINGEN
1C1A CMP #$53   ; 'S' FUER SWITCH ?
1C1C BEQ $1C21  ; JA, DANN VERWZEIEN
1C1E JMP $CF08  ; SONST 'SYNTAX ERROR'
***************** PIS FUER SWITCH
1C21 JSR $079B  ; BYTEWERT (0-255)HOLEN
1C24 LDA $65
1C26 BEQ $1C2C  ; WENN WERT=0, DANN FEHLER
1C28 CPX $FB
1C2A BCC $1C2F  ; ? WENN WERT<ANZAHL DER PROGRAMME, DANN WEITER
1C2C JMP $D248  ; SONST FEHLERMELDUNG
1C2F STX $FC    ; PROGRAMMNUMMER ABSPEICHERN
1C31 JSR $1C85  ; ADRESEN 43-56 IN ZWISCHENSPEICHER UBERNEHMEN
1C34 LDX $FC    ; EINGABEWERT * 8
1C36 DEX
1C37 TXA
1C38 ASL
1C39 ASL
1C3A ASL
1C3B TAY
1C3C LDX #$00   ; ZWISCHENSPEICHER IN REGISTER 43-46 SCHREIBEN
1C3E LDA $1D20,Y
1C41 STA $2B,X
1C43 INY
1C44 INX
1C45 CPX #$04
1C47 BNE $1C3E
1C49 LDX #$00   ; ZWISCHENSPEICHER IN REGISTER 55-56 SCHREIBEN
1C4B LDA $1D20,Y
1C4E STA $37,X
1C50 INY
1C51 INX
1C52 CPX #$02
1C54 BNE $1C4B
1C56 LDA $FC    ; LFD. PROGRAMMNR.
1C58 STA $FA    ; GLEICH ALTER PROGRAMMNR.
1C5A JSR $C660  ; BASIC BEFEHL CLR
**************** REM- ZEILA AUSDRUCKEN
1C5D JSR $C68E  ; CHRGET ZEIGER (7A,7B) AUF BASICSTART
1C60 JSR $0073  ; CHRGET, NEUES KOMANDO HOLEN
1C63 BEQ $1C6B  ; BEIM ZEILENENDE VERZWEIGEN
1C65 CMP #$8F   ; 'REM' CODE ?
1C67 BEQ $1C79  ; JA, DANN AUSDRUCKEN
1C69 BNE $1C60  ; SONST ZURUECK
1C6B LDY #$01
1C6D LDA ($7A),Y; ZEILENENDE AUCH PROGRAMMENDE ?
1C6F BNE $1C60  ; NEIN, DANN ZURUECK
1C71 INY
1C72 CPY #$02
1C74 BNE $1C6D
1C76 JMP $C474  ; PROGRAMMENDE, DANN IN BASIC DIREKTMODUS
1C79 JSR $CAD7  ; LEERZEILE AUSGEBEN
1C7C LDA $7A    ; AKKU   AUF L-BYTE DES STRINGS
1C7E LDY $7B    ; Y REG. AUF H-BYTE DES STRINGS
1C80 JSR $CB1E  ; STRING (=REM ZEILE) AUSDRUCKEN
1C83 BEQ $1C76  ; ZURUECK INS BASIC
***************** REGISTER ABSPEICHERN
1C85 LDX $FA    ; ALTE PROGRAMMNR. * 8
1C87 DEX
1C88 TXA
1C89 ASL
1C8A ASL
1C8B ASL
1C8C TAY
1C8D LDX #$00   ; ZEIGER 43-46 IN ZWISCHENSPEICHER UEBERNEHMEN
1C8F LDA $2B,X
1C91 STA $1D20,Y
1C94 INY
1C95 INX
1C96 CPX #$04
1C98 BNE $1C8F
1C9A LDX #$00   ; ZEIGER 55-56 IN ZWISCHENSPEICHER
1C9C LDA $37,X
1C9E STA $1D20,Y
1CA1 INY
1CA2 INX
1CA3 CPX #$02
1CA5 BNE $1C9C
1CA7 RTS        ; ZURUECK
***************** PIE FUER ERGAENZUNG
1CA8 JSR $1C85  ; ZEIGER (43-56) IN ZWISCHENSPEICHER
1CAB JSR $0073  ; CHRGET, NEUES ZEICHEN HOLEN
1CAE JSR $CD8A  ; NUMERISCHEN AUSDRUCK HOLEN
1CB1 JSR $D1B8  ; FLIESKOMMA IN 2-BYTE FORMAT WANDELN
1CB4 LDA $FD    ; HOECHSTE ADRESSE DES 1. PROGRAMMS =
1CB6 STA $2B    ; NIEDRIGSTE ADRESSE DES 2. PROGRAMMS
1CB8 LDA $FE
1CBA STA $2C
1CBC LDA $65    ; EINGEGEBENER WERT
1CBE CLC
1CC1 STA $FD    ; UND ABSPEICHERN (L-BYTE)
1CC3 LDA $64
1CC5 ADC $FE    ; ADDITION DER H-BYTES
1CC7 STA $FE    ; UND ABSPEICHERN
1CC9 LDA $FB    ; ANZAHL DER VERWALTETEN PROGRAMME HOLEN
1CCB STA $FC    ; UND DER LFD. PROGRAMMNR. GLEICHSETZEN
1CCD INC $FB    ; ? ANZAHL DER PROGRAMME UM 1 ERHOEHEN
1CCF LDA $2B    ; BASICANFANG UM 1 ZURUECK
1CD1 BNE $1CD5
1CD3 DEC $2C
1CD5 DEC $2B
1CD7 JSR $E3F8  ; 0 AN DEN BASICANFANG UND BA. UM EINS ERHOEHEN
1CDA LDA $FE    ; UEBERPRUEFUNG, OB DIE NEUE BASICENDADRESSE
1CDC CMP $0284  ; UEBERHAUPT NOCH IM SPEICHERBEREICH LIEGT
1CDF BCC $1CF1
1CE1 BNE $1CEA
1CE3 LDA $FD
1CE5 CMP $0283
1CE8 BCC $1CF1  ; LIEGT ADRESSE IM SPEICHERBEREICH, DANN WEITER
1CEA LDA #$0C   ; SONST FEHLERMELDUNG (L-BYTE DER FM LADEN)
1CEC LDY #$CD   ; H-BYTE LADEN
1CEE JMP $CB1E  ; STRING 'REDO FROM START' AUSDRUCKEN
1CF1 LDA $FD    ; BERECHNETES BASICENDE
1CF3 STA $37    ; DEM ECHTEN GLEICHSETZEN
1CF5 LDA $FE
1CF7 STA $38
1CF9 JSR $C644  ; BASIC ROUTIENE NEW
1CFC LDA $FC    ; LFD. PROGRAMMNR. =
1CFE STA $FA    ; ALTE PROGRAMMNR
1D00 JMP $1C31  ; EINSPRUNG IN DIE SWITCH ROUTINE
***************** INITIALISIERUNG
1D03 LDX #$00
1D05 STX $FA    ; ALTE PROGRAMMNR. INITIALISIEREN
1D07 STX $74    ; CHRGET AENDERN
1D09 INX
1D0A STX $FB    ; ANZAHL DER PROGRAMME UND
1D0C STX $FC    ; LFD. PROGRAMMNR. AUF1
1D0E LDA $2B
1D10 STA $FD    ; HIMEM ZEIGER INITIALISIEREN
1D12 LDA $2C
1D14 STA $FE
1D16 LDA #$4C   ; CHRGET AENDERN
1D18 STA $73
1D1A LD #$1C
1D1C STA $75
1D1E RTS
Listing 3. »Basic-Switch« (Assembler-Darstellung)
10 rem ******************************************
20 rem ****                                  ****
30 rem **** zeilennummerngesteuertes restore ****
40 rem ****                                  ****
50 rem ****      1984 by christoph sauer     ****
60 rem ****  hubertusstr.14 / 8000 muen. 19  ****
70 rem ****                                  ****
80 rem ******************************************
90 fort=0to47:readd:s=s+d:next:ifs<>5219thenprint"fehler":end
100 poke55,0:poke56,peek(56)-1:clr:a=peek(56)
110 restore:fort=a*256toa*256+47
120 readd
130 poket,d
140 next
150 print"{clr}{down}{down}fertig. start mit":print"{down}sys"a*256
160 data032,121,000,201,044
170 data240,003,076,008,207
180 data032,115,000,032,138
190 data205,032,247,215,165
200 data020,133,063,165,021
210 data133,064,032,019,198
220 data032,248,200,165,095
230 data164,096,056,233,001
240 data032,036,200,076,116
250 data196,116,196
Listing 4. »Zeilen-RESTORE« (Basic-Lader)
1D00 JSR $0079  ; CHRGOT, LFD. BASICZEICHEN
1D03 CMP #$2C   ; KOMMA ?
1D05 BEQ $1D0A  ; JA, DANN VERZWEIGEN
1D07 JMP $CF08  ; SONST 'SYNTAX ERROR'
1D0A JSR $0073  ; CHRGET, NAECHSTES ZEICHEN
1D0D JSR $CD8A  ; AUSDRUCK HOLEN
1D10 JSR $D7F7  ; IN 2-BYTE FORMAT WANDELN
1D13 LDA $14    ; UEBERTRAGUNG DER ZEILENNR. (LB)
1D15 STA $3F
1D17 LDA $15    ; HB UEBERTRAGEN
1D19 STA $40
1D1B JSR $C613  ; ADRESSE DER DATAZEILE BERECHNEN
1D1E JSR $C8F8  ; NAECHSTE DATAZEILE SUCHEN
1D21 LDA $5F
1D23 LDY $60
1D25 SEC
1D26 SBC #$01
1D28 JSR $C824  ; RESTORE DURCHFUEHREN
1D2B JMP $C474  ; RUECKSPRUNG INS BASIC
Listing 5. »Zeilen-RESTORE« (Assembler-Darstellung)
PDF Diesen Artikel als PDF herunterladen
Mastodon Diesen Artikel auf Mastodon teilen
← Vorheriger ArtikelNächster Artikel →