please dont rip this site
Ken's computer

D:\PROJECTS\TIMEMON\MONITOR\MULTIPLEXERINTERFACE.CPP


// MultiplexerInterface.cpp: implementation of the MultiplexerInterface class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "monitor.h"
#include "MultiplexerInterface.h"
#include "monitorDoc.h"
#include "Settings.h"

#include <assert.h>


#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif



void TrimTrailingJunk(CString& str)
{
    while( (str.Right(1) == '\015') || (str.Right(1) == '\012') )
        str = str.Left(str.GetLength() - 1);
    str.TrimRight();
}


// **************************************************************************
// Checksum object

Checksum::Checksum()
{
    Clear();
}

Checksum::~Checksum()
{
}

void Checksum::Add(unsigned char c)
{
    plain_checksum ^= c;
    cyclic_checksum ^= c;
    cyclic_checksum += (unsigned short)c << 8;
    bool carry = (cyclic_checksum & 1) ? true : false;
    cyclic_checksum >>= 1;
    cyclic_checksum |= carry ? 0x8000 : 0;
    cyclic_checksum += 817;
}



// **************************************************************************
// MultiplexerInterface object
// and related functions

// ----------------------------------------
// Thread startup procedure for driver
UINT DriverThreadProc( LPVOID pParam )
{
    MultiplexerInterface* pObj = (MultiplexerInterface*)pParam;
    if (pObj == NULL)
        return -1;    // illegal parameter
    pObj->fDriverThreadRunning = true;
    pObj->vmessage("Main driver thread started");
    pObj->Driver();                     // The interface driver for the object
    pObj->vmessage("Main driver thread stopped");
    pObj->fDriverThreadRunning = false;
    return 0;    // thread completed successfully
}


// ----------------------------------------
// Thread startup procedure for output driver
UINT OutputDriverThreadProc( LPVOID pParam )
{
    MultiplexerInterface* pObj = (MultiplexerInterface*)pParam;
    if (pObj == NULL)
        return -1;    // illegal parameter
    pObj->fOutputDriverThreadRunning = true;
    pObj->vmessage("Output driver thread started");
    pObj->OutputDriver();                       // The interface driver for the object
    pObj->vmessage("Output driver thread stopped");
    pObj->fOutputDriverThreadRunning = false;
    return 0;    // thread completed successfully
}


//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

void MultiplexerInterface::GeneralInitializations()
{
    parent = NULL;
    parent_channel = 0;
    hCom = NULL;
    com_open = false;
    last_port_number = 1;
    LastError = 0;
    LastErrorContext = COM_ERRCONTEXT_NO_ERROR;
    fExitDriverThread = false;
    fDriverThreadRunning = false;
    fExitOutputDriverThread = false;
    fOutputDriverThreadRunning = false;
    suspend_activity = false;
    read_in_progress = false;
    write_in_progress = false;
    InitializeDriverState();
}

void MultiplexerInterface::InitializeDriverState()
{
    vmessage("Initializing buffers");
    packets = 0;
    bad_packet_count = 0;
    missing_packet_count = 0;
    prior_serial_initialized = false;
    character_count = 0;
    receiver_state = 0;
    crx_state = 0;
    crx_read_response_received = false;
    crx_write_response_received = false;
    transmitter_state = 0;
    transmit_channel = 0;
    transmit_serial = 0;
    fail_count1 = 0;
    fail_count2 = 0;
    fail_count3 = 0;
    error_flag = false;
    int i;
    for( i = 0; i < 8; i++ )
    {
        rbuf_enqueue_idx[i] = rbuf_dequeue_idx[i] = 0;
        tbuf_enqueue_idx[i] = tbuf_dequeue_idx[i] = 0;
        rbuf_overrun_count[i] = 0;
        cascaded[i] = NULL;     
    }
    outq_enqueue_idx = 0;
    outq_dequeue_idx = 0;
    outq_free = mux_interface_outqueuesize - 1;
    outq_free_outer_lock = false;
    outq_free_critical_section = false;
    outq_growing = false;
}


MultiplexerInterface::MultiplexerInterface(class CMonitorDoc* doc)
{
    MultiplexerInterface::doc = doc;
    GeneralInitializations();
}

MultiplexerInterface::MultiplexerInterface(MultiplexerInterface* parent, int parent_channel)
{
    GeneralInitializations();
    MultiplexerInterface::parent = parent;
    MultiplexerInterface::parent_channel = parent_channel;
}

MultiplexerInterface::~MultiplexerInterface()
{
    if(com_open)
    {
        vmessage("Setting COM break");
        SetCommBreak(hCom);                 // set BREAK to reset multiplexer (stops output)
        Sleep(200);
        vmessage("Clearing COM break");
        ClearCommBreak(hCom);
    }
    ClosePort();    // close port if it is open and kill the associated driver thread and child interface objects
    if(mux_interface == this)
        mux_interface = NULL;
}


void MultiplexerInterface::message(const char* sz, bool error, bool newline)
{
    if(doc)
        doc->message(sz, error, newline);
}

void MultiplexerInterface::tail_msg(const char* sz, bool error)
{
    if(doc)
        doc->tail_msg(sz, error);
}

void MultiplexerInterface::vmessage(const char* sz, bool error, bool newline)
{
    if(doc)
        doc->vmessage(sz, error, newline);
}

void MultiplexerInterface::vtail_msg(const char* sz, bool error)
{
    if(doc)
        doc->vtail_msg(sz, error);
}


// --------------------------------------------------------------------------------------
// Open a COM port and start associated driver thread
//
// The COM port provides the connection to the multiplexer hardware.
// (only valid for objects created via default constructor since the
// other constructor sets up a link to a cascaded multiplexer board).
//
// Returns true if successful.
// 
// If this function fails the GetLastError() method may be called
// to retrieve the operating-system supplied error code.
// GetLastErrorContext() may be called to retrieve a code describing
// which operating-system function failed.  GetLastErrorString()
// returns a formatted string describing what the error was and where
// it occurred.
bool MultiplexerInterface::OpenPort(int port_number, DWORD baud_rate)
{
    DCB dcb;
    CString port;

    ClosePort();        // close port incase a different port was previously opened
    last_port_number = port_number;
    port.Format("COM%i", port_number);
    vtail_msg("Opening operating-system handle to COM port");
    hCom = CreateFile(port,
        GENERIC_READ | GENERIC_WRITE,
        0,    /* comm devices must be opened w/exclusive-access */
        NULL, /* no security attrs */
        OPEN_EXISTING, /* comm devices must use OPEN_EXISTING */
        0,    /* not overlapped I/O */
        NULL  /* hTemplate must be NULL for comm devices */
        );

    LastErrorContext = OPENING_PORT;
    if (hCom == INVALID_HANDLE_VALUE) {
show_comm_err:
        LastError = ::GetLastError();
        LPVOID lpMsgBuf;
        FormatMessage( 
            FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,    NULL,
            LastError,
            MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
            (LPTSTR) &lpMsgBuf,    0,    NULL );// Display the string.
        LastErrorString.Format("Error %s %s:  %s", GetLastErrorContextString(), port, lpMsgBuf);
        TrimTrailingJunk(LastErrorString);
        LocalFree( lpMsgBuf );
        if(com_open) {
            CloseHandle(hCom);
            com_open = false;
        }
        return false;
    }
    vmessage("Opened operating-system handle to COM port");
    com_open = true;


    if( !SetupComm( hCom, 0X8000, 0X8000 ) )        // request 32k input buffer and 32k output buffer
    {
        LastErrorContext = SETTING_BUFSIZES;
        goto show_comm_err;
    }
 
    if( !GetCommState(hCom, &dcb) ) 
    {
        LastErrorContext = READING_DCB;
        goto show_comm_err;
    }

    bool high_baud_rate;
    switch(baud_rate)
    {
    case CBR_56000 :
    case CBR_115200 :
    case CBR_128000 :
    case CBR_256000 :
        high_baud_rate = true;
    default :
        high_baud_rate = false;     // baud rate not supported
    }

    dcb.BaudRate = baud_rate;
    dcb.ByteSize = 8;
    dcb.Parity = NOPARITY;
    dcb.StopBits = high_baud_rate ? TWOSTOPBITS : ONESTOPBIT;
    dcb.fBinary = 1;
    dcb.fParity = 0;
    dcb.fOutxCtsFlow = 0;
    dcb.fOutxDsrFlow = 0;
    dcb.fDtrControl = DTR_CONTROL_DISABLE;
    dcb.fDsrSensitivity = 0;
    dcb.fTXContinueOnXoff = 1;
    dcb.fOutX = 0;
    dcb.fInX = 0;
    dcb.fErrorChar = 0;
    dcb.fNull = 0;
    dcb.fRtsControl = RTS_CONTROL_DISABLE;
    dcb.fAbortOnError = 0;


    if( !SetCommState(hCom, &dcb) ) 
    {
        LastErrorContext = WRITING_DCB;
        goto show_comm_err;
    }
 

    // Set up timeouts so that read operations always return
    // immediately with whatever is buffered (even if it is 0 bytes)
    // and write operations don't time out.
    COMMTIMEOUTS cmto;
    cmto.ReadIntervalTimeout = MAXDWORD;
    cmto.ReadTotalTimeoutMultiplier = 0;
    cmto.ReadTotalTimeoutConstant = 1;
    cmto.WriteTotalTimeoutMultiplier = MAXDWORD;
    cmto.WriteTotalTimeoutConstant = MAXDWORD;


    if( !SetCommTimeouts(hCom, &cmto) )
    {
        LastErrorContext = WRITING_TIMEOUTS;
        goto show_comm_err;
    }
    // Start driver thread for COM port interface
    assert(!fDriverThreadRunning);
    suspend_activity = false;
    fExitDriverThread = false;
    AfxBeginThread(DriverThreadProc, this);
    return true;
}

