Werner Cirsovius:

 

Bald nach der Beschäftigung mit dem JOYCE unter CP/M habe ich mir die Schnittstellenerweiterung CPS 8256 und einen Akustikkoppler mit 300 Baud zugelegt. Da ich damals selber gerne programmierte, habe ich mich ausgiebig mit der Behandlung der seriellen Schnittstelle auseinandergesetzt. Im Doppelheft 4+5/1987 der "JOYCE-NEWS" wurde der folgende Artikel zu diesem Thema veröffentlicht.
Die JOYCE-NEWS wurde leider sehr bald eingestellt.
Später fand sich unter www.systemed.net/pcw/cps8256.html eine Zusammenstellung der CPS8256, die ich übersetzt habe.
Noch später (2004!) tauchte im Internet eine Seite zum Interface CPS256 auf, inklusive Schaltplan.
Für weitere Informationen hier eine Zilog Dokumentation, in der auch der Z80 UART (langes Dokument, ca. 2Mb!) beschrieben wird und eine Beschreibung des Intel Timers 8253 (Hier als 82C24, der mit dem 8253 kompatibel ist).

 

INTERFACE-Programmierung

 

Werner Cirsovius, durch seine Firmware-Artikel in NEWS 2+3/87 bereits bestens bekannt, beschreibt hier die Serielle Schnittstelle und ihre Programmierung. In Unterschied zu manch anderen Listings, vorwiegend in Pascal geschrieben und daher nicht von jedem verwendbar, wird in der NEWS nicht der Source-Asssemblerkode oder der Basic-Lader auf Diskette veröffentlicht, sondern das Ergebnis dieses Artikels, ein speziell von Werner für den JOYCE angepaßtes Datenübertragungsprogramm aus dem Public-Domain-Tool: KERMIT!

VORWORT

Diese Zusammenfassung wendet sich an alle, die den JOYCE mit der Schnittstellenerweiterung aufgerüstet haben und nun den Weg in die weite Welt über die serielle Schnittstelle suchen. Es sollen Tips und Hinweise gegeben werden für eine Programmierung mit Schwerpunkt auf Maschinensprache, da hier gewisse Möglichkeiten bei der Optimierung von Programmen der Datenfernübertragung (DFÜ) Programmen liegen, also bei Mailboxes etc.

Grundsätzlich sollte bei der Programmierung unter CP/M immer auf die BDOS Schnittstelle zugegriffen werden. In seltenen Fällen, so z.B. bei der zeichenorientierten Ein/Ausgabe über die Konsole, kann die BIOS Schnittstelle verwendet werden, um Spalten- und Zeilenzählmechanismus des BDOS zu umgehen. Keinesfalls sollte unter CP/M PLUS die BIOS Schnittstelle für Diskettenoperationen direkt verwendet werden, da dann das Systen abstürzen kann. Dies deshalb, weil beim CP/M PLUS des JOYCE die Sprungziele der BIOS Tabelle in der BIOS Bank liegen, die natürlich bei normalen Programmabläufen nicht eingestellt ist.

Eine Ausnahme von dieser Empfehlung ist das Abeiten mit der DFÜ. Hier ist es nämlich wichtig, genaue Informationen über den Zustand der DFÜ Einrichtung zu erhalten. Ebenfalls muß u.U. ein gezieltes Setzen oder Rücksetzen von bestimmten Kontrollbits erfolgen. Diese Funktionen werden weder vom BDOS noch vom BIOS unterstützt.

Der nachfolgende Bericht gibt einen kurzen Überblick über den Aufbau des JOYCE gefolgt von einer Beschreibung der Schnittstellenerweiterung.

Danach wird die Programmierung der seriellen Schnittstelle beschrieben.

Jeder, der zwecks Aufrüstung des JOYCE von 256kBytes auf das Doppelte oder aber aus Neugier das Gerät aufgeschraubt hat, wird enttäuscht sein über die wenigen Chips, die diese Maschine zu bieten hat (Siehe Blockdiagramm in Bild 1).

Fast besser bestückt ist da die Schnittstellenerweiterung.
Obwohl der Floppy Controller über den sog. 'BIOS extended Jump block' direkt programmiert werden kann, soll nicht weiter auf diese Möglichkeit eingegangen werden. CP/M PLUS kann sowohl die Laufwerke als auch die restlichen I/O Geräte viel besser über BDOS Aufrufe verwalten.
    Schraubt man nun die Schnittstellenerweiterung auf, so sieht man sofort zwei große Chips:
  • Z80 DART Dies ist ein Chip, der zwei UARTs (serielle Schnittstellen) beinhaltet.
  • 8253 Ein Timer Chip mit drei unabhängigen Timer Kanälen.
