Часы на макетке я собрал за вечер, а выровнять не смог за неделю. Секунды тикали в loop(): if (millis() - byla >= 1000) — прибавить единицу, раз в минуту перенести в минуты. На голом столе часы шли идеально. В сборе — с опросом датчика, дисплеем и печатью строк — за сутки отстали минут на пять. Причина честная: тик срабатывал только тогда, когда loop() добирался до проверки, а loop был занят — то датчик, то экран, то строка в порт на тридцать миллисекунд. Каждая секунда растягивалась на случайный хвост, и хвосты суммировались.
Первая помощь — аккуратный millis-гейт из урока про millis вместо delay: прибавлять к byla ровно тысячу, а не переприсваивать millis(). Это убирает накопление, но не устраняет корень: момент тика всё ещё зависит от занятости loop(). Когда секунда обязана быть секундой — часы, шедулеры, генерация тона с ровным шагом — тик должен приходить сам, из железа. Самый короткий путь к такому тику — прерывание по таймеру.
В этом уроке настраиваем Timer2 в режим CTC через регистры: тысяча прерываний в секунду, свой счётчик миллисекунд, мигалка, которой безразлично, чем занят loop(). Ограничение назову сразу: движок симулятора ISR-макросы не исполняет, поэтому скетч с регистрами даю как код для платы, а механику CTC собираем в симуляторе из переменных — она при этом честная, один в один с железной логикой.
CTC: сброс по совпадению
По умолчанию таймер живёт в Normal-режиме: считает от нуля до верха разрядности, перетекает и начинает заново. Для 8-битного Timer2 период — всегда 256 тактов, дробный период не собрать. Режим CTC — Clear Timer on Compare — меняет правило: верх задаёте вы, записью в регистр сравнения OCR2A. Счётчик тикает, и как только его значение догоняет OCR2A, аппаратура в один такт сбрасывает его в ноль и взводит флаг совпадения OCF2A; если прерывание разрешено, зовётся обработчик.
Период CTC — OCR2A плюс один такт: счёт идёт с нуля, и нулевое значение тоже считается. Настраиваю миллисекундный тик: кварц 16 МГц, предделитель 64 — 250 000 тиков в секунду; на период 1000 Гц нужно 250 тактов, значит OCR2A = 249. Ошибка на единицу тут не косметика — ниже посчитаем, во что превращается 250 вместо 249 за сутки. Предделитель и границу подбирают парой: чем больше граница, тем точнее можно попасть в период и тем реже дёргается процессор.
Почему Timer2, а не соседи: Timer0 занят millis() — его регистры я не трогаю вообще, Timer1 длиннее и отдан серво; Timer2 в ядре работает только на tone(), и пока зуммер молчит, он свободен. Классический выбор под свой тик — тем более что tone() и наш тик редко нужны одновременно.
// Timer2: прерывание ровно 1000 раз в секунду.// 16 МГц / предделитель 64 = 250 000 тактов в секунду;// период CTC = OCR2A + 1 = 250 тактов = 1 мс.volatile int tik = 0; // меняется из ISR — обязательно volatile ISR(TIMER2_COMPA_vect) { // зовётся сама, без вашего участия tik = tik + 1; // коротко: один инкремент, никаких Serial} void setup() { Serial.begin(9600); pinMode(12, OUTPUT); TCCR2A = (1 << WGM21); // WGM21=1, WGM22=0: режим CTC TCCR2B = (1 << CS22); // CS22=1, остальные CS=0: предделитель 64 OCR2A = 249; // граница сравнения: 250 тактов на период TIMSK2 = (1 << OCIE2A); // разрешаем прерывание по совпадению A} void loop() { static int bylo = 0; if (tik - bylo >= 1000) { // прошла секунда тиков bylo = bylo + 1000; if (digitalRead(12) == HIGH) { digitalWrite(12, LOW); } else { digitalWrite(12, HIGH); } Serial.print("sec="); Serial.println(bylo / 1000); }}Разбор по строкам. TCCR2A = (1 << WGM21) — бит формы волны: включаем CTC. TCCR2B = (1 << CS22) — биты тактирования: предделитель 64; забытый TCCR2B — таймер стоит, прерываний ноль, и это самая тихая ошибка из всех возможных. OCR2A = 249 — граница сравнения. TIMSK2 = (1 << OCIE2A) — разрешение прерывания по совпадению; без него флаг OCF2A исправно взводится, но никто его не читает.
ISR(TIMER2_COMPA_vect) зовётся сама тысяча раз в секунду, и от того, что в ней написано, зависит всё. Правила те же, что в уроке про внешние прерывания: только volatile-переменные, минимум тактов, никакого Serial и delay. Масштаб потерь: ISR на 50 тактов при тысяче вызовов в секунду съедает 50 000 тактов из 16 000 000 — меньше трёх десятых процента процессора. Тот же ISR с Serial.print — сотни тактов и миллисекунды блокировок на каждый тик: loop задыхается, а millis() на реальной плате внутри ISR ещё и застывает.
Рабочая схема такая: в ISR только tik = tik + 1, всё остальное делает loop() — сравнивает tik с прошлым значением, мигает, печатает, кормит часы. Разделение то же, к которому мы пришли в уроке про конечный автомат: автомат живёт в loop, железо считает время. И volatile не украшение, а условие жизни: без него компилятор имеет право закэшировать tik, и loop будет вечно смотреть на устаревшее значение.
Модель CTC в симуляторе
Симулятор регистры не исполняет, поэтому модель собрана из переменных — и это честно: шаги модели повторяют шаги конфигурации один к одному. takty — модель тактов: один оборот loop() с delay(1) равен одному такту, а предделитель 8 пропускает восемь тактов из восьми, учитывая один. tcnt — модель счётчика TCNT2: растёт на каждый учтённый такт и на равенстве с SRVNIT — моделью OCR2A — сбрасывается в ноль, увеличивая tiki. В реальном скетче ровно это делает железо: сброс по совпадению и вызов ISR(TIMER2_COMPA_vect) без единой строчки вашего кода. Числа модели уменьшены против настоящих: в железе предделитель 64 и граница 249, здесь — 8 и 20, чтобы переполнения было видно глазом.
Светодиод переключается каждые десять тиков — секундная мигалка модели. Печать раз в сто миллисекунд выкладывает tcnt= и tiki=, и плоттер рисует главное: пилу счётчика от нуля до границы с обрывом на совпадении — ту же картину в живом графике, которую на плате показал бы осциллограф на выводе OC2A. Поменяйте SRVNIT с 20 на 10 — пила вдвое короче, тики вдвое чаще: период CTC равен границе плюс один такт, и вся настройка частоты сводится к одному числу.
- Поменяйте SRVNIT с 20 на 10: тики пойдут вдвое чаще, светодиод — вдвое быстрее. Период CTC равен границе плюс один такт: изменили число — изменили частоту, предделитель не трогали.
- Сломайте CTC: уберите строку tcnt = 0 из блока совпадения и запустите. tcnt уедет за границу и не вернётся, tiki замрёт — на плате без бита WGM21 таймер просто считает до 255 и перетекает: Normal-режим, у которого период один на всех.
- Посчитайте границу для прерывания раз в секунду на Timer1: предделитель 1024 даёт 15 625 тиков в секунду, значит в регистр сравнения пишем 15 624. Проверьте, что значение влезает в 16 бит — до 65 535 влезает с запасом.
- Перенесите регистровый скетч из code-блока на настоящую Uno и сравните sec= с секундами телефона за минуту. На столе разницы вы не увидите — и именно поэтому ошибка в единицу границы, съедающая шесть минут в сутки, так коварна.
- CTC: верх счёта задаёт регистр сравнения OCR2A; при совпадении счётчик сбрасывается аппаратно и зовётся прерывание; период равен границе плюс один такт
- Миллисекундный тик на Uno: кварц 16 МГц, предделитель 64, OCR2A = 249 — 250 тактов на период, ровно 1000 Гц
- Цепочка регистров: WGM21 включает CTC, CS22 задаёт предделитель, TIMSK2 и бит OCIE2A разрешают прерывание; без последнего всё тикает молча
- ISR — только volatile-инкремент, решения принимает loop(): тысяча коротких обработчиков в секунду съедает доли процента процессора, длинные — задавливают скетч
- Ошибка на единицу в границе — это ошибка частоты навсегда: OCR2A = 250 вместо 249 отнимает у часов почти шесть минут в сутки
- 1. Почему для тика 1000 Гц в OCR2A пишут 249, а не 250?
- 2. В OCR2A записали 250 вместо 249. Что произойдёт?
- 3. Таймер тикает, флаг совпадения взводится, но прерывание не приходит. Что забыли?
- 4. Что допустимо в ISR, который зовётся 1000 раз в секунду?
Частые вопросы по теме урока
- Почему для тика 1000 Гц в OCR2A пишут 249, а не 250?
- CTC отсчитывает 0, 1, ..., OCR2A и сбрасывается: всего OCR2A + 1 такт на период. 249 + 1 = 250 тактов при 250 000 тактов в секунду — ровно 1000 Гц. потому что период — граница плюс один такт: счёт идёт с нуля, и ноль тоже тик
- В OCR2A записали 250 вместо 249. Что произойдёт?
- Период станет 251 такт вместо 250, частота — 996,02 Гц. Отставание 0,4 процента за сутки — это 345 секунд. Кварц тут ни при чём: ошибка в тактовой сетке. частота упадёт до 996 Гц — часы отстанут почти на шесть минут в сутки
- Таймер тикает, флаг совпадения взводится, но прерывание не приходит. Что забыли?
- Флаг OCF2A взводится независимо, но обработчик вызывается только при разрешённом прерывании в регистре маски TIMSK2. Это самая тихая из ошибок: всё работает, кроме самого события. разрешение: TIMSK2 = (1 << OCIE2A)
- Что допустимо в ISR, который зовётся 1000 раз в секунду?
- ISR должна укладываться в десятки тактов: инкремент volatile-счётчика или взвод флага. Serial, delay и рисование внутри обработчика на реальной плате вешают скетч. tik = tik + 1 по volatile-переменной