// ----------------------------------------------------------------------------------
// Close the COM port if one is open ... also kill the associated driver thread.
// The driver thread will destroy all child interface objects when it exits (it must
// destroy them since it created them and no other thread can destroy them).
void MultiplexerInterface::ClosePort()
{
    if(fDriverThreadRunning)
        vtail_msg("Stopping driver threads prior to port close");
    fExitDriverThread = true;
    while(fDriverThreadRunning)
        Sleep(0);
    if(com_open)
    {
        vtail_msg("Closing operating-system handle to COM port");
        if( !CloseHandle(hCom) )
        {
            CString str;
            LPVOID lpMsgBuf;
            FormatMessage( 
                FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,    NULL,
                ::GetLastError(),
                MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
                (LPTSTR) &lpMsgBuf,    0,    NULL );// Display the string.
            str.Format("Error closing COM port:  %s", lpMsgBuf);
            TrimTrailingJunk(str);
            MessageBox(NULL, str, "CloseHandle()", MB_ICONEXCLAMATION);
            LocalFree( lpMsgBuf );
        }
        vmessage("Operating-system handle to COM port closed");
        com_open = false;
    }
}

const char* MultiplexerInterface::GetLastErrorContextString()
{
    switch(LastErrorContext) 
    {
    case COM_ERRCONTEXT_NO_ERROR : return "<<<NO ERROR>>>"; 
    case OPENING_PORT : return "opening";
    case SETTING_BUFSIZES : return "setting buffer sizes";
    case READING_DCB : return "reading settings for";
    case WRITING_DCB : return "changing settings for";
    case WRITING_TIMEOUTS : return "changing timeout settings for";
    case READING_DATA : return "reading data from";
    case WRITING_DATA : return "writing data to";
    default : return "<<<BAD ERROR CONTEXT CODE>>>";
    }
}








//****************************************************************************************
// Channel buffer status functions
//
// These functions return status information about the data buffers for each
// channel.  They are safe to call from any thread at any time.
//


// returns true if device at address is a multiplexer (and thus has subchannels)
bool MultiplexerInterface::IsCascaded(const char* address)
{
    if( address[1] == 0 )
    {
        unsigned addr = address[0] - '0';
        if((addr > 6) || !cascaded[addr])
            return false;
        return true;
    }
    else
    {
        // to do: resolve cascaded address
        return false;
    }
}

unsigned MultiplexerInterface::GetTxBufFree(const char* address)
{
    if( address[1] == 0 )
    {
        unsigned addr = address[0] - '0';
        int count;
        if(addr > 7)
        {
            message("Invalid Address in call to MultiplexerInterface::GetTxBufFree()", true);
            CString s;
            s.Format("Address specified = \"%s\"", address);
            message(s, true);
            return 0;
        }       
        count = tbuf_enqueue_idx[addr] - tbuf_dequeue_idx[addr];
        if(count < 0)
            count += mux_interface_txbufsize;
        return (mux_interface_txbufsize - 1) - count;
    }
    else
    {
        // to do: resolve cascaded address
        return 0;
    }
}

unsigned MultiplexerInterface::GetRxBufCount(const char* address)
{
    if( address[1] == 0 )
    {
        unsigned addr = address[0] - '0';
        int count;
        if(addr > 7)
        {
            message("Invalid Address in call to MultiplexerInterface::GetRxBufCount()", true);
            CString s;
            s.Format("Address specified = \"%s\"", address);
            message(s, true);
            return 0;
        }
        count = rbuf_enqueue_idx[addr] - rbuf_dequeue_idx[addr];
        if(count < 0)
            count += mux_interface_rxbufsize;
        return count;
    }
    else
    {
        // to do: resolve cascaded address
        return 0;
    }
}



//****************************************************************************************
// Channel buffer access functions
//
// These functions allow the receive buffers for each channel to be read and
// the transmit buffers for each channel to be written.  Although it is safe 
// for any one thread to access any channel's buffer via these functions it is
// not safe for more than one thread to access any one particular buffer.
//
// By convention, channels 0..6 should be accessed only by the worker thread 
// or higher-level driver threads if they are not cascaded.  Cascaded addresses
// should only be accessed by the MultiplexerInterface main driver thread.
//
// Channel 7's receive buffer should only be read by the MultiplexerInterface 
// main driver thread.  Channel 7's transmit buffer should only be written
// by the worker thread or higher-level driver threads.
//
// All receive buffers are written by the MultiplexerInterface main driver
// thread.  All transmit buffers are read by the MultiplexerInterface main
// driver thread.
//
unsigned MultiplexerInterface::WriteTxBuf(const char* address, void *buffer, unsigned count)
{
    if( address[1] == 0 )
    {
        unsigned addr = address[0] - '0';
        if(addr > 7)
        {
            message("Invalid Address in call to MultiplexerInterface::WriteTxBuf()", true);
            CString s;
            s.Format("Address specified = \"%s\"", address);
            message(s, true);
            return 0;
        }
// To do: convert to using memcpy() instead of PutTxChar()
// if performance is important
        unsigned i = 0;
        char *cp = (char*)buffer;
        while( (i < count) && PutTxChar(address, cp[i]) )
            i++;
        return i;
    }
    else
    {
        // to do: resolve cascaded address
        return 0;
    }   
}

unsigned MultiplexerInterface::ReadRxBuf(const char* address, void *buffer, unsigned count)
{
    if( address[1] == 0 )
    {
        unsigned addr = address[0] - '0';
        if(addr > 7)
        {
            message("Invalid Address in call to MultiplexerInterface::ReadRxBuf()", true);
            CString s;
            s.Format("Address specified = \"%s\"", address);
            message(s, true);
            return 0;
        }

// To do: convert to using memcpy() instead of GetRxChar()
// if performance is important
        unsigned i = 0;
        char *cp = (char*)buffer;
        while( (i < count) && GetRxChar(address, cp + i) )
            i++;
        return i;
    }
    else
    {
        // to do: resolve cascaded address
        return 0;
    }
}

bool MultiplexerInterface::PutTxChar(const char* address, char c)
{
    if( address[1] == 0 )
    {
        unsigned addr = address[0] - '0';
        unsigned ttei;
        if(addr > 7)
        {
            message("Invalid Address in call to MultiplexerInterface::PutTxChar()", true);
            CString s;
            s.Format("Address specified = \"%s\"", address);
            message(s, true);
            return false;
        }
        ttei = (tbuf_enqueue_idx[addr] + 1) & mux_interface_txbufmask;
        if(ttei == tbuf_dequeue_idx[addr])
            return false;
        tbuf[addr][tbuf_enqueue_idx[addr]] = c;
        tbuf_enqueue_idx[addr] = ttei;
        return true;
    }
    else
    {
        // to do: resolve cascaded address
        message("Cascaded Address in call to MultiplexerInterface::PutTxChar() is not yet supported", true);
        CString s;
        s.Format("Address specified = \"%s\"", address);
        message(s, true);
        return false;
    }
}

