 
Date of release: 03 Mar 1994             Release 12 
 
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: chbl@stud.uni-sb.de or 
chris@phil15.uni-sb.de. See the end for details. 
 
It's the eleventh 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, 1994 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 
================================== 
 
The start and stop bit voltages mentioned in this document have been wrong 
all the time since release 2 or something like that, and same is true for 
the break and the quiescent state. The sad thing is that I've had lots of 
arguments about this via email, and I did my very best to convince everybody 
of my faulty opinion. :( My apologies to everyone who tried to fight it out 
with me and gave up facing my stubbornness. It's fixed in this release 
(thanks very much, Brian). 
 
Fixed two bad labels and a bracket in the examples. 
 
New irq detection routine can handle interrupt levels 8-15. 
 
 
What I'm doing 
============== 
 
I'm looking for info on serial port programming under Windows in Enhanced 
Mode. 
 
Finally I've decided to write a UART library of my own. It is currently 
in its beta phase. If you'd like to join the exquisite club of beta testers, 
drop me a note or simply download it from pfsparc02.phil15.uni-sb.de, file 
pub/E-Technik/afd/uart_lib.zip. It is not yet capable of interrupt levels 
above 7, but that'll be fixed soon. 
 
 
What others are doing 
===================== 
 
There is a file available from the AFD (called The_Serial_Port.more05) that 
contains an article by Bob Niland covering serial communication under 
Windows. It is regularly posted to comp.sys.ibm.pc.hardware.comm and other 
groups; if you obtain it from there it's probably more actual. 
 
 
The "Automatic File Delivery" (AFD) service 
=========================================== 
 
An "Automatic File Delivery" service has been installed that allows you to 
obtain the latest version of this file and many others. Simply send an 
email to et11hks4@etcip9.ee.uni-sb.de with the subject "help" or "index". 
Etcip7 and etcip8 are backup machines in case etcip9 hangs; you can write 
to these, too. 
 
Please note that this service is just a temporary solution, and a quite 
buggy one, too. I'd like to replace it with real anonymous FTP on the same 
old Sun cluster, but that's not my business. It is, however, my business 
to maintain the anonymous FTP archives at pfsparc02.phil15.uni-sb.de 
(134.96.82.13), so have a look at pub/E-Technik/afd for the files. 
 
 
Acknowledgements 
================ 
 
The following persons have contributed (directly or indirectly :-) to this 
summary by providing information or making suggestions/reporting errors: 
 
      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> 
 
 
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; 
there's only a difference if the line has more than two states. Since this 
is not the case with the RS232C (EIA232) 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 note. 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, while 1200 bps at 7e1 carry 840 bits of information 
per second that modems send at 600 baud. 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 per byte = 10 bits per byte. 
          At 2400 bps, this is 240 bytes/characters per second. 2400 bps are 
          normally transmitted using QAM where 4 bits are clustered, and hence 
          encoded to 600 baud. 
 
     7e1: 1 start bit, 7 data bits, 1 parity bit, 1 stop bit = 10 bits per 
          byte. 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 TTYp uses two different line states called 'mark' and 'space'. (For the 
sake of clearness I name the line states 'high' for positive and 'low' 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' 
                       |   |       |   |   |   | 
      mark   ----------+   +-------+   +---+   +-------  low   '1' 
 
                        (1)  --------(2)-------- (3) 
 
  (1) start bit   (2) data bits   (3) stop bit(s) 
 
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 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. It is used to 
synchronize transmitter and receiver and is always a logical '0' (so the 
line goes 'high'). 
 
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 
'high' state of the line, a logical '1' by 'low'. 
 
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' steps in 
the data word (including parity bit) 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 'low' state for a minimum time (that means the 
stop bit is a logical '1'). 
 
The protocol is usually described by a sequence of numbers and letters, 
eg. 8n1 means 1 start bit (always), 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. 
 
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 voltage he's talking about was 24 volts. 
 
 
The physical transmission 
------------------------- 
 
Teletypes use 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, see the Registers section). The RS232C 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. 
 
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. This connection is called a 'null-modem' connection. RX1 is connected 
   to TX2 and vice versa, GND1 to GND2. In addition to this, connect RTS 
   to CTS & DCD and connect DTR to DSR (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 metres 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-232 spec. has an official limit of 50 ft for RS-232 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 Comminications", by McNamara (Digital 
  Press, 1992). It lists the maximum distances for an RS-232 
  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. 
 
 
 
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 
    RxD         3     2    i   Receive Data 
    RTS         4     7    o   Request To Send 
    CTS         5     8    i   Clear To Send 
    DTR        20     4    o   Data Terminal Ready 
    DSR         6     6    i   Data Set Ready 
    RI         22     9    i   Ring Indicator 
    DCD         8     1    i   Data Carrier Detect 
    GND         7     5    -   Signal ground 
     -          1     -    -   Protective ground       Don't use this one 
                                                       for 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 
'high' state to signal events. 
 
The lines are: 
 
  RxD, TxD: These lines carry the data; 1 is transmitted as 'low' and 0 is 
    transmitted as 'high'. 
 
  RTS, CTS: Are used by the PC and the modem/printer/whatsoever (further 
    on referred to as the data set) 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. 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, i.e. 
    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'. 
    (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. 
 
  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): 
 
  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!) 
 
 
