Schnittstellen-Dschungel: Neue Rechner - Neue Schnittstellen, Teil 2

Wie bereits im vorigen Teil angekündigt, wollen wir uns in dieser Serie auch ausführlich mit den Fähigkeiten des Serial Communication Controllers (kurz: SCC) beschäftigen. Er unterstützt zwei der seriellen Schnittstellen des Mega STE bzw. des TT sowie deren mysteriösen LAN-Port.

In der Fachwelt wird häufig mit solchen Begriffen wie LAN, Ethernet, ISO/OSI usw. um sich geworfen, jedoch dürfte es vielen Lesern unbekannt sein, was es damit auf sich hat. Deshalb wollen wir hier zunächst einen Ausflug in die Grundlagen der sogenannten Lokalen Netze machen. Und das ist nicht nur für TT- und Mega STE-Besitzer interessant, denn schließlich läßt sich auch über den zumeist ungenutzten MIDI-Port ein (wenn auch nicht sonderlich schnelles) Netzwerk aufbauen.

LAN & Co.

Fangen wir am besten mal vorne an: Was bedeutet LAN? LAN ist die Abkürzung für „Local Area Network“ oder zu gut deutsch: Lokales Netz. Man unterscheidet unter anderem auch noch zwischen MAN (Metropolitan Area Network) und WAN (Wide Area Network); der Unterschied besteht hauptsächlich in der Ausdehnung des Netzes. Ein LAN verbindet Rechner innerhalb eines oder mehrerer Gebäude, das MAN kann sich schon über eine ganze Stadt erstrecken und WANs erstrecken sich über noch größere Gebiete. Hierbei sind die Grenzen natürlich fließend.

Wir wollen uns an dieser Stelle nur auf die LANs beschränken, weil es sich kaum ein Atari-Benutzer wird leisten können, eine kilometerlange Standleitung von der Telekom zu mieten.

Natürlich wird es umso einfacher (und schneller), zwei Rechner miteinander kommunizieren zu lassen, je mehr (Daten- und Handshake-)Leitungen zur Verfügungen stehen. Mit steigender Leitungszahl explodieren jedoch auch die Kosten. Deshalb wird in der Regel bei Lokalen Netzen die serielle Datenübertragung bevorzugt. Die Leitungslänge hängt natürlich direkt davon ab, wie die Rechner untereinander verbunden sind; das bezeichnet man als Topologie.

Von der Topologie hängt auch direkt die Behandlung beim Ausfall eines Elementes ab. Sind die Rechner beispielsweise sternförmig (siehe Abbildung 1) angeordnet mit einem zentralen Element, so kommt der Ausfall dieses Zentralknotens dem Ausfall des gesamten Systems gleich. Zudem hat unser Atari nur einen einzigen Netzwerkanschluß und ist somit nicht als zentraler Knoten geeignet.

Sind alle Rechner an ein gemeinsames Medium angeschlossen, spricht man von einer Busstruktur (Abbildung 2). Hierbei ist jedoch Absprache unter den Teilnehmern notwendig, wann welcher Teilnehmer den Bus benutzen darf, und es besteht unter Umständen die Gefahr des Aushungerns. Das bedeutet, daß ein Teilnehmer den Zugang zum Bus niemals bekommt, weil die anderen Teilnehmer ihn blockieren. Die Busstruktur hat den Vorteil, daß der Ausfall eines Rechners keinen Einfluß auf das Gesamtsystem hat.

Bei einer Ringstruktur (Abbildung 3) besitzt jeder Rechner einen Vorgänger-sowie einen Nachfolgerechner, so daß alle Rechner insgesamt ringförmig miteinander verbunden sind. Das ist hardwaremäßig zwar einfach zu realisieren, hat jedoch den Nachteil, daß der Ausfall eines Rechners in der Regel den Ausfall des Gesamtsystems nach sich zieht.

Abb. 1: Stern-Struktur

ISO/OSI

Um die verschiedenen Möglichkeiten der Hardware (man denke nur einmal an die möglichen verwendbaren Kabeltypen) sowie der Software-Protokolle unter einen Hut zu bringen, wurde von der International Standardization Organisation (ISO) das OSI-Referenzmodell geschaffen (OSI = Open System Interconnection). Bei diesem Modell handelt es sich um eine heute von allen Kommunikationsentwicklern und -anbietern akzeptierte Festlegung zur Abwicklung von Kommunikationsvorgängen zwischen Netzwerkdiensten und -funktionen in unterschiedlichen System weiten auf der Basis allgemeiner Protokolle und Schnittstellen. Es gliedert sich auf in sieben Schichten (siehe Abbildung 4), die folgendermaßen charakterisiert werden:

