Structured Programming

Jeff continues his examination of UARTs and explains how to detect the presence of a serial port.


July 01, 1991
URL:http://www.drdobbs.com/architecture-and-design/structured-programming/184408589

Figure 1


Copyright © 1991, Dr. Dobb's Journal

Figure 1


Copyright © 1991, Dr. Dobb's Journal

Figure 2


Copyright © 1991, Dr. Dobb's Journal

Figure 3


Copyright © 1991, Dr. Dobb's Journal

Figure 3


Copyright © 1991, Dr. Dobb's Journal

Figure 4


Copyright © 1991, Dr. Dobb's Journal

Figure 1


Copyright © 1991, Dr. Dobb's Journal

Figure 3


Copyright © 1991, Dr. Dobb's Journal

JUL91: STRUCTURED PROGRAMMING

STRUCTURED PROGRAMMING

The Chip Is Bad!

Jeff Duntemann, KG7JF

Given the nature and quality of our software, it's not especially surprising that we put such faith in the quality of our hardware. When things go sour, we begin with a very strong assumption that we futzed a pointer dereference somewhere rather than having been the innocent victim of a sizzled memory chip.

Part of this is the mass-production commodity nature of PC components, which makes them tremendously reliable relative to how things were 15 years ago. Besides (as we can always fall back to insisting) software is an art, whereas hardware is simply a science.

Hardware used to be an art too. I wire-wrapped my first computer on a piece of surplus perfboard that had been pulled out of a military god-knows-what and sold by the pound at the Santa Fe Hamfest. It was based on a bizarre CPU from RCA called the CDP 1802. I used surplus toggle switches for input and surplus LEDs for a display, and I will admit right here that I did it wrong, in a truly spectacular fashion. For while the machine seemed to be fully functional (that is, nothing caught fire when I turned power on) it wouldn't execute the little 40-byte machine-code programs I laboriously toggled in.

I toggled in progressively shorter test programs, none of which worked, until I was down to a program consisting of a single opcode (7BH) which on the 1802 switched a dedicated serial output pin high. No deal. The output pin stayed low. The CPU chip was obviously bad. I bought another, swapped CPUs, and tried again. No dice. I even bought a third (silly boy!) before I realized that something else was to blame.

All chips in the minimachine were, in fact, good. What I had done was this: I wired the toggle switches upside down, so that an up switch sent a 0-bit to the CPU rather than a 1-bit. However, the LEDs were showing correct binary patterns because I had mistakenly used hex inverter chips rather than hex driver chips to drive the LEDs. The switches were inverting bits sent to the CPU, and the inverters were deinverting the bits before sending them to the LEDs. So while the toggles and the LEDs said 7BH, what the CPU was getting was 84H, which (if I remember correctly) was the second half of an ADD instruction.

Aren't you glad that hardware isn't an art anymore? Lordy, I sure am.

When Chips Really are Bad

I was pretty surprised to hear that many thousands (no one will admit to quite how many) of IBM PS/2 machines were built with a defective UART chip. The chip in question is National's 16550, which supposedly had FIFO buffers built in on both transmit and receive. Trouble was, the FIFOs didn't work. We're not talking static damage here, nor bad fabrication. The bug was right in there at the mask level. National soon issued the 16550A, which had fully functional FIFOs (sorta sings, doesn't it?) but that left large numbers of 16550s in unsuspecting hands.

The fallout from this little embarrassment is that nobody really incorporates FIFO support in their comm software for fear of running on machines that contain the bum UART. This is dumb. Software should be soft -- and if there's a way of detecting a hardware feature reliably, you're giving up a competitive edge if you don't use it.

This is yet another reason to heed the lesson of the fallen Viking: Because if you know the hardware intimately, you can tell missing features from existing features, and good chips from the bad. The C guys have known this for years. I have some hope that the Pascal gang will come along in time.

A little later in this column I'll explain how to detect the presence of a serial port, and determine exactly which UART chip is installed at any given port. It's remarkably easy -- but ya gotta know the hardware!

For now, it's back to the UART registers.

UART Registers

