Go Down

Topic: Arduino-Controlled RC Transmitter (Read 29 times) previous topic - next topic

Syvwlch

Nov 01, 2007, 03:29 pm Last Edit: Nov 01, 2007, 03:33 pm by Syvwlch Reason: 1
I whipped up a little interface yesterday, to connect an Arduino to a Radio Control Transmitter (a Multiplex Royal Evo 12, in my case). This can be used to send commands from the Arduino to the transmitter, which will mix them with the manual input and radio them to the radio-controlled model.


Channels 1, 2, 4 and 5 are moving, sticks centered.


The first application I want this for is to pan a video camera with two servos on the model, with input from head motion tracking on the Arduino, i.e. see from the model's cockpit. (Yes, I know, it's been done... but not by me and not on Arduino AFAIK.) I've got the video goggles, camera and video transmitter/receiver from the Spy Gear RC car Jack of All Trades has made famous here: http://jakeofalltrades.wordpress.com/2007/09/30/25-head-mounted-display/.

It could also be used as a PC-to-RC interface, using Arduino as a USB-to-RC bridge. Any kind of processing could be done on land, and the resulting commands sent to the model. Ultimately, you could close the loop with a data transmitter on board, but that is beyond the scope of this little hack...

First off, had to make a trainer cable for the RC transmitter, which would normally be used to connect a student's radio to a teacher's, in order to share control of the model. For Multiplex Royal Evos, the best info I found was here: http://www.designsoft.com.au/ahome/rc/EVOtraining/, but you should be able to google for whatever works for your radio.


Need to buffer the 5V signal from the Arduino to the 7.2V of the radio, with a transistor.


Next, made a little connector from some headers, perfboard and shrinkwrap for the Arduino end of the cable.


Quick and dirty connector at the Arduino end. Center pin not connected.


Last, needed to write some code to generate the PPM frames, which consist of a series of pulses, one per channel, rounded out by a synchronization pulse to a 20ms frame. The pulses are very similar to servo control pulses, which are a well treated subject in Arduino lore. I started out with todbot's Serial_Servo_Better (http://todbot.com/blog/spookyarduino/) and added the frame wrapper around the pulses, some dirty code to sweep values and some cryptic code comments.

The basic idea is that the Arduino is busy doing precise timing with delayMicroseconds() while the pulses are being sent, but you can do whatever you want for the remainder of the 20ms frame, i.e. the synchronization pulse. If you need more than that to get stuff done (lots of processing, or lots of channels leaving a small synch pulse), I guess this would need to be written with interrupts to make it more 'fire and forget'. Never done interrupts, so will try that later.

I've run out of characters, so I will post the code in a reply to this post.
----------
Mathieu

Syvwlch

As promised, here it is, in all its quick and dirty glory!

Code: [Select]
/*
* Trainer PPM Interface
* -------------------
*
*
* Created 31 October 2007
* copyleft 2007 Mathieu Glachant
* mathieu@ban-sidhe.com
* http://ban-sidhe.com/
*
* adapted from todbot's Serial-Servo-Better
*/

int servoPin = 12;           // Control pin for trainer interface

                            // A pulse starts with a low signal of fixed width (0.3ms),
                            // followed by a high signal for the remainder of the pulse.
                            // Total pulse width is proportional to servo position (1 to 2ms)
int pulseStart = 300;        // pulse start width in microseconds
int pulseMin = 724;          // pulse minimum width minus start in microseconds
int pulseMax = 2048;         // pulse maximum width in microseconds
int conversionFactor = 5.7;   // (pulseMax - pulseMin - pulseStart)/180

                            // A frame is a succession of pulses, in order of channels,
                            // followed by a synchronisation pulse to fill out the frame.
                            // A frame's total length is fixed (20ms)
int frameLength = 20;        // The duration in millisecs of a frame

long lastFrame = 0;          // The time in millisecs of the last frame
int channelNumber = 2;       // Number of channels to send (keep below frameLength/pulseMax)
int servo[2];                // Values to set for the servos in degrees
int channel[2];              // Values to send on channels (duration of pulse minus start, in microseconds)
int i;                       // Counter in for loop
int j = 0;                   // Counter for servo updates

void setup() {
 pinMode(servoPin, OUTPUT);  // Set servo pin as an output pin
 Serial.begin(9600);         // connect to the serial port
 for ( i = 0; i < channelNumber; i = i + 1 ) {servo[i] = 0;}
 for ( i = 0; i < channelNumber; i = i + 1 ) {channel[i] = pulseMin;}
 Serial.println("Trainer_PPM_Interface ready");
}

void loop() {
 
    // Save the time of frame start
 lastFrame = millis();

   // This for loop generates the pulse train, one per channel  
 for ( i = 0; i < channelNumber; i = i + 1 ) {
   digitalWrite(servoPin, LOW);   // Initiate pulse start
   delayMicroseconds(pulseStart); // Duration of pulse start
   digitalWrite(servoPin, HIGH);  // Stop pulse start
   delayMicroseconds(channel[i]); // Finish off pulse
 }
   digitalWrite(servoPin, LOW);   // Initiate synchronisation pulse
   delayMicroseconds(pulseStart); // Duration of start of synchronisation pulse
   digitalWrite(servoPin, HIGH);  // Stop synchronisation pulse start

   // We're done generating pulses and using delayMicroseconds()
   // Time to do some other processing before the next frame
   // How much time depends on how many channels you are running

   // Let's change the servo positions
   j=j+1;
   if (j==4) {j=0;}
   
   if  (j==0) {
     for ( i = 0; i < channelNumber; i = i + 1 ) {
       servo[i] = servo[i]+1;
       if (servo[i] >= 360) {servo[i]=0;}
     }
   }
       
   // Calculate pulse durations from servo positions                                  
 for ( i = 0; i < channelNumber; i = i + 1 ) {
   channel[i] = abs(servo[i]-180);
   channel[i] = int(channel[i]*conversionFactor)+pulseMin;
 }  
 
 //if (j==0) {
 //  Serial.println(channel[0]+pulseStart);
 //}
 
 // We're ready to wait for the next frame
 // Some jitter is allowed, so to the closest ms
 while (millis() - lastFrame < frameLength) {  
   delay(1);
 }
}
----------
Mathieu

Syvwlch

Mashed it up with some code I'd made to generate the pitch and bank angles from an accelerometer (based off of Dave Mellis Smoothing Tutorial) and I can now control two servos on the model by tilting the arduino forwards/backwards or left/right.

Also cleaned up the ugly bit where I defined 5.7 as an integer in the previous code  :-[ and made better use of defined constants in my variable declarations.

The new code doesn't fit in 5500 characters, so I can't quote it here, but I posted it here instead: http://ban-sidhe.com/blog/?p=1467
----------
Mathieu

Skylord123

Whoa, Nice job buddy  :) i like it lol. It will be more then i will ever do =D

Nice job again btw :)

Syvwlch

----------
Mathieu

Go Up