Eigentlich besteht das Modell aus acht Schichten, jedoch wird Schicht 0, die das Übertragungsmedium beschreibt, üblicherweise nicht mitgezählt.

Schicht 1 (Bit-Übertragung/Physical Layer) legt fest, wie die Bits über das Medium übertragen werden (z.B. Spannungen etc.).

Schicht 2 (Sicherung/Data Link Layer) regelt den physikalischen sowie logischen Zugriff auf das Medium.

Schicht 3 (Vermittlung/Network Layer) sorgt dafür, daß Rechner A Rechner B im Netzwerk auch findet.

Schicht 4 (Transportdienst/Transport Layer) ist bereits in der Lage, Daten von Rechner A zu Rechner B zu verschicken.

Schicht 5 (Kommunikationssteuerung/ Session Layer) steuert die Sitzung und veranlaßt die Datenübertragung.

Schicht 6 (Datendarstellung/Presentation Layer) bereitet die Daten für den aktuellen Rechner in der benötigten Form auf.

Schicht 7 (Anwendung/Application Layer) schließlich ist das eigentliche Anwendungsprogramm.

Kabelprobleme

Nachdem wir nun einen Überblick über den Aufbau von Lokalen Netzen gewonnen haben, wollen wir uns mit einigen wichtigen Details beschäftigen.

Für die Auswahl des Mediums gibt es mehrere (leider teilweise gegensätzliche) Kriterien:

Die wichtigsten in der Praxis verwendeten Leitungen sind:

Vorteil: einfach zu handhaben
Nachteil: nur für kürzere Entfernungen geeignet

Vorteile: billig, bis zu 20 km gut ersetzbar
Nachteil: Der Skin-Effekt bei hohen Frequenzen bewirkt, daß bei steigender Übertragungsrate der Strom dazu tendiert, nur auf der Oberfläche des Innenleiters zu fließen. Das führt dazu, daß der Leitungswiderstand bei steigender Frequenz stark ansteigt. Trotzdem sind 400 MB/sec erreichbar.

Vorteile: hohe Übertragungsgeschwindigkeit, Störunempfindlichkeit, kaum Dämpfung bei hohen Frequenzen
Nachteil: Notwendigkeit eines Lasers zur Datenübertragung

Da wir Atari-Besitzer es jedoch normalerweise nicht mit derart hohen Geschwindigkeiten sowie Entfernungen zu tun haben, besitzt der LAN-Port des Atari weder einen Laser zur Glasfasersteuerung noch BNC-Anschlüsse für Koaxialkabel.

Abb. 2: Bus-Struktur
Abb. 3: Ringstruktur

Ethernet & CSMA/CD

Wie bereits erwähnt, stellt der Zugang zum Medium ein Problem für Bus-Topologien dar. Beim weitverbreiteten Ethernet hängen nämlich alle Teilnehmer an einem Koaxialkabel und können theoretisch gleichzeitig senden. Um nun nicht absichtlich die Übertragung eines anderen Teilnehmers zu stören, muß ein sendewilliger Rechner zunächst prüfen, ob die Leitung frei ist (Carrier-Sense-Eigenschaft). Erst dann darf er auf die Leitung zugreifen, was jedoch einen anderen Teilnehmer, der das Medium ebenfalls als frei erkannt hat, nicht daran hindert, auch zu senden (Multiple-Access-Eigenschaft). Um eine Kollision zu erkennen, überprüft ein sendender Teilnehmer ständig, ob sich auf dem Medium nur die Daten befinden, die er auch gesendet hat. Ist das nicht der Fall, muß von einer Datenkollision ausgegangen und die momentane Übertragung sofort abgebrochen werden (Collision-Detection-Eigenschaft). Daher zählt Ethernet zu den sogenannten CSMA/CD-Verfahren.

Token-Bus

Eine andere Art der Zugangsregelung bei Bus-Topologien ist die, ein sogenanntes Token zu vergeben. Derjenige Rechner, der das Token hält, besitzt den exklusiven Zugang zum Medium und kann allen anderen Rechnern Nachrichten schicken oder von ihnen anfordern. Erst wenn er das Token an den nächsten Rechner weitergibt, kann dieser selbst aktiv werden. Der Gerechtigkeit halber sollte das Token in einem logischem Ring von einem Rechner zum nächsten weitergegeben werden, damit keiner benachteiligt wird (siehe Abbildung 5). Jedoch wirft auch diese Lösung einige Probleme auf. So muß es zum Beispiel genau ein Token geben. Stürzt jedoch der Rechner, der das Token hält, ab, gibt es gar kein Token mehr und niemand kann mehr auf den Bus zugreifen; es muß also ein neues Token generiert werden.

