Uno timer2-interrupt formula to calculate frequency

Hi everybody,

does there exist a simple formula to calculate the calling-frequency of a timer-interrupt that is triggered by using timer2 based on this timer-mode

  TCCR2A = 0;// set entire TCCR2A register to 0
  TCCR2B = 0;// same for TCCR2B
  TCNT2  = 0;//initialize counter value to 0
  
  // turn on CTC mode
  TCCR2A |= (1 << WGM21);
  // Set CS21 bit for 8 prescaler
  TCCR2B = (1<<CS21); 
  // enable timer compare interrupt
  TIMSK2 |= (1 << OCIE2A);
  OCR2A = 16;

This time I'm not interested in diving very deep into all the details about prescalers, different timer-modes etc. etc. etc.

So if somebody wants to post a link to something like "explain the details of the AVR-timers"
please don't post.

I am aware of that one simple formula will not be able to create all frequencies. I don't need them all.
A certain set with gaps inbetween will be totally suffcient.

So something like
calling-frequency = ..... Clockfrequency / (prescaler * OCR2A-value) + - * whatDoIKnowForfactors
will be sufficient

I'm vey curious if the experts can avoid pointing to the big "This explains and covers it all timer-modes and calculations-tutorial"

best regards Stefan

Just think of it this way

Get first the clock speed of the MCU (8 MHz or 16 MHz, or anything in between)

Divide that with the pre-scaler (the CSxx bits in TCCR2B) --> this is how fast your timer count

So if you have 16 MHz and you use a pre-scaler of 64, your counter counts 250 000 per second

Then the value of TOP (depending on how you set it up), determines the frequency of the interrupt. So if your TOP value is 50 000, you would hit that five times a second

"You can count 250 000 per second but you only count up to 50 000" hence the five times a second

Hi hrzigbee,

thank you for answering.

If I try to write down a formula would this mean

for timer-interrupt-calling-frequency 5 Hz

Value for OCR2A = 16.000.000 / (64 * 5 Hz) = 50.000 resulting in coding

  // turn on CTC mode
  TCCR2A |= (1 << WGM21);
  // Set CS21 bit for prescaler 64
  TCCR2B = (1<<CS22); 
  // enable timer compare interrupt
  TIMSK2 |= (1 << OCIE2A);
  OCR2A = 50000;

???
No does not work. If a code OCR2A = 50000; I measure 1534 Hz.
I guess this is due to OCR2A beeing a 8bit or 16bit register than can't hold a value of 50.000.

So what does "TOP" mean in this case?

There are some details missing in yoru explanation.

I want to have the ISR-routine as short as possible no additional count up a variable to value x to slow down the frequency.

best regards Stefan

Timer2 is 8 bit so yeah you cant set OCR2A with 50K

The TOP value is defined by the way you setup WGM bits. In normal fast PWM mode, the TOP value if 0xFF. In other modes, you can set the TOP value to something else (which will allow you to setup different frequencies)

In CTC mode, OCR2A defines the TOP value, so assuming you set OCR2A with 100, you get a freq of

(16MHz/64) / (100) = 2500 Hz

As your TOP becomes smaller, the frequency gets higher.

Imagine you can count at 250 000 Hz but you only need to count from zero to 100, so you would hit the 100 mark 2500 times in one second

The other way to think of it is

Imagine a racetrack of length XXX. If you drive a car at certain speed, you finish one lap in a certain time. Now if you half the length of the track, in the same speed, you finish the lap in half (or you do two laps in the same time as before)

The length is your TOP, the speed is your counter speed, the number of times you finish the lap is your frequency

TOP (the value to go into OCR2A) is:
((16MHz / DesiredFrequency) / Prescale) - 1

The calling frequency is:
16MHz / (TOP+1) / Prescale

For TOP=50 (OCR2A = 50) the frequency is:
16000000 / (50+1) / 8 = 39215.686...

About 39.216 kHz

