cancel
Showing results for 
Search instead for 
Did you mean: 

delay function based on timer, overflow safe?

Benjamin Brammer
Senior II

Hello Everybody,

I was thinking about a flexible way to implement a µs or ms delay function with a free running timer as tick source. One of the biggest problems that comes to my mind is the overflow problem when the timer wraps around, so that some classical approaches, I have found on the Internet would lead to false behaviour.

I have thouight of the following solution for the problem and would like some feedback from the community:

I assumed using a timer with a 16-Bit counter but you could also use a 32-Bit counter.

/**
 * @brief	µs delay function based on Timer15.
 * 			Maximum 65536µs delay possible.
 * @param	ucnt µs to delay
 * @return	none
 */
void delay_TIM15(uint16_t ucnt)
{
	uint16_t start = TIM15->CNT;
	while((start + ucnt) > tim15_tick());
}
#define tim15_tick()					(TIM15->CNT)

in my solution an overflow or timer wrap around would be no problem since the sum of start + ucnt is stored inside an uint16_t variable and would do the same wrap around as the counter. Or am I wrong?

best regards

Benjamin

1 ACCEPTED SOLUTION

Accepted Solutions
Piranha
Chief II
start + ucnt

When this wraps-around, the target time will be smaller than the current counter and loop will be skipped immediately. And there is no sense of adding these in the loop condition, because these don't change in or during the loop.

For unsigned integers C language guarantees correct arithmetic on wrap-around. For example, for unsigned 16-bit values in C:

2 - 0xFFFF == 3

Therefore overflow safe version is as simple as this:

void delay_TIM15(uint16_t ucnt)
{
	uint16_t start = TIM15->CNT;
	while ((uint16_t)(tim15_tick() - start) < ucnt);
}

And the (uint16_t) cast is absolutely critical here!

View solution in original post

14 REPLIES 14

Okay, so you call this function say with ucnt=10 at the moment when start = TIM15->CNT = 0xFFFF.

Then start+ucnt will be 0x10009, and as tim15_tick()=TIM15->CNT will never get higher than 0xFFFF, this function will loop forever.

Homework: fix it.

JW

S.Ma
Principal

The HW timer is used to slow down to SW manageable interrupt rate (from us to ms)

Once the timer overflow, get a timer interrupt which will increase a RAM counter (it's like the SW emulated 32 bit timer from 16 bit HW timer)

The HW timer overflow period must be long enough so that all interrupts that the core could queue is ok. (this is the MCU tricky part: interrupt priority, duration, max latency). All MCU shall deal with this checkup to be glitch free.

In the overflow interrupt, you can also decrease a signed RAM countdown. The main loop sets this RAM counter which will be decremented down to zero by the timer interupt. Then the main loop can know the time elapsed. Optionally set the counter to -1 when disabled, or else.

Of course, once you are using RAM downcounters, you can have multiple of them.

No, not good.

Do the comparison after you delta the difference

ie (current - start) < timeout

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..

But wouldn't (start + ucnt) wrap around if I would cast the sum to be of uint16_t? so like:

while( (uint16_t)(start + ucnt) > tim15_tick())

when the timer reaches 0xFFFF it will wrap around and start counting from zero. That's why I thought this would work..

What do you mean by RAM counter? A simple counting variable?

I would rather not use ISR driven delays as they have some extra MCLK counts until they are serviced. I think the direct approach with a free running timer or counter clock like Systick is better.

but your supposed example would have an error if start would be 0xFFFF, timout would be 10, then current would wrap around in the first step and the equation would allways be true. Or am I not seeing here something?

Systick has a 32 bit ram counting variable and only alone is used to make delays. (not concatenating SW counter with HW counter makes it ok).

Delta in 32 bit mode will work even if overflow as long as the requested delay is within the 32 bit range.

When the delay represent a lot of core cycles, then interrupt is the normal way to do things.

For short delays (in microseconds), use the same timer which count says miliseconds, and use its 4 output compares to set a compare flag when the delay(s) has elapsed. You get best bang for the buck with a general purpose timer.... that one without interrupt.

> But wouldn't (start + ucnt) wrap around if I would cast the sum to be of uint16_t? so like:

> while( (uint16_t)(start + ucnt) > tim15_tick())

> when the timer reaches 0xFFFF it will wrap around and start counting from zero. That's why I thought this would work..

Yes, but you did not write that down in the first post. As there's most probably no Cortex-M compiler with sizeof(int)=2, the typecast (which is a special case of modulo - and that is just a fancy word mathematicians use instead of "wrap around") is *the* key here.

[EDIT] and yes, Piranha is right in the post below, with this addition the corner case when the sum wraps over, fails early - I concentrated too much on the mod part :blushing:[/EDIT]

JW

PS. A sidenote: While I wrote the point is in modulo arithmetic, actually using the mod operator i.e. % in C may lead to fail depending on the particular way how it is used, as it does not really implement modulo in the mathematical sense of word (i.e. positive remainder of integer division) in the case of negative operands. There's even a minor gotcha for those using really old compilers, as C90 and C99 differ in this particular detail, C90 leaving the sign of result to the implementation to decide, whereas C99 explicitly prescribing that % is what remains after / with the same two operands being truncated towards zero, i.e. in C99 and later, -10 % +3 == -1

It's up to you do decide how to implement it. If it's a SW delay, use a dedicated countdown variable which is -1 when disabled.

Otherwise, if say a 16 bit timer even overflows, delay 0x5000 + currnt timer 0xF000 = timer next delayed event 0x4000 which will be a proper delay.