Pages: [1]   Go Down
Author Topic: A smaller and faster millis() and micros()?  (Read 2222 times)
0 Members and 1 Guest are viewing this topic.
New Jersey
Offline Offline
Newbie
*
Karma: 0
Posts: 17
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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
  
Code:
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
  
Code:
OCR0A = 249;  // For 16Mhz clock

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

Code:
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

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


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

      
      
Code:
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
Logged

Global Moderator
Dallas
Online Online
Shannon Member
*****
Karma: 198
Posts: 12747
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset


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

Code:
SIGNAL(TIMER0_OVF_vect)
{
  ++timer0_millis;  
}

Logged

SF Bay Area (USA)
Offline Offline
Tesla Member
***
Karma: 124
Posts: 6645
Strongly opinionated, but not official!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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!)
Logged

Offline Offline
Edison Member
*
Karma: 3
Posts: 1001
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
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.
Logged

New Jersey
Offline Offline
Newbie
*
Karma: 0
Posts: 17
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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

I agree.  Should have seen that.

Charlie
Logged

New Jersey
Offline Offline
Newbie
*
Karma: 0
Posts: 17
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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
Logged

London
Offline Offline
Tesla Member
***
Karma: 10
Posts: 6250
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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.


Logged

New Jersey
Offline Offline
Newbie
*
Karma: 0
Posts: 17
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
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
Logged

London
Offline Offline
Tesla Member
***
Karma: 10
Posts: 6250
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi Charlie,

Quote
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?

Quote
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.
« Last Edit: March 29, 2010, 11:17:53 am by mem » Logged

New Jersey
Offline Offline
Newbie
*
Karma: 0
Posts: 17
Arduino rocks
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

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().  
Code:
PORTB = PORTB ^ B00100000;
  delayMicroseconds(25);
or
Code:
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.  

Quote
...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.

Quote
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
Logged

London
Offline Offline
Tesla Member
***
Karma: 10
Posts: 6250
Have fun!
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
... 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.

Quote
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
 
Quote
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)
« Last Edit: March 29, 2010, 10:51:11 pm by mem » Logged

Pages: [1]   Go Up
Jump to: