[comp.os.msdos.programmer] Serial Comms Routines for Turbo C - Here's some

rhys@batserver.cs.uq.oz.au (Rhys Weatherley) (11/01/90)

Due to a few requests, here are my "bullet-proof" routines for serial comms
under Turbo C.  "Bullet-proof" that is, except 'comsave' and 'comrestore',
which were recently added, but still need some work, as they cause funny
things to happen to other comms programs after they have been used :-).  The
rest is OK as far as I'm able to test on my XT machine.  They have support
for up to 115.2 Kbaud, but I don't know if they can handle interrupts that
fast!

They are released to the public domain, and you can do anything you like
with them, but it would be nice if you mentioned who you got them from :-).

I invite criticisms, and down-right flames (if warranted) on this code, to
help improve its suitability for any comms serial application in Turbo C.
Suggestions for making this work under Microsoft C as well, are welcome,
but I can't test MSC versions myself.

Rhys.
------------------------cut-here--comms.h--------------------------------
/* COMMS.H - Communications Routines for Turbo C/C++ */
/*  Version 1.0 by Rhys Weatherley, July 1990.	     */
/*	Internet: rhys@batserver.cs.uq.oz.au	     */

#ifndef	__COMMS_H__
#define	__COMMS_H__

/* Defines for various port parameters */

#define	BAUD_110	0		/* Baud rates */
#define	BAUD_150	1
#define BAUD_300	2
#define	BAUD_600	3
#define	BAUD_1200	4
#define BAUD_2400	5
#define	BAUD_4800	6
#define	BAUD_9600	7
#define	BAUD_19200	8
#define BAUD_38400	9
#define	BAUD_115200	10

#define	BITS_7		0x00		/* Data bits */
#define	BITS_8		0x10

#define STOP_1		0x00		/* Stop bits */
#define STOP_2		0x20

#define PARITY_NONE	0x00		/* Parity modes */
#define	PARITY_ODD	0x40
#define PARITY_EVEN	0x80

/* Declare the functions as C for Turbo C++ */

#ifdef	__cplusplus
extern	"C" {
#endif

/* Enable a COM port for Interrupt Driven I/O by this module */
void	comenable (int port);

/* Save the current setting of a COM port to be restored later */
/* Call this function before calling 'comenable' on the port.  */
void	comsave (int port);

/* Set the communications parameters for a COM port */
/* e.g. params = BAUD_2400 | BITS_8		    */
void	comparams (int port,int params);

/* Disable the interrupt I/O for a COM port */
/* If 'leavedtr' != 0, then leave DTR up    */
void	comdisable (int port,int leavedtr);

/* Restore the setting of a COM port - after 'comdisable' */
void	comrestore (int port);

/* Turn a COM port's test loop-back mode on or off */
/* NOTE: comenable always turns the loop-mode off  */
void	comtest (int port,int on);

/* Return the number of available input chars on a COM port */
int	comavail (int port);

/* Flush all input from the COM port */
void	comflush (int port);

/* Receive a character from the COM port: -1 if not ready */
int	comreceive (int port);

/* Test to see if the COM port is ready for a new */
/* character to transmit.			  */
int	comready (int port);

/* Output a character to a COM port */
void	comsend (int port,int ch);

/* Test to see if a carrier is present */
int	comcarrier (int port);

/* Test to see if the DSR (Data Set Ready) signal is present */
int	comdsr (int port);

/* Drop the DTR signal on a COM port */
void	comdropdtr (int port);

/* Raise the DTR signal on a COM port */
void	comraisedtr (int port);

/* Set the BREAK pulse on a COM port to 0 or 1 */
void	combreak (int port,int value);

#ifdef	__cplusplus
}
#endif

#endif	__COMMS_H__
----------------------cut-here--comms.c------------------------------------
/* COMMS.C - Communications Routines for Turbo C/C++ */
/*  Version 1.0 by Rhys Weatherley, July 1990.	     */
/*	Internet: rhys@batserver.cs.uq.oz.au	     */

#include "comms.h"		/* Declarations for this module */
#include <dos.h>

/* Various PIC/COM masks and values */

#define PIC_MASK 0x21
#define	PIC_EOI  0x20
#define ERR_MSK	 0x9E

/* Definitions for interrupt handling */

