Timer2 Microsecond timing issues

Hey everyone,

I’ve started toying around with some custom PWM stuff requiring some pretty precise timing so i’ve done my best to implement a counter that increments every 4 microseconds using the Timer2 overflow flag with the help of some articles that i’ve read.

In the following code snippet my goal is to have a period of 3ms and a pulse width of 1ms on pwmOut.

The issue i’m having is that the clockOut is not triggering every 4 microseconds, instead its triggering approx every 3.250 microseconds, which is obviously throwing out the pwm signal’s timing. I have tried increasing the amount of cycles that timer2 has to count to increase this pulse width without much luck, it will either not change or wildly overshoot to a pulse width of about 4.6 microseconds or greater.

Maybe there is something obvious that i’m missing but any help would be appreciated! Thanks.

Edit: I forgot to mention, i’m using an Arduino Uno

#include <avr/interrupt.h>
#include <avr/io.h>

const byte pwmOut = B00000100; 
const byte clockOut = B00010000; 
const byte timerDefault = 247; 

volatile unsigned int microseconds = 0;   //used to keep count of how many interrupts were fired


void setup() {
  pinMode(12, OUTPUT);
  pinMode(10, OUTPUT);

  //setting up the timer
  cli(); //disable interrupts
  TCCR2B = 0x00;        //Disbale Timer2 while we set it up
  TCNT2  = timerDefault;        //Reset Timer Count to 191 out of 255, 64 cycles to count gives 4 us/trigger
  TIFR2  = 0x00;        //Timer2 INT Flag Reg: Clear Timer Overflow Flag
  TIMSK2 = 0x01;        //Timer2 INT Reg: Timer2 Overflow Interrupt Enable
  TCCR2A = 0x00;        //Timer2 Control Reg A: Wave Gen Mode normal
  TCCR2B = (1 << CS00);        //Timer2 Control Reg B: Timer Prescaler set to 128
  sei();  

}

ISR(TIMER2_OVF_vect) {

  microseconds+=4;              //Increments the interrupt counter
  PORTB ^= clockOut;        
   
  TCNT2 = timerDefault;           //Reset Timer to default
  TIFR2 = 0x00;          //Timer2 INT Flag Reg: Clear Timer Overflow Flag
}

void loop() {
    if(microseconds > 2999){ 
    
     PORTB = 1 << 2;          //digitalWrite(13,toggle);
     microseconds =0;

     //re configure clock
     TCNT2 = timerDefault;          
     TIFR2 = 0x00;          //Timer2 INT Flag Reg: Clear Timer Overflow Flag
    }
  
    if(microseconds > 999){
      PORTB = 0 << 2;
    }
}

you already have a micros() function, curtesy of the Arduino environment, why don't you use it ?

without a critical section, your loop reading and writing multi-byte variables shared with the ISR, will likely drive to disappointing results.

I don't remember why I didn't use the micros() function to be honest. I do agree that sharing variables between the loop and the ISR could cause issues; most of the contents of the loop could be moved to the ISR. However when I leave the loop function empty and just toggle clockOut from the ISR, the pulse width remains the same approx. 3.125 microseconds.

Anywho I'll try again with micros() and see if it can give me a better result.

You can also drive a pin directly from a timer and control both its frequency and duty cycle if you require a high degree of accuracy. You are then limited to specific pins, depending on which timer you use.

From Max Piersons DMX reception, the way he set up Timer2 to fire every 4us, it being a little more complex than your method, i thought i’d share

cli(); 
  
  //NOTE:  this will disable PWM on pins 3 and 11.
  bitClear(TCCR2A, COM2A1);
  bitClear(TCCR2A, COM2A0); //disable compare match output A mode
  bitClear(TCCR2A, COM2B1);
  bitClear(TCCR2A, COM2B0); //disable compare match output B mode
  bitSet(TCCR2A, WGM21);
  bitClear(TCCR2A, WGM20);  //set mode 2, CTC.  TOP will be set by OCRA.
  
  bitClear(TCCR2B, FOC2A);
  bitClear(TCCR2B, FOC2B);  //disable Force Output Compare A and B.
  bitClear(TCCR2B, WGM22);  //set mode 2, CTC.  TOP will be set by OCRA.
  bitClear(TCCR2B, CS22);
  bitClear(TCCR2B, CS21);
  bitSet(TCCR2B, CS20);   // no prescaler means the clock will increment every 62.5ns (assuming 16Mhz clock speed).
  
  OCR2A = 64;                
  /* Set output compare register to 64, so that the Output Compare Interrupt will fire
  *  every 4uS.  */
  
  bitClear(TIMSK2, OCIE2B);  //Disable Timer/Counter2 Output Compare Match B Interrupt
  bitSet(TIMSK2, OCIE2A);    //Enable Timer/Counter2 Output Compare Match A Interrupt
  bitClear(TIMSK2, TOIE2);   //Disable Timer/Counter2 Overflow Interrupt Enable          
  
  sei();                     //reenable interrupts now that timer2 has been configured.

