Термостат для теплицы выкинул фокус, к которому я не был готов. Секундный тик я уже умел делать регистрами — Timer2 в CTC из урока про прерывание по таймеру жил в проекте с самой весны. Потом я добавил в тот же скетч зуммер тревоги через tone() — и часы встали: tone() забрал Timer2 себе и молча переписал мои регистры. Карта таймеров из обзора ATmega328 говорит это прямо, но я в тот день её не перечитал.
Дорог было две. Первая — библиотека TimerOne из соответствующего урока: две строки и готово. Во флеше Nano оставалось 700 байт, а библиотека ест около двух килобайт — мимо. Вторая дорога — те же регистры, что я уже писал для Timer2, только на Timer1. Там меня ждал сюрприз: регистры стали шестнадцатибитными, порядок записи в них строгий, зато период, который на Timer2 собирался из крошки, здесь лезет в один регистр целиком — секунда одним числом.
В этом уроке: как Timer1 считает в CTC-режиме и почему 16 бит меняют всё, откуда берётся таблица предделителей и как по ней выбрать пару «предделитель плюс граница» так, чтобы период получился без накопленной ошибки. Практика — секундный тик для термостата, который уживается с tone() на Timer2, и разбор ловушки: Servo.h отбирает Timer1 так же нагло, как tone() отбирает Timer2.
16 бит против 8: что меняется на деле
Timer2 из старого урока — восьмибитный: его граница сравнения не бывает больше 255. При предделителе 1024 один тик длится 64 мкс, и максимальный период Timer2 — 256 тиков, то есть 16,4 миллисекунды. Секунду на нём собирают в программном счётчике: ISR каждые 16 мс, счётчик тиков в переменной, шестьдесят два прерывания на каждую секунду. Работает, но это уже не «аппаратура считает время», это я считаю время руками и плачу за каждый визит в обработчик.
Timer1 считает до 65535. При том же предделителе 1024 частота тиков 15 625 в секунду — и целая секунда это ровно 15 625 тиков. Число лезет в 16 бит с запасом: пишу в границу сравнения 15 624 (на единицу меньше, потому что нулевой такт периода тоже считается) — и таймер сам, без единого программного пересчёта, раз в секунду взводит прерывание. Разрешение периодов — от долей миллисекунды до 4,19 с (65536 тиков по 64 мкс), всё одним регистром.
Регистров в игре четыре, и все имена знакомые по Timer2, только с единицей: TCCR1A — форма волны, TCCR1B — режим CTC и предделитель, OCR1A — граница сравнения, TIMSK1 — разрешение прерывания. Одна тонкость новенькая: OCR1A — 16-битный регистр, физически он состоит из двух половинок, и чип переливает их через временный регистр. Компилятор соблюдает порядок сам, когда ты пишешь OCR1A = 15624; вручную раскладывать байты пришлось бы только в ассемблерных вставках: старший байт — первым, младший — вторым. В loop() живое значение OCR1A не меняю: обновление на лету для 16-битных регистров делается только разом, из одной переменной.
// Timer1 в CTC: прерывание ровно раз в секунду, без библиотек.// 16 МГц / 1024 = 15625 тиков в секунду; период = OCR1A + 1 тик,// поэтому граница = 15624. Timer0 (millis) и Timer2 (tone) не трогаем.volatile int sekundy = 0; ISR(TIMER1_COMPA_vect) { // зовётся раз в секунду сама sekundy = sekundy + 1; // только счёт: volatile, никакого Serial} void setup() { Serial.begin(9600); pinMode(12, OUTPUT); TCCR1A = 0; // WGM11/WGM10 = 0: пины таймера молчат TCCR1B = (1 << WGM12) | (1 << CS12) | (1 << CS10); // WGM12 = 1: режим CTC; CS12+CS10: предделитель 1024 OCR1A = 15624; // 15625 тиков на период = ровно 1 с TIMSK1 = (1 << OCIE1A); // разрешаем прерывание по совпадению A} void loop() { static int proshlo = 0; if (sekundy - proshlo >= 10) { // отчёт каждые 10 секунд proshlo = proshlo + 10; digitalWrite(12, !digitalRead(12)); // маячок живого такта Serial.print("minuty: "); Serial.println(sekundy / 60); }}Разбор по строкам. TCCR1A = 0 — пины 9 и 10 таймер не трогает, аппаратного toggle не будет. TCCR1B собирает два дела сразу: бит WGM12 включает CTC, биты CS12 и CS10 ставят предделитель 1024. Забытый TCCR1B — та же тихая ошибка, что и на Timer2: таймер стоит, прерываний ноль, монитор молчит. OCR1A = 15624 — граница; единица разницы (15625 вместо 15624) не косметика — один лишний тик на период это 64 мкс в каждую секунду, за сутки часы уедут на 5,5 секунд. TIMSK1 = (1 << OCIE1A) — разрешение прерывания; без него флаг совпадения исправно взводится, но никто его не читает.
Внутри ISR — те же правила, что в уроке про внешние прерывания: volatile-переменные, минимум тактов, никакого Serial и delay. Выгода именно на длинных периодах: раз в секунду чип может позволить себе обработчик хоть на сотню тактов — это 0,0006 процента времени. Сравни с миллисекундным тиком Timer2, где тысяча визитов в секунду приучает к дисциплине «только инкремент». И то, что ISR раз в секунду зовётся сама, — главный подарок: loop() может утонуть в чтении датчика хоть на полминуты, секунды продолжат идти.
Пара «предделитель плюс граница» выбирается под целость: период обязан раскладываться в целое число тиков. Секунда при 1024 — 15 625 тиков, целое, идеально. Полсекунды при 1024 — 7 812,5 тика: дробь, период поплывёт на полтакта каждый раз. Лечение — сдвинуть предделитель: при 256 тиков в секунду 62 500, полсекунды — ровно 31 250. Эту арифметику я держу в таблице ниже и перепроверяю на бумажке до записи регистров, а не после недели отставших часов. И отдельная мина: Servo.h отбирает Timer1 целиком — разбор конфликта у меня уже был; серво в проекте и секундный тик на Timer1 не живут в одном скетче.
| Предделитель | Тик счётчика | Макс. период (65536 тиков) | Секунда — это тиков |
|---|---|---|---|
| 1 | 62,5 нс | 4,1 мс | 16 000 000 — не влезает |
| 8 | 0,5 мкс | 32,8 мс | 2 000 000 — не влезает |
| 64 | 4 мкс | 262 мс | 250 000 — не влезает |
| 256 | 16 мкс | 1,05 с | 62 500 — влезает |
| 1024 | 64 мкс | 4,19 с | 15 625 — влезает |
- 1. Какой регистр задаёт верх счёта Timer1 в CTC-режиме (режим 4)?
- 2. Почему секундный тик собран на предделителе 1024, а не на 64?
- 16 бит меняют всё: секунда одним регистром (OCR1A = 15624 при предделителе 1024), без программного пересчёта
- период = (OCR1A + 1) × тик предделителя; проверяй целость тиков и максимум 65536 до записи
- лишний тик в границе = 64 мкс на каждую секунду = 5,5 с уехавших часов за сутки
- Timer1 делит судьбу с Servo.h: серво в скетче — тик на Timer1 не собирают
Частые вопросы по теме урока
- Какой регистр задаёт верх счёта Timer1 в CTC-режиме (режим 4)?
- В режиме CTC (WGM12=1) счётчик TCNT1 растёт до значения OCR1A и аппаратно сбрасывается в ноль. TIMSK1 только разрешает прерывание, TCCR1A — форма волны, писать период в TCNT1 не нужно. OCR1A — граница сравнения, по совпадению с ней счётчик сбрасывается
- Почему секундный тик собран на предделителе 1024, а не на 64?
- 16 МГц / 64 = 250 000 тиков в секунду — больше максимума 65535, регистр обрежет такое значение молча. При 1024 секунда — целые 15 625 тиков, граница 15624 влезает с запасом. секунда при 64 — это 250 000 тиков, граница не влезает в 16 бит