[comp.lang.c] Serial Com

mark.longo@canremote.uucp (MARK LONGO) (11/15/89)

  I am writing a small terminal program with quick`c'.  I am using the
libraries supplied by microsoft (bios_serialcom).  I do NOT WISH TO USE
A FOSSIL as this is a project , that forbids the use of one.

  My `SEND' routine works fine , but I am having trouble with the
receive part.  Basically what happens is that characters are not `all'
being read from the com port.

 For example.  If I type ATZ anbd and press return , i simply get K ,
instead of OK.  [at 1200/2400 bps] ... But at 300 bps .. I get `OK'

 When I call a BBS at 1200/2400 ... I can get most of what comes in ..
but not everything. Example:

   Please Enter Your Name:    , looks like:
   ease Enter Your Name:

 If I call at 300 bps , there is no problem.

 Now ... Is it because I do not have the serial buffer big enough, If so
, how do I set it bigger?

 Here is the BASIC idea behind my receive routine:

    unsigned int status;

    status = _bios_serialcom (_COM_STATUS,0,0);

    while (status&0x100)

    {
      status = _bios_serialcom (_COM_SEND,0,0);
      putch(status);
      status = _bios_serialcom (_COM_STATUS,0,0);
    }

  and that is it:  I tried using a If (status&0x100) , but still ... no
luck ... please help me out here!

    Mark Longo

 BOSS BBS 1-519-658-6433

---
 * Via ProDoor 3.1aR  Bit Systems ~ 330 megs ~ HST ~ Doors ~ (519)767-1755
 * RNet 1.02: CanConf ~ Bit Systems ~ Guelph ~ 519-767-1755 HST

mike@relgyro.stanford.edu (Mike Macgirvin) (11/16/89)

