timestamp in ms and 64-bit counter

I need a timestamp in millisecond in linux epoch. It is a number that doesn't fit in a 32-bits number.

I'm using a 32-bit MCU (STM32L4R9...) so I don't have a 64-bits hw counter. I need to create a mixed sw/hw 64-bits counter. It's very simple, I configure a 32-bits hw timer to run at 1kHz and increment an uint32_t variable in timer overflow ISR.

Now I need to implement a GetTick() function that returns a uint64_t. I know it could be difficult, because of race conditions. One solutions is to disable interrupts, but I remember another solution.

extern volatile uint32_t ticks_high;

uint64_t GetTick(void) { uint32_t h1 = ticks_high; uint32_t l1 = hwcnt_get(); uint32_t h2 = ticks_high;

if (h1 == h2) return ((uint64_t)h1

Reply to
pozz
Loading thread data ...

I presume your second line should be "return ((uint64_t)h2

Reply to
David Brown

All this seems to be more complex than it needs to be. You guys are focuse d on limitations when things happen fast. Do you know how slow things can happen?

A 32 bit counter incremented at 1 kHz will roll over every 3 years or so. If you can assure that the GetTick is called once every 3 years simpler cod e can be used.

Have a 64 bit counter value which is the 32 bit counter incremented by the

1kHz interrupt and another 32 bit counter (the high part of the 64 bits) wh ich is incremented when needed in the GetTick code.

uint64_t GetTick(void) { static uint32_t ticks_high; uint32_t ticks_hw= hwcnt_get(); static uint32_t ticks_last;

if (ticks_last > ticks_hw) ticks_high++; ticks_last = ticks_hw; return ((uint64_t)ticks_high

Reply to
Rick C

2 ^ 32 / (24 * 60 * 60 * 1000) = 49.71026962... (days)

regards, Bernd

Reply to
Bernd Linsel

A 1Khz 32-bit counter rolls over about every 49.7 days.

Reply to
Robert Wessel

Yes, I forgot to divide by 24 hours. Nevermind... lol

Actually, it still works, just a shorter minimum time.

--
  Rick C. 

  + Get 1,000 miles of free Supercharging 
 Click to see the full signature
Reply to
Rick C

In your timer ISR low32 += 1 if (low32 == 0) high32 += 1;

in your GetTick()

do { l1 = low32; h1 = high32; while (l1 > low32);

return your or'd h1 and l1

It still can be incorrect with out disabling interrupts. Consider: timer interrupt priority 1 dma interrupt priority 2 your code lowest priority In the middle of your h1 == h2 the dma interrupt goes off. In the dma routine your timer interrupt goes off You unwind back to your code and your h2 and possibly h1 are incorrect.

--
Chisolm 
Texas-American
Reply to
Joe Chisolm

The potential problem with this is if the code is used re-entrantly - i.e., you have multiple threads that each use GetTick() and more than one calls it during the cross-over point. It's highly unlikely to happen - there is only a risk once every 49 days (I got that wrong too). But you don't want to leave these little potential race conditions in the code - you'll never find them in testing, but they always happen at your most important customer site.

I think it's quite likely that the code already has a 1 KHz interrupt routine doing other things, so incrementing a "high" counter there would not be an issue. But if there is no interrupt for other purposes, then it is a nice idea to do the update during the GetTick call like this. However, you need locking (or a more advanced lockfree solution, but that would likely be a fair bit less efficient on a microcontroller. It might be worth considering on a multi-processor system).

The easiest method is disabling interrupts in a double-checked lock. If your system timing can't handle a few cycles of disabled interrupts every 49 days, you should be worrying about the rest of the design.

I think this should do it:

uint64_t GetTick(void) { static volatile uint32_t ticks_high; static volatile uint32_t ticks_last;

uint32_t ticks_hw = hwcnt_get();

if (ticks_last > ticks_hw) { uint32_t old_processor_state = disableInterrupts(); ticks_hw = hwcnt_get(); if (ticks_last > ticks_hw) { ticks_high++; ticks_last = ticks_hw; } restoreInterrupts(old_processor_state); } return ((uint64_t)ticks_high

Reply to
David Brown

low32 is the hw counter that runs at 1kHz and generates and interrupt every 2^32 ms. It isn't a software variable, it's a register.

You are supposing to use an hw counter that generates interrupt at 1kHz, but this is my case.

I couldn't get your point. Ok, when the low priority code is back after interrupts, h1, h2 and l1 could be incorrect, but they are coherent among them, so the result of GetTick() is still valid, maybe slightly inaccurate.

Reply to
pozz

I

is

y

on

64

Yes, you are absolutely right about that.

Actually, there is no need for a 1 kHz interrupt. I believe the OP has a h ardware counter ticking at 1 kHz so the overflow event would be 49 days. T here may be a faster interrupt for some other purpose, but not needed for t he hardware timer which may well be run on a low power oscillator and backu p battery.

My intent was to do something just plain simple, but in multitasking system it is not so simple. I don't typically use complications like interrupts. I do FPGA work where I roll the hardware for the peripherals as well as d esigning the instruction set for the processor. I seldom use multitasking other than potentially interrupts which usually don't need to use locking a nd such. If resources are not shared locking is not required.

That works.

--
  Rick C. 

  -- Get 1,000 miles of free Supercharging 
 Click to see the full signature
Reply to
Rick C

Joe is saying read the LSB then the MSB, then read the LSB again to be sure it hasn't changed due to a tick. If it has, try again until it doesn't change.

If LSB has changed, MSB might have. If LSB hasn't changed, and you haven't waited for an entire cycle of the LSB register, then you know MSB is good.

If you've repeated the loop until LSB cycled to exactly where you were before, you have bigger problems.

However, Joe got it wrong. The do... while loop should use not-equal, not >. Otherwise if you read LSB as it rolls over l1 is < low32, and you're broken because you didn't detect that.

Clifford Heath.

Reply to
Clifford Heath

If the two reads of the high tick counter are different, then the point in time when the high tick counter had its higher value, and the lower tick counter was 0 had to have occurred between the two reads of the high tick counter, so it is a valid time point that occurred during the call to GetTick(). The fact that it might be 'stale' compared to the return time is no different than if it returned a totally accurate time-stamp, but just after the return the higher priority task interrupt this task.

(Note, you need to use h2, not h1, to build the 64 bit time stamp, h1 with a 0 bottom word is likely outside your call window, as you likely did the first read with the counter at max, and the second after it wrapped to 0.)

Reply to
Richard Damon

Ok, but he was saying *his* approach (while loop) is *still* incorrect without disabling interrupts. I don't think so.

Sure!

Reply to
pozz

Another consideration.

Bugs that *could* happen every 49 days are critical, because they could really happen even more rarely.

If you have a ms resolution, I think it's better to work with a 16-bits hw counter, coupled with a 32-bits sw counter.

In this way, the potential bug could happen much more frequently (every minute) and the 48-bits timestamp could represents datetime until... I don't know exactly, but I don't worry what it is.

Reply to
pozz

We don't know that - the OP hasn't told us all the details. An interrupt that hits every millisecond (or some other regular time), used as part of the global timebase and for executing regular functions, is very common. Maybe he has something like this, maybe not.

Fair enough. And we don't know if there is multi-tasking involved here or not. If GetTick is only called from one thread, there is no problem. Also if he has cooperative multitasking rather than pre-emptive, there will be no problem. Your suggested solution is good (IMHO), but it is important to understand its restrictions too.

Reply to
David Brown

That is a useful thought. It is very important to write code in a way that it can be tested. And even then, remember that testing can only prove the /presence/ of bugs, never to prove their /absence/.

Another trick during testing is to speed up the timers. If you can make the 1 kHz timer run at 1 MHz for testing, you'll get similar benefits.

Reply to
David Brown

Sure!

Hmmm.., most probably increasing the frequency of the timer will break some other parts of the application (timeouts on serial lines...).

Reply to
pozz

The Linux approach is still better: Initialize the timer count variable with a value just some seconds before it wraps.

Regards, Bernd

Reply to
Bernd Linsel

Yes, we do know. He told us in the first post.

"I'm using a 32-bit MCU (STM32L4R9...) so I don't have a 64-bits hw counter. I need to create a mixed sw/hw 64-bits counter. It's very simple, I configure a 32-bits hw timer to run at 1kHz and increment an uint32_t variable in timer overflow ISR."

--
  Rick C. 

  -+ Get 1,000 miles of free Supercharging 
 Click to see the full signature
Reply to
Rick C

ElectronDepot website is not affiliated with any of the manufacturers or service providers discussed here. All logos and trade names are the property of their respective owners.