Sub milli-second timing

Hello, I'd like to use interrupts to time PWM signals. Many of you are familiar with this solution for sub-millisecond time tracking:

unsigned long hpticks(void) {
    return (timer0_overflow_count << 8) + TCNT0;
}

This does not work in Arduino 12. What is the alternative? Another thread here (search "timer0_overflow_count") suggests using millis() to solve a particular problem. I've tried combining millis() with TCNT0 but can't seem to get a reliable time. How does one measure time under 1 millisecond?

Thanks
/Daryl

How does one measure time under 1 millisecond?

There are problems with the code that you showed, not least is that it doesn't disable interrupts while it fetches the timer-related data. This can cause inconsistent and/or incorrect data elements to be used.

The code below will return a value representing the approximate number of elapsed Timer0 ticks. There are several caveats that must be observed when using it.- It utilizes implementation details that are present in wiring.c which may change in future versions.

  • The value returned will roll over every (2^32)/F_CPU/64 seconds.
  • When calling it successively to measure elapsed time, it is possible that a later call will return a smaller value than an earlier call due to the rollover mentioned above. This can be handled with a little bit of code.
  • The second term in the value of t0_ticks has no integer truncation error at 8MHz and 16MHz but will at 20MHz. This could be corrected for 20MHz by modifying the function to return a "half ticks" value instead of full ticks.
extern "C"
{
  // defined in wiring.c
  extern unsigned long timer0_clock_cycles;
  extern unsigned long timer0_millis;
};

unsigned long hpticks(void)
{
  uint16_t t0;
  unsigned long clock_cycles;
  unsigned long millis;
 
  uint8_t sreg = SREG;
  cli();
  
  // get the current value of TCNT and check for rollover
  t0 = (uint16_t)TCNT0;
  if (t0 > (uint16_t)TCNT0)
    t0++;

  // get a snapshot of the other timer values
  clock_cycles = timer0_clock_cycles;
  millis = timer0_millis;
  SREG = sreg;

  // compute the number of Timer0 ticks represented by the data
  unsigned long t0_ticks = (clock_cycles / 64) + (millis * (1000L * clockCyclesPerMicrosecond() / 64)) + t0;
  return(t0_ticks);
}

Thanks, Don, I'll give that a try.

/Daryl

Don: do you have any thoughts on how you could create a similar function that returned microseconds instead of ticks? (And worked for multiple CPU speeds.)

do you have any thoughts on how you could create a similar function that returned microseconds instead of ticks?

My first attempt at this produced a function that returned CPU cycles which could, of course, be easily converted to microseconds. Upon reflection, however, I realized that the resolution would be that of a timer tick anyway (each timer tick is 64 CPU cycles) so it seemed pointless to "waste" a portion of the range of the return value for no benefit.

If desired, Timer0 ticks can be converted to microseconds using this:

#define timer0TicksToMicroseconds(t)  ((t) * 64L / (F_CPU / 1000000L))

Of course, this formula will only work correctly if F_CPU is an integral multiple of 10^6. It could be rewritten to support non-integral multiples but the useful range would be substantially smaller. For example, it could be written thusly:

#define timer0TicksToMicroseconds(t)  ((t) * 64L * 1000000L / F_CPU))

This works correctly for any F_CPU but the useful range is only about 67uS.

Here is an updated version that corrects an error with properly detecting a pending timer interrupt. It also uses a rearranged conversion formula that works properly with any F_CPU that is an integral multiple of 1000.

I believe that it should work correctly on any Arduino-type device but I have only tested it on my setup here which is based on the mega644. The device-dependent aspect is the name of the register containing the Timer0 overflow flag. The conditional definition handles the variations of which I am aware.

extern "C"
{
  // defined in wiring.h
  extern unsigned long timer0_clock_cycles;
  extern unsigned long timer0_millis;
};

// define the timer interrupt flag register if necessary
#if !defined(TIFR0)
  #if defined(TIFR)
    #define TIFR0 TIFR
  #else
    #error AVR device not supported
  #endif
#endif

