PRODUCT : TURBO C NUMBER : 445 VERSION : 1.0 & 1.5 OS : DOS 2.X & 3.X DATE : OCTOBER 5, 1988 PAGE : 1/13 TITLE : INTERRUPT DRIVEN SERIAL COMMUNICATIONS /*-------------------------------------------------------------* SERIAL.C The following code shows how to take advantage of some of the Turbo C extensions to the C language to do asynchronous communications without having to write supporting assembly- language routines. This program bypasses the less-than-adequate PC BIOS com- munications routines and installs a serial interrupt handler. Direct access to PC hardware allows the program to run at faster baud rates and eliminates the need for the main program to continuously poll the serial port for data; thus, implementing background communications. Data that enters the serial port is stored in a circular buffer. * Compile this program with Test Stack Overflow OFF. *-------------------------------------------------------------*/ #include "serial.h" #include #include #include #include #define VERSION 0x0101 #define FALSE 0 #define TRUE (!FALSE) #define NOERROR 0 /* No error */ #define BUFOVFL 1 /* Buffer overflowed */ #define ESC 0x1B /* ASCII Escape character */ #define ASCII 0x007F /* Mask ASCII characters */ #define SBUFSIZ 0x4000 /* Serial buffer size */ int SError = NOERROR; int portbase = 0; void interrupt(*oldvects[2])(); static char ccbuf[SBUFSIZ]; unsigned int startbuf = 0; unsigned int endbuf = 0; PRODUCT : TURBO C NUMBER : 445 VERSION : 1.0 & 1.5 OS : DOS 2.X & 3.X DATE : OCTOBER 5, 1988 PAGE : 2/13 TITLE : INTERRUPT DRIVEN SERIAL COMMUNICATIONS /* Handle communications interrupts and put them in ccbuf */ void interrupt com_int(void) { disable(); if ((inportb(portbase + IIR) & RX_MASK) == RX_ID) { if (((endbuf + 1) & SBUFSIZ - 1) == startbuf) SError = BUFOVFL; ccbuf[endbuf++] = inportb(portbase + RXR); endbuf &= SBUFSIZ - 1; } /* Signal end of hardware interrupt */ outportb(ICR, EOI); enable(); } /* Output a character to the serial port */ int SerialOut(char x) { long int timeout = 0x0000FFFFL; outportb(portbase + MCR, MC_INT | DTR | RTS); /* Wait for Clear To Send from modem */ while ((inportb(portbase + MSR) & CTS) == 0) if (!(--timeout)) return (-1); timeout = 0x0000FFFFL; /* Wait for transmitter to clear */ while ((inportb(portbase + LSR) & XMTRDY) == 0) if (!(--timeout)) return (-1); disable(); outportb(portbase + TXR, x); enable(); return (0); } PRODUCT : TURBO C NUMBER : 445 VERSION : 1.0 & 1.5 OS : DOS 2.X & 3.X DATE : OCTOBER 5, 1988 PAGE : 3/13 TITLE : INTERRUPT DRIVEN SERIAL COMMUNICATIONS /* Output a string to the serial port */ void SerialString(char *string) { while (*string) SerialOut(*string++); } /* This routine returns the current value in the buffer */ int getccb(void) { int res; if (endbuf == startbuf) return (-1); res = (int) ccbuf[startbuf++]; startbuf %= SBUFSIZ; return (res); } /* Install our functions to handle communications */ void setvects(void) { oldvects[0] = getvect(0x0B); oldvects[1] = getvect(0x0C); setvect(0x0B, com_int); setvect(0x0C, com_int); } /* Uninstall our vectors before exiting the program */ void resvects(void) { setvect(0x0B, oldvects[0]); setvect(0x0C, oldvects[1]); } /* Turn on communications interrupts */ void i_enable(int pnum) { int c; disable(); c = inportb(portbase + MCR) | MC_INT; PRODUCT : TURBO C NUMBER : 445 VERSION : 1.0 & 1.5 OS : DOS 2.X & 3.X DATE : OCTOBER 5, 1988 PAGE : 4/13 TITLE : INTERRUPT DRIVEN SERIAL COMMUNICATIONS outportb(portbase + MCR, c); outportb(portbase + IER, RX_INT); c = inportb(IMR) & (pnum == COM1 ? IRQ4 : IRQ3); outportb(IMR, c); enable(); } /* Turn off communications interrupts */ void i_disable(void) { int c; disable(); c = inportb(IMR) | ~IRQ3 | ~IRQ4; outportb(IMR, c); outportb(portbase + IER, 0); c = inportb(portbase + MCR) & ~MC_INT; outportb(portbase + MCR, c); enable(); } /* Tell modem that we're ready to go */ void comm_on(void) { int c, pnum; pnum = (portbase == COM1BASE ? COM1 : COM2); i_enable(pnum); c = inportb(portbase + MCR) | DTR | RTS; outportb(portbase + MCR, c); } /* Go off-line */ void comm_off(void) { i_disable(); outportb(portbase + MCR, 0); } void initserial(void) { endbuf = startbuf = 0; setvects(); PRODUCT : TURBO C NUMBER : 445 VERSION : 1.0 & 1.5 OS : DOS 2.X & 3.X DATE : OCTOBER 5, 1988 PAGE : 5/13 TITLE : INTERRUPT DRIVEN SERIAL COMMUNICATIONS comm_on(); } void closeserial(void) { comm_off(); resvects(); } /* Set the port number to use */ int SetPort(int Port) { int Offset, far *RS232_Addr; switch (Port) { /* Sort out the base address */ case COM1 : Offset = 0x0000; break; case COM2 : Offset = 0x0002; break; default : return (-1); } RS232_Addr = MK_FP(0x0040, Offset); /* Find out where the port is. */ if (*RS232_Addr == NULL) return (-1);/* If NULL, then port not used. */ portbase = *RS232_Addr; /* Otherwise, set portbase. */ return (0); } /* This routine sets the speed; will accept funny baud rates. */ /* Setting the speed requires that the DLAB be set on. */ int SetSpeed(int Speed) { char c; int divisor; if (Speed == 0) /* Avoid divide by zero */ return (-1); else PRODUCT : TURBO C NUMBER : 445 VERSION : 1.0 & 1.5 OS : DOS 2.X & 3.X DATE : OCTOBER 5, 1988 PAGE : 6/13 TITLE : INTERRUPT DRIVEN SERIAL COMMUNICATIONS divisor = (int) (115200L/Speed); if (portbase == 0) return (-1); disable(); c = inportb(portbase + LCR); outportb(portbase + LCR, (c | 0x80)); /* Set DLAB */ outportb(portbase + DLL, (divisor & 0x00FF)); outportb(portbase + DLH, ((divisor >> 8) & 0x00FF)); outportb(portbase + LCR, c); /* Reset DLAB */ enable(); return (0); } /* Set other communications parameters */ int SetOthers(int Parity, int Bits, int StopBit) { int setting; if (portbase == 0) r e t urn (-1); if (Bits < 5 || Bits > 8) r e t urn (-1); if (StopBit != 1 && StopBit != 2) return (-1); if (Parity != NO_PARITY && Parity != ODD_PARITY && Parity != EVEN_PARITY) return (-1); setting = Bits-5; setting |= ((StopBit == 1) ? 0x00 : 0x04); setting |= Parity; disable(); outportb(portbase + LCR, setting); enable(); return (0); } /* Set up the port */ int SetSerial(int Port, int Speed, int Parity, int Bits, int StopBit) PRODUCT : TURBO C NUMBER : 445 VERSION : 1.0 & 1.5 OS : DOS 2.X & 3.X DATE : OCTOBER 5, 1988 PAGE : 7/13 TITLE : INTERRUPT DRIVEN SERIAL COMMUNICATIONS { if (SetPort(Port)) return (-1); if (SetSpeed(Speed)) return (-1); if (SetOthers(Parity, Bits, StopBit)) return (-1); return (0); } /* Control-Break interrupt handler */ int c_break(void) { i_disable(); fprintf(stderr, "\nStill online.\n"); return(0); } main() { /* Communications parameters */ int port = COM2; int speed = 1200; int parity = NO_PARITY; int bits = 8; int stopbits = 1; int c, done = FALSE; if (SetSerial(port, speed, parity, bits, stopbits) != 0) { fprintf(stderr, "Serial Port setup error.\n"); return (99); } initserial(); ctrlbrk(c_break); fprintf(stdout, "TURBO C TERMINAL\n...You're now in terminal mode, press [ESC] to quit...\n\n"); /* The main loop acts as a dumb terminal. We repeatedly check the keyboard buffer, and communications buffer. */ PRODUCT : TURBO C NUMBER : 445 VERSION : 1.0 & 1.5 OS : DOS 2.X & 3.X DATE : OCTOBER 5, 1988 PAGE : 8/13 TITLE : INTERRUPT DRIVEN SERIAL COMMUNICATIONS do { if (kbhit()) { /* Look for an Escape key */ switch (c=getch()) { case ESC: done = TRUE; /* Exit program */ break; /* You may want to handle other keys here... */ } SerialOut(c); } if ((c=getccb()) != -1) fputc(c & ASCII, stdout); } while (!done && !SError); /* Check for errors */ switch (SError) { case NOERROR: fprintf(stderr, "\nbye.\n"); closeserial(); return (0); case BUFOVFL: fprintf(stderr, "\nBuffer Overflow.\n"); closeserial(); return (99); default: fprintf(stderr, "\nUnknown Error, SError = %d\n", SError); closeserial(); return (99); } } /*-------------------------------------------------------------* SERIAL.H Some definitions used by SERIAL.C *-------------------------------------------------------------*/ #define COM1 1 PRODUCT : TURBO C NUMBER : 445 VERSION : 1.0 & 1.5 OS : DOS 2.X & 3.X DATE : OCTOBER 5, 1988 PAGE : 9/13 TITLE : INTERRUPT DRIVEN SERIAL COMMUNICATIONS #define COM2 2 #define COM1BASE 0x3F8 /* Base port address for COM1 */ #define COM2BASE 0x2F8 /* Base port address for COM2 */ /* The 8250 UART has 10 registers accessible through 7 port addresses. Here are their addresses relative to COM1BASE and COM2BASE. Note that the baud rate registers, (DLL) and (DLH) are active only when the Divisor-Latch Access-Bit (DLAB) is on. The (DLAB) is bit 7 of the (LCR). o TXR Output data to the serial port. o RXR Input data from the serial port. o LCR Initialize the serial port. o IER Controls interrupt generation. o IIR Identifies interrupts. o MCR Send contorl signals to the modem. o LSR Monitor the status of the serial port. o MSR Receive status of the modem. o DLL Low byte of baud rate divisor. o DHH High byte of baud rate divisor. */ #define TXR 0 /* Transmit register (WRITE) */ #define RXR 0 /* Receive register (READ) */ #define IER 1 /* Interrupt Enable */ #define IIR 2 /* Interrupt ID */ #define LCR 3 /* Line control */ #define MCR 4 /* Modem control */ #define LSR 5 /* Line Status */ #define MSR 6 /* Modem Status */ #define DLL 0 /* Divisor Latch Low */ #define DLH 1 /* Divisor latch High */ /*-------------------------------------------------------------* Bit values held in the Line Control Register (LCR). bit meaning --- ------- 0-1 00=5 bits, 01=6 bits, 10=7 bits, 11=8 bits. 2 Stop bits. 3 0=parity off, 1=parity on. 4 0=parity odd, 1=parity even. 5 Sticky parity. PRODUCT : TURBO C NUMBER : 445 VERSION : 1.0 & 1.5 OS : DOS 2.X & 3.X DATE : OCTOBER 5, 1988 PAGE : 10/13 TITLE : INTERRUPT DRIVEN SERIAL COMMUNICATIONS 6 Set break. 7 Toggle port addresses. *-------------------------------------------------------------*/ #define NO_PARITY 0x00 #define EVEN_PARITY 0x18 #define ODD_PARITY 0x08 /*-------------------------------------------------------------* Bit values held in the Line Status Register (LSR). bit meaning --- ------- 0 Data ready. 1 Overrun error - Data register overwritten. 2 Parity error - bad transmission. 3 Framing error - No stop bit was found. 4 Break detect - End to transmission requested. 5 Transmitter holding register is empty. 6 Transmitter shift register is empty. 7 Time out - off line. *-------------------------------------------------------------*/ #define RCVRDY 0x01 #define OVRERR 0x02 #define PRTYERR 0x04 #define FRMERR 0x08 #define BRKERR 0x10 #define XMTRDY 0x20 #define XMTRSR 0x40 #define TIMEOUT 0x80 /*-------------------------------------------------------------* Bit values held in the Modem Output Control Register (MCR). bit meaning --- ------- 0 Data Terminal Ready. Computer ready to go. 1 Request To Send. Computer wants to send data. 2 Auxillary output #1. 3 Auxillary output #2. (Note: This bit must be PRODUCT : TURBO C NUMBER : 445 VERSION : 1.0 & 1.5 OS : DOS 2.X & 3.X DATE : OCTOBER 5, 1988 PAGE : 11/13 TITLE : INTERRUPT DRIVEN SERIAL COMMUNICATIONS set to allow the communications card to send interrupts to the system.) 4 UART ouput looped back as input. 5-7 Not used. *-------------------------------------------------------------*/ #define DTR 0x01 #define RTS 0x02 #define MC_INT 0x08 /*-------------------------------------------------------------* Bit values held in the Modem Input Status Register (MSR). bit meaning --- ------- 0 Delta Clear To Send. 1 Delta Data Set Ready. 2 Delta Ring Indicator. 3 Delta Data Carrier Detect. 4 Clear To Send. 5 Data Set Ready. 6 Ring Indicator. 7 Data Carrier Detect. *-------------------------------------------------------------*/ #define CTS 0x10 #define DSR 0x20 /*-------------------------------------------------------------* Bit values held in the Interrupt Enable Register (IER). bit meaning --- ------- 0 Interrupt when data received. 1 Interrupt when transmitter holding reg. empty. 2 Interrupt when data reception error. 3 Interrupt when change in modem status register. 4-7 Not used. *-------------------------------------------------------------*/ #define RX_INT 0x01 /*-------------------------------------------------------------* Bit values held in the Interrupt Identification Register (IIR). PRODUCT : TURBO C NUMBER : 445 VERSION : 1.0 & 1.5 OS : DOS 2.X & 3.X DATE : OCTOBER 5, 1988 PAGE : 12/13 TITLE : INTERRUPT DRIVEN SERIAL COMMUNICATIONS bit meaning --- ------- 0 Interrupt pending. 1-2 Interrupt ID code. 00=Change in modem status register, 01=Transmitter holding register empty, 10=Data received, 11=reception error, or break encountered. 3-7 Not used. *-------------------------------------------------------------*/ #define RX_ID 0x04 #define RX_MASK 0x07 /* These are the port addresses of the 8259 Programmable Interrupt Controller (PIC). */ #define IMR 0x21 /* Interrupt Mask Register port */ #define ICR 0x20 /* Interrupt Control Port */ /* An end of interrupt needs to be sent to the Control Port of the 8259 when a hardware interrupt ends. */ #define EOI 0x20 /* End Of Interrupt */ /* The (IMR) tells the (PIC) to service an interrupt only if it is not masked (FALSE). */ #define IRQ3 0xF7 /* COM2 */ #define IRQ4 0xEF /* COM1 */