Zurück zu den JOYCE-Aktivitäten
Bald nach der Beschäftigung mit dem JOYCE unter CP/M habe ich mir die Schnittstellenerweiterung CPS 8256 und einen Akustik-Koppler 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.
John Elliot, der sehr engagiert ist, veröffentlichte 2002 das Dokument: PCW Hardware.
Noch später (2004!) tauchte im Internet eine Seite zum Interface CPS8256 auf, inklusive Schaltplan. Die Origunalseite gibt es nicht mehr, dafür findet sich eine Beschreibung hier, der Schaltplan ist hier zu finden.
Für weitere Informationen hier eine Zilog Dokumentation, in der auch der Z80 UART 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. Außerdem findet man einen Baustein des Typs 74LS373, ein acht Bit Latch.

Die serielle Schnitttstelle (SIO) ist aufgebaut aus: Die parallele Schnittstelle (CEN) ist aufgebaut aus: 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: 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
 SC1SC0RL1RL0M2M1M0BCD
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 21, 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:

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

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

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

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......