A simple demo of how to read RC signals and drive servos with Arduino

Most autopilots have the ability to read signals from a RC receiver and drive servos. For instance, in the case of ArduPilot, we read the current throttle setting when the autopilot is engaged (so we can maintain that setting, although we'll later nudge it up and down to maintain altitude) and we drive the rudder servo and ESC. In the case of BlimpDuino in RC mode, we read the throttle and left/right commands from the RC receiver and translate them into commands to the motor drivers (the vectoring thruster servo is connected straight to the receiver and doesn't have to go through the autopilot at all).

In Arduino, you read RC signals by using the PulseIn command, and you drive servos with the servo.write function added by the servo library. The problem is that PulseIn waits for the next pulse, and the more channels you're reading the more time your CPU is spent waiting. A good rule of thumb is that each PulseIn, without modification, takes about 30% of your CPU time, so you can see that about two channels is the max. However, the form of the function is pulseIn(pin, HIGH, timeout) and you can adjust "timeout" to minimize the waiting time and improve that effeciency. I've been playing around with 50000 (it's in microseconds), but your mileage may vary. Anybody having better luck with a different value?

Anyway, if you want to see how this works, here is a simple demo that just reads two RC channels and mirrors the commands with two servos. It's just like regular RC, but it's all going through the Arduino (so it can take over and go into autonomous mode anytime you want). Just read the comments in the code and plug the RC channels and servos into the right pins in your dev board (a Decimila and ProtoShield--don't forget the breadboard--work great). Here's a picture of my setup:

Views: 30898

Comment by Phil Wilshire on June 9, 2008 at 5:34am
Hi Chris,

You have hit on my current study area for this technology.
I am still only just looking since my time is limited.

The timeout for the PulseIn command just limits the time taken by the function waiting for the pulse to cycle. If the function times out it returns 0.

I am hopefully looking at the attachInterrupt(irq,func) function.

http://www.arduino.cc/en/Reference/AttachInterrupt

If you can get the service function to read a timer ( possibly one of the PWM timers if we are not going to use them).
Then we should be able to decode a single channel pulse train or even a multi
channem one with very little CPU overhead.


By the way the PWM's seem to be used for AnalogWrites to certain pins.

Anyway I am hoping to get away from the busy wait loops for Digital Input timing.

Phil




This should allow an edge on digital 0 or 1 to trigger running func.

See hardware/cores/arduino/WInterrupts.c

But you are limited in what you can do in the function.
Comment by quix-fz on June 10, 2008 at 6:15am
here's my code that reads a servo:

volatile int curinp=0; //current servo input (signal has to be on digital 2)
volatile long li; //last input-signal change
extern volatile unsigned long timer0_overflow_count;

unsigned long hpticks (void){ //für 20Mhz arduinos, sollte für die neueren passen
return (((timer0_overflow_count * 256UL) + TCNT0) * 64UL ) / (F_CPU / 1000000UL) ;
}

void setup(){
pinMode( 5, OUTPUT);
li=hpticks();
attachInterrupt(0, readservo, CHANGE);
}

void readservo(){
long c=hpticks();
if((c-li)<3000){
curinp=c-li;

digitalWrite(5, HIGH);
delayMicroseconds(curinp); //drive servo on pin5. you could modify this value
digitalWrite(5, LOW);
}
li=c;
}

i think i have hpticks() from this site... rest is mine. you should be able to read the complete ppm signal of all channels (called sum-signal?) this way.

3D Robotics
Comment by Chris Anderson on June 10, 2008 at 6:42am
quiz-fx,

Thanks for the cool code--I'll benchmark that when I get back to my hardware. But when you say that the incoming RC signal has to be on digital 2, do you mean that you can't read multiple channels simultaneously this way? The main point of our exercise is to find a way to read multiple channels (at least 2 or 3) efficiently.
Comment by Phil Wilshire on June 10, 2008 at 7:15am
Hi Chris,
Thanks quiz-fx for the code.

I have one comment.

Is it possible to read a TCNT0 value just after the TC0 overflow interrupt has been
triggered but before the overflow count has been updated in the ISR.

This will mean that the overflow value may not have been updated when we read both values. This will cause a jump back in time.
I have not studied the architecture too much to see if this is possible.

On to the multiple channels question.

You can read the multiplex pulse train on digital 2 and use a long pulse value to reset a counter.

As each pulse arrives below the sync pulse length save the value in an array indexed by the counter then increment the counter.

Also,

In the example, the digital pulse output is inside the interrupt routine.

It may be a good idea
to put that in a loop outside the ISR but you will have to watch out for updates to the incoming pulse values caused by the interrupt.

So thinking on the fly... read into an input array and update an output array after the long sync pulse.

The pulse outputs will be read from the output array.

This code is a great help. Thanks


Phil


I guess you could turn individual incoming channels into a multiplex signal using an or gate.
Comment by Phil Wilshire on June 10, 2008 at 7:16am
Last lines
For some reason I get extra lines in my comments !!
Phil

3D Robotics
Comment by Chris Anderson on June 10, 2008 at 7:55am
Phil,

>On to the multiple channels question, You can read the multiplex pulse train on digital 2
>and use a long pulse value to reset a counter.

That works if you're willing to hack your RC receiver to get the PPM signal, as per this post, but what if you just want to read straight PWM, one channel per pin? Is there a way to extend this technique to multiple digital pins?

My instinct is to try PulseIn(pin, HIGH, [some small timeout number) and replace any returned 0s (timed out) with the last known good value. (Typical RC pulses are from 1200 to 2500 so 0 should never be a valid result.) That should minimize waiting time and I'll optimize the timeout value to sample often enough.
Comment by quix-fz on June 10, 2008 at 2:17pm
getting the raw ppm signal would be the best way.

i just had the idea:

you could connect both servo-signals to pin2 and adjust the above code! since the ppm decoder in the receiver is usually a simple shifter, the signals will come one after the other... separated by a short 0.3ms pause, if the channels are next to eachother.

could be tricky to keep them apart, but doable. they'll always come in the same order. and if they are next to eachother, after the two pulses there will be a longer pause!!

3D Robotics
Comment by Chris Anderson on June 10, 2008 at 9:38pm
quix-fz,

That's a cool idea. Let me try both ways and benchmark them...I'll let you know how it goes.

3D Robotics
Comment by Chris Anderson on June 10, 2008 at 11:20pm
Using a line like:

if (channel1value == 0) {channel1value = lastgood1;} else {lastgood1 = channel1value;}

I can use timeouts of between 15000 and 20000 in the PulseIn function and read two channels simultaneously. But only a 10% gain in performance (ie, still losing about 60% of CPU time to PulseIns). Next will try the interrupt technique.
Comment by quix-fz on June 14, 2008 at 6:28pm
i was experimenting a bit with my code. the servo glitched slightly every 3-4 seconds. the problem is that delaymicrosecond will disable interupts, which means that the measurements will be way off... no matter if i do it inside the interupt or inside loop (every 20ms).

i looked through the atmel docs, and it seems that it should be possible to setup a interupt that would handle the (multiple) servo driving (next to the existing interupt for reading).

you would set the pulse-length as timer-value, and set to decrement, when zero, the interupt is called, where you set the current servo-pin to low, set the timer-value for the next servo and its pin to high... count-down to zero, interupt is called, current servo-pin to low, timer-value for next servo, pin high, etc....

any comments on this?

Comment

You need to be a member of DIY Drones to add comments!

Join DIY Drones

© 2017   Created by Chris Anderson.   Powered by

Badges  |  Report an Issue  |  Terms of Service