C PROGRAMMING

TINYCOMM: The Tiny Communications Program

Al Stevens

This month's column is about asynchronous serial communication and how it fits into the "C Programming" column project. We will develop the first of a series of tools designed to connect one computer to another with modems and the telephone system. To illustrate these tools, we will build TINYCOMM, a tiny communications program with the ability to originate or answer calls with another computer, converse interactively with the keyboard and screen, capture messages to a disk log file, and upload message files.

Just as December's TWRP, the Tiny Word Processor, is no competition for the high-end word processors, TINYCOMM is no threat to Smartcom, Procomm, or Qmodem. Our "C Programming" column project is slowly gathering a collection of small but useful tools that we will eventually integrate into a program targeted to a specific purpose. In the meantime we can learn from these small examples, seeing how the C language is applied in building such applications.

You do not need an in-depth understanding of serial input/output and modems to use this month's code, but it could help you. I recommend Mastering Serial Communications by Peter W. Gofton, 1986, Sybex Inc. The book belabors the complexity of serial I/O, and that is a genuine concern. You will learn here, however, that the basic steps for making a modem connection to a remote computer involve a minimum of C code.

Communications

When humans converse, we use channels of communication appropriate to the conversations and our proximity to one another. The most common channel is the spoken word issued within earshot of the participants. Less common are fortune cookies, convicts clanking on water pipes, and bottled messages floating in the ocean, but whatever the medium and the message, some are better suited than others.

People can choose convenient media as time and opportunity permits, but when computers converse, the medium must be exactly matched to the message. A participant in an electronic conversation must use a medium known, agreeable, and available to the other participants.

Therefore, if you want to send a message to your mother and she has a computer, modem, and communications software, your computer can call her computer. If hers can answer the phone and receive the message, she will eventually get to read it. That may or may not require the participation of one or both of you at the time of the transmission. She'd probably prefer a call.

The Communicating Hardware

Programs that communicate with remote computers must manage two layers of hardware: the modem and the serial port. The local modem translates the local program's data into tones that are sent across telephone lines to the remote modem. The local modem then receives the remote modem's tones and translates them into data. The computer sends and receives data values to and from the modem through the serial port. Management of this communication requires functions for the serial port and the modem. Which way you select to do this depends on the hardware itself. We will use the IBM PC serial ports and Hayes-compatible modems.

The Asynchronous Serial Port

A serial port is a bit stream device that is used in applications where eight data lines are not available and/or the distances between devices preclude the use of the lower signal strengths of the computer.

The asynchronous serial port adds a start bit and one or two stop bits to each byte. There may be a parity bit that may use odd or even parity. A byte may consist of from five to eight data bits. The transmission speed is expressed in bits per second and is called the "baud rate." Senders and receivers of asynchronous serial messages must agree on the number of data bits; the number of stop bits; whether odd, even, or no parity exists; and the baud rate of the transmission.

The IBM PC can have one or two serial ports. A program can communicate with one of these serial ports by reading and writing the port registers directly, by calling the serial I/O ROM BIOS functions, or by using DOS. Each approach has advantages and disadvantages. The book mentioned earlier explains these trade-offs; space prevents me from offering you the comprehensive treatment the subject requires. For our purposes, you may assume that I have weighed the benefits and made the best selection for the examples at hand. We will use direct port addressing and an interrupt service routine for the receiver side of the serial transmissions.

These functions employ an XON, XOFF protocol where the receiving computer effectively turns the sender's transmissions on and off. When its input buffer gets nearly full, the receiver sends the XOFF character to the sender. The sender suspends transmitting until the receiver sends the XON character. The receiver watches its buffer as characters are removed and sends the XOFF when the level is below the safety margin.

File transfer protocols such as XModem and Kermit use their own packet management techniques to handle such delays. XModem can be a problem in networks because the XModem protocol specification includes fixed timeout periods that can be exceeded by latent delays introduced by the network. The local communications software must know when one of these protocols is in use, because they are often used to transmit binary files -- files that might have the XON or XOFF characters as valid data bytes. During these transmissions, the XON/XOFF protocol is disabled.

