[net.micro.pc] RS232 interrupt code

starr@shell.UUCP (10/19/83)

Many people have expressed an interest in interrupt driven
communications. Here is a set of routines I wrote under the
CiC86 compiler which does this. The TRM was a BITCH for figuring
out how to do this. In the first issue of the TRM, there was a
fatal misprint, which caused much lost time in getting this to
work.

I have implemented the code in several communications programs
I have written, and it WILL work at 9600 baud. If you want to
write data coming in at 9600 baud to the screen, though, do NOT
use DOS. Write directly into screen memory.

Functions "sysint", "intrinit", "inportb" and "outportb" were
supplied with the C86 compiler.

sysint allows one to perform a system interrupt and set and check
all registers on interrupt.

intrinit sets up for interrupt processing. You pass it the interrupt
vector number, the number of bytes of extra stack space required, and
the address of the function to be invoked upon interrupt.

inportb and outportb read/write a byte from/to an I/O port.

Two more routines are called in here. One is "hl2x". This is my
own routine. It is named for the 80xx register naming conventions.
You have AH and AL. Together they are called AX. hl2x takes two
arguments, the first being the high byte (e.g. AH), and the second
being the low byte (e.g. AL) and returns a 16-bit unsigned integer,
where the high and low bytes are masked to 8 bits, then packed
into a single integer and returned.

The other routine called is "wait", used in com_break, where the
argument is the number of milliseconds to delay. My implementation
of this was too embarrasing to display in public.

You will notice that "hl2x" and "wait" were not included. They are
pretty easy to write.

At the end of the source is an assembler routine called
"com_set". This routine could be easily written in C. When I
first started writing this, I thought most of the work needed
to be done in assembler for speed. I backed alot of the
assembler code out and replaced it with C. This is one noncritical
function which SHOULD be in C.

The code was under development for some time. Many externally
declared variables may not need to exist, and some declarations
may be unnecessary. I wanted to clean the code up, but don't
presently have the time. I decide to copy the source code intact,
without changes, so that I wouldn't make a stupid blunder.

If someone uses this code and cares to clean it up, I would
apperciate getting a copy of the new (and perhaps more useful)
code.

Note: Although implied otherwise, only com1 is supported.


NAME

  com_open	; RS232 interrupt driven communications handler
  com_read	;
  com_write	;
  com_close	;

SYNOPSIS

  com_open (device,baud,parity,databits,stopbits)
  char *device............Must be "com1" or "com2"
  int baud................Allows 110,150,300,600,1200,4800 or 9600
  char parity.............Must be 'e', 'o', or 'n'
  int databits............Number of data bits, 7 or 8 allowed
  int stopbits............Number of stop bits, 1 or 2 allowed

  com_read (string)
  char *string............Read a string from com buffer

  com_write (string)
  char *string............Write string to the com device

  com_close ()............Close the communications line (essential!!!)

  com_break ()............Send break to com: line

FUNCTION

  This group of functions allow interrupt driven communications
  with either com device. At present, only one device may be open
  at a time. To handle communications up to 9600 baud, the com
  buffer is set to 1024 bytes internal to the function.

  A character will be placed in the com buffer whenever an interrupt
  is generated by the 8250 modem chip. The buffer fills in a circular
  fashion, so you better read and write the data often (especially at
  high baud rates), or you will lose data.



/*******************************************************************
 RS232 communications interface
 Author : RF Starr 1982

 Callable functions:

 com_open
 com_read
 com_write
 com_break
 com_close
*******************************************************************/

#define CR 13
#define LF 10
#define IER 0x3f9
#define MCR 0x3fc
#define MSR 0x3fe
#define LSR 0x3fd
#define INTA00 0x20
#define EOI 0x20
#define BUFSIZE 4096   /* The buffer that the interrupt vector fills */
#define ON 1
#define OFF 2
#define XON 17
#define XOFF 19
#define XTRA 127
#define LINEFEED 10
char c;
int kk;
struct regval
 { unsigned int ax,bx,cx,dx,si,di,ds,es; } srv,rrv;
