www.pudn.com > commutil.zip > COMM.CPP


// ******************************************************************** // 
//                                                                      // 
//      COMM.CPP                                                        // 
//      Copyright (c) 1993, Michael Holmes and Bob Flanders             // 
//      C++ Communication Utilities                                     // 
//                                                                      // 
//      Chapter 7: Receiving a FAX                                      // 
//      Last changed in chapter 7                                       // 
//                                                                      // 
//      This file contains the functions to implement the basic         // 
//      communications class and facilities.  Each instance of the      // 
//      Comm class will control a single communications port.           // 
//                                                                      // 
// ******************************************************************** // 
 
 
class Comm 
    { 
    public: 
        Comm(int b,                         // define a comm instance 
             int i,                         //   base addr, interrupt 
             int d,                         //   baud rate divisor 
             int l,                         //   line control setting 
             int fc = 0,                    //   flow control flag 
             UINT si = 3200,                //   input queue size 
             UINT so = 1500);               //   and output queue size 
        UINT Read(char *c,                  // read a character from queue 
                  char *m,                  // ..and get modem status reg 
                  char *l),                 //   and line status register 
             Set8n(void),                   // set 8 data bits, no parity 
             ICount(void),                  // get depth of input queue 
             OCount(void),                  // ..or output queue 
             IFree(void),                   // free space in input queue 
             OFree(void);                   // ..or output queue 
        int  Modem(void),                   // rtn modem status register 
             ModemChanged(void),            // rtn TRUE if msr changed 
             IFlow(void),                   // rtn input flow ctrl status 
             IEmpty(void),                  // rtn TRUE if input queue 
             OEmpty(void);                  // ..or output queue empty 
        long GetSpeed(void);                // rtn current speed in bps 
        void SetSpeed(int d),               // set up port's divisor 
             SetBPS(long s),                // set up port's speed 
             SetLine(int l),                // ..and line control register 
             Write(int c),                  // write a character 
             Write(char *s),                // ..or a string of characters 
             Write(char *s, int l),         // ..or a block of characters 
             IClear(void),                  // clear input queue 
             OClear(void),                  // ..and output queue 
             DTR(long t = 1500L),           // lower DTR temporarily 
             RTS(int),                      // RTS signal control 
             IntRoutine(void);              // interrupt service routine 
       ~Comm();                             // destructor 
 
    private: 
        UINT base,                          // base port address 
             irq,                           // interrupt number 
             divisor,                       // baud rate divisor 
             line,                          // initial line control 
             i_size,                        // input buffer size 
             o_size,                        // output buffer size 
             i_start,                       // flow ctl restart limit 
             i_stop,                        // ..and upper stop limit 
             i_count,                       // characters in input queue 
             o_count,                       // ..and output queue 
             Deque(void);                   // deque an output queue char 
        char *i_buf,                        // input buffer 
             *i_get,                        // ..and nxt user get location 
             *i_put,                        // ..and nxt comm put location 
             *i_limit,                      // ..and last location 
             *i_last,                       // ..and last i_put location 
             i_of,                          // ..and input overflow flag 
             *o_buf,                        // output buffer 
             *o_get,                        // ..and nxt comm get location 
             *o_put,                        // ..and nxt user put location 
             *o_limit,                      // ..and last location 
             msr_changed,                   // msr changed flag 
             int_msr,                       // last interrupt msr 
             int_lsr,                       // ..and last interrupt lsr 
             fifo,                          // 16550 flag 
             flow,                          // flow control enable flag 
             o_flow,                        // output flow controlled flag 
             i_flow,                        // input flow controlled flag 
             empty_trans;                   // empty transmitter flag 
        void InstallInt(void),              // install interrupt svc rtn 
             DeInstallInt(void),            // de-install interrupt rtn 
             SetLimits(void),               // set up flow ctl limits 
             CheckFifo(void),               // set up fifo flags 
             Queue(int c,                   // queue char to input queue 
                   int m,                   //   saving modem status reg 
                   int l),                  //   and line status register 
             interrupt (*old_comm)(...);    // old comm interrupt pointer 
    }; 
 
 
extern 
Comm   *comm;                               // current Comm instance 
void    interrupt far comm_int(...);        // comm interrupt routine 
 
 
 
