TxDuino: C++ classes (windows)
In order to make interfacing with the
TxDuino
as easy as possible, I wrote a
little c++ class to take care of all the heavy lifting. You connect to a
TxDuino device by creating a TxDuino
object. The constructor accepts a
device name. In windows that looks like "\.COM8
" (my arduino is installed on
COM port 8). In linux it'll look something like "/dev/ttyS2
" except that I
haven't gotten around to implementing the linux part of this class yet.
The class, along with the supporting classes and test program are included here: TxDuino Class Source.
/** * file TxDuino.h * date: Oct 27, 2009 * brief: * * detail: */ #ifndef CTXDUINO_H_ #define CTXDUINO_H_ #include #include #include "types.h" namespace txduino { /** * brief operating system dependendant implementation structure */ struct STxDuinoImpl; /** * brief Class encapsulating the interface / API for communicating with one * TxDuino device. The TxDuino sends a standard RC PPM signal encoding * commands for up to 8 PWM channels (servos, engine controllers). * * The magnitude of each channel is divided into 250 discrete segments. * Exactly how those segments are interpreted by the actuators is determined * by the configuration of this object. * * note: channels are zero indexed * * todo add a constructor that accepts a csv file with the saturations and * neutral points that can be generated from the test program */ class CTxDuino { private: STxDuinoImpl* m_osInfo; /// pointer to os-dependant implementation std::string m_devName; /// system device name, /// i.e. "\.COM2″, "/dev/tty2″ u8 m_chan [9]; /// value for each channel [0,250] u8 m_neutral [8]; /// the "center" for each actuator u8 m_minsat [8]; /// the minimum value for each channel u8 m_maxsat [8]; /// the maximum value for each channel public: /** * brief Constructs a new TxDuino object serving as an interface * into on particular TxDuino device. * param strDevice device string, i.e. "\.COM2″, "/dev/tty2″ */ CTxDuino( std::string strDevice ); /** * brief cleans up OS resources reserved for this serial connection */ virtual ~CTxDuino(); /** * brief sends the current channel definitions to the device */ void send(); /** * brief sets the value of an actuator as a percent of it's viable * range. * param chan the channel to set * param percent -100% < percent < 100%; value to set channel to */ void setPercent( s32 chan, f64 percent ); /** * brief returns the percent value the indicated actuator is set to, * note: if the neutral point is equal to one of the saturation * points this value may be unreliable * param chan the channel to get * return percent value of actuator on indicated channel */ f64 getPercent( s32 chan ); /** * brief sets the raw value of the actuator pulse width * param chan the channel to set * param value 0 < value < 250; value to set channel to */ void setRaw( s32 chan, u8 value ); /** * brief returns the raw value the indicated actuator is set to * param chan the channel to get * return a value between 0 and 250 indicated the pulse length for * that actuator (multiply by 4us and add 700us to get the * actual pulse length) */ u8 getRaw( s32 chan ); /** * brief sets the raw value of the actuator pulse width corresponding * to a neutral state of that actuator * param chan the channel to set * param value 0 < value < 250; value to set channel to */ void setNeutral( s32 chan, u8 value ); /** * brief returns the raw value corresponding to a neutral state of * the indicated actuator * param chan the channel to get * return a value between 0 and 250 indicated the pulse length for * that actuator (multiply by 4us and add 700us to get the * actual pulse length) */ u8 getNeutral( s32 chan ); /** * brief sets the raw value of the actuator pulse width corresponding * to the minimum state of the indicated actuator * param chan the channel to set * param value 0 < value < 250; value to set channel to */ void setMinSat( s32 chan, u8 value ); /** * brief returns the raw value corresponding to a minimum state of * the indicated actuator * param chan the channel to get * return a value between 0 and 250 indicated the pulse length for * that actuator (multiply by 4us and add 700us to get the * actual pulse length) */ u8 getMinSat( s32 chan ); /** * brief sets the raw value of the actuator pulse width corresponding * to the maximum state of the indicated actuator * param chan the channel to set * param value 0 < value < 250; value to set channel to */ void setMaxSat( s32 chan, u8 value ); /** * brief returns the raw value corresponding to a maximum state of * the indicated actuator * param chan the channel to get * return a value between 0 and 250 indicated the pulse length for * that actuator (multiply by 4us and add 700us to get the * actual pulse length) */ u8 getMaxSat( s32 chan ); /** * brief return the device name that was used to connect to this * txduino */ std::string getName(); }; } #endif /* CTXDUINO_H_ */ /** * file CTxDuino.cpp * date: Oct 27, 2009 * brief: * * detail: */ #include "CTxDuino.h" #include "compile.h" #include #include #include #include #include "IllegalArgumentException.h" #include "IOException.h" #ifdef TXD_MINGW #include #endif namespace txduino { #ifdef TXD_MINGW struct STxDuinoImpl { HANDLE hComPort; /// handle to the opened COM device }; #endif #ifdef TXD_LINUX struct STxDuinoImpl { FILE hSerialFile; }; #endif /** * The initial state of the individual actuators is initialized as follows * * verbatim * minimum saturation: 0 * neutral: 125 * maximum saturation: 250 * value: 125 * endverbatim * * Note that this is probably not appropriate for your system. Many of the * servos that we've used have minimum saturations around 10 and maximum * saturations around 240. Use the actuator test program to determine what * these values should be. */ CTxDuino::CTxDuino( std::string strDevice ) { using std::cout; using std::endl; using std::stringstream; // intiialize all the arrays for( int i=0; i < 8; i++ ) { m_chan [i] = 125; m_minsat [i] = 0; m_maxsat [i] = 250; m_neutral [i] = 125; } // stop byte m_chan[8] = 0xFF; // initialize the OS dependant information m_osInfo = new STxDuinoImpl(); /* ---------------------------------------------------------------------------- * Windows Specific Implementation: * ---------------------------------------------------------------------------*/ #ifdef TXD_MINGW // open the file using the windows API m_osInfo->hComPort = CreateFile( strDevice.c_str(), // file name GENERIC_READ | GENERIC_WRITE, // access mode: read and write FILE_SHARE_READ|FILE_SHARE_WRITE, // (sharing) NULL, // (security) 0: none OPEN_EXISTING, // (creation) i.e. don't make it 0, // (overlapped operation) NULL); // no template file // check to make sure the file open succeeded if( m_osInfo->hComPort == INVALID_HANDLE_VALUE ) { stringstream message( stringstream::in | stringstream::out ); message << "Invalid Device Name: " << strDevice; throw IllegalArgumentException(message.str()); } // get the current settings on the com port DCB dcb; GetCommState( m_osInfo->hComPort, &dcb ); // change the settings, the TxDuino uses a BAUD rate of 9600 dcb.fBinary = 1; dcb.BaudRate = CBR_9600; dcb.Parity = NOPARITY; dcb.ByteSize = 8; dcb.StopBits = ONESTOPBIT; // set the new settings for the port SetCommState( m_osInfo->hComPort, &dcb ); #endif } CTxDuino::~CTxDuino() { /* --------------------------------------------------- * Windows Specific Implementation: * --------------------------------------------------*/ #ifdef TXD_MINGW // close the device if it's open if( m_osInfo->hComPort != INVALID_HANDLE_VALUE ) CloseHandle( m_osInfo->hComPort ); #endif delete m_osInfo; } void CTxDuino::send() { using std::stringstream; // ensure that the last byte of the packet is the stop byte m_chan[8] = 0xFF; /* --------------------------------------------------- * Windows Specific Implementation: * --------------------------------------------------*/ #ifdef TXD_MINGW DWORD bytesWritten; BOOL retVal = WriteFile( m_osInfo->hComPort, // output handle m_chan, // buffer of bytes to send 9, // number of bytes to send from buffer &bytesWritten, // pointer to a word that receives number of // bytes written NULL); // pointer to an OVERLAPPED struct if( bytesWritten != 9 ) { stringstream message( stringstream::in | stringstream::out ); message << "Bytes written to device less than expected: " << bytesWritten << ", expecting 9"; throw IOException(message.str()); } if( retVal == 0 ) { stringstream message( stringstream::in | stringstream::out ); message << "Writing to device failed; error code: " << GetLastError(); throw IOException(message.str()); } #endif } /** * If the percent is positive, the raw value is calculated as follows * * verbatim * raw = neutral + (max - neutral) * percent * endverbatim * * if the percent is negative, the raw value is calculated as follows * * verbatim * raw = neutral - (neutral - min) * percent * endverbatim */ void CTxDuino::setPercent( s32 chan, f64 percent ) { using std::stringstream; if(chan < 0 || chan > 7) { stringstream message( stringstream::in | stringstream::out ); message << "Invalid channel number: " << chan << HERE << "valid channels are 0-7"; } if(percent > 0) { m_chan[chan] = (u8) (m_neutral[chan] + ( m_maxsat[chan] - m_neutral[chan]) * percent ); } else { m_chan[chan] = (u8) (m_neutral[chan] - ( m_minsat[chan] - m_neutral[chan]) * percent ); } } /** * If the value is strictly less than the neutral value then the percent value * is calculated by * * verbatim * percent = -( neutral - value ) / (neutral - min); * endverbatim * * If the value is strictly greater than the neutral value then the percent * value is calculated by * * verbatim * percent = ( value - neutral ) / (max - neutral); * endverbatim * * If the value is equal to the neutral value then the percent value is zero. */ f64 CTxDuino::getPercent( s32 chan ) { using std::stringstream; if(chan < 0 || chan > 7) { stringstream message( stringstream::in | stringstream::out ); message << "Invalid channel number: " << chan << HERE << "valid channels are 0-7"; } if( m_chan[chan] < m_neutral[chan] ) { return (double)(-(m_neutral[chan] - m_chan[chan])) / (m_neutral[chan] - m_minsat[chan]); } else if( m_chan[chan] > m_neutral[chan] ) { return (double)(m_chan[chan] - m_neutral[chan]) / (m_maxsat[chan] - m_neutral[chan]); } else { return 0.0; } } void CTxDuino::setRaw( s32 chan, u8 value ) { using std::stringstream; if(chan < 0 || chan > 7) { stringstream message( stringstream::in | stringstream::out ); message << "Invalid channel number: " << chan << HERE << "valid channels are 0-7"; } if(value > 250) { stringstream message( stringstream::in | stringstream::out ); message << "Invalid value: " << value << HERE << "valid channels are 0-250"; } m_chan[chan] = value; } u8 CTxDuino::getRaw( s32 chan ) { using std::stringstream; if(chan < 0 || chan > 7) { stringstream message( stringstream::in | stringstream::out ); message << "Invalid channel number: " << chan << HERE << "valid channels are 0-7"; } return m_chan[chan]; } void CTxDuino::setNeutral( s32 chan, u8 value ) { using std::stringstream; if(chan < 0 || chan > 7) { stringstream message( stringstream::in | stringstream::out ); message << "Invalid channel number: " << chan << HERE << "valid channels are 0-7"; } if(value > 250) { stringstream message( stringstream::in | stringstream::out ); message << "Invalid value: " << value << HERE << "valid channels are 0-250"; } m_neutral[chan] = value; } u8 CTxDuino::getNeutral( s32 chan ) { using std::stringstream; if(chan < 0 || chan > 7) { stringstream message( stringstream::in | stringstream::out ); message << "Invalid channel number: " << chan << HERE << "valid channels are 0-7"; } return m_neutral[chan]; } void CTxDuino::setMinSat( s32 chan, u8 value ) { using std::stringstream; if(chan < 0 || chan > 7) { stringstream message( stringstream::in | stringstream::out ); message << "Invalid channel number: " << chan << HERE << "valid channels are 0-7"; } if(value > 250) { stringstream message( stringstream::in | stringstream::out ); message << "Invalid value: " << value << HERE << "valid channels are 0-250"; } m_minsat[chan] = value; } u8 CTxDuino::getMinSat( s32 chan ) { using std::stringstream; if(chan < 0 || chan > 7) { stringstream message( stringstream::in | stringstream::out ); message << "Invalid channel number: " << chan << HERE << "valid channels are 0-7"; } return m_minsat[chan]; } void CTxDuino::setMaxSat( s32 chan, u8 value ) { using std::stringstream; if(chan < 0 || chan > 7) { stringstream message( stringstream::in | stringstream::out ); message << "Invalid channel number: " << chan << HERE << "valid channels are 0-7"; } if(value > 250) { stringstream message( stringstream::in | stringstream::out ); message << "Invalid value: " << value << HERE << "valid channels are 0-250"; } m_maxsat[chan] = value; } u8 CTxDuino::getMaxSat( s32 chan ) { using std::stringstream; if(chan < 0 || chan > 7) { stringstream message( stringstream::in | stringstream::out ); message << "Invalid channel number: " << chan << HERE << "valid channels are 0-7"; } return m_maxsat[chan]; } std::string CTxDuino::getName() { return m_devName; } }
A re-write of the serialTest.exe
program that sends sinusoidal commands to
the plane using this new class demonstrates its use.
/** * file serialTest.cpp * date: Oct 27, 2009 * brief: * * detail: * This is a simple test program that demonstrates how to connect to and * write commands to the arduino transmitter interface using windows. * * the TxDuino is an interface into the futaba FP-TP-FM transmitter module, * which accepts an RC PPM input. This signal contains a maximum of 8 * servo channels. */ #include #include #include #include "CTxDuino.h" #include "constants.h" using namespace std; using namespace txduino; int main( int argc, char** argv ) { // check to ensure that the command line included a device to open if( argc < 2 ) { cout << "Usage: serialTest.exe [Device Name]n" " where [Device Name] is the name of the COM port file onn " " windows (i.e. \\.\COM8), or the name of the serialn " " device on *nix (i.e. /dev/tty8)n" << endl; return -1; } // grab a pointer to the device to open char* strDevName = argv[1]; // create the txduino device CTxDuino tx(strDevName); // send a sinusoidal input on all channels (except for channel 3, which is // usually the throttle) for 10 seconds for(int i=0; i < 1000; i++) { for(int j=0; j < 8; j++) tx.setRaw(j, (unsigned char) (125.0 + 75.0 * sin( 2.0 * PI * i / 100.0 )) ); tx.setRaw(2, 0); for(int j=0; j < 8; j++) cout << setw(3) << (int)tx.getRaw(j) << " | "; cout << endl; tx.send(); #ifdef TXD_MIGNW Sleep(1); #endif #ifdef TXD_LINUX usleep(0.001); #endif } return 0; }
Comments
Comments powered by Disqus