Außerdem findet man einen Baustein des Typs 74LS373, ein acht Bit Latch.

Die serielle Schnitttstelle (SIO) ist aufgebaut aus:
  • 1/2 Z80 DART UART mit Hardware Handshake Signalen
  • 2/3 8253 Zwei Timer für Sender und Empfänger Raten
Die parallele Schnittstelle (CEN) ist aufgebaut aus:
  • 1 74LS373 Acht Bit Latch für die Daten
  • 1/2 Z80 DART II für Strobe und BUSY
  • 1/3 8253 Für das Timing
Beachtenswert ist die Verwendung des zweiten UART's für die parallele Schnittstelle!

Im weiteren Verlauf soll nun auf die Programmierung der seriellen Schnittstelle (SIO) eingegangen werden, denn die parallele Schnittstelle (CEN) kann CP/M viel besser bedienen.

Für die Adressierung der I/O Ports ergeben sich nun folgende Adressen in Hex:
  • E0 UART Daten Port. Über diese Adresse können Daten gesendet oder empfangen werden.
  • E1 UART Control Port, über diesen Port können bestimmte Parameter für das UART gesetzt oder aber bestimmte Zustände abgefragt werden. Die Programmierung ist allerdings recht kompliziert.
  • E4 Timer Kanal 0. Dieser setzt die Übertragungsrate für den Sender (Transmitter).
  • E5 Timer Kanal 1. Dieser setzt die Übertragungsrate für den Empfänger (Receiver).
  • E7 Timer Control, über diese Adresse werden Timer Parameter eingestellt.
Da die Programmierung des Timers einfacher ist als die des UART's, soll mit ihr begonnen werden.

Bevor einem Kanal Daten übergeben werden können, muß zunächst ein Kontrollwort an Port E7 geschickt werden. Dieses Wort hat folgendes Format:

BIT76543210
&xnbsp;SC1SC0RL1RL0M2M1M0BCD
  • SC1, SC0 bestimmen den Timer, der programmiert werden soll. Für Kanal 0 ist das SC1,SC0=0,0 und für Kanal 1 SC1,SC0=0,1
  • RL1, RL0 bestimmen, in welcher Reihenfolge das Teilerwort geschickt wird. Die Bits werden beide auf 1,1 gesetzt, dies entspricht dem üblichen Modus des Z80 Prozessors (erst das niederwertige Byte)
  • M2, M1, M0 bestimmen den Zählmodus. Hier wird 0,1,1 gewählt, was einem Rechteckgenerator mit Teilung 1:1 entspricht.
  • BCD gibt noch die Art der Teilung an, sie ist auf 0 zu setzen. Das entspricht einer Binärteilung.
Es ist zu erkennen, daß nur das Bit SC0 geändert wird. Daraus ergeben sich die beiden möglichen Kontrollworte:
Timer 0:0011 0110 oder 36Hex
Timer 1:0111 0110 oder 76Hex
Ist dieses Wort dem Timer 8253 auf Port E7 übermittelt, so muß nun dem Kanal ein 16 Bit Teilungsfaktor gesendet werden. Für Kanal 0 auf Port E4 und Kanal 1 auf Port E5.
Kennt man die Baudrate, die man einstellen will, so berechnet sich dieser Wert wie folgt:
Die Frequenz, die am UART anliegen muß, beträgt dem üblicherweise 16fachen der Baudrate (Dieser Faktor 16 muß dem UART noch mitgeteilt werden, bzw. ist der voreingestellte Wert):

fUART = 16*BAUD

Die Frequenz, die am Timer anliegt, ist gerade die halbe CPU Frequenz:

fTIMER = fCPU/2

Folglich muß für den Teiler T gelten:

fTIMER = T*fUART

Genug der Algebra, die endgültige Gleichung lautet dann:

T = fCPU / (32*BAUD)

Bei einer Baudrate von 300 Baud und einer CPU Frequenz von 4MHz wird:

T = 417 oder hex 01A1

Hier noch die mögliche Programmierung für den Sender:

... mit MAC oder RMAC:

        timer   equ     0e7h
        baud0   equ     0e4h
        word    equ     0011$0110b
        value   equ     417

doit:   mvi     a,wrd
        out     timer
        mvi     a,low value
        out     baud0
        mvi     a,high value
        out     baud0
        ret

... mit BASIC

1000 baud=300
1010 value=4000000!/(32*baud)
1020 OUT &HE7,&H36
1030 OUT &HE4,value MOD 256
1040 OUT &HE4,value\256
1050 RETURN

... mit TURBO PASCAL

