Anwendung des Tastaturprozessors

Dieser Artikel soll anhand eines Assembler-Beispielprogramms auf die sehr interessante Programmierung des in CMOS-Technik aufgebauten Single-Chip-Tastaturprozessors “HD 6301 VI” im ATARI ST eingehen. Der im folgenden nur noch “IKBD” (Intelligent KeyBoarD) genannte Prozessor wird neben anderen Herstellern auch von HITACHI Semiconductors produziert, die mir freundlicherweise das “HD 6301 VI - USER’S MANUAL” zur Verfügung stellten und deshalb nicht unerwähnt bleiben sollten, genausowenig wie mein guter Freund Thomas Schneider, von dem ich vor langer Zeit die Idee zum Programm in GFA-BASIC bekam. Die Interrupt-Programmierung des IKBD läßt sich aber nur in Assembler (bzw. C mit Inline-Assembler) bewerkstelligen, weshalb ich auch auf diese immer beliebter werdende Sprache auf dem ATARI ST zurückgreife.

Das “Betriebssystem” des IKBD erlaubt einige interessante Kommandos, die, wie wir noch sehen werden, in einfacher Form vom ST aus aktiviert werden können. Übrigens befindet sich in allen Rechnern der ATARI ST-Serie der gleiche IKBD mit unverändertem Betriebssystem; eine Update-Version ist meines Erachtens auch nicht nötig (abgesehen von dem an wenigen Stellen sehr unsauberen Programmierstil).

Vorneweg sei aber gesagt, daß ich in diesem Artikel nicht auf das disassemblierte ROM-Listing (4 kByte) des IKBD eingehen möchte, da sicher nur wenigen ST-Usern auch der 8-Bit-Befehlssatz des 6301 bekannt ist, eine derartige Ausführung also sinnlos und für die meisten Leser uninteressant wäre. Ich stelle es aber jedem interessierten Leser frei, das von mir kommentierte Listing als ASCII-File (ca. 2500 Zeilen) gegen Einsenden einer formatierten Leerdiskette zu erhalten, auf die ich dann das Listing kopieren kann. Meine Adresse finden Sie im Kopf des Hauptprogramms.

Vielmehr möchte ich als Abschluß des Beitrages ein vollkommen in Assembler geschriebenes Programm vorstellen, welches ähnlich dem Control-Accessory das Setzen der Uhrzeit nach jedem Reset erspart, aber durch die Anwesenheit im AUTO-Ordner keinen Speicherplatz reserviert hält.

Wie schon in der “ST-Computer” 2/89 auf den Seiten 65ff von Alex Esser berichtet, besitzt der ATARI ST mit dem IKBD eine resetfeste Software-Uhrzeit, die über den integrierten TIMER sekundenweise hochgezählt wird und auch über das aktuelle Datum Auskunft geben kann. In dem vorgestellten Programm wird die aktuelle Uhrzeit nur einmal nach jedem Kaltstart (Einschalten des Rechners) eingegeben, nach jedem weiteren Warmstart (Reset) wird das Programm aus dem AUTO-Ordner geladen und die aktuelle Zeit des IKBD in die GEMDOS-Uhr kopiert.

Achtung: Wie jeder stolze Mega ST-Besitzer bestimmt schon längst bemerkt hat, ist das Programm bei eingebauter Echtzeit-Uhr natürlich sinnlos. Aber da der 1040er letztlich Computer des Jahres 1988 geworden ist, müßte der Mammutteil der STs ohne Hardware-Uhr ausgerüstet sein.

Viele User haben noch das umständliche Control-Accessory auf ihrer Boot-Diskette, was nach dem Lesen dieses Artikels zumindest aus der Sicht einer resetfesten Uhrzeit Schnee von gestern werden müßte. Das Programm kann natürlich jederzeit aufgerufen werden, es gibt dann sekundenweise laufend die aktuelle Uhrzeit plus Datum aus dem IKBD genauso wieder, wie es auch nach einem Reset erscheint und eine Korrektur ermöglicht.

Diese Korrektur aber wird knallhart überwacht : Es können nur real existente Daten vom 01.01.1980 bis zum 31.12.2079 eingegeben werden, ebenso natürlich nur dann ein 29. Februar, wenn auch ein Schaltjahr vorhanden ist. Die Beschränkung auf 2079 stammt nicht von mir, sondern von Digital Research, die für das GEMDOS verantwortlich sind. Außerdem wäre ich, ähnlich den meisten anderen Lesern, zu diesem Zeitpunkt 113 Jahre alt und würde mich kaum mehr mit Computern beschäftigen.

Die Korrektur kann nach jeder Einheit (Tag, Monat,...) durch Druck auf [RETURN] beendet werden, dann übernimmt das Programm die unveränderten Werte. Dies erspart weniger peniblen Menschen das genaue Stellen der Sekunden.

Beendet werden kann das Programm entweder durch das Betätigen der [J]-Taste oder viel einfacher durch [RETURN]. Jede andere Taste ermöglicht eine nochmalige Korrektur der Daten.

Der Prozessor

Für diejenigen unter den Lesern, die aber noch nicht gewußt haben, daß der ATARI ST einen eigenen Tastaturprozessor besitzt, folgt vorerst eine kleine Erläuterung zur Existenzberechtigung dieses “Single-Chip-Prozessors” aus der Familie des 8-Bit 6800. Die “Freaks” können diese Abschnitte getrost überfliegen.

