// // DIGI.CPP // // Source code from: // // Serial Communications: A C++ Developer's Guide, 2nd Edition // by Mark Nelson, IDG Books, 1999 // // Please see the book for information on usage. // // This file contains all of the code used by the DigiBoard class. // All access of the DigiBoard is done via the INT 14H interface // described in the DOC file available from DigiBoard. // #include #include #include "pc8250.h" #include "rs232.h" #include "digi.h" // The DigiBoard constructor is nice and simple. It has to read // in the old settings to save them, then set the new ones according // to the parameters passed in the constructor. The only private // member exclusive to the derived class is the line-status flag, // which is initialized to 0. The call to function 0x20 is used // to disable BIOS timing emulation. DigiBoard::DigiBoard( 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 ) { union REGS r; port_name = port; error_status = RS232_SUCCESS; first_debug_output_line = RS232::FormatDebugOutput(); debug_line_count = FormatDebugOutput(); if ( !valid_port() ) { error_status = (RS232Error) DIGIBOARD_DRIVER_NOT_FOUND; return; } read_settings(); saved_settings = settings; settings.Adjust( baud_rate, parity, word_length, stop_bits, dtr, rts, xon_xoff, rts_cts, dtr_dsr ); write_settings(); r.h.ah = 0x20; r.h.al = 0; r.x.dx = port_name; int86( 0x14, &r, &r ); line_status = 0; } // The destructor just restores the old state, nothing more. DigiBoard::~DigiBoard( void ) { settings = saved_settings; write_settings(); } // // A call to function 0x0c and 0x05 are needed to read all the // parameters found in the Settings class. All that is // needed after that is a bunch of switch statements to convert // the enumerated results that come back from the driver to // settings usable by programmers. // void DigiBoard::read_settings( void ) { union REGS r; settings.BaudRate = -1L; settings.Parity = '?'; settings.WordLength = -1; settings.StopBits = -1; settings.Dtr = -1; settings.Rts = -1; settings.XonXoff = -1; settings.RtsCts = -1; settings.DtrDsr = -1; r.h.ah = 0xc; r.x.dx = port_name; int86( 0x14, &r, &r ); if ( r.h.ah == 0xff ) return; switch ( r.h.cl ) { case 0x00 : settings.BaudRate = 110L; break; case 0x01 : settings.BaudRate = 150L; break; case 0x02 : settings.BaudRate = 300L; break; case 0x03 : settings.BaudRate = 600L; break; case 0x04 : settings.BaudRate = 1200L; break; case 0x05 : settings.BaudRate = 2400L; break; case 0x06 : settings.BaudRate = 4800L; break; case 0x07 : settings.BaudRate = 9600L; break; case 0x08 : settings.BaudRate = 19200L; break; case 0x09 : settings.BaudRate = 38400L; break; case 0x0a : settings.BaudRate = 57600L; break; case 0x0b : settings.BaudRate = 75600L; break; case 0x0c : settings.BaudRate = 115200L; break; case 0x0d : settings.BaudRate = 50L; break; case 0x0e : settings.BaudRate = 75L; break; case 0x0f : settings.BaudRate = 134L; break; case 0x10 : settings.BaudRate = 200L; break; case 0x11 : settings.BaudRate = 1800L; break; } switch ( r.h.bh ) { case 0 : settings.Parity = 'N'; break; case 1 : settings.Parity = 'O'; break; case 2 : settings.Parity = 'E'; break; } switch ( r.h.ch ) { case 0 : settings.WordLength = 5; break; case 1 : settings.WordLength = 6; break; case 2 : settings.WordLength = 7; break; case 3 : settings.WordLength = 8; break; } switch ( r.h.bl ) { case 0 : settings.StopBits = 1; break; case 1 : settings.StopBits = 2; break; } settings.XonXoff = ( r.h.ah & 3 ) ? 1: 0; settings.DtrDsr = ( r.h.al & 0x21 ) ? 1 : 0; settings.RtsCts = ( r.h.al & 0x12 ) ? 1 : 0; r.x.dx = port_name; r.h.ah = 5; r.h.al = 0; int86( 0x14, &r, &r ); settings.Dtr = ( r.h.bl & MCR_DTR ) ? 1 : 0; settings.Rts = ( r.h.bl & MCR_RTS ) ? 1 : 0; } // Setting the digiboard up with all the parameters found in the // Settings class requires three INT 14H calls. Function 4 // sets the standard communications parameters, Function 5 sets // the modem control lines, and function 0x1e sets up handshaking. // RS232Error DigiBoard::write_settings( void ) { union REGS r; RS232Error status = RS232_SUCCESS; r.x.dx = port_name; r.h.ah = 4; r.h.al = 0; switch ( toupper( settings.Parity ) ) { case 'E' : r.h.bh = 1; break; case 'O' : r.h.bh = 2; break; default : settings.Parity = 'N'; status = RS232_ILLEGAL_PARITY_SETTING; case 'N' : r.h.bh = 0; break; } switch ( settings.StopBits ) { default : settings.StopBits = 1; status = RS232_ILLEGAL_STOP_BITS; case 1 : r.h.bl = 0; break; case 2 : r.h.bl = 1; break; } switch ( settings.WordLength ) { case 5 : r.h.ch = 0; break; case 6 : r.h.ch = 1; break; case 7 : r.h.ch = 2; break; default : settings.WordLength = 8; status = RS232_ILLEGAL_WORD_LENGTH; case 8 : r.h.ch = 3; break; } switch ( settings.BaudRate ) { case 110L : r.h.cl = 0x00; break; case 150L : r.h.cl = 0x01; break; case 300L : r.h.cl = 0x02; break; case 600L : r.h.cl = 0x03; break; case 1200L : r.h.cl = 0x04; break; case 2400L : r.h.cl = 0x05; break; case 4800L : r.h.cl = 0x06; break; default : settings.BaudRate = 9600L; status = RS232_ILLEGAL_BAUD_RATE; case 9600L : r.h.cl = 0x07; break; case 19200L : r.h.cl = 0x08; break; case 38400L : r.h.cl = 0x09; break; case 57600L : r.h.cl = 0x0a; break; case 76800L : r.h.cl = 0x0b; break; case 115200L : r.h.cl = 0x0c; break; case 50L : r.h.cl = 0x0d; break; case 75L : r.h.cl = 0x0e; break; case 134L : r.h.cl = 0x0f; break; case 200L : r.h.cl = 0x10; break; case 1800L : r.h.cl = 0x11; break; } int86( 0x14, &r, &r ); r.x.dx = port_name; r.h.ah = 0x1e; r.h.bh = (unsigned char) ( ( settings.XonXoff ) ? 3: 0 ); r.h.bl = (unsigned char) ( ( settings.RtsCts ) ? 0x12 : 0 ); r.h.bl |= ( settings.DtrDsr ) ? 0x21 : 0; int86( 0x14, &r, &r ); r.x.dx = port_name; r.h.ah = 5; r.h.al = 1; r.h.bl = (unsigned char) ( ( settings.Dtr ) ? 1 : 0 ); r.h.bl |= ( settings.Rts ) ? 2 : 0; int86( 0x14, &r, &r ); return status; } // Function 6 is called to return the name of the port, but // it also functions effectively as a check to see if the // DigiBoard considers the port to be a valid one. int DigiBoard::valid_port( void ) { union REGS r; r.x.dx = port_name; r.h.ah = 6; r.h.al = 0; int86( 0x14, &r, &r ); return ( r.h.ah != 0xff ); } // Reading a byte uses the two BIOS emulation functions, one to // read in the modem status, and the other to read the character. // This function, like many of the other ones, updates the // line status flags with the result read back from the board. int DigiBoard::read_byte( void ) { union REGS r; if ( error_status < 0 ) return error_status; r.h.ah = 3; r.x.dx = port_name; int86( 0x14, &r, &r ); line_status |= r.h.ah; if ( r.h.ah & LSR_DATA_READY ) { r.h.ah = 2; r.x.dx = port_name; int86( 0x14, &r, &r ); line_status |= r.h.ah; return( r.h.al ); } return( RS232_TIMEOUT ); } // This function also uses a standard BIOS function call. int DigiBoard::write_byte( int c ) { union REGS r; if ( error_status < 0 ) return error_status; r.x.dx = port_name; r.h.ah = 0x01; r.h.al = (char) c; int86( 0x14, &r, &r ); line_status |= r.h.ah; if ( r.h.ah & 0x80 ) return RS232_TIMEOUT; return RS232_SUCCESS; } // DigiBoard has two private functions, 14 and 15, which are // used to read or write blocks of data. They both transfer // as much data as possible, then return a count. int DigiBoard::read_buffer( char *buffer, unsigned int count ) { union REGS r; struct SREGS s; if ( error_status < 0 ) return error_status; r.x.dx = port_name; r.x.cx = count; r.h.ah = 0x0f; s.es = (unsigned int) ( (long) (void __far *) buffer >> 16 ); r.x.bx = (unsigned int) (long) (void __far *) buffer; int86x( 0x14, &r, &r, &s ); ByteCount = r.x.ax; buffer[ ByteCount ] = '\0'; if ( ByteCount != count ) return RS232_TIMEOUT; return( RS232_SUCCESS ); } int DigiBoard::write_buffer( char *buffer, unsigned int count ) { union REGS r; struct SREGS s; if ( error_status < RS232_SUCCESS ) return error_status; r.x.dx = port_name; r.x.cx = count; r.h.ah = 0x0e; s.es = (unsigned int) ( (long) (void __far *) buffer >> 16 ); r.x.bx = (unsigned int) (long) (void __far *) buffer; int86x( 0x14, &r, &r, &s ); ByteCount = r.x.ax; if ( ByteCount != count ) return RS232_TIMEOUT; return RS232_SUCCESS; } // This function does no work on its own. Instead, it uses // the adjust function to change the settings member, then // calls the write_settings() function to do the job. RS232Error DigiBoard::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(); } // DigiBoard function 7 sets a break of a variable time in 10 // millisecond ticks. int DigiBoard::Break( long milliseconds ) { union REGS r; if ( error_status < RS232_SUCCESS ) return error_status; r.x.dx = port_name; r.h.ah = 7; r.h.al = 1; r.x.bx = (int) ( milliseconds / 10 ); int86( 0x14, &r, &r ); return RS232_SUCCESS; } // All four of the modem status functions just use BIOS function // 3 to read in the MSR of the UART. They then just mask off // the appropriate bit from the MSR and return a boolean value // to the calling program. int DigiBoard::Cd( void ) { union REGS r; if ( error_status < RS232_SUCCESS ) return error_status; r.x.dx = port_name; r.h.ah = 3; int86( 0x14, &r, &r ); line_status |= r.h.ah; return ( r.h.al & MSR_CD ) != 0; } int DigiBoard::Ri( void ) { union REGS r; if ( error_status < RS232_SUCCESS ) return error_status; r.x.dx = port_name; r.h.ah = 3; int86( 0x14, &r, &r ); line_status |= r.h.ah; return ( r.h.al & MSR_RI ) != 0; } int DigiBoard::Cts( void ) { union REGS r; if ( error_status < RS232_SUCCESS ) return error_status; r.x.dx = port_name; r.h.ah = 3; int86( 0x14, &r, &r ); line_status |= r.h.ah; return ( r.h.al & MSR_CTS ) != 0; } int DigiBoard::Dsr( void ) { union REGS r; if ( error_status < RS232_SUCCESS ) return error_status; r.x.dx = port_name; r.h.ah = 3; int86( 0x14, &r, &r ); line_status |= r.h.ah; return ( r.h.al & MSR_DSR ) != 0; } // Like the modem status functions, the four line status functions // use BIOS function 3 to read the LSR from the UART, then mask // off the appropriate bits and return a logical true or false to // the calling program. int DigiBoard::ParityError( int reset ) { union REGS r; int status; if ( error_status < RS232_SUCCESS ) return error_status; r.x.dx = port_name; r.h.ah = 3; int86( 0x14, &r, &r ); line_status |= r.h.ah; status = ( line_status & LSR_PARITY_ERROR ) != 0; if ( reset != UNCHANGED && reset != 0 ) line_status &= ~LSR_PARITY_ERROR; return status; } int DigiBoard::BreakDetect( int reset ) { union REGS r; int status; if ( error_status < RS232_SUCCESS ) return error_status; r.x.dx = port_name; r.h.ah = 3; int86( 0x14, &r, &r ); line_status |= r.h.ah; status = ( line_status & LSR_BREAK_DETECT ) != 0; if ( reset != UNCHANGED && reset != 0 ) line_status &= ~LSR_BREAK_DETECT; return status; } int DigiBoard::FramingError( int reset ) { union REGS r; int status; if ( error_status < RS232_SUCCESS ) return error_status; r.x.dx = port_name; r.h.ah = 3; int86( 0x14, &r, &r ); line_status |= r.h.ah; status = ( line_status & LSR_FRAMING_ERROR ) != 0; if ( reset != UNCHANGED && reset != 0 ) line_status &= ~LSR_FRAMING_ERROR; return status; } int DigiBoard::HardwareOverrunError( int reset ) { union REGS r; int status; if ( error_status < RS232_SUCCESS ) return error_status; r.x.dx = port_name; r.h.ah = 3; int86( 0x14, &r, &r ); line_status |= r.h.ah; status = ( line_status & LSR_OVERRUN_ERROR ) != 0; if ( reset != UNCHANGED && reset != 0 ) line_status &= ~LSR_OVERRUN_ERROR; return status; } // The five handshaking and modem control functions are all // basically lazy. They modify a the appropriate value in // the settings member, then call write_settings() to do the // real work. int DigiBoard::XonXoffHandshaking( int setting ) { if ( setting != UNCHANGED ) { settings.XonXoff = ( setting != 0 ); write_settings(); } return settings.XonXoff; } int DigiBoard::RtsCtsHandshaking( int setting ) { if ( setting != UNCHANGED ) { settings.RtsCts = ( setting != 0 ); write_settings(); } return settings.RtsCts; } int DigiBoard::DtrDsrHandshaking( int setting ) { if ( setting != UNCHANGED ) { settings.DtrDsr = ( setting != 0 ); write_settings(); } return settings.DtrDsr; } int DigiBoard::Dtr( int setting ) { if ( setting != UNCHANGED ) { settings.Dtr = ( setting != 0 ); write_settings(); } return ( settings.Dtr != 0 ); } int DigiBoard::Rts( int setting ) { if ( setting != UNCHANGED ) { settings.Rts = ( setting != 0 ); write_settings(); } return ( settings.Rts != 0 ); } // DigiBoard only lets us peek ahead by one byte into the // input buffer. If there is a character there, this function // reads it in and stores it in the appropriate place. int DigiBoard::PeekBuffer( void *buffer, unsigned int count ) { union REGS r; if ( count ) { r.h.ah = 3; r.x.dx = port_name; int86( 0x14, &r, &r ); line_status |= r.h.ah; if ( r.h.ah & 0x80 ) return RS232_ERROR; if ( r.h.ah & 1 ) { r.h.ah = 0x14; r.x.dx = port_name; int86( 0x14, &r, &r ); line_status |= r.h.ah; *( (char *) buffer ) = r.h.al; ByteCount = 1; } else ByteCount = 0; } return RS232_SUCCESS; } // These functions all use private digiboard INT 14 calls to // return buffer counts. The only complication is that there // is no direct way to determine the RX space free. It has to // be calculated indirectly as the RX buffer size - the buffer // space used. int DigiBoard::TXSpaceUsed( void ) { union REGS r; if ( error_status < RS232_SUCCESS ) return error_status; r.x.dx = port_name; r.h.ah = 0xfd; r.h.al = 1; int86( 0x14, &r, &r ); return( r.x.cx ); } int DigiBoard::TXSpaceFree( void ) { union REGS r; if ( error_status < RS232_SUCCESS ) return error_status; r.x.dx = port_name; r.h.ah = 0x12; int86( 0x14, &r, &r ); return r.x.ax; } int DigiBoard::RXSpaceUsed( void ) { union REGS r; if ( error_status < RS232_SUCCESS ) return error_status; r.x.dx = port_name; r.h.ah = 0x0a; int86( 0x14, &r, &r ); return r.x.ax; } int DigiBoard::RXSpaceFree( void ) { union REGS r; int buffer_size; if ( error_status < RS232_SUCCESS ) return error_status; r.x.dx = port_name; r.h.ah = 0x1b; r.h.al = 1; int86( 0x14, &r, &r ); buffer_size = r.x.bx; r.h.ah = 10; r.x.dx = port_name; int86( 0x14, &r, &r ); return( buffer_size - r.x.ax ); } // DigiBoard provides two private INT 14 calls to handle flushing // the TX and RX buffers. int DigiBoard::FlushRXBuffer( void ) { union REGS r; if ( error_status < RS232_SUCCESS ) return error_status; r.x.dx = port_name; r.h.ah = 0x10; int86( 0x14, &r, &r ); return RS232_SUCCESS; } int DigiBoard::FlushTXBuffer( void ) { union REGS r; if ( error_status < RS232_SUCCESS ) return error_status; r.x.dx = port_name; r.h.ah = 0x11; int86( 0x14, &r, &r ); return RS232_SUCCESS; } int DigiBoard::FormatDebugOutput( char *buffer, int line_number ) { union REGS r; if ( buffer == 0 ) return( first_debug_output_line + 4 ); if ( line_number < first_debug_output_line ) return RS232::FormatDebugOutput( buffer, line_number ); switch( line_number - first_debug_output_line ) { case 0 : r.x.dx = port_name; r.h.ah = 6; r.h.al = 1; int86( 0x14, &r, &r ); sprintf( buffer, "Derived class: DigiBoard Boards/Channels: %2d/%2d " "Ver: %2x.%02x 1st Port: COM%-2d", r.x.cx, r.x.ax, r.h.bh, r.h.bl, r.x.dx + 1 ); break; case 1 : ParityError( UNCHANGED ); sprintf( buffer, "Parity Err: %d " "Break Det: %d " "Overrun Err: %d " "Framing Err: %d ", ( line_status & LSR_PARITY_ERROR ) ? 1 : 0, ( line_status & LSR_BREAK_DETECT ) ? 1 : 0, ( line_status & LSR_OVERRUN_ERROR ) ? 1 : 0, ( line_status & LSR_FRAMING_ERROR ) ? 1 : 0 ); break; case 2 : sprintf( buffer, "Buffer Counts: RX Used/Free: %5u/%5u " "TX Used/Free: %5u/%5u", RXSpaceUsed(), RXSpaceFree(), TXSpaceUsed(), TXSpaceFree() ); break; case 3 : sprintf( buffer, "RI: %2d CD: %2d CTS: %2d DSR: %2d", Ri(), Cd(), Cts(), Dsr() ); break; default : return RS232_ILLEGAL_LINE_NUMBER; } return RS232_SUCCESS; } char * DigiBoard::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 DIGIBOARD_DRIVER_NOT_FOUND : return( "Driver not found" ); default : return( "Undefined error" ); } }