Listing One, page 138, is serial.h. This header file declares the prototypes and macros for the serial port functions. It also defines status register signals, the timer interrupt vector, and some control parameters for our program. The comstat macro reads the status of the serial port. The input_char_ready macro returns a true value if there is a byte waiting in the serial input buffer and a false value if not.

Listing Two, page 138, is serial.c. These functions manage serial port initialization, input, and output. The initcomport function initializes the communications port by using the structure named initcom to contain the initialization parameters that are written to the 8250 universal asynchronous receiver/transmitter (UART). The integers named COMPORT, PARITY, STOPBITS, WORDLEN, and BAUD are initialized with the default values assumed by the functions.

The initcomport function sets up the serial receiver interrupt process. In this process, all serial input characters are read by the interrupt service routine named newcomint, which collects the characters as they are read into a circular buffer. The initialization steps and the serial input and output operations use definitions derived from the port number for COM1 or COM2. Those definitions are in serial.h. (You can modify the code to work with other machines that use the 8250 UART and the 8259 Programmable Interrupt Controller by changing the base port address defined in serial.h as BASEPORT and the interrupt request line named IRQ, also in serial.h.) First the current contents of the serial interrupt vector are read and saved away. Then the vector is initialized with the address of newcomint. The 8250 UART has two registers that must be initialized. The program asserts the data terminal ready (DTR), request to send (RTS), and user defined output 2 (OUT2) signals in the modem control register, and writes the interrupt enable register with the data available signal set to generate interrupts. The 8259 programmable interrupt controller is written to allow the proper IRQ line to cause an interrupt. Then the interrupt controller is reset and all UART input registers read to clear any stray interrupts that might be hanging around.

The restore_serialint function in serial.c should be called at the end of a program that has used serial input/output. This function returns the serial interrupt vector to its original value.

The clear_serial_queue function resets the serial receiver's circular buffer to an empty condition. This function is used by the modem manager to get rid of any unneeded text condition responses from the modem.

The interrupt service routine newcomint is for serial input. It resets the interrupt controller, reads the serial input byte, and stuffs it into the circular buffer unless the character is an XON or XOFF with XON/XOFF protocols enabled. In this case the function sets a flag so the transmitter knows to suspend or resume transmissions. If XON/XOFF is enabled and the buffer is at or beyond its threshold, newcominttransmits the XOFF character to tell the sender to hold up for a while.

The readcomm function waits for a byte to be available in the receiver's circular buffer and then extracts it for the calling function. A program that polls for serial input should call this function only after getting a true return from the input_char_ready macro because readcomm waits for a character to be received. When readcomm sees that the buffer is at a safe level after newcomint has transmitted the XOFF, readcomm transmits the XON character to tell the sender to resume transmissions.

Programs that read ASCII text from a remote computer with seven data bits and odd or even parity should logically AND the return from readcomm with 0x7f to strip the parity bit. If you are reading a binary stream, such as an archived file, you should accept the full eight-bit value.

The writecomm function sends the byte in its parameter to the remote computer through the serial port. Note that readcomm and writecomm return a false value if the port times out as a result of the elapse of the programmed TIMEOUT value with no character being received or written. TIMEOUT is defined in serial.c and is expressed in seconds.

The timer functions in serial.c provide another example of the use of the interrupt function type. These timer functions process timeouts or suspend processing for a specified number of seconds. Any program that will use the functions in serial.c must first call intercept_timer to attach to the timer interrupt vector. Before returning to DOS, the program must call restore_timer to reset the vector. If you use the modem functions in modem.c you do not need to make these calls, because the modem functions do it for you.

serial.h has two macros named set_timer and timed_out that operate the timer. The first macro sets a timer value in seconds. The second macro returns a true value if the most recent setting has elapsed. set_timer puts a value into the ticker variable. The value in ticker is approximately 18.2 times the number of seconds to count; the timer interrupt occurs 18.2 times per second. The newtimer interrupt chains the interrupt; then, if ticker is greater than zero, newtimer decrements it. When the timed_out macro finds that ticker is not greater than zero, it returns a true value.

sleep uses the timer to suspend the program for a specified number of seconds. Turbo C already has just such a function in its library, but Microsoft C does not.