Damit Sie mit einem Computer wie dem ATARI überhaupt kommunizieren können, benötigen Sie eine Peripherieschnittstelle. Hier gibt es verschiedene Möglichkeiten; am ATARI sind es die RS-232 (serielle Datenübertragung), die Centronics- (8-Bit-parallel) und die DMA-Schnittstelle (8-Bit-parallel, 10 MBit/s) für Harddisk, Laserdrucker oder eine Applikation mit VME-Bus (z.B. Netzwerke mehrerer Rechner bzw. Ein-/Ausgabegeräten).

Nicht zu vergessen sind natürlich noch die Floppy (seriell, 250 kBit/s), der ROM-Port (Cartridge) und die MIDI-Schnittstelle (“Musical Instrument Digital Interface”, seriell) zum gleichzeitigen Betrieb mehrerer Synthesizer, die dem ST unter anderem bei Musik-Profis eine so große Popularität bescherte.

Aber niemand kann sich einen Personalcomputer ohne TASTATUR vorstellen. Diese für die Programmentwicklung und Textverarbeitung wichtigste Schnittstelle wird bei größeren Rechnern i.allg. durch einen eigenen Prozessor verwaltet.

So auch bei unserem ST. Ein eigener Prozessor deshalb, weil eben die Abfrage der vielen Tasten plus angeschlossener Maus und/oder Joystick selbst in einer Matrixanordnung dem Hauptrechner sehr viel Zeit wegnehmen würde. Und Zeit ist Geld -> Geld hat aber fast keiner von uns -> Ein eigener Prozessor muß her!

So also geschehen auch im ATARI. Wenn Sie nun eine der auf dem ST befindlichen 95 Tasten betätigen, merkt das als allererstes der IKBD. Dieser sendet nun den Tastaturcode über seine interne serielle Schnittstelle an die ACIA (s.u.), diese löst daraufhin bei dem “Interruptverwalter”, bei uns ein “MFP 68901”, einen Interrupt aus, der nach einer Prioritätenliste entscheidet, ob denn die CPU im Moment gestört werden darf. Das ist wichtig, da sonst z.B. während eines Floppy-Zugriffes dessen getimter Ablauf gestört werden könnte. Sind alle Hindernisse beseitigt, wird bei der CPU der endgültige Interrupt ausgelöst. Jetzt unterbricht diese also das momentane Programm und holt sich das Zeichen über ein spezielles Interrupt-Programm (welches auch den Tastaturmatrix-Scancode in den ASCII-Code verwandelt und den bekannten “Tastaturklick” erzeugt) vom Datenregister der Tastatur-ACIA in den Tastaturpuffer im RAM. Hier kann der User jetzt den ASCII-Code der gedrückten Taste über die bestehenden GEMDOS-Routinen des TOS holen. Übrigens sendet der IKBD nach jeder Mausbewegung ein ganzes Paket an Koordinaten und Daten auf diese Art und Weise indirekt an die CPU. Soweit also die Daseinsberechtigung des IKBD.

Jetzt habe ich hoffentlich auch noch die letzten 1040er-, 520er- und 260er-User neugierig gemacht, deshalb geht es jetzt ans Eingemachte.

Direkte Programmierung

Für die direkte Programmierung des IKBD ist die interne, asynchron (d.h. ohne Synchronisierungssignal) arbeitende serielle Schnittstelle der Tastatur-ACIA (6850) im ST zuständig. Diese besitzt zwei Register, das Controlregister bei $fffc00 und das Datenregister bei $fffc02. Wer hierzu Genaueres wissen möchte, sei an das ST-Profibuch von Sybex verwiesen; eine genauere Erläuterung würde hier viel zu weit führen.

Viel bequemer sind doch die Möglichkeiten, die uns das XBIOS bietet. Die dort vorhandenen Funktionen XBIOS 22 (SETTIME) und XBIOS 23 (GETTIME) nehmen mir um ein Haar die ganze Arbeit ab. Sie haben aber bekanntlich den GEMDOS-Haken, daß sie nur gerade Sekunden verarbeiten. Diese Verschwendung des Sekundentaktes umgehe ich einfach durch die eigene Programmierung dieser Routinen.

Der IKBD stellt uns durch die Funktionen $1b (Uhrzeit stellen) und $1c (Uhrzeit holen) alles Nötige zur Verfügung. Natürlich ist auch hier eine illegale Methode möglich : Die Funktionen $20 (RAM beschreiben) und $21 (RAM lesen) ermöglichen dasselbe, da das Datum im internen RAM des IKBD immer in Speicherzelle $82..$84 und die Zeit in $85..$87 zwischengespeichert wird. Die beiden legalen Funktionen machen letztendlich auch gar nichts anderes, als die genannten Speicherstellen im IKBD-RAM zu modifizieren bzw. auszulesen.