Was ist aber, wenn ein Rechner annimmt, daß sein Vorgänger abgestürzt ist und ein zweites Token generiert? Oder wie lange darf ein Teilnehmer das Token halten, bevor er es weitergeben muß? Wer generiert das Token nach einem Systemstart? Mit anderen Worten: Probleme über Probleme. Auch dieses Verfahren stellt also nicht der Weisheit letzten Schluß dar.

Token-Ring

Etwas anders sieht es bei einer Ring-Topologie aus. Da diese Topologie für uns Atari-Besitzer besonders interessant ist, wollen wir darauf auch besonders ausführlich eingehen. Es gibt bereits einige MIDI-Netz-Konzepte, die auf der Ring-Topologie basieren, wie zum Beispiel das Spiel „Midi-Maze“.

Beim Token-Ring muß ein Rechner die Nachricht, die er empfängt und die nicht für ihn bestimmt ist, an den nächsten Rechner weiterleiten. Fällt ein Rechner aus, bricht ohnehin der ganze Ring zusammen, so daß uns das Ausfallproblem schon mal nicht mehr zu interessieren braucht. Aber wann darf ein Teilnehmer eine Nachricht in den Ring einbringen? Es könnte ja gleich eine Nachricht ankommen, die sofort weitergeleitet werden muß; es können auch nicht beliebig viele Nachrichten zwischengepuffert werden. Die Lösung ist auch hier wieder ein Token.

Wer das Token hält, darf Nachrichten in den Ring einleiten, und die übrigen Teilnehmer sind währenddessen dazu verurteilt, nur Nachrichten weiterzuleiten und sich zu kopieren, wenn die Zieladresse mit der eigenen Adresse übereinstimmt.

Nach einem Ringumlauf wird die Nachricht dann vom Sender wieder vom Ring entfernt und ein neues Token generiert, das an den Nachfolger weitergeleitet werden kann.

Frames

An dieser Stelle ist es angebracht, den Begriff eines Frames zu erläutern. Ein Frame ist ein leerer Datenrahmen, in dem die verschiedenen Bit-Positionen verschiedene Bedeutungen haben. Der Sinn eines Frames ist der, daß ein Teilnehmer nicht beliebig viele Daten hintereinander über das Medium schickt, sondern nur in kleinen Paketen. Denn wenn in einem langen Datenstrom zum Beispiel ein Byte verlorengeht, herrscht plötzlich Chaos, so daß der gesamte Datenstrom wiederholt werden muß. Geht dagegen ein Byte in einem Paket verloren, ist das an der Paketlänge erkennbar, und es muß nur dieses eine Paket erneut gesendet werden.

Ein Daten-Frame kann beispielsweise folgendermaßen aussehen: Er beginnt in der Regel mit einem Startsymbol, das so sonst nicht Vorkommen kann. Denkbar ist zum Beispiel die Bit-Folge 01111110. Wenn im Datenteil dieses Byte Vorkommen sollte, fügt der Kommunikationsbaustein automatisch in die Mitte eine 0 ein, die bei Empfang ebenso automatisch wieder entfernt wird, so daß das Programm nichts davon merkt. Das setzt natürlich voraus, daß der Kommunikationsbaustein eine gewisse Intelligenz besitzt, die der MFP 68901 leider nicht vorweisen kann. Das wird auch einer der Gründe sein, warum Atari den SCC in den neuen Rechnern verwendet.

Der SCC kann nämlich automatisch nach diesem Byte suchen und entlastet somit die CPU erheblich. Der weitaus wichtigere Grund dürfte jedoch darin zu suchen sein, daß der Apple Macintosh den gleichen (!) Chip für Netzwerkanwendungen benutzt, so daß Ataris und MACs problemlos gekoppelt werden können, sobald es entsprechende Software gibt.

Der Grund für ein solches Startsymbol ist der, daß ein Frame-Anfang dadurch eindeutig erkannt werden kann. Das XModem-Protokoll bietet so etwas leider nicht, daher kann es dort zu Problemen kommen, den nächsten Blockanfang zu erkennen, wenn einmal die Synchronisation verlorengegangen ist. Aus den gleichen Gründen existiert ein ebenso möglichst eindeutiges Endsymbol, daß einen Frame abschließt.

In der Regel enthält ein Frame auch Felder für Quell- und Zieladresse, damit der Empfänger weiß, ob ein Paket für ihn ist und woher es kommt. Meist gibt es auch Gruppen- oder Broadcast-Adressen, mit denen gezielt Gruppen von Teilnehmern oder alle Teilnehmer adressiert werden können.

Der eigentliche Datenteil kann entweder feste oder variable Länge haben. Variable Länge bietet den Vorteil, daß bei kurzen Nachrichten weniger Bandbreite verschwendet wird, dafür erfordert sie jedoch aufwendigere Software.

Schließlich sollte noch eine Prüfsumme vorhanden sein, anhand derer Datenfehler erkannt werden können.

