[comp.sys.ibm.pc] Serial Communications in C

vxb@mhuxh.UUCP (Vern Bradner) (12/22/87)

Can anyone share their knowledge/experiences in writing C routines to
communicate with remote systems via the PC serial port?

I tried inp and outp without much success. Are there any C libraries
that might make this job easier?

I have seen requests for this information recently, but I have not seen 
the answers posted (I will post a summary if anyone is interested).

Thanks in advance for any help.

                  Vern Bradner ihnp4!mhuxh!vxb

osm@metavax.UUCP (Owen Scott Medd) (12/23/87)

In article <991@mhuxh.UUCP> vxb@mhuxh.UUCP (Vern Bradner) writes:
>Can anyone share their knowledge/experiences in writing C routines to
>communicate with remote systems via the PC serial port?
>
>I tried inp and outp without much success. Are there any C libraries
>that might make this job easier?

I've just glanced at the documentation on writing interrupt handlers in
Microsoft C 5.0.  This looks like a very nice facility to avoid coding
serial drivers in assembler.  Has anyone made an attempt to rewrite any
of the public domain serial drivers in MSC5.0?

[ It's one item on my list of things to do in my copious spare time :-> ]

Owen
-- 
USMail:   Meta Systems, Ltd.  315 East Eisenhower Parkway, Ann Arbor, MI  48108
Phone:	  +1 313 663 6027
UUCP:	  uunet!umix!metavax!osm
Internet: osm%metavax.uucp@umix.cc.umich.edu

manes@dasys1.UUCP (Steve Manes) (12/24/87)

In article <991@mhuxh.UUCP> vxb@mhuxh.UUCP (Vern Bradner) writes:
>Can anyone share their knowledge/experiences in writing C routines to
>communicate with remote systems via the PC serial port?
>
>I tried inp and outp without much success. Are there any C libraries
>that might make this job easier?

Writing code to control the PC's serial port can range from fairly simple
to gruesomely elaborate depending on what you want to do.  However, in most
applications you want to avoid the IBM's built-in ROM BIOS serial support
because it doesn't support interrupt-driven character receipt, i.e. it
doesn't buffer.  If you're not all that experienced writing code for
hardware and particularly the PC's 8250/16450 UART and 8259 interrupt
controller the first thing you'll need to do is study how they work.  <PC
Tech> did an excellent series on the IBM hardware interrupt structure
several years ago.  Another good source of reference is <The IBM Personal
Computer for the Inside Out> by Sargent & Shoemaker (Addison-Wesley).  The
8250 can be tricky to work with if you're starting out with a blank page.
I probably spent 6 months tweaking and refining (and fixing many subtle
bugs) in my comm library.  You can save yourself a lot of aggravation by
purchasing the Greenleaf comm library, or equivalent.  The downside is that
you'll lose a valuable education in hardware programming.  The upside:
you'll sleep better knowing that you've got a predebugged library to work
with.

You can't just pop bits in and out of the comm port if you want an
effective serial device controller.  The UART has to be prepared and
initialized beforehand.  The Sargent/Shoemaker book explains this well.

-- 
+-----------------------------------------------------------------------
+ Steve Manes         Roxy Recorders, Inc.                 NYC
+ decvax!philabs!cmcl2!hombre!magpie!manes       Magpie BBS: 212-420-0527
+ uunet!iuvax!bsu-cs!zoo-hq!magpie!manes              300/1200/2400

dipto@umbc3.UMD.EDU (Dipto Chakravarty) (12/25/87)

In article <2574@metavax.UUCP> osm@metavax.UUCP (Owen Scott Medd) writes:
>
>In article <991@mhuxh.UUCP> vxb@mhuxh.UUCP (Vern Bradner) writes:
>>Can anyone share their knowledge/experiences in writing C routines to
>>communicate with remote systems via the PC serial port?
>>
>I've just glanced at the documentation on writing interrupt handlers in
>Microsoft C 5.0.  This looks like a very nice facility to avoid coding
>serial drivers in assembler.  Has anyone made an attempt to rewrite any
>of the public domain serial drivers in MSC 5.0?
>


I am currently working on an interrupt driven multi-station LAN, using
the MSC compiler. So far the project has been exceedingly interesting. 
Since we havenot yet received the 5.0 we were compelled to mess around
with assembly. I am eagerly awaiting for the arrival of 5.0 which is 
SUPPOSED to be real good for developing serial communications related 
softwares.

I would really like to hear from our readers about their experiences
with serial communications and PCs. My favorite questions to our readers
will be the following :-

	* Can a RAM resident program be written completely in C (MSC) ?
	  If yes, then pplleeaassee email me a code segment of it!

	* Does anyone know of a serial driver which has been developed 
	  in C without having to resort to assembly at all.

Happy holidays to all of our readers !
                                                           Dipto



-- 
BITNET : dipto@umbc2	      ------\
ARPANET: dipto@umbc3.UMD.EDU  -------> In-real-life: Dipto Chakravarty
USMAIL : CMSC, UMBC,Md 21228  ------/

pjh@mccc.UUCP (Peter J. Holsberg) (12/27/87)

There is an excellent book by Joe Campbell -- "C Programmer's Guide to
Serial Communications."  It covers IBM PC serial port (as well s the
Kaypro CP/M machine serial port).