/* ******************************************************************** * 
 * 
 *  Comm -- define communications port instance 
 * 
 * ******************************************************************** */ 
 
Comm::Comm(int b,                           // comm port base address 
           int i,                           // interrupt number 
           int d,                           // baud rate divisor 
           int l,                           // line control setting 
           int fc,                          // flow control flag 
           UINT si,                         // input queue size 
           UINT so)                         // output queue size 
{ 
UINT    max_size = 64535U / 3;              // max number of input chars 
 
base = b;                                   // set up instance base addr 
irq = i;                                    // ..interrupt request 
o_size = so;                                // ..output buffer size 
flow = fc;                                  // ..flow control flag 
CheckFifo();                                // ..fifo flag if available 
SetSpeed(d);                                // ..line speed 
SetLine(l);                                 // ..line format 
 
if (si < 512)                               // q. less than .5k of buffer? 
    si = 512;                               // a. yes .. set to minimum 
 
if (si > max_size)                          // q. greater than maximum? 
    si = max_size;                          // a. yes .. set to max size 
 
if (so < 256)                               // q. really small output buf? 
    so = 256;                               // a. yes .. set to minimum 
 
i_size = si;                                // save input buffer size 
empty_trans = 1;                            // ..set empty transmitter flag 
msr_changed = 1;                            // ..set msr changed flag 
o_flow = i_flow = 0;                        // ..clear active flags 
o_count = i_count = 0;                      // ..and current queue counts 
 
i_buf = i_get = i_put =                     // allocate input buffer 
            new char[i_size * 3];           // ..for data, lsr and msr 
i_limit = i_buf + (i_size - 1) * 3;         // ..set limit address 
memset(i_buf, 0, i_size * 3);               // ..and clear area to nulls 
 
o_buf = o_get = o_put = new char[o_size];   // allocate output buffer 
o_limit = o_buf + o_size - 1;               // ..set limit address 
memset(o_buf, 0, o_size);                   // ..and clear area to nulls 
 
SetLimits();                                // set up flow ctl limits 
InstallInt();                               // ..and interrupt routine 
 
int_msr = IN(MSR);                          // set up initial MSR 
 
} 
 
 
 
/* ******************************************************************** * 
 * 
 *  SetLimits -- set up receive buffer flow control limits 
 * 
 *  This routine sets up the flow control limits for receive 
 *  operations.  Flow control will be asserted when the buffer 
 *  reaches a point when there is not enough room to receive 
 *  a second worth of data in the input buffer.  Flow control will 
 *  be de-asserted when 50% of the upper limit is read by the 
 *  application. 
 * 
 * ******************************************************************** */ 
 
void    Comm::SetLimits(void) 
{ 
 
i_stop = (UINT) (115200L / 10) / divisor;   // get 1 second in characters 
 
if (i_stop > i_size)                        // q. limit too high? 
    i_stop = i_size / 2;                    // a. yes .. set at half 
 else 
    i_stop = i_size - i_stop;               // else .. set limit point 
 
i_start = i_stop / 2;                       // set restart point at 50% 
 
} 
 
 
 
/* ******************************************************************** * 
 * 
 *  CheckFifo -- if UART is a 16550, set FIFO variable to true 
 * 
 * ******************************************************************** */ 
 
void    Comm::CheckFifo(void) 
{ 
 
fifo = 0;                                   // assume standard uart 
OUT(FCR, 0xcf);                             // try to enable FIFOs 
 
if ((IN(IIR) & 0xc0) != 0xc0)               // q. FIFO bits found? 
    return;                                 // a. no .. just return 
 
OUT(FCR, 0);                                // turn off FIFOs 
fifo = 1;                                   // set fifo available flag 
 
} 
 
 
 
/* ******************************************************************** * 
 * 
 *  SetSpeed -- set up port baud rate divisor 
 * 
 * ******************************************************************** */ 
 
