THE SERIAL PORT, RELEASE 13

The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

                          THE SERIAL PORT, RELEASE 13
                                       
   Contents:
   
     * About the Author & Introduction 
     * Changes since the last publication 
     * What I'm doing 
     * What others are doing 
     * No more "Automatic File Delivery" (AFD) service 
     * Acknowledgements (quite a bunch of people by now...) 
     * FAQ Introduction 
     * Historical summary 
     * The TTY (teletyping) protocol 
     * The physical transmission 
     * Hardware 
     * The connectors 
     * Other asynchronous hardware than RS-232C 
     * Connecting devices (or computers) 
     * Base addresses & interrupts 
     * Logical vs. physical ports 
     * The chipsets 
     * How to detect which chip is used 
     * Data sheet information 
     * Known problems with several chips 
     * Registers 
     * Excursion Why and how to use the FIFOs (by Scott C. Sadow) 
     * Multi-Port Serial Adapters 
     * Some words about timing 
     * Handshaking 
     * BIOS API (Application Programs Interface) 
     * Mice 
     * Modems 
     * Encoding schemes 
     * Hayes commands 
     * Microcom commands 
     * IRQ sharing - can it be done? (this applies to ISA bus systems
       only) 
     * Programming 
       
   
   
     _________________________________________________________________
   
   
   
                       ABOUT THE AUTHOR & INTRODUCTION
                                       
   Date of release: 22 Feb 1995
   Release 18
   
   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:
   [email protected] or [email protected]. See the end for details.
   
   It's the eighteenth publication of this file. Some errors have been
   corrected and some information has been added (which has surely
   brought other errors with it, see Murphy's Law).
   
   [] brackets often indicate comments to sneaked material; copied lines
   are indented. I've made great efforts to always mention who's to be
   credited. Please tell me if you find something that you've written
   that's not correctly associated with your name.
   
   This compilation of information is (C) Copyright 1993 - 1995 by
   Christian Blum; all rights reserved. This file is not to be reproduced
   commercially, not even partially, without written permission. You are
   allowed to use it in any other way you like. I don't want any
   (monetary) profit being drawn out of it (neither by me nor by others!
   I don't mind if you have a look or two at it at work though... :-).
   Please feel free to provide this file to others for free or at your
   own expenses.
   
   
     _________________________________________________________________
   
   
   
                      CHANGES SINCE THE LAST PUBLICATION
                                       
   Used more proper interrupt acknowledging in the examples (namely the
   method I suggested some chapters before :) in order to avoid lock-ups
   on MCA computers. (Thanks, Erik)
   
   Added some info on the auto flow-control feature of the TL16C550C.
   (Thanks, naddy)
   
   
     _________________________________________________________________
   
   
   
                                WHAT I'M DOING
                                       
   I'm no longer very much into DOS (though I still make some money with
   it :), so don't expect me reading all the groups regularly that I'm
   posting this to.
   
   
     _________________________________________________________________
   
   
   
                            WHAT OTHERS ARE DOING
                                       
   There is a file available from ftp.phil.uni-sb.de, pub/staff/chris
   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 up to date.
   
   
     _________________________________________________________________
   
   
   
               NO MORE "AUTOMATIC FILE DELIVERY" (AFD) SERVICE
                                       
   The automatic mail reply service I've mentioned in earlier releases
   of this file is still available, but I won't do anything to keep it
   alive from now on. Please obtain the files via anonymous ftp from
   ftp.phil.uni-sb.de (134.96.80.220), pub/staff/chris.
   
   If you don't have access to ftp, try using an ftp to email gateway
   ([email protected] or [email protected], and probably a
   lot more; put 'help' in the body to obtain instructions). If
   everything fails, write to me.
   
   
     _________________________________________________________________
   
   
   
             ACKNOWLEDGEMENTS (QUITE A BUNCH OF PEOPLE BY NOW...)
                                       
   The following persons have contributed (directly or indirectly :-) to
   this summary by providing information or making suggestions/reporting
   errors. Tell me if your name is missing.
   
   Thanks to: Madis Kaal <[email protected]>, Steve Poulsen
   <[email protected]>, Scott C. Sadow <[email protected]>, Dan Norstedt
   <?>, Alan J. Brumbaugh <[email protected]>, Mike Surikov
   <[email protected]>, Varol Kaptan
   <E66964%[email protected]>, Richard F. Drushel
   <[email protected]>, John A. Limpert <[email protected]>, Brent
   Beach <[email protected]>, Torbjoern (sp?) Lindgren
   <[email protected]>, Stephen Warner <[email protected]>,
   Kristian Koehntopp <[email protected]>, Angelo Haritsis
   <[email protected]>, Jim Graham <[email protected]>, Ralf Brown
   <[email protected]>, Alfred Arnold <[email protected]>,
   Andrew M. Langmead <[email protected]>, Richard Clayton
   <[email protected]>, Christof Baumgaertner
   <[email protected]>, Goran Bostrom <[email protected]>, Brian Mork
   <[email protected]>, Richard Steven Walz <[email protected]>,
   Scott David Daniels <[email protected]>, Brian Onn
   <[email protected]>, Erik Suurmaa <[email protected]>, Terence
   Edwards <[email protected]>, and Christian 'naddy'
   Weisgerber <[email protected]>
   
   
     _________________________________________________________________
   
   
   
                               FAQ INTRODUCTION
                                       
   One of the most universal parts of the PC (except for the CPU, of
   course :-) is its serial port. You can connect a mouse, a modem, a
   printer, a plotter, another PC, dongles :) ...
   
   But its usage (both software and hardware) is one of the best-kept
   secrets for most users, besides that it is not difficult to understand
   how to connect (not plug in) devices to it and how to program it.
   
   Regard this file as a manual for the serial port of your PC for both
   hardware and software.
   
   
     _________________________________________________________________
   
   
   
                              HISTORICAL SUMMARY
                                       
   In early days of telecommunication, errand-boys and optical signals
   (flags, lights, clouds of smoke) were the only methods of transmitting
   information across long distances. With increasing requirements on
   speed and growing amount of information, more practical methods were
   developed. One milestone was the first wire-bound transmission on May
   24th, 1844 ("What hath God wrought", using the famous Morse alphabet).
   Well, technology improved a bit, and soon there were machines that
   could be used like typewriters, except that you typed not only on your
   own sheet of paper but also on somebody elses. The only thing that has
   changed on the step from the teletype to your PC regarding serial
   communications is speed.
   
   
     _________________________________________________________________
   
   
   
                        THE TTY (TELETYPING) PROTOCOL
                                       
   Definition: A protocol is a clear description of the LOGICAL method
   of transmitting information. This does NOT include physical
   realization.
   
   There is a difference between bits per second and baud (named after J.
   M. E. Baudot, one of those guys who gave a real push to teletyping):
   'baud' means 'state changes of the line per second' while 'bits per
   second' ... well, bits per second means bits per second. You may find
   this a bit weird because the numbers are often the same; there's only
   a difference if the line has more than two states. Since this is not
   the case with the RS-232C (EIA-232) port of your PC, most people don't
   differentiate between 'baud' and 'bits per second', while I do. For
   your convenience, I've replaced baud with bps even in copied material
   without special notice. Where you still find baud, it should read bps
   in most cases (I didn't change labels in source codes, pin names in
   data sheet information etc.). To illustrate the difference I give you
   some figures: 2400 bps at 8n1 carry 1920 bits of information per
   second, and modems send them at 600 baud thru' the phone wires using
   eight line states, while 1200 bps at 7e1 carry 840 bits of information
   per second that modems send at 600 baud using four different line
   states. I know it's confusing... that's why I quote this from a letter
   I received from Brent Beach. He explained it more clearly than I did
   (I've added some information):
   
   Perhaps a small diagram might help, showing the relationship among the
   players:
   

                                    [bps]             [baud]
     CPU Data              Serial                               Phone
     Bus      -- bytes --> Port  -- bits --> Modem -- tones --> line --
                                                                      |
                                                                      |
     CPU Data              Serial                                     |
     Bus      <-- bytes -- Port  <-- bits -- Modem <-- tones ----------
                   (1)               (2)               (3)

   
   The serial port accepts bytes from the CPU data bus and passes bits to
   the modem. In doing this, the serial port can add or delete bits,
   depending on the coding scheme in use.
   
   At (1) we are concerned with bytes per second. At (2) we are concerned
   with bits per second, and at (3) it's baud. We distinguish because the
   number of bits at (2) need not be equal to the number of bits (that
   is, bytes times 8) at (1), and the number of state changes at (3) is
   not necessarily the same as the number of bits before.
   
   Bits can be stripped going from (1) to (2): the serial port may
   transmit only 6 or 7 of the 8 bits in the byte. Bits can be added
   going from (1) to (2): the serial port can add a parity bit and stop
   bits. From (2) to (3), bits may be clustered to groups that are
   transmitted using different encoding schemes like 'Frequency Shift
   Keying' or 'Quadrature Amplitude Modulation', to name some.
   
   You can determine the transfer rate in bytes per second depending on
   the serial port speed and the coding system. For example,
   
   8n1
          1 start bit + 8 data bits + 1 stop bit = 10 bits per word. At
          2400 bps, this is 240 bytes/characters per second. 2400 bps are
          normally transmitted using QAM ('Quadrature Amplitude
          Modulation') where 4 bits are clustered, and hence encoded to
          600 baud.
          
   7e1
          1 start bit, 7 data bits, 1 even parity bit, 1 stop bit = 10
          bits per word. At 1200 bps, this is 120 bytes/characters per
          second. 1200 bps are encoded using DPSK ('Differential Phase
          Shift Keying', two bits are clustered), and this results again
          in 600 baud.
          
   
   Now let's leave modems for a while and have a look at the serial port
   itself.
   
   The TTY protocol uses two different line states called 'mark' and
   'space'. (For the sake of clearness I name the line states 'high'
   (voltage) for positive and 'low' (voltage) for negative voltages). If
   no data is transmitted, the line is in its quiescent 'low' ('mark')
   state or in the 'break' state ('high'). Data looks like:
   

   space            +---+       +---+   +---+         high  '0' +12V
                    |   |       |   |   |   |
   mark   ----------+   +-------+   +---+   +-------  low   '1' -12V

                     (1)  --------(2)-------- (3)

  (1) start bit   (2) data bits   (3) stop bit(s)

   
   Steve Walz reported that in most (all?) books these kind of diagrams
   are drawn the other way round (I just copied what I saw on the
   oscilloscope) and that he'd use the labels 'high' and 'low' the other
   way round, corresponding to the signals on the TTL level (a matter of
   taste I guess); here is what he told me:
   
   In American texts, we will expect to see the data frame for serial
   transfer of all kinds represented, despite the method of transfer
   (RS-232C, RS-422, and optical even), as being an interruption of a
   normally HI state, and we expect to see the diagram you drew in the
   older release 8, but with the labelling corrected as I have indicated:
   

         mark  ----------+   +-------+   +---+   +-------  high  '1' -12V
             logical  1  | S | 1   1 | 0 | 1 | 0 | Stop
        space            +---+       +---+   +---+         low   '0' +12V
                          (1) --------(2)---------(3)
                 (1) start bit   (2) data bits   (3) stop bit(s)

   
   Thus transmitting the bit stream 01011, which is LSB first, MSB last.
   
   Indeed it seems to us that a zero SHOULD be the quiescent state, and
   the one an active state, but the first teletypes used a current loop
   to continuously monitor the state of the line, and thus current flow
   was regarded as a 1 and it is "MARK" -ing time, and a signal then left
   a "SPACE" in the graph of current flow designating a zero. Thus the
   bits following the start bit at level zero were true to their bit
   values, and a 11111 in 5 bit baudot looked like this, using three
   dashes per bit:
   

   mark   ------   ------------------------ 1  HI  +5V TTL  -12V RS-232C
  space         ---                         0  LO   0V TTL  +12V RS-232C
                 s  1  1  1  1  1  stop

   
   and the baudot 10101 would appear thus:
   

   mark   ------   ---   ---   ------------ 1  HI  +5V TTL  -12V RS-232C
  space         ---   ---   ---             0  LO   0V TTL  +12V RS-232C
                 s  1  0  1  0  1  stop

   
   and the baudot 01010 would appear thus:
   

   mark   ------      ---   ---   --------- 1  HI  +5V TTL  -12V RS-232C
  space         ------   ---   ---          0  LO   0V TTL  +12V RS-232C
                 s  0  1  0  1  0  stop

   
   and finally baudot 00000 would appear:
   

   mark   ------                  --------- 1  HI  +5V TTL  -12V RS-232C
  space         ------------------          0  LO   0V TTL  +12V RS-232C
                 s  0  0  0  0  0  stop

   
   Now I know that we don't send five bit baudot over RS-232C now, but I
   wasn't about to try 8 bits, if you don't mind! :)
   
   I know that people get confused about the proper way to draw these,
   since we use inverted voltages to send them via RS-232C interface now,
   but they are still called logical "1" and "mark" when it is really -12
   Volts DC, and it is called "0" and "space" when it is +12 Volts. And
   logical one or "mark" corresponds to +5 Volts, while logical zero is
   "space" and corresponds to 0 Volts. It is this way both within the
   parallel bus of the computer or the transmit output of a UART/USART,
   with the exception that this data frame is terminated by remaining
   logic "1" or "mark" as a stop bit and preface to the next data frame.
   
   Both transmitter (TX) and receiver (RX) use the same data rate
   (measured in bps, see above), which is the reciprocal value of the
   smallest time interval between two changes of the line state. TX and
   RX know about the number of data bits (probably with a parity bit
   added), and both know about the (minimum!) size of the stop step
   (called the stop bit or the stop bits, depending on the size of the
   stop step; normally 1, 1.5 or 2 times the size of a data bit). Data is
   transmitted bit-synchronously and word-asynchronously, which means
   that the size of the bits, the length of the words etc.pp. is clearly
   defined while the time between two words is undefined.
   
   The start bit indicates the beginning of a new data word (this means
   one single character). It is used to synchronize transmitter and
   receiver and is always a logical '0' (so the line goes 'high' or
   'space').
   
   Data is transmitted LSB to MSB, which means that the least significant
   bit (LSB, Bit 0) is transmitted first with 4 to 7 bits of data
   following, resulting in 5 to 8 bits of data. A logical '0' is
   transmitted by the 'space' state of the line (+12V), a logical '1' by
   'mark' (-12V).
   
   A parity bit can be added to the data bits to allow error detection.
   There are two (well, actually five) kinds of parity: odd and even
   (plus none, mark and space). Odd parity means that the number of 'low'
   or 'mark' steps in the data word (including an optional parity bit,
   but not the framing bits) is always odd, so the parity bit is set
   accordingly (I don't have to explain 'even' parity, must I?). It is
   also possible to set the parity bit to a fixed state or to omit it.
   See Registers section for details on types of parity.
   
   The stop bit does not indicate the end of the word (as it could be
   derived from its name); it rather separates two consecutive words by
   putting the line into the quiescent state for a minimum time (that
   means the stop bit is a logical '1' or 'mark') in order for the next
   start bit to be clearly visible.
   
   The framing protocol is usually described by a sequence of numbers and
   letters, eg. 8n1 means 1 start bit (always the same, thus omitted), 8
   bits of data, no parity bit, 1 stop bit. 7e2 would indicate 7 bits of
   data, even parity, 2 stop bits (but I've never seen this one...). The
   usual thing is 8n1 or 7e1.
   
   Your PC is capable of serial transmission at up to 115,200 bps (step
   size of 8.68 microseconds!). Typical rates are 300 bps, 1200 bps, 2400
   bps and 9600 bps, with 19200 bps, 38400 bps and 57600 bps becoming
   more and more popular with high speed modems. Note that some serial
   ports have difficulties with high speeds! I've seen PS/2's failing to
   operate at more than 38400 bps! How come that IBM machines are often
   the least IBM compatible? :-)
   
   This is what John A. Limpert told me about teletypes:
   
   
          
          Real (mechanical) teletypes used 1 start bit, 5 data bits and
          1.42 stop bits. Support for 1.5 stop bits in UARTs was a
          compromise to make the UART timing simpler. Normal speeds were
          60 WPM (word per minute), 66 WPM, 75 WPM and 100 WPM. A word
          was defined as 6.1 characters. The odd stop bit size was a
          result of the mechanical nature of the machine. It was the time
          that the printer needed to finish the current character and get
          ready for the next character. Most teletypes used a 60 mA loop
          with a 130 V battery. 20 mA loops and lower battery voltages
          became common when 8 level ASCII teletypes were introduced. The
          typical ASCII teletype ran at 110 bps with 2 stop bits (11 bits
          per character).
          
   
   It's surely more exact than what I wrote in previous releases. I've
   just got to add that at least in Germany 50 bps was a familiar speed.
   And I think the lower battery voltage he's talking about was 24 volts.
   
   
     _________________________________________________________________
   
   
   
                          THE PHYSICAL TRANSMISSION
                                       
   Teletypes used a closed-loop line with a quiescent current of 20ma
   and a space current of 0ma (typically), which allows to detect a
   'broken line' (hence the name of the 'break' flag, see the Registers
   section). The RS-232C port of your PC uses voltages rather than
   currents to indicate logical states: 'mark'/'low' is signaled by -3v
   to -15v (typically -12V) and represents a logical '1', 'space'/'high'
   is signaled by +3v to +15v (typically +12V) and represents a logical
   '0'. The typical output impedance of the serial port of a PC is 2
   kiloohms (resulting in about 5ma @ 10v), the typical input impedance
   is about 4.3 kiloohms, so there should be a maximum fan-out of 5 (5
   inputs can be connected to 1 output). Please don't rely on this, it
   may differ from PC to PC.
   
   Three lines (RX, TX & ground) are at least needed to make up a
   bidirectional connection.
   
   Q. Why does my PC have a 25pin/9pin connector if there are only 3
   lines needed?
   A. There are several status lines that are only used with modems etc.
   See the Hardware section and the Registers section of this file.
   
   Q. How can I easily connect two PCs by a three-wire lead?
   A. Connect RX1 to TX2 and vice versa, GND1 to GND2. In addition to
   this, connect RTS to CTS & DCD and connect DTR to DSR at each end
   (modem software often relies on that). See the hardware section for
   further details.
   
   Please be aware that at 115,200 bps (ie. ca. 115 kHz, but we need the
   harmonics up to at least 806 kHz) lines can no longer be regarded as
   'ideal' transmission lines. They are low-pass filters and tend to
   reflect and mutilate the signals, but some ten meters of twisted wire
   should always be OK (I use 3m of screened audio cable for file
   transfer purposes, and it works fine. Not that other kinds of wire
   wouldn't do; I took what I found). See a good book on transmission
   lines if you're interested in why long lines can be a problem.
   
   This has been posted to comp.os.msdos.programmer by Andrew M.
   Langmead:
   
   The RS-232C spec. has an official limit of 50 ft for RS-232C cables.
   Realistically they can be much longer. The book "Managing UUCP and
   Usenet" by O'Reilly and Associates has a table that they credit to
   "Technical Aspects of Data Communications", by McNamara (Digital
   Press, 1992). It lists the maximum distances for an RS-232C
   connection.
   

  Baud Rate       | max distance      | max distance
                  | shielded cable    | unshielded cable
  ----------------------------------------------------------
        110       |      5000ft       |     3000ft
        300       |      5000ft       |     3000ft
        1200      |      3000ft       |     3000ft
        2400      |      1000ft       |     500ft
        4800      |      1000ft       |     250ft
        9600      |      250ft        |     250ft

   
   Please note that "baud" is correct in this case, because we're
   speaking of the transmission line itself.
   
   This is what Torbjoern (sp?) Lindgren told me:
   
          I have successfully transmitted at 115,200 with over 30m long
          cables! And it wasn't especially good wires. I had some old
          telecables with 20 individual wires, and used 7 of them for
          transfer, and left the others unconnected.
          
          I don't remember the exact length, but I know it was something
          over 30m, and it probably was closer to 40m than 30m. The
          unused lines probably shielded the lines from each other or
          something like that. The computers used were two PC-compatibles
          with off-the-shelf com-ports. Nothing fancy.
          
   
   Note that some serial ports are more critical with mutilated signals
   than others, so you just have to try and find out yourself what works.
   
   
     _________________________________________________________________
   
   
   
                                   HARDWARE
                                       
   
     _________________________________________________________________
   
   
   
                                THE CONNECTORS
                                       
   PCs have 9pin/25pin male SUB-D connectors. The pin layout is as
   follows (seen from outside your PC):
   

        1                         13         1         5
      _______________________________      _______________
      \  . . . . . . . . . . . . .  /      \  . . . . .  /
       \  . . . . . . . . . . . .  /        \  . . . .  /
        ---------------------------          -----------
        14                       25           6       9

 Name (V24)  25pin  9pin  Dir  Full name               Remarks
--------------------------------------------------------------------------
    TxD         2     3    o   Transmit Data           Data
    RxD         3     2    i   Receive Data            Data
    RTS         4     7    o   Request To Send         Handshaking
    CTS         5     8    i   Clear To Send           Handshaking
    DTR        20     4    o   Data Terminal Ready     Status
    DSR         6     6    i   Data Set Ready          Status
    RI         22     9    i   Ring Indicator          Status
    DCD         8     1    i   Data Carrier Detect     Status
    GND         7     5    -   Signal ground           Reference level
     -          1     -    -   Protective ground       Don't use this one
                                                       as signal ground!

   
   The most important lines are RxD, TxD, and GND. Others are used with
   modems, printers and plotters to indicate internal states.
   
   '1' ('mark', 'low') means -3v to -15v, '0' ('space', 'high') means +3v
   to +15v. On status lines, 'high' is the active state: status lines go
   to the positive voltage level to signal events.
   
   The lines are:
   
   RxD, TxD
          These lines carry the data; 1 is transmitted as 'mark' (what I
          call 'low') and 0 is transmitted as 'space' ('high').
          
   RTS, CTS
          Are used by the PC and the modem/printer/whatsoever (further on
          referred to as the data set, or DCE) to start/stop a
          communication. The PC sets RTS to 'high', and the data set
          responds with CTS 'high'. (always in this order). If the data
          set wants to stop/interrupt the communication (eg. imminent
          buffer overflow), it drops CTS to 'low'; the PC uses RTS to
          control the data flow.
          
   DTR, DSR
          Are used to establish a connection at the very beginning, ie.
          the PC and the data set 'shake hands' first to assure they are
          both present. The PC sets DTR to 'high', and the data set
          answers with DSR 'high'. Modems often indicate hang-up by
          resetting DSR to 'low' (and sometimes are hung up by dropping
          DTR).
          
          (These six lines plus GND are often referred to as '7
          wire'-connection or 'hand shake'-connection.)
          
   DCD
          The modem uses this line to indicate that it has detected the
          carrier of the modem on the other side of the phone line. The
          signal is rarely used by the software.
          
   RI
          The modem uses this line to signal that 'the phone rings' (even
          if there is neither a bell fitted to your modem nor a phone
          connected :-).
          
   GND
          The 'signal ground', ie. the reference level for all signals.
          
   Protective ground
          This line is connected to the power ground of the serial
          adapter. It should not be used as a signal ground, and it MUST
          NOT be connected to GND (even if your DMM [Digital MultiMeter]
          shows up an ohmic connection!). Connect this line to the screen
          of the lead (if there is one). Connecting protective ground on
          both sides makes sure that no large currents flow thru' GND in
          case of an insulation defect on one side (hence the name).
          
   
   Technical data (typical values for PCs):
   

  Signal level: -10.5v/+11v
  Short circuit current: 6.8ma
  Output impedance: ca 2 kiloohms (non-linear!)
  Input impedance: ca 4.3 kiloohms (non-linear!)

   
     _________________________________________________________________
   
   
   
                   OTHER ASYNCHRONOUS HARDWARE THAN RS-232C
                                       
   There are several other standards that use the same chipset and
   protocol as RS-232C. RS-422 and the more robust (but compatible)
   version RS-485 (to name some) use two wires for every signal. The
   transmitters can usually be disabled and enabled by software, which
   makes it possible to use such equipment in a bus system (RX and TX
   part share the same lines). Despite from the possibility to enable and
   disable the receiver/transmitter section of the port, they are fully
   compatible to existing RS-232C software if a compatible chipset is
   used.
   
   It's not possible to connect eg. RS-232C to RS-485 without an
   appropriate interface.
   
   
     _________________________________________________________________
   
   
   
                      CONNECTING DEVICES (OR COMPUTERS)
                                       
   When you connect a data set or DCE (eg. a modem), use this
   connection:
   

        GND1    to    GND2
        RxD1    to    RxD2
        TxD1    to    TxD2
        DTR1    to    DTR2
        DSR1    to    DSR2
        RTS1    to    RTS2
        CTS1    to    CTS2
        RI1     to    RI2
        DCD1    to    DCD2

   
   In other words, simply connect each pin of the first plug with the
   corresponding pin of the other. This can easily be done using a
   25-wire ribbon cable and two crimp connectors.
   
   When you connect another computer (or any other DTE, like a terminal),
   this is the wiring you need (it is called a "null modem" connection):
   

        GND1    to    GND2
        RxD1    to    TxD2
        TxD1    to    RxD2
        DTR1    to    DSR2
        DSR1    to    DTR2
        RTS1    to    CTS2
        CTS1    to    RTS2

   
   If software wants it, connect DCD1 to CTS1 and DCD2 to CTS2.
   
   If hardware handshaking is not needed, you can omit the status lines.
   Connect:
   

        GND1    to    GND2
        RxD1    to    TxD2
        TxD1    to    RxD2

   
   Additionally, connect (if software needs it):
   

        RTS1    to    CTS1 & DCD1
        RTS2    to    CTS2 & DCD2
        DTR1    to    DSR1
        DTR2    to    DSR2

   
   You won't need long wires for these! :-)
   
   Remember: the names DTR, DSR, CTS & RTS refer to the lines as seen
   from the DTE (your PC). This means that for your data set DTR & RTS
   are incoming signals and DSR & CTS are outputs! Modems, printers,
   plotters etc. are connected 1:1, ie. pin x to pin x.
   
   
     _________________________________________________________________
   
   
   
                         BASE ADDRESSES & INTERRUPTS
                                       
   Normally, the following list is correct for your PC; note however
   that if the BIOS can't find a port, it won't leave spaces in its port
   table, so if there is no UART at 0x3E8, the port at 0x2E8 will be
   called COM3 by DOS. Compare the section on logical vs. phyical names.
   

  Port Name   Base address    Int #  Int level (IRQ)

    COM1         0x3F8        0xC      4
    COM2         0x2F8        0xB      3
    COM3         0x3E8        0xC      4
    COM4         0x2E8        0xB      3

   
   In your programs, you should refer to the table in the BIOS data
   segment. This is an excerpt from Ralf Brown's interrupt list (the
   actual author of this section is Robin Walker):
   

  Format of BIOS Data Segment at segment 40h:
  Offset Size Description
   00h WORD Base I/O address of 1st serial I/O port, zero if none
   02h WORD Base I/O address of 2nd serial I/O port, zero if none
   04h WORD Base I/O address of 3rd serial I/O port, zero if none
   06h WORD Base I/O address of 4th serial I/O port, zero if none
        Note: Above fields filled in turn by POST as it finds serial
        ports. POST never leaves gaps. DOS and BIOS serial device
        numbers may be redefined by re-assigning these fields.

   
   Please note that this table is not the bible and that the BIOS is not
   an evangelist (and I'm rather sceptical anyway :-). Your BIOS might
   not tell you the pure truth; if you get a zero it does not necessarily
   mean that there are no more serial ports available. Your programs
   should nevertheless have a look at the usual places for comm ports.
   See the "Programming" section for an example program that checks if a
   UART is installed at a given base address. Compare the "logical vs.
   physical names" section below.
   
   Another good idea is writing a small program that's then run in the
   AUTOEXEC.BAT and that fills the empty fields in the table with the
   correct values. My Award BIOS fails to recognize my fourth port at
   0x2E8, so I typed a few bytes (14 altogether) in the debugger that
   write 0x2E8 to 0040:0006 and wrote them to a .COM file called in the
   AUTOEXEC.BAT.
   
   Also see the Programming section for a routine that detects the
   interrupt level/number that a UART uses. It's not a good idea to
   hard-code level 4 and 3; make it at least user configurable.
   
   See the chapter "Multi-Port Serial Adapters" for further information.
   
   
     _________________________________________________________________
   
   
   
                          LOGICAL VS. PHYSICAL PORTS
                                       
   DOS users (like card manufacturers) tend to confuse logical and
   physical names. COM1, COM2, etc. are logical names for the serial
   ports 0, 1, etc. found by the BIOS during POST (Power-On Self Test).
   The BIOS searches at four different I/O addresses for UARTS: 0x3F8,
   0x2F8, 0x3E8, 0x2E8, in exactly this order. Every UART found has an
   entry in the comm port table at segment 0x40, offset 0. The BIOS
   manages up to four different UARTs, because the table has no more than
   four spaces. To make the confusion complete, Microsoft decided that
   DOS users wouldn't be comfortable with counting from zero, so they
   numbered the logical names of the comm ports from 1 to 4. Thus COM1 is
   the first UART found by the BIOS during POST, COM2 the second, and so
   on. Usually COM1 has 0x3F8 as base addresses, COM2 0x2F8 and so on,
   but that's not necessarily the case. Please do not use the logical DOS
   names when you really mean physical addresses. It is not possible to
   'jumper a UART as COM3', at least not directly.
   
   
     _________________________________________________________________
   
   
   
                                 THE CHIPSETS
                                       
   In PCs, serial communication is realized with a set of three chips
   (there are no further components needed! (I know of the need of
   address logic & interrupt logic ;-) )): a UART (Universal Asynchronous
   Receiver/Transmitter) and two line drivers. Normally, the
   82450/16450/8250 does the 'brain work' while the 1488 and 1489 drive
   the lines (they are level shifting inverters; the 1488 drives the
   outputs).
   
   These chips are produced by many manufacturers; it's of no importance
   which letters are printed in front of the numbers (mostly NS for
   National Semiconductor). Don't regard the letters behind the number
   also (if it's not the 16550A or the 82C50A); they just indicate
   special features and packaging (Advanced, New, MILitary, bug fixes
   [see below] etc.) or classification. Letters in between the numbers
   (eg. 16C450) indicate technology (C=CMOS).
   
   You might have heard that it is possible to replace the 16450 by a
   16550A to improve reliability and reduce software overhead. This is
   only useful if your software is able to use the FIFO (first in-first
   out) buffer feature. The chips are fully pin-compatible except for two
   pins that are not used by any serial adapter card known to the author:
   pin 24 (CSOUT, chip select out) and pin 29 (NC, no internal
   connection). With the 16550A, pin 24 is -TXRDY and pin 29 is -RXRDY,
   signals that aren't needed (except for DMA access - but not in the PC)
   and that even won't care if they are shorted to +5V or ground.
   Therefore it should always be possible to simply replace the 16450 by
   the 16550A - even if it's not always useful due to lacking software
   capabilities. IT IS DEFINITELY NOT NECESSARY FOR COMMUNICATION AT UP
   TO LOUSY 9600 BPS! These rates can easily be handled by any CPU, and
   the interrupt-driven communication won't slow down the computer
   substantially. But if you want to use high-speed transfer with or
   without using the interrupt features (ie. by 'polling'), or
   multitasking, or multiple channels 'firing' at the same time, or disk
   I/O during transmission, it is recommendable to use the 16550A in
   order to make transmission more reliable if your software supports it
   (see excursion some pages below).
   
   There *are* differences between the 16550A, 16550AF, and 16550AFN. The
   16550AF has one more timing parameter (t_RXI) specified that's
   concerned with the -RXRDY pin and that's of no importance in the PC.
   And the 16550AFN is the only one still believed to be free of bugs
   (see below). So the best choice for your PC is 16550AFN, but you are
   well off with the 16550AN, too. [Info from a posting of Jim Graham.]
   
   Don't worry about the missing 'A' if you have chips named xxx16550
   which are not from National Semiconductor (eg. UM16550). As long as
   the first example in the 'Programming' section tells you that it is a
   16550A, everything is fine. I've never heard of non-NS 16550s with the
   FIFO bug (see below).
   
   
     _________________________________________________________________
   
   
   
                       HOW TO DETECT WHICH CHIP IS USED
                                       
   This is really not difficult. The 8250 normally has no scratch
   register (see data sheet info below), the 16450/82450 has no FIFO, the
   16550 has no working FIFO :-) and the 16550A performs alright. See the
   Programming section for an example program that detects which one is
   used in your PC.
   
   Note that there _are_ versions of the 8250 that _do_ have a scratch
   register! It's rather impossible to distinguish them from the 16450,
   but then it's not necessary either... I know of the SAB 82C50 from
   Siemens and the UM8250B (from UMC, a taiwanese company with a globe
   symbol; thanks, Alfred, for helping me out with that). You won't find
   8250s in fast computers however, because their bus timing is too slow.
   
   
     _________________________________________________________________
   
   
   
                            DATA SHEET INFORMATION
                                       
   Some hardware information taken from the data sheet of National
   Semiconductor (shortened and commented):
   
   Pin description of the 16450 (16550A) [Dual-In-Line package]:
   

                   +-----+ +-----+
               D0 -|  1  +-+   40|- VCC
               D1 -|  2        39|- -RI
               D2 -|  3        38|- -DCD
               D3 -|  4        37|- -DSR
               D4 -|  5        36|- -CTS
               D5 -|  6        35|- MR
               D6 -|  7        34|- -OUT1
               D7 -|  8        33|- -DTR
             RCLK -|  9        32|- -RTS
              SIN -| 10        31|- -OUT2
             SOUT -| 11        30|- INTR
              CS0 -| 12        29|- NC (-RXRDY)
              CS1 -| 13        28|- A0
             -CS2 -| 14        27|- A1
         -BAUDOUT -| 15        26|- A2
              XIN -| 16        25|- -ADS
             XOUT -| 17        24|- CSOUT (-TXRDY)
              -WR -| 18        23|- DDIS
               WR -| 19        22|- RD
              VSS -| 20        21|- -RD
                   +-------------+

   
   Note: The status signals are negated compared to the port! If you
   write a '1' to the appropriate register bit, the pin goes 'low' (to
   ground level). On its way to the port, the signal is inverted again;
   this means that the status line at the port goes 'high' if you write a
   '1'. The same is true for inputs: you get a '1' from the register bit
   if the line at the port is 'high'. SIN and SOUT are inverted, too.
   (negative voltage at the port means +5v at the UART).
   
   A0, A1, A2, Register Select, Pins 26-28: Address signals connected to
   these 3 inputs select a UART register for the CPU to read from or to
   write to during data transfer. A table of registers and their
   addresses is shown below. Note that the state of the Divisor Latch
   Access Bit (DLAB), which is the most significant bit of the Line
   Control Register, affects the selection of certain UART registers. The
   DLAB must be set high by the system software to access the Baud
   Generator Divisor Latches. [I'm sorry, but it's called that way even
   if it's a bps rate generator... :-)]. 'x' means don't care.
   

  DLAB  A2  A1  A0    Register
    0    0   0   0    Receive Buffer (read) Transmitter Holding Reg. (write)
    0    0   0   1    Interrupt Enable
    x    0   1   0    Interrupt Identification (read)
    x    0   1   0    FIFO Control (write) [undefined with the 16450. CB]
    x    0   1   1    Line Control
    x    1   0   0    Modem Control
    x    1   0   1    Line Status
    x    1   1   0    Modem Status
    x    1   1   1    Scratch [special use on some boards. CB]
    1    0   0   0    Divisor Latch (LSB)
    1    0   0   1    Divisor Latch (MSB)

   
   -ADS
          Address Strobe, Pin 25: The positive edge of an active Address
          Strobe (-ADS) signal latches the Register Select (A0, A1, A2)
          and Chip Select (CS0, CS1, -CS2) signals. Note: An active -ADS
          input is required when Register Select and Chip Select signals
          are not stable for the duration of a read or write operation.
          If not required, tie the -ADS input permanently low. [As it is
          done in your PC. CB]
          
   -BAUDOUT
          Baud Out, Pin 15: This is the 16x clock signal from the
          transmitter section of the UART. The clock rate is equal to the
          main reference oscillator frequency divided by the specified
          divisor in the Baud Generator Divisor Latches. The -BAUDOUT may
          also be used for the receiver section by tying this output to
          the RCLK input of the chip. [Yep, that's true for your PC. CB].
          
   CS0, CS1, -CS2
          Chip Select, Pins 12-14: When CS0 and CS1 are high and CS2 is
          low, the chip is selected. This enables communication between
          the UART and the CPU.
          
   -CTS
          Clear To Send, Pin 36: When low, this indicates that the modem
          or data set is ready to exchange data. This signal can be
          tested by reading bit 4 of the MSR. Bit 4 is the complement of
          this signal, and Bit 0 is '1' if -CTS has changed state since
          the previous reading (bit0=1 generates an interrupt if the
          modem status interrupt has been enabled).
          
   D0-D7
          Data Bus, Pins 1-8: Connected to the data bus of the CPU.
          
   -DCD
          Data Carrier Detect, Pin 38: blah blah blah, can be tested by
          reading bit 7 / bit 3 of the MSR. Same text as -CTS.
          
   DDIS
          Driver Disable, Pin 23: This goes low whenever the CPU is
          reading data from the UART. It can be used to control bus
          arbitrary logic.
          
   -DSR
          Data Set Ready, Pin 37: blah, blah, blah, bit 5 / bit 1 of MSR.
          
   -DTR
          Data Terminal Ready, Pin 33: can be set active low by
          programming bit 0 of the MCR '1'. Loop mode operation holds
          this signal in its inactive state.
          
   INTR
          Interrupt, Pin 30: goes high when an interrupt is requested by
          the UART. Reset low by the MR.
          
   MR
          Master Reset, Pin 35: Schmitt Trigger input, resets internal
          registers to their initial values (see below).
          
   -OUT1
          Out 1, Pin 34: user-designated output, can be set low by
          programming bit 2 of the MCR '1' and vice versa. Loop mode
          operation holds this signal inactive high. [Not used in the PC.
          CB]
          
   -OUT2
          Out 2, Pin 31: blah blah blah, bit 3, see above. [Used in your
          PC to connect the UART to the interrupt line of the slot when
          '1'. CB]
          
   RCLK
          Receiver Clock, Pin 9: This input is the 16x bps rate clock for
          the receiver section of the chip. [Normally connected to
          -BAUDOUT, as in your PC. CB]
          
   RD, -RD
          Read, Pins 22 and 21: When RD is high *or* -RD is low while the
          chip is selected, the CPU can read data from the UART. [One of
          these is normally tied. CB]
          
   -RI
          Ring Indicator, Pin 39: blah blah blah, Bit 6 / Bit 2 of the
          MSR. [Bit 2 only indicates change from active low to inactive
          high! Curious, isn't it? CB]
          
   -RTS
          Request To Send, Pin 32: blah blah blah, see DTR (Bit 1).
          
   SIN
          Serial Input, Pin 10.
          
   SOUT
          Serial Output, Pin 11.
          
   -RXRDY, -TXRDY
          refer to NS data sheet. These pins are used for DMA channeling.
          Since they are not connected in your PC, I won't describe them
          here.
          
   VCC
          Pin 40, +5v
          
   VSS
          Pin 20, GND
          
   WR, -WR
          same as RD, -RD for writing data.
          
   XIN, XOUT
          Pins 16 and 17: Connect a crystal here (1.5k betw. xtal & pin
          17) and pin 16 with a capacitor of approx. 20p to GND and other
          xtal conn. 40p to GND. Resistor of approx. 1meg parallel to
          xtal. Or use pin 16 as an input and pin 17 as an output for an
          external clock signal of up to 8 MHz.
          
   
   Absolute Maximum Ratings:
   
   Temperature under bias: 0 C to +70 C
   Storage Temperature: -65 C to 150 C
   All input or output voltages with respect to VSS: -0.5v to +7.0v
   Power dissipation: 1W
   
   Further electrical characteristics see the very good data sheet of
   (National Semiconductor Corp.).
   
   UART Reset Configuration
   

Register/Signal        Reset Control      Reset State
--------------------------------------------------------------------
  IER                       MR            0000 0000
  IIR                       MR            0000 0001
  FCR                       MR            0000 0000
  LCR                       MR            0000 0000
  MCR                       MR            0000 0000
  LSR                       MR            0110 0000
  MSR                       MR            xxxx 0000 (according to signals)
  SOUT                      MR            high (neg. voltage at the port)
  INTR (RCVR errs)     Read LSR/MR        low
  INTR (data ready)    Read RBR/MR        low
  INTR (THRE)          Rd IIR/Wr THR/MR   low
  INTR (modem status)  Read MSR/MR        low
  -OUT2                     MR            high
  -RTS                      MR            high
  -DTR                      MR            high
  -OUT1                     MR            high
  RCVR FIFO           MR/FCR1&FCR0/DFCR0  all bits low
  XMIT FIFO           MR/FCR1&FCR0/DFCR0  all bits low

   
   
     _________________________________________________________________
   
   
   
                      KNOWN PROBLEMS WITH SEVERAL CHIPS
                                       
   (From material Madis Kaal received from Dan Norstedt and stuff Erik
   Suurmaa sent me)
   
   8250 and 8250-B:
   
     * These UARTs pulse the INT line after each interrupt cause has been
       serviced (which none of the others do). [Generates interrupt
       overhead. CB]
       
     * The start bit is about 1 us longer than it ought to be. [This
       shouldn't be a problem. CB]
       
     * 5 data bits and 1.5 stop bits doesn't work.
       
     * When a 1 is written to the bit 1 (Tx int enab) in the IER, a Tx
       interrupt is generated. This is an erroneous interrupt if the THRE
       bit is not set. [So don't set this bit as long as the THRE bit
       isn't set. CB]
       
     * The first valid Tx interrupt after the Tx interrupt is enabled is
       probably missed. Suggested workaround:
       
         1. Wait for the THRE bit to become set.
         2. Disable CPU interrupts. [?]
         3. Write Tx interrupt enable to the IER.
         4. Write Tx interrupt enable to the IER again. [Don't ask me
            why. I don't think it's necessary. CB]
         5. Enable CPU interrupts. [?]
   
     * The TEMT (bit 6) doesn't work properly.
       
     * If both the Rx and Tx interrupts are enabled, and a Rx interrupt
       occurs, the IIR indication of the Tx interrupt may be lost.
       Suggested workarounds:
       
         1. Test THRE bit in the Rx routine, and either set IER bit 1 or
            call the Tx routine directly if it is set.
         2. Test the THRE bit instead of using the IIR for Tx.
   
   
   [If one of these chips vegetates in your PC, go get your solder iron
   heated... CB]
   
   8250A, 82C50A, 16450 and 16C450:
   
     * (Same problem as above:) If both the Rx and Tx interrupts are
       enabled, and a Rx interrupt occurs, the IIR indication may be
       lost; Suggested workarounds:
       
         1. Test THRE bit in the Rx routine, and either set IER bit 1 or
            call the Tx routine directly if it is set.
         2. Test the THRE bit instead of using the IIR.
         3. [Don't enable both interrupts at the same time. CB]
         4. [Replace the chip by a 16550AFN; it has this bug fixed. CB]
   
   
   16550 (without the A):
   
     * Rx FIFO bug: Sometimes the FIFO will get extra characters. [This
       seemed to be very embarrassing for NS; they've added a simple
       detection method for the 16550A (bit 6 of IIR). CB]
       
   
   16550 AF
   
     * When the TX FIFO is enabled, a character loss can appear if the
       CPU writes a byte into the THR while the last one is still in the
       shift register (not completely sent). [This is documented by
       National Semiconductor; I've never experienced that, but that
       might be because I've never seen a 16550 AF :) CB]
       
     * Terence Edwards reports that his RS485 adapter with 16550 AF chips
       and a 16 MHz xtal gets parity bits wrong at 512 kbps; not very
       astonishing I'd say because the chip is only guaranteed to operate
       at 256kbps, with an 8 MHz xtal, and parity generators are rather
       slow circuits.
       
   
   No 16550 AFN bugs reported (yet?)
   
   [Same is true for the 16552, a two-in-one version of the 16550AFN, and
   the 16554, a quad-in-one version. CB]
   
   You might call this a bug, though: in FIFO mode, THRE (bit 5 or LSR)
   is cleared when there is at least one character in the Tx FIFO, not if
   the FIFO can't take any more bytes; that's rather absurd, but that's
   the way it is.
   
   A very solid method of handling the UART interrupts that avoids all
   possible int failures has been suggested by Richard Clayton, and I
   recommend it as well. Let your interrupt handler do the following:
   
    1. Disarm the UART interrupts by masking them in the IMR of the ICU.
    2. Send a specific or an unspecific EOI to the ICU (first slave, then
       master, if you're using channels above 7).
    3. Enable CPU interrupts (STI) to allow high priority ints to be
       processed.
    4. Read IIR and follow its contents until bit 0 is set.
    5. Check if transmission is to be kicked (when XON received or if CTS
       goes high); if yes, call tx interrupt handler manually.
    6. Disable CPU interrupts (CLI).
    7. Rearm the UART interrupts by unmasking them in the IMR of the ICU.
    8. Return from interrupt.
       
   
   This way you can arm all four UART ints at initialization time without
   having to worry about stuck interrupts. Start transmission by simply
   calling the tx interrupt handler after you've written characters to
   the tx fifo of your program.
   
   If you need details about programming the ICU, refer to Chris Hall's
   document about the 8259 that's available from my archive.
   
   
     _________________________________________________________________
   
   
   
                                  REGISTERS
                                       
   First some tables; full descriptions follow. Base addresses as
   specified by IBM for a full-blown system; compare the section on
   logical & physical names.
   

1st  2nd  3rd  4th  Offs. DLAB  Register
------------------------------------------------------------------------------
3F8h 2F8h 3E8h 2E8h  +0     0   RBR  Receive Buffer Register (read only) or
                                THR  Transmitter Holding Register (write only)
3F9h 2F9h 3E9h 2E9h  +1     0   IER  Interrupt Enable Register
3F8h 2F8h 3E8h 2E8h  +0     1   DL   Divisor Latch (LSB)  These registers can
3F9h 2F9h 3E9h 2E9h  +1     1   DL   Divisor Latch (MSB)  be accessed as word
3FAh 2FAh 3EAh 2EAh  +2     x   IIR  Interrupt Identification Register (r/o) or
                                FCR  FIFO Control Register (w/o, 16550+ only)
3FBh 2FBh 3EBh 2EBh  +3     x   LCR  Line Control Register
3FCh 2FCh 3ECh 2ECh  +4     x   MCR  Modem Control Register
3FDh 2FDh 3EDh 2EDh  +5     x   LSR  Line Status Register
3FEh 2FEh 3EEh 2EEh  +6     x   MSR  Modem Status Register
3FFh 2FFh 3EFh 2EFh  +7     x   SCR  Scratch Register (16450+ and some 8250s,
                                     special use with some boards)


           80h      40h      20h      10h      08h      04h      02h      01h
Register  Bit 7    Bit 6    Bit 5    Bit 4    Bit 3    Bit 2    Bit 1    Bit 0
-------------------------------------------------------------------------------
IER         0        0        0        0      EDSSI    ELSI     ETBEI    ERBFI
IIR (r/o) FIFO en  FIFO en    0        0      IID2     IID1     IID0    pending
FCR (w/o)  - RX trigger -     0        0      DMA sel  XFres    RFres   enable
LCR       DLAB     SBR    stick par  even sel Par en  stopbits  - word length -
MCR         0        0       AFE     Loop     OUT2     OUT1     RTS     DTR
LSR       FIFOerr  TEMT     THRE     Break    FE       PE       OE      RBF
MSR       DCD      RI       DSR      CTS      DDCD     TERI     DDSR    DCTS

   
   EDSSI
          Enable Delta Status Signals Interrupt
          
   ELSI
          Enable Line Status Interrupt
          
   ETBEI
          Enable Transmitter Buffer Empty Interrupt
          
   ERBFI
          Enable Receiver Buffer Full Interrupt
          
   FIFO en
          FIFO enable
          
   IID#
          Interrupt IDentification
          
   pending
          an interrupt is pending if '0'
          
   RX trigger
          RX FIFO trigger level select
          
   DMA sel
          DMA mode select
          
   XFres
          Transmitter FIFO reset
          
   RFres
          Receiver FIFO reset
          
   DLAB
          Divisor Latch Access Bit
          
   SBR
          Set BReak
          
   stick par
          Stick Parity select
          
   even sel
          Even Parity select
          
   stopbits
          Stop bit select
          
   word length
          Word length select
          
   FIFOerr
          At least one error is pending in the RX FIFO chain
          
   TEMT
          Transmitter Empty (last word has been sent)
          
   THRE
          Transmitter Holding Register Empty (new data can be written to
          THR)
          
   Break
          Broken line detected
          
   FE
          Framing Error
          
   PE
          Parity Error
          
   OE
          Overrun Error
          
   RBF
          Receiver Buffer Full (Data Available)
          
   DCD
          Data Carrier Detect
          
   RI
          Ring Indicator
          
   DSR
          Data Set Ready
          
   CTS
          Clear To Send
          
   DDCD
          Delta Data Carrier Detect
          
   TERI
          Trailing Edge Ring Indicator
          
   DDSR
          Delta Data Set Ready
          
   DCTS
          Delta Clear To Send
          
   AFE
          Automatic Flow control Enable
          
   
     * RBR (Receive Buffer Register) 3F8h 2F8h 3E8h 2E8h +0 r/o
       
       This is where you get received characters from. This register is
       read-only.
       
     * THR (Transmitter Holding Register) 3F8h 2F8h 3E8h 2E8h +0 w/o
       
       Send characters by writing them to this register. It is
       write-only.
       
     * IER (Interrupt Enable Register) 3F9h 2F9h 3E9h 2E9h +1 r/w
       
       Enable several interrupts by setting these bits:
       
        Bit 0
                If set, DR (Data Ready) interrupt is enabled. It is
                generated if data waits to be read by the CPU.
                
        Bit 1
                If set, THRE (THR Empty) interrupt is enabled. This
                interrupt tells the CPU to write characters to the THR.
                
        Bit 2
                If set, Status interrupt is enabled. It informs the CPU
                of occurred transmission errors during reception.
                
        Bit 3
                If set, Modem status interrupt is enabled. It is
                triggered whenever one of the delta-bits is set (see
                MSR).
                
   
       Bits 4-7 are not used and should be set 0.
       
     * DL (Divisor Latch) 3F8h 2F8h 3E8h 2E8h +0 r/w
       
       To access this *WORD*, set DLAB in the LCR to 1. Then write a word
       (16 bits) to this register or write the lower byte to base+0 and
       the higher byte to base+1 (the order is not important) to program
       the bps rate as follows:
       

     xtal frequency in Hz / 16 / desired rate = divisor
     xtal frequency in Hz / 16 / divisor      = obtained rate
   
       Your PC uses an xtal frequency of 1.8432 MHz (that's 1843200 Hz
       :-).
       
       Do *NOT* use 0 as a divisor (your maths teacher told you so)! It
       results in a rate of about 3500 bps, but it is not guaranteed to
       work with all chips in the same way.
       
       An error of up to 3-5 percent is irrelevant.
       
       Some values (1.8432 MHz quartz, as in the PC):
       

     bps rate    Divisor (hex)   Divisor (dec)   Percent Error
     ========    =============   =============   =============
         50          900             2304            0.0%
         75          600             1536            0.0%
        110          417             1047            0.026%
        134.5        359              857            0.058%
        150          300              768            0.0%
        300          180              384            0.0%
        600           C0              192            0.0%
       1200           60               96            0.0%
       1800           40               64            0.0%
       2000           3A               58            0.69%
       2400           30               48            0.0%
       3600           20               32            0.0%
       4800           18               24            0.0%
       7200           10               16            0.0%
       9600            C               12            0.0%
      19200            6                6            0.0%
      38400            3                3            0.0%
      57600            2                2            0.0%
     115200            1                1            0.0%
   
       The 16450 is capable of up to 512 kbps according to NS.
       
       NS specifies that the 16550A is capable of 256 kbps if you use a 4
       MHz or an 8 MHz crystal. But a staff member of 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. See the "known problems" section.
       
       BTW: Ever tried 1.76 bps, the slowest rate possible? 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. (Turbo Pascal "programmers"
       beware! :)
       
       The bits 6 and 7 allow you to detect if the FIFOs of the 16550+
       have been activated.
       

   Bit 3  Bit 2  Bit 1  Bit 0    Priority   Source    Description
     0      0      0      1                 none      no interrupt pending
     0      1      1      0      highest    Status    OE, PE, FE or BI of the
                                                      LSR set. Serviced by
                                                      reading the LSR.
     0      1      0      0      second     Receiver  DR or trigger level rea-
                                                      ched. Serviced by read-
                                                      ing RBR 'til under level
     1      1      0      0      second     FIFO      No Receiver FIFO action
                                                      since 4 words' time
                                                      (neither in nor out) but
                                                      data in RX-FIFO. Serviced
                                                      by reading RBR.
     0      0      1      0      third      Transm.   THRE. Serviced by read-
                                                      ing IIR (if source of
                                                      int only!) or writing
                                                      to THR.
     0      0      0      0      lowest     Modem     One of the delta flags
                                                      in the MSR set. Serviced
                                                      by reading MSR.
   Bit 6 & 7: 16550A: set if FCR bit 0 set.
              16550:  bit 7 set, bit 6 cleared if FCR bit 0 set.
              others: clear
   Other bits: clear (but don't rely on it; this is subject to change).
   
       In most software applications bits 3 to 7 should be masked when
       servicing the interrupt since they are not relevant. These bits
       cause trouble with old software relying on that they are
       cleared...
       
       NOTE! Even if some of these interrupts are disabled, the service
       routine can be confronted with *all* states shown above when the
       IIR is loop-polled until bit 0 is set (don't ask me why; it's just
       that I encontered this, and it's not much more work to play it
       safe). Check examples in the Programming section.
       
     * FCR (FIFO Control Register) 3FAh 2FAh 3EAh 2EAh +2 w/o
       
       This register allows you to control the FIFOs of the 16550+. It
       does not exist on the 8250/16450.
       

   Bit 0:    FIFO enable.
   Bit 1:    Clear receiver FIFO. This bit is self-clearing.
   Bit 2:    Clear transmitter FIFO. This bit is self-clearing.
   Bit 3:    DMA mode (pins -RXRDY and -TXRDY), see below
   Bits 6-7: Trigger level of the DR-interrupt.

   Bit 7  Bit 6    Receiver FIFO trigger level
     0      0          1
     0      1          4
     1      0          8
     1      1         14
   
       Note: if bit 0 is cleared, all other bits are ignored.
       
       DMA mode operation is not available with your PC, but for the sake
       of completeness... here we go.
       
       If bit 3 is 0, DMA mode 0 is selected. The -RXRDY pin goes
       active-low whenever there is at least one character in the RX FIFO
       or in the RBR if the FIFO is disabled. -TXRDY goes active-low when
       the TX FIFO or the THR is empty. It goes high if one character is
       written to the THR (same as THRE, that's bit 5 of the LSR).
       
       If this bit is 1, DMA mode 1 is selected. The -RXRDY pin goes low
       if the trigger level of the RX FIFO is reached or if reception
       timed out (no characters received for a time that would have
       allowed to receive 4 characters). -TXRDY goes low when the TX FIFO
       is empty. It goes high again if the FIFO is completely full. (Not
       that setting this bit to '1' would fix the weird behaviour of the
       THRE bit in FIFO mode operation, though). If the FIFOs are
       disabled, DMA mode 1 operates in the same way as DMA mode 0.
       
     * LCR (Line Control Register) 3FBh 2FBh 3EBh 2EBh +3 r/w
       
       This register allows you to select the transmission protocol. It
       also contains the DLAB bit which switches the function of the
       addresses +0 and +1.
       

   Bit 1  Bit 0    word length         Bit 2      Stop bits
     0      0        5 bits              0            1
     0      1        6 bits              1          1.5/2
     1      0        7 bits         (1.5 if word length is 5)
     1      1        8 bits   (1.5 does not work with some chips, see above)

   Bit 5  Bit 4  Bit 3     Parity type       Bit 6   SOUT condition
     x      x      0       no parity           0     normal operation
     0      0      1       odd parity          1     forces TxD +12V (break)
     0      1      1       even parity       Bit 7   DLAB
     1      0      1       mark parity         0     normal registers
     1      1      1       space parity        1     divisor at reg 0, 1
   
       Mark parity: The parity bit is always '1' (the line is 'low').
       Space parity: The parity bit is always '0' (the line is 'high').
       
     * MCR (Modem Control Register) 3FCh 2FCh 3ECh 2ECh +4 r/w
       
       This register allows to program some modem control lines and to
       switch to loopback mode.
       
        Bit 0
                Programs -DTR. If set, -DTR is low and the DTR pin of the
                port goes 'high'.
                
        Bit 1
                Programs -RTS. dito.
                
        Bit 2
                Programs -OUT1. Normally not used in a PC, but used with
                some multi-port serial adapters to enable or disable a
                port. Best thing is to write a '1' to this bit.
                
        Bit 3
                Programs -OUT2. If set to 1, interrupts generated by the
                UART are transferred to the ICU (Interrupt Control Unit)
                while 0 sets the interrupt output of the card to high
                impedance. (This is PC-only).
                
        Bit 4
                '1': local loopback. All outputs disabled. This is a
                means of testing the chip: you 'receive' all the data you
                send. Interrupts are fully operational in this mode.
                
        Bit 5
                (Texas Instruments TL16C550C only, maybe some more; this
                is not a standard feature) '1': Enable automatic flow
                control. If RTS (bit 1) is '0', only auto-CTS is done,
                which means that no more characters are sent from the
                FIFO and no more Tx interrupts are generated as long as
                CTS is '0'. If RTS (bit 1) is '1', the RTS signal is
                dropped whenever the FIFO trigger level is reached. Note
                that if this bit is '1', delta CTS (see below) won't
                generate a modem status interrupt!
                
   
     * LSR (Line Status Register) 3FDh 2FDh 3EDh 2EDh +5 r/w
       
       This register allows error detection and polled-mode operation.
       
        Bit 0
                Data Ready (DR). Reset by reading RBR (but only if the RX
                FIFO is empty, 16550+).
                
        Bit 1
                Overrun Error (OE). Reset by reading LSR. Indicates loss
                of data.
                
        Bit 2
                Parity Error (PE). Indicates transmission error. Reset by
                LSR.
                
        Bit 3
                Framing Error (FE). Indicates missing stop bit. Reset by
                LSR.
                
        Bit 4
                Break Indicator (BI). Set if RxD is 'space' for more than
                1 word ('break'). Reset by reading LSR.
                
        Bit 5
                Transmitter Holding Register Empty (THRE). Indicates that
                a new word can be written to THR. Reset by writing THR.
                Note that this bit works in a weird way when FIFOs are
                enabled: it goes 0 whenever there are characters in the
                TX-FIFO, not when the FIFO is full!
                
        Bit 6
                Transmitter Empty (TEMT). Indicates that no transmission
                is running. Reset by reading LSR.
                
        Bit 7
                (16550+ only) Set if at least one character in the RX
                FIFO has been received with an error. Cleared by reading
                LSR if there is no further error in the FIFO. Clear with
                all other chips.
                
   
     * MSR (Modem Status Register) 3FEh 2FEh 3EEh 2EEh +6 r/w
       
       This register allows you to check several modem status lines. The
       delta bits are set if the corresponding signals have changed state
       since the last reading (except for TERI which is only set if -RI
       changed from active-low to inactive-high, that is if the RI line
       at the port changed from 'high' to 'low' and the phone stopped
       ringing).
       
        Bit 0
                Delta CTS. Set if CTS has changed state since last
                reading.
                
        Bit 1
                Delta DSR. Set if DSR has changed state since last
                reading.
                
        Bit 2
                TERI. Set if -RI has changed from low to high (ie. RI at
                port has changed from +12V to -12V).
                
        Bit 3
                Delta DCD. Set if DCD has changed state since last
                reading.
                
        Bit 4
                CTS. 1 if 'high' at port.
                
        Bit 5
                DSR. dito.
                
        Bit 6
                RI. dito.
                
        Bit 7
                DCD.
                
   
       In loopback mode (MCR bit 4 = 1), bit 4 shows the state of RTS
       (MCR bit 1), bit 5 shows the state of DTR (MCR bit 0), RI shows
       the state of OUT1 (MCR bit 2), and DCD shows the state of OUT2
       (MCR bit 3). The delta registers act accordingly to the 'level
       transitions' of the data written to MCR. This is a good means of
       testing if a UART is present.
       
     * SCR (Scratch Register) 3FFh 2FFh 3EFh 2EFh +7 r/w
       
       This is an all-purpose 8 bit store. NS recommends to store the
       value of the FCR (which is w/o) in this register for further use,
       but this is not mandatory and not recommended by me (see below).
       This register is only available with the 16450+; the standard 8250
       doesn't have a scratch register (but then again some versions do).
       
       On some boards (especially RS-422/RS-485 boards), this register
       has a special meaning (enable receiver/transmitter drivers etc.),
       and with multi-port serial adapters it is often used to select the
       interrupt levels of the several ports and to determine which port
       has triggered interrupt. So you shouldn't use it for anything else
       in your programs.
       
   
     _________________________________________________________________
   
   
   
         EXCURSION: WHY AND HOW TO USE THE FIFOS (BY SCOTT C. SADOW)
                                       
   Scott C. Sadow writes:
   
          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-232C CARD can have the following
   configuration:
   

          Base      IRQ    Channel  Interrupt
         Address   Level   Number     Vector
         =======   =====   =======  =========
           2A0       7        0        2BF
           2A8       7        1        2BF
           2B0       7        2        2BF
           2B8       7        3        2BF
           1A0       5        0        1BF
           1A8       5        1        1BF
           1B0       5        2        1BF
           1B8       5        3        1BF

   
   [The base addresses should be configurable by jumpers or DIP
   switches.]
   
   Note that the Interrupt Vector Registers overlap Scratch Registers, so
   the detect_UART routine must be changed for these boards. [See the
   Programming Section.]
   
   
     _________________________________________________________________
   
   
   
                           SOME WORDS ABOUT TIMING
                                       
   The 8250 is a rather slow peripheral chip; it has a cycle delay for
   both reading and writing of 500nsec, which means that after every read
   or write access to any of the chip's registers the CPU has to wait at
   least 500nsec before reading or writing one of its registers again.
   Good thing that this chip is only used with some old XTs... the
   8088/8086/V20/V30 family is slow enough for that.
   
   The 16450 and 16550A are rather fast; they need a delay of 125nsec
   after read access and 150nsec after write access before any other
   transfer. This means we only have a problem with these fancy new
   machines that allow cycle times of 50nsec and less. Luckily they add
   wait states to I/O bus accesses (wait states are additional cycles
   during which the bus does not change its state) or use a slower clock
   speed for I/O transfers (8 or 12 MHz). So if you have 12 MHz I/O clock
   speed and one wait state for I/O transfers, you don't have to worry.
   
   Some people believe in delaying I/O operations by adding NOPs or JMP
   $+2 to every I/O instruction (both do nothing but wasting time), but I
   don't think that's any good with a chip that needs stable data lines
   for at least 100nsec (so the CPU or the bus controller has to add a
   wait state anyway). You can always blame the hardware or the setup if
   your program doesn't work for timing reasons. :)
   
   However, there may be a problem with block instructions, esp. OUTSB.
   This instruction allows you to fill the Tx fifo of the 16550A rather
   fast (just 5 cycles per transfer on the 286, others take longer), but
   even a 25MHz 286 takes 200nsec for each transfer, so this should be on
   the safe side, too. I don't use this instruction, but for other
   reasons than timing difficulties. It's just not very useful: it takes
   more time to make sure in advance that you don't overrun your buffer
   margins during an OUTSB than to check for the margins after every
   single transfer.
   
   Please note that all this relates to ISA 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 is ready to send and receive data, while the data set sets
          DSR. With most modems, the meaning of these signals is slightly
          different: DTR is ignored or causes the modem to hang up if it
          is dropped, while DSR signals that a connection has been
          established.
          
   RTS/CTS
          While DTR and DSR are mostly used to establish a connection,
          RTS and CTS have been specially designed for data flow control.
          The computer signals with RTS ('Request To Send') that it
          wishes to send data to the data set, while the data set (modem)
          sets CTS ('Clear To Send') when it is ready to do one part of
          its job: to send data thru' the phone wires.
          
   
   A normal handshaking protocol between a computer and a modem looks
   like this:
   

DTR  ___--------------------------------------------------------------____

DSR  _____-------------------------------------------------------------___

RTS  ___________-----------------------_____----------------------________

CTS  ____________-------____------------_____----------------------_______

       (1)(2) (3)(4)   (5) (6)      (7)(8)(9)(10)            (11)(12)(13)

   
    1. The computer sets DTR to indicate that it wants to make use of the
       modem.
    2. The modem signals that it is ready and that a connection has been
       established.
    3. The computer requests permission to send.
    4. The modem informs the computer that it is now ready to receive
       data from the computer and send it through the phone wires.
    5. The modem drops CTS to signal to the computer that its internal
       buffers are full; the computer stops sending characters to the
       modem.
    6. The buffers of the modem have been purged, so the computer may
       continue to send data.
    7. This situation is not clear; either the computer's buffers are
       full and it wants to inform the modem of this, or it doesn't have
       any more data to be send to the modem. Normally, modems are
       configured to stop any transmission between the computer and the
       modem when RTS is dropped.
    8. The modem acknowledges RTS cleared by dropping CTS.
    9. RTS is again raised by the computer to re-establish data
       transmission.
   10. The modem shows that it is ready to do its job.
   11. No more data is to be sent.
   12. The modem acknowledges this.
   13. DTR is dropped by the computer; this causes most modems to hang
       up. After hang-up, the modem acknowledges with DSR low. If the
       connection breaks, the modem also drops DSR to inform the computer
       about it.
       
   
   
     _________________________________________________________________
   
   
   
                  BIOS API (APPLICATION PROGRAMS INTERFACE)
                                       
   PC programs are meant to use the BIOS routines to program the UARTs.
   Even though this is NOT RECOMMENDED by me (awfully slow, limited and
   complicated), I give you the BIOS calls as specified by Big Blue. Call
   INT 14h with:
   

  AH=00h    Serial port - Initialize

     AL: see table
     DX: Port number (0-3; 0 equ. 0x3f8, 1 equ. 0x2f8, etc., see Hardware)

     Bit 7  Bit 6  Bit 5      Rate [bps]         Bit 4  Bit 3     Parity
       1      1      1        9600                 0      0       none
       1      1      0        4800                 1      0       none
       1      0      1        2400                 0      1       odd
       1      0      0        1200                 1      1       even
       0      1      1         600
       0      1      0         300               Bit 1  Bit 0     Data bits
       0      0      1         150                 0      0         5
       0      0      0         110                 0      1         6
                                                   1      0         7
     Bit 2   0 -> 1 stop bit, 1 -> 2 stop bits     1      1         8

     Returns:
     AH: RS-232C line status bits
       Bit
        0: RBF  - input data is available in buffer
        1: OE   - data has been lost
        5: THRE - room is available in output buffer
        6: TEMT - output buffer empty
     AL: Modem status bits
        3: always 1
        7: DCD - carrier detect

   AH=01h    Serial port - Write character

     AL: character to be sent
     DX: Port

     Returns:
     AH: Bit 7 clear if successful, set if not. Bits 0-6 see INT 14h AH=03h

   AH=02h    Serial port - Read character

     DX: Port

     Returns:
     AH: Line Status (see AH=03h)
     AL: Received character (if AH bit 7 is clear)

     Note:
     This routine times out if DSR is not asserted, even if data is
     available! (That's why you need the short wires from the "Connecting
     devices" chapter with some programs).

   AH=03h    Serial port - Get port status

     DX: Port

     Returns:
     AH: Line Status
        Bit 7: Timeout
        Bit 6: TEMT Transmitter empty
        Bit 5: THRE Transmitter Holding Register Empty
        Bit 4: Break (broken line detected)
        Bit 3: FE Framing error
        Bit 2: PE Parity error
        Bit 1: OE Overrun error
        Bit 0: RDF Receiver buffer full (data available)
     AL: Modem Status
        Bit 7: DCD Carrier detect
        Bit 6: RI Ring indicator
        Bit 5: DSR Data set ready
        Bit 4: CTS Clear to send
        Bit 3: DDCD Delta carrier detect
        Bit 2: TERI Trailing edge of ring indicator
        Bit 1: DDSR Delta data set ready
        Bit 0: DCTS Delta Clear to send

   
   BIOS variables in the Data Segment at segment 40h:
   

     Offset   Size     Description
      00h     WORD     Base I/O address of 1st serial I/O port, zero if none
      02h     WORD     Base I/O address of 2nd serial I/O port, zero if none
      04h     WORD     Base I/O address of 3rd serial I/O port, zero if none
      06h     WORD     Base I/O address of 4th serial I/O port, zero if none

   
   Note: Above fields filled in turn by POST as it finds serial ports.
   POST never leaves gaps. DOS and BIOS serial device numbers may be
   redefined by re-assigning these fields. [POST: Power-On Self Test. CB]
   
   [Madis Kaal told me that there are BIOSes that leave gaps in the
   table, and I know of some that don't recognize COM4 correctly.]
   
   This information is sneaked from Ralf Brown's famous interrupt list
   (hope he doesn't mind). If you want more detailed facts on this
   interrupt, refer to this list. It's available from lots of FTP sites
   (choose one in your vicinity; it is *huge*).
   
   
     _________________________________________________________________
   
   
   
                                     MICE
                                       
   The Microsoft Serial Mouse (or compatibles) is the device that is
   most often used with the Serial Port of the PC; it's the one with the
   two buttons. Mouse Systems compatible mice have three buttons. Here's
   some information I received from Stephen Warner and Angelo Haritsis:
   
   Pins Used
          TxD, RTS and/or DTR are used as power sources for the mouse.
          RxD is used to receive data from the mouse.
          
   Mouse reset
          Set UART to 'broken line' state (set bit 6 of the LCR) and
          clear the bits 0-1 of the MCR; wait a while and reverse the
          bits again.
          
   Serial transmission parameters
          Microsoft Mouse 1200 bps, 7 data bits, 1 stop bit, no parity
          Mouse Systems Mouse 1200 bps, 8 data bits, 1 stop bit, no
          parity
          
   Data packet format of the Microsoft mouse
          The data packet consists of 3 bytes. It is sent to the computer
          every time the mouse changes state (ie. the mouse is moved or
          the buttons are released/ pressed).
          

              D6    D5    D4    D3    D2    D1    D0

  1st byte    1     LB    RB    Y7    Y6    X7    X6
  2nd byte    0     X5    X4    X3    X2    X1    X0
  3rd byte    0     Y5    Y4    Y3    Y2    Y1    Y0

   
          The byte marked with 1 is sent first and then the others. The
          bit D6 in the first byte is used for synchronizing the software
          to the mouse packets if it goes out of sync.
          
          LB is the state of the left button (1 being the LB is pressed)
          RB is the state of the right button (1 being the RB is pressed)
          X0-7 movement of the mouse in the X direction since last packet
          (+ right)
          Y0-7 movement of the mouse in the Y direction since last packet
          (+ down )
          
          The Microsoft Mouse uses RTS as power source. Whenever RTS is
          set to '0' and reset to '1', the mouse performs an internal
          reset and sends the character 'M' to signal its presence.
          Three-button-mice send 'M3' if you drop and raise RTS (see
          above) in Microsoft mode; this is compatible with the Microsoft
          mouse driver and allows the firmware to check if it is really a
          three-button mouse.
          
          [Scott David Daniels received this info from Brian Onn]
          
   Data packet format of the Mouse Systems mouse
          The data packet consists of 5 bytes.
          

              D7    D6    D5    D4    D3    D2    D1    D0

  1st byte    1     0     0     0     0     LB    MB    RB
  2nd byte    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 as well as bus mice and
   greatly reduces programming overhead. See Ralf Brown's interrupt list,
   interrupt 33h. It is available from lots of FTP sites (eg.
   garbo.uwasa.fi, /pc/programming), the files are called inter*.zip.
   
   
     _________________________________________________________________
   
   
   
                                    MODEMS
                                       
   This chapter is rather brief for several reasons. I'm no modem expert
   at all and there exist better sources than this document if you want
   information on modems. Patrick Chen, the author of "The Joy of
   Telecomputing", has written such a file, and there's one available
   from Sergey Shulgin, too (I don't have their internet addresses). You
   can obtain these files from my archive; they are named "modem1" and
   "modem2".
   
   A modem (MOdualtor-DEModulator) is an interface between the serial
   port of your computer and the public telephone network. Modern modems
   are small computers of their own: they accept commands, do the dialing
   for you, buffer incoming data, perform data compression and such
   things. Several standards have been established (Bell, CCITT), and
   several "command languages" are in use, with the Hayes and Microcom
   commands being the most popular ones.
   
   Modems have two internal modes: the command mode and the data mode.
   After power-up, the modem is in the command mode, and this mode can be
   restalled by sending an 'escape sequence' (normally a pause of at
   least 1 second, then three '+' signs in one second, then a pause of at
   least 1 second).
   
   All I know about modems is some commands and some encoding schemes; I
   share this knowledge with you - please share yours with me!
   
   
     _________________________________________________________________
   
   
   
                               ENCODING SCHEMES
                                       
   I've sneaked this table from the posting 'FAQ zu
   /Z-NETZ/TELECOM/ALLGEMEIN' of Kristian Koehntopp
   <[email protected]> in 'de.newusers.questions'. He has
   copyrighted his posting, so please contact him if you wish to
   reproduce this information in any commercial way. These are the
   schemes recommended by CCITT (more than one speed means
   fallback/auto-retrain speeds):

          Transmission speed in bps  Baud  Modulation duplex     usage
   --------------------------------------------------------------------
   V.17        14400                 2400     TCM      half       FAX
               12000, 9600, 7200     2400     TCM      half       FAX
                4800                 2400     QAM      half       FAX
   V.21          300                  300     FSK      full
   V.22         1200                  600    DPSK      full
   V.22bis      2400                  600     QAM      full
   V.23         1200/75              1200/75  FSK   asymmetric    BTX
   V.27ter      4800                 1600    DPSK      half       FAX
                2400                 1200    DPSK      half       FAX
   V.29         9600                 2400     QAM      half       FAX
                7200                 2400     QAM      half       FAX
   V.32         9600                 2400   TCM/QAM    full
                4800                 2400     QAM      full
   V.32bis     14400                 2400     TCM      full
               12000, 9600, 7200     2400     TCM      full
                4800                 2400     QAM      full

   
   FSK
          Frequency Shift Keying
          
   DPSK
          Differential Phase Shift Keying
          
   QAM
          Quadrature Amplitude Modulation
          
   TCM
          Trellis Coded Modulation
          
   
   Other V-Recommendations often heard of:
   
   V.24
          Meaning of the signals at the serial port.
          
   V.28
          Electrical levels (V.24, V.28, and ISO 2110 are equivlaent to
          EIA RS232)
          
   V.42
          Data protection method, not dependening on the modulation
          scheme in use.
          
   V.42bis
          Compression scheme, also called BTLZ.
          
   
   Erich Smythe <[email protected]> posted a very informative and
   humorous article explaining different modulation schemes used with
   modems. You can find it in the FTP archive, named
   The_Serial_Port.more06.
   
   
     _________________________________________________________________
   
   
   
                                HAYES COMMANDS
                                       
   Each command line starts with 'AT', then several commands, then
   carriage return.
   
   The list is not comprehensive at all; most modems have several
   commands of their own, but these commands are available with most
   modems:
   

A/  Repeat last command (no prepending AT)

A   Take over phone line (if you've already picked up the phone).

B   Set communications standard.
    B0 - CCITT
    B1 - Bell

C   Switch carrier on/off.
    C0 - carrier off
    C1 - carrier on

D   Dial a number. Normally followed by
    T - tone dial
    P - pulse dial
    nothing - according to actual setting (see ATP/ATT)
    then a sequence of the follwing characters:
    0-9 - the numbers to be dialed
    W - wait for dial tone
    , - wait 2 seconds
    @ - wait 5 seconds (?)
    ! - flash (put the phone on the hook for 1/2 second)
    > - earth key
    R - start connection right after dialing (eg. ATDPR equals ATA)
    If you just enter ATD, the modem takes over the line without dialing.

E   Echo on/off in the command mode
    E0 - no echo
    E1 - echo

H   Hang up

L   Volume control; followed by 0-3 (0 equ. lowest, 3 equ. highest volume)

M   Monitor
    M0 - Speaker off
    M1 - Speaker on while dialing and establishing a connection
    M2 - Speaker always on
    M3 - Speaker on while establishing a connection

O   Switch to data mode
    O0 - promptly
    O1 - with retrain (reduction of the data rate)

P   Pulse dial

Q   Responses to commands on/off
    Q0 - on
    Q1 - off

S   Set/read internal register, eg.
    S17=234 set reg. 17 to 234
    S17?    read reg. 17

T   Tone dial

V   Verbose mode on/off
    V0 - short responses
    V1 - full responses

X   Phone tones recognition on/off
    X0 - Ignore busy sign, don't wait for dial tone, and just answer with
         "CONNECT" when a connection has been established (other settings
         produce more detailed messages)
    X1 - Ignore busy sign, don't wait for dial tone, but give full connect
         message
    X2 - Ignore busy sign but wait for dial tone
    X3 - Don't ignore busy sign, but don't wait for dial tone
    X4 - Don't ignore anything

Y   Break setting
    Y0 - Don't hang up when break signal is detected
    Y1 - Hang up when break is detected (&D2, &M0)

Z   Initialize modem
    Z  - Default parameters
    Z0 - Setting 0
    Z1 - Setting 1

&C  DCD mode
    &C0 - always 1
    &C1 - DCD according to carrier

&D  DTR mode
    &D0 - ignore DTR
    &D1 - switch to command mode when DTR goes 0
    &D2 - hang up if DTR goes 0
    &D3 - initialize modem when DTR goes 0

&F  Set operation mode
    &F0 - according to Hayes, no data protocol
    &F1 - according to Microcom; MNP1-4 or MNP5 as specified by %C
    &F2 - according to Sierra; MNP1-4 or MNP5 as specified by %C
    &F3 - according to Sierra, V.42 or V.42bis as specified by %C

    These are the default settings:
    &F0 - B0, E1, L2, M1, P, Q0, V1, Y0, X1, &C1, &D0, &G0, &R0, &S0,
          S0=0, S1=0, S2=43, S3=13, S4=10, S5=8, S6=2, S7=30, S8=2,
          S9=6,S10=14, S11=75, S12=50, S14=AAh, S16=80h, S21=20h,
          S22=76h, S23=7, S25=5, S26=1, S27=40h
    &F1 - \A3, \C0, \E0, \G0, \K5, \N1, \Q0, \T0, \V0, \X0, %A0, %C1,
          %E1, %G0, &G1, S36=7h, S46=138h, S48=128h, S82=128h
    &F2 - \A3, \C2, \E0, \G1, \K5, \N3, \Q1, \T0, \V1, \X0, %A13, %C1,
          S36=7h, S46=138h, S48=128h, S82=128h
    &F3 - \A3, \C0, \E0, \G0, \K5, \N3, \O1, \T0, \V1, \X0, %A0, %C1,
          %E0, S36=7h, S46=138h, S48=7h, S82=128h

&G  Guard tone
    &G0 - off
    &G1 - 550 Hz
    &G2 - 1800 Hz

&K  Data flow control
    &K0 - none
    &K3 - bidirectional RTS/CTS handshaking
    &K4 - bidirectional XON/XOFF
    &K5 - unidirectional XON/XOFF

&M  Synchronous/asynchronous operation
    &M0 - asynchronous (the usual thing)
    &M1 - command mode asynchronous, data mode synchronous.
    &M2 - switch to synchronous mode, start dialing after DTR 0->1
    &M3 - switch to synchronous mode, don't dial

&Q  Further specification of the communication
    &Q0 to &Q3 - no V.42bis
    &Q5 - V.42bis
    &Q6 - V.42bis off, buffer data

&R  CTS mode
    &R0 - CTS follows RTS with the delay time of S26
    &R1 - CTS is 1 if the modem is in the data mode

&S  DSR mode
    &S0 - DSR always 1
    &S1 - according to CCITT V.24

&T  Test
    &T0 - normal operation (no test)
    &T1 - local analog loopback
    &T3 - local digital loopback
    &T4 - accept distant digital loopback
    &T5 - ignore distant digital loopback
    &T6 - start distant digital loopback
    &T7 - start distant digital loopback and self test
    &T8 - start distant analog loopback and self test

&V  Show modem status

&Wn Save actual configuration (some modems only). Setting can be
    restored with ATZn. n normally ranges between 0 and 1.
    The following parameters are stored:
    B, C, E, L, M, P/T, Q, V, X, Y, &C, &D, &G, &R, &S, &T4/&T5,
    S0, S14, S18, S21, S22, S25, S26, S27

&X  Specify clock source for synchronous operation
    &X0 - modem generates clock
    &X1 - modem synchronizes with local clock
    &X2 - modem synchronizes with distant clock

&Y  Define default setting (see &W and Z)
    &Y0 - setting 0 is default
    &Y1 - setting 1 is default

&Z  Store phone number in diary
    &Zn=XXXXXX stores phone number XXXXXX under index n, where
    XXXXXX can be up to 30 digits and n ranges between 0 and 3.

   
     _________________________________________________________________
   
   
   
                              MICROCOM COMMANDS
                                       
   

\A  Set block length for MNP
    \A0 - 64 characters
    \A1 - 128 characters
    \A2 - 192 characters
    \A3 - 256 characters

\Bn Send break signal for n times 100ms (MNP defaults to n=3).

\C  Set buffering
    \C0 - none at all
    \C1 - buffer data for 4 seconds as long as 200 characters aren't
          reached or as long as no MNP block is found
    \C2 - don't buffer. Switch back to normal operation after reception
          of the control character (fall-back, see %C)

D/n Dial phone number n in the diary (see &Z)

DL  Redial last number

\E  Echo on/off in data mode
    \E0 - no echo
    \E1 - echo

\G  Data flow on/off (see \Q)
    \G0 - off
    \G1 - on

\J  Data rate adjust
    \J0 - the data rates computer-modem and modem-modem are independent
    \J1 - the data rate computer-modem follows the data rate modem-modem

\Kn Break setting (don't know anything about this, just that it exists ;-)

\N  MNP select
    \N0 - standard mode, no MNP, data is buffered
    \N1 - direct mode, no MNP, no buffering
    \N2 - MNP, data is buffered
    \N3 - allow MNP on/off during connection, data is buffered

\O  Switch on MNP during connection (the rest of the line is being ignored!)

\Pn Same as &Z

\Q  Set handshake (compare &K)
    \Q0 - no handshaking
    \Q1 - XON/XOFF
    \Q2 - modem controls data flow with CTS
    \Q3 - data flow control with RTS/CTS

\S  List complete configuration

\Tn Set idle timer
    \T0 - timer off
    \Tnn - break connection after nn minutes without data exchange
           (1-90)

\U  Acknowledge MNP operation; rest of line is ignored!

\V  Verbose mode
    \V0 - messages according to Hayes, even if MNP (no \REL)
    \V1 - messages according to Microcom (\REL appended if MNP)

\X  Filter XON/XOFF characters
    \X0 - filter XOM/XOFF characters
    \X1 - don't filter them (the usual thing)

\Y  Same as AT\O\U with the difference that it is not necessary to
    first send AT\O to one modem and then AT\U to the other; just
    send AT\Y to each modem within 5 seconds

%An Specify control character that provokes fallback from MNP to
    normal operation (see \C2). n=0..255 (ASCII code)

%C  MNP5
    %C0 - not allowed
    %C1 - allowed

%E  auto-retrain
    %E0 - no auto-retrain allowed
    %E1 - auto-retrain allowed according to CCITT

%R  Show all S registers

%V  Same as I3 (but don't ask me what it is ;-) Gives info on the firmware
    version with some modems.

   
     _________________________________________________________________
   
   
   
     IRQ SHARING - CAN IT BE DONE? (THIS APPLIES TO ISA BUS SYSTEMS ONLY)
                                       
   Yes and no. Yes, it can be done in principle, and no, it can't be
   done by just configuring two ports to use the same interrupt.
   
   Let us first consider the hardware involved. PCs have ICUs (interrupt
   control units, or PICs - programmable interrupt controllers) of the
   8259A type. They can be programmed to be triggered by a high signal
   level or a raising edge, which is already annoying because low level
   or falling edge would make add-on card design simpler. But to top this
   all off, they have internal pull-up resistors! Which means that if no
   card is using the interrupt, it is in the triggered state.
   
   How would cards share interrupts? They'd only be allowed to have their
   IRQ output in two states: active high or 'floating'. 'Floating' means
   the line is not driven at all, neither high nor low, it 'floats'. If
   all sharers of an interrupt line in the PC would only drive the line
   high or let it 'float', we'd have a simple interrupt sharing scheme
   (that would allow for even simpler design if the active state of the
   line was low) - if there wasn't this nasty internal pull-up resistor
   in the 8259A. <sarcasm on> Sadly IBM didn't provide an external
   pull-down resistor on the main board of the very first PC, so later
   designs could not have one either for compatibility's sake. <sarcasm
   off> 1.5kOhms would be a fine value; the 8259A produces 300uA that
   have to be sunk below 0.8v (so 2.6kOhms would be enough in theory, but
   having some safety margin can't hurt).
   
   So how can you have your ports sharing a common interrupt line? There
   are two approaches to this, each assuming you're familiar with using a
   soldering iron. What you must provide is a logical OR of all interrupt
   outputs that drive the line; while this can be done with an OR gate of
   course, it is far more practical to use some wired-OR facility. First
   you'll have to add the external pull-down resistor, either on the main
   board (where it really belongs) or on one of the cards. Use 1.5kOhms
   for this. Then cut the line between the card edge connector and the
   IRQ line driver (LS125) on each and every card. Do this carefully; if
   it's a multi-layer card, you'd better cut the pin of the LS125, or
   maybe you can just replace a jumper with a diode. Now solder a diode
   (1N4148 will do, slow power diodes won't) over the cut with the
   cathode (usually marked with a ring, but you'd better check that
   thoroughly if there are multiple rings; the 1N4148 normally has a
   yellow cathode ring) to the card edge connector. There you are! Now
   hardware will no longer be in the way of interrupt sharing. (A
   'cleaner' solution would be to use a LS126 line driver instead of the
   diode with 'enable' connected to 'input', but that's only practical
   with from-scratch designs.)
   
   Now let's face the software problems. In theory, interrupt sharing
   works fine between different pieces of hardware, but practically this
   is limited to real operating systems that do all interrupt processing
   by themselves; MSDOS doesn't do that, so it's not a good option for
   PCs (even Linux users boot DOS sometimes, if only to play games).
   Sharing interrupts even between UARTs becomes problematic if there are
   several programs involved, eg. the mouse driver and some comm
   application; they'd have to know of each other. 'Daisy chaining' the
   interrupt (a program 'hooks' the interrupt by placing its handler's
   address in the IRQ serivce table and letting the handler call the
   address it found in that table at install time when it exits; no
   interrupt acknowledging is done by the handlers themselves, just by
   the stub handler at the end of the chain) doesn't work because DOS
   doesn't even provide a stub interrupt handler! So one of the programs
   would have to issue EOI (end of interrupt) to the ICU, but which one?
   How would it know it's the last one in the chain? Better forget daisy
   chaining interrupts under DOS if you want your programs to work
   reliably.
   
   The situation is much simpler if all UARTs sharing the same interrupt
   are used by the same program. This program has to be aware of the
   sharing mechanism, but programs that can make use of more than one
   serial port (especially libraries) usually are. Now there's only one
   problem to be solved: lock-up situations. As I already wrote, the ICUs
   in the PC are programmed to use raising edge trigger mode, and you
   can't change this without crashing the system. Now consider the
   following situation. Two UARTs share one IRQ line. UART #1 raises the
   line because it needs service; the service routine is called and
   detects that UART #1 needs service. Before it can perform the serivce,
   UART #2 raises the IRQ, too. Now UART #1 is serviced, the line should
   go to the 'low' state but it doesn't because of the other UART keeping
   it high; the handler checks the next UART in its table and sees that
   UART #2 needs service, too. Now UART #1 receives another character and
   keeps the line high while UART #2 is being serviced. How should the
   handler know that this has happened? If it just issued EOI and
   returned, the IRQ line would never have gone 'low' during the service,
   so there won't be any future raising edges to be detected, and thus no
   more interrupts!
   
   What does the service routine do to avoid lock-ups? It has to mask the
   interrupt in the ICU; this resets the edge detector. If it unmasks the
   interrupt again at the end of the handler and the line is still
   'high', this will trigger the edge detector and the interrupt will be
   scheduled again. See the 'known problems' section for a very solid
   method of handling interrupts suggested by Richard Clayton.
   
   Windows allows for UARTs sharing interrupts; just make sure the COM
   ports are configured properly in the system setup.
   
   A note to Linux users: Linux is fully capable of sharing interrupts
   between serial ports if the hardware problems described above are
   solved. Using the same interrupt for several UARTs even reduces CPU
   load, so it is definitely a Good Thing as long as there are not too
   many sharers. Having a well-designed and kernel-supported multi-port
   card is even better because these cards provide a mechanism for the
   handler to detect which UART has triggered interrupt without having to
   look at every single IIR, which reduces overhead even further.
   
   
     _________________________________________________________________
   
   
   
                                 PROGRAMMING
                                       
   Now for the clickety-clickety thing. I hope you're a bit keen in
   assembler programming. Programming the UART in high level languages
   is, of course, possible, but not at very high rates. I give you
   several routines in assembler and C that do the dirty work for you.
   
   If you're keen on examples of how to program the UART in high level
   languages, even interrupt-driven, you should have a look at some code
   I received from Frank Whaley (ftp: "The_Serial_Port.more04") and at
   the "Async Routines Library" Scott A. Deming is currently developing
   (ftp: "asyam.zip").
   
   First thing to do is detect which chip is used. It shouldn't be
   difficult to convert this C function into assembler; I'll omit the
   assembly version.
   

int detect_UART(unsigned baseaddr)
{
   // this function returns 0 if no UART is installed.
   // 1: 8250, 2: 16450 or 8250 with scratch reg., 3: 16550, 4: 16550A
   int x,olddata;

   // check if a UART is present anyway
   olddata=inp(baseaddr+4);
   outp(baseaddr+4,0x10);
   if ((inp(baseaddr+6)&0xf0)) return 0;
   outp(baseaddr+4,0x1f);
   if ((inp(baseaddr+6)&0xf0)!=0xf0) return 0;
   outp(baseaddr+4,olddata);
   // next thing to do is look for the scratch register
   olddata=inp(baseaddr+7);
   outp(baseaddr+7,0x55);
   if (inp(baseaddr+7)!=0x55) return 1;
   outp(baseaddr+7,0xAA);
   if (inp(baseaddr+7)!=0xAA) return 1;
   outp(baseaddr+7,olddata); // we don't need to restore it if it's not there
   // then check if there's a FIFO
   outp(baseaddr+2,1);
   x=inp(baseaddr+2);
   // some old-fashioned software relies on this!
   outp(baseaddr+2,0x0);
   if ((x&0x80)==0) return 2;
   if ((x&0x40)==0) return 3;
   return 4;
}

   
   If it's not a 16550A, FIFO mode operation won't work, but there's no
   problem in switching it on nevertheless as long as no 16550 is used
   and your software is aware that there is no TX FIFO available (see
   below). If your software doesn't use the FIFOs explicitly, write 0x7
   to the FCR and mask bits 3, 6 & 7 of the IIR. This does not reduce
   interrupt overhead but makes transmission more reliable without
   changing anything for the software. But remember that the 16550 has a
   bug with its FIFOs (see hardware section), so if the function above
   returns 3, switch the FIFOs off.
   
   Mike Surikov has provided me with an altered version of this function
   that works correctly with multi-port serial adapters, too. It's
   available from the ftp archive mentioned at the beginning. Look for
   the file "The_Serial_Port.more03".
   
   The prototype of this useful function has also been provided by Mike
   Surikov; I've rewritten it from scratch though. It allows you to
   detect which interrupt is used by a certain UART. There is an assembly
   version of Mike's version (which can only detect intlevels 0-7) of
   this function as well. It's available from the ftp archive as "
   The_Serial_Port.more02". Here's the header of that file:
   
   
     _________________________________________________________________
   
   This file contains the assembler version of the procedure
   'detect_IRQ'. Mike Surikov wrote the C source, and I've translated it
   to assembly language. Simply adjust BASEADDR, assemble it (with MASM
   or TASM) and run the .EXE file you get.
   
   
     _________________________________________________________________
   

int detect_IRQ(unsigned base)
{
  // returns: -1 if no intlevel found, or intlevel 0-15
  char ier,mcr,imrm,imrs,maskm,masks,irqm,irqs;

  _asm cli;            // disable all CPU interrupts
  ier = inp(base+1);   // read IER
  outp(base+1,0);      // disable all UART ints
  while (!(inp(base+5)&0x20));  // wait for the THR to be empty
  mcr = inp(base+4);   // read MCR
  outp(base+4,0x0F);   // connect UART to irq line
  imrm = inp(0x21);    // read contents of master ICU mask register
  imrs = inp(0xA1);    // read contents of slave ICU mask register
  outp(0xA0,0x0A);     // next read access to 0xA0 reads out IRR
  outp(0x20,0x0A);     // next read access to 0x20 reads out IRR
  outp(base+1,2);      // let's generate interrupts...
  maskm = inp(0x20);   // this clears all bits except for the one
  masks = inp(0xA0);   // that corresponds to the int
  outp(base+1,0);      // drop the int line
  maskm &= ~inp(0x20); // this clears all bits except for the one
  masks &= ~inp(0xA0); // that corresponds to the int
  outp(base+1,2);      // and raise it again just to be sure...
  maskm &= inp(0x20);  // this clears all bits except for the one
  masks &= inp(0xA0);  // that corresponds to the int
  outp(0xA1,~masks);   // now let us unmask this interrupt only
  outp(0x21,~maskm);
  outp(0xA0,0x0C);     // enter polled mode; Mike Surikov reported
  outp(0x20,0x0C);     // that order is important with Pentium/PCI systems
  irqs = inp(0xA0);    // and accept the interrupt
  irqm = inp(0x20);
  inp(base+2);         // reset transmitter interrupt in UART
  outp(base+4,mcr);    // restore old value of MCR
  outp(base+1,ier);    // restore old value of IER
  if (masks) outp(0xA0,0x20);  // send an EOI to slave
  if (maskm) outp(0x20,0x20);  // send an EOI to master
  outp(0x21,imrm);     // restore old mask register contents
  outp(0xA1,imrs);
  _asm sti;
  if (irqs&0x80)       // slave interrupt occured
    return (irqs&0x07)+8;
  if (irqm&0x80)       // master interrupt occured
    return irqm&0x07;
  return -1;
}

   
   Now the non-interrupt version of TX and RX.
   
   Let's assume the following constants are set correctly (either by
   'CONSTANT EQU value' or by '#define CONSTANT value'). You can easily
   use variables instead, but I wanted to save the extra lines for the
   ADD commands then necessary... A cute trick for calculating I/O
   addresses in assembly programs is this: load an index register (BX,
   BP, SI, or DI) with the base address (and keep it there), then use LEA
   DX,[BX+offset] before each IN/OUT instead of MOV DX,base; ADD
   DX,offset. It saves you one or two cycles. :)
   

  UART_BASEADDR   the base address of the UART
  UART_BAUDRATE   the divisor value (eg. 12 for 9600 bps)
  UART_LCRVAL     the value to be written to the LCR (eg. 0x1b for 8e1)
  UART_FCRVAL     the value to be written to the FCR. Bit 0, 1 and 2 set,
                  bits 6 & 7 according to trigger level wished (see above).
                  0x87 is a good value, 0x7 establishes compatibility
                  (except that there are some bits to be masked in the IIR).

   
   First thing to do is initializing the UART. This works as follows:
   

UART_init proc near
  push ax  ; we are 'clean guys'
  push dx
  mov  dx,UART_BASEADDR+3  ; LCR
  mov  al,80h  ; set DLAB
  out  dx,al
  mov  dx,UART_BASEADDR    ; divisor
  mov  ax,UART_BAUDRATE
  out  dx,ax
  mov  dx,UART_BASEADDR+3  ; LCR
  mov  al,UART_LCRVAL  ; params
  out  dx,al
  mov  dx,UART_BASEADDR+4  ; MCR
  xor  ax,ax  ; clear loopback
  out  dx,al
  ;***
  pop  dx
  pop  ax
  ret
UART_init endp

void UART_init()
{
   outp(UART_BASEADDR+3,0x80);
   outpw(UART_BASEADDR,UART_BAUDRATE);
   outp(UART_BASEADDR+3,UART_LCRVAL);
   outp(UART_BASEADDR+4,0);
   //***
}

   
   If we wanted to use the FIFO functions of the 16550A, we'd have to add
   some lines to the routines above (where the ***s are). In assembler:
   

  mov  dx,UART_BASEADDR+2  ; FCR
  mov  al,UART_FCRVAL
  out  dx,al

   
   And in C:
   

   outp(UART_BASEADDR+2,UART_FCRVAL);

   
   Don't forget to disable the FIFO when your program exits! Some other
   software may rely on this!
   
   Not very complex so far, isn't it? Well, I told you so at the very
   beginning, and I wanted to start easy. Now let's send a character.
   

UART_send proc near
  ; character to be sent in AL
  push dx
  push ax
  mov  dx,UART_BASEADDR+5
us_wait:
  in   al,dx  ; wait until we are allowed to write a byte to the THR
  test al,20h
  jz   us_wait
  pop  ax
  mov  dx,UART_BASEADDR
  out  dx,al  ; then write the byte
  pop  dx
  ret
UART_send endp

void UART_send(char character)
{
   while ((inp(UART_BASEADDR+5)&0x20)==0);
   outp(UART_BASEADDR,(int)character);
}

   
   This one sends a null-terminated string.
   

UART_send_string proc near
  ; DS:SI contains a pointer to the string to be sent.
  push si
  push ax
  push dx
  cld  ; we want to read the string in its correct order
uss_loop:
  lodsb
  or   al,al  ; last character sent?
  jz   uss_end
  ;*1*
  mov  dx,UART_BASEADDR+5
  push ax
uss_wait:
  in   al,dx
  test al,20h
  jz   uss_wait
  mov  dx,UART_BASEADDR
  pop  ax
  out  dx,al
  ;*2*
  jmp  uss_loop
uss_end:
  pop  dx
  pop  ax
  pop  si
  ret
UART_send_string endp

void UART_send_string(char *string)
{
   int i;
   for (i=0; string[i]!=0; i++)
      {
      //*1*
      while ((inp(UART_BASEADDR+5)&0x20)==0);
      outp(UART_BASEADDR,(int)string[i]);
      //*2*
      }
}

   
   Of course we could have used our already programmed function/procedure
   UART_send instead of the piece of code limited by *1* and *2*, but we
   are interested in high-speed code and thus save the call/ret.
   
   It shouldn't be a hard nut for you to modify the above
   function/procedure so that it sends a block of data rather than a
   null-terminated string. I'll omit that here.
   
   Note that all these routines don't make any use of the TX FIFO! If we
   know for sure that it's a 16550A we're dealing with, and that its
   FIFOs are enabled, we could as well write up to 16 characters whenever
   bit 5 (THRE) of the LSR goes 1.
   
   Now for reception. We want to program routines that do the following:
   
     * Check if a character has been received or an error occured
     * Read a character if there's one available
       
   
   Both the C and the assembler routines return 0 (in AX) if there is
   neither an error condition nor a character available. If a character
   is available, Bit 8 is set and AL or the lower byte of the return
   value contains the character. Bit 9 is set if we lost data (overrun),
   bit 10 signals a parity error, bit 11 signals a framing error, bit 12
   shows if there is a break in the data stream and bit 15 signals if
   there are any errors in the FIFO (if we turned it on). The
   procedure/function is much smaller than this paragraph:
   

UART_get_char proc near
  push dx
  mov  dx,UART_BASEADDR+5
  in   al,dx
  xchg al,ah
  and  ax,9f00h
  test 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 simple
   polling UART software.
   
   You know the way. Go and add functions to check if a data set is
   there, then establish a connection. Don't know how? Set DTR, wait for
   DSR. If you want to send, set RTS and wait for CTS before you actually
   transmit data. You don't need to store old values of the MCR: this
   register is readable. Just read in the data, AND/OR the bits as
   required and write the byte back.
   
   Let us now write the interrupt-driven versions of the routines. This
   is going to be a bit voluminous, so I draw the scene and leave the
   painting to you. If you want to implement interrupt-driven routines in
   a C program use either the inline-assembler feature or link the
   objects together. Of course you can also program interrupts in C (or
   other languages for that matter (are there any? :)).
   
   You'll find a complete program using interrupts at the end of this
   chapter.
   
   First thing to do is initialize the UART the same way as shown above.
   But there is some more work to be done before you enable the UART
   interrupt: FIRST SET THE INTERRUPT VECTOR CORRECTLY! Use function 25h
   of the DOS interrupt 21h. Remember to store the old value (obtained by
   calling DOS interrupt 21h function 35h) and to restore this value when
   exiting to DOS again. See also the note on known bugs if you've got a
   8250.
   

UART_INT      EQU 0Ch  ; for COM2 / COM4 use 0bh
UART_ONMASK   EQU 11101111b  ; for COM2 / COM4 use 11110111b
UART_OFFMASK  EQU NOT UART_ONMASK
UART_IERVAL   EQU ?   ; replace ? by any value between 0h and 0fh
                      ; (dependent on which ints you want)
                      ; DON'T SET bit 1 now! (not with this kind of service
                      ; routine, that is)
UART_OLDVEC   DD  ?

initialize_UART_interrupt proc near
  push ds
  push es  ; first thing is to store the old interrupt
  push bx  ; vector
  mov  ax,3500h+UART_INT
  int  21h
  mov  word ptr UART_OLDVEC,bx
  mov  word ptr UART_OLDVEC+2,es
  pop  bx
  pop  es
  push cs  ; build a pointer in DS:DX
  pop  ds
  lea  dx,interrupt_service_routine
  mov  ax,2500h+UART_INT
  int  21h ; and ask DOS to set this pointer as the new interrrupt vector
  pop  ds
  mov  dx,UART_BASEADDR+4  ; MCR
  in   al,dx
  or   al,8  ; set OUT2 bit to enable interrupts
  out  dx,al
  mov  dx,UART_BASEADDR+1  ; IER
  mov  al,UART_IERVAL  ; enable the interrupts we want
  out  dx,al
  in   al,21h  ; last thing to do is unmask the int in the ICU
  and  al,UART_ONMASK
  out  21h,al
  sti  ; and free interrupts if they have been disabled
  ret
initialize_UART_interrupt endp

deinitialize_UART_interrupt proc near
  push ds
  lds  dx,UART_OLDVEC
  mov  ax,2500h+UART_INT
  int  21h
  pop  ds
  in   al,21h  ; mask the UART interrupt
  or   al,UART_OFFMASK
  out  21h,al
  mov  dx,UART_BASEADDR+1
  xor  al,al
  out  dx,al   ; clear all interrupt enable bits
  mov  dx,UART_BASEADDR+4
  out  dx,al   ; and disconnect the UART from the ICU
  ret
deinitialize_UART_interrupt endp

   
   Now the interrupt service routine. It has to follow several rules:
   first, it MUST NOT change the contents of any register of the CPU!
   Then it has to tell the ICU (did I tell you that this is the interrupt
   control unit? It is also called PIC Programmable Interrupt Controller)
   that the interrupt is being serviced. Next thing is test which part of
   the UART needs service. Let's have a look at the following procedure:
   

interupt_service_routine proc far  ; define as near if you want to link .COM
  ;*1*                             ; it doesn't matter anyway since IRET is
  push ax                          ; always a FAR command
  push cx
  push dx
  push bx
  push sp
  push bp
  push si
  push di
  ;*2*   replace the part between *1* and *2* by pusha on an 80186+ system
  push ds
  push es
  in   al,21h
  or   al,UART_OFFMASK
  out  21h,al
  mov  al,20h    ; remember: first thing to do in interrupt routines is tell
  out  20h,al    ; the ICU about the service being done. This avoids lock-up
int_loop:
  mov  dx,UART_BASEADDR+2  ; IIR
  in   al,dx  ; check IIR info
  test al,1
  jnz  int_end
  and  ax,6  ; we're interested in bit 1 & 2 (see data sheet info)
  mov  si,ax ; this is already an index! Well-devised, huh?
  call word ptr cs:int_servicetab[si]  ; ensure a near call is used...
  jmp  int_loop
int_end:
  in   al,21h
  and  al,UART_ONMASK
  out  21h,al
  pop  es
  pop  ds
  ;*3*
  pop  di
  pop  si
  pop  bp
  pop  sp
  pop  bx
  pop  dx
  pop  cx
  pop  ax
  ;*4*   *3* - *4* can be replaced by popa on an 80186+ based system
  iret
interupt_service_routine endp

   
   This is the part of the service routine that does the decisions. Now
   we need four different service routines to cover all four interrupt
   source possibilities (EVEN IF WE DIDN'T ENABLE THEM! Let's play this
   safe).
   

int_servicetab    DW int_modem, int_tx, int_rx, int_status

int_modem proc near
  mov  dx,UART_BASE+6  ; MSR
  in   al,dx
  ; do with the info what you like; probably just ignore it...
  ; but YOU MUST READ THE MSR or you'll lock up the interrupt!
  ret
int_modem endp

int_tx proc near
  ; get next byte of data from a buffer or something
  ; (remember to set the segment registers correctly!)
  ; and write it to the THR (offset 0)
  ; if no more data is to be sent, disable the THRE interrupt
  ; If the FIFOs are switched on (and you've made sure
  ; it's a 16550A!), you can write up to 16 characters

  ; end of data to be sent?
  ; no, jump to end_int_tx
  mov  dx,UART_BASEADDR+1
  in   al,dx
  and  al,00001101b
  out  dx,al
end_int_tx:
  ret
int_tx endp

int_rx proc near
  mov  dx,UART_BASEADDR
  in   al,dx
  ; do with the character what you like (best write it to a
  ; FIFO buffer [not the one of the 16550A, silly! :)])
  ; the following lines speed up FIFO mode operation
  mov  dx,UART_BASEADDR+5
  in   al,dx
  test al,1
  jnz  int_rx
  ; these lines are a cure for the well-known problem of TX interrupt
  ; lock-ups when receiving and transmitting at the same time
  test al,40h
  je   dont_unlock
  call int_tx
dont_unlock:
  ret
int_rx endp

int_status proc near
  mov  dx,UART_BASEADDR+5
  in   al,dx
  ; do what you like. It's just important to read the LSR
  ret
int_status endp

   
   How is data sent now? Write it to a FIFO buffer (that's nothing to do
   with the built-in FIFOs of the 16550!) that is read by the interrupt
   routine. Then set bit 1 of the IER and check if this has already
   started transmission. If not, you'll have to start it by hand (just
   call the int_tx routine). THIS IS DUE TO THOSE NUTTY GUYS AT BIG BLUE
   WHO DECIDED TO USE EDGE TRIGGERED INTERRUPTS INSTEAD OF PROVIDING ONE
   SINGLE FLIP FLOP FOR THE 8253/8254! See the "Known Problems" section
   for another good method of handling the UART interrupts that avoids
   all these problems.
   
   This procedure can be a C function, too. It is not time-critical at
   all.
   

  ; copy data to buffer

  mov  dx,UART_BASEADDR+1  ; IER
  in   al,dx
  or   al,2  ; set bit 1
  out  dx,al
  nop
  nop  ; give the UART some time to kick the interrupt...
  nop
  mov  dx,UART_BASEADDR+5  ; LSR
  cli  ; make sure no interrupts get in-between if not already running
  in   al,dx
  test al,40h  ; is there a transmission running?
  jz   dont_crank  ; yes, so don't mess it up
  call int_tx  ; no, crank it up
  sti
dont_crank:

   
   Well, that's it! Your main program has to take care of the buffers,
   nothing else!
   
   Remember to call deinitialize_UART_interrupt before exiting to DOS! In
   C, this can easily be done by adding the function to the at-exit list
   with the atexit() function. You won't have to worry about the myriads
   of ways your program could terminate then.
   
   For those of you who prefer learning by watching rather than learning
   by doing ("lazy" is such an ignorant word :-), here's the source of a
   small terminal program. It can be assembled with TASM or ML without
   any change. Wire together two PCs (three-wire-connection, see the
   beginning of this file) and start it on each of them. You can then
   type messages on both keyboards that can be viewed on both screens. If
   you press F1, a large string is being sent (but not displayed on the
   sender's screen). Ctrl-X terminates the program.
   
   
     _________________________________________________________________
   

  ; just a small terminal program using interrupts.
  ; It's quite dumb: it uses the BIOS for screen output
  ; and keyboard input
  ; assemble and link as .EXE (just type ml name)
  ; If you have a 16550 (not a 16550A), you may lose
  ; characters since the fifos are turned on (see "Known problems
  ; with several chips")
  ; If your BIOS locks the interrupts while scrolling (some do),
  ; you may encounter data loss at high rates.

model small
dosseg

INTNUM      equ 0Ch          ; COM1; COM2: 0Bh
OFFMASK     equ 00010000b    ; COM1; COM2: 00001000b
ONMASK      equ not OFFMASK
UART_BASE   equ 3F8h         ; COM1; COM2: 2F8h
UART_RATE   equ 12           ; 9600 bps, see table in this file
UART_PARAMS equ 00000011b    ; 8n1, see tables
RXFIFOSIZE  equ 8096         ; set this to your needs
TXFIFOSIZE  equ 8096         ; dito.
                             ; the fifos must be large on slow computers
                             ; and can be small on fast ones
                             ; These have nothing to do with the 16550A's
                             ; built-in FIFOs!

.data
long_text  db  0dh
    db  "This is a very long test string. It serves the purpose of",0dh
    db  "demonstrating that our interrupt-driven routines are capable",0dh
    db  "of coping with pressure situations like the one we provoke",0dh
    db  "by sending large bunches of characters in each direction at",0dh
    db  "the same time. Run this test by pressing F1 at a low data",0dh
    db  "rate and a high data rate to see why serial transmission and",0dh
    db  "reception should be programmed interrupt-driven. You won't lose",0dh
    db  "a single character as long as you don't overload the fifos, no",0dh
    db  "matter how hard you try!",0dh,0

ds_dgroup  macro
  mov  ax,DGROUP
  mov  ds,ax
  assume  ds:DGROUP
endm

ds_text  macro
  push  cs
  pop   ds
  assume  ds:_TEXT
endm

rx_checkwrap  macro
  local rx_nowrap
  cmp  si,offset rxfifo+RXFIFOSIZE
  jb  rx_nowrap
  lea  si,rxfifo
rx_nowrap:
endm

tx_checkwrap  macro
  local tx_nowrap
  cmp  si,offset txfifo+TXFIFOSIZE
  jb  tx_nowrap
  lea  si,txfifo
tx_nowrap:
endm

.stack 256

.data?
old_intptr  dd  ?
rxhead      dw  ?
rxtail      dw  ?
txhead      dw  ?
txtail      dw  ?
bitxfifo    dw  1  ; size of built-in TX fifo (1 if no fifo)
rxfifo      db  RXFIFOSIZE dup (?)
txfifo      db  TXFIFOSIZE dup (?)

.code
start  proc far
  call  install_interrupt_handler
  call  clear_fifos
  call  clear_screen
  call  init_UART
continue:
  call  read_RX_fifo
  call  read_keyboard
  jnc  continue
  call  clean_up
  mov  ax,4c00h
  int  21h  ; return to DOS
start  endp

interrupt_handler  proc far
  assume  ds:nothing,es:nothing,ss:nothing,cs:_text
  push  ax
  push  cx
  push  dx  ; first save the regs we need to change
  push  ds
  push  si
  in  al,21h
  or  al,OFFMASK   ; disarm the interrupt
  out 21h,al
  mov  al,20h  ; acknowledge interrupt
  out  20h,al

ih_continue:
  mov  dx,UART_BASE+2
  xor  ax,ax
  in  al,dx  ; get interrupt cause
  test  al,1  ; did the UART generate the int?
  jne  ih_sep  ; no, then it's somebody else's problem
  and  al,6  ; mask bits not needed
  mov  si,ax  ; make a pointer out of it
  call  interrupt_table[si]  ; serve this int
  jmp  ih_continue  ; and look for more things to be done
ih_sep:

  in  al,21h
  and al,ONMASK  ; rearm the interrupt
  out 21h,al

  pop  si
  pop  ds
  pop  dx  ; restore regs
  pop  cx
  pop  ax
  iret
interrupt_table  dw  int_modem,int_tx,int_rx,int_status
interrupt_handler  endp

int_modem  proc near
  ; just clear modem status, we are not interested in it
  mov  dx,UART_BASE+6
  in  al,dx
  ret
int_modem  endp

int_tx  proc near
  ds_dgroup
  ; check if there's something to be sent
  mov  si,txtail
  mov  cx,bitxfifo
itx_more:
  cmp  si,txhead
  je  itx_nothing
  cld
  lodsb
  mov  dx,UART_BASE
  out  dx,al  ; write it to the THR
  ; check for wrap-around in our fifo
  tx_checkwrap
  ; send as much bytes as the chip can take when available
  loop itx_more
  jmp  itx_dontstop
itx_nothing:
  ; no more data in the fifo, so inhibit TX interrupts
  mov  dx,UART_BASE+1
  mov  al,00000001b
  out  dx,al
itx_dontstop:
  mov  txtail,si
  ret
int_tx  endp

int_rx  proc near
  ds_dgroup
  mov  si,rxhead
irx_more:
  mov  dx,UART_BASE
  in  al,dx
  mov  byte ptr [si],al
  inc  si
  ; check for wrap-around
  rx_checkwrap
  ; see if there are more bytes to be read
  mov  dx,UART_BASE+5
  in  al,dx
  test  al,1
  jne  irx_more
  mov  rxhead,si
  test  al,40h  ; Sometimes when sending and receiving at the
  jne  int_tx   ; same time, TX ints get lost. This is a cure.
  ret
int_rx  endp

int_status  proc near
  ; just clear the status ("this trivial task is left as an exercise
  ; to the student")
  mov  dx,UART_BASE+5
  in  al,dx
  ret
int_status  endp

read_RX_fifo  proc near
  ; see if there are bytes to be read from the fifo
  ; we read a maximum of 16 bytes, then return in order
  ; not to break keyboard control
  ds_dgroup
  cld
  mov  cx,16
  mov  si,rxtail
rx_more:
  cmp  si,rxhead
  je  rx_nodata
  lodsb
  call  output_char
  ; check for wrap-around
  rx_checkwrap
  loop  rx_more
rx_nodata:
  mov  rxtail,si
  ret
read_RX_fifo  endp

read_keyboard  proc near
  ds_dgroup
  ; check for keys pressed
  mov  ah,1
  int  16h
  je  rk_nokey
  xor  ax,ax
  int  16h
  cmp  ax,2d18h  ; is it Ctrl-X?
  stc
  je  rk_ctrlx
  cmp  ax,3b00h  ; is it F1?
  jne  rk_nf1
  lea  si,long_text  ; send a very long test string
  call  send_string
  jmp  rk_nokey
rk_nf1:
  ; echo the character to the screen
  call  output_char

  call  send_char
rk_nokey:
  clc
rk_ctrlx:
  ret
read_keyboard  endp


install_interrupt_handler  proc near
  ds_dgroup
  ; install interrupt handler first
  mov  ax,3500h+INTNUM
  int  21h
  mov  word ptr old_intptr,bx
  mov  word ptr old_intptr+2,es
  mov  ax,2500h+INTNUM
  ds_text
  lea  dx,interrupt_handler
  int  21h
  ret
install_interrupt_handler  endp

clear_fifos  proc near
  ds_dgroup
  ; clear fifos (not those in the 16550A, but ours)
  lea  ax,rxfifo
  mov  rxhead,ax
  mov  rxtail,ax
  lea  ax,txfifo
  mov  txhead,ax
  mov  txtail,ax
  ret
clear_fifos  endp

init_UART  proc near
  ; initialize the UART
  mov  dx,UART_BASE+3
  mov  al,80h
  out  dx,al  ; make DL register accessible
  mov  dx,UART_BASE
  mov  ax,UART_RATE
  out  dx,ax  ; write bps rate divisor
  mov  dx,UART_BASE+3
  mov  al,UART_PARAMS
  out  dx,al  ; write parameters

  ; is it a 16550A?
  mov  dx,UART_BASE+2
  in   al,dx
  and  al,11000000b
  cmp  al,11000000b
  jne  iu_nofifos
  mov  bitxfifo,16
  mov  dx,UART_BASE+2
  mov  al,11000111b
  out  dx,al  ; clear and enable the fifos if they exist
iu_nofifos:
  mov  dx,UART_BASE+1
  mov  al,00000001b  ; allow RX interrupts
  out  dx,al
  mov  dx,UART_BASE
  in  al,dx  ; clear receiver
  mov  dx,UART_BASE+5
  in  al,dx  ; clear line status
  inc  dx
  in  al,dx  ; clear modem status
  ; free interrupt in the ICU
  in  al,21h
  and  al,ONMASK
  out  21h,al
  ; and enable ints from the UART
  mov  dx,UART_BASE+4
  mov  al,00001000b
  out  dx,al
  ret
init_UART  endp

clear_screen  proc near
  mov  ah,0fh  ; allow all kinds of video adapters to be used
  int  10h
  cmp  al,7
  je  cs_1
  mov  al,3
cs_1:
  xor  ah,ah
  int  10h
  ret
clear_screen  endp

clean_up  proc near
  ds_dgroup
  ; lock int in the ICU
  in  al,21h
  or  al,OFFMASK
  out  21h,al
  xor  ax,ax
  mov  dx,UART_BASE+4  ; disconnect the UART from the int line
  out  dx,al
  mov  dx,UART_BASE+1  ; disable UART ints
  out  dx,al
  mov  dx,UART_BASE+2  ; disable the fifos (old software relies on it)
  out  dx,al
  ; restore int vector
  lds  dx,old_intptr
  mov  ax,2500h+INTNUM
  int  21h
  ret
clean_up  endp

output_char  proc near
  push  si
  push  ax
oc_cr:
  push  ax
  mov  ah,0eh  ; output character using BIOS TTY
  int  10h     ; it's your task to improve this
  pop  ax
  cmp  al,0dh  ; add LF after CR; change it if you don't like it
  mov  al,0ah
  je  oc_cr
  pop  ax
  pop  si
  ret
output_char  endp

send_char proc near
  push  si
  push  ax
  ds_dgroup
  pop  ax
  mov  si,txhead
  mov  byte ptr [si],al
  inc  si
  ; check for wrap-around
  tx_checkwrap
  mov  txhead,si
  ; test if the interrupt is running at the moment
  mov  dx,UART_BASE+5
  in  al,dx
  test  al,40h
  je  sc_dontcrank
  ; crank it up
  ; note that this might not work with some very old 8250s
  mov  dx,UART_BASE+1
  mov  al,00000011b
  out  dx,al
sc_dontcrank:
  pop  si
  ret
send_char  endp

send_string  proc near
  ; sends a null-terminated string pointed at by DS:SI
  ds_dgroup
  cld
ss_more:
  lodsb
  or  al,al
  je  ss_end
  call send_char
  jmp  ss_more
ss_end:
  ret
send_string  endp

end start

   
     _________________________________________________________________
   
   Stephen Warner provided me with an assembly source of a TSR program
   that puts every character it receives from the serial port in the
   keyboard buffer. This allows to remotely control nearly every other
   program; it works with ATs and higher computers only. I decided not to
   add it to this file since it doesn't show anything about programming
   the serial port that's not already covered by other listings in this
   file.
   
   If you are interested in it, you can obtain it from the ftp archive
   (it is named "The_Serial_Port.more01"). See the beginning of this
   file.
   
   One more thing: always remember that at 115,200 bps there is service
   to be done at least every 85 microseconds! On an XT with 4.77 MHz this
   is about 40 assembler commands! So forget about servicing the serial
   port at this rate in high-level languages on such computers. Using a
   16550A is strongly recommended at high rates (turn on FIFOs) but not
   necessary with otherwise decent hardware.
   
   The interrupt service routines can be accelerated by not pushing that
   much registers, and pusha and popa are fast replacements for 8 other
   pushs/pops.
   
   Well, that's the end of my short :-) summary. Don't hesitate to
   correct me if I'm wrong (preferably via email) in the details (I hope
   not, but it's not easy to find typographical and other errors in a
   text that you've written yourself). And please help me to complete
   this file! If you've got anything to add, email it to me and I'll
   spread it round.
   
   I've received a lot of feedback from you, and I'd like to thank
   everybody who encouraged me to continue the work on this file.
   
   Yours
   Chris
   
   P.S. You surely have noticed that English isn't my native tongue... so
   please excuse everything that's not pleasant for the eye, or, even
   better, tell me about it! It shouldn't be an ordeal though, at least
   some have assured me so...
   
   
     _________________________________________________________________
   
    This article compiled by <[email protected]>. The most recent
    version is available on the WWW server
    http://www.paranoia.com/~filipg [Copyright] [Disclaimer]