Der USR-Befehl
Verwendung von Maschinen-Unterprogrammen in BASIC
Wenn ein Assembler-Programm erzeugt wurde stellt sich die Frage „Wie kommt das Programm ins BASIC?“ Hierfür bietet das ATARI-Basic eine sehr nützliche Funktion, mit dem Namen "USR" an. Die Funktion "USR" werde ich im weiteren etwas näher beleuchten. "USR" erwartet mindestens eine Adresse an der das Maschinen-Programm gespeichert ist, diese kann auf unterschiedliche weise an die "USR-Funktion übergeben werden
- das Maschinen-Programm wird in einen Speicherbereich geschrieben und dann über die Adresse (USR(adr)) aufgerufen
- das Maschinen-Programm wird in eine String-Variable eingelesen und mit USR(adr(ML$)) aufgerufen
Der Befehl "USR" ist tatsächlich eine Funktion denn sie kann sowohl Parameter übernehmen als auch Parameter zurück liefern. Um das zu demonstrieren ein kurzes Beispiel, die Addition von zwei Werten mit Hilfe von Assembler, das macht zwar nicht wirklich Sinn aber verdeutlicht wie die "USR"-Funktion arbeitet.
Dazu zeige ich zuerst einmal den ersten Teil des ATARI BASIC Programms
20 DIM INP$(1)
30 DIM ML$(27)
40 FOR I=1 TO 27:READ A:ML$(I)=CHR$(A):NEXT I
150 END
200 DATA 104,104,133,225,104,133,224,104
210 DATA 133,227,104,133,226,24,165,224
220 DATA 101,226,133,212,165,225,101,227
230 DATA 133,213
240 DATA 96
Dieses Programm liest das in den Zeilen 200-240 abgelegte Maschinenprogramm und speichert dieses in der Zeichenkette ML$. Wird dieses Programm ausgeführt so kann im Anschluss mit dem Kommando
? ML$
die Zeichenkette ausgegeben werden.
Noch macht dieses Programm jedoch gar nichts, denn es fehlt der Aufruf des in der Zeichenkette enthaltenen Maschinen-Programms. Dazu erweitern wir das BASIC-Programm um die folgenden Zeilen
60 PRINT "INPUT A";:INPUT A
70 PRINT "INPUT B";:INPUT B
100 SUMME=USR(ADR(ML$),A,B)
110 PRINT "ERGEBNIS: A+B=";SUMME
Wird das Programm nun gestartet, so werden die beiden Variablen A und B erfragt und an das Maschinenprogramm übergeben, die Berechnung durchgeführt und das Ergebnis in der Variablen SUMME zurück geliefert.
Nun noch einmal das vollständige Programm, für die Freunde von Copy & Paste (kann natürlich nur bei Verwendung eines Emulators verwendet werden).
20 DIM ML$(27)
30 FOR I=1 TO 27:READ A:ML$(I)=CHR$(A):NEXT I
40 GRAPHICS 0
50 PRINT "INPUT A";:INPUT A
60 PRINT "INPUT B";:INPUT B
70 SUMME=USR(ADR(ML$),A,B)
80 PRINT "ERGEBNIS: A+B=";SUMME
150 END
200 DATA 104,104,133,225,104,133,224,104
210 DATA 133,227,104,133,226,24,165,224
220 DATA 101,226,133,212,165,225,101,227
230 DATA 133,213
240 DATA 96
Wie funktioniert das aber genau ?
Dazu schauen wir uns mal das Assembler-Listing des Maschinen-Programms an, eine Erläuterung folgt später.
0110 SUMME = $D4
0120 ZAHL1 = $E0
0130 ZAHL2 = $E2
0140 START
0160 PLA
0170 RECHNE
0180 PLA
0190 STA ZAHL1+1
0200 PLA
0210 STA ZAHL1
0220 PLA
0230 STA ZAHL2+1
0240 PLA
0245 STA ZAHL2
0246 ;
0250 CLC
0260 ;
0270 LDA ZAHL1
0290 ADC ZAHL2
0310 STA SUMME
0311 ;
0320 LDA ZAHL1+1
0330 ADC ZAHL2+1
0340 STA SUMME+1
0341 ;
0350 RTS
Auf den ersten Blick sicherlich nichts sagend, aber ich werde versuchen dass zu erläutern. Zuvor werfen wir aber noch einen zweiten Blick auf die "USR"-Funktion.
Im zusammen Hang mit der "USR"-Funktion ist es wichtig zu verstehen wie die Parameter an das Maschinen-Programm übergeben werden. Bei einem Aufruf in der Form
X=USR(Adr(ML$), Param1, Param2)
werden alle Werte innerhalb der Klammern an das Maschinen-Programm übergeben, aber das ist noch nicht alles es wird zusätzlich noch ein COUNTER für die Anzahl der Parameter sowie die Rücksprung-Adresse ins BASIC mitgegeben. Da die "USR"-Funktion nicht auf zwei Parameter beschränkt ist. halte ich die folgende schreibweise für besser geeignet
X=USR(Adr(ML$), Param1, ..., ParamX)
Der gesamte Datenaustausch zwischen BASIC und dem Maschinen-Programm geschieht über den Hardware Stack (Stapel), dieser Stack ist als s.g. Last in / First out konzipiert. Das Bedeutet, das zuletzt auf diesen Stapel gelegte Byte wird zuerst von diesem Stapel zurück genommen.
Das ATARI BASIC organisiert das speichern der übergeben Wert auf diesem Stapel, dazu geht es wie folgt vor
- Speichern der Adresse für den Rücksprung ins BASIC
- Speichern der übergebenen Parameter, beginnend mit dem letzten Parameter
- Speichern des COUNTER für die Anzahl der übergeben Werte
Da ein Bild immer mehr als tausend Worte sagt hier noch eine Darstellung dieser Reihenfolge.
Nicht vergessen sollte man die Reihenfolge des Höher- und Niederwertigen Bytes!
Weiterhin ist darauf zu achten das wirklich so viele Bytes vom Stapel genommen werden wie übergeben wurden, dabei aber nicht das zusätzliche COUNT-Byte vergessen.
Werden dem Stapel zu wenig oder zu viel Werte entnommen ist die Rücksprung-Adresse ins BASIC futsch und das Programm stürzt unweigerlich ab.
Erläuterung des Assembler-Quellcodes
0100 *=$06000110 SUMME = $D40120 ZAHL1 = $E00130 ZAHL2 = $E2
Die Zeile 0100 definiert die Start-Adresse des erzeugten Programms, ist für uns nicht von Bedeutung macht aber den Compiler glücklich. Im Anschluss werden die Variablen SUMME, ZAHL1 und ZAHL2 definiert, diese Variablen beinhalten im weiteren Verlauf die übergebenen Wert und das Ergebnis.
0140 START0160 PLA0170 RECHNE0180 PLA 0190 STA ZAHL1+1 0200 PLA 0210 STA ZAHL1 0220 PLA 0230 STA ZAHL2+1 0240 PLA 0245 STA ZAHL2 0246 ;
Nun beginnt der eigentlich wichtige Teil für das lesen der übergebenen Parameter. Dem Assembler-Kommando PLA kommt dabei eine wichtige Bedeutung zu. Diese Anweisung holt ein Byte vom Stapel, speichert ihn im Akkumulator und löscht ihn vom Stapel. Somit zeigt die Stapelspitze auf das nächste Byte.
0250 CLC;0260 ;
Das Kommando CLC in Zeile 0250 löscht das Flag für den Übertrag, da dieses Flag bei der Addition verwendet wird. Der Sinn ist das Anzeigen eines Übertrags wenn das Ergebnis der Addition nicht mehr in ein Byte passt.
0270 LDA ZAHL1 0290 ADC ZAHL2 0310 STA SUMME 0311 ;
0320 LDA ZAHL1+1 0330 ADC ZAHL2+1 0340 STA SUMME+1 0341 ;
Die Addition wird in zwei Teilen durchgeführt, einmal für die MSB's und einmal für die LSB's, da es sich bei den übergeben Werten um 2-Byte Werte handelt. Die Vorgehensweise ist dabei jeweils gleich, aus diesem Grund beschreibe ich auch nur einen Teil.
In der Zeile 0270 wird mit dem LDA-Kommando der Akkumulator mit dem LSB der Variablen ZAHL1 geladen, in Zeile 290 wird zu diesem, im Akkumulator befindlichen Wert, das LSB der Variablen ZAHL2 mit dem ADC-Kommando hinzu addiert. Das Ergebnis dieser Operation wird dann in Zeile 0310 mit dem STA-Kommando in das LSB der Variablen SUMME gespeichert.
Die selbe Operation wird anschließen mit den MSB's durchgeführt, damit ist die Addition beendet.
0350 RTS
Zum Abschluss wird in Zeile 0350 mit dem RTS-Kommando die Kontrolle wieder an das BASIC-Programm übergeben.
Das Berechnungsergebnis wird als Funktionsergebnis vom "USR"-Kommando zurück gegeben.
Ich hoffe das diese Einführung in das "USR"-Kommando nicht zu trocken war und wünsche allen Lesern viel Spaß beim programmieren des ATARI.
Literatur Verzeichnis
- Programmierung des 6502; 2. Auflage, 1982 (SYBEX); Zaks,Rodnay
- Der ATARI Assembler; 1982 (IDEA);Inman, Don / Inman, Kurt