void    Comm::SetSpeed(int d)               // baud rate divisor 
{ 
 
divisor = d;                                // save divisor for later 
OUT(IER, 0);                                // clear interrupts enable 
 
if (fifo)                                   // q. is UART a 16550 chip? 
    OUT(FCR, FCR_16550);                    // a. yes .. enable fifo 
 
IN(LSR);                                    // read/reset line status reg 
IN(MSR);                                    // ..and modem status register 
IN(IIR);                                    // ..and interrupt id register 
IN(RBR);                                    // ..and receive buffer reg 
 
__asm   cli                                 // stop interrupts 
OUT(LCR, IN(LCR) | LCR_DLAB);               // set divisor latch bit 
OUT(DLM, d >> 8);                           // out msb portion of divisor 
OUT(DLL, d & 0xff);                         // ..then the lsb portion 
OUT(LCR, IN(LCR) & ~LCR_DLAB);              // clear divisor latch bit 
OUT(MCR, MCR_DO);                           // enable DTR, OUT2 and RTS 
__asm   sti                                 // ..and interrupts 
 
OUT(IER, IER_RBF | IER_TBE | IER_MSI);      // then enable UART interrupts 
 
} 
 
 
 
/* ******************************************************************** * 
 * 
 *  SetBPS -- set port's speed 
 * 
 * ******************************************************************** */ 
 
void    Comm::SetBPS(long s)                // speed in BPS 
{ 
 
SetSpeed((int)(115200L / s));               // calc and set divisor 
 
} 
 
 
 
/* ******************************************************************** * 
 * 
 *  GetSpeed -- retrieve current port speed 
 * 
 * ******************************************************************** */ 
 
long   Comm::GetSpeed(void) 
{ 
 
return((long) (115200L / (long) divisor));  // return current speed 
 
} 
 
 
 
/* ******************************************************************** * 
 * 
 *  SetLine -- set up port's line control register 
 * 
 * ******************************************************************** */ 
 
void    Comm::SetLine(int l)                // new line control setting 
{ 
 
line = l & ~LCR_DLAB;                       // save new value in instance 
OUT(LCR, line);                             // ..and write new LCR 
 
} 
 
 
 
/* ******************************************************************** * 
 * 
 *  Set8n -- set 8 data bits, no parity 
 * 
 * ******************************************************************** */ 
 
UINT    Comm::Set8n(void) 
{ 
UINT    oldline;                            // old LCR value 
 
 
oldline = IN(LCR);                          // get the current LCR value 
 
line = ((oldline & LCR_STOP) | LCR_WLEN);   // setup 8 databits, no parity 
OUT(LCR, line);                             // ..and write new LCR 
 
return(oldline);                            // return old LCR value 
 
} 
 
 
 
/* ******************************************************************** * 
 * 
 *  InstallInt -- install interrupt service routine 
 * 
 * ******************************************************************** */ 
 
void    Comm::InstallInt(void) 
{ 
int     mask;                               // interrupt mask 
 
 
old_comm = getvect(irq + 8);                // save old comm interrupt rtn 
comm = this;                                // ..save instance address 
setvect(irq + 8, comm_int);                 // ..establish new routine 
 
mask = inportb(I8259M);                     // get current interrupt mask 
mask &= ~(1 << irq);                        // determine new mask 
outportb(I8259M, mask);                     // ..and put in place 
 
} 
 
 
 
/* ******************************************************************** * 
 * 
 *  DeInstallInt -- de-install interrupt service routine 
 * 
 * ******************************************************************** */ 
 
void    Comm::DeInstallInt(void) 
{ 
int     mask;                               // interrupt mask 
 
 
OUT(IER, 0);                                // disable UART interrupt 
OUT(FCR, 0);                                // ..and fifo, if available 
setvect(irq + 8, old_comm);                 // re-establish old comm rtn 
 
mask = inportb(I8259M);                     // get current interrupt mask 
mask |= (1 << irq);                         // determine new mask 
outportb(I8259M, mask);                     // ..and turn off comm int 
 
} 
 
 
 
/* ******************************************************************** * 
 * 
 *  ModemChanged -- returns TRUE if the modem status register changed 
 * 
 * ******************************************************************** */ 
 
int     Comm::ModemChanged(void) 
{ 
int     rtn;                                // return value 
 
 
rtn = msr_changed;                          // get current status 
msr_changed = 0;                            // ..and clear flag 
return(rtn);                                // ..and return current status 
 
} 
 
 
 
/* ******************************************************************** * 
 * 
 *  Modem -- returns the modem status register 
 * 
 * ******************************************************************** */ 
 
int     Comm::Modem(void) 
{ 
 
return(int_msr);                            // return last modem status 
 
} 
 
 
 
