---------------------------------------- Floppy-Kurs: "Es rappelt in der Kiste..." (Teil 7) ----------------------------------------
Hallo und herzlich Willkommen zum siebten Teil des Floppy-Kurses. In diesem
Monat möchten wir uns an die Floppy-Programmierung in Assembler heranwagen.
Hierbei werden wir alle notwendigen Routinen zum I/ O-Handling von Maschinenspache aus kennenlernen. Allerdings sollten
Sie schon einige Assembler-Vorerfahrung
mitbringen und zumindest Kenntnis vom
Befehlssatz des 6510- Prozessors haben.
DER EINSTIEG IN ASSEMBLER
Wie Sie bemerkt haben, waren alle bishe- rigen Programmbeispiele und Befehlserläuterungen in BASIC programmiert. Dies
diente hauptsächlich der Einfachheit
halber. Wenn Sie nun ein angehender
Assemblerprogrammierer sind, so werden
Sie sich desöfteren schon einmal gefragt
haben, wie Sie die Floppy in Maschinensprache ansprechen können. Dies unterscheidet sich von BASIC zwar schon in
einigen Punkten, da es etwas aufwendiger
ist, jedoch können wir hier komplett auf
schon im Betriebssystem des 64 ers vorhandene Routinen zurückgreifen, die lediglich mit den richtigen Parametern
gefüttert werden müssen. Diese Routinen
sind dieselben, die auch vom BASIC des
C64 benutzt werden, weshalb die Parameterübergabe der von BASIC sehr ähnlich
ist. Kommen wir zunächst einmal zu einigen grundlegenden Dingen:
ÜFFNEN UND SCHLIESSEN VON FILES Jedesmal, wenn wir einen Zugriff auf die Floppy ausführen wollten, mussten wir
zuvor immer einen Filekanal öffnen.
Hierzu wurden mit Hilfe des OPEN-Befehls
einige Parameter an die Floppy übergeben, die zur eindeutigen Definition des
Kanals notwendig waren. Diese Parameter
waren die Filenummer, die Devicenummer
und die Sekundäradresse. Oft folgte der
ganzen Anweisung dann auch noch ein Filename, dessen Appendix ebenso die verlangte Operation spezifizierte. Man kann
also sagen, daß diese Werte alles Grundwerte sind, die zum Datenaustausch mit
der Floppy benötigt werden. Aus diesem
Grund müssen sie auch bei jeder Fileoperation angegeben werden. Möchten wir nun
eine Operation von Assembler aus durchführen, so müssen wir diese Parameter
zunächst dem Betriebssystem mitteilen.
Dies geschieht über die beiden ROM-Routinen " SETPAR" und " SETNAM" . SETPAR
hat die Einsprungadresse $ FFBA. An sie
werden die Filenummer, sowie Geräteund
Sekundäradresse übergeben. SETNAM legt den Filenamen ( inklusive Appendix) fest.
Sie steht bei $ FFBD. Beide Routinen tun
nichts anderes, als die gegebenen Werte
in Betriebssystem-Zwischenspeichern der
Zeropage abzulegen.
Nachdem die Werte festgelegt sind, können wir die Betriebssystemroutine " OPEN" aufrufen, die uns den Kanal mit der
Floppy öffnet. Ihre Einsprungadresse ist
bei $ FFC0 . Zum Schließen benutzen wir
die Routine " CLOSE" bei $ FFC3 .
Wenn wir ein File öffnen, so müssen wir
zunächst einmal seinen Namen festlegen.
Diesen legen wir irgendwo im Speicher, an einer bekannten Adresse, ab. Die Länge des Namens benötigen wir ebenso. Als
nächstes können wir SETNAM und SETPAR
aufrufen. SETNAM wird die Adresse des
Filenamens und seine Länge, SETPAR die
logische Filenummer, die Geräteund die
Sekundäradesse übergeben. Hier eine
Öbersicht der Registerbelegungen:
SETPAR: Akku = Filenummer X-Reg. = Geräteadresse Y-Reg. = Sekundäradresse SETNAM: Akku = Länge des Filenamens X-Reg. = Low-Byte der Speicher- adresse des Namens Y-Reg. = High-Byte der Speicher- adresse des Namens
Ich möchte Ihnen hierzu ein Beispiel
liefern. Angenommen, Sie wollten das
sequentielle File " TEST" zum Lesen öffnen. Als Filenummer wollen wir die 1 wählen, als Sekundäradresse 2 . Die Geräteadresse ist in Bezug auf die Floppy
natürlich 8( oder 9,10,11 wenn Sie eine
zweite, dritte oder vierte Floppy besitzen) . All diese Parameter entsprechen
also dem BASIC-Befehl:
OPEN 1,8,2,"TEST,S,R"
Möchten wir diesen Befehl nun in Maschinensprache umsetzen, so schreiben wir folgendes Assembler-Programm. Hierbei
gehe ich davon aus, daß wir den Filenamen " TEST, S, R" als ASCII-Code bei Adresse $0334( dem Kasettenpuffer) abgelegt
haben:
LDA #$01 ;Filenummer 1 LDX #$08 ;Geräteadresse 8 LDY #$02 ;Sekundäradresse 2 JSR $FFBA ;SETPAR aufrufen LDA #$08 ;Filename ist 8 Zeichen lang LDX #$34 ; und liegt bei Adresse LDY #$03 ; $0334 JSR $FFBD ;SETNAM aufrufen
Die Parameter für das File " TEST" wären
damit festgelegt. Da das ", S, R" im Filenamen enthalten ist, weiß die Floppy
auch gleich schon, daß wir ein sequentielles File lesen möchten.
Als Nächstes müssen wir das zuvor spezifizierte File öffnen. Dies geschieht
über die oben schon erwähnte Betriebssystemroutine " OPEN" . Sie benötigt keine Parameter und wird direkt aufgerufen:
JSR $ FFC0 ; File mit zuvor gesetzem Namen und Parametern öffnen
Nun ist das File " TEST" geöffnet. Da
aber auch jeder Datenfluß einmal ein
Ende hat, muß unser File irgendwann einmal wieder geschlossen werden. Dies geschieht, wer hätte das gedacht, mit der
ROM-Routine " CLOSE", die bei Adresse
$ FFC3 angesprungen wird. Da auch mehrere
Files gleichzeitig offen sein können, muß ihr die Filenummer des zu schließenden Files im Akku übergeben werden:
LDA #$01 ; File mit Filenummer 1 JSR $ FFC3 ; schließen
DER DATENAUSTAUSCH Wie man ein File öffnet, sollte Ihnen
nun klar sein. Wenn Sie jedoch auch noch
mit ihm komunizieren wollen ( was sie bestimmt tun möchten, da es anders sinnlos wäre ein File zu öffnen), so müssen
Sie weiterhin fünf Betriebssystem-Routinen kennen, die Ihnen dies ermöglichen. Zunächst wären da " CHKIN" und
" CHKOUT" . Sie dienen der Umleitung der
Standard-Ein/ Ausgabe auf den entsprechenden Filekanal. Wenn Sie aus einem
File lesen wollen, so sollten Sie nach
dem Üffnen desselben die Routine CHKIN
aufrufen. Mit ihr teilen Sie dem Betriebssystem mit, daß Sie, wenn Sie
jetzt etwas einlesen, die Daten aus diesem Filekanal haben möchten. Möchten Sie
in ein File schreiben, so müssen Sie
CHKOUT aufrufen um in das geöffnete File
schreiben zu können. Beide Routinen
benötigen die Filenummer des entsprechenden Files im X-Register. Die Einsprungadresse von CHKIN ist $ FFC6, die
von CHKOUT $ FFC9 .
Wenn Sie die Standard-Ein/ Ausgabe mit
Hilfe unserer beiden Routinen umgeleitet
haben, so können Sie die Ihnen viel- leicht schon bekannten Routinen BASIN
($ FFCF) und BASOUT ($ FFD2) zum Lesen und
Schreiben von Daten vom, bzw. an, das
Ein/ Ausgabe-Gerät benutzen. Rufen Sie
diese Routinen bei unveränderter Standard- Ein-/ Ausgabe auf, so erscheint bei
BASIN ein Cursor auf dem Bildschirm, der
dem Assemblerprogramm eine Eingabe von
der Tastatur übermittelt, bei BASOUT
wird ein Zeichen an die aktuelle Cursorposition gedruckt und der Cursor um eine
Stelle weiterbewegt ( wie Sie bemerken
ist das Standard-Eingabegerät die Tastatur, das Standard-Ausgabegerät der Bildschirm) .
Bei umgeleiteter Eingabe erhalten Sie
nun beim Aufruf von BASIN das aktuelle
Byte des geöffneten Files im Akku
zurück. Bei umgeleiteter Ausgabe wird
jedes Byte, daß Sie in den Akku laden
und anschließend an BASOUT übergeben in
das geöffnete File hineingeschrieben.
Haben Sie nun Ihre Fileoperationen beendet, so ist es notwendig, die fünfte Routine zu benutzen, von der ich oben
sprach. Sie heißt " CLRCH" und wird verwendet, um die Standard-Ein/ Ausgabe wieder zurückzusetzen auf ' Tastatur' und
' Bildschirm' . Ihre Einsprungadresse ist
$ FFCC. Sie wird VOR dem Aufruf von CLOSE
benutzt und benötigt keine Parameter.
FEHLERERKENNUNG UND BEHANDLUNG Bevor wir uns der Praxis zuwenden, zunächst noch ein Wort zur Fehlererkennung. Hierüber können wir nämlich beim
Lesen eines Files auch erkennen, wann
wir sein letztes Byte gelesen haben.
Prinzipiell gilt: tritt während der Arbeit einer der Floppy-Betriebssystem- Routinen ein Fehler auf, so wird das an
das aufrufende Programm durch ein gesetztes Carry-Bit zurückgemeldet. So
können Sie also nach jeder der Routinen
durch eine einfache " BCS"- Verzweigung
(" Branch on Carry Set") auf eine Fehlerbehandlungsroutine verzweigen. Hierbei steht dann im Akku ein Fehlercode, mit
dessen Hilfe Sie die Art des Fehlers
feststellen können. Hier eine Liste mit
den möglichen Fehlermeldungen und den
Ursachen für einen aufgetretenen Fehler.
In eckigen Klammern stehen jeweils die
Betriebssystem-Routinen, die den entsprechenden Fehler auslösen können.
Steht nichts dahinter, so handelt es
sich um einen allgemeinen Fehler:
0 :" Break Error"- Die RUN/ STOP-Taste
wurde gedrückt.
1 :" too many files"- Der 64 er verwaltet
intern maximal 10 offene Files. Sie
versuchten ein elftes File zu öffnen.
Oder aber sie haben schon zu viele
Files zur Floppy hin offen ( Sie erinnern sich: man darf maximal 3 sequentielle, oder 1 relatives und 1 sequentielles File gleichzeitig offen
halten) .{ OPEN}2 :" file open"- Sie versuchten, ein
schon offenes File nochmal zu öffnen.
{ OPEN}3 :" file not open"- Sie versuchten, ein
ungeöffnetes File anzusprechen.{ CH-KIN, CHKOUT, CLOSE}4 :" file not found"- Das File, das Sie
zum Lesen öffnen wollten existiert
gar nicht.{ OPEN}5 :" device not present"- Die Floppy
( oder das gewählte Gerät) ist nicht
eingeschaltet.{ OPEN}6 :" not an input file"- Sie versuchten
aus einem zum Schreiben geöffneten
File zu lesen.{ CHKIN}7 :" not an output file"- Sie versuchten
in ein zum Lesen geöffneten File zu
schreiben.{ CHKOUT}8 :" missing filename"- Sie gaben keinen
Filenamen an.{ OPEN}9 :" illegal device number"- Sie gaben
eine ungültige Devicenummer an.
{ OPEN}
Beachten Sie bitte, daß nicht unbedingt
alle Fehler aus obiger Liste auftreten müssen, da man die oben genannten Routinen auch zum Anprechen von anderen Geräten verwenden kann ( z. B. Drucker, Datasette, etc.) . Üffnen Sie z. B. einen Kanal zum Drucker, so brauchen Sie keinen
Filenamen - der Fehler 8 wird in dem
Fall also nie auftreten.
Als weitere Fehlererkennungshilfe stellt
uns das Betriebssystem auch eine Statusspeicherstelle zur Verfügung. Sie ist
absolut identisch mit der Variablen " ST" von BASIC. Fragt ein BASIC-Programm diese Variable ab, so greift der BASIC-Interpreter auf eben diese Speicherstelle zurück. Die Assemblerprogrammierer
finden den I/ O-Status in der Zeropage, nämlich in Speicherstelle $90( dez.
144) . Um feststellen zu können, ob während der Arbeit mit der Floppy ein Fehler aufgetreten ist, müssen wir sie lediglich einlesen und analysieren. Ein
Fehler wird hierbei durch das gesetzt
sein eines oder mehrerer Bits dieser Speicherstelle gemeldet. Hier eine Belegung der Bits ( nur für die Arbeit mit
der Floppy gültig, bei Kasettenbetrieb
gilt eine andere Belegung) :
Bit Bedeutung
0 Fehler beim Schreiben 1 Fehler beim Lesen 6 Datei-Ende wurde erreicht (Lesen) 7 Gerät nicht vorhanden oder abge- schaltet ("device not present")
Sie sehen, daß Sie hier, wie auch in
BASIC, das Ende eines Files durch gesetzt sein des 6 . Bits von ST feststellen können. Dann nämlich hat ST den Wert
64, den wir bei BASIC-Abfragen auch immer verwendeten. Sie sehen übrigens
auch, warum bei erneutem Lesen trotz
beendetem File die Fehlernummer 66 zurückgeliefert wird. Das File ist dann
nämlich zu Ende und es trat ein Fehler
beim Lesen auf, weil es ja gar nichts mehr zu lesen gibt. Damit sind die Bits
1 und 6 gesetzt (2+64) was dem Wert 66 entspricht.
Wenn ST den Wert 0 enthält, so ist alles
gut gegangen. Auf diese Weise können wir
in Assembler sehr einfach den Fehler
abfragen: Wir lesen die Speicherstelle
$90 einfach ein und verzweigen mittels
BNE auf eine Fehlerbehandlungsroutine.
ZUSAMMENFASSUNG Abschließend zu diesen Routinen möchte
ich Ihnen nun zwei Universal-Lese/ Schreib-Routinen vorstellen, die
Sie zum Lesen, bzw. Schreiben eines Files verwenden können. Sie finden diese
Routinen auch auf dieser MD unter dem
Namen " FK. I/ O. S" . Sie sind im Hypra-Ass- Format gespeichert. Um sie sich anzuschauen können Sie sie wie ein BASIC-Programm laden und listen.
Zunächst einmal möchte ich Ihnen die
" ReadFile"- Routine vorstellen. Sie liest
ein File an eine beliebige Adresse ein.
Hierbei übergeben Sie Lowund High-Byte
in Xund Y-Register, sowie die Länge
des Filenamens im Akku. Der Filename
selbst soll immer bei $0334( dez.820) stehen:
ReadFile:
stx $fb ;Startadresse in sty $fc ; Zeiger schreiben ldx #$34 ;Namensadresse=$0334 ldy #$03 ;in X/Y (Len in Ak.) jsr $ffbd ;Und Name setzen lda #01 ;Filenummer=1 ldx #08 ;Geräteadresse=1 ldy #00 ;Sek.=0 (=PRG lesen) jsr $ffba ;Parameter setzen jsr open ;File öffnen ldx #01 ;Ausgabe auf FNr. 1
jsr chkin ; umleiten
ldy #00 ;Index auf 0 setzen loop1: jsr basin ;Byte lesen sta ($fb),y ;und auf Zeiger- adresse speichern inc $fb ;Den Zeiger in bne l1 ; $FB/$FC um 1 inc $fc ; erhöhen
l1 : lda $90 ; File-Ende erreicht?
beq loop1 ; Nein, dann weiter!
jsr $ffcc ;Ja, also Standard- Ein-/Ausgabe zurücksetzen. lda #01 ;Und File mit File- jmp $ffc3 ; nummer 1 schließen
Als nächstes stelle ich Ihnen die " WriteFile"- Routine vor. Sie speichert Daten
in einem beliebigen Speicherbereich auf Diskette und wird mit denselben Vorraussetzungen aufgerufen wie " ReadFile" :
Name bei $0334, Namenslänge im Akku und
Startadresse des zu speichernden Bereichs in X-/ Y-Register. Zusätzlich müssen Sie die Endadresse dieses Bereichs
( in Lo/ Hi-Darstellung) vorher in den
Speicherstellen $ FD/$ FE abgelegt haben:
WriteFile:
stx $fb ;Startadresse in sty $fc ; Zeiger schreiben ldx #$34 ;Namensadresse=$0334 ldy #$03 ;in X/Y (Len in Ak.) jsr $ffbd ;Und Name setzen lda #01 ;Filenummer=1 ldx #08 ;Geräteadresse=1 ldy #01 ;Sek.=1 (=PRG schreiben) jsr $ffba ;Parameter setzen
jsr $ ffc0 ; File öffnen ldx #01 ; Eingabe auf FNr.1 jsr $ ffc9 ; umleiten ldy #00 ; Index auf 0 setzen loop2 : lda ($ fb), y ; Byte aus Sp. holen jsr $ ffd2 ; und an Floppy senden
inc $fb ;Den Zeiger in bne l2 ; $FB/$FC um 1 inc $fc ; erhöhen l2: lda $fe ;Quellzeiger $FB/$FC cmp $fc ; mit Zielzeiger bne loop2 ; $FD/$FE lda $fd ; vergleichen. Wenn cmp $fb ; ungleich, dann bne loop2 ; weitermachen. jsr $ffcc ;Wenn gleich, dann Standard-I/O zurücksetzen. lda #01 ;und File mit File- jmp $ffc3 ;nummer 1 schließen
Wie Sie sehen, habe ich hier den Trick
mit den Sekundäradressen verwendet. Da
bei der Sekundäradresse die Werte 0 und
1 für " Programm lesen" bzw." Programm
schreiben" stehen, erübrigt sich ein
umständliches anhängen von ", P, R" bzw.
", P, W" an den Filenamen. Dafür jedoch
können mit den Routinen nur PRG-Files
gelesen und geschrieben werden.
Bitte Teil 2 des Floppy-Kurses laden !