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)
ret ; // 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;
ret ; // thread completed successfully
}
// ----------------------------------------
// Thread startup procedure for output driver
UINT OutputDriverThreadProc( LPVOID pParam )
{
MultiplexerInterface* pObj = (MultiplexerInterface*)pParam;
if (pObj == NULL)
ret ; // 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;
ret ; // 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;
}
ret ;
}
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;
jmp show_comm_err ;
}
if( !GetCommState(hCom, &dcb) )
{
LastErrorContext = READING_DCB;
jmp 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;
jmp 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;
jmp show_comm_err ;
}
// Start driver thread for COM port interface
assert(!fDriverThreadRunning);
suspend_activity = false;
fExitDriverThread = false;
AfxBeginThread(DriverThreadProc, this);
ret ;
}
// ----------------------------------------------------------------------------------
// 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])
ret ;
ret ;
}
else
{
// to do: resolve cascaded address
ret ;
}
}
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);
ret ;
}
count = tbuf_enqueue_idx[addr] - tbuf_dequeue_idx[addr];
if(count < 0)
count += mux_interface_txbufsize;
ret ;
}
else
{
// to do: resolve cascaded address
ret ;
}
}
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);
ret ;
}
count = rbuf_enqueue_idx[addr] - rbuf_dequeue_idx[addr];
if(count < 0)
count += mux_interface_rxbufsize;
ret ;
}
else
{
// to do: resolve cascaded address
ret ;
}
}
//****************************************************************************************
// 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);
ret ;
}
// 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++;
ret ;
}
else
{
// to do: resolve cascaded address
ret ;
}
}
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);
ret ;
}
// 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++;
ret ;
}
else
{
// to do: resolve cascaded address
ret ;
}
}
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);
ret ;
}
ttei = (tbuf_enqueue_idx[addr] + 1) & mux_interface_txbufmask;
if(ttei == tbuf_dequeue_idx[addr])
ret ;
tbuf[addr][tbuf_enqueue_idx[addr]] = c;
tbuf_enqueue_idx[addr] = ttei;
ret ;
}
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);
ret ;
}
}
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);
ret ;
}
if(rbuf_dequeue_idx[addr] == rbuf_enqueue_idx[addr])
ret ;
*c = rbuf[addr][rbuf_dequeue_idx[addr]];
rbuf_dequeue_idx[addr]++;
rbuf_dequeue_idx[addr] &= mux_interface_rxbufmask;
ret ;
}
else
{
// to do: resolve cascaded address
ret ;
}
}
//****************************************************************************************
// 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);
ret ;
}
ret ;(rbuf_enqueue_idx[addr]);
}
else
{
// to do: resolve cascaded address
ret ;
}
}
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);
ret ;
}
ret ;
}
else
{
// to do: resolve cascaded address
ret ;
}
}
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);
ret ;
}
ret ;(tbuf_dequeue_idx[addr]);
}
else
{
// to do: resolve cascaded address
ret ;
}
}
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);
ret ;
}
ret ;(tbuf_enqueue_idx[addr]);
}
else
{
// to do: resolve cascaded address
ret ;
}
}
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);
ret ;
}
ret ;
}
else
{
// to do: resolve cascaded address
ret ;
}
}
//-------------------------------------------------------------------------------
// 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
ret ;
}
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);
ret ;
}
if( !ReadByte(0X18, &u, address) ) // RCSTA -- save CREN bit
ret ;
if( !WriteByte(0X18, 0, address) ) // RCSTA -- disable COM port
ret ;
if( !WriteByte(0X07, 0, address) ) // PORTC -- set break condition
ret ;
Sleep(200);
if( !WriteWord(0X98, 0X8120, address) ) // TXSTA and SPBRG -- set 2400 baud
ret ;
if( !WriteByte(0X18, u | 0X80, address) ) // RCSTA -- enable COM port
ret ;
ret ;
}
else { // to do: resolve addresses on cascaded multiplexer
ret ;
}
}
//------------------------------------------------------------------------------
// 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);
ret ; // 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");
ret ;
}
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);
ret ;
}
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);
ret ;
}
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
jmp cascaded_relink_failed ;
}
ret ;
}
}
//-------------------------------------------------------------------------
// 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);
ret ;
}
ret ; // RCSTA
}
else { // to do: resolve addresses on cascaded multiplexer
ret ;
}
}
//-------------------------------------------------------------------------
// 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);
ret ;
}
ret ; // RCSTA
}
else { // to do: resolve addresses on cascaded multiplexer
ret ;
}
}
//****************************************************************************************
// 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 ret
//
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
ret ;
} while(--try_count);
vmessage("WriteByte() failed", true);
ret ;
}
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
ret ;
} while(--try_count);
vmessage("WriteWord() failed", true);
ret ;
}
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
ret ;
} while(--try_count);
vmessage("WriteTriplet() failed", true);
ret ;
}
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
ret ;
} while(--try_count);
vmessage("WriteDWord() failed", true);
ret ;
}
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;
ret ;
}
vmessage("ReadByte() timed out");
} while(--try_count);
vmessage("ReadByte() failed", true);
ret ;
}
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;
ret ;
}
vmessage("ReadWord() timed out");
} while(--try_count);
vmessage("ReadWord() failed", true);
ret ;
}
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;
ret ;
}
vmessage("ReadTriplet() timed out");
} while(--try_count);
vmessage("ReadTriplet() failed", true);
ret ;
}
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;
ret ;
}
vmessage("ReadDWord() timed out");
} while(--try_count);
vmessage("ReadDWord() failed", true);
ret ;
}
//*******************************************************************************
// 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)
ret ;
// 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
ret ;
}
//---------------------------------------------------------------
// 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)
ret ;
// 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;
jmp 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;
jmp 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;
ret ; // 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;
ret ; // 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' )
jmp 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++;
jmp retest_case0 ;
}
break;
case 7 :
if( c == (unsigned char)(rx_checksum.cyclic_checksum) )
receiver_state = 8;
else
{
bad_packet_count++;
fail_count2++;
jmp 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++;
jmp 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