3324 lines
128 KiB
Plaintext
3324 lines
128 KiB
Plaintext
Date of release: 25 Dec 1995 Release 19
|
|
|
|
This is a summary on serial communication using the TTY protocol. It
|
|
contains information on the TTY protocol and hardware and software implemen-
|
|
tations for IBM PCs which has been derived from National Semiconductor data
|
|
sheets and practical experience of the author and his supporters. Starting
|
|
with release 5, some information on modems has been added.
|
|
|
|
If you want to contribute to this file in any way, please email me
|
|
(probably just reply to this posting). My email address is:
|
|
chris@phil.uni-sb.de. See the end for details.
|
|
|
|
It's the nineteenth publication of this file. Some errors have been corrected
|
|
and some information has been added (which has surely brought other errors
|
|
with it, see Murphy's Law).
|
|
|
|
[] brackets often indicate comments to sneaked material; copied lines are
|
|
indented. I've made great efforts to always mention who's to be credited.
|
|
Please tell me if you find something that you've written that's not correctly
|
|
associated with your name.
|
|
|
|
This compilation of information is (C) Copyright 1993 - 1995 by Christian
|
|
Blum; all rights reserved. This file is not to be reproduced commercially,
|
|
not even partially, without written permission. You are allowed to use it
|
|
in any other way you like. I don't want any (monetary) profit being drawn
|
|
out of it (neither by me nor by others! I don't mind if you have a look
|
|
or two at it at work though... :-). Please feel free to provide this file
|
|
to others for free or at your own expenses.
|
|
|
|
|
|
Changes since the last publication
|
|
==================================
|
|
|
|
Fixed some misinformation in the 'Mice' chapter.
|
|
|
|
|
|
What I'm doing
|
|
==============
|
|
|
|
I'm no longer very much into DOS (though I still make some money with it :),
|
|
so don't expect me reading all the groups regularly that I'm posting this
|
|
to.
|
|
|
|
|
|
Where to get this and other files from
|
|
======================================
|
|
|
|
The home location for this document is
|
|
|
|
ftp://ftp.phil.uni-sb.de/pub/staff/chris/The_Serial_Port
|
|
|
|
Other serial port stuff mentioned in this document can be found in the
|
|
same directory.
|
|
|
|
|
|
Acknowledgements (quite a bunch of people by now...)
|
|
================
|
|
|
|
The following persons have contributed (directly or indirectly :-) to
|
|
this summary by providing information or making suggestions/reporting
|
|
major errors. Tell me if your name is missing.
|
|
|
|
Madis Kaal <mast@anubis.kbfi.ee>
|
|
Steve Poulsen <stevep@ims.com>
|
|
Scott C. Sadow <NS16550A@mycro.UUCP>
|
|
Dan Norstedt <?>
|
|
Alan J. Brumbaugh <brumba@maize.rtsg.mot.com>
|
|
Mike Surikov <surikov@adonis.iasnet.com>
|
|
Varol Kaptan <E66964%trmetu.bitnet@relay.EU.net>
|
|
Richard F. Drushel <rfd@po.CWRU.Edu>
|
|
John A. Limpert <johnl@n3dmc.svr.md.us>
|
|
Brent Beach <ub359@freenet.victoria.bc.ca>
|
|
Torbjoern (sp?) Lindgren <tl@etek.chalmers.se>
|
|
Stephen Warner <ee_d316@dcs.kingston.ac.uk>
|
|
Kristian Koehntopp <kris@black.toppoint.de>
|
|
Angelo Haritsis <ah@doc.ic.ac.uk>
|
|
Jim Graham <jim@n5ial.mythical.com>
|
|
Ralf Brown <ralf@cs.cmu.edu>
|
|
Alfred Arnold <zam036@zam112.zam.kfa-juelich.de>
|
|
Andrew M. Langmead <aml@world.std.com>
|
|
Richard Clayton <richard@locomotive.com>
|
|
Christof Baumgaertner <baumg@rhrk.uni-kl.de>
|
|
Goran Bostrom <GORAN@infovox.se>
|
|
Brian Mork <bmork@opus-ovh.spk.wa.us>
|
|
Richard Steven Walz <rstevew@armory.com>
|
|
Scott David Daniels <daniels@cse.ogi.edu>
|
|
Brian Onn <Brian.Onn@Canada.Sun.COM>
|
|
Erik Suurmaa <erik@lerdeil.ee>
|
|
Terence Edwards <Terence@tedwards.demon.co.uk>
|
|
Christian 'naddy' Weisgerber <naddy@mips.pfalz.de>
|
|
Darcy Boese <possum@chardonnay.niagara.com>
|
|
|
|
|
|
|
|
Introduction
|
|
============
|
|
|
|
One of the most universal parts of the PC (except for the CPU, of course :-)
|
|
is its serial port. You can connect a mouse, a modem, a printer, a plotter,
|
|
another PC, dongles :) ...
|
|
|
|
But its usage (both software and hardware) is one of the best-kept secrets
|
|
for most users, besides that it is not difficult to understand how to
|
|
connect (not plug in) devices to it and how to program it.
|
|
|
|
Regard this file as a manual for the serial port of your PC for both
|
|
hardware and software.
|
|
|
|
|
|
Historical summary
|
|
------------------
|
|
|
|
In early days of telecommunication, errand-boys and optical signals (flags,
|
|
lights, clouds of smoke) were the only methods of transmitting information
|
|
across long distances. With increasing requirements on speed and growing
|
|
amount of information, more practical methods were developed. One milestone
|
|
was the first wire-bound transmission on May 24th, 1844 ("What hath God
|
|
wrought", using the famous Morse alphabet). Well, technology improved a bit,
|
|
and soon there were machines that could be used like typewriters, except that
|
|
you typed not only on your own sheet of paper but also on somebody elses.
|
|
The only thing that has changed on the step from the teletype to your PC
|
|
regarding serial communications is speed.
|
|
|
|
|
|
The TTY (teletyping) protocol
|
|
-----------------------------
|
|
|
|
Definition: A protocol is a clear description of the LOGICAL method of
|
|
transmitting information. This does NOT include physical realization.
|
|
|
|
There is a difference between bits per second and baud (named after J. M. E.
|
|
Baudot, one of those guys who gave a real push to teletyping): 'baud' means
|
|
'state changes of the line per second' while 'bits per second' ...
|
|
well, bits per second means bits per second. You may find this a bit weird
|
|
because the numbers are often the same; there's only a difference if the
|
|
line has more than two states. Since this is not the case with the RS-232C
|
|
(EIA-232) port of your PC, most people don't differentiate between 'baud' and
|
|
'bits per second', while I do. For your convenience, I've replaced baud with
|
|
bps even in copied material without special notice. Where you still find baud,
|
|
it should read bps in most cases (I didn't change labels in source codes, pin
|
|
names in data sheet information etc.). To illustrate the difference I give you
|
|
some figures: 2400 bps at 8n1 carry 1920 bits of information per second, and
|
|
modems send them at 600 baud thru' the phone wires using eight line states,
|
|
while 1200 bps at 7e1 carry 840 bits of information per second that modems
|
|
send at 600 baud using four different line states. I know it's confusing...
|
|
that's why I quote this from a letter I received from Brent Beach. He explained
|
|
it more clearly than I did (I've added some information):
|
|
|
|
Perhaps a small diagram might help, showing the relationship among the
|
|
players:
|
|
|
|
[bps] [baud]
|
|
CPU Data Serial Phone
|
|
Bus -- bytes --> Port -- bits --> Modem -- tones --> line --
|
|
|
|
|
|
|
|
CPU Data Serial |
|
|
Bus <-- bytes -- Port <-- bits -- Modem <-- tones ----------
|
|
(1) (2) (3)
|
|
|
|
The serial port accepts bytes from the CPU data bus and passes bits to the
|
|
modem. In doing this, the serial port can add or delete bits, depending on
|
|
the coding scheme in use.
|
|
|
|
At (1) we are concerned with bytes per second. At (2) we are concerned with
|
|
bits per second, and at (3) it's baud. We distinguish because the number of
|
|
bits at (2) need not be equal to the number of bits (that is, bytes times 8)
|
|
at (1), and the number of state changes at (3) is not necessarily the same
|
|
as the number of bits before.
|
|
Bits can be stripped going from (1) to (2): the serial port may transmit
|
|
only 6 or 7 of the 8 bits in the byte. Bits can be added going from (1) to
|
|
(2): the serial port can add a parity bit and stop bits. From (2) to (3),
|
|
bits may be clustered to groups that are transmitted using different
|
|
encoding schemes like 'Frequency Shift Keying' or 'Quadrature Amplitude
|
|
Modulation', to name some.
|
|
|
|
You can determine the transfer rate in bytes per second depending on the
|
|
serial port speed and the coding system. For example,
|
|
|
|
8n1: 1 start bit + 8 data bits + 1 stop bit = 10 bits per word.
|
|
At 2400 bps, this is 240 bytes/characters per second. 2400 bps are
|
|
normally transmitted using QAM ('Quadrature Amplitude Modulation')
|
|
where 4 bits are clustered, and hence encoded to 600 baud.
|
|
|
|
7e1: 1 start bit, 7 data bits, 1 even parity bit, 1 stop bit = 10 bits
|
|
per word. At 1200 bps, this is 120 bytes/characters per second. 1200
|
|
bps are encoded using DPSK ('Differential Phase Shift Keying', two
|
|
bits are clustered), and this results again in 600 baud.
|
|
|
|
|
|
Now let's leave modems for a while and have a look at the serial port itself.
|
|
|
|
The TTY protocol uses two different line states called 'mark' and 'space'.
|
|
(For the sake of clearness I name the line states 'high' (voltage) for
|
|
positive and 'low' (voltage) for negative voltages). If no data is
|
|
transmitted, the line is in its quiescent 'low' ('mark') state or in the
|
|
'break' state ('high'). Data looks like
|
|
|
|
space +---+ +---+ +---+ high '0' +12V
|
|
| | | | | |
|
|
mark ----------+ +-------+ +---+ +------- low '1' -12V
|
|
|
|
(1) --------(2)-------- (3)
|
|
|
|
(1) start bit (2) data bits (3) stop bit(s)
|
|
|
|
Steve Walz reported that in most (all?) books these kind of diagrams are drewn
|
|
the other way round (I just copied what I saw on the oscilloscope) and
|
|
that he'd use the labels 'high' and 'low' the other way round, corresponding
|
|
to the signals on the TTL level (a matter of taste I guess); here is what he
|
|
told me:
|
|
|
|
In American texts, we will expect to see the data frame for serial transfer
|
|
of all kinds represented, despite the method of transfer (RS-232C, RS-422,
|
|
and optical even), as being an interruption of a normally HI state, and we
|
|
expect to see the diagram you drew in the older release 8, but with the
|
|
labelling corrected as I have indicated:
|
|
|
|
mark ----------+ +-------+ +---+ +------- high '1' -12V
|
|
logical 1 | S | 1 1 | 0 | 1 | 0 | Stop
|
|
space +---+ +---+ +---+ low '0' +12V
|
|
(1) --------(2)---------(3)
|
|
(1) start bit (2) data bits (3) stop bit(s)
|
|
Thus transmitting the bit stream 01011, which is LSB first, MSB last.
|
|
|
|
Indeed it seems to us that a zero SHOULD be the quiescent state, and the
|
|
one an active state, but the first teletypes used a current loop to
|
|
continuously monitor the state of the line, and thus current flow was
|
|
regarded as a 1 and it is "MARK" -ing time, and a signal then left a "SPACE"
|
|
in the graph of current flow designating a zero. Thus the bits following
|
|
the start bit at level zero were true to their bit values, and a 11111 in
|
|
5 bit baudot looked like this, using three dashes per bit:
|
|
|
|
mark ------ ------------------------ 1 HI +5V TTL -12V RS-232C
|
|
space --- 0 LO 0V TTL +12V RS-232C
|
|
s 1 1 1 1 1 stop
|
|
|
|
and the baudot 10101 would appear thus:
|
|
|
|
mark ------ --- --- ------------ 1 HI +5V TTL -12V RS-232C
|
|
space --- --- --- 0 LO 0V TTL +12V RS-232C
|
|
s 1 0 1 0 1 stop
|
|
|
|
and the baudot 01010 would appear thus:
|
|
|
|
mark ------ --- --- --------- 1 HI +5V TTL -12V RS-232C
|
|
space ------ --- --- 0 LO 0V TTL +12V RS-232C
|
|
s 0 1 0 1 0 stop
|
|
|
|
and finally baudot 00000 would appear:
|
|
|
|
mark ------ --------- 1 HI +5V TTL -12V RS-232C
|
|
space ------------------ 0 LO 0V TTL +12V RS-232C
|
|
s 0 0 0 0 0 stop
|
|
|
|
Now I know that we don't send five bit baudot over RS-232C now, but I
|
|
wasn't about to try 8 bits, if you don't mind! :)
|
|
|
|
I know that people get confused about the proper way to draw these, since
|
|
we use inverted voltages to send them via RS-232C interface now, but they
|
|
are still called logical "1" and "mark" when it is really -12 Volts DC, and
|
|
it is called "0" and "space" when it is +12 Volts. And logical one or "mark"
|
|
corresponds to +5 Volts, while logical zero is "space" and corresponds to 0
|
|
Volts. It is this way both within the parallel bus of the computer or the
|
|
transmit output of a UART/USART, with the exception that this data frame is
|
|
terminated by remaining logic "1" or "mark" as a stop bit and preface
|
|
to the next data frame.
|
|
|
|
Both transmitter (TX) and receiver (RX) use the same data rate (measured
|
|
in bps, see above), which is the reciprocal value of the smallest time
|
|
interval between two changes of the line state. TX and RX know about the
|
|
number of data bits (probably with a parity bit added), and both know about
|
|
the (minimum!) size of the stop step (called the stop bit or the stop bits,
|
|
depending on the size of the stop step; normally 1, 1.5 or 2 times the size
|
|
of a data bit). Data is transmitted bit-synchronously and word-asynchronously,
|
|
which means that the size of the bits, the length of the words etc.pp. is
|
|
clearly defined while the time between two words is undefined.
|
|
|
|
The start bit indicates the beginning of a new data word (this means one
|
|
single character). It is used to synchronize transmitter and receiver and
|
|
is always a logical '0' (so the line goes 'high' or 'space').
|
|
|
|
Data is transmitted LSB to MSB, which means that the least significant
|
|
bit (LSB, Bit 0) is transmitted first with 4 to 7 bits of data following,
|
|
resulting in 5 to 8 bits of data. A logical '0' is transmitted by the
|
|
'space' state of the line (+12V), a logical '1' by 'mark' (-12V).
|
|
|
|
A parity bit can be added to the data bits to allow error detection.
|
|
There are two (well, actually five) kinds of parity: odd and even (plus
|
|
none, mark and space). Odd parity means that the number of 'low' or 'mark'
|
|
steps in the data word (including an optional parity bit, but not the
|
|
framing bits) is always odd, so the parity bit is set accordingly (I don't
|
|
have to explain 'even' parity, must I?). It is also possible to set the
|
|
parity bit to a fixed state or to omit it. See Registers section for
|
|
details on types of parity.
|
|
|
|
The stop bit does not indicate the end of the word (as it could be derived
|
|
from its name); it rather separates two consecutive words by putting the
|
|
line into the quiescent state for a minimum time (that means the stop bit
|
|
is a logical '1' or 'mark') in order for the next start bit to be clearly
|
|
visible.
|
|
|
|
The framing protocol is usually described by a sequence of numbers and
|
|
letters, eg. 8n1 means 1 start bit (always the same, thus omitted), 8 bits
|
|
of data, no parity bit, 1 stop bit. 7e2 would indicate 7 bits of data,
|
|
even parity, 2 stop bits (but I've never seen this one...). The usual thing
|
|
is 8n1 or 7e1.
|
|
|
|
Your PC is capable of serial transmission at up to 115,200 bps (step size
|
|
of 8.68 microseconds!). Typical rates are 300 bps, 1200 bps, 2400 bps and
|
|
9600 bps, with 19200 bps, 38400 bps and 57600 bps becoming more and more
|
|
popular with high speed modems. Note that some serial ports have difficulties
|
|
with high speeds! I've seen PS/2's failing to operate at more than 38400 bps!
|
|
How come that IBM machines are often the least IBM compatible? :-)
|
|
|
|
John A. Limpert told me some stuf about teletypes:
|
|
|
|
Real (mechanical) teletypes used 1 start bit, 5 data bits and 1.42 stop
|
|
bits. Support for 1.5 stop bits in UARTs was a compromise to make the
|
|
UART timing simpler. Normal speeds were 60 WPM (word per minute),
|
|
66 WPM, 75 WPM and 100 WPM. A word was defined as 6.1 characters.
|
|
The odd stop bit size was a result of the mechanical nature of the
|
|
machine. It was the time that the printer needed to finish the current
|
|
character and get ready for the next character. Most teletypes used
|
|
a 60 mA loop with a 130 V battery. 20 mA loops and lower battery voltages
|
|
became common when 8 level ASCII teletypes were introduced. The typical
|
|
ASCII teletype ran at 110 bps with 2 stop bits (11 bits per character).
|
|
|
|
It's surely more exact than what I wrote in previous releases. I've just got
|
|
to add that at least in Germany 50 bps was a familiar speed. And I think the
|
|
lower battery voltage he's talking about was 24 volts.
|
|
|
|
|
|
The physical transmission
|
|
-------------------------
|
|
|
|
Teletypes used a closed-loop line with a quiescent current of 20ma and a
|
|
space current of 0ma (typically), which allows to detect a 'broken line'
|
|
(hence the name of the 'break' flag, see the Registers section). The RS-232C
|
|
port of your PC uses voltages rather than currents to indicate logical states:
|
|
'mark'/'low' is signaled by -3v to -15v (typically -12V) and represents a
|
|
logical '1', 'space'/'high' is signaled by +3v to +15v (typically +12V) and
|
|
represents a logical '0'. The typical output impedance of the serial port of
|
|
a PC is 2 kiloohms (resulting in about 5ma @ 10v), the typical input impedance
|
|
is about 4.3 kiloohms, so there should be a maximum fan-out of 5 (5 inputs can
|
|
be connected to 1 output). Please don't rely on this, it may differ from PC
|
|
to PC.
|
|
|
|
Three lines (RX, TX & ground) are at least needed to make up a bidirectional
|
|
connection.
|
|
|
|
Q. Why does my PC have a 25pin/9pin connector if there are only 3 lines
|
|
needed?
|
|
A. There are several status lines that are only used with modems etc. See the
|
|
Hardware section and the Registers section of this file.
|
|
|
|
Q. How can I easily connect two PCs by a three-wire lead?
|
|
A. Connect RX1 to TX2 and vice versa, GND1 to GND2. In addition to this,
|
|
connect RTS to CTS & DCD and connect DTR to DSR at each end (modem software
|
|
often relies on that). See the hardware section for further details.
|
|
|
|
Please be aware that at 115,200 bps (ie. ca. 115 kHz, but we need the
|
|
harmonics up to at least 806 kHz) lines can no longer be regarded as 'ideal'
|
|
transmission lines. They are low-pass filters and tend to reflect and mutilate
|
|
the signals, but some ten meters of twisted wire should always be OK (I use 3m
|
|
of screened audio cable for file transfer purposes, and it works fine. Not
|
|
that other kinds of wire wouldn't do; I took what I found). See a good book on
|
|
transmission lines if you're interested in why long lines can be a problem.
|
|
|
|
This has been posted to comp.os.msdos.programmer by Andrew M. Langmead:
|
|
|
|
The RS-232C spec. has an official limit of 50 ft for RS-232C cables.
|
|
Realistically they can be much longer. The book "Managing UUCP and
|
|
Usenet" by O'Reilly and Associates has a table that they credit to
|
|
"Technical Aspects of Data Communications", by McNamara (Digital
|
|
Press, 1992). It lists the maximum distances for an RS-232C
|
|
connection.
|
|
|
|
Baud Rate | max distance | max distance
|
|
| shielded cable | unshielded cable
|
|
----------------------------------------------------------
|
|
110 | 5000ft | 3000ft
|
|
300 | 5000ft | 3000ft
|
|
1200 | 3000ft | 3000ft
|
|
2400 | 1000ft | 500ft
|
|
4800 | 1000ft | 250ft
|
|
9600 | 250ft | 250ft
|
|
|
|
Please note that "baud" is correct in this case, because we're speaking of
|
|
the transmission line itself.
|
|
|
|
This is what Torbjoern (sp?) Lindgren told me:
|
|
|
|
I have successfully transmitted at 115,200 with over 30m long cables!
|
|
And it wasn't especially good wires. I had some old telecables with 20
|
|
individual wires, and used 7 of them for transfer, and left the others
|
|
unconnected.
|
|
|
|
I don't remember the exact length, but I know it was something over
|
|
30m, and it probably was closer to 40m than 30m. The unused lines
|
|
probably shielded the lines from each other or something like that.
|
|
The computers used were two PC-compatibles with off-the-shelf
|
|
com-ports. Nothing fancy.
|
|
|
|
Note that some serial ports are more critical with mutilated signals than
|
|
others, so you just have to try and find out yourself what works.
|
|
|
|
|
|
|
|
Hardware
|
|
========
|
|
|
|
|
|
The connectors
|
|
--------------
|
|
|
|
PCs have 9pin/25pin male SUB-D connectors. The pin layout is as follows
|
|
(seen from outside your PC):
|
|
|
|
1 13 1 5
|
|
_______________________________ _______________
|
|
\ . . . . . . . . . . . . . / \ . . . . . /
|
|
\ . . . . . . . . . . . . / \ . . . . /
|
|
--------------------------- -----------
|
|
14 25 6 9
|
|
|
|
Name (V24) 25pin 9pin Dir Full name Remarks
|
|
--------------------------------------------------------------------------
|
|
TxD 2 3 o Transmit Data Data
|
|
RxD 3 2 i Receive Data Data
|
|
RTS 4 7 o Request To Send Handshaking
|
|
CTS 5 8 i Clear To Send Handshaking
|
|
DTR 20 4 o Data Terminal Ready Status
|
|
DSR 6 6 i Data Set Ready Status
|
|
RI 22 9 i Ring Indicator Status
|
|
DCD 8 1 i Data Carrier Detect Status
|
|
GND 7 5 - Signal ground Reference level
|
|
- 1 - - Protective ground Don't use this one
|
|
as signal ground!
|
|
|
|
The most important lines are RxD, TxD, and GND. Others are used with
|
|
modems, printers and plotters to indicate internal states.
|
|
|
|
'1' ('mark', 'low') means -3v to -15v (any voltage below ca. +2v if 1489
|
|
line receivers are used, as in most PCs), '0' ('space', 'high') means +3v
|
|
to +15v. On status lines, 'high' is the active state: status lines go
|
|
to the positive voltage level to signal events.
|
|
|
|
The lines are:
|
|
|
|
RxD, TxD: These lines carry the data; 1 is transmitted as 'mark' (what I
|
|
call 'low') and 0 is transmitted as 'space' ('high').
|
|
|
|
RTS, CTS: Are used by the PC and the modem/printer/whatsoever (further
|
|
on referred to as the data set, or DCE) to start/stop a communication.
|
|
The PC sets RTS to 'high', and the data set responds with CTS 'high'.
|
|
(always in this order). If the data set wants to stop/interrupt the
|
|
communication (eg. imminent buffer overflow), it drops CTS to 'low';
|
|
the PC uses RTS to control the data flow.
|
|
|
|
DTR, DSR: Are used to establish a connection at the very beginning, ie.
|
|
the PC and the data set 'shake hands' first to assure they are both
|
|
present. The PC sets DTR to 'high', and the data set answers with DSR
|
|
'high'. Modems often indicate hang-up by resetting DSR to 'low' (and
|
|
sometimes are hung up by dropping DTR).
|
|
|
|
(These six lines plus GND are often referred to as '7 wire'-connection or
|
|
'hand shake'-connection.)
|
|
|
|
DCD: The modem uses this line to indicate that it has detected the
|
|
carrier of the modem on the other side of the phone line. The signal is
|
|
rarely used by the software.
|
|
|
|
RI: The modem uses this line to signal that 'the phone rings' (even if
|
|
there is neither a bell fitted to your modem nor a phone connected :-).
|
|
|
|
GND: The 'signal ground', ie. the reference level for all signals.
|
|
|
|
Protective ground: This line is connected to the power ground of the
|
|
serial adapter. It should not be used as a signal ground, and it
|
|
MUST NOT be connected to GND (even if your DMM [Digital MultiMeter] shows
|
|
up an ohmic connection!). Connect this line to the screen of the lead (if
|
|
there is one). Connecting protective ground on both sides makes sure that
|
|
no large currents flow thru' GND in case of an insulation defect on one
|
|
side (hence the name).
|
|
|
|
Technical data (typical values for PCs):
|
|
|
|
Signal level Tx: -10.5v / +11v
|
|
Signal level Rx: <+2v / >+3v
|
|
Short circuit current: 6.8ma
|
|
Output impedance: ca 2 kiloohms (non-linear!)
|
|
Input impedance: ca 4.3 kiloohms (non-linear!)
|
|
|
|
|
|
Other asynchronous hardware than RS-232C
|
|
----------------------------------------
|
|
|
|
There are several other standards that use the same chipset and protocol as
|
|
RS-232C. RS-422 and the more robust (but compatible) version RS-485 (to name
|
|
some) use two wires for every signal. The transmitters can usually be
|
|
disabled and enabled by software, which makes it possible to use such
|
|
equipment in a bus system (RX and TX part share the same lines). Despite
|
|
from the possibility to enable and disable the receiver/transmitter section
|
|
of the port, they are fully compatible to existing RS-232C software if a
|
|
compatible chipset is used.
|
|
|
|
It's not possible to connect eg. RS-232C to RS-485 without an appropriate
|
|
interface.
|
|
|
|
|
|
Connecting devices (or computers)
|
|
------------------
|
|
|
|
When you connect a data set or DCE (eg. a modem), use this connection:
|
|
|
|
GND1 to GND2
|
|
RxD1 to RxD2
|
|
TxD1 to TxD2
|
|
DTR1 to DTR2
|
|
DSR1 to DSR2
|
|
RTS1 to RTS2
|
|
CTS1 to CTS2
|
|
RI1 to RI2
|
|
DCD1 to DCD2
|
|
|
|
In other words, simply connect each pin of the first plug with the
|
|
corresponding pin of the other. This can easily be done using a
|
|
25-wire ribbon cable and two crimp connectors.
|
|
|
|
When you connect another computer (or any other DTE, like a terminal), this
|
|
is the wiring you need (it is called a "null modem" connection):
|
|
|
|
GND1 to GND2
|
|
RxD1 to TxD2
|
|
TxD1 to RxD2
|
|
DTR1 to DSR2
|
|
DSR1 to DTR2
|
|
RTS1 to CTS2
|
|
CTS1 to RTS2
|
|
|
|
If software wants it, connect DCD1 to CTS1 and DCD2 to CTS2.
|
|
|
|
If hardware handshaking is not needed, you can omit the status lines.
|
|
Connect:
|
|
|
|
GND1 to GND2
|
|
RxD1 to TxD2
|
|
TxD1 to RxD2
|
|
|
|
Additionally, connect (if software needs it):
|
|
|
|
RTS1 to CTS1 & DCD1
|
|
RTS2 to CTS2 & DCD2
|
|
DTR1 to DSR1
|
|
DTR2 to DSR2
|
|
|
|
You won't need long wires for these! :-)
|
|
|
|
Remember: the names DTR, DSR, CTS & RTS refer to the lines as seen from
|
|
the DTE (your PC). This means that for your data set DTR & RTS are incoming
|
|
signals and DSR & CTS are outputs! Modems, printers, plotters etc. are
|
|
connected 1:1, ie. pin x to pin x.
|
|
|
|
|
|
Base addresses & interrupts
|
|
---------------------------
|
|
|
|
Normally, the following list is correct for your PC; note however that
|
|
if the BIOS can't find a port, it won't leave spaces in its port
|
|
table, so if there is no UART at 0x3E8, the port at 0x2E8 will be
|
|
called COM3 by DOS. Compare the section on logical vs. phyical names.
|
|
|
|
Port Name Base address Int # Int level (IRQ)
|
|
|
|
COM1 0x3F8 0xC 4
|
|
COM2 0x2F8 0xB 3
|
|
COM3 0x3E8 0xC 4
|
|
COM4 0x2E8 0xB 3
|
|
|
|
In your programs, you should refer to the table in the BIOS data segment.
|
|
This is an excerpt from Ralf Brown's interrupt list (the actual author
|
|
of this section is Robin Walker):
|
|
|
|
Format of BIOS Data Segment at segment 40h:
|
|
Offset Size Description
|
|
00h WORD Base I/O address of 1st serial I/O port, zero if none
|
|
02h WORD Base I/O address of 2nd serial I/O port, zero if none
|
|
04h WORD Base I/O address of 3rd serial I/O port, zero if none
|
|
06h WORD Base I/O address of 4th serial I/O port, zero if none
|
|
Note: Above fields filled in turn by POST as it finds serial
|
|
ports. POST never leaves gaps. DOS and BIOS serial device
|
|
numbers may be redefined by re-assigning these fields.
|
|
|
|
Please note that this table is not the bible and that the BIOS is not an
|
|
evangelist (and I'm rather sceptical anyway :-). Your BIOS might not tell
|
|
you the pure truth; if you get a zero it does not necessarily mean that
|
|
there are no more serial ports available. Your programs should nevertheless
|
|
have a look at the usual places for comm ports. See the "Programming" section
|
|
for an example program that checks if a UART is installed at a given base
|
|
address. Compare the "logical vs. physical names" section below.
|
|
|
|
Another good idea is writing a small program that's then run in the
|
|
AUTOEXEC.BAT and that fills the empty fields in the table with the
|
|
correct values. My Award BIOS fails to recognize my fourth port at
|
|
0x2E8, so I typed a few bytes (14 altogether) in the debugger that
|
|
write 0x2E8 to 0040:0006 and wrote them to a .COM file called in the
|
|
AUTOEXEC.BAT.
|
|
|
|
Also see the Programming section for a routine that detects the interrupt
|
|
level/number that a UART uses. It's not a good idea to hard-code level
|
|
4 and 3; make it at least user configurable.
|
|
|
|
See the chapter "Multi-Port Serial Adapters" for further information.
|
|
|
|
|
|
Logical vs. physical ports
|
|
--------------------------
|
|
|
|
DOS users (like card manufacturers) tend to confuse logical and
|
|
physical names. COM1, COM2, etc. are _logical_ names for the serial
|
|
ports 0, 1, etc. found by the BIOS during POST (Power-On Self Test).
|
|
The BIOS searches at four different I/O addresses for UARTS: 0x3F8,
|
|
0x2F8, 0x3E8, 0x2E8, in exactly this order. Every UART found has an
|
|
entry in the comm port table at segment 0x40, offset 0. The BIOS
|
|
manages up to four different UARTs, because the table has no more than
|
|
four spaces. To make the confusion complete, Microsoft decided
|
|
that DOS users wouldn't be comfortable with counting from zero, so
|
|
they numbered the logical names of the comm ports from 1 to 4. Thus
|
|
COM1 is the first UART found by the BIOS during POST, COM2 the second,
|
|
and so on. Usually COM1 has 0x3F8 as base addresses, COM2 0x2F8 and so
|
|
on, but that's not necessarily the case. Please do not use the logical
|
|
DOS names when you really mean physical addresses. It is _not_
|
|
possible to 'jumper a UART as COM3', at least not directly.
|
|
|
|
|
|
The chipsets
|
|
------------
|
|
|
|
In PCs, serial communication is realized with a set of three chips
|
|
(there are no further components needed! (I know of the need of address
|
|
logic & interrupt logic ;-) )): a UART (Universal Asynchronous
|
|
Receiver/Transmitter) and two line drivers. Normally, the 82450/16450/8250
|
|
does the 'brain work' while the 1488 and 1489 drive the lines (they are
|
|
level shifting inverters; the 1488 drives the outputs).
|
|
|
|
These chips are produced by many manufacturers; it's of no importance
|
|
which letters are printed in front of the numbers (mostly NS for National
|
|
Semiconductor). Don't regard the letters behind the number also (if it's not
|
|
the 16550A or the 82C50A); they just indicate special features and packaging
|
|
(Advanced, New, MILitary, bug fixes [see below] etc.) or classification.
|
|
Letters in between the numbers (eg. 16C450) indicate technology (C=CMOS).
|
|
|
|
You might have heard that it is possible to replace the 16450 by a 16550A
|
|
to improve reliability and reduce software overhead. This is only useful if
|
|
your software is able to use the FIFO (first in-first out) buffer feature.
|
|
The chips are fully pin-compatible except for two pins that are not used by
|
|
any serial adapter card known to the author: pin 24 (CSOUT, chip select out)
|
|
and pin 29 (NC, no internal connection). With the 16550A, pin 24 is -TXRDY
|
|
and pin 29 is -RXRDY, signals that aren't needed (except for DMA access -
|
|
but not in the PC) and that even won't care if they are shorted to +5V or
|
|
ground. Therefore it should always be possible to simply replace the 16450
|
|
by the 16550A - even if it's not always useful due to lacking software
|
|
capabilities. IT IS DEFINITELY NOT NECESSARY FOR COMMUNICATION AT UP TO LOUSY
|
|
9600 BPS! These rates can easily be handled by any CPU, and the
|
|
interrupt-driven communication won't slow down the computer substantially. But
|
|
if you want to use high-speed transfer with or without using the interrupt
|
|
features (ie. by 'polling'), or multitasking, or multiple channels 'firing' at
|
|
the same time, or disk I/O during transmission, it is recommendable to use the
|
|
16550A in order to make transmission more reliable if your software supports
|
|
it (see excursion some pages below).
|
|
|
|
There *are* differences between the 16550A, 16550AF, and 16550AFN. The 16550AF
|
|
has one more timing parameter (t_RXI) specified that's concerned with the
|
|
-RXRDY pin and that's of no importance in the PC. And the 16550AFN is the
|
|
only one still believed to be free of bugs (see below). So the best choice for
|
|
your PC is 16550AFN, but you are well off with the 16550AN, too. [Info from a
|
|
posting of Jim Graham.]
|
|
|
|
Don't worry about the missing 'A' if you have chips named xxx16550 which are
|
|
not from National Semiconductor (eg. UM16550). As long as the first example
|
|
in the 'Programming' section tells you that it is a 16550A, everything is
|
|
fine. I've never heard of non-NS 16550s with the FIFO bug (see below).
|
|
|
|
|
|
How to detect which chip is used
|
|
--------------------------------
|
|
|
|
This is really not difficult. The 8250 normally has no scratch register (see
|
|
data sheet info below), the 16450/82450 has no FIFO, the 16550 has no working
|
|
FIFO :-) and the 16550A performs alright. See the Programming section for
|
|
an example program that detects which one is used in your PC.
|
|
|
|
Note that there _are_ versions of the 8250 that _do_ have a scratch register!
|
|
It's rather impossible to distinguish them from the 16450, but then it's not
|
|
necessary either... I know of the SAB 82C50 from Siemens and the UM8250B
|
|
(from UMC, a taiwanese company with a globe symbol; thanks, Alfred, for
|
|
helping me out with that). You won't find 8250s in fast computers however,
|
|
because their bus timing is too slow.
|
|
|
|
|
|
Data sheet information
|
|
----------------------
|
|
|
|
Some hardware information taken from the data sheet of National
|
|
Semiconductor (shortened and commented):
|
|
|
|
Pin description of the 16450 (16550A) [Dual-In-Line package]:
|
|
|
|
+-----+ +-----+
|
|
D0 -| 1 +-+ 40|- VCC
|
|
D1 -| 2 39|- -RI
|
|
D2 -| 3 38|- -DCD
|
|
D3 -| 4 37|- -DSR
|
|
D4 -| 5 36|- -CTS
|
|
D5 -| 6 35|- MR
|
|
D6 -| 7 34|- -OUT1
|
|
D7 -| 8 33|- -DTR
|
|
RCLK -| 9 32|- -RTS
|
|
SIN -| 10 31|- -OUT2
|
|
SOUT -| 11 30|- INTR
|
|
CS0 -| 12 29|- NC (-RXRDY)
|
|
CS1 -| 13 28|- A0
|
|
-CS2 -| 14 27|- A1
|
|
-BAUDOUT -| 15 26|- A2
|
|
XIN -| 16 25|- -ADS
|
|
XOUT -| 17 24|- CSOUT (-TXRDY)
|
|
-WR -| 18 23|- DDIS
|
|
WR -| 19 22|- RD
|
|
VSS -| 20 21|- -RD
|
|
+-------------+
|
|
|
|
Note: The status signals are negated compared to the port! If you write a
|
|
'1' to the appropriate register bit, the pin goes 'low' (to ground level).
|
|
On its way to the port, the signal is inverted again; this means that the
|
|
status line at the port goes 'high' if you write a '1'. The same is true
|
|
for inputs: you get a '1' from the register bit if the line at the port is
|
|
'high'. SIN and SOUT are inverted, too. (negative voltage at the port
|
|
means +5v at the UART).
|
|
|
|
A0, A1, A2, Register Select, Pins 26-28:
|
|
Address signals connected to these 3 inputs select a UART register for
|
|
the CPU to read from or to write to during data transfer. A table of
|
|
registers and their addresses is shown below. Note that the state of the
|
|
Divisor Latch Access Bit (DLAB), which is the most significant bit of the
|
|
Line Control Register, affects the selection of certain UART registers.
|
|
The DLAB must be set high by the system software to access the Baud
|
|
Generator Divisor Latches. [I'm sorry, but it's called that way even if it's
|
|
a bps rate generator... :-)]. 'x' means don't care.
|
|
|
|
DLAB A2 A1 A0 Register
|
|
0 0 0 0 Receive Buffer (read) Transmitter Holding Reg. (write)
|
|
0 0 0 1 Interrupt Enable
|
|
x 0 1 0 Interrupt Identification (read)
|
|
x 0 1 0 FIFO Control (write) [undefined with the 16450. CB]
|
|
x 0 1 1 Line Control
|
|
x 1 0 0 Modem Control
|
|
x 1 0 1 Line Status
|
|
x 1 1 0 Modem Status
|
|
x 1 1 1 Scratch [special use on some boards. CB]
|
|
1 0 0 0 Divisor Latch (LSB)
|
|
1 0 0 1 Divisor Latch (MSB)
|
|
|
|
-ADS, Address Strobe, Pin 25: The positive edge of an active Address
|
|
Strobe (-ADS) signal latches the Register Select (A0, A1, A2) and Chip
|
|
Select (CS0, CS1, -CS2) signals.
|
|
Note: An active -ADS input is required when Register Select and Chip
|
|
Select signals are not stable for the duration of a read or write
|
|
operation. If not required, tie the -ADS input permanently low. [As it is
|
|
done in your PC. CB]
|
|
|
|
-BAUDOUT, Baud Out, Pin 15: This is the 16x clock signal from the
|
|
transmitter section of the UART. The clock rate is equal to the main
|
|
reference oscillator frequency divided by the specified divisor in the
|
|
Baud Generator Divisor Latches. The -BAUDOUT may also be used for the
|
|
receiver section by tying this output to the RCLK input of the chip. [Yep,
|
|
that's true for your PC. CB].
|
|
|
|
CS0, CS1, -CS2, Chip Select, Pins 12-14: When CS0 and CS1 are high and CS2
|
|
is low, the chip is selected. This enables communication between the UART
|
|
and the CPU.
|
|
|
|
-CTS, Clear To Send, Pin 36: When low, this indicates that the modem or
|
|
data set is ready to exchange data. This signal can be tested by reading
|
|
bit 4 of the MSR. Bit 4 is the complement of this signal, and Bit 0 is '1'
|
|
if -CTS has changed state since the previous reading (bit0=1 generates an
|
|
interrupt if the modem status interrupt has been enabled).
|
|
|
|
D0-D7, Data Bus, Pins 1-8: Connected to the data bus of the CPU.
|
|
|
|
-DCD, Data Carrier Detect, Pin 38: blah blah blah, can be tested by
|
|
reading bit 7 / bit 3 of the MSR. Same text as -CTS.
|
|
|
|
DDIS, Driver Disable, Pin 23: This goes low whenever the CPU is reading
|
|
data from the UART. It can be used to control bus arbitrary logic.
|
|
|
|
-DSR, Data Set Ready, Pin 37: blah, blah, blah, bit 5 / bit 1 of MSR.
|
|
|
|
-DTR, Data Terminal Ready, Pin 33: can be set active low by programming
|
|
bit 0 of the MCR '1'. Loop mode operation holds this signal in its
|
|
inactive state.
|
|
|
|
INTR, Interrupt, Pin 30: goes high when an interrupt is requested by the
|
|
UART. Reset low by the MR.
|
|
|
|
MR, Master Reset, Pin 35: Schmitt Trigger input, resets internal registers
|
|
to their initial values (see below).
|
|
|
|
-OUT1, Out 1, Pin 34: user-designated output, can be set low by
|
|
programming bit 2 of the MCR '1' and vice versa. Loop mode operation holds
|
|
this signal inactive high. [Not used in the PC. CB]
|
|
|
|
-OUT2, Out 2, Pin 31: blah blah blah, bit 3, see above. [Used in your PC to
|
|
connect the UART to the interrupt line of the slot when '1'. CB]
|
|
|
|
RCLK, Receiver Clock, Pin 9: This input is the 16x bps rate clock for
|
|
the receiver section of the chip. [Normally connected to -BAUDOUT, as in
|
|
your PC. CB]
|
|
|
|
RD, -RD, Read, Pins 22 and 21: When RD is high *or* -RD is low while the
|
|
chip is selected, the CPU can read data from the UART. [One of these is
|
|
normally tied. CB]
|
|
|
|
-RI, Ring Indicator, Pin 39: blah blah blah, Bit 6 / Bit 2 of the MSR.
|
|
[Bit 2 only indicates change from active low to inactive high! Curious,
|
|
isn't it? CB]
|
|
|
|
-RTS, Request To Send, Pin 32: blah blah blah, see DTR (Bit 1).
|
|
|
|
SIN, Serial Input, Pin 10.
|
|
|
|
SOUT, Serial Output, Pin 11.
|
|
|
|
-RXRDY, -TXRDY: refer to NS data sheet. These pins are used for DMA
|
|
channeling. Since they are not connected in your PC, I won't describe them
|
|
here.
|
|
|
|
VCC, Pin 40, +5v
|
|
|
|
VSS, Pin 20, GND
|
|
|
|
WR, -WR: same as RD, -RD for writing data.
|
|
|
|
XIN, XOUT, Pins 16 and 17: Connect a crystal here (1.5k betw. xtal & pin 17)
|
|
and pin 16 with a capacitor of approx. 20p to GND and other xtal conn. 40p
|
|
to GND. Resistor of approx. 1meg parallel to xtal. Or use pin 16 as an input
|
|
and pin 17 as an output for an external clock signal of up to 8 MHz.
|
|
|
|
|
|
Absolute Maximum Ratings:
|
|
|
|
Temperature under bias: 0 C to +70 C
|
|
Storage Temperature: -65 C to 150 C
|
|
All input or output voltages with respect to VSS: -0.5v to +7.0v
|
|
Power dissipation: 1W
|
|
|
|
Further electrical characteristics see the very good data sheet of NS.
|
|
|
|
|
|
UART Reset Configuration
|
|
|
|
Register/Signal Reset Control Reset State
|
|
--------------------------------------------------------------------
|
|
IER MR 0000 0000
|
|
IIR MR 0000 0001
|
|
FCR MR 0000 0000
|
|
LCR MR 0000 0000
|
|
MCR MR 0000 0000
|
|
LSR MR 0110 0000
|
|
MSR MR xxxx 0000 (according to signals)
|
|
SOUT MR high (neg. voltage at the port)
|
|
INTR (RCVR errs) Read LSR/MR low
|
|
INTR (data ready) Read RBR/MR low
|
|
INTR (THRE) Rd IIR/Wr THR/MR low
|
|
INTR (modem status) Read MSR/MR low
|
|
-OUT2 MR high
|
|
-RTS MR high
|
|
-DTR MR high
|
|
-OUT1 MR high
|
|
RCVR FIFO MR/FCR1&FCR0/DFCR0 all bits low
|
|
XMIT FIFO MR/FCR1&FCR0/DFCR0 all bits low
|
|
|
|
|
|
|
|
Known problems with several chips
|
|
---------------------------------
|
|
|
|
(From material Madis Kaal received from Dan Norstedt and stuff Erik Suurmaa
|
|
sent me)
|
|
|
|
8250 and 8250-B:
|
|
|
|
* These UARTs pulse the INT line after each interrupt cause has
|
|
been serviced (which none of the others do). [Generates interrupt
|
|
overhead. CB]
|
|
|
|
* The start bit is about 1 us longer than it ought to be. [This
|
|
shouldn't be a problem. CB]
|
|
|
|
* 5 data bits and 1.5 stop bits doesn't work.
|
|
|
|
* When a 1 is written to the bit 1 (Tx int enab) in the IER,
|
|
a Tx interrupt is generated. This is an erroneous interrupt
|
|
if the THRE bit is not set. [So don't set this bit as long as
|
|
the THRE bit isn't set. CB]
|
|
|
|
* The first valid Tx interrupt after the Tx interrupt is enabled
|
|
is probably missed. Suggested workaround:
|
|
1) Wait for the THRE bit to become set.
|
|
2) Disable CPU interrupts. [?]
|
|
3) Write Tx interrupt enable to the IER.
|
|
4) Write Tx interrupt enable to the IER again.
|
|
[Don't ask me why. I don't think it's necessary. CB]
|
|
5) Enable CPU interrupts. [?]
|
|
|
|
* The TEMT (bit 6) doesn't work properly.
|
|
|
|
* If both the Rx and Tx interrupts are enabled, and a Rx interrupt
|
|
occurs, the IIR indication of the Tx interrupt may be lost.
|
|
Suggested workarounds:
|
|
1) Test THRE bit in the Rx routine, and either set IER bit 1
|
|
or call the Tx routine directly if it is set.
|
|
2) Test the THRE bit instead of using the IIR for Tx.
|
|
|
|
[If one of these chips vegetates in your PC, go get your solder
|
|
iron heated... CB]
|
|
|
|
8250A, 82C50A, 16450 and 16C450:
|
|
|
|
* (Same problem as above:)
|
|
If both the Rx and Tx interrupts are enabled, and a Rx interrupt
|
|
occurs, the IIR indication may be lost; Suggested workarounds:
|
|
1) Test THRE bit in the Rx routine, and either set IER bit 1
|
|
or call the Tx routine directly if it is set.
|
|
2) Test the THRE bit instead of using the IIR.
|
|
3) [Don't enable both interrupts at the same time. CB]
|
|
4) [Replace the chip by a 16550AFN; it has this bug fixed. CB]
|
|
|
|
16550 (without the A):
|
|
|
|
* Rx FIFO bug: Sometimes the FIFO will get extra characters.
|
|
[This seemed to be very embarrassing for NS; they've added a
|
|
simple detection method for the 16550A (bit 6 of IIR). CB]
|
|
|
|
16550 AF
|
|
|
|
* When the TX FIFO is enabled, a character loss can appear if
|
|
the CPU writes a byte into the THR while the last one is still
|
|
in the shift register (not completely sent). [This is documented
|
|
by National Semiconductor; I've never experienced that, but that
|
|
might be because I've never seen a 16550 AF :) CB]
|
|
|
|
* Terence Edwards reports that his RS485 adapter with 16550 AF
|
|
chips and a 16 MHz xtal gets parity bits wrong at 512 kbps; not
|
|
very astonishing I'd say because the chip is only guaranteed to
|
|
operate at 256kbps, with an 8 MHz xtal, and parity generators are
|
|
rather slow circuits.
|
|
|
|
|
|
No 16550 AFN bugs reported (yet?)
|
|
|
|
[Same is true for the 16552, a two-in-one version of the 16550AFN, and the
|
|
16554, a quad-in-one version. CB]
|
|
|
|
You might call this a bug, though: in FIFO mode, THRE (bit 5 or LSR) is
|
|
cleared when there is at least one character in the Tx FIFO, not if the
|
|
FIFO can't take any more bytes; that's rather absurd, but that's the way
|
|
it is.
|
|
|
|
A very solid method of handling the UART interrupts that avoids all possible
|
|
int failures has been suggested by Richard Clayton, and I recommend it as
|
|
well. Let your interrupt handler do the following:
|
|
1. Disarm the UART interrupts by masking them in the IMR of the ICU.
|
|
2. Send a specific or an unspecific EOI to the ICU (first slave, then
|
|
master, if you're using channels above 7).
|
|
3. Enable CPU interrupts (STI) to allow high priority ints to be processed.
|
|
4. Read IIR and follow its contents until bit 0 is set.
|
|
5. Check if transmission is to be kicked (when XON received or if CTS
|
|
goes high); if yes, call tx interrupt handler manually.
|
|
6. Disable CPU interrupts (CLI).
|
|
7. Rearm the UART interrupts by unmasking them in the IMR of the ICU.
|
|
8. Return from interrupt.
|
|
This way you can arm all four UART ints at initialization time without
|
|
having to worry about stuck interrupts. Start transmission by simply calling
|
|
the tx interrupt handler after you've written characters to the tx fifo of
|
|
your program.
|
|
|
|
If you need details about programming the ICU, refer to Chris Hall's
|
|
document about the 8259 that's available from my archive.
|
|
|
|
|
|
Registers
|
|
=========
|
|
|
|
First some tables; full descriptions follow. Base addresses as specified by
|
|
IBM for a full-blown system; compare the section on logical & physical names.
|
|
|
|
|
|
1st 2nd 3rd 4th Offs. DLAB Register
|
|
------------------------------------------------------------------------------
|
|
3F8h 2F8h 3E8h 2E8h +0 0 RBR Receive Buffer Register (read only) or
|
|
THR Transmitter Holding Register (write only)
|
|
3F9h 2F9h 3E9h 2E9h +1 0 IER Interrupt Enable Register
|
|
3F8h 2F8h 3E8h 2E8h +0 1 DL Divisor Latch (LSB) These registers can
|
|
3F9h 2F9h 3E9h 2E9h +1 1 DL Divisor Latch (MSB) be accessed as word
|
|
3FAh 2FAh 3EAh 2EAh +2 x IIR Interrupt Identification Register (r/o) or
|
|
FCR FIFO Control Register (w/o, 16550+ only)
|
|
3FBh 2FBh 3EBh 2EBh +3 x LCR Line Control Register
|
|
3FCh 2FCh 3ECh 2ECh +4 x MCR Modem Control Register
|
|
3FDh 2FDh 3EDh 2EDh +5 x LSR Line Status Register
|
|
3FEh 2FEh 3EEh 2EEh +6 x MSR Modem Status Register
|
|
3FFh 2FFh 3EFh 2EFh +7 x SCR Scratch Register (16450+ and some 8250s,
|
|
special use with some boards)
|
|
|
|
|
|
80h 40h 20h 10h 08h 04h 02h 01h
|
|
Register Bit 7 Bit 6 Bit 5 Bit 4 Bit 3 Bit 2 Bit 1 Bit 0
|
|
-------------------------------------------------------------------------------
|
|
IER 0 0 0 0 EDSSI ELSI ETBEI ERBFI
|
|
IIR (r/o) FIFO en FIFO en 0 0 IID2 IID1 IID0 pending
|
|
FCR (w/o) - RX trigger - 0 0 DMA sel XFres RFres enable
|
|
LCR DLAB SBR stick par even sel Par en stopbits - word length -
|
|
MCR 0 0 AFE Loop OUT2 OUT1 RTS DTR
|
|
LSR FIFOerr TEMT THRE Break FE PE OE RBF
|
|
MSR DCD RI DSR CTS DDCD TERI DDSR DCTS
|
|
|
|
EDSSI: Enable Delta Status Signals Interrupt
|
|
ELSI: Enable Line Status Interrupt
|
|
ETBEI: Enable Transmitter Buffer Empty Interrupt
|
|
ERBFI: Enable Receiver Buffer Full Interrupt
|
|
FIFO en: FIFO enable
|
|
IID#: Interrupt IDentification
|
|
pending: an interrupt is pending if '0'
|
|
RX trigger: RX FIFO trigger level select
|
|
DMA sel: DMA mode select
|
|
XFres: Transmitter FIFO reset
|
|
RFres: Receiver FIFO reset
|
|
DLAB: Divisor Latch Access Bit
|
|
SBR: Set BReak
|
|
stick par: Stick Parity select
|
|
even sel: Even Parity select
|
|
stopbits: Stop bit select
|
|
word length: Word length select
|
|
FIFOerr: At least one error is pending in the RX FIFO chain
|
|
TEMT: Transmitter Empty (last word has been sent)
|
|
THRE: Transmitter Holding Register Empty (new data can be written to THR)
|
|
Break: Broken line detected
|
|
FE: Framing Error
|
|
PE: Parity Error
|
|
OE: Overrun Error
|
|
RBF: Receiver Buffer Full (Data Available)
|
|
DCD: Data Carrier Detect
|
|
RI: Ring Indicator
|
|
DSR: Data Set Ready
|
|
CTS: Clear To Send
|
|
DDCD: Delta Data Carrier Detect
|
|
TERI: Trailing Edge Ring Indicator
|
|
DDSR: Delta Data Set Ready
|
|
DCTS: Delta Clear To Send
|
|
AFE: Automatic Flow control Enable
|
|
|
|
|
|
RBR (Receive Buffer Register) 3F8h 2F8h 3E8h 2E8h +0 r/o
|
|
------------------------------------------------------------------------------
|
|
|
|
This is where you get received characters from. This register is read-only.
|
|
|
|
|
|
|
|
THR (Transmitter Holding Register) 3F8h 2F8h 3E8h 2E8h +0 w/o
|
|
------------------------------------------------------------------------------
|
|
|
|
Send characters by writing them to this register. It is write-only.
|
|
|
|
|
|
|
|
IER (Interrupt Enable Register) 3F9h 2F9h 3E9h 2E9h +1 r/w
|
|
------------------------------------------------------------------------------
|
|
|
|
Enable several interrupts by setting these bits:
|
|
Bit 0: If set, DR (Data Ready) interrupt is enabled. It is generated
|
|
if data waits to be read by the CPU.
|
|
Bit 1: If set, THRE (THR Empty) interrupt is enabled. This interrupt
|
|
tells the CPU to write characters to the THR.
|
|
Bit 2: If set, Status interrupt is enabled. It informs the CPU of
|
|
occurred transmission errors during reception.
|
|
Bit 3: If set, Modem status interrupt is enabled. It is triggered
|
|
whenever one of the delta-bits is set (see MSR).
|
|
Bits 4-7 are not used and should be set 0.
|
|
|
|
|
|
|
|
DL (Divisor Latch) 3F8h 2F8h 3E8h 2E8h +0 r/w
|
|
------------------------------------------------------------------------------
|
|
|
|
To access this *WORD*, set DLAB in the LCR to 1. Then write a word (16 bits)
|
|
to this register or write the lower byte to base+0 and the higher byte to
|
|
base+1 (the order is not important) to program the bps rate as follows:
|
|
|
|
xtal frequency in Hz / 16 / desired rate = divisor
|
|
xtal frequency in Hz / 16 / divisor = obtained rate
|
|
|
|
Your PC uses an xtal frequency of 1.8432 MHz (that's 1843200 Hz :-).
|
|
|
|
Do *NOT* use 0 as a divisor (your maths teacher told you so)! It results in
|
|
a rate of about 3500 bps, but it is not guaranteed to work with all chips in
|
|
the same way.
|
|
|
|
An error of up to 3-5 percent is irrelevant.
|
|
|
|
Some values (1.8432 MHz quartz, as in the PC):
|
|
|
|
bps rate Divisor (hex) Divisor (dec) Percent Error
|
|
50 900 2304 0.0%
|
|
75 600 1536 0.0%
|
|
110 417 1047 0.026%
|
|
134.5 359 857 0.058%
|
|
150 300 768 0.0%
|
|
300 180 384 0.0%
|
|
600 C0 192 0.0%
|
|
1200 60 96 0.0%
|
|
1800 40 64 0.0%
|
|
2000 3A 58 0.69%
|
|
2400 30 48 0.0%
|
|
3600 20 32 0.0%
|
|
4800 18 24 0.0%
|
|
7200 10 16 0.0%
|
|
9600 C 12 0.0%
|
|
19200 6 6 0.0%
|
|
38400 3 3 0.0%
|
|
57600 2 2 0.0%
|
|
115200 1 1 0.0%
|
|
|
|
The 16450 is capable of up to 512 kbps according to NS.
|
|
|
|
NS specifies that the 16550A is capable of 256 kbps if you use a 4 MHz
|
|
or an 8 MHz crystal. But a staff member of NatSemi told one of my
|
|
friends on the phone that it runs correctly at 512 kbps as well; I
|
|
don't know if the 1488/1489 manage this, though. This is true for the
|
|
16C552, too. See the "known problems" section.
|
|
|
|
BTW: Ever tried 1.76 bps, the slowest rate possible with your PC's
|
|
serial ports? Kindergarten kids write faster.
|
|
|
|
The Microsoft mouse uses 1200 bps, 7n1, the Mouse Systems mouse uses 1200
|
|
bps, 8n1. See the Mice chapter for details.
|
|
|
|
|
|
|
|
IIR (Interrupt Identification Register) 3FAh 2FAh 3EAh 2EAh +2 r/o
|
|
------------------------------------------------------------------------------
|
|
|
|
This register allows you to detect the cause of an interrupt. Only one
|
|
interrupt is reported at a time; they are priorized. If an interrupt occurs,
|
|
Bit 0 tells you if the UART has triggered it. Follow the information in this
|
|
register, then test bit 0 again. If it is still not set, there is another
|
|
interrupt to be serviced. BTW: If you AND the value of this register with
|
|
06h, you get a pointer to a table of four words... ideal for near calls.
|
|
Another hint: make sure your software reads this register just once and then
|
|
follows the information it got before it is read again, otherwise your code
|
|
won't work. (Turbo Pascal programmers beware! port[] is not a variable :)
|
|
|
|
The bits 6 and 7 allow you to detect if the FIFOs of the 16550+ have been
|
|
activated.
|
|
|
|
|
|
Bit 3 Bit 2 Bit 1 Bit 0 Priority Source Description
|
|
0 0 0 1 none no interrupt pending
|
|
0 1 1 0 highest Status OE, PE, FE or BI of the
|
|
LSR set. Serviced by
|
|
reading the LSR.
|
|
0 1 0 0 second Receiver DR or trigger level rea-
|
|
ched. Serviced by read-
|
|
ing RBR 'til under level
|
|
1 1 0 0 second FIFO No Receiver FIFO action
|
|
since 4 words' time
|
|
(neither in nor out) but
|
|
data in RX-FIFO. Serviced
|
|
by reading RBR.
|
|
0 0 1 0 third Transm. THRE. Serviced by read-
|
|
ing IIR (if source of
|
|
int only!) or writing
|
|
to THR.
|
|
0 0 0 0 lowest Modem One of the delta flags
|
|
in the MSR set. Serviced
|
|
by reading MSR.
|
|
Bit 6 & 7: 16550A: set if FCR bit 0 set.
|
|
16550: bit 7 set, bit 6 cleared if FCR bit 0 set.
|
|
others: clear
|
|
Other bits: clear (but don't rely on it; this is subject to change).
|
|
|
|
In most software applications bits 3 to 7 should be masked when servicing
|
|
the interrupt since they are not relevant. These bits cause trouble with
|
|
old software relying on that they are cleared...
|
|
|
|
NOTE! Even if some of these interrupts are disabled, the service routine
|
|
can be confronted with *all* states shown above when the IIR is loop-polled
|
|
until bit 0 is set (don't ask me why; it's just that I encontered this, and
|
|
it's not much more work to play it safe). Check examples in the Programming
|
|
section.
|
|
|
|
|
|
|
|
FCR (FIFO Control Register) 3FAh 2FAh 3EAh 2EAh +2 w/o
|
|
------------------------------------------------------------------------------
|
|
|
|
This register allows you to control the FIFOs of the 16550+. It does not exist
|
|
on the 8250/16450.
|
|
|
|
Bit 0: FIFO enable.
|
|
Bit 1: Clear receiver FIFO. This bit is self-clearing.
|
|
Bit 2: Clear transmitter FIFO. This bit is self-clearing.
|
|
Bit 3: DMA mode (pins -RXRDY and -TXRDY), see below
|
|
Bits 6-7: Trigger level of the DR-interrupt.
|
|
|
|
Bit 7 Bit 6 Receiver FIFO trigger level
|
|
0 0 1
|
|
0 1 4
|
|
1 0 8
|
|
1 1 14
|
|
|
|
Note: if bit 0 is cleared, all other bits are ignored.
|
|
|
|
DMA mode operation is not available with your PC, but for the sake of
|
|
completeness... here we go.
|
|
|
|
If bit 3 is 0, DMA mode 0 is selected. The -RXRDY pin goes active-low
|
|
whenever there is at least one character in the RX FIFO or in the RBR if
|
|
the FIFO is disabled. -TXRDY goes active-low when the TX FIFO or the THR
|
|
is empty. It goes high if one character is written to the THR (same as THRE,
|
|
that's bit 5 of the LSR).
|
|
|
|
If this bit is 1, DMA mode 1 is selected. The -RXRDY pin goes low if
|
|
the trigger level of the RX FIFO is reached or if reception timed out
|
|
(no characters received for a time that would have allowed to receive 4
|
|
characters). -TXRDY goes low when the TX FIFO is empty. It goes high again
|
|
if the FIFO is completely full. (Not that setting this bit to '1' would fix
|
|
the weird behaviour of the THRE bit in FIFO mode operation, though). If the
|
|
FIFOs are disabled, DMA mode 1 operates in the same way as DMA mode 0.
|
|
|
|
|
|
|
|
LCR (Line Control Register) 3FBh 2FBh 3EBh 2EBh +3 r/w
|
|
------------------------------------------------------------------------------
|
|
|
|
This register allows you to select the transmission protocol. It also contains
|
|
the DLAB bit which switches the function of the addresses +0 and +1.
|
|
|
|
Bit 1 Bit 0 word length Bit 2 Stop bits
|
|
0 0 5 bits 0 1
|
|
0 1 6 bits 1 1.5/2
|
|
1 0 7 bits (1.5 if word length is 5)
|
|
1 1 8 bits (1.5 does not work with some chips, see above)
|
|
|
|
Bit 5 Bit 4 Bit 3 Parity type Bit 6 SOUT condition
|
|
x x 0 no parity 0 normal operation
|
|
0 0 1 odd parity 1 forces TxD +12V (break)
|
|
0 1 1 even parity Bit 7 DLAB
|
|
1 0 1 mark parity 0 normal registers
|
|
1 1 1 space parity 1 divisor at reg 0, 1
|
|
|
|
Mark parity: The parity bit is always '1' (the line is 'low').
|
|
Space parity: The parity bit is always '0' (the line is 'high').
|
|
|
|
|
|
|
|
MCR (Modem Control Register) 3FCh 2FCh 3ECh 2ECh +4 r/w
|
|
------------------------------------------------------------------------------
|
|
|
|
This register allows to program some modem control lines and to switch to
|
|
loopback mode.
|
|
|
|
Bit 0: Programs -DTR. If set, -DTR is low and the DTR pin of the port
|
|
goes 'high'.
|
|
Bit 1: Programs -RTS. dito.
|
|
Bit 2: Programs -OUT1. Normally not used in a PC, but used with some
|
|
multi-port serial adapters to enable or disable a port. Best
|
|
thing is to write a '1' to this bit.
|
|
Bit 3: Programs -OUT2. If set to 1, interrupts generated by the UART
|
|
are transferred to the ICU (Interrupt Control Unit) while 0
|
|
sets the interrupt output of the card to high impedance.
|
|
(This is PC-only).
|
|
Bit 4: '1': local loopback. All outputs disabled. This is a means of
|
|
testing the chip: you 'receive' all the data you send.
|
|
Interrupts are fully operational in this mode.
|
|
Bit 5: (Texas Instruments TL16C550C only, maybe some more; this
|
|
is not a standard feature) '1': Enable automatic flow
|
|
control. If RTS (bit 1) is '0', only auto-CTS is done, which
|
|
means that no more characters are sent from the FIFO and
|
|
no more Tx interrupts are generated as long as CTS is '0'.
|
|
If RTS (bit 1) is '1', the RTS signal is dropped whenever the
|
|
FIFO trigger level is reached. Note that if this bit is '1',
|
|
delta CTS (see below) won't generate a modem status interrupt!
|
|
|
|
|
|
|
|
LSR (Line Status Register) 3FDh 2FDh 3EDh 2EDh +5 r/w
|
|
------------------------------------------------------------------------------
|
|
|
|
This register allows error detection and polled-mode operation.
|
|
|
|
Bit 0 Data Ready (DR). Reset by reading RBR (but only if the RX FIFO is
|
|
empty, 16550+).
|
|
Bit 1 Overrun Error (OE). Reset by reading LSR. Indicates loss of data.
|
|
Bit 2 Parity Error (PE). Indicates transmission error. Reset by LSR.
|
|
Bit 3 Framing Error (FE). Indicates missing stop bit. Reset by LSR.
|
|
Bit 4 Break Indicator (BI). Set if RxD is 'space' for more than 1 word
|
|
('break'). Reset by reading LSR.
|
|
Bit 5 Transmitter Holding Register Empty (THRE). Indicates that a new
|
|
word can be written to THR. Reset by writing THR. Note that this
|
|
bit works in a weird way when FIFOs are enabled: it goes 0
|
|
whenever there are characters in the TX-FIFO, not when the FIFO
|
|
is full!
|
|
Bit 6 Transmitter Empty (TEMT). Indicates that no transmission is
|
|
running. Reset by reading LSR.
|
|
Bit 7 (16550+ only) Set if at least one character in the RX FIFO has
|
|
been received with an error. Cleared by reading LSR if there is
|
|
no further error in the FIFO. Clear with all other chips.
|
|
|
|
|
|
|
|
MSR (Modem Status Register) 3FEh 2FEh 3EEh 2EEh +6 r/w
|
|
------------------------------------------------------------------------------
|
|
|
|
This register allows you to check several modem status lines. The delta bits
|
|
are set if the corresponding signals have changed state since the last reading
|
|
(except for TERI which is only set if -RI changed from active-low to
|
|
inactive-high, that is if the RI line at the port changed from 'high' to
|
|
'low' and the phone stopped ringing).
|
|
|
|
Bit 0: Delta CTS. Set if CTS has changed state since last reading.
|
|
Bit 1: Delta DSR. Set if DSR has changed state since last reading.
|
|
Bit 2: TERI. Set if -RI has changed from low to high (ie. RI at port
|
|
has changed from +12V to -12V).
|
|
Bit 3: Delta DCD. Set if DCD has changed state since last reading.
|
|
Bit 4: CTS. 1 if 'high' at port.
|
|
Bit 5: DSR. dito.
|
|
Bit 6: RI. dito.
|
|
Bit 7: DCD.
|
|
|
|
In loopback mode (MCR bit 4 = 1), bit 4 shows the state of RTS (MCR bit 1),
|
|
bit 5 shows the state of DTR (MCR bit 0), RI shows the state of OUT1 (MCR
|
|
bit 2), and DCD shows the state of OUT2 (MCR bit 3). The delta registers
|
|
act accordingly to the 'level transitions' of the data written to MCR.
|
|
This is a good means of testing if a UART is present.
|
|
|
|
|
|
|
|
SCR (Scratch Register) 3FFh 2FFh 3EFh 2EFh +7 r/w
|
|
------------------------------------------------------------------------------
|
|
|
|
This is an all-purpose 8 bit store. NS recommends to store the value of the
|
|
FCR (which is w/o) in this register for further use, but this is not
|
|
mandatory and not recommended by me (see below). This register is only
|
|
available with the 16450+; the standard 8250 doesn't have a scratch register
|
|
(but then again some versions do).
|
|
|
|
On some boards (especially RS-422/RS-485 boards) this register has a special
|
|
meaning (enable receiver/transmitter drivers etc.), and with multi-port
|
|
serial adapters it is often used to select the interrupt levels of the
|
|
several ports and to determine which port has triggered interrupt. So you
|
|
shouldn't use it for anything else in your programs.
|
|
|
|
|
|
|
|
Excursion: Why and how to use the FIFOs (by Scott C. Sadow)
|
|
-----------------------------------------------------------
|
|
|
|
Normally when transmitting or receiving, the UART generates one
|
|
interrupt for every character sent or received. For 2400 bps, typically
|
|
this is 240/second. For 115,200 bps, this means 11,520/second. With FIFOs
|
|
enabled, the number of interrupts is greatly reduced.
|
|
|
|
A transmitter holding register empty interrupt is not generated until the
|
|
FIFO is empty (last byte is being sent).
|
|
|
|
So if you know it's a 16550A and the FIFOs are enabled, your TX interrupt
|
|
routine can write up to 16 characters to the THR. Monitoring bit 5 (THRE) of
|
|
the LSR is _no_good_ because this bit will be cleared immediately after your
|
|
routine has written the first character to the THR! The chip does not
|
|
provide any feedback on the fill level at all.
|
|
|
|
Thus, the number of transmitter interrupts is reduced by a factor of 16.
|
|
For 115,200 bps, this means only 720 interrupts per second. For receive
|
|
data interrupts, the processing is similar to transmitter interrupts. The
|
|
main difference is that the number of bytes in the FIFO (the trigger level)
|
|
can be specified. When the trigger level is reached, a receive data
|
|
interrupt is generated; any other data received is just put in the FIFO.
|
|
The receive data interrupt is not cleared until the number of bytes in the
|
|
FIFO is below the trigger level again.
|
|
|
|
To add 16550A support to existing code, there are 2 requirements to be met:
|
|
|
|
1) When reading the IIR to determine the interrupt source, only
|
|
use the lower 3 bits.
|
|
|
|
2) After the existing UART initialization code, try to enable the
|
|
FIFOs by writing to the FCR. (A value of C7 hex will enable FIFO
|
|
mode, clear both FIFOs, and set the receive trigger level at 14
|
|
bytes). Next, read the IIR. If Bit 6 of the IIR is not set, the
|
|
UART is not a 16550A, so write 0 to the FCR to disable FIFO mode.
|
|
|
|
|
|
Multi-Port Serial Adapters
|
|
--------------------------
|
|
|
|
This is material I received from Mike Surikov.
|
|
|
|
I want to give you some information on Multi-Serial adapters that
|
|
provide four or eight asynchronous serial communication ports.
|
|
|
|
Some of them have an Interrupt Vector (one for each four
|
|
channels). The Interrupt Vector is used to enable/disable
|
|
global interrupt and to detect which of the four channels is
|
|
creating the interrupt (one IRQ is used for a group of four
|
|
channels). Bit 7 of the Interrupt Vector is used to enable or
|
|
disable ALL four channels by writing a logical 1 to enable or 0
|
|
to disable interrupts. At the same time, each channel can be
|
|
enabled or disabled separately by programming the OUT2 (and/or
|
|
OUT1) signal in the 16450 chip.
|
|
|
|
When you read the interrupt vector, you get an indication which
|
|
port has triggered the interrupt, as it is shown below.
|
|
|
|
[Since this may be different with each board, check your manual for
|
|
details.]
|
|
|
|
MSB LSB
|
|
7 6 5 4 3 2 1 0 <-- Interrupt Vector Register
|
|
Channel 0 interrupt indicator (0-active)
|
|
N/A Channel 1 interrupt indicator (0-active)
|
|
Channel 2 interrupt indicator (0-active)
|
|
Channel 3 interrupt indicator (0-active)
|
|
Global interrupt: 1-enable; 0-disable
|
|
|
|
For example, an 8 PORT RS-232C CARD can have the following
|
|
configuration:
|
|
|
|
Base IRQ Channel Interrupt
|
|
Address Level Number Vector
|
|
|
|
2A0 7 0 2BF
|
|
2A8 7 1 2BF
|
|
2B0 7 2 2BF
|
|
2B8 7 3 2BF
|
|
1A0 5 0 1BF
|
|
1A8 5 1 1BF
|
|
1B0 5 2 1BF
|
|
1B8 5 3 1BF
|
|
|
|
[The base addresses should be configurable by jumpers or DIP switches.]
|
|
|
|
Note that the Interrupt Vector Registers overlap Scratch
|
|
Registers, so the detect_UART routine must be changed for these
|
|
boards. [See the Programming Section.]
|
|
|
|
|
|
|
|
Some words about timing
|
|
-----------------------
|
|
|
|
The 8250 is a rather slow peripheral chip; it has a cycle delay for both
|
|
reading and writing of 500nsec, which means that after every read or write
|
|
access to any of the chip's registers the CPU has to wait at least 500nsec
|
|
before reading or writing one of its registers again. Good thing that this
|
|
chip is only used with some old XTs... the 8088/8086/V20/V30 family is slow
|
|
enough for that.
|
|
|
|
The 16450 and 16550A are rather fast; they need a delay of 125nsec after
|
|
read access and 150nsec after write access before any other transfer.
|
|
This means we only have a problem with these fancy new machines that allow
|
|
cycle times of 50nsec and less. Luckily they add wait states to I/O bus
|
|
accesses (wait states are additional cycles during which the bus does
|
|
not change its state) or use a slower clock speed for I/O transfers (8 or
|
|
12 MHz). So if you have 12 MHz I/O clock speed and one wait state for I/O
|
|
transfers, you don't have to worry.
|
|
|
|
Some people believe in delaying I/O operations by adding NOPs or JMP $+2 to
|
|
every I/O instruction (both do nothing but wasting time), but I don't think
|
|
that's any good with a chip that needs stable data lines for at least
|
|
100nsec (so the CPU or the bus controller has to add a wait state anyway).
|
|
You can always blame the hardware or the setup if your program doesn't work
|
|
for timing reasons. :)
|
|
|
|
However, there may be a problem with block instructions, esp. OUTSB. This
|
|
instruction allows you to fill the Tx fifo of the 16550A rather fast (just
|
|
5 cycles per transfer on the 286, others take longer), but even a 25MHz 286
|
|
takes 200nsec for each transfer, so this should be on the safe side, too.
|
|
I don't use this instruction, but for other reasons than timing difficulties.
|
|
It's just not very useful: it takes more time to make sure in advance that
|
|
you don't overrun your buffer margins during an OUTSB than to check for
|
|
the margins after every single transfer.
|
|
|
|
Please note that all this relates to ISA and VLB boards. I don't have
|
|
any experience with EISA or other fancy things like PCI!
|
|
|
|
|
|
|
|
Handshaking
|
|
-----------
|
|
|
|
The method of exchanging signals for data flow control between computers
|
|
and data sets is called handshaking. The most popular and most often used
|
|
handshaking variant is called XON/XOFF; it's done by software, while other
|
|
methods are hardware-based.
|
|
|
|
XON/XOFF
|
|
|
|
Two bytes that are not mapped to normal characters in the ASCII charset are
|
|
called XON (DC1, Ctrl-Q, ASCII 17) and XOFF (DC3, Ctrl-S, ASCII 19).
|
|
Whenever either one of the sides wants to interrupt the data flow from the
|
|
other (eg. full buffers), it sends an XOFF ('Transmission Off'). When its
|
|
buffers have been purged again, it sends an XON ('Transmission On') to
|
|
signal that data can be sent again. (With some implementations, this can
|
|
be any character).
|
|
|
|
XON/XOFF is of course limited to text transmission. It cannot be used with
|
|
binary data since binary files tend to contain every single one of the 256
|
|
characters. This is called in-band signaling by the way.
|
|
|
|
That's why hardware handshaking is normally used with modems, while
|
|
XON/XOFF is often used with printers and plotters and terminals.
|
|
|
|
DTR/DSR
|
|
|
|
The 'Data Terminal Ready' and 'Data Set Ready' signals of the serial port
|
|
can be used for handshaking purposes, too. Their names express what they
|
|
do: the computer signals with DTR that it is ready to send and receive data,
|
|
while the data set sets DSR. With most modems, the meaning of these signals
|
|
is slightly different: DTR is ignored or causes the modem to hang up if it
|
|
is dropped, while DSR signals that a connection has been established.
|
|
|
|
RTS/CTS
|
|
|
|
While DTR and DSR are mostly used to establish a connection, RTS and CTS
|
|
have been specially designed for data flow control. The computer signals
|
|
with RTS ('Request To Send') that it wishes to send data to the data set,
|
|
while the data set (modem) sets CTS ('Clear To Send') when it is ready to
|
|
do one part of its job: to send data thru' the phone wires.
|
|
|
|
A normal handshaking protocol between a computer and a modem looks like this:
|
|
|
|
|
|
DTR ___--------------------------------------------------------------____
|
|
|
|
DSR _____-------------------------------------------------------------___
|
|
|
|
RTS ___________-----------------------_____----------------------________
|
|
|
|
CTS ____________-------____------------_____----------------------_______
|
|
|
|
(1)(2) (3)(4) (5) (6) (7)(8)(9)(10) (11)(12)(13)
|
|
|
|
(1) The computer sets DTR to indicate that it wants to make use of the
|
|
modem.
|
|
(2) The modem signals that it is ready and that a connection has been
|
|
established.
|
|
(3) The computer requests permission to send.
|
|
(4) The modem informs the computer that it is now ready to receive data from
|
|
the computer and send it through the phone wires.
|
|
(5) The modem drops CTS to signal to the computer that its internal buffers
|
|
are full; the computer stops sending characters to the modem.
|
|
(6) The buffers of the modem have been purged, so the computer may continue
|
|
to send data.
|
|
(7) This situation is not clear; either the computer's buffers are
|
|
full and it wants to inform the modem of this, or it doesn't have any
|
|
more data to be send to the modem. Normally, modems are configured to
|
|
stop any transmission between the computer and the modem when RTS is
|
|
dropped.
|
|
(8) The modem acknowledges RTS cleared by dropping CTS.
|
|
(9) RTS is again raised by the computer to re-establish data transmission.
|
|
(10) The modem shows that it is ready to do its job.
|
|
(11) No more data is to be sent.
|
|
(12) The modem acknowledges this.
|
|
(13) DTR is dropped by the computer; this causes most modems to hang up.
|
|
After hang-up, the modem acknowledges with DSR low. If the connection
|
|
breaks, the modem also drops DSR to inform the computer about it.
|
|
|
|
|
|
BIOS API (Application Programs Interface)
|
|
-----------------------------------------
|
|
|
|
PC programs are meant to use the BIOS routines to program the UARTs.
|
|
Even though this is *NOT RECOMMENDED* by me (awfully slow, limited and
|
|
complicated), I give you the BIOS calls as specified by Big Blue. Call
|
|
INT 14h with:
|
|
|
|
|
|
AH=00h Serial port - Initialize
|
|
|
|
AL: see table
|
|
DX: Port number (0-3; 0 equ. 0x3f8, 1 equ. 0x2f8, etc., see Hardware)
|
|
|
|
Bit 7 Bit 6 Bit 5 Rate [bps] Bit 4 Bit 3 Parity
|
|
1 1 1 9600 0 0 none
|
|
1 1 0 4800 1 0 none
|
|
1 0 1 2400 0 1 odd
|
|
1 0 0 1200 1 1 even
|
|
0 1 1 600
|
|
0 1 0 300 Bit 1 Bit 0 Data bits
|
|
0 0 1 150 0 0 5
|
|
0 0 0 110 0 1 6
|
|
1 0 7
|
|
Bit 2 0 -> 1 stop bit, 1 -> 2 stop bits 1 1 8
|
|
|
|
Returns:
|
|
AH: RS-232C line status bits
|
|
Bit
|
|
0: RBF - input data is available in buffer
|
|
1: OE - data has been lost
|
|
5: THRE - room is available in output buffer
|
|
6: TEMT - output buffer empty
|
|
AL: Modem status bits
|
|
3: always 1
|
|
7: DCD - carrier detect
|
|
|
|
AH=01h Serial port - Write character
|
|
|
|
AL: character to be sent
|
|
DX: Port
|
|
|
|
Returns:
|
|
AH: Bit 7 clear if successful, set if not. Bits 0-6 see INT 14h AH=03h
|
|
|
|
AH=02h Serial port - Read character
|
|
|
|
DX: Port
|
|
|
|
Returns:
|
|
AH: Line Status (see AH=03h)
|
|
AL: Received character (if AH bit 7 is clear)
|
|
|
|
Note:
|
|
This routine times out if DSR is not asserted, even if data is
|
|
available! (That's why you need the short wires from the "Connecting
|
|
devices" chapter with some programs).
|
|
|
|
AH=03h Serial port - Get port status
|
|
|
|
DX: Port
|
|
|
|
Returns:
|
|
AH: Line Status
|
|
Bit 7: Timeout
|
|
Bit 6: TEMT Transmitter empty
|
|
Bit 5: THRE Transmitter Holding Register Empty
|
|
Bit 4: Break (broken line detected)
|
|
Bit 3: FE Framing error
|
|
Bit 2: PE Parity error
|
|
Bit 1: OE Overrun error
|
|
Bit 0: RDF Receiver buffer full (data available)
|
|
AL: Modem Status
|
|
Bit 7: DCD Carrier detect
|
|
Bit 6: RI Ring indicator
|
|
Bit 5: DSR Data set ready
|
|
Bit 4: CTS Clear to send
|
|
Bit 3: DDCD Delta carrier detect
|
|
Bit 2: TERI Trailing edge of ring indicator
|
|
Bit 1: DDSR Delta data set ready
|
|
Bit 0: DCTS Delta Clear to send
|
|
|
|
|
|
BIOS variables in the Data Segment at segment 40h:
|
|
|
|
Offset Size Description
|
|
00h WORD Base I/O address of 1st serial I/O port, zero if none
|
|
02h WORD Base I/O address of 2nd serial I/O port, zero if none
|
|
04h WORD Base I/O address of 3rd serial I/O port, zero if none
|
|
06h WORD Base I/O address of 4th serial I/O port, zero if none
|
|
Note: Above fields filled in turn by POST as it finds serial
|
|
ports. POST never leaves gaps. DOS and BIOS serial device
|
|
numbers may be redefined by re-assigning these fields.
|
|
[POST: Power-On Self Test. CB]
|
|
[Madis Kaal told me that there are BIOSes that leave gaps in the table,
|
|
and I know of some that don't recognize COM4 correctly.]
|
|
|
|
|
|
This information is sneaked from Ralf Brown's famous interrupt list (hope
|
|
he doesn't mind). If you want more detailed facts on this interrupt, refer
|
|
to this list. It's available from lots of FTP sites (choose one in your
|
|
vicinity; it is *huge*).
|
|
|
|
|
|
|
|
Mice
|
|
----
|
|
|
|
The Microsoft Serial Mouse (or compatibles) is the device that is most often
|
|
used with the serial port of the PC; it's the one with the two buttons. Mouse
|
|
Systems compatible mice have three buttons. Here's some information I
|
|
received from Stephen Warner and Angelo Haritsis:
|
|
|
|
Pins Used:
|
|
TxD, RTS and/or DTR are used as power sources for the mouse.
|
|
RxD is used to receive data from the mouse.
|
|
|
|
Mouse reset:
|
|
Set UART to 'broken line' state (set bit 6 of the LCR) and clear the bits
|
|
0-1 of the MCR; wait a while and reverse the bits again.
|
|
|
|
Serial transmission parameters:
|
|
Microsoft Mouse 1200 bps, 7 data bits, 1 stop bit, no parity
|
|
Mouse Systems Mouse 1200 bps, 8 data bits, 1 stop bit, no parity
|
|
|
|
Data packet format of the Microsoft mouse:
|
|
The data packet consists of 3 bytes. It is sent to the computer every time
|
|
the mouse changes state (ie. the mouse is moved or the buttons are released/
|
|
pressed).
|
|
|
|
D6 D5 D4 D3 D2 D1 D0
|
|
|
|
1st byte 1 LB RB Y7 Y6 X7 X6
|
|
2nd byte 0 X5 X4 X3 X2 X1 X0
|
|
3rd byte 0 Y5 Y4 Y3 Y2 Y1 Y0
|
|
|
|
The byte marked with 1 is sent first and then the others. The bit D6 in the
|
|
first byte is used for synchronizing the software to the mouse packets
|
|
if it goes out of sync.
|
|
|
|
LB is the state of the left button (1 being the LB is pressed)
|
|
RB is the state of the right button (1 being the RB is pressed)
|
|
X0-7 movement of the mouse in the X direction since last packet (+ right)
|
|
Y0-7 movement of the mouse in the Y direction since last packet (+ down )
|
|
|
|
The Microsoft Mouse uses RTS as power source. Whenever RTS is set to '0'
|
|
and reset to '1', the mouse performs an internal reset and sends the
|
|
character 'M' to signal its presence. Three-button-mice send 'M3' if you
|
|
drop and raise RTS (see above) in Microsoft mode; this is compatible
|
|
with the Microsoft mouse driver and allows the firmware to check if it
|
|
is really a three-button mouse.
|
|
[Scott David Daniels received this info from Brian Onn]
|
|
|
|
|
|
Data packet format of the Mouse Systems mouse:
|
|
The data packet consists of 5 bytes.
|
|
|
|
D7 D6 D5 D4 D3 D2 D1 D0
|
|
|
|
1st byte 1 0 0 0 0 LB MB RB
|
|
2nd byte Xa7 Xa6 Xa5 Xa4 Xa3 Xa2 Xa1 Xa0
|
|
3rd byte Ya7 Ya6 Ya5 Ya4 Ya3 Ya2 Ya1 Ya0
|
|
4th byte Xb7 Xb6 Xb5 Xb4 Xb3 Xb2 Xb1 Xb0
|
|
5th byte Yb7 Yb6 Yb5 Yb4 Yb3 Yb2 Yb1 Yb0
|
|
|
|
Bits 7-3 of the 1st byte are used for synchronization; it's rather
|
|
improbable that they appear the same way in any of the other bytes.
|
|
|
|
Note that the mouse systems mouse sends two independent bytes for
|
|
each direction in each packet!
|
|
|
|
LB is the state of the left button (1 being the LB is pressed)
|
|
MB is the state of the middle button (1 being the MB is pressed)
|
|
RB is the state of the right button (1 being the RB is pressed)
|
|
Xa0-7 movement of the mouse in the X direction since last packet (+ right)
|
|
Ya0-7 movement of the mouse in the Y direction since last packet (+ up )
|
|
Xb0-7 movement of the mouse in the X direction since Xa
|
|
Yb0-7 movement of the mouse in the Y direction since Ya
|
|
|
|
The mouse should rather be used with the mouse driver software; this
|
|
ensures compatibility to future changes as well as bus mice and greatly
|
|
reduces programming overhead. See Ralf Brown's interrupt list, interrupt 33h.
|
|
It is available from lots of FTP sites (eg. garbo.uwasa.fi, /pc/programming),
|
|
the files are called inter*.zip.
|
|
|
|
|
|
Modems
|
|
======
|
|
|
|
This chapter is rather brief for several reasons. I'm no modem expert at all
|
|
and there exist better sources than this document if you want information on
|
|
modems. Patrick Chen, the author of "The Joy of Telecomputing", has written
|
|
such a file, and there's one available from Sergey Shulgin, too (I don't have
|
|
their internet addresses). You can obtain these files from my archive;
|
|
they are named "modem1" and "modem2".
|
|
|
|
|
|
A modem (MOdualtor-DEModulator) is an interface between the serial port of
|
|
your computer and the public telephone network. Modern modems are small
|
|
computers of their own: they accept commands, do the dialing for you, buffer
|
|
incoming data, perform data compression and such things. Several standards
|
|
have been established (Bell, CCITT), and several "command languages" are in
|
|
use, with the Hayes and Microcom commands being the most popular ones.
|
|
|
|
Modems have two internal modes: the command mode and the data mode. After
|
|
power-up, the modem is in the command mode, and this mode can be restalled
|
|
by sending an 'escape sequence' (normally a pause of at least 1 second,
|
|
then three '+' signs in one second, then a pause of at least 1 second).
|
|
|
|
All I know about modems is some commands and some encoding schemes; I
|
|
share this knowledge with you - please share yours with me!
|
|
|
|
|
|
Encoding schemes
|
|
----------------
|
|
|
|
I've sneaked this table from the posting 'FAQ zu /Z-NETZ/TELECOM/ALLGEMEIN'
|
|
of Kristian Koehntopp <kris@black.toppoint.de> in 'de.newusers.questions'.
|
|
He has copyrighted his posting, so please contact him if you wish to reproduce
|
|
this information in any commercial way.
|
|
|
|
These are the schemes recommended by CCITT (more than one speed means
|
|
fallback/auto-retrain speeds):
|
|
|
|
Transmission speed in bps Baud Modulation duplex usage
|
|
--------------------------------------------------------------------
|
|
V.17 14400 2400 TCM half FAX
|
|
12000, 9600, 7200 2400 TCM half FAX
|
|
4800 2400 QAM half FAX
|
|
V.21 300 300 FSK full
|
|
V.22 1200 600 DPSK full
|
|
V.22bis 2400 600 QAM full
|
|
V.23 1200/75 1200/75 FSK asymmetric BTX
|
|
V.27ter 4800 1600 DPSK half FAX
|
|
2400 1200 DPSK half FAX
|
|
V.29 9600 2400 QAM half FAX
|
|
7200 2400 QAM half FAX
|
|
V.32 9600 2400 TCM/QAM full
|
|
4800 2400 QAM full
|
|
V.32bis 14400 2400 TCM full
|
|
12000, 9600, 7200 2400 TCM full
|
|
4800 2400 QAM full
|
|
|
|
FSK Frequency Shift Keying
|
|
DPSK Differential Phase Shift Keying
|
|
QAM Quadrature Amplitude Modulation
|
|
TCM Trellis Coded Modulation
|
|
|
|
Other V-Recommendations often heard of:
|
|
|
|
V.24 - Meaning of the signals at the serial port.
|
|
V.28 - Electrical levels (V.24, V.28, and ISO 2110 are equivlaent to EIA
|
|
RS232)
|
|
V.42 - Data protection method, not dependening on the modulation scheme
|
|
in use.
|
|
V.42bis - Compression scheme, also called BTLZ.
|
|
|
|
Erich Smythe <esmythe@digex.net> posted a very informative and humorous
|
|
article explaining different modulation schemes used with modems. You
|
|
can find it in the FTP archive, named The_Serial_Port.more06.
|
|
|
|
|
|
Hayes commands
|
|
--------------
|
|
|
|
Each command line starts with 'AT', then several commands, then carriage
|
|
return.
|
|
|
|
The list is not comprehensive at all; most modems have several commands of
|
|
their own, but these commands are available with most modems:
|
|
|
|
A/ Repeat last command (no prepending AT)
|
|
|
|
A Take over phone line (if you've already picked up the phone).
|
|
|
|
B Set communications standard.
|
|
B0 - CCITT
|
|
B1 - Bell
|
|
|
|
C Switch carrier on/off.
|
|
C0 - carrier off
|
|
C1 - carrier on
|
|
|
|
D Dial a number. Normally followed by
|
|
T - tone dial
|
|
P - pulse dial
|
|
nothing - according to actual setting (see ATP/ATT)
|
|
then a sequence of the follwing characters:
|
|
0-9 - the numbers to be dialed
|
|
W - wait for dial tone
|
|
, - wait 2 seconds
|
|
@ - wait 5 seconds (?)
|
|
! - flash (put the phone on the hook for 1/2 second)
|
|
> - earth key
|
|
R - start connection right after dialing (eg. ATDPR equals ATA)
|
|
If you just enter ATD, the modem takes over the line without dialing.
|
|
|
|
E Echo on/off in the command mode
|
|
E0 - no echo
|
|
E1 - echo
|
|
|
|
H Hang up
|
|
|
|
L Volume control; followed by 0-3 (0 equ. lowest, 3 equ. highest volume)
|
|
|
|
M Monitor
|
|
M0 - Speaker off
|
|
M1 - Speaker on while dialing and establishing a connection
|
|
M2 - Speaker always on
|
|
M3 - Speaker on while establishing a connection
|
|
|
|
O Switch to data mode
|
|
O0 - promptly
|
|
O1 - with retrain (reduction of the data rate)
|
|
|
|
P Pulse dial
|
|
|
|
Q Responses to commands on/off
|
|
Q0 - on
|
|
Q1 - off
|
|
|
|
S Set/read internal register, eg.
|
|
S17=234 set reg. 17 to 234
|
|
S17? read reg. 17
|
|
|
|
T Tone dial
|
|
|
|
V Verbose mode on/off
|
|
V0 - short responses
|
|
V1 - full responses
|
|
|
|
X Phone tones recognition on/off
|
|
X0 - Ignore busy sign, don't wait for dial tone, and just answer with
|
|
"CONNECT" when a connection has been established (other settings
|
|
produce more detailed messages)
|
|
X1 - Ignore busy sign, don't wait for dial tone, but give full connect
|
|
message
|
|
X2 - Ignore busy sign but wait for dial tone
|
|
X3 - Don't ignore busy sign, but don't wait for dial tone
|
|
X4 - Don't ignore anything
|
|
|
|
Y Break setting
|
|
Y0 - Don't hang up when break signal is detected
|
|
Y1 - Hang up when break is detected (&D2, &M0)
|
|
|
|
Z Initialize modem
|
|
Z - Default parameters
|
|
Z0 - Setting 0
|
|
Z1 - Setting 1
|
|
|
|
&C DCD mode
|
|
&C0 - always 1
|
|
&C1 - DCD according to carrier
|
|
|
|
&D DTR mode
|
|
&D0 - ignore DTR
|
|
&D1 - switch to command mode when DTR goes 0
|
|
&D2 - hang up if DTR goes 0
|
|
&D3 - initialize modem when DTR goes 0
|
|
|
|
&F Set operation mode
|
|
&F0 - according to Hayes, no data protocol
|
|
&F1 - according to Microcom; MNP1-4 or MNP5 as specified by %C
|
|
&F2 - according to Sierra; MNP1-4 or MNP5 as specified by %C
|
|
&F3 - according to Sierra, V.42 or V.42bis as specified by %C
|
|
|
|
These are the default settings:
|
|
&F0 - B0, E1, L2, M1, P, Q0, V1, Y0, X1, &C1, &D0, &G0, &R0, &S0,
|
|
S0=0, S1=0, S2=43, S3=13, S4=10, S5=8, S6=2, S7=30, S8=2,
|
|
S9=6,S10=14, S11=75, S12=50, S14=AAh, S16=80h, S21=20h,
|
|
S22=76h, S23=7, S25=5, S26=1, S27=40h
|
|
&F1 - \A3, \C0, \E0, \G0, \K5, \N1, \Q0, \T0, \V0, \X0, %A0, %C1,
|
|
%E1, %G0, &G1, S36=7h, S46=138h, S48=128h, S82=128h
|
|
&F2 - \A3, \C2, \E0, \G1, \K5, \N3, \Q1, \T0, \V1, \X0, %A13, %C1,
|
|
S36=7h, S46=138h, S48=128h, S82=128h
|
|
&F3 - \A3, \C0, \E0, \G0, \K5, \N3, \O1, \T0, \V1, \X0, %A0, %C1,
|
|
%E0, S36=7h, S46=138h, S48=7h, S82=128h
|
|
|
|
&G Guard tone
|
|
&G0 - off
|
|
&G1 - 550 Hz
|
|
&G2 - 1800 Hz
|
|
|
|
&K Data flow control
|
|
&K0 - none
|
|
&K3 - bidirectional RTS/CTS handshaking
|
|
&K4 - bidirectional XON/XOFF
|
|
&K5 - unidirectional XON/XOFF
|
|
|
|
&M Synchronous/asynchronous operation
|
|
&M0 - asynchronous (the usual thing)
|
|
&M1 - command mode asynchronous, data mode synchronous.
|
|
&M2 - switch to synchronous mode, start dialing after DTR 0->1
|
|
&M3 - switch to synchronous mode, don't dial
|
|
|
|
&Q Further specification of the communication
|
|
&Q0 to &Q3 - no V.42bis
|
|
&Q5 - V.42bis
|
|
&Q6 - V.42bis off, buffer data
|
|
|
|
&R CTS mode
|
|
&R0 - CTS follows RTS with the delay time of S26
|
|
&R1 - CTS is 1 if the modem is in the data mode
|
|
|
|
&S DSR mode
|
|
&S0 - DSR always 1
|
|
&S1 - according to CCITT V.24
|
|
|
|
&T Test
|
|
&T0 - normal operation (no test)
|
|
&T1 - local analog loopback
|
|
&T3 - local digital loopback
|
|
&T4 - accept distant digital loopback
|
|
&T5 - ignore distant digital loopback
|
|
&T6 - start distant digital loopback
|
|
&T7 - start distant digital loopback and self test
|
|
&T8 - start distant analog loopback and self test
|
|
|
|
&V Show modem status
|
|
|
|
&Wn Save actual configuration (some modems only). Setting can be
|
|
restored with ATZn. n normally ranges between 0 and 1.
|
|
The following parameters are stored:
|
|
B, C, E, L, M, P/T, Q, V, X, Y, &C, &D, &G, &R, &S, &T4/&T5,
|
|
S0, S14, S18, S21, S22, S25, S26, S27
|
|
|
|
&X Specify clock source for synchronous operation
|
|
&X0 - modem generates clock
|
|
&X1 - modem synchronizes with local clock
|
|
&X2 - modem synchronizes with distant clock
|
|
|
|
&Y Define default setting (see &W and Z)
|
|
&Y0 - setting 0 is default
|
|
&Y1 - setting 1 is default
|
|
|
|
&Z Store phone number in diary
|
|
&Zn=XXXXXX stores phone number XXXXXX under index n, where
|
|
XXXXXX can be up to 30 digits and n ranges between 0 and 3.
|
|
|
|
|
|
Microcom commands
|
|
-----------------
|
|
|
|
\A Set block length for MNP
|
|
\A0 - 64 characters
|
|
\A1 - 128 characters
|
|
\A2 - 192 characters
|
|
\A3 - 256 characters
|
|
|
|
\Bn Send break signal for n times 100ms (MNP defaults to n=3).
|
|
|
|
\C Set buffering
|
|
\C0 - none at all
|
|
\C1 - buffer data for 4 seconds as long as 200 characters aren't
|
|
reached or as long as no MNP block is found
|
|
\C2 - don't buffer. Switch back to normal operation after reception
|
|
of the control character (fall-back, see %C)
|
|
|
|
D/n Dial phone number n in the diary (see &Z)
|
|
|
|
DL Redial last number
|
|
|
|
\E Echo on/off in data mode
|
|
\E0 - no echo
|
|
\E1 - echo
|
|
|
|
\G Data flow on/off (see \Q)
|
|
\G0 - off
|
|
\G1 - on
|
|
|
|
\J Data rate adjust
|
|
\J0 - the data rates computer-modem and modem-modem are independent
|
|
\J1 - the data rate computer-modem follows the data rate modem-modem
|
|
|
|
\Kn Break setting (don't know anything about this, just that it exists ;-)
|
|
|
|
\N MNP select
|
|
\N0 - standard mode, no MNP, data is buffered
|
|
\N1 - direct mode, no MNP, no buffering
|
|
\N2 - MNP, data is buffered
|
|
\N3 - allow MNP on/off during connection, data is buffered
|
|
|
|
\O Switch on MNP during connection (the rest of the line is being ignored!)
|
|
|
|
\Pn Same as &Z
|
|
|
|
\Q Set handshake (compare &K)
|
|
\Q0 - no handshaking
|
|
\Q1 - XON/XOFF
|
|
\Q2 - modem controls data flow with CTS
|
|
\Q3 - data flow control with RTS/CTS
|
|
|
|
\S List complete configuration
|
|
|
|
\Tn Set idle timer
|
|
\T0 - timer off
|
|
\Tnn - break connection after nn minutes without data exchange
|
|
(1-90)
|
|
|
|
\U Acknowledge MNP operation; rest of line is ignored!
|
|
|
|
\V Verbose mode
|
|
\V0 - messages according to Hayes, even if MNP (no \REL)
|
|
\V1 - messages according to Microcom (\REL appended if MNP)
|
|
|
|
\X Filter XON/XOFF characters
|
|
\X0 - filter XOM/XOFF characters
|
|
\X1 - don't filter them (the usual thing)
|
|
|
|
\Y Same as AT\O\U with the difference that it is not necessary to
|
|
first send AT\O to one modem and then AT\U to the other; just
|
|
send AT\Y to each modem within 5 seconds
|
|
|
|
%An Specify control character that provokes fallback from MNP to
|
|
normal operation (see \C2). n=0..255 (ASCII code)
|
|
|
|
%C MNP5
|
|
%C0 - not allowed
|
|
%C1 - allowed
|
|
|
|
%E auto-retrain
|
|
%E0 - no auto-retrain allowed
|
|
%E1 - auto-retrain allowed according to CCITT
|
|
|
|
%R Show all S registers
|
|
|
|
%V Same as I3 (but don't ask me what it is ;-) Gives info on the firmware
|
|
version with some modems.
|
|
|
|
|
|
|
|
IRQ sharing - can it be done? (this applies to ISA bus systems only)
|
|
-----------------------------
|
|
|
|
Yes and no. Yes, it can be done in principle, and no, it can't be done
|
|
by just configuring two ports to use the same interrupt.
|
|
|
|
Let us first consider the hardware involved. PCs have ICUs (interrupt control
|
|
units, or PICs - programmable interrupt controllers) of the 8259A type. They
|
|
can be programmed to be triggered by a high signal level or a raising edge,
|
|
which is already annoying because low level or falling edge would make add-on
|
|
card design simpler. But to top this all off, they have internal pull-up
|
|
resistors! Which means that if no card is using the interrupt, it is in
|
|
the triggered state.
|
|
|
|
How would cards share interrupts? They'd only be allowed to have their
|
|
IRQ output in two states: active high or 'floating'. 'Floating' means the line
|
|
is not driven at all, neither high nor low, it 'floats'. If all sharers of
|
|
an interrupt line in the PC would only drive the line high or let it 'float',
|
|
we'd have a simple interrupt sharing scheme (that would allow for even
|
|
simpler design if the active state of the line was low) - if there wasn't
|
|
this nasty internal pull-up resistor in the 8259A. <sarcasm on> Sadly IBM
|
|
didn't provide an external pull-down resistor on the main board of the very
|
|
first PC, so later designs could not have one either for compatibility's
|
|
sake. <sarcasm off> 1.5kOhms would be a fine value; the 8259A produces 300uA
|
|
that have to be sunk below 0.8v (so 2.6kOhms would be enough in theory,
|
|
but having some safety margin can't hurt).
|
|
|
|
So how can you have your ports sharing a common interrupt line? There are
|
|
two approaches to this, each assuming you're familiar with using a soldering
|
|
iron. What you must provide is a logical OR of all interrupt outputs that
|
|
drive the line; while this can be done with an OR gate of course, it is far
|
|
more practical to use some wired-OR facility. First you'll have to add the
|
|
external pull-down resistor, either on the main board (where it really
|
|
belongs) or on one of the cards. Use 1.5kOhms for this. Then cut the line
|
|
between the card edge connector and the IRQ line driver (LS125) on each and
|
|
every card. Do this carefully; if it's a multi-layer card, you'd better cut
|
|
the pin of the LS125, or maybe you can just replace a jumper with a diode.
|
|
Now solder a diode (1N4148 will do, slow power diodes won't) over the cut
|
|
with the cathode (usually marked with a ring, but you'd better check that
|
|
thoroughly if there are multiple rings; the 1N4148 normally has a yellow
|
|
cathode ring) to the card edge connector. There you are! Now hardware will no
|
|
longer be in the way of interrupt sharing. (A 'cleaner' solution would be to
|
|
use a LS126 line driver instead of the diode with 'enable' connected to
|
|
'input', but that's only practical with from-scratch designs.)
|
|
|
|
Now let's face the software problems. In theory, interrupt sharing works fine
|
|
between different pieces of hardware, but practically this is limited to real
|
|
operating systems that do all interrupt processing by themselves; MSDOS
|
|
doesn't do that, so it's not a good option for PCs (even Linux users boot DOS
|
|
sometimes, if only to play games). Sharing interrupts even between UARTs
|
|
becomes problematic if there are several programs involved, eg. the mouse
|
|
driver and some comm application; they'd have to know of each other. 'Daisy
|
|
chaining' the interrupt (a program 'hooks' the interrupt by placing its
|
|
handler's address in the IRQ serivce table and letting the handler call the
|
|
address it found in that table at install time when it exits; no interrupt
|
|
acknowledging is done by the handlers themselves, just by the stub handler at
|
|
the end of the chain) doesn't work because DOS doesn't even provide a stub
|
|
interrupt handler! So one of the programs would have to issue EOI (end of
|
|
interrupt) to the ICU, but which one? How would it know it's the last one in
|
|
the chain? Better forget daisy chaining interrupts under DOS if you want your
|
|
programs to work reliably.
|
|
|
|
The situation is much simpler if all UARTs sharing the same interrupt are
|
|
used by the same program. This program has to be aware of the sharing
|
|
mechanism, but programs that can make use of more than one serial port
|
|
(especially libraries) usually are. Now there's only one problem to be
|
|
solved: lock-up situations. As I already wrote, the ICUs in the PC are
|
|
programmed to use raising edge trigger mode, and you can't change this
|
|
without crashing the system. Now consider the following situation. Two
|
|
UARTs share one IRQ line. UART #1 raises the line because it needs service;
|
|
the service routine is called and detects that UART #1 needs service. Before
|
|
it can perform the serivce, UART #2 raises the IRQ, too. Now UART #1 is
|
|
serviced, the line should go to the 'low' state but it doesn't because of
|
|
the other UART keeping it high; the handler checks the next UART in its
|
|
table and sees that UART #2 needs service, too. Now UART #1 receives another
|
|
character and keeps the line high while UART #2 is being serviced. How should
|
|
the handler know that this has happened? If it just issued EOI and returned,
|
|
the IRQ line would never have gone 'low' during the service, so there won't
|
|
be any future raising edges to be detected, and thus no more interrupts!
|
|
|
|
What does the service routine do to avoid lock-ups? It has to mask the
|
|
interrupt in the ICU; this resets the edge detector. If it unmasks the
|
|
interrupt again at the end of the handler and the line is still 'high',
|
|
this will trigger the edge detector and the interrupt will be scheduled
|
|
again. See the 'known problems' section for a very solid method of handling
|
|
interrupts suggested by Richard Clayton.
|
|
|
|
Windows allows for UARTs sharing interrupts; just make sure the COM ports
|
|
are configured properly in the system setup.
|
|
|
|
A note to Linux users: Linux is fully capable of sharing interrupts between
|
|
serial ports if the hardware problems described above are solved. Using the
|
|
same interrupt for several UARTs even reduces CPU load, so it is definitely a
|
|
Good Thing as long as there are not too many sharers. Having a well-designed
|
|
and kernel-supported multi-port card is even better because these cards
|
|
provide a mechanism for the handler to detect which UART has triggered
|
|
interrupt without having to look at every single IIR, which reduces overhead
|
|
even further.
|
|
|
|
|
|
|
|
Programming
|
|
===========
|
|
|
|
Now for the clickety-clickety thing. I hope you're a bit keen in
|
|
assembler programming. Programming the UART in high level languages is,
|
|
of course, possible, but not at very high rates. I give you several
|
|
routines in assembler and C that do the dirty work for you.
|
|
|
|
If you're keen on examples of how to program the UART in high level
|
|
languages, even interrupt-driven, you should have a look at some code
|
|
I received from Frank Whaley (ftp: "The_Serial_Port.more04") and at
|
|
the "Async Routines Library" Scott A. Deming is currently developing
|
|
(ftp: "asyam.zip").
|
|
|
|
First thing to do is detect which chip is used. It shouldn't be difficult
|
|
to convert this C function into assembler; I'll omit the assembly version.
|
|
|
|
int detect_UART(unsigned baseaddr)
|
|
{
|
|
// this function returns 0 if no UART is installed.
|
|
// 1: 8250, 2: 16450 or 8250 with scratch reg., 3: 16550, 4: 16550A
|
|
int x,olddata;
|
|
|
|
// check if a UART is present anyway
|
|
olddata=inp(baseaddr+4);
|
|
outp(baseaddr+4,0x10);
|
|
if ((inp(baseaddr+6)&0xf0)) return 0;
|
|
outp(baseaddr+4,0x1f);
|
|
if ((inp(baseaddr+6)&0xf0)!=0xf0) return 0;
|
|
outp(baseaddr+4,olddata);
|
|
// next thing to do is look for the scratch register
|
|
olddata=inp(baseaddr+7);
|
|
outp(baseaddr+7,0x55);
|
|
if (inp(baseaddr+7)!=0x55) return 1;
|
|
outp(baseaddr+7,0xAA);
|
|
if (inp(baseaddr+7)!=0xAA) return 1;
|
|
outp(baseaddr+7,olddata); // we don't need to restore it if it's not there
|
|
// then check if there's a FIFO
|
|
outp(baseaddr+2,1);
|
|
x=inp(baseaddr+2);
|
|
// some old-fashioned software relies on this!
|
|
outp(baseaddr+2,0x0);
|
|
if ((x&0x80)==0) return 2;
|
|
if ((x&0x40)==0) return 3;
|
|
return 4;
|
|
}
|
|
|
|
If it's not a 16550A, FIFO mode operation won't work, but there's no
|
|
problem in switching it on nevertheless as long as no 16550 is used and
|
|
your software is aware that there is no TX FIFO available (see below). If
|
|
your software doesn't use the FIFOs explicitly, write 0x7 to the FCR and
|
|
mask bits 3, 6 & 7 of the IIR. This does not reduce interrupt overhead but
|
|
makes transmission more reliable without changing anything for the software.
|
|
But remember that the 16550 has a bug with its FIFOs (see hardware section),
|
|
so if the function above returns 3, switch the FIFOs off.
|
|
|
|
Mike Surikov has provided me with an altered version of this function that
|
|
works correctly with multi-port serial adapters, too. It's available from
|
|
the ftp archive mentioned at the beginning. Look for the file
|
|
"The_Serial_Port.more03".
|
|
|
|
The prototype of this useful function has also been provided by Mike
|
|
Surikov; I've rewritten it from scratch though. It allows you to detect which
|
|
interrupt is used by a certain UART. There is an assembly version of Mike's
|
|
version (which can only detect intlevels 0-7) of this function as well. It's
|
|
available from the ftp archive as "The_Serial_Port.more02".
|
|
|
|
int detect_IRQ(unsigned base)
|
|
{
|
|
// returns: -1 if no intlevel found, or intlevel 0-15
|
|
char ier,mcr,imrm,imrs,maskm,masks,irqm,irqs;
|
|
|
|
_asm cli; // disable all CPU interrupts
|
|
ier = inp(base+1); // read IER
|
|
outp(base+1,0); // disable all UART ints
|
|
while (!(inp(base+5)&0x20)); // wait for the THR to be empty
|
|
mcr = inp(base+4); // read MCR
|
|
outp(base+4,0x0F); // connect UART to irq line
|
|
imrm = inp(0x21); // read contents of master ICU mask register
|
|
imrs = inp(0xA1); // read contents of slave ICU mask register
|
|
outp(0xA0,0x0A); // next read access to 0xA0 reads out IRR
|
|
outp(0x20,0x0A); // next read access to 0x20 reads out IRR
|
|
outp(base+1,2); // let's generate interrupts...
|
|
maskm = inp(0x20); // this clears all bits except for the one
|
|
masks = inp(0xA0); // that corresponds to the int
|
|
outp(base+1,0); // drop the int line
|
|
maskm &= ~inp(0x20); // this clears all bits except for the one
|
|
masks &= ~inp(0xA0); // that corresponds to the int
|
|
outp(base+1,2); // and raise it again just to be sure...
|
|
maskm &= inp(0x20); // this clears all bits except for the one
|
|
masks &= inp(0xA0); // that corresponds to the int
|
|
outp(0xA1,~masks); // now let us unmask this interrupt only
|
|
outp(0x21,~maskm);
|
|
outp(0xA0,0x0C); // enter polled mode; Mike Surikov reported
|
|
outp(0x20,0x0C); // that order is important with Pentium/PCI systems
|
|
irqs = inp(0xA0); // and accept the interrupt
|
|
irqm = inp(0x20);
|
|
inp(base+2); // reset transmitter interrupt in UART
|
|
outp(base+4,mcr); // restore old value of MCR
|
|
outp(base+1,ier); // restore old value of IER
|
|
if (masks) outp(0xA0,0x20); // send an EOI to slave
|
|
if (maskm) outp(0x20,0x20); // send an EOI to master
|
|
outp(0x21,imrm); // restore old mask register contents
|
|
outp(0xA1,imrs);
|
|
_asm sti;
|
|
if (irqs&0x80) // slave interrupt occured
|
|
return (irqs&0x07)+8;
|
|
if (irqm&0x80) // master interrupt occured
|
|
return irqm&0x07;
|
|
return -1;
|
|
}
|
|
|
|
|
|
|
|
Now the non-interrupt version of TX and RX.
|
|
|
|
Let's assume the following constants are set correctly (either by
|
|
'CONSTANT EQU value' or by '#define CONSTANT value'). You can easily use
|
|
variables instead, but I wanted to save the extra lines for the ADD
|
|
commands then necessary... A cute trick for calculating I/O addresses in
|
|
assembly programs is this: load an index register (BX, BP, SI, or DI)
|
|
with the base address (and keep it there), then use LEA DX,[BX+offset]
|
|
before each IN/OUT instead of MOV DX,base; ADD DX,offset. It saves you
|
|
one or two cycles. :)
|
|
|
|
UART_BASEADDR the base address of the UART
|
|
UART_BAUDRATE the divisor value (eg. 12 for 9600 bps)
|
|
UART_LCRVAL the value to be written to the LCR (eg. 0x1b for 8e1)
|
|
UART_FCRVAL the value to be written to the FCR. Bit 0, 1 and 2 set,
|
|
bits 6 & 7 according to trigger level wished (see above).
|
|
0x87 is a good value, 0x7 establishes compatibility
|
|
(except that there are some bits to be masked in the IIR).
|
|
|
|
First thing to do is initializing the UART. This works as follows:
|
|
|
|
UART_init proc near
|
|
push ax ; we are 'clean guys'
|
|
push dx
|
|
mov dx,UART_BASEADDR+3 ; LCR
|
|
mov al,80h ; set DLAB
|
|
out dx,al
|
|
mov dx,UART_BASEADDR ; divisor
|
|
mov ax,UART_BAUDRATE
|
|
out dx,ax
|
|
mov dx,UART_BASEADDR+3 ; LCR
|
|
mov al,UART_LCRVAL ; params
|
|
out dx,al
|
|
mov dx,UART_BASEADDR+4 ; MCR
|
|
xor ax,ax ; clear loopback
|
|
out dx,al
|
|
;***
|
|
pop dx
|
|
pop ax
|
|
ret
|
|
UART_init endp
|
|
|
|
void UART_init()
|
|
{
|
|
outp(UART_BASEADDR+3,0x80);
|
|
outpw(UART_BASEADDR,UART_BAUDRATE);
|
|
outp(UART_BASEADDR+3,UART_LCRVAL);
|
|
outp(UART_BASEADDR+4,0);
|
|
//***
|
|
}
|
|
|
|
If we wanted to use the FIFO functions of the 16550A, we'd have to add
|
|
some lines to the routines above (where the ***s are).
|
|
In assembler:
|
|
mov dx,UART_BASEADDR+2 ; FCR
|
|
mov al,UART_FCRVAL
|
|
out dx,al
|
|
And in C:
|
|
outp(UART_BASEADDR+2,UART_FCRVAL);
|
|
|
|
Don't forget to disable the FIFO when your program exits! Some other
|
|
software may rely on this!
|
|
|
|
Not very complex so far, isn't it? Well, I told you so at the very
|
|
beginning, and I wanted to start easy. Now let's send a character.
|
|
|
|
UART_send proc near
|
|
; character to be sent in AL
|
|
push dx
|
|
push ax
|
|
mov dx,UART_BASEADDR+5
|
|
us_wait:
|
|
in al,dx ; wait until we are allowed to write a byte to the THR
|
|
test al,20h
|
|
jz us_wait
|
|
pop ax
|
|
mov dx,UART_BASEADDR
|
|
out dx,al ; then write the byte
|
|
pop dx
|
|
ret
|
|
UART_send endp
|
|
|
|
void UART_send(char character)
|
|
{
|
|
while ((inp(UART_BASEADDR+5)&0x20)==0);
|
|
outp(UART_BASEADDR,(int)character);
|
|
}
|
|
|
|
This one sends a null-terminated string.
|
|
|
|
UART_send_string proc near
|
|
; DS:SI contains a pointer to the string to be sent.
|
|
push si
|
|
push ax
|
|
push dx
|
|
cld ; we want to read the string in its correct order
|
|
uss_loop:
|
|
lodsb
|
|
or al,al ; last character sent?
|
|
jz uss_end
|
|
;*1*
|
|
mov dx,UART_BASEADDR+5
|
|
push ax
|
|
uss_wait:
|
|
in al,dx
|
|
test al,20h
|
|
jz uss_wait
|
|
mov dx,UART_BASEADDR
|
|
pop ax
|
|
out dx,al
|
|
;*2*
|
|
jmp uss_loop
|
|
uss_end:
|
|
pop dx
|
|
pop ax
|
|
pop si
|
|
ret
|
|
UART_send_string endp
|
|
|
|
void UART_send_string(char *string)
|
|
{
|
|
int i;
|
|
for (i=0; string[i]!=0; i++)
|
|
{
|
|
//*1*
|
|
while ((inp(UART_BASEADDR+5)&0x20)==0);
|
|
outp(UART_BASEADDR,(int)string[i]);
|
|
//*2*
|
|
}
|
|
}
|
|
|
|
Of course we could have used our already programmed function/procedure
|
|
UART_send instead of the piece of code limited by *1* and *2*, but we are
|
|
interested in high-speed code and thus save the call/ret.
|
|
|
|
It shouldn't be a hard nut for you to modify the above function/procedure
|
|
so that it sends a block of data rather than a null-terminated string. I'll
|
|
omit that here.
|
|
|
|
Note that all these routines don't make any use of the TX FIFO! If we know
|
|
for sure that it's a 16550A we're dealing with, and that its FIFOs are
|
|
enabled, we could as well write up to 16 characters whenever bit 5 (THRE)
|
|
of the LSR goes 1.
|
|
|
|
Now for reception. We want to program routines that do the following:
|
|
- check if a character has been received or an error occured
|
|
- read a character if there's one available
|
|
|
|
Both the C and the assembler routines return 0 (in AX) if there is
|
|
neither an error condition nor a character available. If a character is
|
|
available, Bit 8 is set and AL or the lower byte of the return value
|
|
contains the character. Bit 9 is set if we lost data (overrun), bit 10
|
|
signals a parity error, bit 11 signals a framing error, bit 12 shows if
|
|
there is a break in the data stream and bit 15 signals if there are any
|
|
errors in the FIFO (if we turned it on). The procedure/function is much
|
|
smaller than this paragraph:
|
|
|
|
UART_get_char proc near
|
|
push dx
|
|
mov dx,UART_BASEADDR+5
|
|
in al,dx
|
|
xchg al,ah
|
|
and ax,9f00h
|
|
test ah,1
|
|
jz ugc_nochar
|
|
mov dx,UART_BASEADDR
|
|
in al,dx
|
|
ugc_nochar:
|
|
pop dx
|
|
ret
|
|
UART_get_char endp
|
|
|
|
unsigned UART_get_char()
|
|
{
|
|
unsigned x;
|
|
x = (inp(UART_BASEADDR+5) & 0x9f) << 8;
|
|
if (x&0x100) x|=((unsigned)inp(UART_BASEADDR))&0xff;
|
|
return x;
|
|
}
|
|
|
|
This procedure/function lets us easily keep track of what's happening
|
|
with the RxD pin. It does not provide any information on the modem status
|
|
lines! We'll program that later on.
|
|
|
|
If we wanted to show what's happening with the RxD pin, we'd just have to
|
|
write a routine like the following (I use a macro in the assembler version
|
|
to shorten the source code):
|
|
|
|
DOS_print macro pointer
|
|
; prints a string in the code segment
|
|
push ax
|
|
push ds
|
|
push dx
|
|
push cs
|
|
pop ds
|
|
mov dx,pointer
|
|
mov ah,9
|
|
int 21h
|
|
pop dx
|
|
pop ds
|
|
pop ax
|
|
endm
|
|
|
|
UART_watch_rxd proc near
|
|
uwr_loop:
|
|
; check if keyboard hit; we want a possibility to break the loop
|
|
mov ah,1 ; Beware! Don't call INT 16h with high transmission
|
|
int 16h ; rates, it won't work!
|
|
jnz uwr_exit
|
|
call UART_get_char
|
|
or ax,ax
|
|
jz uwr_loop
|
|
test ah,1 ; is there a character in AL?
|
|
jz uwr_nodata
|
|
push ax ; yes, print it
|
|
mov dl,al ;\
|
|
mov ah,2 ; better use this for high rates: mov ah,0eh
|
|
int 21h ;/ int 10h
|
|
pop ax
|
|
uwr_nodata:
|
|
test ah,0eh ; any error at all?
|
|
jz uwr_loop ; this speeds up things since errors should be rare
|
|
test ah,2 ; overrun error?
|
|
jz uwr_noover
|
|
DOS_print overrun_text
|
|
uwr_noover:
|
|
test ah,4 ; parity error?
|
|
jz uwr_nopar
|
|
DOS_print parity_text
|
|
uwr_nopar:
|
|
test ah,8 ; framing error?
|
|
jz uwr_loop
|
|
DOS_print framing_text
|
|
jmp uwr_loop
|
|
uwr_exit:
|
|
ret
|
|
overrun_text db "*** Overrun Error ***$"
|
|
parity_text db "*** Parity Error ***$"
|
|
framing_text db "*** Framing Error ***$"
|
|
UART_watch_rxd endp
|
|
|
|
void UART_watch_rxd()
|
|
{
|
|
union {
|
|
unsigned val;
|
|
char character;
|
|
} x;
|
|
while (!kbhit()) {
|
|
x.val=UART_get_char();
|
|
if (!x.val) continue; // nothing? Continue
|
|
if (x.val&0x100) putc(x.character); // character? Print it
|
|
if (!(x.val&0xe00)) continue; // any error condidion? No, continue
|
|
if (x.val&0x200) printf("*** Overrun Error ***");
|
|
if (x.val&0x400) printf("*** Parity Error ***");
|
|
if (x.val&0x800) printf("*** Framing Error ***");
|
|
}
|
|
}
|
|
|
|
The RX routines make use of the RX FIFO without any additional programming.
|
|
|
|
If you call these routines from a function/procedure as shown below,
|
|
you've got a small terminal program!
|
|
|
|
terminal proc near
|
|
ter_loop:
|
|
call UART_watch_rxd ; watch line until a key is pressed
|
|
xor ax,ax ; get that key from the keyboard buffer
|
|
int 16h
|
|
cmp al,27 ; is it ESC?
|
|
jz ter_end ; yes, then end this function
|
|
call UART_send ; send the character typed if it's not ESC
|
|
jmp ter_loop ; don't forget to check if data comes in
|
|
ter_end:
|
|
ret
|
|
terminal endp
|
|
|
|
void terminal()
|
|
{
|
|
int key;
|
|
while (1)
|
|
{
|
|
UART_watch_rxd();
|
|
key=getche();
|
|
if (key==27) break;
|
|
UART_send((char)key);
|
|
}
|
|
}
|
|
|
|
These, of course, should be called from an embedding routine like the
|
|
following (the assembler routines concatenated will assemble as an .EXE
|
|
file. Put the lines 'code segment' and 'assume cs:code,ss:stack' to the
|
|
front).
|
|
|
|
main proc near
|
|
call UART_init
|
|
call terminal
|
|
mov ax,4c00h
|
|
int 21h
|
|
main endp
|
|
code ends
|
|
stack segment stack 'stack'
|
|
dw 128 dup (?)
|
|
stack ends
|
|
end main
|
|
|
|
void main()
|
|
{
|
|
UART_init();
|
|
terminal();
|
|
}
|
|
|
|
Here we are. Now you've got everything you need to program simple
|
|
polling UART software.
|
|
|
|
You know the way. Go and add functions to check if a data set is there,
|
|
then establish a connection. Don't know how? Set DTR, wait for DSR.
|
|
If you want to send, set RTS and wait for CTS before you actually transmit
|
|
data. You don't need to store old values of the MCR: this register is
|
|
readable. Just read in the data, AND/OR the bits as required and write the
|
|
byte back.
|
|
|
|
|
|
Let us now write the interrupt-driven versions of the routines. This is going
|
|
to be a bit voluminous, so I draw the scene and leave the painting to you. If
|
|
you want to implement interrupt-driven routines in a C program use either the
|
|
inline-assembler feature or link the objects together. Of course you can also
|
|
program interrupts in C (or other languages for that matter (are there
|
|
any? :)).
|
|
|
|
You'll find a complete program using interrupts at the end of this chapter.
|
|
|
|
First thing to do is initialize the UART the same way as shown above.
|
|
But there is some more work to be done before you enable the UART
|
|
interrupt: FIRST SET THE INTERRUPT VECTOR CORRECTLY! Use function 25h of
|
|
the DOS interrupt 21h. Remember to store the old value (obtained by calling
|
|
DOS interrupt 21h function 35h) and to restore this value when exiting
|
|
to DOS again. See also the note on known bugs if you've got a 8250.
|
|
|
|
UART_INT EQU 0Ch ; for COM2 / COM4 use 0bh
|
|
UART_ONMASK EQU 11101111b ; for COM2 / COM4 use 11110111b
|
|
UART_OFFMASK EQU NOT UART_ONMASK
|
|
UART_IERVAL EQU ? ; replace ? by any value between 0h and 0fh
|
|
; (dependent on which ints you want)
|
|
; DON'T SET bit 1 now! (not with this kind of service
|
|
; routine, that is)
|
|
UART_OLDVEC DD ?
|
|
|
|
initialize_UART_interrupt proc near
|
|
push ds
|
|
push es ; first thing is to store the old interrupt
|
|
push bx ; vector
|
|
mov ax,3500h+UART_INT
|
|
int 21h
|
|
mov word ptr UART_OLDVEC,bx
|
|
mov word ptr UART_OLDVEC+2,es
|
|
pop bx
|
|
pop es
|
|
push cs ; build a pointer in DS:DX
|
|
pop ds
|
|
lea dx,interrupt_service_routine
|
|
mov ax,2500h+UART_INT
|
|
int 21h ; and ask DOS to set this pointer as the new interrrupt vector
|
|
pop ds
|
|
mov dx,UART_BASEADDR+4 ; MCR
|
|
in al,dx
|
|
or al,8 ; set OUT2 bit to enable interrupts
|
|
out dx,al
|
|
mov dx,UART_BASEADDR+1 ; IER
|
|
mov al,UART_IERVAL ; enable the interrupts we want
|
|
out dx,al
|
|
in al,21h ; last thing to do is unmask the int in the ICU
|
|
and al,UART_ONMASK
|
|
out 21h,al
|
|
sti ; and free interrupts if they have been disabled
|
|
ret
|
|
initialize_UART_interrupt endp
|
|
|
|
deinitialize_UART_interrupt proc near
|
|
push ds
|
|
lds dx,UART_OLDVEC
|
|
mov ax,2500h+UART_INT
|
|
int 21h
|
|
pop ds
|
|
in al,21h ; mask the UART interrupt
|
|
or al,UART_OFFMASK
|
|
out 21h,al
|
|
mov dx,UART_BASEADDR+1
|
|
xor al,al
|
|
out dx,al ; clear all interrupt enable bits
|
|
mov dx,UART_BASEADDR+4
|
|
out dx,al ; and disconnect the UART from the ICU
|
|
ret
|
|
deinitialize_UART_interrupt endp
|
|
|
|
Now the interrupt service routine. It has to follow several rules:
|
|
first, it MUST NOT change the contents of any register of the CPU! Then it
|
|
has to tell the ICU (did I tell you that this is the interrupt control
|
|
unit? It is also called PIC Programmable Interrupt Controller) that the
|
|
interrupt is being serviced. Next thing is test which part of the UART needs
|
|
service. Let's have a look at the following procedure:
|
|
|
|
interupt_service_routine proc far ; define as near if you want to link .COM
|
|
;*1* ; it doesn't matter anyway since IRET is
|
|
push ax ; always a FAR command
|
|
push cx
|
|
push dx
|
|
push bx
|
|
push sp
|
|
push bp
|
|
push si
|
|
push di
|
|
;*2* replace the part between *1* and *2* by pusha on an 80186+ system
|
|
push ds
|
|
push es
|
|
in al,21h
|
|
or al,UART_OFFMASK
|
|
out 21h,al
|
|
mov al,20h ; remember: first thing to do in interrupt routines is tell
|
|
out 20h,al ; the ICU about the service being done. This avoids lock-up
|
|
int_loop:
|
|
mov dx,UART_BASEADDR+2 ; IIR
|
|
in al,dx ; check IIR info
|
|
test al,1
|
|
jnz int_end
|
|
and ax,6 ; we're interested in bit 1 & 2 (see data sheet info)
|
|
mov si,ax ; this is already an index! Well-devised, huh?
|
|
call word ptr cs:int_servicetab[si] ; ensure a near call is used...
|
|
jmp int_loop
|
|
int_end:
|
|
in al,21h
|
|
and al,UART_ONMASK
|
|
out 21h,al
|
|
pop es
|
|
pop ds
|
|
;*3*
|
|
pop di
|
|
pop si
|
|
pop bp
|
|
pop sp
|
|
pop bx
|
|
pop dx
|
|
pop cx
|
|
pop ax
|
|
;*4* *3* - *4* can be replaced by popa on an 80186+ based system
|
|
iret
|
|
interupt_service_routine endp
|
|
|
|
This is the part of the service routine that does the decisions. Now we
|
|
need four different service routines to cover all four interrupt source
|
|
possibilities (EVEN IF WE DIDN'T ENABLE THEM! Let's play this safe).
|
|
|
|
int_servicetab DW int_modem, int_tx, int_rx, int_status
|
|
|
|
int_modem proc near
|
|
mov dx,UART_BASE+6 ; MSR
|
|
in al,dx
|
|
; do with the info what you like; probably just ignore it...
|
|
; but YOU MUST READ THE MSR or you'll lock up the interrupt!
|
|
ret
|
|
int_modem endp
|
|
|
|
int_tx proc near
|
|
; get next byte of data from a buffer or something
|
|
; (remember to set the segment registers correctly!)
|
|
; and write it to the THR (offset 0)
|
|
; if no more data is to be sent, disable the THRE interrupt
|
|
; If the FIFOs are switched on (and you've made sure it's a 16550A!), you
|
|
; can write up to 16 characters
|
|
|
|
; end of data to be sent?
|
|
; no, jump to end_int_tx
|
|
mov dx,UART_BASEADDR+1
|
|
in al,dx
|
|
and al,00001101b
|
|
out dx,al
|
|
end_int_tx:
|
|
ret
|
|
int_tx endp
|
|
|
|
int_rx proc near
|
|
mov dx,UART_BASEADDR
|
|
in al,dx
|
|
; do with the character what you like (best write it to a
|
|
; FIFO buffer [not the one of the 16550A, silly! :)])
|
|
; the following lines speed up FIFO mode operation
|
|
mov dx,UART_BASEADDR+5
|
|
in al,dx
|
|
test al,1
|
|
jnz int_rx
|
|
; these lines are a cure for the well-known problem of TX interrupt
|
|
; lock-ups when receiving and transmitting at the same time
|
|
test al,40h
|
|
je dont_unlock
|
|
call int_tx
|
|
dont_unlock:
|
|
ret
|
|
int_rx endp
|
|
|
|
int_status proc near
|
|
mov dx,UART_BASEADDR+5
|
|
in al,dx
|
|
; do what you like. It's just important to read the LSR
|
|
ret
|
|
int_status endp
|
|
|
|
How is data sent now? Write it to a FIFO buffer (that's nothing to do with
|
|
the built-in FIFOs of the 16550!) that is read by the interrupt routine.
|
|
Then set bit 1 of the IER and check if this has already started transmission.
|
|
If not, you'll have to start it by hand (just call the int_tx routine). THIS
|
|
IS DUE TO THOSE NUTTY GUYS AT BIG BLUE WHO DECIDED TO USE EDGE TRIGGERED
|
|
INTERRUPTS INSTEAD OF PROVIDING ONE SINGLE FLIP FLOP FOR THE 8253/8254!
|
|
See the "Known Problems" section for another good method of handling the
|
|
UART interrupts that avoids all these problems.
|
|
|
|
This procedure can be a C function, too. It is not time-critical at all.
|
|
|
|
; copy data to buffer
|
|
|
|
mov dx,UART_BASEADDR+1 ; IER
|
|
in al,dx
|
|
or al,2 ; set bit 1
|
|
out dx,al
|
|
nop
|
|
nop ; give the UART some time to kick the interrupt...
|
|
nop
|
|
mov dx,UART_BASEADDR+5 ; LSR
|
|
cli ; make sure no interrupts get in-between if not already running
|
|
in al,dx
|
|
test al,40h ; is there a transmission running?
|
|
jz dont_crank ; yes, so don't mess it up
|
|
call int_tx ; no, crank it up
|
|
sti
|
|
dont_crank:
|
|
|
|
Well, that's it! Your main program has to take care of the buffers,
|
|
nothing else!
|
|
|
|
Remember to call deinitialize_UART_interrupt before exiting to DOS! In C,
|
|
this can easily be done by adding the function to the at-exit list with
|
|
the atexit() function. You won't have to worry about the myriads of ways
|
|
your program could terminate then.
|
|
|
|
For those of you who prefer learning by watching rather than learning by
|
|
doing ("lazy" is such an ignorant word :-), here's the source of a
|
|
small terminal program. It can be assembled with TASM or ML without
|
|
any change. Wire together two PCs (three-wire-connection, see the
|
|
beginning of this file) and start it on each of them. You can then
|
|
type messages on both keyboards that can be viewed on both screens.
|
|
If you press F1, a large string is being sent (but not displayed on
|
|
the sender's screen). Ctrl-X terminates the program.
|
|
|
|
|
|
----8<--------8<--------8<--------8<--------8<--------8<--------8<----
|
|
|
|
; just a small terminal program using interrupts.
|
|
; It's quite dumb: it uses the BIOS for screen output
|
|
; and keyboard input
|
|
; assemble and link as .EXE (just type ml name)
|
|
; If you have a 16550 (not a 16550A), you may lose
|
|
; characters since the fifos are turned on (see "Known problems
|
|
; with several chips")
|
|
; If your BIOS locks the interrupts while scrolling (some do),
|
|
; you may encounter data loss at high rates.
|
|
|
|
model small
|
|
dosseg
|
|
|
|
INTNUM equ 0Ch ; COM1; COM2: 0Bh
|
|
OFFMASK equ 00010000b ; COM1; COM2: 00001000b
|
|
ONMASK equ not OFFMASK
|
|
UART_BASE equ 3F8h ; COM1; COM2: 2F8h
|
|
UART_RATE equ 12 ; 9600 bps, see table in this file
|
|
UART_PARAMS equ 00000011b ; 8n1, see tables
|
|
RXFIFOSIZE equ 8096 ; set this to your needs
|
|
TXFIFOSIZE equ 8096 ; dito.
|
|
; the fifos must be large on slow computers
|
|
; and can be small on fast ones
|
|
; These have nothing to do with the 16550A's
|
|
; built-in FIFOs!
|
|
|
|
.data
|
|
long_text db 0dh
|
|
db "This is a very long test string. It serves the purpose of",0dh
|
|
db "demonstrating that our interrupt-driven routines are capable",0dh
|
|
db "of coping with pressure situations like the one we provoke",0dh
|
|
db "by sending large bunches of characters in each direction at",0dh
|
|
db "the same time. Run this test by pressing F1 at a low data",0dh
|
|
db "rate and a high data rate to see why serial transmission and",0dh
|
|
db "reception should be programmed interrupt-driven. You won't lose",0dh
|
|
db "a single character as long as you don't overload the fifos, no",0dh
|
|
db "matter how hard you try!",0dh,0
|
|
|
|
ds_dgroup macro
|
|
mov ax,DGROUP
|
|
mov ds,ax
|
|
assume ds:DGROUP
|
|
endm
|
|
|
|
ds_text macro
|
|
push cs
|
|
pop ds
|
|
assume ds:_TEXT
|
|
endm
|
|
|
|
rx_checkwrap macro
|
|
local rx_nowrap
|
|
cmp si,offset rxfifo+RXFIFOSIZE
|
|
jb rx_nowrap
|
|
lea si,rxfifo
|
|
rx_nowrap:
|
|
endm
|
|
|
|
tx_checkwrap macro
|
|
local tx_nowrap
|
|
cmp si,offset txfifo+TXFIFOSIZE
|
|
jb tx_nowrap
|
|
lea si,txfifo
|
|
tx_nowrap:
|
|
endm
|
|
|
|
.stack 256
|
|
|
|
.data?
|
|
old_intptr dd ?
|
|
rxhead dw ?
|
|
rxtail dw ?
|
|
txhead dw ?
|
|
txtail dw ?
|
|
bitxfifo dw 1 ; size of built-in TX fifo (1 if no fifo)
|
|
rxfifo db RXFIFOSIZE dup (?)
|
|
txfifo db TXFIFOSIZE dup (?)
|
|
|
|
.code
|
|
start proc far
|
|
call install_interrupt_handler
|
|
call clear_fifos
|
|
call clear_screen
|
|
call init_UART
|
|
continue:
|
|
call read_RX_fifo
|
|
call read_keyboard
|
|
jnc continue
|
|
call clean_up
|
|
mov ax,4c00h
|
|
int 21h ; return to DOS
|
|
start endp
|
|
|
|
interrupt_handler proc far
|
|
assume ds:nothing,es:nothing,ss:nothing,cs:_text
|
|
push ax
|
|
push cx
|
|
push dx ; first save the regs we need to change
|
|
push ds
|
|
push si
|
|
in al,21h
|
|
or al,OFFMASK ; disarm the interrupt
|
|
out 21h,al
|
|
mov al,20h ; acknowledge interrupt
|
|
out 20h,al
|
|
|
|
ih_continue:
|
|
mov dx,UART_BASE+2
|
|
xor ax,ax
|
|
in al,dx ; get interrupt cause
|
|
test al,1 ; did the UART generate the int?
|
|
jne ih_sep ; no, then it's somebody else's problem
|
|
and al,6 ; mask bits not needed
|
|
mov si,ax ; make a pointer out of it
|
|
call interrupt_table[si] ; serve this int
|
|
jmp ih_continue ; and look for more things to be done
|
|
ih_sep:
|
|
|
|
in al,21h
|
|
and al,ONMASK ; rearm the interrupt
|
|
out 21h,al
|
|
|
|
pop si
|
|
pop ds
|
|
pop dx ; restore regs
|
|
pop cx
|
|
pop ax
|
|
iret
|
|
interrupt_table dw int_modem,int_tx,int_rx,int_status
|
|
interrupt_handler endp
|
|
|
|
int_modem proc near
|
|
; just clear modem status, we are not interested in it
|
|
mov dx,UART_BASE+6
|
|
in al,dx
|
|
ret
|
|
int_modem endp
|
|
|
|
int_tx proc near
|
|
ds_dgroup
|
|
; check if there's something to be sent
|
|
mov si,txtail
|
|
mov cx,bitxfifo
|
|
itx_more:
|
|
cmp si,txhead
|
|
je itx_nothing
|
|
cld
|
|
lodsb
|
|
mov dx,UART_BASE
|
|
out dx,al ; write it to the THR
|
|
; check for wrap-around in our fifo
|
|
tx_checkwrap
|
|
; send as much bytes as the chip can take when available
|
|
loop itx_more
|
|
jmp itx_dontstop
|
|
itx_nothing:
|
|
; no more data in the fifo, so inhibit TX interrupts
|
|
mov dx,UART_BASE+1
|
|
mov al,00000001b
|
|
out dx,al
|
|
itx_dontstop:
|
|
mov txtail,si
|
|
ret
|
|
int_tx endp
|
|
|
|
int_rx proc near
|
|
ds_dgroup
|
|
mov si,rxhead
|
|
irx_more:
|
|
mov dx,UART_BASE
|
|
in al,dx
|
|
mov byte ptr [si],al
|
|
inc si
|
|
; check for wrap-around
|
|
rx_checkwrap
|
|
; see if there are more bytes to be read
|
|
mov dx,UART_BASE+5
|
|
in al,dx
|
|
test al,1
|
|
jne irx_more
|
|
mov rxhead,si
|
|
test al,40h ; Sometimes when sending and receiving at the
|
|
jne int_tx ; same time, TX ints get lost. This is a cure.
|
|
ret
|
|
int_rx endp
|
|
|
|
int_status proc near
|
|
; just clear the status ("this trivial task is left as an exercise
|
|
; to the student")
|
|
mov dx,UART_BASE+5
|
|
in al,dx
|
|
ret
|
|
int_status endp
|
|
|
|
read_RX_fifo proc near
|
|
; see if there are bytes to be read from the fifo
|
|
; we read a maximum of 16 bytes, then return in order
|
|
; not to break keyboard control
|
|
ds_dgroup
|
|
cld
|
|
mov cx,16
|
|
mov si,rxtail
|
|
rx_more:
|
|
cmp si,rxhead
|
|
je rx_nodata
|
|
lodsb
|
|
call output_char
|
|
; check for wrap-around
|
|
rx_checkwrap
|
|
loop rx_more
|
|
rx_nodata:
|
|
mov rxtail,si
|
|
ret
|
|
read_RX_fifo endp
|
|
|
|
read_keyboard proc near
|
|
ds_dgroup
|
|
; check for keys pressed
|
|
mov ah,1
|
|
int 16h
|
|
je rk_nokey
|
|
xor ax,ax
|
|
int 16h
|
|
cmp ax,2d18h ; is it Ctrl-X?
|
|
stc
|
|
je rk_ctrlx
|
|
cmp ax,3b00h ; is it F1?
|
|
jne rk_nf1
|
|
lea si,long_text ; send a very long test string
|
|
call send_string
|
|
jmp rk_nokey
|
|
rk_nf1:
|
|
; echo the character to the screen
|
|
call output_char
|
|
|
|
call send_char
|
|
rk_nokey:
|
|
clc
|
|
rk_ctrlx:
|
|
ret
|
|
read_keyboard endp
|
|
|
|
|
|
install_interrupt_handler proc near
|
|
ds_dgroup
|
|
; install interrupt handler first
|
|
mov ax,3500h+INTNUM
|
|
int 21h
|
|
mov word ptr old_intptr,bx
|
|
mov word ptr old_intptr+2,es
|
|
mov ax,2500h+INTNUM
|
|
ds_text
|
|
lea dx,interrupt_handler
|
|
int 21h
|
|
ret
|
|
install_interrupt_handler endp
|
|
|
|
clear_fifos proc near
|
|
ds_dgroup
|
|
; clear fifos (not those in the 16550A, but ours)
|
|
lea ax,rxfifo
|
|
mov rxhead,ax
|
|
mov rxtail,ax
|
|
lea ax,txfifo
|
|
mov txhead,ax
|
|
mov txtail,ax
|
|
ret
|
|
clear_fifos endp
|
|
|
|
init_UART proc near
|
|
; initialize the UART
|
|
mov dx,UART_BASE+3
|
|
mov al,80h
|
|
out dx,al ; make DL register accessible
|
|
mov dx,UART_BASE
|
|
mov ax,UART_RATE
|
|
out dx,ax ; write bps rate divisor
|
|
mov dx,UART_BASE+3
|
|
mov al,UART_PARAMS
|
|
out dx,al ; write parameters
|
|
|
|
; is it a 16550A?
|
|
mov dx,UART_BASE+2
|
|
in al,dx
|
|
and al,11000000b
|
|
cmp al,11000000b
|
|
jne iu_nofifos
|
|
mov bitxfifo,16
|
|
mov dx,UART_BASE+2
|
|
mov al,11000111b
|
|
out dx,al ; clear and enable the fifos if they exist
|
|
iu_nofifos:
|
|
mov dx,UART_BASE+1
|
|
mov al,00000001b ; allow RX interrupts
|
|
out dx,al
|
|
mov dx,UART_BASE
|
|
in al,dx ; clear receiver
|
|
mov dx,UART_BASE+5
|
|
in al,dx ; clear line status
|
|
inc dx
|
|
in al,dx ; clear modem status
|
|
; free interrupt in the ICU
|
|
in al,21h
|
|
and al,ONMASK
|
|
out 21h,al
|
|
; and enable ints from the UART
|
|
mov dx,UART_BASE+4
|
|
mov al,00001000b
|
|
out dx,al
|
|
ret
|
|
init_UART endp
|
|
|
|
clear_screen proc near
|
|
mov ah,0fh ; allow all kinds of video adapters to be used
|
|
int 10h
|
|
cmp al,7
|
|
je cs_1
|
|
mov al,3
|
|
cs_1:
|
|
xor ah,ah
|
|
int 10h
|
|
ret
|
|
clear_screen endp
|
|
|
|
clean_up proc near
|
|
ds_dgroup
|
|
; lock int in the ICU
|
|
in al,21h
|
|
or al,OFFMASK
|
|
out 21h,al
|
|
xor ax,ax
|
|
mov dx,UART_BASE+4 ; disconnect the UART from the int line
|
|
out dx,al
|
|
mov dx,UART_BASE+1 ; disable UART ints
|
|
out dx,al
|
|
mov dx,UART_BASE+2 ; disable the fifos (old software relies on it)
|
|
out dx,al
|
|
; restore int vector
|
|
lds dx,old_intptr
|
|
mov ax,2500h+INTNUM
|
|
int 21h
|
|
ret
|
|
clean_up endp
|
|
|
|
output_char proc near
|
|
push si
|
|
push ax
|
|
oc_cr:
|
|
push ax
|
|
mov ah,0eh ; output character using BIOS TTY
|
|
int 10h ; it's your task to improve this
|
|
pop ax
|
|
cmp al,0dh ; add LF after CR; change it if you don't like it
|
|
mov al,0ah
|
|
je oc_cr
|
|
pop ax
|
|
pop si
|
|
ret
|
|
output_char endp
|
|
|
|
send_char proc near
|
|
push si
|
|
push ax
|
|
ds_dgroup
|
|
pop ax
|
|
mov si,txhead
|
|
mov byte ptr [si],al
|
|
inc si
|
|
; check for wrap-around
|
|
tx_checkwrap
|
|
mov txhead,si
|
|
; test if the interrupt is running at the moment
|
|
mov dx,UART_BASE+5
|
|
in al,dx
|
|
test al,40h
|
|
je sc_dontcrank
|
|
; crank it up
|
|
; note that this might not work with some very old 8250s
|
|
mov dx,UART_BASE+1
|
|
mov al,00000011b
|
|
out dx,al
|
|
sc_dontcrank:
|
|
pop si
|
|
ret
|
|
send_char endp
|
|
|
|
send_string proc near
|
|
; sends a null-terminated string pointed at by DS:SI
|
|
ds_dgroup
|
|
cld
|
|
ss_more:
|
|
lodsb
|
|
or al,al
|
|
je ss_end
|
|
call send_char
|
|
jmp ss_more
|
|
ss_end:
|
|
ret
|
|
send_string endp
|
|
|
|
end start
|
|
|
|
---->8-------->8-------->8-------->8-------->8-------->8-------->8----
|
|
|
|
Stephen Warner provided me with an assembly source of a TSR program that
|
|
puts every character it receives from the serial port in the keyboard
|
|
buffer. This allows to remotely control nearly every other program; it works
|
|
with ATs and higher computers only. I decided not to add it to this file
|
|
since it doesn't show anything about programming the serial port that's
|
|
not already covered by other listings in this file. If you are interested
|
|
in it, you can obtain it from the ftp archive (it is named
|
|
"The_Serial_Port.more01"). See the beginning of this file.
|
|
|
|
One more thing: always remember that at 115,200 bps there is service to
|
|
be done at least every 85 microseconds! On an XT with 4.77 MHz this is
|
|
about 40 assembler commands! So forget about servicing the serial port at
|
|
this rate in high-level languages on such computers. Using a 16550A is
|
|
strongly recommended at high rates (turn on FIFOs) but not necessary
|
|
with otherwise decent hardware.
|
|
|
|
The interrupt service routines can be accelerated by not pushing that
|
|
much registers, and pusha and popa are fast replacements for 8 other
|
|
pushs/pops.
|
|
|
|
|
|
Well, that's the end of my short :-) summary. Don't hesitate to correct
|
|
me if I'm wrong (preferably via email) in the details (I hope not, but it's
|
|
not easy to find typographical and other errors in a text that you've
|
|
written yourself). And please help me to complete this file! If you've got
|
|
anything to add, email it to me and I'll spread it round.
|
|
|
|
I've received a lot of feedback from you, and I'd like to thank everybody
|
|
who encouraged me to continue the work on this file.
|
|
|
|
|
|
Yours
|
|
|
|
Chris
|
|
|
|
P.S. You surely have noticed that English isn't my native tongue... so please
|
|
excuse everything that's not pleasant for the eye, or, even better, tell me
|
|
about it! It shouldn't be an ordeal though, at least some have assured me
|
|
so...
|
|
|