_C Programming Column_ by Al Stevens [LISTING ONE] /* ---------- serial.h --------------- * Serial Port Definitions */ extern int ticker, COMPORT; extern char *nextin, *nextout; /* ----------- serial prototypes ----------- */ void initcomport(void); int readcomm(void); int writecomm(int); void clear_serial_queue(void); /* -------- timer prototypes --------- */ void sleep(unsigned); int set_timer(unsigned); void intercept_timer(void); void restore_timer(void); void restore_serialint(void); /* ----------------- macros ------------------- */ #define comstat() (inp(LINESTATUS)) #define input_char_ready() (nextin!=nextout) #define timed_out() (ticker==0) #define set_timer(secs) ticker=secs*182/10+1 #define XON 17 #define XOFF 19 /* ---------------- serial port addresses ----------------- */ /* - 8250 UART base port address: COM1 = 3f8, COM2 = 2f8 - */ #define BASEPORT (0x3f8-((COMPORT-1)<<8)) #define TXDATA BASEPORT /* transmit data */ #define RXDATA BASEPORT /* receive data */ #define DIVLSB BASEPORT /* baud rate divisor lsb */ #define DIVMSB (BASEPORT+1) /* baud rate divisor msb */ #define INTENABLE (BASEPORT+1) /* interrupt enable */ #define INTIDENT (BASEPORT+2) /* interrupt ident'n */ #define LINECTL (BASEPORT+3) /* line control */ #define MODEMCTL (BASEPORT+4) /* modem control */ #define LINESTATUS (BASEPORT+5) /* line status */ #define MODEMSTATUS (BASEPORT+6) /* modem status */ /* --------------- serial interrupt stuff ------------------ */ #define IRQ (4-(COMPORT-1)) /* 0-7 = IRQ0-IRQ7 */ #define COMINT (12-(COMPORT-1)) /* interrupt vector 12/11*/ #define COMIRQ (~(1 << IRQ)) #define PIC01 0x21 /*8259 Programmable Interrupt Controller*/ #define PIC00 0x20 /* " " " " */ #define EOI 0x20 /* End of Interrupt command */ #define TIMER 0x1c /* PC timer interrupt vector */ /* --------------- line status register values ------------- */ #define XMIT_DATA_READY 0x20 /* ------------ modem control register values -------------- */ #define DTR 1 #define RTS 2 #define OUT2 8 /* ----------- interrupt enable register signals ------------ */ #define DATAREADY 1 /* ------------- serial input interrupt buffer -------------- */ #define BUFSIZE 1024 #define SAFETYLEVEL (BUFSIZE/4) #define THRESHOLD (SAFETYLEVEL*3) #ifndef TRUE #define TRUE 1 #define FALSE 0 #endif [LISTING TWO]] /* ---------- serial.c --------------- * Serial Port Communications Functions */ #include #include #include #include "serial.h" #if COMPILER == MSOFT #define getvect _dos_getvect #define setvect _dos_setvect #endif char recvbuff[BUFSIZE]; char *nextin = recvbuff; char *nextout = recvbuff; int buffer_count; int COMPORT = 1; /* COM1 or COM2 */ int PARITY = 0; /* 0 = none, 1 = odd, 2 = even */ int STOPBITS = 1; /* 1 or 2 */ int WORDLEN = 8; /* 7 or 8 */ int BAUD = 1200; /* 110,150,300,600,1200,2400 */ int TIMEOUT = 10; /* number of seconds to time out */ int xonxoff_enabled = TRUE; static int waiting_for_XON; static int waiting_to_send_XON; int ticker; /* ----- the com port initialization parameter byte ------ */ static union { struct { unsigned wordlen : 2; unsigned stopbits : 1; unsigned parity : 3; unsigned brk : 1; unsigned divlatch : 1; } initbits; char initchar; } initcom; static void (interrupt far *oldtimer)(void); static void interrupt far newtimer(void); static void (interrupt far *oldcomint)(void); static void interrupt far newcomint(void); /* -------- initialize the com port ----------- */ void initcomport(void) { initcom.initbits.parity = PARITY == 2 ? 3 : PARITY; initcom.initbits.stopbits = STOPBITS-1; initcom.initbits.wordlen = WORDLEN-5; initcom.initbits.brk = 0; initcom.initbits.divlatch = 1; outp(LINECTL, initcom.initchar); outp(DIVLSB, (char) ((115200L/BAUD) & 255)); outp(DIVMSB, (char) ((115200L/BAUD) >> 8)); initcom.initbits.divlatch = 0; outp(LINECTL, initcom.initchar); /* ------ hook serial interrupt vector --------- */ if (oldcomint == NULL) oldcomint = getvect(COMINT); setvect(COMINT, newcomint); outp(MODEMCTL, (inp(MODEMCTL) | DTR | RTS | OUT2)); outp(PIC01, (inp(PIC01) & COMIRQ)); outp(INTENABLE, DATAREADY); outp(PIC00, EOI); /* ----- flush any old interrupts ------ */ inp(RXDATA); inp(INTIDENT); inp(LINESTATUS); inp(MODEMSTATUS); } /* ------ restore the serial interrupt vector ---------- */ void restore_serialint(void) { if (oldcomint) setvect(COMINT, oldcomint); } /* ------- clear the serial input buffer --------- */ void clear_serial_queue(void) { nextin = nextout = recvbuff; buffer_count = 0; } /* ---- serial input interrupt service routine ------- */ static void interrupt far newcomint(void) { int c; outp(PIC00,EOI); if (nextin == recvbuff+BUFSIZE) nextin = recvbuff; /* circular buffer */ c = inp(RXDATA); /* read the input */ if (xonxoff_enabled) if (c == XOFF) /* test XON */ waiting_for_XON = 1; else if (c == XON) /* test XOFF */ waiting_for_XON = 0; if (!xonxoff_enabled || (c != XON && c != XOFF)) { *nextin++ = (char) c; /* put char in buff*/ buffer_count++; } if (xonxoff_enabled && !waiting_to_send_XON && buffer_count > THRESHOLD) { while ((inp(LINESTATUS) & XMIT_DATA_READY) == 0) ; outp(TXDATA, XOFF); /* send XOFF */ waiting_to_send_XON = 1; } } /* ---- read a character from the input buffer ----- */ int readcomm(void) { set_timer(TIMEOUT); while (!input_char_ready()) if (timed_out()) return FALSE; if (nextout == recvbuff+BUFSIZE) nextout = recvbuff; --buffer_count; if (waiting_to_send_XON && buffer_count < SAFETYLEVEL) { waiting_to_send_XON = 0; writecomm(XON); } return *nextout++; } /* ---- write a character to the comm port ----- */ int writecomm(int c) { while (waiting_for_XON) ; set_timer(TIMEOUT); while ((inp(LINESTATUS) & XMIT_DATA_READY) == 0) if (timed_out()) return FALSE; outp(TXDATA, c); return TRUE; } /* ---- intercept the timer interrupt vector ----- */ void intercept_timer(void) { if (oldtimer == NULL) { oldtimer = getvect(TIMER); setvect(TIMER, newtimer); } } /* ---------- sleep for n seconds ------------ */ void sleep(unsigned secs) { set_timer(secs); while (!timed_out()) ; } /* ---- restore timer interrupt vector ------- */ void restore_timer() { if (oldtimer) setvect(TIMER, oldtimer); } /* ------ ISR to count timer ticks ------- */ static void interrupt far newtimer() { (*oldtimer)(); if (ticker) --ticker; } [LISTING THREE] /* -------- modem.h ------------ * Modem Definitions */ /* -------- Hayes modem control strings --------- */ #define RESETMODEM "ATZ\r~" #define INITMODEM "ATE0M1S7=60S11=55V1X3S0=0\r~" #define HANGUP "~+++~ATH0\r~ATS0=0\r~" #define ANSWER "ATS0=1\r~" /* --------- prototypes ---------- */ void initmodem(void); void placecall(void); void answercall(void); void disconnect(void); void release_modem(void); [LISTING FOUR] /* ------------ modem.c --------- */ #include #include #include "serial.h" #include "modem.h" char DIAL[] = "ATDT"; char PHONENO[21]; int direct_connection; /* true if connected without a modem */ /* ----------- write a command to the modem ------------ */ static void modout(char *s) { while(*s) { if (*s == '~') sleep(1); else if (!writecomm(*s)) break; s++; } } /* ----------- initialize the modem ---------- */ void initmodem(void) { intercept_timer(); initcomport(); if (!direct_connection) { modout(RESETMODEM); modout(INITMODEM); } } /* -------- release the modem --------- */ void release_modem(void) { if (!direct_connection) modout(RESETMODEM); restore_timer(); restore_serialint(); } /* ----------- place a call -------------- */ void placecall(void) { if (!direct_connection) { modout(DIAL); modout(PHONENO); modout("\r"); clear_serial_queue(); } } /* ------------- answer a call ------------ */ void answercall(void) { if (!direct_connection) { modout(ANSWER); clear_serial_queue(); } } /* ------------ disconnect the call ----------------- */ void disconnect(void) { if (!direct_connection) { modout(HANGUP); clear_serial_queue(); } } [LISTING FIVE] /* ------ tinycomm.c ---------- */ #include #include #include #include #include #include #include "serial.h" #include "modem.h" #if COMPILER==MSOFT #define getch mscgetch #define putch mscputch #define gets mscgets static void mscgets(char *); static int mscgetch(void); static void mscputch(int); static void gotoxy(int,int); static void clrscr(void); #endif int keyhit(void); #define TMSG "\r\n\r\nTINYCOMM: << %s >>\r\n" #define LOGFILE "tinycomm.log" #define ESC 27 extern char PHONENO[]; extern int COMPORT; static FILE *logfp; static FILE *uploadfp; static int running=1; static int connected,answering; static int forcekey, forcecom, forcemenu; static union REGS rg; /* ----- prototypes ------ */ static void tinymenu(void); static void log(int); static void upload(void); void main(int argc, char *argv[]) { int c; if (argc > 1) COMPORT = atoi(argv[1]); if (argc > 2) strcpy(PHONENO, argv[2]); initmodem(); while (running) { if (!connected || forcemenu) { forcemenu = 0; tinymenu(); /* display and process the menu */ } /* ------ poll for a keystroke --------- */ if (keyhit() || forcekey) { c = forcekey ? forcekey : getch(); forcekey = (answering && c == '\r') ? '\n' : 0; if (c == ESC) tinymenu(); else if (connected) { if (answering) log(c); /* answerer echos his own key */ writecomm(c); /* transmit the keystroke */ } } /* ------- poll for serial input ---------- */ if (input_char_ready() || forcecom) { c = forcecom ? forcecom : readcomm(); forcecom = (answering && c == '\r') ? '\n' : 0; log(c); /* display the serial input */ if (answering) writecomm(c); /* answerer echos serial input */ } } release_modem(); } /* ------- display and process the TINYCOMM menu --------- */ static void tinymenu(void) { int c; clrscr(); gotoxy(20,5), cprintf("------ TINYCOMM Menu ------"); gotoxy(20,7), cprintf("P-lace Call"); gotoxy(20,8), cprintf("A-nswer Call"); gotoxy(20,9), cprintf("H-ang Up"); gotoxy(20,10), cprintf("L-og Input %s", logfp == NULL ? "[OFF]" : "[ON]"); gotoxy(20,11), cprintf("S-end Message File"); gotoxy(20,12), cprintf("T-elephone Number (%s)", PHONENO[0] ? PHONENO : "???-????"); gotoxy(20,13), cprintf("E-xit to DOS"); gotoxy(20,14), cprintf(connected ? "Esc to return to session" : ""); gotoxy(20,16), cprintf("Enter Selection > "); c = getch(); putch(toupper(c)); switch (toupper(c)) { case 'P': /* Place a call */ if (!connected) { cprintf(TMSG, "Dialing"); initmodem(); /* initialize the modem */ placecall(); /* dial the phone number */ connected = 1; cprintf(TMSG, "Esc for the menu"); } break; case 'A': /* Answer a call */ if (!connected) { cprintf(TMSG, "Waiting"); initmodem(); /* initialize the modem */ answercall(); /* wait for an incoming call */ answering = connected = 1; cprintf(TMSG, "Esc for the menu"); } break; case ESC: /* Return to the session */ if (connected) cprintf(TMSG, "Esc for the menu"); break; case 'L': /* Log input on/off*/ if (logfp == NULL) logfp = fopen(LOGFILE, "a"); else { fclose(logfp); logfp = NULL; } forcemenu++; break; case 'E': /* Exit to DOS */ cprintf(TMSG, "Exiting"); running = 0; case 'H': /* Hang up */ if (connected) { cprintf(TMSG, "Hanging up"); disconnect(); connected = answering = 0; } break; case 'S': /* Send a message file */ upload(); break; case 'T': /* Change the phone number */ cprintf(TMSG, "Enter Telephone Number: "); gets(PHONENO); forcemenu++; break; default: putch(7); break; } } /* --------- upload an ASCII file ---------- */ static void upload(void) { char filename[65]; int c = 0; if (uploadfp == NULL && connected) { cprintf(TMSG, "Enter file drive:path\\name > "); gets(filename); if ((uploadfp = fopen(filename, "r")) == NULL) cprintf(TMSG, "Cannot open file"); else { cprintf(TMSG, "Press Esc to stop sending file"); while ((c = fgetc(uploadfp)) != EOF) { if (c == '\n') { writecomm('\r'); log(answering ? '\r' : readcomm()); } writecomm(c); log(answering ? c : readcomm()); if (keyhit()) if (getch() == ESC) { cprintf(TMSG, "Abandoning file"); break; } } fclose(uploadfp); uploadfp = NULL; } } } /* ----- echo modem or keyboard input and write to log ----- */ static void log(int c) { putch(c); if (logfp) fputc(c, logfp); } /* -------------------------------------------------------- Clone functions to keep Ctrl-Break from crashing the system by aborting before interrupt vectors get restored -------------------------------------------------------- */ #if COMPILER==TURBOC /* --------- use bios to test for a keystroke -------- */ int keyhit() { rg.h.ah = 1; int86(0x16, &rg, &rg); return ((rg.x.flags & 0x40) == 0); } #else /* ------- substitute for getch for MSC --------- */ static int mscgetch(void) { rg.h.ah = 0; int86(0x16, &rg, &rg); return rg.h.al; } /* ------- substitute for putch for MSC --------- */ static void mscputch(int c) { rg.x.ax = 0x0e00 | (c & 255); rg.x.bx = 0; int86(0x10, &rg, &rg); } /* -------- gotoxy clone ------------ */ static void gotoxy(int x, int y) { rg.h.ah = 2; rg.x.bx = 0; rg.h.dh = (char) y-1; rg.h.dl = (char) x-1; int86(0x10, &rg, &rg); } /* -------- clrscr clone ------------- */ static void clrscr(void) { rg.x.ax = 0x0600; rg.h.bh = 7; rg.x.cx = 0; rg.x.dx = (24 << 8) + 79; int86(0x10, &rg, &rg); } /* ----------- gets clone ------------- */ static void mscgets(char *s) { int c; while (1) { if ((c = mscgetch()) == '\r') break; mscputch(c); *s++ = (char) c; } *s = '\0'; } #endif [LISTING SIX] ; ------------- keyhit.asm --------------- ; ; Use this in MSC C programs in place of kbhit ; This function avoids Ctrl-Break aborts ; _text segment para public 'code' assume cs:_text public _keyhit _keyhit proc near mov ah,1 int 16h mov ax,1 jnz keyret mov ax,0 keyret: ret _keyhit endp _text ends end [LISTING SEVEN] tinycomm (serial.h, modem.h) modem (serial.h, modem.h) serial (serial.h) [LISTING EIGHT] # TINYCOMM.MAK: make file for TINYCOMM.EXE with Microsoft C/MASM # .c.obj: cl /DMSOFT=1 /DCOMPILER=MSOFT -c -W3 -Gs $*.c tinycomm.obj : tinycomm.c serial.h modem.h window.h modem.obj : modem.c serial.h modem.h serial.obj : serial.c serial.h keyhit.obj : keyhit.asm masm /MX keyhit; tinycomm.exe : tinycomm.obj modem.obj serial.obj keyhit.obj link tinycomm+modem+serial+keyhit,tinycomm,,\lib\slibce