/* COM port offsets */

#define COM_DATA	 0	/* Data received on this I/O address */
#define COM_IER  	 1	/* This register enables interrupts */
#define	COM_LCR		 3	/* Line control register */
#define COM_MCR		 4	/* Control Register (signals) */
#define COM_STAT	 5	/* Status Register */
#define	COM_MSR		 6	/* Modem Status Register */

/* Data for installing COM port interrupts */

#define COM_INT_1   0xC		/* 0xC handles IRQ4 or COM1 by standard */
#define INT_MASK_1  0x10	/* Mask for PIC (programmable interrupt
				   controller) 8259A */
#define COM_INT_2   0xB		/* 0xB handles IRQ3 or COM2 by standard */
#define INT_MASK_2  0x8		/* Mask for PIC (programmable interrupt
				   controller) 8259A */

/* Define the interrupt buffer structure */

#define	MAX_INT_BUFSIZ	4096
struct	IntBuf	{
		  char	buffer[MAX_INT_BUFSIZ];
		  int	input,output,size;
		  int	statport,dataport;
		  int	testbit;
		  int	oldier,oldmcr,oldlcr,oldbaud;
		};

static	struct	IntBuf	Com1Buf,Com2Buf;

/* Define the data for the interrupt handlers */

static void interrupt int_com1();
static void interrupt int_com2();
static void interrupt (*old_c1)();
static void interrupt (*old_c2)();

/* Change the test loop-back properties of a COM port (given MCR reg) */
static	int	changetest (mcrport,on)
int	mcrport,on;
{
  if (on)
    {
      outportb (mcrport, inportb (mcrport) | 0x10);
      return (0x10);
    }
   else
    {
      outportb (mcrport, inportb (mcrport) & 0x0F);
      return (0);
    }
}

/* Turn a COM port's test loop-back mode on or off */
/* NOTE: comenable always turns the loop-mode off  */
void	comtest (port,on)
int	port,on;
{
  switch (port)
    {
      case 1: Com1Buf.testbit = changetest (Com1Buf.dataport + COM_MCR,on);
	      break;
      case 2: Com2Buf.testbit = changetest (Com2Buf.dataport + COM_MCR,on);
	      break;
      default: break;
    }
}

/* Return the number of available input chars on a COM port */
int comavail (port)
int port;
{
	switch(port) {
		case 1:	return (Com1Buf.size);
		case 2: return (Com2Buf.size);
		default:return 0;
	}
}

/* Flush all input from the COM port */
void	comflush (port)
int	port;
{
	switch (port) {
		case 1: disable ();	/* Disallow ints while flushing */
			Com1Buf.size = 0;
			Com1Buf.output = Com1Buf.input;
			enable ();
			break;
		case 2: disable ();	/* Disallow ints while flushing */
			Com2Buf.size = 0;
			Com2Buf.output = Com2Buf.input;
			enable ();
			break;
		default:break;
	}
} /* comflush */

/* Receive a character from the COM port: -1 if not ready */
int comreceive(port)
int port;
{
	int ch;
	switch(port) {
		case 1:	if (Com1Buf.size == 0) return -1;
			else {
				ch = Com1Buf.buffer[Com1Buf.output];
				Com1Buf.output = (Com1Buf.output + 1) %
					MAX_INT_BUFSIZ;
				--Com1Buf.size;
				return ch;
			}
		case 2: if (Com2Buf.size == 0) return -1;
			else {
				ch = Com2Buf.buffer[Com2Buf.output];
				Com2Buf.output = (Com2Buf.output + 1) %
					MAX_INT_BUFSIZ;
				--Com2Buf.size;
				return ch;
			}
		default:return (-1);
	}
}

/*  COM1 Interrupt handler.  HARDWARE DEPENDENT */

static void interrupt int_com1()
{
	char ch;
	disable();	/* Make sure ints are disabled */
	/* If no error occured, then store the character */
	if ((ch = (inportb(Com1Buf.statport)&ERR_MSK)) == 0)
	  {
	    ch = inportb(Com1Buf.dataport);/* Get the character */
	    if (Com1Buf.size < MAX_INT_BUFSIZ) /* Store the char */
	      {
		Com1Buf.buffer[Com1Buf.input] = ch;
		Com1Buf.input = (Com1Buf.input + 1) % MAX_INT_BUFSIZ;
		++Com1Buf.size;
	      }
	  }
	 else
	  ch = inportb(Com1Buf.dataport);/* Remove the erroneous character */
	outportb(PIC_EOI,0x20);	/* Tell PIC we have handled the int */
}

