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