Будильник-фоторамка был моим самым нарядным проектом: шторка на серво открывалась к рассвету, зуммер играл мажорную трель, подсветка плавно набирала яркость через analogWrite(9, ...). Каждая часть по отдельности работала. Собранные вместе — серво честно открывалось, трель играла, а подсветка застыла на минимуме: analogWrite на пин 9 больше не влиял на яркость. Я полчаса перепаивал провода, проверял резистор, перешивал плату — пока не дошёл, что сломался не пин. У пина больше не было таймера.
Так я познакомился с картой занятости Uno. У ATmega328P три аппаратных таймера, и все три расхватаны ещё до вашего setup(): один крутит millis() и delay(), второй — Servo.h, третий — tone(). Библиотеки забирают таймер целиком и молча, а всё, что было на нём подвешено, — ШИМ соседних пинов — умирает без единой ошибки в мониторе. В этом уроке — карта: какой таймер на чём сидит, что такое предделитель и разрядность, почему пины 5 и 6 мигают вдвое чаще остальных и как разводить периферию, чтобы ничего не гасило ничего.
Разобраться стоит не ради теории: примерно половина загадочных багов с ШИМ и таймингами на Uno — конфликт на таймерах. В конце — симулятор, который показывает механику честно, на переменных: один такт, три счётчика с разными предделителями и переполнение, от которого рождается мигание.
Что такое аппаратный таймер
Таймер — это счётчик, который тикает сам, без вашего кода. Источник тиков — кварц на 16 МГц; столько тиков в секунду ни одному скетчу не нужно, поэтому предделитель делит частоту — на 8, на 64, до 1024. Счётчик прибавляет единицу на каждый уцелевший такт и считает от нуля до верхней границы своей разрядности: 8 бит дают максимум 255, 16 бит — 65 535. По пути он следит за двумя событиями: переполнение — счётчик прыгнул через максимум, и совпадение — он догнал число, заранее записанное в регистр сравнения OCR. На каждом событии таймер умеет дёрнуть аппаратно: взвести флаг, позвать прерывание, переключить ШИМ-пин. Как именно зовутся прерывания — картина из урока про attachInterrupt, только источник события не пин, а сам счётчик.
Всё, что вы считали софтверно, у таймера получается без единой строчки в loop(). Мигалки на millis() из соответствующего урока — это чужой таймер, работающий на вас: millis() просто пересчитывает тики Timer0. И ШИМ из урока про analogWrite — тоже таймеры: duty cycle 0-255 совпадает с разрядностью счётчика не по красивой случайности, а по устройству.
И теперь карта занятости. Ядро Arduino при старте настраивает все три таймера под себя, у каждого — свои ШИМ-пины. Пока не подключено ни одной библиотеки, всё делится мирно. Конфликт начинается, когда два потребителя претендуют на один таймер, — и проигрывает всегда более слабый: ваш analogWrite, а не библиотека.
| Таймер | Разрядность | Предделители | Кем занят по умолчанию | Что сломается при конфликте |
|---|---|---|---|---|
| Timer0 | 8 бит, счёт до 255 | 1, 8, 64, 256, 1024 | millis(), delay(), micros() | ШИМ на пинах 5 и 6; правка его регистров ломает всю хронометрию скетча |
| Timer1 | 16 бит, счёт до 65 535 | 1, 8, 64, 256, 1024 | Servo.h | analogWrite на пинах 9 и 10 |
| Timer2 | 8 бит, счёт до 255 | 1, 8, 32, 64, 128, 256, 1024 | tone() | analogWrite на пинах 3 и 11; зато единственный умеет ход от часового кварца 32 768 Гц |
Числа, из которых складывается millis(). Timer0 тикает через предделитель 64: 16 000 000 / 64 = 250 000 тиков в секунду. Счётчик 8-битный — переполняется каждые 256 тиков, то есть 976,5 раза в секунду. Один тик счётчика — 4 мкс, 256 тиков — 1024 мкс: не ровно миллисекунда, а чуть больше, и ядро добирает дробь коррекцией, чтобы millis() не уплыл. Отсюда же и единственное отличие частот ШИМ: на пинах 5 и 6 он выходит на 976 Гц, а на 3, 9, 10 и 11 — на 490 Гц. Пины с тильдой не равны между собой, потому что их обслуживают разные таймеры в разных настройках.
Timer1 — старший: 16 бит, счёт до 65 535. Ему отдают задачи, где нужна дальность, а не скорость. Плавное движение серво сидит на Timer1 целиком: Servo.h генерирует импульсы с периодом 20 мс (50 Гц) именно его средствами, и пины 9-10 для ШИМ закрыты, пока библиотека живёт. Timer2 — младший из рабочих: он кормит зуммер и tone(), поэтому тональность звука — это буквально настройка таймера, отсюда и границы 31-65 535 Гц из того урока. У Timer2 есть и уникальный талант: он умеет ходить от внешнего часового кварца 32 768 Гц вместо основного генератора — режим для часов реального времени; на Uno я в это не лезу, потому что есть DS3231 с собственным кварцем и температурной компенсацией.
Мнение с границей: Timer0 я не трогаю никогда. Однажды я поднял его предделитель ради более точного ШИМ на 5-м пине — millis() замедлился в восемь раз, delay() вместе с ним, и все таймауты скетча поехали; двадцать минут я искал магию в датчике, а она была в одном бите CS. Если таймера не хватает — смотрите в сторону четвёртого, сторожевого: watchdog и режимы сна закрывают большинство желаний получить ещё один независимый тик.
// Серво на 9 (Timer1), tone на 8 (Timer2), ШИМ-подсветка на 6 (Timer0).#include <Servo.h> Servo shtorka;int yarkost = 0;int shag = 1; void setup() { shtorka.attach(9); // импульсы 50 Гц от Timer1: ШИМ на 10 теперь мёртв pinMode(8, OUTPUT); pinMode(6, OUTPUT); // пины 5 и 6: ШИМ 976 Гц, на остальных 490 Serial.begin(9600);} void loop() { shtorka.write(90); // серво живёт само, от Timer1 tone(8, 880, 120); // Timer2: пока играет, пины 3 и 11 молчат analogWrite(6, yarkost); // Timer0: millis() рядом, регистры не трогаем yarkost = yarkost + shag; if (yarkost >= 255) shag = -1; if (yarkost <= 0) shag = 1; delay(30);}Модель: один такт, три счётчика, три скорости
Движок симулятора регистры TCCR0B не эмулирует — и не надо: карта таймеров про железо. Но механику счёта — такт, предделитель, переполнение — можно собрать честно из переменных, и ниже она собрана. Один оборот loop() с delay(1) — один модельный такт. Три переменные t0, t1 и t2 изображают три счётчика с предделителями 1, 8 и 64: каждый растёт через раз и сбрасывается, перепрыгнув через TOP 255 — верхний предел 8-битного счётчика. На реальной плате те же шаги делают биты CS в регистрах TCCR, только такт там — 4 микросекунды от кварца, а не миллисекунда от delay(1).
Светодиод мигает от переполнения t0 — ровно так рождаются миллисекунды: не магия, а счётчик, дошедший до верха. Печать раз в сто миллисекунд выкладывает t0=, t1= и t2= в монитор, и плоттер разложит их в три пилы разной крутизны: у t0 склоны самые крутые, t2 ползёт в шестьдесят четыре раза медленнее. За минуту наблюдения видно всё, что говорит таблица выше: один такт — три счётчика — три скорости.
- Замедлите t0: оберните строку t0 = t0 + 1 в if (takty % 8 == 0), как у t1. Светодиод замедлится в восемь раз — наглядная цена предделителя, и это ровно то, что случилось с millis() в моей истории с битом CS.
- Посчитайте переполнения Timer0 за сутки при предделителе 64: 976,5 переполнения в секунду на 86 400 секунд. Ровно поэтому millis() нуждается в дробной коррекции — целыми тиками по 1024 мкс миллисекунду не собрать.
- Уберите сброс if (t0 > TOP) t0 = 0 и запустите: счётчик уедет за 255 и продолжит расти. На плате 8-битный счётчик так не умеет — он перетекает через верх; суть Normal-режима именно такая: счёт до верха, флаг, перетекание.
- Сверьте свою макетку с картой: если в скетче живут и серво, и tone(), ШИМ-пины у вас должны быть только 5 и 6 — остальные тильды при занятых Timer1 и Timer2 мертвы.
Переполнения, прерывание TOIE0 и цена тактов: выключи — и millis() замрёт у тебя на глазах.
Подгружаем интерактив…
- Таймер — счётчик от кварца: предделитель делит частоту, разрядность задаёт верх, события — переполнение и совпадение с регистром OCR
- Timer0 (8 бит) обслуживает millis(), delay(), micros() и ШИМ на 5/6; Timer1 (16 бит) — Servo.h и ШИМ на 9/10; Timer2 (8 бит) — tone() и ШИМ на 3/11
- Числа Timer0 при предделителе 64: 250 000 тиков в секунду, переполнение каждые 1024 мкс — отсюда 976 Гц ШИМ на пинах 5 и 6 против 490 Гц на остальных
- Конфликты молчаливы: Servo.h гасит ШИМ 9/10, tone() — 3/11, правка Timer0 ломает всю хронометрию; ни одной ошибки в мониторе не будет
- Три таймера — три скорости одного и того же такта: разводите периферию по карте занятости до сборки схемы, а не после первого мёртвого пина
- 1. Какой таймер обслуживает millis() на Uno?
- 2. В скетч добавили Servo.h и вызвали attach(). Что отвалится?
- 3. Почему ШИМ на пинах 5 и 6 выходит на 976 Гц, а на 3, 9, 10, 11 — на 490 Гц?
- 4. Чем Timer1 отличается от Timer0 и Timer2?
Частые вопросы по теме урока
- Какой таймер обслуживает millis() на Uno?
- Ядро настраивает Timer0 на предделитель 64 и пересчитывает его переполнения в миллисекунды с дробной коррекцией. Поэтому трогать Timer0 опаснее всего. Timer0 — 8 бит с предделителем 64
- В скетч добавили Servo.h и вызвали attach(). Что отвалится?
- Servo.h работает на Timer1, а пины 9 и 10 — его ШИМ-выходы. Timer0 и Timer2 библиотека не трогает, millis() продолжает идти. ШИМ на пинах 5 и 6
- Почему ШИМ на пинах 5 и 6 выходит на 976 Гц, а на 3, 9, 10, 11 — на 490 Гц?
- Частота ШИМ — свойство таймера, а не пина. У Timer0 настройка жёстко связана с обслуживанием millis(), отсюда и вдвое более высокая частота на его пинах. их обслуживает Timer0 в той настройке, которую ему задало ядро для millis()
- Чем Timer1 отличается от Timer0 и Timer2?
- Timer1 — единственный 16-битный таймер ATmega328P. Ядро не оставляет его свободным: его под себя забирает Servo.h, когда та подключена. 16 бит: счёт до 65 535 — отсюда импульсы серво и длинные интервалы