Безопасное расширение TCNT1/ICR1 до 24 бит с помощью TOV1

Я надеюсь рассчитать внешние события на Arduino 328p, которые будут происходить достаточно медленно, чтобы они переполняли 16-битный счетчик TCNT1. В другом сценарии я бы заранее настроил счетчик, но в данном случае мне нужно полное разрешение таймера для других целей.

Поэтому я включаю прерывание TOV1 и просто увеличиваю 8-битное значение, позволяя ему переворачиваться по мере необходимости.

Затем при прерывании ICP1 я сдвигаю это значение << 16 в 32-битную переменную и добавляю значение, захваченное в ICR1, получая 24-битное значение, которое я могу использовать для измерения медленного интервала.

Есть ли в этом плане скрытые подводные камни? Возможные условия гонки, когда TOV1 еще не был обновлен, если ICR1 фиксируется только тогда, когда TCNT1 только что перевернулся, или генерирует TOV1 после захвата ICR1, но до того, как я переключу счетчик переполнения?

, 👍0


1 ответ


Лучший ответ:

1

Действительно, существует возможное условие расы. Рассмотрим этот наивный код, который предоставляет 32-битные метки времени:

/* Подсчет переполнений. */
volatile uint16_t timer1_overflow_count;
ISR(TIMER1_OVF_vect)
{
    timer1_overflow_count++;
}

/* События ввода меток времени. */
volatile uint32_t captured_timestamp;
volatile bool did_capture;
ISR(TIMER1_CAPT_vect)
{
    captured_timestamp = timer1_overflow_count << 16 | ICR1;
    did_capture = true;
}

Поскольку TIMER1_CAPT имеет более высокий приоритет, чем TIMER1_OVF, то:

  • Если ICF1 поднимается до TOV1, TIMER1_CAPT_vect запускается первым, и это нормально.

  • Если ICF1 и TOV1 поднимаются одновременно, TIMER1_CAPT_vect запускается первым, что все еще нормально, так как захваченное значение равно 0xffff.

  • Если TOV1 поднимается перед ICF1, то любой ISR может работать первым:

    • если процессор может сразу обработать IRQ TIMER1_OVF, то TIMER1_OVF_vect запускается первым, и результат верен

    • если обработка прерывания задерживается до тех пор, пока ICF1 не повысится, то TIMER1_CAPT_vect запускается первым и захватывает неправильное значение для timer1_overflow_count.

Решение

Существует необработанное переполнение, когда bit_is_set(TIFR1, TOV1). Переполнение может произойти одновременно или после захвата, и в этом случае оно не влияет на метку времени. Мы знаем, что это произошло до захвата, когда захваченное значение мало, где “маленький” может быть определен как имеющий самый значимый бит. Если это так, то мы должны добавить 0x10000 к полученной нами метке времени.

ISR, защищенный от гонки, может быть записан следующим образом:

ISR(TIMER1_CAPT_vect)
{
    uint16_t captured_low = ICR1;
    uint16_t captured_high = timer1_overflow_count;
    if (bit_is_set(TIFR1, TOV1) && !(captured_low & 0x8000)) {
        captured_high++;
    }
    captured_timestamp = (uint32_t) captured_high << 16 | captured_low;
    did_capture = true;
}
,

Спасибо за ваш анализ. Я понимаю все о вашем решении, кроме фразы *мы должны добавить 0x0100 к метке времени*. В коде вы (эффективно) добавляете "0x10000", что я и ожидал бы от пропущенного переполнения. Опечатка?, @Jim Mack

@JimMack: Упс! Вы правы, я допустил опечатку. Исправлено, спасибо!, @Edgar Bonet