Note that the Turbo C functions getvect and setvect are used to read and write the serial and timer interrupt vectors. If you are using Microsoft C, these are changed to _dos_getvect and _dos_setvect, the MSC equivalents. If you are porting this software to a compiler that does not support the interrupt function type, you must use assembly language for the entry and exit to each of the interrupt functions. The book I mentioned earlier has an example of this technique.

The serial port detects the break condition, framing error, parity error, and overrun error, any of which might happen. Why do we not check and correct for these conditions? We do not for two reasons: First, the exchange of human-readable ASCII data assumes that the user can tell when the characters being read are incorrect. You can visually compensate for these so-called "line hits" or terminate the transmission if the errors exceed a tolerable threshold. Second, the other exchanges--uploading and downloading files with XModem, Kermit, et al.--have their own error-correcting protocols involving checksums, timeouts, and retries. We will explore these techniques in the months to come.

The Modem

Modems are semi-intelligent devices that connect remote computers with telephone lines and that can be programmed to operate in different ways. This programming implies a language for the modem--a way to set the modem's modes and read the modem's status. The accepted standard for this language is the Hayes command set.

Because a modem supports terminal connections, it recognizes and responds with language that people can understand. Well, almost. The Hayes command set is hardly what you would call a natural language, but a person can learn it. Oddly, the modem speaks a more cogent language than it understands. You tell it ATEOM1V1S0 = 0 and it answers OK. You say ATDT 17033710188 and it says BUSY, CONNECT, NO ANSWER, or NO CARRIER. You can learn its language if you want, but any communications program worth its salt will know the language and hide it from you.

Listing Three, page 139, is modem.h. It has configuration parameters and prototypes for the modem functions. The parameters' strings initialize and reset the modem, dial a number, and answer an incoming call. In some communications programs, these parameters are maintained in a configuration file built by a setup program. We'll just hardcode them this way. The curly characters in the RESETMODEM, INITMODEM, and HANG UP parameters are not part of the Hayes command set. Rather, they tell the modout function to wait one second while sending a command to the modem.

Vendors who sell so-called Hayes-compatible modems do not always get it right. Also, many modems have configuration micro switches that set default values. The string value of the INITMODEM parameter is one that works with the Toshiba T-1000's internal modem and the US Robotics Courier 2400. This is a black art. If you have modem problems with this program, you might need to mess with the parameters, the micro switches, or both.

Listing Four, page 139, modem.c contains the functions that control the modem. The initmodem function initializes the serial port and the modem. It calls intercept_timer to let the program hook the timer interrupt vector. Programs should call the next function, release_modem, before they terminate. It restores the timer and serial interrupt vectors and resets the modem. If you fail to use this procedure, your computer will go off into the reeds and the bulrushes when you exit to DOS. The placecall function dials the number in the PHONENO parameter. The answer-call function prepares the modem to automatically answer the phone. The disconnect function disconnects the modem from the phone line. The modout function is used by the others to send a command to the modem. The function tests for the curly character in the command string and modout calls the sleep function in serial.c to tell the program to wait one second for each curly character.

A program that supports direct connection of the serial ports of two computers with no modems will set the direct_connection variable to a true value, which suppresses the modem commands. This mode is one way to test a communications program when two phone lines are not available. You must connect the serial ports with a "null modem" cable, which crosses the send and receive lines -- usually pins 2 and 3 -- in the cable connectors.

TINYCOMM

The code in serial.h, serial.c, modem.h, and modem.c represents the primitive functions required to communicate by modem. To use these functions, you need a higher-level communications program. Listing Five, page 139, is tinycomm.c, the bare beginnings of such a program. Its purpose is to demonstrate the application of the communications functions, but it has a good bit of functionality packed into such a tiny package.

When you run TINYCOMM, you can specify the serial port -- 1 or 2 -- and the telephone number on the command line as shown here:

  C>tinycomm 2 555-1212

After this command, you see this menu.

     ------ TINYCOMM Menu ------
P-lace Call
A-nswer Call
H-ang Up
L-og Input [OFF]
S-end Message File
T-elephone Number (???-????)
E-xit to DOS

Enter Selection >