Other asynchronous hardware than RS232 
-------------------------------------- 
 
There are several other standards that use the same chipset and protocol as 
RS232. RS422 and the more robust (but compatible) version RS485 (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 RS232 software if a 
compatible chipset is used. 
 
It's not possible to connect eg. RS232 to RS485 without an appropriate 
interface. 
 
 
Connecting devices (or computers) 
------------------ 
 
When you connect a data set (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 easyly be done using a 
25-wire ribbon cable and two crimp connectors. 
 
When you connect another computer, this is the wiring you need: 
 
        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, a so-called null-modem connection 
can be used. 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! 
 
The null-modem connection is used to establish an XON/XOFF-connection 
between two PCs (see the Programming section for details about XON/XOFF). 
 
Remember: the names DTR, DSR, CTS & RTS refer to the lines as seen from 
the 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: 
 
    Port     Base address    Int #  Int level 
 
    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. 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 COM ports. 
See the "Programming" section for an example program that checks if a UART is 
installed at a given base address. 
 
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. 
 
Also see the Programming section for a routine that detects the interrupt 
level/number that a UART uses. 
 
See the chapter "Multi-Port Serial Adapters" for further information. 
 
 
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. 
 
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 features. 
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 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, 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 *is* a difference between the 16550A and the 16550AF. 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. So the best choice for your PC is 
16550AFN (the N indicates 40-pin DIP plastic package), 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. 
 
 
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). 
 
 
 
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! If you write a '1' to the appro- 
priate 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... :-)]. 
 
  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 16 x 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. 
 
-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 16 x 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 indicates only 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) 
 
    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 bit 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 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. 
 
        [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. I've never 
             had any need to do this. CB] 
          4) [Replace the chip by a 16550A; it has this bug fixed. CB] 
 
    16550 (without the A): 
 
        * Rx FIFO bug: Sometimes a 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] 
 
No 16550A(F) bugs reported (yet?) 
 