So as a formula

value of OCR2A for a given ISR-calling-frequency
OCR2A = 16.000.000 / (prescaler * ISR-calling-frequency )

whenever the result of this calculation becomes bigger than 255
the value of prescaler has to be increased until the result is smaller than 255
possible values for prescaler are
8,32,64,128,256,1024

With your example-numbers:
OCR2A = 16.000.000 / (64 * 2500 Hz) = 100

Another example for frequency 300 Hz
OCR2A = 16.000.000 / (64 * 300 Hz) = 833 too big increase prescaler
OCR2A = 16.000.000 / (128 * 300 Hz) = 417 too big increase prescaler

OCR2A = 16.000.000 / (256 * 300 Hz) = 208 smaller than 255

==> use prescaler 256 and OCR2A = 208

best regards Stefan

1 Like

That is off by 1. You need to subtract 1 from TOP after calculating the number of counts since 0 and TOP are included in the counting.

Hi John,

yes I understand. I was writing a demo-code that should be able to adapt the prescaler in a wide range to make it work.

What does the TLA "TOP" mean in this case is it an abbreviation?

I tried to shorten the code by things like (1 << prescaler + 3) and similar things but the numbers that must be stored in the TCCR2B-register can't be used in a simple (1 << prescaler + 3) for the calculation of the OCR2A value over the complete prescaler-range.

What would work is an array or an array of struct holding the values for the TCCR2B-register and for the calculation of the OCR2A-value.

I was too lazy to optimise it up to this level.
So here is a code that demonstrates the principle of using timer-interrupt on timer2 to create the step-pulses in a set and forget manner

If you see a possability to make the code more compact feel free to modify it.

// this demo-code belongs to the public domain
// this code demonstrates how to blink the  onboard-LED
// of an Arduino-Uno (connected to IO-pin 13)
// in a nonblocking way using timing based on function millis()
// and creating step-pulses for a stepper-motor "in the backround"
// through setting up a timer-interrupt through configuring timer2
// stepper-motors need a tight equalised timing except for acceleration 
// decceleration where the timing is still well defined
// 
// A T T E N T I O N !    R E M A R K
// some other libraries that make use of timer2 may conflict with this

// you may start with a frequency of 200 Hz to make sure you don't stall
// the stepper-motor with a too high starting frequency
// and then work up from there to find the stepper-frequency that still works
// reliable in start/stop-mode without acceleration/decceleration
const byte stepperPulsePin = 9;
unsigned long stepFrequency = 200;
// on fullstep-mode on a 1.8° per Step motor 800 steps means 800 / 200 = 4 full turns
// on halfstep-mode on a 1.8° per Step motor 800 steps means 800 / 200 = 2 full turns
unsigned long numberOfSteps = 600;

//nbt nonblockingtimer
boolean TimePeriodIsOver (unsigned long &expireTime, unsigned long TimePeriod) {
  unsigned long currentMillis  = millis();
  if ( currentMillis - expireTime >= TimePeriod )
  {
    expireTime = currentMillis; // set new expireTime
    return true;                // more time than TimePeriod) has elapsed since last time if-condition was true
  }
  else return false;            // not expired
}
unsigned long BlinkTimer = 0;
const int     onBoardLED = 13;

unsigned long DebugTimer = 0;

volatile long  HighCounter = 0;
volatile long  StepCounter = 0;
volatile boolean CreateStepSignal = false;