Je mehr dieser Aufgaben an den Kommunikationsbaustein übergeben werden können, umso weniger Rechenzeit geht für das Lokale Netz verloren, denn der eigene Rechner soll schließlich den Hauptteil der Rechenzeit dem Benutzer zur Verfügung stellen und nicht für Netzwerkprotokolle (und damit indirekt für andere Benutzer) verbrauchen.

Abb: 4: Genereller Aufbau des 7-Schichten-Modells der ISO
Abb. 5: Token-Bus

Token-Probleme

Zurück zum Token: Es kann die verschiedensten Formen haben. Zum Beispiel kann es ein besonders kurzer Frame sein, der nur angibt, daß es sich um ein Token handelt und keinen Datenteil besitzt. Oder es könnte ein normaler Daten-Frame sein, in dem ein Token-Bit gesetzt ist, das angibt, daß es sich bei diesem Frame um ein Token handelt.

Hier tauchen wieder ähnliche Probleme auf wie bei Bus-Topologien. Um diese Probleme in den Griff zu bekommen, führt man eine sogenannte Monitorstation ein, die den Ring überwacht und gegebenenfalls Fehler korrigiert.

Mögliche Fehlerquellen (abgesehen von Hardware-Fehlern) sind:

Ein fehlerhafter Frame kann viele Ursachen haben. Die häufigste ist ein Bit-Fehler im Datenteil, der durch die Prüfsumme (meist CRC-codiert) relativ sicher erkannt werden kann, ebenso wie zu kurze oder zu lange Frames. Das Fehlen eines Endfeldes wird dadurch erkannt, daß das (eindeutige) Startsymbol zu früh kommt. Dadurch ist dieser Fall auf einen zu kurzen Frame zurückführbar, analog verhält es sich mit einem fehlenden Startsymbol.

Um endlos kreisende Frames zu erkennen, enthält jeder Frame ein Monitor-Bit, das bei Generierung des Frames 0 ist. Passiert der Frame die Monitorstation, wird das Bit auf 1 gesetzt ifnd der Frame weitergereicht. Erreicht jedoch ein Frame mit gesetztem Monitor-B it die Monitorstation, handelt es sich um eine Fehlersituation. Die Fehlerbehandlung ist dabei die gleiche wie bei einem Token-Verlust: Es werden alle Nachrichten vom Ring entfernt, indem ein Purge-Frame (Ringreinigung) an alle Teilnehmer gesendet wird. Anschließend generiert die Monitorstation ein neues Token.

Um eine Token-Duplizierung zu erkennen, überprüft eine sendende Station die Quelladresse des empfangenen Frames. Stimmt diese nicht mit der eigenen Adresse überein, wird die Übertragung abgebrochen und kein neues Token generiert, wodurch es wieder nur ein Token geben sollte.

Schlußbemerkungen

Wir konnten in diesem Schnelldurchgang durch das Gebiet der Lokalen Netze die meisten Probleme nur anreißen, weshalb wir für weitergehende Informationen auf die Literaturliste verweisen möchten. Wie wohl jeder gemerkt hat, gibt es eine Unzahl an Problemen und Lösungsmöglichkeiten. So ist es zum Beispiel auch nicht verwunderlich, daß allein die Spezifikation von AppleTalk ein dickes Buch füllt.

Im nächsten Teil werden wir dann sehen, welche der hier beschriebenen vielfältigen Möglichkeiten der im Atari verwendete Serial Communications Controller speziell bietet und wie er die Software-Entwicklung für Lokale Netze unterstützt.

Abschließend folgt jetzt noch der zweite Teil unseres Terminal-Programms.

Oliver Scholz & Uwe Hax

Literatur:

Winfried Dulz:
Lokale Netze,
Lehrstuhl für Rechnerarchitektur und Verkehrstheorie der Friedrich-Alexander-Universität Erlangen-Nürnberg

Hans-Peter Boell:
Lokale Netze - Momentane Möglichkeiten und zukünftige Entwicklung,
McGraw-Hill Book Company GmbH

W. Stallings:
Local Networks: An Introduction,
Macmillan Publishing Company

Gerd E. Keiser:
Local Area Networks,
McGraw-Hill Book Company

/*
 * CONFIG.C
 * Portkonfiguration fur TT44TT
 * Copyright (c) 1991 by MAXON
 * Autoren Oliver Scholz & Uwe Hax 
 */

#include <tos.h>
#include <aes.h>
#include <stdio.h>
#include <string.h>
#include <portab h>

#include "TT44TT.h"
#include "termdefs.h" 
#include "proto.h"

#define GLOBAL extern 
#include "variable.h"

/*
 * Dialogbox zur Portkonfiguration 
 */

