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