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; } |