Перейти к содержанию

Прерывание по переполнению таймера

Когда счётчик таймера доходит до максимума и начинает заново, аппаратура ставит флаг переполнения. Если разрешено прерывание, процессор тут же вызывает обработчик — и программа узнаёт, что прошёл ещё один полный период таймера. На этом событии держатся системные часы Arduino.

Обновлено Редакция GAW

Прерывание по переполнению таймера (timer overflow interrupt) — прерывание, которое вызывается, когда счётчик таймера переходит через максимальное значение и начинает счёт заново. У AVR признак события — флаг TOVn в регистре TIFRn, разрешение — бит TOIEn в регистре TIMSKn, обработчик — вектор TIMERn_OVF. У таймеров STM32 аналогичное событие называется событием обновления.

Как работает у AVR

В обычном режиме (Normal, WGM02:0 = 0) Timer0 считает вверх до 0xFF и переходит в 0; по даташиту ATmega328P флаг TOV0 ставится в том же такте, когда TCNT0 становится нулём. Microchip описывает флаг как девятый бит счётчика, который только ставится. Прерывание выполняется, если одновременно установлены бит I в SREG, TOIE0 и TOV0; при входе в обработчик флаг сбрасывается аппаратно, вручную его сбрасывают записью единицы.

Момент установки флага зависит от режима: в Fast PWM TOV ставится на каждом TOP, в Phase Correct — на BOTTOM. Период переполнений:

TOVF=N⋅(TOP+1)fclkT_\text{OVF} = \frac{N \cdot (\text{TOP} + 1)}{f_\text{clk}}

где N — коэффициент предделителя.

Расширение разрядности

Обработчик, который увеличивает переменную, делает из 8- или 16-битного таймера счётчик любой длины: старшие разряды считает программа, младшие — аппаратура. Microchip прямо упоминает этот приём в описании режима Normal.

millis() и micros() в Arduino

Ядро Arduino запускает Timer0 с делителем 64; переполнение происходит каждые 64 · 256 тактов, на 16 МГц — каждые 1024 мкс. Обработчик ISR(TIMER0_OVF_vect) прибавляет к timer0_millis целую миллисекунду, накапливает дробную часть в timer0_fract (в единицах по 8 мкс) и, когда набегает целая миллисекунда, добавляет ещё одну. micros() складывает число переполнений с текущим значением TCNT0 и учитывает уже выставленный, но ещё не обработанный флаг TOV0. Пошаговый разбор кода с номерами строк — в уроке «millis(), micros() и delay()», а настройка таймеров AVR — на странице «Таймеры AVR».

Частые вопросы

Как включить прерывание по переполнению Timer0 на AVR?

Записать единицу в бит TOIE0 регистра TIMSK0 и разрешить прерывания глобально (sei(), бит I в SREG), а обработчик объявить как ISR(TIMER0_OVF_vect). По даташиту прерывание выполняется, когда одновременно установлены I, TOIE0 и флаг TOV0. Флаг при входе в обработчик сбрасывается сам.

Как сбросить флаг TOV вручную?

Записать в него единицу, а не ноль: TIFR0 = (1 << TOV0). Запись нуля в остальные биты ничего не меняет. Чтение-изменение-запись всего регистра опасна: прочитанные единицы других флагов запишутся обратно и сбросят их. Исключение оговорено в даташите ATmega328P: инструкция SBI у этого кристалла меняет только указанный бит, поэтому для флагов в регистрах с адресами 0x00–0x1F её использовать можно.

Почему millis() в Arduino иногда прибавляет 2 мс за раз?

Потому что переполнение Timer0 на 16 МГц происходит каждые 64 · 256 / 16 = 1024 мкс, а не ровно через 1 мс. Обработчик прибавляет 1 мс и накапливает остаток 24 мкс в дробной части; когда она набегает до целой миллисекунды, к счёту добавляется лишняя единица. Поэтому некоторые значения millis() пропускаются.

Источники

  1. Microchip. ATmega48A/PA/88A/PA/168A/PA/328/P Data Sheet Complete (DS40002061B, 2020), разд. 15.7.1, 15.9.6, 15.9.7; табл. векторов прерываний, разд. 8.5 (флаги, сбрасываемые записью единицы; SBI/CBI для 0x00–0x1F) — Normal mode (WGM02:0 = 0): счёт вверх до TOP = 0xFF и с нуля, TOV0 ставится в том же такте, когда TCNT0 становится нулём; флаг ведёт себя как девятый бит, только ставится и не сбрасывается сам; вместе с прерыванием, которое автоматически сбрасывает TOV0, разрешение таймера можно увеличить программно; TIMSK0 бит 0 — TOIE0, прерывание выполняется при установленных I в SREG, TOIE0 и TOV0; TIFR0 бит 0 — TOV0, сбрасывается аппаратно при входе в обработчик или записью логической единицы; момент установки зависит от режима WGM; вектор 17 — TIMER0_OVF; в Fast PWM TOV0 ставится на каждом TOP
  2. Arduino. ArduinoCore-avr 1.8.8 (commit 86df345), cores/arduino/wiring.c — делитель 64, переполнение каждые 256 тиков: MICROSECONDS_PER_TIMER0_OVERFLOW = clockCyclesToMicroseconds(64 · 256); MILLIS_INC — целые миллисекунды, FRACT_INC — дробная часть, сдвинутая на 3, FRACT_MAX = 1000 >> 3; ISR(TIMER0_OVF_vect) прибавляет их к timer0_millis и timer0_fract и увеличивает timer0_overflow_count; millis() читает значение при запрещённых прерываниях

Нашли ошибку?