Rollover safe timer (tick) comparisons

11
我有一个硬件计数器用于时间考虑,它计算毫秒并以一个16位无符号值存储。如何安全地检查定时器的值是否已经超过了一定时间,并安全地处理不可避免的溢出:
//这段代码有点牵强,但它说明了我的意图 const uint16_t print_interval = 5000; // 毫秒 static uint16_t last_print_time;
if((ms_timer() - last_print_time) > print_interval) { printf("故障!\n"); last_print_time = ms_timer(); }
当ms_timer溢出到0时,此代码将失败。
8个回答

11

实际上在这里您不需要做任何事情。假设ms_timer()返回uint16_t类型的值,那么问题中列出的原始代码将正常工作。

(还得假设计时器在检查之间没有两次溢出...)

为了验证这一点,请尝试执行以下测试:

uint16_t t1 = 0xFFF0;
uint16_t t2 = 0x0010;
uint16_t dt = t2 - t1;

dt 的值将等于 0x20


当我运行原始代码时,它可以一直工作到溢出点,然后对于每个计数打印输出。 - JeffV
4
鉴于原始行为和你的替代解决方案,我最后的猜测是,你的 x 变量 / ms_timer() 函数是/返回一个大于16位的整数,这会在计算时间差时导致意外的类型提升。 - smh

3

我曾经为这种情况编写以下代码。
我进行了测试,并确保它可以完美运行。
此外,在下面的代码中,使用32位计时器滴答代替uint16_t,并将0xFFFF更改为0xFFFFFFFF

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!\n");
    last_print_time = cur_tick;
}

3
我使用这段代码来说明一个错误以及可能的解决方案,使用有符号比较。
/* ========================================================================== */
/*   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);

}

示例输出:

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$ 

INT_MAX 旁边提供的延迟长度不正确,对吗? - AndresR

1

避免这个问题最安全的方法可能是使用带符号的32位值。以您的示例为例:

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!\n");
    last_print_time = ms_timer();
}

1

这似乎适用于最多64k/2的间隔,对我来说很合适:

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!\n");
    last_print_time = x;
}

利用带符号整数的性质。(二进制补码

1

只需检查 ms_timer < last_print_time,如果是,则添加 2^16 吗?

编辑:如果可以,请将其升级为 uint32。


0

我发现使用不同的定时器API对我更有效。我创建了一个定时器模块,具有两个API调用:

void timer_milliseconds_reset(unsigned index);
bool timer_milliseconds_elapsed(unsigned index, unsigned long value);

计时器索引也在计时器头文件中定义:
#define TIMER_PRINT 0
#define TIMER_LED 1
#define MAX_MILLISECOND_TIMERS 2

我使用无符号长整型来作为我的计时器计数器(32位),因为这是我硬件平台上本地大小的整数,它能够给我提供从1毫秒到大约49.7天的经过时间。你也可以有16位的计时器计数器,这将会给你提供从1毫秒到大约65秒的时间。

计时器计数器是一个数组,并由硬件定时器(中断、任务或轮询计数器值)递增。它们可以在处理递增函数时被限制到数据类型的最大值,从而获得不会发生溢出的计时器。

/* 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;
    }
}

那么你的代码就变成了:

//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!\n");
    timer_milliseconds_reset(TIMER_PRINT);
}

-1

有时我会这样做:

#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
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接