R/C PPM out "dithering"

Hi all, I’m new to the forum but have been playing with Arduino for a few years now and really having fun with them.

My latest project is building my own model airplane transmitters using Arduino and a 2.4 ghz transmitter module.

I’ve coded up a couple of these, one for me and one for a buddy. He’s discovered that there’s a bit of “jitter” or variation in the timing of the output pulse train.

What we’ve found is that if the code is reduced to just the interrupt setup and run code, with the array values all set between 1000 and 2000, the output is very consistent.

But, if I add in the loop() code to read a set of analog and digital inputs, corresponding to the pots and switches for control, then the output pulse timing seems to be variable - resulting in small random-looking movement. This is visible with a servo driver that has a pulse meter on it.

As I mentioned, these changes are small - if the meter is reading 1531 it might jump around from 1530 to 1532.

Again, this jumping doesn’t happen if the code to update the channels array values with input from the analogRead() calls is commented out.

I’ve verified that the analogRead() values are very steady by maintaining a previous read value and only updating the array if it’s changed - and outputting to the serial monitor the change. I’m getting no output, when the pots are left alone, yet the dithering of the output pulses still happens.

On old Boeing engineer described encountering this issue years ago due to the variable amount of time needed to save machine state before entering an ISR - depending on where the code was being interrupted more or less time might pass between the time the interrupt compare register triggered the invocation and the ISR actually occurred. This lead to inaccuracies in the timing of ISR entry.

His solution was to set the interrupt to occur slightly before the calculated desired time and then, in the ISR, “creep up” on the desired time by checking the clock. On Arduino we can’t do that because all interrupts are turned off before and ISR is invoked so calls to micros() would not return increasing values.

So - to my question: Are we expecting too much accuracy here? Is the output pulse is 1000μs to 2000μs with a +/-1 “as good as it gets”? Can we improve this to the point that better accuracy is possible?

Thanks in advance for any and all input…

-Eric

Globally defined is an array of pulse position values:

#define channel_number 8

int channels[channel_number];

setup() contains the following code:

  cli();
  TCCR1A = 0;
  TCCR1B = 0;
  
  OCR1A = 100;
  TCCR1B |= (1 << WGM12);
  TCCR1B |= (1 << CS11);  // 8 prescaler: 0,5 microseconds at 16mhz
  TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt
  sei();

and the ISR looks like:

ISR(TIMER1_COMPA_vect) {  //leave this alone
  static boolean state = true;
  
  TCNT1 = 0;
  
  if(state) {  //start pulse
    digitalWrite(ppm_out_pin, onState);
    OCR1A = PPM_PulseLen * 2;
    state = false;
  }
  else{  //end pulse and calculate when to start the next pulse
    static byte cur_chan_numb;
    static unsigned int calc_rest;
  
    digitalWrite(ppm_out_pin, !onState);
    state = true;

    if(cur_chan_numb >= channel_number){
      cur_chan_numb = 0;
      calc_rest = calc_rest + PPM_PulseLen;// 
      OCR1A = (PPM_FrLen - calc_rest) * 2;
      calc_rest = 0;
    }
    else{
      OCR1A = (channels[cur_chan_numb] - PPM_PulseLen) * 2;
      calc_rest = calc_rest + channels[cur_chan_numb];
      cur_chan_numb++;
    }     
  }
}

Please read this before posting a programming question then paste the whole program or a shorter but complete one that exhibits the problem so that it can be seen in context.

ericjohndustrude:
As I mentioned, these changes are small - if the meter is reading 1531 it might jump around from 1530 to 1532.

On a system running two interrupts (Arduino default is Timer0 for millis(), micros() etc.) and a user-defined Timer1 interrupt, the theoretical jitter for your Timer1 interrupt is 4 microseconds as the Timer0 interrupt runtime is around 4µs and this is the time by which your Timer1 interrupt may be delayed in case that two interrupts have to run at the same time (which is not possible).

If you actually see the servo jitter, the real time jitter is much(!) higher than 4µs.

I don't know, how your "pulse meter" device is measuring and displaying values, but most likely this "pulse meter" will only show low-pass filtered values, that are averaged over more than one pulse.

ericjohndustrude:
Again, this jumping doesn't happen if the code to update the channels array values with input from the analogRead() calls is commented out.

Of course, wrong handling of the channels array messes everything up.

One mistake with the channels array is visible in the few lines of code which you posted, it is the wrong declaration of the array:

int channels[channel_number];

Correct declaration for a variable that is accessed from "normal code" as well as from "interrupt handler code" would be to declare the variables "volatile". Fixed declaration:

volatile int channels[channel_number];

But possibly this is not the only mistake in your code, and the next mistake will also add up inaccuracy: As the elements of your array are 2-byte "int" values, you will have to use "atomic write" to those int values in the array. Explanation can be found here:

On your Arduino, the RAM is "shared memory", which is shared among "normal code" and "interrupt handler code". If you change a two-byte "int" within normal code, it is possible that an interrupt is occurring after the high-byte in the int has been written a new value, before the low-byte is written to the int. If you don't take precautions that the assignment of a new value to an int is locked as "atomic write", your interrupt routine may see a completely different value than you wanted.

Thank you jurs! You shed some light with the "two byte atomic write" comment. I really appreciate it.

I had tried the array declaration with "volatile" and without - both had the same behavior.

I'll post back when I find the culprit.

-Eri