Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Design

Structured Programming


MAY91: STRUCTURED PROGRAMMING

I discovered CB radio in 1972, when it was still innocent and fairly polite, and gas cost 32 cents a gallon. My first radio was an ancient, dented Viking Messenger I, with a slightly rusty all-steel case that was full of tubes, lordy, and a vibrator that buzzed like a swarm of killer bees. It was the best radio that $25 could buy, however, and I discovered to my delight that by diddling the pi-network loading coil a little bit one could coax eight or nine watts out of it, while my less technical friends had to content themselves with five.

All that summer I called myself Shakespeare, cruising the Northwest Side of Chicago in my Chevelle, discussing e. e. cummings with some guy in Norwood Park who called himself Theater Nut, with occasional side comments by a sultry-voiced vixen known only as Legs. (After some difficulty and a little triangulation we located Legs' 10-20 and discovered that she had just turned 13. Ahh, radio: The Great Equalizer!)

I went off the air abruptly one night, while trying to rig the Viking for inside operation. It was a small matter of attaching an ordinary 117V line cord to a nine-pin connector on the back panel. Piece of cake for an old electron-jockey like me -- except that I counted the pins counterclockwise rather than clockwise, and instead of connecting the 117V line to the power transformer input, I connected it across the 12V tube filament feed. The instant I plugged it in, all 14 of the Viking's tubes glowed bright blue for a glorious moment and then went off to join Colonel Armstrong in the Great Beyond.

It took me ten months to round up replacement tubes for the Viking, and in the meantime the Arabs got mean, the truckers went on strike, gas went through the roof, and CB hit the big time. When I got back on the air, I politely asked after Theater Nut, but what I heard instead was "HOWBOUT THAT TENNESSEE TOILET FLUSHER! THIS HYAR'S THE HILLBILLY BUSHWHACKER! YOU OAN THE CHANNEL, COME OAN!"

And that's the only part I can print. I finally ran into Theater Nut at Superdawg, to discover that he had sold his radio in disgust and was returning to grad school. He listened to my tale of the fallen Viking with amusement. "Helluva way to get to know your hardware, Jeff."

Amen.

Theater Nut was the only CBer I ever heard refer to his radio as "hardware" (and he had plenty of other affectations) but the advice was a little prophetic for 1973, and remains sound: Know your hardware. The C guys take this as gospel, but far too many Pascal people investigate their hardware with the same distance and distaste they might exhibit picking up doggy doo-doo in the backyard after a hard rain.

Designing good comm software requires knowing your hardware, even if you buy the low-level interface routines from someone else. Remember: They'll sell code to you, but they won't debug your app for you. Sooner or later you'll be tracing a roach and find yourself staring at the other guy's code. If it's all a mystery, you'll be out of luck. Knowing what all those register INs and OUTs are for will at least give you a fighting chance. Get the source when you can, but at very least know what the box is up to.

The Chip With Two Brains

The PC's serial port pretty much comes down to a single chip: The 8250 on the PC and the 16450 on the AT. The two are very similar, and the 16450 can in fact be taken as a slightly enhanced version of the 8250. On first look at the register matrix and the list of all the controls and such supported by these chips, most newcomers recoil in horror. The horror might be real, but it's no worse than confronting a B-tree manager or the schematic of a double-conversion superhet receiver. You take it a piece at a time, and hide those details that you don't need to confront at any given moment.

From a height, the UART chip is a little schizoid, and performs two distinct jobs: It manages the serial/parallel conversion of data that is the soul of what a UART does, and it also manages (in partnership with another chip on the PC motherboard) a system of interrupts to make serial communications reliable. The interrupts aren't essential to making the UART work, exactly, and to understand the UART best you should set aside worries about the interrupt system. You can in fact write a "toy" communication program without using interrupts at all, and such a program can help explain a lot about what the UART can and should do.

So before we get into interrupts at all, we're going to confront and understand the UART portion of the 8250/16450 by itself.

I/O Ports

As I explained in my March 1991 column, a UART's job is to convert a byte of data between "parallel" form, with all bits lined up side by side in eight separate wires or memory locations, and "serial" form, where all the bits move down a single wire, one after the other. The conversion works both ways, as needed. Typically, in a PC serial port, data moving out of the computer is converted from parallel form to serial form, and data moving into the computer is converted from serial form to parallel form.

Software that you write, regardless of language, "talks" to the UART through I/O ports. An I/O port, if you haven't ever confronted one in your work, is a little like a memory location occupied by some sort of hardware gadget rather than the familiar memory storage bin. An I/O port, like a memory location, has an address, which is a unique numeric value specifying one single I/O port and no other. The 86-family of chips has an I/O bus, which is the machinery for moving information between the CPU and I/O ports. You needn't worry about how the I/O bus operates at an electrical level. Simply understand that there are 65,535 possible I/O port addresses, and very few of them have any sort of hardware device attached to them. Most I/O port addresses are just "empty air."

By writing a byte of data to the port at the right I/O address, you can place a byte of memory inside a hardware device. Similarly, by reading a byte of data from a port at the right I/O address, you can pick up a byte of data from inside a hardware device.

Different languages have different ways of dealing with I/O ports. Turbo Pascal treats I/O ports as elements of a 65,535-element array. To read a byte from an I/O port, you assign from an element of that array: LineStatus := Port[$03FD];. Writing to an I/O port is done by writing a value to an element of the Port array: Port[$03F8] := OutChar;.

Based I/O Ports

The PC UART chips (there must be a separate chip for each serial port) communicate with your software through I/O ports. Each chip is connected to eight adjacent I/O addresses. Which actual physical I/O addresses these are can be changed, usually by setting DIP switches on the motherboard or on the serial port board. However, regardless of what addresses are actually used, you can always assume that the eight I/O addresses occupied by a serial port UART will be eight-in-a-row, without gaps.

The I/O address of the very first of those eight adjacent I/O ports' UART is known as the base I/O address of the port as a whole. There are two standard base I/O addresses for serial ports in the PC. One, for the COM1: serial port device, is $03F8. The other, for the COM2: serial port device, is $02F8. It's possible to put additional serial ports in a PC, but there are no recognized standard base I/O address locations for them, and (far worse, as I'll explain in a future column) no standard interrupt priority levels for them either. Until you get seriously conversant with the PC's serial port and interrupt management hardware, stick with COM1: and COM2:.

UART Registers

Although there are eight I/O addresses associated with each UART chip, it's not quite true that there are only eight pathways into the UART through the I/O bus. Some of the I/O ports do double duty, and this represents a large part of the confusion of working with the PC serial ports.

To help avoid confusion, the term register is applied to each separate function an I/O port may serve. Two registers may share an I/O port address. The commonest case is when an I/O port serves one purpose when read from, and an entirely different purpose when written to. The simplest example is in moving data into and out of the UART chip. Incoming data (that is, data coming into the PC from the modem) is held in one location inside the UART, and outgoing data (that is, data waiting to be serialized and sent to the modem) are held in two completely different places inside the UART. However, when you read from the base I/O port, (that is, offset 0; the first of the eight I/O addresses a UART is connected to) you get the incoming data, but when you write to the base I/O port, you load a byte into the holding register to be transmitted.

Always remember this: A UART I/O port and a UART register are not necessarily identical! For example, the base I/O port serves no fewer than three different UART registers, all through a single I/O address. To help you sort out the registers from the ports, the individual UART registers all have standard names and three-character acronyms to identify them. I didn't make these names up; you'll find them in all discussions of the PC's UARTs and serial ports, so they're certainly worth coming to know. The UART register names and their abbreviations are summarized in Figure 1, along with the register's offset from the base address, and whether it is connected to the I/O bus when written to (W) or read from (R).

Figure 1: UART I/O addresses and register names

  Register name             Acronym    Read/      Offset   COM1:    COM2:
                                       from base           address  address
  --------------------------------------------------------------------------

  Receive buffer register   RBR        R          0        $03F8    $02F8
  Transmit holding register THR        W          0        $03F8    $02F8
  Interrupt enable register IER        R/W        1        $03F9    $02F9
  Interrupt ID register     IIR        R          2        $03FA    $02FA
  FIFO control register
  (PS/2 only)               FCR        W          2        $03FA    $02FA
  Line control register     LCR        R/W        3        $03FB    $02FB
  Modem control register    MCR        R/W        4        $03FC    $02FC
  Line status register      LSR        R/W        5        $03FD    $02FD
  Modem status register     MSR        R/W        6        $03FE    $02FE
  Scratch register
  (AT/PS2 only)             SCR        R/W                 $03FF    $02FF

When the Divisor Latch Access Bit is = 1, these registers are available instead of RBR and IER:

  Divisor latch, low byte   DLL        R/W        0        $03F8    $02F8
  Divisor latch, high byte  DLM        R/W        1        $03F9    $02F9

The last two registers shown in Figure 1 share I/O addresses with two other registers, depending on the state of a status bit inside the UART called DLAB, the Divisor Latch Access Byte. DLAB=0 (the default) the base I/O address and the address at offset 1 are occupied by registers RBR and IER. However, toggle DLAB to 1, and you can no longer access RBR and IER. Instead, register DLL is accessed through the base I/O address, and register DLM is accessed through the address at offset 1. (I'll come back to what these registers are for in awhile. No sense adding to the confusion unnecessarily!)

Symbolic Names and I/O Addresses

However, in most cases the confusion isn't all that bad. Except for the DLAB business, which I'll explain in due time, most registers are defined by their I/O address and whether they are read from or written to. From now on, I'm going to refer to UART registers by their three-character acronyms rather than their I/O addresses or I/O address offsets. In fact, for the purposes of keeping confusion down, I've set up a group of constant definitions in Listing One (page 148) that define constants using the standard UART register names as identifiers. These "computed constants" contain the I/O addresses of the UART registers whose names they carry. In other words, the value of constant MCR is calculated as PORTBASE+4, where PORTBASE can be either $03F8 (COM1:) or $02F8 (COM2:), as required. For example, if you're using COM1:, MCR is calculated as $03F8+4, or $03FC. On the other hand, if you're using COM2:, MCR is calculated as $02F8+4, or $02FC.

There may be an explanation as to why the UART's I/O addresses must be shared when there are 65,535 available, and that explanation probably has to do with the mechanics of manufacturing the chip itself, but there's no point moaning over it. Study Figure 1 and refer back to it when confusion begins to rise during the rest of this discussion. Sooner or later it'll all start to gel.

Using UART Registers

The best way to start in learning how to use UART registers is to build simple procedures for reading a character from the UART and writing a character to the UART.

Putting a character into the UART for transmission out of the PC is simply a matter of writing the character into the transmit holding buffer register, THR:

  
PROCEDURE OutChar(Ch : Char);

BEGIN     
	Port(THR) := Byte(Ch);   
END;

You simply assign a value to the Port array element corresponding to the THR I/O address, and the value is inside the UART, ready to be transmitted. The type cast Byte (Ch) handles the type mismatch existing between the Port array, which is an ARRAY OF Byte, and the Char value to be transmitted.

Getting a received character out of the UART is every bit as simple:

  
FUNCTION InChar : Char;

BEGIN
       InChar := Char(Port[RBR]);
END;

The RBR constant is one of those defined in Listing One. You simply read from the appropriate element of the Port array, and the UART places its received value in your hand.

UART Register Bit Fields

With only a few exceptions, the UART registers shown in Figure 1 are not whole-byte values. Most of the registers are divided into smaller fields that are only 1 bit in size. Most of these are status flags of one sort or another, indicating whether some condition is or is not present.

At this point, even trying to name all the bit fields for you would be confusing, because there are a lot of them, many of them subtle and a little obscure, some of them with names and standard acronyms and some without. Some bits, furthermore, are undefined and are not used. Rather than lay them out all at once, I'll explain them as I need them for the purposes of this discussion.

The easiest one to begin with has to do with the status of the receive buffer register, RBR. It's pointless or even hazardous to assume that a valid character has been received by the UART from the outside world without some definite signal to that effect. If you read RBR before a full character has been received, you can end up with a meaningless garbage value. For this reason, one of the UART bit fields indicates whether or not a complete character has been received and is waiting in RBR. This bit field, called Data Ready (DR), is bit 0 of the line status register, LSR. If DR is a 1-bit, a character is waiting to be read in RBR. If DR is a 0-bit, no character is yet ready to read.

Testing the state of the DR bit requires "masking out" the other bit fields in LSR before testing its state. It's not difficult:

   FUNCTION InStat : Boolean;
   BEGIN
       InStat := Boolean(Port[LSR] AND $01)
   END;

ANDing the value fetched from LSR with $01 forces all bits in LSR to 0-bits except bit 0, corresponding to the 1-bit in the value $01. This leaves you with two possible values, $00 and $01. The type cast to type Boolean depends on the special knowledge that under the hood, Boolean True values are bytes with the bit 0 set to 1, and Boolean False values are bytes with the bit 0 set to 0. That DR corresponds so neatly to the two Boolean values is mostly coincidence, and is something of a special case. It applies only when the bit field to be tested is present in bit 0 of the register in question. However, because you may be calling the status function frequently within a fairly tight loop, it makes sense to take advantage of any special case that cuts down the amount of processing that needs to be done.

InStat allows you to have confidence that any character you read has been completely received by the UART and doesn't still have a few bits missing. If function InStat returns True, you can go right to RBR and receive a valid character. If InStat returns False, nothing's come in yet, so wait a bit and try again.

Testing Flags: The General Case

Testing flags in the general case requires both a masking operation and a testing operation to return a Boolean state. You must first mask out all bits in a register except the bit you want to check, and then compare that bit against its equivalent value based on its position in the register.

Again, this is best explained by example. There is another bit field in the line status register that indicates when the last character you passed to the UART for transmission is completely sent and gone. There is no FIFO buffer in the PC or AT UART chips. (There is one in the PS/2, but there aren't enough PS/2 machines out there to make it worth using in generally-applicable software.) If you write a new character to the Transmit Holding Register (THR) before the last one is completely out of the UART, you can garble what remains to be sent of the outgoing character. So in a way similar to testing DR, you need to test another bit, the Transmit Hold Register Empty bit (THRE). THRE is not conveniently located at bit 0 of LSR, so you need a more general method of testing bit fields than we used with DR.

The THRE flag resides at bit 5 of LSR. If you take a byte of 0-bits and raise only bit 5 to a 1-bit, the value of the byte changes from 0 to $20. This is the equivalent "value" of the bit field THRE, given its position within its register, LSR. To make efficient use of THRE in your programs, it would thus make sense to declare a constant like this:

  CONST
      THRE = $20;

With this constant defined, we first mask out the unwanted bits of LSR, leaving untouched only bit 5. Then we test whether the remaining value is equal to THRE with its bit set to 1, and the resulting Boolean value indicates whether the THRE bit was set to 1 or to 0:

  (LSR AND THRE) = THRE

This expression returns True if THRE = 1, and False if THRE = 0. You can use this same general mechanism for testing any bit field within a byte. First AND the register against the bit field value, then test the resulting byte value against the bit field value. It's that simple!

Baud Rate and the Divisor Latches

Internal mechanisms of the UART chip limit the number of I/O addresses to which it can respond to eight. Eight isn't quite enough addresses to give each register an address of its own, so some doubling up is required. The most complex case of multiple use of I/O addresses lies in the setting of the UART baud rate.

Most people understand that the baud rate of a serial port is an indicator of how fast data moves through the port. Baud rate is roughly equivalent to bits per second, in that 300 baud represents 300 bits per second moving through the port, and 1200 baud represents 1200 bits per second.

Setting the baud rate of the UART chip is done by placing a value in two UART registers called the divisor latches. This value represents a bit rate divisor, by which an internal clock is divided to produce a time constant that controls the movement of bits through the UART. The larger the divisor value, the slower the UART's baud rate. The divisor value for 300 baud is 384, while the divisor for 1200 baud is 96. The divisors for other baud rates are even less.

Why do we have to place a single value into two registers? Consider: The divisor for 300 baud is 384, which expressed in binary occupies 9 bits. 9 bits won't fit into a byte no matter how hard we push on them, so the divisor must be considered a 16-bit quantity. All UART registers are byte-sized registers. This forces the divisor value to reside in two 8-bit registers.

The divisor latches share the first two I/O addresses with RBR/THR (base address) and IER (offset 1.) The base I/O address of the UART, therefore, is shared by three registers: RBR, THR, and the least significant byte of the divisor latches, DLL.

This sharing is managed by a flag bit in the Line Control Register, LCR. Bit 7 of LCR is the Divisor Latch Access Bit (DLAB). DLAB is normally 0, and when 0, the divisor latches are inaccessible from outside the UART. To write a value into the divisor latches, or to read the state of the current value stored there, you must first set DLAB to 1. When DLAB = 1, the least significant divisor latch byte is accessed through the base address, and the most significant divisor latch byte is accessed through offset 1. Keeping in mind our previous discussions, a routine to set the baud rate would look like this:

  CONST          DLAB = $80;

  PROCEDURE SetRate(Divisor: Word);
  BEGIN
       { Set the DLAB flag to 1: }
  Port[LCR] := Port[LCR] OR DLAB;

  {Load the divisor latches: }
  Port[DLL] := Lo(Divisor);
  Port[DLM] := Hi(Divisor);
  { Clear the DLAB flag to 0:}
  Port[LCR] := Port[LCR] AND (NOT DLAB);
  END;

In essence, you set DLAB to 1, split the divisor value into two pieces and load the pieces into the two divisor latches, then set DLAB back to 0, its usual state.

A Polling Terminal Program

It's a short walk from what we've discussed so far to producing a real, functioning (if somewhat weak-witted) terminal program in Turbo Pascal. Listing Two (page 148) is such a program. I call it PollTerm because it uses polling rather than interrupts to access the UART. Polling is simply a repeated query. In PollTerm we ask the UART if a character is ready, and if not, we loop around and ask again, and again, and again, and when one eventually becomes ready, we go in and grab it.

Polling works, but it has its problems; far better to have the UART notify us somehow when a character is ready to grab. This we can do with interrupts, but it'll take another whole column or so for me to work through the mess it takes to set such a system up and keep it going.

The fundamental logic of PollTerm is pretty simple:

  Set the serial port up.
   LOOP:
      If a character comes in from the serial port, write the character to the screen;
      If a character was typed at the keyboard, send it to the serial port for transmission.
   Go to LOOP.

This pseudocode omits certain niceties like interpreting keyboard commands such as exiting the program gracefully. PollTerm has the minimal machinery for parsing keyboard commands, although it only recognizes two: Ctrl-X exits the program, and Ctrl-Z clears the screen. Nonetheless, PollTerm demonstrates the basic logic common to all terminal programs, and when we create an interrupt-driven version in a future column, it won't look a great deal different.

PollTerm demonstrates a couple of other UART register operations that I haven't discussed in depth. The data word length and parity mode must be set, again by writing 2-bit fields to a UART register:

  Port[LCR] := BITS8 OR NOPARITY;

Similarly, you need to set certain "hardware handshake" lines to their correct state by writing bit fields to the modem control register MCR. I haven't the space in this column to explain every bit field in every UART register, but I'll cover the most important ones next time.

For now, crank up PoolTerm and give it a try. You can read MCI Mail with it (I just did a little while ago) but watch and see if you don't occasionally lose a character or two while a continuous stream of text is coming down the line to your machine. (This is most likely on slow machines like 4.77-MHz 8088 PCs.) That's the most important problem that interrupts correct.

The major point I've wanted to make this time has only been that the serial port hardware is knowable, and eminently accessible from Turbo Pascal. Once you have a handle on the UART registers and what they do, you can write communications software in any language that will allow you to read and write I/O ports.

Turbo Pascal Does Windows!

I've just received a fairly reliable beta test copy of Turbo Pascal for Windows, and by the time you read this column, Borland should have made its presence public and begun shipping it. The product has been a very long time in the works, but Borland did well to take its time and do it right. After a short look, I can't think of anything seriously wrong with the product. It doesn't make Windows programming quite what I'd call easy, but let's say it makes Windows programming possible in less than a normal lifetime.

You still have to digest lots of information about Windows' event-driven machinery, but the class library hides this necessary complexity about as well as can be done. If you're a longtime Windows user you'll have no trouble writing Windows apps almost immediately. If you're a Windows virgin, plan on a little study and some practice in the environment itself. Turbo Pascal for Windows is not Turbo Pascal 6.5 or 7.0. It's an entirely new and separate product that will be sold and (one assumes) evolve side by side with the "original" text-based Turbo Pascal.

Products Mentioned:

Turbo Pascal for Windows Borland International 1800 Green Hills Road Scotts Valley, CA 95066 408-438-8400

I'll have more to say about Turbo Pascal for Windows in my next column. If you've had your nose pressed up against the Window all these years, watching the C guys inside "enjoying" themselves with the Microsoft SDK, take heart. It's your turn now--and I have a hunch some of the C guys are going to be watching you crank out rapid-fire Turbo Pascal Windows applications with no little envy. Microsoft Windows development doesn't have to hurt anymore.

Really.


_STRUCTURED PROGRAMMING COLUMN_
by Jeff Duntemann


[LISTING ONE]
<a name="0129_0012">


CONST
  COMPORT  = 2;               {  1 = COM1:  2 = COM2: }
  COMBASE  = $2F8;
  PORTBASE = COMBASE OR (COMPORT SHL 8); { $3F8 for COM1: $2F8 for COM2: }

  { 8250 control registers, masks, etc. }
  RBR      = PORTBASE;        { 8250 Receive Buffer Register       }
  THR      = PORTBASE;        { 8250 Transmit Holding Register     }
  IER      = PORTBASE + 1;    { 8250 Interrupt Enable Register     }
  LCR      = PORTBASE + 3;    { 8250 Line Control Register         }
  MCR      = PORTBASE + 4;    { 8250 Modem Control Register        }
  LSR      = PORTBASE + 5;    { 8250 Line Status Register          }

  DLL      = PORTBASE;        { 8250 Divisor Latch LSB when DLAB=1 }
  DLM      = PORTBASE + 1;    { 8250 Divisor Latch MSB when DLAB=1 }

  DLAB     = $80;     { Value for Divisor Latch Access Bit  }
  THRE     = $20;     { Value for Transmit Holding Register Empty bit }
  BAUD300  = 384;     { Value for 300 baud operation        }
  BAUD1200 = 96;      { Value for 1200 baud operation       }
  DTR      = $01;     { Value for Data Terminal Ready       }
  RTS      = $02;     { Value for Ready To Send             }
  NOPARITY = 0;       { Comm format value for no parity     }
  BITS8    = $03;     { Comm format value for 8 bits        }





<a name="0129_0013">
<a name="0129_0014">
[LISTING TWO]
<a name="0129_0014">

{--------------------------------------------------------------}
{                         POLLTERM                             }
{                             by Jeff Duntemann                }
{                             Turbo Pascal V6.0                }
{                             Last update 2/3/91               }
{ This is a *truly* dumb terminal program that operates by     }
{ polling the serial port's registers and does NOT use         }
{ interrupts.  It's a good illustration of why interrupts are  }
{ necessary...                                                 }
{ PollTerm can be set to use either COM1: or COM2: by setting  }
{ the COMPORT constant to 1 (for COM1) or 2 (for COM2:) as     }
{ as appropriate and recompiling.                              }
{--------------------------------------------------------------}

PROGRAM PollTerm;

USES DOS,CRT;

{$I REGISTERS.DEF }

VAR
  Quit       : Boolean;      { Flag for exiting the program }
  HiBaud     : Boolean;      { True if 1200 baud is being used }
  KeyChar    : Char;         { Character from keyboard }
  CommChar   : Char;         { Character from the comm port }
  Divisor    : Word;         { Divisor value for setting baud rate }
  Clearit    : Byte;         { Dummy variable }
  NoShow     : SET OF Char;  { Don't show characters set }

PROCEDURE SetRate(Divisor : Word);

BEGIN
  { Set the DLAB flag to 1: }
  Port[LCR] := Port[LCR] OR DLAB;
  { Load the divisor latches: }
  Port[DLL] := Lo(Divisor);
  Port[DLM] := Hi(Divisor);
  { Clear the DLAB flag to 0:}
  Port[LCR] := Port[LCR] AND (NOT DLAB);
END;

FUNCTION InStat : Boolean;

BEGIN
  { Bit 0 of LSR goes high when a char is waiting: }
  InStat := Boolean(Port[LSR] AND $01);
END;

FUNCTION OutStat : Boolean;

BEGIN
  { Bit 5 of LSR goes high when the THR is ready for another char: }
  OutStat := ((LSR AND THRE) = THRE);
END;

FUNCTION InChar : Char;

BEGIN
  InChar := Char(Port[RBR]);
END;

PROCEDURE OutChar(Ch : Char);   { Send a character to the comm port }

BEGIN
  Port[THR] := Byte(Ch)     { Put character ito Transmit Holding Register }
END;

PROCEDURE UhUh;

VAR
  I : Integer;

BEGIN
  FOR I := 1 TO 2 DO
    BEGIN
      Sound(50);
      Delay(50);
      NoSound;
      Delay(50);
    END;
END;


{>>>>>POLLTERM MAIN PROGRAM<<<<<}

BEGIN
  HiBaud := True;               { PollTerm defaults to 1200 baud; if "300"}
  Divisor := BAUD1200;          { is entered after "POLLTERM" on the      }
  IF ParamCount > 0 THEN        { command line, then 300 baud is used     }
    IF ParamStr(1) = '300' THEN
      BEGIN
        HiBaud := False;
        Divisor := BAUD300
      END;

  SetRate(Divisor);                 { Set the baud rate           }

  Port[IER] := 0;                   { Disable 8259 interrupts     }
  Port[LCR] := BITS8 OR NOPARITY;   { Set word length and parity  }
  Port[MCR] := DTR OR RTS;          { Enable DTR, & RTS }
  Clearit := Port[RBR];             { Clear any garbage from RBR  }
  Clearit := Port[LSR];             { Clear any garbage from LSR  }

  DirectVideo := True;
  NoShow := [#0,#127];              { Don't display NUL or RUBOUT }

  ClrScr;
  Writeln('>>>POLLTERM by Jeff Duntemann');

  Quit := False;        { Exit POLLTERM when Quit goes to True }
  REPEAT

    IF InStat THEN      { If a character comes in from the modem }
      BEGIN
        CommChar := InChar;                        { Go get character  }
        CommChar := Char(Byte(CommChar) AND $7F);  { Mask off high bit }
        IF NOT (CommChar IN NoShow) THEN           { If we can show it,}
          Write(CommChar)                          {  then show it! }
      END;

    IF KeyPressed THEN  { If a character is typed at the keyboard }
      BEGIN
        KeyChar := ReadKey;       { First, read the keystroke }
        IF KeyChar = Chr(0) THEN  { We have an extended scan code here }
          UhUh                    {   but we're not using it! }
        ELSE
        CASE Ord(KeyChar) OF
         24 : Quit := True;   { Ctrl-X: Exit PollTerm }
         26 : ClrScr;         { Ctrl-Z: Clear the screen }
         ELSE BEGIN
                WHILE NOT OutStat DO BEGIN END;  { I.e., nothing }
                OutChar(KeyChar)
              END;
        END;  { CASE }
      END

  UNTIL Quit
END.


Copyright © 1991, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.