_C Programming Column_
by Al Stevens

Listing One
// ------ serial.h
#ifndef SERIAL_H
#define SERIAL_H

const int maxports = 2;
// ------------ timer macros
#define timed_out(timer)         (timer==0)
#define set_timer(timer, secs)   timer=(secs)*182/10+1
#define disable_timer(timer)     timer = -1
#define timer_running(timer)     (timer > 0)
#define countdown(timer)         --timer
#define timer_disabled(timer)    (timer == -1)
#define TIMER  8
// ----------- won't need this next compiler version
typedef int bool;
const bool false = 0;
const bool true  = !false;
// ------- 8259 Programmable Interrupt Controllers
const int PIC01 = 0x21;
const int PIC00 = 0x20;
const int EOI   = 0x20; // End of Interrupt command
// --------------- line status register values
const int TBE = 0x20;
// ------------ modem control register values
const int DTR  = 1;
const int RTS  = 2;
const int OUT2 = 8;
// ------------ modem status register values
const int RLSD = 0x80;
const int DSR  = 0x20;
const int CTS  = 0x10;
// ----------- interrupt enable register signals
const int DataReady = 1;
// ----- serial port initialization parameter byte
union portinit   {
    struct {
        unsigned wordlen  : 2;
        unsigned stopbits : 1;
        unsigned parity   : 3;
        unsigned brk      : 1;
        unsigned divlatch : 1;
    } initbits;
    char initchar;
};
// ---- parameters for serial I/O
struct CommParameters   {
    int  parity;         // 0, 1, 2 = none, odd, even
    int  stopbits;       // 1 or 2
    int  databits;       // 7 or 8
    int  baud;           // 300, 600, etc.
    bool handshake;      // false = none, true = DSR/DTR
    int  buffersize;     // size of input buffer
    int  timeout;        // DTR timeout
};
// ---- the CommPort class
class CommPort {
    int portno;
    portinit initcom;
    static char* mp_recvbuff;
    static char* mp_nextin;
    static char* mp_nextout;
    static int buffersize;
    static int buffercount;
    static int refcount;
    CommParameters& commparms;
    CommPort* OutPort;
    static void interrupt newtimer(...);
    static void interrupt (*oldtimer)(...);
    static int serialtimer;

    int BasePort()
        { return (0x3f8-((portno)<<8)); }
    int TxData()
        { return BasePort(); }
    int RxData()
        { return BasePort(); }
    int DivLSB()
        { return BasePort(); }
    int DivMSB()
        { return BasePort()+1; }
    int IntEnable()
        { return BasePort()+1; }
    int IntIdent()
        { return BasePort()+2; }
    int LineCtl()
        { return BasePort()+3; }
    int ModemCtl()
        { return BasePort()+4; }
    int LineStatus()
        { return BasePort()+5; }
    int ModemStatus()
        { return BasePort()+6; }
    int irq()
        { return 4-(portno); }
    int vector()
        { return 12-(portno); }
    int ComIRQ()
        { return ~(1 << irq()); }
    void CommInterrupt();
    void Initialize();
    static CommPort* mp_CommPort[maxports];
    static void interrupt (*oldcomint[maxports])(...);
    static void interrupt newcomint1(...);
    static void interrupt newcomint2(...);
    static void interrupt (*newcomint[maxports])(...);
public:
    CommPort(int port, CommParameters& cp);
    ~CommPort();
    void Register(CommPort* pcom)
        { OutPort = pcom; }
    static char* nextchar();
};
#endif


Listing Two
// --------- serial.cpp
#include <dos.h>
#include "serial.h"

void interrupt (*CommPort::oldtimer)(...);
int CommPort::serialtimer = -1;