/* ******************************************************************** * 
 * 
 *  IFlow -- return status of input flow control 
 * 
 * ******************************************************************** */ 
 
int     Comm::IFlow(void) 
{ 
 
return(i_flow);                             // rtn TRUE if input flow 
                                            // ..has been restricted 
} 
 
 
 
/* ******************************************************************** ** 
 * 
 *  Read -- read a character, the msr and lsr from the port's input queue 
 * 
 *  Returns: -1 = input queue was empty 
 *            0 = character returned 
 *            n = number of characters overflowed buffer 
 * 
 * ******************************************************************** */ 
 
UINT    Comm::Read(char *c,                 // queued character 
                   char *m,                 // modem status register 
                   char *l)                 // line status register 
{ 
 
 
if (i_count == 0)                           // q. input queue empty? 
    return(-1);                             // a. yes .. return w/err code 
 
*c = *i_get++;                              // get char from input queue 
*m = *i_get++;                              // ..then get modem status 
*l = *i_get++;                              // ..then get line status 
i_count--;                                  // ..finally decrement count 
 
if (i_get > i_limit)                        // q. reached end of buffer? 
    i_get = i_buf;                          // a. yes .. set to beginning 
 
if (i_flow && (ICount() <= i_start))        // q. port need restarting? 
    RTS(1);                                 // a. yes .. raise RTS line 
 
return(((UCHAR) *l == 0xff) ?               // return with character or 
                    ((*m << 8) + *c) : 0);  // ..with overflow count 
                                            // ..based on lsr flag 
} 
 
 
 
/* ******************************************************************** ** 
 * 
 *  IEmpty -- return TRUE if input queue is empty 
 * 
 * ******************************************************************** */ 
 
int     Comm::IEmpty(void) 
{ 
 
return((i_count == 0) &&                    // TRUE if queue empty 
            ((UCHAR) i_put[2] != 0xff));    // ..and not in overflow 
 
} 
 
 
 
/* ******************************************************************** ** 
 * 
 *  OEmpty -- return TRUE if output queue empty 
 * 
 * ******************************************************************** */ 
 
int     Comm::OEmpty(void) 
{ 
 
return(o_count == 0);                       // TRUE if queue empty 
 
} 
 
 
 
/* ******************************************************************** ** 
 * 
 *  ICount -- get depth of the input queue 
 * 
 * ******************************************************************** */ 
 
UINT    Comm::ICount(void) 
{ 
 
return(i_count);                            // return nbr in queue 
 
} 
 
 
 
/* ******************************************************************** ** 
 * 
 *  OCount -- get depth of the output queue 
 * 
 * ******************************************************************** */ 
 
UINT    Comm::OCount(void) 
{ 
 
return(o_count);                            // return nbr in queue 
 
} 
 
 
 
/* ******************************************************************** ** 
 * 
 *  IFree -- get free space in input queue 
 * 
 * ******************************************************************** */ 
 
UINT    Comm::IFree(void) 
{ 
 
return(i_size - i_count);                   // buffer size less in-use 
 
} 
 
 
 
/* ******************************************************************** ** 
 * 
 *  OFree -- get free space in output queue 
 * 
 * ******************************************************************** */ 
 
UINT    Comm::OFree(void) 
{ 
 
return(o_size - o_count);                   // buffer size less in-use 
 
} 
 
 
 
/* ******************************************************************** ** 
 * 
 *  Write -- write a character to the output queue 
 * 
 * ******************************************************************** */ 
 
void    Comm::Write(int c)                  // character to queue up 
{ 
 
while (NOT OFree())                         // q. room in output queue? 
    ;                                       // a. no .. wait a bit 
 
*o_put++ = c;                               // put char into output queue 
 
if (empty_trans)                            // q. pump need priming? 
    {                                       // a. yes .. try to do output 
    empty_trans = 0;                        // clear empty tranmitter flag 
    o_count++;                              // ..and count character 
 
    if ((NOT flow || IN(MSR) & MSR_CTS)     // q. output flow satisfied? 
                && (c = Deque()) != -1)     // ..and queue not empty? 
        OUT(THR, c);                        // a. yes .. send char to port 
    } 
 
 else 
    o_count++;                              // else .. count characters 
 
if (o_put > o_limit)                        // q. reach end of buffer? 
    o_put = o_buf;                          // a. yes .. set to beginning 
 
} 
 
 
 
