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