Random jitter problem capturing R/C servo signal using interrupts/16bit timer

FIXED: http://arduino.cc/forum/index.php/topic,160938.msg1204942.html#msg1204942

(OSEPP Mega 2560 R3 board)

I'm new to Arduino, so please excuse my ignorance... but, I need a bit of help or understanding on this one. I'm well versed in PIC micro controllers, though not so much in AVR's or higher-level languages. Most of my work has been in assembly, so I hope my code style doesn't offend anyone. :blush:.

I have a need to capture and manipulate an R/C servo signal (just one channel) at a stable 1us resolution. The pulseIn() is not close enough. So, being experienced in PICs, I thought I'd give the interrupts and a 16-bit timer a go...

/* 
   *********************************************************************
   * 4/15/13 NoRoHS                                                    *
   * For Mega 2560 R3 (OSEPP) - 5,950 bytes used                       * 
   * R/C signal applied to pin 21 (int.2), timer5 used                 *
   * Results: tmr_val ='s PWM in 1us resolution (1000us to 2000us)     *
   *********************************************************************
*/    

  word tmr_val;                                 // Captured PWM in 1us
  volatile word first_tmr_val;                  // First timer5 read
  volatile word second_tmr_val;                 // Second timer5 read

void setup(){
  attachInterrupt(2, cap_pwm_start, RISING);    // Prime interrupt looking for first PWM
  pinMode(21, INPUT);                           // Redundant - automatically an input
  TCCR5A = 0x00;                                // Force normal counting mode (needed... why????)
  TCCR5B = 0x01;                                // Turn Timer5 ON (no prescaling)
  Serial.begin(9600);                           // Start debug
}

void loop(){
  while (digitalRead(21));                      // Wait while incoming PWM is high
  tmr_val = second_tmr_val - first_tmr_val;     // Calculate captured PWM time
  tmr_val = map(tmr_val,16095,31945,1005,1995); // Map PWM to 1000us-2000us (YTV Servo Cycler proofed)
  Serial.println(tmr_val);                      // Print captured PWM value
  while (!digitalRead(21));                     // Wait while incoming PWM is low
}

void cap_pwm_start(){                           // ISR - PWM RISING
  first_tmr_val = TCNT5;                        // First read of timer5 (on-the-fly)
  attachInterrupt(2, cap_pwm_end, FALLING);     // Start looking for end of PWM
}

void cap_pwm_end(){                             // ISR - PWM FALLING
  second_tmr_val = TCNT5;                       // Second read of timer5 (on-the-fly)
  attachInterrupt(2, cap_pwm_start, RISING);    // Start looking for next PWM
}

I'm getting what I consider to be 99% accurate and usable data, but completely at random, I'm getting odd data values. These can be large enough to ruin the 1us accuracy I'm looking for.

I've written several interrupt driven programs trying to find a resolution and they all are showing the same problem. The code here is only one of them. I've tried (what seems like) 20 variations and countless "under the hood" debugs, like eliminating the Serial function and replacing it with an LED to indicate anomalies, I've tried battery driven to eliminate the USB, different interrupt ports, a single attachInterrupt() using the CHANGE type, using different timers, stopping the timer to read/write/clear timer data, using an R/C transmitter/receiver instead of the servo driver (Spektrum DX6i & AR6115e) and I even used my oscilloscope's calibration pulse... they all respond the same way and at the same scale - random jitter in the data. BTW, the oscilloscope's best resolution to look at the jitter from the servo driver shows no movement at all with a 5us per division setting. I believe it to be rock-steady.

This occurs on average once per second, but entirely at a random rate. They may only be a few cycles apart, or as long as several seconds apart. The following are cut/paste snippets captured from the serial monitor. The data is updated at the frame rate of every 20ms. I can get 30 to 100 (or even 200) updates clean and stable, then something like this just shows up:

Results as the program is written:
...
1534
1534
1534
1534
1534
1530 <<< Problem
1534
1534
1534
1534
1534
1534
...

Results without any mapping: (raw Timer5 data)
...
24572
24575
24574
24575
24574
24573
24573
24482 <<< Problem
24574
24575
24575
24575
24574
24577
24572
...