WORD conf_port(CONF_RS *port)
{
    WORD cx,cy,cw,ch;
    WORD exitobj,ret;
    OLDSET old;
    CONF_RS oldport;

    /* Die alten Werte merken */ 
    oldport.baudrate=port->baudrate; 
    oldport.flowctrl=port->flowctrl; 
    oldport.ucr=port->ucr;

    into_dial(port,&old);
    form_center(port_dial,&cx,&cy,&cw,&ch); 
    form_dial(FMD_START,cx,cy,cw,ch,cx,cy,cw,ch); 
    objc_draw(port_dial,ROOT,MAX_DEPTH,cx,cy,cw,ch);

    show_mouse();

    do
    {
        exitobj=(form_do(port_dial,0) & 0x7FFF);

        switch(exitobj)
        {
            case BITS8:     port->ucr &= ~0x60;
                            break;

            case BITS7:     port->ucr &= ~0x60;
                            port->ucr |= 0x20; 
                            break;

            case STOP1:     port->ucr &= ~0x18;
                            port->ucr |= 0x08; 
                            break;

            case STOP2:     port->ucr &= ~0x18;
                            port->ucr |= 0x18; 
                            break;

            case PARN:      port->ucr &= ~0x06;
                            break;

            case PARE:      port->ucr |= 0x06;
                            break;

            case PARO:      port~>ucr &= ~0x06;
                            port->ucr |= 0x04;
                            break;

            case NOPROT:    port->flowctrl=P_NONE; 
                            break;

            case XONXOFF:   port->flowctrl=P_XON; 
                            break;

            case RTSCTS:    port->flowctrl=P_RTS;
                            break;

            case BAUDDWN:   if (port->baudrate < 15)
                            {
                                port->baudrate++; 
                                into_dial(port,&old); 
                                objC_draw(port_dial,BAUDRATE,MAX_DEPTH,cx,cy,cw,ch);
                            }
                            break;

            case BAUDUP:    if (port->baudrate > 0)
                            {
                                port->baudrate—-; 
                                into_dial(port,&old); 
                                objc_draw(port_dial, BAUDRATE,MAX_DEPTH, cx, cy, cw, ch);
                            }
                            break;
        } /* switch */
    }
    while (exitobj!=PORTOK && exitobj!=PORTABRT);

    port_dial[exitobj].ob_state &= ~SELECTED;

    if (exitobj==PORTOK)
        ret=1; /* OK, neue Einstellung gültig */ 
    else {
        ret=0; /* alte Parameter wiederherstellen */ 
        port->baudrate=oldport.baudrate; 
        port->flowctrl=oldport.flowctrl; 
        port->ucr=oldport.ucr;
    }

    hide_mouse();
    form_dial(FMD_FINISH, cx, cy, cw, ch, cx, cy, cw, ch); 

    return(ret);
}

/*
 * zur Baudrate passenden String holen 
 */

VOID get_baud_strmg (WORD rate_index, CHAR *buf)
{
    CHAR text[16][6]={ "19200","9600","4800", 
        "3600","2400","2000","1800","1200","600", 
        "300", "200","150","134","110","75","50" };

    strcpy (buf,text[rate_index]);
}

/*
 * akt Parameter in Dialog eintragen
 */

VOID into_dial(CONF_RS *port, OLDSET *indices)
{
    CHAR *baud;
    init_dial();

    baud=port_dial[BAUDRATE].ob_spec.tedinfo->te_ptext;

    get_baud_string(port->baudrate,baud);

    switch(port->flowctrl)
    {
        case P_NONE:    indices->iflow=NOPROT;
                        break;

        case P_XON:     indices->iflow=XONXOFF;
                        break;

        case P_RTS:     indices->iflow=RTSCTS;
    }

    switch ((port->ucr) & 0x06)
    {
        case 0x00:
        case 0x02:      indices->ipar=PARN;
                        break;

        case 0x04:      indices->ipar=PARO;
                        break;

        case 0x06:      indices->ipar=PARE;
    }

    if ( ((port->ucr) & 0x18) — 0x18 ) 
        indices->istop=STOP2; 
    else
        indices->istop=STOP1;

    if ( ((port->ucr) & 0x60) == 0x20 ) 
        indices->idata=BITS7; 
    else
        indices->idata=BITS8;

    port_dial[indices->iflow].ob_state|= SELECTED; 
    port_dial[indices->idata].ob_state|= SELECTED; 
    port_dial[indices->istop].ob_state|= SELECTED; 
    port_dial[indices->ipar].ob_state |= SELECTED;
}

/*
 * alle Objekte im Dialog deselektieren 
 */