bool MultiplexerInterface::GetRxChar(const char* address, char *c)
{
    if( address[1] == 0 )
    {
        unsigned addr = address[0] - '0';
        if(addr > 7)
        {
            message("Invalid Address in call to MultiplexerInterface::GetRxChar()", true);
            CString s;
            s.Format("Address specified = \"%s\"", address);
            message(s, true);
            return false;
        }
        if(rbuf_dequeue_idx[addr] == rbuf_enqueue_idx[addr])
            return false;
        *c = rbuf[addr][rbuf_dequeue_idx[addr]];
        rbuf_dequeue_idx[addr]++;
        rbuf_dequeue_idx[addr] &= mux_interface_rxbufmask;
        return true;
    }
    else
    {
        // to do: resolve cascaded address
        return false;
    }   
}




//****************************************************************************************
// High-performance (direct access) channel buffer access functions
//
// These functions allow the caller to obtain pointers to the actual buffers
// and buffer enqueue and dequeue indexes so that the caller may write to
// transmit buffers and read from receive buffers directly (avoiding the
// call overhead of the higher-level access functions).
//
// The pointers returned by these functions are valid as long as the object
// is in scope.
//
// Although it is safe for any one thread to access any buffer, it is not safe
// for more than one thread to access any one particular transmit buffer.
// Receive buffers may be accessed by any number of threads.
//
// By convention, channels 0..6 should be accessed only by the worker thread 
// or higher-level driver threads if they are not cascaded.  Cascaded addresses
// should only be accessed by the MultiplexerInterface main driver thread.
//
// Channel 7's receive buffer should only be read by the MultiplexerInterface 
// main driver thread.  Channel 7's transmit buffer should only be written
// by the worker thread or higher-level driver threads.
//
// All receive buffers are written by the MultiplexerInterface main driver
// thread.  All transmit buffers are read by the MultiplexerInterface main
// driver thread.
//
// Note that the dequeue indexes for the receive buffers may not be obtained
// via these functions ... the Rx dequeue indexes are only for use by the
// higher-level buffer access functions.  The caller of these functions is
// expected to maintain its own independent dequeue index.  Two consequences
// of this scheme are:
//
//    1). Multiple sinks may be attached to each receive stream.  Each sink
//        will receive a copy of all of the data received and there is no
//        need for sinks to consume data synchronously (or for them to belong
//        to the same thread, for that matter).
//
//    2). Receive buffer overruns are not detected ... if a receive buffer
//        overrun ever occurs the result would be that the enqueue index
//        laps the dequeue index and thus one full buffer worth of data
//        would be lost.
//
const unsigned* MultiplexerInterface::GetRxEnqueueIdx(const char* address)
{
    if( address[1] == 0 )
    {
        unsigned addr = address[0] - '0';
        if(addr > 7)
        {
            message("Invalid Address in call to MultiplexerInterface::GetRxEnqueueIdx()", true);
            CString s;
            s.Format("Address specified = \"%s\"", address);
            message(s, true);
            return NULL;
        }
        return &(rbuf_enqueue_idx[addr]);
    }
    else
    {
        // to do: resolve cascaded address
        return NULL;
    }   
}

const char* MultiplexerInterface::GetRxBuf(const char* address)
{
    if( address[1] == 0 )
    {
        unsigned addr = address[0] - '0';
        if(addr > 7)
        {
            message("Invalid Address in call to MultiplexerInterface::GetRxBuf()", true);
            CString s;
            s.Format("Address specified = \"%s\"", address);
            message(s, true);
            return NULL;
        }
        return rbuf[addr];
    }
    else
    {
        // to do: resolve cascaded address
        return NULL;
    }   
}


const unsigned* MultiplexerInterface::GetTxDequeueIdx(const char* address)
{
    if( address[1] == 0 )
    {
        unsigned addr = address[0] - '0';
        if(addr > 7)
        {
            message("Invalid Address in call to MultiplexerInterface::GetTxDequeueIdx()", true);
            CString s;
            s.Format("Address specified = \"%s\"", address);
            message(s, true);
            return NULL;
        }
        return &(tbuf_dequeue_idx[addr]);
    }
    else
    {
        // to do: resolve cascaded address
        return NULL;
    }   
}

unsigned* MultiplexerInterface::GetTxEnqueueIdx(const char* address)
{
    if( address[1] == 0 )
    {
        unsigned addr = address[0] - '0';
        if(addr > 7)
        {
            message("Invalid Address in call to MultiplexerInterface::GetTxEnqueueIdx()", true);
            CString s;
            s.Format("Address specified = \"%s\"", address);
            message(s, true);
            return NULL;
        }
        return &(tbuf_enqueue_idx[addr]);
    }
    else
    {
        // to do: resolve cascaded address
        return NULL;
    }   
}

char* MultiplexerInterface::GetTxBuf(const char* address)
{
    if( address[1] == 0 )
    {
        unsigned addr = address[0] - '0';
        if(addr > 7)
        {
            message("Invalid Address in call to MultiplexerInterface::GetTxBuf()", true);
            CString s;
            s.Format("Address specified = \"%s\"", address);
            message(s, true);
            return NULL;
        }
        return tbuf[addr];
    }
    else
    {
        // to do: resolve cascaded address
        return NULL;
    }
}





//-------------------------------------------------------------------------------
// Sets the "break" condition on the specified port (NULL = PC's COM port)
// for 200ms and then clears the "break" condition and resets the port's
// baud rate to 2400.
//
// If a multiplexer is attached to the specified port then it will reset its
// baud rate to 2400 and reset its internal buffers and slave ports.  
//
bool MultiplexerInterface::Break(const char* address)
{
    if(address == NULL) {   // root -- this PC's COM port
        bool rval;
        vmessage("Suspending driver thread activity");
        suspend_activity = true;
        while( read_in_progress || write_in_progress )
            Sleep(0);
        Sleep(0);           // make sure other threads have really seen the suspend_activity flag
        while( read_in_progress || write_in_progress )
            Sleep(0);

        vmessage("Setting COM break");
        SetCommBreak(hCom);
        Sleep(200);
        vmessage("Clearing COM break");
        ClearCommBreak(hCom);
        vmessage("Re-opening port at 2400 baud");
        rval = OpenPort(last_port_number, CBR_2400);    // will close port if already opened and then re-open with new baud rate
        return rval;
    }
    if(address[1] == 0) {   // address terminates on this multiplexer
        unsigned addr = address[0] - '0';
        unsigned u;

        if(addr > 6)
        {
            message("Invalid Address in call to MultiplexerInterface::Break()", true);
            CString s;
            s.Format("Address specified = \"%s\"", address);
            message(s, true);
            return false;
        }

        if( !ReadByte(0X18, &u, address) )  // RCSTA -- save CREN bit
            return false;
        if( !WriteByte(0X18, 0, address) )  // RCSTA -- disable COM port
            return false;
        if( !WriteByte(0X07, 0, address) )  // PORTC -- set break condition
            return false;
        Sleep(200);
        if( !WriteWord(0X98, 0X8120, address) ) // TXSTA and SPBRG -- set 2400 baud
            return false;
        if( !WriteByte(0X18, u | 0X80, address) )   // RCSTA -- enable COM port
            return false;
        return true;
    }
    else {  // to do: resolve addresses on cascaded multiplexer
        return false;
    }
}


