Прерывание по переполнению таймера
Когда счётчик таймера доходит до максимума и начинает заново, аппаратура ставит флаг переполнения. Если разрешено прерывание, процессор тут же вызывает обработчик — и программа узнаёт, что прошёл ещё один полный период таймера. На этом событии держатся системные часы 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. Период переполнений:
где 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() пропускаются.
Источники
- 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
- 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() читает значение при запрещённых прерываниях
Нашли ошибку?
Смотрите также
- Таймеры AVRTimer0/1/2, режимы WGM, предделители, формулы ШИМ, захват, примеры
- millis, micros и delayПрерывание Timer0, FRACT_INC, micros через TCNT0, delay и свой тик
- Предделитель таймераPrescaler: делитель тактов перед счётчиком таймера
- ПрерываниеЗапрос периферии, вектор и обработчик прерывания
- Fast PWMШИМ со счётом только вверх: вдвое быстрее Phase Correct
- Событие обновления (UEV)Момент, когда таймер применяет новые ARR, PSC и CCRx