VOID init_dial(VOID)
{
    WORD objects[10]={BITS7, BITS8, STOP1, STOP2, 
        PARN, PARO, PARE,NOPROT, RTSCTS, XONXOFF); 
    WORD i;

    for (i=0; i<10; i++)
        port_dial[objects[i]].ob_state &= ~SELECTED;
}

/*
 * alle 4 Ports initialisieren 
 */

VOID init_ports(CONF_RS *port)
{
    WORD i;

    for (i=0; i<4; i++)
    {
        port[i].baudrate=1; 
        port[i].flowctrl=P_RTS; 
        port[i].ucr=DEFUCR;
    }
}

/*
 * Einstellung aus Port lesen 
 */

VOID read_port(WORD device)
{
    LONG reg;
    WORD aux;

    aux=(WORD)_bconmap(-1);
    _bconmap (device+aux_offset);

    port[device].baudrate=(WORD)Rsconf(-2,-1.-1,-1,-1,-1); 
    reg=Rsconf(-1,-1,-1,-1,-1,-1); 
    port[device].ucr=(WORD)(reg >> 24) & 0xFF;

    /* Flowcontrol kann leider auf 'saubere' Weise nicht gelesen werden */

    _bconmap(aux);
}

/*
 * Einstellung in Port schreiben 
 */

VOID write_port(WORD device)
{
    WORD aux;

    aux=(WORD)_bconmap(-1),
    _bconmap(device+aux_offset);
    Rsconf(port[device].baudrate, port[device].flowctrl, port[device].ucr,-1,-1,-1);
    _bconmap(aux);
}

/*
 * OUTPUT.C
 * Ausgaberoutinen fur TT44TT
 * Copyright (c) 1991 by MAXON
 * Autoren: Oliver Scholz & Uwe Hax 
 */

#include <portab.h>
#include <vdi.h>
#include <string.h>
#include <tos.h>
#include <aes.h>

#include "termdefs h"
#include "proto.h"
#include "tt44tt.h"

#define GLOBAL extern 
#include "variable h"

/*
 * liest alle fur das Terminal angekommenen
 * Zeichen, gibt sie in dem Fenster aus und
 * aktualisiert das virtuelle Terminal 
 */

VOID do_output (WORD wmö_index)
{
    CHAR c;

    while (Bconstat (wind_index+aux_offset) )
    {
        c=Bconin(wind_index+aux_offset); 
        if (c!=ESCAPE)
        {
            if (terminal[wind_index].escape!=WAITING) 
                handle_escape(wind_index, c); 
            else {
                insert_char(wind_index,c); 
                print_char(wind_index,c);
            }
        }
        else
            terminal[wind_index].escape=ESCAPE;
    }
}

/*
 * initialisiert ein virtuelles Terminal
 */

VOID init_terminal(WORD wind_index)
{
    WORD i,j;

    for (i=0; i<TERM_HEIGHT; i++)
    {
        for (j=0; j<TERM_WIDTH; j++) 
        terminal[wind_index].screen[i][j]=' ';
        terminal[wind_index].screen[i][TERM_WIDTH] = EOS;
    }
    terminal[wind_index].x=terminal[wind_index].y = 0;
    terminal[wind_index].escape=WAITING;
}

/*
 * Hier können verschiedene Escapesequenzen
 * behandelt werden, um z.B ein VT52 Terminal
 * zu emulieren 
 */

VOID handle_escape(WORD wind_index, CHAR c)
{
    terminal[wind_index].escape=WAITING;
}

/*
 * Gibt ein Zeichen auf einem
 * virtuellen Terminal aus 
 */

VOID insert_char(WORD wind_index, CHAR c)
{
    WORD x,y;

    update_pos(wind_index);

    switch (c)
    {
        case CR:    terminal[wind_index].x=0;
                    break;
        case LF:    term_lf(wind_index);
                    break;
        case TAB:   x=terminal[wind_index] x;
                    terminal[wind_index].x=x+8-x%8; 
                    break; 
        case BACKSPACE:
                    if (terminal[wind_index].x>0) 
                        terminal[wind_index].x—-; 
                    break;
        case BELL:  break;
        default:    x=terminal[wind_index].x++;
                    y=terminal[wind_index].y; 
                    terminal[wind_index].screen[y][x]=c; 
                    if (x >= TERM_WIDTH)
                    { /* Auto Wrap */
                        terminal[wind_index].x=0; 
                        term_lf(wind_index);
                    }
    }
}

/*
 * führt auf einem virtuellen Terminal einen
 * Line-Feed aus 
 */

VOID term_lf (WORD wind_index)
{
    WORD i;

    if (terminal[wind_index].y==TERM_HEIGHT-1)
    {
        for (i=0; i<TERM_HEIGHT-1; i++)
            strcpy(terminal[wind_index].screen[i],terminal[wind_index].screen[i+1]); 
            for (i=0; i<TERM_WIDTH; i++) 
                terminal[wind_index].screen[TERM_HEIGHT-1][i]=' ';
            terminal[wind_index].screen[TERM_HEIGHT-1][TERM_WIDTH]=EOS;
    }
    else
        terminal[wind_index].y++
}

