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:
BIT | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
&xnbsp; | SC1 | SC0 | RL1 | RL0 | M2 | M1 | M0 | BCD |
- 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:
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:
- Disk Driver, z.B. Ausführen direkter UPD765 Controller Kommandos
- SIO Driver
- Terminal Emulator, Infos über den Bildschirm
- Tastatur, Tastendefinition wie unter SETKEYS
- 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: | Accu | Gewählter Modus |
&xnbsp; | 00H: Kein Handshake |
&xnbsp; | FFH: Handshake |
&xnbsp; | D | Anzahl Stop Bits |
&xnbsp; | 0 : 1 Stop Bit |
&xnbsp; | 1 : 1,5 Stop Bits |
&xnbsp; | 2 : 2 Stop Bits |
&xnbsp; | E | Parität |
&xnbsp; | 0 : Keine |
&xnbsp; | 1 : Ungerade |
&xnbsp; | 2 : Gerade |
&xnbsp; | H | Anzahl Datenbits des Enpfängers |
&xnbsp; | L | Anzahl Datenbits des Senders (5, 6, 7 o. 8) |
Subfunktion: | UART Bits DTR und RTS setzen/löschen |
Register: | Accu | 7DH 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 Bd | 9: | 1800 Bd |
&xnbsp; | 2: | 75 Bd | 10: | 2400 Bd |
&xnbsp; | 3: | 110 Bd | 11: | 3600 Bd |
&xnbsp; | 4: | 134,5 Bd | 12: | 4800 Bd |
&xnbsp; | 5: | 150 Bd | 13: | 7200 Bd |
&xnbsp; | 6: | 300 Bd | 14: | 9600 Bd |
&xnbsp; | 7: | 600 Bd | 15: | 19200 Bd |
- Funktion 3: UART Werte lesen
XBIOS_FUN: | 00BCH |
Nach Aufruf dieser Funktion sind die Register wie folgt belegt: |
&xnbsp; | Accu | 00H: kein Handshake |
&xnbsp; | FFH: Handshake |
&xnbsp; | B | Kodierte Epfänger Baudrate |
&xnbsp; | C | Kodierte Sender Baudrate |
&xnbsp; | D | Stop Bits (s.o.) |
&xnbsp; | E | Parität (s.o.) |
&xnbsp; | H | Datenbits des Empfängers (s.o.) |
&xnbsp; | L | Datenbits 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:
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | Bit |
— | — | — | — | — | TxBE | — | RxCA | RR0 |
- 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:
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | Bit |
— | FE | OE | PE | — | — | — | — | RR1 |
- 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:
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | Bit |
— | — | CTS | DSR | DCD | — | — | — | RR0 |
- 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......
|
|