Since this is the part of the code that is testing for a ‘break’ in this case the ISR looks like this :

ISR(TIMER2_COMPA_vect) {
  if (bitRead(PIND, PIND0)) {  // if a one is detected, we're not in a break, reset zerocounter.
    zerocounter = 0;
    }
  else {
    zerocounter++;             // increment zerocounter if a zero is received.
    if (zerocounter == 20)     // if 20 0's are received in a row (80uS break)
      {   
      bitClear(TIMSK2, OCIE2A);    //disable this interrupt and enable reception interrupt now that we're in a break.
      bitSet(UCSR0B, RXCIE0);
      }
  }
} //end Timer2 ISR

Which is of course hardly relevant to your situation. But just to mention a few things

PORTB = 1 << 2;

This is not only wasting time (if the bitshift doesn’t get optimized out), but also sets all other pins on PORTB to LOW.
And sharing a 16-bit value between the ISR and the main program should be avoided. If you want to increment a counter, make the counter 8-bit of which you use only 7, and test for the 8th bit outside of the ISR, and increment on change.

Here is another example code snippet based on something I got from Nick Gammon’s Web Site Gammon Forum : Electronics : Microprocessors : Timers and counters (which appears to be down at the moment)

It is set up to use timer1 to drive pin10 (OC1B) at around 38KHz at a 33% duty cycle. It is quite compact and runs autonomously. You can switch it on and off in the loop() by, say, switching off the pre-scaler.

You appear to want a 333Hz frequency with a 33% duty cycle, so you’d have to play with OCR1A and and the pre-scaler to adapt it.

noInterrupts();
//  OC1B must be defined as an output pin !
//  Mode 15
TCCR1A =  ( _BV(WGM10) | _BV(WGM11) ) ;
TCCR1B =  ( _BV(WGM12) | _BV(WGM13) ) ;

TCCR1A |= _BV(COM1B1) ;  // connect OC1B (pin 10) to timer


OCR1A = ( F_CPU / CarrierFrequency ) - 1  ;    //  /1 prescaler  - for around 38KHz
OCR1B =  ( OCR1A / 3 )    ;           //   25 % duty cycle  to 75%

TIFR1 |= bit( OCF1B ) ;   // clear interrupt flag.
TIMSK1 |= (1 << OCIE1B);

TCNT1 = 0 ;

GTCCR = bit (PSRASY);        // reset prescaler now  ?? N. Gammon

TCCR1B |= _BV(CS10);         // prescaler /1

interrupts();

will_d:
Hey everyone,

I've started toying around with some custom PWM stuff requiring some pretty precise timing so i've done my best to implement a counter that increments every 4 microseconds using the Timer2 overflow flag with the help of some articles that i've read.

In the following code snippet my goal is to have a period of 3ms and a pulse width of 1ms on pwmOut.

The issue i'm having is that the clockOut is not triggering every 4 microseconds, instead its triggering approx every 3.250 microseconds, which is obviously throwing out the pwm signal's timing. I have tried increasing the amount of cycles that timer2 has to count to increase this pulse width without much luck, it will either not change or wildly overshoot to a pulse width of about 4.6 microseconds or greater.

Maybe there is something obvious that i'm missing but any help would be appreciated! Thanks.

Yes, I don't believe you can achieve 4µs per interrupt on an Uno, its not fast enough
(timer0 interrupts are also happening with higher prorioty so you are probably
getting ISR's running back-to-back and not letting the main program progress
if you do try this - the timing won't be accurate if so.

You are also trying to do this in a way that can never be frequency stable.

You need to set the timer into a mode where TOP can be specified as 0x3F, then use
overflow interrupts to trigger at a rock solid 250kHz (though I doubt this can be
handled at that rate). Don't monkey with TCNT2, let the hardware do the work - you
were expecting the time delay between interrupt condition arising and the assignment
to TCNT2 in the ISR happening to be constant - its not, since interrupts cannot happen
on every clock (some instructions are multi-clock).

Also the "microseconds += 4;" won't necessarily take a constant time to execute either on
an 8-bit micro - basically never rely on instruction timings, they are sensitive to many
things (such as compiler version, optimization settings, etc).

From what I recall timer2 is quite limited in its options but I think there is a fast-PWM
mode where ICR2 is used to set TOP, that's the one to go for.

Edit: I forgot to mention, i'm using an Arduino Uno

One other little trick that might help if you are short of CPU cycles. You can replace

  PORTB ^= clockOut;

by the equivalent (but faster)

  PINB = clockOut;

This is a hardware feature of the pad drivers on the ATmega processors.

Thank you for all your help guys! The frequency and duty cycle does change dependant on other factors but all of your suggestions and tips should get me going in the right direction. I appreciate all the help.