-- 
Peter Holsberg                  UUCP: {rutgers!}princeton!mccc!pjh
Technology Division             CompuServe: 70240,334
Mercer College                  GEnie: PJHOLSBERG
Trenton, NJ 08690               Voice: 1-609-586-4800

tommie@psivax.UUCP (Tom Levin) (01/07/88)

In article <991@mhuxh.UUCP> vxb@mhuxh.UUCP (Vern Bradner) writes:
>Can anyone share their knowledge/experiences in writing C routines to
>communicate with remote systems via the PC serial port?
>

I have used the Greenleaf Communications package with complete success
on two different projects.  The package contains a set of linkable
libraries for a specific C compiler (I have the Turbo C version),
along with a very complete manual.

This package has routines for just about anything you would ever want 
to do through a serial port including full Hayes modem support.  It 
can handle something like 16 serial ports at 9600 baud.

I bought my copy from the Programmers Connection for about 
$100.00 - $125.00.  (can't exactly remember...drain bramage!)

All in all, this is one of the finest tools I own!  Buy it!
-- 
Tom Levin  (The Robo-Programmer)          {ihnp4|sdcrdcf|ttidca|scgvaxd|nrcvax|
part man, part machine...                  jplpro|hoptoad |csun|quad1|bellcore|
all hacker!                                logico|rdlvax}!psivax!tommie

Usenet_area_"Cs.I.Pc"@watmath.waterloo.edu (01/07/88)

From Usenet: seismo!uunet!psivax!tommie
From: tommie@psivax.UUCP (Tom Levin)
Newsgroups: comp.sys.ibm.pc
Subject: Re: Serial Communications in C
Keywords: Greenleaf Communications Package
Message-ID: <1975@psivax.UUCP>
Date: 6 Jan 88 22:32:06 GMT
References: <991@mhuxh.UUCP>
Reply-To: tommie@psivax.UUCP (Tom Levin)
Organization: Pacesetter Systems Inc., Sylmar, CA
Lines: 22

In article <991@mhuxh.UUCP> vxb@mhuxh.UUCP (Vern Bradner) writes:
>Can anyone share their knowledge/experiences in writing C routines to
>communicate with remote systems via the PC serial port?
>

I have used the Greenleaf Communications package with complete success
on two different projects.  The package contains a set of linkable
libraries for a specific C compiler (I have the Turbo C version),
along with a very complete manual.

This package has routines for just about anything you would ever want 
to do through a serial port including full Hayes modem support.  It 
can handle something like 16 serial ports at 9600 baud.

I bought my copy from the Programmers Connection for about 
$100.00 - $125.00.  (can't exactly remember...drain bramage!)

All in all, this is one of the finest tools I own!  Buy it!
-- 
Tom Levin  (The Robo-Programmer)          {ihnp4|sdcrdcf|ttidca|scgvaxd|
nrcvax|
part man, part machine...                  jplpro|hoptoad |csun|quad1|
bellcore|
all hacker!                                logico|rdlvax}!psivax!tommie

--- via UGate v1.6
 * Origin: watmath (221/163)

nev@edison.GE.COM (Niles VanDenburg) (01/11/88)

In article <991@mhuxh.UUCP>, vxb@mhuxh.UUCP (Vern Bradner) writes:
> Can anyone share their knowledge/experiences in writing C routines to
> communicate with remote systems via the PC serial port?

since this discussion has sort of degenerated of late I would like to
fire it back up a little by actually supplying a C code segment that I
wrote a while ago to handle XON/XOFF serial communications in C.  

WARNING: this code segment has only been used in one program and is
being presented here only for its educational value and to promote
discussion about serial interfaces in C for an IBM-PC.  Any other use of
the code hereby presented is not authorized.  This code is presented
ASIS.  There is no representation that this is good code.  In fact it is
recommended that every one who uses this code for unauthorized uses
realizes that he/she is taking total responsibility for any liability
thereby resulting.

Now with that out of the way here is the code (~350 lines follow):

/*
 * compiler: Turbo C version 1.0
 */

#include	<stdio.h>
#include	<stdlib.h>
#include	<string.h>
#include	<dos.h>
#include	<bios.h>

#define TRUE	1
#define FALSE	0

#define XOFF	0x13			/* XOFF/DC3 character */
#define XON	0x11			/* XON/DC1 character */
 
#define MODEM_CONTROL	0x0b		/* value of the ACE's modem control */
					/*  register: RTS, DTR, and OUT2 true */
					/*  OUT2 required to get interrupts */
#define TX_SIZE		64		/* transmit circular buffer size */
#define RX_SIZE		1024		/* receive circular buffer size */

#define INT_MASK_REG	0x21		/* I/O port 8259 interrupt controller */

#define COMM_PORT_INIT	0xe3		/* default initialization for ACE : */
					/*  9600 BPS, no parity, 1 stop, */
					/*  8 bits/char  See BIOS for details */

char volatile tx_buf[TX_SIZE];		/* circular buffer sending chars */
char volatile *tx_buf_head;		/* ptr to next char to send */
char volatile *tx_buf_tail;		/* ptr to next avail loc for insert */
char volatile *tx_buf_end;		/* ptr to last possible loc in buffer */
char volatile rx_buf[RX_SIZE];		/* circular buf for input chars */
char volatile *rx_buf_head;		/* ptr to next char to remove */
char volatile *rx_buf_tail;		/* ptr to next avail loc for insert */
char volatile *rx_buf_end;		/* ptr to last possible loc in buffer */

char volatile xoff_sent = FALSE;	/* flag set to indicate an Xon should */
					/*  be sent when data buffer has more */
					/*  space (can be set by ISR) */

char xoff_enable = TRUE;		/* flag used to indicate Xon/Xoff */
					/*  s/w flow control is permitted */
					/*  default set TRUE only set FALSE */
					/*  when in file transfer mode */

int volatile num_in_tx_buf = 0;		/* count of characters being held in */
					/*  in the transmit FIFO */
					/*  used to determine if possible to */
					/*  put another character into FIFO */
int volatile num_in_rx_buf = 0;		/* count of characters being held in */
					/*  in the receive FIFO */
					/*  used to determine when S/W flow */
					/*  control characters should be sent */

char com1 = TRUE;			/* use com port 1 flag */
char com_init = COMM_PORT_INIT;		/* value used for BIOS com init int */
char com_div = FALSE;			/* flag to ind. BRG divisor supplied */
int com_div_value;			/* value of supplied BRG divisor */
int data_reg = 0x3f8;			/* I/O port for ACE data register */
int int_en_reg = 0x3f9;			/* ACE interrupt enable register port */
int int_id_reg = 0x3fa;			/* ACE interrupt ID register port */
int line_con_reg = 0x3fb;		/* ACE line control register port */
int modem_con_reg = 0x3fc;		/* ACE modem control register port */
int line_sts_reg = 0x3fd;		/* ACE line status register port */
int modem_sts_reg = 0x3fe;		/* ACE modem status register port */
int int_mask = 0x10;			/* 8259 interrupt controller mask for */
					/*  com port */
int dos_int = 0x0c;			/* int number for com ints */
					/* NOTE above values are default for */
					/*  COM1: */
 
union REGS rg;				/* structure used for int86 calls */
					/*  to pass register values */

/*
 * ISR for serial interrupts from Asynch. Comm. Element (ACE) - gets interrupt
 *  ID and if no interrupts are active sends EOI code to 8259 to permit further
 *  interrupts and then returns to interrupted routine.  Otherwise if
 *  interrupt ID indicates transmitter holding register empty send Xoff if
 *  appropriate or if no more data to be sent turn off transmitter holding
 *  register empty interrupt enable or if data to be sent send next byte and
 *  adjust tail pointer (including possible wrap around of circular queue)
 *  and decrement number in transmit queue.  Otherwise it is assumed that
 *  the interrupt ID register indicates that received data character is
 *  available and the character is read into the received data circular queue
 *  and that queue's head pointer is adjusted (including wraparound) and
 *  the number in received data queue is incremented.  If less than 16
 *  spaces are left in the received data queue then the sent Xoff flag is
 *  set and depending on whether the transmitter holding register is empty
 *  or not either an Xoff is sent to the ACE or the send Xoff flag is set
 *  so that the next transmitter holding register empty interrupt shall cause
 *  the Xoff to be sent.  For efficiency the ISR stays active until all
 *  sources of interrupts are processed.
 */
void interrupt proc_intr()
{
    static char send_xoff = FALSE;
    register unsigned char i;

top:
    disable();
    if ((i = inportb(int_id_reg)) == 1) {
	outportb(0x20, 0x20);		/* send EOI to interrupt controller */
	return;
    }
    enable();				/* permit higher priority ints. */
    if (i == 2) {				/* Tx interrupt ? */
    	if (inportb(line_sts_reg) & 0x20) {	/* THRE ? needed on XT only */
            if (xoff_enable && send_xoff) {	/* need to send Xon ? */
                outportb(data_reg, XOFF);
                send_xoff = FALSE;
            } else if (tx_buf_head == tx_buf_tail) {	/* more to send ? */
                outportb(int_en_reg, 1);      /* no - reset interrupt enable */
            } else {
                outportb(data_reg, *tx_buf_tail++);	/* yes - send it */
                if (tx_buf_tail == tx_buf_end) {	/* adjust tail ptr ? */
		    tx_buf_tail = tx_buf;
                }
                --num_in_tx_buf;
            }
        }
        goto top;
    } else if (i == 4) {			/* Rx interrupt ? */
	*rx_buf_head++ = inportb(data_reg);	/* get data from ACE */
	if (rx_buf_head == rx_buf_end) {	/* adjust head pointer ? */
	    rx_buf_head = rx_buf;
	}
	++num_in_rx_buf;
						/* need send Xoff ? */
	if (xoff_enable && (num_in_rx_buf > RX_SIZE - 16)) {
	    if (inportb(line_sts_reg) & 0x20) {	 /* THRE ? ok send immed. ? */
		outportb(data_reg, XOFF);		/* yes send it */
	    } else {
		send_xoff = TRUE;		/* no flg send upon THRE int. */
	    }
	    xoff_sent = TRUE;		/* tell BG to send Xon when ok */
	}
        goto top;
    } else {		/* other sources of interrupt (should never occur) */
	inportb(modem_sts_reg);		/* clears modem status interrupt */
	inportb(line_sts_reg);		/* clears rec. line status interrupt */
        goto top;
    }
}
/*
 * send a character to serial interface (COM1/2), if transmit buffer full
 *  delay until space is available in the buffer, put character in circular
 *  queue at the head pointer then increment the head pointer adjusting
 *  for possible wraparound of the pointer, increment the number of
 *  characters in the output queue, make sure that transmitter interrupts are
 *  enabled as well as receive interrupts (which are never turned off)
 */
void send_char(ch)
char ch;
{
    while (disable(), num_in_tx_buf >= TX_SIZE - 2) {
	enable();
	disable();
    }
    *tx_buf_head++ = ch;
    if (tx_buf_head == tx_buf_end) {
	tx_buf_head = tx_buf;
    }
    ++num_in_tx_buf;
    outportb(int_en_reg, 3);		/* insure tx interrupts are enabled */
    enable();
}
/*
 * get a character from the receive queue if any, return TRUE if character
 *  found, FALSE if no character available. If Xoff previously sent see if
 *  adequate space is now available in the receive queue so that character
 *  reception can now resume and if so then an Xon is sent.
 */
int receive_char(chp)
char *chp;
{
    register char c;

    if (num_in_rx_buf) {
 
        disable();
        c = *rx_buf_tail++;
        if (rx_buf_tail == rx_buf_end) {
            rx_buf_tail = rx_buf;
        }
        --num_in_rx_buf;
        enable();
        if (xoff_sent && (num_in_rx_buf < (RX_SIZE - 64))) {
            send_char(XON);
            xoff_sent = FALSE;
        }
 
        *chp = (char) (c & char_mask);
        return (TRUE);
    } else {
        return (FALSE);
    }
}
/*
 * Initialize the tx/rx buffers, UART, and interrupt vectors for UART
 *  Note that the COM1/2 interrupt vector is assumed to be disposable. 
 *  Note in the UART cleanup the setting of OUT2 is required or else no 
 *  interrupts are ever generated by the async. card. Initialization 
 *  finishes by sending an Xon to the serial interface to re-enable data 
 *  transfers from the remote unit.
 */

void init()

{
    register int i;
 
		/* setup FIFO buffers */
 
    tx_buf_head = tx_buf_tail = tx_buf;
    tx_buf_end = tx_buf + TX_SIZE - 1;
    rx_buf_head = rx_buf_tail = rx_buf;
    rx_buf_end = rx_buf + RX_SIZE - 1;

		/* initialize UART register pointers */
 
    if (!com1) {		/* only change if not COM1 */
	data_reg = 0x2f8;
	int_en_reg = 0x2f9;
	int_id_reg = 0x2fa;
	line_con_reg = 0x2fb;
	modem_con_reg = 0x2fc;
	line_sts_reg = 0x2fd;
	modem_sts_reg = 0x2fe;
	int_mask = 0x08;
	dos_int = 0x0b;
    }

		/* initialize UART */
 
    rg.h.ah = 0;
    rg.h.al = com_init;
    rg.x.dx = 1 - com1;
    int86(0x14, &rg, &rg);

		/* cleanup UART setup */
 
    disable();
    if (com_div) {
        i = inportb(line_con_reg) | 0x80;	/* insure div latch on */
        outportb(line_con_reg, i);
        outportb(data_reg, com_div_value);      /* send divisor value to ACE */
        outportb(int_en_reg, com_div_value >> 8);
    }
    i = inportb(line_con_reg) & 0x7f;		/* insure div latch off */
    outportb(line_con_reg, i);
    outportb(modem_con_reg, MODEM_CONTROL);	/* set RTS, DTR, OUT2 */
    inportb(line_sts_reg);			/* clear status reg */
    inportb(data_reg);				/* discard any char rcvd */
    i = inportb(INT_MASK_REG) & ~int_mask;	/* enable intr on 8259 */
    outportb(INT_MASK_REG, i);
    outportb(int_en_reg, 1);			/* enable rx data ints */
    enable();

		/* setup linkage to COM1/2 interrupt routine */
 
    setvect(dos_int, proc_intr);
 
		/* send Xon in case link is waiting on software flow control */
 
    send_char(XON);
}
/*
 * Restore machine state upon exit, send Xoff to tell remote unit
 *  to stop sending data and kill interrupt processing on the async 
 *  interface
 */
void restore()
{
    register int temp;
    
    send_char(XOFF);			/* tell other end to stop sending */
 
		/* disable interupt processing on COM port */
 
    disable();				/* disable intr on 8259 */
    temp = inportb(INT_MASK_REG) | int_mask;
    outportb(INT_MASK_REG, temp);
    outportb(int_en_reg, 0);			/* disable recv data int */
    outportb(modem_con_reg, 0); 		/* reset RTS, DTR, OUT2 */
    enable();
}
/*
 * find and read initialization parameters from a user specified file
 *  given on the command line.  The file must have the following format:
 *  binary file,
 *	byte 1 - normal initialization data used by BIOS to initialize UART
 *	byte 2 - flag indicating presence of user supplied BRG divisor value
 *	byte 3 - com1 flag TRUE (1) if use COM1 else FALSE (0) for COM2
 *	byte 4 - LSBs of user supplied BRG divisor value (only present if 
 *  byte 2 is TRUE)
 *	byte 5 - MSBs of user supplied BRG divisor value (only present if 
 *  byte 2 is TRUE)
 */
static void init_file(incoming)
char *incoming;
{
    FILE *stream;
    int found;
 
    found = FALSE;
    if (incoming != NULL) {
	if ((stream = fopen(incoming, "rb")) == NULL) {
	    puts("could not open requested init file\n");
	} else {
	    found = TRUE;
	}
    }
    if (found) {
	com_init = fgetc(stream);
	com_div = fgetc(stream);
	com1 = fgetc(stream);
	if (com_div) {
	    i = fgetc(stream);
	    com_div_value = (i & 255) | (fgetc(stream) << 8);
	}
	fclose(stream);
    }
}
/*
 * Main routine, can accept one optional command line parameter - the
 *  name of an initialization data file. The user specified data file 
 *  is read in and using the parameters from the file or the default 
 *  parameter values the software and hardware is initialized.
 */

main(argc, argv)
int	argc;
char	*argv[];
{
    char ch, *chp;

    if (argc < 2) {
	chp = NULL;
    } else {
	chp = argv[1];
    }

    init_file(chp);		/* read in initialization data if present */
 
    init();			/* initialize software and hardware */

/*
 * The main body of the code goes here $$$$$$$$$$$$$$$$$$$$$$$
 */

    restore();
}