Interrupt jitter

I am working on code to create very precise PWM pulses using an Atmega328P on an Arduino Nano. I'm using timer 1 and the output compare in an ISR to make my PWM timing.

I have my example mostly working, except it has a little jitter. I initially suspected it was the millis() ISR, but disabling timer 0 did not make the jitter go away.

So my questions are:

  1. Are there any other ISRs running in the background by default?
  2. Does setting TCCR0A/B to 0 disable the millis interrupt?

Here is my code:


#define sig_pin1 10  //set PWM signal output pin on the arduino
#define onState 1  //set polarity of the pulses: 1 is positive, 0 is negative

int pulse_length_min = 1000;
int pulse_length_max = 2000;
int pulse_length = 1500;
int okay_to_do_i2c = true;
int period = 20000;
int timeout_count = 0;
int start_watchdog=false;

#define PULSE_SCALER 2;

//Calling address 0 will cause device to reset.
void (*resetFunc)(void) = 0;

void setup() {
  // put your setup code here, to run once:
  pinMode(sig_pin1, OUTPUT);

  cli(); 
  TCCR1A = 0; // set entire TCCR1 register to 0
  TCCR1B = 0;
  
  TCCR0B = 0; // disable millis() background ISR
  TCCR0A = 0; // disable millis() background ISR

  OCR1A = 100;  // compare match register, change this
  TCCR1B |= (1 << WGM12);  // turn on CTC mode
  TCCR1B |= (1 << CS11);  // 8 prescaler: 0,5 microseconds at 16mhz
  TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt
  sei();
}

void loop() {
  start_watchdog=true;

  //Wait for "okay_to_do_i2c to avoid interrupting PWM ISR. To avoid jitter due to i2c ISR.
  while (!okay_to_do_i2c){}
  
  while (okay_to_do_i2c){}
  timeout_count=0;
}

ISR(TIMER1_COMPA_vect){
  static boolean state = true;
  okay_to_do_i2c=false;

  TCNT1 = 0;

  if (state) {  //start pulse
    digitalWrite(sig_pin1, onState);
    OCR1A = pulse_length * PULSE_SCALER;
    state = false;
  } else{  //end pulse and calculate when to start the next pulse
    digitalWrite(sig_pin1, !onState);
    OCR1A = (period - pulse_length)*PULSE_SCALER;
    state = true;
  }
  //if something went awry with the I2C in loop, we can trigger a reset.
  if (start_watchdog){
    timeout_count += 1;
    if (timeout_count > 10){
      resetFunc();
    }
  }
}

Of course I found my issue a minute after posting, lol. My little "watch dog" was triggering. I forgot to set my "okay_to_do_i2c" flag back to true at the end of my ISR. Oops. Perfectly rock-solid PWM now.

Thanks

Actually my "watchdog" idea is more broken than I realized. I'm gonna move it to timer 2 like a normal watchdog would be, rather than try to hack it into timer 1.

can you quantify this?
and how accurate do you need the PWM to be?

Quantifying using an oscilloscope. I want submicrosecond resolution so I can fully test the resolution of some servos.

Basically I'm making a servo tester to test them before they go into an RC plane.

Btw I reenabled millis last night. Need to look closer, but it did not appear to be causing jitter. May just leave it disabled anyway. The source of my "jitter" was primarily the "watchdog".

Some of the things I said last night may not be right. It was late and I was typing faster than I was thinking, lol.

For example I may need to reevaluate whether millis was really introducing any jitter into the signal, and how I want my watchdog to work.

Do you take into account the resolution of your oscilloscope?

I am not below the resolution of my scope.

many have complained about servo jitter. it's not obvious to me what the cause is. i've always assumed it's the electronics and pot in the servo. i guess you're thinking its the jitter in the pulse with.

since an RC servo pulse is from 1 - 2 msec, sub-usec resolution suggests < 0.1 accuracy. assuming the 180 deg < 0.18 deg resolution.

my observations is jitter is a few deg.

The digitalWrite() function adds unnecessary latency, use direct port manipulation instead. Or, EVEN BETTER, use the time modes that allow the hardware to autonomously toggle an output pin on compare match with no software involvement.

I've thought about it, and find it generally not worth it to do direct port manipulation. I'm more concerned about repeatability of the pulse width, not 100% accuracy. Digitalwrite has consistent latency.

Guys, I've solved my problem, you can stop suggesting solutions :stuck_out_tongue:

If you're curious what the servos are going into: 48" MXS-EXP V2 Plus-Green

why are you putting an Arduino in the loop?
aren't you using a transmitter with joysticks?

and most importantly - without interruptions. If you want fast and accurate gpio control - do not use interrupts, the entry time of which depends on the system load

It's not in the loop. I'm making a servo tester to (stress) test on the ground before putting them in the plane.

A servo tester is a device I should have gotten years ago. I've seen the ones commercially available and haven't been happy with them, so I'm making my own.

Interrupts can be used along with the hardware-controlled pin change feature. Inside the ISR you can adjust the time for the next pin change event as long as you make it relative to the time that the hardware captured. This way pin change times are always based on hardware events and aren't effected by ISR entry latency.

1 Like

Entry time isn't something I'd fully considered. By system load do you mean, any interrupts in higher priority, or something else?

Entry time is partially affected by the number of clock cycles the current instruction needs to complete execution, before going to the ISR, right?

So that may introduce a few cycles of jitter since some of the instructions I'm using in my "while" loop aren't going to have consistent execution time.

If I need more resolution than I can get now, setting the timer to change the pin would offer as much precision as the atmega can provide, period. Thanks for the suggestion.

Btw I was looking through the docs, I didn't know this chip had a built in watchdog. I'm going to set that up instead of the nonsense I'm doing now.

Yeah that makes sense. Thanks!

The hardware will capture the timer value at the occurrence of the input pin trigger condition (+/- some number of clock cycle variation that you can find in the datasheet). At approximately the same time, the interrupt is queued. So, the latency getting into the ISR is irrelevant as long as you do all your timing based on the captured value.

The latency mattered to my original implementation, not the one you are recommending.

i tried running a timing loop using micros(). and got following results (i realize you may be interested in configuring processor registers to control the pulse width)

  90  0
  91  0
  92  0
  93  0
  94  0
  95  0
  96  0
  97  0
  98  0
  99  0
  100  14519
  101  0
  102  0
  103  0
  104  2617
  105  0
  106  0
  107  0
  108  720
  109  0
  110  0

Code:

void
setup (void)
{
    Serial.begin (9600);
}

unsigned cnt [150];

void
loop (void)
{
    unsigned long usecLst = micros ();
    unsigned long usec;

    while ((usec = micros()) - usecLst < 100)
        ;
    cnt [usec - usecLst]++;

    if (10000 <= ++cnt [0])  {
        for (int i = 90; i <= 110; i++)  {
            Serial.print ("  ");
            Serial.print (i);
            Serial.print ("  ");
            Serial.print (cnt [i]);
            Serial.println ();
        }

        cnt [0] = 0;
    }
}