11

我有一个硬件计数器,我可以观察到时间考虑。它以毫秒为单位,以 16 位无符号值存储。如何安全地检查计时器值是否已经过了一定时间并安全地处理不可避免的翻转:

//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();
}

当 ms_timer 溢出为 0 时,此代码将失败。

4

8 回答 8

11

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

(还假设计时器在检查之间不会溢出两次......)

为了说服自己是这种情况,请尝试以下测试:

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

dt将等于0x20

于 2008-09-14T19:14:09.997 回答
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$ 
于 2010-06-04T18:01:32.313 回答
3

我曾经为这种情况编写如下代码。
我用测试用例进行了测试,并确保它 100% 有效。
此外,在下面的代码中使用 32 位计时器滴答声更改为uint32_tfromuint16_t0xFFFFFFFFfrom 。0xFFFF

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;
}
于 2017-08-24T03:21:01.897 回答
1

只需检查 ms_timer < last_print_time 是否添加 2^16 否?

编辑:如果可以的话,你还需要一个 uint32 。

于 2008-09-14T16:25:59.293 回答
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();
}
于 2008-09-14T16:57:21.323 回答
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;
}

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

于 2008-09-14T17:03:39.797 回答
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

我将 unsigned long int 用于我的计时器计数器(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);
}
于 2008-10-02T19:42:34.173 回答
-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
}
于 2012-08-11T04:28:07.337 回答