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