Übrigens empfiehlt es sich nicht, mit der Funktion $20 zu spielen, da die gesamten 128 Bytes des IKBD-RAMs ($0080..$00ff) so ziemlich komplett für Variablen und den Stack verbraucht werden. Das Betriebssystem befindet sich im ROM bei $f000..$ff6d (bzw. $ffed), die Interrupt-Vektoren bei $ffee..$ffff, interne Register (Timer, Sende- und Empfangsdaten,...) bei $0000..$001f. Die übrigen Speicherbereiche sind im aktiven Modus 7 nicht benutzbar und bringen bei einem Leseversuch den Wert $ff. Für die übrigen Modi (0..6) wiederum ist das Betriebssystem des IKBD nicht ausgelegt.

Die Funktion XBIOS 34 (KBDVBASE) gibt uns nach ihrem Aufruf einen Pointer (Basisadresse) auf die Startadressen der Tastatur-Interruptvektoren zurück. Das Betriebssystem des ATARI unterscheidet nämlich zwischen einem einfachen Tastendruck oder einer Mausbewegung oder Joystickmeldung oder...

Für jede Quelle, die an den IKBD angeschlossen ist, gibt es in der Tabelle der Vektoren eine eigene Interrupt-Routinenadresse (s. Tabelle 1).

Vielleicht ist noch zu erwähnen, daß der ATARI ST zwei ACIAs besitzt, eine für MIDI, die andere für den IKBD. Beide sind hardwaremäßig absolut identisch, erfüllen auch denselben Zweck:

Umformung der anliegenden 8-Bit-parallelen Daten in serielle asynchrone Übertragung und umgekehrt. Lediglich die serielle Übertragungsgeschwindigkeit beträgt genormte 31250 Baud (Bit/s) bei MIDI und 7812.5 Baud beim IKBD.

Midisys und ikbdsys sind die beiden Routinen, die direkt nach einem Ereignis der MIDI-ACIA bzw. der IKBD-ACIA aufgerufen werden. Uns sollen aber nur die Wege der für den IKBD zuständigen ACIA interessieren, da MIDI den Rahmen dieses Artikels sprengen würde.

Basisadresse + 0: enthält Adresse midivec für Midi-Routine
+ 4: vkbderr Tastatur-Error
+ 8: vmiderr MIDI-Error
+ 12: statvec Status-Routine
+ 16: mousevec Maus-Routinen
+ 20: clockvec Uhrzeit-Routine
+ 24: joyvec Joystick-Routinen
+ 28: midisys MIDI-Systemvektor
+ 32: ikbdsys Tastatur-Systemvektor

Tabelle 1: Für jede Quelle, die an den IKBD angeschlossen ist, gibt es in der Tabelle der Vektoren eine eigene Interrupt-Routinenadresse.

Überwachung

Da der IKBD jedes Vorkommnis in seinem Arbeitsbereich sofort meldet, würde ein unüberschaubarer Datensalat entstehen. Keine Maschine und erst recht kein Mensch könnte erkennen, ob jetzt gerade die Maus dran war oder einer der beiden Joysticks eine Bewegung abgab, eine Taste gedrückt oder vielleicht die aktuelle Uhrzeit gesendet wurde. Deshalb ist das Betriebssystem des IKBD so programmiert, daß immer vor einer Meldung ein sog. Header gesendet wird.

Die Interrupt-Routine in ikbdsys verwaltet dann auch die gesendeten Pakete (Kette von gesendeten Datenbytes plus Header) und verzweigt in die entsprechenden, oben aufgeführten Unterroutinen. Eine solche Unterroutine darf aber nicht länger als 1 Millisekunde dauern und muß mit dem Assembler-Mnemonic RTS (ReTurn from Subroutine) abgeschlossen sein.

In unserem Falle soll ja eine eigene Interrupt-Routine geschrieben werden, deshalb muß der Uhrzeitvektor in der obigen Tabelle auf unsere eigene Routine “umgebogen” werden. Dieser Vektor zeigt bekanntlich auf die verschwenderische 2-Sekundentaktroutine der Funktion XBIOS 23 (GETTIME).

Also, equal goes it loose: Die Adresse, auf die nach einer Uhrzeitanforderung verzweigt werden soll, steht ja bekanntlich in clockvec, also 20 Bytes über der Basisadresse, die uns der Aufruf von XBIOS 34 (KBDVBASE) zurückgegeben hat. Wenn wir jetzt diesen Vektor auf unsere eigene Routine umbiegen, würde sie bei einer Uhrzeitanforderung auch ausgeführt werden...

Der aufmerksame Leser hat sich längst gefragt: Wie mache ich denn so eine Anforderung?

Ganz einfach mit der Funktion XBIOS 25 (IKBDWS). Diese verlangt die Adresse des Strings und auch die Anzahl der Bytes-1, die gesendet werden sollen. “Bytes-1” deshalb, weil, wie der eingeweihte Assembler-Programmierer weiß, eine “dbf”-Schleife verwendet wird. Diese dekrementiert ein CPU-internes Register solange, bis dessen Inhalt kleiner als Null ist. Erst dann wird die Schleife verlassen. Wenn also ein Byte gesendet werden soll, muß eine Null übergeben werden.

