В ящике с батарейками у меня валяются пять ИК-пультов: от музыкального центра, которого нет уже года три, от китайской люстры, от кондиционера в старой квартире. Все они рабочие, батарейки живые, а нажимать их не на что. Когда я первый раз подключил к Arduino ИК-приёмник и увидел, как в мониторе порта от каждой кнопки появляется число, у меня было ощущение взлома: дешёвая плата читает язык, на котором техника разговаривает десятилетиями.
Только вот путь к этому числу не самый очевидный. Пульт шлёт невидимый свет — глаз его не воспринимает вообще. Камера телефона видит хотя бы факт вспышки: наведите старый пульт на объектив и понажимайте кнопки, на экране будет фиолетово-белая пульсирующая точка. Но камера не скажет, какая именно кнопка нажата, а для Arduino нужен номер команды: «влево» — одно число, «ок» — другое.
В этом уроке соберём читалку кодов: приёмник TSOP4838, библиотека IRremote, четыре строки логики. Сначала поймём, как пульт физически разговаривает с приёмником, потом подключим его к Uno и вытащим таблицу команд из любого пульта, который найдётся дома. В симуляторе есть виртуальный пульт — коды в него зашиты такие же, как у типового пульта из набора Arduino, так что таблица получится настоящая.
Невидимый свет: несущая 38 кГц и приёмник TSOP4838
ИК-пульт — это фонарик, который светит светом с длиной волны около 940 нанометров: глаз такое не видит, а фотоприёмник — отлично. Но просто гореть, пока кнопка нажата, пульт не может: солнце и лампы тоже светят в инфракрасном диапазоне, и приёмник путался бы в помехах. Поэтому пульт мигает своим светодиодом с частотой 38 кГц — тридцать восемь тысяч раз в секунду, — а уже этими пачками света кодирует биты.
Число, которое стоит запомнить с оговоркой: 38 кГц — самое типичное значение несущей, но встречаются пульты на 36 и на 40 кГц. Приёмник TSOP4838 настроен на 38 кГц и прощает соседние частоты — с пультом на 36 кГц он обычно тоже работает, просто хуже держит дальность. Внутри TSOP стоит полосовой фильтр: он вырезает всё, что не пульсирует около 38 кГц, поэтому солнцу и лампам пробиться на выход не даётся.
Сам приёмник — трёхногая деталь размером с ноготь: питание 5 вольт (допустимо от 2.5 до 5.5), земля и выход сигнала. Выход — открытый коллектор: деталь не умеет выдавать высокое напряжение сама, она только прижимает линию к нулю, когда поймала вспышку, и отпускает, когда нет. Значит, линию надо подтянуть к питанию — проще всего включить на пине режим INPUT_PULLUP и взять внутреннюю подтяжку Arduino. Готовые модули на платке, например KY-022, уже содержат резистор подтяжки — с ними подключение вообще в три провода. Если смотреть на выпуклую сторону TSOP4838 с ножками вниз, слева направо будут OUT, GND и плюс питания, но у китайских клонов порядок случается другой: я однажды целый вечер тыкал перепутанные GND и VCC, и приёмник честно молчал.
#include <IRremote.hpp> const int IR_PIN = 2; // выход TSOP4838 void setup() { Serial.begin(9600); // второй аргумент включает мигание встроенным светодиодом при приёме IrReceiver.begin(IR_PIN, ENABLE_LED_FEEDBACK); Serial.println("Наведите пульт и жмите кнопки");} void loop() { if (IrReceiver.decode()) { Serial.print("Протокол: "); Serial.print(IrReceiver.getProtocolString()); Serial.print(" адрес: 0x"); Serial.print(IrReceiver.decodedIRData.address, HEX); Serial.print(" команда: 0x"); Serial.println(IrReceiver.decodedIRData.command, HEX); IrReceiver.resume(); // готовы к следующей посылке }}Три вызова здесь — сердце любой работы с IRremote. IrReceiver.begin() стартует приёмник и цепляет прерывание на пин: дальше библиотека сама ловит каждое изменение линии и меряет его длительность, пока скетч занят своими делами. decode() возвращает true, если в буфере лежит полностью разобранная посылка, а после обработки resume() говорит: буфер свободен, шлите следующую. Забытый resume() — самая частая ошибка новичка: скетч компилируется, первую кнопку показывает, а дальше молчит, и кажется, что сломался пульт.
Само число — decodedIRData.command — это команда кнопки, один байт от 0 до 255. Рядом живёт decodedIRData.address — адрес пульта: производители зашивают его, чтобы пульт от телевизора не переключал каналы ресиверу. Для нас это готовый фильтр: если в комнате два пульта, по адресу можно игнорировать чужой. А getProtocolString() напечатает название протокола — у дешёвых пультов это почти всегда NEC.
Мнение, проверенное на нескольких макетках: сажайте приёмник на пин 2, если схема позволяет. На Uno это один из двух пинов с аппаратным прерыванием, и когда через пару проектов вы добавите отправку ИК или серво, свободное прерывание пригодится. Старые туториалы сажали приёмник на пин 11 — в старых версиях библиотеки для этого была причина, конфликт с таймером, но при приёме в IRremote 4.x этот пин ничем не лучше остальных.
Тупик: декодер на pulseIn, который я писал два вечера
Сначала я решил, что библиотека — это читерство, а настоящий инженер напишет декодер сам. Логика казалась прозрачной: pulseIn() меряет длительность импульсов, я сравниваю интервалы с порогом, собираю биты в байты. На деле пачка NEC начинается с 9-миллисекундной вспышки несущей, дальше идут биты, где логическая единица — 560 микросекунд света и 1.69 миллисекунды темноты, а ноль — те же 560 микросекунд света и 560 микросекунд темноты. И вот тут ловушка: TSOP на выходе отдаёт не честные импульсы, а перевёрнутые пачки, да ещё и с мусором между ними. pulseIn() то и дело ловил ложный фронт, тайминги плавали на десятки микросекунд, и к третьему вечеру у меня была тетрадь с таблицей интервалов и ноль работающего кода.
Протрезвел я, когда открыл исходники IRremote: там прерывание ловит каждое изменение пина, а длительности считает аппаратный таймер — точнее и стабильнее, чем любой опрос из loop(). Два вечера я изобретал то, что библиотечные авторы отлаживали годами. С тех пор у меня правило: сначала открываю примеры готовой библиотеки и читаю их до конца, и только если библиотеки нет — пишу свой декодер.
NEC: 32 бита, две инверсии и ваша таблица кодов
Самый частый протокол дешёвых пультов — NEC. Каждая посылка несёт ровно 32 бита: 8 бит адреса, 8 бит инверсии адреса, 8 бит команды и 8 бит инверсии команды. Инверсия — встроенная проверка целостности: у команды 0x43 инверсия 0xBC, и если приёмник принял пару, где они не дополняют друг друга, посылка битая и выбрасывается. За счёт этого случайная помеха почти никогда не превращается в ложную команду — очень экономная защита, два лишних байта вместо контрольной суммы.
Второй хитрый механизм NEC — повтор. Если кнопку держат, пульт не пересылает все 32 бита заново, а шлёт короткий кадр-повтор каждые 108–110 миллисекунд. Для читалки это значит: одно удержание напечатает в мониторе десяток строк. Как отличить первый кадр от повтора и зачем это нужно при управлении светом — тема следующего урока, а пока наша задача скромнее: собрать таблицу.
Таблицу кодов веду прямо в шапке скетча, комментарием: код всегда с собой, в отличие от блокнота. Вот она для типового пульта из наборов Arduino — того самого, с кнопками PLAY и VOL.
| Кнопка | command (dec) | command (hex) | Чем полезна в проекте |
|---|---|---|---|
| PLAY / PAUSE | 67 | 0x43 | вкл/выкл: одна кнопка — один переключатель |
| VOL+ | 21 | 0x15 | прибавить: держишь — летят повторы |
| VOL- | 7 | 0x07 | убавить: тоже с автоповтором |
| CH | 70 | 0x46 | переключение режимов или пресетов |
| Цифры 1–9 | 12, 24, 94, 8, 28, 90, 66, 82, 74 | 0x08–0x5E | выбор пунктов меню, каналов, таймеров |
- Удерживайте кнопку пульта пару секунд и посчитайте строки в мониторе. Повторы NEC идут каждые 108–110 мс, значит за секунду удержания должно появиться около девяти строк. Сверьте расчёт с тем, что увидите на экране.
- Добавьте в читалку вывод decodedIRData.address (в большом скетче урока он уже есть) и соберите полный паспорт пульта: протокол, адрес и команда для каждой кнопки.
- Сравните форматы: замените HEX на DEC в строке печати команды и убедитесь, что 0x43 и 67 — одно и то же число в двух одеждах. В таблице ниже урока оба столбца неспроста.
- Запишите свою таблицу кодов комментарием в шапке скетча — на следующем уроке эти числа превратятся в команды для света.
- Пульт светит ИК-диодом на 940 нм, промодулированным несущей 38 кГц; встречаются также 36 и 40 кГц
- TSOP4838 питается от 5 В, выход открытый коллектор — нужна подтяжка (INPUT_PULLUP или модуль с резистором)
- Рабочая тройка IRremote: begin(), decode(), resume(); без resume() читается только одна посылка
- decodedIRData.command — байт 0–255, decodedIRData.address — адрес, отсеивающий чужие пульты
- Посылка NEC — ровно 32 бита: адрес, его инверсия, команда, её инверсия
- Таблицу кодов своего пульта держите комментарием в шапке скетча
- 1. Почему пульт не просто светит ИК-диодом, а мигает им с частотой 38 кГц?
- 2. Скетч напечатал команду первой кнопки и замолчал. Что забыли?
- 3. Сколько бит несёт одна посылка протокола NEC?
- 4. Зачем в коде проверять decodedIRData.address?
- 5. Выход TSOP4838 подключают к пину через подтяжку, потому что...
Теперь у вас есть рентген для любого пульта в доме: три провода, одна библиотека и таблица, которую вы собрали сами. В следующем уроке превратим числа в действия: повесим на кнопки включение света, научимся отличать нажатие от удержания по флагу повтора и сделаем плавную регулировку яркости PWM — тот случай, когда автоповтор NEC из помехи превращается в фичу.
Частые вопросы по теме урока
- Почему пульт не просто светит ИК-диодом, а мигает им с частотой 38 кГц?
- Несущая 36–40 кГц (типично 38) позволяет полосовому фильтру TSOP отрезать постоянные ИК-источники и оставить только пульт. Чтобы приёмник мог отфильтровать помехи от солнца и ламп
- Скетч напечатал команду первой кнопки и замолчал. Что забыли?
- После decode() буфер занят; без resume() библиотека не примет следующую посылку и decode() больше не вернёт true. IrReceiver.resume()
- Сколько бит несёт одна посылка протокола NEC?
- 32 бита — это четыре байта, причём два из них — инверсии первых двух, простейшая проверка целостности без контрольной суммы. 32 бита: адрес, его инверсия, команда, инверсия команды
- Зачем в коде проверять decodedIRData.address?
- Адрес зашит производителем пульта: по нему плата отличает свой пульт от телевизора или пульта соседа. Чтобы игнорировать чужой пульт, если в комнате их два
- Выход TSOP4838 подключают к пину через подтяжку, потому что...
- Открытый коллектор тянет линию вниз; высокий уровень нужен подтяжкой — внутренней INPUT_PULLUP или внешним резистором. выход открытый коллектор: деталь умеет только прижимать линию к нулю