【问题标题】:Rollover safe timer (tick) comparisons翻转安全计时器(滴答)比较
【发布时间】:2010-09-08 20:29:27
【问题描述】:

我在硬件中有一个计数器,我可以观察它以考虑时间问题。它以毫秒为单位,以 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 时,此代码将失败。

【问题讨论】:

    标签: c++ c timer rollover embedded


    【解决方案1】:

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

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

    为了说服自己,请尝试以下测试:

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

    dt 将等于 0x20

    【讨论】:

    • 当我运行原始代码时,它会一直运行到溢出点,然后打印每个计数。
    • 考虑到原始行为和您的替换解决方案,我最后的刺探是您的 x 变量/ms_timer() 函数是/返回大于 16 位的 int,这会导致计算时差时意外的类型提升。
    【解决方案2】:

    我使用这段代码来说明错误和使用签名比较的可能解决方案。

    /* ========================================================================== */
    /*   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 旁边提供的延迟长度不正确,对吧?
    【解决方案3】:

    我曾经为这种情况编写如下代码。
    我用测试用例进行了测试,并确保它可以 100% 工作。
    此外,将下面代码中的uint16_t0xFFFFFFFF0xFFFF 更改为uint32_t,并使用32 位计时器滴答声。

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

    【讨论】:

      【解决方案4】:

      检查 ms_timer 是否

      编辑:如果可以的话,您还需要最多 uint32。

      【讨论】:

        【解决方案5】:

        避免该问题的最安全方法可能是使用带符号的 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();
        }
        

        【讨论】:

          【解决方案6】:

          这似乎适用于高达 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;
          }
          

          利用有符号整数的性质。 (twos complement)

          【讨论】:

            【解决方案7】:

            我发现使用不同的计时器 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);
            }
            

            【讨论】:

              【解决方案8】:

              有时我会这样做:

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

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2020-06-04
                • 1970-01-01
                相关资源
                最近更新 更多