best way to read a servo signal on arduino

the atmega168 on the arduino has a timer1 which has input capture capability (see this application note and datasheet for atmega168 for details). using it, it's possible to do the pulse timing in completly in hardware (no errors, no jitter), and having a interupt readout the measured time.

here's the code:

unsigned int serinp[8]; //servo positions

void setup_timer1(){
//disable all interupts
TIMSK1 &= ~( _BV(TOIE1) | _BV(ICIE1) | _BV(OCIE1A) | _BV(OCIE1B));

//set timer mode
TCCR1A &= ~( _BV(WGM11) | _BV(WGM10) );
TCCR1B &= ~( _BV(WGM12) | _BV(WGM13) | _BV(ICNC1));

//capture raising edge
TCCR1B |= _BV(ICES1); //capture raising edge

//prescaler 1/8
TCCR1B |= _BV(CS11);
TCCR1B &= ~( _BV(CS12) | _BV(CS10) );

//disable outputs
TCCR1A &= ~( _BV(COM1A0) | _BV(COM1A1) | _BV(COM1B0) | _BV(COM1B1));

//enable capture interupt
TIMSK1 |= (1ICIE1);

static unsigned int lasticr; //icr at last caputre
static unsigned char cserinp; //current input servo

unsigned int licr;

//TCCR1B ^= _BV(ICES1);

if(licr>5000){ //pulse too long, means start of new frame
}else if(licr>1000){ //pulse good, take reading, go to next channel
//too short, nothing to see here

void setup()
pinMode( 8, INPUT ); //sum

void loop()
//use serinp!!!

it'll take a sum signal on pin 8. you can also connect the signal for a single servo, then just serinp[0] will be populated. the values in serinp are 2 times the microseconds, so between 2000-4000 (instead of 1000-2000), yes, there's extra accuracy in there!

the timer-capture only works on digital pin 8!

if you don't have access to the sum signal, a possible way to read multiple servos would be to connect them all to pin 8. since the receivers usually output the pulses one after the other, this should work (you'll have to adjust the code in the capture-interupt).

Views: 9223

Comment by PD on December 20, 2011 at 4:35am

I want to read the 8 channel PPM Signal from an ACT Rx receiver using an arduinoMega. Can I try the code above?

Comment by PD on December 20, 2011 at 6:26am

Now I use the APM_radio code as suggested by Chris. I connect the PPM signal to the input pin 49 of the arduino Mega board and it works fine.

Comment by Hami Sunny on April 1, 2012 at 4:34am

Hello Everyone!

I have to measure the pulse width of very short pulses which I am giving to arduino from function generator with 1 Hz freq, the pulses are between 5 to 15 microseconds. I am using arduino nano board and here is my code which I am using but its not giving me good results so if someone can help me out here I would be grateful, here is my code

#define ICP PINB0
//Variables holding three timestamps
  volatile uint16_t ov_counter, rising, falling;
//capture Flag
  volatile unsigned int flag = 0;
  volatile uint32_t counts;
  volatile uint16_t time;
  int val = 0;     // variable to store the read value
  int lastPulse = LOW;
  int count = 1;
//Initialize timer
  float pulse_width= 0.0;

//capture ISR

//if (digitalRead(8) == HIGH)

if ((PINB & B00000001) == B00000001)


            //save start time


            //set to trigger on falling edge

             TCCR1B &= 0xBF;
            //reset overflow counter





            //save falling time

            TCNT1 = 0;
            //rising edge triggers next
            TCCR1B |= 0x40;
            counts=(uint32_t)falling-(uint32_t)rising + (uint32_t)ov_counter;
            /*you can convert coutns to seconds and send to LCD*/
//Overflow ISR
//increment overflow counter

void setup()
  Serial.begin(9600) ;
//Set Initial Timer value
//First capture on rising edge
TCCR1B|=(1ICES1)| (1CS10) | (1ICNC1);
//TCCR1B|=(1ICES1)| (1CS10);
//Enable input capture and overflow interrupts
//Enable global interrutps

void loop()

   val = digitalRead(8);   // read the input pin
            if (val != lastPulse)
               lastPulse = val;
              if (val == LOW)    
             //   cli();
                pulse_width = counts*4;
                Serial.print("width = ");
                Serial.print("  count = ");
                                //clear overflow counters;

                //clear interrupt flags to avoid any pending interrupts
            //    TIFR1=(1ICF1)|(1TOV1);
                //enable input capture and overflow interrupts
             //   TIMSK1|=(1ICIE1)|(1TOIE1);
           //   highCounter++;
            //    sei();



Comment by Randy Perkins on December 5, 2012 at 5:25am

Thanks for the post and code.  Just joined to comment that I had a couple small issues getting it to compile on arduino.  Did a search on 1ICIE1 and at least one other person had trouble.

--- these error --

best_way_to_read_a_servo_signal_on_arduino.cpp:25:12: error: invalid suffix "ICIE1" on integer constant
best_way_to_read_a_servo_signal_on_arduino.cpp:53:1: error: expected initializer before ‘pinMode’
best_way_to_read_a_servo_signal_on_arduino.cpp:54:15: error: expected constructor, destructor, or type conversion before ‘;’ token
best_way_to_read_a_servo_signal_on_arduino.cpp:55:1: error: expected declaration before ‘}’ token

---were corrected with this patch ----

--- best_way_to_read_a_servo_signal_on_arduino.ino.orig 2012-12-05 06:55:05.934622586 -0500
+++ best_way_to_read_a_servo_signal_on_arduino.ino 2012-12-05 06:56:45.926621361 -0500
@@ -19,7 +19,7 @@
TCCR1A &= ~( _BV(COM1A0) | _BV(COM1A1) | _BV(COM1B0) | _BV(COM1B1));

//enable capture interupt
-TIMSK1 |= (1ICIE1);
+TIMSK1 |= _BV(ICIE1);

@@ -47,6 +47,7 @@

void setup()
pinMode( 8, INPUT ); //sum


basically it was one typo ( or a lack of a macro on my part), and one missing opening brace.

I'm just getting started so the errors could be on my end, but these changes worked for me.

thanks again for the code


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

Join DIY Drones

© 2019   Created by Chris Anderson.   Powered by

Badges  |  Report an Issue  |  Terms of Service