My best guess (if it's "under the hood") is some unknown interrupt overhead that's happening between the interrupt detection and executing the "TCNT5" reads. This I could understand, but the randomness has my head scratching.

I might just be spoiled by the rock-steady interrupts on the PIC. Should I be expecting this random error or should I do something different to steady the data?

Thanks in advance for taking the time to read all this. :slight_smile:

Gary

NoRoHS:
My best guess (if it's "under the hood") is some unknown interrupt overhead that's happening between the interrupt detection and executing the "TCNT5" reads. This I could understand, but the randomness has my head scratching.

You are 1/3 correct. The culprit is not unknown nor is it random. The interrupt service routine that handles millis and its ilk is very likely interferring.

The solution is to use "Input Capture". One (or both) of these libraries from Paul Stoffregen should make an excellent starting point...
http://www.pjrc.com/teensy/td_libs_FreqCount.html
http://www.pjrc.com/teensy/td_libs_FreqMeasure.html

I might just be spoiled by the rock-steady interrupts on the PIC.

Implying AVR interrupts are not "rock-steady" which is simply wrong.

I meant no disrespect. The PIC's I work with (16's) are way simpler then these. It's proving to be a steep learning curve. I'm just doing the best I can.

I thought I avoided the millis() issue by killing the serial functions and going to an LED to flash ON as out-of-range data was found. I guess I didn't avoid it after all.

I spent the day trying to figure out the ins and outs of the capture feature, but it got to be too much for me. Thanks for the links and your input. The links should help.

Gary

NoRoHS:
I thought I avoided the millis() issue by killing the serial functions and going to an LED to flash ON as out-of-range data was found. I guess I didn't avoid it after all.

Shutting off timer 0 stops the interrupts...

TCCR0B = 0;

I spent the day trying to figure out the ins and outs of the capture feature, but it got to be too much for me.

Post questions.

I vaguely recall this subject coming up previously on this forum. You may find what you need with a bit of help from Google. This looks promising...
https://www.google.com/search?q=read+rc+signal+arduino&oq=read+rc+signal

Thanks for the links and your input.

You are welcome.

Hi,

Depending on the RC Technology you are using, there is also a degree of variation from the transmitter/receiver

Duane B

rcarduino.blogspot.com

Thank you! This saved me a full day's poking around trying to find the problem. I'm very grateful for that.

I added the above line to the code and it's now rock steady. I'll be sure to turn it back on as needed. Thankfully, the rest of the code does not anticipate needing timer0. I'm just reading the receiver channel and a sensor, performing some PID and then outputting. The reason for moving to the AVR and Aduino was that the PID code in PIC assembly was getting out of hand.

Running and working code for 1us resolution R/C PWM reads:

/* 
   *********************************************************************
   * 4/17/13 NoRoHS                                                    *
   * For Mega 2560 R3 (OSEPP) - 5,950 bytes used                       * 
   * R/C signal applied to pin 21 (int.2), timer5 used                 *
   * Results: tmr_val ='s PWM in 1us resolution (1000us to 2000us)     *
   *********************************************************************
*/    

  word tmr_val;                                 // Captured PWM in 1us
  volatile word first_tmr_val;                  // First timer5 read
  volatile word second_tmr_val;                 // Second timer5 read

void setup(){
  attachInterrupt(2, cap_pwm_start, RISING);    // Prime interrupt looking for first PWM
  pinMode(21, INPUT);                           // Redundant - automatically an input
  TCCR0B = 0x00;                                // Shut off timer0 (stop interrupt to clean up time samples)
  TCCR5A = 0x00;                                // Force normal counting mode (needed... why????)
  TCCR5B = 0x01;                                // Turn Timer5 ON (no prescaling)
  Serial.begin(9600);                           // Start debug
}

void loop(){
  while (digitalRead(21));                      // Wait while incoming PWM is high
  tmr_val = second_tmr_val - first_tmr_val;     // Calculate captured PWM time
  tmr_val = map(tmr_val,16095,31945,1005,1995); // Map PWM to 1000us-2000us (YTV Servo Cycler proofed)
  Serial.println(tmr_val);                      // Print captured PWM value
  while (!digitalRead(21));                     // Wait while incoming PWM is low
}

void cap_pwm_start(){                           // ISR - PWM RISING
  first_tmr_val = TCNT5;                        // First read of timer5 (on-the-fly)
  attachInterrupt(2, cap_pwm_end, FALLING);     // Start looking for end of PWM
}

void cap_pwm_end(){                             // ISR - PWM FALLING
  second_tmr_val = TCNT5;                       // Second read of timer5 (on-the-fly)
  attachInterrupt(2, cap_pwm_start, RISING);    // Start looking for next PWM
}

DuaneB:
Hi,

Depending on the RC Technology you are using, there is also a degree of variation from the transmitter/receiver

http://rcarduino.blogspot.ae/2012/03/reading-from-rc-reveiver-do-you-need.html

Duane B

rcarduino.blogspot.com

Thanks Duane. Good information.

  TCCR5A = 0x00;                                // Force normal counting mode (needed... why????)

The Arduino Core initializes all timers (except 0) to Phase Correct PWM. The relevant code is here (init)...
https://github.com/arduino/Arduino/blob/master/hardware/arduino/cores/arduino/wiring.c#L188

When re-purposing a timer, it's a good idea to completely reinitialize it (for that timer, set every register to some value).

Thanks for the explanation and information. :slight_smile:

In the two weeks I've been working with Arduino, it's becoming clear it's more than just a breakout board. This, I didn't know before plopping down my debit card. I've been very pleased to see so many people involved and sharing. Help like yours just might keep this from becoming another dust-bunny on the shelf (God knows I've got enough of them :blush:).

The head scratching over the TCCR5A was solved by just searching and reading about someone else having the same problem. It was without explanation. My //Comment was a reminder for me to look into it more. I'm glad you took the time to share. Maybe someone else will find all this helpful too.