Скетч из статьи 2017 года выглядел как решение всех проблем: пульт, свет, полстраницы кода. Я скопировал его в IDE и нажал проверку — красное: IRrecv does not name a type, decode_results was not declared. Красное поле ошибок тогда показалось мне поломкой платформы; на деле это был сигнал, что экосистема уехала вперёд. Первым делом я полез в комментарии к статье — там уже сидело двадцать человек с той же ошибкой и ноль ответов. Статья была честной, код был рабочим — для второй версии IRremote, которой давно нет. Библиотека сменила API, и это первый случай в моём опыте, когда половина уроков в интернете устарела молча и одновременно: скетчи сломались не у авторов, а у читателей.
К этому моменту у нас всё готово: приёмник подключали в уроке про VS1838B, язык сигнала разобрали в уроке про несущую и протоколы, а читалку в новом синтаксисе уже собирали в уроке про приём кодов. Сегодня разберём саму библиотеку: как ставить четвёртую версию, чем IrReceiver отличается от IrSender и как за пять минут перевести любой старый скетч по таблице.
Ставится библиотека через Менеджер библиотек — процедуру я разбирал в отдельном уроке: в поиске набираете IRremote, автор Armin Joachimsmeyer, берёте последнюю версию 4.x. Всё, что подписано 2.x, устанавливать не надо даже ради совместимости: старый скетч проще переписать, чем откатывать библиотеку и терять поддержку новых плат. После установки загрузите проверочный скетч на плату по порядку первой загрузки — и можно приступать. Библиотека ставится за десяток секунд и не тянет зависимостей. И не пугайтесь слова версии в старых скетчах: весь учебный код из этого раздела собирается на 4.x без оговорок.
Что изменилось в четвёртой версии
Главное изменение: вместо объекта, который вы создаёте сами — IRrecv irrecv(PIN) — библиотека даёт два готовых синглтона, IrReceiver и IrSender. Приём и передача разделены: один знает свой пин и таймер, другой — свой, и путать их не приходится. IrReceiver.begin() принимает номер пина и флаг ENABLE_LED_FEEDBACK, при котором встроенный светодиод платы мигает на каждую принятую посылку — дешёвая, но самая полезная индикация, которую я знаю. Библиотека при этом уместилась в модульную структуру: неиспользуемые протоколы и передатчик вырезаются на этапе компиляции, и скетч с одним приёмником NEC не тащит за собой чужой код. Собирается она не только для AVR: ESP32 и ESP8266 — полноправные цели, что для старой версии было болью.
Вторая перемена — данные. Старый API складывал результат в структуру decode_results, и значение кнопки лежало в results.value: сыром 32-битном числе, где у NEC адрес и команда сидели вместе в четырёх байтах. Новички нередко проверяли это число целиком — и ловили команды чужих пультов. Новый decodedIRData разобран по полям: command — команда кнопки, address — адрес пульта, protocol — номер протокола, flags — служебные биты вроде признака повтора. Адрес — тот самый фильтр от чужих пультов: пульт от телевизора больше не дёргает вашу подсветку, если сравнить один байт. Готовый байт команды и байт адреса — два числа, на которых держится весь раздел. flags — отдельная радость: признак повтора NEC теперь читается одним байтом, без самодельных таймеров поверх decode.
Ещё деталь, на которой спотыкаются: подключайте IRremote.hpp, а не IRremote.h. Старый заголовок остался для совместимости, но новый компилируется быстрее и не тянет устаревшие определения — интерфейс тот же, просто собран по-современному. А если собранный через .h скетч печатает в мониторе сообщение, что вы используете код версии 2.0, — это не ошибка, это встроенная подсказка: библиотека узнала старые вызовы и сама говорит, что пора мигрировать. Подсказка печатается один раз при первом decode() и дальше монитор не засоряет.
#include <IRremote.hpp> const int IR_PIN = 2; void setup() { Serial.begin(9600); IrReceiver.begin(IR_PIN, ENABLE_LED_FEEDBACK); Serial.println("IRremote 4.x gotov");} void loop() { if (IrReceiver.decode()) { Serial.print("addr=0x"); Serial.print(IrReceiver.decodedIRData.address, HEX); Serial.print(" cmd=0x"); Serial.print(IrReceiver.decodedIRData.command, HEX); Serial.print(" prot="); Serial.println(IrReceiver.decodedIRData.protocol); IrReceiver.resume(); }}Таблица миграции: было — стало
Вся миграция умещается в одну таблицу: слева API второй версии, справа четвёртой. Замены механические, логика скетча не меняется: ловим посылку, читаем команду, освобождаем буфер. Единственное, к чему присматриваются, — что брать вместо results.value: для NEC это command, для нестандартных протоколов — сырые байты.
Берите таблицу как чек-лист и идите по старому скетчу сверху вниз: заголовок, инициализация, чтение, поля, resume. Блоки при этом остаются те же: setup с инициализацией, loop с decode и освобождением буфера. Пять-семь строк — и скетч из 2017 года собирается на последней версии без единой правки логики. Я проверял на трёх своих старых проектах: ни один не потребовал больше десяти минут, и только у одного нашёлся ещё один decode_results, спрятанный в функции-помощнике. Если у старого скетча есть своя обвязка — светодиоды, логи, меню — она трогается последней и только если мешает.
| Было (v2) | Стало (v4) |
|---|---|
| #include <IRremote.h> | #include <IRremote.hpp> |
| IRrecv irrecv(2); irrecv.enableIRIn(); | IrReceiver.begin(2, ENABLE_LED_FEEDBACK); |
| irrecv.decode(&results) | IrReceiver.decode() |
| results.value | IrReceiver.decodedIRData.command |
| results.decode_type | IrReceiver.decodedIRData.protocol |
| irrecv.resume() | IrReceiver.resume() |
| IRsend irsend; irsend.sendNEC(data, 32); | IrSender.begin(); IrSender.sendNEC(address, command, repeats); |
Пара нюансов, которых в таблице не видно. Первое: decodedRawData — сырые 32 бита посылки; у протоколов, где команда шире байта или разбор нестандартный, читают именно их. Разница видна на живом пульте: напечатайте command и decodedRawData рядом и увидите, как байты адреса и команды складываются в одно 32-битное число — хорошее упражнение на понимание кадра. Но для NEC и большинства пультов из наборов command и address — готовые поля, и именно они уходят в switch. У Samsung и LG встречаются свои раскладки байт, и там сравнение с raw data иногда честнее, чем с command.
Отдельный источник правды — папка examples в самой библиотеке: MinimalReceiver, SimpleReceiver, SendAndReceive. Авторы держат их в актуальном состоянии, и любой вопрос «как правильно» закрывается чтением трёх коротких скетчей быстрее, чем поиском по форумам. Мой маршрут при переезде: таблица выше, потом сверка с MinimalReceiver, и только потом правки логики под свою задачу.
Понижать версию библиотеки ради старого скетча — тупик, в который я однажды сворачивал. Да, скетч из статьи соберётся на 2.4.0. Но вместе с ним вы теряете поддержку новых плат и получаете конфликты с другими библиотеками за таймеры, а потом всё равно переезжаете — уже дважды. Откат — это ещё и согласие на старые баги: во второй версии приём и передача делили один таймер, и связка «примаю и шлю» в одном скетче отказывалась работать. Пять строк миграции дешевле любого отката, я проверил на своём времени.
IrSender — коротко о передаче. Схема зеркальная: ИК-светодиод через резистор 100–150 Ом на пин 3 — на Uno передача сидит на таймере TIMER2, — IrSender.begin() в setup, дальше IrSender.sendNEC(адрес, команда, число повторов). Несущую 38 кГц на выходе пина генерирует тот же таймер: диод мигает так же, как мигал бы настоящий пульт, с той же скважностью и сопоставимой дальностью — 3–5 метров с типовым диодом через 150 Ом. Дублирующий пульт для второй платы собирается за вечер: две кнопки, два вызова sendNEC — и приёмник соседней макетки исполняет ваши приказы. Если питание у плат раздельное, не забудьте общую землю: без неё приёмник соседа увидит только мусор. Приёмную сторону этого диалога мы отработали в уроке про управление светом с пульта: туда ложится и отправка.
- IRremote 4.x ставится через Менеджер библиотек; автор Armin Joachimsmeyer; подключайте IRremote.hpp
- Два готовых синглтона: IrReceiver для приёма и IrSender для передачи — объекты создавать не нужно
- Миграция: enableIRIn() — IrReceiver.begin(), results.value — decodedIRData.command, decode_type — protocol
- decodedIRData.command и decodedIRData.address — готовые байты; decodedRawData — сырые 32 бита для нестандартных протоколов
- Понижать версию ради старого скетча — тупик: пять строк миграции дешевле отката и конфликтов за таймеры
- Передача на Uno идёт через пин 3 (TIMER2): ИК-диод через резистор 100–150 Ом, IrSender.sendNEC(адрес, команда, повторы)
- 1. Скетч не собирается: IRrecv does not name a type. Что делать?
- 2. Какой заголовок рекомендован для IRremote 4.x?
- 3. Где в IRremote 4.x лежит команда кнопки после decode()?
- 4. На каком пине Uno по умолчанию работает IrSender?
- 5. Скетч со старым API собрался через .h. Что печатает библиотека в мониторе?
Библиотека на месте, старые скетчи переведены, IrSender ждёт очереди в проектах. Таблица миграции при этом остаётся под рукой: старые скетчи будут попадаться ещё годы. Дальше по разделу две дороги: углублять приём — дойти до выключателя света в уроке про команды и повтор, если ещё не дошли, — или попробовать передачу: ИК-пульт на две кнопки и вторая плата в роли исполнителя. А чтобы увидеть кадр NEC глазами, напечатайте паузы между посылками и включите Serial Plotter: удержание кнопки нарисует редкие всплески через каждые 108 мс.
Частые вопросы по теме урока
- Скетч не собирается: IRrecv does not name a type. Что делать?
- Кроме имени объекта сменились begin, decode и поля данных; таблица миграции покрывает все пять строк замеными. Переписать строки по таблице миграции на API 4.x
- Какой заголовок рекомендован для IRremote 4.x?
- Новый заголовок .hpp компилируется быстрее и не тянет устаревшие определения; .h оставлен для совместимости со старыми скетчами. IRremote.hpp
- Где в IRremote 4.x лежит команда кнопки после decode()?
- decodedIRData разобран по полям: command — байт команды, address — адрес, protocol — протокол, flags — служебные биты. decodedRawData — сырые 32 бита, он для нестандартных раскладок вроде Samsung и LG. В IrReceiver.decodedIRData.command
- На каком пине Uno по умолчанию работает IrSender?
- Передача привязана к таймеру: на Uno это TIMER2 и пин 3; ИК-светодиод вешают через резистор 100–150 Ом. На 3 — за передачей стоит таймер TIMER2
- Скетч со старым API собрался через .h. Что печатает библиотека в мониторе?
- Библиотека узнаёт старые вызовы и печатает подсказку о миграции — это сообщение, а не ошибка. Сообщение о том, что используется код версии 2.0