I have a counter in hardware that I can observe for timing considerations. It counts miliseconds and is stored in a 16 bit unsigned value. How do I safely check if a timer value has passed a certain time and safely handle the inevitable rollover:
//this is a bit contrived, but it illustrates what I'm trying to do
const uint16_t print_interval = 5000; // milliseconds
static uint16_t last_print_time;
if(ms_timer() - last_print_time > print_interval)
{
printf("Fault!\n");
last_print_time = ms_timer();
}
This code will fail when ms_timer overflows to 0.
You don't actually need to do anything here. The original code listed in your question will work fine, assuming ms_timer()
returns a value of type uint16_t.
(Also assuming that the timer doesn't overflow twice between checks...)
To convince yourself this is the case, try the following test:
uint16_t t1 = 0xFFF0;
uint16_t t2 = 0x0010;
uint16_t dt = t2 - t1;
dt
will equal 0x20
.
I use this code to illustrate the bug and possible solution using a signed comparison.
/* ========================================================================== */
/* timers.c */
/* */
/* Description: Demonstrate unsigned vs signed timers */
/* ========================================================================== */
#include <stdio.h>
#include <limits.h>
int timer;
int HW_DIGCTL_MICROSECONDS_RD()
{
printf ("timer %x\n", timer);
return timer++;
}
// delay up to UINT_MAX
// this fails when start near UINT_MAX
void delay_us (unsigned int us)
{
unsigned int start = HW_DIGCTL_MICROSECONDS_RD();
while (start + us > HW_DIGCTL_MICROSECONDS_RD())
;
}
// works correctly for delay from 0 to INT_MAX
void sdelay_us (int us)
{
int start = HW_DIGCTL_MICROSECONDS_RD();
while (HW_DIGCTL_MICROSECONDS_RD() - start < us)
;
}
int main()
{
printf ("UINT_MAX = %x\n", UINT_MAX);
printf ("INT_MAX = %x\n\n", INT_MAX);
printf ("unsigned, no wrap\n\n");
timer = 0;
delay_us (10);
printf ("\nunsigned, wrap\n\n");
timer = UINT_MAX - 8;
delay_us (10);
printf ("\nsigned, no wrap\n\n");
timer = 0;
sdelay_us (10);
printf ("\nsigned, wrap\n\n");
timer = INT_MAX - 8;
sdelay_us (10);
}
Sample output:
bob@hedgehog:~/work2/test$ ./timers|more
UINT_MAX = ffffffff
INT_MAX = 7fffffff
unsigned, no wrap
timer 0
timer 1
timer 2
timer 3
timer 4
timer 5
timer 6
timer 7
timer 8
timer 9
timer a
unsigned, wrap
timer fffffff7
timer fffffff8
signed, no wrap
timer 0
timer 1
timer 2
timer 3
timer 4
timer 5
timer 6
timer 7
timer 8
timer 9
timer a
signed, wrap
timer 7ffffff7
timer 7ffffff8
timer 7ffffff9
timer 7ffffffa
timer 7ffffffb
timer 7ffffffc
timer 7ffffffd
timer 7ffffffe
timer 7fffffff
timer 80000000
timer 80000001
bob@hedgehog:~/work2/test$
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With