Подсветка для полки с коллекцией собралась за вечер: лента, полевой транзистор, Arduino и ИК-приёмник, который месяц лежал в ящике. Пока я отлаживал логику, ленту заменил скромный одиночный светодиод на пине 12 — меньше рисков и никакого транзистора. Коды кнопок я лениво взял из первой попавшейся статьи — с виду всё совпадало: пульт такой же чёрный, кнопки те же. Схема заработала сразу, и неделю всё было хорошо. Пока однажды полка не вспыхнула от пульта телевизора: я прибавлял громкость, а у ленты на команде 21 висело включение.
Диагноз занял минуту: коды из чужой таблицы совпали с кодами моего пульта, а фильтра по адресу в скетче не было — одной проверки из урока про биты NEC хватило бы, чтобы отгородиться от телевизора. Чинить я сел не фильтром, а порядком. У любого пультового проекта должна быть своя карта кодов — документ, где у каждой кнопки записаны её номер и её роль. Без карты проект живёт на чужих кодах и умирает от первого пульта соседа. Карта у меня кочует между проектами почти без изменений: меняется действие в третьей колонке, а кнопки и коды остаются теми же — один и тот же пульт управляет ночником, полкой и будильником.
Здесь соберём карту целиком: свяжем кнопки с действиями через switch, зажжём одиночный светодиод и трёхцветный, разберём, почему мигание строится на millis, и честно поговорим о том, чем опасны готовые таблицы из интернета.
Карта кодов: кнопка, код, действие
Карта — это три колонки: надпись на кнопке, число, которое приезжает в decodedIRData.command, и действие, закреплённое за этим числом. Числа берутся из собственной таблицы, снятой читалкой в первом уроке раздела: у типового пульта из наборов это 67 для OK, 21 для VOL+ и 7 для VOL-. Пока команд меньше трёх, карта живёт в голове; на четвёртой она переезжает в шапку скетча комментарием и дальше сопровождает проект всю жизнь.
В скетче карта разворачивается в switch по команде — такой переключатель мы уже собирали для ночника в уроке про управление светом, поэтому про механику case и break ни слова. Вместо этого два приёма, которые отличают карту от разового скетча. Первый: каждый код получает именованную константу — CMD_OK, CMD_FASTER, CMD_SLOWER. Через месяц голое 67 в case придётся расшифровывать по таблице, а CMD_OK читается сам. Второй: в default живёт не пустота, а печать незнакомой команды — карта честно сообщает, что пульт прислал код, которого она не знает. Это первый звонок «у вас другой пульт», и он дешевле любой отладки.
Третий приём я подсмотрел у промышленных приборов: карта печатает себя при старте. Одна строка в setup() — Serial.println с перечнем кодов и ролей — и в мониторе появляется оглавление скетча. Звучит избыточно, пока вы не откроете проект через полгода: оглавление в логе отвечает на вопрос «что тут на какие кнопки повешено» быстрее, чем чтение кода. Цена символическая — около 30 мс печати при 9600 бод один раз за включение.
#include <IRremote.hpp> const int IR_PIN = 2;const int LED_PIN = 12; const byte CMD_OK = 67; // OK: включить и выключитьconst byte CMD_FASTER = 21; // VOL+: мигать чащеconst byte CMD_SLOWER = 7; // VOL-: мигать медленнееconst byte CMD_OFF = 9; // EQ: погасить bool lampOn = false;int blinkMs = 400;unsigned long lastBlink = 0;bool ledState = false; void setup() { Serial.begin(9600); IrReceiver.begin(IR_PIN, ENABLE_LED_FEEDBACK); pinMode(LED_PIN, OUTPUT);} void loop() { if (IrReceiver.decode()) { byte cmd = IrReceiver.decodedIRData.command; switch (cmd) { case CMD_OK: lampOn = !lampOn; break; case CMD_FASTER: blinkMs = constrain(blinkMs - 100, 100, 1000); break; case CMD_SLOWER: blinkMs = constrain(blinkMs + 100, 100, 1000); break; case CMD_OFF: lampOn = false; blinkMs = 400; break; default: Serial.print("ne znayu kod "); Serial.println(cmd); break; } IrReceiver.resume(); } // мигание без delay: decode() не замирает между вспышками unsigned long now = millis(); if (lampOn && now - lastBlink >= blinkMs) { ledState = !ledState; digitalWrite(LED_PIN, ledState); lastBlink = now; } if (!lampOn) { digitalWrite(LED_PIN, LOW); }}Светодиод сидит на пине 12 — у него нет аппаратного ШИМ, поэтому только digitalWrite: горит или нет; резистор на светодиоде держите 220 Ом — на любом цифровом выходе Uno этого достаточно. Мигание вынесено за пределы decode-блока на millis(): пока лампа вспыхивает и гаснет, приёмник продолжает слушать, и ни один кадр не теряется. Поставьте вместо этого delay(400) внутрь case — и приём замрёт на полсекунды: половина серии повторов улетит в никуда, а первое нажатие будет ощущаться с запозданием. Механику неблокирующего мигания я разбираю в уроке про millis вместо delay — здесь взяли её как готовый инструмент.
Печать в default — тихий персонаж этой карты. В мирной жизни она молчит, но подключите второй пульт — и лог сразу покажет чужие коды, ещё до всяких догадок. Это же готовый мостик к сканеру из следующего урока: печать незнакомых команд — половина его работы.
Целью карты бывает и трёхцветный светодиод. У RGB-модуля три канала, и каждому нужен пин с ШИМ — на Uno это 3, 5, 6, 9, 10 и 11, приёмник при этом вольно живёт на своём пине. Классическая раскладка: цифровые кнопки выбирают цвет — красный, зелёный, синий, белый по номеру, — VOL+ и VOL- крутят общую яркость, OK гасит всё. Логика не меняется ни на бит: те же case, только вместо digitalWrite — три analogWrite с пропорциями канала.
Мнение после нескольких таких карт: пульт для RGB — идеальный вход, потому что заменяет два потенциометра и метры проводов, но оттенки подбирать кнопками мучительно. Поэтому я оставляю в карте две служебные кнопки — следующего цвета и сброса — и перебираю варианты ими. Цвета держу в массиве из трёх байт: case копирует его в текущие каналы одной строкой, без трёх лишних переменных и без шанса рассинхронизировать каналы.
Симулятор: карта в деле
| Кнопка | command | В скетче | Роль в карте |
|---|---|---|---|
| OK | 67 (0x43) | case CMD_OK | включить и выключить: один кадр — одно переключение |
| VOL+ | 21 (0x15) | case CMD_FASTER | минус 100 мс к темпу: мигание чаще, граница 100 мс |
| VOL- | 7 (0x07) | case CMD_SLOWER | плюс 100 мс к темпу: мигание реже, потолок 1000 мс |
| EQ | 9 (0x09) | case CMD_OFF | погасить и вернуть темп 400 мс |
| Остальные | любой | default | печать незнакомого кода в Serial |
Что попробовать: включите лампу и зажмите VOL+ десять раз — на сотне миллисекунд темп упрётся в границу constrain, и дальше карта станет глуха к ускорению. Потом нажмите любую цифру: в мониторе мелькнёт незнакомый код из default-ветки. И загляните в Serial сразу после старта: оглавление карты печатается само. Вглядитесь и в формат печати: cmd= приходит десятичным числом, а в таблице урока те же коды сидят в hex — та самая пара «67 и 0x43» из урока про bin, hex и dec, только вживую: одно число для case, второе для сверки с чужими таблицами. Именно для таких экспериментов следующий урок собирает сканер: он выпишет код каждой кнопки вашего пульта и сохранит таблицу прямо в шапке скетча.
- Карта кодов — три колонки: кнопка, command, действие; живёт в шапке скетча и переезжает из проекта в проект
- Каждому коду — именованная константа: CMD_OK в case читается через полгода, голое 67 — нет
- Мигание строится на millis после decode-блока: delay внутри case замораживает приём и глотает кадры
- RGB требует три ШИМ-пина (3, 5, 6, 9, 10, 11 у Uno); цвет держу в массиве из трёх байт и копирую одной строкой
- Коды из чужих статей не переносятся: своя таблица снимается сканером за минуту на пульт
- 1. Где брать коды для карты своего проекта?
- 2. Почему мигание лампы вынесено на millis, а не сделано delay внутри case?
- 3. RGB-светодиод на Uno: какие пины годятся для трёх каналов?
- 4. Пульт прислал код, которого нет в карте. Что делает скетч из урока?
- 5. Почему светодиод на пине 12 управляется только digitalWrite?
Карта собрана: у каждой кнопки своя роль, у каждого кода своё имя. Осталась деталь, без которой карта не строится на новом пульте, — сами коды. Следующий урок делает сканер: он прогоняет все кнопки через Serial Monitor и складывает готовую таблицу в шапку скетча — та самая минута, после которой чужие таблицы больше не нужны.
Частые вопросы по теме урока
- Где брать коды для карты своего проекта?
- Коды — выбор производителя пульта, а не стандарт: у соседского экземпляра те же кнопки назовут другие числа. Единственная надёжная таблица — снятая читалкой из первого урока раздела с вашего собственного пульта. Из своей таблицы, снятой читалкой с вашего пульта
- Почему мигание лампы вынесено на millis, а не сделано delay внутри case?
- Между decode() и resume() приёмник глух, а delay растягивает это окно на полсекунды: серия повторов и свежие нажатия улетают в никуда. Поэтому всё тяжёлое — печать, логика, мигание — живёт за пределами decode-блока. delay внутри decode-блока глушит приёмник, и кадры теряются
- RGB-светодиод на Uno: какие пины годятся для трёх каналов?
- Три канала требуют analogWrite, а ШИМ у Uno живёт на шести пинах: 3, 5, 6, 9, 10, 11. На остальных каналы смешаются только двумя состояниями. Берите по одному пину на канал — один пин, один analogWrite. 3, 5, 6 или 9, 10, 11 — любые из ШИМ-набора
- Пульт прислал код, которого нет в карте. Что делает скетч из урока?
- Печать незнакомой команды — первый звонок чужого пульта или снятой не той таблицы. Заодно это заготовка сканера из следующего урока: печать незнакомых команд — половина его работы. Печатает незнакомый код в Serial через default-ветку
- Почему светодиод на пине 12 управляется только digitalWrite?
- ШИМ делают таймеры, и их выходы разведены на 3, 5, 6, 9, 10, 11. Пин 12 из их числа, поэтому там только включить и выключить. Для плавной регулировки карту переселяют на ШИМ-пин — поведение case не меняется. На нём нет аппаратного ШИМ у Uno