Time issues with software-PWM (interrupts with CTC mode)

Hi,

I’m working on a solution that allows a higher resolution for the pulse width of a PWM than that offered by the PWM standard libraries (analogWrite) and by the Servomotor library (Servo::writeMicroseconds). My goal is to control an ESC as accurately as possible with an Arduino (even if eventually the resolution of my current ESC is smaller - Arduino is not just about to reach a solution, but also enjoying along the path).

The basic concept is to work with interrupts generated by Timer1 (16 bits resolution) in CTC mode and prescaler = 1. As the ATmega328 chip clock is 16MHz, this timer can perform an interruption within 4.096ms after the start of the counter - it’s OK with the objective of the project because the desired pulse width is between 1ms and 2ms. The maximum theoretical resolution time interrupt is 1/16 microsecond (or 62.5 nanoseconds, the microcontroller clock width). Of course, that’s assuming the microcontroller clock is “perfect”.

Inside the function called by interrupt, the OCR1A register is updated with the number of ticks that must be fired the next interrupt - this is OK since the next desired interruption is not very close, the idea is to stay within the interruption for a few hundred Instructions (a few microseconds).

Currently I do not have an oscilloscope, it would be the perfect tool to measure the pulse width generated. Thus, I used the Timer0 and Timer2 together to generate a “virtual 16-bit counter,” which measures how many clocks pass between two interruptions. To perform this “16-bit timer”, we must simply set the Timer2 prescaler for 1 and the Timer0 prescaler to 256, and to synchronize the starting point of both. The increment Timer0 must occurs exactly when the Timer2 reach the value 0x00.

My problem (after four paragraphs, I finally got it!) is that the pulse width measurement is not always equal. In over 70% of my samples, the width measured in clocks is equal to the expected or only 1 clock different, but in some cases the difference may reach 14 clocks. My question is:

A) if the clock of all timers processor are equal,
B) and if the mechanism that calls the interruption is deterministic and always take the same number of clocks,
C) and always the same instructions are executed to capture the time information from my “virtual 16-bit timer”,

… then I would expect that 100% of times the counting of “time” (in fact, clocks) between the interrupt calls were equal. Or should not I? Does anyone have any idea what could cause the variation of up to 14 clock cycles in the calls to interrupts? Or is there some flaw in the methodology for measuring clocks? The same instructions asssembly not always take the same number of clocks to be completed?

Already, I thank everyone who took the time to read this post.

// IMPORTANT: ONLY WORKS WITH ATMega328 (Arduino UNO R3)!

#define PWM_PERIOD (64000)         // PWM period, in clocks (= 4ms at 16MHz)
#define PWM_DUTY   (16000-1)         // pulse width, in clocks (= 1ms at 16MHz)

// this code will never be preempted: only called inside interruption code,
// that cannot be preempted by another interruption
int get_time()
{
  byte high = TCNT0;       // prescaler of timer0 = 256 (MSB of 16-bits virtual counter)
  byte low = TCNT2;        // could be late: after copy of TCNT0, TCNT2 "overflows" and back to 0x00 and beyond. 
  
  if (low < 0x40)          // assumption: time to copy TCNT0 register is lower than 64 clock cycles.      
  {
    high = TCNT0;          // assumption: the "if"-test + another copy of TCNT0 register < 192 clock cycles.
  }
  
  int now = (high << 8) + low;
  now &= 0x0000FFFF;       // Maybe could be best written. Just ensures value is inside [o,65535] 

  return now;  
}

void setup()
{
  Serial.begin(9600);
  pinMode(2, OUTPUT);

  TCCR1B = B00001000;    // [x] No "filter" to ICP1 (external interruption)
                         // [x] Edge direction for pin ICP1 (external interruption)
                         // [x] Reserved
                         // [01] CTC-mode (first two bits of "0100")
                         // [000] Timer disabled.
  TCCR1A = B11001000;    // [11] OC1A=1 quando TCNT1=OCR1A
                         // [00] Disconnect OC1B comparison
                         // [10] Force compare OCR1A?
                         // [00] CTC (last two bits of "0100")
                         
  TIMSK1 = B00000010;    // Disables interruptions by pin ICP1 (external), by comparasion with OCR1B register e by overflow. 
                         // Interrupts only when TCNT1=OCR1A 

// Creating a 16-bits timer from two 8-bit timers.
// Maybe we could connect the external clock pin of the HIGH timer to the external pin of the LOW timer (and enables overflow-interruption)
// But then we must measure the signal propagation time on the wire.

  TCCR0A = B00000000;    // disable all interruptions for timer0 and timer2
  TCCR2A = B00000000;    // disable all interruptions for timer0 and timer2
  TCCR0B = B00000000;    // disable all interruptions for timer0 and timer2
  TCCR2B = B00000000;    // disable all interruptions for timer0 and timer2

  TCNT2 = 0x12;          // timer2 initial value, just a "trial and error" estimative.
  TCNT0 = 0x00;          // seta valor inicial
 
  TCCR2B = B00000001;    // prescaler 1;
  TCCR0B = B00000100;    // prescaler 256. when TCNT2 overflows, TCNT0 must increment

  // If the TCNT2 estimative is not accurate with your AVR compiler, don't worry. if TCNT0 increments
  // when TCNT2 is 0XFF and not when TCNT2 = 0x00, 1/256 of time measures will hava a +256 clocks error (in advance).
  // Just drop time interval measures with magnitude of 256 cycles (the interruption precision is dozen of times better). 
  
  byte oldSREG = SREG;
  cli();                 // technically, all interruptions are disabled now, but it's a good practice.
  OCR1A = PWM_DUTY;      // 1 ms
  SREG = oldSREG;
  //sei();

  TCCR1B &= B11111000;    // timer1 disabled
  PORTD  |= B00000100;    // switch on pin 2 (Arduino UNO R3 only!)
  TCCR1B |= B00000001;    // timer1 enabled, prescaler = 1 (16MHz)
}

volatile unsigned long lastPulseTime = 0;
volatile unsigned long lastPulseDuration = 0;

SIGNAL(TIMER1_COMPA_vect)
{
  PORTD ^= B00000100;      // flips pin 2 state

  int now = get_time();  
  if (now < lastPulseTime) now += 65536;    // when time wraps.
  
  int duration = (now - lastPulseTime);  // save the last 
  duration &= 0x0000FFFF;

  // DEBUG code: just saves the interval between to interrupt calls between the duty cycle
  if (OCR1A == PWM_DUTY)
  {
    lastPulseDuration = duration;
  }
  else
  {
    lastPulseTime = now;     // update the time to the next interval calculation
  }

  OCR1A = PWM_PERIOD - OCR1A;    // assumption: current OCR1A is large enough
                                 // to do not trigger while we are inside this interruption
}

void loop()
{
  unsigned long x = lastPulseDuration;  // OK, critical session protection needed.
                                        // but lastPulseDuration is not a CONTROL variable of PWM, it is just
                                        // a variable with DEBUG purpose. If a interruption occurs while copying memory,
                                        // just drop off invalid data (it will occurs very very rarely).
  Serial.println(x, DEC);
  delay(250);          // 1000ms (default prescaler of timer0 is not 256)
}

P.S.: Sorry, my English sucks! And sorry if this is not the right forum section to post doubts like this.

Forget it.

http://www.atmel.com/Images/doc8020.pdf

Atmel uses a similar approach to soft-PWM. The explanation for jitter is the current instruction must be completed BEFORE interruption occurs (I was thinking the current instruction was aborted and then re-executed after interrupt code returns).