A smaller and faster millis() and micros()?

Hi all,

Here's my attempt to make the millis and micros function smaller and faster with a fast ISR .
Please feel free to point out any oversights since I'm more of a tinkerer than a programmer.

I realize this will only work with chips that use TIFR0 since you can set OCR0A other than 255.
You also need a 16Mhz (OCR0A = 249) or 8Mhz clock (OCR0A = 124).

#IFDEF statements could make wiring.c compatible with older chips by using the original method when TIFR is used.

So here's what I did for a 16Mhz clock:

The init() function has two lines added

1- Set Timer0 to Fast PWM mode 7. It's normally set to Fast PWM mode 3. See Page 108 Table 14-8 ATMega48/88/168/328

sbi(TCCR0B, WGM02);  // Yes in TCCR0B
      sbi(TCCR0A, WGM01);
      sbi(TCCR0A, WGM00);

2- Set OCR0A to 249. Including 0 this will make 250 ticks.
16Mhz / (64 * (249 + 1)) = 1000uS per Overflow

OCR0A = 249;  // For 16Mhz clock

The ISR would now be much shorter because fractions are gone:

SIGNAL(TIMER0_OVF_vect)
        
       {unsigned long m = timer0_millis;  
      m++;
      timer0_millis = m;  }

The millis() function stays the same

The micros() function has 3 lines changed

m = timer0_millis;  // used to be m = timer0_overflow_count;

if ((TIFR0 & _BV(TOV0)) && (t < 249)) // 249 instead of 255

return ((m * 1000ul) + (unsigned int(t) * 4); // (64 / clockCyclesPerMicrosecond()) would also work in place of 4
// An 8Mhz clock would use 8 since it has a resolution of 8uS

For testing, the only thing the ISR for Timer0 did was toggle pin 13: PORTB ^= B00100000.
I checked the frequency with a frequency counter on my DMM and it was super close to calculated = 500HZ.
I am using an ATMega168 Boarduino with 16Mhz resonator.
I didn't test with 8Mhz.

Thanks for reading,

Charlie Hughes

Is there a reason the interrupt service routine can't be reduced to a simple increment...

SIGNAL(TIMER0_OVF_vect)
{
  ++timer0_millis;  
}

This will also break PWM on at least one pin, since you're now using an OCR to cause the timer interrupt, instead of "overflow."

(normally, timer0 counts continuously, flips one PWM output on OCR0A, the other PWM on OCR0B, and does the timer interrupt on overflow. A very ... well utilized ... timer!)

This will also break PWM on at least one pin ...

I would expect issues also with the second PWM for duty cycles in the 250 to 255 range. The timer will never count that high and so PWM will stop. Also any attempt to use PWM on the first channel (for timer0) will upset the millis timer (slower or faster depending on the value passed to analogWrite).

It seems like a clever rewrite, but it does not allow for millis and PWM on the same timer.

Is there a reason the interrupt service routine can't be reduced to a simple increment...

I agree. Should have seen that.

Charlie

Thanks westfw and BenF,

I figured something like this would bite me, hence the ? in the title.

I guess tone() would also be affected?

Charlie

tone uses timer2 so would not be affected.

But the impact on the timer0 PWMs seems a high price to pay for a savings of a small fraction of a percent of flash memory.

tone uses timer2 so would not be affected.

But the impact on the timer0 PWMs seems a high price to pay for a savings of a small fraction of a percent of flash memory.

Well it's accuracy is closer to expected, and the ISR is now reduced to one increment. Interrupts should be as short as possible. The flash memory savings is a bonus. You never know when you need those 50 or so extra bytes.

With that being said, I agree that breaking PWM for Timer0 isn't good. But for those who aren't going to use those features in a project, this could be used (Unsupported of course).

The tone.cpp file does use Timer0 and OCR0A but I just glossed over it.

Charlie

Hi Charlie,

Well it's accuracy is closer to expected

The millis timer accuracy seems to be determined more by the accuracy of the crystal than errors in the core implimentation. I am interested to know what practical advantage you have found for the modified millis implementation?

The tone.cpp file does use Timer0 and OCR0A ...

Tone.cpp as distributed with arduino does not use timer0.
Although there are defines for all the timers, the code only uses timer2.

Hi mem,

By more accurate I meant the ouput frequency of toggling pin13 with the above changes prooved closer to the calculated frequency of 1 / (delay*2) whether using delay() or delayMicroseconds().

PORTB = PORTB ^ B00100000;
  delayMicroseconds(25);

or

PORTB = PORTB ^ B00100000;
  delay(1);

In other words the current ISR is slower and resulted in a slower freq for my test. delayMicroseconds() doesn't use Timer0 but seems to be affected probably due to the overhead of Timer0 ISR. Also, the faster the ISR completes, the better your program will behave as expected, practical or not. But one practical application might be an intervalometer with X and Y stepping motors and LCD display.

...accuracy seems to be determined more by the accuracy of the crystal than errors in the core implimentation

The crystal or resonator accuracy makes some difference, but measuring cycles per second where each measurement's error doesn't compound the way it would for a wall clock's error after 24 hours. Maybe I'm missing something.

Although there are defines for all the timers, the code only uses timer2.

Why would all timers be defined if only timer2 is used?
If I used timer2 for something else, would tone() be unavailable?

Thanks again,
Charlie

... the faster the ISR completes, the better your program will behave as expected, practical or not.

For those applications that need to the highest available timing accuracy, the best way is to use Input Capture and not use an interrupt at all.

Why would all timers be defined if only timer2 is used?

Timers 0 and 1 are not used by default so they don't interfere with millis (on timer0) or the Servo library (on timer1) but experienced users that need to use other timers with tone can change the defines to enable this.
Bear in mind this library was origionaly implemented as a class where it was easier to add multiple tones, it became more awkward to use other timers when this code was ported into the Arduino core

If I used timer2 for something else, would tone() be unavailable?

Yes, you cannot use tone if timer2 is used for something else (unless you modify the code in tone.cpp to use a free timer)