/*  COM2 Interrupt handler.  HARDWARE DEPENDENT */

static void interrupt int_com2()
{
	char ch;
	disable();	/* Make sure ints are disabled */
	/* If no error occured, then store the character */
	if ((ch = (inportb(Com2Buf.statport)&ERR_MSK)) == 0)
	  {
	    ch = inportb(Com2Buf.dataport);/* Get the character */
	    if (Com2Buf.size < MAX_INT_BUFSIZ) /* Store the char */
	      {
		Com2Buf.buffer[Com2Buf.input] = ch;
		Com2Buf.input = (Com2Buf.input + 1) % MAX_INT_BUFSIZ;
		++Com2Buf.size;
	      }
	  }
	 else
	  ch = inportb(Com2Buf.dataport);/* Remove the erroneous character */
	outportb(PIC_EOI,0x20);	/* Tell PIC we have handled the int */
}

/* Enable a COM port given its I/O port address and other data */
static	void	doenable (port)
int	port;
{
  /* Set DLAB bit to zero to ensure that we */
  /* access the correct COM port registers  */
  outportb(port + COM_LCR,inportb(port + COM_LCR) & 0x7F);

  /* Turn off the chip's interrupts to start with */
  outportb(port + COM_IER,0);
  outportb(port + COM_MCR,8); /* Just DTR up for now */

  /* Read status and data ports to clear any outstanding errors */
  inportb(port + COM_STAT);
  inportb(port);

  /* Set ints for data available */
  outportb(port + COM_IER,1);
}

/* Enable a COM port for Interrupt Driven I/O by this module */
void comenable(port)
int port;
{
	char ch;
	int dataport;
	switch(port) {
		case 1: /* Initialise buffers for COM1 */
			Com1Buf.input = 0;
			Com1Buf.output = 0;
			Com1Buf.size = 0;
			dataport = *((int far *)0x400L);
			Com1Buf.dataport = dataport;
			Com1Buf.statport = Com1Buf.dataport + COM_STAT;
			Com1Buf.testbit = 0;

			doenable (dataport);	/* Setup the regs */

			/* Setup the ISR details */
			old_c1 = getvect(COM_INT_1); /* Save old COM1 int */
			setvect(COM_INT_1,int_com1); /* Install new int */

			/* Now turn on DTR, RTS and OUT2: all ready */
			outportb(dataport + COM_MCR,0x0B);

			/* Program the PIC to handle COM1 interrupts */
			ch = inportb(PIC_MASK);	/* Read current mask */
			ch &= (0xFF^INT_MASK_1);/* Reset mask for COM1 */
			outportb(PIC_MASK,ch);	/* Send it to the 8259A */
			break;
		case 2: /* Initialise buffers for COM2 */
			Com2Buf.input = 0;
			Com2Buf.output = 0;
			Com2Buf.size = 0;
			dataport = *((int far *)0x402L);
			Com2Buf.dataport = dataport;
			Com2Buf.statport = Com2Buf.dataport + COM_STAT;
			Com2Buf.testbit = 0;

			doenable (dataport);	/* Setup the regs */

			/* Setup the ISR details */
			old_c2 = getvect(COM_INT_2); /* Save old COM2 int */
			setvect(COM_INT_2,int_com2); /* Install new int */

			/* Now turn on DTR, RTS and OUT2: all ready */
			outportb(dataport + COM_MCR,0x0B);

			/* Program the PIC to handle COM2 interrupts */
			ch = inportb(PIC_MASK);	/* Read current mask */
			ch &= (0xFF^INT_MASK_2);/* Reset mask for COM2 */
			outportb(PIC_MASK,ch);	/* Send it to the 8259A */
			break;
		default:break;
	}
}