static int rs_vec = 0x14;  /* RS232_IO in BIOS */
unsigned char combuf[BUFSIZE];
int dummy;
static int cptr = 0;
static int head = 0;


/* ***************************************************************** */
/* ****       \rs232 communications interface to BIOS		**** */
/* ***************************************************************** */


int_com()    /* Communications interrupt service routine */
{
 unsigned char inportb(),cbuf;
 unsigned int lsr_data;

 /* Check lsb of lsr... if 0, no data is available */
 lsr_data = inportb (0x3fd);
 if ( (lsr_data & 0x0001) == 0 )
  {
   outportb (0x3fc,0xb);  /* Reset MCR */
   outportb (0x20,0x20);  /* Send EOI to 8259 */
   return;
  }

 /* Data ready... get from com device */
 cbuf = inportb (0x3f8);  /* Read RBR for character */
 if (cptr >= BUFSIZE) cptr = 0;

 combuf[cptr++] = cbuf;

 outportb (0x3fc,0xb);	/* Reset MCR */
 outportb (0x20,0x20);	/* Send EOI to 8259 */
 return;
}


/* Routine to set up the RS232 interface ( baud rate, #data bits, etc.) */
com_open (device,baud,parity,databits,stopbits)
char *device;
int baud;
char parity;  /* Must be 'e', 'o', or 'n' */
int databits,
    stopbits;
{
  unsigned int rs232_param;
  unsigned int baud_bit,parity_bit,stop_bit,data_bit;
  char *stk_buf;
  /* extern com_int(); */
   extern char *alloc();

  switch (baud)
   {
    case 110:
    baud_bit = 0 ; break;

    case 150:
    baud_bit = 1 ; break;

    case 300:
    baud_bit = 2 ; break;

    case 600:
    baud_bit = 3 ; break;

    case 1200:
    baud_bit = 4 ; break;

    case 2400:
    baud_bit = 5 ; break;

    case 4800:
    baud_bit = 6 ; break;

    case 9600:
    baud_bit = 7 ; break;
   }

   switch (parity)
   {
    case 'n':
    parity_bit = 0 ; break;

    case 'o':
    parity_bit = 1 ; break;

    case 'e':
    parity_bit = 3 ; break;
   }

   switch (stopbits)
   {
    case 1:
    stop_bit = 0 ; break;

    case 2:
    stop_bit = 1 ; break;
   }

   switch (databits)
   {
    case 7:
    data_bit = 2 ; break;

    case 8:
    data_bit = 3 ; break;
   }

   rs232_param = 0;
   rs232_param |= (baud_bit << 5);
   rs232_param |= (parity_bit << 3);
   rs232_param |= (stop_bit << 2);
   rs232_param |= data_bit;

   srv.ax = rs232_param;  /* Tell Int14 to initialize for rs232 */
   srv.dx = 0;	/* Select COM1: */
   sysint (rs_vec,&srv,&rrv);
   intrinit (int_com,3000,12);
   com_set ();	 /* Activate com interrupt rs_vec */
   outportb (0x3fc,0x9);

   return;
}

com_read (string)   /* Read a string from COM1: */
char *string;
{
  int i,nchar,j,k;
  int quit_flg;
  unsigned char cbuf;

  *(string) = '\0';  /* Just in case */
  quit_flg = OFF ; i = 0;
  k = cptr;  /* Latch onto last character pointer */
  if (k == head) return 0;
  if (k > head)
   {
    while (head < k)
    { cbuf = combuf[head++];
      if (cbuf == '\0') cbuf = ' ';
      *(string+i++) = cbuf; }
    *(string+i) = '\0'; return i;
   }
  while (head < BUFSIZE)
   {
    cbuf = combuf[head++];
    if (cbuf == '\0') cbuf = ' ';
    *(string+i++) = cbuf;
   }
  head = 0;
  while (head < k)
   {
    cbuf = combuf[head++];
    if (cbuf == '\0') cbuf = ' ';
    *(string+i++) = cbuf;
   }
  *(string + i) = '\0';
  return i;  /* The number of characters read */
}

