Problem with getting time using a function

Hi, im working on a project and for compatibility with other things i need to define a function NOW() that returns a int64_t value that contains the time since startup in nanoseconds. So basically just like millis() and micros() but a nanos(). Of course the reasion for using a 64 bit integer is so that the timer does not overflow after a few seconds, and simply multiplying the output of micros() would not be good as it overflows after roughly 4200 seconds. So i created this function:

in header file:

extern int64_t NOW();

and in source:

int64_t NOW() {

    static uint32_t g_lastMicroseconds = 0;
    static int64_t g_currentTime = 0;

    uint32_t time = micros();

    if (time != g_lastMicroseconds) {
        g_currentTime += int64_t(time - g_lastMicroseconds)*MICROSECONDS;
        g_lastMicroseconds = time;
    }

    return g_currentTime;

}

MICROSECONDS is the conversion of the output in microseconds from micros() to nanoseconds.

Now the problem is, if i use this the output is some factor too large by 1000, but does not have the overflow problem at 4200 seconds.

But if I use this definition:

int64_t NOW() {

    static int64_t g_lastMicroseconds = 0;
    static int64_t g_currentTime = 0;

    int64_t time = micros();

    if (time != g_lastMicroseconds) {
        g_currentTime += (int64_t(time - g_lastMicroseconds))*MICROSECONDS;
        g_lastMicroseconds = time;
    }

    return g_currentTime;

}

I get the correct output without the factor 1000 but i get an overflow error at 4200 seconds.

How can i fix this issue? This must some kind of casting/conversion error from signed to unsigned or similar.

use uint64_t not a signed type.

how you plan to get nano second precision when using micros() is beyond my understanding though...

You'll need to call that function frequently enough to maintain the count (so don't miss a ~4200 seconds gap) and that means you could probably use millis() if your time unit is in thousands of seconds... if delta time is in microseconds then using micros() will be good enough

1 Like

Why ?
Can you explain what the project is about ?
Could this be a XY problem ? https://xyproblem.info/

Nanoseconds does not make sense, since the execution of code takes time as well.
If you need 64-bit nanoseconds, then you need a external 64-bit 1GHz hardware timer (I don't know if they exist, I don't know how the Arduino can read the 64-bit).

When I look into my crystal ball, then I see a XY-problem which has another XY-problem in it.

1 Like

The conversion from MILLIS to nanoseconds is 1,000,000. The conversion you want is MICROS to nanoseconds: 1,000. That might explain the factor of 1000 error. Check that MICROSECONDS is 1000. A better name might be MICROS_TO_NANOS.

If you want more precision, on an AVR processor you can count time at the hardware clock frequency (16 MHz). For longer intervals, you can make the Overflow counter 32-bit.

// Fast Timer
// Written by John Wasser
//
// Returns the current time in 16ths of a microsecond.
// Overflows every 268.435456 seconds.

// Note: Since this uses Timer1, Pin 9 and Pin 10 can't be used for
// analogWrite().



void StartFastTimer()
{
  noInterrupts ();  // protected code
  // Reset Timer 1 to WGM 0, no PWM, and no clock
  TCCR1A = 0;
  TCCR1B = 0;

  TCNT1 = 0;  // Reset the counter
  TIMSK1 = 0; // Turn off all Timer1 interrupts

  // Clear the Timer1 Overflow Flag (yes, by writing 1 to it)
  // so we don't get an immediate interrupt when we enable it.
  TIFR1 = _BV(TOV1);

  TCCR1B = _BV(CS10); // start Timer 1, no prescale
  // Note: For longer intervals you could use a prescale of 8
  // to get 8 times the duration at 1/8th the resolution (1/2
  // microsecond intervals).  Set '_BV(CS11)' instead.

  TIMSK1 = _BV(TOIE1); // Timer 1 Overflow Interrupt Enable
  interrupts ();
}

volatile uint16_t Overflows = 0;

ISR(TIMER1_OVF_vect)
{
  Overflows++;
}

unsigned long FastTimer()
{
  unsigned long currentTime;
  uint16_t overflows;

  noInterrupts();
  overflows = Overflows;  // Make a local copy

  // If an overflow happened but has not been handled yet
  // and the timer count was close to zero, count the
  // overflow as part of this time.
  if ((TIFR1 & _BV(TOV1)) && (TCNT1 < 1024))
    overflows++;

  currentTime = overflows; // Upper 16 bits
  currentTime = (currentTime << 16) | TCNT1;
  interrupts();

  return currentTime;
}

// Demonstration of use
#if 1
void setup()
{
  Serial.begin(115200);
  while (!Serial);
  StartFastTimer();
}

void loop()
{
  static unsigned long previousTime = 0;
  unsigned long currentTime = FastTimer();

  Serial.println(currentTime - previousTime);
  previousTime = currentTime;

  delay(100);
}
#endif

Its not about the resolution or whatever, the main reason for all of this it to support stuff that uses NOW() as a time function. Which is also why using a unsigned integer would cause problems as some of them use calculations that result in negative time for timing.

As stated, im trying to support other stuff that uses NOW() for time. So i cant change anything. What is fine though is the resolution not reaching nanosecond timing just as micros() doesnt on the uno or similiar.

Im mainly using a teensy 4.0 for tesing but want to support as many platforms as possible, hence why im trying to use micros() as it will always be implemented.

As for the millis to nanoseconds conversion, my bad that was a typo, i meant micros to nanoseconds, you can see the logic behind it in the code. But even then it would not explain the 2 different versions having different outcomes.

it does not matter. as per your definition

NOW() that returns a int64_t value that contains the time since startup in nanoseconds

➜ so it can only be positive.

