Pages: [1]   Go Down
Author Topic: Accurate millis at 20 MHz?  (Read 3223 times)
0 Members and 1 Guest are viewing this topic.
Global Moderator
Dallas
Offline Offline
Shannon Member
*****
Karma: 212
Posts: 13085
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset


Running your AVR processor at 20 MHz?  millis not quite right?  This may help.

The changes are based on the Arduino 0022 Core.  This is the original code...

Code:
// the prescaler is set so that timer0 ticks every 64 clock cycles, and the
// the overflow handler is called every 256 ticks.
#define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(64 * 256))

// the whole number of milliseconds per timer0 overflow
#define MILLIS_INC (MICROSECONDS_PER_TIMER0_OVERFLOW / 1000)

// the fractional number of milliseconds per timer0 overflow. we shift right
// by three to fit these numbers into a byte. (for the clock speeds we care
// about - 8 and 16 MHz - this doesn't lose precision.)
#define FRACT_INC ((MICROSECONDS_PER_TIMER0_OVERFLOW % 1000) >> 3)
#define FRACT_MAX (1000 >> 3)

volatile unsigned long timer0_overflow_count = 0;
volatile unsigned long timer0_millis = 0;
static unsigned char timer0_fract = 0;

SIGNAL(TIMER0_OVF_vect)
{
// copy these to local variables so they can be stored in registers
// (volatile variables must be read from memory on every access)
unsigned long m = timer0_millis;
unsigned char f = timer0_fract;

m += MILLIS_INC;
f += FRACT_INC;
if (f >= FRACT_MAX) {
f -= FRACT_MAX;
m += 1;
}

timer0_fract = f;
timer0_millis = m;
timer0_overflow_count++;
}

unsigned long millis()
{
unsigned long m;
uint8_t oldSREG = SREG;

// disable interrupts while we read timer0_millis or we might get an
// inconsistent value (e.g. in the middle of a write to timer0_millis)
cli();
m = timer0_millis;
SREG = oldSREG;

return m;
}

The changes are highlighted...

Quote
#if (F_CPU == 16000000L) || (F_CPU == 8000000L)

// the prescaler is set so that timer0 ticks every 64 clock cycles, and the
// the overflow handler is called every 256 ticks.
#define MICROSECONDS_PER_TIMER0_OVERFLOW (clockCyclesToMicroseconds(64 * 256))

// the whole number of milliseconds per timer0 overflow
#define MILLIS_INC (MICROSECONDS_PER_TIMER0_OVERFLOW / 1000)

// the fractional number of milliseconds per timer0 overflow. we shift right
// by three to fit these numbers into a byte. (for the clock speeds we care
// about - 8 and 16 MHz - this doesn't lose precision.)
#define FRACT_INC ((MICROSECONDS_PER_TIMER0_OVERFLOW % 1000) >> 3)
#define FRACT_MAX (1000 >> 3)

typedef uint8_t timer0_fract_t;

#elif (F_CPU == 20000000L)

#define MILLIS_INC (0)

#define FRACT_INC (512)
#define FRACT_MAX (625)

typedef uint16_t timer0_fract_t;

#endif



volatile unsigned long timer0_overflow_count = 0;
volatile unsigned long timer0_millis = 0;
static timer0_fract_t timer0_fract = 0;

SIGNAL(TIMER0_OVF_vect)
{
  // copy these to local variables so they can be stored in registers
  // (volatile variables must be read from memory on every access)
  unsigned long m = timer0_millis;
  timer0_fract_t f = timer0_fract;

/* rmv: The code below generates considerably less code (emtpy Sketch is 440 versus 418)...

  m += MILLIS_INC;
  f += FRACT_INC;
  if (f >= FRACT_MAX) {
    f -= FRACT_MAX;
    m += 1;
  }
...rmv */

  f += FRACT_INC;

  if (f >= FRACT_MAX)
  {
    f -= FRACT_MAX;
    m = m + MILLIS_INC + 1;
  }
  else
  {
    #if MILLIS_INC != (0)
    m += MILLIS_INC;
    #endif
  }

  timer0_fract = f;
  timer0_millis = m;
  timer0_overflow_count++;
}

micros, delay, delayMicroseconds will not work correctly at 20 MHz.  This change only corrects millis.

At the time of this post, the code is completely untested.