/* Save the current setting of a COM port to be restored later */
/* Call this function before calling 'comenable' on the port.  */
/* *** NOTE: haven't got this working properly yet! ***	       */
void	comsave (port)
int	port;
{
  int dataport;
  switch (port)
    {
      case 1:	dataport = *((int far *)0x400L);
		Com1Buf.oldlcr = inportb (dataport + COM_LCR);
		Com1Buf.oldmcr = inportb (dataport + COM_MCR);
		outportb (dataport + COM_LCR,Com1Buf.oldlcr & 0x7F);
		Com1Buf.oldier = inportb (dataport + COM_IER);
		outportb (dataport + COM_LCR,Com1Buf.oldlcr | 0x80);
		Com1Buf.oldbaud = inport (dataport);
		outportb (dataport + COM_LCR,Com1Buf.oldlcr);
      		break;
      case 2:	dataport = *((int far *)0x402L);
		Com2Buf.oldlcr = inportb (dataport + COM_LCR);
		Com2Buf.oldmcr = inportb (dataport + COM_MCR);
		outportb (dataport + COM_LCR,Com2Buf.oldlcr & 0x7F);
		Com2Buf.oldier = inportb (dataport + COM_IER);
		outportb (dataport + COM_LCR,Com2Buf.oldlcr | 0x80);
		Com2Buf.oldbaud = inport (dataport);
		outportb (dataport + COM_LCR,Com2Buf.oldlcr);
      		break;
      default:	break;
    }
}

/* Set the communications parameters for a COM port */
void	comparams (port,params)
int	port,params;
{
  static int divisors[11] =		/* COM port baud rate divisors */
	   {0x0417,0x0300,0x0180,0x00E0,0x0060,
	    0x0030,0x0018,0x000C,0x0006,0x0003,
	    0x0001};
  int value,dataport;
  switch (port)
    {
      case 1: dataport = Com1Buf.dataport; break;
      case 2: dataport = Com2Buf.dataport; break;
      default: return;
    }
  if (params & BITS_8)
    value = 0x03;
   else
    value = 0x02;
  if (params & STOP_2)
    value |= 0x04;
  if (params & PARITY_ODD)
    value |= 0x08;
   else if (params & PARITY_EVEN)
    value |= 0x18;
  outportb (dataport + COM_LCR,value | 0x80);	/* Set params and DLAB */
  outport  (dataport,divisors[params & 0x0F]);	/* Set the baud rate */
  outportb (dataport + COM_LCR,value);		/* Clear DLAB bit */
}

/* Disable the interrupt I/O for a COM port */
/* If 'leavedtr' != 0, then leave DTR up    */
void comdisable(port,leavedtr)
int port,leavedtr;
{
	char ch;
	switch(port){
		case 1: ch = inportb(PIC_MASK);	/* Get 8259A (PIC) Mask */
			ch |= INT_MASK_1;	/* Set Interrupt Mask COM1 */
			outportb(PIC_MASK,ch);	/* Write int. mask to 8259A*/

			/* Clear the interrupt enable register */
			outportb(Com1Buf.dataport + COM_IER,0);

			/* Clear OUT2, and set DTR as required */
			if (leavedtr)
			  outportb(Com1Buf.dataport + COM_MCR,1);
			 else
			  outportb(Com1Buf.dataport + COM_MCR,0);

			setvect(COM_INT_1,old_c1);/* Restore COM1 int */
			break;
		case 2: ch = inportb(PIC_MASK);	/* Get 8259A (PIC) Mask */
			ch |= INT_MASK_2;	/* Set Interrupt Mask COM1 */
			outportb(PIC_MASK,ch);	/* Write int. mask to 8259A*/

			/* Clear the interrupt enable register */
			outportb(Com2Buf.dataport + COM_IER,0);

			/* Clear OUT2, and set DTR as required */
			if (leavedtr)
			  outportb(Com2Buf.dataport + COM_MCR,1);
			 else
			  outportb(Com2Buf.dataport + COM_MCR,0);

			setvect(COM_INT_2,old_c2);/* Restore COM2 int */
			break;
		default:break;
	}
}

