Обработка опрокидывания таймера

#c #timer #embedded #real-time #atomic

#c #таймер #встроенный #в режиме реального времени #атомарный

Вопрос:

У меня есть 32-разрядный аппаратный таймер, который я хотел бы расширить до 64-разрядной эффективной длины в программном обеспечении.

В моей встроенной системе у меня есть 32-разрядный аппаратный «таймер ядра» (CT), который работает на частоте ~ 40 МГц, поэтому он завершается примерно через 107 секунд.

Это отлично подходит для точного определения времени периодов до 107 секунд. Но я хотел бы сделать одинаково точное время для более длительных периодов.

Он также имеет 32-разрядный регистр «период» — когда значение CT совпадает с регистром периода, генерируется прерывание.

Мой ISR выглядит следующим образом (упрощенный для наглядности):

 const UINT32 ONE_MILLISECOND = TICK_RATE/1000;

UINT64 SwRTC;

void CT_ISR(void) {
    PeriodRegister  = ONE_MILLISECOND;
    SwRTC  = ONE_MILLISECOND;
    ClearCTInterrupt();
}
  

Итак, теперь у меня есть 64-разрядный «SwRTC», который можно использовать для измерения более длительных периодов, но только с точностью до 1 миллисекунды, плюс 32-разрядный аппаратный таймер с точностью до 1/40 МГц (25 наносекунд). Оба используют одни и те же единицы измерения (TICK_RATE).

Как я могу объединить оба, чтобы получить 64-разрядный таймер, который одинаково точен, при этом получая прерывания с частотой 1000 Гц?

Моя первая попытка выглядела так:

 UINT64 RTC(void){

    UINT64 resu<

    DisableInterrupts(); // to allow atomic operations

    result = (SwRTC amp; 0xFFFFFFFF00000000ull)   ReadCoreTimer();

    EnableInterrupts();

    return resu<
}
  

Но это бесполезно, потому что, если CT завершится, когда прерывания будут отключены, я получу результат с небольшим числом в 32 битах младшего порядка, но без увеличения битов старшего порядка на ISR.

Может быть, что-то подобное сработает — прочитайте это дважды и верните более высокое значение:

 UINT64 RTC(void){

    UINT64 result1, result2;

    DisableInterrupts(); // to allow atomic operations

    result1 = (SwRTC amp; 0xFFFFFFFF00000000ull)   ReadCoreTimer();

    EnableInterrupts();

    DisableInterrupts(); // again

    result2 = (SwRTC amp; 0xFFFFFFFF00000000ull)   ReadCoreTimer();

    EnableInterrupts();

    if (result1 > result2)
        return result1;
     else
        return result2;
}
  

Я не уверен, сработает ли это или есть скрытая проблема, которую я пропустил.

Каков наилучший способ сделать это?

(Некоторые могут спросить, почему мне в первую очередь нужно так точно определять время таких длительных периодов. Это в основном для простоты — я не хочу использовать 2 разных метода синхронизации в зависимости от периода; Я бы предпочел использовать один и тот же метод все время.)

Комментарии:

1. Что такое TICK_RATE? Почему вы просто не увеличиваете еще один 32-разрядный счетчик каждый раз, когда происходит опрокидывание, а затем используете два объединенных поля для вычисления прошедшего времени.

2. TICK_RATE — это скорость, с которой тикает аппаратный таймер (такты в секунду). Я не получаю прерывания при переключении 32-разрядного таймера, поэтому я не могу этого сделать. (Потому что вместо этого я использую регистр периода для генерации прерываний с частотой 1 кГц.)

3. Хорошо, какое оборудование вы используете тогда?

4. «Я не хочу использовать 2 разных метода синхронизации в зависимости от периода; Я бы предпочел использовать один и тот же метод все время». Лучший совет, который кто-либо может вам дать: «Не делайте этого». Стандартная практика — использовать аппаратный счетчик для синхронизации до 1 секунды и использовать счетчик миллисекунд для синхронизации более 1 секунды. Вторая строка процедуры прерывания должна быть SwRTC ; такой, чтобы SwRTC считать только миллисекунды.

5. Для меня не имеет смысла одна вещь (если я правильно понял). Похоже, что в строке SwRTC = ONE_MILLISECOND; ONE_MILLISECOND нет 2<<32 . Можете ли вы указать точное значение TICK_RATE ? Потому что единственный способ для правильной работы этого — увеличивать SwRTC при 2<<32 каждом ReadCoreTimer() переполнении, и я не думаю, что это происходит. Другими словами, x % 100 это не то же самое, что x amp; 0xFF00 .