Modem Control Register (MCR) -- this register gathers several miscellaneous modem control functions into one place. (See Figure 1.) There are two output pins (apart from the data pin) on the RS-232C port. Data Terminal Ready (DTR) is asserted by setting MCR bit 0 to 1. DTR basically means that the modem is ready to talk to whatever is on the other end of the line, and once the conversation has started, DTA must remain asserted. Drop DTR (that is, set bit 0 of MCR to 0) and the connection between the modem and its opposite number will be broken.

A 1-bit placed in MCR bit 1 asserts the Request To Send (RTS) line, which is a holdover from the days when modems were "half-duplex" and data could move in only one direction at a time down the line. The line had to be "turned around" after each end finished talking, and RTS was one of two RS-232C pins that handled the turnaround protocol. (The other, an input pin, is Clear To Send.) In these full-duplex days, RTS is typically asserted when you begin communication and simply left asserted until the connection is broken. You really only need to fool with it intelligently if you must support half-duplex operation.

There are two undedicated output pins on the PC's UART chip. These two pins allow hardware designers to build special functions into the hardware that are controllable from software through the MCR. Writing a 0 or 1 to the OUT1 and OUT2 bits just passes those logical states to those general-purpose output pins.

OUT1 has no standard use on the PC, but keep in mind that any given serial board or internal modem board may make use of it, and those uses are probably different from board to board. The Hayes internal modems, for example, use the OUT1 bit (and pin) to do a hardware reset on the entire modem board. In IBM-standard PC serial adapters, OUT2 is used to enable and disable the interrupt machinery on the serial adapter as a whole. OUT2 may be thought of as a gate placed between the UART and the interrupt machinery inside the PC. In other words, before you can do anything with interrupts at all, you must set OUT2 to 1. Also, when the UART is reset, (a hardware operation through a pin on the UART chip) OUT2 reverts to its default state of 0, disabling adapter interrupts.

Bit 4 initiates a sort of UART self-test called "loopback," in which UART inputs are connected to UART outputs and software can determine whether the UART is working internally. Attempting the loopback test can also detect with great reliability whether a UART is present at all. More on this a little later.

The MCR bit fields are summarized in Figure 1.

Line Status Register (LSR) The name of LSR is a little misleading--we're not really talking about the line here. The status reflected in LSR is the status of the UART machinery that converts bytes to outbound streams of serial bits and inbound streams of serial bits to whole bytes. Both successes and failures are reflected in LSR. See Figure 2.

The Data Ready (DR) field, bit 0, is useful mostly for polled communications, as I presented in the POLL-TERM.PAS program two columns ago. DR goes to 1 when a completed incoming character is available in the Receive Buffer Register, RBR. Polling DR isn't how you do things in an interrupt-driven environment, as I'll explain in time.

The Overrun Error (OE) field, bit 1, goes to 1 if the CPU fails to read a completed character from RBR before the next character comes in and overwrites RBR. One character "overruns" the next, destroying it -- and this flag is the only way you'll know.

The Parity Error (PE) bit, bit 2, goes to 1 if the incoming character does not reflect the parity bit requirements set up in bits 3-5 of the Line Control Register. in bits 3-5 of the Line Control Register. This is part of the UART's primitive built-in character-by-character error detection, and is not used much anymore. A noise pulse blasting one of the bits of an incoming character will also change that character's parity, causing an error to be flagged in PE.

Block-by-block error detection (through protocols such as XModem or KERMIT) is by far the better way to go.

Bit 3, the Framing Error (FE) bit, goes to 1 if the UART does not correctly receive the incoming character's stop bit. Framing errors happen when something stops a character halfway through. The UART loses track of the number of incoming bits when it can't frame the incoming character between start and stop bits.

The most common way framing errors happen is for the remote system to send a break signal down the line. A break signal is simply holding the communications line to a steady space state for longer than the duration of a single character at the current baud rate. The remote system can send a break signal as a way to get your system's attention right now. Conceptually, it's an interrupt that works across the communications line.

The Break Interrupt (BI) bit of LSR (bit 4) goes to 1 when the UART detects that the remote system has held the line to a space for longer than one character time.

Oddly, although DR, OE, PE, and FE can trigger a system interrupt when they go to 1, BI cannot. An incoming break signal, however, will cause a framing error, and if line control status interrupts are enabled, a framing error will trigger an interrupt, and an interrupt service routine can check the BI flag to see if the framing error was in fact caused by a break signal.