Ja, aber was soll ich denn wann senden? Tja, die Auflistung der erlaubten und dokumentierten Befehle an den IKBD findet man z.B. imo.g. ST-Profibuch oder den ähnlichen Systemfachbüchern zum ATARI ST. Ich möchte mich neben den schon erwähnten Befehlen $20 und $21 auf die Befehle $1b (Uhrzeit stellen) und $1c (Uhrzeit holen) beschränken. Der Befehl $1b, Uhrzeit stellen, ist sehr einfach aufgebaut. Die Zeit wird immer als gepackte BCD-Zahl gesendet. Eine gepackte BCD-Zahl ist eine im Hexadezimalsystem dargestellte Dezimalzahl, z.B. für (dez 29) kommt dann $29 (= dez 41). Klar? Wenn nicht, dann nochmal lesen.

Gesendet wird zuerst der Header $1b, dann ein gepacktes BCD-Byte als Jahreszahl (z.B. $89 für 1989), ein Byte Monat (z.B. $01 für Januar), ein Byte Tag (z.B. $31 für den 31.), ein Byte Stunde (z.B. $22 für 22 Uhr), ein Byte Minute (z.B. $39 für 39.Minute) und ein Byte Sekunde (z.B. $51 für 51.Sekunde). Der ganze String sähe also wie folgt aus :

setdata dc.b $1b,$89,$01,$31,$22,$39,$51

Nibbles (Halbbytes), die keine Dezimalzahlen (0..9) enthalten, also z.B. $8a für die Jahreszahl, werden vom IKBD ignoriert. In diesem Fall wird demnach nur die 8 gesetzt, der andere Wert bleibt erhalten. Nach jedem Kaltstart des IKBD enthält die Uhr nur Nullen; also wird die Jahreszahl $80 eingetragen. Übrigens startet die IKBD-Uhr leider erst dann, wenn sie einmal gestellt wurde!

Das ganze Programm zum Senden der obigen Uhrzeit sähe dann so aus (die Zeilen 210..212 im Listing bewirken genau dasselbe):

settime pea     setdata(pc) Stringadr. auf Stack
        move.w  #6,-(a7)    Anzahl Bytes-1
        move.w  #25,-(a7)   XBIOS-Funktion 25..
        trap    #14         ..ausführen
        addq.l  #8,a7       Stack korrigieren

So, jetzt wollen wir nur noch die Uhrzeit lesen; sonst wäre ja alles umsonst gewesen. Dazu müssen wir die Clock-Interrupt-Routine schreiben, also die Routine, die nach einer Uhrzeitanforderung über den indirekten Interrupt des IKBD angesprungen wird. Sie könnte so aussehen (Zeilen 405..409 im Listing):

clock   moveq   #5,d0           (5+1 )-Bytes sollen
                                gelesen werden 
        lea     leseuhr(pc),a1  a1 zeigt auf reser-
                                vierten Speicherplatz
repeat  move.b(a0)+,(a1)+       Inhalt von a0 -> Inhalt von a1 kopieren
        dbf     d0,repeat       Schleife ausführen.,
        rts                     ..und mit “RTS” abschließen

        section bss 
save_old ds.l 1                 Speicher für alten Vektor (s.u.)
leseuhr  ds.b 6                 6 Bytes im Speiche (BSS) reservieren, 
                                Clock-Header wird nicht mitkopiert!

Die Interrupt-Routine wäre fertig, jetzt muß nur noch der zuständige Vektor “umgebogen” werden, um bei einem Interrupt auch auf die obige Routine zu springen (ab Zeile 16 im Listing):

vektor  move.w  #34,-(a7)       ACIA-Vektorfeld.,
        trap    #14             ..holen
        addq.l  #2,a7           Stack korrigieren
        move.l  d0,a0           Basisadresse nach a0

        lea     save_old(pc),a1 Speicher für alten Vektor
        move.l  20(a0),(a1)     alten Vektor im Speicher sichern
        lea     clock(pc),a2    obige Interrupt-Routine..
        move.l  a2,20(a0)       ..wird jetzt installiert

Das wär’s... Nach jedem Senden von $1c, halt Moment, das geht etwa so (Zeilen 363..367 im Listing):

gettime pea     getdata         Stringadr. auf Stack
        clr.w   -(a7)           Anzahl zu sendender
                                Bytes-1 = 0 
        move    #25,-(a7)       XBIOS25..
        trap    #14             ..ausführen
        addq.l  #8,a7           Stack korrigieren

        section data 
getdata dc.b    $1c             Das soll gesendet werden

Jetzt wird nach jedem Senden von $1c die aktuelle Uhrzeit in den Speicher ab leseuhr geschrieben; logischerweise im gleichen gepackten BCD-Format wie beim Stellen der Uhrzeit.

Aber leider stört uns ja noch eines : Wir wissen nicht, wann der Interrupt auftreten wird. Eine zu früh erfolgte Abfrage von leseuhr bringt entweder ein total falsches Ergebnis oder die vorherige Uhrzeit, je nachdem, was im reservierten Speicher stand. Eine zu späte Abfrage (mit riesigem Zeit-Sicherheitsabstand) führt uns auch nicht weiter, denn das kostet unnötig verbrauchte Rechenzeit. Aber wie für (fast) alles im Soft- und Hardwarebereich gibt es auch hier einen kleinen Trick: Man schreibt z.B. an die Stelle des Tags in “leseuhr” einen Wert, der niemals als Tag auftreten kann, z.B. ein $ff. Eine ständige Abfrage dieses $ff nach dem Anfordern der neuen Zeit (s. gettime) wird zwangsweise so lange durchgeführt, bis ein anderer Wert vom Interrupt eingetragen ist. Hierbei ist es gleich, welches Byte von leseuhr abgefragt wird, da die neue Uhrzeit immer in einem Interrupt eingetragen und somit immer komplett kopiert wird. Erst danach fährt die CPU mit dem normalen Programm fort. Somit haben wir genau SO lange gewartet, wie es nötig war.

