ИК-будильник был самым простым проектом за год: команда с пульта включает зуммер через tone(), вторая команда выключает. Собрал за полчаса, включил — первый писк прозвенел красиво. Второе нажатие пульта молчало, и молчало оно уже навсегда: decode() не возвращал true до перезагрузки платы. Я пересобрал схему трижды, подменил приёмник, зуммер и даже пульт — а виноват оказался не провод и не деталь. tone() и приёмник IRremote на Uno делят один таймер, и каждый вызов tone() перенастраивает его под себя.
Конфликты таймеров — те грабли, о которые бьются все, кто скрещивает две библиотеки на Uno. Микроконтроллер здесь всего три таймера, и ядро Arduino заняло их заранее: место для IRremote нашлось ровно то же, что для зуммера. Разберём, кто на каком таймере живёт, какие пины ШИМ при этом умирают, почему четвёртая версия библиотеки сделала конфликт мягче второй и какие три обхода работают прямо сейчас: пара stop и restart, макрос смены таймера и приёмник без таймера вовсе.
Симптом у всех конфликтов таймеров один и потому коварный: каждая библиотека по отдельности работает, а вместе — нет. В моём будильнике зуммер звучал от кнопки пульта, приёмник верно принимал команды до первого писка, и только их союз ломал всё. Отсюда мой тест на таймерные ссоры: соберите обе библиотеки в минимальном скетче, где каждая делает ровно одно движение — писк и одну команду. По очереди гасите вызовы одной из них и смотрите, у кого сбивается ритм: три минуты теста заменяют три вечера перестановки проводов.
Три таймера Uno: кто чем занят
Карта до банального простая. Timer0 — восьмибитный, на нём стоят millis(), micros() и delay(), а заодно ШИМ на пинах 5 и 6: трогать его нельзя, остановится само время. Timer1 — единственный шестнадцатибитный, его забирает Servo.h, и ШИМ живёт на пинах 9 и 10. Timer2 — восьмибитный, дом tone() и ШИМ на пинах 3 и 11. Полную разборку устройства часов платы мы делали в уроке про таймеры ATmega328 — здесь достаточно этой тройки. Для масштаба: тик приёма 50 микросекунд на плате 16 МГц — это прерывание каждые 800 тактов процессора, поэтому таймер под него выделяют раз и навсегда.
Как это выглядит снаружи, пока не знаешь про таймеры. analogWrite() на пине 3 выдаёт то ноль, то полную — модуль ШИМ занят счётом тиков приёма, а не генерацией скважности. tone() звучит исправно, но после него decode() молчит. Зуммер в моём будильнике звучал всегда — обманчиво здоровый зуммер, при котором подозреваешь что угодно, кроме него самого.
Приёмнику IRremote нужен тик каждые 50 микросекунд, и на Uno по умолчанию его вешают на Timer2. Отсюда два столкновения, описанных прямо в документации библиотеки: analogWrite() на пинах 3 и 11 при живом приёме не работает, и каждый вызов tone() полностью переинициализирует Timer2, после чего тик приёма исчезает. Механику тика подробно разбирали в уроке про прерывания: обработчик дёргается 20 000 раз в секунду, и таймер занят этим постоянно.
Servo.h и IRremote на дефолтных настройках соседствуют спокойно: серво ест Timer1, приёмник Timer2. Конфликт приходит позже, когда приёмник руками пересаживают на Timer1 — а делать это иногда приходится, сейчас покажу как.
Во второй версии библиотеки всё было жёстче: Timer2 занимали наглухо и приёмом, и передачей — несущую генерировала аппаратная ШИМ того же таймера, намертво привязанная к пину 3. tone() рядом с ИК на Uno не жил в принципе, и пин 3 для обычного analogWrite тоже терялся. Старые скетчи из статей с enableIRIn() собирались только на второй версии, и я одно время таскал эти конфликты за собой, пока не пересел на четвёртую.
Четвёртая версия сменила подход, и это половина причины её любить. Передачу сделали программной: несущую генерирует сам скетч через delayMicroseconds, и ИК-светодиод можно вешать на любой пин — разбор нового API мы делали в третьем уроке раздела. Timer2 остался только у приёмника: пин 3 вернули пользователям, но тик приёма на Uno по-прежнему живёт на таймере зуммера. Зато у четвёртой версии есть три штатных обхода, которых во второй не было вовсе.
#include <IRremote.hpp> const int IR_PIN = 2;const int TONE_PIN = 8;bool piskOn = false; void setup() { Serial.begin(9600); IrReceiver.begin(IR_PIN, ENABLE_LED_FEEDBACK); pinMode(TONE_PIN, OUTPUT);} void loop() { if (IrReceiver.decode()) { int cmd = IrReceiver.decodedIRData.command; IrReceiver.resume(); if (cmd == 67) { piskOn = !piskOn; if (piskOn) { IrReceiver.stopTimer(); // тик приёма уходит на паузу tone(TONE_PIN, 2200); } else { noTone(TONE_PIN); IrReceiver.restartTimer(); // тик 50 мкс вернулся } } }}Первый обход — поочерёдное владение: IrReceiver.stopTimer() перед tone() и IrReceiver.restartTimer() после noTone(). Работает это потому, что каждый вызов tone() заново инициализирует Timer2 под себя — библиотека честно предупреждает об этом в документации, и её пример ReceiveDemo построен ровно на этой паре строк. Граница метода: пока зуммер пищит, приём молчит. Для будильника и сигнализации это не потеря — пока пищит, читать пульт незачем.
Как выбирать из трёх обходов, подскажет сам зуммер. Разовый короткий писк на событие — стоп-старт парой: приём теряет максимум один кадр, а чаще ни одного. Долгий звук, который должен звучать, пока пользователь не передумал, — останавливайте таймер на весь период звука и не читайте пульт под вой: отвечать всё равно некому. Постоянный звуковой фон в проекте, где пульт нужен одновременно, — повод для третьего варианта, переселения приёмника на другой таймер.
Второй обход — переселить приёмник. Одна строка #define IR_USE_AVR_TIMER1 перед #include <IRremote.hpp> переносит приём на Timer1, и Timer2 целиком достаётся tone(). Но Timer1 — родина Servo.h: сервопривод встанет, а ШИМ уйдёт с пинов 9 и 10. Выбирая таймер, вы выбираете, с чем расстанетесь, — это чистая арифметика трёх таймеров на три потребителя.
Третий обход — для проектов только с NEC: приёмник TinyIRReceiver из той же экосистемы ловит кадры без всякого таймера, на смене уровня пина. Таймеров не занимает ни одного, но понимает один протокол. И напомню про соседей без таймеров, но с вредным характером: WS2812 из урока про ленту закрывает прерывания на время отправки — восемь пикселей это 240 микросекунд тишины, четыре пропущенных тика приёма из одиннадцати на бит. Кадр при этом ломается: декодер прощает отклонение лишь в четверть длительности.
Карта таймеров: кто что ест и что ломается
| Потребитель | Таймер | Пины ШИМ | Что сломается, если занять |
|---|---|---|---|
| millis, micros, delay (ядро) | Timer0 | 5, 6 | остановится время: весь скетч встанет |
| Servo.h; IRremote после макроса | Timer1 | 9, 10 | сервопривод и ШИМ на 9 и 10 |
| tone(); приёмник IRremote на Uno | Timer2 | 3, 11 | tone() и analogWrite на 3 и 11 |
| Программная передача v4 | таймер не нужен | любой пин | лишь джиттер до 4 мкс от micros |
Симулятор: целевой сценарий без таймеров
- На Uno три таймера: Timer0 у millis и delay, Timer1 у Servo.h, Timer2 у tone() и приёмника IRremote по умолчанию
- Тик приёма — 50 мкс: analogWrite на пинах 3 и 11 и каждый tone() на Uno ломают приём
- Паттерн сосуществования: IrReceiver.stopTimer() перед tone(), IrReceiver.restartTimer() после noTone()
- Смена таймера одной строкой: #define IR_USE_AVR_TIMER1 до include — но тогда встанет Servo.h и уйдёт ШИМ с 9 и 10
- В четвёртой версии передача программная: ИК-светодиод живёт на любом пине, Timer2 остался только приёму
- WS2812 и прочие запретители прерываний рвут кадры: восемь пикселей — 240 мкс тишины, четыре пропущенных тика
- 1. На Uno приёмник IRremote по умолчанию живёт на таймере...
- 2. Команда включает tone() на две секунды, после чего decode() молчит. Что случилось?
- 3. Куда переезжает приёмник после строки #define IR_USE_AVR_TIMER1?
- 4. Почему Servo.h и IRremote 4.x на дефолтных настройках дружат на Uno?
- 5. Что в четвёртой версии изменилось для передачи?
Таймеры разошлись по домам: зуммер на Timer2 с пропусками, серво на Timer1, приёмник — где вы его поселили. Если в проекте живёт ещё и мотор на analogWrite, помните про пины 3 и 11: при живом приёме они мёртвы, разносите мотор и ИК по разным таймерным домам.
Осталась вторая половина неглухого проекта — цикл, который не блокируется сам: IrReceiver.resume и неблокирующий loop — про порядок decode, resume и обработку после. А проверить, что приём жив после всех переселений, проще всего читалкой из самого первого урока: она печатает только cmd= и к зуммерам равнодушна.
Частые вопросы по теме урока
- На Uno приёмник IRremote по умолчанию живёт на таймере...
- Тик приёма 50 мкс по умолчанию вешают на Timer2 — тот же таймер, что обслуживает tone() и ШИМ пинов 3 и 11. Timer2
- Команда включает tone() на две секунды, после чего decode() молчит. Что случилось?
- tone() полностью перенастраивает Timer2 под себя, и тик приёма пропадает. Лечение — stopTimer перед писком и restartTimer после noTone: приём вернётся без перезагрузки. Каждый tone() переинициализирует Timer2 — тик приёма исчез
- Куда переезжает приёмник после строки #define IR_USE_AVR_TIMER1?
- Макрос до include переносит приём на Timer1, освобождая Timer2 для tone(). Плата — Servo.h и ШИМ на пинах 9 и 10. На Timer1 — и встанет Servo.h
- Почему Servo.h и IRremote 4.x на дефолтных настройках дружат на Uno?
- Конфликт приходит, только когда приёмник вручную пересаживают на Timer1 макросом — тогда два потребителя претендуют на один таймер. Servo живёт на Timer1, приёмник — на Timer2: разные таймеры
- Что в четвёртой версии изменилось для передачи?
- Программная ШИМ через delayMicroseconds освободила аппаратный таймер и пин 3: Timer2 остался только у приёмника. Несущая генерируется программно — светодиод можно повесить на любой пин