//------------------------------------------------------------------------------
// Change the baud rate for the specified port (NULL = PC's COM port)
//
// If the specified port is connected to a multiplexer (root or cascaded)
// then the following additional actions are taken:
//    1). A "break" is sent on the line to force the multiplexer to 2400 baud
//    2). Dummy packets are sent to assure that the multiplexer's receiver
//        is in-phase with the packet stream
//    3). The multiplexer is commanded to set its baud rate to the new setting
//    4). The port to which the multiplexer is connected is set to the new rate
//    5). More dummy packets are sent to assure correct receiver packet phase
bool MultiplexerInterface::SetBaudRate(DWORD baud_rate, const char* address)
{
// To do: resolve cascaded addresses

    unsigned char spbrg;
    unsigned char txsta = 0x20;
    CString s;
    Checksum c;
    char *brt;
    bool rval;
    unsigned u;


    switch(baud_rate)
    {
    case CBR_2400 :
        spbrg = 129;        //2404
        brt = "2400";
        break;
    case CBR_4800 :
        spbrg = 65;         //4735
        brt = "4800";
        break;
    case CBR_9600 :
        spbrg = 32;         //9470
        brt = "9600";
        break;
    case CBR_14400 :
        spbrg = 21;         //14205
        brt = "14400";
        break;
    case CBR_19200 :
        spbrg = 15;         //19531
        brt = "19200";
        break;
    case CBR_38400 :
        spbrg = 7;          //39062
        brt = "38400";
        break;


/*  These baud rates don't work -- the MCU's SCI module is too sensitive
    to baud rate errors when BRGH is set.

    case CBR_56000 :
//      txsta = 0X24;
        txsta = 0X65;       // 9 bit transmission ... 9th bit is high
        spbrg = 21;         //56818
        brt = "56000";
        break;
    case CBR_115200 :
        txsta = 0X65;
        spbrg = 10;         //113636
        brt = "115200";
        break;
    case CBR_128000 :
        txsta = 0X65;
        spbrg = 9;          //125000
        brt = "128000";
        break;
    case CBR_256000 :
        txsta = 0X65;
        spbrg = 4;          //250000
        brt = "256000";
        break;
*/
    default :
        message("Illegal baud rate in call to MultiplexerInterface::SetBaudRate()", true);
        return false;       // baud rate not supported
    }



    if(address == NULL) {   // root -- set baud rate of PC's COM port and master MCU

        s.Format("Setting baud rate to %s", brt);
        message(s);

/*
        if( doc && doc->thread_safe_settings )
        {
            s.Format("SPBRG tweak %i   orig spbrg %i   new spbrg %i", 
                doc->thread_safe_settings->spbrg_tweak, 
                spbrg, 
                spbrg + doc->thread_safe_settings->spbrg_tweak);
            message(s);
            spbrg += doc->thread_safe_settings->spbrg_tweak;
        }
*/


        Break(address);

        tail_msg("Establishing link...");
        vmessage("Sending pre-control packet dummy packets");
        PutTxChar("7", '.');        // send dummy packets to re-establish packet phase incase it was thrown off
        while((tbuf_enqueue_idx[7] != tbuf_dequeue_idx[7]) || GetTxBytesQueued() || outq_growing)
            Sleep(0);
        PutTxChar("7", '.');
        while((tbuf_enqueue_idx[7] != tbuf_dequeue_idx[7]) || GetTxBytesQueued() || outq_growing)
            Sleep(0);   
        PutTxChar("7", '.');
        while((tbuf_enqueue_idx[7] != tbuf_dequeue_idx[7]) || GetTxBytesQueued() || outq_growing)
            Sleep(0);   
        PutTxChar("7", '.');
        while((tbuf_enqueue_idx[7] != tbuf_dequeue_idx[7]) || GetTxBytesQueued() || outq_growing)
            Sleep(0);   
        PutTxChar("7", '.');
        while((tbuf_enqueue_idx[7] != tbuf_dequeue_idx[7]) || GetTxBytesQueued() || outq_growing)
            Sleep(0);
    
        vmessage("Sending TXSTA and SPBRG control packet");
        PutTxChar("7", '@');
        PutTxChar("7", 'B');
        PutTxChar("7", txsta ^ 0X6C);
        c.Add(txsta);
        PutTxChar("7", spbrg ^ 0X35);
        c.Add(spbrg);
        PutTxChar("7", c.plain_checksum);
        PutTxChar("7", (char)(c.cyclic_checksum));
        PutTxChar("7", (char)(c.cyclic_checksum >> 8));

        while((tbuf_enqueue_idx[7] != tbuf_dequeue_idx[7]) || GetTxBytesQueued() || outq_growing)
            Sleep(0);

        Sleep(100);

        s.Format("Re-opening port at %s baud", brt);
        vmessage(s);

        rval = OpenPort(last_port_number, baud_rate);   // will close port if already opened and then re-open with new baud rate

        vmessage("Sending post-reopen dummy packets");
        PutTxChar("7", '.');        // send dummy packets to re-establish packet phase incase it was thrown off
        while((tbuf_enqueue_idx[7] != tbuf_dequeue_idx[7]) || GetTxBytesQueued() || outq_growing)
            Sleep(0);
        PutTxChar("7", '.');
        while((tbuf_enqueue_idx[7] != tbuf_dequeue_idx[7]) || GetTxBytesQueued() || outq_growing)
            Sleep(0);   
        PutTxChar("7", '.');
        while((tbuf_enqueue_idx[7] != tbuf_dequeue_idx[7]) || GetTxBytesQueued() || outq_growing)
            Sleep(0);   
        PutTxChar("7", '.');
        while((tbuf_enqueue_idx[7] != tbuf_dequeue_idx[7]) || GetTxBytesQueued() || outq_growing)
            Sleep(0);   
        PutTxChar("7", '.');
        while((tbuf_enqueue_idx[7] != tbuf_dequeue_idx[7]) || GetTxBytesQueued() || outq_growing)
            Sleep(0);

        vmessage("Finished resetting baud rate");
        return rval;
    }
    else {  // not master port ... set baud rate of a slave port
        if( IsCascaded(address) ) {
            Break(address);
            s.Format("%s7", address);   // address of control channel on multiplexer chained off of given address

            tail_msg("Establishing cascaded link...");
            vmessage("Pre baud rate change dummy register read");
            if(!ReadByte(0X20, &u, s, 6)) { // dummy read from master MCU attached to slave MCU at <address> .. retries up to 6 times so that packet phase will be re-established
cascaded_relink_failed:
                message("Failed to relink with cascaded multiplexer", true);
                return false;
            }
    
            vmessage("Sending TXSTA and SPBRG control packet");
            PutTxChar(s, '@');
            PutTxChar(s, 'B');
            PutTxChar(s, txsta ^ 0X6C);
            c.Add(txsta);
            PutTxChar(s, spbrg ^ 0X35);
            c.Add(spbrg);
            PutTxChar(s, c.plain_checksum);
            PutTxChar(s, (char)(c.cyclic_checksum));
            PutTxChar(s, (char)(c.cyclic_checksum >> 8));

            Sleep(500);     // wait long enough for packet to definitely reach its destination (there is no Ack)
        }

        s.Format("Changing slave port baud rate to %s", brt);
        vmessage(s);

        if(!WriteWord(0X98, ((unsigned)spbrg << 8) | (unsigned)txsta, address)) {
            message("Failed to set slave port baud rate", true);
            return false;
        }
            
        if( IsCascaded(address) ) {
            vmessage("Post baud rate change dummy register read");
            if(!ReadByte(0X20, &u, s, 6))   // dummy read from master MCU attached to slave MCU at <address> .. retries up to 6 times so that packet phase will be re-established
                goto cascaded_relink_failed;
        }
        return true;
    }
}



//-------------------------------------------------------------------------
// Enables reception (not valid for root .. root is always enabled)
//
// Returns true if successful
bool MultiplexerInterface::EnableInput(const char* address)
{
    if(address[1] == 0) {   // address terminates on this multiplexer
        unsigned addr = address[0] - '0';

        if(addr > 6)
        {
            message("Invalid Address in call to MultiplexerInterface::EnableInput()", true);
            CString s;
            s.Format("Address specified = \"%s\"", address);
            message(s, true);
            return false;
        }
        return WriteByte(0X18, 0X90, address);  // RCSTA
    }
    else {  // to do: resolve addresses on cascaded multiplexer
        return false;
    }
}

//-------------------------------------------------------------------------
// Disables reception (not valid for root .. root is always enabled)
//
// Returns true if successful
bool MultiplexerInterface::DisableInput(const char* address)
{
    if(address[1] == 0) {   // address terminates on this multiplexer
        unsigned addr = address[0] - '0';

        if(addr > 6)
        {
            message("Invalid Address in call to MultiplexerInterface::EnableInput()", true);
            CString s;
            s.Format("Address specified = \"%s\"", address);
            message(s, true);
            return false;
        }
        return WriteByte(0X18, 0X80, address);  // RCSTA
    }
    else {  // to do: resolve addresses on cascaded multiplexer
        return false;
    }
}