Das nachfolgende Programm, komplett auf dem DevpacST-Assembler V2.0 von HiSoft (Markt & Technik) geschrieben, zeigt eine mögliche Realisierung des oben beschriebenen Zusammenhangs; eine einführende Beschreibung wurde schon ganz am Anfang des Artikels gemacht. Eine Anpassung an die Syntax anderer Assembler dürfte keine Probleme bereiten, sofern diese den vollen MOTOROLA-Befehlssatz unterstützen. Wenn nicht die Längen “.B” oder “.L” angegeben wurden, handelt es sich um “.W” (Wortlänge). Nur noch wenige Assembler verlangen auch die (Standard-) Angabe der Wortlänge. Die ausführliche Dokumentierung dürfte das Programm auch dem Einsteiger verständlich machen, obwohl es so kurz wie möglich geschrieben wurde und darunter bestimmt die Modularität leiden mußte. Das Programm läuft schon seit einiger Zeit fehlerfrei auf verschiedenen Rechnern; da nur legale Adressen und Aufrufe benutzt werden, gibt es keine Komplikationen zwischen altem TOS und Blitter-TOS. Sollten die folgenden Betriebssysteme für den ST immer noch nicht die IKBD-Uhr unterstützen, so läuft das Programm mit Sicherheit auch auf diesen Versionen.

***************************************************
* Dieses Programm aktiviert die Tastaturprozessor-*
* uhr nach einem Kaltstart, um dann z.Eingabe der *
* aktuellen Uhrzeit aufzufordern. Das Programm    *
* erkennt selbständig, ob d. Tastaturuhrzeit noch *
* gestellt werden muss, oder sie schon aktiviert  *
* ist. Im letzten Fall wird die laufende Uhr auch *
* angezeigt, stellen ist aber auch hier möglich.  *
* (c) Sieghard Schäfer, bis 22.Oktober 1988       *
* (auf DevpacST-Assembler)                        *
***************************************************

    move.l  4(a7),a0        Basepage
    move.l  $c(a0),d0       Text
    add.l   $1c(a0),d0      BSS
    add.l   #$100,d0        Basepage

    move.l  d0,-(a7)        Länge
    move.l  a0,-(a7)        Adresse
    clr.w   -(a7)           Dummy
    move.w  #$4a,-(a7)      SETBLOCK
    trap    #1

    move.w  #34,-(a7)       KBDVBASE
    trap    #14             ACIA-Vektorfeld..
    move.l  d0,a0           ..Basis-Adr. in a0

    lea     basis(pc),a1
    move.l  a0,(a1)         Basis sichern
    lea     altv(pc),a1     alten Clock-Vektor.,
    move.l  20(a0),(a1)     ..sichern
    lea     keyin(pc),a1    eig. Clock-Vektor.,
    move.l  a1,20(a0)       ..übernehmen
*********************************************
    move.w  #4,-(a7)        GETREZ
    trap    #14             Auflösung testen
    lea     16(a7),a7       kompl. Stack korrig.
    lea     color(pc),a0    ursprüngl. Auflösung.,
    move.w  d0,(a0)         ..nach 'color'
    btst    #1,d0           Monochrom ?
    bne.s   mono
*********************************************
    moveq   #1,d0           nein, dann Medium.,
    bsr     res             ..einstellen

    moveq   #3,d1           Farbe aus Palette 3..
    bsr     get_pal         ..holen,..
    lea     farbe(pc),a0
    move.w  d0,(a0)         ..in 'farbe' sichern

    clr.w   d1              Wert aus Palette 0..
    bsr     get_pal         ..holen und..
    eor.w   #$777,d0        ..EXOR in.,
    moveq   #3,d1           ..Palette 3..
    bsr     set_pal         ..schreiben
*********************************************
mono        bsr clock       $1c (Uhrabfrage senden)

    lea     flag(pc),a0     Flag (Zeit gestellt?)
    move.w  #$1000,d0       Warteschleife
time        tst.b 2(a6)     Interrupt bearbeitet?
    bne.s   lauft           ja, Bildschirm aufbauen
    dbf     d0,time         nein, weiter warten
    move.w  #-1,(a0)        Flag setzen
    moveq   #2,d0           Uhrzeit m. Wert aus..
in_time     move.w -8(a6),(a6)+ ..'save' setzen + ins..
    dbf     d0,in_time      ..Sendereg. kopieren
    subq.l  #6,a6           a6 wieder korrigieren
    bsr     prozess         Datum & Uhr > Prozessor
    bra.s   na_ja           Bildschirm aufbauen

lauft       clr.w (a0)      Flag löschen

