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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
/**
 *  file       TxDuino.h
 *  date:      Oct 27, 2009
 *  brief:
 *
 *  detail:
 */
 
#ifndef CTXDUINO_H_
#define CTXDUINO_H_
 
 
#include <vector>
#include <string>
 
#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_ */
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
/**
 *  file       CTxDuino.cpp
 *  date:      Oct 27, 2009
 *  brief:
 *
 *  detail:
 */
 
#include "CTxDuino.h"
#include "compile.h"
 
#include <iostream>
#include <iomanip>
#include <sstream>
#include <cmath>
 
#include "IllegalArgumentException.h"
#include "IOException.h"
 
#ifdef TXD_MINGW
#include <windows.h>
#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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
/**
 * 	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 <iostream>
#include <iomanip>
#include <cmath>
 
#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 are closed.