Сигнализация на гараж: сирена через tone() на зуммере, поворотные фары на сервоприводе, подсветка на диммере. По отдельности всё работало; в одном скетче получился консерт — сирена свистит, сервоповорот дёргается рывками, подсветка замерла вполнакала. Три библиотеки воевали за процессорные таймеры, и выигрывала каждый раз одна. Это тот случай, где теоретическое «у Uno три таймера» превращается в конкретную схему «кто кого съест», и лучше собрать её заранее, чем искать виноватого в порту. Гараж тот я перебирал три вечера, и два из них ушло на то, чтобы понять: код ни при чём, воюет железо под скетчем.
Разбор занял час и свёлся к таблице занятости, которую я с тех пор держу над столом. tone() занимает Timer2 — и пока звучит нота, аппаратный ШИМ на пинах 3 и 11 молчит: это прямо в справке по функции, но кто читает справку до того, как подсветка умерла. Servo.h под свои импульсы берёт Timer1 — analogWrite на пинах 9 и 10 исчезает, и об этой паре мы подробно говорили в уроке про Servo против ШИМ. Timer0 отдан millis() и delay() — его не отдаёт никому даже сама среда. Итог простой: на Uno всего три счётчика, и каждый уже занят с момента старта — среда настраивает их до первой вашей строки.
Здесь разберу карту занятости целиком, разберу гаражную ловушку с сиреной и сервоповоротом и два рабочих решения: звук в паузах на millis-расписании и библиотека Tone, где таймер выбирается строкой конфигурации. База по звуку — в уроке про зуммер и tone(); карта таймеров в общем виде — в обзоре таймеров ATmega328. Если ваша плата не Uno, карту придётся перечитать: на Mega, например, tone() тоже берёт Timer2, но ШИМ-пины у таймеров другие, и таблица жильцов выглядит иначе.
Карта занятости: кто у кого что отбирает
Почему так: таймер — общий счётчик, и каждая библиотека настраивает его под свой ритм. tone() перестраивает Timer2 на частоту ноты — хоть 1800 герц сирены, хоть 440 герц мелодии — и стоящий на том же счётчике ШИМ пинов 3 и 11 складывает полномочия. Нота закончилась, noTone() вызвана, но регистры остаются перекроенными до следующего analogWrite, который заново настроит счётчик. Отсюда симптом «подсветка умерла при звуке и ожила при перерисовке» — молча, без единого сообщения в мониторе. Частота ноты задаётся делением кварца: 1800 герц сирены — это конкретное значение в регистре сравнения Timer2, и пока оно там стоит, счётчик служит звуку, а не ШИМ.
Servo.h держит Timer1 постоянно, а не эпизодически: attach() ставит прерывания каждые 20 мс и не отпускает счётчик до detach(). Поэтому конфликт с сервами не выглядит как помехи — ШИМ на 9 и 10 пропадает насовсем. У tone() есть и вторая рамка: одна нота на одном пине за раз, второй вызов tone() перехватит звук, а не подыграет в унисон. SoftwareSerial таймеров не берёт вовсе — он жжёт процессор запретами прерываний, и это другая история с другим лечением: каждый байт на 9600 бод — примерно десять миллисекунд, когда все прерывания запрещены.
У каждого конфликта на карте своё время жизни, и лечатся они по-разному. tone() ломает ШИМ эпизодически: пока нота звучит, пины 3 и 11 молчат; после noTone() и свежего analogWrite счётчик перенастраивается, и подсветка оживает — потеря обратима. Servo.h работает иначе: attach() перекраивает Timer1 под кадры по 20 мс, и до detach() пины 9 и 10 не оживут, сколько ни вызывай analogWrite. А строка с Timer0 — не про потери, а про запрет: на его заводских настройках стоят millis, delay и всё расписание скетча, так что селить туда никого нельзя ни за какой звук.
Практический вывод карты: у каждого таймера один хозяин за раз, и расписание жильцов стоит проверять до сборки, а не после. Если звук нужен вместе с ШИМ на 3 или 11 — ищите другой пин или другую библиотеку звука; если вместе с сервами — не трогайте Timer1. Вся смена частот через регистры, о которой пойдёт речь в следующем уроке, опирается на эту же карту: сначала решить, кто хозяин, потом крутить биты. Проверка занимает минуту и состоит из одного вопроса: какая библиотека в этом скетче последней трогала регистры TCCR — её настройка и победила.
| Жилец | Таймер | Что теряет скетч, пока жилец занял счётчик |
|---|---|---|
| tone() | Timer2 | ШИМ на пинах 3 и 11; вторая нота тоже не прозвучит: нота одна на весь чип |
| Servo.h | Timer1 | ШИМ на пинах 9 и 10; прерывания TimerOne и его тики |
| TimerOne.h | Timer1 | серво и ШИМ 9/10: делит счётчик с Servo.h — кто встал позже, тот и победил |
| millis()/delay() | Timer0 | системные часы — не отдавать никому, даже за красивый звук, и не крутить его предделитель ради экспериментов |
Теперь гаражная ловушка целиком. Сервоповорот фар крутил Servo.h — Timer1. Сирену делал tone() — Timer2. Диммер подсветки сидел на analogWrite(11) — и умер при первом же свисте: пин 11 из того же Timer2. Я перевёл диммер на свободный пин, а сирену по совету форума поставил на библиотеку Tone с конфигурацией на Timer1 — чтобы развести звук и подсветку. Результат: сирена звучит, а сервоповорот дёргается каждые полсекунды — библиотека Tone перезаписывает регистры Timer1 у живого Servo.h. Свист и дёрганье одновременно — классика разводки без карты: я вылечил один конфликт и внёс второй. Дёрганье выглядело как дребезг механики, и я дважды перебирал качалку фар, прежде чем полез в исходники библиотеки Tone и увидел там Timer1.
Лечение вышло скучным и правильным: сирену поставил в паузы. Поворот фар занимает три секунды, между поворотами — тишина: millis-расписание из урока про планировщик держит фазу «звук» отдельно от фазы «поворот», и таймеры больше не пересекаются. Диммер переехал на пин 5 — Timer0 крутит его ШИМ молча, потому что tone() туда не добирается. Тем же приёмом разводятся и другие соседи: подсветка и звук, звук и мотор, серво и тик таймера. Принцип работает и в обратную сторону: если звук обязан длиться непрерывно, поворот строится на паузах — что-то одно живёт в своей временной нише.
Заодно проговорю границу, которой я сам тогда не видел: непрерывный звук и серво в одном скетче — не приговор, если у них разные счётчики. Штатная сирена tone(8) живёт на Timer2 и качалку фар не трогает — дёрганье началось лишь в моей ранней версии, где Tone насильно переселили на Timer1. Условия разведения звучат так: подсветке и прочим диммерам — пины 5 и 6 под Timer0, серво — пины 9 и 10 под Timer1, звуку — любой цифровой пин со штатным tone() на Timer2; при такой разводке непрерывный вой уживается с поворотом, и паузы нужны разве что ушам. Библиотека Tone выручает, когда одной штатной ноты мало — второй голос, свой тембр, — но её таймер должен быть свободен от остальных жильцов, иначе получите ту же войну с новым адресом.
Если непрерывный звук нужен по-настоящему, Tone-библиотека остаётся честным инструментом с одним условием: таймер выбирается осознанно и сверяется со всем скетчем. Дефолт у неё тот же Timer2, вариант с Timer1 совместим с Servo.h ровно настолько, насколько два жильца совместимы с одной комнатой. Третий путь — свой щелчок зуммером на свободном таймере в режиме CTC, тем же способом, которым в уроке про прерывание по совпадению строился опросный ритм: таймер это тоже займёт, но под вашим контролем и без сюрпризов в чужих регистрах. И раз уж свой тик — помните про дисциплину обработчиков: пара строк с volatile-флагом, вся остальная работа в loop.
// Сигнализация без таймерной войны: серво на Timer1, сирена на Timer2// звучит только в паузах поворота, диммер сидит на Timer0-пине.#include <Servo.h> Servo povorot; // attach() занимает Timer1: пины 9 и 10 - сервоconst byte PIN_SIRENA = 8; // tone() займёт Timer2: пины 3 и 11 не трогаемconst byte PIN_DIMMER = 5; // подсветка на Timer0-пине: свист ей не страшенconst byte PIN_KNOP = 2; unsigned long tPovorot = 0;unsigned long tSvist = 0;unsigned long tDvig = 0;int ugol = 20;int napr = 1;boolean trevoga = false; void setup() { pinMode(PIN_DIMMER, OUTPUT); pinMode(PIN_KNOP, INPUT_PULLUP); povorot.attach(9); // с этой строки Timer1 принадлежит сервам analogWrite(PIN_DIMMER, 90); // подсветка: ровный свет на Timer0 Serial.begin(9600);} void loop() { unsigned long t = millis(); if (t - tPovorot >= 20) { // поворот: шаг каждые 20 мс tPovorot = t; ugol += napr; if (ugol >= 60 || ugol <= 20) napr = -napr; povorot.write(ugol); } if (digitalRead(PIN_KNOP) == LOW) trevoga = true; if (trevoga && (ugol <= 21 || ugol >= 59)) { // пауза поворота - звук if (t - tSvist >= 500) { tSvist = t; tone(PIN_SIRENA, 1800, 200); // Timer2 занят нотой: 3 и 11 молчат ШИМом } } if (!trevoga) noTone(PIN_SIRENA); if (t - tDvig >= 3000) { // тревога длится три секунды поворота tDvig = t; trevoga = false; }}Симулятор: как выглядит мёртвый ШИМ
Движок симулятора таймеры не эмулирует: ни занятие Timer2, ни смерть ШИМ — регистров в нём нет. Модель показывает логику конфликта: зуммер на D8 звучит через tone() и замолкает через noTone(), а светодиод D12 изображает диммер с пина 11 — пока свистит сирена, плавное дыхание замерзает, в паузе оживает. Кнопка D2 — тревога: две секунды звука, и всё повторяется по нажатию. Светодиод здесь стоит вместо диммера сознательно: яркость в симуляторе плавная на любом пине, и замирание дыхания читается даже без осциллографа.
Смотрите в монитор: при каждой тревоге скетч печатает, что ШИМ на 11 стоит, и LED замирает тёмным; после noTone() дыхание продолжается с того же уровня яркости. На реальной плате ту же логику несёт скетч из code-блока: сирена в паузах поворота, диммер на пине 5, серво на Timer1 — и ни один жилец не наступает соседу на счётчик. Так карта занятости превращается из таблицы в поведение устройства. Тот же тест проделайте со своим скетчем: выкиньте вызов tone() — оживший диммер точно назовёт таймер, на котором сидела сирена.
- 1. Какие пины теряют ШИМ, пока звучит tone() на Uno?
- 2. Какой таймер отдаёт Servo.h под свои импульсы?
- 3. Подсветка на analogWrite(5, ...) и сирена tone(8, ...) в одном скетче. Что станет с подсветкой?
- 4. Что произойдёт, если библиотеку Tone настроить на Timer1, когда в скетче уже есть Servo.h?
- 5. Сколько нот одновременно умеет tone() на Uno?
- tone() занимает Timer2: пока звучит нота, ШИМ на пинах 3 и 11 мёртв, а вторая нота не прозвучит вовсе
- Servo.h держит Timer1 постоянно — analogWrite на пинах 9 и 10 пропадает; Timer0 отдан millis() и delay() и никому не сдаётся
- Таблицу «библиотека → таймер → что умирает» держите над столом: обе потери молчат, без сообщений в порту
- Свист и дёрганье серва одновременно — проверьте, чей таймер у звука: Tone на Timer1 воюет с Servo.h
- Рабочие решения: звук в паузах по millis-расписанию или Tone-библиотека с осознанным выбором таймера
Частые вопросы по теме урока
- Какие пины теряют ШИМ, пока звучит tone() на Uno?
- tone() занимает Timer2, а на нём сидят ШИМ-пины 3 и 11. Пины 5 и 6 — Timer0, 9 и 10 — Timer1, их свист не трогает. 3 и 11
- Какой таймер отдаёт Servo.h под свои импульсы?
- Servo.h держит Timer1 на прерываниях каждые 20 мс, поэтому analogWrite на пинах 9 и 10 пропадает сразу после attach(). Timer1
- Подсветка на analogWrite(5, ...) и сирена tone(8, ...) в одном скетче. Что станет с подсветкой?
- tone() забирает только Timer2 (пины 3 и 11). Пин 5 живёт на Timer0 с millis и delay — звук ему не сосед. останется жива: пин 5 сидит на Timer0, его tone() не трогает
- Что произойдёт, если библиотеку Tone настроить на Timer1, когда в скетче уже есть Servo.h?
- Таймер один на всех: Tone на Timer1 перезаписывает настройку Servo.h, и серво дёргается. Звук непрерывно — только на свободном таймере. серво начнёт дёргаться: Tone перезаписывает регистры Timer1
- Сколько нот одновременно умеет tone() на Uno?
- Второй вызов tone() перехватит звук первого: функция держит одну ноту на одном пине. Аккорды — это уже свой тик на свободном таймере. одну на одном пине