/*
 * Gibt ein Zeichen im Terminalfenster aus 
 */

VOID print_char(WORD wind_index, CHAR c)
{
    GRECT t1,t2;
    WORD x,y,w,h;
    CHAR zeichen[4];
    CHAR nonprintable[3];

    /* Fehler im Turbo C (2.03) hier wird
       das statische Array falsch initialisiert, 
       daher Initialisierung 'zu Fuß' */

    nonprintable[0]=CR; 
    nonprintable[1]]=BACKSPACE; 
    nonprintabie[2]=TAB;

    if (window[wind_index].handle>=0)
    {
        /* nicht druckbare Zeichen abfangen */ 
        if (strchr(nonprintabie, c))
        {
            update_cursor(wind_index); 
            update_pos(wind_index);
            return;
        }
        if (c==BELL)
        { /* kurz Bimmeln. . */
            Cconout(BELL);
            return;
        }

        if (c==LF)
        {
            cursor(wind_index,CURSOR_OFF); 
            wind_get(window[wind_index].handle,WF_WORKXYWH,&x,&y,&w,&h);

            /* in der letzten Zeile des Fensters: scrollen */ 
            if (window[wind_index].y_corner+h/hchar-1 = terminal[wind_index].tmp_y)
            {
                /* letzte Zeile des Terminals Slider verschieben */ 
                if (TERM_HEIGHT-1 != terminal[wind_index].tmp_y)
                {
                    window[wind_index].y_corner++; 
                    pos_slider(wind_index.VERTICAL);
                }
                scroll(wind_index, SCROLL_UP);
            }

            cursor(wind_index, CURSOR_ON); 
            return;
        }

        cursor (wind_index, CURSOR_OFF);

        wind_get(window[wind_index].handle,WF_FIRSTXYWH,&t1.g_x,&t1.g_y,&t1.g_w,&t1.g_h);
        
        wind_get(window[wind_index].handle,WF_WORKXYWH,&x,&y,&w,&h);

        /* Zeichenkoordinaten */
        t2.g_x=(terminal[wind_index].tmp_x - window[wind_index].x_corner)*wchar + x; 
        t2.g_y=(terminal[wind_indexj.y - window[wind_index].y_corner)*hchar + y; 
        t2.g_w=wchar;
        t2.g_h=hchar

        zeichen[0]=c;
        zeichen[1]=EOS;

        /* Zeichen im Fenster ausgeben */ 
        while (t1.g_w && t1.g_h)
        {
            if (rc_intersect(&t2,&t1))
            {
                clipping(&t1,TRUE); 
                v_gtext(vdi_handle,t2.g_x,t2.g_y+distances[4],zeichen);
            }
            wind_get(window[wind_index].handle,
                            WF_NEXTXYWH,&t1.g_x,&t1.g_y,
                            &t1.g_w,&t1.g_h);
        }
        clipping(&t1,FALSE);
    }
    cursor(wind_index,CURSOR_ON);
}

/*
 * Cursorposition updaten 
 */

VOID update_pos(WORD wind_index)
{
    terminal[wind_index].tmp_x=terminal[wind_index].x; 
    terminal[wind_index].tmp_y=terminal[wind_index].y,
}

/*
 * Scrollen im Fenster vertikal um eine Zeile 
 */