na_ja       lea init(pc),a0  VT52-Initialis.
    bsr     text            ..ausführen

    bsr     star            Sterne oben zeichnen

    lea     text1(pc),a0    Text1..
    bsr     text            ..schreiben

    moveq   #30,d7          31-mal..
    moveq   #-1,d0          ..Zeichen $ff..
    bsr     print           ..schreiben

    lea     text2(pc),a0    Text2..
    bsr     text            ..schreiben

    bsr     ausgabe         komplette Zeitausgabe

    lea     text4(pc),a0    Text4..
    bsr     text            ..schreiben

    bsr     star            Sterne unten zeichnen

    lea     flag(pc),a0
    tst.w   (a0)            läuft Uhrzeit schon ?
    bne.s   weiter          nein, dann 'weiter'
    bra     direkt          sonst laufend anzeigen
*********************************************
nochmal     lea err_txt(pc),a0
    bsr     text            'errortxt' schreiben
    lea     ding(pc),a0
    moveq   #-1,d5          Fehlerflag setzen
    clr.w   d5              unteres Wort löschen
    bra.s   tune            'Ding'-Ton ausgeben

weiter      lea cursor(pc),a0  Cursor a.Anfang
    clr.l   d5              Fehlerflag ganz löschen

tune        bsr text
********************************************* 
    lea     syntax(pc),a5   Syntax-Check-POINTER
    lea     store(pc),a4    Zw.-speicher-POINTER..
    moveq   #-1,d0          \  ..zuerst.,
    move.l  d0,(a4)          | ..mit 'f'..
    move.w  d0,4(a4)        /  ..auffüllen
keystrt     moveq #2,d6     je 3 Datenbytes
key         clr.w d4        Eingabebyte löschen
    moveq   #1,d7           2 Nibble-Hälften
keypaar     bsr tast        auf Taste warten
    cmp.b   #$d,d0          [RETURN]?
    bne.s   no_ret
    tst.w   d7              ja, dann aber.,
    beq.s   keypaar         ..nur nach vollem Byte

    lea     store(pc),a0
    tst.l   d5              Fehler gewesen ?
    bmi.s   no_set          ja, neue Zeit ignorier.

    bne     ende            raus während Stellen!

    lea     syntax+6(pc),a5 Syntax-POINTER und.,
    lea     store+3(pc),a4  ..Store-POINTER auf..
    lea     u_point(pc),a0  ..'ZEIT '-Stellen
    bsr     text            Cursor auf ZEIT
    move.w  #-1,d5          zweiten Durchgang.,
    bra.s   keystrt         ..für ZEIT ausführen

no_set      moveq   #2,d0
copy        move.w  -8(a0),(a0)+alte Daten übernehmen.,
    dbf     d0,copy
    bra     direkt          ..und laufend anzeigen

no_ret      cmp.b #$30,d0   Taste <0?
    bcs.s   keypaar         dann ignorieren!
    cmp.b   #$39,d0         Taste >9?
    bhi.s   keypaar         dann auch ignorieren!

    move.w  d0,d1           nach d1 kopieren,.,
    and.b   #$f,d0          ..nur unteres Nibble!
    tst.w   d7              schon 2.Durchgang?
    beq.s   take_it         ja -> Sprung 'take_it'
    lsl.w   #4,d0           sonst oberes Nibble
take_it     or.b d0,d4      ob./unt. Nibble in d4 
    sub.b   #$20,d1         Taste-32 ->Dig.Ziffer..
    move.w  d1,d0           ..nach d0 kopieren.,
    bsr     out             ..und ausgeben
    dbf     d7,keypaar      Immer 2 Tasten holen

    cmp.b   #$12,l(a5)      Monatseingabe?
    bne.s   nomonth

    cmp.b   #2,d4           ja, dann Februar?
    bne.s   other
    cmp.b   #$29,-1(a4)     ja, >29.Februar?
    bhi     nochmal         ja, Fehler !

other       cmp.b #8,d4     sonst : >=August?
    bcc.s   august          nein, dann.,
    btst    #0,d4           ..ab Jan. gerad. Monat?
    bne.s   nomonth         ungerade, alles ok!
    bra.s   day_30          gerade

august      btst #0,d4      ..ab Aug. gerad. Monat?
    beq.s   nomonth         ungerade, alles ok
day_30      cmp.b #$31,-1(a4)   der 31.im 30Tage-Monat? 
    beq     nochmal         nochmal!!!

nomonth     cmp.b #$99,1(a5)  Jahreseingabe?
    bne.s   no_year
    cmp.b   #2,-1(a4)       ja, Februar?
    bne.s   no_year

    bsr     bcd_hex         ja, in hex umwandeln

    divu    #4,d0           Schaltjahr berechnen
    swap    d0
    tst.w   d0              Schaltjahr?
    bne.s   test_28
    move.b  #$29,d1         ja, 29.2 erlaubt!
    bra.s   testfeb
test_28     move.b #$28,d1  nein, dann nur 28.2.! 
testfeb     cmp.b -2(a4),d1 Festlegung beachtet?
    bcs     nochmal         nein, Fehler!!!

