关于C ++:滚动安全计时器(刻度)比较

关于C ++:滚动安全计时器(刻度)比较

Rollover safe timer (tick) comparisons

我在硬件中有一个计数器,出于时序考虑,我可以观察一下。 它以毫秒为单位,并以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_tuint16_t更改为uint32_t,将0xFFFFFFFF0xFFFF更改为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
}


推荐阅读

    2010年底DIY硬件总结和安装参考

    2010年底DIY硬件总结和安装参考,,它似乎只是一眨眼的功夫从过去的最后一眼。看看现在的岗位似乎就在昨天,但看看当年的内容是真的走了,如果

    笔记本电脑常见硬件故障分析

    笔记本电脑常见硬件故障分析,,1。电脑非电(功率指示灯不亮)笔记本电脑脑电现象的处理方法,可按以下顺序检测: (1)首先检查适配器是否正确连接

    怎么检测电脑硬件问题

    怎么检测电脑硬件问题,电脑硬件,检测,怎么,电脑硬件故障的几种简单检查方法:一、首先确定你的电源已经打开、所有的连线全部连接到位。笔

    3月份安装峰值最突出的硬件价格表

    3月份安装峰值最突出的硬件价格表,,月,传统的DIY装机高峰,电脑城遍布城市的学生装军条件下呈现出一派欣欣向荣的景象。对骄傲的日子,浩浩荡荡

    常识硬件的计算机日常维护

    常识硬件的计算机日常维护,,硬件(防尘、防高温、防磁、防潮、防静电、防震) 应将电脑放在一个干净的房间,避免灰尘太多造成的不利影响,对各种

    确定DIY图文教程计算机硬件和奸商

    确定DIY图文教程计算机硬件和奸商,,开学季的到来,越来越多的同性恋自己也做够的食物,选择X宝,Y东好的产品,或实体组装自己的购买,也能处理问题,

    给电脑升级cpu|给电脑升级硬件

    给电脑升级cpu|给电脑升级硬件,,1. 给电脑升级硬件1. 把戴尔笔记本翻到背面,拆卸后面板固定的螺丝,取下背面盖板。2. 找到内存扩展插槽,插上

    笔记本电脑的硬件问题

    笔记本电脑的硬件问题,,今天早上我用我的NEC笔记本电脑,玩坏了的时候,突然听到了电源和笔记本电源接口的噪音很小,类似海豚的声音,我赶紧关上