VOID scroll (WORD wind_index, WORD direction)
{
    GRECT t1;
    WORD x,y,w,h;
    WORD line;
    WORD xyarray[8];
    WORD temp;
    MFDB screen;
    WORD work_out[57];
    WORD i;
    WORD y_pos;
    CHAR out[TERM_WIDTH+1];

    wind_get(window[wind_index].handle,WF_FIRSTXYWH,&t1.g_x,&t1.g_y,&t1.g_w,&t1.g_h); 
    wind_get(window[wind_index].handle,WF_WORKXYWH,&x,&y,&w,&h); 
    vq_extnd(vdi_handle,1,work_out);

    /* solange noch Rechtecke in der Liste */ 
    while (t1.g_w && t1.g_h)
    {
        clipping(&t1,TRUE);

        if (t1.g_h>hchar)
        {
            /* nach oben kopieren */ 
            xyarray[0]=t1.g_x; 
            xyarray[1]=t1.g_y+hchar; 
            xyarray[2]=t1.g_x+t1.g_w-1; 
            xyarray[3]=t1.g_y+t1.g_h-1; 
            xyarray[4]=t1.g_x; 
            xyarray[5]=t1.g_y; 
            xyarray[6]=t1.g_x+t1.g_w-1; 
            xyarray[7]=t1.g_y+t1.g_h-1-hchar.

            /* nach unten kopieren */ 
            if (direction == SCROLL_DOWN) 
                for (i=0, i<4; i++)
                {
                    temp=xyarray[i]; 
                    xyarray[i]=xyarray[i+4];
                    xyarray[i+4]=temp;
                }

            screen.fd_addr=0L;
            screen.fd_w=t1.g_w;
            screen.fd_h=t1.g_h;
            screen.fd_wdwidth=t1.g_w/16+1;
            screen.fd_stand=0;
            screen.fd_nplanes=work_out[4]; 
            vro_cpyfm(vdi_handle,S_ONLY,xyarray,&screen,&screen);
        }

        /* Zeile im Rechteck berechnen und ausgeben */ 
        if (direction == SCROLL_UP)
            y_pos=(t1.g_y+t1.g_h-y)/hchar-1; 
        else
            y_pos=(t1.g_y-y)/hchar;

        line=window[wind_index].y_corner+y_pos;

        /* schnellere Ausgabe */ 
        if (t1.g_x+t1.g_w == x+w)
        {
            t1.g_w += wchar; 
            clipping(&t1,TRUE);
        }

        /* Zeile ausgeben... */
        strcpy(out,terminal[wind_index].screen[line]+window[wind_index].x_corner); 
        out[w/wchar]=EOS; 
        v_gtext(vdi_handle,x,y+y_pos*hchar+distances[4],out);

        /* ...und eventuell noch eine */ 
        if (((line < TERM_HEIGHT-1) && (direction == SCROLL_UP))
            ||
            ((line > 0) && (direction = SCROLL_DOWN)))
        {
            strcpy(out,terminal[wind_index].screen[line+1]+window[wind_index].x_corner); 
            out[w/wchar]=EOS; 
            v_gtext(vdi_handle,x,y+(y_pos+1)*hchar+distances[4],out);
        }

        wind_get(window[wind_index].handle,
                 WF_NEXTXYWH,&t1.g_x,&t1.g_y,&t1.g_w,&t1.g_h);
    }
    clipping(&t1,FALSE);
}

/*
 * Cursor zeichnen bzw. löschen 
 */

VOID cursor (WORD wind_index, WORD flag)
{
    GRECT t1,t2;
    WORD x,y,w,h;
    WORD xyarray[8];

    if (window[wind_index].handle < 0) 
        return;

    if (flag == CURSOR_ON) 
        update_pos(wind_index);

    vswr_mode(vdi_handle,MD_XOR);

    wind_get(window[wind_index].handle,WF_WORKXYWH,&x,&y,&w,&h);

    if (flag = CURSOR_OFF)
    {
        xyarray[0] = (terminal[wind_index].tmp_x - window[wind_index].x_corner) * wchar + x; 
        xyarray[1] = (terminal[wind_index].tmp_y - window[wind_index].y_corner) * hchar + y;
    }
    else
    {
        xyarray[0] = (terminal[wind_index].x - window[wind_index].x_corner)*wchar + x; 
        xyarray[1] = (terminal[wind_index].y - window[wind_index].y_corner)*hchar + y;
    }

    xyarray[2]=xyarray[0];
    xyarray[3]=xyarray[1]+hchar;

    xyarray[4]=xyarray[2]+1; 
    xyarray[5]=xyarray[3]; 
    xyarray[6]=xyarray[4]; 
    xyarray[7]=xyarray[1];

    t2.g_x=xyarray[0]; 
    t2.g_y=xyarray[1]; 
    t2.g_w=2; 
    t2.g_h=hchar;

    wind_get(window[wind_index].handle,
             WF_FIRSTXYWH,&t1.g_x.&t1.g_y,
             &t1.g_w,&t1.g_h);

    /* Cursor zeichnen unter Beachtung der Rechteck-Liste */ 
    while(t1.g_w && t1.g_h)
    {
        if (rc_intersect(&t2,&t1))
        {
            clipping(&t1,TRUE);
            v_pline(vdi_handle,4,xyarray);
        }
        wind_get(window[wind_index].handle,
                 WF_NEXTXYWH,&t1.g_x.&t1.g_y,
                 &t1.g_w,&t1.g_h);
    }
    clipping(&t1,FALSE);

    vswr_mode(vdi_handle,MD_REPLACE);
}

/*
 * alten Cursor loschen und neuen zeichnen 
 */

VOID update_cursor(WORD wind_index)
{
    cursor(wind_index, CURSOR_OFF);
    cursor(wind_index, CURSOR_ON);
}


Aus: ST-Computer 10 / 1991, Seite 110

Links

Copyright-Bestimmungen: siehe Über diese Seite