Jan 072010
 

I’m working on a robot for the Sparkfun autonomous vehicle contest and I need to read inputs from my RC receiver so that I can have remote control of the robot in addition to computer control. The folks over at DIYdrones had an old thread going about using the pulseIn function, but that’s plain ugly. The interrupt example in the thread uses a delay inside the interrupt routine and that’s a bad idea too.

The example below measures the pulse width of each pulse in the PWM train and passes the value along to another servo. I’m using a Mega because I need multiple UARTs and it can handle more interrupts, but this code should run on a Duemilanove with the change shown in the code.

Yes, the code will glitch every 71 minutes, but that’s OK. My batteries will be dead by then.

Here’s the code:

 // Reading servos with interrupts 
 // For an Arduino Mega 
 // Scott Harris January 2010 
 // 
 // This work is licensed under the Creative Commons Attribution-Share Alike 3.0 United States License. 
 // To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/3.0/us/ 

 #include <Servo.h> 

 volatile long servo1; // servo value 
 volatile long count1; // temporary variable for servo1 

 #define int0 (PINE & 0b00010000) // Faster than digitalRead 
 //#define int0 (PIND & 0b00000010) //For Duemilenove. Untested! 
 
 Servo myServo; 

 void handleInterrupt() 
 { 
 if(int0) 
 count1=micros(); // we got a positive edge 
 else 
 servo1=micros()-count1; // Negative edge: get pulsewidth 
 } 

 void setup() 
 { 
 Serial.begin(9600); 
 pinMode(2,INPUT); 
 attachInterrupt(0,handleInterrupt,CHANGE); // Catch up and down 
 myServo.attach(9); 
 } 

 void loop() 
 { 
 delay(10); 
 Serial.println(servo1,DEC); // Pulsewidth in microseconds 
 myServo.writeMicroseconds(servo1); // Mirror servo on another pin 
 } 
 Posted by at 1:17 AM

  11 Responses to “Reading servo PWM with an Arduino”

  1. I am trying this code with a pro mini (328) I have commented out the servo move code and am just looking at the values returned from the println. I can see 1520 in there (centered sticks) but the values alternate between it and values around 19000 and 20000. If I move the stick I can see the range from 900 to 2100 but still all the erroneous 19000 to 20000 range values. Any ideas what is causing this?

  2. I'm getting an undefined symbol error on PINE, with Aduino build 23. Any idea where this definition might have gone to?

    Which pin are you attaching this to. From the receiver, do you attach just the signal wire, or do you need to attach a ground as well?

    thanks!!

  3. PINE is only defined if you select a mega as your target board. The Duemillanove and Uno dont' have a E port.

    Here's the port mapping for Arduinos:
    http://arduino.cc/en/Hacking/Atmega168Hardware
    http://arduino.cc/en/Hacking/PinMapping2560

    The servo signal goes the the pin you've chosen and you must have a common ground between the arduino and Rx. Good luck.

  4. Thanks for a great post, Scott. For the Uno and Nano, the following change had to be made for the int0 definition:

    #define int0 (PIND & 0b00000100) //For Nano & Uno. Changed to pin D2, from D1.

    Your untested code was off by one bit location, actually calling out pin D1, rather than D2.

    After that small change, my servo was up and running.

  5. Hi,
    This code is very risky, there is no protection of the shared variable servo1 which can be updated by the ISR as loop is reading it. For an explanation and alternative approach with protection see – http://rcarduino.blogspot.com/2012/04/how-to-read-multiple-rc-channels-draft.html

    Duane B

    rcarduino.blogspot.com

  6. Yup. There's a slim chance that the value will change while you're reading it, but that didn't matter for my application. Also, the use of micros() inside of an interrupt routine is bad practice too.

    I've written a much nicer PWM decoder that fixes all of this.

    I like your blog! I've also got an original Sand Scorcher from when I was a kid.

  7. Why will the code glitch ever 71 minutes? Is there a way to fix the glitch?

  8. The counter used for the micros() command is 32 bits. 2^32/10^6/60 is about 71 minutes.

    See http://arduino.cc/en/Reference/Micros

  9. Just checking, did you connect the input wire of the servo to the arduino?

  10. You might consider attaching the servo input to pin 8, which has the “input capture” capability with 16-bit timer1. Basically, if you put timer1 in free running mode, and set up input capture edge detection, the hardware will capture the 16 value of the counter register, at the time of the input edge, and additionally trigger an interrupt. That means that you do not need to worry about delay between the edge and the interrupt routine running. The timer can be set up to detect either rising or falling edges, you want both, so you want to flip the setting bit each time in the interrupt handler, to be able to capture the next transition.

    You can even capture multiple channels from an RC receiver, provided that the RC receiver (like most) uses a shift register to generate pulses for successive channels. (That is the pulse on channel 2 starts at the exact time the pulse on channel 1 ends, and so on.) By OR’ing togerther channel 1, 3 and 5, you will see a train of three pulses (channel 1, 3 and 5), and the width of the two troughs in between them will be channel 2 and 4.

 Leave a Reply

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

(required)

(required)

Prove you\'re not a spammer *