program BaudRate;
const
  Word   = $36;
  Baud0  = $E4;
  Value  = 417;

procedure bd_set(Word, Baud0,
                 Value : integer);
const
  Timer  = $E7;
begin
  port[Timer] := Word;
  port[Baud0] := lo(Value);
  port[Baud0] := hi(Value);
end;

begin
  bd_set(Word, Baud0, Value);
end.

Diese Programmierung ist natürlich nur dann interessant, wenn die Baudrate während eines Programmlaufes geändert werden soll. Meistens wird die Rate wohl nur einmal eingestellt werden, so daß dies ebenso mit der Utility SETSIO erfolgen kann. Für unser Beispiel der Einstellung der Sendebaudrate auf 300Bd gibt man dann ein

SETSIO TX 300

Zwar unterstützt das reguläre BIOS die Programmierung der Baudrate (BIOS Aufruf 2l, DEVINI = DEVice INItialization), jedoch ist die Unterstützung durch den "BIOS extended JUMP block" (XBIOS) besser.

Was ist nun das XBIOS?

Im regulären BIOS gibt es die Funktion 30, die USERF (= USER Function) heißt. Diese kann im JOYCE dadurch genutzt werden, indem einem CALL auf den entsprechenden BIOS Vektor direkt eine 16 Bit Adresse folgt, die eine Funktion im XBIOS anwählt.

Allgemein:

        CALL    USERF
        DW      XBIOS_FUN

Die Funktionen im XBIOS können unterteilt werden in:

  1. Disk Driver, z.B. Ausführen direkter UPD765 Controller Kommandos
  2. SIO Driver
  3. Terminal Emulator, Infos über den Bildschirm
  4. Tastatur, Tastendefinition wie unter SETKEYS
  5. Verschiedenes, z.B. Zugriff auf JOYCE Video RAM

Für die DFÜ interessant ist Gruppe 2, SIO Driver. Hier sind drei Funktionen implementiert:

  • Funktion 1: Initialisierung des UART
    XBIOS_FUN:00B6H
    Subfunktion:UART Werte setzen
    Register:AccuGewählter Modus
    &xnbsp;00H: Kein Handshake
    &xnbsp;FFH: Handshake
    &xnbsp;DAnzahl Stop Bits
    &xnbsp;0 : 1 Stop Bit
    &xnbsp;1 : 1,5 Stop Bits
    &xnbsp;2 : 2 Stop Bits
    &xnbsp;EParität
    &xnbsp;0 : Keine
    &xnbsp;1 : Ungerade
    &xnbsp;2 : Gerade
    &xnbsp;HAnzahl Datenbits des Enpfängers
    &xnbsp;LAnzahl Datenbits des Senders (5, 6, 7 o. 8)
    Subfunktion:UART Bits DTR und RTS setzen/löschen
    Register:Accu7DH RTS ein
    &xnbsp;7EH RTS aus
    &xnbsp;7FH DTR ein
    &xnbsp;80H DTR aus
  • Funktion 2: Baudraten setzen
    XBIOS_FUN:00B9H
    Register:H kodierte Baudrate für Empfänger
    &xnbsp;L kodierte Baudrate für Sender
    Die Kodierung lautet
    Code:0:8:1200 Bd
    &xnbsp;1:50 Bd9:1800 Bd
    &xnbsp;2:75 Bd10:2400 Bd
    &xnbsp;3:110 Bd11:3600 Bd
    &xnbsp;4:134,5 Bd12:4800 Bd
    &xnbsp;5:150 Bd13:7200 Bd
    &xnbsp;6:300 Bd14:9600 Bd
    &xnbsp;7:600 Bd15:19200 Bd
  • Funktion 3: UART Werte lesen
    XBIOS_FUN:00BCH
    Nach Aufruf dieser Funktion sind die Register wie folgt belegt:
    &xnbsp;Accu00H: kein Handshake
    &xnbsp;FFH: Handshake
    &xnbsp;BKodierte Epfänger Baudrate
    &xnbsp;CKodierte Sender Baudrate
    &xnbsp;DStop Bits (s.o.)
    &xnbsp;EParität (s.o.)
    &xnbsp;HDatenbits des Empfängers (s.o.)
    &xnbsp;LDatenbits des Senders (s.o.)
Das oben geschilderte Verfahren der Einstellung der Senderrate läßt sich dann mit dem XBIOS wie folgt lösen
        CALL USERF      ;Laden der UART Werte
        DW   00BCH
        MOV  H,B        ;Empfänger so lassen
        MVI  L,6        ;Sender 300 Baud
        CALL USERF      ;Rate setzen
        DW   00B9H