Questions?  Does the code work for you?
« Last Edit: August 25, 2011, 05:44:11 pm by Coding Badly » Logged

United Kingdom
Offline Offline
Tesla Member
***
Karma: 227
Posts: 6639
Hofstadter's Law: It always takes longer than you expect, even when you take into account Hofstadter's Law.
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

Hi,

I haven't tried your code, but I have one comment and one query:

  • It appears to me that the original code should work with any clock frequency in a reasonable range, whereas your code only works at 8, 16 and 20MHz. As a minimum, I would add "#else <newline> #error This code only works with F_CPU = 8000000, 16000000 or 20000000" before the first #endif in your code.
  • What is the reason that the original code isn't accurate enough? Could it be improved so that it is more accurate, while still retaining the ability to work with any reasonable F_CPU?
Logged

Formal verification of safety-critical software, software development, and electronic design and prototyping. See http://www.eschertech.com. Please do not ask for unpaid help via PM, use the forum.

Massachusetts, USA
Offline Offline
Tesla Member
***
Karma: 212
Posts: 8975
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

It appears to me that the original code should work with any clock frequency in a reasonable range,

The interrupt interval has to be a multiple of 8 microseconds or the >>3 (/8) in the original code will throw away data.  The /8 scaling was apparently done to fit the microsecond part of the accumulator into one byte.  It works out nicely for 8 MHz and 16 MHz so it worked for Arduino but not so much at other clock rates.  I think 1 MHz, 2 MHz, 4 MHz and 24 MHz will also work.

There are other places in the wiring.c and wiring.h code where you get wrong answers if you use a clock rate other than 8 MHz and 16 MHz.
Logged

Send Bitcoin tips to: 1L3CTDoTgrXNA5WyF77uWqt4gUdye9mezN
Send Litecoin tips to : LVtpaq6JgJAZwvnVq3ftVeHafWkcpmuR1e

Global Moderator
Dallas
Offline Offline
Shannon Member
*****
Karma: 212
Posts: 13085
View Profile
WWW
 Bigger Bigger  Smaller Smaller  Reset Reset

It appears to me that the original code should work with any clock frequency in a reasonable range

The reasonable range is 1, 2, 4, 8, and 16 MHz.  The code produces an inaccurate millis (and micros) at all other clock frequencies.

Quote
whereas your code only works at 8, 16 and 20MHz

By design.  It's meant solely as a test case for someone with a 20 MHz board.

Quote
As a minimum, I would add "#else <newline> #error This code only works with F_CPU = 8000000, 16000000 or 20000000" before the first #endif in your code.

As folks test it and provide feedback, I will fill it out (e.g. try to fix micros) and add support for more clock frequencies (and a trap for unsupported frequencies as you suggested).

Thank you for the feedback!

Quote
What is the reason that the original code isn't accurate enough?

#define FRACT_INC ((MICROSECONDS_PER_TIMER0_OVERFLOW % 1000) >> 3)

The assumption is that FRACT_INC is a whole number.  At 16 MHz, the actual value is exactly 3.  At 20 MHz, the actual value is 102.4.  The 0.4 is truncated away leaving a 0.39% error.

Quote
Could it be improved so that it is more accurate, while still retaining the ability to work with any reasonable F_CPU?

I believe making the fraction variables 16 bit would eliminate the error for 20 MHz but there is not a general purpose solution that addresses all clock frequencies.
Logged

Left Coast, CA (USA)
Offline Offline
Brattain Member
*****
Karma: 362
Posts: 17307
Measurement changes behavior
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

Quote
I think 1 MHz, 2 MHz, 4 MHz and 24 MHz will also work.

Cool, lets shoot for 24Mhz, I'll just put a ice pack on the chip.  smiley-wink

Kidding aside, I bet overclocking at 24Mhz on a 328p @ 5vdc would work OK. Heck look at how many people run at 16Mhz @ 3.3vdc and get away with it.


Lefty


« Last Edit: August 29, 2011, 03:55:08 pm by retrolefty » Logged

Portugal
Offline Offline
God Member
*****
Karma: 6
Posts: 962
View Profile
 Bigger Bigger  Smaller Smaller  Reset Reset

I also have read several times people using their AVR's at 32Mhz with no problems at all.
Logged

Pages: [1]   Go Up
Jump to: