micros() goes backwards in time

Last Tuesday I watched my GPS tick backwards as a leap second was added. So I was amused today to see the micros() function do the same thing -- take a step backwards.

I am using an ATtiny167 with an 8MHz internal oscillator. I'm also using the asynchronous timer with a 32.768kHz crystal. On the 167, the asynchronous timer is timer 0, the same timer that micros() uses. So I expected micros to run slow by a factor of 8000/32.8 = 244.

Instead it ran faster than 1/244 speed and was not entirely predictable. After ruling out that the timer itself was running at anything other than ~32kHz I discovered what was happening. The function micros(), which delay() calls, was sometimes running backwards! And since delay truncates the value of micros() to 16 bits this causes delay() to count off milliseconds that had not yet passed.

Now why would micros() ever go back in time? Any guesses?

(It's in the datasheet.)

jboyton:
Any guesses?

I guess you should post your code.

...R

jboyton:
Now why would micros() ever go back in time? Any guesses?

The micros() function creates a 32-bit counter.
This counter rolls over its max value after roughly 70 minutes to 0.

This counter goes "back in time" after about 35 minutes when assigning the counter value to a "signed long", and the counter goes "back in time" after about 70 minutes when assigning the value to an "unsigned long".

When assigning the counter to a 'long', it even becomes negative after some time!

I was calling the delay() function which doesn't use a signed variable. What it does is truncate the 32-bit micros() result to an unsigned 16-bit value.

Here is the code for delay():

void delay(unsigned long ms)
{
  uint16_t start = (uint16_t)micros();
  while (ms > 0) {
    if (((uint16_t)micros() - start) >= 1000) {
      ms--;
      start += 1000;
    }
  }
}

And here is my code:

// ATtiny167
//
// 8MHz internal osciallator, divide by 8 fuse unprogrammed
// 32.768kHz crystal connected to pins 13/14 (XTAL1/2)
//
// pin 20 (PB0) connected to 1K resistor to LED to ground
//
//  pre-    ____ 32 kHz ____     ____ 8 MHz _____
// scaler  cycle    rollover    cycle    rollover
// 1       30.5us   7.8ms       125ns    32us
// 64      1953us   500ms       8us      2048us   (default system configuration)
//
// 8000/32.768 = 244

// Set to 0 for normal clocking
// Set to 1 to use 32kHz asynch clock
//
#define ENABLE_ASYNCHRONOUS_CLOCK 1

void setup()
{
  pinMode(4, OUTPUT);
 
#if ENABLE_ASYNCHRONOUS_CLOCK
  uint8_t a = TCCR0A;    // save clock control reg state
  uint8_t b = TCCR0B;
  TCCR0A = 0;            // disable clock
  TCCR0B = 0;
  ASSR = 0x20;           // enable asynchronous mode
  TCCR0A = a;            // restore control registers
  TCCR0B = b;
#endif
}

// Blink LED 10 times per second if timer is using 8MHz system source
// Blink LED once every 24.4 seconds if timer is using 32kHz asynchronous source

void loop() {
  digitalWrite(4, HIGH);
  delay(10);
  digitalWrite(4, LOW);
  delay(90);
}

You are mis-interpreting what you're seeing. micros() is certainly not "running backwards" ever, and there is nothing at all wrong with the code for delay() either. delay() is doing unsigned arithmetic to determine when 1000uS has passed, so the truncation to 16 bits is not a problem at all, and will always give the correct result.

If you add print statements to your code, so you can see the values for millis() and micros(), you'll likely figure out where the problem really is. I haven't looked at the data sheet to see exactly what your re-programming of the timer is doing, but that's where I'd bet your problem really is.

Regards,
Ray L.

So quick to come to a conclusion! Did you miss the earlier hint that the answer is in the datasheet?

The code below checks to see if micros() has gone backwards and, if so, illuminates an LED.

When using the system clock it stays dark.
When using the 32kHz asynchronous clock, it lights up immediately.

#define ENABLE_ASYNCHRONOUS_CLOCK 1

void setup()
{
#if ENABLE_ASYNCHRONOUS_CLOCK
  uint8_t a = TCCR0A;    // save clock control reg state
  uint8_t b = TCCR0B;
  TCCR0A = 0;            // disable clock
  TCCR0B = 0;
  ASSR = 0x20;           // enable asynchronous mode
  TCCR0A = a;            // restore control registers
  TCCR0B = 2;
#endif

  pinMode(4, OUTPUT);
  digitalWrite(4, LOW);
}

void loop()
{
  unsigned long t0 = micros();
  
  if (micros() < t0)
    digitalWrite(4, HIGH);
}
ASSR = 0x20;           // enable asynchronous mode

I vaguely recall it is necessary to wait for the control registers to update after enabling asynchronous mode. I don't see a while loop.

I realize that problem is unlikely to be the problem at hand but a bug is a bug.

Confirmed. You really should be waiting for the Update Busy flags to clear before leaving setup.

And magic constants like 0x20 are a bad idea. Without checking the datasheet it is impossible to know what that value means or if it is correct.

ASSR = (1 << AS0);

Thanks, Coding Badly. I am aware of that requirement. But it's not the issue.

Shall I just give it away? It's right there in the datasheet. I just didn't look. It was a puzzle that kept me busy for a while, scratching my head. I like puzzles.

Yes, please.

10.9 Asynchronous Operation of Timer/Counter0

When Timer/Counter0 operates asynchronously, some considerations must be taken....

...During asynchronous operation, the synchronization of the interrupt flags for the asynchronous timer takes 3 processor cycles plus one timer cycle. The timer is therefore advanced by at least one before the processor can read the timer value causing the setting of the interrupt flag.

This makes it possible for TCNT0 = 0 before the overflow interrupt routine updates the rollover count. So micros() miscalculates. It effectively goes backward one rollover period.

The delay() function is checking micros() like crazy; with a system clock 244X that of the timer clock there is a high probability that it will find this case. In fact, when micros() goes backwards TCNT is always zero and the step backwards is 2040 microseconds.

OK, so there is a (documented) hardware anomaly that needs to be dealt with when the counter rolls over. That is very different from your initial description of "...micros() ... running backwards...", which implies that micros actually counts backwards for some (presumably random) period of time, rather than simply that the software is not correctly handling the rollover. The fact that millis() truncates to 16-bits is a complete red herring...

Regards,
Ray L.

Huh. Interesting problem / bug.

But this only applies in asynchronous mode, right? It wouldn't normally happen?

Yes, it only happens with the asynchronous timer. If I were using an Atmega328 I wouldn't have noticed since the only asynchronous timer is timer 2. But on the ATtiny167 it's timer 0.

Is it a bug? It's documented. But yes, it seems like a flaw to me too.

Do I need micros(), millis() and delay()? Maybe not. But sometimes they can be quite handy. So I will probably get them to work, somehow.

At the moment I'm not really sure how best to do that.

RayLivingston:
That is very different from your initial description of "...micros() ... running backwards...", which implies that micros actually counts backwards for some (presumably random) period of time, rather than simply that the software is not correctly handling the rollover.

I never said the timer was counting backwards. But successive calls to micros() produced values 8184 and 6144, respectively. Isn't that backwards?

Does it make sense to support micros? With the timer running at 32768 Hz the granularity is a bit course...

1000000 / 32768 = 30.517578125 µs per tick.

What's the quanta for 1-Wire? ... Looks like 15 µs with a preference for much lower. That variation of micros won't even work for 1-Wire. Why bother with it?

Can you rewrite micros() to use another timer?

void delay(unsigned long ms)
{
  uint16_t start = (uint16_t)micros();
  while (ms > 0) {
    if (((uint16_t)micros() - start) >= 1000) {
      ms--;
      start += 1000;
    }
  }
}

If that is the code which it really uses, it is very bad code. This would accumulate very large errors.

With an 8MHz system clock, micros() has an 8us resolution. Is that good enough for 1-Wire? Either way, it's useful to have a system timer function. And sometimes a resolution finer than 1ms is valuable. Why not make it available?

Another timer could be used instead. But with the '167 another timer means the other timer. Since timer 0 is dedicated to keeping precise time, albeit at a lesser resolution, it makes sense to me to figure out how to use it for this purpose.

I'm just not sure what logic to employ. I know how to fix delay() for this asynchronous bug, but not micros(). At least not without thinking about it a little more.