tone() против ШИМ: конфликт таймеров Arduino Uno и способы развести их
Диммер подсветки на analogWrite(11) месяц прожил в режиме нон-стоп и умер в одну секунду после того, как я добавил в скетч звонок: tone(8, 1319) — и гирлянда замерла вполнакала, пока нота не отзвучала. Вечер я честно истратил на ложный след: прозвонил мосфет, перепаял провод, заподозрил просадку питания от зуммера и даже прилепил конденсатор на линию. Причина была в чипе: у ATmega328 всего три аппаратных таймера, и звук сел на тот же Timer2, который выдавал ШИМ на пин 11. Монитор порта молчал — конфликту таймеров докладывать некому.
Ниже — карта всех трёх таймеров Uno: кто на каком живёт и какие ШИМ-пины глушит, разбор пары «tone() + Servo.h» и соседства с ИК-приёмником, таблица из пяти решений с честной ценой каждого и два скетча — разведение жильцов по таймерам и возврат ШИМ после noTone(). Про симулятор скажу сразу и честно: аппаратных таймеров в браузере нет, звук tone() эмулируется через Web Audio, поэтому сам конфликт в нём физически не воспроизводится. Зато мелодию и логику «нота → noTone() → analogWrite» можно проверить без платы в симуляторе — и только потом переносить на железо.
Три таймера Uno: кто где живёт
Расклад жёсткий: все три счётчика ATmega328 разобраны до первой вашей строки — среда настраивает их при старте. Timer0 кормит millis(), micros() и delay() и заодно выдаёт ШИМ на пины 5 и 6. Timer1 — единственный 16-битный, его забирает Servo.h: прерывание каждые 20 мс и никакого освобождения до detach(). Timer2 — 8-битный: на нём сидит tone(), перестраивая счётчик под частоту ноты, и на нём же висит ШИМ пинов 3 и 11. Теорию с регистрами по всем трём я разложил в обзоре таймеров ATmega328, здесь — прикладная таблица.
| Таймер | Штатный жилец | ШИМ-пины | Что ломает жилец |
|---|---|---|---|
| Timer0 | millis(), micros(), delay() | 5 и 6 (980 Гц) | сам по себе ничего; опасен только при вмешательстве в предделитель — тогда поплывут все тайминги скетча |
| Timer1 | Servo.h (или TimerOne, если добавить) | 9 и 10 (490 Гц) | analogWrite на 9 и 10 пропадает сразу после attach() и не возвращается до detach() |
| Timer2 | tone()/noTone() | 3 и 11 (490 Гц) | ШИМ молчит, пока звучит нота; после неё пины остаются мёртвыми до свежего analogWrite |
Частоты из коробки помогают диагностике: 490 Гц на пинах 3, 9, 10 и 11 против 980 Гц на 5 и 6. Глазу разница не видна, а частотомеру — видна: пин с 980-герцовским ШИМ сидит на Timer0, и звук ему не сосед. За каким пином какой счётчик закреплён физически — карта распиновки Uno; без неё перенос нагрузки вслепую часто попадает на тот же занятый таймер, с которого вы только что сбежали.
- свет на пине 3 или 11 замирает ровно на время ноты и оживает после — почерк Timer2
- яркость на 9 или 10 пропала после добавления серво и не вернулась — почерк Timer1
- после noTone() диммер стоит до перезагрузки — пропущен свежий analogWrite()
- millis() тянет резину, delay() держит не восемь секунд, а в разы дольше — кто-то крутил предделитель Timer0
Диагностика и разводка: звук уходит с пинов 3 и 11
Проверка занимает минуту и не требует осциллографа: закомментируйте в скетче все tone() и noTone(), залейте — и смотрите на свет. Ожил: виноват Timer2, а не проводка, не мосфет и не сгоревший пин. Заодно держите в голове рамку самой функции: нота одна на весь чип, второй вызов tone() перехватит звук, а не подыграет в унисон, и привязки к ШИМ-пинам у звука нет — tone() работает с любым цифровым пином, поэтому сирене в скетче ниже достался восьмой, а «памятные» 3 и 11 остались нетронутыми. Механику tone() и noTone() построчно я разбирал в уроке про зуммер и tone() — там же про электрику пина.
#include <Servo.h> Servo kamera;const byte PIN_SIRENA = 8; // tone() -> Timer2: пины 3 и 11 не трогаемconst byte PIN_SVET = 5; // диммер -> Timer0: свист сирены ему не страшенconst byte PIN_SERVO = 9; // attach() -> Timer1: пин 10 остаётся в резерве int ugol = 60;int shag = 1;int yarkost = 0;int napr = 1;unsigned long tServo = 0;unsigned long tNota = 0; void setup() { pinMode(PIN_SVET, OUTPUT); kamera.attach(PIN_SERVO); // с этой строки Timer1 принадлежит серво Serial.begin(9600);} void loop() { // диммер: дыхание на пине 5, Timer0 - tone() сюда не добирается yarkost += napr; if (yarkost >= 255 || yarkost <= 0) napr = -napr; analogWrite(PIN_SVET, yarkost); delay(4); // серво: шаг каждые 20 мс, Timer1 if (millis() - tServo >= 20) { tServo = millis(); ugol += shag; if (ugol >= 120 || ugol <= 60) shag = -shag; kamera.write(ugol); } // сирена: нота на 700 мс, Timer2 - пины 3 и 11 в это время молчат ШИМом if (millis() - tNota >= 700) { tNota = millis(); tone(PIN_SIRENA, 1568, 200); }}- звук — на любом цифровом пине без ШИМ-привязки: 2, 4, 7, 8 годятся все
- диммеры и моторы — на пины 5 и 6: tone() туда не добирается
- серво — сигнальный пин 9 или 10, Timer1 отдаётся серво целиком
- Timer0 — только millis(): ни звука, ни своего тика, ни изменения предделителя
tone() и Servo.h вместе: где конфликт, а где его нет
Пара «tone() и Servo.h» сама по себе работает годами: сирена на Timer2, серво на Timer1, счётчики разные, пересекаться нечему — у меня такая связка крутится в сигнализации с поворотной камерой. Ломается в двух известных местах. Первое — пересадка звука на Timer1: библиотека Tone умеет выбирать таймер, и вариант с Timer1 дёргает серво на каждом кадре, потому что оба жильца перекраивают один счётчик; то же делает TimerOne. Второе — ИК-приёмник: Irremote по умолчанию берёт тот же Timer2, что и tone(), и один из двух проигрывает; этот дуэт разобран в уроке про IRremote, Timer2 и серво. А механику смерти ШИМ на 9 и 10 после attach() разложил урок про Servo против ШИМ.
- штатный tone() + Servo.h: Timer2 против Timer1, конфликтовать нечему
- Tone-библиотека на Timer1 + Servo.h: серво дёргается — таймер один на всех
- IRremote на Timer2 + tone(): оба хотят один счётчик, выживает тот, кто встал раньше
- TimerOne + Servo.h: та же война за Timer1, что и у Tone
Пять решений и цена каждого
Каждый выход из конфликта чем-то платит, и лучше увидеть цену до пайки, а не после. Я собрал пять путей, которые реально применяю, — от двухстрочного переноса пина до платы с шестью таймерами.
| Решение | Механика | Цена |
|---|---|---|
| Перенести ШИМ на пины 5/6 | диммер уезжает на Timer0, звук остаётся на Timer2 | пины 5 и 6 заняты; Timer0 дальше никому не отдаётся — на нём millis() |
| noTone() + свежий analogWrite() | нота закончилась — analogWrite() перестраивает Timer2 обратно на ШИМ | звук и свет только по очереди; забытый analogWrite = мёртвый пин до перезагрузки |
| Библиотека Tone или TimerOne | звук или тик переносится на выбранный таймер | таймер занят насовсем; вариант с Timer1 дёргает серво |
| Mega 2560 | шесть таймеров вместо трёх, жилец у каждого | плата дороже и шире; расклад другой: tone() глушит уже пины 9 и 10 |
| Перенастроить частоту таймера | Timer2 через регистры TCCR ставится на 31 кГц для тихих лент | tone() на этой плате больше не звучит: счётчик отдан ШИМ |
Моё мнение: в девяти случаях из десяти хватает первых двух строк таблицы — перенести диммер и не забывать analogWrite(). Платить за Mega ради развязки «мелодия плюс подсветка», на мой взгляд, расточительно; другое условие — непрерывный вой сирены вместе с серво в одном корпусе: вот там шестой таймер окупается. Рабочие скетчи под оба простых пути уже написаны: мелодии по массивам нот собираются по уроку про мелодии, сигнальная логика «порог → сирена → сброс» — в уроке про звуковую сигнализацию.
const byte PIN_SIRENA = 8; // Timer2 уходит под звукconst byte PIN_SVET = 11; // ШИМ этого пина живёт на том же Timer2const int YARKOST = 180; boolean svistit = false; void setup() { pinMode(PIN_SVET, OUTPUT); analogWrite(PIN_SVET, YARKOST); Serial.begin(9600);} void loop() { unsigned long t = millis() % 6000; // шестисекундный цикл демо if (!svistit && t >= 4000) { // последние две секунды - свист svistit = true; tone(PIN_SIRENA, 1800); // Timer2 занят: ШИМ пина 11 замер Serial.println("tone: dimmer vstal"); } if (svistit && t < 4000) { // круг пошёл заново svistit = false; noTone(PIN_SIRENA); // гасим ноту... analogWrite(PIN_SVET, YARKOST); // ...и перестраиваем Timer2 на ШИМ Serial.println("noTone + analogWrite: svet vernulsya"); }}Крайние случаи: смена частоты таймера и Mega
Оба пути оставляю на десерт, потому что у каждого есть невозвратная цена. Перенастройка Timer2 через регистры TCCR на 31 кГц делает ШИМ беззвучным для лент и моторов — и вычёркивает tone() с этой платы: счётчик один, и он уже нанят ШИМ; вся техника расписана в уроке про ШИМ 31 кГц. Mega 2560 пинает конфликты количеством: шесть таймеров, tone() и там живёт на Timer2, но глушит теперь пины 9 и 10, серво первым делом занимает Timer5 с его пинами 44-46, а Timer1, Timer3 и Timer4 остаются свободными. Привычная карта Uno на Mega не работает — расклад пересобирается с нуля.
- выписал всех жильцов скетча: millis() — Timer0, серво — Timer1, tone() — Timer2, плюс Tone и IRremote, если есть
- звук не на пинах 3 и 11, свет не на 9 и 10, если в скетче серво
- после каждой ноты — noTone() и свежий analogWrite() на пин света
- предделитель Timer0 не трогаю даже ради красивого баса