我在硬件中有一个计数器,出于时序考虑,我可以观察一下。 它以毫秒为单位,并以16位无符号值存储。 如何安全检查计时器值是否已过一定时间并安全处理不可避免的翻转:
1 2 3 4 5 6 7 8 9 10
| //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!\
");
last_print_time = ms_timer();
} |
当ms_timer溢出到0时,此代码将失败。
您实际上不需要在这里做任何事情。假设ms_timer()返回uint16_t类型的值,则问题中列出的原始代码将可以正常工作。
(还假设计时器在两次检查之间不会两次溢出...)
要说服自己,请尝试以下测试:
1 2 3
| uint16_t t1 = 0xFFF0;
uint16_t t2 = 0x0010;
uint16_t dt = t2 - t1; |
dt将等于0x20。
在这种情况下,我经常像下面那样编写代码。
我用测试用例进行了测试,并确保它可以100%工作。
此外,在下面的代码中使用32位定时器刻度将uint32_t从uint16_t更改为uint32_t,将0xFFFFFFFF从0xFFFF更改为0xFFFFFFFF。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| uint16_t get_diff_tick(uint16_t test_tick, uint16_t prev_tick)
{
if (test_tick < prev_tick)
{
// time rollover(overflow)
return (0xFFFF - prev_tick) + 1 + test_tick;
}
else
{
return test_tick - prev_tick;
}
}
/* your code will be.. */
uint16_t cur_tick = ms_timer();
if(get_diff_tick(cur_tick, last_print_time) > print_interval)
{
printf("Fault!\
");
last_print_time = cur_tick;
} |
我使用此代码通过签名比较来说明该错误和可能的解决方案。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| /* ========================================================================== */
/* timers.c */
/* */
/* Description: Demonstrate unsigned vs signed timers */
/* ========================================================================== */
#include <stdio.h>
#include <limits.h>
int timer;
int HW_DIGCTL_MICROSECONDS_RD()
{
printf ("timer %x\
", 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\
", UINT_MAX);
printf ("INT_MAX = %x\
\
", INT_MAX);
printf ("unsigned, no wrap\
\
");
timer = 0;
delay_us (10);
printf ("\
unsigned, wrap\
\
");
timer = UINT_MAX - 8;
delay_us (10);
printf ("\
signed, no wrap\
\
");
timer = 0;
sdelay_us (10);
printf ("\
signed, wrap\
\
");
timer = INT_MAX - 8;
sdelay_us (10);
} |
样本输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| 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$ |
这似乎适用于最大64k / 2的间隔,这适合我:
1 2 3 4 5 6 7 8 9 10 11
| const uint16_t print_interval = 5000; // milliseconds
static uint16_t last_print_time;
int next_print_time = (last_print_time + print_interval);
if((int16_t) (x - next_print_time) >= 0)
{
printf("Fault!\
");
last_print_time = x;
} |
利用带符号整数的性质。 (补码)
避免该问题的最安全方法可能是使用带符号的32位值。使用您的示例:
1 2 3 4 5 6 7 8 9 10 11
| const int32 print_interval = 5000;
static int32 last_print_time; // I'm assuming this gets initialized elsewhere
int32 delta = ((int32)ms_timer()) - last_print_time; //allow a negative interval
while(delta < 0) delta += 65536; // move the difference back into range
if(delta < print_interval)
{
printf("Fault!\
");
last_print_time = ms_timer();
} |
只需检查ms_timer
编辑:如果可能的话,您还需要最多一个uint32。
我发现使用其他计时器API更适合我。我创建了一个具有两个API调用的计时器模块:
1 2
| void timer_milliseconds_reset(unsigned index);
bool timer_milliseconds_elapsed(unsigned index, unsigned long value); |
计时器索引也在计时器头文件中定义:
1 2 3
| #define TIMER_PRINT 0
#define TIMER_LED 1
#define MAX_MILLISECOND_TIMERS 2 |
我将unsigned long int用于我的计时器计数器(32位),因为那是我的硬件平台上的本机大小的整数,这使我经过的时间从1毫秒变为大约49.7天。您可能具有16位的计时器,这将使您经过的时间从1毫秒到大约65秒。
计时器计数器是一个数组,由硬件计时器递增(中断,任务或计数器值的轮询)。在处理无翻转计时器增量的函数中,可以将它们限制为数据类型的最大值。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| /* variable counts interrupts */
static volatile unsigned long Millisecond_Counter[MAX_MILLISECOND_TIMERS];
bool timer_milliseconds_elapsed(
unsigned index,
unsigned long value)
{
if (index < MAX_MILLISECOND_TIMERS) {
return (Millisecond_Counter[index] >= value);
}
return false;
}
void timer_milliseconds_reset(
unsigned index)
{
if (index < MAX_MILLISECOND_TIMERS) {
Millisecond_Counter[index] = 0;
}
} |
然后您的代码变为:
1 2 3 4 5 6 7 8 9
| //this is a bit contrived, but it illustrates what I'm trying to do
const uint16_t print_interval = 5000; // milliseconds
if (timer_milliseconds_elapsed(TIMER_PRINT, print_interval))
{
printf("Fault!\
");
timer_milliseconds_reset(TIMER_PRINT);
} |
有时我会这样:
1 2 3 4 5 6 7 8 9 10 11 12 13
| #define LIMIT 10 // Any value less then ULONG_MAX
ulong t1 = tick of last event;
ulong t2 = current tick;
// This code needs to execute every tick
if ( t1 > t2 ){
if ((ULONG_MAX-t1+t2+1)>=LIMIT){
do something
}
} else {
if ( t2 - t1 >= LIMT ){
do something
} |