no_year     cmp.b (a5),d4   unt.Grenze d. Eingabe.,
    bcs     error           ..darunter? -> Fehler!
    cmp.b   1(a5),d4        über oberer Grenze ?
    bhi     error           ja, Fehler!!!
    move.b  d4,(a4)+        in STORE abspeichern,.,
    add.l   #2,a5           ..SYNTAX-POINTER +2

    lea     esc_c(pc),a0
    bsr     text            Ein Zeichen nach rechts
    dbf     d6,key          nächste Tastenfolge

    tst.w   d5              Schon mal hier gewesen?
    bne.s   ende

    lea     space(pc),a0
    bsr     text            nein, 'space' einfügen
    move.w  #-1,d5          Flag setzen
    bra     keystrt         noch mal von vorne!
********************************************** 
prozess     bsr tausch      Jahr & Tag vertauschen
    pea     (a0)
    pea     $190006         IKBDWS
    trap    #14             Neue Daten an Prozessor
    addq.l  #8,a7
    rts
********************************************** 
ende        bsr.s prozess   Daten an Tastaturproz.

direkt      lea ok_text(pc),a0
    bsr     text            'ok_text' schreiben

prt_uhr     bsr clock       Uhrzeit abfragen

wait        tst.b 2(a6)
    beq.s   wait            Uhrzeit empfangen?

    bsr     ausgabe         ja, kompl.Stringausgabe

    move.w  #$b,-(a7)       CONSTAT
    trap    #1
    addq.l  #2,a7
    tst.w   d0              Tastaturpuffer leer?
    beq.s   prt_uhr

    bsr     tast            nein, dann Taste holen
    cmp.b   #$d,d0          [RETURN]?
    beq.s   fin             ja, -> fertig
    or.b    #$20,d0         sonst vielleicht..
    cmp.b   #'j',d0         ..'j' oder 'J'?
    beq.s   fin             ja, -> fertig
    lea     ok_cls(pc),a0
    bsr     text            sonst Eingabe..
    bra     weiter          ..wiederholen!

fin         bsr tausch      Tag & Jahr vertauschen
    clr.l   d7              Datum-Register löschen
    addq.l  #1,a0           soll auf Store zeigen
    move.l  a0,a6           nach a6 kopieren
    move.b  (a6)+,d4        Jahr holen
    moveq   #$80,d1         Jahres-Offset in d1
    sbcd.b  d1,d4           Jahres-Offset abziehen
    bsr.s   bcd_hex         d4<bcd> -> d0<hex>
    or.w    d0,d7           ..abspeichern,..
    lsl.w   #4,d7           ..nach links schieben

    bsr.s   read_bh         Monat
    or.w    d0,d7
    lsl.w   #5,d7

    bsr.s   read_bh         Tag
    or.w    d0,d7

    move.w  d7,-(a7)
    move.w  #$2b,-(a7)      SET DATE
    trap    #1              Datum an GEMDOS

    clr.l   d7              Zeit-Register löschen
    bsr.s   read_bh         Stunde
    or.w    d0,d7
    lsl.w   #6,d7

    bsr.s   read_bh         Minute
    or.w    d0,d7
    lsl.w   #5,d7

    bsr.s   read_bh         Sekunde
    lsr.b   #1,d0           geteilt durch 2 (...)
    or.w    d0,d7

    move.w  d7,-(a7)
    move.w  #$2d,-(a7)      SET TIME
    trap    #1              Zeit an GEMDOS
    addq.l  #8,a7           Stack korrigieren

    move.l  basis(pc),a0
    move.l  altv(pc),20(a0) alt. Clockvektor zurück 

    move.w  color(pc),d0
    btst    #2,d0           vorher HIGH-Resolution?
    bne.s   not_col

    bsr     res             nein, dann..
    move.w  farbe(pc),d0
    moveq   #3,d1           ..alt.Wert v.Palette3..
    bsr     set_pal         ..zurückschreiben

not_col     lea cls(pc),a0
    bsr.s   text            Bildschirm-Cls
    clr.w   -(a7)           TERM
    trap    #1              SCHLUSS

*********************************************

error       lea err_txt(pc),a0
    bsr.s   text            'errortxt' schreiben
    lea     links(pc),a0
    bsr.s   text            zwei Zeichen nach links
    or.l    #$ffff0000,d5   Flag für ERROR setzen
    bra     key             Eingabe wiederholen
* - - - - - - - - - - - - - - - - - - - - - - - - 
read_bh     move.b (a6)+,d4  Nächstes Zeichen lesen 
bcd_hex     clr.l d0
    move.b  d4,d0           Umrechnung v. BCD ins.,
    move.b  d4,d1
    lsr.b   #4,d0           ..Hexadezimal-Format
    mulu    #10,d0          Benötigt wird das.,
    and.b   #$f,d1          ..BCD in d4,hex kommt.,
    add.b   d1,d0           ..kommt dann in d0
    rts
********************************************* 
text        move.l a0,-(a7)
    move.w  #9,-(a7)        PRINT LINE
    trap    #1              Textstring ausgeben
    addq.l  #6,a7
    rts
********************************************* 
tausch      lea code+1(pc),a0    Hier wird das..
    move.b  3(a0),d0        ..USA-Format des 6301..
    move.b  1(a0),3(a0)     ..dem deutschen..
    move.b  d0,1(a0)        ..GEMDOS angepasst
    rts
********************************************* 
star        moveq #42,d7
    moveq   #'*',d0         Sterne schreiben
print       move.l d0,-(a7)  d0 retten
    bsr.s   out             Zeichen ausgeben
    move.l  (a7)+,d0        d0 zurück
    dbf     d7,print        Druckt (d7+1)Zeichen d0
    rts