Ответ №1:

Я думаю, что я почти решил это сам:

 UINT64 Rtc(void){

    UINT64 softwareTimer = SwRTC;
    UINT32 lowOrderBits = softwareTimer;                        // just take low-order 32 bits
    UINT64 coreTimer = ReadCoreTimer();

    if (lowOrderBits > coreTimer)                               // if CT has rolled over since SwRTC was updated
        softwareTimer  = 0x100000000;                           // then increment high-order 32 bits of software count

    return (softwareTimer amp; 0xFFFFFFFF00000000ull)   coreTimer; 
}
  

Сначала считывается 64-разрядный программный таймер, затем 32-разрядный аппаратный таймер.

Аппаратный таймер (обновляется каждые 25 нС) всегда должен быть >= 32-разрядным младшим разрядом программного таймера (обновляется только каждые 1 мС).

Если это не так, это указывает на то, что аппаратный таймер был перенесен с момента считывания программного таймера.

Итак, в этом случае я увеличиваю слово старшего порядка программного таймера.

Затем просто объедините 32 бита старшего порядка из программного времени с 32 битами младшего порядка из аппаратного таймера.

Один приятный побочный эффект — нет необходимости отключать прерывания.

Единственная проблема, которую я вижу, заключается в том, что, если оптимизация компилятора переупорядочит код так, чтобы аппаратный таймер считывался первым? Тогда я мог бы получить прерывание, которое увеличивает программный таймер, прежде чем у меня появится возможность его прочитать.

Сначала я думал, что смогу это исправить, отключив прерывания при чтении обоих таймеров, но что, если компилятор переупорядочит код, чтобы DisableInterrupts() пришел слишком поздно?

Комментарии:

1. Помимо комментария к вопросу, переупорядочение инструкций здесь не единственная проблема. Что произойдет, если CT_ISR процедура прерывания будет вызвана между чтениями в SwRTC и ReadCoreTimer() ?

2. @Groo: Я не думаю, что это проблема. Если CT_ISR() вызывается между двумя чтениями, это повлияет на 32 бита SwRTC старшего порядка (это все, что я здесь использую), только если аппаратный CT недавно перевернулся (в последнюю миллисекунду). Это будет обнаружено оператором if(), который увеличит «softwareTimer», чтобы компенсировать это. (Вот почему я использую сохраненную копию SwRTC в softwareTimer в инструкции сравнения и возврата вместо SwRTC).) Так что я думаю, что все в порядке. Если я не ошибся… [Я все еще беспокоюсь о переупорядочении инструкций.]

3. Отвечая на мой собственный вопрос, повторное упорядочение инструкций: если и аппаратный таймер, и программный таймер объявлены «изменчивыми» (как и должно быть), компилятор не будет изменять порядок доступа к ним.

4. Одна из потенциальных проблем с этим решением в вытесняющей ОС: поток, выполняющий rtc (), может быть заменен на весь период таймера (107 секунд, маловероятно, но если у вас таймер с очень высоким разрешением с тиком 1нс или 0,1 нс, это более возможно), вы можете пропустить опрокидывание. Чтобы исправить это, вам нужно будет отключить вытеснение при вызове этой функции, чтобы убедиться, что логика работает так, как задумано.

Ответ №2:

Например, если это счетчик вверх, я обычно

 elapsed = ((nowtime-starttime)amp;MASK) (rollovers<<SIZE);
  

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

предполагая, что он отсчитывает каждое значение и не пропускает, скажем, 0xFF ..FFF до 0x00 … 01.

Downcounter просто делает все наоборот starttime-nowtime. сейчас> в прошлый раз.

Некоторые таймеры имеют прерывание опрокидывания, которое вы иногда можете просто опросить вместо прерывания, если хотите, и снова, если вы можете гарантировать, что выборка выполняется один или более раз за опрокидывание, вы можете просто использовать это, чтобы пометить количество опрокидываний.

Если в какой-то момент вы пропустите опрокидывание, то, естественно, вы будете отключены из-за количества таймера 4 гига или чего-то еще.

Некоторые аппаратные средства могут позволить вам использовать один таймер для генерации выходных тактовых импульсов, которые затем можно передавать в качестве входных данных другому таймеру и каскадировать таким образом (иногда они делают это на чипе).