//****************************************************************************************
// MCU interface functions
//
// These functions allow registers in the MCUs to be read and written.
// They all operate by sending command packets to the master MCU's control
// channel and then waiting for a response to be recieved by the main
// driver thread ( see ProcessCommandRx() ).
//
// The last two parameters of all of these functions specify the nubmer of
// times to atempt the operation and the number of timer tics (100ms per)
// to wait before giving up on each attempt.  Both of these parametrs have
// default values (thus may be omitted).
//
// These functions may only be called by the worker thread or higher-level
// interface threads.  The MultiplexerInterface driver threads and the main 
// user-interface thread (responsible for timer) must both be running.
//
// These functions may only be called by one thread at a time (only one MCU
// interface function may be active at any given moment).
//
// The return values are true if successful.
//
bool MultiplexerInterface::WriteByte(unsigned register_address, unsigned value, const char* address, unsigned try_count, unsigned timeout)
{
// To do: resolve addresses longer than 1 char
    do {
        Checksum check;
        char device_address;
        char addrlen;

        device_address = address[0] - '0';
        addrlen = device_address << 5;

        PutTxChar("7", '@');
        PutTxChar("7", 'W');
        PutTxChar("7", addrlen);
        check.Add(addrlen);
        PutTxChar("7", (char)register_address);
        check.Add((char)register_address);
        PutTxChar("7", (char)value);
        check.Add((char)value);

        reg_xfer_xs = check.plain_checksum;
        reg_xfer_cl = (char)(check.cyclic_checksum);
        reg_xfer_ch = (char)(check.cyclic_checksum >> 8);
        crx_write_response_received = false;

        PutTxChar("7", reg_xfer_xs);
        PutTxChar("7", reg_xfer_cl);
        PutTxChar("7", reg_xfer_ch);


        assert(doc);
        doc->countdown_timer = timeout;
        while(!crx_write_response_received && doc->countdown_timer)
            Sleep(0);   // wait for other thread to send request and receive and interpret response

        if(!crx_write_response_received)
            vmessage("WriteByte() timed out");
        else
            return true;
    } while(--try_count);
    vmessage("WriteByte() failed", true);
    return false;
}

bool MultiplexerInterface::WriteWord(unsigned register_address, unsigned value, const char* address, unsigned try_count, unsigned timeout)
{
// To do: resolve addresses longer than 1 char
    do {
        Checksum check;
        char device_address;
        char addrlen;

        device_address = address[0] - '0';
        addrlen = (device_address << 5) | 1;

        PutTxChar("7", '@');
        PutTxChar("7", 'W');
        PutTxChar("7", addrlen);
        check.Add(addrlen);
        PutTxChar("7", (char)register_address);
        check.Add((char)register_address);
        PutTxChar("7", (char)value);
        check.Add((char)value);
        PutTxChar("7", (char)(value >> 8));
        check.Add((char)(value >> 8));

        reg_xfer_xs = check.plain_checksum;
        reg_xfer_cl = (char)(check.cyclic_checksum);
        reg_xfer_ch = (char)(check.cyclic_checksum >> 8);
        crx_write_response_received = false;

        PutTxChar("7", reg_xfer_xs);
        PutTxChar("7", reg_xfer_cl);
        PutTxChar("7", reg_xfer_ch);


        assert(doc);
        doc->countdown_timer = timeout;
        while(!crx_write_response_received && doc->countdown_timer)
            Sleep(0);   // wait for other thread to send request and receive and interpret response

        if(!crx_write_response_received)
            vmessage("WriteWord() timed out");
        else
            return true;
    } while(--try_count);
    vmessage("WriteWord() failed", true);
    return false;
}

bool MultiplexerInterface::WriteTriplet(unsigned register_address, unsigned value, const char* address, unsigned try_count, unsigned timeout)
{
// To do: resolve addresses longer than 1 char
    do {
        Checksum check;
        char device_address;
        char addrlen;

        device_address = address[0] - '0';
        addrlen = (device_address << 5) | 2;

        PutTxChar("7", '@');
        PutTxChar("7", 'W');
        PutTxChar("7", addrlen);
        check.Add(addrlen);
        PutTxChar("7", (char)register_address);
        check.Add((char)register_address);
        PutTxChar("7", (char)value);
        check.Add((char)value);
        PutTxChar("7", (char)(value >> 8));
        check.Add((char)(value >> 8));
        PutTxChar("7", (char)(value >> 16));
        check.Add((char)(value >> 16));

        reg_xfer_xs = check.plain_checksum;
        reg_xfer_cl = (char)(check.cyclic_checksum);
        reg_xfer_ch = (char)(check.cyclic_checksum >> 8);
        crx_write_response_received = false;

        PutTxChar("7", reg_xfer_xs);
        PutTxChar("7", reg_xfer_cl);
        PutTxChar("7", reg_xfer_ch);


        assert(doc);
        doc->countdown_timer = timeout;
        while(!crx_write_response_received && doc->countdown_timer)
            Sleep(0);   // wait for other thread to send request and receive and interpret response

        if(!crx_write_response_received)
            vmessage("WriteTriplet() timed out");
        else
            return true;
    } while(--try_count);
    vmessage("WriteTriplet() failed", true);
    return false;
}

bool MultiplexerInterface::WriteDWord(unsigned register_address, unsigned value, const char* address, unsigned try_count, unsigned timeout)
{
// To do: resolve addresses longer than 1 char
    do {
        Checksum check;
        char device_address;
        char addrlen;

        device_address = address[0] - '0';
        addrlen = (device_address << 5) | 3;

        PutTxChar("7", '@');
        PutTxChar("7", 'W');
        PutTxChar("7", addrlen);
        check.Add(addrlen);
        PutTxChar("7", (char)register_address);
        check.Add((char)register_address);
        PutTxChar("7", (char)value);
        check.Add((char)value);
        PutTxChar("7", (char)(value >> 8));
        check.Add((char)(value >> 8));
        PutTxChar("7", (char)(value >> 16));
        check.Add((char)(value >> 16));
        PutTxChar("7", (char)(value >> 24));
        check.Add((char)(value >> 24));

        reg_xfer_xs = check.plain_checksum;
        reg_xfer_cl = (char)(check.cyclic_checksum);
        reg_xfer_ch = (char)(check.cyclic_checksum >> 8);
        crx_write_response_received = false;

        PutTxChar("7", reg_xfer_xs);
        PutTxChar("7", reg_xfer_cl);
        PutTxChar("7", reg_xfer_ch);


        assert(doc);
        doc->countdown_timer = timeout;
        while(!crx_write_response_received && doc->countdown_timer)
            Sleep(0);   // wait for other thread to send request and receive and interpret response

        if(!crx_write_response_received)
            vmessage("WriteDWord() timed out");
        else
            return true;
    } while(--try_count);
    vmessage("WriteDWord() failed", true);
    return false;
}

bool MultiplexerInterface::ReadByte(unsigned register_address, unsigned* value, const char* address, unsigned try_count, unsigned timeout)
{
// To do: resolve addresses longer than 1 char
    do {
        Checksum check;
        char device_address;
        char addrlen;

        device_address = address[0] - '0';
        addrlen = device_address << 5;

        PutTxChar("7", '@');
        PutTxChar("7", 'R');
        PutTxChar("7", addrlen);
        check.Add(addrlen);
        PutTxChar("7", (char)register_address);
        check.Add((char)register_address);

        reg_xfer_length = 1;
        reg_xfer_xs = check.plain_checksum;
        reg_xfer_cl = (char)(check.cyclic_checksum);
        reg_xfer_ch = (char)(check.cyclic_checksum >> 8);
        crx_read_response_received = false;

        PutTxChar("7", reg_xfer_xs);
        PutTxChar("7", reg_xfer_cl);
        PutTxChar("7", reg_xfer_ch);


        assert(doc);
        doc->countdown_timer = timeout;
        while(!crx_read_response_received && doc->countdown_timer)
            Sleep(0);   // wait for other thread to send request and receive and interpret response

        if(crx_read_response_received)
        {
            *value = reg_xfer >> 24;
            return true;
        }
        vmessage("ReadByte() timed out");
    } while(--try_count);
    vmessage("ReadByte() failed", true);
    return false;
}


bool MultiplexerInterface::ReadWord(unsigned register_address, unsigned* value, const char* address, unsigned try_count, unsigned timeout)
{
// To do: resolve addresses longer than 1 char
    do {
        Checksum check;
        char device_address;
        char addrlen;

        device_address = address[0] - '0';
        addrlen = (device_address << 5) | 1;

        PutTxChar("7", '@');
        PutTxChar("7", 'R');
        PutTxChar("7", addrlen);
        check.Add(addrlen);
        PutTxChar("7", (char)register_address);
        check.Add((char)register_address);

        reg_xfer_length = 2;
        reg_xfer_xs = check.plain_checksum;
        reg_xfer_cl = (char)(check.cyclic_checksum);
        reg_xfer_ch = (char)(check.cyclic_checksum >> 8);
        crx_read_response_received = false;

        PutTxChar("7", reg_xfer_xs);
        PutTxChar("7", reg_xfer_cl);
        PutTxChar("7", reg_xfer_ch);


        assert(doc);
        doc->countdown_timer = timeout;
        while(!crx_read_response_received && doc->countdown_timer)
            Sleep(0);   // wait for other thread to send request and receive and interpret response

        if(crx_read_response_received)
        {
            *value = reg_xfer >> 16;
            return true;
        }
        vmessage("ReadWord() timed out");
    } while(--try_count);
    vmessage("ReadWord() failed", true);
    return false;
}

