/************************************************************************/ /* */ /* Minimal Function, All C interrupt serial routine */ /* */ /* Supports buffered input, output */ /* */ /* */ /************************************************************************/ /* */ /* This code is donated to the public domain */ /* */ /* The original author wrote this program for Microsoft C. */ /* This version is adapted for Turbo C by Carl Smotricz, */ /* August 1993. It tests for #define __TURBOC__ and should */ /* still work or almost work with Microsoft C. */ /* */ /************************************************************************/ #ifdef _MSC_VER #include #endif #include #include #include "wt.h" #include "wtser.h" /****************/ /* Prototypes */ /****************/ onexit_t ser_int_term(); /************************************************************************/ /* Definitions of the 8250's registers */ /************************************************************************/ word ser_vect; /* Interrupt vector to use */ word DataPort; /* Data register */ word IER; /* Interrupt enable register */ word IIR; /* Interrupt ID register */ word LCR; /* Line control register */ word MControl; /* Modem control register */ word LStatus; /* Line Status */ word MStatus; /* Modem status */ word IntControl; /* For 8259 interrupt controller*/ word EnableIRQ4; /* Enable interrupt 4 */ word DisableIRQ4;/* Disable interrupt 4 */ word RS8259; word EOI; word TxReady; /* Transmit buffer empty */ intfp old_vect; /* Old interrupt vect */ /************************************************************************/ /* Definitions of the interrupt enable register */ /************************************************************************/ #define IER_RX 0x01 /* Enable Data Ready interrupts */ #define IER_TX 0x02 /* Enable Transmit Empty Interrupts */ #define IER_LS 0x04 /* Enable line status interrupts */ #define IER_MS 0x08 /* Enable modem status interrupts */ /************************************************************************/ /* Definitions of the interrupt ID register */ /************************************************************************/ #define IIR_IPending 0x01 /* Interrupt is pending */ #define IIR_Mask 0x06 /* Mask to get Interrupt type */ #define IIR_MS 0x00 /* Modem status interrupt */ #define IIR_TX 0x02 /* Transmit empty interrupt */ #define IIR_RX 0x04 /* Data available interrupt */ #define IIR_LS 0x06 /* Line status interrupt */ #define BUFSIZE 512 /* How many characters in the buffer? */ #define XOFF_SIZE 384 /* How full do we get before XOFFing */ #define XON_SIZE 128 /* How empty to we get before XONing */ #define XOFF 19 /* value of the XOFF character */ #define XON 17 /* value of the XON character */ /************************************************************************/ /* Baud rate divisor table */ /************************************************************************/ const word bdiv_tab[9] = { 0x0417, /* 110 */ 0x0300, /* 150 */ 0x0180, /* 300 */ 0x00c0, /* 600 */ 0x0060, /* 1200 */ 0x0030, /* 2400 */ 0x0018, /* 4800 */ 0x000c, /* 9600 */ 0x0006 /* 19200 */ /* new as of 02/98 */ }; /************************************************************************/ /* */ /* Definitions for the input buffer. */ /* */ /* We make it a 'far' array so as not to eat up valuable data */ /* space in the small and medium models. */ /* */ /* The buffers are simple circular buffers, where there is */ /* one pointer to fill the buffer and another to empty it. */ /* A count variable keeps track of the size of the buffer */ /* (although it could be determined by computing */ /* (fill-empty)%BUFSIZE). */ /* */ /************************************************************************/ static byte far in_buf[BUFSIZE]; static int in_fill =0; /* Where to place the next char */ static int in_empty =0; /* Where to get the next char */ static int in_count =0; /* How much data is in buffer */ static int xoff_sent =0; /* Did we send an XOFF? */ /************************************************************************/ /* */ /* Definitions for the output buffer */ /* */ /************************************************************************/ static byte far out_buf[BUFSIZE]; static int out_fill =0; static int out_empty =0; static int out_count =0; static int tx_off =1; /* These means the interrupts */ /* for the transmitter are now */ /* off (since we have nothing */ /* to send right now). */ static int been_inited = 0; /* Driver initialized */ static int current_port = -1; /* current COM port */ /************************************************************************/ /* */ /* ser_int_icount: Return the number of bytes in the input */ /* buffer. 0 means no data available. */ /* */ /************************************************************************/ int ser_int_icount() { return(in_count); } /************************************************************************/ /* */ /* ser_int_ocount: Returns the number of bytes in the output */ /* buffer. This information is useful if you */ /* are uploading a large block of data and do */ /* not want to hand this library data quicker */ /* than it can be transmitted. E.g., */ /* */ /* while (more data) */ /* { */ /* while (ser_int_ocount()) */ /* ; */ /* send_data(); */ /* } */ /* */ /************************************************************************/ int ser_int_ocount() { return(out_count); } /************************************************************************/ /* */ /* ser_int_ofree: Returns the amount of free space left in */ /* the output buffer. Useful in preventing */ /* hanging in ser_int_putc while sending a */ /* a large block of data. E.g., */ /* */ /* while (more_data) */ /* { */ /* while (ser_int_ofree() == 0) */ /* do_something_else(); */ /* ser_int_putc(data); */ /* } */ /* */ /************************************************************************/ int ser_int_ofree() { return(BUFSIZE-out_count); } /************************************************************************/ /* */ /* ser_int_iflush: Throw away any data in the input buffer. */ /* Note that if a character arrives just as */ /* this subroutine is returning, there will */ /* be data in the input buffer upon return. */ /* Thus, you cannot rely upon the buffer */ /* being empty (for whatever reason). */ /* */ /************************************************************************/ void ser_int_iflush() { _disable(); /*****************************************/ in_fill = 0; in_empty = 0; in_count = 0; /*****************************************/ _enable(); } /************************************************************************/ /* */ /* int_handler: This routine catches the serial port */ /* interrupts, and performs accordingly. */ /* */ /************************************************************************/ #if defined(_MSC_VER) static intfunc int_handler() #endif #if defined(__TURBOC__) static void interrupt far int_handler() #endif { int ax; static int send_now = 0; /* While there is more work to do... */ while (0 == ((ax = _inp(IIR)) & IIR_IPending)) { switch (ax & IIR_Mask) { case IIR_RX: /* New data has arrived */ ax = _inp(DataPort); if (in_count != BUFSIZE) { in_buf[in_fill] = (unsigned char) ax; in_fill = (in_fill+1) & (BUFSIZE-1); in_count++; /* Check for the buffer filling up... */ if ((in_count == XOFF_SIZE) && (!xoff_sent)) { if (tx_off) { _outp(DataPort,XOFF); tx_off = 0; } else { send_now = XOFF; } xoff_sent = 1; } } break; case IIR_TX: /* Ready to transmit another */ if (send_now) { _outp(DataPort,send_now); send_now = 0; } else if (out_count) { _outp(DataPort,out_buf[out_empty]); out_empty = (out_empty+1) & (BUFSIZE-1); out_count--; } else { /* Since we aren't feeding it another */ /* byte, it won't interrupt again */ tx_off = 1; } break; default: break; } } _outp(RS8259,EOI); } /************************************************************************/ /* */ /* ser_int_init: Initialize the serial port handler. */ /* Note that if you change the baud rate */ /* via the ROM bios (or MSC's _bios* funcs) */ /* for example, you need to call this routine */ /* again to restart the interrupts. */ /* */ /* The argument, com_port, should be 0 thru 3 for COM1 thru COM4 */ /************************************************************************/ void ser_int_init(void) { register int ax; register int i; if (been_inited) { ser_int_term(); } else { _onexit(ser_int_term); been_inited = 1; } _disable(); old_vect = _dos_getvect(ser_vect); _dos_setvect(ser_vect, &int_handler); /*****************************************/ _disable(); ax = _inp(IntControl); ax = ax & EnableIRQ4; _outp(IntControl,ax); _outp(IER,IER_RX | IER_TX); for (i=0;i<6;i++) _inp(DataPort+i); ax = _inp(LCR); ax = ax & 0x3f; _outp(LCR,ax); _outp(MControl,0xb); _outp(RS8259,EOI); _enable(); /*****************************************/ } /************************************************************************/ /* */ /* ser_int_term: Turn off the interrrupts to make the */ /* world safe for us to exit... */ /* */ /************************************************************************/ onexit_t ser_int_term() { #if 0 /* Wait for all data in the buffer to be transmitted */ while (out_count) {} /* Wait for the UART to drain out */ while ((inp(LStatus) & 0x60) != 0x60) {} #endif _disable(); /*****************************************/ _outp(IntControl,_inp(IntControl) | DisableIRQ4); _outp(LCR,_inp(LCR) & 0x7f); _outp(IER,0); _outp(MControl,0); /*****************************************/ _enable(); _dos_setvect(ser_vect, old_vect); #if defined(_MSC_VER) return 0; #endif } /************************************************************************/ /* */ /* ser_int_putc: Transmit a character out the UART */ /* */ /************************************************************************/ void ser_int_putc(int c) { _disable(); /*****************************************/ /* If there is nothing ahead of us, just send it! */ if (tx_off) { _outp(DataPort,c); tx_off = 0; } else /* Put character in the buffer */ { if (out_count == BUFSIZE) { /*****************************************/ /* Buffer is full, wait for space */ _enable(); while (out_count == BUFSIZE) ; _disable(); /*****************************************/ } out_buf[out_fill] = (unsigned char) c; out_fill = (out_fill + 1) & (BUFSIZE-1); out_count++; } /*****************************************/ _enable(); } /************************************************************************/ /* */ /* ser_int_getc: Get a character from the input buffer. */ /* Returns character or -1 if no data available */ /* */ /************************************************************************/ int ser_int_getc() { int b; if (in_count == 0) return(-1); b =in_buf[in_empty]; in_empty = (in_empty+1) & (BUFSIZE-1); _disable(); /*****************************************/ in_count--; /*****************************************/ _enable(); if ((in_count == XON_SIZE) && xoff_sent) { ser_int_putc(XON); xoff_sent = 0; } return(b); } /************************************************************************/ /* */ /* ser_int_break: Raise BREAK for 200 ms, then turn it off */ /* for 200 ms. */ /* */ /* This routine is structured so that you can call it repeatedly */ /* and the other end will sense distinct break signals rather */ /* than one very long one. */ /* */ /************************************************************************/ void ser_int_break() { int ax; unsigned long far *ticks; unsigned long start, cur; ticks = (unsigned long far *) MK_FP(0x40,0x6c); start = *ticks; _disable(); /*****************************************/ /* Turn on BREAK signal */ /*****************************************/ ax = _inp(LCR); ax |= 0x40; _outp(LCR,ax); _enable(); /* Wait for 200 ms */ for (;;) { cur =*ticks; if ((cur-start) > 4) /* 220 uSecs */ break; } _disable(); /*****************************************/ /* Turn off BREAK signal */ /*****************************************/ ax = _inp(LCR); ax &= ~0x40; _outp(LCR,ax); _enable(); start =*ticks; for (;;) { cur =*ticks; if ((cur-start) > 4) break; } } /**********************************************/ /* */ /* ser_setbaud(): set baud rate on comm port. */ /* */ /**********************************************/ void ser_setbaud(int baud) { word divisor; int lcr_data; _disable(); divisor = bdiv_tab[baud >> 5]; lcr_data = _inp(LCR); lcr_data |= 0xc0; _outp(LCR,lcr_data); _outp(DataPort, divisor & 0xff); _outp(DataPort+1, divisor / 0x100); lcr_data &= 0x3f; _outp(LCR,lcr_data); _enable(); } /* end ser_setbaud() */ /**************************************************/ /* */ /* ser_setmisc(): set parity, data and stop bits. */ /* */ /**************************************************/ void ser_setmisc(int pari, int data, int stop) { int lcr_data; lcr_data = _inp(LCR) & 0xc0; lcr_data |= (byte) (pari | stop | data); _outp(LCR, lcr_data); } /* end ser_setmisc() */ /*****************************************************/ /* */ /* ser_setport(): Set up the current serial port and */ /* associated parameters. */ /* */ /*****************************************************/ void ser_setport(int ser_port) { if (ser_port != current_port) { if (been_inited) { ser_int_term(); } switch (ser_port) { case 0: /* COM1 */ ser_vect =0xc; DataPort =0x3f8; IntControl =0x21; EnableIRQ4 =0xef; DisableIRQ4=0x10; RS8259 =0x20; EOI =0x20; TxReady =0x20; break; case 1: /* COM2 */ ser_vect =0xb; DataPort =0x2f8; IntControl =0x21; EnableIRQ4 =0xF7; DisableIRQ4=0x08; RS8259 =0x20; EOI =0x20; TxReady =0x20; break; case 2: /* COM3 */ ser_vect =0xc; DataPort =0x3e8; IntControl =0x21; EnableIRQ4 =0xef; DisableIRQ4=0x10; RS8259 =0x20; EOI =0x20; TxReady =0x20; break; case 3: /* COM4 */ ser_vect =0xb; DataPort =0x2e8; IntControl =0x21; EnableIRQ4 =0xF7; DisableIRQ4=0x08; RS8259 =0x20; EOI =0x20; TxReady =0x20; break; } /* end switch */ IER = DataPort + 1; IIR = DataPort + 2; LCR = DataPort + 3; MControl = DataPort + 4; LStatus = DataPort + 5; MStatus = DataPort + 6; current_port = ser_port; been_inited = 0; ser_int_init(); } /* end if current_port */ } /* end ser_setport() */ /* EOF(wtser.c) */