You should keep in mind that not all framing errors are necessarily caused by an incoming break signal. Line connection problems or noise pulses can cause framing errors as well. To be sure that a framing error is caused by a break signal, you must check BI!

I'll go into more detail on the enabling of line status interrupts when we take up interrupts in a future column.

Bit 5 of LSR, Transmit Holding Register Empty, (THRE) goes to a 1 when the UART has emptied the Transmit Holding Register by starting to transmit the byte previously in the register. This bit in a one-state means that you may load the next byte into the UART for transmission.

Don't confuse THRE with bit 6 of LSR, the Transmitter Empty bit, TE. THRE and TE are related, but there is a critical difference: THRE indicates when it is safe to load the next character into the UART. TE goes to 1 when the character being transmitted has been fully serialized and sent out of the UART.

THRE and TE report the status of two different components of the UART. The transmit holding register monitored by THRE is just that: A place to hold a character pending the start of its transmission. The TE bit reports the condition of the UART's internal shift register that bumps bits off the end of the transmitted byte to send them down the line, nose-to-tail. A subtlety to be remembered is that TE also keeps an eye on the transmit holding register. When the shift register and the transmit holding register are empty, TE goes to 1.

In other words, THRE tells you when it is safe to load the next character in for transmission. TE tells you when the last character is out the door and that the UART is truly and fully idle. Check this bit before shutting down the UART -- you don't want to leave any loose bits rattling around inside the serial port!

Modem Status Register (MSR) reports the status of four of the RS-232C input signal lines present at the business end of the serial port. These are the Clear To Send (CTS) and the Data Set Ready (DSR) lines mentioned earlier; the Ring Indicator (RI) line, which goes to a one-state when the line is being energized with the phone company's ring signal; and the Data Carrier Detect (DCD) line, which goes to a one-state when the serial port detects a data carrier coming in from the remote system.

MSR tells you two things: The current state of these four lines right now, and whether or not any of the four lines have changed since the last time you read MSR. Figure 3 should make this clearer. The four high bits of MSR report the current state of the modem input pins, and the low four bits report whether those bits have changed since the last time MSR was polled.

The "delta" names shown in Figure 3 just indicate that something has changed. (From delta, the engineer's symbol for change.) Only Trailing Edge of Ring Indicator (TERI) bears further explanation: The TERI bit goes high when the ring signal has stopped. (That is, when the serial port has detected the "trailing edge" of the ring signal, looking upon the signal as a trace on an oscilloscope.) RI, however, reflects the instantaneous state of the ring signal: 1 if present, 0 if not. TERI indicates that a full ring signal has come and gone since the last poll of MSR. This allows you to count full rings without having to constantly poll RI to determine when a ring has completed.

Reading MSR clears bits 0-3, and the bits will stay cleared until one of the signal lines changes. You can poll MSR to see if the input signals have changed, but you can also configure the UART to generate an interrupt when any of the "delta" bits change. This makes it possible to let an interrupt service routine count incoming rings and update a counter for you, allowing your software to answer an incoming call only after a preset number of rings.

Again, I'll have more to say about MSR once we get into the interrupt side of things.

Scratch Register (SCR) at offset 7 from the port base is just that: A 1-byte holding register that will hold any byte-sized value as long as you leave it there, ready to be read back any time. SCR doesn't control anything. It's just a handy pocket to tuck something in for the time being.

Actually, what SCR does best is indicate whether or not the simplest PC UART chip, the 8250, is present in a machine, as I'll explain shortly.

Detecting Serial Ports

That's the register set in some detail. I haven't explained the interrupt-related bits thoroughly, nor the FIFO status/ control bits. We'll get to those in more detail in future columns, when I explain how to create serial port interrupt support and how to use the FIFOs. (One can't explain everything in 3500 words!)

Knowing the register set thoroughly suggest certain highly useful tricks. Perhaps the most useful of any is simply a means of knowing whether there actually is a UART installed at either of the two standard port locations (COM1 and COM2) or the pseudo-standard port locations (COM3 or COM4.)

