Сравнение безопасного таймера опрокидывания (тика)

У меня есть аппаратный счетчик, за которым я могу наблюдать для учета времени. Он считает миллисекунды и хранится в 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.


person JeffV    schedule 14.09.2008    source источник


Ответы (8)


На самом деле вам не нужно ничего делать здесь. Исходный код, указанный в вашем вопросе, будет работать нормально, если ms_timer() возвращает значение типа uint16_t.

(Также предполагается, что таймер не переполняется дважды между проверками...)

Чтобы убедиться в этом, проведите следующий тест:

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

dt будет равно 0x20.

person smh    schedule 14.09.2008
comment
Когда я запускаю исходный код, он работает до точки переполнения, а затем печатает для каждого счета. - person JeffV; 15.09.2008
comment
Мой последний удар в темноте, учитывая исходное поведение и ваше решение по замене, заключается в том, что ваша переменная x/функция ms_timer() является/возвращает целое число больше 16 бит, что вызывает непреднамеренное продвижение типа при вычислении разницы во времени . - person smh; 15.09.2008

Я использую этот код, чтобы проиллюстрировать ошибку и возможное решение с помощью сравнения со знаком.

/* ========================================================================== */
/*   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$ 
person bobc    schedule 04.06.2010
comment
Задержка, указанная рядом с INT_MAX, имеет неправильную длину, верно? - person AndresR; 05.10.2016

Раньше я писал код, подобный следующему, для такого случая.
Я протестировал тестовый пример и убедился, что он работает на 100%.
Кроме того, измените на uint32_t вместо uint16_t и 0xFFFFFFFF вместо 0xFFFF в приведенном ниже коде с 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;
}
person Hill    schedule 24.08.2017

Просто проверьте, соответствует ли ms_timer ‹ last_print_time, и если да, то добавьте 2^16 нет?

Редактировать: вам также нужно до uint32 для этого, если вы можете.

person Jason Punyon    schedule 14.09.2008

Вероятно, самым безопасным способом избежать этой проблемы было бы использование 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();
}
person ReaperUnreal    schedule 14.09.2008

Кажется, это работает для интервалов до 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;
}

Использует природу целых чисел со знаком. (дополнение до двух)

person JeffV    schedule 14.09.2008

Я обнаружил, что использование другого 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);
}
person Community    schedule 02.10.2008

Иногда делаю так:

#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
}
person Jeff    schedule 11.08.2012