Floppy-Kurs: "Es rappelt in der Kiste..." (Teil 1)
In Anlehnung an diverse Geräusche die
das Laufwerk unseres Computerlieblings
an und wann von sich gibt, wünsche ich
Sie herzlich willkommen zum ersten Teil
unseres neuen Floppykurses.
In den nächsten Monaten will ich Ihnen
hier die Funktionsweise und effektive
Benutzung des Floppylaufwerks 1541 des
64 ers erklären. Beginnend mit den Grundlagen zur Datenspeichrung auf Diskette
werden wir uns über sequentielle und
relative Dateiverwaltung immer mehr an
die ' Innereien' der Floppy herantasten, und uns auch mit dem Diskettenaufbau und
Direktzugriff beschäftigen. Danach will
ich Ihnen zeigen, wie einfach es ist die
Floppy in Assembler zu programmieren und
Ihnen desweiteren meine gesammelten Tips und Tricks zur Floppy offenbaren. Ich
wünsche Ihnen also viel Spaß bei diesem
Kurs und will gleich zu Sache kommen. . .
GRUNDLAGEN DER DATENEIN-/AUSGABE
Dieser erste Teil des Floppy-Kurses soll
ganz den Grundlagen und der sequentiellen Fileprogrammierung gewidmet sein.
Kommen wir zunächst einmal zu den Grundbegriffen der Dateneinund ausgabe des
64 ers. Wenn Sie sich mit BASIC auskennen, so wissen Sie, daß es prinzipiell 5 BASIC-Befehle gibt, mit denen wir Daten
an externe, oder von externen Geräten
senden und empfangen können. Diese sind:
* OPEN, zum Üffnen eines Datenkanals,
* PRINT#, zum Senden von Daten in einen Datenkanal,
* GET#, zum Lesen von Daten von einem Datenkanal,
* INPUT#, zum einfacheren ( BASICorientierten) Lesen von einem Datenkanal, und
* CLOSE, zum abschließenden Schließen eines Datenkanals.
Dateneinund ausgaben werden nun prinzipiell mit drei verschiedenen Parametern zu den obigen Befehlen gesteuert.
Sie geben an, welchem logischen Kanal
der aktuelle Datenaustausch zugeordnet
wird und geben Informationen über die
Art des Zugriffs ( lesen/ schreiben) und
über das entsprechende Gerät, das angesprochen werden soll. Diese drei Parameter sind die " logische Filenummer", die
" Sekundäradresse" und die " Gerätenummer" .
Die logische Filenummer ist eine Zahl
zwischen 1 und 127, die als Kennung für
eine Ein-/ Ausgabeoperation dient. Durch
den OPEN-Befehl wird ihr eine bestimmte
Fileoperation zugewiesen, die sie ein- deutig definiert. Wird also ein Datenkanal zur Floppy mit der logischen Filenummer 1 und ein Datenkanal zum Drucker
mit der logischen Filenummer 2 geöffnet, so weiß das Betriebssystem des 64 ers
immer, wohin es Daten mit einer der beiden Filenummern senden soll. Ein
" PRINT#1, A$" sendet so die Stringvariable A$ an den momentan offenen Floppykanal ( nachdem die Filenummer 1 so durch
' OPEN' definiert wurde), wohingegen der
Befehl " PRINT#2, A$" dieselbe Stringvariable an den Drucker sendet ( nachdem
ihm die Filenummer 2 zugeordnet wurde) .
Die Gerätenummer spezifiziert nun das
Gerät, mit dem Daten ausgetauscht werden
sollen. Sie kann Werte zwischen 0 und 15 annehmen. Hierzu gibt es eine Liste, die
die verschiedenen Geräte spezifiziert.
Das Betriebssystem des C64 benötigt diese Nummer deshalb, weil die verschiedenen Peripheriegeräte mit unterschiedlichen Betriebssystemroutinen angesprochen werden müssen. Die Verteilung der Gerätenummern an die entsprechenden Geräte
ist wiefolgt festgelegt:
Ger.num. Gerät
0 Bildschirm 1 Datasette 2 RS232-Schnittstelle 4 Drucker 1 5 Drucker 2 8 Floppylaufwerk 1 9 Floppylaufwerk 2 10 Floppylaufwerk 3 11 Floppylaufwerk 4
Die restlichen Nummern sind unbelegt.
Wie Sie sehen ist es also möglich bis zu
4 Floppylaufwerke und 2 Drucker an den
C64 anzuschließen, die alle getrennt
voneinander angesprochen werden können.
Ebenso ist die Kommunikation mit dem
Bildschirm über die Gerätenummer 0 möglich. Hierbei empfangen Sie direkt die Eingaben, die gerade vom Benutzer auf
den Bildschirm geschrieben werden.
Die Sekundäradresse einer Ein-/ Ausgabeoperation gibt nun die Art einer
Datenoperation an. Sie kann Werte zwischen 0 und 15 annehmen, wobei es folgende Bedeutungen gibt:
SAdr. Bedeutung
0 Programm laden 1 Programm speichern 2-14 Daten Lesen oder Schreiben 15 Floppybefehlskanal
In der Regel werden die Sekundäradressen
zwischen 2 und 14 verwendet. Die Adressen 0,1 und 15 sind spezielle Adressen, die eingens für die Verwaltung von Massenspeichern gedacht sind. Normalerweise
muß man nämlich beim Zugriff auf einen
Massenspeicher ( so wie auch das Floppylaufwerk einer ist) grundsätzlich die Art des Zugriffs explizit im Filenamen
angeben. Möchte man aber ein Programmfile ( dazu später mehr) lesen oder schreiben, so kann man sich die Angabe im Filenamen sparen und die Adressen 0 oder 1 benutzen. Die Floppy weiß in dem Fall
direkt, daß sie ein Programmfile lesen, bzw. schreiben soll. Dazu will ich Ihnen
später noch ein paar Beispiele liefern.
Die Sekundäradresse 15 ist ausschließlich für das Floppylaufwerk reserviert.
Mit ihr wird der sogenannte Floppybefehlskanal geöffnet. Dieser ist nicht an
ein spezielles File gebunden und dient
der Öbertragung von Befehlen an die
Floppy selbst. Die 1541 verfügt nämlich, wie der C64 auch, über einen eigenen
Mikroprozessor ( den 6502, ein Artverwandter des 6510, der im 64 er seinen
Dienst verrichtet) und eigene Ein-/ Ausgabechips. Sie stellt im Prinzip
einen autonomen Computer dar, der richtig Programmiert werden kann. Die wichtigsten Funktionen sind in spezeillen Floppybefehlen zusammengefasst und werden über den erwähnten Befehlskanal aufgerufen ( wie z. B. der Befehl, die einliegende Diskette zu formatieren, oder
ein spezielles File von der einliegenden
Diskette zu löschen) . Der Floppybefehlskanal hat später, wenn wir die Direktzugriffbefehle behandeln, und in Zusammenhang mit der relativen Dateiverwaltung
eine große Bedeutung.
Die obig beschriebenen Parameter, die
der OPEN-Befehl benötigt müssen wiefolgt
angewandt werden ( Praxisbeispiele werden
wir im Laufe dieses Kurses noch genügend
kennenlernen, deshalb hier nur eine Syntaxdefinition) :
OPEN ( log. Filenr.),( Ger. nr.),( Sek. adr.)
DER FLOPPYBEFEHLSKANAL
Wir wollen uns nun mit der Benutzung des
Floppybefehlskanals beschäftigen und
einmal die alltäglichen Floppybefehle
durchgehen.
Bevor Sie also einen Befehl an die
Floppy schicken, müssen Sie den Befehlskanal öffnen. Dies geschieht mit dem
Befehl " OPEN 1,8,15" . Wir öffnen hier
einen Kanal mit der logischen Filenummer
1, verbunden mit dem Gerät Nummer 8( der
Floppy nämlich) und mit der Sekundäradresse 15( eben dem Befehlskanal derselbigen) . Nun können Sie der Floppy Befehle senden, die diese dann unabhängig vom
64 er bearbeiten wird. Sie können also
Ihren 64 er währenddessen in seinem aktuellen Programm fortfahren lassen. Er
arbeitet, solange die Floppy selbst arbeitet, unabhängig von ihr. Nur, wenn
während dieser Zeit ein weiterer Diskettenzugriff notwendig wird, wird der 64 er
angehalten ( s. u.)
Ich will Ihnen nun die Standard- Floppybefehle auflisten, die wir einfach
benutzen können. Später beim Direktzugriff und bei der relativen Dateiverwaltung werden wir ebenfalls über den Befehlskanal der Floppy Anweisungen geben, uns die Daten, die wir von ihr verlangen
entprechend vorzubereiten.
Kommen wir jedoch erst einmal zu den
einfachen Befehlen:
* Der NEW-Befehl:
Mit diesem Befehl formatieren wir eine
neue Diskette. Grundsätzlich bestehen
die Floppybefehle aus einem oder mehreren Buchstaben, sowie den zu jedem Befehl variierenden Parametern. Beim NEW-Befehl ist die Befehlskennung ein " N:", nach diesen beiden Zeichen folgt nun der
Name, den die Diskette erhalten soll, sowie eine zwei Zeichen lange Identifikationskennung (" ID") . Floppybefehle
werden nach dem Üffnen des Befehlskanals
immer mit einem " PRINT# lfn"( lfn= logische Filenummer) an die Floppy geschickt. Mit dem folgenden BASIC-Kommando fordern wir die Floppy also
dazu auf, die einliegende Diskette mit
dem Namen " MEINE DISK" und der ID " MD" zu formatieren. Der Befehlskanal wurde
unter der logischen Filenummer 1 geöffnet ( wie in obigem Beispiel), also sen- den wir den Befehl auch an Kanal 1 :
PRINT#1,"N:MEINE DISK,MD"
Die Floppy beginnt nun mit der Formatierung der Diskette. Der 64 er meldet sich
mit einem " READY." zurück, und Sie können während der Formatierung mit ihm
arbeiten. Sie können den Befehlskanal
nun offen halten und weitere Floppybefehle senden, oder aber auch irgend eine
andere Tätigkeit mit dem Rechner tun.
Achten Sie allerdings darauf, daß beim
weiteren Senden eines Befehls, sowie dem
Schließen des Befehlskanals, der Rechner
blockiert wird. Das liegt daran, daß die
Floppy während einer internen Operation
dem 64 er signalisiert, daß sie gerade
" BUSY", also am Arbeiten ist. Soll der
C64 nun mit der Floppy kommunizieren, so
wartet er solange, bis die Floppy wieder
frei wird. Demnach führt jeder weitere
Befehl, der die Floppy anspricht unweigerlich zu einem zwischenzeitlichen Stop des Rechners.
Der NEW-Befehl kennt übrigens zwei Syntaxen. Öbergeben Sie einen Namen UND
eine ID, dann wird die Diskette physisch
formatiert. Das heißt, sie wird Spur um
Spur neu angelegt, wobei die neuen Spuren mit Nullen aufgefüllt werden. Daten, die sich evtl. auf ihr befanden werden
somit komplett gelöscht. Öbergeben Sie
aber nur einen Diskettennamen, und keine
ID, so wird die Diskette " softformatiert" . Es wird lediglich der neue Name
über den alten geschrieben, und alle
Blocks als unbelegt gekennzeichnet. Die
ID bleibt die alte. Die alten Daten sind
jedoch immer noch auf den Spuren enthalten und können evtl. gerettet werden.
Ausserdem ist das softformatieren weitaus schneller als ein " hardformatieren" . Es funktioniert allerdings nur bei
schon einmal hardformatierten Disketten, da diese die grundlegende Diskettenstruktur schon enthalten.
Sie schließen den oben geöffneten Be- fehlskanal übrigens wieder mit " CLOSE
1" . Tun Sie das bitte immer, wenn Sie
ein Datenfile, oder den Befehlskanal
nicht mehr brauchen, da der 64 er intern
immer nur 10 offene Kanäle verwalten
kann.
* Der RENAME-Befehl:
Mit dem Rename-Befehl können Sie den
Namen eines schon bestehenden Files umbenennen. Die Syntax ist eigentlich
recht einfach. Das Kürzel für RENAME ist
" R:" es folgen nun der neue und der alte
Filename getrennt durch ein "="- Zeichen.
Hier ein Beispiel: PRINT#1," R: MAGIC
DISK= GAME ON"
Dieser Befehl benennt das File " GAME ON" in " MAGIC DISK" um. Wieder benutzten wir
die log. Filenummer 1, die oben im
OPEN-Befehl definiert wurde.
* Der VALIDATE-Befehl:
VALIDATE bedeutet " überprüfen", und sel- biges tut der Validate-Befehl der
Floppy. Haben Sie nämlich Grund zu glauben, daß die Diskettenstruktur durcheinandergekommen ist, so zum Beispiel wenn
Sie ein, oder mehrere Files speichern
wollten, für die aber nicht ausreichend
Platz vorhanden war, dann sollten Sie
diesen Befehl verwenden. Er untersucht
die Diskette auf ihre korrekte Struktur
und korrigiert alles, was nicht einer
normalen Diskettenstruktur entspricht.
In dem Beispiel mit einem nur teilweise
gespeicherten Programm sind die schon
geschiebenen Blocks dieses Files als
belegt gekennzeichnet, obwohl das File
im Directoryeintrag mit "0 Blocks" und
einem "*" als unbrauchbar gekennzeichnet
ist. Der Validate-Befehl erkennt nun die
fälschlicherweise belegten Blocks und
gibt sie wieder frei. Desweiteren löscht
er den markierten Eintrag aus dem Directory. Er benötigt keinerlei Parameter
und kann folgendermaßen aufgerufen werden:
PRINT#1," V"
Die Floppy beginnt nun mit der Validierung. Diese kann je nach dem wie voll
und wieviele einzelne Files auf der Diskette enthalten sind, bis zu mehrere
Minuten in Anspruch nehmen.
* Der SCRATCH-Befehl:
Dieser Befehl löscht ein File auf Diskette. Englisch " to scratch" bedeutet
" kratzen" und im Öbertragenen Sinne
" kratzt" die Floppy tatsächlich ein File
von der Diskettenoberfläche.
Der Scratch-Befehl wird mit " S:" eingeleitet, gefolgt von dem oder den Filenamen, die gelöscht werden sollen. Hierbei
dürfen Sie sogar sogenannte Filepatterns
benutzen, die eine Fileangabe abkürzen.
Ein " Pattern" ist eine Maske, die man
der Floppy für einen Filenamen übergibt.
Alle Files, deren Namen dieser Maske
entsprechen sind damit angesprochen,
werden in unserem Fall also durch den
Scratch-Befehl gelöscht. Hier einige
Beispiele:
PRINT#1,"S:FILE1" PRINT#1,"S:TEST1,TEST2,TEST10,TEST11" PRINT#1,"S:TEST?" PRINT#1,"S:TEST*"
Im ersten Beispiel wird das File namens
" FILE1" gelöscht. Das zweite Beispiel
löscht die Files " TEST1"," TEST2"," TEST10", und " TEST11" . Selbiges können
wir aber auch mit Filepatterns verkürzen. So können wir zum Beispiel mit dem
Fragezeichen eine Fileangabe abkürzen.
es steht für ein beliebiges Zeichen. Das
dritte Beispiel löscht also gleichzeitig
die Files " TEST1" und " TEST2" . Im vierten Beispiel benutzen wir den Asterisk
("*") als Abkürzung. Er steht für alles, was hinter den Zeichen " TEST" folgt. Wir
löschen mit diesem Befehl also auf Einmal alle vier Files aus dem zweiten Bei- spiel.
DIE VERSCHIEDENEN DATEITYPEN
Nachdem wir nun Grundsätzliches über die
Kommunikation mit der Floppy gelernt
haben, möchte ich Sie nun in die einzelnen File-Arten selbiger einführen. Das
benötigen wir, um die unterschiedliche
Programmierung der einzelnen File-Typen
zu verstehen. Insgesamt kennt die 1541 fünf verschiedene File-Typen. Diese
sind:* Programm-Dateien ( PRG) :
In diesen Files sind Daten sequentiell, also Byte hinter Byte abgespeichert. Sie
enthalten ausschließlich Programmdaten, also direkt ausführbare BASIC-, oder
Maschinenprogramme.
* Sequentielle Dateien ( SEQ) :
Diese Fileart unterscheidet sich in
nichts mit der vorherigen. Die Daten
liegen hier ebenfalls sequentiell auf
der Diskette vor. Der einzige Grund,
warum man zwei Filearten zur sequentiellen Speicherung gewählt hat, ist der, daß in SEQ-Files immer nur Daten von und
zu Programmen gespeichert werden sollen
und keine Programme selbst. Wenn Sie
also eine Adressverwaltung programmieren, so sollten Sie Ihre Adressen in
einer SEQ-Datei speichern. Dies kennzeichnet Ihre Daten eben als Daten und
nicht als ausführbares Programm ( obwohl
Sie genausogut eine PRG-Datei verwenden
könnten!) . Die Unterscheidung ist besonders wichtig für den LOAD-Befehl, der
SEQ-Dateien gar nicht erst liest, sondern ausschließlich PRG-Dateien in den
Speicher des 64 ers transferiert.
* Relative Dateien ( REL) :
In relativen Dateien liegen die Daten
für uns Anwender nicht hintereinander, sondern relativ zueinander vor. Hierbei
wird beim Anlegen einer REL-Datei eine
Datensatzlänge vorgegeben, die für ein
REL-File immer gleich bleibt. Wenn Sie nun Daten in die Datei scheiben, werden
selbige zu einem Datensatz zusammengefasst und mit einer fortlaufenden Satznummer versehen. Wenn Sie zum Beispiel
die Adressen Ihrer Adressverwaltung in
relativen Datensätzen speichern, und
sich merken, unter welchen Datensatznummern Sie die einzelnen Adressen wiederfinden, so können Sie direkt auf einen
Datensatz zugreifen. Die Vorteile gegenüber sequentieller Speicherung liegen
auf der Hand: durch den Direktzugriff
sind die Daten erstens schneller erreichbar, weil nicht eine komplette Datei eingelesen werden muß, sondern eben
immer nur ein einziger Datensatz, und
sie können zweitens extern gelagert werden, so daß Sie kostbaren Arbeitsspeicher im 64 er sparen. Die Programmierung
von relativen Dateien wird uns im 2 .
Teil dieses Kurses noch näher beschäftigen.
Bitte Teil 2 laden. . .
* User-Dateien ( USR) Diese Dateien sind speziell für die direkte Floppyprogrammierung gedacht. Da
die 1541 ja über einen eigenen Prozessor
verfügt kann dieser auch direkt in Maschinensprache programmiert werden. Legt
man nun ein Assemblerprogramm in einem
USR-File ab, so kann dieses von der
Floppy direkt in ihren eigenen Arbeitsspeicher geladen, und dort von ihr ausgeführt werden.
* Deleted Files ( DEL) Mit der Kennung " DEL" werden Files markiert, die von der Diskette gelöscht
wurden. Die Floppy löscht nun ein File
nicht wirklich, sondern sie versieht den
File-Eintrag im Directory einfach mit
der DEL-Kennung und gibt die vom File
belegten Blocks schlichtweg als ' unbelegt' wieder frei. Das bedeutet, daß ein
File nach seiner Löschung auch immer
wieder restauriert werden kann, solange man keine neuen Daten auf die Diskette
geschrieben hat. Ein mit DEL gekennzeichneter Eintrag ist normalerweise im
Directory nicht sichtbar. Wie das dennoch möglich ist, und wie man ein
gelöschtes File wieder retten kann, soll
und auch in einer der nächsten Ausgaben
dieses Kurses beschäftigen.
DIE PROGRAMMIERUNG SEQUENTIELLER DATEIEN
Speziell die SEQund REL-Dateien sollen
nun in diesem und dem nächsten Teil dieses Kurses zum Zuge kommen. Beginnen wir
mit der Programmierung sequentieller
Files.
Wie oben schon erwähnt, liegen in SEQ
und PRG Dateien die Daten sequentiell, also Byte hinter Byte, vor. In der Reihenfolge, mit der wir Daten in ein solches File hineinschreiben, müssen wir
sie auch wieder auslesen. Die Vorgehens- weise hierbei ist denkabr einfach.
Zunächst wollen wir einmal ein File
schreiben. Hierzu müssen wir lediglich
ein SEQ-File zum Schreiben öffnen und
dann mittels des PRINT#- Befehls Daten
hineinschreiben. Hierzu ein Beispiel:
OPEN 1,8,2,"DATEN,S,W" PRINT#1,ZA PRINT#1,a$ PRINT#1,x% PRINT#1,CHR$(0);CHR$(40);
CLOSE 1
Hier öffnen wir zunächst einmal ein File
mit dem Namen " DATEN" . Innerhalb der
Namensangabe verlangt die Floppy nun
noch eine Spezifizierung des File-Typs
und der Datenoperation ( Lesen oder
Schreiben) die durchgeführt werden soll.
Das " S" steht dabei für eine ( S) equentielle Datei. Andere mögliche Filetypen
wären " R" für relative," P" für Programm-, oder aber auch " U" für Userdateien. Die " P" Angabe nimmt dabei eine
Sonderstellung ein. Da sie der am häufigsten benutzte Filetyp ist, muß sie
nicht explizit angegeben werden. Wenn
keine Typenangabe gemacht ist, nimmt die
Floppy automatisch an, daß es sich um
eine " PRG"- Datei handelt. Dies funktioniert jedoch nur, wenn eine der beiden
Sekundäradressen 0 oder 1 benutzt wurde.
Dazu gleich mehr.
Direkt nach dem Filetyp kommt, ebenfalls
durch ein Komma getrennt, eine Bezeichnung der Datenoperation, die durchgeführt werden soll. Im Beispiel benutzten wir die Operation " W", was für ( W-) rite= schreiben steht. Insgesamt sind
hier aber drei verschiedene Operanden
möglich:
( R) ead - zum Lesen einer Datei
( W) rite - zum Schreiben einer Datei ( A) ppend - zum Anhängen an eine alte
Datei
Die " A"- Option entspricht im Prinzip der
" W"- Option. Daten werden in einem File
gespeichert, jedoch mit dem Unterschied, daß diese Daten an eine schon bestehende
Datei angehängt werden, und nicht etwa
wie bei " W" eine neue Datei auf der Diskette angelegt wird.
Nachdem im obigen Beispiel eine sequentielle Datei zum Scheiben geöffnet wurde, können wir Daten mittels des
PRINT#- Befehls in sie hineinschreiben.
Dies kann in BASIC auf verschiedenste
Arten geschehen, die Betriebssystemroutine des PRINT#- Befehls passt sich automatisch den Variablentypen an ( wie Sie
sehen wurden in obigem Beispiel String-, Floatund Integerveriablen geschrieben) . Möchte man ein " echtes" Zeichen in
ein File schreiben, so benutzt man in der Regel die CHR$- Funktion, die nur
einzelne Bytes schreibt, in obigem Beispiel die Bytes mit den Werten 0 und 40 .
Wichtig ist die Angabe eines Semikolons
nach der CHR$- Ausgabe. Dies bedeutet
nämlich, wie beim normalen PRINT-Befehl
auch, daß nach der " Ausgabe" auf Diskette die nächste Ausgabe direkt folgen
soll. Andernfalls hängt BASIC nämlich
automatisch ein " Carriage Return"( CR, ASCII-Wert 13) an die Ausgabe an. Bei
der Ausgabe von " reinen" Bytes kann dies
hinderlich sein, weshalb man bei CHR$- Ausgaben in der Regel ein " ;" mitangibt.
BASIC-Variablen MÖSSEN ohne Semikolon
ausgegeben werden, da der INPUT#- Befehl, mit dem man die geschriebenen Daten später wieder lesen muß das CR als Endmarkierung eines Wertes heranzieht. Einzelne Bytes liest man mit der GET#- Funktion, die ja sowieso immer nur ein
einzelnes Byte einliest. Hier ein Beispiel, das die obig geschriebene Datei
wieder einliest:
OPEN 1,8,2,"DATEN,S,R" INPUT#1,ZA INPUT#1,A$ INPUT#1,x% GET#1,Z1$: GET#1,Z2$ CLOSE 1
Die zwei einzelnen Zeichen aus obigem
Beispiel sind nun in den String-Variablen Z1$ und Z2$ gespeichert. Möchte man sie wieder in echte Byte-Werte
umwandeln, muß man die ASC-Funktion wiefolgt benutzen:
Z1%=ASC(Z1$) Z2%=ASC(Z2$)
Wie Sie sehen, haben wir diesmal auch
beim OPEN-Befehl die " R"- Angabe gemacht, damit die Floppy weiß, daß wir Daten
lesen wollen.
Was Sie bei der sequentilellen Fileprogrammierung unbedingt anmerken sollten ist, daß die Daten eben " sequentiell", also hintereinander, in einem File liegen. Sie müssen sie demnach haargenau in
der Reihenfolge wieder einlesen, mit der
Sie sie geschrieben haben. Dies ist gar
nicht so selbstverständlich wie es aussehen mag, wie Sie im nächsten Monat bei
der relativen Dateiverwaltung festellen
werden.
Haargenau so, wie wir ein SEQ-File gelesen und geschrieben haben, können Sie
mit PRG-Files hantieren. Sie müssen lediglich das " S" im Filenamen beim OPEN-Befehl in ein " P" abändern. Ansonsten
ändert sich nichts.
Pfiffig bei der Arbeit mit PRG-Files ist
nun die Anwendung der " besonderen" Sekundäradressen 0 und 1 . Wie ich obig
schon beschrieben hatte, können wir damit der Floppy gleich schon mitteilen, daß wir ein PRG-File lesen oder schreiben wollen. Die explizite Angabe ", P, R"
oder ", P, W" entfällt. Die Sekundäradresse 0 steht für " PRG-File lesen", die
Adresse 1 für " PRG-File schreiben" . Hier
einfach einmal zwei Beispiele:
" OPEN 1,8,0," MEINEDATEI"( Üffnet das PRG-File " MEINEDATEI" zum
lesen)
" OPEN 1,8,1," MEINEDATEI"( Üffnet das PRG-File " MEINEDATEI" zum
schreiben)
Nun können Sie ganz normal mit INPUT# und GET# lesen, bzw, mit PRINT# schreiben. Ebenso müssen Sie Ihre Files natürlich wieder wie gewohnt mit CLOSE
schließen.
Die Verkürzung über die Sekundäradressen
wird häufig in Kopierprogrammen benutzt, weil man so nicht noch umständlich den
Filenamen-Appendix anzuhängen hat ( besonders in Assembler eine zwar einfache, aber lästige Arbeit) .
Das war es dann für diesen Monat. Im
nächsten Monat wollen wir und dann an
die relative Dateiverwaltung wagen, die
zwar komplizierter zu Programmieren ist, mit der jedoch auch sehr flexibel und
schnell gearbeitet werden kann. Bis dahin ein allzeit " Gut Hack",
Uli Basters (ub).
Floppy-Kurs: "Es rappelt in der Kiste" (Teil 2)
Hallo zum zweiten Teil des Floppy-Kurses. In der vorletzten MD hatten einiges über den Floppybefehlskanal und
die Programmierung sequentieller Files
gelernt. In diesem Teil wollen wir uns
nunmehr mit der relativen Dateiverwaltung beschäftigen, die zwar etwas komplizierter zu programmieren, dafür aber
weitaus flexibler als die sequentielle
Dateiverwaltung zu handhaben ist.
DIE RELATIVE DATENVERWALTUNG
Im ersten Teil des Floppy-Kurses hatten
wir bei den von der Floppy unterstützten
Filetypen auch die sogenannten REL-Files
besprochen. Sie bezeichnen Dateien, die
einen RELativen Aufbau haben. Was das bedeutet wollen wir nun klären.
Sicherlich erinnern Sie sich, daß die
sequentiellen Files, wie der Name schon
sagt Daten " sequentiell", also Byte hinter Byte enthalten. Wenn wir Daten in
einem solchen File gespeichert hatten, so mussten wir immer das komplette File
in den Rechner einlesen, um die Daten
weiterverwnden zu können. Bei relativen
Files ist dieser Aufbau nun anders geregelt. Sie arbeiten mit sogenannten " Datensätzen", oder engl." Records" . Im
Prinzip kann man eine relative Datei mit
einem externen Variablenfeld vergleichen. Wir geben der Floppy lediglich an, daß wir z. B. das 24 . Element ( sprich:
Record) ansprechen wollen, und schon
können wir es lesen oder schreiben. Bei
einem sequentiellen File hätten wir die
23 Eintragungen vorher erst überlesen
müssen, bis wir auf das gewollte Element
hätten zugreifen können.
Die Vorteile von REL-Files liegen damit
auf der Hand:
1) Schnellerer Zugriff, da wir nur die
benötigten Daten ansprechen müssen.
2) Speicherersparnis im Rechner selbst, da alle Daten extern gelagert sind
und kein Speicherplatz mit momentan
nicht benötigten Daten belegt wird.
Jedoch hat die relative Dateiverwaltung
auch einen Nachteil: da sie von BASIC
aus nicht unterstützt wird, ist ihre
Programmierung relativ umständlich. Dennoch lässt sich auch das lernen, und wer
es einmal kann der wird merken, daß die
Vorteile überwiegen.
DIE PROGRAMMIERUNG
Bevor wir überhaupt irgendetwas aus oder
in ein relatives File lesen oder schreiben können müssen wir es erst einmal
generieren. Öberhaupt wird ein relatives
File ganz und gar anders behandelt als
ein sequentielles. So wird beim Üffnen nicht mehr zwischen " Lesen" und " Schreiben" unterschieden. Wir öffnen ein solches File ganz einfach um der Floppy
anzuzeigen, daß wir es nun benutzen wollen. Ob wir nun lesen oder schreiben ist
ganz egal. Die Floppy erkennt automatisch, wenn wir etwas schreiben oder
lesen. Das heißt also, daß wir bei der
Benutzung eines relativen Files immer
auch den Floppybefehlskanal öffnen müssen. Öber spezielle Befehle wird die
Floppy dann über die folgende Operation
informiert. Sie stellt dann, je nach
Befehl, beim Lesen auf dem relativen
Filekanal die gewünschten Daten bereit, bzw. erwartet auf diesem die Daten, die
geschrieben werden sollen.
Wollen wir nun also einmal ein relatives
File anlegen, damit wir es benutzen können. Bevor wir das tun, sollten wir uns
überlegen, wie lang ein Datensatz unseres REL-Files werden soll, und wie viele
davon wir vorläufig darin speichern wol- len. Ersteres müssen wir deshalb tun, weil die Datensätze eines relatives Files immer gleich lang sein müssen, um
der Floppy das Auffinden eines Datensatzes zu ermöglichen. Nehmen wir einfach
einmal an, daß wir 300 Datensätze zu je
80 Zeichen anlegen wollen. Wie oben
schon erwähnt, öffnen wir zuerst einmal
den Floppybefehlskanal. Anschließend
folgt der OPEN-Befehl für das relative
File. Wir legen dabei wie gewohnt die
logische Filenummer, die Gerätenummer
und die Sekundäradresse fest, und geben
einen Namen für unsere Datei an. An diesen angehängt folgt die Typenkennung
", L," sowie der Länge des Datensatzes
in diesem File. Hier ein kleiner Hinweis: im ersten Teil dieses Kurses
erwähnte ich, daß die Kennung für eine
relative Datei beim Öffnen ein " R" ist.
Das war leider eine Fehlinformation!" L" ist die richtige Bezeichnung für den
OPEN-Befehl!
Hier nun ein Beispiel, in dem wir das relative File " TEST" mit 80 Zeichen pro
Datensatz eröffnen:
OPEN 1,8,15 OPEN 2,8,3,"TEST,R,"+CHR$(80)
Der Befehlskanal hat nun die logische
Filenummer "1", das relative File die
"2" . Wichtig beim Üffnen desletzeren ist
auch die Wahl der Sekundäradresse, da
diese bei der Befehlsübergabe an den
Befehlskanal verwendet wird. Wählen Sie
bei der Sekundäradresse bitte nicht die
vorreservierten Nummern 0,1 und 15( sh.
Floppy-Kurs, Teil 1), sondern nur Nummern zwischen 2 und 14 . Nachdem wir nun
also alles geöffnet hätten, was wir
benötigen, müssen wir jetzt erst einmal
die gewünschten 300 Datensätze in der
REL-Datei anlegen. Das funktioniert eigentlich ganz einfach: wir sprechen lediglich mit einem speziellen Befehl den
300 . Datensatz an, und schreiben den
Wert 255 hinein. Die Floppy generiert in diesem Fall nämlich automatisch alle
fehlenden Datensätze - in unserem Beispiel also alle 300 . Das Generieren dieser Sätze kann jetzt einige Zeit in Anspruch nehmen, da die Floppy nun den
Speicherplatz, den sie für alle Sätze
braucht, automatisch belegt und mit den
Bytewerten 255 füllt. Stören Sie sich
bitte nicht daran, wenn während dieses
Arbeitsgangs ( wie auch bei allen anderen
Operationen mit relativen Files) die
Floppy-LED zu blinken beginnt, oder
trotz Zugriffs zeitweise erlischt. Das
ist ganz normal bei der Arbeit mit relativen Files.
Wenn die Floppy mit dem Anlegen der relativen Datei fertig ist blinkt sie
übrigens sowieso, da wir durch den Zugriff auf einen noch nicht existierenden
Datensatz einen " Record not present"- Fehler erzeugt haben, der uns jedoch
nicht weiter stören soll. Durch Auslesen
des Befehlskanals stoppen wir das LED-Blinken. Hier nun das Ganze als Programm.
10 OPEN 1,8,15 20 OPEN 2,8,3,"TEST,R,"+CHR$(80) 30 HI=INT(300/256): LO=300-256*HI 40 PRINT#1,"P"+CHR$(3)+CHR$(LO)+CHR$(HI) +CHR$(1) 50 PRINT#2,CHR$(255) 60 INPUT#1,A,B$,C,D: 70 PRINT A,B$,C,D 80 CLOSE1: CLOSE2
Die OPEN-Befehle aus den Zeilen 10 und
20 kennen wir ja schon. In Zeile 30 spalten wir die Zahl 300( die Anzahl der
Datensätze, die wir in unserer Datei
verwenden möchten) in Lowund High-Byte
auf, da mit dem CHR$- Befehl ja immer nur
8- Bit-Werte ( von 0 bis 255) übergeben
werden können, und durchaus mehr Datensätze möglich sein können, müssen wir
einen 16- Bit-Wert in Lo/ Hi-Folge an die
Floppy senden. Dies geschieht in der
folgenden Zeile - mit dem ersten PRINT#- Befehl senden wir den Positionierungsbefehl an die Floppy. Wohlgemerkt
geschieht dies über den Befehlskanal!
Aus dem Beispiel ist die Syntax des Positionierungsbefehls ersichtlich. Beginnend mit dem Zeichen " P"( für " positionieren") werden in Reihenfolge die Sekundäradresse der REL-Datei auf die sich
die Positionierung beziehen soll, die
Datensatznummer in Lo/ Hi-Folge, sowie
die Byteposition innerhalb des entsprechenden Datensatzes mittels CHR$- Codes
an die Floppy übermittelt. In Zeile 50 wird nun über den logischen Kanal des
REL-Files der Wert 255 in den positionierten Datensatz geschrieben. Da er, wie alle anderen vor ihm, noch nicht
existiert, beginnt die Floppy nun damit
alle Datensätze anzulegen, um den
Schreibbefehl in Record 300 ausführen zu
können. Es ist übrigens wichtig, daß Sie
beim Erzeugen einer REL-Datei den Wert
255 schreiben, weil dieser nämlich als
Endmarkierung beim Lesen dient. Hierzu jedoch später mehr.
In den Zeile 60 und 70 lesen wir nun
noch den Fehlerkanal aus und geben die
" Record not present"- Fehlermeldung aus, um die blinkende Floppy-LED zu löschen
und schließen anschließend die beiden
offenen Files - schon haben wir eine
REL-Datei zur Verfügung!
DER SCHREIBZUGRIFF
Möchten wir nun mit unserer selbst erstellten REL-Datei arbeiten, so müssen
wir sie natürlich öffnen. Hierbei ist
darauf zu achten, daß wir dieselbe Datensatzlänge angeben, wie wir sie beim
Erzeugen der Datei verwendet haben. Andernfalls kommt die Floppy nämlich mit
der Verwaltung der Datensätze durcheinander, was verheerende Folgen bei
Schreibzugriffen haben kann. Benutzen
Sie am Besten also den gleichen OPEN-Befehl, den Sie auch beim Erstellen benutzt haben! ! !
Wenn wir jetzt etwas in unsere Datei
schreiben möchten, so verfahren wir im
Prinzip genauso, wie beim Erstellen der
Datei ( denn das war ja nichts anderes
als das Schreiben in eine REL-Datei) .
Wir öffnen also zunächst Befehlskanal
und REL-Datei, positionieren mittels
Befehlskanal auf den gewünschten Datensatz und schreiben über die logische
Filenummer der REL-Datei Daten in diesen
Satz hinein. Hier ein Beispiel:
10 OPEN 1,8,15 20 OPEN 2,8,3,"TEST,R,"+CHR$(80) 30 PRINT#1,"P"+CHR$(3)+CHR$(1)+CHR$(0)+ CHR$(1) 40 PRINT#2,"DIESER TEXT WIRD NUN IN DA TENSATZ NUMMER EINS GESPEICHERT!"; 50 CLOSE1: CLOSE2
Im Positionierbefehl wird wieder die
Sekundäradresse 3 verwendet. Diesmal
positionieren wir jedoch auf Byte 1 des
ersten Datensatzes unserer Datei " TEST"
( Die Werte 1 und 0 entsprechen der
LO/ HI-Darstellung der Zahl 1-->256*0+1=1) . In Zeile 40 wird dann mit einem
ganz normalen PRINT#- Befehl ein Text in
den positionierten Datensatz geschrieben. Da der Datensatz diesmal schon existiert brauchen wir demnach auch keinen
Fehler von der Floppy auszulesen, da
vorraussichtlich keiner auftritt.
Anstelle des Textes könnte natürlich
auch eine Stringvariable stehen. Achten
Sie bitte darauf, daß Sie nie längere
Texte in einen Datensatz schreiben, als
selbiger lang ist, da Sie sonst einen
Teil der Daten im folgenden Datensatz
verlieren könnten.
Wichtig ist auch, ob Sie beim Schreiben
das Semikolon (" ;") verwenden, oder
nicht. Verwenden Sie es nicht, so können
Sie beim Lesen den INPUT#- Befehl verwenden. Dieser erkennt das Ende eines Lesevorgangs immer an einem " Carriage Return
Code"( kurz " CR"= CHR$(13)), der von
einem PRINT# OHNE Semikolon immer auto- matisch gesendet wird. In dem Fall müssen Sie aber auch bedenken, daß ein Text
den Sie schreiben nie länger als die
Datensatzlänge-1 sein darf, da das CR
ebenfalls als ganzes Zeichen in den Datensatz geschrieben wird.
Nun ist, wie wir später sehen werden, das Lesen mittels INPUT# nicht immer von
Vorteil, weshalb Sie einen Text auch MIT
Semikolon schreiben können. In dem Fall
müssen wir später beim Lesen eine GET#- Schleife verwenden, da keine Endmarkierung ( das " CR") für den INPUT#- Befehl
geschrieben wurde.
DER LESEZUGIFF
Auch der Lesezugriff weicht nicht sonderlich von den bisherigen Beispielen
ab. Wie immer öffnen die beiden Floppykanäle und positionieren auf den
gewünschten Datensatz. Nun haben wir
zwei Möglichkeiten unsere Daten wieder auszulesen:
Wurden die Daten OHNE Semikolon geschrieben, so genügt ein einfaches
" INPUT#2, A$" um unseren Text wieder zu
Lesen und in A$ abzulegen.
Wurden Sie MIT Semikolon geschrieben, so
müssen wir den umständlicheren Weg über
eine GET#- Abfrage gehen. Dazu zwei Anmerkungen:
1) Bei der GET#- Abfrage sollten nicht
mehr Zeichen gelesen werden, als maximal in dem Datensatz vorhanden sein
können. Bei einer Länge von 80 Zeichen wird die GET#- Schleife also
nicht mehr als 80 mal durchlaufen.
2) Was tun wir, wenn der Datensatzinhalt
kürzer ist, als die festgelegte Datensatzlänge? Wie ich oben schon einmal erwähnte, dient der Byte-Wert 255 als Endmarkierung innerhalb einer
REL-Datei. Dies stellt sich so dar, daß ein leerer Datensatz alle Bytes
mit dem Wert 255 gefüllt hat. Schreiben wir nun einen Text in diesen Da- tensatz, so werden alle benötigten
Zeichen mit dem Text überschieben.
Das darauf folgende Zeichen enthält
dann aber immer noch den Wert 255 .
Dementsprechend können wir sagen, daß
wenn wir beim Lesen eines Strings
dieses Zeichen erhalten, der String
zu Ende sein muß.
Durch diese beiden Punkte ergibt sich
also folgende Schleife zum Lesen eines
Strings:
90 ... 100 A$="" 110 FOR i=1 TO 80 120 GET#2,B$ 130 IF ASC(B$)=255 THEN 160 140 A$=A$+B$ 150 NEXT 160 ... DATABANKING MIT RELATIVEN FILES
Sie sehen, daß das Arbeiten mit REL-Files trotz aller Gegenteiligen Vorraussagen eigentlich relativ einfach ist.
Wir müssen jeweils nur richtig positionieren und können dann beliebig Lesen
und Schreiben. Nun gibt es jedoch noch
einige Kniffe, die man kennen sollte, wenn man effektiv mit relativen Dateien
arbeiten möchte. Diese will ich nun ansprechen.
DATENFELDER:
Bei jeder Datenverarbeitung werden Sie
meist mehrere Angaben in einem Datensatz
machen. Einfachstes Beispiel ist hier
eine Adressverwaltung. Hier müssen Sie
pro Datensatz einen Namen, Strasse, Wohnort, Telefonnummer, etc. angeben, die Sie nachher auch immer wieder auf
anhieb in Ihrer Adressdatei finden müssen. Diese einzelnen Einträge in einem
Datensatz nennt man Datenfelder. Wie bei relativen Files so üblich, sollte man
sich dann jeweils auf eine maximale Länge eines Datenfeldes beschränken. Sie
könnten nun für jedes Datenfeld eine
eigene REL-Datei anlegen, also beispielsweise eine Datei mit 30 Zeichen
pro Satz für alle Namen, eine mit 40 Zeichen für alle Straßen, eine mit 15 Zeichen für alle Telefonnummern, eine
mit 4 Zeichen für alle Postleitzahlen
usw. Diese Lösung birgt jedoch ein größeres Problem in sich: der C64 verwaltet
nämlich immer nur EINE offene REL-Datei.
Das bedeutet in der Praxis, daß Sie jedesmal, wenn Sie eine komplette Adresse
ausgeben wollen, alle Ihre REL-Files
nacheinander öffnen, lesen und schließen
müssen. Was das an Programmierund
Zeitaufwand beim Zugriff bedeutet ist
verheerend. Deshalb geht man in der Regel einen anderen Weg. Halen wir doch
einfach einmal an dem Beispiel der
Adressverwaltung fest. Zunächst wollen
wir uns einmal überlegen, welche und wieviele Felder wir verwenden wollen, und wie lang sie im einzelnen sein sollen. Für unsere kleine Adressverwaltung
wollen wir 6 Felder pro Datensatz definieren:
1) "Name" (20 Zeichen) 2) "Vorname" (15 Zeichen) 3) "Straße" (30 Zeichen) 4) "Postleitzahl" ( 4 Zeichen) 5) "Ort" (30 Zeichen) 6) "Telefon" (15 Zeichen)
Kommen wir nun zu dem Lösungsweg, denn
man hier in der Regel geht. Anstelle von
6 einzelnen Dateien legen wir nun eine
einzige Datei an, in der in einem Datensatz jeweils alle 6 Felder abgelegt werden. Dadurch ergibt sich eine Datensatzlänge von 114 Zeichen (20+15+30+4+30+15=114) . Wir können nun wiederum zwei
Wege gehen, mit denen wir die sechs Felder in einem Datensatz speichern. Ich
gehe dabei davon aus, daß die Einträge vom Programm schon abgefragt wurden und
in Stringvariablen stehen:
Die einfachere, dafür jedoch unflexiblere, Methode sieht folgendermaßen aus:
Wir schreiben mit mehreren PRINT#- Befehlen OHNE Semikolon alle Stringvariablen hintereinander in ein Datenfeld
hinein. Später können wir sie genauso
wieder mittels INPUT# einlesen. Hierbei
ist es egal, wie lang ein Feldeintrag
ist, solange er die vorgegebene Länge
nicht überschreitet ( wäre das bei allen
Feldern nämlich der Fall, so würden wir
mehr Zeichen in einen Datensatz schreiben, wie dieser lang ist, was man tunlichst unterlassen sollte) . Je nach dem, wie flexibel unser Adressverwaltungsprogramm sein soll entstehen nun jedoch
diverse Schwierigkeiten. So müssen wir
zum Beispiel immer alle Felder eines
Datensatzes einlesen, wenn wir eine Datei z. B. nach einem einzelnen Feld sortieren möchten. Für gerade diese Aufgabe werden dann immer 5 Felder zuviel gelesen, was sich wiederum auf die Verarbeitungszeit ReLativ auswirkt. Das zweite
Problem ist die Endmarkierung nach jedem
Feldeintrag. Wie oben ja schon dargestellt müssen wir bei dieser Methode
OHNE Semikolon arbeiten, und in dem Fall
hängt PRINT# immer ein ' CR' an einen
String an. Dadurch müssen wir von den
obigen Feldlängen jeweils ein Zeichen
abziehen ( der Name z. B. darf nicht mehr
20, sondern nur noch 19 Zeichen lang
sein) .
Sie sehen also, daß diese Methode zwar
einfacher zu programmieren, aber sicherlich unflexibler ist.
( bitte Teil 2 Laden. . . .)
Kommen wir zu der flexibleren Methode.
Hierbei halten wir wie oben an den
Feldlängen fest. Da wir nun ja wissen, wie lang ein Feldeintrag maximal sein
kann, können wir im Prinzip vorausberechnen, an welcher Stelle im Datensatz
welches Feld zu finden, bzw. abzulegen
ist, ohne daß sich zwei Felder überschneiden. Hierbei müssen wir darauf
achten, daß entweder ein Feld immer so
lang ist, wie es maximal sein darf ( bei
kürzeren Einträgen wird der Rest einfach
mit SPACE-Zeichen aufgefüllt), oder aber
wir verwenden den Trick mit der GET#- Abfrage beim Lesen. Letzeres ist wohl
die eleganteste Lösung, da sie weniger
Programmieraufwand erfordert und gleichzeitig die Leseund Schreibzugriffe
beschleunigt, da immer nur so viele Zeichen gelesen werden, wie auch wirklich
benötigt werden ( und nicht immer die
maximale Feldlänge) . Wollen wir nun einmal ausrechnen, an welchen Bytepositionen die einzelnen Felder abgelgt werden:
Feldename Beginn Länge
Name 1.Byte 20 Bytes Vorname 21.Byte 15 Bytes Straße 36.Byte 30 Bytes PLZ 66.Byte 4 Bytes Ort 70.Byte 30 Bytes Telefon 100.Byte 15 Bytes
Anhand der Länge eines Feldes können wir
immer die erste Position des folgendenden Feldes berechnen, indem wir die Anfangsposition im Datensatz mit der
Feldlänge addieren.
Nun wissen Sie ja, daß man beim Positionierbefehl nicht nur die Datensatznummer, sondern auch die Byteposition innerhalb des gewählten Datensatzes angeben kann. Und über diese Methode können wir nun ganz bequem jede der 6 Feldpositionen einstellen und den Feldeintrag hineinschreiben. Beim Lesen können
wir, da wir für jedes Feld ja die An- fangsposition innerhalb eines Datensatzes kennen, dieses ebenfalls direkt
anwählen und Lesen. Das ist vor allem
beim Sortieren einer REL-Datei von Vorteil, da wir nun nach allen sechs Feldern eine Datei beliebig sortieren können, ohne die übrigen fünf Felder noch
extra einlesen zu müssen. Das erhöht die
Arbeitsgeschwindigkeit ungemein. Ein
anderer Geschwindigkeitsvorteil ergibt
sich beim Ändern von Datensätzen. Wollen
wir zum Beispiel in einer Adressdatei
nur die Straße verändern, weil die betreffende Person umgezogen ist, so genügt es auf das Feld " Straße"( Byte 36 im entsprechenden Datensatz) zu positionieren und den neuen Eintrag hineinzuschreiben. Hierbei müssen Sie jedoch
auch beachten, daß der neue Straßenname
unter Umständen kürzer ist, als der alte. Wir müssen dann nämlich noch ein
CHR$(255) nachschicken, damit unsere
GET#- Schleife auch ihre Endmarkierung
findet. Andernfalls würde Sie die rest- lichen Zeichen des alten Straßennamens
mitlesen. Ein Beispiel:
Alte Straße --> "Bahnhofstrasse 76" Neue Straße --> "Am Brunnen 2" Ergebnis beim Lesen OHNE eine neue Endmar- kierung --> "Am Brunnen 2se 76"
Schreiben Sie aber aich bitte nur dann
ein CHR$(255) danach, wenn der neue
Straßenname kleiner als die maximale
Feldlänge ist. Andernfalls würden Sie
wieder über die Feldgrenze hinausschreiben und so das erste Zeichen des darauffolgenden Feldes überschreiben!
SORTIEREN ODER INDIZIEREN:
Oben habe ich schon einmal die Möglichkeit der Sortierung einer Datenbank angesprochen. Die augenscheinlich einfachste Art der Sortierung wäre wohl das alphabetische Ordnen der Einträge eines
Feldes ( z. B. des Namensfeldes), und das
anschließende Umkopieren der Datensätze, so daß im ersten Datensatz auch wirklich
der alphabetisch erste und im letzten
Datensatz der alphabetisch letzte Name
steht. Diese Lösung verwendet aber wohl
keiner, da man sich vorstellen kann, daß
die Sortierung durch das Umkopieren der
Datensätze einen extremen Zeitaufwand
bedeutet ( gerade bei einfachen Sortieralgorithmen ist die Anzahl der Austausche zwischen zwei Einträgen ungemein
hoch) . Zusätzlich erfordert diese Methode nach jedem Neueintrag das Umkopieren
der kompletten REL-Datei, da ein neuer
Datensatz ja ebenfalls irgendwo einsortiert werden muß, und damit alle folgenden Datensätze einen Datensatz weiter
nach hinten rücken. Lassen Sie uns also
diese Methode in dem Mülleinmer werfen
und die schnelle, komfortable und flexible Methode herauskramen:" Indizierung" heißt das Zauberwort!
Im Prinzip bedeutet dieses Wort nicht
mehr als " Sortieren", jedoch ist die
Handhabung etwas anders. Hier möchte ich
noch einmal auf das Beispiel der Adressverwaltung zurückgreifen. Gehen wir also
davon aus, daß wir unsere Adressen nach
den Namen der eingetragenen Personen
ordnen wollen. Zu diesem Zweck lassen
wir unseren Sortieralgorithmus nach und
nach alle Namen aus der REL-Datei auslesen und alphabetisch in eine Liste einordnen. Dabei wollen wir uns zu jedem
Namen auch seine Datensatznummer merken.
Letztere können wir dann als Referenz
auf den alphabetisch richtigen Datensatz
verwenden. Ich möchte Ihnen dies anhand
eines Beispiels verdeutlichen. Nehmen wir
einmal eine Adressdatei, die die folgenden vier Namenseinträge, in der Reihenfolge in der sie eingegeben wurden, enthält:
Satznummer Eintrag (Name)
1 Müller 2 Becker 3 Schmidt 4 Meier
Nun sortieren wir unsere Datei nach Namen und erhalten folgende Reihenfolge:
2 Becker 4 Meier 1 Müller 3 Schmidt
Die aphabetisch richtige Reihenfolge
legen wir nun z. B. in einem Variablenfeld ab. Dieses Variablenfeld nennt man
Index. Wenn wir nun den alphabetisch
ersten Eintrag lesen wollen, so schauen
wir uns einfach den ersten Eintrag in
der Indexliste an, und lesen den Daten- satz ein, der dort steht - im Beispiel
also den zweiten.
Damit wir nicht jedes mal neu sortieren
müssen ist es ratsam die Indexliste in
einem sequentiellen File auf Diskette
abzuspeichern. Wenn wir nun jedesmal, wenn die Adressverwaltung gestartet wird
das Indexfeld einlesen, so können wir
ganz einfach und schnell alphabetisch
geordnete Datensätze finden und bearbeiten. Ebenso sollten wir darauf achten, daß neue Datensätze in die Indexliste
richtig einsortiert werden und der Index
wieder neu auf Diskette gespeichert
wird.
Die Indizierung bietet jedoch noch weitere Vorteile. So können wir auch ganz
beliebig nach verschiedenen Feldern sortieren und gleichzeitig beide sortierten
Dateien weiterverwenden. Wenn Sie Ihre
Adressen manchmal besser über den Vornamen finden können, so ist es sinnvoll
auch nach dem Vornamen zu indizieren und
diesen Index als Alternative Verwenden zu können. Beim Umschalten zwischen zwei
Indizes müssen Sie dann jeweils die neue
Indexdatei einlesen. Dies ist sogar bequem möglich, da wir zu der einen offenen REL-Datei auch noch eine SEQ-Datei
öffnen dürfen, ohne daß der 64 er
verrückt spielt. Wir können so als deh eit den Index wechseln, ohne dabei
noch umständlicherweise die REL-Datei
schließen und anschließend wieder öffnen
zu müssen.
EIN WORT ZU NUMERISCHEN EINTRÄGEN:
Vielleicht wollen Sie irgenwann einmal
auch numerische Einträge in einer REL-Datei speichern. Das wäre bei unserer
Adressverwaltung zum Beispiel bei dem
Postleitzahlenfeld sehr gut denkbar. In
manchen Fällen kann uns das auch einen
Speicherplatzvorteil im Gegensatz zu der
Speicherung als String geben. Hierbei
müssen wir jedoch immer gewisse Regeln beachten, sonst werden Sie ganz schön
Probleme mit den Datensatzlängen bekommen. Schreiben Sie nämlich eine numerische Variable ( wie z. B." A" als Float-, oder " A%" als Integervariable) mittels
" PRINT#2, A"( oder " PRINT#2, A%") in eine
Datei, so wandelt die PRINT#- Routine
Ihre Variable immer in eine Zeichenkette, also eine String, um. Wenn diese
numerische Variable nun verschieden viele Stellen besitzen kann, so werden Sie
ganz schöne Schwierigkeiten bekommen.
Logischerweise hat die Zahl "100" eine
Stelle weniger als die Zahl "1000" . Hinzu kommt, daß auch noch ein "-" als Vorzeichen davor stehen kann und daß der
PRINT#- Befehl immer noch ein SPACE-Zeichen ("") vor einer Zahl ausgibt, oder daß ganz kleine oder ganz große
Zahlen in der wissenschaftlichen
Schreibweise ( also z. B."3 .456+ E10") ausgegeben werden.
Wie lang sollen wir dann unser Feld nun
machen? Eine Antwort darauf ist schwierig, da das dann immer auch ganz von dem
Verwendungszweck abhängt. Entweder müssen Sie dem Benutzer schon bei der Eingabe der Zahlen gewisse Einschränkungen
auferlegen, oder aber durch eigene Umkonvertierung einer Zahl eine bestimmte
Bytelänge festlegen können.
Letzteres möchte ich hier als Beispiel
aufzeigen. Wir wollen uns auf positive
Integerzahlen beschränken, also ganze
Zahlen ( ohne Nachkommastellen), die von
0 bis 65535 gehen. In diesem Fall genügt
es eine solche Zahl in Lowund Highbyte
aufzuspalten und die beiden CHR$- Codes
zu speichern. In der Praxis sieht das
ähnlich wie in der Syntax des Positionierbefehls für REL-Dateien aus:
ZA=1992 HI=INT(ZA/256): LO=ZA-256*LO PRINT#2,CHR$(LO);CHR$(HI);
Damit können S) e nun alle oben genannten
Zahlen schreiben. Aber eben nur diese.
Möchten Sie z. B. Float-Zahlen schreiben, so sollten Sie sich überlegen, wie groß
oder klein diese maximal sein können.
Dann können Sie sie sich einmal mit dem
normalen PRINT-Befehl auf dem Bildschirm
ausgeben lassen und die Stellen, die sie
einnehmen abzählen. Wenn Sie sicher sein
können, daß dies die maximale Länge einer Ihrer Zahlen ist, so können Sie sie
als Feldlänge definieren. Beachten Sie
aber auch immer, daß der PRINT#- Befehl
vor einer Zahl immer ein Leerzeichen
druckt und daß zusätzlich immer noch ein
Minus-Zeichen vor einer Zahl stehen
kann. Außerdem können Sie solche Zahlen
auch nur mittels des INPUT#- Befehls wieder einlesen, weshalb Sie noch ein Zeichen mehr für das CR mitrechnen sollten.
Ein anderer Weg um Float-Zahlen zu speichern wäre das direkte übernehmen der
5- Byte-Mantissen- Darstellung, wie das
Betriebssystem des 64 ers sie benutzt,
jedoch ist diese Möglichkeit relativ
kompliziert, da sie den intensiven Gebrauch von Betriebssystemsroutinen erfordert, wovon wir momentan die Finger
lassen wollen ( in einer der späteren
Folge: dieses Kurses werde ich aber noch
einmal auf dieses Thema zurückkommen) .
So. Das wäre es dann wieder einmal für
diesen Monat. In der nächsten Ausgabe
der " Magic Disk" wollen wir uns mit den
Direktzugriffsbefehlen der Floppy befassen, die es uns ermöglichen, auf die
Diskettenblocks direkt zuzugreifen.
(ub)
Floppy-Kurs: "Es rappelt in der Kiste..." (Teil 3)
Hallo und herzlich Willkommen zum dritten Teil dieses Kurses. Nachdem wir uns
in den ersten beiden Teilen um die sequentielle und die relative Dateiverwaltung gekümmert hatten, wollen wir nun in
die tieferen Ebenen der Floppy-Programmierung einsteigen. Diesen Monat
soll es um die Direktzugriffsbefehle
gehen, mit denen wir Daten auf einer
Diskette direkt manipulieren können.
Dabei haben wir die Möglichkeit auf die
einzelnen Datenblöcke selbiger zuzugreifen und sie unseren Wünschen entsprechend zu verändern. Bevor wir beginnen, sollten erst einmal ein paar Grundlagen
geklärt werden.
DIE DISKETTENSTRUKTUR
Zunächst wollen wir uns einmal die
Grundstruktur einer Diskette anschauen.
Nachdem sie mit dem Format-Befehl der
Floppy formatiert wurde, enthält Sie 35 konzentrisch angeordnete Spuren ( engl.
" Tracks") und pro Spur 17-21 Sektoren.
Diese Anordung ist typisch für das
1541- Format. Die unterschiedliche Anzahl
von Sektoren pro Spur ergibt sich aus
der physikalischen Anordnug der Spuren.
Dadurch, daß die Spuren von außen nach
innen immer kleiner werdenden Kreisen
entsprechen, wird demnach auch der
Kreisumfang, oder die " Länge" einer Spur
immer kleiner. Je kleiner nun die
Spurlänge ist, desto weniger Sektoren
haben auf ihr Platz. Die folgene Grafik
über den Aufbau einer Diskette soll Ihnen das verdeutlichen. . .
( Anm. d. Red. : Bitte wählen Sie jetzt
den 2 . Teil des Floppykurses aus dem
Textmenu!)
Daraus ergibt sich nun die folgende Sektorenverteilung:
Spuren Sektoren-pro-Spur
1-17 21 18-24 19 25-30 18 30-35 17
Wichtig ist, daß Sie wissen, wieviele
Sektoren ein bestimmter Track hat, da
wir diese Information bei den Direktzugriffsbefehlen benötigen, um einen speziellen Block anzusprechen. Versuchen
Sie nun aber Sektor 19 von Spur 35 zu
lesen, so erhalten Sie natürlich eine
Fehlermeldung von der Floppy zurück ( obwohl es einen Sektor 19 bei den Tracks
1-24 gibt!) .
DIE FLOPPY - EIN EIGENSTÄNDIGER COMPUTER
Wie ich vielleicht schon einmal erwähnte
stellt die 1541 einen vollblütigen, unabhängen Computer dar. Sie verfügt
über einen eigenen 8- Bit-Prozessor ( den
6502, der Vorgänger des 6510 aus dem
64 er, der auch im VC20 Verwendung fand), zwei I/ O Bausteine, die man auch VIA
nennt ( Typ 6522, das Vorläufermodell des
6526- CIA-Chips, wie er auch zweimal im
C64 vorhanden ist, und ebenfalls im VC20 eingesetzt war), einem 16- KB-ROM mit dem
Betriebssystem ( DOS) und 2 KB RAM als
Arbeitsspeicher für letzeres und Zwischenspeicher für Diskettenblocks. Die
VIA-Bausteine übernehmen dabei den Datentransfer mit dem 64 er, sowie die Bedienung der Floppymechanik. Natürlich
können Sie dem Floppyprozessor auch ein
Programm geben, das er abarbeiten soll.
Dieses muß in Maschinensprache geschrieben sein, wobei der Befehlssatz des 6502
identisch mit dem des 6510( aus dem
64 er) ist. Die Speicheraufteilung der
Floppy sieht folgendermaßen aus:
Adresse Belegung
$0000-$08002 KB RAM ab $1800 VIA1( serieller Bus) ab $1 C00 VIA2( Laufwerkssteuerung)$ C000-$ FFFF Betriebssystem-ROM (16 KB)
Alle übrigen Bereiche sind unbelegt. Wie
beim C64 auch, können Sie mit einem Programm, daß sich im Floppy-RAM befindet
die Betriebssystemroutinen der Floppy
aufrufen ( so funktionieren z. B. einige
Software-Floppyspeeder) .
Kommen wir nun zum RAM der Floppy, denn
das soll uns eigentlich interessieren.
Ein Datenblock auf einer Diskette ist, wie Sie wissen 256 Bytes lang. Diese
Datenlänge wird auch als Einheit für das
Floppy-RAM benutzt:256 Byte entsprechen
einer sogenannten " Page"( engl." Seite"
- den Assemblerprogrammierern unter Ihnen sicherlich ein geläufiger Begriff) .
Das Floppy-RAM wird nun in acht Pages
unterteilt, die von 0 bis 7 durchnummeriert sind. Die Pages 0,1 und 2 sollten
wir dabei außer Acht lassen, da sie für
die Zeropage, den Stack und den Befehls- puffer der Floppy vorreserviert
sind. Die Pages 3-7 sind Datenpuffer für
Diskettenblöcke. Wir werden sie von nun
an " Puffer" nennen.
DIE DIREKTZUGRIFFSBEFEHLE
Kommen wir nun endlich zu den Blockbefehlen selbst. Eigentlich gibt es drei
Gruppen, von Befehlen, mit denen wir
Abläufe innerhalb des " Floppy-Computers" steuern können: die Blockbefehle zum
lesen, schreiben, belegen und freigeben
von Blocks, die Memorybefehle, mit denen
wir den Floppy-Speicher manipulieren
können und die User-Befehle, die eine Verkürzung von Blockund Memorybefehlen
darstellen.
Bevor wir nun einen dieser Befehle benutzen können, müssen wir natürlich den
Floppybefehlskanal öffnen. Da wir aber
immer auch einen Arbeitspuffer im
Floppy-RAM benötigen, um die Direktzugriffsbefehle ( einige Blockbefehle und
zwei Userbefehle) anwenden zu können, müssen wir gleichzeitig auch einen Puffer für uns vorreservieren. Entweder
überlassen wir dabei die Wahl des Puffers der Floppy, oder aber wir bestimmen
die Puffernummer selbst. Letztes wird
wohl nur in ganz besonderen Fällen
vonnöten sein, weshalb wir uns auf die
erste Methode festlegen wollen. Das Üffnen des Befehlskanals mit der Zuweisung
eines Arbeitspuffers geschieht über das
Üffnen eines eigenen Datenkanals, der
für den Datentransfer mit dem Puffer
herangezogen wird. Dies geschieht folgendermaßen:
OPEN 1,8,15 :REM Befehlskanal öffnen OPEN 2,8,2,"#" :REM Pufferkanal öffnen
Hier öffnen wir also wie gewohnt den
Befehlskanal, dem wir die logische Filenummer 1 zuordnen. Der zweite OPEN-Befehl öffnet einen Kanal zur Floppy, den wir zum Auslesen und Schreiben unseres Datenpuffers verwenden werden. Das
Doppelkreuz ("#") zeigt der Floppy an, daß wir mit einem Datenpuffer arbeiten
möchten. Welche Nummer dieser hat, sei
der Floppy überlassen. Sie sucht nun
einfach einen freien Puffer aus, und
ordnet ihm unserem Kanal zu. Möchten wir
wissen, welcher Puffer ausgewählt wurde, so können wir seine Nummer direkt nach
dem Üffnen durch Lesen des Pufferkanals
erfahren:
GET#2,PN$ PRINT ASC(PN$)
Hierbei ist es wichtig, daß Sie den GET-Befehl innerhalb eines Programms
benutzen, da er vom Direktmodus aus
nicht verwendbar ist.
Wenn wir gleich einen bestimmten Puffer
für uns reservieren möchten, so müssen
wir beim OPEN-Befehl einfach die Puffernummer mit übergeben. Folgender Befehl
reserviert den Puffer Nummer 3( Floppy-Adressbereich $0300-$03 FF) für uns:
OPEN 2,8,2,"#3"
Wichtig ist, daß wir uns die Sekundäradresse merken, mit der wir den Pufferkanal geöffnet hatten. Sie wird später
dazu verwendet, um der Floppy mitzuteilen auf welchem Datenkanal Sie Daten für
uns bereitzustellen hat, bzw. von uns
empfangen wird ( ähnlich den Befehlen für
die RELative Dateiverwaltung) . Wie
üblich wählen wir bei der Sekundäradresse eine der freien Nummern von 2-14 .
DIE BLOCKBEFEHLE Nachdem wir nun einen Datenpuffer reserviert haben und einen Filekanal für diesen Puffer offen halten, können wir uns
jetzt mit den Befehlen beschäftigen, die
wir der Floppy geben können. Die erste
Gruppe dieser Befehle sind die Blockbefehle. Mit ihnen können wir die einzelnen Blocks einer Diskette " hardwaremäßig" ansprechen. Aufgrund eines Blockbefehls wird physisch auf den Block zugegriffen und in den Puffer eingelesen, bzw. aus ihm heraus geschrieben. Für die
Beispiele innerhalb der Befehlsbeschreibungen gelten die obig definierten logischen Filenummern ( Befehlskanal=1, Pufferkanal=2), sowie die Sekundärdresse 2 für den Pufferkanal.
1) BLOCK-READ (" B-R") Mit diesem Blockbefehl lesen wir einen
Diskettenblock in unseren Datenpuffer
ein. Hierbei müssen wir die Sekundäradresse unseres Pufferkanals, die Drive- nummer ( immer 0), den Track und den Sektor als Parameter übergeben. Hier die
allgemeine Syntax:
PRINT#BFN,"B-R";SA;DN;TR;SE
Hierbei gilt:
BFN = logische Filenummer des Befehlska- nals SA = Sekundäradresse des Pufferkanals DN = Drivenummer (immer 0) TR = Tacknummer SE = Sektornummer
( Diese Variablennamen und Abkürzungen
werde ich auch im Folgenden weiterverwenden.)
Mit der folgenden Anweisung wird also
Sektor 1 von Track 18 in unseren Puffer
eingelesen. Von dort aus können Sie Ihn
nun mittels einer GET-Schleife auslesen:
... 100 PRINT#1,"B-R 2 0 18 1" 110 FOR I=0 TO255 120 GET#2,a$: PRINT ASC(a$) 130 NEXT
. . .
2) BLOCK-WRITE (" B-W") Mit diesem Blockbefehl schreiben wir
einen Block aus unserem Datenpuffer auf
die Diskette. Seine Syntax ist analog
der von " B-R", nur daß diesmal der Block
geschrieben wird:
PRINT#BFN,"B-W";SA;DN;TR;SE
3) BUFFER-POINTER (" B-P") Für jeden Datenpuffer der Floppy existiert ein Zeiger, der auf das aktuelle
Byte im Puffer zeigt. Möchte man nun ein
ganz spezielles Byte aus dem Puffer auslesen, so muß nicht unbedingt der ganze
Puffer gelesen werden, um das gewünschte
Byte zu erhalten. Sie können mit dem
" B-P"- Befehl den Pointer auf selbiges positionieren und anschließend auslesen.
Als Parameter benutzen Sie ganz einfach die Sekundäradresse des Pufferkanals und
die Nummer des gewünschten Bytes minus
1 . Möchten Sie z. B. das 200 . Byte auslesen, so gehen Sie bitte wiefolgt vor:
PRINT#1,"B-P 2 199" GET#2,A$: A=ASC(A$)
Die zweite, oben angeführte Zeile liest
nun das 200 . Byte in eine Stringvariable
und speichert es als Zahl in der Variablen " A" .
4) BLOCK-ALLOCATE (" B-A") Mit diesem Floppybefehl können Sie gezielt einzelne Disketten-Blocks als " belegt" kennzeichnen. Das ist dann notwendig, wenn Sie Daten mittels " B-W" auf
einer Diskette speichern, zusätzlich
aber noch normale DOS-Files darauf ablegen wollen. Wird ein Block nämlich nicht
als belegt gekennzeichnet, so wird er
beim Speichern eines DOS-Files unter
Umständen überschrieben. Die belegten Blocks sind in der " Block-Allocation- Map"( BAM) einer Diskette gespeichert, die sich in Block 18,0 befindet. Mit dem
Block-Allocate- Befehl wird hier dann der
entsprechende Block als belegt eingetragen. Mehr zum Aufbau der BAM werden wir
im nächsten Teil des Floppy-Kurses lernen. Die allgemeine Syntax des " B-A"- Befehls lautet:
PRINT#BFN,"B-A"; DN; TR; SE
Möchten Sie also z. B. den Block 5 von
Spur 34 als belegt kennzeichnen, so müssen Sie folgenden Befehl benutzen:
PRINT#1,"B-A 0 34 5"
5) BLOCK-FREE (" B-F") Dieser Befehl ist das Gegenstück zu
Block-Allocate. Mit ihm geben Sie einen
belegten Block wieder zur Benutzung
frei. Die Syntax ist dabei identisch zu
" B-A" :
PRINT#BFN,"B-F"; DN; TR; SE
Um den obig belegten Block wieder freizugeben senden Sie den folgenden Befehl
an die Floppy:
PRINT#1,"B-F 0 34 5"
6) BLOCK-EXECUTE ( B-E) Dieser Befehl ist identisch mit Block-Read. Er beinhaltet jedoch zusätzlich, daß der Diskettenblock, der in den Puffer geladen werden soll, ein ausführbares Floppy-Programm ist, das nach dem
Laden direkt angesprungen wird.
DIE MEMORYBEFEHLE
Diese Floppybefehle beziehen sich auf
die Floppy selbst und haben mit dem Direktzugriff nur indirekt zu tun. Trotzdem sind sie nützlich zum Auswerten von
Daten, weshalb sie hier aufgeführt sind:
1) MEMORY-READ (" M-R") Dieser Befehl enstspricht dem PEEK- Befehl von BASIC. Mit ihm können Sie
gezielt einzelne, oder mehrere Speicheradressen der Floppy auslesen. Die allgemeine Syntax lautet dabei wiefolgt:
PRINT# BFN," M-R" ; CHR$( LO) ; CHR$( HI) ;
CHR$( N)
Hierbei stehen " LO" und " HI" für Lowund Highbyte der Adresse die gelesen
werden soll und " N" für die Anzahl Bytes
(0-255), die ab dort übertragen werden
sollen. Das Lowund Highbyte einer
Adresse ermitteln Sie mit folgenden Formeln:
HI= INT( Adresse/256) LO= Adresse-256* HI
Alle Parameter müssen als CHR$- Codes, also dirket, übertragen werden, und dürfen nicht als ASCII-Codes ( wie in den
bisherigen Befehlen) erscheinen. Die zu
lesenden Bytes werden ebenfalls als absoluter CHR$- Code auf dem BEFEHLSKANAL
( !) bereitgestellt ( nicht etwa auf dem Pufferkanal, da der interne Floppyspeicher nichts mit Datenblocks zu tun hat) .
Als Beispiel für die Verwendung von
" M-R" sei folgendes Programm aufgeführt.
Es liest den ASCII-Code der ID der zuletzt initialisierten Diskette aus dem
Floppyspeicher aus. Dieser wird vom
Floppy-Betriebssystem automatisch in den
Speicherstellen 18 und 19 gespeichert
( LO=18 ; HI=0 ; N=2) :
10 OPEN 1,8,15 20 PRINT#1,"M-R"; CHR$(18); CHR$(0); CHR$(2) 30 GET#1,I1$: GET#1,I2$ 40 CLOSE 1 50 PRINT "ID DER ZULETZT BENUTZTEN DIS KETTE: ";I1$;I2$
2) MEMORY-WRITE Mit diesem Befehl schreiben Sie Daten in
den Floppy-Speicher. Er entpricht dem
POKE-Befehl von BASIC. Auch hier über- tragen Sie zunächst die Adresse in Lowund Highbytedarstellung, sowie die Anzahl der zu schreibenden Bytes. Letztere
hieraufhin gesandt. Hier die allgemeine
Syntax:
PRINT# BFN," M-W" ; CHR$( LO) ; CHR$( HI) ;
CHR$( N) ; CHR$( W1) ; CHR$( W2) ; . . . ;
CHR$( Wn-1) ; CHR$( Wn)
3) MEMORY-EXECUTE Dieser Befehl dient dem Ausführen eines
Assembler-Programms innerhalb der
Floppy. Damit hat auch er einen Verwandten in BASIC: den SYS-Befehl. Als Parameter übergeben Sie hier einfach nur die
Startadresse des aufzurufenden Assembler- Programms:
PRINT#BFN,"M-E"; CHR$(LO); CHR$(HI) DIE USERBEFEHLE
Die dritte Gruppe der Floppy-Direkt- Befehle sind die Userbefehle. Sie stel- len eine Abkürzung von einigen Befehlen
aus den anderen beiden Gruppen dar. Die
Userbefehle beginnen alle mit einem " U", gefolgt von einer Zahl zwischen 1 und 9 .
Die ersten beiden Userbefehle sind wohl
die wichtigsten:
1)" U1" Der U1- Befehl ersetzt den Block-Read- Befehl. In der Praxis wird er öfter verwendet, da er gegenüber " B-R" einen Vorteil bietet. Wird mit " B-R" ein Block
eingelesen und anschließend der Inhalt
des Puffers vom 64 er aus ausgelesen, so
wird das erste Byte nicht mitgesandt.
Möchten wir dieses nun aber auch lesen, so müssen wir den " U1"- Befehl benutzen.
Er sendet es zusätzlich mit - im Prinzip
ist der U1- Befehl also besser als " B-R" .
Die Syntax ist identisch mit der von
" B-R" :
PRINT# BFN," U1" ; SA; DN; TR; SE Ansonsten verhält sich alles so, wie
auch bei " B-R" .
2)" U2" Der zweite Userbefehl ist der U2- Befehl
( Ähnlichkeiten mit dem Namen einer bekannten Rock-Band sind rein zufällig) .
Er ersetzt den " B-W" Befehl. Auch hier
ist der Vorteil des ersten Bytes gegeben. Sie benutzen den U2- Befehl, wie den
" B-W"- Befehl:
PRINT#LFB,"U2"; SA; DN; TR; SE
3)" U3- U8" Diese sechs Userbefehle ersetzen ganz
bestimmte " M-E"- Kommandos. Wird einer
dieser Befehle gesandt, so springt die
Floppy in eine Sprungtabelle ab Adresse
$0500 und führt das dort stehende Programm aus. In der Regel besteht selbiges
aus einer Reihe von " JMP $ XXXX"- Anweisungen, mit denen Sie nun auf einfache Weise, vom 64 er aus, selbstdefinierte Programme in der Floppy aktivieren können. Die Userbefehle 3-8 benötigen keine Parameter. Bei Benutzung der Befehle werden folgende Adressen jeweils
angesprungen:
Befehl Springt an Adresse
U3 $0500 U4 $0503 U5 $0506 U6 $0509 U7 $050C U8 $050F
Damit ersetzen diese Userbefehle also
" M-E"- Befehle wie z. B. :
PRINT#BFN,"M-E";CHR$(9);CHR$(5);
Wenn Sie den obigen Befehl abkürzen wollen, so genügt auch ein einfaches:
PRINT# BFN," U6"
4)" U9" Der " U9"- Befehl wurde eingebaut, um die
Kompatibilität der 1541 zum VC20, dem
Vorgänger des C64, zu wahren. Mit ihm können Sie die Floppy in den Modus des
entsprechenden Rechners schalten. Dies
geschieht über folgende Kombinationen
des " U9"- Befehls:
PRINT#BFN,"U9+"; schaltet in C64-Modus PRINT#BFN,"U9-"; schaltet in VC20-Modus
5)" U:" Mit diesem Userbefehl lösen Sie einen
RESET in der Floppy aus. Das Floppysystem wird dann wieder in den Einschaltzustand zurückversetzt. Der " U:"- Befehl
entspricht einem " SYS 64738" beim C64 .
Auch er benötigt keine Parameter.
Das war es dann wieder für diese Ausgabe. In der nächsten Magic-Disk werden
wir uns dann mit dem Aufbau von Files, der BAM und des Directorys beschäftigen.
In diesem Zusammenhang werden wir dann
die hier erlernten Floppy-Befehle in
praktische Beispiele umsetzen.
(ub) ---------------------------------------- FLOPPY-KURS "Es rappelt in der Kiste..." (Teil 4) ----------------------------------------
Hallo und herzlich willkommen zum vierten Teil unseres Floppy-Kurses. Nachdem
wir das letzte Mal die Direktzugriffsbefehle kennengelernt haben, wollen wir
uns dieses Mal mit der Struktur auf der
Diskette beschäftigen. Dabei werden wir
lernen, wie wir einzelne Diskettenblocks
so manipulieren, daß zum Beispiel der
Typ eines Files geändert ist, ein File
nicht mehr gelöscht werden kann, etc.
WAS STEHT WO?
Sicherlich erinnern Sie sich noch daran, daß die 1541 eine Diskette in 35 Spuren
zu je 17-21 Sektoren aufteilt. Wir wollen uns nun anschauen, wie das DOS ( so
nennt man das Betriebssystem der Floppy) nun Daten auf diesen Blocks ablegt.
Dabei müssen wir gleich einmal unterscheiden zwischen DOSinternen Verwaltungsblöcken und den reinen Datenblökken. In ersteren stehen Informationen
wie z. B. der Name der Diskette, ihre ID, welche Blocks belegt sind, und welche
nicht, die Directoryeinträge, etc. Den
Verwaltungsblöcken ist ausschließlich
der gesamte 18 . Track vorreserviert.
Datenblöcke weden nie dort abgelegt, dazu dienen jedoch dann alle anderen
Tracks (1-17 und 19-35) .
DIE DATENBLÜCKE
Zunächst wollen wir uns mit der Struktur eines Datenblocks beschäftigen. Diese
ist die einfachere, und demnach schneller erklärt. Dazu will ich Ihnen nun
zunächst einmal beschreiben, was in der
Floppy vorgeht, wenn ein File auf der
Diskette gespeichert wird.
Zu aller erst erhält die Floppy vom C64 die Meldung, daß nun ein Schreibzugriff
erfolgen soll. Gleichzeitig mit dieser
Information wird der Filename des zu
schreibenden Files, sowie sein Filetyp
übertragen. Die Floppy schaut nun in den
Blocks, die für die Directoryeinträge
definiert sind nach, wo ein Eintrag noch
frei ist, oder ob ein neuer hinzugefügt
werden soll. Warum das so ist, wollen
wir später bei den Verwaltungsblocks
klären. Nehmen wir also einfach einmal
an, daß Platz für einen Eintrag gefunden
wurde, und daß der angegebene Filename
noch nicht auf der Diskette existiert
( dies wird nämlich ebenfalls festgestellt und bei Zutreffen wird mit der
Fehlermeldung " File Exists" abgebro- chen) . Das DOS trägt nun im freien Eintrag den Namen und Typ des zu speichernden Files ein. Anschließend macht es
sich auf die Suche nach einem freien
Block in den Tracks 1-17, bzw.19-35, in
dem Sie die ersten Daten des Files ablegen kann. Wurde einer gefunden, so wird
seine Track und Sektornummer ebenfalls
im Directoryeintrag gespeichert. Im andern Fall wird mit der Meldung " Disk
Full" abgebrochen. Nun wird damit begonnen, die zu schreibenden Daten vom Rechner zu empfangen und zu speichern. Da
ein Diskettenblock nur 256 Bytes lang
ist, und zu speichernde Daten in der
Regel ein größeres Volumen haben, sucht
sich die Floppy jedesmal wieder einen
neuen freien Block, wenn der letzte
vollgeschrieben ist. Diese Blocks werden
anschließend als ' belegt' gekennzeichnet
und auf der Diskette vermerkt.
Nun fragen Sie sich bestimmt schon, wie
das DOS später wieder erkennt, welche
Blocks zu welchem File gehören und in welcher Reihenfolge diese geladen werden
sollen. Das ist nun ganz einfach gelöst.
Wie ich oben schon erwähnte kann ein
Datenblock 256 Bytes fassen. Als reine
Datenbytes werden davon jedoch nur 254 genutzt. Die ersten zwei Bytes sind nämlich für die Folgeblocknummer reserviert. Im ersten Byte steht der Track, im zweiten der Sektor des nächsten
Blocks, der zu diesem File gehört. In
diesem ist dann wiederum der nächste
Block vermerkt und so weiter. Um festzustellen, wann ein File aufhört, enthält
der letzte Block als Tracknummer den
Wert 0 . Im Byte für die Sektornummer ist
nun ein Wert enthalten, der die Anzahl
der in diesem Block benutzten Bytes
kennzeichnet, da am Ende, je nach Länge
der gespeicherten Daten, mit hoher Wahrscheinlichkeit nicht alle Datenbytes des
Blocks für das File benötigt werden.
Dieser Aufbau gilt übrigens für alle
Filetypen. Ausnahmen bestätigen jedoch die Regel, und so ist es auch in diesem
Fall. Relative Datenfiles benutzen eine
andere Datenstruktur, was in Ihrer besonderen Funktion begründet ist. Sie
benutzen sogenannte " Side-Sector- Blocks" um ihren relativen Aufbau verwalten zu
können. Da die Manipulation an relativen
Files jedoch wenig ratsam ist, und nur
selten einen Sinn macht, wollen wir den
Aufbau von Side-Sektor- Blöcken wegfallen
lassen.
DIE DIRECTORY-BLOCKS
Nun wissen wir also, auf welche Weise
Datenblocks miteinander verkettet sind.
Was uns nun interessieren soll, ist die
Art und Weise, mit der das DOS nun eine
Diskette verwaltet. Im Prinzip haben wir
das in obigem Beispiel auch schon angesprochen, nur wie funktioniert z. B. das
Heraussuchen eines freien Directoryeintrags, oder freien Blocks?
Nun, wie schon erwähnt, ist der Track 18 ausschließlich dem DOS vorbehalten. Im
Prinzip ist er komplett für das Directory zuständig, wobei hier natürlich
auch Informationen enthalten sind, die
wir nicht beim üblichen LOAD"$",8 angezeigt bekommen. Außerdem ist in einem
ganz speziellen Block die sogenannte
" BAM" abgelegt, was für " Block Availabillity Map" steht. Öbersetzt bedeutet
das nichts anderes als " Block Verfügbarkeits Karte", bzw. Liste. Hier steht
eingetragen, welche Blocks der Diskette
schon belegt sind, und welche nicht.
DER DISK-HEADER-BLOCK
Den Block, in dem die BAM enthalten ist, nennt man " Disk-Header- Block" . Er ist
der wichtigste Block auf der ganzen Diskette, da nur an ihm das DOS eine Diskette überhaupt erkennt und bearbeiten kann. Er liegt im Sektor 0 des 18 .
Tracks. Außer der BAM sind hier noch
viele andere Informationen gespeichert, wie z. B. Diskettenname und - ID. Wollen
wir uns nun eine Liste anschauen, aus
der Sie ersehen können, welche Bytes des
Disk-Header- Blocks welche Informationen
enthalten:
Byte Aufgabe
000 Hier ist die Tracknummer des ersten Directoryblocks enthal- ten (normalerweise 18). 001 Hier steht die Sektornummer des ersten Diretoryblocks (norma- lerweise 1) 002 Hier steht das 1541- Formatkenn-zeichen. Selbiges entspricht dem ASCII-Zeichen "A" (Code: 65 = $41). 003 Dieses Byte kennzeichnet, ob die eingelegte Diskette doppel- seitig formatiert ist (dann
steht hier der Wert 1) . Das ist
wichtig für die Benutzer einer
1571- Floppy, die in der Lage
ist, Disketten beidseitig zu
beschreiben. Die 1541 beachtet
dieses Byte nicht.
004-007 In diesen 4 Bytes ist die
Blockbelegung des ersten Tracks
vermerkt.
008-139 Blockbelegungsbytes für die
Tracks 2-34 .
140-143 Hier steht die Blockbelegung
des letzten,35 . Tracks.
144-159 Hier steht der Diskettenname, der bei der Formatierung angegeben wurde, und zwar im AS-CII- Code. Ist ein Name kürzer
als 16 Zeichen, so werden die
restlichen Bytes mit dem Wert
160 aufgefüllt.
160-161 Hier steht zweimal der Wert
160, was übrigens dem ASCII-Zeichen ' SHIFT-SPACE' entspricht.
162-163 Hier ist die zweistellige ID
der Diskette vermerkt.
164 Wert 160 .
165-166 Formatangabe der Diskette. Diese ist bei 1541- Disketten immer
"2 A" . Sie sehen sie im normalen
Directory, daß Sie mit
LOAD"$",8 geladen haben, immer
am Ende der ersten Zeile, in
der auch Diskname und - ID zu
finden sind.
167-170 Gefüllt mit ' SHIFT-SPACE'( Wert
160) .
171-255 Unbenutzer Bereich. Gefüllt mit
Nullen.
Wie Sie sehen kann hier schon eine Fülle
an Informationen abgelesen werden.
Zunächst einmal sehen wir hier, bei welchem Block das Directory beginnt. Das
ist in der Regel Sektor 1 von Track 18 .
Dieser Wert könnte jedoch mit Hilfe eines Diskmonitors, oder eines kleinen Block-Lese- und-Schreibprogramms beliebig
geändert werden.
Das Formatkennzeichen ist bei der Floppy
1541 das " A" . Es zeigt dem DOS an, daß
dies eine von einer 1541( oder einem
kompatiblen Laufwerk, z. B.1570,1571) formatierte Diskette ist. Dieses Formatkennzeichen wurde deshalb eingeführt, weil Commodore für ältere Bürorechner
ebenfalls eigene Laufwerke gebaut hat, die wiederum eine andere Diskettenstruktur aufweisen ( z. B. mehr Tracks und Sektoren) . Disketten von solchen Laufwerken
können von der 1541 zwar teilweise gelesen, nicht aber beschrieben werden. Deshalb verweigert die Floppy auch den
Schreibzugriff auf Disketten mit anderem
Formatkenneichen. Dies kann man sich
aber auch zunutze machen: ändert man
nämlich den Wert des 3 . Bytes, so kann
man eine Diskette softwaremäßig
schreibschützen. Ein Speichern, Löschen, oder Softformatieren ist nun nicht mehr
möglich. Wohl aber kann von der Diskette geladen werden.
Das 4 . Byte ist für die 1541 nutzlos.
Hier steht in der Regel eine 0( nur
sinnvoll in Verwendung von Laufwerken
mit 2 Schreib/ Leseköpfen) .
Wie Sie weiterhin aus obiger Liste erkennen können, sind nun jeweils vier
Bytes für die Blockbelegung eines jeden
der 35 Tracks reserviert. Die Bytes 4 bis 143 enthalten die oben schon angesprochene BAM. Auf sie werden wir gleich
zurückkommen.
Es folgen zum Schluß noch zwei Informationen über die Diskette. Zum einen der
16 Zeichen lange Diskettenname, zum anderen die ID, gefolgt von einem ' SHIFT-SPACE' und der DOS-Versionsnummer ( normalerweise "2 A") . Auch diese beiden Bereiche können verändert werden. Zusätzlich sollten Sie wissen, daß Sie nicht
nur die ID, sondern auch das SHIFT-SPACE- Zeichen, sowie das "2 A" verändern
können. Dadurch hat man die Möglichkeit
eine 5- stellige " ID" herzustellen( natürlich zählen für das DOS immer nur
noch die ersten beiden Zeichen) . Auf
diese Art und Weise ist es z. B. Möglich, daß die Diskettenseiten der Magic-Disk
die IDs " SIDE1" und " SIDE2" haben ( haben
Sie einmal darauf geachtet?) .
DIE BAM
Die BAM verwaltet, wie schon erwähnt, die freien und belegten Blocks einer
Diskette.140 Bytes sind für sie im
Disk-Header- Block reserviert. Wir haben
ebenso gelernt, daß jeweils vier Bytes
die Blockbelegungen eines Tracks kodieren. Wollen wir uns dies einmal anhand
des ersten Tracks betrachten. Dieser
verfügt ja über 21 Sektoren. Seine
" BAM-Bytes" sind in den Bytes 4 bis 7 des Disk-Header- Blocks (18/0) zu finden.
Im ersten Byte dieser vier Bytes ist nun
die Anzahl der freien Blöcke auf Track 1 vermerkt. In den folgenden drei Bytes sind nun die Zustände der Blocks 0-20 bitweise kodiert. Ist ein Bit in einem
der drei Bytes gesetzt, so entspricht
das der Information, daß der zugehörige
Block frei ist. Ist es gelöscht, so ist
der Block belegt. Die Verteilung der
Sektoren auf die drei Bytes sieht nun
folgendermaßen aus. Die Bits 7-0( in
dieser Reihenfolge!) des ersten Bytes
geben die Belegung der Sektoren 0-7 an.
Die Bits 7-0 des zweiten Bytes zeigen
den Status für die Blocks 8-15 . Die Bits
7-3 des dritten Bytes stehen für die
Blocks 16-20 . Bits 2-0 sind hier unbelegt. Zur besseren Öbersicht hier nochmal eine Tabelle (" DHB"= Disk-Header- Block) :
Byte 1 (für Track 1, Byte 5 des DHB)
Bit 7 6 5 4 3 2 1 0 Sektor 00 01 02 03 04 05 06 07 --------------------------------------
Byte 2 (für Track 1, Byte 6 des DHB)
Bit 7 6 5 4 3 2 1 0 Sektor 08 09 10 11 12 13 14 15 --------------------------------------
Byte 3 (für Track 1, Byte 7 des DHB)
Bit 7 6 5 4 3 2 1 0 Sektor 16 17 18 19 20 -- -- -- --------------------------------------
Alle gesetzten Bits (= freier Block) dieser drei Bytes werden nun gezählt und in
das 0 . Byte der entsprechenden Track-BAM
( für Track 1, Byte 4 des DHB), eingetragen. So baut sich nun die gesamte BAM
auf, wobei sich in Byte 3 die Belegung
natürlich ändert, da es ja auch Tracks
gibt, die nur 19,18 oder 17 Sektoren
haben. In letztem Fall sind dann nur
noch das 7 . und 6 . Bit von Byte 3 be- nutzt.
DIE DIRECTORY-BLOCKS
Kommen wir nun zu den Directoryblocks.
In ihnen sind die auf der Diskette enthaltenen Filenamen und deren Typ gespeichert. In einen Directoryblock passen 8 Fileeinträge. Sind alle Einträge voll, so wird ein neuer Directoryblock angelegt. Track und Sektor des erste Directoryblocks können aus dem Disk-Header- Block entnommen werden. Normalerweise
ist das Block 18/1 . Kommen wir nun zum
Aufbau eines Directoryblocks.
Zunächst einmal sind diese Blocks ebenso
wie normale Datenblocks über die Angabe
des jeweils nächsten Blocks miteinander
verkettet. Deshalb stehen in den ersten
beiden Bytes jeweils Track und Sektor des nächsten Directoryblocks. Der Letzte
ist mit dem Wert 0 als Trackund dem
Wert 255 als Sektornummer markiert. Hier nun eine Öbersicht mit der genauen Belegung eines Directoryblocks:
ByteNr. Aufgabe
000 Track des Folgeblocks 001 Sektor des Folgeblocks 002-033 Fileeintrag Nr. 1 034-065 Fileeintrag Nr. 2 066-097 Fileeintrag Nr. 3 098-129 Fileeintrag Nr. 4 130-161 Fileeintrag Nr. 5 162-193 Fileeintrag Nr. 6 194-225 Fileeintrag Nr. 7 226-255 Fileeintrag Nr. 8
Jeder einzelne Eintrag belegt also 32 Bytes im Directoryblock. Ich muß dies
allerdings korrigieren. Im Prinzip werden immer nur die ersten 30 Bytes benutzt. Die letzten beiden Bytes eines
Eintrags sind immer unbenutzt. Daher ist
Eintrag Nr.8 auch nur 30 Bytes lang.
Die 2 unbenutzten Bytes fehlen hier ein- fach, was auch keinen Unterschied macht.
Nun wollen wir uns einmal anschauen, welche Informationen in den 30 Bytes
eines File-Eintrags zu finden sind. Auch
hier will ich Ihnen eine Tabelle geben, damit die Öbersichtlichkeit gewahrt
bleibt:
000 Byte für den Filetyp.
001-002 Track und Sektor des ersten
Datenblocks dieses Files.
003-01816 Bytes für den Filenamen.
019-020 Bei relativen Files stehen hier
Track und Sektor des ersten
Side-Sektor- Blocks ( sonst unbenutzt) .
021 Bei relativen Files seht hier
die Länge eines Datensatzes
( sonst unbenutzt) .
022-025 unbenutzt 026-027 Zwischenspeicher des Tracks und
Sektors beim Öberschreiben mit dem Klammeraffen (" ") vor dem Filenamen. Dies ist nur ein temporärer Speicher. Normaler- weise werden diese Bytes nicht beschrieben. 028-029 Hier steht die Länge des Files in Blocks als 16-Bit-Lo/Hi- Zahl.
In Byte 0 ist der Filetyp kodiert. Desweiteren finden sich hier auch Informationen darüber, ob das entsprechende
File ordnungsgemäß geschlossen wurde und
ob es schreibgeschützt ist. In ersterem
Fall ist das File im normalen Directory
mit einem "*" markiert. Das zeigt an, daß das File ungültig ist. Ein File ist
z. B. ungültig, wenn Sie versuchen etwas
auf eine volle Diskette zu speichern.
Die Floppy erkennt nun während des
Schreibens, das kein Platz mehr vorhanden ist, und bricht ab. Zuletzt wird das fehlerhafte File noch als ungültig
erklärt, damit man es nicht mehr laden
kann. Mit einem Validate-Befehl an die
Floppy wird ein solches File dann aus
dem Directory entfernt.
Ein schreibgeschütztes File kann nicht
mehr überschrieben oder gelöscht werden, solange es geschützt ist. Dadurch können
Sie das ungewollte Löschen von Daten
verhindern. Ein so geschütztes File ist
im Directory mit einer spitzen Klammer
nach links ("<") markiert. Wenn Sie sich
einmal das Inhaltsverzeichnis der Magic- Disk anschauen, so werden Sie dort
mehrere Trennstrich-Files finden, die
schreibgeschützt sind.
Im Filetyp-Byte werden die einzelnen
Informationen nun folgendermaßen codiert: Ist Bit 7 gesetzt, so ist ein
File gültig, es wurde ordnungsgemäß geschlossen und kann geladen werden. Wenn
Bit 6 gesetzt ist, so ist das File
schreibgeschützt ( normalerweise ist es
gelöscht, also ungeschützt) . Die Bits 0
bis 2 enthalten die möglichen Filecodierungen. Werte von 0 bis 4 sind sinnvoll.
Bitte Teil 2 des Floppy-Kurses laden! ! !
Deren Bedeutung ist folgendermaßen aufgeteilt:
Bits2-0 Wert Filetyp
000 0 DEL (deleted) 001 1 SEQ (sequentielle Datei) 010 2 PRG (Programmdatei) 011 3 USR (Userdatei) 100 4 REL (relative Datei)
Wie Sie sehen, können aber noch andere
Werte eingetragen werden, die zwar keinen Filetyp spezifizieren, aber dennoch
möglich sind. Dies hat zur Folge, daß
die Files ganz merkwürdige Bezeichnungen
erhalten. Wird zum Beispiel der Wert 15( Bit 3 auch noch mitbenutzt) eingetragen, so hat ein File die Bezeichnung
" ?" .
Alle anderen Bits des Filetyp-Bytes eines Directoryeintrags sind unbenutzt.
Ebenfalls interessant sind das 28 . und
das 29 . Byte eines Fileeintrags. Damit
können Sie nämlich ebenfalls einen kleinen Effekt für den Directoryeintrag erzielen. Da hier die Länge des Files in
Blocks angegeben ist, kann diese auch
verändert werden. Dies hat keinerlei
Einfluß auf das Laden oder Speichern
eines Files, da die Blockangabe vom DOS
nur dazu verwendet wird, um beim Ausgeben eines Directorys die Filelänge direkt greifbar zu haben. Wenn Sie nun
jemanden verblüffen wollen, so kann hier
z. B. der Wert 0 eingetragen werden. Laut
Directory befindet sich in dem File dann
nichts. Trotzdem kann sich dahinter ein
Wahnsinns-Programm verstecken.
Einen änhlichen Effekt können Sie auch
durch Manipulation der BAM erzielen.
Schreiben Sie hier nämlich andere Werte
in die jeweils 0 . Bytes der Trackbelegungen, so erhalten Sie eine andere Anzahl an freien Blocks. Das DOS addiert nämlich diese 35 Bytes auf, um die verbleibende Speicherkapazität (" Free
Blocks") einer Diskette zu ermitteln und
im Directory anzeigen zu können. Achten
Sie danach jedoch bitte darauf, daß Sie
nichts mehr auf eine solche Diskette
speichern, da nun nämlich vermeintliche
freie Blöcke beschrieben werden könnten, was zu erheblichem Datenverlust führen
kann. Am Besten ist es dann, einen
Schreibzugriff durch Ändern des Formatkennzeichens zu verhindern. Öbrigens
sollte ich erwähnen, daß Änderungen an
der BAM jederzeit durch den Validate-Befehl der Floppy wieder rückgängig gemacht werden können. Dieses Floppykommando geht nämlich nach und nach alle
Files der Diskette durch und merkt sich
die jeweils belegten Blocks. Hiernach
kann die ursrüngliche BAM wieder rekonstruiert werden.
Und noch etwas ist zu den Directoryeinträgen zu sagen. Wenn Sie ein File löschen, so wird es keineswegs komplett
von der Diskette entfernt. Das einzige, was der Scratch-Befehl bewirkt, ist das
Freigeben der belegten Diskettenblocks.
Desweiteren wird als Filetyp der Wert 0 eingetragen, was dem Filetyp " DEL" entspricht. Gleichzeitig ist das File als
' ungültig' markiert und nicht schreibgeschützt ( wieso auch) . Ein solcher Fileeintrag kann jederzeit wiederhergestellt werden, indem man einen definerten, gültigen, Filetyp einträgt und anschließend die Diskette validiert. Nun
ist das File wieder so vorhanden, wie
vor dem Löschen ( inklusive des Vermeks, welche Blocks belegt sind) .
Wird jedoch zwischenzeitlich etwas auf
der Diskette gespeichert, so sucht sich
das DOS zunächst immer einen solchen
' leeren' Fileeintrag heraus, bevor es
einen neuen Eintrag im letzten Directoryblock anlegt. Dadurch erklärt sich
auch, warum neu geschriebene Files
manchmal mitten im Directory zu finden sind und nicht am Ende. Hier wurde vorher an dieser Stelle ein altes File
gelöscht.
Dies soll es dann wieder einmal gewesen
sein für diesen Monat. Wenn Sie Ihre neu
hinzugewonnenen Kenntnisse einmal ausprobieren möchten, so nehmen Sie sich
einfach einen Diskmonitor zur Hand und
versuchen Sie einmal einige Directoryeinträge oder den Disk-Header- Block zu
verändern. Weiterhin kann mit dem Programm " Disk-Manager" auf dieser MD das
Directory noch weitaus komfortabler manipuliert werden. Nun wissen Sie ja
wahrscheinlich auch, was dabei geschieht.
Im nächsten Teil des Floppykurses wollen
wir uns mit der Floppybedienung in Assembler beschäftigen. Dabei werden wir
dann auch einige Programme erstellen, die sich die hier kennengelerntern In- formationen zunutze machen.
(ub) ---------------------------------------- Floppy-Kurs "Es rappelt in der Kiste..." (Teil 5) ----------------------------------------
Herzlich Willkommen zum 5 . Teil unseres
Floppykurses. In diesem Monat wollen wir
die Kenntnisse, die wir bisher über die
Floppy erfahren haben in die Praxis umsetzen. Das heißt, daß wir in diesem
Kursteil einige nützliche Anwendungen
zum bisher Erlernten programmieren werden. Diese werden zunächst in BASIC behandelt. Im nächsten Kursteil wollen wir
dann auf die Programmierung in Assembler
eingehen.
1) DISKETTE SCHREIBSCHÖTZEN
Kommen wir zu unserem ersten Programm- beispiel. Wie ich im dritten Teil dieses
Kurses schon erwähnte ist es ganz einfach möglich, eine Diskette softwaremäßig schreibzuschützen. Sie können anschließend weder etwas auf die Diskette
schreiben, noch etwas von ihr löschen.
Das Laden von Programmen funktiert jedoch nach wie vor. Einzige Möglichkeit
die Diskette wieder beschreibbar zu machen ist dann das Formatieren ( oder
nochmaliges Behandeln mit unserem Programm) .
Wie realisieren wir nun einen solchen
Schreibschutz? Nun, wie wir mittlerweile
ja wissen, legt die Floppy beim Formatieren einer Diskette im Diskheaderblock
einige wichtige Informationen über die
Diskette ab. Hier steht unter anderem
auch das Formatkennzeichen, das bei der
1541 den Buchstaben " A" darstellt. Andere Commodorelaufwerke ( z. B. das eines
alten CBM4040) haben dort ein anderes
Zeichen stehen. Da Commodore bei den
alten Laufwerkstypen ähnliche Aufzeich- nugsformate benutzte, sind die Disketten
von Commodorelaufwerken bis zu gewissen
Grenzen untereinander kompatibel. Meist
unterscheiden sie sich nur in der Aufzeichnungsdichte ( mehr Tracks auf einer
Diskette) . Um nun zu vermeiden, das
Laufwerke von verschiedenen Rechnern die
Daten einer Diskette eines anderen Laufwerks zerstören, wurde eine Sicherung in
die 1541 eingebaut. Sie beschreibt ausschließlich nur Disketten, die ein " A" als Formatkennzeichen tragen. Andere
werden nicht verändert, wohl aber kann
von Ihnen gelesen werden. Es genügt nun
also, das Formatkennzeichen in ein anderes Zeichen umzuwandeln, und die 1541 wird keine Daten mehr darauf schreiben.
Dies geschieht im Prinzip ganz einfach.
Wir müssen lediglich den Diskheaderblock
einlesen, das 3 . Byte ( hier steht nämlich das Formatkennzeichen) in ein anderes Zeichen als " A" abwandeln und den
Block wieder auf die Diskette zurückschreiben. Abschließend muß die Diskette noch initialisiert werden, damit das
Betriebssystem der Floppy, das DOS, die
neue Formatkennung auch in den Floppyspeicher übernimmt.
Ein Programm, das eine Diskette mit
Schreibschutz versieht und ihn wieder
entfernt finden Sie auf dieser MD unter
dem Namen " FK. SCHREIBSCHUTZ" . Die Unterroutine, mit der eine Diskette geschützt
wird, steht in den Zeilen 400 bis 470 dieses Programms. Hier sind sie nochmals
aufgelistet:
Unterroutine " Diskette sichern" :
400 OPEN 1,8,15,"I": OPEN 2,8,2,"#" 410 PRINT#1,"U1 2 0 18 0" 430 PRINT#1,"B-P 2 2" 440 PRINT#2,"B"; 450 PRINT#1,"U2 2 0 18 0" 460 PRINT#1,"I" 470 CLOSE1:CLOSE2:RETURN
Zunächst einmal öffnen wir hier den Befehlskanal und übergeben gleichzeitig einen Initialisierungsbefehl an ihn. Mit
letzterem werden alle Grunddaten der
eingelegten Diskette ( z. B. ID, Formatkennung, etc.) in den Speicher der
Floppy übertragen. Man sollte vor einer
direkten Manipulation des Diskheaderblocks diesen Befehl aufrufen. Desweiteren öffnen wir einen Pufferkanal mit der
Kanalnummer 2( also Sekundäradresse 2) um Blockoperationen durchführen zu können. In Zeile 410 wird nun Spur 0 von
Track 18 in den Puffer, dem Kanal 2 zugeordnet ist, eingelesen. Dies tun wir
mit dem " U1"- Befehl, der besser ist als
" Block-Read"(" B-R") . Hiernach folgt die
Positionierung des Pufferzeigers auf das
2 . Byte ( mit dem 0 . Byte eigentlich das
dritte Byte) des Puffers. In Zeile 440 wird nun der Buchstabe " B" an diese Position geschrieben. Wichtig ist hierbei
das Semikolon (" ;") nach dem PRINT-Befehl, da letzterer ohne Semikolon noch
ein CHR$(13) nachsenden würde, womit wir
einen Teil der BAM überschreiben würden!
Nun muß der geänderte Block nur noch mit
Hilfe des " U2"- Befehls auf die Diskette
zurückgeschrieben werden. Damit unsere
Änderung auch in den Floppyspeicher
übernommen wird muß abschließend die
Diskette wieder initialisiert werden. In
Zeile 470 werden die beiden Filekanäle
wieder geschlossen und zum Hauptprogramm
zurückgekehrt.
Nun soll unser Programm den Diskettenschutz ja auch wieder rückgängig machen
können. Dies geschieht im Prinzip wieder
genauso. Wir müssen lediglich die fremde
Formatkennung wieder in ein " A" umwandeln. Jedoch ergibt sich hierbei ein
kleineres Problem. Dadurch, daß die Diskette ja schreibgeschützt ist, können
wir den modifizierten Disk-Header ja
nicht mehr auf die Diskette zurückschreiben! Hier behilft man sich mit
einem kleinen Trick. In der Speicherstelle $0101( dez.257) des Floppy-RAMs
" merkt" sich das DOS das Formatkennzei- chen der eingelegten Diskette. Es wird
bei jedem Initialisierungsbefehl wieder
aktualisiert. Ändern wir nun diese Speicherstelle so um, daß in ihr der Wert
" A" steht, so können wir dem DOS vorgaukeln, es hätte eine Diskette mit korrekter Formatkennung vor sich. Erst jetzt
ist es wieder möglich auf die Diskette
zu schreiben. Auch hier möchte ich Ihnen
die Unterroutine, die entsprechendes tut
auflisten. Sie steht im oben erwähnten
Programm in den Zeilen 300-370 :
Unterroutine " Diskette entsichern" :
300 OPEN 1,8,15,"I": OPEN 2,8,2,"#" 310 PRINT#1,"U1 2 0 18 0" 320 PRINT#1,"M-W";CHR$(1);CHR$(1); CHR$(1);CHR$(65); 330 PRINT#1,"B-P 2 2" 340 PRINT#2,"A"; 350 PRINT#1,"U2 2 0 18 0" 360 PRINT#1,"I" 370 CLOSE1:CLOSE2:RETURN
In den Zeilen 300 und 310 öffnen wir, wie gewohnt, unsere beiden Filekanäle
und lesen den Diskheaderblock in den
Puffer ein. Anschließend jedoch benutzen
wir den Memory-Write- befehl der Floppy
um den Inhalt von $0101 zu in " A" abzuändern. Die Adresse $0101 wird durch
das Low-Byte 1 und das High-Byte 1 dargestellt. Desweiteren wollen wir nur ein
Byte übertragen. Selbiges hat den AS-CII- Code 65, was dem Buchstaben " A" entspricht. In dieser Reihenfolge werden
die Parameter nun an den String " M-W" angehängt. Nach dieser Operation können
wir nun den Buffer-Pointer auf das Formatkennzeichen positionieren und über
den Pufferkanal den Buchstaben " A" heinschreiben. Da die Floppy seit dem " M-W"- Befehl glaubt, es läge eine Diskette
mit korrekter Formatkennung vor, können
wir in Zeile 350 auch erfolgreich den
Diskheaderblock zurückschreiben. Eine
Abschließende Initialisierung macht die
Diskette ab nun wieder beschreibbar.
Nun soll unser Programm aber auch in der
Lage sein, zu erkennen, ob eine Diskette
schreibgeschützt ist, oder nicht. Auch
dies ist sehr einfach zu realisieren.
Wir gehen dabei wieder den Weg über die
Speicherstelle $0101 des Floppy-RAMs.
Hier die Unterroutine die in den Zeilen
200 bis 260 zu finden ist:
Unterroutine "Formatkennung prüfen:" 200 OPEN 1,8,15,"I" 210 PRINT#1,"M-R";CHR$(1);CHR$(1); CHR$(1) 220 GET#1,A$ 230 PRINT "DISKETTE IST "; 240 IF A$="A" THEN PRINT "NICHT"; 250 PRINT "GESCHUETZT!" 260 CLOSE1:RETURN
Hier wird zunächst nur der Befehlskanal
geöffnet. Einen Puffer benötigen wir
nicht. Gleichzeitig wird die Diskette
initialisiert und somit ihr Formatkenn- zeichen in die Speicherstelle $0101 übertragen. In Zeile 210 greifen wir
diesmal mit Hilfe des Memory-Read- Befehls (" M-R") auf selbige zu. Wieder
werden Lowund High-Byte 1, sowie die
Länge 1 als CHR$- Codes übergeben. Anschließend können wir das Zeichen aus
dem Befehlskanal auslesen. Es folgt nun
eine Prüfung, ob es dem Buchstaben " A" entspricht. Wenn ja, so wird zuerst der
Text " nicht" ausgegeben und anschließend
der Text " geschuetzt!" . Andernfalls gibt
das Programm nur " geschuetzt" aus. Zum
Schluß schließen wir den Befehlskanal
wieder und kehren zum Hauptprogramm
zurück.
2) ÄNDERN DES DISKETTENNAMENS UND DER ID
Sicherlich haben Sie schon einmal ein
Programm benutzt, das den Namen und die
ID einer Diskette nachträglich verändert, ohne Daten zu zerstören. Wollen wir nun einmal selbst so etwas schreiben.
Zunächst möchten wir den Diskettennamen
verändern. Wie Sie aus dem letzten Kurs
bestimmt noch wissen, steht der Name
einer Diskette in den Bytes 144-159 des
Dir-Header- Blocks. Um ihn zu ändern
brauchen wir lediglich diesen Block in
einen Pufferspeicher einzulesen und den
neuen Namen an dieser Position hineinzuschreiben. Dabei müssen wir noch auf
zwei Dinge achten: Ist der neue Name
kürzer als 16 Zeichen, so müssen die
restlichen Zeichen mit dem ASCII-Code
160 aufgefüllt werden. Das ist der Code
für ' SHIFT-SPACE' der von der Floppy
automatisch an kürzere Namen angefügt
wird. Desweiteren darf der neue Name
natürlich nicht länger als 16 Zeichen
sein, da wir sonst ja weitere Daten im
Diskheaderblock überschreiben würden.
Das Programm mit dem Sie Disknamen und
- ID ändern können, finden Sie auf dieser
MD unter dem Namen " FK. NAMECHANGE" . Hier nun ein Auszug mit der Namensänderungsroutine bei Zeile 200 :
Unterroutine " Diskname ändern" :
200 OPEN 1,8,15,"I": OPEN 2,8,2,"#" 210 PRINT#1,"U1 2 0 18 0" 220 PRINT#1,"B-P 2 144" 230 NA$="" 240 FOR I=1 TO 16 250 GET#1,A$: NA$=NA$+A$ 260 NEXT 270 PRINT "AKTUELLER NAME: ";NA$ 280 INPUT " NEUER NAME";NA$ 285 IF LEN(NA$)>16 THEN NA$= LEFT$(NA$,16) 290 IF LEN(NA$)<16 THEN NA$=NA$+ CHR$(160): GOTO290 300 PRINT#1,"B-P 2 144" 310 PRINT#2,NA$; 320 PRINT#1,"U2 2 0 18 0" 330 PRINT#1,"I" 340 CLOSE1:CLOSE2:RETURN
In den Zeilen 200 und 210 öffnen wir,
wie gewohnt, unsere Kanäle und lesen den
Diskheaderblock in den Floppypuffer.
Anschließend wird der Pufferzeiger auf
das 144 . Byte des Puffers positioniert.
In den Zeilen 230-260 werden nun die 16 Bytes des Diskettennamens mit Hilfe einer Schleife ausgelesen und in der Variablen NA$ abgelegt. Diese wird anschließend ausgegeben um den aktuellen
Diskettennamen anzuzeigen. Es wird daraufhin nach dem neuen Namen gefragt, der nun seinerseits in die Variable NA$ kommt. In der Zeile 285 wird zunächst
geprüft, ob der neue Name zu lang ist.
Wenn ja, so wird er auf die ersten 16 Zeichen gekürzt. In Zeile 290 wird abgefragt, ob der eingegebene Name nicht zu
kurz ist. Wäre das der Fall, so würde
beim Schreibvorgang der alte Name nicht
ganz überschrieben werden und wäre dann
noch sichtbar. In diesem Fall füllt die
Schleife in Zeile 290 den Rest des
Strings NA$ mit ' SHIFT-SPACES' auf. Erst
jetzt kann wieder auf das erste Byte des Namens ( Byte 144 des Diskheaderblocks) positioniert werden und der String NA$ dorthin geschrieben werden. Abschließend
wird die Änderung mittels " U2" auf der
Diskette aktualisiert, letztere nochmmals initialisiert und alle Kanäle werden geschlossen.
Wollen wir die ID einer Diskette verändern, so müssen wir ähnlich arbeiten.
Hierbei wollen wir berücksichtigen, daß
wir für die Directoryanzeige sogar fünf
Zeichen ( anstelle von 2) zur Verfügung
haben. Sie sicherlich ist Ihnen aufgefallen, daß in den letzten drei Zeichen
der ID im Directory ( rechts oben) immer
der Text "2 A" steht, was eine Formatkennung darstellt. Diese Formatkennung
hat jedoch keinerlei Einfluß auf die, die im dritten Byte des Diskheaderblocks
zu finden ist. Sie wird lediglich zur
Anzeige beim Laden des Diretorys benötigt und kann im Prinzip beliebig geändert werden. Die drei Formatkennzei- chenbytes liegen glücklicherweise auch
direkt hinter den beiden Bytes für die
Disketten-ID, nämlich in den Bytes 162-166 des Diskheaderblocks. Hier nun der
Programmteil, der die ID einer Diskette
ändert:
Unterroutine "ID ändern" 400 OPEN 1,8,15,"I": OPEN 2,8,2,"#" 410 PRINT#1,"U1 2 0 18 0" 420 PRINT#1,"B-P 2 162" 430 ID$="" 440 FOR I=1 TO 5 450 GET#1,A$: ID$=ID$+A$ 460 NEXT 470 PRINT "AKTUELLE ID: ";ID$ 480 INPUT " NEUE ID";ID$ 490 IF LEN(ID$)>5 THEN ID$=LEFT$(ID$,5) 500 PRINT#1,"B-P 2 162" 510 PRINT#2,ID$; 520 PRINT#1,"U2 2 0 18 0" 530 PRINT#1,"I" 540 CLOSE1: CLOSE2: RETURN
Im Prinzip arbeitet diese Unterroutine
genauso wie die, die den Namen ändert.
Nur daß diesmal nicht 16, sondern nur 5 Zeichen für die ID eingelesen werden
müssen. Desweiteren kann eine ID auch
kürzer sein, als 5 Zeichen, nämlich
dann, wenn Sie wirklich nur die beiden
ID-Zeichen ändern wollen, das Formatkennzeichen aber gleich bleiben soll.
Deshalb wird in Zeile 490 der String ID$ nur auf 5 Zeichen gekürzt, falls er länger sein sollte. Ein kürzerer Text wird
als solcher übernommen und an die Entsprechende Position ( nämlich 162) geschrieben. Auch hier wird die Änderung
dann mittels " U2" gespeichert und die
Diskette initialisiert.
Das soll es dann wieder einmal gewesen
sein für diesen Monat. Ich hoffe ich
habe Sie zum " Spielen" mit den Blockbefehlen animiert. Nächsten Monat wollen
wir uns mit einer weiteren Anwendung der Blockbefehle beschäftigen und dann die
Programmierung der Floppy in Maschinensprache angehen.
(ub)
Floppy-Kurs: "Es rappelt in der Kiste..." (Teil 6)
Hallo und herzlich Willkommen zum sechsten Teil unseres Floppykurses. Wir wollen unsere Kenntnisse auch hier wieder
vertiefen und zwei weitere Beispielprogramme kennenlerenen. Eines, um das Directory auf dem Bildschirm anzuzeigen
und eines um gelöschte Files wieder zu
retten.
BEISPIEL 1: DIRECTORY ANZEIGEN
Unser erstes Programmbeispiel soll eine
Routine sein, mit der Sie das Directory
der eingelegten Diskette ohne Speicherverlust einlesen können. Dies ist ein
Hinweis auf eine besondere Eigenheit der Floppy. Wenn wir uns nämlich das Inhaltsverzeichnis einer Diskette einfach
nur anzeigen lassen wollen, so können
wir durch Angabe des Filenamens "$" die
Floppy dazu bewegen, uns sehr viel Arbeit abzunehmen. Sie beginnt dann nämlich damit, alle Directoryblöcke von
selbst einzulesen und sie für uns aufzubereiten. Ein direktes " herumpfuschen" auf der Diskette mittels der Block-Befehle entfällt.
Wollen wir uns nun verdeutlichen, was
passiert, wenn wir wie gewohnt das Directory mit LOAD"$",8 in den Speicher
des Computers laden. Wie schon erwähnt, ist das Dollarzeichen für die Floppy das
Kennzeichen für eine spezielle Aufbereitung des isketteninhalts. Sie sendet an
den C64 nun Daten, die dieser, wie bei
auch beim Laden eines normalen Programms
in seinen Basic-Speicher schreibt. Der
Computer behandelt das Directory im
Prinzip also wie ein Basic-Programm.
Dies wird dadurch bestätigt, daß das eingelesene Directory auch wie ein Basic- Programm, mit dem Befehl LIST auf
dem Bildschirm angezeigt wird. Sie können es sogar mit NEW wieder löschen, oder mit RUN starten, was jedoch nur
einen Syntax-Error bewirkt, da das Directory ja keine echten Basic-Befehle
enthält. Sinnigerweise übermittelt die
Floppy in der Directoryliste als erstes
auch immer die Blockanzahl eines Files, was für das Basic des C64 einer Zeilennummer entspricht. Diese Zeilennummern
sind zwar nicht immer in einer numerischen Reihenfolge, jedoch macht das Basic unseres Computers da keinen Unterschied. Der interne Aufbau eines Basic-Programms erlaubt es tatsächlich, Zeilennummern wahllos zu vergeben. Wenn wir
ein Programm direkt eingeben ist das
natürlich nicht möglich, da die Eingaberoutine des Betriebssystems die Zeile
anhand der Zeilennummer automatisch an
der logisch richtigen Position des Programms einfügt.
Der langen Rede kurzer Sinn ist, daß wir
beim Öbermitteln des Directorys von der
Floppy darauf achten müssen, daß diese
es uns in einer Form schickt, die eigentlich nur der Basic-Programm- Speicher
versteht, so daß die LIST-Routine das
' Directory-Programm' listen kann. Deshalb gibt e) einige Bytes, die für uns
wertlos sind und überlesen werden müssen. Hierzu erkläre ich Ihnen einfach
einmal den Aufbau des Files, das uns die
Floppy bei Angabe des Filenamens "$" übermittelt. Zuerst erhalten wir von
ihr, wie bei jedem File, daß normalerweise mittels LOAD in den Speicher des
Computers gelangt, eine Adresse, an die
das " Programm" geladen werden soll. Dies
ist die Basic-Startadresse $0801( dez.
2049) im Low/ High-Byteformat. Wir benötigen diese natürlich nicht, und können
sie deshalb überlesen. Von nun an ist
der Aufbau einer jeden Directoryzeile
gleich. Zuerst werden dabei zwei Linkbytes übertragen, die der Basic-Interpre-
ter benötigt, um die einzelnen Zeilen zu
verketten. Beide können wir getrost
überlesen. Als nächstes erhalten wir
dann die Blockanzahl in Low/ High-Byte- Darstellung, was für den Basic-Inter- preter einer Zeilennummer entpräche.
Diese Nummer müssen wir nun erst einmal
umrechnen, bevor wir sie ausgeben können. Hieran anschließend folgen nun ein
oder mehrere Spacezeichen, sowie der
Filename und die Filekennung im Klartext. Alle diese letzten Zeichen werden
im ASCII-Code übertragen, so daß wir sie
lediglich mit PRINT ausgeben müssen. Am
Ende bekommen wir dann noch eine Null
übermittelt, die das Ende der ( Basic-) Zeile kennzeichnet. An ihrer Stelle
müssen wir lediglich einen Zeilenvorschub ausgeben ( ein PRINT ohne Parameter) . Das Ende des Files ist natürlich
dann erreicht, wenn die Statusvariable
ST gleich 64 ist. Im Prinzip ist alles
also ganz einfach. Hier ist das entsprechende Basic-Programm, Sie finden es
auf dieser MD unter dem Namen " FK. DIR" :
10 gosub200 20 end 30 : 31 : 90 rem ***************** 91 rem * zeichen lesen * 92 rem ***************** 93 : 100 get#1,a$ 110 ifa$=""thena=0:return 120 a=asc(a$):return 130 : 131 : 190 rem ********************** 191 rem * directory anzeigen * 192 rem ********************** 193 : 200 print"{clr}";:open1,8,0,"$" 210 fori=1to4:get#1,a$:next 215 : 220 gosub100:bl=a 230 gosub100:bl=bl+256*a 240 print bl; 250 get#1,a$:printa$; 260 ifa$<>""then250 270 print 280 fori=1to2:get#1,a$:next 290 ifst=0then220 300 close1 310 return
In den Zeilen 100-120 sehen Sie eine
Routine, die uns ein Byte mittels GET# einliest und es als numerischen Wert in
der Variablen " a" abspeichert. Diese
Routine ist deshalb notwendig, da eine
Umwandlung des Strings " a$" bei dem Bytewert 0 einen " illegal quantity error" verursacht. Das liegt daran, daß ein
String, der nur den Bytewert 0 als Zeichen enthält, einem Leerstring entspricht. Deshalb muß mit der IF-THEN- Abfrage überprüft werden, ob " a$" ein
Leerstring ist, oder nicht.
Ab Zeile 200 sehen wir nun das Programm, das das Directory einliest und auf dem Bildschirm ausgibt. In Zeile 200 wird
zunächst einmal der Bildschirm gelöscht
und der Filekanal, der uns das Directory
sendet, geöffnet. Ich habe hier ganz
bewußt die Sekundäradresse 0 benutzt, da
sie der Floppy ja automatisch mitteilt, daß wir ein Lesefile öffnen ( sh. voherige Teile dieses Kurses) . In Zeile 210 werden nun die Startadresse und die beiden Linkbytes der ersten Zeile überlesen.
Danach beginnt die Hauptschleife des
Unterprogramms. In den Zeilen 220 und
230 lesen wir hier zunächst mit Hilfe
unserer " Zeichen lesen"- Unterroutine, Lowund High-Byte der Blockanzahl aus, verrechnen diese beiden Werte zu der
reellen Blockanzahl und speichern sie in
der Variablen " bl"( Wert= Lowbyte+256*- Highbyte) . Die Blockanzahl wird jetzt in
Zeile 240 auf dem Bildschirm ausgegeben.
Wir hängen an den PRINT-Befehl ganz bewußt ein Semikolon an, damit der folgende Text direkt hinter der Blockanzahl ausgegeben wird. Dies geschieht in den
Zeilen 250 und 260 . Dort lesen wir jeweils ein Zeichen mittels GET# ein, und
geben es anschließend auf dem Bildschirm
aus. War das eingelesene Zeichen ein
Leerstring, entspricht es dem Bytewert 0 und die Zeile ist zu Ende. Jetzt wird in
Zeile 270 eine Leerzeile ausgegeben, daß
der Cursor am Anfang der nächsten Zeile
steht. In Zeile 280 werden die beiden
Linkbytes der folgenden Directory-Zeile
überlesen. In der IF-THEN- Abfrage in
Zeile 290 wird überprüft, ob das Ende
des Files schon überlesen wurde. Wenn
nicht, wird an den Anfang der Hauptschleife verzeigt und alles beginnt von
vorne. Wenn doch, so wird der Filekanal
geschlossen und zum aufrufenden Programm
zurückverzweigt.
BEISPIEL 2: GELOESCHTE FILES RETTEN
Um Sie in die Benutzung der Blockbefehle weiter einzuführen, wollen wir uns jetzt
einem weiteren Programmbeispiel in BASIC
zuwenden. Wir wollen ein einfaches " UN-DELETE"- Programm schreiben. Mit ihm soll
es möglich sein, versehentlich mit dem
Floppybefehl " SCRATCH"(" S:") gelöschte
Dateien wiederherzustellen. Hierzu wollen wir zunächst einmal klären, was die
Floppy eigentlich macht, wenn sie ein
File löschen soll. Als erstes bekommt
Sie einen Befehl übermittelt, der sie
dazu auffordert ein ( oder auch mehrere) Files zu löschen. Als Beispiel wollen
wir einmal ein File mit Namen " TEST" heranziehen. Der entsprechende Floppybefehl an den Befehlskanal muß also lauten
" S: TEST" . Hieraufhin beginnt die Floppy
nun, das Inhaltsverzeichnis der eingelegten Diskette nach einer Datei zu
durchsuchen, die den Namen " TEST" trägt.
Wird sie gefunden, so trägt die Lösch-Routine des Floppy-Betriebssystems lediglich den Wert 0 als Filetyp für dieses
File ein, und sucht nun alle Blocks he- raus, die von dem File belegt werden.
Jetzt holt sich die Floppy die BAM der
Diskette in den Speicher, kennzeichnet
alle vom File belegten Datenblocks als
' unbelegt' und schreibt die BAM wieder
zurück auf die Diskette. Der Filetyp 0 entspricht dem Filetyp " DEL", der normalerweise im Directory nicht mehr angezeigt wird. An dieser Leerstelle wird
das File einfach übersprungen. Im Prinzip sind aber noch alle seine Informationen auf der Diskette enthalten. Zumindest einmal solange, bis Sie wieder
ein neues File auf die Diskette schreiben. Dann nämlich sucht sich die Floppy
den ersten freien Directoryeintrag heraus, der in unserem Fall, der des Files
" TEST" ist, und trägt dort das neue File
ein. Desweiteren kann es dann auch passieren, daß die Datenblocks, die von
" TEST" belegt wurden möglicherweise von
denen des neuen Files überschrieben werden. Ein UNDELETE-Programm hat deshalb
nur dann einen Sinn, wenn noch nichts neues auf die Diskette geschrieben wurde
( obwohl es auch andere Programme gibt, die selbst dann noch das eine oder andere retten können - hierauf kommen wir
später zurück) . Es muß lediglich alle
Einträge im Directory nach Filetyp-0- Einträgen durchsuchen und diese Anzeigen. Hieraufhin muß vom Benutzer entschieden werden, welchen Filetyp das
alte File hatte ( dies ist die einzige
Information, die über selbiges verloren
ging) . Ist dieser angegeben, so braucht
unser Programm nur noch den Entsprechenden Code in den Fileeintrag zu schreiben
und alle Blocks des Files zu verfolgen, die von ihm belegt wurden und sie abermals als ' belegt' zu kennzeichnen. Dies
kann man über die umständliche Metode
tun, indem man aus den Bytes 1 und 2 des
Fileeintrags Track und Sektor ersten
Datenblocks ermittelt, und nun alle verketteten Blöcke ( nächster Block steht
immer in den ersten beiden Bytes eines
Datenblocks) heraussucht uns diese mit- tels Block-Allocate- Befehl (" B-A") als
belegt kennzeichnet. Die zweite Methode
ist einfach den Validate-Befehl der
Floppy zu benutzen. Dieser sucht nämlich
automatisch alle Blocks von gültigen
Fileeinträgen im Directory heraus und
kennzeichnet sie als ' belegt' . Beide
Methoden haben ihre Vorund Nachteile.
Benutzen wir die erste Methode, so haben
wir bei höherem Programmieraufwand eine
weitaus höhere Arbeitsgeschwindigkeit
( da nur die Blocks eines Files herausgesucht werden müssen und nicht aller Files der Diskette, was sich vor allem
dann bemerkbar macht, wenn Sie besonders
viele Files auf der Diskette haben) .
Andererseits ist es bei dieser Methode
nicht so einfach, die Blocks eines REL-Files wiederzubelegen, da diese mit
zusätzlichen Side-Sektor- Blöcken arbeiten, die ebenfalls gesucht werden müssen. Das aber wiederum macht der Validate- Befehl für uns. Ausserdem arbeitet er
bei Disketten mit wenigen Files weitaus schneller als unsere Routine, da er ja
ein Floppyprogramm ist und ein zeitraubender Datenaustausch zwischen Floppy
und C64 entfällt. Da wir unser Programm
in BASIC schreiben wollen, ist er bestimmt immer noch etwas schneller. Ich
werde mich in meinem Beispiel nun jedoch
auf die " von Hand"- Methode beschränken.
Wenn Sie möchten, können Sie das Programm ja so erweitern, daß z. B. bei weniger als fünf Files automatisch der
Validate-Befehl benutzt wird und im anderen Fall die " von Hand"- Routine.
Zunächst will ich Ihnen nun eine Unterroutine vorstellen, die uns das Inhaltsverzeichnis einliest und alle Informationen darüber in Variablenfeldern
ablegt. Sie steht in dem Beispielprogramm " FK. UNDEL" auf dieser MD in den
Zeilen 200-510 . Wir benötigen sie, um
die einzelnen Fileeinträge zu isolieren
und feststellen zu können, welcher Eintrag einem DEL-File entspricht. Ich habe die Routine jedoch so ausgelegt, daß Sie
sie auch für andere Zwecke benutzen können, da sie so ziemlich alle Informationen des Directorys ausliest.
Vorher sind jedoch noch einige Anmerkungen zu der Routine zu machen. Vorab sei
gesagt, daß in den Zeilen 600-620 dieselbe Routine steht, wie wir sie auch
schon im Dir-Programm benutzten. Sie
liest ein Zeichen ein, und wandelt es in
einen Bytewert um, der nach Aufruf in
der Variablen " a" steht. Desweiteren
benötigen wir noch einige Variablenfelder, die im Hauptprogramm mit Hilfe der
DIM-Anweisung dimensioniert werden müssen. Hier eine Liste der Felder:
Name Elemente Inhalt
TY$ 144 Filetyp FT 144 Starttrack des Files FS 144 Startsektor des Files BL 144 Blockanzahl des Files NA$ 144 Name des Files
DT 18 Tracknummern des Dirs DS 18 Sektorennummern des Dirs
Die ersten fünf Felder aus dieser Liste
dienen ausschließlich der Speicherung
von Informationen über ein File. Da das
Directory maximal 144 Einträge beinhalten kann, werden alle diese Felder auf
maximal 144 Elemente dimensioniert. Die
Felder DT und DS werden benutzt um Track
und Sektor der Folgeblocks des Directorys zu speichern. Rein theoretisch ist
das zwar nicht unbedingt notwenig, da
die Folge der Directoryblöcke immer
gleich ist ( Block 1 in 18/1, Block 2 in
18/4, etc.), jedoch ist so einfacher mit
den Blocks zu handhaben. Im Prinzip
könnten wir auch das Feld DT wegfallen
lassen, da das Directory IMMER in Track
18 steht, jedoch kann es sich ja auch um
eine manipulierte Diskette handeln, die
das Directory woanders stehen hat ( hierauf wollen wir in einem der nächsten
Teile des Floppykurses zurückkommen) .
Nun möchte ich Ihnen die Routine, mit
denen wir die Diretcoryinformationen
einlesen nicht länger vorenthalten. Hier
der erste Teil von Zeile 200 bis 295 :
200 print"Gelesene Files:" 210 open1,8,15,"i":open2,8,2,"#" 220 print#1,"u1 2 0 18 0" 230 gosub600:tr=a 240 gosub600:se=a 245 : 250 dn$="":id$="" 260 print#1,"b-p 2 143" 270 fori=0to15:get#2,a$:dn$=dn$+a$:next 280 print#1,"b-p 2 162" 290 fori=1to5:get#2,a$:id$=id$+a$:next 295 :
In diesem Teil unserer Unterroutine werden die Vorbereitungen zum Einlesen des
Directorys getroffen, sowie der Diskettenname und die ID in die Variablen
" dn$" und " id$" eingelesen. Diese Variablen können Sie im Hauptprogramm eben- falls weiterverwenden.
Nachdem also in Zeile 200 ein Informationstext ausgegeben wurde, öffnen wir
in Zeile 210 den Befehlskanal und einen
Pufferkanal. Ersterer erhält die Filenummer 1, letzterer die Filenummer 2 .
Beim Öffnen des Befehlskanals initialisieren wir die Diskette auch gleichzeitig. In Zeile 220 senden wir nun den
ersten Befehl an die Floppy. Der U1- Befehl in dieser Zeile liest uns den
Dir-Header- Block ( Track 18, Sektor 0) in
den Puffer. In Zeile 230 und 240 lesen
wir nun die ersten beiden Bytes dieses
Blocks ein, und ordnen sie den Variablen
TR und SE zu, die als Variablen für den
aktuellen Track/ Sektor benutzt werden.
Sie enthalten nun Track und Sektor des
ersten Directoryblocks. Bevor wir uns
jedoch daran machen dieses auszulesen, nehmen wir uns aus dem Dir-Header- Block
noch den Diskettennamen und ID heraus.
In Zeile 250 werden diese beiden
Variablen zunächst einmal gelöscht. Als nächstes positionieren wir den Pufferzeiger auf die Position 144 des Dir-Header- Blocks. Die 16 dort folgenden
Bytes enthalten den Namen der Diskette, der mit Hilfe der Schleife in Zeile 270 in dn$ eingelesen wird. Ebenso wird mit
der ID verfahren. Sie steht in den 5 Bytes ab Position 163 . In Zeile 280 wird
darauf positioniert und der String id$ wird in Zeile 290 eingelesen. Kommen wir
nun zum zweiten Teil der Routine, in dem
die Directoryeinträge eingelesen werden:
299 q=0 300 print#1,"u1 2 0";tr;se 305 a=int(q/8):dt(a)=tr:ds(a)=se 310 gosub600:tr=a 320 gosub600:se=a 330 forj=1to8 335 printq 340 gosub600:ty(q)=a 350 gosub600:ft(q)=a 360 gosub600:fs(q)=a 370 x$="" 380 fori=1to16:get#2,a$:x$=x$+a$:next 390 na$(q)=x$ 400 fori=1to9:get#2,a$:next 410 gosub600:bl(q)=a 420 gosub600:bl(q)=bl(q)+a*256 425 get#2,a$:get#2,a$ 430 q=q+1 440 next j 450 iftr<>0then300 460 : 470 close1:close2 480 q=q-1 500 ifna$(q)=""thenq=q-1:goto500 510 return
In Zeile 299 wird nun zunächst die Laufvariable " q" initialisiert. Sie gibt
später an, wieviele Einträge in diesem
Directory gefunden wurden. Der U1- Befehl
in Zeile 300 liest nun den ersten Directoryblock ein, dessen Track und Sektor
ja noch in TR und SE stehen. Anschliessend werden beide Variablen im 0 .
Element von " dt" und " ds" abgelegt.
Hierbei wird die Hilfsvaiable " a" als
Indexvariable verwendet. Ihr wird der
Wert ( q dividiert durch 8) zugeordnet, da in einem Block ja immer 8 Fileeinträge stehen. In den Zeilen 310 und 320 werden nun schon einmal Trackund
Sektornummer des Folgeblocks in TR und
SE eingelesen.
Bitte Teil 2 des Floppy-Kurses laden !
Teil 2 Floppy-Kurs 6
Nun wird eine Schleife durchlaufen, die
die acht Fileeinträge dieses Directoryblocks in den entsprechenden Feldern
ablegt. Zur Information geben ich Ihnen
hier nocheinmal den Aufbau eines Directoryeintrags an ( sh. Floppy-Kurs, Teil
4) :
ByteNr. Aufgabe
000 Byte für den Filetyp.
001-002 Track und Sektor des ersten Datenblocks dieses Files.
003-01816 Bytes für den Filenamen.
019-020 Bei relativen Files stehen hier
Track und Sektor des ersten Si-Sektor- Blocks ( sonst unbenutzt)021 Bei relativen Files seht hier die Länge eines Datensatzes ( sonst unbenutzt) .
022-025 unbenutzt 026-027 Zwischenspeicher des Tracks und Sektors beim Öberschreiben mit
dem Klammeraffen ("") vor dem
Filenamen.
028-029 Hier steht die Länge des Files
in Blocks als 16- Bit-Lo/ Hi-Zahl
030-031 unbenutzt
In den Zeilen 340-360 werden nun, obiger
Liste entsprechend, der Reihe nach das
Filetypenbyte, sowie Starttrack und
- sektor des Files in die entsprechenden
Felder eingelesen. Die anschließenden 16 Zeichen stellen den Filenamen dar und
werden als Text, mit Hilfe einer
Schleife in das Feld " na$" eingelesen.
In Zeile 400 überlesen wir nun 9 Zeichen
des Fileeintrags, die für uns nicht von
Nutzen sind. In den Zeilen 410 und 420 werden nun noch Lowund High-Byte der
Blockanzahl eingelesen und umgerechnet
im Feld " bl" abgelegt. Hiernach werden
noch die beiden letzten unbenutzten
Bytes überlesen, um den Pufferzeiger auf den Anfang des nächsten Eintrags zu
positionieren. Nun wird die Laufvariable
q um eins erhöht und an den Schleifenanfang zurück verzweigt. Ist die
Schleife 8 Mal durchlaufen worden, so
haben wir alle Einträge dieses
Directoryblocks eingelesen und können
den nächsten Block laden. Da der letzte
Directoryblock immer Track 0 als Folgetrack beinhaltet können wir mit der IF-THEN- Abfrage in Zeile 450 prüfen, ob der
letzte benutzte Directoryblock schon
eingelesen wurde, oder nicht. Ist " tr" ungleich null, so muß die Schleife nochmals wiederholt werden und es wird zu
Zeile 300 zurückverzweigt. Im anderen
Fall sind wir beim letzten Directoryblock angelangt und können die Filekanäle schließen. In Zeile 480 wird nun
die Laufvariable " q" um 1 erniedrigt, da
dieser Feldindex beim letzten Durchlauf
ja noch keine neue Zuweisung erhielt.
Zeile 500 dient nun dazu, den effektiv
letzten Eintrag in diesem Directoryblock herauszusuchen, da ja nicht unbedingt
alle 8 Einträge in diesem Block benutzt
sein müssen. Dies geschieht einfach
darin, daß wir " q" solange herunerzählen
bis in der Variablen na$( q) ein Text
steht. Da Leereinträge nur Nullen enthalten, ist der Filename bei solchen
Einträgen 16 Mal der Wert CHR $(0), was
einem Leerstring ("") entspricht.
Abschließend wird in das aufrufende Programm zurückverzweigt. In unseren Feldvariablen befinden sich nun alle Informationen des Directorys, sowie der
Diskettenname in " dn$" und die ID in
" id$" . In der Laufvariablen " q" steht
die Anzahl der vorhandenen Einträge
minus 1( beachten Sie bitte, daß im
Indexfeld 0 ebenfalls ein Eintrag enthalten ist." q" bezeichnet lediglich die
Nummer des letzten Eintrags) . Nun wissen
wir also schon, wie wir die Directoryinformationen in den Computer bekommen.
Die Hauptroutine unseres UN-DEL- Progamms
muß jetzt also nur noch die Unterroutine bei Zeile 200 aufrufen um sie zu
erhalten. Um jetzt alle DEL-Einträge
herauszusuchen, müssen wir lediglich
alle Einträge heraussuchen, die in den
unteren 3 Bits des Filetypenbytes den
Wert 0 stehen haben. Wir müssen uns deshalb auf die unteren 3 Bits beschränken, da in den Bits 7 und 6 ja auch noch Informationen über das File gespeichert
sind. In den Zeilen 1100-1120 des Hauptprogramms werden diese Einträge nun herausgesucht und deren Feldindizes im
Feld " li" abgelegt. Das Hauptprogramm
von UNDEL beginnt übrigens bei Zeile
1000 . Aus Platzgründen will ich Ihnen ab
jetzt nur noch die Zeilen auflisten, die
für uns interessant sind. Der Rest dient
hauptsächlich der Bildschirmformatierung
und soll uns hier deshalb nicht interessieren. Das ganze Programm können Sie
sich ja einmal auf dieser MD anschauen.
Hier nun die Zeilen 1100-1120 :
1100 z=0
1105 fori=0 toq 1110 if( ty( i) and7)=0 thenli( z)= i: z= z+11120 next
Wie Sie sehen, benutzen wir hier die
Laufvariable " z" als Indexvariable für
das Feld " li" . Mit dem Ausdruck " ty( i) and 7" isolieren wir die unteren drei
Bits des Typenbytes und löschen die
evtl. gesetzten Bits 7 und 6 für den
IF-THEN- Vergleich. Wenn " z" nun größer
Null ist, so wurden DEL-Files gefunden.
Selbige werden in den Zeilen 1190-1230 auf dem Bildschirm ausgegeben und es
wird gefragt, welches davon gerettet
werden soll. In den Zeilen 1240 bis 1350 wird nun nach dem Typ des Files gefragt
und ob es evtl. auch vor erneutem
Löschen geschützt werden soll. Hier
wieder ein Programmauszug:
1240 gosub100:print"Zu retten: ";na$(li( 1250 print"0 - DEL" 1260 print"1 - SEQ" 1270 print"2 - PRG" 1280 print"3 - USR" 1290 print"4 - REL" 1300 input"Welchen Filetyp soll ich zuordne ";t1 1310 if(n<0)or(n>4)then1300 1320 print"File auch schuetzen (J/N) ?" 1330 geta$:ifa$=""then1330 1340 ifa$="j"thent1=t1+64 1350 t1=t1+128:ty(li(n))=t1
Wie Sie sehen, wird in die Variable " t1" eine Zahl zwischen 0 und 4 eingelesen.
Die Zahlen, die die einzelnen Typen
zuordnen, entsprechen den Codes, die
auch die Floppy zur Erkennung des Filetyps benutzt. In den Zeilen 1320-1340 wird nun gefragt, ob das File auch vor
dem Löschen geschützt werden soll. Dies
regelt Bit 6 im Filetyp-Byte. Soll das
File geschützt werden, so wird zu der
Variablen " t1" der Wert 64 hinzuaddiert, was dem Setzen des 6 . Bits entspricht.
Zusätzlich müssen wir noch Bit 7 setzen, das anzeigt, daß das File ein gültiges File ist. Dies geschieht durch addieren des Wertes 128 . Nun müssen wir nur
noch den neuen Wert für den Filetyp in
die Feldvariable " ty" übernehmen. Als
nächstes muß der neue Filetyp im Directory eingetragen und die Datenblocks
des Files als ' belegt' gekennzeichnet
werden. Dies geschieht in den Zeilen
1370-1550 . Hier wieder ein Auszug:
1370 gosub100:print"Schreibe neuen Eintrag" 1380 bl=int(li(n)/8):ei=li(n)-bl*8 1390 tr=dt(bl):se=ds(bl) 1400 open1,8,15,"i":open2,8,2,"#" 1410 print#1,"u1 2 0";tr;se 1420 po=2+ei*32 1430 print#1,"b-p 2";po 1440 print#2,chr$(t1); 1450 print#1,"u2 2 0";tr;se 1460 :
In den Zeilen 1380-1390 wird nun zunächst den Variablen TR und SE die Werte für den Directoryblock, in dem der
Eintrag steht zugewiesen. Als nächstes
öffnen wir einen Befehlsund einen
Pufferkanal mit den logischen Filenummern 1 und 2( wie auch schon in der
Dir-Lese- Routine) . In Zeile 1410 wird
der Block des Eintrags eingelesen.
Nun muß der Pufferzeiger auf den Anfang
des Eintrags positioniert werden. Da
jeder Fileeintrag 32 Bytes lang ist, errechnet sich seine Anfangsposition aus
seiner Position im Block ( steht in der
Variablen " ei", die in Zeile 1380 definiert wurde) multipliziert mit 32 .
Zusätzlich müssen wir noch zwei Bytes
aufaddieren, in denen Trackund
Sektornummer des Folgeblocks stehen
( ganz am Anfang des Directoryblocks) . In
Zeile 1430 wird nun auf die errechnete
Position positioniert. Nun müssen wir an
dieser Stelle nur noch das neue Typenbyte eintragen ( geschieht in Zeile 1440) und den Directoryblock mittels " U2" wieder auf die Diskette zurückschreiben.
Schon ist der Fileeintrag geändert!
Zum Schluß müssen wir noch alle vom File
belegten Blocks heraussuchen und wieder
neu belegen. Den ersten davon finden wir
in den Feldern " ft" und " fs" . Hier
wieder ein Programmauszug:
1465 z=0 1470 print"Belege Blocks neu... " 1480 tr=ft(li(n)):se=fs(li(n)) 1490 print#1,"b-a 0";tr;se 1500 print#1,"u1 2 0";tr;se 1505 z=z+1 1510 gosub600:tr=a 1520 gosub600:se=a 1530 iftr<>0then1490 1540 close1:close2 1550 printz;"Blocks gefunden."
Hier wird die Variable " z" dazu benutzt, die gefundenen Blocks zu zählen. In Zeile 1480 werden nun die Feldeinträge in " ft" und " fs" in TR und SE Übertra- gen. Zeile 1490 benutzt nun den Block-Allocate- Befehl um diesen Block als
' belegt' zu kennzeichnen. In Zeile 1500 wird er dann in den Puffer gelesen, da
seine ersten beiden Bytes ja auf den
nächsten Block des Files zeigen. Dessen
Trackund Sektornummer wird nun in den
Zeilen 1510 und 1520 in TR und SE eingelesen und für den nächsten Durchlauf
der Schleife verwendet. Sollte TR jedoch
den Wert 0 aufweisen, so sind wir beim
letzten Block angelangt und können die
Blockbelegung beenden. Dies regelt die IF-THEN- Anweisung in Zeile 1530 .
Somit wären alle wichtigen Teile des UN-DEL- Programms besprochen. Um einen
Gesamtüberblick zu erhalten, rate ich
Ihnen, sich das Programm auf dieser MD
einmal auszudrucken und anzuschauen.
Öbrigens beinhaltet das Programm noch
einen kleinen Schönheitsfehler: Wenn Sie
nämlich z. B.9 Files auf einer Diskette
haben, und das letzte davon löschen, so findet es die Directory-Lese- Routine
nicht mehr. Das liegt daran, daß nach
dem Löschen des Eintrags der zweite
Directoryblock ja nicht mehr benötigt
wird und deshalb als Folgeblock Track 0, Sektor 255 im ersten Directoryblock
eingetragen wird. Um diesen Fehler zu
beheben, können Sie ja eine Routine
schreiben, die alle Directoryblocks der
Reihe nach einliest. Das ist zwar sehr
zeitaufwendig, könnte jedoch den Fehler
beheben. Sie können dabei davon ausgehen, daß die Folgetracks und - sektoren
beim Directory immer gleich sind. Bei
einem vollen Directory (144 Einträge), können Sie so diese Einträge mit Hilfe
eines Diskettenmonitors einfach
herausfinden.
Ich verabschiede mich hiermit von Ihnen
bis zur nächsten Ausgabe, wo wir uns an
die Floppyprogrammierung in Assembler
heranwagen möchten.
(ub) ---------------------------------------- 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 !
---------------------------------------- Floppy-Kurs: "Es rappelt in der Kiste..." (Teil 7.2) ----------------------------------------
Sie können diese Routinen nun universell
verwenden, um Programmdaten ( z. B. die
High-Score- Tabelle in einem Spiel) auf
Diskette zu schreiben und wieder von ihr
zu lesen. Aber Achtung: die Routinen
sind nur zum reinen Lesen und Schreiben
gedacht! Files, die normalerweise mit
LOAD in den Rechner geladen werden, oder
mit SAVE abgespeichert wurden, können
nicht ganz so behandelt werden! Hierbei
müssen Sie beachten, daß die SAVE-Routine des Betriebssystems immer die
Anfangsadresse des Files in den ersten
beiden Bytes ( in Lo/ Hi-Byte- Darstellung) mitspeichert. Lesen Sie ein solches File
mit " BASIN" ein, so erhalten Sie in den
ersten beiden Bytes immer zuerst diese Adresse. Beachten Sie also, daß diese
beiden Bytes nicht zu den eigntlichen
Daten des Files gehören! Ebenso können
Sie kein mit " WriteFile" geschriebenes
File mit dem LOAD-Befehl laden, weil
hierbei nämlich die ersten beiden Bytes
als Startadresse gewertet werden und das
File anschließend irgendwohin in den
Speicher geladen wird! Zum Kopieren von
Files sind diese Routinen wiederum optimal geeignet, da sie die Ladeadresse ja
mitladen und mitspeichern.
Sie können sie aber auch so modifizieren, daß diese Adresse mitberücksichtigt
wird. Hierzu müssen Sie " ReadFile" lediglich so umprogrammieren, daß sie die
ersten beiden Bytes liest und als Anfangsadresse nimmt, bzw. daß " WriteFile" die gegebene Anfangsadresse, die in Xund Y-Register übergeben wird, vorher an
das zu speichernde File sendet. Noch
einfacher geht das aber mit den Betriebssystemroutinen für LOAD und SAVE.
Sie nehmen uns diese Arbeit nämlich kom- plett ab und sind zudem noch relativ
flexibel zu handhaben.
LOAD UND SAVE Wie schon erwähnt stellt uns das Betriebssystem ebenso zwei Routinen zur
Verfügung, mit denen wir komplette Files
nachladen, bzw. speichern können. Der
Aufruf ist hierbei sehr einfach. Zunächst einmal müssen wir unsere Files
wieder mittels SETNAM und SETPAR spezifizieren. Danach werden die Prozessorregister einfach nur noch mit den entprechenden Parametern gefüttert und die
benötigte Routine wird aufgerufen.
Kommen wir zuerst zur LOAD-Routine. Sie
liegt bei $ FFD5 und hat folgende Aufrufparameter:
Akku : Operationsflag ($00 oder $01) X-Reg. : Low-Byte der Ladeadresse Y-Reg. : High-Byte der Ladeadresse
Das Operationsflag, das in den Akku
kommt, hat folgende Bewandnis: die LOAD-Routine kann nämlich auch zum Verifizieren eines Files verwendet werden ( BA-SIC- Befehl " VERIFY") . Hierbei wird haargenau so verfahren wie beim Laden, jedoch mit dem Unterschied, daß das File
nicht in den Speicher geschrieben, sondern nur gelesen wird. Möchte man nun
laden, so muß der Wert 0 im Akku stehen.
Möchte man verifizieren, so gehört der
Wert 1 in ihn hinein. Desweiteren kann
in Xund Y-Register die Startadresse
des File übergeben werden, an die es
geladen werden soll. Die LOAD-Routine
überliest dann ganz einfach die ersten
beiden Bytes, die diese Adresse ja angeben, und liest das File anschließend an
die gegebene Adresse. Möchten Sie das
File jedoch an seine vorgegebene Adresse
laden, so müssen Sie Xund Y-Register
lediglich mit 0 füllen.
Zum besseren Verständins einmal eine
kleine Routine, die ein File mittels LOAD-Routine an seine im File vorgegebene Adresse lädt. Sie verlangt als Aufrufparameter die Filenamenlänge im Akku
und den Filenamen bei Adresse $0334 :
LDX #$34 ;Name bei $0334 (Länge ist LDY #$03 ; noch vom Aufruf im Akku) JSR $FFBD ;Namen setzen (SETNAM) LDA #01 ;Filenummer=1 LDX #08 ;Gerät Nr. 8 LDY #00 ;Sek.=0 für "PRG laden" JSR $FFBA ;Parameter setzen (SETPAR) LDA #00 ;Load-Flag in Akku TAX ;X/Y löschen (Startadresse TAY ;aus dem File holen) JMP $FFD5 ;und laden
Wie Sie sehen, werden SETNAM und SETPAR
genauso benutzt wie in der " ReadFile"- Routine. Im Prinzip können Sie die Angabe einer Filenummer weglassen, da Sie
sie selbst ja nicht brauchen. Es wird in
dem Fall gerade die Nummer gewählt, die
im Akku steht. Nur hat das den Nachteil,
daß der Ladevorgang nicht immer funktioniert, wenn Sie noch weitere Files offen
halten.
Das File steht am Ende der Routine an
der Stelle im Speicher, die von seinen
ersten beiden Bytes spezifiziert wurde.
Möchten Sie es allerdings an eine bestimmte Adresse laden, z. B.$1234, so
müssen Sie die beiden Befehle " TAX" und
" TAY" durch die folgenden beiden Zeilen
ersetzen:
LDX #$34 LDY #$12
Kommen wir nun zur SAVE-Routine des Betriebssystems. Sie wird ähnlich aufgerufen. Zunächst müssen wieder mittels SET-NAM und SETPAR der Name und die Parameter des Files gesetzt werden. Anschließend kann die SAVE-Rotine aufgerufen
werden. Sie benötigt jedoch ein paar
mehr Parameter, nämlich Startund End- adresse. Da hierfür die drei Prozessorregister nicht ausreichen, wurde folgender Weg gegangen: Zunächst legen Sie die
Staradresse des zu speichernden Bereichs
in Lo' Hi Darstellung in zwei aufeinanderfolgenden Adressen der Zeropage ab.
Hierzu bieten sich $ FB und $ FC an, da
sie vom Betriebssystem nicht benutzt
werden. Nun laden Sie einen " Zeropage-Zeiger" in den Akku. Dieser ist nichts
anderes als die Nummer der Speicherstelle in der Sie das Low-Bytes der Startadresse abgelegt haben. In unserem Beispiel also $ FB. Die Endadresse des zu
speichernden Bereichs legen Sie in Xund Y-Register ab und rufen anschließend
die SAVE-Routine auf. Ihe Einsprungadresse liegt bei $ FFD( . Hier wieder
eine Beispielroutine. Sie verlangt
wieder den Filenamen bei $0334 und
dessen Länge im Akku. Desweiteren müssen
Sie die Startadresse in $ FB/$ FC abgelegt
haben und die Endadresse in Xund Y-Register übergeben:
STX $FD ;Endadresse in $FD/$FE STY $FE ; sichern LDX #$34 ;Filenamen bei $0334 LDY #$03 ; setzen JSR $FFBD ; (SETNAM) LDA #01 ;Filenr. 1 LDX #08 ;Geräte Nr. 8 LDY #01 ;Sek.=1 für "PRG speichern" JSR $FFBA ;Parameter setzen (SETPAR) LDA #$FB ;Zeiger auf Startadr. laden LDX $FD ;Endadresse wieder in LDY $FE ; X- und Y-Reg. holen JMP $FFD8 ;Und speichern!
Wie Sie sehen, wird die Endadresse hierbei in $ FD/$ FE zwischengespeichert, um
den Aufruf korrekt durchführen zu können. Diese beiden Speicherstellen sind
übrigens ebenfalls vom Betriebssystem
unbenutzt.
Die beiden Programmbeispiele zum Laden
und Speichern mittels LOAD und SAVE finden Sie übrigens ebenfalls in dem File
" FK. I/ O. S" auf dieser MD. Ich verabschiede mich nun wieder einmal von Ihnen, und wünsche Frohe Weihnachten und
ein schönes neues Jahr, in dem wir dann
tiefer in die Assemblerprogrammierung
der Floppy einsteigen und einige Programmbeispiele des täglichen Gebrauchs
kennenlernen werden.
(ub) ---------------------------------------- Floppy-Kurs: "Es rappelt in der Kiste..." (Teil 8) ----------------------------------------
Herzlich Willkommen zum achten Teil des
Floppy-Kurses. In diesem Monat wollen
wir noch etwas weiter in die Floppyprogrammierung in Assembler einsteigen. Im
letzten Monat hatten wir ja schon angesprochen, wie man ein File öffnet und
mit ihm kommuniziert, sowie man Files
lädt und speichert. Diesmal will ich
Ihnen eine andere, effektiviere Methode
zeigen, mit der man die Floppy ansprechen kann.
DIE ALTE METHODE
In der letzten Ausgabe des Floppy-Kurses
hatten wir gelernt, wie man ein File
öffnet, es zur Datenübertragung bereitmacht, Daten an es sendet und von ihm
empfängt, und es anschließend wieder
schließt. Die dort behandelte Arbeitsweise war die einfachste, die man zum
reinen Lesen oder Schreiben eines Files
anwendet. Es gibt jedoch eine weitere
Methode Files anzusprechen. Diese ist
zwar ein wenig umständlicher, dafür aber
schneller und weitaus flexibler.
Rekapitulieren wir: Nach der ersten Methode gingen wir immer folgendermaßen
vor:
1)" SETNAM" mit den Parametern für den
Filenamen aufrufen.
2)" SETPAR" mit den Datenkanalparametern aufrufen.
3) Mit " OPEN" das File öffnen 4 a) War das File eine Ausgabedatei, so
mussten wir " CKOUT" aufrufen.
4 b) War das File eine Eingabedatei, so mussten wir " CHKIN" aufrufen.
5 a) War das File eine Ausgabedatei, so
wurden die Daten mit Hilfe von " B-SOUT" geschrieben.
5 b) War das File eine Eingabedatei, so
wurden die Daten mit Hilfe von " BA-SIN" gelesen.
6) Abschließend wurde mittels " CLRCH" die Standardausgabe wieder zurückgestellt.
7) Das File wurde mit " CLOSE" geschlossen.
Wie gesagt, ist diese Methode die einfachste, wenn Sie ein einziges File lediglich Lesen oder Schreiben wollen.
Wenn Sie nun aber mehrere Files offenhalten und ansprechen wollen, oder aber
auf einem Kanal Lesen und Schreiben wollen ( so wie das beim Befehlskanal unter
Mitbenutzung von Pufferkanälen oft der
Fall ist), so versagt diese Methode. Das
liegt daran, daß Sie mit der einfachen
Methode eigentlich nicht wirklich mit den Dateien kommunizieren. Dies geschieht dann nämlich mit einem Umweg
über das Betriebssystem. Das Aufrufen
von " CKOUT" entspricht zum Beispiel dem
Basic-Befehl " CMD" . Sie leiten damit die
Daten, die normalerweise auf dem Bildschirm ausgegeben werden, auf den
Floppykanal um. Nun ist es aber wiederum
nicht so einfach, einen zum Ausgabekanal
erklärten Filekanal, als Eingabekanal zu
benutzen. Ein folgendes " CHKIN" zeigt
nämlich keine Wirkung. Auch wenn Sie
zwei Files offenhalten, so funktioniert
das Umschalten zwischen diesen beiden
Files nicht immer. Der Grund dafür liegt
in der Art und Weise wie die Standard-Ein/ Ausgabefiles im C64 verwaltet werden.
DIE NEUE METHODE
Um nun effizienter mit der Floppy zu
kommunizieren, müssen wir auf spezielle Betriebssystemroutinen zurückgreifen, die eigens für die Datenkommunikation
mit der Floppy ( bzw. dem IEC-Bus, an dem
Floppy und Drucker angeschlossen sind), dienen. Diese Routinen werden im Prinzip
auch bei Verwendung der ersten Methode
vom Betriebssystem benutzt, jedoch muß
ein Standard-Kanal eben auch andere, virtuelle, Geräte ( wie Bildschirm, Tastatur, etc.) ansprechen können, weshalb
die Restriktionen dort etwas umständlicher sind. Deshalb ist eine Ausgabe über
BASOUT, oder die Eingabe über BASIN, auch immer langsamer, als wenn Sie die
entsprechenden I/ O-Routinen für den
IEC-Bus benutzen, da bei den beiden ersten Routinen erst einmal entschieden
werden muß, welche effektiven Ein-/ Ausgaberoutinen nun wirklich benutzt werden
müssen ( in unserem Fall nämlich die
IEC-Busroutinen) .
Kommen wir nun jedoch endlich zu den
neuen Routinen und deren Funktionsprin- zip. Hierzu müssen wir uns zunächst noch
einmal verdeutlichen, daß die Floppy im
Prinzip ein eigenständiger Computer ist, der nichts anderes tut, als darauf zu
warten, daß Befehle über den IEC-Bus
kommen, um sie auszuführen. Hierbei meine ich nicht die Floppy-Befehle, wie wir
sie bisher kennengelernt haben. Diese
stellen wiederum eine höhere Ebene von
Befehlen dar. Da der IEC-Bus als Kommunikationsschnittstelle zwischen C64 und
Floppy dient, werden auf Ihm Daten entweder vom Rechner zur Floppy oder umgekehrt hinund herbewegt. Die Verarbeitung dieser Daten ist dann dem jeweiligen Gegengerät überlassen, daß die Daten
empfangen hat. Noch dazu kann es vorkommen, daß sich bis zu 7 Geräte ( der C64, max.2 Drucker und max. vier Floppys) über ein und denselben Bus miteinander
unterhalten müssen. In dem Fall muß vor
einer Datenübertragung natürlich festgelegt werden welches der Geräte nun mit
welchem ( im Normalfall dem 64 er) kommu- nizieren soll. Hierzu dienen nun insgesamt sechs Betriebssystemroutinen.
Als erstes müssen wir wieder einmal ein
File auf die gewohnte Art und Weise öffnen. Zunächst übergeben wir die Filenamenparameter mit Hilfe von " SETNAM"( Akku= Länge des Namens, X/ Y= Zeiger auf Namensstring) . Anschließend muß " SETPAR" aufgerufen werden. Hierbei kommen Filenummer und Geräteadresse, wie gewohnt, in Akku und X-Register. Im Y-Register
wird die Sekundäradresse abgelegt. Jedoch müssen wir hier, anders als sonst, die gewünschte Sekundäradresse plus $60(= dez.96) übergeben. Dies hat Floppyinterne Gründe. Möchten wir also den Befehlskanal ansprechen, so muß die Sekundäradresse $6 F ( dez.111) übergeben
werden. Möchten wir ein PRG-File lesen, so müssten wir den Wert $60 übergeben, etc. Zuletzt wird wieder wie üblich das
File mittels " OPEN" geöffnet.
Soweit also nichts Neues. Nun müssen wir uns, wie vorher auch, verdeutlichen, ob
die Floppy senden, oder empfangen soll.
Anstelle von " CHKIN" oder " CKOUT" müssen
diesmal jedoch andere Routinen benutzt
werden. Möchten wir Daten empfangen, so
soll die Floppy mit uns " reden" . Deshalb
heißt die entsprechende Routine, die sie
dazu auffordert uns Daten zuzusenden
" TALK"( engl." reden") . Wollen wir Daten
senden soll die Floppy uns " zuhören" .
Die hierfür gedachte Unterroutine heißt
" LISTEN"( engl." hören") . Da es, wie
oben schon einmal beschrieben, bis zu 6 Geräte geben kann, die uns zuhören oder
mit uns reden sollen, muß mit " TALK" oder " LISTEN" auch mitgeteilt werden, welches der Geräte nun für uns bereit
sein soll. Hierzu müssen Sie vor Aufruf
der Routinen die Geräteadresse des gewünschten Gerätes im Akku ablegen.
Als nächstes muß dem Gerät an der anderen Seite mitgeteilt werden, welcher
seiner offenen Kanäle senden oder empfangen soll. Hierzu muß die Sekundär- adresse an das Gerät übermittelt werden.
Dies geschieht über die Routinen
" SECTLK" und " SECLST" . Beide verlangen
die gewünschte Sekundäradresse ( wieder
plus $60) als Parameter im Akku.
" SECTLK" müssen Sie nach einem " TALK" aufrufen," SECLST" nach einem " LISTEN" .
Nun ist alles vorbereitet, um Daten zu
lesen oder zu schreiben. Dies erledigen
Sie nun am besten mit den beiden speziellen IEC-Ein-/ Ausgaberoutinen, die
ich oben schon ansprach. Sie können sie
genauso wie " BASIN" und " BASOUT" benutzen. Zum Lesen von Daten benutzen Sie
die Routine " IECIN", die das gelesene
Byte im Akku zurückgibt. Zum Schreiben
benutzen Sie " IECOUT", der das zu
schreibende Byte im Akku übergeben werdeb muß.
Haben Sie die Einoder Ausgabe beendet, so müssen Sie das entsprechende Empfangs-, bzw. Sendegerät wieder in den
Wartezustand zurückbringen. Dies geschieht über die Routinen " UNTALK" und" UNLIST" . Die erstere müssen Sie nach
dem Empfang, die letztere nach dem Senden von Daten benutzen. Dies müssen Sie
auch immer dann tun, wenn Sie anschließend auf dem selben Kanal eine andere
Operation ausführen wollen ( z. B. Befehl
an Befehlskanal senden und anschließend
Floppystatus auslesen) .
Wenn Sie mit Ihrer Operation nun fertig
sind, so können Sie den zuvor geöffneten
Filekanal wieder schließen.
Wie Sie sehen ist diese Methode etwas
aufwendiger. Dafür aber können Sie nun
problemlos den Befehlskanal öffnen und
z. B. den " Memory-Read"- Befehl (" M-R") benutzen. Hierbei öffnen Sie einen Filekanal und teilen der Floppy mit, daß sie
Daten empfangen soll (" LISTEN" und
" SECLST" aufrufen) . Nun senden Sie mittels " IECOUT" den Befehlsstring. Anschließend setzen Sie die Floppy wieder
in der Normalstatus zurück, indem Sie
" UNLIST" aufrufen. Hiernach können Sie nun die zuvor verlangten Daten vom Befehlskanal abholen. Senden Sie einfach
ein " TALK" und " SECTLK" und lesen Sie
die Daten mit " IECIN" ein. Abschließend
muß die Floppy wieder mit " UNTALK" in
den Wartezustand geschickt werden und
wir können das File schließen.
Desweiteren ist das Bedienen von zwei
offenen Files nun weitaus besser möglich. Bevor Sie eines dieser Files ansprechen, müssen Sie einfach den entsprechenden Öbertragungsmodus festlegen und
seine Sekundäradresse an die Floppy
schicken. Denken Sie immer daran, daß
Sie nach jeder Leseoder Schreiboperation ein " UNTALK" oder " UNLIST" senden
müssen, damit die Floppy auf weitere
Befehle reagiert.
Hier nun noch die Einsprung-Adressen der
neuen I/ O-Routinen:
LISTEN: $FFB1 TALK: $FFB4 SECLST: $FF93 SECTLK: $FF96 UNLIST: $FFAE UNTALK: $FFAB IECOUT: $FFA8 IECIN: $FFA5 PROGRAMM-BEISPIELE
Kommen wir nun noch zu einigen Programmbeispielen, die Ihnen die Benutzung der
neuen I/ O-Routinen erläutern sollen.
Die Routinen aus Beispiel 1 und 2 finden
Sie, als Hypra-Ass Quellcode, auf dieser
MD unter dem Namen " FK. IO2 . S" . Der
Quellcode für Beispiel 3 steht im File
" FK. SHOWBAM. S" . Da es sich dabei um ein
eigenständiges Programm handelt, existiert hierzu auch noch ein, durch
' RUN' startbares, File namens " FK. SHOW-BAM" .
1) LESEN UND SCHREIBEN EINES FILES Zunächst einmal möchte ich Ihnen zeigen, wie die beiden Routinen " READFILE" und
" WRITEFILE" aus dem letzten Kursteil in
der neuen Methode aussehen. Hierbei müssen wir lediglich den Aufruf von " CH-KIN/ CKOUT" mit dem von " TALK/ SECTALK" oder " LISTEN/ SECLIST" ersetzen. Der Aufruf von " CLRCH" am Ende der Öbertragung
muß mit einem " UNTALK" bzw." UNLIST" ersetzt werden. Desweiteren muß den Sekundäradressen beim Üffnen der Wert $60 hinzuaddiert werden ('$60' bei ReadFile,'$61' bei WriteFile) .
Der Rest des Programms sollte sich von
selbst erklären. Damit man die alte und
neue Version der Routinen nicht miteinander verwechselt, heißen die neuen
Versionen " IECREAD" und " IECWRITE" . Die
Parameter, die beim Aufruf übergeben
werden müssen, sind dieselben wie bei
den alten Versionen:
IECREAD stx $ fb ; Startadresse in sty $ fc ; Zeiger ablegen.
ldx #$34 ;Zeiger auf ldy #$03 ; Filenamen jsr setnam ; setzen. lda #01 ;Fileparameter ldx #08 ; setzen ldy #$60 ; ($60=PRG Lesen) jsr setpar jsr open ;File öffnen lda #08 ;"Gerät Nr. 8: jsr talk ; rede bitte!" lda #$60 ;"Und zwar auf Se- jsr sectlk ; kundäradr. $60)!" ldy #00 ;Offset=0 loop1 jsr iecin ;Zeichen lesen sta ($fb),y; und ablegen inc $fb ;Zeiger bne l1 ; um 1 inc $fc ; erhöhen l1 lda $90 ;Ende erreicht? beq loop1 ;Nein, dann weiter!
lda #08 ; Sonst:" Gerät jsr untalk ; Nr.8 : Ruhe bitte!" lda #01 ; File jmp close ; schließen ;*************************************** IECWRITE stx $ fb ; Startadresse in sty $ fc ; Zeiger ablegen
ldx #$34 ;Filenamen ldy #$03 ; setzen jsr setnam lda #01 ;Fileparameter ldx #08 ; setzen ldy #$61 ; ($61=PRG schr.) jsr setpar jsr open ;File öffnen lda #08 ;"Gerät Nr8: Hör' jsr listen ; mir bitte zu!' lda #$61 ;"Und zwar auf Sek.
jsr seclst ; Adr.$61 !")
ldy #00 ;Offset=0 loop2 lda ($fb),y;Zeichen holen jsr iecout ; und abschicken inc $fb ;Zeiger bne l2 ; um 1 inc $fc ; erhöhen l2 lda $fe ;Und mit cmp $fc ; Endadresse bne loop2 ; vergleichen lda $fd ;Wenn ungleich cmp $fb ; dann weiter bne loop2 lda #08 ;"Gerät Nr.8: jsr unlist ; Bin fertig!" lda #01 ;File jmp close ; schließen 2) FLOPPYSTATUS ANZEIGEN
Ein weiterer Vorteil, der sich aus der
Benutzung der direkten IEC-Routinen ergibt, ist, daß Sie nun problemlos, während des Arbeitens mit einem File, Statusmeldungen auf dem Bildschirm ausgeben
können. Da die Standard Ein-/ Ausgabe ja
nicht verändert wurde, erscheinen Texte, die mit BSOUT ausgegeben werden sollen, auch weiterhin auf dem Bildschirm. Umgekehrt wird bei BASIN von der Tastatur, und nicht etwa aus dem offenen File gelesen.
Als Beispiel für eine solche Anwendung
möchte Ich Ihnen das Auslesen des Fehlerkanals der Floppy zeigen. Hierzu muß
lediglich der Befehlskanal geöffnet, und
die Fehlermeldung, die im ASCII-Code von
der Floppy generiert und übermittelt
wird, Zeichen für Zeichen ausgelesen und
auf dem Bildschirm ausgegeben werden.
Ich denke, die Routine erklärt sich von
selbst:
ERRCHN lda #00 ;Länge Filename=0 jsr setnam ; für "Kein Name" lda #01 ;Fileparameter ldx #08 ; setzen ldy #$6f ; (=Befehlskanal) jsr setpar jsr open ;Filekanal öffnen lda #08 ;Floppy Nr.8 zum jsr talk ; Senden auffordern lda #$6f ;Und zwar auf Kanal jsr sectlk ; mit Sek.Adr. $6F ecloop1 jsr iecin ;Zeichen lesen jsr bsout ;Und auf dem Bild- schirm ausgeben lda $90 ;Fileende erreicht? beq ecloop1; Nein, also weiter lda #08 ;Floppy Nr.8 jsr untalk ; zurücksetzen lda #01 ;Und Befehlskanal jmp close ; schließen
3) BAM ANZEIGEN Als Nächstes wollen wir ein Programm
schreiben, daß uns anzeigt, welche
Blocks einer Diskette belegt sind, und
welche nicht. Hierzu müssen wir die BAM
(" Block Availability Map"), die im
Disk-Header- Block ( Track 18 Sektor 0) zu
finden ist, auslesen und zu einer grafikschen Anzeige dekodieren. Dies ist
ein hervorragendes Beispiel um die Benutzung der neuen I/ O-Routinen zu demonstrieren und gleichzeitig zu zeigen, wie man mit der BAM umgeht. Sie erinnern
sich vielleicht noch an deren Aufbau.
Ich hatte ihn in Teil 5 dieses Kurses
besprochen:
Im Disk-Header- Block sind die Bytes 4 bis einschließlich 143 für die BAM reserviert. Hierbei zeigen jeweils 4 Bytes
in Folge die Blockbelegung für einen
Track an. Im ersten Byte steht dabei die
Gesamtanzahl der freien Blocks dieses
Tracks. Die Bits des zweiten Bytes ko- dieren die Belegung der Sektoren 0-7, die Bits des dritten Bytes die Belegung
der Sektoren 8-15 und die Bits des vierten Bytes die Belegung der Sektoren 16-23 . Hat ein Track weniger Sektoren, als
im vierten Byte angezeigt werden, so
sind die restlichen Bits dieses Bytes
unbenutzt ( so z. B. immer die letzten
zwei Bits, da es ja nie mehr als 21 Tracks gibt) . Ein gesetztes Bit bedeutet, daß der entsprechende Sektor frei
ist, ein gelöschtes Bit, daß er belegt
ist.
Wollen wir einmal klären, welche Dinge
zur Lösung des obigen Problems notwendig
sind. Zunächst müssen wir den Befehlskanal und anschließend einen Pufferkanal
öffnen. Nun muß der Befehl zur Floppy
gesandt werden, der den Block 18/0 in
den geöffneten Puffer liest. Ist dies
geschehen, müssen wir nur noch auf das
vierte Byte des Puffers positionieren
( dort beginnt ja die BAM) und die vier Belegungs-Bytes für jeden Track einlesen
und darstellen.
Bitte Teil 2 des Kurses laden!
Teil 2 Floppy Kurs 8
Kommen wir also zu unserem Programm:
main lda #00 ;Bildschirm- sta 53280 ; farben lda #11 ; ein- sta 53281 ; stellen lda #<(text1) ;Text für Anzeige ldy #>(text1) ; auf Bildschirm jsr strout ; ausgeben mloop5 lda #00 ;Filename setzen jsr setnam ; (0='Kein Name') lda #01 ;Parameter: LFN 1 ldx #08 ; Ger.Adr 8 ldy #$6f ; Sek.Adr 15 jsr setpar ; setzen jsr open ;Bef.kan. öffnen lda #01 ;Filename "#" ldx #<(bufnam); (für 'Puffer ldy #>(bufnam); reservieren') jsr setnam ; setzen lda #02 ;Fileparameter ldx #08 ; setzen ldy #$62 ; (Sek.Adr=2) jsr setpar jsr open ;Puff.kan. öffnen
Bis hier sollte wohl alles klar sein.
Wir haben in den obigen Zeilen den Befehlskanal und einen Pufferkanal mit der
Sekundäradresse 2 geöffnet ( bitte denken
Sie daran, daß beim Üffnen und beim Benutzen der Routinen SECTLK und SECLST
diese Adresse plus $60 übergeben werden
muß) .
Als nächstes können wir die beiden
Floppybefehle zum Lesen des Blocks 18/0 und zum Positionieren auf Byte 4 senden:
lda #<(com1) ;Befehl 'Block ldy #>(com1) ; 18/0 lesen' jsr sendcom ; senden. lda #<(com2) ;Befehl 'Puffer- ldy #>(com2) ; zeiger auf jsr sendcom ; Byte 4' senden.
Wie Sie sehen, benutze ich hier eine
eigene Routine, um die Befehle zu senden. Sie heisst " SENDCOM" und benötigt
einen Zeiger auf den Befehlstext in Akku
und Y-Register. Zur Beschreibung der
Routine kommen wir später. Hier noch die
Syntax beiden Befehle, die oben gesandt
werden. Sie finden Sie ganz am Ende des
Quelltextes:
com1 .tx "u1 2 0 18 0" .by 13,0 com2 .tx "b-p 2 4" .by 13,0
Wie Sie sehen benutzen wir die Befehle
" U1"( die bessere Version von " B-R") und
" B-P" . Die einzelnen Parameter werden
dabei als ASCII-Werte übertragen. Wie
Sie auch bemerken, so wird bei den Be- fehlen die tatsächliche Sekundäradresse, nämlich 2 und nicht etwa $62, benutzt.
Der Wert 13 am Ende jedes Befehls wird
benötigt, um der Floppy das Ende des
Befehlsstrigs zu signalisieren. Die 0 wird nicht mitgesandt. Sie dient als
Endmarkierung für die SENDCOM-Routine.
Machen wir nun jedoch weiter im Quelltext des Hauptprogramms. Die nun folgenden Zeilen versetzen die Floppy in den
Sendestatus und bereiten die Bildschirmausgabe der Tracks vor. Hierbei
wird ein Zeiger auf den Bildschirmspeicher in $ FB/$ FC abgelegt, der nach der
Ausgabe eines Sektors um 40 erhöht wird.
So erreichen wir eine vertikale Ausgabe
des Tracks. Für den nächsten Schleifendurchlauf wird der Zeiger wieder auf den
Startwert+1( nächste Spalte) zurückgesetzt:
lda #08 ;Floppy Nr. 8 zum jsr talk ;Senden auffordern lda #$62 ;Und zwar auf
jsr sectlk ; Sek. Adr 2
lda #34 ;Trackzähler sta $03 ; initialisieren lda #163 ;Grundwert Zeiger sta mloop3+1 ; einstellen mloop3 ldx #163 ;Bildschirmpo- ldy #04 ; sitionszeiger stx $fb ; initialisieren sty $fc inc mloop3+1 ;Spalte für näch- sten Track um 1 erhöhen
Nun erfolgt das eigentliche Lesen und
Ausgeben der BAM-Bytes. Hierbei müssen
wir immer das jeweils erste Byte überlesen, da es ja keine Blockbelegung ansich, sondern nur die Anzahl der freien
Blocks enthält. Die folgenden zwei Bytes
können normal ausgegeben werden. Da das
letzte Byte immer eine unterschiedliche
Anzahl an unbelegten Bits enthält, müs- sen wir hier aufpassen, daß nur soviele
Bits ausgegeben werden, wie tatsächlich
benutzt werden. Dies geschieht über die
Tabelle " SECTAB", die die Anzahl der
benutzen Blocks des letzten Bytes eines
jeden Tracks beinhaltet. Hier jedoch
erst einmal wieder das Listing:
jsr iecin ;1. Byte überlesen jsr iecin ;2. Byte lesen jsr byteout1 ; und ausgeben jsr iecin ;3. Byte lesen jsr byteout1 ; und ausgeben jsr iecin ;4. Byte lesen ldy $03 ;Tracknr. als Zei- ger holen ldx sectab,y ;Anzahl benutzte Bits des Tracks holen. jsr byteout2 ;Und Byte ausgeben dec $03 ;Trackzähler-1 bpl mloop3 ;Nochmal, wenn >0
Wie Sie sehen, benutze ich eine eigene
Routine zur Ausgabe eines BAM-Bytes. Sie
heisst " BYTEOUT1" und gibt die Bits 0-7 des BAM-Bytes vertikal auf dem Bildschirm aus. Hierbei wird ein " o" geschrieben, wenn der Block belegt ist, und ein " .", wenn er unbelegt ist. Die
Routine " BYTEOUT2" ist im Prinzip dieselbe, wie BYTEOUT1 . Der einzige Unterschied ist, daß die zweite Version die
Anzahl der auszugebenden Bits im X-Register verlangt. Diese wird nun über
die SECTAB-Tabelle eingelesen. Achten
Sie darauf, daß die Liste rückwärts gelesen wird, und somit der Reihe nach die
Blockanzahlen der Tracks 35-1( jeweils
nur für das letzte Byte und minus 1) in
der Liste verzeichnet sind. Sie finden
die Liste ebenfalls am Ende des Quellcodes.
Fahren wir nun weiter im Hauptprogramm.
Nachdem alle BAM-Bytes gelesen und ausgegeben wurden, können wir Befehlsund
Pufferkanal wieder schließen. Vorher muß die Floppy allerdings wieder in den Wartezustand zurückversetzt werden. Hierauf
folgt noch eine Tastenabfrage, die das
Programm bei Tastendruck wiederholt und
bei der Taste '<' den Bildschirm löscht
und zurückspringt:
lda #08 ;Floppy wieder jsr untalk ; zurücksetzen. lda #02 ;Pufferkanal jsr close ; schließen lda #01 ;Befehlskanal jsr close ; schließen mloop4 jsr inkey ;Taste holen beq mloop4 ; Keine --> weiter cmp #95 ;= '<' ? beq end ;Ja, also ENDE. jmp mloop5 ;Nein,also nochmal end jmp $e544
Wollen wir uns nun die Routine SENDCOM
anschauen. Im Prinzip ist sie nichts
anderes als eine STROUT-Routine, nur daß die Ausgabe nicht auf dem Bildschirm, sondern an die Floppy erfolgt. Sie finden sie in den Zeilen 990-1070 des
Quelltextes:
sendcom sta $ fb ; Zeiger auf String
sty $ fc ; ablegen
lda #08 ;Floppy Nr.8 zum jsr listen ; Empfang auffor- dern lda #$6f ;Und zwar von Sek. jsr seclst ; Adr. 15 ldy #00 ;Offset=0 scloop1 lda ($fb),y ;Zeichen lesen beq s1 ;Wenn 0 --> fertig jsr iecout ;Zeichen senden iny ;Zeiger um 1 bne scloop1 ; erhöhen und inc $fc ; Schleife wieder- jmp scloop1 ; holen s1 lda #08 ;Ende. Floppy Nr.
jmp unlist ;8 zurücksetzen
Das soll es dann wieder einmal für diesen Monat gewesen sein. Sie sind nun
komplett in die Bedienung der Floppy
1541 eingeführt und sollten eigentlich
auch schon eigene Programme schreiben
können. Trotzdem wird es nächsten Monat
noch einen Teil dieses Floppy-Kurses
geben, in dem wir ein ganz besonderes
Beispielprogramm besprechen werden wollen.
(ub) ---------------------------------------- Floppy-Kurs "Es rappelt in der Kiste..." (Teil 9) ----------------------------------------
Herzlich Willkommen zum neunten und
letzten Teil des Floppy-Kurses. In den
vorangehenden Folgen haben wir einiges
über den Aufbau von Disketten und das
Ansprechen der Floppy gelernt. Ich möchte diesen Kurs nun mit einem anspruchsvollen Programmbeispiel abschließen, das
wir uns im Laufe der heutigen Folge
erarbeiten wollen. Es dient als Beispiel
für die Programmierung der Floppy in
Assembler und gleichzeitig auch als Beispiel für das was möglich ist, wenn eine
Diskette richtig manipuliert wird.
DIE AUFGABE
Sicherlich kennen Sie das Problem: Sie
sind gerade dabei Ihre Diskettensammlung
aufzuräumen und Ihre Programme so umzukopieren, daß jede Diskette optimale
Platzausnutzung aufweisen soll. Am Besten so, daß restlos jeder Block dieser
Diskette beschrieben ist. Beim Zusammenstellen bleiben dann aber noch noch
17 Blocks übrig - Mist, kein Platz mehr
für das letzte,30 Blocks lange Programm!
Was tun? Im Prinzip bleiben Ihnen nur
die folgenden Möglichkeiten:
1) Sie fangen nochmal von vorne an ( Gähn
- Kopieren dauert lange) .
2) Sie belassen alles, wie es ist ( Ärgerlich -17 Blocks verschenkt) .
3) Sie packen das letzte Programm ( Wieder Gähn - dauert auch lange und ausserdem hat das File hinterher bestimmt genau 18 Blocks, so daß es
doch nicht passt) .
4) Sie benutzen das Programm " USEDIR", das wir uns in diesem Kursteil erarbeiten wollen.
Nun werden Sie fragen:" Na schön, und
wie will dieses Programm nun 13 weitere
Blocks freibekommen, wenn die Diskette
voll ist?" . Ganz einfach: aus dem Directory." USEDIR" nimmt sich die Tatsache
zunutze, daß der Track 18, in dem das
Directory einer Diskette steht, nicht
für normale Files zur Verfügung steht.
Er ist lediglich für die Fileeinträge
reserviert. In jedem der 18 Fileeintragsblocks ( Sektoren 1-18,0 ist für
DiskHeader und BAM reserviert) können
nun 8 Einträge stehen, was einer maximal
mögliche Anzahl von 144 Einträgen entspricht. Da eine normale Diskette aber
so gut wie nie so viele Files beherbergt, liegen eine Menge dieser Directoryblocks brach. Gerade bei einer randvollen Diskette werden sie bis in alle
Ewigkeit unbenutzt bleiben, da ja keine neuen Files mehr hinzukommen können. Im
günstigsten Fall, nämlich dann, wenn Sie
weniger als 9 Files auf der Diskette
haben, sind das 17 Blocks, die noch
zusätzlich frei sind!
Diese Blocks soll UseDir nun einfach als
Datenblocks verwenden. Es muß lediglich
einen Fileeintrag kreieren, in dem der
Zeiger für den ersten Track und Sektor
auf einen der unbenutzten DirBlocks
zeigt. Wird dieses File dann geladen, so
greift das DOS der Floppy schön brav auf
diese sonst ungenutzten Blocks zu, so
als gäbe es keinen Unterschied. Tatsächlich hat die Diskette dann jedoch 683 Blocks ( maximal) anstelle von nur 664 !
DIE THEORIE
Was muß unser Programm nun tun, um ein
File auf die oben beschriebene Weise
umzukopieren. Zunächst einmal wollen wir
hier eine Einschränkung vereinbaren, die uns die Arbeit erleichtern soll: ein
File, das auf diese Weise installiert
werden soll, muß länger sein, als es
freie Directoryblöcke gibt. Daraus ergibt sich natürlich, daß die Diskette
mindestens noch einen ' normalen', freien
Datenblock hat. Das zu installierende
File muß desweiteren natürlich kleiner, oder gleich lang der insgesamt verfügbaren Blöcke sein und sollte vom Typ " PRG" sein. Daraus ergeben sich folgende Aufgaben für unser Programm:
1) Das zu installierende File einlesen
und benötigte Anzahl von Blöcken ermitteln.
2) Anzahl der freien Blöcke der Zieldiskette ermitteln.
3) Anzahl der freien Directoryblöcke der
Zieldiskette ermitteln.
4) Vergleichen, ob das File den obig
gegebenen Restriktionen entspricht
und ob noch genügend Platz auf der
Zieldiskette ist.
5) Wenn ja, dann weiter, sonst abbrechen.
6) Nun muß berechnet werden, wieviele
Bytes in den Directoryblocks Platz
haben. Alles restliche wird anschließend, als normales File, mit Hilfe
der WriteFile-Routine aus dem vorletzten Kursteil auf die Diskette
geschrieben.
7) Jetzt suchen wir den Fileeintrag des
soeben gespeicherten Files aus den
Directoryblocks heraus und Lesen die
Informaton für den ersten Track und
Sektor aus dem Eintrag aus.
8) Diese Information wird sogleich in
die Trackund Sektornummer des ersten freien Directoryblocks abgeändert.
9) Jetzt müssen wir nur noch die fehlenden Directoryblocks schreiben und den
letzten Directoryblock auf den ersten
Track/ Sektor des geschriebenen Files
zeigen lassen - Fertig ist die Installation!
DIE BENÜTIGTEN PROGRAMMTEILE
Kommen wir nun also zu unserem Programm.
Dabei möchte ich Ihnen zunächst einmal
eine Liste der darin enthaltenen Routinen geben. Hierbei habe ich in zwei Arten unterschieden: in Steuerund IO-Routinen. Diese beiden Namen treffen
Ihre Bedeutung zwar nicht voll und ganz, jedoch musste irgendwo eine Grenze gezogen werden.
Die erste Art, die Steuerroutinen also, sind alles Funktionen, die entweder
nichts direkt mit der Floppy zu tun haben, und somit für uns uninteressant
sind, oder aber Funktionen, die wir in
vorangegangenen Kursteilen schon besprochen hatten, und somit nicht noch einer
zweiten Dokumentation bedürfen. All diese Routinen werden nur mit ihrem Namen, ihrer Funktion und ihren Parametern aufgeführt, damit Sie wissen, wozu sie da sind, und wie man sie benutzt.
Die zweite Art von Routinen sind größtenteils Floppy-Routinen, die eine Beschreibung benötigen und im Laufe dieses
Kursteils alle nocheinmal genauer dokumentiert sind.
Desweiteren werden diverse Speicherzellen als Zwischenspeicher für verschiedentliche Werte benutzt. Diese haben, der besseren Öbersichlichkeit wegen, richtige Namen, und können im Prinzip
überall im Speicher stehen. Dennoch will
ich die von mir benutzten Speicherzellen
hier einmal aufführen. Sie liegen, bis
auf einige Außnahmen, im Speicherbereich
von $0332-$03 FF, dem Kasettenpuffer also, der bei Floppybenutzung ja unbenutzt, und deshalb verwendbar ist. Hier
nun also die besprochenen Beschreibungen:
1) BENUTZTE SPEICHERZELLEN: * MEM0-MEM4 Die fünf Zeropageadressen von $02 bis $06. Sie werden für die Zwischenspei- cherung verschiedenster Ergebnisse herangezogen. * FILEMEM ($0F00) Dies ist die Anfangsadresse des Spei- chers, in den das zu installierende Programm geladen wird. * NUMBUFF ($0332) Hier wird der ASCII-String einer Kon- vertierten Zahl abgelegt (maximal 5 Zeichen). * NEED ($0337) Zwischenspeicher für die Länge des gelesenen Files in Blocks. * DSKFREE ($0338/$0339) Speicher für die Anzahl der freien Blocks der Zieldiskette. * ALLFREE ($033A/$033B) Anzahl der insgesamt (inkl. freie Dir- blocks) auf der Zieldiskette verfügba- ren Blocks. * DIRFREE ($033C) Anzahl der freien Directoryblocks * DIRSTRT ($033D) Sektornummer des ersten freien Direc- toryblocks. * FTRACK ($033E) Tracknummer des ersten, vom normal geschriebenen File benutzten, Daten- blocks. * FSECTOR ($033F) Sektornummer des ersten, vom normal geschriebenen File benutzten, Daten- blocks. * DSINDEX ($0340) Indexzeiger auf den aktuellen Eintrag der Dirblockliste. * COMBUFF ($0341-) Zwischenspeicher für Befehle, die an die Floppy gesandt werden sollen. * NAME Dieses Label beszeichnet die Starta- dresse, des 17 Zeichen langen Zwi- schenspeichers für Filenamen. Dieser befindet sich direkt im Sourcecode. 2) STEUERROUTINEN
* GETIN: Diese Funktion verlangt keine
Parameter. Sie liest mit Hilfe der
BASIN-Routine des Betriebssystems einen maximal 16 Zeichen langen String
von der Tastatur ein und wird zum Eingeben des Filenamens benutzt. Dieser
String liegt nach dem Rücksprung ab
der Adresse NAME. Die Länge des Filenamens steht in der Speicherzelle
MEM0 . Am Ende des Namensstring wird
ein Nullbyte als Endmarkierung angefügt.
* STROUT: Diese Routine gibt einen AS-CII- Text auf dem Bildschirm aus. Lo/- Hi-Byte der Textadresse müssen in Akku
und Y-Register übergeben werden. Der
Text muß mit einem Nullbyte enden.
* OPENCOM: Diese Routine öffnet den Befehlskanal mit der logischen Filenummer 1 . Beim Üffnen wird die Diskette
automatisch initialisiert ( Floppybefehl " I")
* OPENIO: Hier wird der Befehlskanal mit
der Filenummer 1( wie OPENCOM) und ein Pufferkanal mit der Filenummer 2( und
Sekundäradresse 2) geöffet.
* RFILE: Dies ist die " ReadFile"- Routine
aus dem letzten Kursteil. Sie liest
das File, dessen Namen bei NAME steht
und dessen Namenslänge in MEM0 abgelegt ist, an die Adresse FILEMEM. Sie
benutzt die Zeropageadressen $ F9/$ FA
als Lesezeiger. Hier steht nach dem
Rücksprung gleichzeitig auch die Endadresse des gelesenen Files.
* WFILE: Schreibt das File in NAME und
MEM0 auf Diskette. Die Startadresse
des zu speichernden Bereichs muß dabei
in den beiden Zeropageadressen $ FD/$ FE, die Endadresse in $ F9/$ FA
stehen.
* STATOUT: Gibt die ermittelten Werte
für " Anzahl benötigte Blocks"," Freie
DirBlocks", und " Freie Diskblocks" auf
dem Bildschirm aus.
3) IO-ROUTINEN: * STRIEC: Wie "STROUT", nur daß diesmal
ein String auf den IEC-Bus ( also an
die Floppy) ausgegeben wird.
* SENDCOM: Da wir zur Erfüllung unserer
Aufgabe immer nur Floppybefehle benötigen, die aus einem festen String und
einer angehängten, variablen Zahl bestehen, wird diese Routine benutzt, um
einen Befehl, dessen String an der
Adresse in Xund Y-Register steht und
dessen abschließende Nummer im Akku
übergeben wurde, an die Floppy zu senden.
* WDUMMY: Diese Routinelegt auf der
Zieldiskette ein File mit dem Namen in
NAME und MEM0 an und löscht es direkt
wieder. Wozu dies notwendig ist, werden wir später sehen.
* GETDSKF: Ermittelt die Anzahl der
freien Blocks der Zieldiskette und
legt sie in DSKFREE ab.
* GETDIRF: Ermittelt die Anzahl der
freien Directoryblocks der Zieldiskette und legt sie in DIRFREE ab. Desweiteren wird die Summe von DSKFREE und DIRFREE berechnet und in ALLFREE abgelegt.
* GETNEED: Berechnet die benötigte Anzahl Blöcke des zuvor eingelesenen
Files und legt Sie bei " NEED" ab.
* CHGENT: Diese Routine sucht den Filenamen in NAME aus dem Directory heraus, liest die Nummern des ersten
Tracks und Sektors dieses Files ein, und überschreibt diese beiden Informationen mit den Werten für den ersten
freien Directoryblock.
* WBLOCKS: Diese Routine schreibt alle
fehlenden Blocks des Files in die
freien Directoryblocks und gibt im
letzten dieser Blocks Trackund Sektornummer des ersten Datenblocks des
' normal' gespeicherten Files an.
DIE PRAXIS
Nach all der trockenen Theorie wollen
wir nun endlich zur Praxis schreiten.
Beginnen möchte ich mit der Steuerroutine ' MAIN' unseres Programms, in der das
oben aufgeführte Aufgabenschema in Programmcode umgesetzt ist. Anschließend
wollen wir uns mit den benutzten Unterroutinen beschäftigen.
1) MAIN Diese Routine steuert das gesamte Programm. Zunächst einmal wollen wir die
Bildschirmfarben setzen, den Titeltext
ausgeben und den Namen des zu installierenden Files ermitteln. Ist dieser Name
gleich dem String X", so soll das Programm mit einem RESET verlassen werden:
main lda #11 ;Bildschirm- sta 53280 ;farben sta 53281 ;setzen. lda #<(text1) ;Titeltext ldy #>(text1) ;auf Bildschirm jsr strout ;ausgeben. jsr getin ;Und Filename einlesen. cpy #1 ;Vergleichen, ob bne m3 ;der Name="X" lda #"x" ;ist. cmp name ;Wenn nein, dann bne m3 ;weitermachen. jmp 64738 ;Sonst: RESET.
Als Nächstes müssen wir das File lesen
und seine Blocklänge berechnen. Hieraufhin wird der Benutzer dazu aufgefordert, die Zieldiskette einzulegen, von der wir
dann die Anzahl der freien Dirund
Diskblocks ermitteln. Gleichzeitig wird
dann auch noch das Dummyfile erzeugt. Am
Ende werden alle ermittelten Werte mittels der Routine STATOUT auf dem Bildschirm ausgegeben:
m3 jsr rfile ;File einlesen jsr getneed ;Anzahl der benö- tigten Blocks berechnen lda #<(text2) ;"Zieldisk ein- ldy #>(text2) ;legen" jsr strout ;ausgeben und mloop1 jsr inkey ;auf Tastendruck beq mloop1 ;warten. lda #<(text3) ;"Untersuche ldy #>(text3) ;Zieldisk" jsr strout ;ausgeben. jsr wdummy ;Dummy-File anle- gen und löschen. jsr openio ;Kanäle öffnen jsr getdskf ;Freie Diskblocks ermitteln jsr getdirf ;Freie Dirblocks ermitteln. jsr closeio ;Kanäle schließen jsr statout ;Werte ausgeben.
Nachdem nun all diese Dinge getan sind, müssen wir nun erst einmal prüfen, ob
das gelesene File und die Daten der
Zieldiskette es uns ermöglichen, das
File auf die Diskette zu schreiben.
Hierbei wird abgebrochen, wenn keine
Dirblocks mehr frei sind, das File kürzer als die noch verfügbaren Dirblocks, oder länger als die gesamt verfügbaren
Blocks ist:
lda dirfree ;DirFree lesen bne m1 ;wenn<>0, weiter. lda #<(errtxt1) ;Sonst "Kein Dir- ldy #>(errtxt1) ;blk mehr frei" jmp errout ;ausg. u. Ende. m1 cmp need ;DirFree mit NEED bcc m2 ;vergleichen. lda #<(errtxt2) ;Wenn >, dann ldy #>(errtxt2) ;"File zu kurz" jmp errout ;ausg. u. Ende. m2 ldy allfree+1 ;Hi-Byte ALLFREE lesen. bne ok ;Wenn <>0, dann genug Platz. lda allfree+0 ;Sonst Lo-Byte lesen cmp need ;u. m. NEED vgl. bcs ok ;Wenn >, Ok. lda #<(errtxt3) ;Sonst "File zu ldy #>(errtxt3) ;lang" aus- jmp errout ;geben
Wurden all diese Vergleiche erfolgreich
bestanden, so kann das File installiert
werden. Hierzu müssen zunächst einige
Vorbereitungen getroffen werden: Als
erstes sollten wir die Sektornummer des
ersten freien Directoryblocks ermitteln.
Dies ginge natürlich dadurch, indem wir
die BAM einlesen würden, und uns einen
unbelegten Block als Startblock heraussuchen würden. Es geht aber noch viel
einfacher: das DOS der Floppy beschreibt
die Sektoren einer Diskette nämlich meistens in einer ganz bestimmten Reihenfolge. Das ist bei den normalen Datenblocks nicht unbedingt gesichert, da bei
einer nahezu vollen Diskette diese Reihenfolge nicht mehr eingehalten werden
kann. Beim Directorytrack 18 können wir uns jedoch mit 100%- iger Sicherheit darauf verlassen, daß sie immer eingehalten wird. So gibt es nämlich eine ganz
bestimmte Reihenfolge, in der die Directorytracks geschrieben werden. Wir müssen lediglich wissen, wieviele Blocks
benutzt sind, und uns die Sektornummer
des nächsten Blocks aus einer Tabelle zu
holen. Dies ist dann automatisch der
erste leere Dirblock. Die angesprochene
Tabelle ist bei dem Label " BLKTAB" abgelegt, und beinhaltet die folgenden Werte
für Sektornummern:
BLKTAB .byte 0,1,4,7,10,13,16 .byte 2,5,8,11,14,17 .byte 3,6,9,12,15,18,$ff
Bitte nun Teil 2 des Floppy-Kurses laden In dieser Reihenfolge werden die Blocks
des Track 18 IMMER belegt. Der Wert $ FF
am Ende der Liste steht hier als Endmarkierung der Liste, die später beim Beschreiben der Dirblocks von Bedeutung
ist. Nun müssen wir erst einmal die Sektornummer des ersten freien Dirblocks
ermitteln:
ok lda #19 ;Von der Gesamt- sektorzahl (19) für Track 18 sec ;die Anzahl der sbc dirfree ;freien Dirblocks subtrahieren (=Anzahl belegte Blocks) und als tay ;Index in Y-Reg. lda blktab,y ;Sektornr. lesen sta dirstrt ;und speichern. sty dsindex ;Index speichern
Nun wollen wir das File auf der Zieldiskette anlegen. Hierzu schreiben wir es zunächst ganz normal mittels der WFILE-Routine. Hierbei sollen jedoch nur die
Bytes geschrieben werden, die nicht mehr
in die Dirblocks passen. Wir müssen nun
also die Anfangsadresse des zu schreibenden Files berechnen. Da die ersten
beiden Bytes eines Datenblocks immer als
Zeiger auf den nächsten Datenblock dienen, müssen wir also den Wert DIR-FREE*254 zur FILEMEM-Adresse hinzuaddieren, um unsere Anfangsadresse zu erhalten:
ldx dirfree ;DIRFREE in X laden (=HiByte Startadr.). txa ;u. in Akku holen dex ;HiByte-1 asl ;Akku*2 eor #$ff ;und invertieren clc ;mit adc #1 ;Öbertrag. adc #<(filemem) ;LoByte von bcc m5 ;FILEMEM inx ;addieren. m5 sta $fd ;unde ablegen txa ;HiByte holen clc ;und mit HiByte adc #>(filemem) ;FILEMEM add. sta $fe ;u. ablegen jsr wfile ;File schreiben
Nachdem die Anfangsadresse berechnet und
in $ FD/$ FE abgelegt wurde, wird die WFI-LE- Routine aufgerufen. Die Endadresse
muß in $ F9/$ FA stehen, wo sie noch von
der RFILE-Routine enthalten ist ( wird
von ihr als Lesezeiger verwedet) .
Ist das File nun geschrieben, so müssen
wir nur noch seinen Fileeintrag aus dem
Directory heraussuchen, Track und Sektor
des ersten freien Dirblocks eintragen
und die fehlenden DIRFREE*254 anfänglichen Bytes in den Dirblocks unterzubringen:
jsr openio ;Kanäle öffnen jsr chgent ;Eintrag suchen und ändern. jsr wblocks ;DirBlocks schreiben. jsr closeio ;Kanäle schließen
lda #<( text4) ;" File installldy #>( text4) ; liert"
errout jsr strout ;ausgeben. eo1 jsr inkey ;Auf Tastendruck beq eo1 ;warten. jmp main ;Und neu starten
Soviel also zu unserer Steuerroutine.
Kommen wir nun zu den Unterfunktionen:
2) STRIEC Diese Routine gibt einen Zeichenstring, dessen Adresse in Akku und Y-Register
steht, an den Befehlskanal aus. Der
String muß mit einem Nullbyte beendet
sein:
striec sta $ fb ; Adresse in Zeisty $ fc ; ger ablegen.
lda #8 ;Floppy auf jsr listen ;Befehlskanal lda #$6f ;empfangsbereit jsr seclst ;machen. ldy #0 ;String lesen siloop1 lda ($fb),y ;und senden. bne si1 lda #8 ;Floppy zurück- jmp unlist ;setzen u. Ende. si1 jsr iecout ;Zeichen senden iny ;und Zeiger+1 bne siloop1 inc $fc jmp siloop1
3) SENDCOM Diese Routine wird dazu verwandt, einen
Floppybefehl zu senden. Hierbei unterscheidet sie sich jedoch von der Routine STRIEC. Es wird nämlich nicht nur der
Zeiger des Befehls übergeben ( in X und
Y-Register), sondern auch ein Wert im
Akku, der vor dem Senden in ASCII umgewandelt und an den Befehlsstring in X/ Y
angehängt wird. Der gesamte Befehl wird
dabei bei COMBUFF abgelegt und dann mittels STRIEC an die Floppy gesandt:
sendcom pha ; Akku retten stx scloop1+1 ; Stringzeiger sty scloop1+2 ; setzen
ldy #0 ;Und String scloop1 lda $c000,y ;nach COMBUFF beq sc1 ;umkopieren. sta combuff,y iny jmp scloop1 sc1 sty mem0 ;Zeiger retten, pla ;Akku holen, jsr i2a ;konvertieren. ldy mem0 ;Zeiger zurückh. ldx #0 ;und in ASCII scloop2 lda numbuff,x ;konvertierte beq sc2 ;Zahl an COMBUFF sta combuff,y ;anhängen. inx iny jmp scloop2 sc2 lda #13 ;CR anhängen sta combuff,y iny lda #0 ;Endmarkierung sta combuff,y ;anhängen
lda #<( combuff) ; und Inhalt von ldy #>( combuff) ; COMBUFF an jmp striec ; Floppy senden.
4) WDUMMY Kommen wir nun zur Routine " WDUMMY" . Wir
sollten zunächst einmal klären, wozu sie
benötigt wird. Wie Sie oben sahen, wird
sie aufgerufen, noch BEVOR irgendendet- was anderes getan wird. Hierbei tut sie
eigentlich nichts anderes, als ein File
mit dem Namen in NAME und MEM0 auf der
Zieldiskette anzulegen und gleich darauf
wieder zu löschen. Das deshalb notwendig, um sicherzustellen, daß auch die
richtige Anzahl an Dirblocks als ' belegt' gekennzeichnet ist. Sollten nämlich genau 8( oder 16,24, etc.) Files auf
der Zieldiskette vorhanden sein, so kann
es zu Problemen kommen. Beim Schreiben
eines neuen Files muß das DOS dann nämlich einen neuen Dirblock hinzufügen, der ab dann nicht mehr zur Datenspeicherung benutzt werden kann. Damit es dabei
keine Konfrontationen gibt, wird das
File also sporadisch schon einmal angelegt und direkt danach wieder gelöscht.
Der neue DirBlock wird dann nicht wieder
freigegeben. Der Eintrag bleibt nämlich
erhalten, es wird lediglich der Filetyp
' DEL' an das Programm vergeben. Hier nun
also die Routine:
wdummy lda mem0 ;Filename ldx #<(name) ;in NAME ldy #>(name) ;und MEM0 jsr setnam ;setzen. lda #1 ;Fileparameter ldx #8 ;"1,8,1" für ldy #1 ;("PRG saven") jsr setpar ;setzen. jsr open ;File öffnen. ldx #1 ;Kanal 1 als jsr ckout ;Ausgabefile jsr bsout ;Byte ausgeben. jsr clrch ;Standardkanäle zurücksetzen. lda #1 ;File wieder jsr close ;schließen. jsr opencom ;Befehlskanal öffnen lda #<(name-2);Name-2 als ldy #>(name-2);Adresszeiger jsr striec ;senden. lda #1 ;Befehlskanal jmp close ;schließen.
Nun kann ich Ihnen auch den Grund zeigen, warum der Filename im Sourcecode
abgelegt wird. Hier ist nämlich vor dem
eigentlichen Namen auch noch der Text
" S:" abgelegt. Wenn wir nun NAME-2 an
STRIEC übergeben, so enspricht das einem
Scratchbefehl für den Filenamen (" S: NA-ME") . Da STRIEC ein Nullbyte als Endmarkierung verlangt, wurde die GETIN-Routine so ausgelegt, daß sie nach dem
letzten eingelesenen Zeichen ein solches
Byte in den NAME-Puffer schreibt. Hier
nun die Source-Definition des Filenamenspuffers. Für den Namen werden 17 Bytes reserviert, da ja maximal 16 Zeichen plus die Endmarkierung 0 vonnöten
sind:
. text " s:" name . byte 0,0,0,0,0,0,0,0 . byte 0,0,0,0,0,0,0,0,0
5) GETNEED Diese Routine wird benutzt, um die An-
zahl der vom Quellfile benötigten Blocks
zu ermitteln. Hierbei wird zunächst die
Startadresse des Filespeichers subtrahiert, um die effektive Länge in Bytes
zu ermitteln. Hiernach wird das High-Byte mit zwei multipliziert. Dies ist
nämlich die Anzahl Bytes, die Aufgrund
des Wegfalls der ersten beiden Bytes
eines Datenblocks zu der Anzahl Lo-Bytes
addiert werden muß. Ist dieser Wert größer als 256, so wird ein Block mehr gebraucht. Jetzt wird noch die Anzahl der
Low-Bytes hinzuaddiert. Gibt es auch
hier einen Öberlauf, so muß wieder ein
Block hinzuaddiert werden. Letztendlich
muß wieder ein Block hinzugefügt werden, da die letzten Bytes, selbst wenn
es weniger als 254 sind, dennoch einen
ganzen Block belegen:
getneed ldx #0 ;NEED stx need ;löschen. lda $f9 ;Endadr. d. Quellfiles (von ldx $fa ;RFILE noch da) lesen. sec ;und Startadresse sbc #<(filemem);subtrahieren. bcs rf1 ;Ergebnis wird in dex ;MEM1 (LoByte) rf1 sta mem1 ;und txa ;MEM2 (HiByte) sec ;abgelegt. sbc #>(filemem) sta mem2 rol ;HiByte*2 bcc cn1 ;Wenn<256, weiter inc need ;Sonst Blocks+1 cn1 clc ;LoByte addieren adc mem1 beq cn2 bcc cn2 inc need ;Bei Öberlauf Blocks+1 cn2 inc need ;Und nochmal Blocks+1 lda mem2 ;Und Hi-Byte clc ;addieren. adc need ;und in NEED cn3 sta need ;ablegen. rts
6) GETDISKF Kommen wir nun zur Routine zum Feststellen der freien Blocks der Diskette. Dieser Wert ist normalerweise nicht direkt
aus dem DiskHeaderBlock zu erfahren. Die
einzige Möglichkeit wäre, die Blockbelegungen der einzelnen Tracks aus der BAM
zu lesen und aufzusummieren. Aber auch
hier wollen wir uns eines kleinen Tricks
bedienen. Wird eine Diskette nämlich
initialisiert ( was wir beim Üffnen des
Befehlskanals schon tun), so liest die
Floppy die BAM der Diskette automatisch
ein und berechnet die Anzahl der freien
Blocks von selbst. Diese Anzahl wird
dann in den Bytes $02 FA ( Lo) und $02 FC
( Hi) des Floppyspeichers abgelegt. Was liegt also näher, als diesen Wert direkt, über den Memory-Read- Befehl der
Floppy auszulesen. Und nichts anderes
tut GETDISKF:
getdskf lda #<(com5) ;Memory-Read ldy #>(com5) ;Befehl jsr striec ;senden. lda #8 ;Floppy sende- jsr talk ;bereit auf lda #$6f ;Befehlskanal jsr sectlk ;machen. jsr iecin ;Lo-Byte lesen sta dskfree+0 ;und sichern. jsr iecin ;Byte überlesen. jsr iecin ;Hi-Byte lesen sta dskfree+1 ;und sichern. lda #8 ;Floppy zurück- jmp untalk ;setzen.
Zusätzlich hierzu muß noch das entspre- chende Memory-Read- Kommando im Sourcetext abgelegt werden (3 Zeichen ab
Adresse $02 FA lesen) :
com5 .text "m-r" .byte 250,2,3,13,0
7) GETDIRF Nun kommen wir zur Routine zum Ermitteln
der freien Directoryblocks. Hier können
wir keinen Umweg gehen, sondern müssen
die BAM direkt auslesen. Da das Directory ausschließlich in Block 18 steht, genügt es, das erste Byte des BAM-Eintrags für Track 18 auszulesen. Dieses
Byte steht an Position 72 des DiskHeaderBlocks. Wir müssen also den Block
18/0 in den Puffer lesen, und den Pufferzeiger auf 72 setzen um an die
gewünschte Information zu kommen.
Gleichzeitig berechnet diese Routine die
Anzahl der insgesamt verfügbaren Blocks
auf Diskette. Hierzu müssen wir lediglich den Inhalt von DSKFREE und DIRFREE addieren und in ALLFREE ablegen.
getdirf lda #<(com6) ;"Block 18/0 ldy #>(com6) ;lesen" jsr striec ;senden. lda #<(com7) ;"Pufferzeiger ldy #>(com7) ;auf Byte 72" jsr striec ;senden. lda #8 ;Floppy sende- jsr talk ;bereit auf lda #$62 ;Pufferkanal jsr sectlk ;machen jsr iecin ;Wert lesen sta dirfree ;und sichern. ldx dskfree+1 ;Hi-Byte lesen clc ;Lo-Byte zu adc dskfree+0 ;DIRFREE bcc gf1 ;addieren. inx gf1 sta allfree+0 ;und in ALLFREE
stx allfree+1 ; ablegen.
lda #8 ;und Floppy jmp untalk ;zurücksetzen.
Auch hier benötigen wir zwei Sourcecodetexte für die Diskkommandos:
com6 .text "u1 2 0 18 0" ;Sekt. 18/0 .byte 13,0 ;lesen com7 .text "b-p 2 72" ;Pufferzgr. .byte 13,0 ;auf 72.
8) CHGENT Kommen wir nun zu einer der wichtigsten
Routinen unseres Programms. Sie durchsucht die Directoryblocks nach unserem
Fileeintrag, liest aus ihm den
Track/ Sektor des ersten ' normalen' Datenblocks aus und legt ihn in den Adressen FTRACK und FSEKTOR ab. Desweiteren
wird hier dann Track und Sektor des ersten, freien Directoryblocks eingetragen
und der Block wieder auf die Diskette zurückgeschrieben. Auch diese Routine
benutzt die Sektorentabelle BLKTAB, um
die Reihenfolge der Directroyblocks zu
ermitteln. Desweiteren benötigt sie noch
eine weitere Tabelle, in der die Anfangspositionen der einzelnen Fileeinträge eines Directoryblocks abgelegt sind. Diese Tabelle heißt ENTTAB
und sieht folgendermaßen aus:
enttab .byte 2,34,66,98,130,162 .byte 194,226
Kommen wir nun jedoch zur eigentlichen
Routine. Sie besteht im Prinzip aus zwei
ineinander verschachtelten Schleifen, die nacheinander die einzelnen Dirblocks
einlesen und jeden einzelnen Eintrag mit
dem Namen in NAME vergleichen. Wird der
Name gefunden, so werden die Schleifen
verlassen. Dann wird nochmals auf diesen
Eintrag positioniert, und zwar so, daß
der Bufferpointer direkt auf die zwei
Bytes für Starttrack und - sektor zeigt und dort die Werte für den ersten freien
Dirblock eingertragen ( Track 18, Sektor
in DIRSTRT) . Abschließend wird dieser
Directoryblock inklusive der Änderung
wieder auf die Diskette zurückgeschrieben:
chgent lda #<(text6) ;"Suche ldy #>(text6) ;Eintrag" jsr strout ;ausgeben lda #1 ;Blockzähler sta mem1 ;initialisieren celoop1 lda #8 ;Floppy zurück- jsr untalk ;setzen. ldy mem1 ;Blkzähler holen lda blktab,y ;Sekt.nr. lesen, sta mem3 ;ablegen, ldx #<(com1) ;und Sektor lesen ldy #>(com1) jsr sendcom inc mem1 ;Blkzähler+1 lda #0 ;Eintragszähler sta mem2 ;initialisieren celoop2 ldy mem2 ;Eintr.z. holen cpy #8 ;Mit 8 vgl. beq celoop1 ;Ja, also näch- sten Block lesen lda enttab,y ;Nein, also Pos. lesen, sta mem4 ;ablegen, ldx #<(com2) ;und Pufferzeiger ldy #>(com2) ;positionieren. jsr sendcom inc mem2 ;Eintr.z+1 lda #8 ;Floppy zum jsr talk ;Senden auf lda #$62 ;Pufferkanal jsr sectlk ;bewegen. jsr iecin ;Filetyp holen. cmp #$82 ;u.m. "PRG" vgl. bne celoop2 ;Wenn <>, dann direkt weiter. jsr iecin ;Sonst Track sta ftrack ;und Sektor jsr iecin ;holen und ab- sta fsector ;legen.
Bitte nun Teil 3 des Floppy-Kurses laden
ldy #$ff ;Jetzt Filenamen celoop3 iny ;mit NAME jsr iecin ;vergleichen cmp name,y beq celoop3 cpy #16 ;Wenn 16 Stellen beq ce1 ;dann OK. cmp #160 ;letztes Byte 160 (Endmarkierung) bne celoop2 ;Nein, also wei- tersuchen. lda name,y ;Letztes Namens- bne celoop2 ;byte=0? Nein, weitersuchen. ce1 lda #8 ;Floppy jsr untalk ;zurücksetzen. lda mem4 ;Letzte Eintrags- clc ;position holen, adc #1 ;1 Addieren ldx #<(com2) ;und setzen ldy #>(com2)
jsr sendcom
lda #8 ;Floppy zum jsr listen ;Empfang auf lda #$62 ;Pufferkanal jsr seclst ;bereit machen. lda #18 ;Tracknr. jsr iecout ;ausgeben. lda dirstrt ;SektorNr. jsr iecout ;ausgeben. lda #8 ;Floppy jsr unlist ;Zurücksetzen lda mem3 ;letzten Dir- ldx #<(com3) ;block holen, ldy #>(com3) ;und zurück- jmp sendcom ;schreiben.
Auch hier werden zwei Floppykommandos
benötigt. Diesmal jedoch welche mit fehlendem letzten Parameter, der von SEND-COM hinzugefügt wird:
com1 .text "u1 2 0 18";Einen DirBlk .byte 0 ;lesen. com2 .text "b-p 2" ;Auf best. .byte 0 ;Byte posit.
9) WBLOCKS Dies ist die zweite, wichtige Routine
unseres Programms. Sie füllt die freien Directoryblocks mit den Bytes vom Fileanfang, bis zu dem Punkt, an dem das
' normale' File beginnt. Gleichzeitig
werden diese Dirblocks mit Hilfe des
Block-Allocate- Befehls als ' belegt' gekennzeichnet:
wblocks lda #<(text7) ;"Schreibe Dir- ldy #>(text7) ;blocks" jsr strout ;ausgeben. ldx #<(filemem);Zeiger auf ldy #>(filemem);Filemem stx $fd ;setzen sty $fe wbloop1 lda #<(com4) ;Pufferzeiger ldy #>(com4) ;auf 0 posi- jsr striec ;tionieren. lda #8 ;Floppy auf jsr listen ;Pufferkanal lda #$62 ;Empfangsbereit jsr seclst ;machen. ldy dsindex ;Indexzgr. holen wb4 iny ;und erhöhen. lda blktab,y ;Sektornr. lesen bpl wb1 ;Wenn>0, dann nicht letzter Block lda ftrack ;Wenn letzter jsr iecout ;Block, dann lda fsector ;Starttrack u. jsr iecout ;-sektor d. norm. jmp wb2 ;Files eintr. wb1 pha ;Sonst Sekt. ret- ten, lda #18 ;Track 18 jsr iecout ;senden, pla ;Sektor holen jsr iecout ;und senden. wb2 ldy #0 ;Anschließend wbloop2 lda ($fd),y ;254 Datenbytes jsr iecout ;senden. iny cpy #$fe bne wbloop2 lda #8 ;Floppy zurück- jsr unlist ;setzen. lda $fd ;Zeiger neu clc ;positionieren adc #$fe ;(+254). bcc wb3 inc $fe wb3 sta $fd
ldy dsindex ; Sektornummer lda blktab, y ; holen,
pha ;retten, ldx #<(com3) ;und Sektor ldy #>(com3) ;schreiben. jsr sendcom pla ;Sektornummer ldx #<(com8) ;zurückholen u. ldy #>(com8) ;Block als 'be- jsr sendcom ;'legt' kenn- zeichnen. inc dsindex ;Index+1 dec dirfree ;freie Dir- blocks-1 (dient als Schlei- fenzähler) bne wbloop1 ;und wiederholen. rts
Auch hier eine Auflistung der benötigten
Floppybefehle. COM3 und COM8 dienen
hierbei als Halbbfehele, die von SENDCOM
ergänzt werden:
com3 . text " u22018" ; Block auf
.byte 0 ;T18 schr. com4 .text "b-p 2 0" ;Bufferptr. .byte 13,0 ;auf 0 setzen com8 .text "b-a 0 18" ;Block be- .byte 13,0 ;legen.
Hiermit ist der Floppykurs nun beendet.
Für diejenigen unter Ihnen, die Geschmack an der Programmierung der Floppy
gefunden haben, halten wir für die nächste Ausgabe einen ganz besonderen Lekkerbissen parat: ab dann gibt es nämlich
einen aufbauenden Floppy-Kurs für Profis, in dem die Floppy direkt programmiert wird.
Öbrigens: Sie sollten es nicht auf sich
sitzen lassen, daß unser Beispielprogramm mit Einschränkungen arbeitet. Öben
Sie sich doch einmal selbst in der
Floppyprogrammierung und versuchen Sie
das Programm so zu modifizieren, daß
auch Files, die kürzer als die vorhande- nen freien Dirblocks sind, installiert
werden können. Viel Spaß dabei und bei
der Programmierung der Floppy ansich
wünscht Ihnen,
Uli Basters (ub).