[Same is true for the 16552, a two-in-one version of the 16550AF, 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 the AFD. 
 
 
 
Registers 
========= 
 
First some tables; full descriptions follow. Base addresses as specified by 
IBM. 
 
 
COM1 COM2 COM3 COM4 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        0      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 
 
 
 
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 NS Germany (I know that this 
abbreviation is not well-chosen :-( ) 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. 
 
BTW: Ever tried 1.76 bps? Kindergarten kids write faster. 
 
The Microsoft mouse uses 1200 bps, 7n1, the Mouse Systems mouse uses 1200 
bps, 8n1. See the Mouse 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. 
 
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. 
 
In most software applications bits 3, 6 & 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. 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 'high' (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. 
 
 
 
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 'high' 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 'high' to 'low'). 
   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. If loopback is selected, it shows the state of OUT1. 
   Bit 7:   DCD. 
 
 
 
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 RS422/RS485-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 gives you no 
feedback 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-232 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 set 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 a register 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 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 boards. I don't have any experience 
with EISA or other fancy things like VLB! 
 
 
 
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's 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's 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 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, 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-232 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! 
 
   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 several FTP sites. 
 
 
 
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, 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 Data 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 ) 
 
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    X7    X6    X5    X4    X3    X2    X1    X0 
  3rd byte    Y7    Y6    Y5    Y4    Y3    Y2    Y1    Y0 
  4th byte    equal to 2nd byte 
  5th byte    equal to 3rd byte 
 
  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. 
 
  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) 
  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 (+ up   ) 
 
The mouse should rather be used with the mouse driver software; this 
ensures compatibility to future changes and greatly reduces software 
overhead. See Ralf Brown's interrupt list, interrupt 33h. 
 
 
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 the AFD service; 
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 
  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. 
 
 
 
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 
 
A   Take over phone line (if you've already dialed with your phone or 
    if you've 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) 
 
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. 
     
    
 
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, wherever possible, in 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 (AFD: "The_Serial_Port.more04") and at 
the "Async Routines Library" Scott A. Deming is currently developing 
(AFD: "asyam.uue"). 
 
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; 
   // first step: see if the LCR is there 
   outp(baseaddr+3,0x1b); 
   if (inp(baseaddr+3)!=0x1b) return 0; 
   outp(baseaddr+3,0x3); 
   if (inp(baseaddr+3)!=0x3) return 0; 
   // next thing to do is look for the scratch register 
   outp(baseaddr+7,0x55); 
   if (inp(baseaddr+7)!=0x55) return 1; 
   outp(baseaddr+7,0xAA); 
   if (inp(baseaddr+7)!=0xAA) return 1; 
   // 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 AFD service 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 AFD service with the subject "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)==0);  // 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(0x20,0x0A);     // next read access to 0x20 reads out IRR 
  outp(0xA0,0x0A);     // next read access to 0xA0 reads out IRR 
  outp(base+1,2);      // lets 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(0x21,~maskm);   // now let us unmask this interrupt only 
  outp(0xA1,~masks); 
  outp(0x20,0x0C);     // enter polled mode 
  outp(0xA0,0x0C); 
  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 al,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 null-modem 
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. 
 
You 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 
  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: 
  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 
  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: 
  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 assembler 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 "Automatic File Delivery" service (it's 
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. Using a 16550A is strongly recommended 
at high rates (turn on FIFOs). 
 
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. 
 
Another last thing: due to the poor construction of the PC interrupt 
system, one interrupt line can only be driven by one device. This means if 
you want to use COM3 and your mouse is connected to COM1, you can't use 
interrupt features without disabling the mouse (write 0x0 to the mouse's 
MCR). 
 
There is a way to avoid this (but only with your own software, NOT WITH 
MOUSE DRIVERS!): cut the line between the card edge-connector and the 
interrupt line driver and solder in a diode (1N4148 will do, slow high-power 
diodes don't!), cathode to edge. Then add a resistor (say, 2.2k) between the 
interrupt line at the card edge and ground. Doing this on every serial card 
makes it possible to connect two or more serial ports to one interrupt line. 
(Multi-port serial adapters have a similar circuitry). If your iron is heated 
anyway, it's a good idea to AND the interrupt signal with BAUDOUT divided by 
4 (this makes the serial interrupt 'level triggered'). You won't encounter 
interrupt hang-ups any more! It's like someone putting his/her finger on your 
doorbell... you definitely WILL OPEN :-). But the ICU can get confused if 
it is triggered more than about half a million times a second, so the 
divider is necessary (BAUDOUT = 16 times the bps rate). 
 
 
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! 
 
 
-- 
Chris Blum  -  finger chris@pfsparc02.phil15.uni-sb.de for details 
 