******************************************** 
outcomp     add.w #$10,d0   Offset f.Computerzahlen 
out         move.w d0,-(a7)
    pea     $30005          BCONOUT
    trap    #13             Druckt Zeichen d0
    addq.l  #6,a7
    rts
********************************************* 
outbyte     clr.w d0
    move.b  (a6)+,d0        Byte holen und..
    move.w  d0,d7           ..kopieren
    lsr.b   #4,d0           High-Nibble
    bsr.s   outcomp
    and.w   #$f,d7          Low-Nibble
    move.w  d7,d0
    bra.s   outcomp         Byte schreiben
********************************************* 
tast        move.w #8,-(a7)  CONIN WITHOUT ECHO
    trap    #1              Tasten-Wert in d0
    addq.l  #2,a7 
    rts
********************************************* 
clock       lea store(pc),a6
    clr.b   2(a6)           Spezial-Dummy setzen

    pea     ask_uhr(pc)     $1c
    clr.w   -(a7)
    move.w  #25,-(a7)       IKBDWS
    trap    #14         Uhrzeit holen
    addq.l  #8,a7
    rts
********************************************* 
ausgabe     bsr.s tausch    Tag & Jahr vertauschen 
    lea     cursor1(pc),a0
    bsr.s   text            Cursor richten
    bsr.s   outbyte         Tag ausgeben
    moveq   #’.',d0
    bsr.s   out             . ausgeben
    bsr.s   outbyte         Monat ausgeben
    moveq   #'.',d0
    bsr.s   out             . ausgeben
    bsr.s   outbyte         Jahr ausgeben
    moveq   #2,d7
    moveq   #' ',d0
    bsr.s   print           3 * Space
    bsr.s   outbyte         Stunde ausgeben
    moveq   #':',d0
    bsr.s   out             : ausgeben
    bsr.s   outbyte         Minute ausgeben
    moveq   #':',d0
    bsr.s   out             : ausgeben
    bsr.s   outbyte         Sekunde ausgeben
    rts
********************************************* 
res         move.w d0,-(a7)
    moveq   #-1,d0
    move.l  d0,-(a7)
    move.l  d0,-(a7)
    move.w  #5,-(a7)        SETSCREEN
    trap    #14             für Bildschirmauflösung
    lea     12(a7),a7
    rts

********************************************* 
get_pal     moveq #-1,d0        (abfragen der..)
set_pal     move.w d0,-(a7)     (setzen der..)
    move.w  d1,-(a7)        ..d1-Palette, d0-Farbe
    move.w  #7,-(a7)        SETCOLOR
    trap    #14
    addq.l  #6,a7
    rts
********************************************* 
keyin       moveq #5,d0     Das ist die neue
    lea     store(pc),a1    Routine, die vom
repeat      move.b (a0)+,(a1)+  Tast.-proz. Daten-
    dbf     d0,repeat       pakete empfängt und in
    rts                     d.Puffer (a1)+ schreibt
*********************************************

syntax  dc.w $0131,$0112,$0099,$0023,$0059,$0059 
init    dc.b    27,'E',27,'f',27,'Y',41,50,0
text1   dc.b    27,'Y',42,50,'*',27,'Y',42,92,'*'
  dc.b  27,'Y',43,50,'*',27,'Y',43,56,'Bitte ' 
  dc.b  'Datum und Uhrzeit stellen',27,'Y',43 
  dc.b  92,'*',27,'Y',44,50,'*',27,'Y',44,56,0 
text2   dc.b    27,'Y',44,92,'*',27,'Y',45,50,'*' 
  dc.b  27,'Y',45,57,'6301->GEMDOS-Uhr '
  dc.b  189,' S.Schäfer',27,'Y',45,92,'*',27
  dc.b  'Y',46,50,'*',27,'Y',46,56,'---> ',0
text4   dc.b ' <—-',27,'Y',46,92,'*',27,'Y',47,50
  dc.b  '*',27,'Y',47,92,'*',27,'Y',48,50,0 
err_txt dc.b   27,'j',27,'f',27,'Y',47,67 
  dc.b  27, 'p ERROR ! ' ,27, 'q',27, 'k',0 
ding    dc.b    7 
cursor  dc.b    27,'e' 
cursor1 dc.b    27,'Y',46,62,0 
space   dc.b    27,'C' 
esc_c   dc.b    27,'C',0 
links   dc.b    7,27,'e',27,'D',27,'D',0
ok_text dc.b    27,'f',27,'Y',47,67,27,'pOK (j/n)?'
  dc.b  27,'q',0 
ok_cls  dc.b    27,'e',27,'Y',47,67,'         ',0
u_point dc.b    27,'Y',46,73,0 
cls     dc.b    27,'E',0

ask_uhr dc.b    $1c 
    even
save    dc.w    $2210,$8812,$0000 
code    dc.w    $001b 
store   dsbss.w 3

basis   dsbss.l 1 
altv    dsbss.l 1 
flag    dsbss.w 1 
color   dsbss.w 1 
farbe   dsbss.w 1

Sieghard Schäfer
Aus: ST-Computer 04 / 1989, Seite 141

Links

Copyright-Bestimmungen: siehe Über diese Seite