Bleibt noch die Frage zu klären, wie das Unterprogramm "USERF" aussehen muß. Hierzu sollte der BIOS Vektor der Funktion 30 am Programmanfang initialisiert werden
INIT:   LHLD 1          ;BIOS Basis Adresse
        LXI  B,3*(30-1) ;Offset laden
        DAD  B          ;Vektor berechnen
        SHLD USERF+1    ;Als Sprungziel speichern
        RET
USERF:  JMP  $-$        ;Sprung zur BIOS Funktion 30

Z80 UART

Während das Programmieren des 8253 Chip relativ simpel ist, so gilt dies nicht für das Z80 UART. Deshalb sollen hier auch nicht alle Möglichkeiten diskutiert werden, besonders da viele Funktion über XBIOS Funktion 00B6H gesetzt werden können. Für Interessenten verweise ich auf das Datenblatt des Z80 DART (DART heißt, daß zwei UART's in einem Chip vorhanden sind, Double UART).
Eingehen will ich aber noch auf die Programmierung des Datentransfers sowie das Verwerten von Statusinformationen. Letzteres wird nicht vom XBIOS unterstützt.
Das DART belegt, wie oben angedeutet, zwei I/O Ports des Z80, nämlich E0H für Datentransfer und E1H für Kontrollen. Port E1H ist jedoch mit mehreren Unterfunktionen ausgestattet, es gibt drei Leseregister (Read Register RR0 - RR2) und acht Schreibregister (Write Register WR0 - WR7). So ist z.B. WR5 verantwortlich für die Bits DTR und RTS. Allerdings wird in dieses Register auch Information über die Anzahl Datenbits geschrieben. Hierdurch kann es zu Konflikten kommen, so daß man diese Funktionen aber das XBIOS abwickeln sollte.
Die noch verbleibende Aufgabe besteht darin, festzustellen ob ein Datentransfer durchgeführt werden kann und ob externe Signale anliegen. Außerdem kann festgestellt werden, ob Fehler vorliegen.

Funktion 1 : Anzeige ob Transfer durchgeführt werden kann.
Format:

76543210Bit
TxBERxCARR0

  • RxCA: Receiver Character Available - zeigt an, ob ein Zeichen empfangen wurde (RxCA = 1) oder noch nicht (RxCA = 0)
  • TxBE: Transmitter Buffer Empty - zeigt an ob ein neues Zeichen gesendet werden kann (TxBE = 1) oder nicht (TxBE = 0)
Eine Routine, die ein Zeichen im Akku senden soll kann wie folgt aussehen:
OUTPUT: PUSH PSW        ; Zeichen retten
WAIT:   IN   0E1H       ; Warten bis Zeichen da
        ANI  0000$0100B
        JZ   WAIT
        POP  PSW
        OUT  0E0H       ; Ausgabe
        RET
Funktion 2 : Anzeige ob Übertragungsfehler vorliegt
Format:

76543210Bit
FEOEPERR1

  • PE: Parity error - die empfangene Parität entspricht nicht dem eingestellten Wert
  • OE: Overrun error - zeigt an, daß ein empfangenes Zeichen nicht rechtzeitig gelesen und von einem folgenden Zeichen überschrieben wurde
  • FE: Framing error - zeigt einen Fehler im Datenformat an
Da diese Statusbits im Register 1 (RR1) liegen, muß dieses Register zuvor adressiert sein.

Eine mögliche Routine zur Fehlerabfrage könnte wie folgt aussehen, hierbei ist die Zero Flag des Prozessors gesetzt, wenn kein Fehler vorliegt:
ERROR:  MVI  A,1
        OUT  0E1H       ; RR1 adressieren
        IN   0E1H       ; Fehler einlesen
        PUSH PSW
        MVI  A,0
        OUT  0E1H       ; RR0 adressieren
        POP  PSW
        ANI  0111$0000B ; Zero Flag beeinflussen
        RET
Funktion 3 : Anzeige von externen Leitungszuständen
Format:

76543210Bit
CTSDSRDCDRR0

  • CTS: Clear to Send - das angeschlossene Gerät kann Daten empfangen, wenn CTS = 1 ist
  • DSR: Data Set Ready - das angeschlossene Gerät ist betriebsbereit, wenn DSR = 1 ist
  • DCD: Data Carrier Detected - zeigt an, ob vom angeschlossenen Modem oder Akustik-Koppler das Trägersignal anliegt, wenn DCD = 1 ist.
Obwohl auch diese Information im Register 0 (RR0) liegt (Siehe Funktion 1), ist die Behandlung etwas anders. Vor den Auslesen muß nämlich zweimal ein Reset auf WR0 gegeben werden. Dies steht allerdings nicht im Datenblatt, ohne das zweite Auslesen ist es mir aber nicht gelungen, eine Statusänderung auf den Leitungen anzuzeigen.
Eine Routine, die die externen Signale einliest und maskiert, könnte wie folgt aussehen:
STATUS: MVI  A,0001$0000B
        OUT  0E1H       ; Reset einmal
        OUT  0E1H       ; .. zweimal
        IN   0E1H       ; Externe Signale lesen
        ANI  0011$1000B ; Bits maskieren
        RET

Zusatz zum gedruckten Artikel:

In einigen Anwendungen ist es notwendig, ein BREAK-Signal abzusenden. Dies ist ein Signal, dass die Sendeleitung für ca. 300 Millisekunden (ms) aktiviert, wodurch auf der Empfangsseite eine Unterbrechung ausgelöst wird. Beim Z80 UART funktioniert dies über das Schreibregister WR5.
[ Das BREAK-Signal sollte in jedem Falle mindest gleich lang sein wie die längste Übertragungsdauer eines Bytes. Die Länge des BREAK-Signals ergibt sich daher aus folgender Überlegung: Bei der niedristen Rate von 50 Baud dauert die Übertragung eines Bits 1/50 Sekunden, also 20 ms. Bei einer maximalen Bitlänge von 12 Bits (= 1xStart + 8xDaten + 1xParität + 2xStop) beträgt die Übertragungsdauer 12x20 ms = 240 ms. Mit 300 ms liegt man also auf der sicheren Seite. ]
Eine Routine, die ein BREAK senden soll kann wie folgt aussehen (entnommen aus dem KERMIT-Treiber):
SENDBR:        MVI        D,1001$1010B        ; Maske für BREAK
        MVI        E,30                ; Länge des BREAK-Signals ist
                                ; 300 Millisekunden
SNDBR1:        MVI        A,1                ; RR1 adressieren
        OUT        0E1H
        IN        0E1H                ; Einlesen
        ANI        0000$0001B        ; Testen des "ALL DONE" Bits
        JZ        SNDBR1                ; Warten bis gesetzt
;
; Nun wird das BREAK-Signal gesendet
;
SETBIT:        MVI        A,5                ; WR5 adressieren
        OUT        0E1H
        LDA        TXBITS                ; Bits für Sender laden
                                ; Diese Bits stehen wie folgt:
                                ; x00x$xxxx  5 Bits
                                ; x01x$xxxx  7 Bits
                                ; x10x$xxxx  6 Bits
                                ; x11x$xxxx  8 Bits
        ORA        D                ; Ausgabe BREAK,
        OUT        0E1H                ; TXENABLE, RTS
;
; Jetzt Verzögerung von 300 Millisekunden durchführen
;
        MOV        A,E                ; Verzögerungswert
        CALL        DELAY
;
; Die Zeit ist abgelaufen.
; Sender in den normalen Zustand versetzen.
;
        MVI        A,5                ; WR5 adressieren
        OUT        0E1H
        LDA        TXBITS                ; Bits für Sender erneut laden
        ORI        1000$1010B        ; Kein BREAK
        OUT        0E1H                ; Aber TXENABLE und RTS
        RET                        ; Fertig
;
; Beispiel für eine Verzögerungsroutine
; Hier für 10 Millisekunden
;
DELAY:        LD        C,40                ; Entspricht 4 MHz
DELAY2:        LD        B,70
DELAY3:        DEC        B
        JP        NZ,DELAY3
        DEC        C
        JP        NZ,DELAY2
        DEC        A
        JP        NZ,DELAY
        RET

Schluß

Der vorliegende Bericht sollte eine Einweisung in die wichtigsten Programmiermöglichkeiten der für DFÜ nutzbaren seriellen Schnittstelle sein. Auf Grund der etwas dürftigen Informationen aus dem Hause SCHNEIDER sind sicherlich nicht alle Möglichkeiten ausgeschöpft.
Zu beachten ist auf jeden Fall eine sorgfältige Programmierung auf dieser direkten Hardwareebene unabhängig von der verwendeten Programmiersprache. Man bedenke bitte, daß über I/O Ports auch die JOYCE Bankumschaltung erfolgt. Eine fehlerhafte Adressierung eines Ports kann unweigerlich zu Systemabstürzen führen. Das ist vielleicht noch nicht so schlimm, übler ist, wenn durch die falsche Programmierung eine Diskette vollständig gelöscht wird.

Nachbemerkung der Redaktion: Wir sind interessiert an Erfahrungsberichten über DFÜ, Mailboxen, Schnittstellen......