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 far *old_comm)(void); // old comm interrupt pointer
};
extern
Comm *comm; // current Comm instance
void (interrupt far comm_int)(void); // comm interrupt routine
#pragma check_stack(off)
/* ******************************************************************** *
*
* 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 = _dos_getvect(irq + 8); // save old comm interrupt rtn
comm = this; // ..save instance address
_dos_setvect(irq + 8, comm_int); // ..establish new routine
mask = _inp(I8259M); // get current interrupt mask
mask &= ~(1 << irq); // determine new mask
_outp(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
_dos_setvect(irq + 8, old_comm); // re-establish old comm rtn
mask = _inp(I8259M); // get current interrupt mask
mask |= (1 << irq); // determine new mask
_outp(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
*
* ******************************************************************** */
void interrupt far comm_int(void)
{
__asm sti // re-enable interrupts
comm->IntRoutine(); // use object's interrupt rtn
}
#pragma check_stack(on)