bool MultiplexerInterface::ReadTriplet(unsigned register_address, unsigned* value, const char* address, unsigned try_count, unsigned timeout)
{
// To do: resolve addresses longer than 1 char
    do {
        Checksum check;
        char device_address;
        char addrlen;

        device_address = address[0] - '0';
        addrlen = (device_address << 5) | 2;

        PutTxChar("7", '@');
        PutTxChar("7", 'R');
        PutTxChar("7", addrlen);
        check.Add(addrlen);
        PutTxChar("7", (char)register_address);
        check.Add((char)register_address);

        reg_xfer_length = 3;
        reg_xfer_xs = check.plain_checksum;
        reg_xfer_cl = (char)(check.cyclic_checksum);
        reg_xfer_ch = (char)(check.cyclic_checksum >> 8);
        crx_read_response_received = false;

        PutTxChar("7", reg_xfer_xs);
        PutTxChar("7", reg_xfer_cl);
        PutTxChar("7", reg_xfer_ch);


        assert(doc);
        doc->countdown_timer = timeout;
        while(!crx_read_response_received && doc->countdown_timer)
            Sleep(0);   // wait for other thread to send request and receive and interpret response

        if(crx_read_response_received)
        {
            *value = reg_xfer >> 8;
            return true;
        }
        vmessage("ReadTriplet() timed out");
    } while(--try_count);
    vmessage("ReadTriplet() failed", true);
    return false;
}

bool MultiplexerInterface::ReadDWord(unsigned register_address, unsigned* value, const char* address, unsigned try_count, unsigned timeout)
{
// To do: resolve addresses longer than 1 char

    do {
        Checksum check;
        char device_address;
        char addrlen;

        device_address = address[0] - '0';
        addrlen = (device_address << 5) | 3;

        PutTxChar("7", '@');
        PutTxChar("7", 'R');
        PutTxChar("7", addrlen);
        check.Add(addrlen);
        PutTxChar("7", (char)register_address);
        check.Add((char)register_address);

        reg_xfer_length = 4;
        reg_xfer_xs = check.plain_checksum;
        reg_xfer_cl = (char)(check.cyclic_checksum);
        reg_xfer_ch = (char)(check.cyclic_checksum >> 8);
        crx_read_response_received = false;

        PutTxChar("7", reg_xfer_xs);
        PutTxChar("7", reg_xfer_cl);
        PutTxChar("7", reg_xfer_ch);


        assert(doc);
        doc->countdown_timer = timeout;
        while(!crx_read_response_received && doc->countdown_timer)
            Sleep(0);   // wait for other thread to send request and receive and interpret response

        if(crx_read_response_received)
        {
            *value = reg_xfer;
            return true;
        }
        vmessage("ReadDWord() timed out");
    } while(--try_count);
    vmessage("ReadDWord() failed", true);
    return false;
}






//*******************************************************************************
// Communications interface driver
//
// The following procedures are responsible for implementing the packet
// transport protocol.  There are 2 threads associated with the driver
// functions -- a "main driver" thread and an "output driver" thread.
//
// The main driver thread is responsible for receiving input from the
// COM port, encoding and decoding packets, and moving data in and out
// of the channel buffers.
//
// The output driver thread is responsible for moving data form the
// output queue to the COM port.  A seperate thread was needed for this
// function because the Windows COM port interface functions only provide
// 2 modes of operation -- "synchronous" and "asynchronous".  These
// driver functions need to read data synchronously (read all available
// data and not wait or keep a pending operation running) but write
// data asynchronously.  The 2nd thread allows the writing of data to
// be asynchronous as far as the main driver thread is concerned.
//
//
// These driver functions implement a layer between the COM port and the
// channel buffers.  Other threads simply write the transmit channel buffers
// and read the receive channel buffers (with the exception of the
// channel 7 buffers).
//
// The channel 7 buffers are connected to the master MCU's control channel
// and thus have special functionality.  Data arriving in the channel 7
// receive buffer is further processed by these driver functions ... 
// control packet contents are extracted and made availible in the reg_xfer_
// data members of the MultiplexerInterface object.  A set of mid-level
// MultiplexerInterface methods use channel 7 and the reg_xfer_ members
// to facilitate reading and writing MCU registers.
//




// ------------------------------------------------------------------------------
// Main driver thread procedure
//
// Only the MultiplexerInterface object which owns the COM port owns a driver
// thread.  Cascaded MultiplexerInterface objects have their ChildTimeSlice()
// procedures called by their parents.
//
// This procedure is responsible for calling the TimeSlice() procedure and
// relinquishing unneeded thread time.  It is also responsible for cleaning up 
// and exiting the thread if fExitDriverThread is set.
void MultiplexerInterface::Driver()
{
    InitializeDriverState();

    if(!fOutputDriverThreadRunning) {
        fExitOutputDriverThread = false;
        TRACE("Starting output driver thread\n");
        AfxBeginThread(OutputDriverThreadProc, this);
    }

    while( !fExitDriverThread )
    {
        if( !TimeSlice() && !fExitDriverThread )
// timeslice and all child timeslices emptied their input buffers on the last go-around ...
// suspend thread for 50ms ... enough time for about 190 characters to arrive at 38.4 kbaud
            Sleep(50);
    }

    fExitOutputDriverThread = true;
    TRACE("Stopping output driver thread\n");
    while(fOutputDriverThreadRunning)
        Sleep(0);

    for( int i = 0; i < 8; i++ ) {
        if( cascaded[i] ) {
            delete cascaded[i];
            cascaded[i] = NULL;
        }
    }
}


// ------------------------------------------------------------------------------
// Output Driver thread procedure
//
// Only the MultiplexerInterface object which owns the COM port owns an output 
// driver thread.
//
// This procedure is responsible for calling the ODTimeSlice() procedure and
// relinquishing unneeded thread time.  It is also responsible for cleaning up 
// and exiting the thread if fExitDriverThread is set.
void MultiplexerInterface::OutputDriver()
{
//  LastErrorString.Format("Output driver thread started");
//  error_flag = true;

    TRACE("Output driver thread started\n");
    while( !fExitOutputDriverThread )
    {
        if( !ODTimeSlice() && !fExitOutputDriverThread )
// output buffer emptied on last iteration...
// suspend thread for 50ms ... enough time for about 190 characters to arrive at 38.4 kbaud
            Sleep(50);
    }
    TRACE("Output driver thread exiting\n");
}



// ------------------------------------------------------------------------------
// Perform driver/interface duties for one timeslice (timeslice is relenquished
// when this function returns).  This is the root controller's driver function
// which interfaces to the COM port.
//
// Accepts input and spools out output.  Packages and unpackages data to/from
// routing packets.  Gives thread time to all its children.
//
// This function processes 1024 input characters at most before returning so
// that any pending events (i.e. fExitDriverThread) don't wait too long.
//
// Returns true if the input buffer still contains more than 1024 bytes worth
// of data (and thus another TimeSlice is needed right away).
//
// This function is called by the main driver thread.
bool MultiplexerInterface::TimeSlice()
{
    char szBuf[1024];
    DWORD bytes_read;
    int i;

    if(suspend_activity)
        return false;

// Read as much as is available or 1024 bytes, whichever is less, from the 
// operating system's COM port input buffer

    read_in_progress = true;
    if(!suspend_activity)
    {
        if( !ReadFile( hCom, szBuf, sizeof(szBuf), &bytes_read, NULL ) )
        {
            LastError = ::GetLastError();
            LastErrorContext = READING_DATA;

            LPVOID lpMsgBuf;
            FormatMessage( 
                FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,    NULL,
                ::GetLastError(),
                MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),                      // Default language
                (LPTSTR) &lpMsgBuf,    0,    NULL );                            // Display the string.
            LastErrorString.Format("Error reading from COM port: %s", lpMsgBuf);
            TrimTrailingJunk(LastErrorString);
            LocalFree( lpMsgBuf );
            error_flag = true;
        }
        read_in_progress = false;

// Process the data received 
// (decode packets and route data to specified channels' receive buffers)
        if( bytes_read != 0 )
            AcceptInput(szBuf, bytes_read);

    } // if(!suspend_activity)
    read_in_progress = false;


// Give time to child MultiplexerInterface objects
    for( i = 0; i < 8; i++ ) {
        if( cascaded[i] ) 
            cascaded[i]->ChildTimeSlice();
    }