/* ******************************************************************** * 
 * 
 *  Write -- write a string of characters to output queue 
 * 
 * ******************************************************************** */ 
 
void    Comm::Write(char *s)                // string of chars to output 
{ 
 
for (; *s;)                                 // for each char in the string 
    Write(*s++);                            // write to the output queue 
 
} 
 
 
 
/* ******************************************************************** * 
 * 
 *  Write -- write a block of characters to output queue 
 * 
 * ******************************************************************** */ 
 
void    Comm::Write(char *s,                // block of chars to output 
                    int  l)                 // length of block 
{ 
 
for (; l--;)                                // for each char in the block 
    Write(*s++);                            // write to the output queue 
 
} 
 
 
 
/* ******************************************************************** ** 
 * 
 *    IClear -- clear input queue of unread characters 
 * 
 * ******************************************************************** */ 
 
void    Comm::IClear(void) 
{ 
 
__asm   cli                                 // stop interrupts 
i_get = i_put = i_buf;                      // reset buffer pointers 
i_count = 0;                                // ..and queue count 
__asm   sti                                 // ..and re-enable interrupts 
 
} 
 
 
 
 
/* ******************************************************************** ** 
 * 
 *    OClear -- clear output queue of unsent characters 
 * 
 * ******************************************************************** */ 
 
void    Comm::OClear(void) 
{ 
 
__asm   cli                                 // stop interrupts 
o_get = o_put = o_buf;                      // reset buffer pointers 
o_count = 0;                                // ..and queue count 
__asm   sti                                 // ..and re-enable interrupts 
 
} 
 
 
 
/* ******************************************************************** * 
 * 
 *  Queue -- put a character into the input queue 
 * 
 * ******************************************************************** */ 
 
void    Comm::Queue(int c,                  // character to store 
                   int m,                   // modem status register 
                   int l)                   // line status register 
{ 
 
if (flow && (ICount() >= i_stop) &&         // q. flow control needed? 
            NOT i_flow)                     // ..and not already on 
    RTS(0);                                 // a. yes .. clear RTS line 
 
switch (IFree())                            // based on avail queue space 
    { 
    case 0:                                 // full input queue 
        if (i_of)                           // q. in overflow? 
            {                               // a. yes .. count lost chars 
            if (*(UINT *) i_last < 65534U)  // q. within lost count range? 
                (*(UINT *) i_last)++;       // a. yes .. tally another one 
            } 
         else 
            { 
            i_of = 1;                       // set overflow flag 
            *(UINT *) i_last = 2;           // init counter to 2 
            i_last[2] = 0xff;               // ..and set error flag in lsr 
            } 
        break;                              // ..and return to caller 
 
    case 1:                                 // almost full 
        i_last = i_put;                     // save last saved addr 
        i_of = 0;                           // clear overflow flag 
 
    default:                                // from empty to almost full 
        i_count++;                          // count characters in queue 
        *i_put++ = c;                       // save character in queue 
        *i_put++ = m;                       // ..and modem status register 
        *i_put++ = l;                       // ..and line status register 
 
        if (i_put > i_limit)                // q. reach end of buffer? 
            i_put = i_buf;                  // a. yes .. set to beginning 
    } 
} 
 
 
 
/* ******************************************************************** * 
 * 
 *  Deque -- get a character from the output queue 
 * 
 * ******************************************************************** */ 
 
UINT    Comm::Deque(void) 
{ 
char    c;                                  // work character 
 
 
if (o_count == 0)                           // q. output queue empty? 
    return(-1);                             // a. yes .. rtn empty handed 
 
c = *o_get++;                               // get char from output queue 
o_count--;                                  // show character being removed 
 
if (o_get > o_limit)                        // q. reached end of buffer? 
    o_get = o_buf;                          // a. yes .. set to beginning 
 
return(c & 0xff);                           // ..and rtn with char to send 
 
} 
 
 
 
/* ******************************************************************** ** 
 * 
 *    ~Comm -- destructor 
 * 
 * ******************************************************************** */ 
 
Comm::~Comm(void) 
{ 
 
OUT(MCR, 0);                                // take down DTR and RTS 
DeInstallInt();                             // remove interrupt service 
delete i_buf;                               // free input 
delete o_buf;                               // ..and output queues 
 
} 
 
 
 
