// ******************** START OF WIN16.CPP ******************** // // // This file contains the source code that implements the // Win16Port class. This class uses the Windows 3.1 comm // driver. #include #include #include "win16.h" #define INPUT_BUFFER_SIZE 1024 #define OUTPUT_BUFFER_SIZE 1024 // The Win16Port constructor opens the port using the MS-Windows // function call OpenComm(). It then does the standard reading of // the existing settings, saving them, and then writing the new // settings. Note that we acquire a pointer to the internal driver // byte that has the Modem Status Lines. Win16Port::Win16Port( RS232PortName port, long baud_rate, char parity, int word_length, int stop_bits, int dtr, int rts, int xon_xoff, int rts_cts, int dtr_dsr ) { char name[ 15 ]; port_name = port; error_status = RS232_SUCCESS; line_status = 0; first_debug_output_line = RS232::FormatDebugOutput(); debug_line_count = FormatDebugOutput(); wsprintf( name, "COM%d", port_name + 1 ); handle = OpenComm( name, INPUT_BUFFER_SIZE, OUTPUT_BUFFER_SIZE ); if ( handle < 0 ) { error_status = translate_windows_error( handle ); return; } modem_status_register = (char FAR *) SetCommEventMask( handle, 0 ); modem_status_register += 35; read_settings(); saved_settings = settings; saved_dcb = dcb; settings.Adjust( baud_rate, parity, word_length, stop_bits, dtr, rts, xon_xoff, rts_cts, dtr_dsr ); write_settings(); } // The destructor restores the previous settings, then closes the // port using the Windows call, Win16Port::~Win16Port( void ) { if ( error_status == RS232_SUCCESS ) { settings = saved_settings; dcb = saved_dcb; write_settings(); CloseComm( handle ); } } // The set function looks much like the Set function for all the other // RS232 derivatives seen in the book. It just adjusts the current // port settings, then calls write_settings() to do the dirty work. RS232Error Win16Port::Set( long baud_rate, int parity, int word_length, int stop_bits ) { settings.Adjust( baud_rate, parity, word_length, stop_bits, UNCHANGED, UNCHANGED, UNCHANGED, UNCHANGED, UNCHANGED ); return write_settings(); } // read_settings() does most of its work on the DCB associated with // the current port. All of the settings that it can figure out // are found in the DCB. The current settings of RTS and DTR can't // be found, so they are set to -1, indicating that they are unknown. void Win16Port::read_settings( void ) { int status; RS232Error error; status = GetCommState( handle, &dcb ); if ( status < 0 ) { error = translate_windows_error( status ); if ( error >= RS232_ERROR ) error_status = error; return; } if ( ( dcb.BaudRate & 0xff00 ) == 0xff00 ) switch( dcb.BaudRate ) { case CBR_110 : settings.BaudRate = 110; break; case CBR_300 : settings.BaudRate = 300; break; case CBR_600 : settings.BaudRate = 600; break; case CBR_1200 : settings.BaudRate = 1200; break; case CBR_2400 : settings.BaudRate = 2400; break; case CBR_4800 : settings.BaudRate = 4800; break; case CBR_9600 : settings.BaudRate = 9600; break; case CBR_14400 : settings.BaudRate = 14400; break; case CBR_19200 : settings.BaudRate = 19200; break; case CBR_38400 : settings.BaudRate = 38400L; break; case CBR_56000 : settings.BaudRate = 56000L; break; case CBR_128000 : settings.BaudRate = 128000L; break; case CBR_256000 : settings.BaudRate = 256000L; break; default : settings.BaudRate = -1; break; } else settings.BaudRate = dcb.BaudRate; switch ( dcb.Parity ) { case NOPARITY : settings.Parity = 'N'; break; case ODDPARITY : settings.Parity = 'O'; break; case EVENPARITY : settings.Parity = 'E'; break; case MARKPARITY : settings.Parity = 'M'; break; case SPACEPARITY : settings.Parity = 'S'; break; default : settings.Parity = '?'; break; } settings.WordLength = dcb.ByteSize; switch ( dcb.StopBits ) { case ONESTOPBIT : settings.StopBits = 1; break; case ONE5STOPBITS : case TWOSTOPBITS : settings.StopBits = 2; break; default : settings.StopBits = -1; break; } settings.Rts = -1; settings.Dtr = -1; settings.XonXoff = dcb.fOutX; settings.RtsCts = dcb.fRtsflow; settings.DtrDsr = dcb.fDtrflow; } // write_settings() does almost everything it needs to do by // modifying the DCB and then calling SetCommState(). Setting // RTS and DTR is done by sending an escape function to the // driver. RS232Error Win16Port::write_settings( void ) { int set_status; RS232Error status = RS232_SUCCESS; if ( settings.BaudRate <= 19200L ) dcb.BaudRate = (int) settings.BaudRate; else if ( settings.BaudRate == 38400L ) dcb.BaudRate = CBR_38400; else status = RS232_ILLEGAL_BAUD_RATE; switch ( toupper( settings.Parity ) ) { case 'N' : dcb.Parity = NOPARITY; break; case 'E' : dcb.Parity = EVENPARITY; break; case 'O' : dcb.Parity = ODDPARITY; break; case 'M' : dcb.Parity = MARKPARITY; break; case 'S' : dcb.Parity = SPACEPARITY; break; default : status = RS232_ILLEGAL_PARITY_SETTING; break; } switch ( settings.WordLength ) { case 8 : dcb.ByteSize = 8; break; case 7 : dcb.ByteSize = 7; break; case 6 : dcb.ByteSize = 6; break; case 5 : dcb.ByteSize = 5; break; default : status = RS232_ILLEGAL_PARITY_SETTING; break; } switch ( settings.StopBits ) { case ONESTOPBIT : dcb.StopBits = 1; break; case ONE5STOPBITS : case TWOSTOPBITS : dcb.StopBits = 2; break; default : status = RS232_ILLEGAL_STOP_BITS; break; } if ( settings.Rts == 0 ) EscapeCommFunction( handle, CLRRTS ); else if ( settings.Rts == 1 ) EscapeCommFunction( handle, SETRTS ); if ( settings.Dtr == 0 ) EscapeCommFunction( handle, CLRDTR ); else if ( settings.Dtr == 1 ) EscapeCommFunction( handle, SETDTR ); dcb.fOutX = dcb.fInX = ( settings.XonXoff != 0 ); dcb.fOutxCtsFlow = dcb.fRtsflow = ( settings.RtsCts != 0 ); dcb.fOutxDsrFlow = dcb.fDtrflow = ( settings.DtrDsr != 0 ); set_status = SetCommState( &dcb ); if ( set_status == 0 ) return status; return translate_windows_error( set_status ); } // The Windows port driver sends back its own error codes in // certain stituations. This function translates them to their // class RS232 equivalents. RS232Error Win16Port::translate_windows_error( int error ) { switch ( error ) { case IE_BADID : return RS232_PORT_NOT_FOUND; case IE_BAUDRATE : return RS232_ILLEGAL_BAUD_RATE; case IE_BYTESIZE : return RS232_ILLEGAL_WORD_LENGTH; case IE_DEFAULT : return (RS232Error) WINDOWS_PORT_DEFAULT_PARAMETERS; case IE_HARDWARE : return RS232_PORT_IN_USE; case IE_MEMORY : return RS232_MEMORY_ALLOCATION_ERROR; case IE_NOPEN : return (RS232Error) WINDOWS_PORT_NOT_OPEN; case IE_OPEN : return (RS232Error) WINDOWS_PORT_ALREADY_OPEN; default : return RS232_ERROR; } } // ReadComm() does all the work necessary to implement this function. int Win16Port::read_byte( void ) { int result; unsigned char c; COMSTAT comstat; if ( error_status < 0 ) return error_status; result = ReadComm( handle, &c, 1 ); if ( result > 0 ) return (int) c; line_status |= GetCommError( handle, &comstat ); return RS232_TIMEOUT; } // Before calling WriteComm(), I check to be sure there is room // for a new character in the output queue. Once that is // determined, WriteComm() does the rest of the work. int Win16Port::write_byte( int c ) { int result; COMSTAT comstat; if ( error_status < 0 ) return error_status; line_status |= GetCommError( handle, &comstat ); if ( comstat.cbOutQue == OUTPUT_BUFFER_SIZE ) return RS232_TIMEOUT; result = WriteComm( handle, &c, 1 ); if ( result > 0 ) return RS232_SUCCESS; line_status |= GetCommError( handle, &comstat ); return RS232_TIMEOUT; } // The read_buffer() routine is slightly more complicated than // read_byte(), but it still calls ReadComm() to do most of the // work. It just has to take into account the possibility that // an incomplete read may take place. int Win16Port::read_buffer( char *buffer, unsigned int count ) { int result; COMSTAT comstat; ByteCount = 0; if ( error_status < 0 ) return error_status; result = ReadComm( handle, buffer, (int) count ); if ( result > 0 ) ByteCount = result; else { ByteCount = -result; line_status |= GetCommError( handle, &comstat ); } if ( ByteCount < count ) return RS232_TIMEOUT; else return RS232_SUCCESS; } // Like read_buffer(), this routine is basically just an extension // of its single byte sibling. However, it has to take into account // the possibility that a partial buffer write may occur. int Win16Port::write_buffer( char *buffer, unsigned int count ) { int result; COMSTAT comstat; unsigned int buffer_space; ByteCount = 0; if ( error_status < 0 ) return error_status; line_status |= GetCommError( handle, &comstat ); buffer_space = OUTPUT_BUFFER_SIZE - comstat.cbOutQue; if ( buffer_space > count ) result = WriteComm( handle, buffer, count ); else result = WriteComm( handle, buffer, buffer_space ); if ( result > 0 ) ByteCount = result; else { ByteCount = -result; line_status |= GetCommError( handle, &comstat ); } if ( ByteCount < count ) return RS232_TIMEOUT; return RS232_SUCCESS; } // This function has a dedicated Windows API call to do its work. int Win16Port::FlushRXBuffer( void ) { int status; if ( error_status < RS232_SUCCESS ) return error_status; status = FlushComm( handle, 1 ); if ( status != 0 ) return translate_windows_error( status ); return RS232_SUCCESS; } // The COMSTAT structure returned from GetCommError contains // the information we need to determine how the space in both // the TX and RX buffers is presently being used. int Win16Port::TXSpaceFree( void ) { COMSTAT comstat; if ( error_status < RS232_SUCCESS ) return error_status; line_status |= GetCommError( handle, &comstat ); return OUTPUT_BUFFER_SIZE - comstat.cbOutQue; } int Win16Port::RXSpaceUsed( void ) { COMSTAT comstat; if ( error_status < RS232_SUCCESS ) return error_status; line_status |= GetCommError( handle, &comstat ); return comstat.cbInQue; } // The Windows API function takes care of sending the break. int Win16Port::Break( long milliseconds ) { long timer; if ( error_status < RS232_SUCCESS ) return error_status; SetCommBreak( handle ); timer = ReadTime() + milliseconds; while ( ReadTime() < timer ) IdleFunction(); ClearCommBreak( handle ); return RS232_SUCCESS; } // The four Modem Status routines all take advantage of directly // peeking at the MSR byte inside the driver. They just mask off // the bit they are interested in, and return a logical result to // the calling routine. int Win16Port::Cd( void ) { if ( error_status < RS232_SUCCESS ) return error_status; return ( *modem_status_register & MSR_CD ) != 0; } int Win16Port::Ri( void ) { if ( error_status < RS232_SUCCESS ) return error_status; return ( *modem_status_register & MSR_RI ) != 0; } int Win16Port::Cts( void ) { if ( error_status < RS232_SUCCESS ) return error_status; return ( *modem_status_register & MSR_CTS ) != 0; } int Win16Port::Dsr( void ) { if ( error_status < RS232_SUCCESS ) return error_status; return ( *modem_status_register & MSR_DSR ) != 0; } // The four line status routines and the software overrrun error // detect routine all get their information from the // Windows API function GetCommError(). It returns all the line // status bits, rearranged by Windows just for fun. int Win16Port::SoftwareOverrunError( int clear ) { COMSTAT comstat; int return_value; if ( error_status < RS232_SUCCESS ) return error_status; line_status |= GetCommError( handle, &comstat ); return_value = ( ( line_status & CE_RXOVER ) != 0 ); if ( clear != UNCHANGED && clear != 0 ) line_status &= ~CE_RXOVER; return return_value; } int Win16Port::ParityError( int reset ) { COMSTAT comstat; int return_value; if ( error_status < RS232_SUCCESS ) return error_status; line_status |= GetCommError( handle, &comstat ); return_value = ( ( line_status & CE_RXPARITY ) != 0 ); if ( reset != UNCHANGED && reset != 0 ) line_status &= ~CE_RXPARITY; return return_value; } int Win16Port::BreakDetect( int reset ) { COMSTAT comstat; int return_value; if ( error_status < RS232_SUCCESS ) return error_status; line_status |= GetCommError( handle, &comstat ); return_value = ( ( line_status & CE_BREAK ) != 0 ); if ( reset != UNCHANGED && reset != 0 ) line_status &= ~CE_BREAK; return return_value; } int Win16Port::FramingError( int reset ) { COMSTAT comstat; int return_value; if ( error_status < RS232_SUCCESS ) return error_status; line_status |= GetCommError( handle, &comstat ); return_value = ( ( line_status & CE_FRAME ) != 0 ); if ( reset != UNCHANGED && reset != 0 ) line_status &= ~CE_FRAME; return return_value; } int Win16Port::HardwareOverrunError( int reset ) { COMSTAT comstat; int return_value; if ( error_status < RS232_SUCCESS ) return error_status; line_status |= GetCommError( handle, &comstat ); return_value = ( ( line_status & CE_OVERRUN ) != 0 ); if ( reset != UNCHANGED && reset != 0 ) line_status &= ~CE_OVERRUN; return return_value; } // All of the handshaking functions rely on write_settings() to do // the dirty work of actually changing the settings. They then // return the current setting to the caller. int Win16Port::XonXoffHandshaking( int setting ) { if ( error_status < RS232_SUCCESS ) return error_status; if ( setting != UNCHANGED ) { settings.XonXoff = ( setting != 0 ); write_settings(); } return( settings.XonXoff ); } int Win16Port::RtsCtsHandshaking( int setting ) { if ( error_status < RS232_SUCCESS ) return error_status; if ( setting != UNCHANGED ) { settings.RtsCts = ( setting != 0 ); write_settings(); } return( settings.RtsCts ); } int Win16Port::DtrDsrHandshaking( int setting ) { if ( error_status < RS232_SUCCESS ) return error_status; if ( setting != UNCHANGED ) { settings.DtrDsr = ( setting != 0 ); write_settings(); } return( settings.DtrDsr ); } // Setting DTR and RTS is done with a Windows API Escape code. // The escape sequence is undocumented but widely known, and // used by so much software it is unlikely to ever change. int Win16Port::Dtr( int setting ) { if ( error_status < RS232_SUCCESS ) return error_status; if ( setting != UNCHANGED ) { if ( settings.DtrDsr == 1 ) return WINDOWS_PORT_HANDSHAKE_LINE_IN_USE; else { if ( ( settings.Dtr = setting ) == 1 ) EscapeCommFunction( handle, SETDTR ); else EscapeCommFunction( handle, CLRDTR ); } } return settings.Dtr; } int Win16Port::Rts( int setting ) { if ( error_status < RS232_SUCCESS ) return error_status; if ( setting != UNCHANGED ) { if ( settings.RtsCts == 1 ) return WINDOWS_PORT_HANDSHAKE_LINE_IN_USE; else { if ( ( settings.Rts = setting ) == 1 ) EscapeCommFunction( handle, SETRTS ); else EscapeCommFunction( handle, CLRRTS ); } } return settings.Rts; } // All the information needed to perform the following functions is // found in the COMSTAT function returned by GetCommError(). int Win16Port::RXSpaceFree( void ) { COMSTAT comstat; if ( error_status < RS232_SUCCESS ) return error_status; line_status |= GetCommError( handle, &comstat ); return INPUT_BUFFER_SIZE - comstat.cbInQue; } int Win16Port::TXSpaceUsed( void ) { COMSTAT comstat; if ( error_status < RS232_SUCCESS ) return error_status; line_status |= GetCommError( handle, &comstat ); return comstat.cbOutQue; } // The Windows API provides a dedicated function to perform this task. int Win16Port::FlushTXBuffer( void ) { int status; if ( error_status < RS232_SUCCESS ) return error_status; status = FlushComm( handle, 0 ); if ( status != 0 ) return translate_windows_error( status ); return RS232_SUCCESS; } // The debug output includes a complete dump of the current DCB // structure for the port, which describes virtually everything // windows knows about the port. The only thing left out here // which could be interesting in the COMSTAT structure. int Win16Port::FormatDebugOutput( char *buffer, int line_number ) { if ( buffer == 0 ) return( first_debug_output_line + 7 ); if ( line_number < first_debug_output_line ) return RS232::FormatDebugOutput( buffer, line_number ); switch( line_number - first_debug_output_line ) { case 0 : wsprintf( buffer, (LPSTR) "Derived class: Win16Port " "Ri: %2d Cts: %2d Cd: %2d Dsr: %2d " "RX Overrun: %d", Ri(), Cts(), Cd(), Dsr(), SoftwareOverrunError() ); break; case 1 : wsprintf( buffer, "TX Used: %5d RX Free: %5d " "Parity Err: %d Break: %d " "Overrun: %d Framing Err: %d", TXSpaceUsed(), RXSpaceFree(), ParityError(), BreakDetect(), HardwareOverrunError(), FramingError() ); break; case 2 : wsprintf( buffer, "DCB: RlsTimeout: %04x CtsTimeout: %04x " "DsrTimeout: %04x fBinary: %1d", dcb.RlsTimeout, dcb.CtsTimeout, dcb.DsrTimeout, dcb.fBinary ); break; case 3 : wsprintf( buffer, "DCB: fRtsDisable: %1d fParity: %1d " "fOutxCtsFlow: %1d fOutxDsrFlow: %1d", dcb.fRtsDisable, dcb.fParity, dcb.fOutxCtsFlow, dcb.fOutxDsrFlow ); break; case 4 : wsprintf( buffer, "DCB: fDtrDisable: %1d fOutX: %1d fInx: %1d " "fPeChar: %1d fNull: %1d fChEvt: %1d", dcb.fDtrDisable, dcb.fOutX, dcb.fInX, dcb.fPeChar, dcb.fNull, dcb.fChEvt ); break; case 5 : wsprintf( buffer, "DCB: fDtrflow: %1d fRtsflow: %1d XonChar: %02x " "XoffChar: %02x XonLim: %04x XoffLim: %04x", dcb.fDtrflow, dcb.fRtsflow, dcb.XonChar, dcb.XoffChar, dcb.XonLim, dcb.XoffLim ); break; case 6 : wsprintf( buffer, "DCB: PeChar: %02x EofChar: %02x EvtChar: %02x " "TxDelay: %04x", dcb.PeChar, dcb.EofChar, dcb.EvtChar, dcb.TxDelay ); break; default : return RS232_ILLEGAL_LINE_NUMBER; } return RS232_SUCCESS; } char * Win16Port::ErrorName( int error ) { if ( error < RS232_NEXT_FREE_ERROR && error >= RS232_ERROR ) return RS232::ErrorName( error ); if ( error < RS232_NEXT_FREE_WARNING && error >= RS232_WARNING ) return RS232::ErrorName( error ); if ( error >= RS232_SUCCESS ) return RS232::ErrorName( error ); switch ( error ) { case WINDOWS_PORT_DEFAULT_PARAMETERS : return "Default parameters in error"; case WINDOWS_PORT_NOT_OPEN : return "Port not open"; case WINDOWS_PORT_ALREADY_OPEN : return "Port already open"; case WINDOWS_PORT_HANDSHAKE_LINE_IN_USE : return "Handshake line in use"; default : return( "Undefined error" ); } } // For Win16 we need a pair of OS specific routines to handle a couple // of timing issues properly. In a program that really uses the idle // function you might want to replace this function with one that // performs a PeekMessage() call int RS232::IdleFunction( void ) { return RS232_SUCCESS; } // // ReadTime() returns the current time of day in milliseconds. It uses // the Windows specific tick count function, instead of polling MS-DOS // or the BIOS. // long ReadTime( void ) { return GetTickCount(); } // ******************** END OF WIN16.CPP ********************