Das Betriebssystem der Atari

Hauptkategorie: 8-Bitter
Erstellt: 20 November 2011
Zugriffe: 2266

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.