/* ******************************************************************** * 
 * 
 *  DTR -- cycle DTR modem signal 
 * 
 * ******************************************************************** */ 
 
void    Comm::DTR(long t)                   // time to hold DTR low 
{ 
 
OUT(MCR, MCR_DO & ~MCR_DTR);                // set off DTR control line 
wait_ms(t);                                 // wait a little bit 
OUT(MCR, MCR_DO);                           // ..and restore DTR 
 
} 
 
 
 
/* ******************************************************************** * 
 * 
 *  RTS -- control RTS modem signal 
 * 
 * ******************************************************************** */ 
 
void    Comm::RTS(int f)                    // RTS enable/disable flag 
{ 
 
OUT(MCR, MCR_DO - (f ? 0 : MCR_RTS));       // set mcr register 
i_flow = NOT f;                             // ..and set flow ctl'd flag 
 
} 
 
 
 
/* ******************************************************************** ** 
 * 
 *    IntRoutine -- communications port interrupt service routine 
 * 
 * ******************************************************************** */ 
 
void    Comm::IntRoutine(void) 
{ 
int     c,                                  // output character 
        cnt,                                // loop counter 
        first_cycle;                        // first cycle flag 
char    iir,                                // interrupt id register 
        lsr;                                // working line status reg 
 
 
while (((iir = IN(IIR)) & IIR_PEND) == 0)   // while there is work to do 
    { 
    switch (iir & IIR_II)                   // handle each interrupt 
        { 
        case IIR_MSI:                       // modem status interrupt 
            msr_changed = 1;                // set msr changed flag 
 
            if ((int_msr = IN(MSR)) &       // q. modem status register 
                        MSR_CTS)            // ..ready for transmits? 
                { 
                if (o_flow ||               // q. flow controlled? 
                        IN(LSR) & LSR_THRE) // ..or transmitter empty? 
                    { 
                    o_flow = 0;             // a. yes .. clear flag 
 
                    if ((c = Deque()) == -1)// q. output queue empty? 
                        empty_trans = 1;    // a. yes .. set flag 
                     else 
                        OUT(THR, c);        // else .. put out a character 
                    } 
                } 
            continue;                       // ..and check next interrupt 
 
        case IIR_LSI:                       // line status interrupt 
            int_lsr = IN(LSR);              // read line status register 
            continue;                       // ..and check again 
 
        case IIR_TBE:                       // transmitter buffer empty 
            cnt = fifo ? 15 : 1;            // set up output fifo size 
            first_cycle = 1;                // ..and first cycle flag 
 
            for (; cnt--;)                  // loop outputing characters 
                { 
                if (flow &&                 // q. flow control enabled 
                    NOT (IN(MSR) & MSR_CTS))// ..and receiver not ready? 
                    { 
                    o_flow = first_cycle;   // a. yes .. show flow ctl'd 
                                            // ..if nothing went out 
                    break;                  // ..and check next interrupt 
                    } 
 
                if ((c = Deque()) == -1)    // q. output queue empty? 
                    { 
                    empty_trans = 1;        // a. yes .. set flag 
                    break;                  // ..and exit this loop 
                    } 
 
                OUT(THR, c);                // put out another character 
                first_cycle = 0;            // ..and clear flag 
                } 
 
            continue;                       // ..and check again 
 
        case IIR_RBF:                       // receiver buffer full 
            while ((lsr = IN(LSR)) & LSR_DR)// while data is available 
                Queue(IN(RBR), int_msr,     // ..get and store 
                        lsr);               // ..in the input queue 
 
            continue;                       // check for next interrupt 
        } 
    } 
 
__asm   mov     al, EOI                     // al = end of interrupt cmd 
__asm   out     I8259, al                   // send EOI to int controller 
 
} 
 
 
 
/* ******************************************************************** * 
 * 
 *  comm_int() -- communications port interrupt service routine header 
 * 
 * ******************************************************************** */ 
 
#pragma option -O2-b-e                      // no global reg allocation 
                                            // ..or dead code elimination 
 
void    interrupt far comm_int(...) 
{ 
 
__asm   sti                                 // re-enable interrupts 
comm->IntRoutine();                         // use object's interrupt rtn 
 
}