| If you have a scan of the 4/91 cover, please mail it to me! |
What to Do When You're Past COM2by Mark NelsonTech Specialist April, 1991(Tech Specialist is now Windows Developer's Journal) |
This page contains my original text and figures for the article that appeared in the April 1991 Tech Specialist. I haven't broken it up into pages, so loading the entire thing might take some time.
In the mini and microcomputer world, RS-232 links provide what is likely the most heavily used form of communications. RS-232 is versatile and relatively inexpensive. It is possible to connect computers that are considered otherwise to be completely incompatible. RS-232 is also used by computers to talk to devices such as modems, dumb terminals, bar code readers, printers, lab equipment, and more. So it made sense that IBM defined a standard RS-232 interface card for their PC. Up to two of the cards could be installed in a PC, and were referred to as COM1 and COM2.
Unfortunately, each COM card installed in a PC needed its own interrupt line. The original IBM PC had only eight interrupt lines, of which just two were available for use by RS-232 ports. Some third party COM cards could be configured to operate at other addresses on other interrupt lines, so it was in theory possible to operate three ports at once, but this required using interrupt lines that were allocated for other devices. And a user who wanted to operate as many as 8 RS-232 ports simultaneously was essentially out of luck.
The solution to this problem comes in the form of the Multiple Port Shared Interrupt (Multiport) board. These are boards that are set up so that multiple communication ports can share a single interrupt on the PC bus. This article discusses how the programmer can use these boards in MS-DOS applications.
There is no such thing as a standard Multiport RS-232 board. There are many different manufacturers of these boards, and each of them has implemented their board in a different fashion. However, most of the boards have enough in common so that the same programming techniques, and even the same code, can be used to operate them.
The boards discussed in this article all have several things in common. First of all, each of them uses a Universal Asynchronous Receiver/Transmitter (UART) for the 8250 family. The 8250 family of UARTs include the 8250, 16450, and 16550 manufactured by National Semiconductor. These are the same UARTs used by IBM in the standard COM1 and COM2. This means that the actual programming of the registers on the UART of a Multiport board can be done using essentially the same code as already exists for standard RS-232 ports. Most of the boards support 4 or 8 UARTs on a single board, although there are some 16 port boards available.
In addition, the manufacturers of multiport boards all support the same I/O pins used on the IBM PC COM1 and COM2. These are:
| TX | RS-232 transmission |
| RX | RS-232 reception |
| RTS | Request to Send |
| CTS | Clear To Send |
| DTR | Data Terminal Ready |
| DSR | Data Set Ready |
| CD | Carrier Detect |
| RI | Ring Indicator |
The Multiport board designers use various techniques to bring these signal lines out to multiple 9 or 25 pin D connectors. The DB-25 connector is an industry standard, but IBM began using a nine pin connector as an alternative when the AT series was introduced. For the most part, the board manufacturers seem to be adhering to the 25 pin standard. The nine pin connector used by IBM may have been implemented to save space on a card edge for more connectors, but the multiport vendors don't attempt to mount their connectors on the card edge anyway, so geographical considerations are of less importance.
What makes a Multiport board work on a PC is the shared interrupt logic. This is special hardware that takes the interrupts from all 4, 8 or 16 UARTs on the board and manages them to produce interrupts on a single line on the PC bus. This is not as simple as just ORing them all together to produce an interrupt signal. The IBM PC bus interrupt lines are edge-triggered, which means that a low to high transition needs to take place in order to create a new interrupt. This causes a problem for the hardware designer. In an ORed logic scheme with two UARTS, the following sequence of events could hang the board up:
At this point, the IRQ line on the PC is still high, but a new interrupt will not be generated. This is because it did not go low after UART 2 was serviced, so an edge was not generated.
The Multiport boards have to intelligently manage the interrupt output going on to the PC bus. The exact method varies from board to board, but the final outcome is the same: an edge needs to be generated for each interrupt that needs to be serviced by the CPU. This implies circuitry somewhat more sophisticated than just a giant OR gate, but doesn't require revolutionary new designs. This allows the board designers to keep the prices at a reasonable level.
All the boards that will be discussed in this article manage the interrupts in this manner. Each of the boards also incorporates an additional feature that helps the programmer: a status register. Whenthe board generates an interrupt, there is a special register on the board that can be read by the interrupt service routine. This register indicates which port(s) are presently in need of servicing. Without this register, the interrupt service routine would have to poll each UART on the board for its current interrupt activity. The status register makes that job a little easier.
The multiport boards discussed in this article differ somewhat from RS-232 interface boards that readers may have seen on various other system. First of all, the 8250 family of UARTs are not considered to be high performance parts. Boards designed for systems such as DEC's VAX line of minicomputers can select the optimum UART for the job, since all system access goes through a device driver. PC board designers don't have this luxury, and must stick to the compatible family of parts, for better or worse.
An even more important distinction between PC multiport boards and those found on other systems is the requirement of processor intervention on every single character event. Higher powered boards relieve the system CPU of "character juggling" through many different mechanisms.
Generally these boards will have a dedicated processor that takes care of processing individual interrupts, buffering data, and transferring it to the main CPU memory via DMA or shared memory. Even if a slave processor is not available, many UARTs have FIFOs that can drastically reduce the number of interrupts required when processing data.
These intelligent boards suffer from only two problems: they cost an arm and a leg, and they are for the most part incompatible with the existing PC code base. So when an application requires support for 4 or more RS-232 lines, it makes sense to look at the lower cost, dumb-but-compatible multi-port boards.
When you have an application that needs to be able to service 4 or more RS-232 ports, you have several choices, including intelligent boards, networked systems, and multiport boards. The decision making process on which board to choose is fairly simple. As long as cost is an important factor, the multiport board board is your first choice. The end user price of under $100/port is well below the price of an intelligent board.
The primary drawback to the multiport board is one of throughput. As was discussed earlier, the CPU in your PC will have to individually transfer each byte from the UART to PC memory, or vice versa. By adding up the number of cycles needed to process each character, you can quickly get an idea of the maximum throughput on a PC. Even with carefully crafted assembly code, a 10 Mhz 80286 machine will only be able to keep up with perhaps 4-6 ports running continuously at 9600 baud. At that rate the CPU will be receiving a new interrupt about once every 250 microseconds, which is barely enough time to save and restore all the registers.
Fortunately, RS-232 throughput for most applications tends to be "bursty". An AT machine will have no trouble managing 8 or even 16 ports in an application where data is being sent only periodically. Think of a typical Point-of-Sale terminal application. Most terminals attached to the PC will be idle most of the time. As long as the processor can send the 2000 or so characters needed to redraw a screen in a second or so, it will have plenty of breathing room to catch up while the clerk runs the customer's Visa card through the imprinting machine.
One additional advantage to consider when selecting a type of board is the availability of library software. Most of the multiport boards discussed in this article have support available from several C and Pascal library vendors. The intelligent boards for the most part are limited to device drivers supplied by the vendor. These device drivers generally implement a basic feature set, but are short on bells and whistles. The competitive environment in the third-party library arena has created a buyer's market, with extensive feature lists available for library purchasers. Sources for these libraries are discussed at the end of this article.
Given that you now have an application that needs one of these multiport boards, and you have purchased one and installed it in your computer, what do you do next? Just having the board itself is not enough, you now need an interface to control and communicate with the RS-232 ports resident on it. The code to do this breaks down into three major sections:
The conventional way to talk to a piece of hardware in the computer world is through a device driver, which is either part of or closely linked to the operating system of the computer. In the MS-DOS world, if you are talking to a device driver you can generally use the standard I/O routines that are supplied with your programming language.
Unfortunately, for various reasons, device drivers on MS-DOS machines will frequently not perform as well as we would like. This leads programmers to use shortcuts that talk directly to the hardware. Serial communications software is no exception to this.
The device driver built into the IBM PC BIOS for the RS-232 ports is of very little value. Since it is a polled mode interface, about the only thing it can reasonably be used for is an output-only interface, such as to a printer. In addition, multiport boards are unknown to the BIOS, so any device driver to support these boards must be added in as a separate program. Most manufacturers of multiport boards do in fact supply device drivers for their boards. Some are true device drivers which are installed via modification of the MS-DOS CONFIG.SYS configuration files. Others are TSR programs which can be installed and removed at any time on a running system.
Using the vendor supplied software can be a good or bad idea. The true MS-DOS device drivers allow you to open ports in C with standard fopen() commands, and read and write with putc() and getc(). Other vendors require you to learn new commands and link in their library routines. At the most inconvenient end of the scale, some vendors emulate the INT 14 BIOS service calls for their COM ports, requiring you to learn the arcane syntax of your particular compiler's version of the int86() function.
All of these are reasonable solutions, but they generally suffer from one or more disadvantages. First of all, the device drivers tend to be somewhat inflexible. MS-DOS does not support loading or unloading device drivers, so they usually have to be left in at all times, imposing an unwelcome burden on applications that don't need them.
Communicating with devices via system interrupts is usually slower than direct function calls. And most importantly, there are a lot of times when you want to have the source to your device interface code, and you don't usually get that from a board vendor.
Fortunately, it is not that difficult to implement your own code to talk to a multiport board. Most C compilers being sold today have the capability to implement a complete interface without having to resort to any assembly code, and still achieve satisfactory performance. As an example, I have supplied some sample code with this article that does just that, and all in only a little more than a hundred lines of C code.
The code for interfacing to the multiport board is contained in the file called COM.C. For the most part, this code glosses over the details of programming the 8250 UART. Various computer magazines have published many good articles over the past few years detailing how this part works, and the reader unfamiliar with the part can refer to these. In addition, I would recommend ordering the data sheets from National Semiconductor. National can be reached in Santa Clara, CA at 408-721-5000.
The code in COM.C is compatible with Microsoft C, Quick C, and Turbo C. The major difference to be handled between the two dialects of C is their implementation of the I/O port input and output routines, and the get and set interrupt routines. These differences are managed through the use of macros conditionally defined in the COM.H header file.
In order to understand how to use this code, you need to look at the procedural interface to the code. The data structures remain mostly hidden from view. In order to understand how the code works, you need to look at those data structures, and examine how they interface with the code.
There are two major types of data structures in this program, and once you understand how they work, the rest of the program's operation quickly makes sense. The two data structures are the BOARD structure and the PORT structure. The PORT structure is very similar to the structure you might use in a conventional single port program. The definition of type PORT in COM.H is shown below:
typedef struct {
unsigned int address; /* Address of the 8250 */
char buffer[256]; /* The receive buffer. */
unsigned char head; /* Offset for insertion into the buff.*/
unsigned char tail; /* Offset for removal from the buffer.*/
unsigned char match; /* The status register match value */
} PORT ;
The structure elements are as follows:
| address: | This is the base address of the 8250 UART. It is needed to read and write characters, as well as to control the UART, set operational parameters, etc. |
| buffer[256]: | This is a simple 256 byte receive circular buffer. The arbitrary size of 256 was chosen to simplify the put and get routines. |
| head: | This is the buffer offset used by the interrupt service routine to add characters to the circular buffer. |
| tail: | This is the buffer offset used to remove characters from the receive buffer. |
| match: | This byte is used to show what bits need to be set in the board status register to indicate that this port has an interrupt needing servicing. |
The only structure element that wouldn't normally be used in a single port operation is the "match" element. Depending on the board type, the match can either indicate which bit is set for the given port in the board status register, or what value in the status register will indicate the port is active. (Digiboard is the only board using the latter algorithm).
The BOARD data structure is a little different. A BOARD structure is what is attached to a particular interrupt line, and it in turn has to keep track of the PORT structures that are owned by a particular board.
The structure elements for BOARD are described here:
| status_address: | This is the address of the status register for the particular board. |
| irq_mask: | This is the mask that has to be applied to the system 8259 interrupt controller in order to enable and disable interrupts. Note that this structure element is redundant, and could be calculated given the interrupt number. Storing it in the structure just means it doesn't have to be recalculated every time interrupts are enabled and disabled. |
| int_number: | This is the CPU interrupt number that the board is attached to. On the PC, this will typically be 11 or 12 for IRQ3 and IRQ4 respectively. |
| old_vector: | When the board is opened, the old interrupt vector is stored here. When the board is closed, that interrupt vector is replaced. |
| ports[4]: | This array holds pointers to the four ports that can be attached to this board. Note that for other types of boards, this array could be 8 or 16 elements long. |
| port_count: | This value indicates how many elements of the ports[] array are presently installed. |
There are only eight routines in the COM.C file, and they implement a complete interface to a multiport board. In this case, the code was written to support a Stargate Technologies Plus-8 board, but it only requires changing a line or two in the interrupt service routine to support other boards. The routines are described below:
This routine is called to perform the logical connection between a multiport board and an interrupt line on the PC. The "address" parameter is the address of the status register on the board. The "number" is the interrupt number to be used on the PC. For the first 8 interrupts on the ISA bus, the interrupt number is 8+IRQn, so IRQ3 and IRQ4 will have interrupt number 11 and 12. This routine initializes the BOARD data structure and returns a pointer to that structure. It saves the old interrupt vector, sets up a new interrupt vector, and enables interrupts on the specified line.
This routine is called to shut down a given board, specified by the "board" parameter. board_close() takes care of disabling interrupts on the correct interrupt line, then restoring the interrupt vector that was in place before the board was opened. It also takes care of closing any of the ports attached to it that have not been closed with an explicit call to port_close().
This routine is called to open a single port up, and attach it to the board it belongs with. The "board" parameter is a pointer to the BOARD structure, which should have already been opened. The "address" parameter is the base address of the UART, and the match parameter is used in the interrupt service routine to check for the status register bit indicating that this particular UART has an interrupt pending.
This routine is called after the port has been opened. It sets up the baud rate, parity, number of data bits, and number of stop bits for the port specified in the "port" parameter. After the parameters are set up for the port, receiver interrupts on that port are enabled.
port_close is called to shut down the UART. All it does is disable interrupts on the selected UART, then free up the space that had been allocated for the port structure. The way the routines are presently written, board_close() will call this routine for each open port, so it doesn't need to be called by the application.
This routine is analagous to the fputc() routine in the C standard i/o run time library. It is passed a character and a pointer to a port data structure. It sits in a loop and waits for the UART to have space available in its transmit holding register. When the UART is ready to send another character, this routine stuffs it into the holding register and returns.
port_getc() is analagous to the fgetc() routine in the standard i/o library. It doesn't have to talk directly to the UART, since incoming characters are being stored in the circular buffer. It checks to see if there are any characters in the buffer, and extracts and returns one if there are. If no characters are available, an error code is returned.
The final routine found in COM.C is interrupt_service_routine(), which is not called by any routines outside of the COM.C file. It is the routine that is branched to when an interrupt is generated by the board. Its job is to service any and all interrupts pending on the multiport board. The way the routine determines which ports need servicing is by reading the status register. Once the status register has been read in, the ISR can loop through the list of open ports, looking for ports that have bits set in the status register.
The device dependent portion of the code is found in the interrupt service routine. The way the ISR decides whether to service a port on the Stargate board is by executing the following lines:
for ( i = 0 ; i < board.port_count ; i++ )
{
if ( board.ports[i]->match & status_reg) {
.
.
.
The "match" value in each port structure has been set up in my sample program for the Stargate status register, where each port has a single bit in the status register. Other boards may expect the bit to be cleared for the given port. The Digiboard products don't use a bit pattern in the status register, they actually put a single port number in the register. These differing cases would only require a minor change to "if" line of code in the ISR.
In order to test this interface code, I wrote a short program called MULTTERM. This program creates four windows on the screen which each have an independent terminal session taking place concurrently. The way I wrote the program, allows the user to switch the keyboard between the four windows by pressing Function keys 1 through 4. Even though
keyboard input can only be directed to one port at a time, all four ports are actively receiving input characters at all times, and displaying them on the screen as they arrive.
The program consists of four modules that can be compiled separately and linked together. There are also three header files. The files are listed below:
| MULTTERM.C: | This is the file that contains main(), and drives the test program. All of the other files contain the support and driver routines. |
| COM.C: | This is the file that has all of the routines that interface to the multiport board. |
| KEYS.C: | This contains the routine to get keys from the keyboard. |
| VIDEO.C: | This contains a "quick and dirty" windowed interface to the display. It is hard coded for CGA/EGA/VGA displays. |
| COM.H: | The function prototypes and structure definitions needed to use the communications functions. |
| VIDEO.H: | The function prototypes and constants needed to use the display interface functions. |
| KEYS.H: | Function prototypes and key definitions for the keyboard interface. |
The operation of MULTTERM.C is relatively simple. The program consists of only three sections. In the first section, four non-overlapping windows are opened up. In the second section of code, the board and the four ports are opened up. My test board was set up with a status register at address 0x580, and the UARTs spaced 8 addresses apart starting at 0x180. The Stargate Plus board uses a single bit in the status register to indicate that a port needs servicing, so the port parameters look like this:
Finally, after the ports are opened, the main terminal emulation loop is entered. This main loop has two sections. In the first section, the input buffers for all four ports are emptied out, with all the characters being written out to the appropriate display window. Once this is done, the second section is entered. This is where the keyboard is read. An Escape key causes an immediate exit, a Function key causes a switch to a particular window, and any other key is output to the appropriate communications port.
To test the operation of this system, I ran it on a standard 10Mhz XT, and connected it to Xenix system on four different ports. A sample screen capture is shown here:
---------------------------------------------------------------------------- | ||23% | |17% ||23% w -t | |17% tty || 3:24pm up 1 day, 21:08, 5 users,| |/dev/ttyi1a || load average: 0.00, 0.00, 0.00 | |18% cat > /dev/ttyi1b ||24% | |Message... ||24% tty | |Message line two... ||/dev/ttyi1b | |19% ||25% Message... | | ||Message line two... | | || | ---------------------------------------------------------------------------- ---------------------------------------------------------------------------- |Message 1: || | |From nelson Sun Mar 18 15:22:48 1990||4% mail nelson | |To: nelson ||Subject: Stargate | |Subject: Stargate ||This message is being sent | |Date: Sun Mar 18 15:22:48 1990 ||through a Stargate board. | | ||(end of message) | |This message is being sent ||5% | |through a Stargate board. || | | || | |d || | ----------------------------------------------------------------------------
A MULTTERM Screen
The sample program was built using the following QuickC 2.0 command line:
QCL /W3 MULTTERM.C VIDEO.C COM.C KEYS.C
If you are using Borland's Turbo C 2.0, use this line:
TCC -w MULTTERM.C VIDEO.C COM.C KEYS.C
The only differences between the two versions of the program are the names of the routines to get and set interrupt vectors, and the routines to input and output bytes to I/O ports. These differences are handled by #define statements in COM.H. Note that this program can be ported easily to any MS- DOS compiler that supports interrupt service routines written in C.
The test program operated fine on the XT, although I didn't subject it to heavy data traffic. It was able to receive data simultaneously on all four windows without loosing any characters. However, if I want to keep up with continuous data on multiple ports I would either need to upgrade to a faster machine or optimize both my Com port and display I/O routines.
If you wanted to develop the routines shown here into a production quality library, there are quite a few areas that would benefit from attention. Most of the refinements can be done one at a time, allowing for a steady evolutionary improvement in your library. Some of the most beneficial improvements would be:
There are quite a few different companies making multiport boards for the PC bus family of machines. A few of the names and phone numbers of companies whose hardware I have used are shown below. The biggest demand for these types of boards has in the past come from users of multitasking operating systems, so the manufacturers have tended to advertise in Unix oriented magazines. If you want to do more research, this would be a good place to look. This is not intended to be a comprehensive list, and any time you spend doing research in area will probably be worthwhile.
Commtech, Inc. Wichita, KS 316-651-0077 Contec U.S.A San Jose, CA 800-888-8884 Digiboard, Inc. St. Louis Park, MN 612-922-8055 Quadram Norcross, GA 404-923-6666 Quatech, Inc. Akron, Ohio 800-553-1170 Stargate Technologies, Inc. Solon, Ohio 216-349-1860
It is relatively easy to go into business selling C libraries. All you need is a few months of time to develop a product, and a little seed money for advertising. This fact, combined with the high demand for quality libraries, has resulted in there being at least a dozen companies selling C libraries for serial communications. With this many entrants in the market, a "features war" has developed, resulting in a good buyers market. For less than $300, you can buy libraries that have an enormous amount of functionality. Most of these library vendors have ads in the pages of this magazine, so the research job is relatively simple. Armed with the list of functions you need, you should be able to estimate the amount of effort required to write your own code. Given this, you are ready to make a build or buy decision.
In addition to the commercial market, there are a few public domain and shareware serial communications libraries available. However, I am not aware of any that offer support for multiport boards. Of course, it could be practical to modify one of these libraries to suit your particular needs.
In order to use more than two RS-232 ports on a PC, you need to purchase a multiport board. While this does mean you have to either build or buy some non-standard device interface software, this can be done without too much new code. So if you have an application that demands communication with multiple source of information in the real world, this can provide an economical means of accomplishing your goal.
/*
* MULTTERM.C Copyright (C) 1990 by Mark R. Nelson
*
* This program uses a Stargate Technologies Plus 8 multiport board to set
* up four independent terminal sessions. Four serial ports are logically
* connected to four windows. The cursor will be in the active window,
* and any keyboard input will be directed out through the port connected
* with that window.
*/
#include <stdio.h>
#include <stdlib.h>
#include "video.h"
#include "keys.h"
#include "com.h"
WINDOW *windows[4];
BOARD *board;
void main()
{
int key;
int window=0;
int c;
int i;
/*
* The following four lines of code open up four windows on the screen.
*/
windows[3] = window_open(13,41,37,10);
windows[2] = window_open(13,1,37,10);
windows[1] = window_open(1,41,37,10);
windows[0] = window_open(1,1,37,10);
/*
* Next, the board is opened, followed by the four ports.
*/
board = board_open( 0x580, 11 );
for ( i=0 ; i<4 ; i++ ) {
port_open( board, 0x180 + (i*8), (char) 1 << i );
port_set( board->ports[i], 19200L, 'N', 8, 1 );
}
/*
* The program sits in this loop until the user hits the Escape key
* to exit. Each port is polled for input characters. All characters
* are dumped out to the appropriate window. Finally, the keyboard is
* checked for keyboard input, and appropriate action is taken on
* keystrokes.
*/
for ( ; ; ) {
for ( i = 0 ; i < 4 ; i ++ )
while ( (c = port_getc( board->ports[i] )) > 1 )
window_putc( windows[ i ], (char) c );
/*
* A -1 from getkey means no key is ready. A 27 is the escape key,
* which means exit the program. The four function keys cause one of
* the four windows to be selected. Finally, any other key is sent out
* through the port connected to the active window.
*/
key = getkey();
switch ( key ) {
case -1 :
break;
case 27 :
board_close( board );
exit(0);
case F1 :
window_select( windows[0] );
window=0;
break;
case F2 :
window_select( windows[1] );
window=1;
break;
case F3 :
window_select( windows[2] );
window=2;
break;
case F4 :
window_select( windows[3] );
window=3;
break;
default :
port_putc( board->ports[window], (char) key );
break;
}
}
}
/*
* COM.H Copyright (C) 1990 by Mark R. Nelson
*
* This header file contains the structures, constants, and function
* prototypes necessary to use the RS-232 routines in COM.C
*/
/*
* This structure defines an RS-232 port.
*/
typedef struct {
unsigned int address; /* Address of the 8250 */
char buffer[256]; /* The receive buffer. */
unsigned char head; /* Offset for insertion into the buff.*/
unsigned char tail; /* Offset for removal from the buffer.*/
unsigned char match; /* The status register match value */
} PORT ;
typedef struct {
unsigned int status_address; /* Address of the boards status reg. */
unsigned char irq_mask; /* The 8259 bits to set for this port */
unsigned char int_number; /* The interrupt number for this port.*/
void (interrupt far *old_vector)();/* The saved old interrupt vector. */
PORT *ports[4]; /* Ports for this board */
int port_count; /* Number of ports currently open */
} BOARD;
BOARD *board_open( unsigned int address, unsigned char int_number );
PORT *port_open( BOARD *board, unsigned int address, unsigned char match );
void port_set(PORT *port, long speed, char parity, int data, int stopbits);
void port_close( PORT *port );
void board_close( BOARD *board );
void port_putc( PORT *port, unsigned char c);
int port_getc( PORT *port );
#ifdef M_I86
#define inportb inp
#define outportb outp
#define getvect _dos_getvect
#define setvect _dos_setvect
#endif
/*
* COM.C Copyright (C) 1990 by Mark R. Nelson
*
* This module contains a complete interface to a multiport RS-232 board.
* It is presently configured for the Stargate Plus 4 board.
*/
#include <stdio.h>
#include <stdlib.h>
#include <dos.h>
#include <conio.h>
#include "com.h"
void interrupt far interrupt_service_routine( void );
static BOARD board;
/*
* This routine opens a multiport board up. It initializes the data
* structure associated with the board, mostly from parameters passed
* by the caller. It needs to make a DOS call with getvect to get
* the old interrupt vector for the board, then another call to
* setvect to connect our interrupt_service_routine to the vector.
*/
BOARD *board_open( unsigned int address, unsigned char int_number )
{
unsigned char temp_port;
board.status_address = address;
board.irq_mask = (char) 1 << (int_number % 8 );
board.int_number = int_number;
board.port_count = 0;
board.old_vector = getvect( int_number );
setvect( int_number, interrupt_service_routine );
/*
* These two lines instruct the 8259 interrupt controller to start
* accepting interrupts from the multiport board.
*/
temp_port = (char) inportb( 0x21 );
outportb( 0x21, ~board.irq_mask & temp_port );
/*
* Return a pointer to the structure.
*/
return( &board );
}
/*
* This routine is called to set up a data structure and open a port
* on a board that should have already been opened.
*/
PORT *port_open( BOARD *board, unsigned int address, unsigned char match )
{
PORT *port;
if ((port = malloc( sizeof( PORT ))) == NULL)
return( NULL );
port->head = 0;
port->tail = 0;
port->match = match;
port->address = address;
board->ports[ board->port_count++ ] = port;
return(port);
}
/*
* This routine sets up the transmission parameters for the UART. See
* the data sheet for details on what is going on here. Finally, it
* enables interrupts so the port can start receiving.
*/
void port_set( PORT *port, long speed, char parity, int data, int stopbits )
{
unsigned char temp_port;
/*
* First write the new baud rate to the UART.
*/
outportb( port->address + 3, 0x80 );
outportb( port->address, (char)(115200L / speed ) & 0xff );
outportb( port->address + 1, (char)(115200L / speed ) >> 8 );
/*
* Set up line control register.
*/
if ( parity== 'E' )
temp_port = 0x8; /* Set bit 3 on for even parity */
else if ( parity == 'O' )
temp_port = 0x18;
else
temp_port = 0;
if ( stopbits == 2 )
temp_port |= 0x4;
if ( data == 6 )
temp_port |= 0x1;
else if ( data == 7 )
temp_port |= 0x2;
else if ( data == 8 )
temp_port |= 0x3;
outportb( port->address + 3, temp_port );
/*
* Turn on OUT2, RTS & DTR. OUT2 is necessary on the PC for
* interrupts to take effect.
*/
outportb( port->address + 4, 0xb );
/*
* Then enable receiver interrupts.
*/
outportb( port->address + 1, 0x1 );
}
/*
* This routine closes a board by disabling interrupts for that line
* and restoring the old interrupt vector. It also closes any ports
* that were left open.
*/
void board_close( BOARD *board )
{
unsigned char temp_port;
unsigned int i;
for ( i = 0 ; i < board->port_count ; i++ )
port_close( board->ports[ i ] );
temp_port = (unsigned char) inportb( 0x21 );
outportb( 0x21, board->irq_mask | temp_port);
setvect( board->int_number, board->old_vector );
}
/*
* This routine closes a port. All this means is disabling receiver
* interrupts, then freeing up the structure. Note that this routine
* should probably not be called by the user, it should be left up
* to board_close() to close all the ports.
*/
void port_close( PORT *port )
{
outportb( port->address + 1, 0x0 );
free( port );
}
/*
* port_putc() is used to output a single character through the given
* port. This is done by just wainting for the transmit holding register
* to be empty, then writing the character to the UART.
*/
void port_putc( PORT *port, unsigned char c )
{
while( (inportb(port->address+5) & 0x20) == 0 )
;
outportb(port->address,c);
}
/*
* port_getc() checks to see if there are any characters in the port's
* input buffer. If there are, the oldest one is pulled out and
* returned, otherwise an error code is returned. Note that the code
* is simplified by having a byte index and a 256 byte buffer.
*/
int port_getc(PORT *port)
{
if ( port->head == port->tail )
return( -1 );
else
return( port->buffer[ port->tail++ ] );
}
/*
* The interrupt service routine has to read in the status register,
* then figure out which port(s) have characters to be read.
* The characters are read in, and stuffed into the buffers.
*/
void interrupt far interrupt_service_routine()
{
int i;
unsigned char status_reg;
PORT *port;
status_reg = (unsigned char) inportb( board.status_address );
for ( i = 0 ; i < board.port_count ; i++ )
{
if ( board.ports[i]->match & status_reg ) {
port=board.ports[ i ];
port->buffer[ port->head ] = (unsigned char) inportb(port->address);
if ((port->head + 1 ) != port->tail)
port->head++;
}
}
/*
* This line sends the EOI signal to the interrupt controller, letting it
* know that we are done.
*/
outportb(0x20,0x20);
}
/*
* VIDEO.H Copyright (C) 1990 by Mark R. Nelson
*
* This header file contains the structures, constants, and function
* prototypes necessary to use the display routines in VIDEO.C
*/
typedef unsigned char COORD;
typedef struct {
COORD first_row;
COORD height;
COORD first_col;
COORD width;
COORD cursor_row;
COORD cursor_col;
} WINDOW;
WINDOW *window_open( COORD row, COORD col, COORD width, COORD height );
void window_printf( WINDOW *window ,char *format,... );
void window_putc( WINDOW *window, char c );
void window_select( WINDOW *window );
void scroll( WINDOW *window );
void restore_screen( void );
void initialize_screen( void );
void set_cursor(COORD row, COORD col );
void get_cursor( COORD *row, COORD *col );
void draw_box( WINDOW *window );
/*
* VIDEO.C Copyright (C) 1990 by Mark R. Nelson
*
* This module contains a rudimentary set of routines to operate a set
* of non-overlapping display windows.
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <dos.h>
#include "video.h"
/*
* These are all the line drawing constants defined.
*/
#define UL_CORNER 218
#define UR_CORNER 191
#define LL_CORNER 192
#define LR_CORNER 217
#define UPPER_TEE 194
#define LOWER_TEE 193
#define LEFT_TEE 195
#define RIGHT_TEE 180
#define CENTER_TEE 197
#define HORIZONTAL_LINE 196
#define VERTICAL_LINE 179
#define CHAR(w,r,c) \
(*physical_screen)[(w)->first_row+(r)][(w)->first_col+(c)].character
#define ATTRIBUTE(w,r,c) \
(*physical_screen)[(w)->first_row+(r)][(w)->first_col+(c)].attribute
#define ELEMENT(w,r,c) \
(*physical_screen)[(w)->first_row+(r)][(w)->first_col+(c)]
WINDOW *current_window=NULL;
/*
* I create a structure so I can write directly to screen as if it were
* a big array. That way the compiler takes care of computing the
* addresses, all I have to do is insert the row and column.
*/
struct video_element {
unsigned char character;
unsigned char attribute;
};
struct video_element (far *physical_screen)[25][80];
struct video_element saved_screen[25][80];
COORD saved_row;
COORD saved_col;
static int screen_initialized=0;
void window_printf(WINDOW *window ,char *format,...)
{
char buffer[81];
va_list args;
int i;
va_start(args,format);
vsprintf(buffer,format,args);
for ( i = 0; i < strlen( buffer ) ; i++ )
window_putc( window, buffer[ i ] );
}
void window_putc( WINDOW *window, char c )
{
if ( c == 13 )
window->cursor_col = 0;
else if (c == 10 ) {
window->cursor_row++;
window->cursor_col = 0;
} else {
CHAR( window, window->cursor_row, window->cursor_col) = c;
window->cursor_col++;
}
if ( window->cursor_col >= window->width ) {
window->cursor_col = 0;
window->cursor_row++;
}
if ( window->cursor_row >= window->height ) {
window->cursor_row--;
scroll( window );
}
if ( window == current_window )
set_cursor( window->first_row + window->cursor_row,
window->first_col + window->cursor_col );
}
void scroll( WINDOW *window )
{
int row;
int col;
for ( row = 0 ; row < ( window->height - 1 ) ; row++ )
for ( col = 0 ; col < ( window->width ) ; col++ )
CHAR( window, row, col ) = CHAR( window, row+1, col );
for ( col = 0 ; col < ( window->width ) ; col++ )
CHAR( window, window->height-1, col ) = ' ';
}
void restore_screen()
{
int row;
int col;
if (screen_initialized != 0 ) {
for (row=0;row<25;row++)
for (col=0;col<80;col++)
(*physical_screen)[row][col]=saved_screen[row][col];
set_cursor( saved_row, saved_col );
screen_initialized = 0;
}
}
/*
* Change the assignment of physical_screen from 0xb8000000L to 0xb0000000L
* if you are on a monochrome system.
*/
void initialize_screen()
{
int row;
int col;
if ( screen_initialized == 0 ) {
get_cursor( &saved_row, &saved_col );
physical_screen=(struct video_element (far *)[25][80])0xb8000000L;
for (row=0;row<25;row++)
for (col=0;col<80;col++)
{
saved_screen[row][col]=(*physical_screen)[row][col];
(*physical_screen)[row][col].character=' ';
(*physical_screen)[row][col].attribute=0x1b;
}
screen_initialized = 1;
atexit( restore_screen );
}
}
void set_cursor(COORD row, COORD col)
{
union REGS inregs;
union REGS outregs;
inregs.h.dh=row;
inregs.h.dl=col;
inregs.h.bh=0;
inregs.h.ah=2;
int86( 0x10, &inregs, &outregs );
}
void get_cursor( COORD *row, COORD *col )
{
union REGS inregs;
union REGS outregs;
inregs.h.ah = 3;
inregs.h.bh = 0;
int86( 0x10, &inregs, &outregs );
*row = outregs.h.dh;
*col = outregs.h.dl;
}
WINDOW *window_open( COORD row, COORD col, COORD width, COORD height )
{
WINDOW *window;
window = malloc( sizeof( WINDOW ) );
if (window == NULL ) {
restore_screen();
printf( "Allocation failure!\n" );
exit(1);
}
initialize_screen();
window->first_row = row;
window->first_col = col;
window->width = width;
window->height = height;
window->cursor_row = 0;
window->cursor_col = 0;
current_window = window;
draw_box( window );
window_select( window );
return( window );
}
void draw_box( WINDOW *window)
{
COORD row;
COORD col;
for ( row = 0 ; row < window->height ; row++ ) {
CHAR( window, row, -1 ) = VERTICAL_LINE ;
CHAR( window, row, window->width ) = VERTICAL_LINE;
}
for ( col = 0 ; col < window->width ; col++ ) {
CHAR( window, -1, col ) = HORIZONTAL_LINE;
CHAR( window, window->height, col ) = HORIZONTAL_LINE;
}
CHAR( window, -1, -1 ) = UL_CORNER;
CHAR( window, -1, window->width ) = UR_CORNER;
CHAR( window, window->height, -1 ) = LL_CORNER;
CHAR( window, window->height, window->width ) = LR_CORNER;
}
void window_select( WINDOW *window )
{
current_window = window;
set_cursor( window->first_row + window->cursor_row,
window->first_col + window->cursor_col );
}
/* * KEYS.H Copyright (C) 1990 by Mark R. Nelson * * This routine contains the constants and function prototypes needed to * use the functions in KEYS.C */ #define F1 15104 #define F2 15360 #define F3 15616 #define F4 15872 int getkey( void );
/*
* KEYS.C Copyright (C) 1990 by Mark R. Nelson
*
* This routine is used to read a character or extended character
* in from the keyboard. It returns immediately if no character is
* available, returning a -1 to indicate no key. A few of the
* extended key codes are defined in KEYS.H
*/
#include <conio.h>
int getkey()
{
int c;
if ( !kbhit())
return( -1 );
c = getch();
if ( c != 0 )
return( c );
else
return( getch() << 8 );
}
|
|
Return to Mark Nelson's Home Page
Copyright (c) 1990-1998, Mark Nelson, All Rights Reserved. |