// Process queued data from one channel's transmit buffer
// (encode a packet and put it in the output queue)
    i = 8;
    bool sent_data = false;
    do {
        if( tbuf_enqueue_idx[transmit_channel] != tbuf_dequeue_idx[transmit_channel] ) {    // have any data to send?
            unsigned starting_outq_free = outq_free;
            unsigned updated_outq_free = starting_outq_free;    // pass this to EncodeOutput so that semaphores don't need to be checked for every byte written
            outq_growing = true;
            EncodeOutput(transmit_channel, outq, &outq_enqueue_idx, &updated_outq_free, mux_interface_outqueuemask);
            sent_data = true;
            starting_outq_free -= updated_outq_free;    // number of bytes added to queue

            while(outq_free_outer_lock || outq_free_critical_section)
                Sleep(0);
            outq_free_outer_lock = true;
            while(outq_free_critical_section)
                Sleep(0);
            outq_free_critical_section = true;
            outq_free -= starting_outq_free;
            outq_free_critical_section = false;
            outq_free_outer_lock = false;
            outq_growing = false;

        }
        transmit_channel++;
        transmit_channel &= 7;
    } while( --i && !sent_data );
    
    if(rbuf_enqueue_idx[7] != rbuf_dequeue_idx[7])
        ProcessCommandRx();     // interpret data from command interpreter channel

    return (bytes_read == sizeof(szBuf)) ? true : false;
}

//---------------------------------------------------------------
// Process data received from the control channel (channel 7)
//
// Decodes control packets.
// This function is called by the main driver thread.
void MultiplexerInterface::ProcessCommandRx()
{
    unsigned char c;
    while( rbuf_dequeue_idx[7] != rbuf_enqueue_idx[7] )
    {
        c = rbuf[7][rbuf_dequeue_idx[7]];
        rbuf_dequeue_idx[7]++;
        rbuf_dequeue_idx[7] &= mux_interface_rxbufmask;

//      CString str;
//      str.Format("CRX %.2X   state %i", c & 0XFF, crx_state);
//      message(str);


        switch(crx_state)
        {
        case 0 :
            crx_state = (c == '@') ? 1 : 0;
            break;
        case 1 :
            switch(c)
            {
            case 'r' :
                crx_state = 2;
                break;
            case 'w' :
                crx_state = 12;
                break;
            default :
                crx_state = 0;
                break;
            }
            break;
        case 2 :
            crx_state = (c == reg_xfer_xs) ? 3 : 0;
            break;
        case 3 :
            crx_state = (c == reg_xfer_cl) ? 4 : 0;
            break;
        case 4 :
            crx_state = (c == reg_xfer_ch) ? (9 - reg_xfer_length) : 0;
            crx_check.Clear();
            reg_xfer = 0;
            break;
        case 5 :
            reg_xfer |= (unsigned)c;
            crx_check.Add(c);
            crx_state++;
            break;
        case 6 :
            reg_xfer |= (unsigned)c << 8;
            crx_check.Add(c);
            crx_state++;
            break;
        case 7 :
            reg_xfer |= (unsigned)c << 16;
            crx_check.Add(c);
            crx_state++;
            break;
        case 8 :
            reg_xfer |= (unsigned)c << 24;
            crx_check.Add(c);
            crx_state++;
            break;
        case 9 :
            crx_state = (c == crx_check.plain_checksum) ? 10 : 0;
            break;
        case 10 :
            crx_state = (c == (unsigned char)(crx_check.cyclic_checksum)) ? 11 : 0;
            break;
        case 11 :
            if(c == (unsigned char)(crx_check.cyclic_checksum >> 8))
                crx_read_response_received = true;
            crx_state = 0;
            break;



        case 12 :
            crx_state = (c == reg_xfer_xs) ? 13 : 0;
            break;
        case 13 :
            crx_state = (c == reg_xfer_cl) ? 14 : 0;
            break;
        case 14 :
            if(c == reg_xfer_ch) 
                crx_write_response_received = true;
            crx_state = 0;
            break;
        }
        if( crx_state == 0 )
            crx_state = (c == '@') ? 1 : 0;
    }
}


//----------------------------------------------------------------------------
// Output driver timeslice
//
// Writes data from outq to the COM port ... runs in its own thread since the
// operating-system's COM write does not return until all bytes have been sent.
// Writes and reads are thus overlapped.
//
// Returns false if outq was empty (and thus the thread can sleep)
// Returns true if data was written (and thus another timeslice is needed)
//
// This function is called by the output driver thread procedure.
bool MultiplexerInterface::ODTimeSlice()
{
    if(suspend_activity)
        return false;

// If there is anything in the output queue then write as much of it as possible 
// to the operating system's COM port output buffer
    write_in_progress = true;
    if( (outq_free < (mux_interface_outqueuesize - 1)) && !suspend_activity )
    {
        // The write will need to be split into 2 parts if the dequeue index would
        // wrap around to the buffer start on its way to catch up with the enqueue 
        // index.  Calculate the particulars of each write necessary.

        char* block1_ptr = outq + outq_dequeue_idx;     
            // pointer to first chunk of data to write

        unsigned bytes_queued = (mux_interface_outqueuesize - 1) - outq_free;

        unsigned v_endidx = outq_dequeue_idx + bytes_queued;
            // virtual end index -- where the enqueue index would be if it didn't wrap around at the end of the buffer

        unsigned block1_endidx = (v_endidx > mux_interface_outqueuesize) ? mux_interface_outqueuesize : v_endidx;
            // where block1 actually ends (taking the end of the buffer into account)

        unsigned block1_size = block1_endidx - outq_dequeue_idx;

        unsigned block2_size = bytes_queued - block1_size;
            // block2 starts at the beginning of the buffer and will normally be null

        DWORD bytes_written;

        if(!com_open) {     // if port not open then just discard the data so other processes don't hang
            bytes_written = block1_size;
            goto skip_write1;
        }

//      vmessage("Writing block1");
        if( !WriteFile( hCom, block1_ptr, block1_size, &bytes_written, NULL ) & 0 )
        {
            vmessage("Error writing block1");
            LastError = ::GetLastError();
            LastErrorContext = WRITING_DATA;

            LPVOID lpMsgBuf;
            FormatMessage( 
                FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,    NULL,
                ::GetLastError(),
                MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),                      // Default language
                (LPTSTR) &lpMsgBuf,    0,    NULL );                            // Display the string.
            LastErrorString.Format("Error writing to COM port: %s", lpMsgBuf);
            TrimTrailingJunk(LastErrorString);
            LocalFree( lpMsgBuf );
            error_flag = true;
        }
        else
        {
//          vmessage("Finished writing block1");
skip_write1:
            write_in_progress = false;
            outq_dequeue_idx += bytes_written;
            outq_dequeue_idx &= mux_interface_outqueuemask;

            while(outq_free_outer_lock || outq_free_critical_section)
                Sleep(0);
            outq_free_outer_lock = true;
            while(outq_free_critical_section)
                Sleep(0);
            outq_free_critical_section = true;
            outq_free += bytes_written;
            outq_free_critical_section = false;
            outq_free_outer_lock = false;

            last_tx_request = block1_size;
            last_tx = bytes_written;
        }

        write_in_progress = true;
        // Write the second block if it is non-null and the first block was completely written
        // (operating system's buffer isn't full)
        if( block2_size && (bytes_written == block1_size) && !suspend_activity )
        {
            if(!com_open) {     // if port not open then just discard the data so other processes don't hang
                bytes_written = block1_size;
                goto skip_write2;
            }

//          vmessage("Writing block2");
            if( !WriteFile( hCom, outq, block2_size, &bytes_written, NULL ) & 0 )
            {
                vmessage("Error writing block2");
                LastError = ::GetLastError();
                LastErrorContext = WRITING_DATA;

                LPVOID lpMsgBuf;
                FormatMessage( 
                    FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,    NULL,
                    ::GetLastError(),
                    MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),                      // Default language
                    (LPTSTR) &lpMsgBuf,    0,    NULL );                            // Display the string.
                LastErrorString.Format("Error writing to COM port: %s", lpMsgBuf);
                TrimTrailingJunk(LastErrorString);
                LocalFree( lpMsgBuf );
                error_flag = true;
            }
            else
            {
//              vmessage("Finished writing block2");
skip_write2:
                write_in_progress = false;
                outq_dequeue_idx += bytes_written;
                outq_dequeue_idx &= mux_interface_outqueuemask;

                while(outq_free_outer_lock || outq_free_critical_section)
                    Sleep(0);
                outq_free_outer_lock = true;
                while(outq_free_critical_section)
                    Sleep(0);
                outq_free_critical_section = true;
                outq_free += bytes_written;
                outq_free_critical_section = false;
                outq_free_outer_lock = false;

                last_tx_request = block2_size;
                last_tx = bytes_written;
            }
        } // if there is block2 data to write
        write_in_progress = false;
        return true;        // need another timeslice
    } // if there is any data in the output queue
    else
    {
        if(!error_flag && (outq_dequeue_idx != outq_enqueue_idx) && !outq_growing)
        {
            LastErrorString.Format("outq_dequeue_idx %u   outq_enqueue_idx %u", outq_dequeue_idx, outq_enqueue_idx);
            error_flag = true;
        }

        write_in_progress = false;
        return false;       // nothing to do ... sleep for awhile
    }
}