//
// This routine returns a value representing the number of Timer0 ticks that have
// occurred since the device began running.  Timer0 ticks occur at a rate of
// F_CPU / 64.  This fact can be used to convert Timer0 ticks to microseconds, if
// desired.  Note, however, that the expression for doing so must be constructed
// carefully to avoid issues relating to integer truncation and overflowing a
// 32-bit value.  In general, it will be simpler to perform such conversions on
// the difference between two values returned by this routine when that difference
// is known or expected to be small compared to the range of a 32-bit value.
//
unsigned long
hpticks(void)
{
  uint8_t sreg = SREG;
  cli();
  
  // get the current value of TCNT and check for a rollover/pending interrupt
  uint16_t t0 = (uint16_t)TCNT0;
  if ((TIFR0 & _BV(TOV0)) && (t0 == 0))
    t0 = 256;

  // get a snapshot of the other timer values
  unsigned long clock_cycles = timer0_clock_cycles;
  unsigned long millis = timer0_millis;
  SREG = sreg;

  // compute the number of Timer0 ticks represented by the data
  unsigned long timer0_ticks = (((millis * (F_CPU / 1000L)) + clock_cycles) / 64) + t0;

  return(timer0_ticks);
}

Hi Don, I gave it a try on my Modern Device BBB Clone. What I'm doing is measuring the PWM signal from a Memsic 2125 Accelerometer. With the unit lying flat (with essentially no movement) I get a stream of numbers like so:

1229
1228
1228
972
1228
1229
1228
1484
1229

The vast majority of numbers are around 1228. Every once in while I get a number well outside what I would expect from the unit just sitting there. Above you see 972 and 1484. What's interesting is that 1228 - 972 = 256 and 1484 - 1228 = 256. (The above is not representative of the ratio of expected numbers to unexpected.) I get more unexpected numbers when using an interrupt vs checking with digitalRead in loop().

Any ideas?

Thanks.
/Daryl

Hi Don, I've messed around a bit and now it seems to be ok. I'll play around with it some more and get back to you if I continue to have trouble.

Thanks.
/Daryl

Hi Don, I had a chance to pick it up and work some more on it. Of course it's not working now... I've whittled things down. If I call hpticks() from loop() it works fine (no interrupts attached). If I call it (or millis()) from my ISR, it appears to "crash" after a few seconds (loop() fails to print to Serial). Here's what I've got now. Any PWM signal on pin 2 should be sufficient to test this. I would appreciate it if someone could try it.

extern "C"{
  // defined in wiring.h
  extern unsigned long timer0_clock_cycles;
  extern unsigned long timer0_millis;
};

// define the timer interrupt flag register if necessary
#if !defined(TIFR0)
  #if defined(TIFR)
    #define TIFR0 TIFR
  #else
    #error AVR device not supported
  #endif
#endif

unsigned long hpticks(void) {
  uint8_t sreg = SREG;
  cli();
  
  // get the current value of TCNT and check for a rollover/pending interrupt
  uint16_t t0 = (uint16_t)TCNT0;
  if ((TIFR0 & _BV(TOV0)) && (t0 == 0)) t0 = 256;

  // get a snapshot of the other timer values
  unsigned long clock_cycles = timer0_clock_cycles;
  unsigned long millis = timer0_millis;
  SREG = sreg;

  // compute the number of Timer0 ticks represented by the data
  unsigned long timer0_ticks = (((millis * (F_CPU / 1000L)) + clock_cycles) / 64) + t0;

  return timer0_ticks;
} 

void setup() {
  Serial.begin(57600);
  pinMode(2, INPUT);
  attachInterrupt(0, onChangeX, CHANGE);
}

void loop() {
  Serial.println("*");
  delay(500);
  // I get a few seconds of this, then nothing
}

void onChangeX() {
  unsigned long currTicks = hpticks();  
}

Thanks for the help
/Daryl

I think I may have found the problem, not sure. I am now calling cli() / sei() around Serial prints of my state variables. I though "volitile" somehow "made variables work" in interrupts. Still would be nice if someone could verify my findings.

Thanks.

/Daryl

Thanks for the code! I plan to use it to control multiple servos without having to use delayMicroseconds() for each one.

I though "volitile" somehow "made variables work" in interrupts.

The volatile attribute doesn't involve any magic. Simply stated, that attribute tells the compiler that the value of the variable might change at any time due to the action of some code other than the function that it is currently optimizing. With this information, the compiler generates code to re-read the variable every time it is needed rather than just using the value that it might already have loaded into registers.

Clearly, you don't want to add that attribute to all variables in a program because doing so would generate larger code than necessary. On the other hand, you must use the volatile attribute for any variable that is read or written both in an interrupt service routine and in the mainline code. More generally, if you were writing code to run in a multi-tasking environment, you'd need to use the volatile attribute for any variable that is read or written in two or more tasks or interrupt service routines.

Thanks, Don, that was helpful.