Timer 0 prescaler and millis()

In the arduino playground, it states that a prescaler of 1 on timer 0 results in a 62.5kHz PWM frequency which affects millis(), micros() etc functions.
And then it states that the default setting is roughly 1000 millis() for 1 sec and 64000 millis() for 1 sec with a prescaler of 1. Why is it not exactly 62500 millis() for 1 sec?

Also, given that with prescaler 1, the ISR fires every 16uS, instead of every 1.024mS using the default, won't that make any sketch (much) slower?

alex_poupakis:
In the arduino playground, it states that a prescaler of 1 on timer 0 results in a 62.5kHz PWM frequency which affects millis(), micros() etc functions.
And then it states that the default setting is roughly 1000 millis() for 1 sec and 64000 millis() for 1 sec with a prescaler of 1. Why is it not exactly 62500 millis() for 1 sec?

The ISR for the millis timer is expecting the interrupt at 976.5625Hz, or once every 1.024mS, with the prescaler set to 64. The ISR makes an allowance for the extra 24uS and at intervals will increment millis by 2 instead of 1.

When the prescaler is changed to 1, the interrupt occurs at a rate of 62,500Hz, or once every 16uS, which is 64 times faster than the default. The ISR is still making the allowance for the fractional difference between 1mS and 1.024mS, with the result being that over 62500 interrupts millis will increase by 64000. Mathmatically, 976.5625 / 1000 is the same ratio as 62500 / 64000.

won't that make any sketch (much) slower?

The interrupt probably can't be processed every 16 us, in which case Arduino would not do anything.

Try it and let us know what happens.

@david_2018 silly me for missing that ::slight_smile:

@jremington with an 18 cycle overhead (The overhead of Arduino Interrupts | Bill Grundmann's Blog) and a few cycles for the timer0 default ISR code, lets say 30 cycles total, the difference between the two settings would be (64-1)prescalers * 30 cycles * 62.5 ns/cycle = 118uS.

In other words, where with the default 64 prescaler, the ISR would take (roughly) 0.18% of the time every second, a prescaler of 1 would take 11.7% of the time every second.

Test that guess and let us know the results.

jremington:
The interrupt probably can't be processed every 16 us, in which case Arduino would not do anything.

It certainly depends on the sketch and what else like analogRead(), or i2c, or Serial printing is going on.

I have run accelerated testing of a non blocking time based controller with the Timer0 prescaler set to 1. It can be useful technique for zipping through 24 hours to validate a complex schedule. In that case, I did not find that the interrupt latency of the Timer0 OVF was an issue.

Nice trick, thanks!

Well, I run the following test: Had the Arduino increase a counter up to a threashold and send the starting and finishing micros() to a Processing sketch. On the sketch, I measure the time between the first and last message and compare the Arduino duration and the Processing duration of the experiment.

Arduino code:

#define max_ticks 100000

long ticks = 0;

void setup() {
  Serial.begin(115200);

  TCCR0B = TCCR0B & B11111000 | B00000001;    // set timer 0 divisor to 1 for PWM frequency of 62500.00 Hz (64000 millis() = 1 sec)
}

void loop() {
  ticks++;

  if (ticks == 1 || ticks == max_ticks){
    Serial.println(micros());
  }
  if (Serial.available()){
    char c = Serial.read();
    if (c == 'r'){
      ticks = 0;
    }
  }
}

Processing sketch:

import processing.serial.*;

Serial arduino;
int ticks = 0;
long tstart = 0, sys_tstart, tend, sys_tend;
boolean started = false, ended = false;
boolean released = true;

int max_ticks = 100000;
int divisor = 1;    // 64 for prescaler 1, 1 for prescaler 64

void setup(){
  arduino = new Serial(this, "COM7", 115200);
  arduino.bufferUntil('\n');
}

void draw(){
  if (ended){
    println("Arduino duration:", (tend-tstart)/divisor, "uS");
    println("Processing duration:", (sys_tend-sys_tstart)/1000, "uS");
    started = false;
    ended = false;
  }
  
  if (released && keyPressed && key == 'r'){
    arduino.write(key);
    started = false;
    ended = false;
    
    released = false;
  }
  if (!keyPressed){
    released = true;
  }
}

void serialEvent(Serial arduino){
  String line = arduino.readString();
  int t = 0;
  
  t = int(trim(line));
  if (!started){
    tstart = t;
    sys_tstart = System.nanoTime();
    started = true;
    println("start : "+t);
  }
  else {
    tend = t;
    sys_tend = System.nanoTime();
    ended = true;
    println("end : "+t);
  }
}

The results are:
prescaler 64 (default)
Arduino duration: 459092 uS
Processing duration: 459003 uS

prescaler 1
Arduino duration: 744341 uS
Processing duration: 744549 uS

Which is wildly slower. I hope I'm missing something in the test, otherwise... wow :stuck_out_tongue:

743341 - 459092 = 248249 microseconds time difference for 100K loop cycles.

Divide by 100K and the difference is 2.48 us per loop.

This seems reasonable for the Timer0 interrupt latency slowing loop(). I guess it all depends on what your sketch requires.

Typically if loop() times are on the order of milliseconds, the sketch is responsive and "non blocking".

Which is wildly slower.

Do you have an example of where the increased loop() time with Timer0 prescaler = 1 is affecting the performance of a sketch?

I meant wildly slower with respect to my guess. My guess was:

30 cycles/interrupt * 62.5 nS/cycle = 1.875 uS / interrupt

prescaler 64
interrupt every 1.024 mS => ~976 interrupts/sec
1 sec => 1.83 mS for timer0 ISR

prescaler 1
interrupt every 16 uS => 64000 interrupts/sec
1 sec => 120 mS for timer0 ISR

So, for the 459 mS it took with the default prescaler, shouldn't it be close to 459 + 60 mS with a prescaler of 1? With these results, either the timer0 ISR takes ~ 120 cycles, or something else is going on.

Nick Gammon says that the interrupt latency is 23 cycles to enter and 19 cycles to exit for 2.625 us independent of any code in the ISR.
https://gammon.com.au/interrupts

Nick's analysis made some assumptions about which registers were saved upon entry, and the minimum latency can be much longer than that, depending on which others are saved. Plus, there is some additional, variable latency depending on the machine state at the moment the interrupt is flagged.

For the Timer0 interrupt, you have to look at the machine code generated by the compiler to be sure.