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 64ers erklären. Beginnend mit den Grund- lagen 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 sequentiel- len Fileprogrammierung gewidmet sein. Kommen wir zunächst einmal zu den Grund- begriffen der Datenein- und ausgabe des 64ers. Wenn Sie sich mit BASIC ausken- nen, 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 (BASIC- orientierten) Lesen von einem Datenka- nal, und * CLOSE, zum abschließenden Schließen eines Datenkanals. Datenein- und ausgaben werden nun prin- zipiell mit drei verschiedenen Parame- tern 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 ange- sprochen werden soll. Diese drei Parame- ter sind die "logische Filenummer", die "Sekundäradresse" und die "Gerätenum- mer". 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 Datenka- nal zur Floppy mit der logischen File- nummer 1 und ein Datenkanal zum Drucker mit der logischen Filenummer 2 geöffnet, so weiß das Betriebssystem des 64ers immer, wohin es Daten mit einer der bei- den Filenummern senden soll. Ein "PRINT#1,A$" sendet so die Stringvaria- ble A$ an den momentan offenen Floppyka- nal (nachdem die Filenummer 1 so durch 'OPEN' definiert wurde), wohingegen der Befehl "PRINT#2,A$" dieselbe Stringva- riable 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 die- se Nummer deshalb, weil die verschiede- nen Peripheriegeräte mit unterschiedli- chen 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 zwi- schen 0 und 15 annehmen, wobei es fol- gende 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 Adres- sen 0, 1 und 15 sind spezielle Adressen, die eingens für die Verwaltung von Mas- senspeichern gedacht sind. Normalerweise muß man nämlich beim Zugriff auf einen Massenspeicher (so wie auch das Floppy- laufwerk einer ist) grundsätzlich die Art des Zugriffs explizit im Filenamen angeben. Möchte man aber ein Programmfi- le (dazu später mehr) lesen oder schrei- ben, so kann man sich die Angabe im Fi- lenamen 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 Floppybe- fehlskanal 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 Artver- wandter des 6510, der im 64er seinen Dienst verrichtet) und eigene Ein- /Ausgabechips. Sie stellt im Prinzip einen autonomen Computer dar, der rich- tig Programmiert werden kann. Die wich- tigsten Funktionen sind in spezeillen Floppybefehlen zusammengefasst und wer- den über den erwähnten Befehlskanal auf- gerufen (wie z.B. der Befehl, die ein- liegende Diskette zu formatieren, oder ein spezielles File von der einliegenden Diskette zu löschen). Der Floppybefehls- kanal hat später, wenn wir die Direktzu- griffbefehle behandeln, und in Zusammen- hang 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 Syn- taxdefinition): 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 Befehls- kanal ö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ära- dresse 15 (eben dem Befehlskanal dersel- bigen). Nun können Sie der Floppy Befeh- le senden, die diese dann unabhängig vom 64er bearbeiten wird. Sie können also Ihren 64er währenddessen in seinem ak- tuellen Programm fortfahren lassen. Er arbeitet, solange die Floppy selbst ar- beitet, unabhängig von ihr. Nur, wenn während dieser Zeit ein weiterer Disket- tenzugriff notwendig wird, wird der 64er angehalten (s.u.) Ich will Ihnen nun die Standard- Floppybefehle auflisten, die wir einfach benutzen können. Später beim Direktzu- griff und bei der relativen Dateiverwal- tung werden wir ebenfalls über den Be- fehlskanal 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 mehre- ren Buchstaben, sowie den zu jedem Be- fehl 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 Identifi- kationskennung ("ID"). Floppybefehle werden nach dem Üffnen des Befehlskanals immer mit einem "PRINT#lfn" (lfn=logi- sche Filenummer) an die Floppy ge- schickt. 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öff- net (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 Formatie- rung der Diskette. Der 64er meldet sich mit einem "READY." zurück, und Sie kön- nen während der Formatierung mit ihm arbeiten. Sie können den Befehlskanal nun offen halten und weitere Floppybe- fehle 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 64er 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 unwei- gerlich zu einem zwischenzeitlichen Stop des Rechners. Der NEW-Befehl kennt übrigens zwei Syn- taxen. Ö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 Spu- ren 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 "softforma- tiert". 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 enthal- ten und können evtl. gerettet werden. Ausserdem ist das softformatieren wei- taus schneller als ein "hardformatie- ren". Es funktioniert allerdings nur bei schon einmal hardformatierten Disketten, da diese die grundlegende Diskettens- truktur 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 64er 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 um- benennen. 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 glau- ben, daß die Diskettenstruktur durchei- nandergekommen 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 Direc- tory. Er benötigt keinerlei Parameter und kann folgendermaßen aufgerufen wer- den: PRINT#1,"V" Die Floppy beginnt nun mit der Validie- rung. Diese kann je nach dem wie voll und wieviele einzelne Files auf der Dis- kette enthalten sind, bis zu mehrere Minuten in Anspruch nehmen. * Der SCRATCH-Befehl: Dieser Befehl löscht ein File auf Dis- kette. 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:" einge- leitet, gefolgt von dem oder den Filena- men, 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ür- zen. 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 vier- ten 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 Ein- mal 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 einzel- nen 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 sequentiel- len 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 programmie- ren, so sollten Sie Ihre Adressen in einer SEQ-Datei speichern. Dies kenn- zeichnet Ihre Daten eben als Daten und nicht als ausführbares Programm (obwohl Sie genausogut eine PRG-Datei verwenden könnten!). Die Unterscheidung ist beson- ders wichtig für den LOAD-Befehl, der SEQ-Dateien gar nicht erst liest, son- dern ausschließlich PRG-Dateien in den Speicher des 64ers 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 zusammenge- fasst und mit einer fortlaufenden Satz- nummer versehen. Wenn Sie zum Beispiel die Adressen Ihrer Adressverwaltung in relativen Datensätzen speichern, und sich merken, unter welchen Datensatznum- mern Sie die einzelnen Adressen wieder- finden, so können Sie direkt auf einen Datensatz zugreifen. Die Vorteile ge- genüber sequentieller Speicherung liegen auf der Hand: durch den Direktzugriff sind die Daten erstens schneller er- reichbar, weil nicht eine komplette Da- tei eingelesen werden muß, sondern eben immer nur ein einziger Datensatz, und sie können zweitens extern gelagert wer- den, so daß Sie kostbaren Arbeitsspei- cher im 64er sparen. Die Programmierung von relativen Dateien wird uns im 2. Teil dieses Kurses noch näher beschäfti- gen. Bitte Teil 2 laden... * User-Dateien (USR) Diese Dateien sind speziell für die di- rekte Floppyprogrammierung gedacht. Da die 1541 ja über einen eigenen Prozessor verfügt kann dieser auch direkt in Ma- schinensprache programmiert werden. Legt man nun ein Assemblerprogramm in einem USR-File ab, so kann dieses von der Floppy direkt in ihren eigenen Arbeits- speicher geladen, und dort von ihr aus- geführt werden. * Deleted Files (DEL) Mit der Kennung "DEL" werden Files mar- kiert, 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 'unbe- legt' 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 gekenn- zeichneter Eintrag ist normalerweise im Directory nicht sichtbar. Wie das den- noch 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 SEQ- und REL-Dateien sollen nun in diesem und dem nächsten Teil die- ses 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 Rei- henfolge, mit der wir Daten in ein sol- ches 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)equen- tielle Datei. Andere mögliche Filetypen wären "R" für relative, "P" für Pro- gramm-, oder aber auch "U" für Userda- teien. Die "P" Angabe nimmt dabei eine Sonderstellung ein. Da sie der am häu- figsten 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 funktio- niert 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 Bezeich- nung der Datenoperation, die durch- geführt werden soll. Im Beispiel benutz- ten 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 Dis- kette angelegt wird. Nachdem im obigen Beispiel eine sequen- tielle Datei zum Scheiben geöffnet wur- de, können wir Daten mittels des PRINT#-Befehls in sie hineinschreiben. Dies kann in BASIC auf verschiedenste Arten geschehen, die Betriebssystemrou- tine des PRINT#-Befehls passt sich auto- matisch den Variablentypen an (wie Sie sehen wurden in obigem Beispiel String-, Float- und Integerveriablen geschrie- ben). 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 Bei- spiel 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 Disket- te 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 Endmar- kierung eines Wertes heranzieht. Einzel- ne Bytes liest man mit der GET#- Funktion, die ja sowieso immer nur ein einzelnes Byte einliest. Hier ein Bei- spiel, 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öch- te man sie wieder in echte Byte-Werte umwandeln, muß man die ASC-Funktion wie- folgt 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 Filepro- grammierung unbedingt anmerken sollten ist, daß die Daten eben "sequentiell", also hintereinander, in einem File lie- gen. 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 aus- sehen mag, wie Sie im nächsten Monat bei der relativen Dateiverwaltung festellen werden. Haargenau so, wie wir ein SEQ-File gele- sen und geschrieben haben, können Sie mit PRG-Files hantieren. Sie müssen le- diglich 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" Se- kundäradressen 0 und 1. Wie ich obig schon beschrieben hatte, können wir da- mit der Floppy gleich schon mitteilen, daß wir ein PRG-File lesen oder schrei- ben wollen. Die explizite Angabe ",P,R" oder ",P,W" entfällt. Die Sekundäradres- se 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# schrei- ben. Ebenso müssen Sie Ihre Files natür- lich 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 (be- sonders 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 da- hin 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 ei- niges über den Floppybefehlskanal und die Programmierung sequentieller Files gelernt. In diesem Teil wollen wir uns nunmehr mit der relativen Dateiverwal- tung beschäftigen, die zwar etwas kom- plizierter 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 hin- ter 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 gere- gelt. Sie arbeiten mit sogenannten "Da- tensätzen", oder engl. "Records". Im Prinzip kann man eine relative Datei mit einem externen Variablenfeld verglei- chen. 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. Den- noch 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 schrei- ben 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 "Schrei- ben" unterschieden. Wir öffnen ein sol- ches File ganz einfach um der Floppy anzuzeigen, daß wir es nun benutzen wol- len. Ob wir nun lesen oder schreiben ist ganz egal. Die Floppy erkennt automa- tisch, wenn wir etwas schreiben oder lesen. Das heißt also, daß wir bei der Benutzung eines relativen Files immer auch den Floppybefehlskanal öffnen müs- sen. Ö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ön- nen. Bevor wir das tun, sollten wir uns überlegen, wie lang ein Datensatz unse- res 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 Fi- les immer gleich lang sein müssen, um der Floppy das Auffinden eines Datensat- zes 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 die- sen angehängt folgt die Typenkennung ",L," sowie der Länge des Datensatzes in diesem File. Hier ein kleiner Hin- weis: 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 Num- mern 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 ei- gentlich ganz einfach: wir sprechen le- diglich 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 Bei- spiel also alle 300. Das Generieren die- ser Sätze kann jetzt einige Zeit in An- spruch 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 rela- tiven Files. Wenn die Floppy mit dem Anlegen der re- lativen Datei fertig ist blinkt sie übrigens sowieso, da wir durch den Zu- griff 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 Low- und 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 Da- tensä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 Positionie- rungsbefehl an die Floppy. Wohlgemerkt geschieht dies über den Befehlskanal! Aus dem Beispiel ist die Syntax des Po- sitionierungsbefehls ersichtlich. Begin- nend mit dem Zeichen "P" (für "positio- nieren") werden in Reihenfolge die Se- kundäradresse der REL-Datei auf die sich die Positionierung beziehen soll, die Datensatznummer in Lo/Hi-Folge, sowie die Byteposition innerhalb des entspre- chenden 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 positio- nierten 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 er- stellten REL-Datei arbeiten, so müssen wir sie natürlich öffnen. Hierbei ist darauf zu achten, daß wir dieselbe Da- tensatzlänge angeben, wie wir sie beim Erzeugen der Datei verwendet haben. An- dernfalls kommt die Floppy nämlich mit der Verwaltung der Datensätze durchein- ander, was verheerende Folgen bei Schreibzugriffen haben kann. Benutzen Sie am Besten also den gleichen OPEN- Befehl, den Sie auch beim Erstellen be- nutzt 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 Daten- satz 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 geschrie- ben. Da der Datensatz diesmal schon exi- stiert 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 verwen- den. Dieser erkennt das Ende eines Lese- vorgangs 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üs- sen 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 Da- tensatz 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 Endmarkie- rung (das "CR") für den INPUT#-Befehl geschrieben wurde. DER LESEZUGIFF Auch der Lesezugriff weicht nicht son- derlich von den bisherigen Beispielen ab. Wie immer öffnen die beiden Floppy- kanäle und positionieren auf den gewünschten Datensatz. Nun haben wir zwei Möglichkeiten unsere Daten wieder auszulesen: Wurden die Daten OHNE Semikolon ge- schrieben, 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 An- merkungen: 1) Bei der GET#-Abfrage sollten nicht mehr Zeichen gelesen werden, als ma- ximal in dem Datensatz vorhanden sein können. Bei einer Länge von 80 Zei- chen wird die GET#-Schleife also nicht mehr als 80 mal durchlaufen. 2) Was tun wir, wenn der Datensatzinhalt kürzer ist, als die festgelegte Da- tensatzlänge? Wie ich oben schon ein- mal 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. Schrei- ben 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 Vorraus- sagen eigentlich relativ einfach ist. Wir müssen jeweils nur richtig positio- nieren 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 an- sprechen. 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üs- sen. 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än- ge eines Datenfeldes beschränken. Sie könnten nun für jedes Datenfeld eine eigene REL-Datei anlegen, also bei- spielsweise 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 je- desmal, wenn Sie eine komplette Adresse ausgeben wollen, alle Ihre REL-Files nacheinander öffnen, lesen und schließen müssen. Was das an Programmier- und Zeitaufwand beim Zugriff bedeutet ist verheerend. Deshalb geht man in der Re- gel 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 sol- len. Für unsere kleine Adressverwaltung wollen wir 6 Felder pro Datensatz defi- nieren:
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 Daten- satz jeweils alle 6 Felder abgelegt wer- den. Dadurch ergibt sich eine Daten- satzlänge von 114 Zeichen (20+15+30+4+ 30+15=114). Wir können nun wiederum zwei Wege gehen, mit denen wir die sechs Fel- der 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 unflexible- re, Methode sieht folgendermaßen aus: Wir schreiben mit mehreren PRINT#- Befehlen OHNE Semikolon alle Stringva- riablen 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 schrei- ben, wie dieser lang ist, was man tun- lichst unterlassen sollte). Je nach dem, wie flexibel unser Adressverwaltungspro- gramm sein soll entstehen nun jedoch diverse Schwierigkeiten. So müssen wir zum Beispiel immer alle Felder eines Datensatzes einlesen, wenn wir eine Da- tei z.B. nach einem einzelnen Feld sor- tieren möchten. Für gerade diese Aufgabe werden dann immer 5 Felder zuviel gele- sen, was sich wiederum auf die Verarbei- tungszeit ReLativ auswirkt. Das zweite Problem ist die Endmarkierung nach jedem Feldeintrag. Wie oben ja schon darge- stellt 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 sicher- lich 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 vorausbe- rechnen, an welcher Stelle im Datensatz welches Feld zu finden, bzw. abzulegen ist, ohne daß sich zwei Felder über- schneiden. 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 gleich- zeitig die Lese- und Schreibzugriffe beschleunigt, da immer nur so viele Zei- chen gelesen werden, wie auch wirklich benötigt werden (und nicht immer die maximale Feldlänge). Wollen wir nun ein- mal ausrechnen, an welchen Bytepositio- nen 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 folgenden- den Feldes berechnen, indem wir die An- fangsposition im Datensatz mit der Feldlänge addieren. Nun wissen Sie ja, daß man beim Positio- nierbefehl nicht nur die Datensatznum- mer, sondern auch die Byteposition in- nerhalb des gewählten Datensatzes an- geben kann. Und über diese Methode kön- nen wir nun ganz bequem jede der 6 Feld- positionen einstellen und den Feldein- trag hineinschreiben. Beim Lesen können wir, da wir für jedes Feld ja die An- fangsposition innerhalb eines Datensat- zes kennen, dieses ebenfalls direkt anwählen und Lesen. Das ist vor allem beim Sortieren einer REL-Datei von Vor- teil, da wir nun nach allen sechs Fel- dern eine Datei beliebig sortieren kön- nen, 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 be- treffende Person umgezogen ist, so ge- nügt es auf das Feld "Straße" (Byte 36 im entsprechenden Datensatz) zu positio- nieren und den neuen Eintrag hineinzu- schreiben. Hierbei müssen Sie jedoch auch beachten, daß der neue Straßenname unter Umständen kürzer ist, als der al- te. 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 hinausschrei- ben und so das erste Zeichen des darauf- folgenden Feldes überschreiben!
SORTIEREN ODER INDIZIEREN:
Oben habe ich schon einmal die Möglich- keit der Sortierung einer Datenbank an- gesprochen. Die augenscheinlich einfach- ste 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 Sortier- algorithmen ist die Anzahl der Austau- sche zwischen zwei Einträgen ungemein hoch). Zusätzlich erfordert diese Metho- de nach jedem Neueintrag das Umkopieren der kompletten REL-Datei, da ein neuer Datensatz ja ebenfalls irgendwo einsor- tiert werden muß, und damit alle folgen- den 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 flexi- ble 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 Adress- verwaltung 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 ausle- sen und alphabetisch in eine Liste ein- ordnen. 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 folgen- den vier Namenseinträge, in der Reihen- folge 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 Na- men und erhalten folgende Reihenfolge:
2 Becker 4 Meier 1 Müller 3 Schmidt
Die aphabetisch richtige Reihenfolge legen wir nun z.B. in einem Variablen- feld 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 bearbei- ten. 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 wei- tere Vorteile. So können wir auch ganz beliebig nach verschiedenen Feldern sor- tieren und gleichzeitig beide sortierten Dateien weiterverwenden. Wenn Sie Ihre Adressen manchmal besser über den Vorna- men 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 be- quem möglich, da wir zu der einen offe- nen REL-Datei auch noch eine SEQ-Datei öffnen dürfen, ohne daß der 64er 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 bekom- men. Schreiben Sie nämlich eine numeri- sche 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 Zeichenket- te, also eine String, um. Wenn diese numerische Variable nun verschieden vie- le Stellen besitzen kann, so werden Sie ganz schöne Schwierigkeiten bekommen. Logischerweise hat die Zahl "100" eine Stelle weniger als die Zahl "1000". Hin- zu kommt, daß auch noch ein "-" als Vor- zeichen 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 schwie- rig, da das dann immer auch ganz von dem Verwendungszweck abhängt. Entweder müs- sen Sie dem Benutzer schon bei der Ein- gabe der Zahlen gewisse Einschränkungen auferlegen, oder aber durch eigene Um- konvertierung 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 Low- und Highbyte aufzuspalten und die beiden CHR$-Codes zu speichern. In der Praxis sieht das ähnlich wie in der Syntax des Positio- nierbefehls 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 ei- ner 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 wie- der einlesen, weshalb Sie noch ein Zei- chen mehr für das CR mitrechnen sollten. Ein anderer Weg um Float-Zahlen zu spei- chern wäre das direkte übernehmen der 5-Byte-Mantissen-Darstellung, wie das Betriebssystem des 64ers sie benutzt, jedoch ist diese Möglichkeit relativ kompliziert, da sie den intensiven Ge- brauch von Betriebssystemsroutinen er- fordert, 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 befas- sen, 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 drit- ten Teil dieses Kurses. Nachdem wir uns in den ersten beiden Teilen um die se- quentielle und die relative Dateiverwal- tung 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 zuzugrei- fen und sie unseren Wünschen entspre- chend 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 Ih- nen das verdeutlichen... (Anm. d. Red.: Bitte wählen Sie jetzt den 2. Teil des Floppykurses aus dem Textmenu!)
Daraus ergibt sich nun die folgende Sek- torenverteilung:
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 Direktzu- griffsbefehlen benötigen, um einen spe- ziellen 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 (ob- wohl 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 64er, 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 2KB RAM als Arbeitsspeicher für letzeres und Zwi- schenspeicher für Diskettenblocks. Die VIA-Bausteine übernehmen dabei den Da- tentransfer mit dem 64er, sowie die Be- dienung der Floppymechanik. Natürlich können Sie dem Floppyprozessor auch ein Programm geben, das er abarbeiten soll. Dieses muß in Maschinensprache geschrie- ben sein, wobei der Befehlssatz des 6502 identisch mit dem des 6510 (aus dem 64er) ist. Die Speicheraufteilung der Floppy sieht folgendermaßen aus:
Adresse Belegung ----------------------------------------
$0000-$0800 2KB RAM ab $1800 VIA1 (serieller Bus) ab $1C00 VIA2 (Laufwerkssteuerung) $C000-$FFFF Betriebssystem-ROM (16 KB) Alle übrigen Bereiche sind unbelegt. Wie beim C64 auch, können Sie mit einem Pro- gramm, 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 Ih- nen sicherlich ein geläufiger Begriff). Das Floppy-RAM wird nun in acht Pages unterteilt, die von 0 bis 7 durchnumme- riert sind. Die Pages 0, 1 und 2 sollten wir dabei außer Acht lassen, da sie für die Zeropage, den Stack und den Be- fehls-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 Blockbe- fehlen 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 Block- und Memorybefehlen darstellen. Bevor wir nun einen dieser Befehle be- nutzen können, müssen wir natürlich den Floppybefehlskanal öffnen. Da wir aber immer auch einen Arbeitspuffer im Floppy-RAM benötigen, um die Direktzu- griffsbefehle (einige Blockbefehle und zwei Userbefehle) anwenden zu können, müssen wir gleichzeitig auch einen Puf- fer für uns vorreservieren. Entweder überlassen wir dabei die Wahl des Puf- fers 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 Üff- nen 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 fol- gendermaß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 File- nummer 1 zuordnen. Der zweite OPEN- Befehl öffnet einen Kanal zur Floppy, den wir zum Auslesen und Schreiben unse- res 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 Puffer- nummer mit übergeben. Folgender Befehl reserviert den Puffer Nummer 3 (Floppy- Adressbereich $0300-$03FF) für uns: OPEN 2,8,2,"#3" Wichtig ist, daß wir uns die Sekundära- dresse merken, mit der wir den Pufferka- nal geöffnet hatten. Sie wird später dazu verwendet, um der Floppy mitzutei- len 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äradres- se eine der freien Nummern von 2-14. DIE BLOCKBEFEHLE Nachdem wir nun einen Datenpuffer reser- viert haben und einen Filekanal für die- sen 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 Blockbe- fehle. Mit ihnen können wir die einzel- nen Blocks einer Diskette "hardwaremä- ßig" ansprechen. Aufgrund eines Blockbe- fehls wird physisch auf den Block zuge- griffen und in den Puffer eingelesen, bzw. aus ihm heraus geschrieben. Für die Beispiele innerhalb der Befehlsbeschrei- bungen gelten die obig definierten logi- schen Filenummern (Befehlskanal=1, Puf- ferkanal=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ära- dresse unseres Pufferkanals, die Drive- nummer (immer 0), den Track und den Sek- tor 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 weiterver- wenden.) 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 exi- stiert ein Zeiger, der auf das aktuelle Byte im Puffer zeigt. Möchte man nun ein ganz spezielles Byte aus dem Puffer aus- lesen, 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 ausle- sen, 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 Varia- blen "A". 4) BLOCK-ALLOCATE ("B-A") Mit diesem Floppybefehl können Sie ge- zielt einzelne Disketten-Blocks als "be- legt" kennzeichnen. Das ist dann notwen- dig, wenn Sie Daten mittels "B-W" auf einer Diskette speichern, zusätzlich aber noch normale DOS-Files darauf able- gen 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 eingetra- gen. Mehr zum Aufbau der BAM werden wir im nächsten Teil des Floppy-Kurses ler- nen. 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üs- sen 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 frei- zugeben 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 Puf- fer geladen werden soll, ein ausführba- res Floppy-Programm ist, das nach dem Laden direkt angesprungen wird. DIE MEMORYBEFEHLE Diese Floppybefehle beziehen sich auf die Floppy selbst und haben mit dem Di- rektzugriff nur indirekt zu tun. Trotz- dem 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 Speicher- adressen der Floppy auslesen. Die allge- meine Syntax lautet dabei wiefolgt: PRINT#BFN,"M-R"; CHR$(LO); CHR$(HI); CHR$(N) Hierbei stehen "LO" und "HI" für Low- und Highbyte der Adresse die gelesen werden soll und "N" für die Anzahl Bytes (0-255), die ab dort übertragen werden sollen. Das Low- und Highbyte einer Adresse ermitteln Sie mit folgenden For- meln: HI=INT(Adresse/256) LO=Adresse-256*HI Alle Parameter müssen als CHR$-Codes, also dirket, übertragen werden, und dür- fen nicht als ASCII-Codes (wie in den bisherigen Befehlen) erscheinen. Die zu lesenden Bytes werden ebenfalls als ab- soluter CHR$-Code auf dem BEFEHLSKANAL (!) bereitgestellt (nicht etwa auf dem Pufferkanal, da der interne Floppyspei- cher 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 zu- letzt 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 Low- und Highbytedarstellung, sowie die An- zahl 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 Verwand- ten in BASIC: den SYS-Befehl. Als Para- meter übergeben Sie hier einfach nur die Startadresse des aufzurufenden Assem- bler-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 ver- wendet, da er gegenüber "B-R" einen Vor- teil bietet. Wird mit "B-R" ein Block eingelesen und anschließend der Inhalt des Puffers vom 64er 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 be- kannten Rock-Band sind rein zufällig). Er ersetzt den "B-W" Befehl. Auch hier ist der Vorteil des ersten Bytes gege- ben. 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 Pro- gramm aus. In der Regel besteht selbiges aus einer Reihe von "JMP $XXXX"- Anweisungen, mit denen Sie nun auf ein- fache Weise, vom 64er aus, selbstdefi- nierte Programme in der Floppy aktivie- ren können. Die Userbefehle 3-8 benöti- gen 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 wol- len, 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 Floppysy- stem wird dann wieder in den Einschalt- zustand 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 Ausga- be. 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 vier- ten Teil unseres Floppy-Kurses. Nachdem wir das letzte Mal die Direktzugriffsbe- fehle 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 wol- len 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 unter- scheiden zwischen DOS-internen Verwal- tungsblöcken und den reinen Datenblök- ken. 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 schnel- ler 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 festge- stellt und bei Zutreffen wird mit der Fehlermeldung "File Exists" abgebro- chen). Das DOS trägt nun im freien Ein- trag den Namen und Typ des zu speichern- den 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 able- gen kann. Wurde einer gefunden, so wird seine Track und Sektornummer ebenfalls im Directoryeintrag gespeichert. Im an- dern Fall wird mit der Meldung "Disk Full" abgebrochen. Nun wird damit begon- nen, die zu schreibenden Daten vom Rech- ner 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äm- lich für die Folgeblocknummer reser- viert. 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 festzu- stellen, 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 Wahr- scheinlichkeit 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 be- sonderen 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 ange- sprochen, nur wie funktioniert z.B. das Heraussuchen eines freien Directoryein- trags, 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 Direc- tory zuständig, wobei hier natürlich auch Informationen enthalten sind, die wir nicht beim üblichen LOAD"$",8 ange- zeigt bekommen. Außerdem ist in einem ganz speziellen Block die sogenannte "BAM" abgelegt, was für "Block Availa- billity Map" steht. Öbersetzt bedeutet das nichts anderes als "Block Verfügbar- keits 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 Dis- kette, da nur an ihm das DOS eine Dis- kette ü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 ange- geben 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' ent- spricht. 162-163 Hier ist die zweistellige ID der Diskette vermerkt. 164 Wert 160. 165-166 Formatangabe der Diskette. Die- se ist bei 1541-Disketten immer "2A". 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 wel- chem Block das Directory beginnt. Das ist in der Regel Sektor 1 von Track 18. Dieser Wert könnte jedoch mit Hilfe ei- nes 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 Format- kennzeichen wurde deshalb eingeführt, weil Commodore für ältere Bürorechner ebenfalls eigene Laufwerke gebaut hat, die wiederum eine andere Diskettenstruk- tur aufweisen (z.B. mehr Tracks und Sek- toren). Disketten von solchen Laufwerken können von der 1541 zwar teilweise gele- sen, nicht aber beschrieben werden. Des- halb 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 er- kennen 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 ange- sprochene BAM. Auf sie werden wir gleich zurückkommen. Es folgen zum Schluß noch zwei Informa- tionen über die Diskette. Zum einen der 16 Zeichen lange Diskettenname, zum an- deren die ID, gefolgt von einem 'SHIFT- SPACE' und der DOS-Versionsnummer (nor- malerweise "2A"). Auch diese beiden Be- reiche können verändert werden. Zusätz- lich sollten Sie wissen, daß Sie nicht nur die ID, sondern auch das SHIFT- SPACE-Zeichen, sowie das "2A" 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 kodie- ren. 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 unbe- legt. Zur besseren Öbersicht hier noch- mal 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) die- ser drei Bytes werden nun gezählt und in das 0. Byte der entsprechenden Track-BAM (für Track 1, Byte 4 des DHB), eingetra- gen. 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 ent- haltenen Filenamen und deren Typ gespei- chert. In einen Directoryblock passen 8 Fileeinträge. Sind alle Einträge voll, so wird ein neuer Directoryblock ange- legt. Track und Sektor des erste Direc- toryblocks 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 Track- und dem Wert 255 als Sektornummer markiert. Hier nun eine Öbersicht mit der genauen Bele- gung 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 wer- den immer nur die ersten 30 Bytes be- nutzt. 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-018 16 Bytes für den Filenamen. 019-020 Bei relativen Files stehen hier Track und Sektor des ersten Side-Sektor-Blocks (sonst unbe- nutzt). 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. Des- weiteren finden sich hier auch Informa- tionen 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 vorhan- den 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 Ma- gic-Disk anschauen, so werden Sie dort mehrere Trennstrich-Files finden, die schreibgeschützt sind. Im Filetyp-Byte werden die einzelnen Informationen nun folgendermaßen co- diert: Ist Bit 7 gesetzt, so ist ein File gültig, es wurde ordnungsgemäß ge- schlossen 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 Filecodie- rungen. Werte von 0 bis 4 sind sinnvoll. Bitte Teil 2 des Floppy-Kurses laden!!! Deren Bedeutung ist folgendermaßen auf- geteilt:
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 kei- nen 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) eingetra- gen, so hat ein File die Bezeichnung "? ". Alle anderen Bits des Filetyp-Bytes ei- nes Directoryeintrags sind unbenutzt. Ebenfalls interessant sind das 28. und das 29. Byte eines Fileeintrags. Damit können Sie nämlich ebenfalls einen klei- nen Effekt für den Directoryeintrag er- zielen. 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 Ausge- ben eines Directorys die Filelänge di- rekt 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 Trackbele- gungen, so erhalten Sie eine andere An- zahl an freien Blocks. Das DOS addiert nämlich diese 35 Bytes auf, um die ver- bleibende 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 Format- kennzeichens zu verhindern. Öbrigens sollte ich erwähnen, daß Änderungen an der BAM jederzeit durch den Validate- Befehl der Floppy wieder rückgängig ge- macht werden können. Dieses Floppykom- mando 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 rekons- truiert werden. Und noch etwas ist zu den Directory- einträ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" ent- spricht. Gleichzeitig ist das File als 'ungültig' markiert und nicht schreib- geschützt (wieso auch). Ein solcher Fi- leeintrag kann jederzeit wiederherge- stellt werden, indem man einen definer- ten, gültigen, Filetyp einträgt und an- schließ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 Direc- toryblock anlegt. Dadurch erklärt sich auch, warum neu geschriebene Files manchmal mitten im Directory zu finden sind und nicht am Ende. Hier wurde vor- her 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 aus- probieren möchten, so nehmen Sie sich einfach einen Diskmonitor zur Hand und versuchen Sie einmal einige Directory- einträge oder den Disk-Header-Block zu verändern. Weiterhin kann mit dem Pro- gramm "Disk-Manager" auf dieser MD das Directory noch weitaus komfortabler ma- nipuliert werden. Nun wissen Sie ja wahrscheinlich auch, was dabei ge- schieht. Im nächsten Teil des Floppykurses wollen wir uns mit der Floppybedienung in As- sembler 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 um- setzen. Das heißt, daß wir in diesem Kursteil einige nützliche Anwendungen zum bisher Erlernten programmieren wer- den. Diese werden zunächst in BASIC be- handelt. 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 ein- fach möglich, eine Diskette softwaremä- ßig schreibzuschützen. Sie können an- schließend weder etwas auf die Diskette schreiben, noch etwas von ihr löschen. Das Laden von Programmen funktiert je- doch nach wie vor. Einzige Möglichkeit die Diskette wieder beschreibbar zu ma- chen ist dann das Formatieren (oder nochmaliges Behandeln mit unserem Pro- gramm). Wie realisieren wir nun einen solchen Schreibschutz? Nun, wie wir mittlerweile ja wissen, legt die Floppy beim Forma- tieren 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. Ande- re 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 Auf- zeichnungsdichte (mehr Tracks auf einer Diskette). Um nun zu vermeiden, das Laufwerke von verschiedenen Rechnern die Daten einer Diskette eines anderen Lauf- werks zerstören, wurde eine Sicherung in die 1541 eingebaut. Sie beschreibt aus- schließ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 ande- res 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äm- lich das Formatkennzeichen) in ein ande- res Zeichen als "A" abwandeln und den Block wieder auf die Diskette zurück- schreiben. Abschließend muß die Diskette noch initialisiert werden, damit das Betriebssystem der Floppy, das DOS, die neue Formatkennung auch in den Floppy- speicher übernimmt. Ein Programm, das eine Diskette mit Schreibschutz versieht und ihn wieder entfernt finden Sie auf dieser MD unter dem Namen "FK.SCHREIBSCHUTZ". Die Unter- routine, 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 Be- fehlskanal und übergeben gleichzeitig einen Initialisierungsbefehl an ihn. Mit letzterem werden alle Grunddaten der eingelegten Diskette (z.B. ID, Format- kennung, etc.) in den Speicher der Floppy übertragen. Man sollte vor einer direkten Manipulation des Diskheader- blocks diesen Befehl aufrufen. Desweite- ren öffnen wir einen Pufferkanal mit der Kanalnummer 2 (also Sekundäradresse 2) um Blockoperationen durchführen zu kön- nen. In Zeile 410 wird nun Spur 0 von Track 18 in den Puffer, dem Kanal 2 zu- geordnet 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 Po- sition 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 Disketten- schutz 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" umwan- deln. Jedoch ergibt sich hierbei ein kleineres Problem. Dadurch, daß die Dis- kette ja schreibgeschützt ist, können wir den modifizierten Disk-Header ja nicht mehr auf die Diskette zurück- schreiben! Hier behilft man sich mit einem kleinen Trick. In der Speicher- stelle $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 Spei- cherstelle so um, daß in ihr der Wert "A" steht, so können wir dem DOS vorgau- keln, es hätte eine Diskette mit korrek- ter 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" ab- zuändern. Die Adresse $0101 wird durch das Low-Byte 1 und das High-Byte 1 dar- gestellt. Desweiteren wollen wir nur ein Byte übertragen. Selbiges hat den AS- CII-Code 65, was dem Buchstaben "A" ent- spricht. 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 For- matkennzeichen positionieren und über den Pufferkanal den Buchstaben "A" hein- schreiben. 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 Low- und High-Byte 1, sowie die Länge 1 als CHR$-Codes übergeben. An- schließ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än- dert, ohne Daten zu zerstören. Wollen wir nun einmal selbst so etwas schrei- ben. 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 hineinzu- schreiben. 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änderungs- routine 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 ei- ner Schleife ausgelesen und in der Va- riablen NA$ abgelegt. Diese wird an- schließend ausgegeben um den aktuellen Diskettennamen anzuzeigen. Es wird da- raufhin 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 abge- fragt, 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 nochm- mals initialisiert und alle Kanäle wer- den geschlossen. Wollen wir die ID einer Diskette verän- dern, 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 aufge- fallen, daß in den letzten drei Zeichen der ID im Directory (rechts oben) immer der Text " 2A" steht, was eine Format- kennung 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än- dert 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 Format- kennzeichen aber gleich bleiben soll. Deshalb wird in Zeile 490 der String ID$ nur auf 5 Zeichen gekürzt, falls er län- ger sein sollte. Ein kürzerer Text wird als solcher übernommen und an die Ent- sprechende Position (nämlich 162) ge- schrieben. 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 Blockbe- fehlen animiert. Nächsten Monat wollen wir uns mit einer weiteren Anwendung der Blockbefehle beschäftigen und dann die Programmierung der Floppy in Maschinens- prache angehen.
(ub)
Floppy-Kurs: "Es rappelt in der Kiste..." (Teil 6) ----------------------------------------
Hallo und herzlich Willkommen zum sech- sten Teil unseres Floppykurses. Wir wol- len unsere Kenntnisse auch hier wieder vertiefen und zwei weitere Beispielpro- gramme kennenlerenen. Eines, um das Di- rectory 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 Speicher- verlust einlesen können. Dies ist ein Hinweis auf eine besondere Eigenheit der Floppy. Wenn wir uns nämlich das In- haltsverzeichnis einer Diskette einfach nur anzeigen lassen wollen, so können wir durch Angabe des Filenamens "$" die Floppy dazu bewegen, uns sehr viel Ar- beit abzunehmen. Sie beginnt dann näm- lich damit, alle Directoryblöcke von selbst einzulesen und sie für uns aufzu- bereiten. Ein direktes "herumpfuschen" auf der Diskette mittels der Block- Befehle entfällt. Wollen wir uns nun verdeutlichen, was passiert, wenn wir wie gewohnt das Di- rectory 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 Aufberei- tung 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 Ba- sic-Programm, mit dem Befehl LIST auf dem Bildschirm angezeigt wird. Sie kön- nen es sogar mit NEW wieder löschen, oder mit RUN starten, was jedoch nur einen Syntax-Error bewirkt, da das Di- rectory 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 Zeilen- nummer entspricht. Diese Zeilennummern sind zwar nicht immer in einer numeri- schen Reihenfolge, jedoch macht das Ba- sic unseres Computers da keinen Unter- schied. Der interne Aufbau eines Basic- Programms erlaubt es tatsächlich, Zei- lennummern wahllos zu vergeben. Wenn wir ein Programm direkt eingeben ist das natürlich nicht möglich, da die Eingabe- routine des Betriebssystems die Zeile anhand der Zeilennummer automatisch an der logisch richtigen Position des Pro- gramms 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 ei- gentlich nur der Basic-Programm-Speicher versteht, so daß die LIST-Routine das 'Directory-Programm' listen kann. Des- halb gibt e) einige Bytes, die für uns wertlos sind und überlesen werden müs- sen. 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ß normaler- weise 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 Linkby- tes ü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ön- nen. Hieran anschließend folgen nun ein oder mehrere Spacezeichen, sowie der Filename und die Filekennung im Klar- text. 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 (Ba- sic-)Zeile kennzeichnet. An ihrer Stelle müssen wir lediglich einen Zeilenvor- schub ausgeben (ein PRINT ohne Parame- ter). 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 entspre- chende 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 By- tewert 0 einen "illegal quantity error" verursacht. Das liegt daran, daß ein String, der nur den Bytewert 0 als Zei- chen enthält, einem Leerstring ent- spricht. 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. voheri- ge Teile dieses Kurses). In Zeile 210 werden nun die Startadresse und die bei- den Linkbytes der ersten Zeile überle- sen. Danach beginnt die Hauptschleife des Unterprogramms. In den Zeilen 220 und 230 lesen wir hier zunächst mit Hilfe unserer "Zeichen lesen"-Unterroutine, Low- und 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 be- wußt ein Semikolon an, damit der folgen- de Text direkt hinter der Blockanzahl ausgegeben wird. Dies geschieht in den Zeilen 250 und 260. Dort lesen wir je- weils 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 Haupt- schleife 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 wol- len 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 Floppybe- fehl an den Befehlskanal muß also lauten "S:TEST". Hieraufhin beginnt die Floppy nun, das Inhaltsverzeichnis der einge- legten 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 ledi- glich 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 norma- lerweise im Directory nicht mehr ange- zeigt wird. An dieser Leerstelle wird das File einfach übersprungen. Im Prin- zip sind aber noch alle seine Informa- tionen auf der Diskette enthalten. Zu- mindest einmal solange, bis Sie wieder ein neues File auf die Diskette schrei- ben. Dann nämlich sucht sich die Floppy den ersten freien Directoryeintrag he- raus, der in unserem Fall, der des Files "TEST" ist, und trägt dort das neue File ein. Desweiteren kann es dann auch pas- sieren, daß die Datenblocks, die von "TEST" belegt wurden möglicherweise von denen des neuen Files überschrieben wer- den. 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 ande- re 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 Anzei- gen. Hieraufhin muß vom Benutzer ent- schieden 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 Entsprechen- den Code in den Fileeintrag zu schreiben und alle Blocks des Files zu verfolgen, die von ihm belegt wurden und sie aber- mals 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 ver- ketteten 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 Vor- und Nachteile. Benutzen wir die erste Methode, so haben wir bei höherem Programmieraufwand eine weitaus höhere Arbeitsgeschwindigkeit (da nur die Blocks eines Files herausge- sucht werden müssen und nicht aller Fi- les 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 arbei- ten, die ebenfalls gesucht werden müs- sen. Das aber wiederum macht der Valida- te-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 zeitrau- bender Datenaustausch zwischen Floppy und C64 entfällt. Da wir unser Programm in BASIC schreiben wollen, ist er be- stimmt 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 Pro- gramm ja so erweitern, daß z.B. bei we- niger als fünf Files automatisch der Validate-Befehl benutzt wird und im an- deren Fall die "von Hand"-Routine. Zunächst will ich Ihnen nun eine Unter- routine vorstellen, die uns das Inhalts- verzeichnis einliest und alle Informa- tionen darüber in Variablenfeldern ablegt. Sie steht in dem Beispielpro- gramm "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 Ein- trag einem DEL-File entspricht. Ich habe die Routine jedoch so ausgelegt, daß Sie sie auch für andere Zwecke benutzen kön- nen, da sie so ziemlich alle Informatio- nen des Directorys ausliest. Vorher sind jedoch noch einige Anmerkun- gen zu der Routine zu machen. Vorab sei gesagt, daß in den Zeilen 600-620 die- selbe 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 Variablenfel- der, die im Hauptprogramm mit Hilfe der DIM-Anweisung dimensioniert werden müs- sen. 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 beinhal- ten kann, werden alle diese Felder auf maximal 144 Elemente dimensioniert. Die Felder DT und DS werden benutzt um Track und Sektor der Folgeblocks des Direc- torys 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 (hie- rauf 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 wer- den die Vorbereitungen zum Einlesen des Directorys getroffen, sowie der Disket- tenname und die ID in die Variablen "dn$" und "id$" eingelesen. Diese Vari- ablen können Sie im Hauptprogramm eben- falls weiterverwenden. Nachdem also in Zeile 200 ein Informa- tionstext ausgegeben wurde, öffnen wir in Zeile 210 den Befehlskanal und einen Pufferkanal. Ersterer erhält die File- nummer 1, letzterer die Filenummer 2. Beim Öffnen des Befehlskanals initiali- sieren wir die Diskette auch gleich- zeitig. 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 Puffer- zeiger 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 Lauf- variable "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 Direc- toryblock ein, dessen Track und Sektor ja noch in TR und SE stehen. Anschlies- send 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 Fileein- träge stehen. In den Zeilen 310 und 320 werden nun schon einmal Track- und 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 Directory- blocks in den entsprechenden Feldern ablegt. Zur Information geben ich Ihnen hier nocheinmal den Aufbau eines Direc- toryeintrags 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-018 16 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 Low- und 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 Schleifen- anfang zurück verzweigt. Ist die Schleife 8 Mal durch- laufen 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 Folge- track 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 noch- mals wiederholt werden und es wird zu Zeile 300 zurückverzweigt. Im anderen Fall sind wir beim letzten Directory- block angelangt und können die File- kanä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 ent- halten, ist der Filename bei solchen Einträgen 16 Mal der Wert CHR $(0), was einem Leerstring ("") entspricht. Abschließend wird in das aufrufende Pro- gramm zurückverzweigt. In unseren Feld- variablen befinden sich nun alle Infor- mationen 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 ent- halten ist. "q" bezeichnet lediglich die Nummer des letzten Eintrags). Nun wissen wir also schon, wie wir die Directory- informationen 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 des- halb auf die unteren 3 Bits beschränken, da in den Bits 7 und 6 ja auch noch In- formationen über das File gespeichert sind. In den Zeilen 1100-1120 des Haupt- programms werden diese Einträge nun he- rausgesucht 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 interes- sieren. Das ganze Programm können Sie sich ja einmal auf dieser MD anschauen. Hier nun die Zeilen 1100-1120: 1100 z=0 1105 fori=0toq 1110 if(ty(i)and7)=0thenli(z)=i:z=z+1 1120 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 File- typs 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 addie- ren des Wertes 128. Nun müssen wir nur noch den neuen Wert für den Filetyp in die Feld- variable "ty" übernehmen. Als nächstes muß der neue Filetyp im Direc- tory 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 zu- nächst den Variablen TR und SE die Werte für den Directoryblock, in dem der Eintrag steht zugewiesen. Als nächstes öffnen wir einen Befehls- und einen Pufferkanal mit den logischen File- nummern 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 Track- und 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 Typen- byte 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 Track- und Sektornummer wird nun in den Zeilen 1510 und 1520 in TR und SE ein- gelesen 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 aus- gehen, 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 sieb- ten Teil des Floppy-Kurses. In diesem Monat möchten wir uns an die Floppy- Programmierung in Assembler heranwagen. Hierbei werden wir alle notwendigen Rou- tinen zum I/O-Handling von Maschinenspa- che 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 Befehlser- lä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 Maschinen- sprache ansprechen können. Dies unter- scheidet sich von BASIC zwar schon in einigen Punkten, da es etwas aufwendiger ist, jedoch können wir hier komplett auf schon im Betriebssystem des 64ers vor- handene Routinen zurückgreifen, die le- diglich mit den richtigen Parametern gefüttert werden müssen. Diese Routinen sind dieselben, die auch vom BASIC des C64 benutzt werden, weshalb die Parame- terübergabe der von BASIC sehr ähnlich ist. Kommen wir zunächst einmal zu eini- gen 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 überge- ben, 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 Fi- lename, dessen Appendix ebenso die ver- langte Operation spezifizierte. Man kann also sagen, daß diese Werte alles Grund- werte sind, die zum Datenaustausch mit der Floppy benötigt werden. Aus diesem Grund müssen sie auch bei jeder Fileope- ration angegeben werden. Möchten wir nun eine Operation von Assembler aus durch- fü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äte- und 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ön- nen 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än- ge 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äte- und 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 öff- nen. 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 besit- zen). All diese Parameter entsprechen also dem BASIC-Befehl:
OPEN 1,8,2,"TEST,S,R"
Möchten wir diesen Befehl nun in Maschi- nensprache umsetzen, so schreiben wir folgendes Assembler-Programm. Hierbei gehe ich davon aus, daß wir den Filena- men "TEST,S,R" als ASCII-Code bei Adres- se $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 File- namen enthalten ist, weiß die Floppy auch gleich schon, daß wir ein sequen- tielles File lesen möchten. Als Nächstes müssen wir das zuvor spezi- fizierte File öffnen. Dies geschieht über die oben schon erwähnte Betriebssy- stemroutine "OPEN". Sie benötigt keine Parameter und wird direkt aufgerufen: JSR $FFC0 ;File mit zuvor gesetzem Na- men und Parametern öffnen Nun ist das File "TEST" geöffnet. Da aber auch jeder Datenfluß einmal ein Ende hat, muß unser File irgendwann ein- mal wieder geschlossen werden. Dies ge- schieht, 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ßen- den 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 sinn- los wäre ein File zu öffnen), so müssen Sie weiterhin fünf Betriebssystem- Routinen kennen, die Ihnen dies ermögli- chen. Zunächst wären da "CHKIN" und "CHKOUT". Sie dienen der Umleitung der Standard-Ein/Ausgabe auf den entspre- chenden Filekanal. Wenn Sie aus einem File lesen wollen, so sollten Sie nach dem Üffnen desselben die Routine CHKIN aufrufen. Mit ihr teilen Sie dem Be- triebssystem mit, daß Sie, wenn Sie jetzt etwas einlesen, die Daten aus die- sem 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 entspre- chenden Files im X-Register. Die Ein- sprungadresse 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 Stan- dard-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 Cursor- position gedruckt und der Cursor um eine Stelle weiterbewegt (wie Sie bemerken ist das Standard-Eingabegerät die Tasta- tur, das Standard-Ausgabegerät der Bild- schirm). 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 been- det, so ist es notwendig, die fünfte Routine zu benutzen, von der ich oben sprach. Sie heißt "CLRCH" und wird ver- wendet, um die Standard-Ein/Ausgabe wie- der 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 Fehlererken- nung. 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 Ar- beit einer der Floppy-Betriebssystem- Routinen ein Fehler auf, so wird das an das aufrufende Programm durch ein ge- setztes Carry-Bit zurückgemeldet. So können Sie also nach jeder der Routinen durch eine einfache "BCS"-Verzweigung ("Branch on Carry Set") auf eine Fehler- behandlungsroutine 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 ent- sprechenden 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 64er 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 erin- nern sich: man darf maximal 3 sequen- tielle, oder 1 relatives und 1 se- quentielles 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 Routi- nen auch zum Anprechen von anderen Gerä- ten verwenden kann (z.B. Drucker, Data- sette, etc.). Üffnen Sie z.B. einen Ka- nal 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 Status- speicherstelle zur Verfügung. Sie ist absolut identisch mit der Variablen "ST" von BASIC. Fragt ein BASIC-Programm die- se Variable ab, so greift der BASIC- Interpreter auf eben diese Speicherstel- le 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äh- rend der Arbeit mit der Floppy ein Feh- ler aufgetreten ist, müssen wir sie le- diglich einlesen und analysieren. Ein Fehler wird hierbei durch das gesetzt sein eines oder mehrerer Bits dieser Speicherstelle gemeldet. Hier eine Bele- gung 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 ge- setzt sein des 6. Bits von ST feststel- len können. Dann nämlich hat ST den Wert 64, den wir bei BASIC-Abfragen auch im- mer 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 Fi- les 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 an- zuschauen 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 Low- und High-Byte in X- und 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 "Wri- teFile"-Routine vor. Sie speichert Daten in einem beliebigen Speicherbereich auf Diskette und wird mit denselben Vorraus- setzungen aufgerufen wie "ReadFile": Name bei $0334, Namenslänge im Akku und Startadresse des zu speichernden Be- reichs in X-/Y-Register. Zusätzlich müs- sen 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 sen- den
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 opti- mal geeignet, da sie die Ladeadresse ja mitladen und mitspeichern. Sie können sie aber auch so modifizie- ren, daß diese Adresse mitberücksichtigt wird. Hierzu müssen Sie "ReadFile" ledi- glich so umprogrammieren, daß sie die ersten beiden Bytes liest und als An- fangsadresse nimmt, bzw. daß "WriteFile" die gegebene Anfangsadresse, die in X- und Y-Register übergeben wird, vorher an das zu speichernde File sendet. Noch einfacher geht das aber mit den Be- triebssystemroutinen 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 Be- triebssystem ebenso zwei Routinen zur Verfügung, mit denen wir komplette Files nachladen, bzw. speichern können. Der Aufruf ist hierbei sehr einfach. Zu- nächst einmal müssen wir unsere Files wieder mittels SETNAM und SETPAR spezi- fizieren. Danach werden die Prozessorre- gister einfach nur noch mit den entpre- chenden Parametern gefüttert und die benötigte Routine wird aufgerufen. Kommen wir zuerst zur LOAD-Routine. Sie liegt bei $FFD5 und hat folgende Aufruf- parameter: 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 Verifizie- ren eines Files verwendet werden (BA- SIC-Befehl "VERIFY"). Hierbei wird haar- genau so verfahren wie beim Laden, je- doch mit dem Unterschied, daß das File nicht in den Speicher geschrieben, son- dern 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 X- und 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 ange- ben, 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 X- und 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 vorgegebe- ne Adresse lädt. Sie verlangt als Auf- rufparameter 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 Anga- be 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 funktio- niert, 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 be- stimmte 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 Be- triebssystems. Sie wird ähnlich aufgeru- fen. Zunächst müssen wieder mittels SET- NAM und SETPAR der Name und die Parame- ter des Files gesetzt werden. Anschlie- ßend kann die SAVE-Rotine aufgerufen werden. Sie benötigt jedoch ein paar mehr Parameter, nämlich Start- und End- adresse. Da hierfür die drei Prozessor- register nicht ausreichen, wurde folgen- der Weg gegangen: Zunächst legen Sie die Staradresse des zu speichernden Bereichs in Lo'Hi Darstellung in zwei aufeinan- derfolgenden 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 Speicherstel- le in der Sie das Low-Bytes der Start- adresse abgelegt haben. In unserem Bei- spiel also $FB. Die Endadresse des zu speichernden Bereichs legen Sie in X- und Y-Register ab und rufen anschließend die SAVE-Routine auf. Ihe Einsprung- adresse 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 X- und 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 hier- bei in $FD/$FE zwischengespeichert, um den Aufruf korrekt durchführen zu kön- nen. Diese beiden Speicherstellen sind übrigens ebenfalls vom Betriebssystem unbenutzt. Die beiden Programmbeispiele zum Laden und Speichern mittels LOAD und SAVE fin- den Sie übrigens ebenfalls in dem File "FK.I/O.S" auf dieser MD. Ich verab- schiede mich nun wieder einmal von Ih- nen, und wünsche Frohe Weihnachten und ein schönes neues Jahr, in dem wir dann tiefer in die Assemblerprogrammierung der Floppy einsteigen und einige Pro- grammbeispiele 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 Floppypro- grammierung in Assembler einsteigen. Im letzten Monat hatten wir ja schon ange- sprochen, 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 anspre- chen kann. DIE ALTE METHODE In der letzten Ausgabe des Floppy-Kurses hatten wir gelernt, wie man ein File öffnet, es zur Datenübertragung bereit- macht, Daten an es sendet und von ihm empfängt, und es anschließend wieder schließt. Die dort behandelte Arbeits- weise 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 Me- thode gingen wir immer folgendermaßen vor: 1 ) "SETNAM" mit den Parametern für den Filenamen aufrufen. 2 ) "SETPAR" mit den Datenkanalparame- tern aufrufen. 3 ) Mit "OPEN" das File öffnen 4a) War das File eine Ausgabedatei, so mussten wir "CKOUT" aufrufen. 4b) War das File eine Eingabedatei, so mussten wir "CHKIN" aufrufen. 5a) War das File eine Ausgabedatei, so wurden die Daten mit Hilfe von "B- SOUT" geschrieben. 5b) 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ückge- stellt. 7 ) Das File wurde mit "CLOSE" geschlos- sen. Wie gesagt, ist diese Methode die ein- fachste, wenn Sie ein einziges File le- diglich Lesen oder Schreiben wollen. Wenn Sie nun aber mehrere Files offen- halten und ansprechen wollen, oder aber auf einem Kanal Lesen und Schreiben wol- len (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 ge- schieht 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 Bild- schirm 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 wer- den.
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, Ta- statur, etc.) ansprechen können, weshalb die Restriktionen dort etwas umständli- cher 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 er- sten Routinen erst einmal entschieden werden muß, welche effektiven Ein-/Aus- gaberoutinen 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 mei- ne 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 Kommu- nikationsschnittstelle zwischen C64 und Floppy dient, werden auf Ihm Daten ent- weder vom Rechner zur Floppy oder umge- kehrt hin- und herbewegt. Die Verarbei- tung dieser Daten ist dann dem jeweili- gen Gegengerät überlassen, daß die Daten empfangen hat. Noch dazu kann es vorkom- men, 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 festge- legt werden welches der Geräte nun mit welchem (im Normalfall dem 64er) kommu- nizieren soll. Hierzu dienen nun insge- samt sechs Betriebssystemroutinen. Als erstes müssen wir wieder einmal ein File auf die gewohnte Art und Weise öff- nen. Zunächst übergeben wir die Filena- menparameter mit Hilfe von "SETNAM" (Ak- ku=Länge des Namens, X/Y=Zeiger auf Na- mensstring). Anschließend muß "SETPAR" aufgerufen werden. Hierbei kommen File- nummer und Geräteadresse, wie gewohnt, in Akku und X-Register. Im Y-Register wird die Sekundäradresse abgelegt. Je- doch müssen wir hier, anders als sonst, die gewünschte Sekundäradresse plus $60 (=dez. 96) übergeben. Dies hat Floppyin- terne Gründe. Möchten wir also den Be- fehlskanal ansprechen, so muß die Se- kundäradresse $6F (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 ge- wünschten Gerätes im Akku ablegen. Als nächstes muß dem Gerät an der ande- ren Seite mitgeteilt werden, welcher seiner offenen Kanäle senden oder emp- fangen 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 spe- ziellen IEC-Ein-/Ausgaberoutinen, die ich oben schon ansprach. Sie können sie genauso wie "BASIN" und "BASOUT" benut- zen. 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 wer- deb muß. Haben Sie die Ein- oder Ausgabe beendet, so müssen Sie das entsprechende Emp- fangs-, bzw. Sendegerät wieder in den Wartezustand zurückbringen. Dies ge- schieht über die Routinen "UNTALK" und "UNLIST". Die erstere müssen Sie nach dem Empfang, die letztere nach dem Sen- den 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 File- kanal und teilen der Floppy mit, daß sie Daten empfangen soll ("LISTEN" und "SECLST" aufrufen). Nun senden Sie mit- tels "IECOUT" den Befehlsstring. An- schließ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 Be- fehlskanal 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 ans- prechen, müssen Sie einfach den entspre- chenden Öbertragungsmodus festlegen und seine Sekundäradresse an die Floppy schicken. Denken Sie immer daran, daß Sie nach jeder Lese- oder Schreibopera- tion 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 Programm- beispielen, 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üs- sen wir lediglich den Aufruf von "CH- KIN/CKOUT" mit dem von "TALK/SECTALK" oder "LISTEN/SECLIST" ersetzen. Der Au- fruf von "CLRCH" am Ende der Öbertragung muß mit einem "UNTALK" bzw. "UNLIST" ersetzt werden. Desweiteren muß den Se- kundä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 mitei- nander 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 er- gibt, ist, daß Sie nun problemlos, wäh- rend des Arbeitens mit einem File, Sta- tusmeldungen 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. Umge- kehrt wird bei BASIN von der Tastatur, und nicht etwa aus dem offenen File ge- lesen. Als Beispiel für eine solche Anwendung möchte Ich Ihnen das Auslesen des Feh- lerkanals 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 gra- fikschen Anzeige dekodieren. Dies ist ein hervorragendes Beispiel um die Be- nutzung der neuen I/O-Routinen zu de- monstrieren 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 re- serviert. 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 vier- ten 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 bedeu- tet, 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 Befehlska- nal 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 Be- fehlskanal und einen Pufferkanal mit der Sekundäradresse 2 geöffnet (bitte denken Sie daran, daß beim Üffnen und beim Be- nutzen 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 sen- den. 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 Quell- text des Hauptprogramms. Die nun folgen- den Zeilen versetzen die Floppy in den Sendestatus und bereiten die Bild- schirmausgabe der Tracks vor. Hierbei wird ein Zeiger auf den Bildschirmspei- cher 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 Schleifen- durchlauf wird der Zeiger wieder auf den Startwert+1 (nächste Spalte) zurückge- setzt:
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 überle- sen, da es ja keine Blockbelegung an- sich, 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 Bild- schirm aus. Hierbei wird ein "o" ge- schrieben, wenn der Block belegt ist, und ein ".", wenn er unbelegt ist. Die Routine "BYTEOUT2" ist im Prinzip die- selbe, wie BYTEOUT1. Der einzige Unter- schied 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 ge- lesen 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 Quellco- des. Fahren wir nun weiter im Hauptprogramm. Nachdem alle BAM-Bytes gelesen und aus- gegeben wurden, können wir Befehls- und Pufferkanal wieder schließen. Vorher muß die Floppy allerdings wieder in den War- tezustand 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 fin- den 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 die- sen 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 wol- len.
(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öch- te diesen Kurs nun mit einem anspruchs- vollen 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 Bei- spiel 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 umzu- kopieren, daß jede Diskette optimale Platzausnutzung aufweisen soll. Am Be- sten so, daß restlos jeder Block dieser Diskette beschrieben ist. Beim Zusam- menstellen bleiben dann aber noch noch 17 Blocks übrig - Mist, kein Platz mehr für das letzte, 30 Blocks lange Pro- gramm! 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 (Är- gerlich - 17 Blocks verschenkt). 3) Sie packen das letzte Programm (Wie- der Gähn - dauert auch lange und aus- serdem hat das File hinterher be- stimmt genau 18 Blocks, so daß es doch nicht passt). 4) Sie benutzen das Programm "USEDIR", das wir uns in diesem Kursteil erar- beiten 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 Direc- tory. "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 Fileein- tragsblocks (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 ent- spricht. Da eine normale Diskette aber so gut wie nie so viele Files beher- bergt, liegen eine Menge dieser Direc- toryblocks brach. Gerade bei einer rand- vollen 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äch- lich 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 er- gibt 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ügba- ren Blöcke sein und sollte vom Typ "PRG" sein. Daraus ergeben sich folgende Auf- gaben für unser Programm: 1) Das zu installierende File einlesen und benötigte Anzahl von Blöcken er- mitteln. 2) Anzahl der freien Blöcke der Zieldis- kette 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 abbre- chen. 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 vor- letzten 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 Track- und Sektornummer des er- sten freien Directoryblocks abgeän- dert. 9) Jetzt müssen wir nur noch die fehlen- den Directoryblocks schreiben und den letzten Directoryblock auf den ersten Track/Sektor des geschriebenen Files zeigen lassen - Fertig ist die In- stallation!
DIE BENÜTIGTEN PROGRAMMTEILE
Kommen wir nun also zu unserem Programm. Dabei möchte ich Ihnen zunächst einmal eine Liste der darin enthaltenen Routi- nen geben. Hierbei habe ich in zwei Ar- ten unterschieden: in Steuer- und IO- Routinen. Diese beiden Namen treffen Ihre Bedeutung zwar nicht voll und ganz, jedoch musste irgendwo eine Grenze gezo- gen werden. Die erste Art, die Steuerroutinen also, sind alles Funktionen, die entweder nichts direkt mit der Floppy zu tun ha- ben, und somit für uns uninteressant sind, oder aber Funktionen, die wir in vorangegangenen Kursteilen schon bespro- chen hatten, und somit nicht noch einer zweiten Dokumentation bedürfen. All die- se Routinen werden nur mit ihrem Namen, ihrer Funktion und ihren Parametern auf- gefü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 Be- schreibung benötigen und im Laufe dieses Kursteils alle nocheinmal genauer doku- mentiert sind. Desweiteren werden diverse Speicherzel- len als Zwischenspeicher für verschie- dentliche 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-$03FF, dem Kasettenpuffer al- so, der bei Floppybenutzung ja unbe- nutzt, und deshalb verwendbar ist. Hier nun also die besprochenen Beschreibun- gen:
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 ei- nen maximal 16 Zeichen langen String von der Tastatur ein und wird zum Ein- geben des Filenamens benutzt. Dieser String liegt nach dem Rücksprung ab der Adresse NAME. Die Länge des File- namens steht in der Speicherzelle MEM0. Am Ende des Namensstring wird ein Nullbyte als Endmarkierung an- gefü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 Be- fehlskanal mit der logischen Filenum- mer 1. Beim Üffnen wird die Diskette automatisch initialisiert (Floppybe- fehl "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 abge- legt ist, an die Adresse FILEMEM. Sie benutzt die Zeropageadressen $F9/$FA als Lesezeiger. Hier steht nach dem Rücksprung gleichzeitig auch die Enda- dresse 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 be- stehen, wird diese Routine benutzt, um einen Befehl, dessen String an der Adresse in X- und Y-Register steht und dessen abschließende Nummer im Akku übergeben wurde, an die Floppy zu sen- den. * 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, wer- den 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 Zieldisket- te und legt sie in DIRFREE ab. Deswei- teren wird die Summe von DSKFREE und DIRFREE berechnet und in ALLFREE abge- legt. * GETNEED: Berechnet die benötigte An- zahl Blöcke des zuvor eingelesenen Files und legt Sie bei "NEED" ab. * CHGENT: Diese Routine sucht den File- namen in NAME aus dem Directory he- raus, liest die Nummern des ersten Tracks und Sektors dieses Files ein, und überschreibt diese beiden Informa- tionen 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 Track- und Sek- tornummer 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 Steuerrouti- ne 'MAIN' unseres Programms, in der das oben aufgeführte Aufgabenschema in Pro- grammcode umgesetzt ist. Anschließend wollen wir uns mit den benutzten Unter- routinen beschäftigen. 1) MAIN Diese Routine steuert das gesamte Pro- gramm. Zunächst einmal wollen wir die Bildschirmfarben setzen, den Titeltext ausgeben und den Namen des zu installie- renden Files ermitteln. Ist dieser Name gleich dem String X", so soll das Pro- gramm 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. Hierauf- hin wird der Benutzer dazu aufgefordert, die Zieldiskette einzulegen, von der wir dann die Anzahl der freien Dir- und Diskblocks ermitteln. Gleichzeitig wird dann auch noch das Dummyfile erzeugt. Am Ende werden alle ermittelten Werte mit- tels der Routine STATOUT auf dem Bild- schirm 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ür- zer 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 heraus- suchen würden. Es geht aber noch viel einfacher: das DOS der Floppy beschreibt die Sektoren einer Diskette nämlich mei- stens in einer ganz bestimmten Reihen- folge. Das ist bei den normalen Daten- blocks nicht unbedingt gesichert, da bei einer nahezu vollen Diskette diese Rei- henfolge nicht mehr eingehalten werden kann. Beim Directorytrack 18 können wir uns jedoch mit 100%-iger Sicherheit da- rauf verlassen, daß sie immer eingehal- ten wird. So gibt es nämlich eine ganz bestimmte Reihenfolge, in der die Direc- torytracks geschrieben werden. Wir müs- sen 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" abge- legt, 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 Endmar- kierung der Liste, die später beim Be- schreiben der Dirblocks von Bedeutung ist. Nun müssen wir erst einmal die Sek- tornummer 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 Zieldis- kette 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 schrei- benden Files berechnen. Da die ersten beiden Bytes eines Datenblocks immer als Zeiger auf den nächsten Datenblock die- nen, müssen wir also den Wert DIR- FREE*254 zur FILEMEM-Adresse hinzuaddie- ren, um unsere Anfangsadresse zu erhal- ten:
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ängli- chen Bytes in den Dirblocks unterzubrin- gen:
jsr openio ;Kanäle öffnen jsr chgent ;Eintrag suchen und ändern. jsr wblocks ;DirBlocks schreiben. jsr closeio ;Kanäle schließen
lda #<(text4) ;"File install- ldy #>(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 Zei- sty $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 unter- scheidet 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 umge- wandelt und an den Befehlsstring in X/Y angehängt wird. Der gesamte Befehl wird dabei bei COMBUFF abgelegt und dann mit- tels 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 notwen- dig, um sicherzustellen, daß auch die richtige Anzahl an Dirblocks als 'be- legt' gekennzeichnet ist. Sollten näm- lich 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äm- lich einen neuen Dirblock hinzufügen, der ab dann nicht mehr zur Datenspeiche- rung benutzt werden kann. Damit es dabei keine Konfrontationen gibt, wird das File also sporadisch schon einmal ange- legt 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 zei- gen, 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 Endmar- kierung 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 Filena- menspuffers. Für den Namen werden 17 Bytes reserviert, da ja maximal 16 Zei- chen 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 subtra- hiert, 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 ge- braucht. 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 wer- den, 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 Feststel- len der freien Blocks der Diskette. Die- ser Wert ist normalerweise nicht direkt aus dem DiskHeaderBlock zu erfahren. Die einzige Möglichkeit wäre, die Blockbele- gungen 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 $02FA (Lo) und $02FC (Hi) des Floppyspeichers abgelegt. Was liegt also näher, als diesen Wert di- rekt, ü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 Source- text abgelegt werden (3 Zeichen ab Adresse $02FA 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 Direc- tory 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 DiskHea- derBlocks. Wir müssen also den Block 18/0 in den Puffer lesen, und den Puf- ferzeiger 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 ledi- glich 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 Sourcecode- texte 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 durch- sucht die Directoryblocks nach unserem Fileeintrag, liest aus ihm den Track/Sektor des ersten 'normalen' Da- tenblocks aus und legt ihn in den Adres- sen FTRACK und FSEKTOR ab. Desweiteren wird hier dann Track und Sektor des er- sten, 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 An- fangspositionen der einzelnen Fi- leeinträge eines Directoryblocks abge- legt 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ückgeschrie- ben:
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 feh- lendem 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 Fi- leanfang, bis zu dem Punkt, an dem das 'normale' File beginnt. Gleichzeitig werden diese Dirblocks mit Hilfe des Block-Allocate-Befehls als 'belegt' ge- kennzeichnet:
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 "u2 2 0 18" ;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 Ge- schmack an der Programmierung der Floppy gefunden haben, halten wir für die näch- ste Ausgabe einen ganz besonderen Lek- kerbissen parat: ab dann gibt es nämlich einen aufbauenden Floppy-Kurs für Pro- fis, in dem die Floppy direkt program- miert wird. Öbrigens: Sie sollten es nicht auf sich sitzen lassen, daß unser Beispielpro- gramm 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).