In article <89111504064675@masnet.uucp> mark.longo@canremote.uucp (MARK LONGO) writes:
>
>  I am writing a small terminal program with quick`c'.  I am using the
>libraries supplied by microsoft (bios_serialcom).  I do NOT WISH TO USE
>A FOSSIL as this is a project , that forbids the use of one.
>
>  My `SEND' routine works fine , but I am having trouble with the
>receive part.  Basically what happens is that characters are not `all'
>being read from the com port.
>
[other text deleted]
	This is a major headache, I agree. It boils down to the fact that
the BIOS serial port functions do NOT work in interrupt mode. That is,
if you fail to read the port quick enough, another incoming character will
write over the top of it. Usually, the CPU can keep up with 300 baud if
you are sending to the screen, and considerably faster if you aren't (the
BIOS scroll window function takes a considerable amount of time). That's
why the first characters of the line are lost, you received two characters
while the screen was scrolling.
	The painful solution is to write (or purchase) an interrupt handler
for the serial port. There is a wealth of literature available on the
subject (everybody and their uncle has had to do this...). I don't have any
titles at my fingertips, sorry. I once wrote a memory resident utility to
set an interrupt handler that used the BIOS interface calls, but it broke
whenever any other modem program exited and turned the handler back off.
There was no way to trap this. Then I bit the bullet, and did what every
body else does... install your own for every program you use which uses
the com ports. 
	Check your local DOS bookstore, and flip through some 'C' books
looking for serial communciations. It's there. There are also third-party
libraries available which have fully operational and debugged interrupt
handler support. (Can someone supply a vendor?)

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
+  Mike Macgirvin              Relativity Gyroscope Experiment (GP-B)    +
+  mike@relgyro.stanford.edu   (36.64.0.50)                              +
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

smasters@gmuvax2.gmu.edu (Shawn Masters) (11/18/89)

Since I assume your using a PC, I'll relay what I found with using the
IBM BIOS serial port routines.
	The IBM BIOS only reads one character at a time, only when it's
asked for, and only the one in the serial ports send recieve buffer.
This causes lose of data in a few instances:
	1)When the data rate is fast enough that the next character is
already started before your program asks for the last one.  The BIOS
will return the character, but if you look at the high 8-bits, it also
returns an error.
	2)When you write to the buffer, when data is in there.  I've
never had this happen as to the nature of those past programs that used
the BIOS call.

	On a fast machine with nothing else to do, you might get away
with a few errors at the low speeds, and a number of errors in the
higher speeds(normally at the begining of lines, why I don't know,
that's just what seems to happen).  If you know what the input and
output is going to be like, then you can grab it with little or no
problem.  This though is rarely true, and a bad way of doing things.
	The way to go, is to intercept the COM interrupt, with a
function that stores the data in a buffer.  When the buffer is near
full, you send a flow control(XOFF or EIA).  Then when the buffer is
near empty(your program has read the values at it's leisure), you send
another flow control to start it back up again.  This is how 9600 is
done reliably on old PC's even with disk transfers and all sorts of
other stuff going on.
	One last thing, to make this work you must ineable the interrupt
on the UART in your PC.  If this isn't done, nothing will be read into
the buffer, or atleast until it checks.  Much like a baby that doesn't
cry(how many new parents are interrupt driven?).

Shawn C. Masters
George Mason University

smasters@gmuvax2.gmu.edu

davidsen@crdos1.crd.ge.COM (Wm E Davidsen Jr) (11/22/89)

In article <375@helens.Stanford.EDU> mike@relgyro.STANFORD.EDU (Mike Macgirvin) writes:

| 	The painful solution is to write (or purchase) an interrupt handler
| for the serial port. There is a wealth of literature available on the
| subject (everybody and their uncle has had to do this...). I don't have any
| titles at my fingertips, sorry. 

  Not too painful... I wrote one a few years ago which was called from C
with the address of a buffer and the length. At each interrupt the
character was put into the buffer. Another asm routine returned a char
to the C program. The whole thing was only a few hundred bytes.

  Before you ask, it's still considered proprietary and I can't send you
a copy. I asked before I posted.
-- 
bill davidsen	(davidsen@crdos1.crd.GE.COM -or- uunet!crdgw1!crdos1!davidsen)
"The world is filled with fools. They blindly follow their so-called
'reason' in the face of the church and common sense. Any fool can see
that the world is flat!" - anon

reino@cs.eur.nl (Reino de Boer) (11/23/89)

mike@relgyro.stanford.edu (Mike Macgirvin) writes:

>In article <89111504064675@masnet.uucp> mark.longo@canremote.uucp (MARK LONGO) writes:
>>
>>  My `SEND' routine works fine , but I am having trouble with the
>>receive part.  Basically what happens is that characters are not `all'
>>being read from the com port.
>>
>[other text deleted]
>	This is a major headache, I agree. It boils down to the fact that
>the BIOS serial port functions do NOT work in interrupt mode. That is,
> ......[text deleted]
>	The painful solution is to write (or purchase) an interrupt handler
>for the serial port. There is a wealth of literature available on the
> ......[text deleted]
>handler support. (Can someone supply a vendor?)

The following is some code in Turbo C, which works at our site, 
hope it helps -- Reino

/*  globdefs.h */

/*  portability questions */
#define DOS_ERRNO _doserrno
#define CREATE_TEXT "wt"

/*  documentation defines */
#define PRIVATE static
#define PUBLIC
#define IMPORT  extern
#define FALSE 0
#define TRUE  1
#define ENDSTR '\0'

/*  generic NULL value */
#define NIL( type ) (( type * ) NULL)

/*  IGNORE( f ) ignores the result of calling f in a graceful way */
#define IGNORE( value ) ( void ) (value)

/*  convert any value to TRUE or FALSE */
#define BOOLEAN( b ) ( (b) ? TRUE : FALSE )

#ifndef GLOBDEFS
#define GLOBDEFS

/*  useful type definitions */

typedef unsigned char byte;
typedef int bool;
typedef char *string;
typedef unsigned int word;
struct file_id_s {
	string filename;
	string version;
};
typedef struct file_id_s file_id_t;
#endif

/*****************************************************************************/
/*  timer.h */

#define ONE_SECOND 19	/*  actually 18.2 */

/*  interface functions */
#define CLOCKTICKS( ) clockticks
#define TIMED_OUT( start, period ) ( NoTimer ? delay( 500 ), TRUE :\
	BOOLEAN( CLOCKTICKS( ) - (start) > (period) ) )
#define WAIT( msecs ) delay( msecs )

/*  should be called before the others */
IMPORT void init_timer( void );

/*  the following should never be used directly */
IMPORT long clockticks;
IMPORT bool NoTimer;

/*****************************************************************************/
/*  timer.c */

#include "globdefs.h"
#include <dos.h>
#include <stdlib.h>

/*  source file identification */
PRIVATE file_id_t FileId = { __FILE__, "%I%" };

/*  exported variables: */
/*  clockticks contains the system dependent clock count. It is increased
    approximately 18.2 times a second. */
/*  NoTimer controls whether clockticks can be used */
PUBLIC long clockticks = 0L;
PUBLIC bool NoTimer = TRUE;

/*  private variables: */
/*  old_timer contains the old vector for the timer interrupt */
PRIVATE void ( interrupt *old_timer )( void );

/*  interrupt number of software timer interrupt */
#define TIMER_INT 0x1c

/*  isr for timer interrupt. Updates clockticks and then chains to the old
	isr which may be in use for other purposes. */
PRIVATE void interrupt timer( void )
{
	clockticks++;
	( *old_timer )();
}

/*  graceful exit */
PRIVATE void exit_timer( void )
{
	NoTimer = TRUE;
	setvect( TIMER_INT, old_timer );
}

/*  initialization routine for this module */
PUBLIC void init_timer( void )
{
	old_timer = getvect( TIMER_INT ); /*  get old vector */
	setvect( TIMER_INT, timer ); /*  install our isr */
	NoTimer = FALSE; /*  we can use clockticks now */
	IGNORE( atexit( exit_timer ) ); /*  prepare graceful exit */
}

/*****************************************************************************/
/*  bits.h */

/*  return value for only bit n set */
#define BIT( n ) ( 1 << (n) )

/*  return b with high bit on */
#define HIGH( b ) ( (b) | BIT( 7 ) )

/*****************************************************************************/
/*  serial.h */

/*  error bits for SerialError */
#define E_OVERRUN BIT( 1 )
#define E_PARITY  BIT( 2 )
#define E_FRAMING BIT( 3 )
#define BREAK_INT BIT( 4 ) /*  Break received */

#define USER_INT 0x0800
#define TIME_OUT 0x1000

/*  Error checking in LSR */
#define E_NONE  0x0000 /*  No error */
#define E_LOGIC 0x0100
#define ERROR_MASK ( E_OVERRUN | E_PARITY | E_FRAMING | BREAK_INT | E_LOGIC )

IMPORT volatile word SerialError;
IMPORT volatile bool Busy;

/*  Buffer handling */
#define BUFFER_SIZE 4096 /*  Must be a power of 2 */

/*  general buffer access routines */
IMPORT  int Inwaiting; /*  should never be used directly */
#define IN_FULL( )   ( Inwaiting  >= BUFFER_SIZE )
#define IN_EMPTY( )  ( Inwaiting  <= 0 )
#define IN_WAITING( )  ( Inwaiting > 0 )
#define IN_OVERFLOW  0x0200

IMPORT  int Outwaiting; /*  should never be used directly */
#define OUT_FULL( )  ( Outwaiting >= BUFFER_SIZE )
#define OUT_EMPTY( ) ( Outwaiting <= 0 )
#define OUT_WAITING( ) ( Outwaiting > 0 )
#define OUT_OVERFLOW 0x0400

IMPORT void dispose_garbage( void );
IMPORT void poll( bool sending );
IMPORT void send( byte b );
IMPORT bool receive( byte *b, bool sending );
IMPORT void do_break( void );
IMPORT void send_break( void );
IMPORT void reset_UART( void );
IMPORT void send_XOFF( void );
IMPORT void send_XON( void );

/*  should be called before any of the other routines */
IMPORT void init_com( void );

/*****************************************************************************/
/*  serial.c */

/* #define STAT /*  when in need of isr statistics */
/* #define M_DEBUG /*  when in need of i/o-debugging */

#include "globdefs.h"
#include <stdlib.h>
#include <stdio.h>
#include <conio.h>
#include <dos.h>
#include "except.h"
#include "bits.h"
#include "ascii.h" /*  defines all ASCII codes as mnemonics */
#include "timer.h"
#include "uims.h" /*  defines message( ) */
#include "serial.h"

/*  source file identification */
PRIVATE file_id_t FileId = { __FILE__, "%I%" };

#ifdef M_DEBUG
#define DEBUGCH( c ) ( (c) == '\r' ? putch( '\n' ) : putch( (c) ) )
#endif

/*  chosen serial port */
#define COM1

/*  base addresses of serial ports */
#define COM1_BASE 0x3f8
#define COM2_BASE 0x2f8
#ifdef COM1
#define COM_BASE COM1_BASE
#else
#define COM_BASE COM2_BASE
#endif

/*  bit rate divisor table */
#define BPS_45_5  2532
#define BPS_50    2304
#define BPS_75    1536
#define BPS_110   1047
#define BPS_134_5 857
#define BPS_150   768
#define BPS_300   384
#define BPS_600   192
#define BPS_1200  96
#define BPS_1800  64
#define BPS_2000  58
#define BPS_2400  48
#define BPS_4800  24
#define BPS_9600  12
#define BPS_19200 6
#define BPS_38400 3

/*  chosen divisor */
#define BPS BPS_9600

/*  port offsets 0x00 and 0x01 from chosen base address */
/*  when DLAB is set in LCR */
#define BAUD0 ( COM_BASE + 0x00 )
#define BAUD1 ( COM_BASE + 0x01 )
/*  When DLAB isn't set in LCR */
#define RBR   ( COM_BASE + 0x00 ) /*  when read from  */
#define THR   ( COM_BASE + 0x00 ) /*  when written to */
#define IER   ( COM_BASE + 0x01 )

/*  other port offsets from chosen base address */
#define IIR   ( COM_BASE + 0x02 )
#define LCR   ( COM_BASE + 0x03 )
#define MCR   ( COM_BASE + 0x04 )
#define LSR   ( COM_BASE + 0x05 )
#define MSR   ( COM_BASE + 0x06 )

/*  port retrieval functions */
#define GET_RBR( ) inportb( RBR )
#define GET_IIR( ) inportb( IIR )
#define GET_LCR( ) inportb( LCR )
#define GET_MCR( ) inportb( MCR )
#define GET_LSR( ) inportb( LSR )
#define GET_MSR( ) inportb( MSR )

/*  port update procedures */
#define SET_THR( v ) outportb( THR, (v) )
#define SET_IER( v ) outportb( IER, (v) )
#define SET_LCR( v ) outportb( LCR, (v) )
#define SET_MCR( v ) outportb( MCR, (v) )

/*  bits for Interrupt Enable Register (IER) */
#define RBR_ENABLE BIT( 0 )
#define THR_ENABLE BIT( 1 )
#define LSR_ENABLE BIT( 2 )
#define MSR_ENABLE BIT( 3 )

/*  chosen enabled classes */
#define ENABLE_BITS ( RBR_ENABLE | THR_ENABLE | LSR_ENABLE | MSR_ENABLE )
#define ENABLE_ALL( )  SET_IER( ENABLE_BITS )
#define DISABLE_ALL( ) SET_IER( 0x00 )

/*  bits for Interrupt Identification Register (IIR) */
#define MSR_ID 0x00
#define NO_ID  0x01
#define THR_ID 0x02
#define RBR_ID 0x04
#define LSR_ID 0x06

/*  bits for Line Control Register (LCR) */
#define DATA_BITS_5 0x00
#define DATA_BITS_6 0x01
#define DATA_BITS_7 0x02
#define DATA_BITS_8 0x03
#define STOP_BITS_1 0x00
#define STOP_BITS_2 0x04 /*  1.5 stop bits when 5 data bits */
#define NO_PARITY   0x00
#define ODD_PARITY  0x08
#define EVEN_PARITY 0x18
#define PARITY_1    0x28
#define PARITY_2    0x38
#define BREAK_BIT BIT( 6 )
#define DLAB BIT( 7 ) /*  Divisor Latch Access Bit */

/*  chosen line control */
#define LINE_CONTROL ( DATA_BITS_8 | STOP_BITS_1 | NO_PARITY )

/*  Line Control Register access routines */
#define SET_BREAK( )   SET_LCR( LINE_CONTROL | BREAK_BIT )
#define CLEAR_BREAK( ) SET_LCR( LINE_CONTROL )
#define SET_DLAB( )    SET_LCR( LINE_CONTROL | DLAB )
#define CLEAR_DLAB( )  SET_LCR( LINE_CONTROL )

/*  bits for Modem Control Register (MCR) */
#define DTR       BIT( 0 )  /*  signal Data Terminal ready       */
#define RTS       BIT( 1 )  /*  request To Send                  */
#define OUT1      BIT( 2 )  /*  reset Hayes 1200b internal modem */
#define OUT2      BIT( 3 )  /*  controls 8250 interrupt signals  */
#define SELF_TEST BIT( 4 )  /*  turn on self-test configuration  */

/*  default settings for MCR */
#define MODEM_CONTROL ( DTR | RTS | OUT2 )

/*  bits for Line Status Register (LSR) */
#define RDR       BIT( 0 ) /*  Received Data Ready */

/*  The next bits are defined in serial.h
#define E_OVERRUN BIT( 1 )
#define E_PARITY  BIT( 2 )
#define E_FRAMING BIT( 3 )
#define BREAK_INT BIT( 4 ) Break received */
#define THR_EMPTY BIT( 5 )
#define TSR_EMPTY BIT( 6 )

/*  error checking in LSR is defined in serial.h
#define E_NONE  0x0000 No error
#define E_LOGIC 0x0100
#define ERROR_MASK ( E_OVERRUN | E_PARITY | E_FRAMING | BREAK_INT | E_LOGIC ) */

/*  bits for Modem Status Register (MSR) */
#define CTS_CHANGED BIT( 0 )
#define DSR_CHANGED BIT( 1 )
#define RI_CHANGED  BIT( 2 )
#define DCD_CHANGED BIT( 3 )
#define CTS_LEVEL   BIT( 4 )
#define DSR_LEVEL   BIT( 5 )
#define RI_LEVEL    BIT( 6 )
#define DCD_LEVEL   BIT( 7 )

/*  Modem Status Register access functions */
#define CTS_VALUE( ) ( ( ModemStatus & CTS_LEVEL ) ? 0x01 : 0x00 )
#define DSR_VALUE( ) ( ( ModemStatus & DSR_LEVEL ) ? 0x01 : 0x00 )
#define RI_VALUE( )  ( ( ModemStatus & RI_LEVEL  ) ? 0x01 : 0x00 )
#define DCD_VALUE( ) ( ( ModemStatus & DCD_LEVEL ) ? 0x01 : 0x00 )

/* Interrupt ReQuest lines */
#define COM1_IRQ 4
#define COM2_IRQ 3
#ifdef COM1
#define COM_IRQ COM1_IRQ
#else
#define COM_IRQ COM2_IRQ
#endif

/*  corresponding Hardware Interrupt Vectors */
#define COM1_INTR 0x0c
#define COM2_INTR 0x0b
#ifdef COM1
#define COM_INTR COM1_INTR
#else
#define COM_INTR COM2_INTR
#endif

/*  registers for Programmable Interrupt Controller (PIC) */
#define ISR 0x20
#define IMR 0x21

/*  send end-of-interrupt */
#define EOI( ) outportb( ISR, 0x20 )

/*  enable, disable interrupts from chosen port */
#define ENABLE( )  outportb( IMR, inportb( IMR ) & ~( 1 << COM_IRQ ) )
#define DISABLE( ) outportb( IMR, inportb( IMR ) |  ( 1 << COM_IRQ ) )

#ifdef STAT
PRIVATE long isrCalled  = 0L;
PRIVATE long isrData    = 0L;
PRIVATE long isrTwice   = 0L;
#endif
PRIVATE long UARTresets = 0L;

/*  defined in serial.h
#define USER_INT 0x0800
#define TIME_OUT 0x1000 */

/*  periods to wait for outgoing or incoming data */
#define OUT_PERIOD ( 2L * ONE_SECOND )
#define IN_PERIOD 6

/*  some handy delay definitions */
#define WAIT( msecs ) delay( msecs )
#define WASTE_CYCLES( ) WAIT( 100 )

/*  length of a break signal in milliseconds */
#define BREAK_LEN 300

/*  exported variables: */
/*  SerialError contains accumulated errors from the serial module */
PUBLIC	volatile word SerialError = E_NONE;

/*  private variables: */
/*  LineStatus contains last value read from LSR */
/*  THR_free is TRUE if THR is empty, otherwise FALSE */
/*  ModemStatus contains last value read from MSR */
/*  Cts contains value of CTS in ModemStatus */
/*  Dsr contains value of DSR in ModemStatus */
/*  Ri contains value of RI in ModemStatus */
/*  Dcd contains value of DCD in ModemStatus */
/*  old_isr contains old interrupt vector for the chosen serial port */
/*  VectorSet is TRUE if old_isr contains valid data, otherwise FALSE */
/*  Installed is TRUE if installation was successful, otherwise FALSE */
/*  disposing is TRUE when disposing garbage */
PRIVATE volatile byte LineStatus = 0;
PRIVATE volatile bool THR_free = FALSE;
PRIVATE volatile byte ModemStatus = 0;
PRIVATE volatile byte Cts = 0;
PRIVATE volatile byte Dsr = 0;
PRIVATE volatile byte Ri  = 0;
PRIVATE volatile byte Dcd = 0;
PRIVATE void ( interrupt *old_isr )( void );
PRIVATE bool VectorSet = FALSE;
PRIVATE bool Installed = FALSE;
PRIVATE bool disposing = FALSE;

/*  exported variables: */
/*  Busy is TRUE if channel is busy, otherwise FALSE */
PUBLIC  volatile bool Busy;

/*  buffer handling */
/*  Defined in serial.h
#define BUFFER_SIZE 4096	    Must be a power of 2 */
/*  INDEX_MASK speeds up buffer operations. If ever BUFFER_SIZE is not a
    power of 2, INDEX_MASK becomes useless:
    x &= INDEX_MASK then becomes x %= BUFFER_SIZE */
#define INDEX_MASK  ( BUFFER_SIZE - 1 )
/*  buffers are arrays of byte */
typedef byte buffer_t[BUFFER_SIZE];

/*  The input buffer contains incoming data stored by the isr( ), in a
	sequential circular queue. The input buffer is emptied by receive( ) */
PRIVATE buffer_t Indata;
PUBLIC  int Inwaiting  = 0;
PRIVATE int Infront    = 0;
PRIVATE int Inrear     = 0;
/*  defined in serial.h
#define IN_FULL( )   ( Inwaiting  >= BUFFER_SIZE )
#define IN_EMPTY( )  ( Inwaiting  <= 0 )
#define IN_WAITING( )  ( Inwaiting > 0 )
#define IN_OVERFLOW  0x0200 */

/*  The output buffer is filled by send( ), and emptied by poll( TRUE ).
    It is a sequential circular queue, as the input buffer */
PRIVATE buffer_t Outdata;
PUBLIC  int Outwaiting = 0;
PRIVATE int Outfront   = 0;
PRIVATE int Outrear    = 0;
/*  defined in serial.h
#define OUT_FULL( )  ( Outwaiting >= BUFFER_SIZE )
#define OUT_EMPTY( ) ( Outwaiting <= 0 )
#define OUT_WAITING( ) ( Outwaiting > 0 )
#define OUT_OVERFLOW 0x0400 */

/*  store b in the input buffer.
    If the buffer is full, the output overflow bit is set in SerialError;
    this should be considered a serious error.
    This routine also does some essential Multilink handling, although it
    never eats any data. */
PRIVATE void put_in( byte b )
{
	if( IN_FULL( ) )
		SerialError |= IN_OVERFLOW;
	else {
		if( b == XOFF )
			Busy = TRUE;
		else if( b == XON )
			Busy = FALSE;
		else {
			Indata[Inrear++] = b; /*  normal buffering */
			Inrear &= INDEX_MASK;
			Inwaiting++;
		}
	}
}

/*  get next byte from input buffer. Used only by receive( ). Returns
    TRUE if there was data, FALSE if queue was empty */
PRIVATE bool get_in( byte *b )
{
	if( IN_EMPTY( ) )
		return( FALSE );
	else {
		disable( ); /*  temporarily disable interrupts */
		*b = Indata[Infront++];
		Infront &= INDEX_MASK;
		Inwaiting--;
		enable( ); /*  free to be interrupted again */
		return( TRUE );
	}
}

/*  put b into the output buffer for later processing by poll( ).
    If output buffer is full, the output overflow bit in SerialError is set */
PRIVATE void put_out( byte b )
{
	if( OUT_FULL( ) )
		SerialError |= OUT_OVERFLOW;
	else {
		Outdata[Outrear++] = b;
		Outrear &= INDEX_MASK;
		Outwaiting++;
	}
}

/*  get next byte from output buffer to be sent to the port. Returns
    FALSE if buffer was empty, otherwise TRUE */
PRIVATE bool get_out( byte *b )
{
	if( OUT_EMPTY( ) )
		return( FALSE );
	else {
		*b = Outdata[Infront++];
		Outfront &= INDEX_MASK;
		Outwaiting--;
		return( TRUE );
	}
}

/*  interrupt service routine for chosen serial port. It is advisable
    to keep all local data static (PRIVATE) */
PRIVATE void interrupt isr( void )
{
	PRIVATE byte id;
	PRIVATE byte data;

#ifdef STAT
	isrCalled++;
#endif
	id = GET_IIR( ); /*  get cause of interrupt */
	enable( ); /*  enable other interrupts */
reswitch:
	if( ( id & MSR_ID ) == MSR_ID )
	{ /*  modem status change */
		ModemStatus = GET_MSR( );
		if( ModemStatus & CTS_CHANGED )
			Cts = CTS_VALUE( );
		if( ModemStatus & DSR_CHANGED )
			Dsr = DSR_VALUE( );
		if( ModemStatus & RI_CHANGED )
			Ri = RI_VALUE( );
		if( ModemStatus & DCD_CHANGED )
			Dcd = DCD_VALUE( );
	}
	if( ( id & LSR_ID ) == LSR_ID )
	{ /*  line status change. Error ? */
		LineStatus = GET_LSR( );
		SerialError |= ( LineStatus & ERROR_MASK );
		if( LineStatus & RDR )
		{ /*  incoming data in RBR */
			data = GET_RBR( ); /*  get data a.s.a.p */
			put_in( data ); /*  store for later processing */
#ifdef STAT
			isrData++;
#endif
		}
		if( LineStatus & THR_EMPTY )
		{ /*  THR empty. Free to send again */
			THR_free = TRUE;
		}
	}
	else
	{
		if( ( id & RBR_ID ) == RBR_ID )
		{ /*  incoming data in RBR */
			data = GET_RBR( ); /*  get data a.s.a.p */
			put_in( data ); /*  store for later processing */
#ifdef STAT
			isrData++;
#endif
		}
		else if( ( id & THR_ID ) == THR_ID )
		{ /*  THR empty. Free to send again */
			THR_free = TRUE;
		}
	}
	id = GET_IIR( ); /*  see if there's another interrupt pending */
	if( id != NO_ID )
	{
#ifdef STAT
		isrTwice++;
#endif
		goto reswitch; /*  handle it immediately */
	}
	disable( ); /*  disable interrupts */
	EOI( ); /*  send end-of-interrupt */
}

/*  try to send one byte to the serial port. Return TRUE on success, FALSE
    on failure */
PRIVATE bool out_byte( byte b )
{
	PRIVATE long start; /*  used in timing loops */

	/*  RS-232C protocol:
	    DTR high (static condition)
		DSR high (should be static condition)

	    set RTS high (always set in Multilink protocol)
	    wait for CTS to become high

	    wait for THR to become empty (UART signal)

	    The waiting is done in loops that time out when nothing changes
	    during the OUT_PERIOD. */

	while( ! ( Dsr && Cts && THR_free ) ) {
		if( ! Dsr ) /*  should never happen, check anyway */ {
			start = CLOCKTICKS( );
			while( ! Dsr && ! TIMED_OUT( start, OUT_PERIOD ) ) {
				/*  Do nothing */
			}
			if( ! Dsr ) {
				SerialError |= TIME_OUT;
				ERROR_MSG( "wachten op DSR duurt te lang" );
				return( FALSE );
			}
		}
		if( ! Cts ) {
			start = CLOCKTICKS( );
			while( ! Cts && ! TIMED_OUT( start, OUT_PERIOD ) ) {
				/*  Do nothing */
			}
			if( ! Cts ) {
				SerialError |= TIME_OUT;
				ERROR_MSG( "wachten op CTS duurt te lang" );
				return( FALSE );
			}
		}
		if( ! THR_free ) {
			start = CLOCKTICKS( );
			while( ! THR_free && ! TIMED_OUT( start, OUT_PERIOD ) ) {
				/*  Do nothing */
			}
			if( ! THR_free ) {
				SerialError |= TIME_OUT;
				ERROR_MSG( "wachten op THR duurt te lang" );
				return( FALSE );
			}
		}
	}
	SET_THR( b ); /*  actually send byte */
	THR_free = FALSE; /*  update global THR status */
#ifdef M_DEBUG
	DEBUGCH( b );
#endif
	return( TRUE );
}

/*  try to send as many bytes as possible */
PRIVATE void out_bytes( void )
{
	PRIVATE byte b;

	if( Busy ) {
		int x, y;

		x = wherex( );
		y = wherey( );
		putch( '*' );
		while( Busy ) {
			poll( FALSE );
			SET_MCR( MODEM_CONTROL ); /*  establish modem control */
		}
		gotoxy( x, y );
		putch( ' ' );
		gotoxy( x, y );
	}
	while( OUT_WAITING( ) ) {
		if( INTERRUPTED( ) )
		{ /*  user interrupted */
			SerialError |= USER_INT;
			return;
		}
		b = Outdata[Outfront]; /*  peek ahead to lose nothing */
		if( out_byte( b ) ) /*  update output buffer */
			IGNORE( get_out( &b ) );
		else
			return;
	}
}

/*  interface routine to poll the port. Use only when sending is
	necessary.
    If sending = TRUE and no errors occurred try to send as many bytes
    as possible. Earlier versions also aborted on serial errors */
PUBLIC void poll( bool sending )
{
	if( sending ) /*  send as many bytes as possible */
		out_bytes( );
	else if( INTERRUPTED( ) )
		SerialError |= USER_INT;
	if( SerialError )
	{
		SerialError &= ~TIME_OUT;
		if( INTERRUPTED( ) )
			FATAL_MSG( "onderbroken" );
		return;
	}
}

/*  interface routine to send a byte to the serial port.
    If it is the only byte to be sent, try to send it immediately, otherwise
    store for later processing by poll( ) */
PUBLIC void send( byte b )
{
	if( Busy ) {
		int x, y;

		x = wherex( );
		y = wherey( );
		putch( '*' );
		while( Busy ) {
			poll( FALSE );
			SET_MCR( MODEM_CONTROL ); /*  establish modem control */
		}
		gotoxy( x, y );
		putch( ' ' );
		gotoxy( x, y );
	}
	if( OUT_EMPTY( ) ) {
		if( ! out_byte( b ) )
		{ /*  sending failed, store for processing by poll */
			put_out( b );
			poll( TRUE );
		}
	}
	else {
		put_out( b );
		poll( TRUE );
	}
}

/*  interface routine to retrieve data from the serial port. Returns TRUE
    if data was present, otherwise returns FALSE. receive( ) waits for
	IN_PERIOD to see if data is coming in. If no data was received,
	poll is called with parameter sending. */
PUBLIC bool receive( byte *b, bool sending )
{
	PRIVATE long start;

	start = CLOCKTICKS( );
	do {
		if( get_in( b ) ) {
#ifdef M_DEBUG
			DEBUGCH( *b );
#endif
			return( TRUE );
		}
		else if( INTERRUPTED( ) ) {
			SerialError |= USER_INT;
			return( FALSE );
		}
	}
	while( ! TIMED_OUT( start, IN_PERIOD ) );
	SerialError |= TIME_OUT;
	poll( sending );
	return( FALSE );
}

/*  dispose of all incoming data. Resets TIME_OUT bit in SerialError */
PUBLIC void dispose_garbage( void )
{
	byte junk;

	disposing = TRUE;
	while( receive( &junk, TRUE ) && ! INTERRUPTED( ) ) {
		/*  Do nothing */
	}
	disposing = FALSE;
	if( INTERRUPTED( ) )
		SerialError |= USER_INT;
	SerialError &= ~TIME_OUT;
}

/*  send a break signal and dispose of all incoming data */
PUBLIC void do_break( void )
{
	SET_BREAK( );
	WAIT( BREAK_LEN );
	CLEAR_BREAK( );
	WASTE_CYCLES( );
}

PUBLIC void send_break( void )
{
	do_break( );
	dispose_garbage( );
}

/*  init variables for this module */
PRIVATE void init_com_data( void )
{
	Busy = FALSE;
	do /*   initialize data per existing signals, and handle all
		possible interrupts pending */ {
		LineStatus = GET_LSR( );
		THR_free = BOOLEAN( LineStatus & THR_EMPTY );
		( void ) GET_RBR( );
		ModemStatus = GET_MSR( );
		Cts = CTS_VALUE( );
		Dsr = DSR_VALUE( );
		Ri = RI_VALUE( );
		Dcd = DCD_VALUE( );
	}
	while( GET_IIR( ) != NO_ID );
}

/*  reset UART to a predefined state */
PRIVATE void reset_chip( void )
{
	disable( ); /*  disable all interrupts */
	DISABLE_ALL( ); /*  disable all interrupt types for the serial port */
	SET_MCR( GET_MCR( ) & ~MODEM_CONTROL ); /*  disable modem control */
	DISABLE( ); /*  disable interrupts for the serial port */
	enable( ); /*  enable all other interrupts */
}

/*  as graceful an exit as possible */
PRIVATE void exit_com( void )
{
	if( VectorSet ) /*  restore old interrupt vector */ {
		VectorSet = FALSE;
		setvect( COM_INTR, old_isr );
	}
	if( Installed ) /*  reset UART */ {
		Installed = FALSE;
		reset_chip( );
	}
#ifdef STAT
	if( Verbose ) {
		IGNORE( fprintf( stderr, "\nisr called %ld\n", isrCalled ) );
		IGNORE( fprintf( stderr, "isr data received %ld\n", isrData ) );
		IGNORE( fprintf( stderr, "isr reswitches %ld\n", isrTwice ) );
		IGNORE( fprintf( stderr, "UART resets %ld\n", UARTresets ) );
	}
#endif
}

PUBLIC void reset_UART( void )
{
	send_XOFF( );
#ifdef STAT
	UARTresets++;
#endif
	reset_chip( );
	disable( ); /*  temporarily disable all interrupts */
	SET_MCR( MODEM_CONTROL ); /*  establish modem control */
	ENABLE( ); /*  enable interrupts for the serial port */
	ENABLE_ALL( ); /*  enable all interrupt types for the serial port */
	EOI( ); /*  send an end-of-interrupt, just in case */
	enable( ); /*  free to be interrupted again */
	if( UARTresets == 1L ) {
		SET_DLAB( ); /*  program the baud rate generator */
		outport( BAUD0, BPS ); /*  set chosen bit-rate */
		CLEAR_DLAB( ); /*  return to normal operation */
	}
	WASTE_CYCLES( ); /*  give PBRG and UART some time to settle */
	send_XON( );
}

PUBLIC void send_XOFF( void )
{
	out_byte( HIGH( DLE ) );
	out_byte( HIGH( SO ) );
}

PUBLIC void send_XON( void )
{
	out_byte( HIGH( DLE ) );
	out_byte( HIGH( SI ) );
}

/*  initialize serial module */
PUBLIC void init_com( void )
{
	long start;

	( void ) atexit( exit_com ); /*  chain exit-proc */
	init_com_data( );
	if( ! VectorSet ) /*  retrieve old interrupt vector */ {
		old_isr = getvect( COM_INTR );
		VectorSet = TRUE;
	}
	setvect( COM_INTR, isr ); /*  install isr( ) */
	reset_UART( );
	Installed = TRUE;
	start = CLOCKTICKS( );
	while( ! Cts ) /*  wait for Cts to come up */ {
		if( TIMED_OUT( start, ONE_SECOND ) ) {
			FATAL_MSG(
				"geen node aangesloten ?..(wachten op CTS duurt te lang)" );
		}
	}
}

The code we use id in fact better documented, but the documentation
would take up much more bandwidth.

-- 
Reino R. A. de Boer
Erasmus University Rotterdam ( Informatica )
e-mail: reino@cs.eur.nl