Detecting a device that contains I/O ports is usually a matter of reading some predictable value from one of the ports. There are devices that identify themselves when polled through their I/O ports, but UARTs are not among them. What you should then do is see if there's any sort of register in the device that can be written to through an I/O port and then read back. If what you read back from the port is what you had written out, you've got a device on the end of your string.

What happens if you read an I/O port that doesn't exist? Nothing ugly, at least. Typically, in modern PC designs, you'll get $FF. There are no guarantees, however. I've seen at least one (an ancient clone long extinct) that returned $00.

Reading back a value written to a register is a reliable indicator that something is out there. But how do you tell if that something is indeed a UART and not some weird, early-vintage or custom infrared coffee pot controller interface board? "Standards" are scant promise that nothing other than a UART will ever be found at port $03E8. Far better than simply writing out and reading back a value is to trigger some sort of response that could only be attributed to the exact device you're expecting.

That's what I went looking for through the forest of registers in the standard PC UART chips. I found it in the UART loopback test.

Looping Back

The loopback test is an intriguing built-in feature present in all National Semi-Conductor UARTs found in PCs. It's a self-diagnostic that, when enabled, throws internal switches that route out-bound characters back into the RBR without ever leaving the chip. Write a character to the THR and that character will immediately appear in the RBR... if the UART is correctly connected and fully functional.

One way to look for a UART, then, is to throw the UART into loopback test node, and then write a character to the THR. If the character can then be read back from RBR, a UART is present.

This works. The trouble is, RBR and THR exist at the same I/O port address, offset 0 from the serial port base I/O address. Writing a value to RBR and reading it back through THR is thus no different from writing a value to any read/write register and getting it back again.

I wanted something a little more specific to UARTs. Fortunately, the loop-back test does more than just short-circuit the UART's internal data path. With loopback enabled, the UART takes the four RS-232C input control signals and connects them internally to the four RS-232C output signals, in this fashion:

CTS to RTS 
DSR to DTR 
RI to OUT1 
DCD to OUT2

If you write a bit pattern to the output signal bits, that bit pattern will be turned around and will be placed on the input signal bits. And this time, the inputs and outputs are in different registers. The output signal bits are in the Modem Control Register (MCR), and the input signal bits are in the Modem Status Register, (MSR). Writing a value to one register and getting that value back from an entirely different register is much more characteristic of a specific device.

But wait... it gets better. In the process, the loopback test reverses two of the signal bits, so that a binary pattern written to MCR will be subtly but predictably changed in passing through the UART to emerge at MSR.

I've laid it out in Figure 4. The full bit-field layouts of MCR and MSR are shown inFigure 1 and Figure 3. After putting the UART into loopback test mode, you can write a distinctive nybble (I've chosen $A) to the low four bits of MCR. The four bits will be passed through to the high four bits of MSR, except that the least significant two bits will be interchanged, as shown in the figure.

The total effect is that a $0A byte written to the port at offset four can immediately be read back from the port at offset 6, but as the value $90. This is mighty distinctive behavior, and I seriously doubt that any other device than a National-based UART will satisfy this kind of test.

The code implementing the test algorithm is shown in the DetectComPort function in Listing One (page 153). There's not much to it: Throw the UART into loopback mode, write $0A to MCR, read the value from MSR, mask off the low four bits, and see if what's left amounts to $90. If so you have a UART at the specified port base. If not, no UART. (The rest is housekeeping.) I've run this test on a couple of dozen PCs over the last month and it pegged every port, every time.

Looking for Mr. Badchip

I mentioned earlier that not all UARTs were created equal. Some were created rather badly, in fact. There are primitive UARTs, middling UARTs, and advanced UARTs. To make the most of the hardware on which your code is running, you need to be able to tell which is which. Again, understanding the hardware at a very detailed level is the key.

The original 8-bit IBM PC and XT machines supported an add-in serial port board containing National's 8250B UART. The 8250 works well. It lacks the scratch register at offset 7 from the base I/O address, nor does it have the internal FIFO buffers of its successor UART models.

The AT-class PCs used a 16-bit add-in serial port board built around National's next-generation UART, the 16450. Its major differences over the 8250B are the scratch register and the ability to work in faster systems and support higher baud rates.