com_fread (string)   /* Read a string from COM1: and filter it */
char *string;
{
  int i,nchar,j,k;
  int quit_flg;
  unsigned char cbuf;

  *(string) = '\0';  /* Just in case */
  quit_flg = OFF ; i = 0;
  k = cptr;  /* Latch onto last character pointer */
  if (k == head) return 0;
  if (k > head)
   {
    while (head < k)
    { cbuf = combuf[head++];
      if ( (cbuf == XON) || (cbuf == XOFF) || (cbuf == XTRA) ) continue;
      if (cbuf == '\0') cbuf = ' ';
      *(string+i++) = cbuf; }
    *(string+i) = '\0'; return i;
   }
  while (head < BUFSIZE)
   {
    cbuf = combuf[head++];
    if ( (cbuf == XON) || (cbuf == XOFF) || (cbuf == XTRA) ) continue;
    if (cbuf == '\0') cbuf = ' ';
    *(string+i++) = cbuf;
   }
  head = 0;
  while (head < k)
   {
    cbuf = combuf[head++];
    if ( (cbuf == XON) || (cbuf == XOFF) || (cbuf == XTRA) ) continue;
    if (cbuf == '\0') cbuf = ' ';
    *(string+i++) = cbuf;
   }
  *(string + i) = '\0';
  return i;  /* The number of characters read */
}

/* Write a string to COM1: */
com_write (string)
char *string;
{
 int i;
 char cc;
 unsigned char inportb();
 unsigned char status;
 i = 0;
 while ( (cc=*(string+i++)) != '\0' )
  {
   srv.ax = hl2x (1,cc);
   srv.dx = 0;
   sysint (rs_vec,&srv,&rrv);
   outportb (0x3fc,0x0b);
  wait:  /* Check for THRE */
   status = inportb (0x3fd); /* Read LSR */
   if ( (status & 0x20) == 0 ) goto wait;
  }
 return;
}

com_close()  /* Close the com device */
{
 unsigned char inportb();
 unsigned char cc;

 outportb (0x3fc,0x00);  /* Reset MCR */
 outportb (0x3f9,0x00);  /* Reset IER */
 cc = inportb (0x21);
 cc |= 0x10;
 outportb (0x21,cc);
 outportb (0x20,0x20);
 return;
}

com_break()  /* Send break down com line */
{
 unsigned char inportb();
 unsigned char cc;
 int i;

 cc = inportb (0x3fb);	/* Read line control register */
 cc |= 0x40;  /* Set break high */
 outportb (0x3fb,cc);
 wait (300);  /* Delay ~300 msec */
 cc &= 0xbf;  /* Reset break bit low */
 outportb (0x3fb,cc);
 return;
}

/*******************************************************************
Routine com_set..... I wrote this when I thought more of the
rs232 communications needed to be written in assembler... I was
wrong. This routine can be easily written in C. I just haven't
had the time.............
*******************************************************************/

code	segment byte public
	assume	cs:code
;	assume	ds:data
	public	com_set 	; Entry to prep the com device & interrupt
				; controller

;
; Sets up the com device
; com_set ()
;
com_set proc	near
	push	bp
	mov	bp,sp

	cli			; Disable the interrupts for now
	mov	dx,3fch
	in	al,dx
	and	al,0f7h ; Set modem control register
	mov	al,0bh
	mov	dx,3fch ;
	out	dx,al	;

	mov	dx,3fbh  ;Set DLAB
	in	al,dx	 ;
	and	al,7fh	 ;
	out	dx,al	 ;

	mov	al,1	 ; Tell the modem to interrupt when a
	mov	dx,3f9h  ; Character is available
	out	dx,al	 ;

	mov	dx,21h	 ;Allow interrupts from com line
	in	al,dx	 ;
	and	al,0efh  ;
	out	dx,al	 ;

	sti
	nop

	pop	bp
	ret
com_set endp

code	ends
	end