void setupTimerInterrupt(unsigned long ISR_call_frequency) {
  const byte Prescaler___8 = (1 << CS20);
  const byte Prescaler__32 = (1 << CS21) + (1 << CS20);
  const byte Prescaler__64 = (1 << CS22);
  const byte Prescaler_128 = (1 << CS22) + (1 << CS20);
  const byte Prescaler_256 = (1 << CS22) + (1 << CS21);
  const byte Prescaler1024 = (1 << CS22) + (1 << CS21) + (1 << CS20);

  const unsigned long CPU_Clock = 16000000;
  const byte toggleFactor = 2;

  unsigned long OCR2A_value;

  cli();//stop interrupts

  TCCR2A = 0;// set entire TCCR2A register to 0
  TCCR2B = 0;// same for TCCR2B
  TCNT2  = 0;//initialize counter value to 0

  TCCR2A |= (1 << WGM21); // turn on CTC mode
  TIMSK2 |= (1 << OCIE2A); // enable timer compare interrupt

  // the prescaler must be setup to a value that the calculation
  // of the value for OCR2A is below 256
  TCCR2B = Prescaler___8;
  OCR2A_value = (CPU_Clock / ( 8 * ISR_call_frequency * toggleFactor) )  - 1;

  if (OCR2A_value > 256) {  // if value too big
    TCCR2B = Prescaler__32; // set higher prescaler
    OCR2A_value = (CPU_Clock / ( 32 * ISR_call_frequency * toggleFactor) )  - 1;
  }

  if (OCR2A_value > 256) { // if value too big
    TCCR2B = Prescaler__64;// set higher prescaler
    OCR2A_value = (CPU_Clock / ( 64 * ISR_call_frequency * toggleFactor) )  - 1;
  }

  if (OCR2A_value > 256) { // if value too big
    TCCR2B = Prescaler_128;// set higher prescaler
    OCR2A_value = (CPU_Clock / ( 128 * ISR_call_frequency * toggleFactor) )  - 1;
  }

  if (OCR2A_value > 256) {  // if value too big
    TCCR2B = Prescaler_256; // set higher prescaler
    OCR2A_value = (CPU_Clock / ( 256 * ISR_call_frequency * toggleFactor) )  - 1;
  }

  if (OCR2A_value > 256) {   // if value too big
    TCCR2B = Prescaler1024;  // set higher prescaler
    OCR2A_value = (CPU_Clock / ( 1024 * ISR_call_frequency * toggleFactor) )  - 1;
  }

  OCR2A = OCR2A_value; // finally set the value of OCR2A

  sei();//allow interrupts
}


void setup() {
  //set pins as outputs
  pinMode(stepperPulsePin, OUTPUT);
  Serial.begin(115200);
  Serial.println( F("Setup-Start") );
  delay(500);
  pinMode(onBoardLED, OUTPUT);
  digitalWrite(stepperPulsePin, LOW);
  digitalWrite(onBoardLED, LOW);

  setupTimerInterrupt(stepFrequency);
}//end setup

ISR(TIMER2_COMPA_vect) {

  if (CreateStepSignal) {
    if (StepCounter > 0) {
      if (HighCounter < 2) {
        digitalWrite(stepperPulsePin, HIGH);
      }
      else {
        HighCounter = 0;
        digitalWrite(stepperPulsePin, LOW);
        StepCounter--;
      }
      HighCounter++;
    }
    else // StepCounter was counted down to 0
    {
      CreateStepSignal = false;
    }
  }
}


void loop() {
  //Blink onboard-LED (IO-püin 13) at 1 Hz
  if ( TimePeriodIsOver(DebugTimer, 500) ) {
    digitalWrite (onBoardLED, !digitalRead(onBoardLED) ); // Blink OnBoard-LED
    Serial.println( F("Steps left to go ") );
    Serial.println(StepCounter);
  }

  // start new step-Pulse-train every x milliseconds
  if ( TimePeriodIsOver(BlinkTimer, 4000) ) {
    Serial.println("testing set and forget step-output");
    if (StepCounter <= 0) {
      StepCounter = numberOfSteps;
      CreateStepSignal = true;
    }
  }
}

best regards Stefan

That is what the datasheet calls the value. If it is an abbreviation or initialism they don't explain. I think it is 'top' and they chose to use all-caps.

This topic was automatically closed 120 days after the last reply. New replies are no longer allowed.