IBM's PS/2 machines originally appeared with National's subsequent UART, the 16550. The 16550, as I mentioned at the start of this column, was supposed to contain 16-character deep FIFO buffers for both the transmit and receive sides. The FIFOs were in the chip somewhere, but they didn't work correctly. National quietly retired the 16550 for the corrected 16550A, but not before an untold number of 16550 chips went out in early PS/2 models like the 50, 60, and the 16MHz 80. (The 20MHz Model 80 contains the 16550A.)

Actually, telling the UART models apart (once you know a National UART exists at a specific port base address) is easier than detecting a UART's presence to begin with.

You begin by writing a distinctive value to the scratch register at base offset 7, and attempting to read it back. If you don't get back what you wrote, you have an 8250B, period. The rest of the test involves whether the FIFO registers exist in the UART, and whether or not the FIFOs work correctly.

Enabling the FIFOs is done by setting bit 0 of the FIFO Control Register (FCR) at offset 2 from the port base. Only the 16550 and the 16550A even have a FIFO Control Register. Writing to the FCR on a 16450 has no effect whatsoever. However, on the 16550 and 16550A, enabling the FIFOs affects two status bits at bits 6 and 7 of the Interrupt Identification Register, IIR. These two bits reflect the status of the send and receive FIFO buffers. If both FIFOs are enabled, both FIFO status bits should be set to 1.

In the 16550A, this is the case. Enable FIFOs on the 16550A, and bits 6 and 7 of IIR immediately go to 1. However, part of the nature of the 16550's internal bug is that only bit 7 goes to 1 when the FIFOs are enabled. (It was a relatively cooperative bug, as bugs go).

So telling the 16450, 16550, and 16550A apart devolves to a CASE statement on bits 6 and 7 of IIR. If both bits are 0, you have a 16450. If bit 6 is 0 and bit 7 is 1, you have a 16550. If bits 6 and 7 are both 1, you have a 16550A.

The Example Program

Listing One, LOOPTEST.PAS, is a simple UART detector utility containing two functions. DetectComPort returns a Boolean value indicating whether or not a UART is present at COM port 1 - 4. Keep in mind that COM3 and COM4 were not originally defined by IBM and are not strong standards. They may exist at I/O base addresses other than $03E8 and $02E8. If LOOP TEST doesn't detect your COM3 or COM4 ports, this is probably the reason.

The other function, DetectUARTType, returns a code from 0 - 4 depending on what National Semiconductor UART model is found. Again, you must pass the port number (1- 4) to DetectUART-Type, and the function assumes that a UART is present at that port number. Turn DetectUARTType loose on a port number at which no UART is installed, and it may tell you an 8250B is there. Check first!

So far, LOOPTEST has reported correctly on all machines available to me for testing. If you come across a serial port setup that fools it, do let me know.

And next time, it's interrupts fersure.


_STRUCTURED PROGRAMMING COLUMN_
by Jeff Duntemann


[LISTING ONE]


PROGRAM LoopTest;    { From "Structured Programming" DDJ 7/91 }
CONST
  UART : ARRAY[0..4] OF STRING =
         (' faulty','n 8250',' 16450',' 16550',' 16550A');