The TINYCOMM menu selections allow you to place a call, tell TINYCOMM to prepare to answer a call, hang up, turn the disk logging off traffic on and off, send an ASCII file to the other end, reprogram the telephone number, and exit to DOS.

Once a connection is made, TINYCOMM sends the characters you type and displays the characters it receives. These displays include the modem's messages, such as CONNECT and NO CARRIER. TINYCOMM expects you to read these messages and react appropriately. There is no automatic recognition of any text input to TINYCOMM. You get in and out of the menu by pressing the Esc key.

TINYCOMM's message, upload, and log features use a straight ASCII protocol with XON/XOFF enabled. This is acceptable for message traffic but would not work at all for the transfer of binary formats such as archived or executable files. File transfer protocols will come in a future installment.

Ctrl Break

When a program takes over an interrupt vector, the program must not terminate abnormally. If it does, the interrupt vector will still point to the memory formerly occupied by the program. The next time the interrupt happens, the system will be awry. TINYCOMM hooks the timer and serial interrupt vectors, so we must not allow it to be terminated other than through the normal exit point of the program where these vectors are restored.

When you press Ctrl C or Ctrl Break, DOS normally displays the ^C token and aborts the program. This would be one of those unwanted terminations I just mentioned. You can take over the Ctrl Break and Ctrl C interrupt vectors (0x1b and 0x23) and prevent the termination, but the stupid ^C token still gets displayed, messing up your well-planned screen display and moving your cursor.

One way to defeat this dubious feature of DOS is to avoid using DOS for keyboard input and screen output functions. TINYCOMM uses Turbo C's getch and putch or its own keyboard and screen functions for Microsoft C, and these measures effectively avoid DOS.

TINYCOMM polls the keyboard before calling getch and polls the serial buffer before calling getcomm. This technique allows either device to get a character in edgewise, which is necessary in a full duplex communications operation. The two usual ways to poll the keyboard are the kbhit function and the bioskey (TC) or _bios_key (MSC) function. Unfortunately, these functions involve the Ctrl Break and Ctrl C logic, so we cannot use them. Instead we must use BIOS to see if a key has been pressed. Both compilers have the int86 function, and we can use this function to call BIOS interrupt 0xl6, which manages the keyboard. The keyhit function in tinycomm.c uses int86 and the problem is solved -- well, not quite. BIOS returns its results in the zero bit of the CPU's flags register. Turbo C's int86 includes the flags register in the REGS union written by int86; the Microsoft C version does not. As a consequence, Microsoft C provides no way that I can find to poll the keyboard without the offensive ^C showing up and perhaps aborting the program. To overcome this obstacle, we use the assembly language function found in Listing Six, keyhit.asm (page 140).

The keyhit function is the only Ctrl Break defensive measure we need with Turbo C. Microsoft C is not as easy. The MSC getch, putch, and gets functions let the break operation get into the act. Therefore, the mscgetch, mscputch, and mscgets functions are added to MSC-compiled versions of tinycomm.c. The gotoxy and clrscr functions are clones of similar Turbo C functions.

Listing Seven, page 140, is tinycomm.prj, the Turbo C environment project make file. Set your compiler defines option (Alt-O/C/D) to these parameters:

  
   MSOFT=1;TURBOC=2;COMPILER=
                       TURBOC

Listing Eight, page 140, is tinycomm.mak, the make file for Microsoft C. It uses the small memory model.

Next month we'll overhaul the TINYCOMM program to use windows, help, menus, and the other tools in our collection. We will add features to download files, program the serial port's parameters from a menu, use the direct connection features of the modem manager, access a phone directory, and maintain the program's setup in a configuration file. We will insert the hooks to add file transfer protocols (but not the protocols yet). As I develop this program, I am testing it by using it for all my online activities, so if I disappear some night in the middle of a heated online exchange, you'll know why.

_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 <stdio.h>
#include <conio.h>
#include <dos.h>
#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 <dos.h>
#include <conio.h>
#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 <stdio.h>
#include <string.h>
#include <ctype.h>
#include <conio.h>
#include <stdlib.h>
#include <dos.h>
#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



Copyright © 1989, Dr. Dobb's Journal