Path: utzoo!utgpu!water!watmath!clyde!rutgers!husc6!cmcl2!brl-adm!umd5!uvaarpa!virginia!uvacs!edison!nev From: nev@edison.GE.COM (Niles VanDenburg) Newsgroups: comp.sys.ibm.pc Subject: Re: Serial Communications in C Summary: C code segment that I use/wrote (Turbo C) for XON/XOFF serial processing using interrupts (long) Message-ID: <1283@edison.GE.COM> Date: 10 Jan 88 23:36:14 GMT References: <991@mhuxh.UUCP> Organization: General Electric Company, Charlottesville, VA Lines: 373 In article <991@mhuxh.UUCP>, vxb@mhuxh.UUCP (Vern Bradner) writes: > Can anyone share their knowledge/experiences in writing C routines to > communicate with remote systems via the PC serial port? since this discussion has sort of degenerated of late I would like to fire it back up a little by actually supplying a C code segment that I wrote a while ago to handle XON/XOFF serial communications in C. WARNING: this code segment has only been used in one program and is being presented here only for its educational value and to promote discussion about serial interfaces in C for an IBM-PC. Any other use of the code hereby presented is not authorized. This code is presented ASIS. There is no representation that this is good code. In fact it is recommended that every one who uses this code for unauthorized uses realizes that he/she is taking total responsibility for any liability thereby resulting. Now with that out of the way here is the code (~350 lines follow): /* * compiler: Turbo C version 1.0 */ #include #include #include #include #include #define TRUE 1 #define FALSE 0 #define XOFF 0x13 /* XOFF/DC3 character */ #define XON 0x11 /* XON/DC1 character */ #define MODEM_CONTROL 0x0b /* value of the ACE's modem control */ /* register: RTS, DTR, and OUT2 true */ /* OUT2 required to get interrupts */ #define TX_SIZE 64 /* transmit circular buffer size */ #define RX_SIZE 1024 /* receive circular buffer size */ #define INT_MASK_REG 0x21 /* I/O port 8259 interrupt controller */ #define COMM_PORT_INIT 0xe3 /* default initialization for ACE : */ /* 9600 BPS, no parity, 1 stop, */ /* 8 bits/char See BIOS for details */ char volatile tx_buf[TX_SIZE]; /* circular buffer sending chars */ char volatile *tx_buf_head; /* ptr to next char to send */ char volatile *tx_buf_tail; /* ptr to next avail loc for insert */ char volatile *tx_buf_end; /* ptr to last possible loc in buffer */ char volatile rx_buf[RX_SIZE]; /* circular buf for input chars */ char volatile *rx_buf_head; /* ptr to next char to remove */ char volatile *rx_buf_tail; /* ptr to next avail loc for insert */ char volatile *rx_buf_end; /* ptr to last possible loc in buffer */ char volatile xoff_sent = FALSE; /* flag set to indicate an Xon should */ /* be sent when data buffer has more */ /* space (can be set by ISR) */ char xoff_enable = TRUE; /* flag used to indicate Xon/Xoff */ /* s/w flow control is permitted */ /* default set TRUE only set FALSE */ /* when in file transfer mode */ int volatile num_in_tx_buf = 0; /* count of characters being held in */ /* in the transmit FIFO */ /* used to determine if possible to */ /* put another character into FIFO */ int volatile num_in_rx_buf = 0; /* count of characters being held in */ /* in the receive FIFO */ /* used to determine when S/W flow */ /* control characters should be sent */ char com1 = TRUE; /* use com port 1 flag */ char com_init = COMM_PORT_INIT; /* value used for BIOS com init int */ char com_div = FALSE; /* flag to ind. BRG divisor supplied */ int com_div_value; /* value of supplied BRG divisor */ int data_reg = 0x3f8; /* I/O port for ACE data register */ int int_en_reg = 0x3f9; /* ACE interrupt enable register port */ int int_id_reg = 0x3fa; /* ACE interrupt ID register port */ int line_con_reg = 0x3fb; /* ACE line control register port */ int modem_con_reg = 0x3fc; /* ACE modem control register port */ int line_sts_reg = 0x3fd; /* ACE line status register port */ int modem_sts_reg = 0x3fe; /* ACE modem status register port */ int int_mask = 0x10; /* 8259 interrupt controller mask for */ /* com port */ int dos_int = 0x0c; /* int number for com ints */ /* NOTE above values are default for */ /* COM1: */ union REGS rg; /* structure used for int86 calls */ /* to pass register values */ /* * ISR for serial interrupts from Asynch. Comm. Element (ACE) - gets interrupt * ID and if no interrupts are active sends EOI code to 8259 to permit further * interrupts and then returns to interrupted routine. Otherwise if * interrupt ID indicates transmitter holding register empty send Xoff if * appropriate or if no more data to be sent turn off transmitter holding * register empty interrupt enable or if data to be sent send next byte and * adjust tail pointer (including possible wrap around of circular queue) * and decrement number in transmit queue. Otherwise it is assumed that * the interrupt ID register indicates that received data character is * available and the character is read into the received data circular queue * and that queue's head pointer is adjusted (including wraparound) and * the number in received data queue is incremented. If less than 16 * spaces are left in the received data queue then the sent Xoff flag is * set and depending on whether the transmitter holding register is empty * or not either an Xoff is sent to the ACE or the send Xoff flag is set * so that the next transmitter holding register empty interrupt shall cause * the Xoff to be sent. For efficiency the ISR stays active until all * sources of interrupts are processed. */ void interrupt proc_intr() { static char send_xoff = FALSE; register unsigned char i; top: disable(); if ((i = inportb(int_id_reg)) == 1) { outportb(0x20, 0x20); /* send EOI to interrupt controller */ return; } enable(); /* permit higher priority ints. */ if (i == 2) { /* Tx interrupt ? */ if (inportb(line_sts_reg) & 0x20) { /* THRE ? needed on XT only */ if (xoff_enable && send_xoff) { /* need to send Xon ? */ outportb(data_reg, XOFF); send_xoff = FALSE; } else if (tx_buf_head == tx_buf_tail) { /* more to send ? */ outportb(int_en_reg, 1); /* no - reset interrupt enable */ } else { outportb(data_reg, *tx_buf_tail++); /* yes - send it */ if (tx_buf_tail == tx_buf_end) { /* adjust tail ptr ? */ tx_buf_tail = tx_buf; } --num_in_tx_buf; } } goto top; } else if (i == 4) { /* Rx interrupt ? */ *rx_buf_head++ = inportb(data_reg); /* get data from ACE */ if (rx_buf_head == rx_buf_end) { /* adjust head pointer ? */ rx_buf_head = rx_buf; } ++num_in_rx_buf; /* need send Xoff ? */ if (xoff_enable && (num_in_rx_buf > RX_SIZE - 16)) { if (inportb(line_sts_reg) & 0x20) { /* THRE ? ok send immed. ? */ outportb(data_reg, XOFF); /* yes send it */ } else { send_xoff = TRUE; /* no flg send upon THRE int. */ } xoff_sent = TRUE; /* tell BG to send Xon when ok */ } goto top; } else { /* other sources of interrupt (should never occur) */ inportb(modem_sts_reg); /* clears modem status interrupt */ inportb(line_sts_reg); /* clears rec. line status interrupt */ goto top; } } /* * send a character to serial interface (COM1/2), if transmit buffer full * delay until space is available in the buffer, put character in circular * queue at the head pointer then increment the head pointer adjusting * for possible wraparound of the pointer, increment the number of * characters in the output queue, make sure that transmitter interrupts are * enabled as well as receive interrupts (which are never turned off) */ void send_char(ch) char ch; { while (disable(), num_in_tx_buf >= TX_SIZE - 2) { enable(); disable(); } *tx_buf_head++ = ch; if (tx_buf_head == tx_buf_end) { tx_buf_head = tx_buf; } ++num_in_tx_buf; outportb(int_en_reg, 3); /* insure tx interrupts are enabled */ enable(); } /* * get a character from the receive queue if any, return TRUE if character * found, FALSE if no character available. If Xoff previously sent see if * adequate space is now available in the receive queue so that character * reception can now resume and if so then an Xon is sent. */ int receive_char(chp) char *chp; { register char c; if (num_in_rx_buf) { disable(); c = *rx_buf_tail++; if (rx_buf_tail == rx_buf_end) { rx_buf_tail = rx_buf; } --num_in_rx_buf; enable(); if (xoff_sent && (num_in_rx_buf < (RX_SIZE - 64))) { send_char(XON); xoff_sent = FALSE; } *chp = (char) (c & char_mask); return (TRUE); } else { return (FALSE); } } /* * Initialize the tx/rx buffers, UART, and interrupt vectors for UART * Note that the COM1/2 interrupt vector is assumed to be disposable. * Note in the UART cleanup the setting of OUT2 is required or else no * interrupts are ever generated by the async. card. Initialization * finishes by sending an Xon to the serial interface to re-enable data * transfers from the remote unit. */ void init() { register int i; /* setup FIFO buffers */ tx_buf_head = tx_buf_tail = tx_buf; tx_buf_end = tx_buf + TX_SIZE - 1; rx_buf_head = rx_buf_tail = rx_buf; rx_buf_end = rx_buf + RX_SIZE - 1; /* initialize UART register pointers */ if (!com1) { /* only change if not COM1 */ data_reg = 0x2f8; int_en_reg = 0x2f9; int_id_reg = 0x2fa; line_con_reg = 0x2fb; modem_con_reg = 0x2fc; line_sts_reg = 0x2fd; modem_sts_reg = 0x2fe; int_mask = 0x08; dos_int = 0x0b; } /* initialize UART */ rg.h.ah = 0; rg.h.al = com_init; rg.x.dx = 1 - com1; int86(0x14, &rg, &rg); /* cleanup UART setup */ disable(); if (com_div) { i = inportb(line_con_reg) | 0x80; /* insure div latch on */ outportb(line_con_reg, i); outportb(data_reg, com_div_value); /* send divisor value to ACE */ outportb(int_en_reg, com_div_value >> 8); } i = inportb(line_con_reg) & 0x7f; /* insure div latch off */ outportb(line_con_reg, i); outportb(modem_con_reg, MODEM_CONTROL); /* set RTS, DTR, OUT2 */ inportb(line_sts_reg); /* clear status reg */ inportb(data_reg); /* discard any char rcvd */ i = inportb(INT_MASK_REG) & ~int_mask; /* enable intr on 8259 */ outportb(INT_MASK_REG, i); outportb(int_en_reg, 1); /* enable rx data ints */ enable(); /* setup linkage to COM1/2 interrupt routine */ setvect(dos_int, proc_intr); /* send Xon in case link is waiting on software flow control */ send_char(XON); } /* * Restore machine state upon exit, send Xoff to tell remote unit * to stop sending data and kill interrupt processing on the async * interface */ void restore() { register int temp; send_char(XOFF); /* tell other end to stop sending */ /* disable interupt processing on COM port */ disable(); /* disable intr on 8259 */ temp = inportb(INT_MASK_REG) | int_mask; outportb(INT_MASK_REG, temp); outportb(int_en_reg, 0); /* disable recv data int */ outportb(modem_con_reg, 0); /* reset RTS, DTR, OUT2 */ enable(); } /* * find and read initialization parameters from a user specified file * given on the command line. The file must have the following format: * binary file, * byte 1 - normal initialization data used by BIOS to initialize UART * byte 2 - flag indicating presence of user supplied BRG divisor value * byte 3 - com1 flag TRUE (1) if use COM1 else FALSE (0) for COM2 * byte 4 - LSBs of user supplied BRG divisor value (only present if * byte 2 is TRUE) * byte 5 - MSBs of user supplied BRG divisor value (only present if * byte 2 is TRUE) */ static void init_file(incoming) char *incoming; { FILE *stream; int found; found = FALSE; if (incoming != NULL) { if ((stream = fopen(incoming, "rb")) == NULL) { puts("could not open requested init file\n"); } else { found = TRUE; } } if (found) { com_init = fgetc(stream); com_div = fgetc(stream); com1 = fgetc(stream); if (com_div) { i = fgetc(stream); com_div_value = (i & 255) | (fgetc(stream) << 8); } fclose(stream); } } /* * Main routine, can accept one optional command line parameter - the * name of an initialization data file. The user specified data file * is read in and using the parameters from the file or the default * parameter values the software and hardware is initialized. */ main(argc, argv) int argc; char *argv[]; { char ch, *chp; if (argc < 2) { chp = NULL; } else { chp = argv[1]; } init_file(chp); /* read in initialization data if present */ init(); /* initialize software and hardware */ /* * The main body of the code goes here $$$$$$$$$$$$$$$$$$$$$$$ */ restore(); }