/* Restore the setting of a COM port - after 'comdisable' */
/* *** NOTE: haven't got this working properly yet! ***   */
void	comrestore (port)
int	port;
{
  int dataport;
  switch (port)
    {
      case 1:	dataport = *((int far *)0x400L);
		outportb (dataport + COM_LCR,Com1Buf.oldlcr & 0x7F);
		outportb (dataport + COM_IER,Com1Buf.oldier);
		outportb (dataport + COM_LCR,Com1Buf.oldlcr | 0x80);
		outport (dataport,Com1Buf.oldbaud);
		outportb (dataport + COM_LCR,Com1Buf.oldlcr);
		outportb (dataport + COM_MCR,Com1Buf.oldmcr);
      		break;
      case 2:	dataport = *((int far *)0x402L);
		outportb (dataport + COM_LCR,Com1Buf.oldlcr & 0x7F);
		outportb (dataport + COM_IER,Com1Buf.oldier);
		outportb (dataport + COM_LCR,Com1Buf.oldlcr | 0x80);
		outport (dataport,Com1Buf.oldbaud);
		outportb (dataport + COM_LCR,Com1Buf.oldlcr);
		outportb (dataport + COM_MCR,Com1Buf.oldmcr);
      		break;
      default:	break;
    }
}

/* Output a character to an I/O port */
static	void	dosend(dataport,statport,ch)
int	dataport,statport,ch;
{
  while (!(inportb(statport) & 0x20))
    ;	/* wait till port is available */
  outportb(dataport,ch);
}

/* Output a character to a COM port */
void	comsend(port,ch)
int port;
unsigned char ch;
{
	switch(port) {
		case 1:	dosend (Com1Buf.dataport,Com1Buf.statport,ch);
			break;
		case 2:	dosend (Com2Buf.dataport,Com2Buf.statport,ch);
			break;
		default:break;
	}
}

/* Test to see if a carrier is present */
int	comcarrier (port)
int	port;
{
  switch (port)
    {
      case 1: return ((inportb (Com1Buf.dataport + COM_MSR) & 0x80) != 0);
      case 2: return ((inportb (Com2Buf.dataport + COM_MSR) & 0x80) != 0);
      default: return 0;
    }
}

/* Test to see if the DSR (Data Set Ready) signal is present */
int	comdsr (port)
int	port;
{
  switch (port)
    {
      case 1: return ((inportb (Com1Buf.dataport + COM_MSR) & 0x20) != 0);
      case 2: return ((inportb (Com2Buf.dataport + COM_MSR) & 0x20) != 0);
      default: return 0;
    }
}

/* Test to see if the COM port is ready for a new */
/* character to transmit.			  */
int	comready (port)
int	port;
{
  switch (port)
    {
      case 1: return ((inportb (Com1Buf.statport) & 0x20) != 0);
      case 2: return ((inportb (Com2Buf.statport) & 0x20) != 0);
      default: return 0;
    }
}

/* Drop the DTR signal on a COM port */
void	comdropdtr (port)
int	port;
{
  switch (port)
    {
      case 1: outportb (Com1Buf.dataport + COM_MCR,0x0A |
				Com1Buf.testbit); break;
      case 2: outportb (Com2Buf.dataport + COM_MCR,0x0A |
				Com2Buf.testbit); break;
    }
}

/* Raise the DTR signal on a COM port */
void	comraisedtr (port)
int	port;
{
  switch (port)
    {
      case 1: outportb (Com1Buf.dataport + COM_MCR,0x0B |
				Com1Buf.testbit); break;
      case 2: outportb (Com2Buf.dataport + COM_MCR,0x0B |
				Com2Buf.testbit); break;
      default: break;
    }
}

/* Set the BREAK pulse on a COM port to 0 or 1 */
void	combreak (port,value)
int	port,value;
{
  switch (port)
    {
      case 1: outportb (Com1Buf.dataport + COM_LCR,
		(value ? inportb (Com1Buf.dataport + COM_LCR) | 0x40
		       : inportb (Com1Buf.dataport + COM_LCR) & 0xBF));
      case 2: outportb (Com2Buf.dataport + COM_LCR,
		(value ? inportb (Com2Buf.dataport + COM_LCR) | 0x40
		       : inportb (Com2Buf.dataport + COM_LCR) & 0xBF));
      default: break;
    }
}
------------------------------cut-here--THE-END--------------------------------

+===============================+==============================+
||  Rhys Weatherley             |  University of Queensland,  ||
||  rhys@batserver.cs.uq.oz.au  |  Australia.  G'day!!        ||
+===============================+==============================+