// 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] ); }