Das Betriebssystem eines Computers kann, unabhängig von Größe, Art, Alter und Aufbau des Rechners, als die Schnittstelle zwischen der Hardware, also der Elektronik, und der Anwendersoftware, also zum Beispiel Programmiersprachen, Text- und Datenverarbeitungsprogrammen und anderer Software bezeichnet werden. Es ist unter anderem dafür verantwortlich, dem Anwender (beziehungsweise Programmierer) definierte Softwarehilfsmittel zur Verfügung zu stellen, mit denen er zum Beispiel ein einzelnes Zeichen auf den Bildschirm oder ein anderes Peripheriegerät bringen oder von ihm holen kann. (Peripherie = griech. Umfeld, also alles 'Drumherum'. In der Computerbranche Ausdruck für Ein-/Ausgabegeräte wie zum Beispiel Terminals oder Drucker).
Außerdem ist das Betriebssystem diejenige Software, die das System nach dem ersten Einschalten in einen Grundzustand bringt. Diesen Vorgang nennt man allgemein Power-Up, was aussagen soll, daß hier die Versorgungsspannung das erste Mal seit dem letzten Ausschalten angelegt wird. Der dabei von dem Computersystem durchlaufene Zyklus wird als PowerUp-Reset bezeichnet.
Während dieses Power-Up-Resets werden sämtliche vom Betriebssystem benötigten Speicherstellen sowie die Ein-/Ausgabebausteine initialisiert.
Gerade auf diesen letzten Punkt, die Ein-/Ausgabebausteine, wird bei heutigen Betriebssystemen gesteigerte Aufmerksamkeit gerichtet. So war es noch vor wenigen Jahren üblich, jeden Datentransfer zum Beispiel vom Speicher auf einen Magnetträger wie eine Floppy-Disk, direkt von der CPU aus zu gestalten und somit Rechenzeit damit zu 'verbraten'. Heute gibt es genügend sogenannte Controller, die nach kurzem Anstoßen durch die CPU den Datentransfer und die Gerätekontrolle alleine übernehmen, so daß das eigentliche System nach Möglichkeit nicht weiter behindert wird.
So kann ein Controller damit beschäftigt sein, auf Anforderung eines zweiten, anderen Controllers, ein (das nächste) Datenelement aus dem Speicher auszulesen und eben dieses Datum an den zweiten Controller zu übergeben.
Dieser zweite Controller könnte dann die Aufgabe haben, dieses Zeichen nach ganz bestimmter Vorgehensweise auf die Floppy-Disk zu schreiben und diesen Vorgang auf Korrektheit hin zu überprüfen.
Solche Vorgehensweisen werden im allgemeinen DMA-Prozesse genannt, was Direkt-Memory-Access, direkter Speicherzugriff ohne die Benutzung der CPU bedeutet.
Der DMA hat den schon oben erwähnten Vorteil, daß bei gut ausgeführtem DMA-Konzept die eigentliche Rechenzeit nicht weiter beeinflußt wird, hat jedoch die Nachteile, daß die Controller aufgrund der beinhalteten Intelligenz nicht eben billig sind. Außerdem ist ein System, bei dem alle Aufgaben von nur einem Baustein bearbeitet werden, wesentlich leichter zu überwachen als eines, bei dem sich diverse, parallel arbeitende, 'Intelligenzbestien' gegenseitig beeinflussen und Fehler 'zuschieben'. Es gilt also leider auch in diesem Computerbereich der Satz, daß "viele Köche den Brei verderben".
Es gibt jedoch noch eine dritte Möglichkeit, ein System aufzubauen. Dabei werden, ähnlich dem DMA-Konzept, Intelligenzen 'verteilt'. Es existieren also auch intelligente Geräte, wie zum Beispiel eine Diskettenstation. Der Unterschied zum DMA-Betrieb besteht jedoch darin, daß die Diskette nicht direkt im Speicher des Hauptsystems 'herumfummelt', sondern nur über eine sogenannte Interruptleitung dem Hauptoder auch Hostsystem mitteilt, daß es neue Daten braucht oder abgeben will. Es ist dann alleinige Aufgabe des Hostsystems, herauszufinden, von wem und auf welche Weise der Interrupt behandelt werden soll.
Im Normalfall wird solch ein Interrupt zuerst die CPU von der Meldung unterrichten. Dies geschieht dadurch, daß die CPU das eigentlich gerade bearbeitete Programm unterbricht, um einen sogenannten Interrupt-Handler aufzurufen. In diesem Interrupt-Handler, der meist Teil des Betriebssystems ist, stehen dann die, beim Auftreten eben dieser Bedingung auszuführenden, Kommandos. Damit wird dann entweder ein Baustein programmiert, der die beim unterbrechenden Gerät anliegenden
Daten einliest und verarbeitet (beziehungsweise an ein anforderndes Gerät übergibt) oder die CPU übernimmt selbst die Datenverarbeitung, um danach zu dem unterbrochenen Benutzerprogramm zurückzukehren.
Bei annähernd allen Geräten die es heutzutage verdienen, Computer genannt zu werden, treffen Sie Mischformen der drei oben angegebenen Konzepte an. Das gilt sowohl für den Hobbycomputermarkt als auch für die wesentlich teureren Bürogeräte.
Jeder Entwickler hat seine ganz perönlichen Einstellungen zu bestimmten Systemkonzepten, Vorlieben und Abneigungen zu einzelnen Bausteinen (oder Bausteinherstellern!), so daß man nicht von 'dem Computerkonzept' sprechen kann und dies wohl auch nie können wird.
Wie nach diesen einleitenden Erklärungen nicht anders zu erwarten, ist also auch das Atari 600XL/800XL-System eine Mischform aller drei Konzepte.
Als Bespiel für das reine CPU-Konzept kann die Tastaturabfrage dienen. Der Tastenkode wird zwar über einen Baustein (POKEY) eingelesen, dieser dient aber lediglich in diesem Falle als Pufferbaustein. Die eigentliche Dekodierarbeit, welcher Tastenkode zu welchem ATASCII-Zeichen gehört, wird von der CPU selbst übernommen.
Das DMA-Konzept ist, wie den entsprechenden Kapiteln zu entnehmen, in technisch ausgesprochen gut ausgereifter Weise beim ANTIC und GTIA enthalten. Der ANTIC versorgt unter anderem als intelligenter DMA-Controller den GTIA mit Daten.
Aber auch die dritte Systemart ist vorhanden: Die Atari 400/800- und 600XL/800XL Modelle verfügen über ein hohes Maß an Interrupts, von denen ein nicht unerheblicher Teil für die Datenübermittlung von oder zu peripheren Geräten über die serielle Schnittstelle und den POKEY-Baustein verwendet wird.
Dabei kann man grundsätzlich sagen, daß das System für einen Computer dieser Preisklasse über ein erstaunliches Niveau verfügt. Nicht unbedingt wegen der grundlegenden Konzeption; man findet sie in ähnlicher Form sicher auch bei Konkurrenzprodukten. Es sind jedoch zwei andere, für den reinen Benutzer nicht unmittelbar zu entdeckende Vorteile, die nur dann auffallen, wenn sie nicht vorhanden sind:
Zum einen ist das Gesamtsystem abgerundet und die einzelnen Komponenten jeweils sauber aufeinander abgestimmt. Dies und anderes stellt sicher, daß das System nach Möglichkeit jeden erdenklichen Fehler beim gleichzeiten Ausführen unterschiedlicher Aktivitäten 'abfängt' und korrigiert. Es gibt genug Computer, die pfiffige Grundelemente beinhalten. Kommt jedoch solch ein Element einmal ein wenig 'aus dem Tritt', gibt es keine Kontrollstuktur, die den Fehler auffängt.
Ein konkretes Beispiel hierzu wäre in den seriellen Schnittstellenroutinen zu finden. Vielleicht ist Ihnen schon einmal aufgefallen, daß der Atari beim Lesen oder Schreiben von/auf Diskette manchmal 'einschläft', also einige Sekunden lang nichts tut, um danach, wie von Geisterhand, völlig korrekt weiterzuarbeiten. Dieser Effekt tritt besonders häufig dann auf, wenn man Diskettenstationen fremder Hersteller verwendet.
Die Ursache des Fehlers ist in der Art der Kommunikation zwischen Computer und Diskettenstation zu suchen. Der Atari wartet nach dem Senden eines Kommandos auf eine Antwort des angesprochenen Gerätes. Kommt die Antwort jedoch etwas zu schnell für den Atari, weil dieser zum Beispiel gerade mit der Bearbeitung von Interrupts beschäftigt war, wertet er diese Antwort als fehlerhaft und wartet auf eine korrekte Meldung. Diese kann natürlich nicht kommen, weil die Diskettenstation der berechtigten Ansicht ist, schon geantwortet zu haben und vielleicht sogar nun selbst auf Daten wartet.
Dieser Konflikt, bei dem also zwei aufeinander wartende Geräte sich gegenseitig blockieren, wird Deadlock genannt und ist eine bei Betriebssystemtechnikern gefürchtete Erscheinung immer komplexer werdender Programme und führt bei
manchen Hobbycomputern zum 'Absturz', da der Konflikt nicht erkannt wird. Nicht jedoch beim 'Nobel-Betriebssystem' von Atari: Hier sorgt ein sogenannter Timeout dafür, daß die Übertragung weitergeht. Die CPU bekommt hierdurch mitgeteilt, daß die Übertragung offensichtlich 'hängt' und versucht sie ein zweites Mal.
Der zweite Vorteil des Atari-Betriebssystems gegenüber denen anderer Hersteller ist schlichtweg der, daß darin Ordnung herrscht.
Das bedeutet, daß das ganze System in kleine, überschaubare Routinen aufgeteilt ist, von denen normalerweise ganz klar definiert werden kann, was sie als Ausgabe bei welcher Eingabe liefern. Es ist, in den hier im Buch beschriebenen 12 KByte, mit einer Ausnahme davon abgesehen worden, 'Spaghetti-Kode' zu schreiben, also endlose Routinen, in denen alles und nichts getan wird. Die eine Ausnahme läßt sich ebenfalls begründen; es handelt sich dabei um eine Interruptroutine und dort wollte Atari einfach Bearbeitungszeit sparen.
Mit wenigen Ausnahmen kann man sagen, daß dieses Betriebssystem nicht von irgendwelchen Hackern geschrieben wurde, sondern von Ingenieuren, die Ihre Arbeit hervorragend verstehen.
So sind zum Beispiel die Ein-Ausgaberoutinen nicht für jeden Baustein anders, sondern es gibt sogenannte Input-OutputControl-Blocks (IOCBs), die die Art des Gerätes, das Kommando und alle damit verbundenen Werte definieren. Die Ausgabe findet nun dadurch statt, daß an die Ausgaberoutine nur noch die Nummer des IOCB übergeben zu werden braucht, und schon 'weiß' der Computer, wie er welches Gerät zu behandeln hat und welche Ein- bzw. Ausgabeoperation ausgeführt werden soll.
Diese Ordnung ist auch Grundlage dafür, daß Atari nicht schon zu Laufzeiten der alten 400/800-Serie diverse Betriebssysteme hervorbrachte, die dann nicht zueinander gepaßt hätten.
Atari hat zwar eine neue Version herausgebracht, in der einige Fehler verbessert wurden, es muß jedoch klar gesagt werden, daß Software, die für den 400/800 geschrieben wurde, voll auf den neuen Systemen lauffähig ist, wenn nur die, den autorisierten Händlern und Softwareproduzenten bekannten und von denen vielfach publizierten, Originaleinsprungadressen verwendet wurden.
Es dürfte auf der Hand liegen, daß ein Pogramm, in dem 'raffiniert getrickst' worden ist, natürlich an eine bestimmte Betriebssystemversion gebunden ist. Ein vernünftig, also ingenieurmäßig, arbeitender Programmierer hat dagegen beim Systemwechsel bei Atari keinerlei Probleme.
Dazu trägt auch noch bei, daß bei Atari dieses Betriebssystem wirklich nur eben das Betriebssystem ist, und nicht zum Beispiel Verquickungen mit einer eingebauten Programmiersprache, wie etwa BASIC, programmiert sind. Da kann es in anderen Systemen zum Beispiel vorkommen, daß in einem BASICInterpreter Routinen für Peripherieschnittstellen definiert sind. Wird also das BASIC abgeschaltet, darf man sich diese Schnittstelle neu programmieren.
Alle diese Mankos sind hier beim Atari 600XL/800XL weitestgehend vermieden worden. Zwar ist im Rechner ein MathematikROM fest eingebaut, es wird jedoch nicht (wirklich!) vom Betriebssystem benutzt.
Die Einschränkung 'wirklich' muß gemacht werden, da in der normalen Systemkonfiguration bei bestimmten Ein-/Ausgabeoperationen plötzlich bestimmte Stellen in diesem ROM angesprochen, beziehungsweise direkt angesprungen werden. Das heißt jedoch nicht, daß dort gerechnet werden soll, sondern daß dieses ROM einmal von irgendwelcher Periherie abgeschaltet und der dann freigewordene Platz mit neuem Programmspeicher belegt werden soll. In diesem Adreßbereich könnten dann zusätzliche Treiberprogramme liegen.
Welcher Art diese Peripherie sein soll, läßt sich nur erahnen, da uns außer der 64 KByte-RAM-Karte beim 600er keine
Geräte bekannt sind, die auf dem hinten anliegenden Systembus angeschlossen würden. Nur in diesem Fall nämlich könnte das Peripheriegerät den im Mathematik-ROM liegenden Adreßbereich füllen.
Ganz allgemein kann aus der Verbindung dieses Betriebssystems mit der Kenntnis über die Bus-Signale und der Tastaturdekodierung gesagt werden, daß es eigentlich gar kein Atari 600XL/800XL-Betriebssystem gibt. Es handelt sich in jedem Fall um ein modifiziertes Betriebssystem von dem leider bei uns (noch ?) nicht ausgelieferten 1200XL !
Dies ist daran zu erkennen, daß das Betriebssystem des 600XL/800XL Tastenabfragen nach Funktionstaste 1 bis 3 enthält, diese Tasten jedoch nicht bei diesem Gerät vorgesehen sind. Der 1200XL dagegen besitzt vier Funktionstasten F1 bis F4. An diesen und wenigen anderen Details bleibt es dem Fachmann nicht unentdeckt, daß dieses Betriebssystem eigentlich kein 600XL/800XL-Programmpaket ist.
Im Unterschied zum alten 400/800-Betriebssystem sind einige Dinge erstaunlich und, um nach so viel positivem auch einmal negative Kritik anzubringen, merkwürdig.
So sind in dem System Routinen enthalten, die den völlig gleichen praktischen Wert haben wie die entsprechenden des alten 400/800-Betriebssystem, aber völlig anders aufgebaut sind und dabei nicht unbedingt besser, schneller oder einfacher. Auch sind einfach komplette Programmblöcke um nur wenige Byte verschoben worden, obwohl sie dort leicht hätten stehenbleiben können. Dies alles ist jedoch nur für diejenigen Programmierer ärgerlich, die sich nicht an die von der Firma Atari vorgegebenen Schnittstellen gehalten haben.
Es bleibt die Frage, ob der offensichtlich von Atari gewünschte 'Erziehungseffekt' überhaupt von den angesprochenen Personenkreisen bemerkt wird; wir glauben es (leider) nicht, da schon Mittel und Wege gefunden wurden, dem 600XL/800XL erfolgreich vorzutäuschen, er sei ein 400/800.
RESET UND INTERRUPTS
Beim Kaltstart, auch Power-Up-Reset genannt, werden sämtliche für den Betrieb benötigten Register und Hardwarebausteine des Atari initialisiert. Hierbei werden ebenfalls die Interrupts eingerichtet, die das System nach dem Kaltstart am Laufen halten.
Es gibt eine ganze Reihe unterschiedlicher Interruptsquellen im Atari 600XL/800XL. Als erste und wichtigste wäre der 50 Hz-Vertical-Blank-Interrupt zu nennen. Er gilt in diesem System als das Zeitnormal oder, anders formuliert, es wird nach ihm die (systeminterne) Uhr gestellt.
Beim Auftreten eines solchen Interrupts, der nur signalisieren soll, daß im Moment keine Bildinformationen gesendet werden, weil der Elektronenstrahl der Monitorbildröhre gerade von unten rechts nach oben links zurückwandert, werden nahezu alle im Hintergrund laufenden Prozesse bearbeitet, die nicht selbst eine Hardware-Unterbrechung auslösen können. Dazu gehören als wichtigste Prozesse die Verarbeitung der Schattenregister und der Timer.
Die Schattenregister haben, bei vom System zyklisch zu schreibenden Registern, die Aufgabe, die zu schreibenden Informationen zu sichern. Es hat bei vielen Registern keinen Sinn, vom Benutzerprogramm aus, einen Wert direkt in eben dieses Hardware-Register zu schreiben, da der entsprechende Baustein nach kurzer Zeit um ein Auffrischen der Information bittet. Ohne ein Schattenregister wüßte dann das System nicht, welchen Wert es übergeben soll. Hat man jedoch die gewünschte Information in ein Schattenregister, also eine ganz normale Speicherstelle geschrieben, kann das Betriebssystem im Vertical Blank Interrupt den Inhalt dieser RAMZelle jedesmal auslesen und ihn in das Hardwareregister übertragen.
Die Verwendung von Schatten-Leseregistern hat meist eine andere Bewandtnis: Viele Meß- und Statusregister erkennen das Auslesen des gemessenen Wertes gleichzeitig als Rücksetzbefehl an, beginnen also mit einer neuen Messung oder löschen einfach nur bestimmte Informationen, wie zum Beispiel Fehlererkennungsbits.
Ist man nun gerade dabei, einen Wert auszumessen, und liest den bis dahin gemessenen Wert, ohne zu wissen, ob die Messung eigentlich abgeschlossen ist, würde in den meisten Fällen ein Fehlergebnis entstehen. Hat man im Gegensatz dazu eine einfache Speicherstelle, bei der man davon ausgeht, daß das Betriebssystem diese schon richtig beschreiben wird, ergeben sich diese Probleme nicht.
Ein weiterer Effekt der Schatten-Leseregister ist der, daß Meßergebnisse gezielt verändert werden können. So können von einem Statusregister nacheinander bestimmte Bits abgefragt werden. Sind sie gesetzt, wird ein dazugehöriger Handler aufgerufen. Bearbeitet dieser Handler nun nicht nur das eine Statusbit, sondern auch gleich noch einige andere, so kann dieser Handler nach der Behandlung die 'fertigen' Bits in der RAM-Zelle zurücksetzen, was ihm im Hardwarestatusregister nicht möglich gewesen wäre.
Die Timer haben die Aufgabe, zeitabhängige Vorgänge innerhalb des Systems zu koordinieren.
Es existieren zwei strikt voneinander zu trennende Arten von Timern: Die eine Sorte sind die Hardwarezähler, die im POKEY enthalten sind. Ist einer der Zähler 1, 2 oder 4 durch die angelegten Takte auf den Wert Null zurückgefallen, kann vom POKEY ein Interrupt ausgelöst werden. Auf diese Art Timer wollen wir hier nicht weiter eingehen, da ihre Funktionsweise eingehend im Kapitel POKEY erläutert ist.
An dieser Stelle interessieren eigentlich nur die fünf anderen Softwaretimer TIMCOUNT1 bis TIMCOUNT5.
Das sind jeweils 16bit-Werte, die vom Benutzer auf, in den gegebenen 16bit-Rahmen, beliebige Werte zu setzen sind und die dann selbsttätig bei jedem Vertical Blank Interrupt decrementiert werden.
Wird nach dem Verringern festgestellt, daß TIMCOUNT1 oder TIMCOUNT2 den Wert Null erreicht hat, wird indirekt über den entsprechenden Sprungvektor TIMER1VKT oder TIMER2VKT die vom Anwender zu programmierende Timer-Interruptroutine angesprungen. Diese Routinen sind immer mit einem normalen RTS bei 'aufgeräumtem' Stack zu beenden. Das heißt, daß nicht noch irgendwelche lokalen Werte auf dem Stack liegen dürfen, wenn zurückgesprungen werden soll.
Hat dagegen einer der Timer TIMCOUNT3, TIMCOUNT4 oder TIMCOUNT5 den Wert Null erreicht, so wird keine Routine angesprungen, sondern jeweils nur das entsprechde 8bit-Flag TIMER3SIG, TIMER4SIG oder TIMER5SIG vom Wert $00 auf $ff gesetzt. Das Anwenderprogramm mag dann die Veränderung dieser Werte selbst bemerken. Es ist auch nicht immer unbedingt sinnvoll, für jeden Timer-Event, also jeden Zeitpunkt, an dem eine bestimmte Operation ausgeführt werden soll, eine automatisch aufzurufende Prozedur zu schreiben, da es sein kann, daß der entsprechende Timer mehrfach herunterzählen soll, bis das gewünschte Ergebnis vorliegt.
Als weitere Operation wird im Vertical Blank Interrupt die sogenannte 'verzögerte' (deferred) Vertical Blank InterruptRoutine aufgerufen. Da diese Routine im Normalfall nach einem Kaltstart nicht existiert, weist ihr Pointer VBLKDVKT auf die Interrupt-Ausgangsroutine EXITVBL. Diese Routine EXITVBL ist auch dann anzuspringen (nicht aufzurufen!), wenn eine solche verzögerte Unterbrechungsroutine existiert und beendet werden soll. EXITVBL stellt sicher, daß das ursprünglich unterbrochene Programm seine alten Registerwerte wiedererhält.
Wichtig zu erwähnen wäre, daß TIMCOUNT1 bei jedem Vertical Blank Interrupt bearbeitet wird, die restlichen Timer dagegen nur dann, wenn das Flag CRITICIO gelöscht ist. Dieses Flag soll verhindern, daß im vollen Betrieb bei sehr schnell folgenden Interrupts, also zum Beispiel beim Lesen von Floppy, der Rechner mit 'Kleinkram' wie Timerverarbeitung aufgehalten wird, und somit eventuell einen Interrupt nicht mehr rechtzeitig bearbeiten kann. Timer 1 wird deshalb immer benutzt, weil er die Aufgabe übertragen bekommen hat, eine mögliche Timeout-Situation zu erkennen. Dies kann er natürlich nur dann tun, wenn er regelmäßig bearbeitet wird.
Neben diesen regelmäßigen Unterbrechungen gibt es auch eine ganze Reihe asynchroner Interrupts, also solche, über deren zeitliches Auftreten nichts genaues gesagt werden kann. Dazu gehören zum Beispiel die Unterbrechungen der seriellen Übertragungsstrecke, die vom POKEY gesammelt und gewandelt werden. Diese und andere Interrupts besitzen ihre Vektoren ab der Adresse VPRECEDE.
Jede dort abgelegt Interruptroutine muß mit den Instruktionen
PLA
RTI
enden, um sauber zum unterbrochenen Progamm zurückzukehren.
Ein-/Ausgabe über Kontrollblöcke
Beim Atari-Computer soll die gesamte Ein- beziehungsweise Ausgabe über sogenannte Kontrollblöcke ablaufen.
Das heißt, es gibt nicht für jedes anzusprechende Gerät eine eigene Adresse, die sich der Benutzer merken muß, sondern es gibt eine Adresse, die den Großteil des Input und Output übernimmt. Dazu wird die Beschreibung des Ein-/Asgabegerätes in einem Input-Output-Control-Block abgelegt. Dort stehen der Name des Gerätes, die Bus-Nummer, Pufferadressen und Pufferlängen sowie Statusinformationen:
IOCB - Eintrag Bedeutung
------------------------------------------------------------
IOCBCHID IOCB-Channel-Identifikationsnummer. Sie
ist der Offset des Eintrages innerhalb
der Tabelle ab HATABS. Vorgegeben sind
schon die Einträge
$00 = 'P' Printer, Drucker
$03 = 'C' Cassette
$06 = 'E' Editor
$09 = 'S' Screen, Bildschirm
$0c = 'K' Keyboard, Tastatur
'D' Diskettenstation
'M' Modem
Die beiden letzten Einträge stehen nicht
initiell in HATABS. Die Diskette nicht,
weil sie nicht direkt über CIO sondern
über eigene Disk-Operationen läuft, und
das Modem deshalb nicht, weil es von
Atari kein in Deutschland zugelassenes
gibt. Die einzige Möglichkeit dazu wäre,
über die Interfacebox zu arbeiten.
IOCBDSKN Laufwerknummer. Die Diskette ist das
einzige am Atari anzuschließende Peri-
pheriegerät, von deren Sorte mehrere
gleichzeitig am Bus anliegen können.
Deshalb reicht die Information 'D' bei
der Datenübertragung nicht aus und wird
um diese Laufwerknummer erweitert. Die
Laufwerknummer kann theoretisch von 1
bis 9 laufen, es gibt jedoch nur Atari-
Stationen, die die Nummern 1 bis 4 er-
kennen.
IOCBCMD An dieser Adresse wird dem zentralen
Ein-/Ausgabeprogrammpaket CIO das Kom-
mando übergeben, was mit den Daten und
dem angemeldetetn Gerät passieren soll.
Es stehen die folgenden, für alle Geräte
geltenden, Kommandos zur Verfügung:
Befehlskode Bedeutung
----------------------------------------
$03 OPEN
Stellt fest, ob das ange-
kündigte Gerät überhaupt
am Bus anliegt.
$05 GET RECORD
Lies den nächsten Block
von dem angemeldeten Ge-
rät, wenn es ein lesbares
Gerät ist, sonst melde
Fehler.
$07 GET CHARACTER(s)
Lies das nächste Zeichen,
beziehungsweise die näch-
sten Zeichen, bis zum
NEWLINE von dem angemel-
deten Gerät, wenn es ein
lesbares Gerät ist, sonst
melde Fehler.
$09 PUT RECORD
Schreibe den übergebenen
Block an den geöffneten
Datenkanal
$0b PUT CHARACTER(s)
Schreibe das nächste
Zeichen, beziehungsweise
alle folgenden Zeichen,
bis zum nächsten NEWLINE
an den geöffneten Kanal.
$0c CLOSE
Schließe den entsprechen-
den offenen Kanal und
schreibe eventuell noch
im Puffer vorhandene Da-
ten vorher über den noch
offenen Kanal.
$0d STATUS
Lies die Statusmeldung
des angesprochenen Ge-
rätes.
$0e SPECIAL
Rufe gerätespezifische
Spezialroutine auf.
Weiterhin existieren einige Spezi-
alkommandos, die jedoch nur bei
jeweils einem Gerät gelten:
$11 DRAW LINE
Zeichnet eine Linie von
einem Punkt zum anderen
in dem programmierten
Grafikmodus.
$12 DRAW LINE WITH RIGHT FILL
Wie $11, nur daß rechts
von der Linie bis zu
einer eventuell rechts
davon befindlichen weite-
ren Linie der Schirmin-
halt ausgefüllt wird.
Die beiden letzten Kommandos galten
einsichtiger Weise nur für den
Bildschirm, wobei die folgenden
Spezialkommandos nur für den Dis-
kettenzugriff gelten:
$20 RENAME DISK-FILE
$21 DELETE DISK-FILE
$22 FORMATIERE DISKETTE
$23 LOCK DISK-FILE
Danach ist das angegebene
File nur noch zum Lesen
zu öffnen.
$24 UNLOCK LOCKED FILE
$25 POINT
Dieses Kommando 'positio-
niert' den Schreib-/Le-
sekopf der Floppy-Disk
logisch auf den angegebe-
nen Sektor und das ange-
gebene Byte.
$26 NOTE
Analog zu POINT liefert
dieses Kommando die Aus-
sage darüber, wo sich die
Diskettenstation gerade
logisch befindet.
IOCBSTAT In diesem Byte wird von CIOMAIN eine
Meldung zurückgeliefert, an der das
aufrufende Modul erkennen kann, wie die
CIO-Routine das Kommando bearbeiten
konnte. Es sind, abhängig von dem Gerät
und dem Kommando, insgesamt folgende
Statusmeldungen möglich:
$01 SUCCESS
Die Operation verlief
erfolgreich.
$80 BREAK_ABORT
Währen der Kommandoaus-
führung wurde die BREAK-
Taste gedrückt.
$81 PREVIOUS_OPEN
Das Gerät (beziehungswei-
se eigentlich sein Kanal)
wurde schon einmal geöff-
net und ist seitdem nicht
wieder abgemeldet (ge-
schlossen) worden.
$82 NON_EXISTANT_DEVICE
Dieses Gerät ist in
HATABS nicht bekannt.
$83 WRITE_ONLY
Es wurde versucht, von
einem Nur-Schreib-Kanal
(zum Beispiel Drucker) zu
lesen.
$84 INVALID_CMD
Das übergebene Kommando
ist nicht existent.
$85 NOT_OPEN
Das zu beschreibende oder
zu lesende Gerät oder
File ist noch nicht ge-
öffnet.
$86 BAD_IOCBNR
Die übergebene IOCB-Num-
mer, die ein Vielfaches
von 16 sein muß, ist
entweder zu groß oder
nicht durch 16 teilbar.
$87 READ_ONLY
Es wurde versucht, ein
Nur-Lese-Gerät oder -File
zu beschreiben.
$88 EOF_ERROR
Es wurde versucht, mehr
aus einem File zu lesen,
als darin enthalten ist.
$89 TRUNCATED
Bei einem Zeilen-Lese-
Kommando wurde eine Zeile
empfangen, die länger war
als der zur Verfügung
stehende Puffer. Die
letzten Zeichen der Zeile
sind nicht gespeichert,
das letzte Zeichen des
Puffers ist NEWLINE.
$8a TIMEOUT
Das Gerät hat sich nicht
wieder gemeldet, nachdem
es mit der Bearbeitung
des Kommandos begonnen
hat.
$8b DEVICE_NACK
Dieser negative Acknowe-
ledge (negative Rückmel-
dung) zeigt an, daß of-
fensichtlich das anzu-
sprechende Gerät nicht
bereit (vorhanden, ange-
schlossen, eingeschaltet
o.ä.) ist.
$8c FRAMING_ERROR
Dieser Kode erscheint,
wenn der POKEY beim Lesen
eines Zeichens fest-
stellt, daß das anzuhän-
gende Stop-Bit nicht kam,
sondern gleich mit der
Übertragung eines weite-
ren Zeichens begonnen
wurde. Dieser Fehler
tritt vor allem dann auf,
wenn der Sender und
Empfänger zu ungleiche
Übertragungsraten besit-
zen. Eine sinnvolle In-
terpretation dieses Feh-
lers kann jedoch auch die
BREAK-Kondition sein.
$8d CURSOR_OVERRANGE
Der Cursor wird außerhalb
des ihm zustehenden Bild-
bereichs gesetzt. Tritt
zum Beispiel bei falschen
DRAWTO-Berechnungen auf.
$8e SIO-OVERRUN
Dieser POKEY-Fehler zeigt
an, daß ein neues Zeichen
eingelesen wurde, bevor
das alte vom System gele-
sen worden ist. Damit ist
das alte Zeichen verlo-
ren.
$8f SIO-CHECKSUM_ERROR
Die empfangene Prüfsumme
stimmte nicht mit dem
berechneten Wert überein.
$90 DEVICE_ERROR
Das Gerät konnte die Ope-
ration nicht vernünftig
ausführen.
$91 BAD_SCREEN_MODE
Dieser Modus ist den
bildschirmverarbeitenden
Routinen unbekannt.
$92 FUNCTION_NOT_IMPLEMENTED
Die verlangte Operation
ist zwar nicht verboten,
aber dennoch nicht vorge-
sehen.
$93 INSUFFICIENT_SCREENMEMORY
Der zur Verfügung stehen-
de Speicher reicht nicht
aus, um den geforderten
Bildschirmmodus zu initi-
alisieren. Dies kann zum
Beispiel passieren, wenn
in einer 16KB-Maschine
DOS geladen ist, ein
BASIC-Programm läuft und
eine hohe Grafikstufe
gewählt werden soll.
Allgemein zu den Fehlermeldungen ist zu sagen, daß ihre
Kodes bei entsprechender Abfrage das Negativ-Flag der CPU
setzen.
IOCBBUFA Hier wird die Anfangsadresse des (meist
vom Benutzer zur Verfügung zu stellen-
den) Datenpuffers festgehalten. Der Wert
ist unverändert nach Ausführung des
Kommandos, unabhängig von Erfolg oder
Mißerfolg der Operation.
IOCBPUTB Hier steht die Anfangsadresse-1 der
Routine, die an das definierte Gerät ein
Zeichen überträgt. Der Vektor zeigt bei
Nur-Lese-Geräten auf die entsprechende
Fehlerbehandlungsroutine.
IOCBBUFL Dies ist die Angabe der Länge des bei
IOCBBUFA beginnenden Puffers.
IOCBAUXn Die vier folgenden Byte sind Hilfsregi-
ster, von denen jedoch das erste,
IOCBAUX1, zeitweilig für die CIO-Pro-
grammierung verwendet wird. Dabei sind
folgende Werte erlaubt:
$01 APPEND
Dieser Kode erlaubt das
anhängende Schreiben an
eine bestehende Datei
beziehungsweise das Lesen
vom Bildschirm.
$02 DIRECTORY
Der folgende OPEN-Befehl
veranlaßt einen Zugriff
auf die Disketten-Direc-
tory.
$04 OPEN_FOR_INPUT
Dieses Kommando kann
theoretisch bei jedem
Gerät angewandt werden,
es sind jedoch physikali-
sche Einschränkungen zu
beachten (zum Beispiel
Drucker).
$08 OPEN_FOR_OUTPUT
Es gilt das gleiche wie
für OPEN_FOR_INPUT gesag-
te.
$12 OPEN_FOR_OUTPUT & INPUT
Wie OPEN_FOR_INPUT.
$10 OPEN_FOR_MIXED_MODE
Dieser Zusatz ist nur
beim Editor und beim
Screen erlaubt.
$20 OPEN_WITHOUT_CLEAR_SCREEN
Ebenfalls nur für Editor
und Screen erlaubt.
Um nun ein Kommando an die CIO-Routinen zu übergeben, wird
im X-Register die Nummer des IOCBs * 16, und im Accu das
unter Umständen zu sendende Zeichen an JUMPTAB+$06 überge-
ben.
Die Diskettenverarbeitung läuft nicht über die CIO-Routinen,
da dort etwas andere Bedingungen vorliegen als bei den
übrigen Geräten. Sie läuft über den Disc-Control-Block ab
Adresse DSKDEVICE.
An diese und die folgenden Adressen werden folgende Werte
übergeben:
DSKDEVICE Bus-Kennummer der Diskette Nummer 1.
Dieser Wert ist Referenz für die übri-
gen, nachfolgenden Geräte.
DSKUNIT Hier steht die eigentliche Nummer des
anzusprechenden Laufwerks.
DSKCMD Es stehen die Kommandos
'!' $21 FORMAT_DISKETTE
'P' $50 PUT_SECTOR_WITHOUT_VERIFY
'R' $52 GET_SECTOR
'S' $53 STATUS_REQUEST
'W' $57 PUT_SECTOR_WITH_VERIFY
zur Verfügung.
DSKSTATUS Hier steht nach der Bearbeitung eines
Kommandos der Status der Operation.
DSKBUFFER Anfangsadresse des Datenpuffers.
DSKTIMOUT Anzahl der Sekunden bis zur Timeout-
Meldung.
DSKBYTCNT Länge des bei DSKBUFFER beginnenden
Puffers.
DSKAUX1
DSKAUX2 Hilfsbyte. Hier steht bei den meisten
Kommandos die zu lesende/schreibende
Blocknummer.
Die Diskettenverarbeitung wird nicht über den CIO-Vektor
angesprungen, sondern über JUMPTAB+$09, also das SIO-Inter-
face.
******************************
* *
* *
* BETRIEBSSYSTEMROUTINEN *
* ---------------------- *
* *
******************************
Im Folgenden werden die einzelnen Unterprogramme des
Betriebssystems erläutert. Sie sind nach ihrer Lage
(Adresse) im ROM geordnet. Die vier Zahlenangaben bedeuten
von links nach rechts (die Adreßangabe kann sich beim
400/800 auf ungefähr gleichartige Routinen beziehen):
Adr. beim 600XL/800XL Adr. beim 400/800 Name
Hex Dez Hex Dez
------------------------------------------------------------
$c000 49152 $xxxx xxxxx CHECKSR0
$c001 49153 $xxxx xxxxx
Diese Adresse enthält die Prüfsumme über alle Bytes aus
den Speicherbereichen
$c002 .. $cfff
$5000 .. $57ff
$d800 .. $dfff
Auf diesen Wert wird von CHECKROM1 zugegriffen.
$c00c 49164 $e6d5 59093 NMIENABLE
Es wird der NMI enabled und der Wert von TRIG3 in
GINTLK gesichert. Beim 400/800 hat diese Routine zu-
sätzlich die Aufgabe, die Portbausteine zu initialisie-
ren.
$c018 49176 $e7b4 59316 NMIFIRST
Jeder NMI spingt indirekt über NMIVKT an diese Stelle.
Hier wird getestet, ob es sich um eine ANTIC-Programm-
Unterbrechung handelt. Wenn es eine ist, dann wird die
ANTIC-Programm-Unterbrechung indirekt über DLIVKT ange-
sprungen. Ist es keine ANTIC-Programm-Unterbrechung,
wird nach dem Pushen des ACCU der RESET-Tastenstatus
überprüft. Ist die RESET-Taste gedrückt, wird der Warm-
startvektor (JUMPTAB + $24) angesprungen. Ist keine
dieser Abfragen erfolgreich, so wird nach dem Retten
der Register X und Y auf dem Stack indirekt VBLKIVKT
aufgerufen.
$c02c 49196 $e6f3 59123 JMPIRQVKT
Jeder INT und jede BREAK-Operation laufen indirekt über
INTVKT zu dieser Adresse. Hier wird im Gegensatz zum
400/800 das DECIMAL-Flag gelöscht, was gerade beim
Erstellen von Interruptroutinen gerne vergessen wird
und sonst beim 400/800 zu Fehlern führte. Da beim INT-
Zyklus der 6202-CPU automatisch das Processor-Status-
Word auf dem Stack abgelegt wird, hat das D-Flag nach
einem RTI wieder den ursprünglichen Wert.
Nach Löschen des Flags wird die jeweilige Interruptrou-
tine indirekt über VIMMEDIRQ angesprungen.
$c030 49200 $e70b 59147 SINRDYIRQ
Diese Routine übernimmt, angestoßen von einem Inter-
rupt, die Kontrolle der seriellen und noch nicht exi-
stenter I/O-Einheiten. Dabei wird wie beim 400/800
getestet, ob ein Zeichen über den seriellen Port einge-
geben worden ist. In diesem Fall müßte Bit 5 vom IRQST
beziehungsweise IRQST@ gesetzt sein und wird die Einle-
seroutine indirekt über VSERIELIN angesprungen.
Weiterhin wird geprüft, ob die Verknüpfung (NEUIOMASK
AND NEUIOPORT) einen Wert ungleich 0 ergibt, also ein
an dieser Stelle angeschlossenes Gerät einen Interrupt
erzeugt hat. Ist dies der Fall, so wird dessen Treiber-
routine indirekt über NEUIOINIV angesprungen.
Ist auch diese Abfrage erfolglos, so wird, ähnlich wie
beim 400/800, der IRQSTATUS dahingehend abgefragt, ob
ein Interrupt des POKEY anliegt, der dem System mit-
teilt, daß das serielle Ausgaberegister zur Aufnahme
eines neuen Datenbytes bereit ist, oder sogar die Über-
tragung des letzten Zeichens beendet ist. Die erste
Bedingung wird durch Bit 4, die zweite durch Bit 3 des
Statusbytes signalisiert. Ist Bit 4 gesetzt, so wird
ein indirekter Ansprung zu der in VSERREADY stehenden
Adresse vorgenommen, bei Bit 3 ein Ansprung zu der in
VSERCLOSE stehenden Adresse. Da diese und die folgenden
Abfragen in einer Schleife vorgenommen werden, wird
der jeweilig gefundene Vektor (also zum Beispiel
VSERREADY) nach NEUIOPTR geladen und von dieser, nun
festen Adresse aus der eigentliche Treiber indirekt
angesprungen.
Ist bis hierhin keine Interruptquelle gefunden, so kann
es unter anderem auch der TIMER1, TIMER2 oder der
TIMER4 des POKEY gewesen sein, der den Interrupt auslö-
ste. Ist eines dieser Geräte aktiv gefunden, so wird
der jeweilige Treiber (VTIMER1, VTIMER2, VTIMER4) nach
der oben beschriebenen Methode aufgerufen.
Ist es auch kein Timerinterrupt, kann es noch eine
Tastenanforderung gewesen sein. Hierbei werden schon an
dieser Stelle zwei Möglichkeiten unterschieden: Jede
normale Taste führt auf den Vektor VKEYBOARD, die er-
wähnte Ausnahme ist die BREAK-Taste. Ist sie gedrückt
und KBDISABLE auf 0 (also enabled), wird indirekt nach
VBREAKKEY gesprungen. Nur wenn bis hier keine Inter-
ruptquelle gefunden wurde, werden die IRQ- Bits des PIA
abgefragt. Ist Bit 7 von PORTACNTL=1, so wird VPRECEDE
aufgerufen, bei Bit 7 von PORTBCNTL=1 VINTERRUPT.
Hier kann nur noch ein softwaremäßiger BREAK anliegen,
der im PSW der CPU signalisiert wird. Ist das BRK-Bit
gesetzt, so wird die ab (VBREAK) liegende Routine auf-
gerufen.
Findet der Prozessor bis hier keine geeignete, aktive
Interruptquelle, dann handelt es sich bei dieser Unter-
brechungsanforderung um einen technischen "Irrtum" und
die Routine kehrt zum unterbrochenen Programm zurück.
$c092 49298 $e785 59269 BRKEVENT
Diese Routine wird angesprungen, wenn die BREAK-Taste
gedrückt wird und das Keyboard enabled ist
(KEYDISABLE=0). Es werden hier der Attract-Mode, das
STARTSTOP-Flag, CURSORINH und IRQST mit IRQST@ ge-
löscht. Damit ist das System unter normalen Bedingungen
wieder arbeitsfähig.
Der Vektor VBREAKKEY zeigt auf BRKEVENT.
$c0cf 49359 $xxxx xxxxx MASKTAB
Diese Tabelle wird benutzt, um einzelne Bits ausblenden
zu können. Sie enthält in aufsteigender Reihenfolge die
Werte
$80, $40, $04, $02, $01, $08, $10, $20 .
$c0d7 49367 $xxxx xxxxx VECTAB
Diese Tabelle steht in direkter Verbindung mit MASKTAB
bei der Verarbeitung der einzelnen Interruptquellen in
SINRDYIRQ. Sie gibt zu der jeweiligen Maske den Offset
des Ansprungvektors zu $200. Sie enthält in aufsteigen-
der Rehenfolge die Werte
$36, $08, $14, $12, $10, $0e, $0c, $0a .
Bespiel: Es soll Bit 1 (also das 2. Bit!!!) getestet
werden.
Also wird ein Offsetregister (X) mit 3 geladen und das
entsprechende Statusbyte mit MASKTAB,X maskiert. Ist
das Ergebnis gleich Null, so ist der Interrupt gefunden
und es muß die Treiberadresse geholt werden. Da X immer
noch den Wert 3 hat, wird einfach nach NEUIOPTR der
Wert von $200,(VECTAB,X) geladen. (VECTAB,X) liefert
den Wert $12, also wird der Vector aus der absoluten
Adresse $212=$200+$12 geholt. Genau das gleiche ge-
schieht mit NEUIOPTR+1 und $201,(VECTAB,X). Danach
enthält NEUIOPTR den Pointer des Handlers des unter-
brechenden Gerätes.
$c0df 49375 $xxxx xxxxx WAITFRRES
Dieser Programmteil sperrt sämtlich Interrupts und
initiiert eine Warteschleife (65536 * Nichtstun), um
danach RESET anzuspringen.
$c0f0 49392 $e7d1 59345 SYSTEMVBL
Diese Interruptroutine wird bei jedem Vertical-Blank-
Interrupt aufgerufen und führt eine große Zahl von
Systemkontrollen und Justierungen durch.
So wird als erstes die Atari-Uhr TIMER incrementiert.
Diese Uhr ist eigentlich keine im üblichen Sinn, son-
dern sie zählt lediglich in 3 Byte (also TIMER,
TIMER+1, TIMER+2) alle ankommenden Vertical Blank Un-
terbrechungen. Da bei unserem Fersehsystem alle 20ms
(Milli-Sekunden: 1000ms=1s) ein sogenanntes Halbbild
fertig sein muß, damit der Elektronenstrahl in der
Bildröhre zur linken oberen Ecke zurücktransportiert
werden kann, muß natürlich auch der Atari dafür sorgen,
daß er zur selben Zeit die neuen Bildinformationen
generiert. Unter anderem hierfür wird der Atari also
alle 20ms unterbrochen, damit er die SYSTEMVBL-Routine
ausführen kann.
Dieses feste Zeitintervall kann nun auch für eine Uhr
benutzt werden, mit dem kleinen Unterschied, daß sie
nicht Sekunden, sondern 1/50 Sekunden angibt. Dabei ist
TIMER+2 der niedrigste (also schnellste) Zähler,
TIMER+1 ist der mittlere und TIMER zählt dann die
Überläufe von TIMER+1. Damit ergibt sich auch gleich
eine leichte Umrechnung von TIMER in die normale Zeit:
UHR = INT(((TIMER+2)+256*((TIMER+1)+256*(TIMER))) / 50)
UHR enthält dann die Anzahl der Sekunden ab System-
start.
Bei jedem Incrementieren von TIMER+1 wird auch ATRACT
erhöht. Dieses Register hat die Aufgabe, die Farben und
Helligkeiten des Bildschirms zu verringern, wenn eine
bestimmte Zeit lang kein Zeichen über die Tastatur
eingegeben wurde. Hat ATRACT den Wert 128 erreicht
(also nach einem Zeitraum von 128*256/50/60 = 10,9
Minuten), so werden Farbe und Helligkeit verändert.
Dies geschieht dadurch, daß ATRACTMSK von $fe auf $f6
zurückgesetzt wird und COLREGSH mit dem Wert von
TIMER+1 geladen wird. Diese Änderungen haben Einfluß
auf die folgende Reinitialisierung der Farbe und Hel-
ligkeit von Vorder- und Hintergrund. Es gilt beim Be-
schreiben der Farb- und Luminanzregister folgende An-
wendungsregel von ATRACTMSK und COLREGSH:
neue_Farbe+LUM = (alte_Farbe+LUM eor COLREGSH) and ATRACTMSK
Durch den Zusammenhang von TIMER+1 und COLREGSH entste-
hen die sich selbsttätig ändernden Farben auf dem
Schirm im Attract-Mode.
Weiterhin werden an dieser Stelle die Schattenregister
aufgefrischt, beziehungsweise neue Werte der Schatten-
register in die Hardwareregister übertragen.
Im Einzelnen sind dies an dieser Stelle der Routine:
von nach Erklärung
-------------------------------------------------------
LPENV LPENV@ Lightpen vertikal
LPENH LPENH@ Lightpen horizontal
DLPTR@ DLPTR Display List Pointer
DMACNTL@ DMACNTL DMA Control-Register
GTIACNTL@ GTIACNTL Monitor Control-Register
FINESCROL wird, wenn es ungleich Null ist, decremen-
tiert und der Wert ((8-FINESCROL) modulo 8) nach
VSCROL geschrieben, um stetiges, weiches Scrollen zu
ermöglichen.
Nun wird CONSOLE mit 8 geladen, um das Auslesen des
Registers und somit das Erkennen eventuell gedrückter
Sondertasten wie SELECT oder ähnlicher zu ermöglichen.
Dann wird die oben unter ATRACT erwähnte Reinitialisie-
rung der Farb- und Luminanzregister durchgeführt. Es
wird der Bereich COLPM0@ .. COLBAK@ in den Bereich
COLPM0 .. COLBAK kopiert, wobei jeder Wert nach der
oben angegebenen Gleichung modifiziert wird.
Dann werden CHARBASE mit CHARBASE@ und CHARCNTL mit
CHARCNTL@ beschrieben. Der erste Wert liefert dem ANTIC
die Highbyte-Adresse des gerade gültigen Zeichengenera-
tors, von denen der Atari 600XL/800XL gleich zwei ein-
gebaut hat, die jedoch auch über dieses Register um
andere erweitert werden können. CHARBASE definiert, ob
die Zeichen normal oder gedreht dargstellt werden sol-
len.
Als nächstes folgt ein Block zum Testen und Modifizie-
ren der weiteren im System enthaltenen Timer TIMCOUNT2
bis TIMCOUNT5.
Diese 5 Timer sind jeweils 16-Bit Zähler, die alle 20ms
über die Routine DECTIMER decrementiert werden. Den
Timern TIMCOUNT0 und TIMCOUNT1 sind die Sprungvektoren
TIMER1VKT sowie TIMER2VKT zugeordnet, die angesprungen
werden, wenn nach dem Decrementieren die Zähler Null
sind. Für die drei übrigen Zähler TIMCOUNT3, TIMCOUNT4
und TIMCOUNT5 werden beim Erreichen von Null die jewei-
ligen Flags TIMER3SIG bis TIMER5SIG gesetzt.
Der zeitliche Ablauf ist so, daß zuerst TIMCOUNT1 de-
crementiert und, bei Bedarf, TIMER1VKT aufgerufen wird.
Dann erfolgen die weiter unten beschrieben Abfragen nur
noch dann, wenn CRITICIO an dieser Programmstelle den
Wert Null hat, also gelöscht ist.
Ist dies der Fall, wird TIMCOUNT2 decrementiert. Beim
Erreichen von Null wird, über JMPTIMER2 und indirekt
über TIMER2VKT, die entsprechende Routine aufgerufen.
Danach werden die Timer 3 bis 5 behandelt.
Als weiterer Block wird die Tastatur behandelt. Sie
bietet über einige Register dem Anwender vielseitige
Möglichkeiten zum Blockieren, Kodeändern oder Maskieren
einzelner Tasten.
Die eigentliche Leseroutine testet zuerst, ob eine
Taste gedrückt ist. Wenn keine gedrückt ist, prüft sie,
ob der Delay-Zähler KEYDELAY Null ist. Ist er es nicht,
wird er decrementiert, ansonsten wird mit der Bearbei-
tung der Joysticks fortgefahren.
Die Variable KEYDELAY verhindert, daß man zu schnell
eingeben kann (Tastenentprellung), da der Rechner nur
eine neue Tasteneingabe akzeptiert, wenn vorher die
letzte Taste sozusagen 'abgeklungen' ist. Sie wird mit
dem Wert 3 initialisiert.
Wenn eine neue Taste gedrückt wird, beginnt der, mit
KREPDELY (Standardwert 40) initialisierte, Zähler
SRTIMER rückwärts zu zählen, wenn diese entsprechende
Taste gedrückt bleibt. Hat er bei einem Interrupt den
Wert Null erreicht und ist diese Taste immer noch
gedrückt und KBDISBLE=0, d.h. enabled, so beginnt ein
Autorepeat der Tastatur. Um die Geschwindigkeit des
Autorepeat festlegen zu können, wird ebenfalls SRTIMER
benutzt. So lange, wie diese eine Taste gedrückt
bleibt, wird SRTIMER mit dem Wert von KEYREP geladen
und bei jedem Vertical-Blank-Interrupt rückwärts ge-
zählt zu Null. Ist er Null, so wird der vom POKEY zur
Verfügung gestellte Tastencode in Matrixform aus Regi-
ster KBCODE, beziehungsweise seinem Schattenregister
KBCODE@ ausgelesen.
KEYREP gibt somit die Repeat-Geschwindigkeit an und
wird mit 5 initialisiert, während die in KREPDELY lie-
gende Initialisierung von SRTIMER die Zeit angibt, bis
die Repeatfunktion überhaupt einsetzt.
Da dieses Betriebssystem, wie oben schon erwähnt, ei-
gentlich das 1200XL - Betriebssystem ist, geschehen an
dieser Stelle nun einige für den 600XL/800XL unsinnige
Dinge:
Es wird der gelesene Tastencode auf einige bestimmte
Sonderzeichen hin überprüft. Der 1200XL verfügt zum
Beispiel über vier freiprogrammierbare Funktionstasten,
F1 bis F4. Merkwürdigerweise überprüft der Atari hier
den Tastaturkode auf CNTL & F1, CNTL & F2, CNTL & F4,
CNTL & 1 sowie einen offensichtlich in der Matrix der
Tastatur nicht existenten Kode im Normalmodus, mit
Shift und Control. Entspricht der gelesene Tastaturkode
keinem der zwölf Werte, so wird dieser Kode in KBCODE@
abgelegt, sonst nicht. Interessant und kein Druckfehler
ist, daß CNTL & F3 an dieser Stelle nicht geprüft wird.
Nach der Tastaturabfrage überprüft der Atari die Joy-
stickports.
Diese Routine beginnt bei $c201, und kann angesprungen
werden, wenn die vorhergehenden Abfragen alle nicht
benötigt werden sollten. Es ist jedoch darauf zu ach-
ten, daß der normale Vertical Blank Interrupt in diese
Routinenteile hineinläuft. Das eben gesagte gilt ebenso
für die Triggerprüfung wie für den Paddle-Check.
Da der 600XL/800XL im Gegensatz zum 400/800 nur noch
über zwei Joystickports verfügt, einige wenige Software
jedoch auf Port 3 und/oder Port 4 zugreift, hat sich
Atari aus Kompatibilitätsgründen dazu entschlossen, die
fehlenden Ports zu simulieren.
Es werden zuerst die oberen 4 Bit des PORT A des PIA
gelesen und gleichzeitig als Wert für JOYSTICK1 und
JOYSTICK3 verwertet; beim 400/800 wurde der Wert für
JOYSTICK3 aus PORT B 'gezogen'. Danach werden die unte-
ren vier Bit von PORT A nach JOYSTICK0 und JOYSTICK2
transportiert.
Nun werden (ab $c219) die Triggereingänge überprüft.
Auch hier werden die Daten von Port 1 und Port 2 eben-
falls für jeweils Port 3 und Port 4 in der Form verwer-
tet, daß sie aus TRIGGER0 und TRIGGER1 gelesen und nach
TRIGGER0@ und TRIGGER2@, beziehungsweise TRIGGER1@ und
TRIGGER3@ geschrieben werden.
Ähnliches gilt für das bei $c22b beginnende Lesen der
Paddle-Eingänge. Die POKEY-Register PADDLE0 .. PADDLE3
werden nach PADDLE0@ .. PADDLE3@, beziehungsweise nach
PADDLE4@ .. PADDLE7@ kopiert und die Paddle-Trigger
gelesen. Dabei gilt folgende Zuordnung:
Aus JOYSTICK0 bit 2 ==: PTRIG0, PTRIG4
JOYSTICK0 bit 3 ==: PTRIG1, PTRIG5
JOYSTICK1 bit 2 ==: PTRIG2, PTRIG6
JOYSTICK1 bit 3 ==: PTRIG3, PTRIG7
An dieser Stelle ist die erste Vertikal-Blank-Unter-
brechungsroutine abgeschlossen und es folgt ein indi-
rekter Sprung über VBLKDVKT zur sogenannten 'verzöger-
ten' V-Blankroutine. Sie ist vom Benutzer frei zu ver-
wenden. Es ist allgemein bei der Verwendung dieses
Vektors darauf zu achten, daß die Interruptroutine
nicht zu lang wird, da sonst der eigentliche Rechen-
prozeß (zum Beispiel BASIC) unter Umständen nicht mehr
zum Zug kommt.
$c25d 49757 $e8ef 59631 JMPTIMER1
Es wird ein indirekter Sprung mit VTIMER1 durchgeführt.
$c260 49760 $e8f2 59634 JMPTIMER2
Es wird ein indirekter Sprung mit VTIMER2 durchgeführt.
$c263 49763 $e8f5 59637 DECTIMER
Die Routine DECTIMER decrementiert einen angegebenen
Timer, wenn er nicht Null ist. Ist der betreffende
Timer entweder vor dem Decrement Null oder hinterher
ungleich Null, so liefert DECTIMER im Accu den Wert $ff
zurück, sonst den Wert $00. Durch das Laden kann das
aufrufende Programm den Wert des Zero-Flags verwerten.
Ist Z=1, so ist der Timer gerade eben zu Null geworden,
sonst ist Z=0.
DECTIMER erwartet die Nummer des zu behandelnden Ti-
mers, multipliziert mit 2, im X-Register beim Aufruf;
also zum Beispiel in X den Wert $06 für TIMCOUNT3.
$c280 49792 $e912 59666 SETVBLVKT
Diese Routine wird allgemein verwendet, um Interrupt-
vektoren oder Timerwerte zu initialisieren. Dazu müssen
an SETVBLVKT folgende Werte übergeben werden:
Wert Übergeben in Register
------------------------------------------------------------
High-Byte der zu speichernden Adresse
beziehungsweise des zu speichernden Wertes X
Low -Byte der zu speichernden Adresse
beziehungsweise des zu speichernden Wertes Y
Registernummer A
Vektornummer kann sein: 0 ==: VIMMEDIRQ
1 ==: TIMCOUNT1
2 ==: TIMCOUNT2
3 ==: TIMCOUNT3
4 ==: TIMCOUNT4
5 ==: TIMCOUNT5
6 ==: VBLKIVKT
7 ==: VBLKDVKT
Die Verktornummer kann auch größer als 7 (oder unsigned
7 bit) werden, dann ist jedoch darauf zu achten, daß
die Routine nur 16bit-Worte und die nur ab geraden
Adressen ablegen kann.
Vor dem Modifizieren der Vektoren wird ein Vertical
Blank Interrupt abgewartet, damit die Vektoren nicht
gerade 'halb geändert' sind, wenn ein Interrupt auf sie
zugreifen will.
$c298 49816 $e93e 59710 EXITVBL
Diese Routine macht nichts weiter, als Y-Register, X-
Register und Accu in dieser Reihenfolge vom Stack zu
holen und mit RTI diesen (beliebigen) Interrupt zu
beenden.
$c29e 49822 f11b 61723 RESETWARM
RESETWARM kann direkt oder über JUMPTAB+$24 aufgerufen
werden. Zu Beginn der Warmstartroutine wird getestet,
ob nicht vielleicht doch gravierende Einflüsse auf das
System eingewirkt haben, die einen Kaltstart mit Initi-
alisierung sämtlicher Register nötig machen würden. Zu
diesen Einflüssen gehört zum Beispiel das Herausziehen
oder Wiedereinstecken eines ROM-Moduls in den ROM-
Schacht. Da beim 600XL/800XL im Gegensatz zum 400/800
nicht mehr automatisch das Gerät beim Wechseln eines
ROM-Moduls ausgeschaltet wird, ist hier also solch ein
Test notwendig.
Dazu wird der Inhalt von TRIGGER3 des POKEY gelesen und
mit dem Inhalt des für diesen Zweck eingerichteten
ROMFLAG verglichen. Welchen Zweck das Lesen des
TRIGGER3 hat, verrät uns ein Blick in das offene Gerät:
Die Leitung, die beim Einstecken des ROM-Modules das
eventuell an gleicher Stelle liegende RAM abschaltet,
ist mit eben dieser Triggerleitung verbunden, die wegen
Weglassens der Joystickports 3 und 4 frei geworden ist.
Wenn nun seit dem letzten Reset entweder ein ROM einge-
steckt oder entfernt worden ist, stimmen TRIGGER3 und
ROMFLAG nicht überein und der Atari forciert einen
RESETCOLD-Aufruf.
Sind TRIGGER3 und ROMFLAG noch identisch, so kann es
zum Beispiel sein, daß zwar wirklich ein ROM-Modul im
Schacht steckt, es jedoch ein anderes als beim letzten
Reset ist. Für diesen Fall werden die letzten Adressen
des ROMs getestet, genauer: Durch Aufruf von NEWCART
wird die Checksumme von $bff0 bis $c0ef gebildet und
mit der Checksumme des letzten Aufrufs verglichen. Ist
die Checksumme nicht in Ordnung, so wird ein Kaltstart
durchgeführt.
Es gibt noch eine dritte Möglichkeit, bei der ein
Warmstart zu einem Kaltstart werden kann: Durch Setzen
von COLDSTART auf einen Wert ungleich Null, wird dieses
erreicht.
Hat keine der genannten Quellen bis hierhin einen Kalt-
start forciert, wird RESET+$02 mit einem Accu-Wert von
$ff aufgerufen, das heißt, die eigentliche Warmstart-
prozedur durchgeführt.
$c2b8 49848 $f125 61733 RESETCOLD
Zu dieser Routine kann direkt oder über JUMPTAB+$27
gesprungen werden. Der Weg über JUMPTAB ist sinnvoller,
da dann Programmkompatibilität zu der 400/800-Serie
besteht.
Zu Beginn des Kaltstarts wird, analog zu RESETWARM,
geprüft, ob es sich wirklich um einen solchen handelt,
oder aber seit dem letzten Kalt- oder Warmstart keine
gravierenden Einflüsse auf das System eingewirkt haben,
so daß der Rechner mit einem Warmstart auskommen könn-
te.
Dazu wird als erstes geprüft, ob drei Adressen, die
beim Kaltstart auf ganz bestimmte Werte gesetzt werden,
diese immer noch enthalten. Die Adressen und ihre In-
halte sind:
POWUPBYT1 muß für Warmstart enhalten $5c
POWUPBYT2 muß für Warmstart enhalten $93
POWUPBYT3 muß für Warmstart enhalten $25
Was diese Werte bedeuten, ist recht unklar, es wird
jedoch davon ausgegangen, daß ein neu eingeschalteter
Speicherbaustein (RAM) bestimmt nicht diese drei Werte
an eben diesen Adressen enthalten kann. Normalerweise
sind dynamische Speicherzellen nach dem Power-up bank-
weise entweder $00 oder $FF, bedingt durch ihren physi-
kalischen Aufbau.
Wenn eine dieser drei Adressen nicht den Warmstartwert
enthält, wird WARMFLAG mit $00 geladen, was der danach
aufgerufenen Routine RESET mitteilt, daß es sich um
einen Kaltstart handelt.
Sind die drei Adressen in Ordnung, wird RESETWARM auf-
gerufen.
$c2d6 49878 $f126 61734 RESET
$c2d8
In diesem Programmteil werden, je nachdem, ob es sich
um einen Kalt- oder Warmstart handelt, mehr oder weni-
ger Register und Bausteine initialisiert. Die eigent-
liche Entscheidung darüber, ob es sich um einen Warm-
oder Kaltstart handelt, wird nicht mehr hier getroffen,
sondern in RESETWARM beziehungsweise RESETCOLD. Wird
RESET aufgerufen, wird das WARMFLAG auf $00 gesetzt,
was der weiteren Routine einen Kaltstart signalisiert.
Wird RESET+$02 aufgerufen, so wird in WARMFLAG der Wert
des übergebenen Accus abgelegt. Ist dieser Wert = $ff,
so signalisiert WARMFLAG einen Warmstart.
Danach beginnen die Resetoperationen. Zuerst wird
DOSVKT auf den Wert TESTROMEN gesetzt. Wird beim Ein-
schalten der Maschine oder einem anderen Kaltstart die
OPTION-Taste gedrückt, wird das im 600XL/800XL be-
findliche BASIC ausgeschaltet. Ist keine Diskette ange-
schlossen oder antwortet sie nur nicht richtig, wird
der Atari nach einiger Zeit versuchen, etwas anderes
sinnvolles zu tun als zu booten. Da er jedoch kein
BASIC zur Verfügung hat und beim 600XL/800XL kein Moni-
tor mehr besteht (der 400/800 ging in einen Echo-Modus,
wenn er weder ROM noch Bootgerät fand), wird er diesen
DOSVKT anspringen, um nicht abzustürzen. Über diesen
Vektor wird er dann bei TESTROMEN das eingebaute Test-
ROM einschalten und anspringen.
Bevor er jedoch dies alles tun kann, hat er noch eine
ganze Reihe anderer Dinge zu tun.
Zuerst wird über CARTGO getestet, ob im Schacht ein
ROM-Modul steckt. Ist dies der Fall, und ist dieses ROM
ausführbar (siehe CARTGO), so kehrt diese Routine nicht
zurück, sondern springt das ROM indirekt über $bffe an.
Als nächstes wird über NEWCART die Checksumme über die
letzten Bytes des eventuellen ROMs gebildet und in
ROMFLAG abgelegt.
Weiter wird dort geprüft, ob das BASIC einzuschalten
ist und die Ports werden initialisiert.
Danach beginnt der Atari mit dem RAM-Test, falls es
sich um einen Kaltstart handelt. Dabei werden die ent-
sprechenden Variablen von SYSINIT gesetzt.
Ist dies geschehen, oder handelt es sich um einen
Warmstart, werden die Variablenbereiche $200 .. $3ed
und $00 .. $7f mit $00 initialisiert. Nun wird
X64KBFLAG auf den Wert gesetzt, den Bit 1 von PORT B
besitzt und die Variablen POWUPBYT1, -2 und -3 werden
mit den Werten $5c, $93 und $25 geladen. Weiter werden
der rechte und linke Rand des Textfensters in RIGMARGIN
und LFTMARGIN eingestellt auf die Werte 39 beziehungs-
weise 2.
Als nächstes kommt eine Eigenschaft des 600XL/800XL -
Betriebssystems zu Tage, über die der 400/800 nicht
verfügt. In den USA ist bekanntermaßen ein anderes
Farbsystem verfügbar als bei uns. Abgesehen von unter-
schiedlicher Bildqualität ist die Signalzusammensetzung
und die Bildfrequenz nicht mit 50Hz (Hertz, 1Hz = 1
komplette Schwingung pro Sekunde), sondern mit 60Hz
vorgegeben. Da jedoch aus dieser Freuenz über den Ver-
tical Blank Interrupt ein Zeitnormal gezogen wird, ist
dieser Unterschied wichtig zu beachten. Bei den alten
Systemen wurde das so gehandhabt, daß einfach überall
die Zeitkonstanten verändert wurden, die auf den Zeit-
takt zurückgriffen. Da aber den Entwicklern der neuen
Geräte aufgegangen ist, daß 'Old-Germany' inzwischen
auch über einen recht großen Computermarkt verfügt,
haben sie die Zeitkonstanten parametrisiert, das heißt,
die Zeitkonstanten sind alle über Tabellen zu er-
reichen.
Um nun jedoch zu unterscheiden, ob es sich bei diesem
Gerät um unser PAL- oder das NTSC-System handelt, wird
ein Register des GTIA gelesen, und zwar NTSCPAL. Sind
die Bits 1 bis 3 dieses Registers alle Null, so handelt
es sich um einen PAL-Baustein, sonst um einen NTSC-Typ.
Je nach Farbsystem werden nun die folgenden Register
unterschiedlich geladen:
Register NTSC PAL Erklärung
------------------------------------------------------------
NTSCPAL@ $00 $01 Flag zur weiteren
Verwendung
KEYRPDELY $30 $28 Initialisierungswert
für SRTIMER, wenn
Taste neu gedrückt
worden ist
KEYREP $06 $05 Initialisierungswert
für SRTIMER, wenn
Taste gedrückt ist
und SRTIMER wenig-
stens einmal abge-
laufen ist.
Um die gesamte Interrupt-, Timer- und Vektormaschinerie
zum Laufen zu bringen werden die Adressen $200 .. $225
(DLIVKT bis VBLKDVKT) initialisiert mit den Werten, die
ab Adresse INIT200 stehen. Direkt danach werden die
Handlervektoren ab HATABS mit den vorgegebenen Adressen
ab INIT31A initialisiert.
Aus den Speichertestroutinen ist noch ein Flag zu ver-
arbeiten: Als Hilfsregister wurde Adresse $0001 mit dem
Wert $00 oder einem anderen für das Ergebnis des Spei-
chertests verwendet.
Ist das Ergebnis nicht Null, so wird über GOMEMTEST das
Test-ROM eingeschaltet, die Variablen CHARCNTL mit $02
und CHARBASE@ mit $e0 (für Zeichengenerator ab $e000)
initialisiert und direkt der Speichertest aufgerufen.
Hat $0001 den Wert Null, wird IOCB0 bestückt. Es werden
das Kommando OPEN, die Namenspufferadresse OPENSCST
sowie das IOCBAUX1 mit READ + WRITE (s. Erklärung IOCB-
Verwendung) progammiert. In Worten heißt das also, daß
IOCB0 für einen Schreib/Lese-Open für den Screen-Editor
vorbereitet wird. Dann wird versucht, über CIOMAIN
diesen Open auszuführen. Da es dabei eigentlich keine
Probleme geben dürfte, wird bei einem eventuellen Feh-
ler unmittelbar ohne zweiten Versuch RESETCOLD ausge-
führt.
Ist bis hierhin alles glatt gegangen, wird geprüft, ob
von Kassette gebootet werden soll (Drücken der START-
Taste), oder eine bootbare Diskette existiert. Wenn ja,
wird über BOOT geladen, sonst wird, falls TMPRAMSIZ =
$01 ist und bit 2 von Adresse $bffd (im ROM) gesetzt
ist, indirekt über COLDCART das ROM angesprungen.
Ist auch dieses nicht der Fall, wird der inzwischen
eventuell geänderte Vektor DOSVKT indirekt als Sprung-
vektor benutzt.
$c3bd 50109 $xxxx xxxxx GOMEMTEST
Hier wird Bit 0 von PORT B auf 0 gesetzt. Damit wird
das im Bereich von $5000 bis $57ff befindliche RAM
ausgeschaltet und das im Bereich $d000 bis $d7ff hinter
den I/O-Bausteinen versteckt liegende Test-ROM wird
über die Memory Management Unit (MMU) auf den Bereich
$5000 bis $57ff kopiert. Dieses scheinbar schwierige
Kopieren wird einfach dadurch erreicht, daß die MMU
unter diesen Bedingungen bei einer Adresse %y101 xxxx
xxxx xxxx (x=egal) das y-Bit auf 1 setzt. Mit anderen
Worten addiert die MMU im Falle des Ansprechens einer
Adresse $5xxx den Wert $8000 und erhält $dxxx.
Hier sitzt nun also das Test-ROM, das (wenigstens) 2
Einsprünge enthält: Die Adressen TESTROM und
TESTROM+$03. Über TESTROM gelangt man in das Menue, das
man auch beim Kaltstart mit gedrückter OPTION-Taste
erhält. TESTROM+$03 geht direkt zum Memory-Test.
Nach dem Umschalten wird nun also einfach TESTROM+$03
angesprungen.
$c431 50225 $xxxx xxxxx COLDCARTC
Es wird ein indirekter Sprung nach (COLDCART) unternom-
men.
$c434 50228 $xxxx xxxxx DOSVKTC
Es wird ein indirekter Sprung nach (DOSVKT) unternom-
men.
$c437 50231 $xxxx xxxxx INITCARTC
Es wird ein indirekter Sprung nach (INITCART) unternom-
men.
$c43a 50234 $xxxx xxxxx CLCUNDRTS
Wie der Name der Routine schon vermuten läßt, wird hier
nur das Carry-Flag gelöscht und Return ausgeführt.
Diese (scheinbar unsinnige) Routine kann leicht auch
von Anwenderprogrammen zum Signalisieren von OK-Mel-
dungen bei Prozedurausgängen benutzt werden.
$c43c 50236 $f0e3 61667 INIT31A
Diese Tabelle enthält die Werte, mit denen die Handler-
Ansprungtabelle ab HATABS initialisiert wird.
$c44b 50251 $f10d 61709 DERRMSG
Hier liegt die Meldung "BOOT ERROR (CR)"
$c459 50265 $e480 58496 INIT200
An dieser Stelle liegen die Daten, die von RESET in die
Interrupt- und Timervektoren, beziehungsweise Timer
selbst, geladen werden.
$c47f 50303 $xxxx xxxxx CARTGO
Diese Routine testet, ob ein Cartridge gesteckt ist.
Wenn diese Bedingung erfüllt ist und die im ROM an
Adresse $bffc und $bffd stehende Adresse größer oder
gleich dem Wert $8000 ist, wobei es eine $x000 - Adres-
se sein muß, so wird ein Sprung indirekt über $bffe
ausgeführt.
Wenn dies nicht der Fall ist, werden über IOPORTINI die
verschiedenen Ports initialisiert und geprüft, was mit
Bit 1 von PORT B zu geschehen hat. Dieses Bit ist ein
Ausgang und gibt an, ob das eingebaute BASIC aktiv
werden kann oder nicht. Das BASIC ist eingeschaltet,
wenn Bit 1 des PORT B den Wert Null hat. Es bekommt
diesen Wert, wenn X64KBFLAG Null ist und die OPTION-
Taste nicht gedrückt ist. Letzteres erfährt man über
Bit 2 von CONSOLE: Ist das Bit gesetzt, so ist die
Taste nicht gedrückt und umgekehrt. Nach Setzen, be-
ziehungsweise Rücksetzen, dieses Bits ist die Routine
beendet. Da sie jedoch keine eigenständige Prozedur
ist, läuft sie in die nächstfolgende Speichertestrou-
tine GETRAMHI hinein und kehrt von dort aus zum Aufru-
fer zurück.
$c4b7 50359 $f258 62040 GETRAMHI
Diese Routine liefert in Register TMPRAMSIZ die Anzahl
der, dem Benutzer zur Verfügung stehenden, vollen 256-
Byte-Blöcke an RAM (Programmspeicher). Dabei wird, bei
Adresse $2800 mit dem Test beginnend, jedes erste Byte
eines solchen Blockes gelesen und das Einerkomplement
dieses Wertes an eben diese Stelle zurückgeschrieben.
Stimmt danach der Speicherzelleninhalt nicht mit dem
'gemerkten' Einerkomplement überein, so handelt es sich
hierbei offensichtlich nicht um eine RAM-Zelle und das
Programm terminiert. Stimmen die beiden Werte überein,
wird der Originalwert wieder zurückgeschrieben und
wieder gelesen.
Ließ sich die Speicherstelle wieder umprogrammieren, so
handelt es sich hierbei mit ziemlicher Sicherheit um
eine RAM-Zelle, ansonsten wird das Programm ebenfalls
abgebrochen. Da zum Testen die Adressen TMPRAMSIZ-$01
und TMPRAMSIZ als Pointer auf die zu testende Speicher-
stelle dienen, liefert also TMPRAMSIZ als High-Wert des
Pointers das High-Byte der ersten nicht-RAM-Adresse und
somit die Anzahl der zur Verfügung stehenden, darunter-
liegenden Blöcke.
$c4d7 50391 $xxxx xxxxx NEWCART
Hier wird die Checksumme über alle Bytes von $bff0 bis
$c0ef gebildet und mit CARTCKSUM verglichen. Sind die
beiden Werte gleich, kehrt die CPU mit gesetztem Zero-
Flag zurück, ansonsten wird der neue Wert in CARTCKSUM
abgelegt und das Zero-Flag vor dem Rücksprung gelöscht.
$c4e8 50406 $f281 62081 IOPORTINI
In diesem Programmteil werden alle bekannten I/O-Ports
und Bausteine initialisiert. Genauer werden die Be-
reiche
$d000 ... $d0ff (GTIA)
$d200 ... $d2ff (POKEY)
$d300 (PORT)
$d302 ... $d3ff (PORT)
$d400 ... $d4ff (ANTIC)
gesamt auf $00 gesetzt. Danach wird PORT B auf Ausgang
geschaltet und alle Bits dieses Ports auf 1 gesetzt.
Danach werden die Statusworte von PORT A und B gelesen,
um eventuell anliegende Interrupts zu löschen.
Sind die Ports initialisiert, wird der POKEY dahinge-
hend gesetzt, daß er seine Sende- und Empfängertakte
von Audiokanal 4 erhält. Weiter werden die Audiokanal-
register AUDCNTL3 und -4 auf den Wert $a0 und AUDIOCNTL
auf den Wert $28 gesetzt. Abschließend wird ein $ff -
Byte an das serielle Senderegister geschickt.
$c543 50499 $f294 62100 SYSINIT
Diese von RESET aufgerufene Prozedur hat die hauptsäch-
liche Aufgabe, sämtliche I/O-Bausteine zu initialisie-
ren. Dazu setzt sie als erstes nach Löschen des BREAK-
Enable-Bits in IRQST@ den BRKEYVKT auf BRKEVENT
($c092). Danach wird RAMSIZE mit dem Wert von TMPRAMSIZ
geladen und MEMTOP ebenfalls entsprechend gesetzt, um
danach die Untergrenze des Benutzer-RAMs in MEMLO mit
dem Wert $700 festzulegen.
Danach werden in dieser Reihenfolge die Initialisie-
rungsroutinen des Editors, des Screens, des Keyboards,
des Printers und der Kassette über die entsprechenden
Vektoren ab EDITORVKT aufgerufen.
Hiernach fehlen noch die ersten Aufrufe von CIOINIT,
SIOINIT, und NMIENABLE um die Interruptstruktur des
Systems komplett zu nutzen sowie ein Aufruf von
DISKINIT.
Kehrt die CPU nach diesen Routinen zurück, so wird
NEUIOINIV auf NEUIOREQ ($c97c) gesetzt und über
NEUINITC endlich NEUINIT aufgerufen.
Nach diesen Hardwarebearbeitungen wird vor dem Rückkeh-
ren aus der Routine noch STARTTST auf $01 gesetzt, wenn
die START-Taste gedrückt ist, ansonst wird STARTTST zu
$00. Dieser Wert wird später von BOOT benutzt.
$c599 50585 $f2cf 62159 BOOT
Zu Beginn dieser Boot-Routine wird geprüft, ob es sich
um einen Boot-Ansprung bei einem Warmstart handelt. Ist
dies der Fall (WARMFLAG ungleich $00) und außerdem ein
vernünftiges Disk Operating System geladen (DOSAKTIV =
1), so wird ein indirekter Sprung nach (DOSINITV) voll-
zogen.
Ist es zwar ein Warmstart, aber ist kein DOS geladen,
so wird einfach die Bootroutinenbearbeitung abgebro-
chen.
Der letzte Fall ist der eigentlich interessante in
dieser Prozedur:
Zuerst wird versucht, von der Diskettenstation Nummer 1
eine Statusmeldung zu erhalten. Dies geschieht nach
Setzen des STATUSCMDs in DSKCMD, Setzen der Nummer 1 in
DSKUNIT und Aufruf von DISKINTERF über JUMPTAB+$03.
Kehrt diese Routine mit gelöschtem Negativ-Bit in der
CPU zurück, so war der Status in Ordnung und es kann
von Diskette gebootet werden. Wenn nicht, kehrt BOOT
mit gesetztem Negativ-Bit zurück.
Ist die Statusabfrage positiv verlaufen, wird Sektor
Nummer 1 ab Adresse $0400 geladen. Dazu wird die Block-
nummer $0001 nach DSKAUX1 (Low-Byte)und DSKAUX2 (High-
Byte) geladen sowie der Wert $0400 nach DSKBUFFER, um
danach den Block mit GETSECTOR zu lesen. GETSECTOR kann
sowohl von Kassette als auch von Diskette lesen und
liefert im Falle eines falsch oder nicht gelesenen
Blockes einen negativen Wert zurück.
In diesem Fall wird die Meldung "BOOT ERROR" ausgegeben
und beim Laden von Cassette das Booten abgebrochen,
ansonst ein erneuter Leseversuch gestartet.
Vor der Beschreibung der weiteren Vorgänge empfiehlt es
sich, zwei artverwandte Begriffe zu klären:
Ein SEKTOR ist ein auf der Diskette eingetragener Satz
von Daten. Beim Atari existieren pro Diskette 40 kon-
zentrische Spuren, die jeweils 18 Sektoren mit jeweils
128 Byte Daten enthalten. Die Tracks werden innerhalb
der Floppy-Disk-Station von $00 bis $27 durchnumeriert,
die Sektoren auf jedem Sektor verwirrender Weise von
$01 bis $12. Warum die unterschiedliche Zählweise ein-
geführt worden ist, ist unklar, sie wurde jedoch nicht
von Atari festgelegt, sondern von dem Hersteller des
Floppy-Disk-Controllers innerhalb der Diskettenstation.
Im Gegensatz zum physikalischen Sektor ist ein BLOCK
eine logische Zusammenfassung von Daten. Sie kann theo-
retisch beliebig viele Datenbytes enthalten. Der Ein-
fachheit halber enthält ein Block beim Atari ebenso
viele Datenbytes wie ein Sektor, er wird nur nicht pro
Track gezählt, sondern durchgehend von $01 bis $02d0.
Für den Atari-Anwender ist normalerweise nur die Be-
trachtung der Blöcke von Interesse, da die Disketten-
station selbst eigene Intelligenz besitzt und somit dem
Benutzer keine Gelegenheit gibt, an die Unterscheidung
von Sektoren und Tracks heranzukommen. Trotzdem sollte
der Zusammenhang einmal geklärt werden, da bei der
Benutzung von Erweiterungen an den Atari (CP/M-Systemen
zum Beispiel) diese Trennung einmal interessant werden
kann. Spätestens bei der Verwendung von Diskettensta-
tionen mit doppelter Schreibdichte enthält ein Block
nur halb so viele Daten wie ein Sektor, so daß das
Betriebssystem hier einige Daten-"Schaufelarbeit" zu
erledigen hat. Aber dazu später mehr.
Ist der erste Block richtig gelesen, werden die ersten
vier Byte nach DSKFLAG, DSKSECCNT und DSKLDADR kopiert.
Sie geben die Sektorlänge, die Anzahl der zu lesenden
Blöcke und die Startadresse, ab der die gelesenen Daten
abgelegt werden sollen, an. Die nächsten zwei Byte
geben die Startadresse des Bootfiles an und werden in
DOSINITV abgelegt.
$c5c9 50633 $f301 62209 BLOCK1
(Dieses Label steht hier deshalb recht verloren inmit-
ten der Routine, weil, von CASBOOT aus, hier hineinge-
sprungen wird!)
Danach wird der gesamte gelesene Block ab der angegebe-
nen Bootadresse abgelegt, DSKSECCNT decrementiert und,
falls noch ein Block zu lesen ist, dieser an den je-
weils folgenden Adressen abgelegt. Tritt ein Lesefehler
auf, so wird beim Booten von Cassette sofort die Bear-
beitung abgebrochen, beim Diskettenboot wird versucht,
das gesamte Bootfile ein weiteres Mal einzulesen.
Sind alle Blöcke richtig eingelesen und wurde von Cas-
sette gebootet, so wird noch ein END_OF_FILE-Block
eingelesen, der jedoch nicht weiter verwendet wird.
Ansonst wird über BLOAD der Anfang der Bootroutine
aufgerufen. Dieser Anfang liegt im sechsten Byte des
ersten gelesenen Blocks.
Bei einem Diskettenprogramm kann hier getestet werden,
ob wirklich alles richtig geladen worden ist.
Wenn alles in Ordnung ist, muß dieser Test mit gelösch-
tem Carry-Flag zurückkehren, damit nicht noch einmal
versucht wird, zu booten.
Ist das Booten erfolgreich verlaufen, wird über
DOSINITC das Diskettenbetriebssystem initialisiert und
danach DOSAKTIV auf 1 gesetzt.
$c637 50743 $f36c 62316 BLOAD
Es wird der Wert von DSKLDADR+6 nach RAMTSTPTR geladen
und danach indirekt über RAMTSTPTR die gebootete Rou-
tine angesprungen.
$c649 50761 $f37e 62334 DOSINITC
Es wird ein indirekter Sprung über DOSINITV ausgeführt.
$c64c 50764 $f381 62337 DSKRDERR
die CPU-Register X und Y werden mit der Low-, be-
ziehungsweise High-Adresse der Fehlermeldung DERR gela-
den. Dieser Programmteil kehrt nicht alleine zurück,
sondern läuft in die folgende Routine hinein.
$c650 50768 $f385 62341 PUTLINE
Diese Routine erwartet im X-Register der CPU den Low-
und im Y-Register den High-Wert der Adresse, ab der ein
auszudruckender Text im Speicher steht. Die Textzeile
muß mit dem Atari-spezifischen NEWLINE($9b) enden und
darf nicht länger als 255 Zeichen sein. Die Routine
zerstört eventuelle Daten in IOCB0, da es diesen be-
nutzt (normale Screen-Bearbeitung). Außerdem darf sie
bei Betriebssystemänderungen nur zusammen mit DSKRDERR
verschoben werden (s. dort)!
$c667 50791 $f39d 62365 GETBLOCK
Beim Booten von Cassette oder Diskette wird diese Rou-
tine für jeden Block einmal aufgerufen. Wird von Cas-
sette gelesen, so hat CASSTART einen Wert ungleich Null
und es wird über JUMPTAB+$2a die Prozedur CASREADBLK
aufgerufen.
Handelt es sich um ein Disketten-Lesen, so wird DSKCMD
mit dem Wert READCMD ($52) geladen, DSKUNIT auf 1
gesetzt und über JUMPTAB+$03 die SIO-Routine DISKINTERF
aufgerufen.
$c67c 50812 $f3b2 CASBOOT
Wenn WARMFLAG auf Warmstart steht und Bit 1 von
DOSAKTIV Eins ist, wird über CASINITC die Routine
CASINIT aufgerufen; anderenfalls wird im Falle eines
Warmstarts zum aufrufenden Programm zurückgekehrt.
Handelt es sich dagegen um einen Kaltstart, kann ge-
bootet werden, wenn STARTTST mit einem Wert ungleich
Null signalisiert, daß beim Eintreffen des Kaltstarts
(also zum Beispiel beim Einschalten des Gerätes) die
START-Taste gedrückt war.
Ist oder war sie es nicht, kehrt CASBOOT zum Aufrufer
zurück.
Ansonst wird ein OPEN auf die Cassette ausgeführt,
wobei der Timeout auf 'lang' gestellt wird. Dazu exi-
stiert das Register GAPTYPE. Wird GAPTYPE auf einen
Wert zwischen Null und 127 gesetzt, wird das Lesen von
Cassette nach recht kurzer Zeit unterbrochen, wenn kein
neuer Block zu lesen ist. Bei einem größeren Wert (als
signed-Wert: negativ!) wird wesentlich länger gewartet.
Dies hat den Sinn, den Anfang eines Files auf Cassette
abzuwarten.
Der eigentliche OPEN wird mit auf 1 gesetztem CASSTART
über JUMPTAB+$2d und CASOPENIN ausgeführt, wobei danach
gleich BLOCK1 aufgerufen wird. Ist der Block richtig
gelesen, so wird von BLOCK1 DOSAKTIV mit dem Wert 2
zurückkehren. Ist dies der Fall, wird als letztes der
Inhalt von DOSINITV nach CASINITV geladen.
$c6ae 50862 $f3e1 62433 CASINITC
Es wird ein indirekter Sprung über CASINITV ausgeführt.
$c6b1 50865 $edea 60906 DISKINIT
DSKTIMOUT wird mit $a0 initialisiert und die Sektoren-
länge der Diskette in DSKSECLEN auf $0080 (also 128
Byte/Sektor) gesetzt. Hier beginnt die unter BOOT be-
schriebene Unterscheidung zwischen Block und Sektor
interessant zu werden!
$c6c1 50881 $edf0 60912 DISKINTERF
Diese Prozedur erledigt sämtlich Initialisierungsaufga-
ben zum Lesen oder Schreiben eines Sektors von Disket-
te, beziehungsweise deren Formatierung oder einfach nur
zum Lesen des Status' der Diskettenstation. Dazu werden
die Variablen DSKDEVICE, DSKSTATUS, beim Status-Komman-
do DSKBUFFER, DSKTIMOUT sowie DSKBYTCNT mit den ent-
sprechenden Werten initialisert. Diese Werte richten
sich nach der Art des Kommandos. Als solche stehen dem
Anwender folgende zu Verfügung:
Name Code in DSKCMD Erklärung
--------------------------------------------
GET SECTOR $52 ('R')
Liest Sektor Nummer DSKAUX1 mit der Länge
DSKBYTCNT von Diskette Nummer DSKDEVICE ein und
schreibt ihn ab DSKBUFFER in den Speicher.
PUT SECTOR $50 ('P')
Schreibt Sektor Nummer DSKAUX1 mit der Länge
DSKBYTCNT auf Diskette Nummer DSKDEVICE mit dem
Speicherinhalt ab DSKBUFFER voll. Im Gegensatz zum
400/800 kann der 600XL/800XL dieses Kommando auch
über das hier beschriebene Diskinterface verwer-
ten. Bei den alten Geräten mußte 'getrickst' wer-
den, um das Kommando anwenden zu können.
WRITE SECTOR $57 ('W')
Dieses Kommando führt zuerst die gleichen Operati-
onen durch wie PUT SECTOR, vollzieht jedoch nach
dem Schreiben noch ein Verify des betreffenden
Sektors.
STATUS REQUEST $53 ('S')
Hier wird eine formatierte Statusangabe von der
Diskette erwartet. Dieses Kommando setzt sich den
Wert von DSKBUFFER selbst auf DEVICSTAT, es muß
(und kann) also kein Pufferbereich für die Status-
meldung übergeben werden.
Das Kommando liefert, abgesehen von der Message
DISK_READ_OK, vier Byte in DEVICSTAT bis
DEVICSTAT+3 zurück:
DEVICSTAT Kommandostatus
Bit 0 ist gesetzt nach einem ungültigen
Kommando.
Bit 1 ist gesetzt nach dem Lesen eines
defekten Sektors
Bit 2 ist gesetzt nach einem erfolglosen
Schreibversuch über PUT SECTOR.
Bit 3 gibt im gesetzten Zustand an, daß
die Diskette schreibgeschützt ist.
Bit 4 schließlich ist gesetzt, wenn das
Laufwerk prinzipiell eingeschaltet
ist und sich nach dem Status-Kom-
mando zurückgemeldet hat.
DEVICSTAT+1 Hardwarestatus
Dieses Byte ist eine von der Dis-
kette gesandte Kopie des Status-
bytes des FD1771 Floppy-Disc-Con-
trollers, der die gesamte Schreib-
Leseverarbeitung innerhalb der Dis-
kettenstation übernimmt. Obwohl der
1771 als recht veralteter FDC ei-
nige wesentlich anders gestaltete
Hardwareeigenschaften besitzt als
die neuen Prozessoren, sind die
Statusworte bei den jeweiligen Ty-
pen gleich geblieben, so daß es
auch bei Verwendung eines anderen
FDC dieser Intelligenzstufe keine
Schwierigkeiten bei der Interpre-
tation dieses Bytes aus Disketten-
stationen anderer Hersteller geben
dürfte. Nur bei reinen Selbstbau-
projekten, die FDCs verwenden, die
wesentlich mehr Parameter zum Ar-
beiten benötigen als die 1771 /
1797- Typen, kann es hier Schwie-
rigkeiten geben.
DEVICSTAT+2 Timeout-Wert
Dieser Wert gibt an, nach wieviel
Sekunden eine Timeout-Kondition
erkannt werden soll (s. allgemeine
Beschreibung des SIO).
DEVICSTAT+3 dieses Byte ist noch ungenutzt.
FORMAT DISK $21 ('!')
Dieses Kommando erwartet als Parameter nur den
Kommandocode, die DSKUNIT sowie einen freien Spei-
cherbereich von 128 Byte ab DSKBUFFER. Bei diesem
Kommando wird der Timeout erheblich höher gesetzt,
da das Formatieren einer Diskette bekanntlich
länger als 7 Sekunden dauert.
Ist die Formatierung beendet, werden an den Benut-
zer Informationen über den Zustand seiner Diskette
zurückgeliefert. In DSKBYTCNT steht die Anzahl der
defekten, also nach dem Formatieren nicht einwand-
frei zu lesenden Sektoren, und ab DSKBUFFER stehen
die Nummern der fehlerhaften Sektoren, falls vor-
handen. Als letzter Eintrag in diesem Puffer steht
$ffff.
Diese Eigenschaft des Atari, halbdefekte Disketten
trotzdem weiter zu benutzen, also nur die defekten
Sektoren kenntlich zu machen und den Rest zu bear-
beiten, ist bei der Verwendung von Disketten durch
den starken Preisverfall in den letzten Jahren
völlig aus der Mode gekommen. Das soll keine nega-
tive Kritik an dieser Methode sein; eher im Gegen-
teil, denn die Tatsache, daß Atari hier nicht an
Progammidee gespart hat, ehrt sie und erleichtert
außerdem den softwaremäßigen Anschluß einer Hard-
disk. Wenn bei einem solchen Medium einmal ein
Sektor (von bei einer 10MegaByte-Platte ca 82.000)
defekt ist, wird nicht gleich die gesamte Platte
weggeworfen, sondern nur dieser eine Sektor ge-
kennzeichnet.
Die von DISKINTERF zu initialisierenden Werte für
DSKTIMOUT und DSKBYTCNT werden der Routine jeweils in
DSKTIMCON beziehungsweise DSKSECLEN übergeben. Die
Pufferadresse DSKBUFFER, das Kommando DSKCMD sowie die
Sektornummer (für Schreiben und Lesen) in DSKAUX1 (Low)
und DSKAUX2 (High) müssen vor dem Aufruf von DISKINTERF
in die Register eingetragen werden.
Als allgemeiner Rückgabewert ist im Y-Register ein
Statuswort enthalten, das im Fehlerfalle negativ ist.
Das N-Flag der CPU ist bei der Rückkehr aus der Routine
entsprechend dem Ergebnis gesetzt.
$c748 51016 $ee6d 61037 PUTADRESS
DSKBUFFER wird nach BUFFERADR kopiert.
$c753 51027 $xxxx xxxxx LOADER
Diese Routine wird in Verbindung mit weiteren Lade- und
Verwaltungsroutinen nicht für den momentanen Gerätepark
der Firma Atari verwendet, sondern bietet dem Anwender
die Möglichkeit, sich selbst Erweiterungen zu der Hand-
lertabelle HATABS zu fertigen und diese neuen Geräte
ebenfalls über IOCBs zu betreiben. Bekanntermaßen sind
die zeichenorientierten Devices Editor, Screen, Key-
board, Lineprinter sowie Cassette am leichtesten über
HATABS anzusprechen, es ist jedoch kein weiterer Platz
in dieser Tabelle vorhanden für eventuelle weitere
Geräte. Dieser Makel der alten 400/800-Serie wurde hier
mit Hilfe von Routinen wie zum Beispiel LINK, UNLINK
und anderen abgeholfen. Der Leser möge entschuldigen,
wenn wir aufgrund fehlender, zu diesen Ports gehörender
Hardware keinerlei Schwächen oder eventuelle Laufzeit-
fehler dieser Routinen hier feststellen können.
Zu Beginn dieser Routine werden RUNADR, HIUSED sowie
ZHIUSED gelöscht.
Danch werden über GETBYTEA zwei Byte gelesen, wobei das
erste Byte in HIBYTELD und das zweite Byte in SECLEN
abgelegt wird. Generell gilt für diese Routine, daß sie
mit gesetztem Carry und einem Y-Wert von $9c zurück-
kehrt, wenn GETBYTEA über das Carryflag einen Fehler
meldet.
Wenn HIBYTELD ungleich $0b ist, wird die Routine aufge-
rufen, deren Adresse bei NEUVEKTOR + 2*HIBYTELD steht.
Ist nach dieser Routine RECLEN gleich LCOUNT, so werden
wieder zwei Byte nach HIBYTELD und SECLEN geladen und
das Spiel geht von vorne los. Ansonsten wird nur noch
ein Zeichen über GETBYTEA gelesen und indirekt über
RUNADR die dort angegebene Routine aufgerufen, danach
LCOUNT incrementiert und, wenn ungleich Null, zurückge-
sprungen zu der Stelle, an der RECLEN mit LCOUNT ver-
glichen wurde. Ist LCOUNT hier jedoch Null, so termi-
niert die Routine.
War HIBYTELD gleich $0b, so wird GETBYTEA zweimal auf-
gerufen und die beiden gelesenen Zeichen als RUNADR
beziehungsweise RUNADR+1 - Inhalte abgelegt, also die
komplette Runadresse definiert. Ist RECLEN an dieser
Stelle gleich Null, so wird die RUNADR gleich wieder
gelöscht, ist RECLEN = $01, bleibt die RUNADR ungeän-
dert und in den übrigen Fällen wird zu dem Wert von
RUNADR der von LODADRESS addiert. In jedem Fall kehrt
jedoch die Routine mit gelöschtem Carry-Flag und einem
Y-Wert =$01 zurück zu dem aufrufenden Programm.
$c7dd 51165 $xxxx xxxxx GETBYTEAC
Es wird ein indirekter Sprung über (LODGBYTEA)
ausgeführt.
$c7e0 51168 $xxxx xxxxx RUNADRC
Es wird ein indirekter Sprung über (RUNADR) ausgeführt.
$c7e3 51171 $xxxx xxxxx PUTCHAR
Dieses Unterprogramm benötigt zwei Parameter: ein In-
formationsbyte im Accu sowie eines in LCOUNT.
Hat LCOUNT den Wert 0, so wird das Byte im Accu in
NEWLDTMP1 und NEWADRLOD abgelegt, bei einem LCOUNT-Wert
von 1 in NEWLDTMP1+1 sowie NEWADRLOD+1. Im letzteren
Fall werden noch einige Berechnungen ausgeführt, die
sich im einzelnen nach dem Wert von HIBYTELD richten:
HIBYTEL Ausgeführte 16-Bit-Berechnung
$00
oder
$0a NEWADRLOD := NEWLDTMP1 + LODADRESS
sonst NEWADRLOD := NEWLDTMP1 + LODZLOADA
Danach wird ein 16bit großes Hilfsregister auf dem
Stack eingerichtet, das den Wert
HIUSEDLOD + RECLEN - 2
enthält. Die danach ablaufenden Vergleiche richten sich
wieder nach dem Wert von HIBYTELD:
HIBYTELD 16bit-Vergleich Oper. wenn erfolgreich
$00
oder
$0a HILF = HIUSEDLOD HIUSEDLOD := HILF
sonst HILF = LODZHIUSE LODZHIUSE := HILF
Ist Hilf allgemein größer als MEMTOP, so kehrt die
Routine mit einem Y-Wert von $9D und gesetztem Negativ-
Flag in der CPU zu der vorletzten Routine in der Proze-
durhierarchie, also nicht zu dem eigentlichen Aufrufer
zurück.
Hat LCOUNT weder den Wert 0 noch 1, so wird einfach das
im Accu übergebene Byte in einer durch den Pointer
LOADERTMP spezifizierten Adresse abgelegt. LOADERTMP
wird berechnet durch LOADERTMP := NEWADRLOD + LCOUNT-2.
$c87b 51323 $xxxx xxxxx ADD28E
Es wird folgende Rechnung mit dem im Accu übergebenen
Wert durchgeführt:
LOADERTMP := NEWADRLOD + Accu
(LOADERTMP) := (LOADERTMP) + LODADRESS *** 16bit-Oper.
Die nachstehende Grafik soll dies etwas veranschau-
lichen:
NEWADRLOD H NEWADRLOD L
+ ACCU
---------------------------
LOADERTMP H LOADERTMP L
größer
BYTE H := BYTE H + LODADRESS H
BYTE L := BYTE L + LODADRESS L
kleiner
$c8a0 51360 $xxxx xxxxx ADD28EWRD
Hat HIBYTELD einen Wert größer oder gleich 4, so wird
der Wert von X gleich dem Low-Byte von LOADADRESS,
sonst gleich dem Low-Byte von LODZLOADA. Im Accu wird
ein Byte übergeben, das folgendermaßen verwendet wird:
LOADERTMP := NEWADRLOD + Accu
(LOADERTMP) := (LOADERTMP) + X *** 8bit-Operation
NEWADRLOD H NEWADRLOD L
+ ACCU
---------------------------
LOADERTMP H LOADERTMP L
größer
BYTE := BYTE + X
kleiner
$c8c3 51395 $xxxx xxxxx ADD28EGET
Wieder wird im Accu ein Byte übergeben, das den Offset
auf eine Adresse darstellt. Hat LCOUNT beim Aufruf
dieser Routine einen geradzahligen Wert, so wird die
erste Berechnung ausgeführt, ansonsten die zweite.
LOADERTMP := NEWADRLOD + Accu *** bei LCOUNT gerade
HIBYTELD := (LOADERTMP) *** 8bit-Operation
NEWADRLOD H NEWADRLOD L
+ ACCU
---------------------------
LOADERTMP H LOADERTMP L
größer
HIBYTELD := BYTE BYTE
kleiner
*** bei LCOUNT ungerade
HILF := LODADRESS + Accu + 256 * HIBYTELD
(LOADERTMP) := HILF H *** 8bit-Operation
LODADRESS H LODADRESS L
+ HIBYTELD ACCU
---------------------------
HILF H HILF L
größer
BYTE := HILF H BYTE
kleiner
$c8f2 51442 $xxxx xxxxx NEUVEKTOR
Diese Tabelle beinhaltet Ansprungpunkte für indirekte
Aufrufe aus LOADER. Die Ansprungvektoren sind im Einzelnen:
PUTCHAR
PUTCHAR
ADD28EWRD
ADD28EWRD
ADD28EWRD
ADD28EWRD
ADD28E
ADD28E
$c90a 51466 $xxxx xxxxx SWITCHROM
Diese Routine schaltet das TestROM ein und startet es
über JUMPTAB+$33 direkt in $5000 . Das Einschalten des
ROMs geschieht über Rücksezten der PORT B7 - Leitung.
Für die ordentliche Rückkehr ins Betriebssystem wird an
dieser Stelle gleich COLDSTART auf den Wert $ff (also
Kaltstart) gesetzt.
$c91a 51482 $xxxx xxxxx NEUINIT
*** ACHTUNG *** Diese Routine darf unter keinen Umstän-
den während eines Programmablaufs aufgerufen werden;
sie führt auf jeden Fall zum unkontrollierten Absturz
der Maschine!
Sie ist vorgesehen für das Austesten später zu entwik-
kelnder Hardware und ruft unter bestimmten, im normalen
Betrieb leicht zu erreichenden Bedingungen Routinen
auf, die im Bereich des Mathe-ROMs abgelegt werden
sollen. Da aber im Moment an dieser Stelle eben noch
das Mathe-ROM liegt, springt die Routine hier 'in die
Wüste'.
Trotzdem sei hier kurz die eigentlich gewünschte Funk-
tion erläutert:
Es werden, beginnend mit bit 0, nacheinander sämtliche
Bits in der Adresse NEUPORT gesetzt und nachgesehen, ob
in $d803 der Wert $80 und in $d80b der Wert $91 steht.
Ist dies der Fall, wird die Routine ab $d819 aufgeru-
fen, wo später wahrscheinlich einmal für das Gerät, das
sich eben gemeldet hat, ein IOCB und eine Handlereintra
gung eingerichtet werden sollen. Ist die Routine ab
$d819 beendet oder ist eines der beiden Bytes nicht
richtig gesetzt, wird die gleiche Abfrage mit dem näch-
sten Bit durchgeführt.
Zum Abschluß wird NEUPORT auf Null gesetzt.
$c941 51521 $e959 SIOINTERF
Diese Routine bildet den Einsprungpunkt für die SIO-
Bearbeitung über Vektor JUMPTAB+$09.
Für uns ist im Moment eigentlich nur von Interesse, daß
zuerst CRITICIO eingeschaltet wird, um von hier aus die
Routine SIO aufzurufen und nach Beendigung der Bearbei-
tung das Y-Register mit dem Wert von DSKSTATUS zu
laden, nachdem CRITICIO wieder gelöscht wurde.
Für den Fall, daß beim Eintritt in diese Routine das
Register NEUVORHDN nicht Null ist, werden allerdings
noch einige weitere Operationen durchgeführt. Zuerst
wird das als Parameter an GETLOWEST zu übergebende X-
Register mit $08 geladen und GETLOWEST aufgerufen.
Kehrt die Routine mit gelöschtem Zero-Flag zurück, so
wird X für die weitere Verwendung gerettet und $d805
aufgerufen (Mathe-ROM!). Ist das Carry-Flag als Status
aus $d805 gelöscht, wird die eben beschriebene Prozedur
mit decrementiertem X-Register wiederholt, ansonsten,
oder wenn GETLOWEST mit Z=1 zurückkommt, wird SIO auf-
gerufen und weiter verfahren wie ohne diesen Exkurs.
$c97c 51580 $xxxx xxxxx NEUIOREQ
Dieser Programmteil wird angesprochen, falls von einem
zukünftigen I/O-Device ein Interrupt kommen sollte. Für
diesen Fall wird der Wert von NEUIODREQ auf dem Stack
gerettet, um die Nummer der Position des niedrigsten
gesetzten Bits des im Accu übergebenen Bytes nun in
NEUIODREQ und gleichfalls in NEUPORT abzulegen. Danach
wird kurzerhand die (noch) im Mathe-ROM liegende Rou-
tine $d808 aufgerufen, um nach deren Beendigung die
alten Werte in NEUPORT und NEUIODREQ wiederherzustel-
len. Danach wird, wie beim I/O-Interrupt üblich, das
gerettete X- und A- Register gepopt und mit RTI zum
unterbrochenen Programm zurückgekehrt.
$c99f 51615 $xxxx xxxxx NEUDEVC1
Es wird Y mit dem Wert $01 geladen und CHECKNEWP
aufgerufen.
$c9a4 51620 $xxxx xxxxx NEUDEVC3
Es wird Y mit dem Wert $03 geladen und CHECKNEWP
aufgerufen.
$c9a9 51625 $xxxx xxxxx NEUDEVC5
Es wird Y mit dem Wert $05 geladen und CHECKNEWP
aufgerufen.
$c9ae 51630 $xxxx xxxxx NEUDEVC7
Es wird Y mit dem Wert $07 geladen und CHECKNEWP
aufgerufen.
$c9b3 51635 $xxxx xxxxx NEUDEVC9
Es wird Y mit dem Wert $09 geladen und CHECKNEWP
aufgerufen.
$c9b8 51640 $xxxx xxxxx NEUDEVCB
Es wird Y mit dem Wert $0b geladen und CHECKNEWP
aufgerufen.
$c9bd 51645 $xxxx xxxxx GETLOWEST
Diese Routine erwartet in X einen Wert zwischen 1 und
8. Je nach der Größe dieser Zahl wird mit dem 2**(8-X)
ten Bit des Wertes von NEUVORHDN begonnen zu testen, ob
es 1 ist. Mit anderen Worten wird mit dem niederwertig-
sten Bit begonnen, wenn X den Wert 8 hat und bricht die
Routine ab, wenn X den Wert 0 hat. Wurde ein beliebiges
Bit in NEUVORHDN gefunden, das den Wert 1 hat, so wird
in NEUIODREQ und NEUPORT genau nur dieses Bit gesetzt.
Beim Anschluß entsprechender Hardware an den
600XL/800XL hat diese Routine dann den pragmatischen
Wert, für jedes, in NEUVORHDN gesetzte Bit einmal einen
Request an das Peripheriegerät zu senden. Diese Routine
wird von SIOINTERF aufgerufen, wenn NEUVORHDN ungleich
Null ist, ansonsten wird sie von CHECKNEWP benutzt.
$c9d8 51672 $xxxx xxxxx NEUPORTERR
Auch diese Routine findet bei dem momentanen Hardware-
angebot keine Verwendung. Sie erhält in Y einen Wert,
der als Offset verwendet wird. Nachdem der Wert von
$d80d+Y und der von $d80e+Y auf dem Stack abgelegt
wurden, wird der Accu mit dem Wert von Adresse $024c
und das X-Register mit dem von $024d geladen, um
danach in Y den Immediatwert $92 zurückzugeben.
$c9ea 51690 $xxxx xxxxx CHECKNEWP
An diese Routine werden zwei Werte übergeben: einer im
Accu und ein zweiter in X. Sie werden in $024c und
$024d abgelegt und der momentane Zustand von CRITICIO
gesichert, um dieses Flag danach auf den Wert 1 zu
setzen.
Anschließend wird in einer Schleife GETLOWEST mit einem
X-Wert von $08 aufgerufen. Kehrt GETLOWEST mit gelösch-
tem Zero-Flag zurück, so wird NEUPORTERR aufgerufen .
Kehrt diese Routine wiedrum mit gesetztem Carry-Flag
zurück, wird der Wert im Accu nach $024c geladen (was
unsinnig erscheint, da NEUPORTERR diesen Wert gerade
erst geladen hat!?!).
Kehrt GETLOWEST mit gesetztem Z-Flag zurück, das heißt
ist kein weiterer I/O-Port vorhanden, so wird Y mit $82
geladen.
Unabhängig davon, welchen Wert bis hierhin GETLOWEST
geliefert hat, werden NEUIODREQ und gleichzeitig
NEUPORT auf Null gesetzt. Danach wird in CRITICIO der
Anfangswert zurückgeladen, der Accu mit dem Wert aus
$024c geladen, der Wert in Y nach $024d gelegt und
entsprechend dieses Wertes die CPU-Flags gesetzt und
zum Aufrufer zurückgekehrt.
Ist das Carry-Flag nach der Bearbeitung des Treibers
Null, wird einfach die Schleife ein weiteres Mal durch-
laufen, diesmal jedoch mit (von GETLOWEST) decremen-
tierten X-Wert.
$ca2f 51759 $xxxx xxxxx BITMASK
Sie wird zum Ausblenden von Bits benötigt und enthält
die Werte $80, $40, $20, $10, $08, $04, $02, $01 .
$ca37 51767 $xxxx xxxxx PREPLINK
Diese Prozedur ist zuständig für das Öffnen eines IOCB-
Kanals, der für die neue, spätere Hardware zur Verfü-
gung gestellt wird. Sie ruft INITLOAD, LINK+$06 und
DEVS2PL3+$17 auf und springt dann mitten in die
CIOMAIN-Routine, um das OPEN-Statement zu beenden.
Tritt während dieser ganzen Bearbeitung ein Fehler auf,
so wird ebenfalls mitten in CIOMAIN gesprungen, aber
diesmal mit einem Y-Wert von $82, der CIOMAIN signali-
sieren soll, daß das angeforderte Device nicht exi-
stiert.
$ca65 51813 $xxxx xxxxx TABELLEN
$cb64 52068 $xxxx xxxxx CHECKFF
Diese Routine bildet die Checksumme (mit Carry!) über
das Byte, auf das ZCHAIN zeigt und die 17 folgenden
Byte. Hat die Checksumme den Wert $ff, so kehrt CHECKFF
mit gesetztem Zero-Flag zurück, sonst ist das Flag
gelöscht. Diese Routine testet also den Inhalt eines
Handlereintrages, der über LINK beziehungsweise UNLINK
verwaltet wird.
$cb73 52083 $xxxx xxxxx FREI0
Ab hier bis $cbff sind 141 freie Byte, die für Pro-
grammerweiterungen verwendet werden können. Aufpassen
beim Systemstart mit der Checksummenbildung! Am besten
läßt man einfach einmal die Checksummenroutinen durch-
laufen und notiert die berechneten Werte für jeden der
Blöcke (s. CHECKROM1 und CHECKROM2).
$cc00 52224 $xxxx xxxxx INTERCHAR
An dieser Stelle beginnt der zweite interne Zeichensatz
von Atari. Er wird eingeschaltet durch das Setzen von
$CC in CHARBASE@ und ermöglicht dann die Verarbeitung
von Sonderzeichen wie 'äöü#' und vielen anderen, an-
stelle der Grafiksymbole.
$d000 53248 $d000 53248 IO-Baugruppen
$e000 57344 $e000 57344 STANDCHAR
Dies ist der Standard-Zeichensatz, der durch das Setzen
von $e0 in CHARBASE@ eingeschaltet wird. Er beinhaltet
Grafiksymbole.
$e400 58368 $e400 58368 EDITORVKT
$e410 58384 $e410 58384 SCREENVKT
$e420 58400 $e420 58400 KBVKT
$e430 58416 $e430 58416 LPTVKT
$e440 58432 $e440 58432 HANDLERTB
Diese Adressen bilden eine Handlertabelle, die für
jedes Device folgende Einträge enthält:
OPEN - Routinenadresse
CLOSE - Routinenadresse
GET - Routinenadresse
PUT - Routinenadresse
STATUS - Routinenadresse
SPECIAL - Routinenadresse
einen Sprungbefehl zur jeweiligen POWERON - Initia-
lisierung
ein Hilfsbyte
Device OPEN CLOS GET PUT STAT SPEC JMP POWR HILF
EDITOR ef93 f22d f249 f2af f21d f22c 4c ef6e 00
SCREEN ef8d f22d f17f f1a3 f21d f9ae 4c ef6e 00
KB f21d f21d f2fc f22c f21d f22c 4c ef6e 00
LPT fec1 ff01 fec0 feca fea2 fec0 4c fe99 00
CASS fce5 fdce fd79 fdb3 fdcb fce4 4c fcdb 00
Die Tabelle enthält jeweils die entsprechende An-
sprungadresse in hexadezimaler Form.
$e450 58368 $e450 58448 JUMPTAB
Diese Sprungtabelle ist die einzige Möglichkeit, Pro-
gramme zu entwerfen, die sowohl auf dem 400/800 als
auch auf dem 600XL/800XL laufen können. Sie beinhaltet
alle wesentlichen Einsprungpunkte von Routinen, die
nicht über indirekte Vektoren laufen können oder sol-
len. In diesem Buch finden Sie einige Referenzen auf
eben diese Tabelle, wobei alle Ansprünge der Übersicht-
lichkeit halber auf JUMPTAB+$xx und nicht direkt auf
die Sprungadresse bezogen sind.
Jeder Tabelleneintrag enthält genau einen Sprungbefehl.
$e450 58368 $e450 58368 JUMPTAB+$00
Sprung zu DISKINIT
$e453 58371 $e453 58371 JUMPTAB+$03
Sprung zu DISKINTERF
$e456 58374 $e456 58374 JUMPTAB+$06
Sprung zu CIOMAIN
$e459 58377 $e459 58377 JUMPTAB+$09
Sprung zu SIOINTERF
$e45c 58380 $e45c 58380 JUMPTAB+$0c
Sprung zu SETVBLVKT
$e45f 58383 $e45f 58383 JUMPTAB+$0f
Sprung zu SYSTEMVBL
$e462 58386 $e462 58386 JUMPTAB+$12
Sprung zu EXITVBL
$e465 58389 $e465 58389 JUMPTAB+$15
Sprung zu SIOINIT
$e468 58392 $e468 58392 JUMPTAB+$18
Sprung zu SENDENABL
$e46b 58395 $e46b 58395 JUMPTAB+$1b
Sprung zu NMIENABLE
$e46e 58398 $e46e 58398 JUMPTAB+$1e
Sprung zu CIOINIT
$e471 58401 $xxxx xxxxx JUMPTAB+$21
Sprung zu TESTROMEN
$e474 58404 $e474 58404 JUMPTAB+$24
Sprung zu RESETWARM
$e477 58407 $e477 58407 JUMPTAB+$27
Sprung zu RESETCOLD
$e47a 58410 $e47a 58410 JUMPTAB+$2a
Sprung zu CASREADBL
$e47d 58413 $e47d 58413 JUMPTAB+$2d
Sprung zu CASOPENIN
$e480 58416 $xxxx xxxxx JUMPTAB+$30
Sprung zu TESTROMEN
Dies ist kein Druckfehler, der Ansprung wird tatsäch-
lich zweimal vorgenommen. Der erste Ansprung ist ei-
gentlich ein Auffangen eines Fehlsprunges, da dort bei
der 400/800-Serie ein Sprung nach Label SIGNON stand,
bei dem die Message "ATARI COMPUTER - MEMO PAD" ausge-
druckt worden ist und dann in eine Echo-Funktion ge-
sprungen wurde. Diese Funktion wurde bei dem neuen
600XL/800XL durch das Memory-TestROM ersetzt, das nun
von hier aus angesprungen wird.
Damit diese 'Auffüll'-Funktion theoretisch von der
eigentlichen TestROM-Einschaltfunktion getrennt ist,
wurden hier diese beiden, vielleicht nur im Moment
gleichen Sprungvektoren eingesetzt.
$e483 58419 $xxxx xxxxx JUMPTAB+$33
Sprung zu TESTSTART
Dieser Ansprung ist natürlich nur dann sinnvoll, wenn
auch das TestROM eingeschaltet, oder aber sein Inhalt
in das RAM ab Adresse $5000 kopiert worden ist.
$e486 58422 $xxxx xxxxx JUMPTAB+$36
Sprung zu NEUDEVICE
$e489 58425 $xxxx xxxxx JUMPTAB+$39
Sprung zu UNLINK
$e48c 58428 $xxxx xxxxx JUMPTAB+$3c
Sprung zu LINK
$e48f 58431 $xxxx xxxxx CALLTAB
Hier liegen 6 Adressen in einer Tabelle, die auf
NEUDEVC1 bis NEUDEVCB zeigen, respektive auf das Byte
direkt vor diesen Adressen. Dies hat den Zweck, mit
Hilfe dieser Tabellenwerte die entsprechenden Device-
Routinen mit RTS aufrufen zu können.
Es dürfte allgemein bekannt sein, daß die 6502-CPU beim
Rücksprung aus einer Prozedur auf dem Stack einen
16bit-Wert als Adresse verlangt, der auf das letzte
abgearbeitete Byte der aufrufenden Routine zeigt, um
dann die danach stehenden Befehle abzuarbeiten. Diesen
Effekt kann man nun ausnutzen, um über eine Tabelle
indirekt Routinen aufrufen zu können. Dazu muß einfach
die aufzurufende Adresse-1 auf dem Stack abgelegt und
ein RTS (ReTurn from Subroutine) ausgeführt werden.
$e49b 58523 $xxxx xxxxx NEUINITC
Es folgt ein direkter Sprung zu NEUINIT.
$e49e 58526 $xxxx xxxxx FREI1
Ab hier sind 35 Byte Programmspeicher noch nicht be-
legt. Beim benutzerseitigen Belegen des Platzes ist
darauf zu achten, daß die ROM-Checksummen (s. CHECKROM1
und CHECKROM2) korrigiert werden!
$e4c0 58560 $xxxx xxxxx RTS
Hier steht ein völlig vereinsamtes RTS. Es wird von
verschiedenen Routinen benutzt.
$e4c1 58561 $e4a6 58543 CIOINIT
Diese Routine löscht alle IOCBs dadurch, daß sie die
Devicenamen CHANNELID ($0340, $0350 ...) auf $ff legt
und die PUTBYTE-Zeiger ($0346+7, $356+7, ...) auf
CIONOTOPN als Fehlermeldung legt.
$e4dc 58588 $e4c1 58561 CIONOTOPN
Wird versucht, Ausgaben über ein nichtexistentes Device
zu senden, so wird diese Routine angesprungen. Sie
liefert in Y einen Wert $85 zurück, der der aufrufenden
Schicht gleichzeitig mit dem gesetzten Negativ-Flag des
CPU-Statusbytes einen DEVICE_NOT_OPEN-Error signali-
siert.
$e4ef 58591 $e4c4 58565 CIOMAIN
Diese Hauptausgaberoutine erwartet zwei Eingabeparame-
ter:
Erstens ein zu sendendes Character im Accu und zweitens
die IOCB-Nummer * 16 des Kanals, an den das Zeichen
geschickt werden soll. Der Multiplikator 16 kommt da-
durch zustande, daß jedes IOCB 16 Byte belegt.
Zuerst werden das übergebene Zeichen und die IOCB-
Nummer in IOCBCHARZ respektive in IOCBNUMZ abgelegt, um
danach die IOCB-Nummer zu überprüfen, ob sie erstens
nicht zu groß ist (da nur 8 IOCBs zur Verfügung stehen
ist $70 die größte erlaubte Nummer), und zweitens, ob
sie überhaupt durch 16 teilbar ist. Ist eine der beiden
Bedingungen nicht erfüllt, so wird in Y der BAD_IOCB-
Error durch den Wert $86 signalisiert.
Generell kann zu den CIO-Routinen gesagt werden, daß
sie mit gesetztem Negativ-Flag zurückkehren, wenn ein
Fehler eingetreten ist. Der Fehlercode kann dann am
Wert von Y erkannt werden.
Danach wird der ab IOCBx liegende Block nach IOCBCHIDZ
kopiert, also an die ausführbare Zero-Page-Stelle.
Hier werden nun wieder einige Abfragen bezüglich noch
nicht verwendeter Konstrukte getätigt. Ist als Channel-
Id IOCBCHIDZ der Wert $7f angegeben und IOCBCMDZ nicht
$0c (POWER_ON_INIT) und HNDLRLOAD Null, so wird ebenso
ein INVALID_CODE-Error signalisiert, als ob IOCBCMDZ
kleiner als 3 (OPEN) wäre. Ist HNDLERLOAD nicht Null,
wird PREPLINK aufgerufen und ein eventuell gemeldeter
Fehler nach 'oben' durchgereicht. Tritt kein Fehler
auf, wird mit der Kommandobearbeitung fortgefahren.
Ist schon zu Beginn IOCBCMDZ gleich $0c, wird sofort
zum CLOSE - Kommando gesprungen.
Sonst wird von hier aus je nach Kommando entweder
CLOSE, CIOSTATSP, CIOREAD oder CIOWRITE aufgerufen.
Hat HNDLRLOAD einen Wert ungleich Null, wird die Rou-
tine SPECHANDL aufgerufen, sonst DEVS2PL3. Kehrt diese
Routine mit Carry = 1 zurück, wird trotzdem noch einmal
HNDLRLOAD angesprungen, sonst wird DEVICSTAT und
DEVICSTAT+1 gelöscht und COMENT aufgerufen.
$e57c 58748 $e533 58675 CIOCLOSE
Hier wird, je nach an CIO übergebenem Parameter, der
entsrechende IOCB auf FREI gesetzt und der dazugehörige
CLOSE-Treiber aus der Tabelle berechnet und angesprun-
gen.
$e597 58775 $e54e 58702 CIOSTATSP
Hier wird, ebenso wie in CIOCLOSE, der entsprechende
Treiber für STATUS respektive SPECIAL-Kommando aufgeru-
fen, wenn das angesprochene Device existiert. Wenn
nicht, wird es nach Möglichkeit eingerichtet und dann
der dazugehörige Handlerteil angesprungen.
$e5b2 58802 $e569 58729 CIOREAD
Zu Beginn wird getestet, ob dieser IOCB eventuell lese-
geschützt ist. Dies ist der Fall, wenn in IOCBAUX1Z das
Bit 2 nicht gesetzt ist. In diesem Fall würde CIOREAD
mit einem WRITE_ONLY-Error zurückkehren. Lesegeschützte
Geräte sind zum Beispiel Drucker.
Handelt es sich um ein lesbares Gerät, wird die noch
zur Verfügung stehende Pufferlänge in IOCBBUFLZ (16
Bit) auf Null verglichen. Ist es Null, wird nur ein
Character über den entsprechenden Handler eingelesen
und zum Aufrufer zurückgekehrt.
Ist die Länge des Puffers dagegen ungleich Null, gibt
es drei Möglichkeiten, das Lesen abzubrechen:
Die erste Abbruchbedingung sieht die Leseroutine, wenn
sie einen Lesefehler bemerkt, also der aufgerufene
Handler sein Carry-Flag setzt, bevor er zu CIOREAD
zrückkehrt.
Als zweites kann der IOCB ein blockorientiertes Gerät
sein (zum Beispiel Floppy). Dann wird das Lesen been-
det, wenn die Pufferlänge nach dem Lesen des letzten
Zeichens Null wurde.
Die dritte Möglichkeit ergibt sich bei zeilenorientier-
ten Geräten, wie Druckers sie darstellen. Dabei wird so
lange gelesen, bis ein NEWLINE ($9b) gefunden wird. Hat
bis dahin der Puffer gereicht, ist alles in Ordnung,
ansonsten sind alle Zeichen, die nach Füllen des Puf-
fers kamen, ignoriert worden, und das letzte Zeichen
des Puffers wurde mit dem NEWLINE überschrieben. Hat
man zum Beispiel einen Puffer mit 5 Byte Länge und eine
Zeile gelesen, die eine Länge von 8 Zeichen hatte, so
bekommt man im Puffer übergeben:
1.CHR 2.CHR 3.CHR 4.CHR NL
Außerdem wird in diesem Fall im Y-Register der
TRUNCATET_CODE übergeben.
Allgemein kann gesagt werden, daß jeder Block, gleich
welchen Formats, nach dem vom Handler aus richtigen
Lesen mit einem NEWLINE-Character endet. Dies war beim
400/800 nicht immer sichergestellt.
Die Entscheidung, ob es sich um ein block- oder zei-
chenorientiertes Gerät handelt, wird von Bit 1 von
IOCBCMDZ gefällt. Ist es Null, handelt es sich um ein
zeilenorientiertes Device.
Als letzte Operation wird in IOCBBUFLZ die korrekte,
endgültige Pufferlänge übergeben.
$e61e 58910 $e5c9 58825 CIOWRITE
Auch beim Schreiben über einen IOCB wird zuerst ge-
prüft, ob der Zugriff überhaupt gestattet ist. Dazu muß
in IOCBAUX1 das 4. Bit gesetzt sein.
Ist das Schreiben erlaubt, so wird kontrolliert, ob die
Pufferlänge Null ist. Ist dies der Fall, so wird ledig-
lich ein einziges Zeichen übertragen. Dies ist kein
Progammierfehler, sondern eine äußerst sinnvolle Ein-
richtung, über die schon der alte 400/800 verfügte: Da
der Pufferlängenzähler von CIOxxxx verändert wird, ist
es sinnvoll, davon auszugehen, daß beim Aufruf dieser
Routine wenigstens ein Zeichen übertragen werden soll,
und nicht wegen dieses einen Zeichens von der darüber-
liegenden Schicht der Pufferzähler gesetzt zu werden
braucht. Die eigentliche Übertragung läuft über den
PUT-Handlerteil des Gerätes.
Ist die Pufferlänge größer als Null, so wird das näch-
ste Zeichen aus dem Puffer gelesen und übertragen, um
danach über INCBFP und DECBUFL den Pufferpointer zu in-
beziehungsweise den Pufferlängenzähler zu decrementie-
ren. Ist beim Schreiben des Zeichens ein Fehler aufge-
treten, wird dies entsprechend über das Y-Register
mitgeteilt.
Handelt es sich bei dem Gerät um ein zeilenorientiertes
Gerät, so wird so lange in CIOWRITE verharrt, bis das
letzte übertragene Zeichen ein NEWLINE ($9b) war, an-
sonsten so lange, bis der Puffer leer ist. Ist bei
einem zeilenorientierten Gerät das letzte im Puffer
befindliche Zeichen nicht NEWLINE, so wird dieses auto-
matisch 'hinterhergeschoben'.
$e670 58992 $e4c4 58564 CIORETURN
Diese Abschlußroutine für alle CIO-Kommandos hat zwei
Einsprungpunkte: CIORETURN und CIORETURN+$02 .
CIORETURN hat als einzige zusätzliche Aufgabe das Able-
gen des an die Routine übergebenen Y-Wertes (Status der
Operation) in IOCBSTATZ zu tun gegenüber CIORETURN+$02.
Dort wird dann die von Benutzer programmierte Puffer-
adresse wieder in IOCBBUFAZ eingetragen, um dann den
kompletten Zero-Page-IOCB wieder in den Userbereich zu
übertragen, damit der nächste Programmteil den IOCB
benutzen kann. Man darf nicht vergessen, daß die CIO-
Routinen nicht in den von Benutzer zu programmierenden
Bereichen $0340 ... $03bf arbeiten, sondern sich den
gerade aktiven Bereich von oben herunterkopieren, damit
sie wesentlich schneller und effektiver in der Zero-
Page arbeiten können (vergleiche hierzu entsprechende
Literatur über Programmiermethoden bei der 6502-CPU).
Nach dem Kopieren wird HNDLRLOAD auf Null gesetzt, um
dann im Accu das letzte gelesene oder geschriebene
Zeichen, in X der IOCB-Index und in Y der Status der
letzten IOCB-Operation zurückgegeben werden. Durch das
letzte Laden des Y-Registers ist das Negativ-Flag des
Prozessor-Status-Wortes signifikant.
$e695 59029 $e63d 58941 COMPENTRY
Der Kanaloffset IOCBCHIDZ wird geprüft. Ist er größer
als 33, so gilt die ID als falsch und COMPENTRY kehrt
mit einem DEVICE_NOT_OPEN-Error zurück.
Ansonsten liegt bei HATABS+Y der Anfang des 3-Byte-
Eintrages in HATABS. Das erste Byte enthält den Namen
des Gerätes, das zweite und dritte einen Zeiger auf den
Beginn des Handlerkonstruktes, wie es zum Beispiel in
HANDLERTB für die initiell schon vorhandenen Geräte
gezeigt ist. Dieses Handlerkonstrukt beinhaltet Adres-
sen zum Betrieb der CIO-Routinen.
Diese Anfangsadresse wird nun in IOCBAUX2 und -3 abge-
legt. Danach wird der Offset des Handlers innerhalb des
Handlerkonstruktes festgestellt, der sich aus dem Kom-
mando und CMDTAB ergibt. Sodann wird der Pointer
IOCBAUX2 und IOCBAUX3 so umgelegt, daß er direkt auf
den Anfang der Handlerroutine zeigt.
Diese ganzen Vorgänge lassen sich am einfachsten an
einem Bild erklären:
leer
' '
leer
' '
leer
' '
leer
' '
leer
' '
leer
' '
Keyboardtab
'K'
Screentab
'S'
Editortab
'E'
Castab
'C'
Printertab
Y-Register 'P' HATABS
hat zum Beispiel
den Wert $06
Also wird erst in IOCBAUX2 -3 ein Pointer auf den
Eintrag 'Editortab' abgelegt und danach Editortab
selbst.
$e6bb 59067 $e633 58931 DECBUFL
Hier erfolgt ein 16bit- Decrement auf den Pufferlängen-
wert in IOCBBUFLZ . Das Zero-Flag wird gesetzt, wenn
nach dem Decrement der Zähler Null ist.
$e6c8 59080 $xxxx xxxxx DECBFP
Der Pufferzeiger in IOCBBUFAZ wird decrementiert. Es
kann keine Aussage über Flags getroffen werden, dies
ist jedoch genau wie bei INCBFP auch nicht sinnvoll, da
alle Programmentscheidungen nicht über den Pufferzei-
ger, sondern ausschließlich den Pufferlängenzähler
erfolgen.
$e6d1 59089 $e670 58992 INCBFP
IOCBBUFAZ wird 16bitweise um Eins erhöht.
$e6d8 59096 $e677 58999 SUBBFL
Hier wird die effektive Pufferlänge berechnet. Bei
zeilenorientierten Geräten ist bekanntermaßen die zu-
rückgelieferte Datenmenge unbekannt. Sie kann zum einen
dadurch bestimmt werden, daß innerhalb des Puffers nach
einem NEWLINE-Character ($9b) gesucht wird, anderer-
seits stellt diese Routine hier die Anzahl der überge-
benen Zeichen in IOCBBUFLZ als Rückgabewert zur Verfü-
gung. Sie berechnet dazu einfach IOCBBUFLZ := IOCBBUFLx
- IOCBBUFLZ , wobei x die Nummer des über IOCBCHIDZ
spezifizierten Gerätes ist.
$e6ea 59114 $e689 59017 GOHANDLER
Diese Routine ruft über CIOJUMP zu dem vorher berechne-
ten Devicehandler. Dazu wird erst das Y-Register mit
dem Wert FUNCTION_NOT_IMPLEMENTED ($92) geladen, um
sicherzustellen, daß eine Fehlermeldung erfolgt, wenn
der danach aufgerufene Handler vielleicht initiell nur
auf ein RTS führt. Nach Rückkehr aus der Handlerroutine
wird je nach überliefertem Y-Wert das Negativ-Flag
gesetzt. Das Carry-Flag ist ebenfalls immer auf Eins
gesetzt.
$e6f4 59124 $e693 59027 CIOJUMP
Hier wird genau das Verfahren angewendet, das in
CALLTAB beschrieben ist. Es wird die Anfangsadresse-1
des Handlerunterprogramms auf dem Stack abgelegt und
danach der Handler indirekt über RTS angesprungen. Die
Tatsache, daß es sich bei den dafür benötigten Tabel-
lenwerten um die Anfangsadressen-1 handelt, ist bei
Erstellung neuer Handlervektorentabelle von äußerster
Wichtigkeit. Leicht kann ein Handler dadurch funktions-
untüchtig werden, daß die falsche Adresse angegeben
worden ist. Es ist auch zu bedenken, daß die ebenfalls
bei jedem Deviceeintrag enthaltene Sprungadresse zu der
Power-up Initialisierung direkt auf die entsprechende
Routine zeigen muß, da der Power-up nicht indirekt über
diese CIOJUMP-Funktion, sondern über den direkt vor der
Adresse stehenden JMP-Opcode aufgerufen wird. Das Kon-
zept ist nicht unbedingt leicht verständlich, aber nach
einiger Gewöhnungszeit lassen sich auch dort Fehler
leicht erkennen.
$e6ff 59135 $e6b8 59064 DEVS2PL3
Dieses Unterprogramm bestimmt den Wert von IOCBDSKNZ,
also die aktuell zu benutzende Diskettenstationsnummer.
Dazu wird die Nummer gelesen, die in dem Folgebyte auf
(IOCBBUFAZ) steht. Liegt sie zwischen $31 und $39
(ASCII-Wert für die Ziffersymbole '1' .. '9'), so wird
der zugehörige Wert $00 .. $09 in IOCBDSKNZ eingetra-
gen, ansonsten ist der Defaultwert Eins.
$e712 59154 $e69e 59038 DEVICSRCH
Es wird das bei (IOCBBUFAZ) liegende Zeichen als Gerä-
tename interpretiert und in der Tabelle ab HATABS ge-
sucht. Wird ein übereinstimmender Eintrag gefunden, so
wird der Offset des Eintrags mit dem richtigen Namen zu
HATABS in IOCBCHIDZ notiert und das Carry-Flag ge-
löscht. Wird in keinem der HATABS-Einträge der gesuchte
Name gefunden, signalisiert ein gesetztes Carry-Flag
der aufrufenden Schicht einen Fehler.
$e72d 59181 $e6c9 59081 COMTAB
Diese Tabelle bildet eine Referenz für jedes IOCB-
Kommando auf einen Vektor-Eintrag in der Handlerrouti-
nentabelle ab HATABS.
Es führen die Kommandos Auf den Vektor ab Byte
3 OPEN 0
4,5 GET_RECORD 4
6,7 GET_CHARACTER 4
8,9 PUT_RECORD 6
10,11 PUT_CHARACTER 6
12 CLOSE 2
13 STATUS_REQUEST 8
14 SPECIAL 10
$e739 59193 $xxxx xxxxx LINKSOMETH
Auch dieser Programmteil ist für spätere Verwendung
weiterer Peripherieeinheiten vorgesehen, bei denen
unter Umständen der in HATABS stehende Bereich zu klein
wird. Es ist nicht zu vergessen, daß bei HATABS noch
Platz freigehalten wurde für den Betrieb von maximal 6
weiteren Geräten.
Alle diese LINK- und UNLINK-Routinen gehen davon aus,
daß ab der Adresse STARTTST eine 19 Byte große Struktur
steht. Interessant ist dabei, daß von einer Kassetten-
benutzung offensichtlich bei Benutzung dieser Konstruk-
te ganz abgesehen werden soll. Dies ist auch ganz
einsichtig, da die beiden an diesen Stellen stehenden
Adressen nur zum Booten über Cassette benutzt werden.
Geht man nun davon aus, daß dieser immense Softwareauf-
wand nur dafür getrieben wird, exclusive Hardware an
den Atari anzuhängen, dürfte klar sein, daß die dafür
vorgesehene Software nicht über Cassette gebootet wird.
Als entsprechend exclusive Hardware war zum Beispiel in
Mundpropaganda ein CP/M-Subsystem angekündigt worden;
gesehen haben wir es bis heute leider noch nicht.
Zu Beginn der Routine wird getestet, ob das WARMFLAG
gesetzt ist. Ist dies der Fall, wird ZCHAIN auf den
Direktwert STARTTST gesetzt und eine Kontrollschleife
begonnen.
Bevor jedoch diese Schleife und ihre Ausgangspunkte
besprochen werden können, sollte ein wenig Theorie
betrieben werden bezüglich der Verwendung linearer
Listen beim Atari 600XL/800XL.
Unter einer einfach verketteten linearen Liste versteht
man ein Gebilde, bei dem jedes Element einen Verweis
auf das nächste hat. Dies ist zum Beispiel oftmals bei
BASIC-Programmtexten der Fall. Es werden in einer Pro-
grammzeile die Zeilennummer, die Adresse der logisch
hierauf folgenden Zeile und danach die eigentlichen
Daten abgelegt. Hat man nun einen Zeiger auf die erste
Zeile, findet man über diese die zweite, dann die
dritte usw.. Das Verfahren ist vielleicht nicht unbe-
dingt das schnellste, es hat jedoch in Punkto Verarbei-
tung einige wesentliche Vorteile. So muß zum Beispiel
beim Einschieben einer neuen Zeile der gesamte, logisch
dahinter stehende Text nicht nach hinten verschoben
werden, sondern es wird lediglich ein Zeiger so umge-
legt, deß er auf das neue Element zeigt und der Zeiger
des neuen Elements auf das Element, auf das der Vorgän-
ger zuletzt zeigte. Damit liegt die neue Zeile logisch
mitten im Text, wo sie jedoch physikalisch (also im
Speicher) liegt, ist völlig egal.
1) Es existiert eine lineare Liste, aus der ausschnitt-
weise zwei Elemente gezeigt werden. Genau zwischen
diese beiden Elemente soll ein weiteres eingefügt wer-
den:
Element n Element n+1
neues Element
2) Dazu wird zuerst der Zeiger von Element n kopiert in
den Zeiger für das neue Element:
Element n Element n+1
neues Element
3) Nun wird der überflüssig gewordene Zeiger von Ele-
ment n so umgelegt, daß er auf das neue Element zeigt.
Von da ab ist das neue Element in der linearen Liste
enthalten.
Element n Element n+1
neues Element
Diese Form der Verkettung heißt deshalb 'einfach ver-
kettet', weil immer nur ein Zeiger (oder neudeutsch:
Link, Verweis) auf den Nachfolger (häufig als successor
bezeichnet) existiert, nicht jedoch ein Link auf den
Vorgänger, der in der englischsprachigen Literatur
Predecessor genannt wird. Solch eine Verkettung wäre
dann unter dem Begriff 'doppelt verkettete Liste' in
der Informatik bekannt.
Sicher dürfte klar sein, daß man nicht einfach ein
solches Element aus der Liste löschen kann beziehungs-
weise, daß ein genaues Protokoll für das Einfügen
(Linken) eines neuen Elements beziehungsweise das Ent-
fernen (Unlinken) eines bestehenden Elementes einer
verketteten Liste eingehalten werden muß. Würde zum
Beispiel in unserem obigen Beispiel erst Schritt 3 und
dann Schritt 2 gemacht, wären alle Elemente ab Element
n+1 unrettbar verloren:
Die Ausgangsposition bleibt wie gehabt.
Element n Element n+1
neues Element
2) Dann wird der Zeiger von Element n zum neuen Element
umgebogen:
Element n Element n+1
neues Element
Schon haben wir den Effekt, daß nun der Zeiger des
neuen Elements nicht auf eine sinnvolle Stelle zeigt
und auch nicht mehr auf das Element n+1 umgebogen
werden kann, denn wo hätte vorher notiert werden sol-
len, wo Element n+1 steht? Die Adresse stand explizit
im Link des n. Elements und wurde schlichtweg
'vermurkst'.
Beim Unlink sind die Probleme etwas anderer Natur:
Nehmen wir an, wir hätten eine lineare Liste, von der
wir drei irgendwo in der Mitte liegende Elemente be-
trachten:
Element n Element n+1 Element n+2
Soll nun Element n+1 entfernt werden, so wird lediglich
der Pointer von Element n so umgelegt, daß er denselben
Wert hat wie der von Element n+1 und schon ist Element
n+1 'umgangen':
Element n Element n+1 Element n+2
Das Problem hierbei ist, sich zu merken, welche Spei-
cherbereiche nun noch von aktuellen Listenelementen
gefüllt sind und welche im Gegensatz dazu angefüllt
sind mit nicht mehr referenzierten Elementen. Diese
Berechnungen anzustellen ist relativ zeitaufwendig und
wird deshalb beim Atari weitmöglichst umgangen. Bei
anderen Systemen (zum Beispiel dem für Atari erhält-
lichen MicroSoft-Basic) ist die gesamte Stringverarbei-
tung auf dynamischer Speicherverwaltung aufgebaut. Der
Ausdruck dynamisch besagt, daß nicht ein konstanter
Speicherraum belegt wird, sondern immer nur gerade das,
was benötigt wird. Bei Verwendung sehr vieler, sehr
kleiner Strings passiert es dann ab und an, daß man
glaubt, der Rechner 'stehe', obwohl er dann nach zwei
bis drei Minuten weiterrechnet. Dann war er in der
sogenannten Garbage-Collect-Routine, die den String-
Müll aufsammeln sollte.
Ergänzend sollte noch erwähnt werden, was mit dem Poin-
ter des letzten Elementes geschieht. Dieser zeigt nun
nicht wirr 'in die Luft', sondern er bekommt einen
definierten Wert zugewiesen, der allgemein NIL (Aus-
sprache wie bei dem Fluß) genannt wird. Er ist wie bei
Atari in dem meisten Fällen gleich Null, es gibt jedoch
auch andere Computerkonzepte, die kaum noch etwas mit
unseren Nullen und Einsen zu tun haben, so daß man
ruhig die Bezeichnung NIL verwenden sollte.
Nach diesem kurzen Exkurs über dynamische Datenverwal-
tung zurück zu unserer gerade begonnenen Schleife. Bei
diesen hier verwendeten Elementen sitzt der Link eines
jeden Elements in Byte Nummer 18 und 19, bezogen auf
die Startadresse des jeweiligen Elementes.
Zeigt zum Beispiel ZCHAIN auf ein beliebiges Element,
so verstehen wir unter dem Konstrukt ZCHAIN.LINK eben
den Link des Elementes, auf das ZCHAIN zeigt.
ZCHAIN Byte 0
Byte 1
.
.
.
Byte 18 diese beiden Bytes sind in die-
Byte 19 sem Fall ZCHAIN.LINK
Die Schleife terminiert (Ausdruck bei Schleifen,
Funktionen und Prozeduren, wenn sie durch interne Er-
eignisse beendet werden) zum Beispiel, wenn ZCHAIN.LINK
den Wert NIL liefert, es sich also bei dem behandelten
Element um das letzte der Liste handelt.
Ansonsten wird das nächste Element geprüft. Dies
geschieht einfach durch Zuweisung:
ZCHAIN := ZCHAIN.LINK
Dadurch zeigt nun ZCHAIN auf das Folgeelement. Bei
diesem wird nun geprüft, ob die Quersumme alle Einträge
vom 0. bis zum 17. Byte gleich $ff ist (CHECKFF). Ist
sie es nicht, terminiert die Schleife ebenfalls.
Ansonsten wird das Carry-Flag gesetzt (über CALLVKTC1)
und, ebenfalls darüber, CALLVKT angesprungen, was wie-
derum veranlaßt, daß ein Befehlsbyte angesprungen wird,
das an Byte 12 der durch ZCHIAN bezeichneten Struktur
steht. Bitte erinnern Sie sich, daß die Handlertabellen
ab EDITORVKT ebenfalls ab Byte 12 einen Sprungbefehl
auf die Initialisierungsroutine des entsprechenden
Handlers hatte. Somit dürfte die Funktion und der Auf-
bau der hier beim 600XL/800XL neu eingeführten Struktu-
ren klar sein: Sie sind funktional identisch mit den
Handlertabellen ab EDITORVKT und vom Aufbau unterschei-
den sie sich lediglich durch die noch weiter benötigten
Parameter zur Listenverwaltung.
Kehrt dieser Initialisierungshandler nun mit gesetztem
Carry-Flag zurück, so terminiert die Schleife eben-
falls, ansonsten wird der oben beschriebene NIL-Test
bezüglich des Folgelinks nun auf das neue Element bezo-
gen.
War zu Beginn der Routine WARMSTART gelöscht, wird
CHAINLINK auf NIL gesetzt und mit der Initialisierung
der IOCBs weiter unten im Programm begonnen.
$e76e 59246 $xxxx xxxxx
Nach Aufruf von DCBINIT, bei dem IOCB 0 mit Daten für
ein Drive mit der Nummer 1 initialisiert wird, findet
ein Aufruf von SIOINTERF über JUMPTAB+$09 statt.
Kehrt das SIO-interface mit negativem Status zurück, so
gilt dies als Fehler ähnlich den CIO-Routinen und die-
ser Fehler wird sofort 'nach oben' durchgereicht.
War die Übertragung in Ordnung, wird CHAINTP1 mit der
Summe aus MEMLO und dem Devicestatus DEVICSTAT geladen.
Ist nach diesen Operationen MEMTOP kleiner als
CHAINTP1, werden DSKAUX1 und DSKAUX2 mit $4e geladen
und DCBINIT aufgerufen. Danach wird zum Beginn der
Routine zurückgesprungen.
Ist MEMTOP größer oder gleich CHAINTP1, wird der Wert
von CHAINTMP gerettet, um danach CHAINTMP mit MEMLO zu
laden. Hiernach wird INITLOAD mit dem geretteten Low-
Byte aus CHAINTMP im Accu aufgerufen.
Kehrt diese Routine mit gesetztem Negativ-Flag zurück,
wird LINK aufgerufen. Im anderen Fall wird, abhängig
vom Carry-Flag, bei gelöschtem Carry komplett neu mit
dieser Routine begonnen, sonst wird dort weitergemacht,
wo DCBINIT mit $4e in DSKAUX1 und DSKAUX2 aufgerufen
wird.
Der einzige Ausgangspunkt aus dieser Schleife ist also
der negative Rückgabewert aus dem SIO-Interface.
$e7be 59326 $xxxx xxxxx DCBINIT
Es werden die Daten aus TABSIOINI in den Disk-Control-
Block ab DSKUNIT geladen, der im Accu übergebene Wert
nach DSKAUX1 und der in Y übergebene Wert in DSKAUX2
abgelegt, um danach SIOINTERF über die Sprungtabelle
bei JUMPTAB+$09 anzuspringen.
$e7d4 59348 $xxxx xxxxx TABSIOINI
Hier stehen die Initialisierungswerte für den DCB (s.
DCBINIT). Sie lauten im einzelnen:
DSKDEVICE $4f ist zu ignorieren
DSKUNIT $01 Laufwerk 1
DSKCMD $40 SIO-CMD GET_DATA
DSKSTAT $40 ist zu ignorieren
DSKBUFFER $02ea ist DEVICSTAT, also
normaler Ablagebereich
für Status
DSKTIMOUT $001e entspricht 30 Sekunden
Wartezeit bis Timeout
DSKBYTCNT $0004 Die Statusinformation
besteht aus vier Byte
$e7de 59358 $xxxx xxxxx INITLOAD
An diese Routine wird im Accu ein Wert für CHAINTP1H
übergeben, der als erstes dort auch abgelegt wird. Der
Low-Teil CHAINTP1L wird auf Null und TEMP3 auf $ff
gesetzt. Ist Bit 0 von CHAINTMP Eins, ist also der
Vektor CHAINTMP ungerade, so wird CHAINTMP incremen-
tiert. Damit ist sichergestellt, daß nach dieser Rou-
tine CHAINTMP auf eine gerade Adresse zeigt.
Dann wird LODADRESS mit dem Wert von CHAINTMP,
LODGBYTEA mit der Adresse INCLOAD und LODZLOADA mit dem
Wert $80 geladen, um danach LOADER aufzurufen.
$e816 59414 $xxxx xxxxx INCLOAD
Nach Incrementieren von TEMP3 wird, falls das Ergebnis
nicht Null ist, der Accu mit dem Byte ($037d + Wert von
TEMP3) geladen sowie das Carry-Flag gelöscht.
Ist TEMP3 nach dem Decrement Null, so wird es auf $80
gesetzt und DCBXINIT aufgerufen, was den DCB mit Werten
für ein NOTE_SECTOR-Kommando lädt und das SIO-Interface
aufruft. Ist der Note-Vorgang positiv verlaufen, wird
ebenfalls der Accu mit dem Byte ($037d + Wert von
TEMP3) geladen und das Carry gelöscht.
$e833 59443 $xxxx xxxxx DCBXINIT
Hier wird, analog zu DCBINIT, der Disk-Control-Block
mit vorgefertigen Werten aus SIOTAB geladen und über
JUMPTAB+$09 das SIOINTERF aufgerufen.
$e851 59473 $xxxx xxxxx SIOTAB
Hier stehen die Initialisierungswerte für den DCB für
das NOTE-Kommando (s. DCBXINIT). Sie lauten im einzel-
nen:
DSKDEVICE $00 ist zu ignorieren
DSKUNIT $01 Laufwerk 1
DSKCMD $26 NOTE_SECTOR
DSKSTAT $40 ist zu ignorieren
DSKBUFFER $03fd Hier beginnt ein sonst
nicht vom OS belegter
RAM-Bereich.
DSKTIMOUT $001e entspricht 30 Sekunden
Wartezeit bis Timeout
DSKBYTCNT $0080 Es werden 128 Byte gele-
sen.
$e85d 59485 $xxxx xxxxx SEARCHLIS
In A und Y wird der High- beziehungsweise der Low-Teil
einer Listenelementadresse übergeben, nach der hierbei
ab Adresse STARTTST gesucht werden soll. Als Rückgabe-
werte sind ebenfalls der Accu, das X-Register und das
Carry-Flag zu beachten. Diese drei Rückgabemechanismen
haben folgende Bedeutung: das Carry-Flag gibt darüber
Auskunft, ob der gesuchte Vektor gefunden wurde und ob
A und X relevante Werte enthalten.
Damit gib es nur zwei Möglichkeiten:
1) Carry = 0 : Der gesuchte Vektor wurde gefunden und
in A (High-Byte) und X (Low-Byte) zu-
rückgegeben. ZCHAIN zeigt auf die Struk-
tur, deren Link gleich dem gesuchten
Pointer ist.
2) Carry = 1 : Entweder hatte ein Link den Wert NIL
oder bei einer der untersuchten Struktu-
ren fand sich eine, deren Checksumme
nicht den Wert $ff hatte (Aufruf von
CHECKFF!).
Im ersten Fall gibt sich zum Beispiel folgendes Bild,
wenn nach LINK(n+1) gesucht wurde:
$03f9 Element1 Element2 Element n Element n+1
LINK(2) LINK(3) LINK(n+1)
ZCHAIN
$e894 59540 $xxxx xxxxx CALLVKTC1
Es wird mit gesetztem Carry-Flag mitten in die LINK-
Routine gesprungen, um als erstes über CALLVKT den
Power-up-Handler anzuspringen.
$e898 59544 $xxxx xxxxx LINK
Diese Routine reiht ein neues Strukturelement, dessen
Anfangsadresse im Accu (High-Wert) und Y (Low-Wert)
übergeben wird, an das Ende der bestehenden Liste ein.
Dazu wird als erstes das letzte Element der bestehenden
Liste gesucht (Aufruf von SEARCHLIS mit A=Y=0) und
dieser NIL-Wert so abgeändert, daß dieser Link nun auf
das neue Element zeigt (s. LINKSOMETH!). Danach wird
der Link des neuen Elements auf NIL gesetzt und direkt
an das 12. Byte der neuen Struktur gesprungen, was zur
Folge hat, daß ein dort stehender Sprungbefehl die
Programmkontrolle an die Power-up-Routine des neu zu
installierenden Devices übergibt. Kehrt diese Routine
mit gesetztem Carry zurück, so wird dieses Element
gleich wieder entfernt (Aufruf von UNLINK mit den alten
Accu- beziehungsweise Y-Werten) und zum Aufrufer zu-
rückgekehrt.
Ist dies nicht der Fall, wird geprüft, ob die aufrufen-
de Routine ein gesetztes oder ein gelöschtes Carry-Bit
übergeben hat. War es gelöscht, so werden das 16. und
das 17. Byte der neuen Struktur gelöscht (MINTLK und
GINTLK). Danach wird, unabhängig des übergebenen Carry-
Flags, MEMLO auf einen neuen Wert gesetzt:
MEMLO := MEMLO + ZCHAIN.MINTLK (16bit-Operation).
Somit kann von der aufrufenden Routine bestimmt werden,
ob nach Einhängen des neuen Elements MEMLO verändert
werden soll oder nicht. Ist das Carry gesetzt, wird
MEMLO verändert, sonst nicht. Das hat speziell dann
Sinn, wenn ganz zu Systembeginn diese Routinen aufgeru-
fen werden und sich dann alle Programme wie DOS, BASIC
und ähnliche, nach dem neuen, verschobenen MEMLO rich-
ten (MEMLO gibt die niedrigste, von dem Betriebssystem
nicht mehr benötigte Adresse in RAM an).
Danach wird die Checksumme der neuen Struktur berechnet
(CHECKFF) und das Ergebnis im Byte 15 der Struktur
abgelegt. Das Byte 15 war auch schon zum Beispiel bei
EDITORVKT ein Hilfsbyte - jetzt hat es eine Funktion
übernommen.
Es ist an dieser Stelle erwähnenswert, daß CHECKFF
neben der Z-Flag-Aussage noch im Accu die Differenz der
Checksumme zum Wert $ff zurückgibt. Dieser Wert kann
nun eingesetzt werden, damit bei weiteren Überprüfungen
die Checksumme richtig ist.
$e900 59648 $xxxx xxxxx CALLVKT
Es wird an das Byte 12 der Struktur gesprungen, auf die
ZCHAIN zeigt:
Höhere Adressen
19
18
17
16
Checkfill 15
High-Byte 14
Low-Byte 13
JMP ($4c) 12
11
10
09
08
07
06
Handler- 05
adressen 04
03
02
01
ZCHAIN 00 Niedrigere Adressen
$e912 59666 $xxxx xxxxx SETBVBLVKC
Diese Adresse beinhaltet lediglich einen Sprung nach
SETVBLVKT.
$e915 59669 $xxxx xxxxx UNLINK
Über SERACHLIS wird das in Accu und Y spezifizierte
Listenelement in der linearen Liste ab STARTTST ge-
sucht. Wird es nicht gefunden, kehrt die Routine mit
gesetztem Carry-Flag zurück.
Das gefundene Element wird entweder gelöscht, wenn
COLDSTART gesetzt, also ungleich Null ist, oder wenn
erstens das Byte 16 und das Byte 17 Null sind und
zweitens die Quersumme über die Struktur den Wert $ff
liefert (CHECKFF). In eben diesen Fällen wird das Ele-
ment auf die Art entfernt, die in LINKSOMETH eingehend
beschrieben wird.
War das Element zu löschen, kehrt UNLINK mit gelösch-
tem, sonst mit gesetztem Carry-Flag zurück.
$e959 59737 $xxxx xxxxx JMPSIOINT
Es erfolgt ein direkter Sprung zum SIOINTERFace.
$e95c $59740 $e944 59716 SIOINIT
Diese Routine ist zuständig für die Initialisierung des
POKEY. Es werden der Cassettenrecordermotor und der Ton
abgeschaltet. Weiterhin wird die NOT_COMMAND-Leitung
des seriellen Busses auf High (inaktiv) gelegt.
$e971 59761 $e959 59737 SIO
Diese Prozedur übernimmt sämtliche Koordinierungsaufga-
ben zur Steuerung des seriellen Busses.
Zuerst wird, um Fehlinterpretationen von Interrupts zu
vermeiden, CRITICIO auf Eins gesetzt und unter
DSKDEVICE nachgesehen, ob es sich bei dem angegebenen
Gerät um die Cassette handelt. Ist dies der Fall, wird
zu CASENTER, dem Cassettenbearbeitungsprogramm, ge-
sprungen. Diese Unterscheidung ist an dieser Stelle
wesentlich, da alle anderen Geräte außer der Cassette
einen internen Kontroller besitzen, der ihnen 'Intelli-
genz' verschafft.
Haben wir es mit solch einem intelligenten Gerät zu
tun, wird CASFLAG auf Null gesetzt, um nach Rückkehr
aus der Lese- beziehungsweise Schreibroutine richtig
reagieren zu können. Danach werden DRETRY und CRETRY
mit $01 beziehungsweise $0d initialisiert und die Über-
tragungsgeschwindigkeit auf ca. 19.200 Bit/Sekunde
festgelegt. Letztere Festlegung erfolgt über das Pro-
grammieren der Audiofrequenzkanäle 3 und 4, die mit $28
beziehungsweise $00 geladen werden.
Danach werden die Geräte-Identifikation, die sich aus
der Geräteart und der Gerätenummer-1 zusammensetzt, das
Kommando sowie das erste und das zweite Hilfsbyte
DSKAUX1 und DSKAUX2 in die entsprechenden Variablen ab
CMDDEVIC geladen, um danach DSKBUFPTR auf CMDDEVIC
zeigen zu lassen, damit die danach aufgerufene Komman-
doroutine weiß, an welcher Stelle sie das zu bearbei-
tende Kommando findet. BUFENDPTR zeigt auf die Stelle
direkt hinter CMDAUX2.
Sind diese Vorbereitungen erledigt, wird über PORT B
die KOMMANDO auf Low gelegt, um allen Geräten zu sagen:
"ACHTUNG, es kommt eine neue Geräteadresse!". Eben
dieses Senden wird von der Routine SENDINIT übernommen.
Hat das angesprochene Gerät einen defekten Status zu-
rückgemeldet oder sich gar ein völlig anderes Gerät
gemeldet, so wird ein Fehlercode in ERRFLAG zurückgege-
ben, der nicht Null ist. In diesem Fall, oder wenn in Y
notiert wurde, daß das Gerät ein NEGTIV_ACKNOWELEDGE
dadurch signalisierte, daß es Y auf Null setzte, wird
CRETRY decrementiert und, falls nicht Null, erneut
versucht, das Gerät anzusprechen. Gelingt dies nicht,
findet eine Fehlermeldung an das aufrufende Modul
statt.
Hat sich jedoch das Gerät ordentlich zurückgemeldet,
wird DSKSTATUS ausgelesen und überprüft, ob es einen
positiven Wert aufweist. Ist dies der Fall, so wird vor
dem ersten Senden oder Lesen eines Datensatzes noch
gewartet, bis das Device sein vorhergehendes Kommando
völlig abgearbeitet hat, oder ein Timeout-Fehler auf-
tritt. Danach wird ein Kommando-Datensatz an das Gerät
gesendet. Der Disk-Control-Block muß die entsprechenden
Parameter für das Senden beinhalten.
Nach dem ordentlichen Abarbeiten dieses Kommandos zeigt
DSKSTATUS an, ob eventuell noch Daten von dem Gerät zu
lesen sind. Ist dies der Fall, werden sie über RECEIVE
eingelesen.
Treten während der gesamten Bearbeitung keine Fehler
auf, so wird in DSKSTATUS eine $01 zurückgegeben, an-
sonsten der Fehlercode. Fehlercodes sind immer negativ
und das N-Flag ist bei Rückkehr aus SIO gesetzt.
Auf SIO zeigt JUMPVKT+$09.
$ea37 59959 $ea1a 59930 WAIT
Dieses Modul wird nach dem Absetzen eines Kommandos an
ein intelligentes Gerät benutzt, um auf die Rückmeldung
zu warten.
Diese Rückmeldung kann zum Beispiel sein, daß das Gerät
das Kommando nicht ausführen kann (zum Beispiel Schrei-
ben auf schreibgeschützte Diskette) oder aber eine
Positiv-Meldung, die hier durch die COMPLETE-Message
dargestellt wird.
Kommt eine negative Rückmeldung oder kommt bis zum
Timeout keine Rückmeldung, so wird das Y-Regster mit
$00 geladen und in ERRORFLAG der entsprechende Fehler
zum aufrufenden Modul zurückgemeldet.
$ea88 60040 $ea6b 60011 SEND
Nach Aufruf von SENDENABL wird die allgemein interrupt-
gesteuerte Ausgabe dadurch angestoßen, daß das erste
Byte aus dem durch DSKBUFPTR spezifizierten in das
Ausgaberegister SEROUT des POKEY geschrieben wird. Ist
dieses Zeichen übertragen, wird ein Interrupt ausge-
löst.
Weiter wird in dieser Routine nichts getan, als in
IRQSTS abzufragen, ob die BREAK-Taste gedrückt wurde.
Ist dies der Fall, wird die Übertragung abgebrochen.
Ansonst wird gewartet, bis vom Interrupthandler irgend-
wann einmal XMITEND auf einen Wert ungleich Null ge-
setzt wird, da dies das Zeichen für "Übertragung been-
det" ist.
Zum Abschluß wird noch der Transmitter über SENDDIS
disabled.
$eaad 60077 $ea90 60048 ISRODN
Diese "Interrupt Service Routine if Output Data Needed"
wird aufgerufen, wenn der POKEY siganlisiert, er hätte
das letzte Zeichen vom Ausgaberegister SEROUT in sein
internes Parallel/Seriell-Wandlungsregister übertragen.
Dann wird DSKBUFPTR incrementiert und kontrolliert, ob
dieser Zeiger sein durch BUFENDPTR angezeigtes Ende
erreicht hat. Ist dies der Fall, so wird das Checksum-
menbyte übertragen, soweit dies noch nicht geschehen
und das Flag CHKSUMSND gesetzt ist.
Sind noch Zeichen zu übertragen, wird das nächste Zei-
chen aus (DSKBUFPTR) genommen und an SEROUT übergeben.
$eaec 60140 $ead1 60113 ISRXD
Signalisiert der POKEY bei einem Interrupt, daß er mit
der Übertragung des im Parallel/Seriell-Wandlungsre-
gister enthalten gewesenen Byte fertig und noch kein
neues Byte in das SEROUT-Register geschrieben worden
sei, wird diese Routine aufgerufen.
Ist hier die Checksumme schon gesendet worden, wird in
XMITEND mit einem Wert ungleich Null signalisiert, daß
die serielle Ausgabe komplett beendet sei. XMITEND wird
nach Beendigung der Interrupt-Routine von SEND abge-
fragt.
$eafd 60157 $eae2 60130 RECEIVE
Handelt es sich bei dem Lesekommando um eines über die
Cassette, wird das Checksummenregister DSKCHECKSUM
gelöscht, sonst nicht. Danach werden sowohl bei Casset-
te, als auch bei jedem intelligenten Gerät die Flags
BUFFULL und RECEIVEND gelöscht. Nach dem Aufruf von
RECEIVEN, in dem das eigentliche Empfangen erst ermög-
licht wird, wird in einer Schleife abgefragt, ob
1) die BREAK-Taste gedrückt ist. Dann wird der Da-
tenempfang abgebrochen,
2) die Timeout-Bedingung über das TIMEFLAG signali-
siert wird (dies wäre der Fall, wenn TIMEFLAG den
Wert Null hätte)
oder
3) RECEIVEND einen Wert ungleich Null hätte.
In jedem der drei Fälle würde das Lesen beendet, wobei
in den ersten zweien dem aufrufenden Modul über
DSKSTATUS ein Fehler durchgereicht würde.
$eb2c 60204 $eb11 60177 ISRSIR
Diese "Interrupt Service Routine at Serial Input Ready"
wird angesprungen, wenn der POKEY bei einem Interrupt
über seine Flags die Meinung kundtut, er hätte ein
Zeichen empfangen und es sei in SERIN abzuholen.
In diesem Fall wird der POKEY-Status aus SKSTAT gelesen
und eventuelle Fehlerbits durch Schreiben in SKSTATRES
gelöscht. Danach wird getestet, ob das angeblich be-
reitliegende Zeichen richtig gelesen wurde, oder ob ein
Framing- oder Overrun-Error aufgetreten ist. In jedem
der beiden Fehlerfälle setzt die Routine DSKSTATUS
entsprechend.
Ist das Zeichen als richtig übertragen eingezeichnet,
wird über BUFFULL geprüft, ob alle einzulesenden Zei-
chen eingetroffen sind. Ist dies der Fall, ist also
BUFFULL nicht Null, wird das Zeichen, das jetzt noch
anliegt, als Checksumme interpretiert und nach dem
Lesen des Zeichens geprüft, ob sie mit der berechneten
Checksumme übereinstimmt. Ist die Prüfsumme nicht in
Ordnung, wird in DSKSTATUS ein Checksummenfehler signa-
lisiert. Unabhängig davon wird über das Setzen von
RECEIVEND signalisiert, daß RECEIVE terminieren kann.
Ist BUFFUL Null, wird das Zeichen gelesen, zu der
Checksumme addiert und in den Puffer ab (DSKBUFPTR)
geschrieben. Danach wird DSKBUFPTR incrementiert und
geprüft, ob BUFENDPTR erreicht wurde. Ist DSKBUFPTR
weiterhin kleiner als BUFENDPTR, wird die Interruptrou-
tine ohne weitere Flagänderungen verlassen.
Bei Gleichheit der beiden Pointer ist das Ende der
eigentlichen Datenübertragung gekommen. Es kann nun
noch unter Umständen eine Checksumme zu empfangen sein.
Ist dies der Fall, also ist NOCHKSUM Null, wird BUFFULL
für den nächsten Interrupt auf $ff gesetzt und die
Unterbrechungsroutine verlassen.
Ist laut NOCHKSUM keine Prüfsumme mehr von dem Sender
zu erwarten, wird NOCHKSUM für den nächsten Aufruf von
RECEIVE gelöscht und vor dem Verlassen der Routine
RECEIVEND auf $ff (Receive vollständig beendet)
gesetzt.
$eb87 60295 $eb6e 60270 LOADPTR
Es werden zwei Berechnungen durchgeführt:
DSKBUFPTR := DSKBUFFER
BUFENDPTR := DSKBUFFER + DSKBYTCNT
Das heißt nichts anderes, als daß die für den SIO
benötigeten Bufferanfangs- und Enddaten aus dem DCB in
den SIO-Control-Block übertragen werden.
$eb9d 60317 $eb84 60292 CASENTER
Dieser Punkt wird angesprungen, wenn es sich um einen
Cassetten-I/O handelt. Dazu wird als erstes unterschie-
den, ob es sich um einen Lese- oder Schreibzugriff
handelt, da beim Lesen die Eingabegeschwindigkeit ge-
messen wird, beim Schreiben dagegen festliegt.
Handelt es sich also um einen Schreibzugriff, wird der
POKEY in seinen Asynchron-Ausgabezählern 3 und 4 auf
600 Bit/Sekunde programmiert. Dann wird Über LOADPTR
der SIO-Kontrollblock initialisiert und mit SEND der
Block gesendet. CASENTER ist ebenfalls zuständig für
das Ein- und Ausschalten des Cassettenmotors.
Handelt es sich dagegen um einen Lesezugriff, wird
CASFLAG gesetzt und, ebenfalls nach Setzen der SIO-
Pufferpointer über LOADPTR, für diesen Block die Lese-
geschwindigkeit festgehalten durch Aufruf der Prozedur
BEGINREAD. Außerdem wird Timer1 so initialisiert, daß
er als Timeout-Interruptgeber fungiert.
Nach einem RECEIVE-Aufruf, in dem der Block entweder
richtig gelesen wird oder aber die Flags entsprechend
gesetzt werden, wird der Cassettenmotor abgeschaltet,
wenn dies gewünscht wurde (nur nach Übertragung des
letzten End Of File-Blocks) und zum Aufrufer zurück-
gekehrt.
$ec11 60433 $ebf0 60400 TIMER1INT
Hier wird hingesprungen, wenn bei einem Vertikal-Blank-
Interrupt der TIMCOUNT1 auf Null gegangen ist. Diese
Routine wird benutzt, um einen Timeout ('endloses'
Warten auf ein Gerät, das nicht antwortet oder nicht
antworten kann) zu erkennen und die 'hängende' Opera-
tion mit den entsprechenden Fehlermeldungen abzubre-
chen. Dazu wird einfach eine Null in TIMEFLAG geladen.
Damit TIMER1 diese Routine finden kann, wird in der
Prozedur SETTIM1V die Adresse von TIMER1INT nach
TIMER1VKT geladen.
$ec17 60439 $ebf6 60406 SENDENABL
Hier wird in SKCNTL und seinem Schattenregister ein
Senden über die serielle Schnittstelle initiiert. Wei-
terhin wird bei Cassettenbenutzung für das dabei ver-
wendete Zweitonverfahren Tonregister 2 mit der niedri-
gen Frequenz und Tonregister 1 mit der hohen Frequenz
geladen. Dann werden in IQREN@ eventuelle alte Inter-
rupts gelöscht und der Output_Data_Interrupt enabled.
Danach wird mitten in die RECEIVEN-Routine gesprungen,
wo die restlichen POKEY-Register für die Übertragung
initialisiert werden.
$ec40 60480 $ec1f 60447 RECEIVEN
Hier wird, analog zu SENDENABL, der POKEY auf asyn-
chrone Datenübertragung eingestellt und der Receiver-
Interrupt in IRQEN erlaubt.
Außerdem wird SKSTATRES beschrieben, um eventuelle alte
Fehlermeldungen zu löschen.
Hiernach steht der Einsprungpunkt für SENDENABL, bei
dem Taktkanal 3 mit 1,77MHz und Kanal 4 mit dem Ausgang
von Kanal 3 versorgt wird.
Dann werden die entsprechenden Kontrollregister
AUDICNTL1 bis AUDICNTL4 entsprechend dem Wert von
IOSOUNDEN so gesetzt, daß der "Übertragungs-Sound" nur
bei gesetztem IOSOUNDEN durchkommt.
Falls keine Cassettenoperationen gewünscht werden,
werden die Tonkanäle 1 und 2 blockiert. Sie werden nur
für das Zweitonverfahren der Cassette benötigt.
$ec84 60548 $ec63 60515 SENDDIS
Es werden die Bits 3, 4 und 5 von IRQEN@ gelöscht, was
jegliches Lesen und Schreiben über Interrupt unterbin-
det. Weiter werden die 4 Tonkontrollregister AUDICNTLx
gelöscht.
$ec9a 60570 $ec79 60537 SETTIMOUT
Diese Routine liefert das Produkt aus dem Low-Wert von
DSKTIMOUT und der Konstanten 32.
Sie liefert den High-Teil des Resultates im X-Register
und den Low-Teil im Y-Register zurück.
$eca9 60585 $ec88 60552 INTTAB
Hier liegen die drei Interruptvektoren ISRSIR, ISRODN
sowie ISRXD.
$ecaf 60591 $ec8e 60558 SENDINIT
Nach kurzem Delay, das den I/O-Baugruppen Gelegenheit
geben soll, sich einzuschwingen, wird SEND aufgerufen
und danach WAIT.
Nach Rückkehr von WAIT wird der Y-Wert in den Accu
kopiert, um die Flags zu setzen. Ist ein Timeout pas-
siert oder konnte das Gerät die Operation nicht ordent-
lich durchführen, ist das Zero-Flag gesetzt.
$ecc8 60616 $eca7 60583 COMPUTE
Diese Routine berechnet den Wert für die POKEY-Fre-
quenzregister 3 und 4 beim Cassettenbetrieb. Sie ruft
dabei ADJUST auf und wird selbst ausschließlich von
BEGINREAD angestoßen.
$ed2e 60718 $ed08 60680 ADJUST
Dieses Unterprogramm wird zur Justage der POKEY-Werte
benutzt. Bei Verwendung eines 50Hz-Gerätes wird der
Wert des Accus um $20 erhöht, falls er den Wert 124
noch nicht erreicht hat. Ist der Accu größer als $7b,
wird der Wert $7c abgezogen.
Bei der NTSC-Version erfolgt die Erhöhung nur um den
Wert $07.
$ed3d 60733 $ed14 60692 BEGINREAD
Dieser Programmteil stellt den POKEY auf die hier ge-
messene Datenübertragungsgeschwindigkeit der Cassette
ein. Dazu muß vorausgesetzt werden, daß die beiden
ersten Byte eines von Cassette zu lesenden Blockes
immer den Wert $aa haben. Dieser Hexadezimalwert ent-
spricht der Binärzahl %10101010, die sich recht gut zur
Synchronisation eignet.
Ist die Synchronisation beendet, werden zwei $55- Byte
im Datenpuffer abgelegt und die Checksumme dahingehend
initialisiert, daß sie mit den beiden $55-Bytes über-
einstimmt. Während der gesamten Routine können sowohl
Timeout- als auch BREAK-Tasten-Unterbrechungen auftre-
ten. Sie würden durch geeignete Statusmeldungen in
DSKSTATUS an das aufrufende Modul zurückgegeben und der
Cassettenmotor abgeschaltet werden.
$ede2 60898 $edbd 60861 SETTIM1V
Diese Routine setzt über JUMPTAB+$0c den Sprungvektor
für Timer1 (TIMER1VKT) auf TIMOUTINT und über
JUMPTAB+$0c den TIMER1 selbst (TIMCOUNT1) auf den in X
(HIGH) und Y (LOW) übergebenen Wert.
$edf9 60921 $edd2 60882 POKTAB
Umrechnungstabelle für COMPUTE.
$ee11 60945 $xxxx xxxxx Tabellen
$eeb1 61105 $xxxx xxxxx ADJTAB
In ADJTAB steht der Zeitwert für ADJUST bei der NTSC-
Version, in ADJTAB+1 der für die bei uns benutzte PAL-
Version.
$eebc 61116 $xxxx xxxxx NEWDEVICE
Diese Routine sucht ab Adresse HATABS nach einem Gerät
mit dem in X übergebenen Namen. Findet es diesen Ein-
trag, so kehrt die Routine mit gesetztem Carry-Flag
zurück, wobei das X-Register auf die Handleradrese des
gefundenen Eintrags zeigt (also hinter den Namen!) und
das Y-Register den Eingangszustand aufweist.
Wird das gesuchte Gerät nicht gefunden, überprüft die
Routine noch einmal alle Einträge, ob sie einen leeren
Eintrag findet. Ist dies der Fall, wird der übergebene
Accu-Wert als HIGH-Teil der Adresse und das Y-Register
als LOW-Teil der Handlertabellenadresse interpretiert
und zusammen mit dem in X übergebenen Namen als neuer
HATABS-Eintrag registriert.
In diesem Fall wird das Carry-Bit gelöscht. Ist kein
weiterer Name frei, wird das Y-Register auf $ff und das
Carry-Flag auf Eins gesetzt.
$eef9 61177 $xxxx xxxxx SPECHANDL
Dieser SPECial HANDLer ruft DCBINIT mit folgenden Para-
metern auf:
Accu Ein Zeichen von Adresse (IOCBBUFAZ)
Y Wert von IOCBDSKNZ, also die Laufwerknummer
Kehrt DCBINIT mit negativem Rückgabewert zurück, wird Y
mit NON_EXISTANT_DEVICE geladen und die Routine ist
beendet.
Anderenfalls werden folgende Register geladen:
Register geladener Wert
---------------------------------------
IOCBCHIDZ $7f ( 8bit)
IOCBPUTBZ Adr. von PUTBYTE1 - 1 (16bit)
IOCBSPARE+IOCBCHIDZ Zeich. v. (IOCBBUFAZ) ( 8bit)
IOCBSPARE+IOCBCHIDZ+1 CHAINTMP ( 8bit)
Y $01
$ef26 61222 $xxxx xxxxx PUTBYTE1
Gleich bei Eintritt in diese Routine werden drei mög-
liche Fehlerquellen überprüft:
1) Die unteren vier Bit des Accu können ungleich Null
sein. Dann wird Y mit INVALID_IOCB-NUMBER geladen
und die Bearbeitung abgebrochen.
2) Den gleichen Returnwert erhält Y, wenn der Inhalt
des X-Registers größer oder gleich $80 ist.
3) Hat STARTTST den Wert Null, wird ein
NON_EXISTANT_DEVICE im Y-Register zurückgemeldet.
Liegt keiner der drei Fälle vor, wird der X-Wert als
IOCBNUMZ verwendet und der durch IOCBCHIDZ spezifizier-
te IOCB in den für IOCBs reservierten Zero-Page-Bereich
kopiert, um mit diesen neuen Werten PREPLINK aufzuru-
fen. Kehrt diese Routine mit negativem Wert zurück,
wird ebenfalls die PUTBYTE-Routine mit einem Y-Wert von
FUNCTION_NOT_IMPLEMENTED_IN_HANDLER abgebrochen.
Im anderen Fall wird der Inhalt von IOCBPUTBZ (16 Bit)
aus dem Stack abgelegt und durch RTS dieser Vektor
angesprungen.
$ef65 61285 $xxxx xxxxx FREI1
Hier liegen sechs unbenutzte und mit Null initialisier-
te frei zu benutzende Programmspreicherbyte. Bitte bei
Belegung auf die korrekte Checksummenberechnung achten
(CHECKROM1 u.a.!).
$ef6b 61291 $xxxx xxxxx CASMOTOFFC
Ein direkter Sprung zu CASMOTOFF.
$ef6e 61294 $f3e4 POWERONA
Hier wird LASTCH gelöscht (auf $ff gesetzt), RAMTOP auf
den Wert aus RAMSIZE, SHIFTLOCK auf $40 (nur Großbuch-
staben), KEYDEFPTR auf KEYDEF und FKTDEFPTR auf FKDEF
gesetzt.
$ef8e 61326 $xxxx xxxxx INITSOME
An dieser Stelle wird im Zuge eines Kalt- oder Warm-
starts ein großer Teil der im System verwendeten Vari-
ablen initialisiert. Dazu gehören zum Beispiel
CHARBASE, CHARSTPTR, DMACNTL@, CHARCNTL, MONSTATUS,
IRQST@, IRQST, CURSORINH, TEXTINDEX, ADDRESS, TABMAP,
Diese E-Mail-Adresse ist vor Spambots geschützt! Zur Anzeige muss JavaScript eingeschaltet sein!@, TEXTSTART und andere.
$f180 61824 $f593 62867 GETCH
Diese Routine liest ein Zeichen ab der angegebenen
Cursorposition und übergibt es im Accu. Die Cursorposi-
tion wird dabei incrementiert.
$f18f 61839 $f18f 61839 GETPLT
GETPLT ist eine von GETCH benutzte Prozedur und liest
das bei ADRESS liegende Zeichen ein und wandelt es vom
internen Bildschirmcode zurück in den ATASCII-Code.
$f1a4 61860 $f5bd 62909 OUTCH
Das im Accu übergebene Zeichen wird auf CLEAR_SCREEN
($7d) und NEWLINE ($9b) überprüft und gegebenenfalls
die entsprechenden Routinen aufgerufen, ansonsten wird
die Zeichenverwaltung von OUTPLT übernommen, bevor der
Cursor incrementiert wird.
$f1ca 61898 $f5e0 62944 OUTPLT
Wenn des Bildschimausgabeflag STARTSTOP nicht Null ist,
läuft der Atari hier in einer Endlosschleife so lange,
bis es wieder gelöscht wird.
Danach wird das im Accu übergebene Zeichen an der
Cursorposition abgelegt.
In dieser Routine ist OUTCH2 ($f1e9) häufig auch von
anderen Modulen benutzter Einsprungpunkt.
$f20b 61963 $f621 63009 RETURM
RETURn from Monitor sorgt dafür, daß bei eingeschalte-
tem Grafikmodus kein Cursor zu sehen ist und versorgt
bei erfolgreich verlaufenen I/O DSKSTATUS mit dem Wert
SUCCESS.
An dieser Stelle sollte nicht ganz unerwähnt bleiben,
daß hier mitten in der Routine (kurz vor dem Ende)
umständlich um das folgende Label herumgesprungen wer-
den muß. Wir halten das für dieses recht aufgeräumte
Betriebssystem für den Ausrutscher für die Leute, die
nicht gerne Perfektes kaufen.
Somit endet die Routine RETURM bei Adresse $f22d und
nicht schon vor dem folgenden Sprung.
Ebenfalls nicht unwesentlich ist, daß die Adresse $f21e
von SWAP als Einsprungpunkt benutz wird. Es ist die
Stelle, an der der aufrufenden Schicht die Erfolgsmel-
dung signalisiert wird. Diese Adresse ist beim 400/800
$f634.
$f223 61987 $xxxx xxxxx TESTROMEN
Dies ist lediglich ein direkter Sprung zu SWITCHROM.
Bitte vor eventuellen Änderungen vorstehenden Absatz
lesen!
$f22e 61998 $xxxx xxxxx SCROLFINE
Ist Bit 7 von FINESCROL Null, wird die Routinenbearbei-
tung abgebrochen, sonst werden der Display-List-Inter-
rupt abgeschaltet, FINESCROL auf Null und der ANTIC-
Programmvektor auf $c0ce, also MASKTAB-1 gesetzt bevor
in die Routine INITSOME gesprungen wird.
$f24a 62026 $f63e 63038 EGETCH
Diese Routine wird benutzt, um eine komplette logische
Zeile (also über max. 120 Zeichen) einzugeben und edie-
ren zu können. Als Rückgabewert wird im Accu das erste
Zeichen der Zeile übergeben, falls nicht BREAK gedrückt
wurde. War dies der Fall, so wird im Accu ein NEWLINE
($9b) übergeben und das Y-Register enthält als Status
den Wert $80.
War die Eingabe in Ordnung, ist also nicht BREAK ge-
drückt worden, hat Y den Wert $01. Da beim ersten
Aufruf nur das erste Zeichen der eingegebenen Zeile
übergeben werden kann, muß nun für jedes weitere einge-
gebene Zeichen EGETCH erneut aufgerufen werden. Dabei
wird dann automatisch immer das nächste Zeichen zurück-
geliefert. Das Ende einer logischen Zeile wird dem
aufrufenden Modul ebenfallsn mit dem NEWLINE-Kode mit-
geteilt.
$f2ad 62125 $f6a1 63137 JSRIND
Es erfolgt von hier aus ein indirekter Sprung zu
(ADDRESS).
$f2b0 62128 $f6a4 63140 EOUTCH
Es wird das im Accu übergebene Zeichen an der Cursorpo-
sition abgelegt. Steuerzeichen werden verarbeitet.
$F2f8 62200 $f6dd 63197 KGETC2
Dies ist kein Einsprungpunkt, das Label wird lediglich
von der folgenden KGETCH-Routine benötigt.
$f302 62210 $f6e2 63202 KBGETCHAR
Hier wird ein Zeichen von der Tastatur gelesen und auf
verschiedene Sonderzeichen hin überprüft. Eines dieser
Sonderzeichen wäre zum Beispiel das mit dem Tastatur-
code $89. Dieses leider im 600XL/800XL nicht verfügbare
Sonderzeichen würde den Tastaturklick toggeln, das
heißt, einschalten, wenn er ausgeschaltet war und umge-
keht. Weiterhin behandelt die Routine aber auch beim
600XL/800XL zu verwendende Sonderzeichen wie zum Bei-
spiel CONTROL-1 zum Anhalten und Weiterlaufenlassen der
Bildschirmausgabe.
$f3e0 62432 $f779 63353 ESCAPE
Das ESCAPEFLG wird auf $80 gesetzt. Es wird benötigt,
um Cursorbefehle als Sonderzeichen auf dem Bildschirm
darstellen zu können.
$f3e6 62438 $f77f 63359 CURSORUP
Der Cursor wird um eine physikalisch Zeile nach oben
transportiert beziehungsweise am oberen Bildschirmrand
auf die unterste Zeile zurückgesetzt (sogenanntes wrap-
around).
$f3f3 62451 $f78c 63372 CURSORDWN
Der Cursor wird eine Zeile nach unten beziehungsweise
von der letzten Zeile aus wieder in die oberste zurück-
gesetzt.
$f400 62464 $f799 63385 CURSORLFT
Der Cursor wird eine Position nach links geschoben.
Befindet er sich zu Beginn am Anfang einer Zeile, so
erfolgt ein Zeilen-wrap-around.
$f411 62481 $f7aa 63402 CURSORRIG
Der Cursor wird eine Spalte nach links weitergeschoben.
Befindet er sich am äußersten Rand des Bildschirms,
wird er auf die erste Spalte derselben Zeile gesetzt.
$f420 62496 $f7b9 63417 CLEARSCRN
Der Bildschirm wird zusammen mit der LOGIGMAP gelöscht.
Danach wird die CURSORHOM-Funktion ausgeführt.
$fe40 62528 $f7d6 63446 CURSORHOM
Der Cursor wird in die linke obere Ecke (Home-Position)
gesetzt. Der Bildschirminhalt bleibt unverändert.
$f450 62544 $f7e6 63462 CURSORBS
Es wird die BACK-SPACE-Funktion ausgeführt, d.h., das
direkt links vom Cursor stehende Zeichen wird gelöscht
und der Cursor steht nun auf dessen Platz.
Steht der Cursor zu Beginn der Routine am Anfang der
Zeile, wird das Kommando ignoriert.
$f47a 62586 $f810 63504 CURSORTAB
Es wird in BITMASK nach der nächsten Tabulatorposition
gesucht und diese angesprungen.
Ist keine weitere Tabulatorposition in der Zeile ver-
fügbar, wird an den Anfang der nächsten Zeile gesprun-
gen.
$f495 62613 $f82d 63533 CURSOSTAB
Die Spalte, an der der Cursor bei Aufruf der Routine
steht, wird Tabulatorposition durch Setzen des entspre-
chenden Bits in BITMASK.
$f49a 62618 $f832 63538 CURSOCTAB
Eine eventuell an der Cursorspalte existente Tabulator-
position wird gelöscht.
$f49f 62623 $f837 63543 INSCHAR
An der Cursorposition wird nach Möglichkeit ein Leer-
zeichen eingeschoben (Insert).
$f4d5 62677 $f86d 63597 DELCHAR
Das Zeichen, das logisch rechts vom Cursor steht, wird
gelöscht und alle weiteren Zeichen werden 'herange-
rückt' (Delete).
$f50c 62732 $f8a5 63653 INSLINE
An der Cursorposition wird eine neue logische Zeile
eingefügt.
$f520 62752 $f8d4 63700 DELLINE
Die durch die Cursorposition bezeichnete logische Zeile
wird gelöscht. Alle anschließenden Zeilen rücken auf.
$f556 62806 $f90a 63754 BELL
Es wird 32 mal KEYCLICK aufgerufen. Das ergibt einen
ca. 0,25s langen Ton.
$f55f 62815 $xxxx xxxxx BOTTOMLIN
Diese Routine positioniert den Cursor in die unterste
Bildschirmzeile und erste Spalte. Diese Position wird
von manchen Terminals als Home-Position angesprungen
und ist immer dann sinnvoll einzusetzen, wenn man nicht
genau weiß, wo man eigentlich auf dem Schirm steht und
was für Informationen auf ihm stehen.
$f565 62821 $f917 63767 DBDDEC
ADDRESS wird zweifach decrementiert und überprüft, ob
das Register dann immer noch in den ihm vorgegebenen
Grenzen liegt oder ein SCREEN_ERROR aufgetreten ist.
$f5ac 62892 $f947 63815 CONVERT
Die durch Spalten- und Zeilennummer spezifizierte Cur-
soradresse wird in die effektive Speicheradresse umge-
rechnet.
$f60a 62986 $f9d4 63956 INCRSB
Nach einem Incrementieren der Cursorposition wird über-
prüft, ob der Cursor am Ende einer Zeile oder ab Bild-
schirmende steht.
Sollte dies der Fall sein, wird automatisch ein Scroll-
Vorgang ausgelöst.
$f6ae 63150 $fa7a 64122 SUBEND
Je nach Wert von X ($00 oder $02) wird ENDPOINTR von
PLOTROWAC oder von PLOTCOLAC abgezogen.
$f6bc 63164 $fa88 64136 ERANGE
ERANGE ist der Einsprungpunkt für den Editor. Hier wird
getestet, ob der Editor geöffnet ist und bei negativem
Ergebnis ein Öffnen vollzogen.
Danach wird überprüft, ob der Cursor innerhalb seiner
normalen Grenzen liegt. Tut er dies nicht, wird
CURSORHOM aufgerufen und ein CURSOR_OVERRUN-Error sig-
nalisiert und die letzte Returnadresse vom Stack genom-
men. Damit gelangt der Fehler also direkt zu dem
CIOMAIN aufrufenden Modul.
$f715 63253 $xxxx xxxxx JMPF21E
Es folgt ein direkter Sprung mitten in die RETURM-
Routine. Dieser Ansprung wird von SWAP benutzt.
$f718 63256 $fae4 64228 OFFCURSOR
Das Zeichen, an dem der Cursor im Moment steht, wird
wieder in den Bildschirm geschrieben - der Cursor also
abgeschaltet.
Alle jetzt folgenden Routinen mit Namen BITxxx beziehen
sich auf Tabulatorenverarbeitung:
$f723 63267 $faeb 64235 BITCON
Die Maske ab MASKTAB+Offset im Accu wird in BITMASK
abgelegt und in X 1/8 des im Accu übergebenen Offsets
zurückgegeben.
$f732 63282 $fafa 64250 BITROL
LOGICMAP bis LOGICMAP+2 wird um ein Bit nach links
geschoben. Das höchste Bit aus LOGICMAP wird im Carry
zurückgegeben.
$f73c 63292 $fb04 64260 BITPUT
Setze durch Accu spezifiziertes Bit in TABMAP.
$f74a 63306 $fb12 64274 BITCLR
Löscht das durch den Accu spezifizierte Bit in TABMAP.
$f758 63320 $fb20 64288 LOGGET
Lädt Accu mit CURSROW + 120 und läuft in die nächste
Routine hinein:
$f75d 63325 $fb25 64293 BITGET
Gibt ein gesetztes Carry-Flag zurück, wenn das durch
den Accu spezifizierte Bit gesetzt ist.
$f76a 63338 $fb32 64306 INATAC
Das im Accu übergebene Bildschirmkode-Zeichen wird in
ein ATASCII-Zeichen gewandelt, wenn es sich nicht um
ein Grafiksymbol handelt.
$f78e 63374 $fb4e 64334 LINEINSERT
Hier erfolgt das hardwareabhängige Verschieben einer
physikalischen Zeile. Es werden die Register ADDRESS,
MLTTMP sowie SAVEADR benutzt.
$f7c2 63426 $fb7b 64379 EXTEND
Diese Routine verlängert eine logische Zeile um eine
weitere physikalische.
$f7e2 63458 $fb9b 64411 CLEARLINE
Diese Routine erledigt das hardwareabhängige Löschen
(nicht: Entfernen!) einer physikalischen Zeile.
$f7f7 63479 $xxxx xxxxx DOSCROLL
Diese Routine erledigt das 'feine' Scrolling auf dem
Bildschirm sowie einen Teil der Grafikverarbeitung.
$f8b1 63665 $fc00 64512 DOBUFC
Diese Routine berechnet die Pufferlänge der momentanen
logischen Zeile, wobei die letzten Leerzeichen igno-
riert werden.
$f90c 63756 $fc5c 64604 STRBEG
BUFSTR wird auf den Anfang der durch die Cursorposition
bestimmten Zeile gesetzt.
$f918 63768 $fc68 64616 DELTIA
Diese Routine hat die Aufgabe, eine physikalische Zeile
dann zu löschen, wenn sie leer und letzte physikalische
einer logischen Zeile ist.
$f93c 63804 $fc8d 64653 TESTCNTL
Diese Prozedur durchsucht die Kontrollzeichentabelle ab
CONTROLS. Ist das Zeichen gefunden, hat X den Offset
auf den gefundenen Tabelleneintrag bezüglich CONTROLS.
Außerdem ist im Erfolgsfall das Zero-Flag gesetzt.
$f94c 63820 $fc9d 64669 PHACRS
CURSROW, CURSCOL und GRAPHEMUL werden ab TEMPROW nach
unten abgelegt.
$f957 63831 $fca8 64680 PLACRS
Die von PHACRS ab TEMPROW nach unten hin geretteten
Cursorpositionen werden wieder geladen.
$f962 63842 $fcb3 64691 SWAP
Es werden die Variablen CURSROW bis OLDGRADR+1 mit
TEXTROW bis TEXTGRAD+1 vertauscht und SWAPFLAG inver-
tiert. Diese Vertauschung muß vorgenommen werden, wenn
von Text nach Grafik innerhalb eines Bildschirmes umge-
schaltet werden soll oder umgekehrt.
$f983 63875 $fcd8 64728 KEYCLICK
An dieser Stelle wird das Klicken der Tastatur ge-
neriert.
$f997 63895 $fce4 64740 COLCR
CURSCOL wird Null, wenn SWAÜFLAG Null und GRAPHEMUL
ungleich Null ist. Ansonsten wird CURSCOL auf den Wert
von LFTMARGIN gesetzt.
$f9a6 63910 $fcf3 64755 PUTMSC
Der Wert von SCRNSTART wird nach ADDRESS geladen.
$f9af 63919 $fcfc 64764 DRAWTO
Es wird, je nach Kommando in IOCBCMDZ entweder eine
Linie gezogen (DRAW) oder ein Bereich ausgefüllt
(FILL).
Die Linie würde gezogen von OLDGRROW, OLDGRCOL zu der
in CURSROW und CURSCOL angegebenen Adresse.
$fb04 64260 $fe45 65093 TABELLEN
$fb0d 64269 $fec6 65222 CONTROLS
Diese Tabelle beinhaltet alle benutzbaren Steuerzeichen
und dahinter die entsprechende Handleradresse.
$fb11 64273 $xxxx xxxxx FKDEF
Hier stehen für jede (beim 600XL/800XL nicht einge-
baute) Funktionstaste F1..F4 16 Byte zu ihrer Bedeu-
tungsdefinition zur Verfügung.
$fb51 64337 $xxxx xxxxx KEYDEF
Hier stehen alle 192 möglichen Tastenkodes, respektive
ihre Bedeutungen.
$fc1a 64538 $ffbe 65470 CPIRQQ
Hier liegt der Keyboard-Interrupt. Es wird das
STARTSTOP-Flag bei Drücken von CONTROL-1 modifiziert,
das ATRACT-Register wird gelöscht und SRTIMER auf den
vorgewählten Delaywert KEYRPDELY gesetzt.
$fcd6 64726 $xxxx xxxxx FREI2
Es stehen hier 2 mit $00 initialisierte Bytes.
$fcd8 64728 $xxxx xxxxx KEYCLICKC
Von hier wird ein direkter Sprung zur KEYCLICK-Routine
gestartet.
$fcdb 64731 $ef41 61249 INIT
In dieser kurzen Routine wird der POKEY auf 600 Bit/Se-
kunde Datenübetragungsrate eingestellt.
$fce6 64742 $ef4c 61260 CASOPEN
Hier wird der Cassettenrecorder für die Ausgabe oder
Eingabe vorbereitet. Es wird ein Fehler zurückgemeldet,
wenn das Gerät schon geöffnet ist.
Tritt hier kein Fehler auf, wird je nach Kommando
(Schreiben oder Lesen) BEEPWAIT für einen oder zwei
Töne aufgerufen, was wiederum einen, beziehungsweise
zwei Kommandotöne zur Cassettenbedienung erzeugt. Da-
nach wird nach Drücken einer Taste der Motor hardware-
mäßig eingeschaltet und kurze Zeit gewartet, bis er
richtig läuft. Beim Schreiben wird gleich ein 20 Sekun-
den langer Startton an die Cassette gesendet und es
werden allgemein die Pufferpointer und Pufferlängenzei-
ger gesetzt.
$fd7a 64890 $efd6 61398 CASRDBYTE
Es wird ein Byte von Cassette gelesen. Ist der Puffer
leer, aber das File noch nicht zu Ende, so wird ein
neuer Datenblock eingelesen. Das Datum wird bei richti-
gem Lesen im Accu zurückgegeben und in Y ergibt sich
der Status der Operation.
$fd8d 64909 $efe9 61417 CASREADBL
Es wird ein kompletter Block von Cassette gelesen und
kontrolliert, ob es der letzte Block war. Ist dies der
Fall, wird weiter kontrolliert, wie viele Byte noch in
diesem Block enthalten sind. Die Anzahl wird in
CASBUFLIM zurückgegeben.
War das File schon komplett gelesen, erfolgt
END_OF_FILE-Errormessage.
$fdb4 64948 $f010 61456 CASPUTBYT
Das im Accu übergebene Zeichen wird im Puffer ab
CASDATA bis CASDATA+$7f abgelegt. Ist der Puffer voll
(CASBUFPTR zeigt in den Puffer hinein auf die nächste
freie Stelle), wird er automatisch geschrieben und neu
initialisiert.
$fdcc 64972 $f028 61480 STATUS
Hier wird lediglich eine Erfolgsmeldung (SUCCESS) in Y
signalisiert.
$fdcf 64975 $f02b 61483 CASCLOSE
Es wird die Cassettenbearbeitung abgeschlossen. Beim
Lesen ist dies recht einfach, beim Schreiben muß jedoch
darauf geachtet werden, daß die letzten im Puffer vor-
handenen Bytes ebenfalls auf die Cassette geschrieben
werden. Zusätzlich muß nach dem letzten Block noch ein
End_Of_Text-Block geschrieben werden. Erst danach darf
der Cassettenmotor abgeschaltet werden.
$fdfc 65020 $f058 61528 BEEPWAIT
Im Accu wird an diese Routine die Anzahl der Piep-Töne
übergeben, die ausgesendet werden sollen. Jeder der
Töne ist ca. 0,25s lang, danach folgt eine Pause von
ca. 0,1s. Nach dem letzten Ton wird KBGETCHAR aufgeru-
fen, also auf die Eingabe eines beliebigen Zeichens
gewartet.
$fe3f 65087 $f095 61589 SIOSYSBUF
Hier wird der SIO-Puffer ab DSKDEVICE für die Cas-
settenoperationen Read beziehungsweise Write vorberei-
tet. Zum Beispiel wird der Cassettenpuffer auf $03fd
gelegt und die Anzahl der zu erwartenden beziehungs-
weise zu schreibenden Zeichen auf 131. Dann wird über
JUMPTAB+$09 das SIOINTERFace aufgerufen.
Der jeweilige Kommandokode ist im Accu zu übergeben.
Wird SIOSYSBUF mit einem Write-Kommando aufgerufen, ist
die Routine höchstwahrscheinlich von WSIOSB angespro-
chen worden.
$fe7c 65148 $f0d2 61650 WSIOSB
Dies ist die Vorbereitungsroutine für das Schreiben auf
Cassette. Im Accu wird der Typ des zu schreibenden
Blockes übergeben. Dabei bedeutet
$fc Dieser Block besteht aus 128 Datenbyte
$fa Dieser Datenblock enthält weniger als
128 signifikante Byte. Die genaue Anzahl
steht in Byte 128 des Blockes. Das
heißt, es werden zwar 128 Byte gelesen,
aber nur maximal 127 verwendet.
$fe Es handelt sich hierbei um einen
END_OF_FILE-Block, bei dem alle Daten-
byte initiell Null sind.
Es muß an diese Routine kein Kommando übergeben werden.
Sie wird nur im Scheib-Fall aufgerufen.
$fe8d 65165 $xxxx xxxxx TABELLE
Sie enthält die Wertepaare
$04 $03
$80 $c0
$02 $01
$40 $e0
$1e $19
$0a $08
$fe99 65177 $ee78 61048 PHINIT
Der Wert für den Timeout beim Printer (PTIMOUT) wird
auf 30 gesetzt.
$fe9f 65183 $ee7e 61054 PHSTLO
Hier liegt für die indirekte Benutzung der Adreßwert
$02ea (DEVICSTAT).
$fea1 65185 $ee80 61056 PHCHLO
Hier liegt für die indirekte Benutzung der Adreßwert
$03c0 (PRINTBUF).
$fea3 65187 $ee81 61057 PHSTAT
Es wird versucht, den Drucker anzusprechen. Wenn dies
nicht gelingt, wird ein gesetztes Negativ-Flag in der
CPU zurückgeliefert.
$fec2 65218 $ee9f 61087 PHOPEN
Nach einem Aufruf von PHSTAT wird die Printerpufferlän-
ge auf 0 gesetzt (PRTBUFPTR). Der Status aus PHSTAT
liegt noch im Y-Register, die Flags sind nicht signifi-
kant.
$fecb 65212 $eea7 61095 PHWRITE
Der Printerpuffer PRINTBUF wird successive gefüllt bis
zu einem NEWLINE-Zeichen. Ist dann der Puffer noch
nicht voll (128 Zeichen), wird er mit Leerzeichen auf-
gefüllt.
Ist der Puffer voll, wird er über JUMPTAB+$09 und
SIOINTERF auf den Drucker geschickt.
$ff02 65282 $eedc 61148 PHCLOSE
Nach einem Aufruf von PRMODE wird geprüft, ob der
PRINTBUF leer ist und wenn nicht, wird er über PHWRITE
geleert. PHWRITE wird nicht am eigentlichen Einsprung-
punkt aufgerufen.
$ff0f 65295 $eee6 61158 SETDBC
In X und Y werden der Low- beziehungsweise High-Teil
der Pufferadresse aus PHCHLO übergeben, um damit und
mit weiteren intern zu holenden Werten die SIO für
einen Druck-Befehl zu initialisieren.
$ff3f 65343 $ef1a 61210 PHPUT
Der Wert aus PRTTIME wird nach PTIMOUT übertragen.
$ff46 65350 $ef1a 61210 PRMODE
Je nach Printmode (Normal, Doppelte_Breite oder Schmal-
druck) werden DSKCMD und DSKAUX1 initialisiert.
$ff6e 65390 $xxxx xxxxx CHECKROM1
Es wird die Checksumme über die folgenden Speicherbe-
reiche berechnet:
$c002 ... $cfff (c000,1 nicht, weil sie
selbst eine Checksumme
darstellen und deshalb
nicht mitberechnet werden
können!
$5000 ... $57ff (TestROM)
$d800 ... $dfff (Mathe-ROM)
Ist diese in CHECKSUM (LOW) und CHECKSUM+1 (HIGH) be-
rechnete Prüfsumme identisch mit den in CHECKSR0 be-
ziehungsweise CHECKSR1 programmierten Werten, erfolgt
positive Meldung durch gelöschtes Carry-Flag, sonst ist
das Carry-Flag gesetzt. Die Routine kann natürlich auch
zum Berechnen der Werte bei neu programmierten Be-
triebssystemteilen verwendet werden. Die Werte stehen
nach der Berechnung ja immer noch in CHECKSUM zur
Verfügung!
Diese und die nächste Funktion benutzen GETCHECKS.
$ff8d 65421 $xxxx xxxxx CHECKROM2
Diese Funktion liefert als Wert ein gelöschtes Carry-
Flag, wenn die in GETCHECKS für die ROM- (RAM-?)
Bereiche
$e000 ... $fff7 ($fff8, $fff9 ist Prüfsumme)
$fffa ... $ffff
berechnete Prüfsumme mit den Werten in CHECKSUM2 (Low)
und CHECKSUM2+1 (High) übereinstimmt, ansonsten ist der
Funktionswert im Carry-Flag Eins.
$ffa4 65444 $xxxx xxxxx GETCHECKS
Bei Aufruf dieser Routine erwartet diese im X-Register
einen Wert, aus dem sie den Speicherbereich erkennen
kann, dessen Checksumme sie zu dem Wert in CHECKSUM
(Low) und CHECKSUM+1 (High) addieren kann, wobei es
egal ist, ob dieser Speicherbereich RAM oder ROM selek-
tiert. Es stehen folgende Werte für X zur Verfügung,
die dann über CHKSUMTAB die entsprechenden Speicherbe-
reiche spezifizieren:
Eingabeparameter in X geprüfter Speicherbereich
-------------------------------------------------------
$00 $c002 ... $cfff
$04 $5000 ... $57ff
$08 $d800 ... $dfff
$0c $e000 ... $fff7
$10 $fffa ... $ffff
Die Routine kehrt als Seiteneffekt mit einem um den
Wert 4 erhöhten X-Register zurück. Das erleichtert das
mehrfache Aufrufen dieser Funktion zum Testen mehrerer
Speicherbereiche.
$ffd2 65490 $xxxx xxxxx CHKSUMTAB
Hier stehen die Anfangs- und um eins erhöhten Endwerte
für den Prüfsummentest GETCHECKS. Es sind demzufolge
die Werte
$c002 $d000
$5000 $5800
$d800 $e000
$e000 $fff8
$fffa $0000
enthalten.
$fff8 65528 $xxxx xxxxx CHECKSUM2
Hier und im folgenden Byte steht die Prüfsumme über die
momentan implementierten Betriebssystemteile in den
Speicherbereichen
$e000 ... $fff7
$fffa ... $ffff
Die beiden Prüfsummenbytes $fff8 und $fff9 sind selbst-
verständlich nicht enthalten. Dies wäre zwar rechne-
risch möglich, würde jedoch den Nachteil in sich bür-
gen, daß die Funktion GETCHECKS nicht mehr als Seiten-
effekt bei Neuimplementierungen von Betriebssystemtei-
len die neue, korrigierte Prüfsumme liefern würde.
$fffa 65530 $fffa 65530 NMIVKT
Dieser Vektor ist der vom Mikroprogramm der R6502-CPU
festgelegte Vektor für die Routine des Nichtmaskierba-
ren Interrupts. Er zeigt auf die Adresse PNMI.
$fffc 65532 $fffc 65532 RESETVKT
Dieser Vektor ist der vom Mikroprogramm der CPU festge-
legte Vektor für die Routine des Hardware-Reset. Er
zeigt auf die Adresse RESETCOLD.
$fffe 65534 $fffe 65534 INTVKT
Dieser Vektor ist der vom Mikroprogramm der CPU festge-
legte Vektor für die Routine des maskierbaren Inter-
rupt. Er zeigt auf die Adresse JMPIRQVKT.