Serial
Serial
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.
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? :-)
This is what John A. Limpert told me 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.
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, '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: -10.5v/+11v
Short circuit current: 6.8ma
Output impedance: ca 2 kiloohms (non-linear!)
Input impedance: ca 4.3 kiloohms (non-linear!)
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).
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.
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...
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.
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]
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".
Encoding schemes
----------------
I've sneaked this table from the posting 'FAQ zu /Z-NETZ/TELECOM/ALLGEMEIN'
of Kristian Koehntopp <[email protected]> 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 <[email protected]> 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;
}
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 EQU0Ch ; for COM2 / COM4 use 0bh
UART_ONMASK EQU11101111b ; for COM2 / COM4 use 11110111b
UART_OFFMASK EQUNOT 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
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...