if you want to cope with signed math, then store the result in a signed variable

I'm pretty sure that this code

  uint64_t a = 100, b = 300;
  int64_t delta = a - b;
  Serial.println(delta < 0 ? F("Negative") :  F("Positive") );

would say Negative. but of course

  Serial.println(a - b < 0 ? F("Negative") :  F("Positive") );

would say Positive and you would get a compilation waring stating that the comparison of unsigned expression < 0 is always false

--

for the function you could try

uint64_t NOW() { // time since start in nanosecond
  static uint64_t currentTime = 0;
  static uint32_t lastMicros = 0;
  uint32_t currentMicros = micros();

  currentTime += (currentMicros - lastMicros) * 1000ul;
  lastMicros = currentMicros;
  return currentTime;
}

I copied your two functions into a sketch and, so far, they are producing the same results within 20-ish microseconds.

Since you don't provide your code for "MICROSECONDS", I made up this declaration:
const uint64_t MICROSECONDS = 1000ull;
Maybe your 'factor of 1000' error is in how MICROSECONDS is defined in your code.

Use only unsigned values?

MICROSECONDS is defined as:
constexpr int64_t MICROSECONDS = 1000;

Unsigned values wont work as a lot of calculations done in the other things are similiar to:

if (NOW() - someTimeInFuture > 10*SECONDS) thingToDo();

And NOW() - someTimeInFuture would result in a negative number or underflow if its unsigned.

The other things i cant change and want to support do similiar things to:

if (NOW() - someTimeInFuture > 10*SECONDS) thingToDo();

And if NOW() returns a unsigned value then NOW() - someTimeInFuture wont return a negative value.

indeed.

but when NOW overflows and becomes negative, what do you do ? (in 292 years :slight_smile: )

I guess whoever wrote that didn't expect the system to be up for 293 years. :slight_smile:

yes, I was just doing the math :slight_smile:

Yep, software im trying to support was originally made for cubesats, they probably wont be up that long. Or they will have a system reset every once in a while.

The time is up to 2300 seconds and both versions of NOW() are running fine. Looking for a roll-over near 4200 seconds.

millis() NOW1()         NOW2()
2291028, 2291029404000, 2291029424000
2295232, 2295233356000, 2295233380000
2299437, 2299437276000, 2299437296000
2303640, 2303641204000, 2303641228000
2307845, 2307845148000, 2307845168000
2312048, 2312049056000, 2312049076000
2316252, 2316252956000, 2316252976000

Looks like NOW2() (your second example) has an overflow problem between 4292 and 4296 seconds. That is probably a uint32_t rollover at 4,294,967,296 microseconds.

millis() NOW1()         NOW2()
4279515, 4279515796000, 4279515820000
4283719, 4283719804000, 4283719824000
4287923, 4287923800000, 4287923820000
4292127, 4292127820000, 4292127844000
4296331, 4296331840000, 1364568000
4300534, 4300535164000, 5567892000
4304738, 4304738608000, 9771332000
4308941, 4308942072000, 13974796000
4313145, 4313145612000, 18178340000

The "NOW1()" function is still keeping the correct time.

const uint64_t MICROSECONDS = 1000ull;

int64_t NOW1()
{
  static uint32_t g_lastMicroseconds = 0;
  static int64_t g_currentTime = 0;

  uint32_t time = micros();

  if (time != g_lastMicroseconds)
  {
    g_currentTime += int64_t(time - g_lastMicroseconds) * MICROSECONDS;
    g_lastMicroseconds = time;
  }

  return g_currentTime;
}

int64_t NOW2()
{
  static int64_t g_lastMicroseconds = 0;
  static int64_t g_currentTime = 0;

  int64_t time = micros();

  if (time != g_lastMicroseconds)
  {
    g_currentTime += (int64_t(time - g_lastMicroseconds)) * MICROSECONDS;
    g_lastMicroseconds = time;
  }

  return g_currentTime;
}

void printull(uint64_t value)
{
  if (value >= 10)
    printull(value / 10);
  Serial.print(char((value % 10) + '0'));
}

void setup()
{
  Serial.begin(115200);
}

void loop()
{
  uint64_t now1 = NOW1();
  uint64_t now2 = NOW2();

  Serial.print(millis());
  Serial.print(", ");
  printull(now1);
  Serial.print(", ");
  printull(now2);
  Serial.println();

  delay(4200);
}

That sums up the issues im having except that NOW1() is giving you the correct output, not multiplied by some factor.

Ive now also tested this:

int64_t NOW() {

    static int32_t g_lastMicroseconds = 0;
    static int64_t g_currentTime = 0;

    int32_t time = micros();

    if (time != g_lastMicroseconds) {
        g_currentTime += int64_t(time - g_lastMicroseconds)*MICROSECONDS;
        g_lastMicroseconds = time;
    }

    return g_currentTime;

}

Which is simply NOW1() but with int32_t instead of uint32_t and now im getting the correct output, will update about rollover in 2100 or 4200 seconds.

Im guessing this is a compiler issue with the teensy 4.0 compiler?

that won't work well as you store an unsigned long into a signed long. when micros() is over half it's capacity then time goes into negative territory

try this

  uint32_t u = 2147483648;
  int32_t  s = u;
  Serial.print(F("u = ")); Serial.println(u);
  Serial.print(F("s = ")); Serial.println(s);  

you'll see

u = 2147483648
s = -2147483648

you need a larger container, like an int64_t

The return value of micros() is already a too small integer with 32 bits unsigned, so it will overflow anyway. The code handles that overflow and should handle the overflow of a signed 32 too.
At least thats my theory. I will see in roughly half an hour.