Arduino-Controlled RC Transmitter

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: $25 Head-Mounted Display | Jake of All Trades.

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 (Spooky Projects – Introduction to Microcontrollers with Arduino – todbot blog) 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.

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

/*
 * 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);
  }
}

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: ban-sidhe.com - This website is for sale! - ban sidhe Resources and Information.

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

Nice job again btw :slight_smile:

Thank you :slight_smile:

Awesome! :smiley: I've been looking for something exactly like this.

I'll be expanding on your brilliant code.

What I want to do is use the wii nunchuck to control one of my helicopters.

On my futaba running mode 2.

The wii nunchuck tophat for cyclic, channels 1 and 2.

The nunchuck C and Z buttons for channel 3(throttle)

Tilt from the nunchuck for channel 4(rudder)

I'm already able to read in the nunchuk data, now to pair the two!!!

I have been playing around with this kind of thing myself. You are a brave man if you will risk your helicopter to nunchuck control. The challenge you will find is that it is very difficult to sense how to physically hold the nunchuck so that the inputs are zeroed. Any slight tilt of the nunchuck causes gravity to change one or more of the outputs (even when I think I am holding the nunchuck in a fixed position) You can't relay on the orientation of the heli to provide feedback because there is always a lag in the helis response (particularly if it has fixed pitch rotors, which it sounds like your is).

In short, it is very very much easier to overcontrol using accelerometer sensor input then it is using a conventional stick.

But, it would be great if you can overcome these obstacles so please post your progress. But be careful.

When you have your project working, try plugging the arduino output into a simulator that takes trainer port input, so you can see what its like before risking life and limb and your helicopter.

Good luck.

Aballen, thanks! That's exactly the kinda thing I wrote this for :slight_smile:

Mem, from my limited experience with helicopters on FMS, I see your point... however, it shouldn't be too hard to include a dead zone and some exponential either in the code on the Arduino or in the radio if it allows for that on student inputs, which should help considerably. Also, the arduino could provide feedback on the control position either via rumbling, tone/pitch or anything else you add to the circuit.

Lastly, he's only planning to have the rudder on the tilt, which seems to me to be the lowest risk choice here, not the cyclic.

In any case, I look forward to hearing about it!

Which all reminds me... if you have one of those usb thingies to plug your radio into a computer as a joystick for a simulator (like FMS mentioned above), the arduino with the same code, plugged into it, would let you try your control scheme in the safety of the virtual world.

Bound to save you time and money as you iron out the kinks.

This would also be a good quick 'n dirty way to make your arduino speak USB-joystick to your computer, by the way...

Nice code! I'm building my way up to telepresence on a crawler bot, so I may end up using it as well - please keep us posted on this COOL project!

I wanted to add to Aballen - it takes a brave soul to put an RC heli under microcontroller control. Wear your armor.

I'm new to the arduino and I think I basically understand how this is working, however I do not understand how he is controlling multiple channels on the transmitter from a single output on the arduino? Are the Pulse widths sent out in series?

If anyone could take a second to explain how that works I would really appreciate it.
Thanks,
Casey

Yes, that's exactly what I'm doing. :wink:

In essence, the radio transmitter is modulating the carrier signal with a series of servo pulses, channel one first, then two, three, etc... End with a synch pulse that is recognizably different from a servo pulse to keep everyone's understanding of which channel is which, and you're all set.

The receiver demodulates to get the series of pulses, then sends the pulse in channel one to the servo connected to output one, and so on.

All the trainer interface does is substitute the pulses for certain channels from the teacher's radio for the ones from the pupil's. Replace the pupil's radio with an arduino and all it takes is a single signal to pass signals for 12 or more servos to the teacher's radio.

Wow,
A thousand thanks matt,

I think I've got a better understanding,

http://www.mftech.de/ppm_en.htm

Was also helpful,
Just a few other questions, if you have the time.

Are all transmitters designed to operate with a 20 millisecond frame? (it seems like they are working with a 22.5 millisecond frame in the above link)

If I only need to address 3 of the 4 channels in my transmitter (I'm working with a Futaba T4YF) does the protocol require me to have 4 pulses within each frame or can I only encode the first three and ignore the 4th?

This is the schematic for the controller jack on the transmitter I'm using, Can I just connect the arduino output pin to pin 1 on the transmitter and the arduino ground to pin 2 on the transmitter? Or does the output of the arduino need to be altered before it gets to the transmitter?

Thanks again,
Casey

I think most transmitters and receivers are actually quite flexible on how long the frame is. They seem to care more about finding a synch pulse after some reasonable length of time, but that length of time is not crucial as long as it somewhat close to 20ms. Your mileage may vary depending on hardware, of course.

I'm also pretty sure that once you have given all the pulses (or channels) that you care about, you can omit any others up to whatever the capacity of the transmitter or receiver is. In my case, if I only want to send channels one and two, I just wait for the end of the frame and give the synch pulse. My Royal Evo transmitter and my Futuba receiver don't seem to mind.

Now, the tricky bit is the hardware connection between the radio and the arduino. This is where you could, conceivably, do some permanent damage if you connect wires the wrong way, and you'll notice that in the case of the Multiplex radios like mine, you have to add a transistor between the two. From what I dimly remember reading on other radios, I think this is required only for Multiplex, tho, and it's not for protection, it's just to keep the signal high enough so that the radio can detect it.

I strongly recommend reading up as much as you can find on the web on the particularities of your transmitter and its trainer interface... I don't have any experience with the Futuba radios, for example. :stuck_out_tongue: If you're nervous about your radio, perhaps you can find a cheap, non-programmable radio that accepts pupil input over a trainer interface from the same brand, and try it first with that hardware?

If you do get it to work with a Futaba radio, the next chap who's trying to do this will appreciate it if you post the details of the connection here, of course. :wink:

Ok, I have it from an experienced RC source that the Futaba buddy system is 0 volts for Low and 5 volts for High, However anything over 4 volts will be considered a High pulse. I haven't tested this yet but I will later this week and report back. However this should line up well with the arduino output capabilities.
Thanks again Matt for all you help.
Casey

OK, Just got the arduino to talk to my rc set up,

Which consists of a Futaba T4YF transmitter and an Alpex APR-4FM receiver.
I hooked up the trainer output from the Transmitter to the oscilloscope program (the electroacoustic Toolbox from faber acoustical) and discovered that ppm was essentially the same as what Matt described with slightly different values.

For some reason the PPM was inverted on the Oscilloscope but I just tweaked the Arduino until I had some thing that matched the signal coming out of the transmitter.

So the Futaba T4YF transmitter uses a frame length of 18 milliseconds, a 400 microsecond low pulse before each of the high pulses and for me the high pulses were 1050 microseconds for dead center.

So this code keeps everything in the center

int outPin = 12;                 // digital pin 12

void setup()
{
  pinMode(outPin, OUTPUT);      // sets the digital pin as output
}

void loop()
{
  digitalWrite(outPin, LOW);    // sets the pin off
  delayMicroseconds(400);        // pauses for 400 microseconds
   digitalWrite(outPin, HIGH);   // sets the pin on
  delayMicroseconds(1050);        // pauses for 1050 microseconds      
  
  digitalWrite(outPin, LOW);    // sets the pin off
  delayMicroseconds(400);        // pauses for 400 microseconds 
   digitalWrite(outPin, HIGH);   // sets the pin on
  delayMicroseconds(1050);        // pauses for 1050 microseconds      
  
  digitalWrite(outPin, LOW);    // sets the pin off
  delayMicroseconds(400);        // pauses for 400 microseconds 
   digitalWrite(outPin, HIGH);   // sets the pin on
  delayMicroseconds(1050);        // pauses for 1050 microseconds      
  
  digitalWrite(outPin, LOW);    // sets the pin off
  delayMicroseconds(400);        // pauses for 400 microseconds 
   digitalWrite(outPin, HIGH);   // sets the pin on
  delayMicroseconds(1050);        // pauses for 1050 microseconds
  
  digitalWrite(outPin, LOW);    // sets the pin off
  delayMicroseconds(400);        // pauses for 400 microseconds 
   digitalWrite(outPin, HIGH);   // sets the pin on
  delayMicroseconds(1050);        // pauses for 1050 microseconds         
  
  digitalWrite(outPin, LOW);    // sets the pin off
    delayMicroseconds(400);        // pauses for 400 microseconds 
  digitalWrite(outPin, HIGH);    // sets the pin off
  delayMicroseconds(10350);        // pauses for 10650 microseconds          
}

So my question for everyone now is I'm controlling 6 of these transmitters which should be no problem with the Arduino, However, I want to have global control of the Arduino from Max/MSP. I can't use the Max/MSP message systems because the timing in Max is only millisecond (not microsecond) resolution. How can I send the Arduino global messages from Max while it keeps running its microsecond routines? Is that clear?
Thanks everyone for your help.
Casey

Thanks for putting the details on a different radio setup here! Should make lifer easier for the next person, I'm sure :slight_smile:

The signal may be reversed because, at least on US radios, you can have positive or negative shift, while on European ones, it's only negative (I think), so that may indeed happen. The Royal Evo, with US firmware, lets you choose which shift polarity you want.

As for timing, if you're only running 6 channels, you should have a fairly long synch pulse... any chance you can handle the rest of what you need to do during the synch pulse? If not, you'd have to use interrupts for the timing, at a guess, but I haven't tried that yet.

So my question for everyone now is I'm controlling 6 of these transmitters which should be no problem with the Arduino, However, I want to have global control of the Arduino from Max/MSP. I can't use the Max/MSP message systems because the timing in Max is only millisecond (not microsecond) resolution. How can I send the Arduino global messages from Max while it keeps running its microsecond routines? Is that clear?
Thanks everyone for your help.
Casey

Casey, you could implement your app by sending serial packets from Max with an array of integers holding the millisecond pulse width for each of the channels for all the transmitters.

The easiest to implement on the arduino side would be to use a single packet for all the data from your six transmitters.

You could us a two dimensional array like the following or define a structure if you are familiar with that construct.

#define NBR_TRANSMITTERS 6
#define NBR_CHANNELS 4
int channelData[ NBR_TRANSMITTERS ] [NBR_CHANNELS];

In your arduino code you would wait until you had NBR_TRANSMITTERS times NBR_CHANNELS times two data bytes available and read this into the two dimensional array. (times two because there are two bytes oer integer)

You could then loop through each transmitters data to send your pulses.

If its possible that Max could send packets more quickly than the transmitters can handle ( it takes 120 milliseconds to process all the data if you handle each transmitter in sequence) there are a few things you could do. The simplest is to only process the latest data, but if throwing away older data is not acceptable then you could process all of the transmitter frames in parallel by starting pulses for each sequential channel for all transmitters at the same time and turning them off as per each transmitters received pulse time.

For example, turn on a pulse for channel 1 on all transmitters and as the time for the shortest pulse is reached, turn that one off. Wait for the next shortest pulse time to be reached and turn that one off etc until all transmitters have stopped pulsing channel 1. Then to the next channel until all channels are done.

The delay between channels will look a little different from what you observed on your transmitter but any radio control receiver will be able to cope.

Implimentation gets a little more complicated if you really do neeed millisecond accuracy because digitalWrite takes around five milliseconds to start and stop a pulse so you either need to allow for that or use the faster but more complicated direct port i/o.

Wow, thanks that is quite helpful,

I got the part about having max send the Arduino messages via the serial port. And I determined that I do indeed need to address the receivers in Parallel ( a super long sync pulse that results from serial communication results in an undesirable pulsing of the motors). And I understand conceptually how you described addressing all of the pins concurrently, however I'm have a hard time putting that into code. Would I still be using the delayMicroseconds function? Thanks
Casey