// ---- interrupt vector chain and for restoring vector on exit
void interrupt (*CommPort::oldcomint[maxports])(...);
// ---- pointers to CommPort objects
CommPort *CommPort::mp_CommPort[maxports];
// ---- input capture buffer
char* CommPort::mp_recvbuff;
char* CommPort::mp_nextin;
char* CommPort::mp_nextout;
int CommPort::buffersize;
int CommPort::buffercount;
int CommPort::refcount;
// ------ ISRs to count timer ticks
void interrupt CommPort::newtimer(...)
{
    (*oldtimer)();
    if (timer_running(serialtimer))
        countdown(serialtimer);
}
// ---- serial input interrupt service routines
void interrupt CommPort::newcomint1(...)
{
    mp_CommPort[0]->CommInterrupt();
}
void interrupt CommPort::newcomint2(...)
{
    mp_CommPort[1]->CommInterrupt();
}
// --- table of ISR addresses
void interrupt (*CommPort::newcomint[maxports])(...) = {
    CommPort::newcomint1,
    CommPort::newcomint2
};
// --- the object-based serial ISR
void CommPort::CommInterrupt()
{
    int c;
    bool timedout = false;
    // ---- send end of interrupt to the 8259
    outp(PIC00,EOI);
    if (mp_recvbuff != 0)   {
        if (mp_nextin == mp_recvbuff+buffersize)
            mp_nextin = mp_recvbuff;    // circular buffer
        c = inp(RxData());              // read the input
        // --- pass the input to the other port
        if (OutPort != 0)   {
            if (commparms.handshake)   {
                // --- test for hardware handshake
                int ms = OutPort->ModemStatus();
                if ((inp(ms) & DSR) == 0)   {
                    // --- other port has turned off DSR
                    int mc = inp(ModemCtl());
                    // --- turn off DTR for this port
                    outp(ModemCtl(), mc & ~DTR);
                    // --- wait
                    set_timer(serialtimer, 1); //commparms.timeout);
                    enable();
                    while ((inp(ms) & DSR) == 0)    {
                        if (timed_out(serialtimer))  {
                            timedout = true;      // timeout
                            break;
                        }
                    }
                    disable();
                    // --- turn DTR back on for this port
                    outp(ModemCtl(), mc | DTR);
                }
            }
            if (!timedout)  {
                set_timer(serialtimer, 2);
                int ls = OutPort->LineStatus();
                // ---- wait for transmitter buffer empty, other port
                enable();
                while ((inp(ls) & TBE) == 0)    {
                    if (timed_out(serialtimer)) {
                        timedout = true;
                        break;
                    }
                }
                disable();
                if (!timedout)  {
                    // --- pass the received byte to the other port
                    outp(OutPort->TxData(), (char)c);
                    disable_timer(serialtimer);
                }
            }
        }
        if (buffercount == buffersize/2)    {
            // ---- post the buffer overrun so dscope can report it
            *mp_nextin = (portno+1) | 0x80;
            *(mp_nextin+1) = 1;
        }
        else if (timedout)   {
            // --- post the timeout so dscope can report it
            *mp_nextin++ = (portno+1) | 0x80;
            *mp_nextin++ = 2;
            buffercount++;
        }
        else    {
            *mp_nextin++ = portno+1;   // put port in buffer for display
            *mp_nextin++ = (char) c;   // put char in buffer for display
            buffercount++;
        }
    }
}
// ---- construct a CommPort object
CommPort::CommPort(int port, CommParameters& cp) : commparms(cp)
{
    portno = port-1;
    buffersize = commparms.buffersize;
    // ensure that the buffer size is an even number
    buffersize++;
    buffersize &= 0xfffe;
    OutPort = 0;
    oldcomint[portno] = 0;
    if (portno >= 0 && portno < maxports)  {
        // --- set the address of this object for the ISR to use
        mp_CommPort[portno] = this;
        if (refcount == 0)
            mp_recvbuff = new char[buffersize];
    }
    // --- initialize the input buffer
    if (refcount == 0)  {
        mp_nextin = mp_nextout = mp_recvbuff;
        buffercount = 0;
    }
    // --- reference counter
    refcount++;
    Initialize();
}
// ---- destroy a CommPort object
CommPort::~CommPort()
{
    if (OutPort != 0)
        OutPort->Register(0); // de-register with the other port
    // --- is this the last of the CommPort objects to be destroyed?
    if (--refcount == 0)    {
        // --- yes, delete the capture buffer and initialize its pointers
        delete mp_recvbuff;
        mp_recvbuff = 0;
        mp_nextin = mp_nextout = 0;
        // --- unhook the timer interrupt
        if (oldtimer != 0)   {
            setvect(TIMER, oldtimer);
            oldtimer = 0;
        }
    }
    // --- unhook the serial interrupt
    if (oldcomint[portno] != 0)  {
        setvect(vector(), oldcomint[portno]);
        oldcomint[portno] = 0;
    }
}
// -------- initialize the com port
void CommPort::Initialize()
{
    // --- build the line control byte
    initcom.initbits.parity   = commparms.parity == 2 ? 3 : commparms.parity;
    initcom.initbits.stopbits = commparms.stopbits-1;
    initcom.initbits.wordlen  = commparms.databits-5;
    initcom.initbits.brk      = 0;
    initcom.initbits.divlatch = 1;
    outp(LineCtl(), initcom.initchar);
    // --- program the baud rate
    outp(DivLSB(), (char) ((115200L/commparms.baud) & 255));
    outp(DivMSB(), (char) ((115200L/commparms.baud) >> 8));
    initcom.initbits.divlatch = 0;
    outp(LineCtl(), initcom.initchar);
    // ---- intercept the timer interrupt vector
    if (oldtimer == 0)    {
        oldtimer = getvect(TIMER);
        setvect(TIMER, newtimer);
    }
    // ------ hook serial interrupt vector
    if (oldcomint[portno] == 0) {
        oldcomint[portno] = getvect(vector());
        setvect(vector(), newcomint[portno]);
    }
    // --- program the modem control register
    outp(ModemCtl(), (inp(ModemCtl()) | DTR | RTS | OUT2));
    // --- program the IRQ into the 8259
    outp(PIC01, (inp(PIC01) & ComIRQ()));
    // --- enable interrupts
    outp(IntEnable(), DataReady);
    // --- end of interrupt
    outp(PIC00, EOI);
    // ----- flush any old interrupts
    inp(RxData());
    inp(IntIdent());
    inp(LineStatus());
    inp(ModemStatus());
}
// ---- fetch the next port and character from the capture buffer
char* CommPort::nextchar()
{
    if (buffercount == 0)
        return 0;
    if (mp_nextout == mp_recvbuff+buffersize)
        mp_nextout = mp_recvbuff;
    mp_nextout += 2;
    --buffercount;
    return mp_nextout-2;
}