VAR
  PortNum : Byte;
{-----------------------------------------------------------------}
{ FUNCTION DetectComPort  by Jeff Duntemann                       }
{ This function returns a Boolean value indicating whether or not }
{ a National Semiconductor UART (one of 8250B, 16450, 16550, or   }
{ 16550A) is present at the COM port passed in the PortNumber     }
{ parameter.  Don't run this function on serial ports that may be }
{ operating in some sort of background mode; it will probably     }
{ disrupt any communication stream currently in progress.         }
{-----------------------------------------------------------------}
FUNCTION DetectComPort(PortNumber : Integer) : Boolean;
CONST
  LOOPBIT = $10;
  PortBases : ARRAY[1..4] OF Integer = ($03F8,$02F8,$03E8,$02E8);
                                       { COM1  COM2  COM3  COM4 }
VAR
  Holder,HoldMCR,HoldMSR : Byte;
  MCRPort,MSRPort,THRPort,RBRPort : Integer;
BEGIN
  { Calculate port numbers for the port being looked for: }
  RBRPort := PortBases[PortNumber];  { RBR is at the base address }
  THRPort := RBRPort;            { RBR and THR have same I/O address }
  MCRPort := RBRPort + 4;        { MCR is at offset 4 }
  MSRPort := RBRPort + 6;        { MSR is at offset 6 }
  { Put the UART into loopback test mode: }
  HoldMCR := Port[MCRPort];             { Save existing value of MCR }
  Port[MCRPort] := HoldMCR OR LOOPBIT;  { Turn on loopback test mode }
  HoldMSR := Port[MSRPort];
  Port[MCRPort] := $0A OR LOOPBIT; { Put pattern to low 4 bits of MCR   }
                                   {   without disabling loopback mode  }
  Holder := Port[MSRPort] AND $F0; { Read pattern from hi 4 bits of MSR }
  IF Holder = $90 THEN    { The $A pattern is changed to $9 inside UART }
    DetectComPort := True
  ELSE
    DetectComPort := False;
  { Restore previous contents of MSR: }
  Port[MSRPort] := HoldMSR;
  { Take the UART out of loopback mode & restore old state of MCR: }
  Port[MCRPort] := HoldMCR AND (NOT LOOPBIT);
END;
{-----------------------------------------------------------------}
{ FUNCTION DetectUARTType  by Jeff Duntemann                      }
{ This function returns a numeric code indicating which UART chip }
{ is present at the selected PortNumber (1-4.)  The UART codes    }
{ returned are as follows:                                        }
{   0 : Error; bad UART or no UART at COMn, where n=PortNumber    }
{   1 : 8250; generally (but not always!) present in PC or XT     }
{   2 : 16450; generally present in AT-class machines             }
{   3 : 16550; in PS/2 mod 50/60/early 80.  FIFOs don't work!     }
{   4 : 16550A; in later PS/2's.  FIFOs fully operative.          }
{ NOTE: This routine assumes a UART is "out there" at the port #  }
{ specified.  Run DetectComPort first to make sure port is there! }
{-----------------------------------------------------------------}
FUNCTION DetectUARTType(PortNumber : Integer) : Integer;
CONST
  PortBases : ARRAY[1..4] OF Integer = ($03F8,$02F8,$03E8,$02E8);
                                       { COM1  COM2  COM3  COM4 }
VAR
  ScratchPort,IIRPort,FCRPort : Integer;
  Holder : Byte;
BEGIN
  { The scratch register is at offset 7 from the comm port base: }
  ScratchPort := PortBases[PortNumber] + 7;
  FCRPort     := PortBases[PortNumber] + 2;
  IIRPort     := FCRPort;       { IIR and FCR are at same offset }
  Port[ScratchPort] := $AA;      { Write pattern to the scratch register }
  IF Port[ScratchPort] <> $AA THEN          { Attempt to read it back... }
    DetectUARTType := 1   { A UART without a scratch register is an 8250 }
  ELSE
    BEGIN      { Now we have to test among the 16450, 16550, and 16550A  }
      Port[FCRPort] := $01;  { Setting FCR bit 0 on 16550 enables FIFOs  }
      Holder := Port[IIRPort] AND $C0;  { Read back to FIFO status bits  }
      CASE Holder OF
        $C0 : DetectUARTType := 4; { Bits 6 & 7 both set = 16550A        }
        $80 : DetectUARTType := 3; { Bit 7 set & bit 6 cleared = 16550   }
        $00 : DetectUARTType := 2; { Neither bit set = 16450             }
        ELSE DetectUARTType := 0;  { Error condition                     }
      END; {CASE}
      Port[FCRPort] := $00;        { Don't leave the FIFOs enabled! }
    END;
END;

BEGIN
  FOR PortNum := 1 TO 4 DO
    BEGIN
      IF DetectComPort(PortNum) THEN
        BEGIN
          Write  ('Port COM',PortNum,' is present,');
          Writeln(' using a',UART[DetectUARTType(PortNum)],' UART.');
        END
      ELSE
        Writeln('Port COM',Portnum,' is not present.');
    END;
END.


Copyright © 1991, Dr. Dobb's Journal

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.