// ------------------------------------------------------------------------------
// Perform driver/interface duties for one timeslice (timeslice is relenquished
// when this function returns).  This is a child controller's driver function
// which interfaces to a parent controller.
//
// Accepts input and spools out output.  Packages and unpackages data to/from
// routing packets.  Gives thread time to all its children.
void MultiplexerInterface::ChildTimeSlice()
{
}


// ------------------------------------------------------------------------------
// Accept input data.
//
// The input data may come from the COM port or a parent multiplexer object.
// This function decodes packets and moves contents into the channel receive 
// buffers for the channels to which the packets were addressed.
//
// This function is called by the main driver thread.
void MultiplexerInterface::AcceptInput(char* buf, unsigned count)
{
    character_count += count;
    for( unsigned i = 0; i < count; i++ )
    {
        unsigned char c = buf[i];
        switch( receiver_state )
        {
        case 0 :
retest_case0:
            receiver_state = (c == '@') ? 1 : 0;
            break;
        case 1 :
            if(  c != 'P' ) 
                goto retest_case0;
            else
                receiver_state++;
            break;
        case 2 :                                    // length and address
            packet_size = (c & 0x1f) + 1;
            packet_channel = (c & 0xe0) >> 5;
            rx_checksum.Clear();
            rx_checksum.Add(c);
            receiver_state++;
            break;
        case 3 :
            serial = c;                             // low byte of serial number
            rx_checksum.Add(c);
            receiver_state++;
            break;
        case 4 :
            serial += (unsigned)c << 8;             // high byte of serial number
            rx_checksum.Add(c);
            rx_substate = packet_size;              // packet byte counter
            tei = rbuf_enqueue_idx[packet_channel]; // tentative enqueue index
//          toc = 0;                                // tentative overrun count
            receiver_state++;
            break;
        case 5 :
            {
                char oc;
                rbuf[packet_channel][tei] = oc = c ^ (unsigned char)(rx_checksum.cyclic_checksum);
                rx_checksum.Add(oc);
                receiver_state = (--rx_substate) ? 5 : 6;
                tei++;
                tei &= mux_interface_rxbufmask;
//              if( tei == rbuf_dequeue_idx[packet_channel] )
//              {
//                  tei = --tei & mux_interface_rxbufmask;
//                  toc++;
//              }
            }
            break;
        case 6 :
            if( c == rx_checksum.plain_checksum )
                receiver_state = 7;
            else
            {
                bad_packet_count++;
                fail_count1++;
                goto retest_case0;
            }
            break;
        case 7 :
            if( c == (unsigned char)(rx_checksum.cyclic_checksum) )
                receiver_state = 8;
            else
            {
                bad_packet_count++;
                fail_count2++;
                goto retest_case0;
            }
            break;
        case 8 :
            if( c == (unsigned char)(rx_checksum.cyclic_checksum >> 8) )
            {
                receiver_state = 0;
                packets++;
                rbuf_enqueue_idx[packet_channel] = tei;
//              rbuf_overrun_count[packet_channel] += toc;
                if( prior_serial_initialized )
                     missing_packet_count += (unsigned short)serial - (unsigned short)(prior_serial + 1);
                prior_serial = serial;
                prior_serial_initialized = true;
            }
            else
            {
                bad_packet_count++;
                fail_count3++;
                goto retest_case0;
            }
            break;
        }
    }
}


// ------------------------------------------------------------------------------
// Encode output data packet.
//
// Encodes all of the data from the specified channel's transmit buffer into
// packets.  Data is written to the buffer specified (allowing this function to 
// work for root as well as child MultiplexerInterface objects).
//
// Precondition:
//    channel specifies which transmit buffer to take the data from
//       and that channel's transmit buffer contains data
//    buf points to the output buffer to write the packet to
//    *enq_idx is an index into the output buffer of the enqueue location
//    *free is the number of bytes free in the output buffer
//    idx_mask is an AND mask for updating *enq_idx (rolling it over to the start of the buffer)
//
// Postcondition:
//    a packet was written to the specified output buffer (if it fits)
//    *enq_idx and *free are updated
//    the channel's transmit buffer is empty (unless output buffer is full)
//
// This function is called by the MultiplexerInterface main driver thread.
void MultiplexerInterface::EncodeOutput(int channel, char* buf, unsigned* enq_idx, unsigned* free, unsigned idx_mask)
{
    int packet_size;
    Checksum tx_checksum;
    char c;

    do {
        tx_checksum.Clear();
        packet_size = tbuf_enqueue_idx[channel] - tbuf_dequeue_idx[channel];
        if(packet_size < 0)
            packet_size += mux_interface_txbufsize;
        assert( packet_size > 0 );
        if( packet_size > 32 )
            packet_size = 32;
        if( ((unsigned)packet_size + 8) > *free )
            return;     // Output buffer is too full to accept the packet

        EnqueueOutc( '@', buf, enq_idx, free, idx_mask );
        EnqueueOutc( 'P', buf, enq_idx, free, idx_mask );

        c = (packet_size - 1) | (channel << 5);
        EnqueueOutc( c, buf, enq_idx, free, idx_mask );
        tx_checksum.Add(c);

        c = transmit_serial & 0XFF;
        EnqueueOutc( c, buf, enq_idx, free, idx_mask );
        tx_checksum.Add(c);

        c = (transmit_serial >> 8) & 0XFF;
        EnqueueOutc( c, buf, enq_idx, free, idx_mask );
        tx_checksum.Add(c);

        for( int i = packet_size; i > 0; i-- )
        {
            c = tbuf[channel][tbuf_dequeue_idx[channel]];
            tbuf_dequeue_idx[channel]++;
            tbuf_dequeue_idx[channel] &= mux_interface_txbufmask;
            EnqueueOutc( c ^ (char)(tx_checksum.cyclic_checksum), buf, enq_idx, free, idx_mask );
            tx_checksum.Add(c);
        }

        c = tx_checksum.plain_checksum;
        EnqueueOutc( c, buf, enq_idx, free, idx_mask );
        c = (char)(tx_checksum.cyclic_checksum);
        EnqueueOutc( c, buf, enq_idx, free, idx_mask );
        c = (char)(tx_checksum.cyclic_checksum >> 8);
        EnqueueOutc( c, buf, enq_idx, free, idx_mask );
    } while( tbuf_enqueue_idx[channel] != tbuf_dequeue_idx[channel] );
}

Source Code        Old Source Code        Older Source Code        Subtree        Local home        Home

See also:


file: /Techref/microchip/sermuxcpp.htm, 72KB, , updated: 2000/1/6 13:06, local time: 2019/8/23 20:43,
TOP NEW HELP FIND: 
34.239.158.107:LOG IN

 ©2019 These pages are served without commercial sponsorship. (No popup ads, etc...).Bandwidth abuse increases hosting cost forcing sponsorship or shutdown. This server aggressively defends against automated copying for any reason including offline viewing, duplication, etc... Please respect this requirement and DO NOT RIP THIS SITE. Questions?
Please DO link to this page! Digg it! / MAKE! / 

<A HREF="http://www.sxlist.com/techref/microchip/sermuxcpp.htm"> Ken's Web Server -- /cgi-bin/tl.exe/Timemon/monitor/MultiplexerInterface.cpp</A>

After you find an appropriate page, you are invited to your to this massmind site! (posts will be visible only to you before review) Just type in the box and press the Post button. (HTML welcomed, but not the <A tag: Instead, use the link box to link to another page. A tutorial is available Members can login to post directly, become page editors, and be credited for their posts.


Link? Put it here: 
if you want a response, please enter your email address: 
Attn spammers: All posts are reviewed before being made visible to anyone other than the poster.
Did you find what you needed?

 

Welcome to sxlist.com!


Site supported by
sales, advertizing,
& kind contributors
just like you!

Please don't rip/copy
(here's why

Copies of the site on CD
are available at minimal cost.
 

Welcome to www.sxlist.com!

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  .