Короткое нажатие на PLAY — а читалка напечатала две строки с одной и той же командой. Я долго считал это капризом пульта и лечил дело паузой delay(150) после каждого срабатывания: она глотала второй кадр, а заодно и следующее быстрое нажатие. Так я потерял половину команд в меню и заодно уверенность в том, что вообще прилетает в decode().
Полечилось всё само, когда я разобрал кадр NEC по байтам — не по готовым полям библиотеки, а по сырым числам. Оказалось, две строки — это два физически разных кадра: полный, из 32 бит, и его короткий двойник-повтор, в котором нет ни одного бита. И что у обоих внутри сидит проверка целостности, которую библиотека делает за меня молча, но которую полезно уметь делать руками.
Здесь соберём кадр NEC до последнего байта: четыре байта и их порядок, инверсия как контрольная сумма на один XOR, расширенный NEC без инверсии адреса и анатомия повторного пакета. Хронометраж бит — вспышки 560 мкс и паузы — уже разбирался в уроке про биты, адрес и команду, повторяться не буду: сегодня вся работа — внутри байтов.
Четыре байта: адрес, зеркало адреса, команда, зеркало команды
Порядок байт в кадре NEC всегда один: адрес, инверсия адреса, команда, инверсия команды. Инверсия — тот же байт с восемью перевёрнутыми битами: у 0x43 это 0xBC, и в XOR пара всегда даёт 0xFF. Приёмник делает одну операцию на байт и мгновенно решает, годится кадр или его место в мусорке. Смысл простой: помеха, сбившая один бит, разъезжается с инверсией на два бита, и проверка валится. Защита на два лишних байта — скромнее контрольной суммы из урока про протоколы, но на дистанции в три метра через комнату она не подводила мне ни разу.
Вторая деталь, без которой сырые числа читаются задом наперёд: биты внутри каждого байта идут младшим вперёд, LSB first. Именно поэтому decodedRawData для PLAY выглядит как 0x00FF43BC и не складывается в голове в привычный порядок: старший байт — адрес 0x00, за ним зеркальный 0xFF, и только в младших байтах команда 0x43 с инверсией 0xBC. Мне этого хватило, чтобы навсегда перейти на hex-печать кадра: десятичные 67 и 188 в логе ни о чём не говорят, а пара 43/BC видна с одного взгляда.
У проверки есть и граница: инверсия ловит одиночные сбитые биты, но не спасёт кадр, потерявший кусок целиком, — впрочем, тот обычно разваливается уже по длине. Проверять пару руками стоит не ради параноидальной надёжности, а ради понимания, что библиотека выбрасывает молча. Замер на грани дальности у моего окна: из каждых десяти кадров до command доживали восемь-девять, остальные разваливались на инверсиях и исчезали без следа — кнопка просто «не сработала».
Практический вывод из этого замера неприятный, но полезный: кнопки, которые «иногда не работают», чаще всего не сломаны — кадры просто разваливаются на проверках у границы дальности. Лечится не кодом, а геометрией: ориентация приёмника, свежая батарейка, чистый путь луча. Разбор дальности с числами я делал в отдельном уроке — после сегодняшнего он читается в два счёта: вы уже знаете, что именно отбрасывает приёмник.
#include <IRremote.hpp> const int IR_PIN = 2; void setup() { Serial.begin(9600); IrReceiver.begin(IR_PIN, ENABLE_LED_FEEDBACK); Serial.println("kadry NEC: para i proverka");} void loop() { if (IrReceiver.decode()) { byte cmd = IrReceiver.decodedIRData.command; byte inv = (~cmd) & 0xFF; // зеркало команды Serial.print("komanda 0x"); Serial.println(cmd, HEX); Serial.print("inversiya 0x"); Serial.println(inv, HEX); if ((cmd ^ inv) == 0xFF) { // пара сходится? Serial.println("para sobralas: 0xFF"); } else { Serial.println("para razvalilas"); } byte addr = IrReceiver.decodedIRData.address; Serial.print("adres 0x"); Serial.println(addr, HEX); IrReceiver.resume(); }}Инверсия считается одной строкой: (~cmd) & 0xFF. Маска обязательна, потому что ~ на знаковом int переворачивает все 32 разряда: без неё к 188 прилипает хвост единиц, и сравнение с 0xFF рассыпается. Отдельные байты из готового слова выковыривают сдвигами и масками — та же арифметика, что в уроках про побитовые операции раздела «Основы»; здесь берём приём готовым.
Про адрес в этой паре — отдельная честность. В симуляторе он всегда ноль: движок подкладывает скетчу только команду, полный паспорт с настоящим адресом печатает лишь живой пульт. На реальной железке в старших байтах кадра окажется адрес производителя — у типового пульта из наборов 0x00FF, у фирменной техники свои значения, и снимает их читалка из первого урока раздела. Сборка 32-битного слова ниже — учебная, но XOR-проверка на ней та же самая, что и в живом кадре.
Собрать слово из байтов можно и обратно, в сторону передачи: addr в старший байт, зеркало адреса во второй, команда и её зеркало в младшие — та же анатомия, прочитанная наоборот. В симуляторе проверить не на чем: передачу движок не эмулирует, но на плате такая строка приходит на ум сама, стоит один раз расписать кадр по байтам.
| Позиция в кадре | Байт | XOR-пара | Замечание |
|---|---|---|---|
| 1-й байт | 0x00 — адрес | 0x00 ^ 0xFF = 0xFF | у типового пульта из наборов адрес 0 |
| 2-й байт | 0xFF — зеркало адреса | 0xFF ^ 0x00 = 0xFF | перевёрнутая копия первого байта |
| 3-й байт | 0x43 — команда PLAY | 0x43 ^ 0xBC = 0xFF | та же проверка на команду |
| 4-й байт | 0xBC — зеркало команды | 0xBC ^ 0x43 = 0xFF | не сошлось — кадр выброшен молча |
Второй пакет: повтор без единого бита
Полный кадр NEC тянется порядка 50–85 миллисекунд: шапка 9 мс вспышки и 4.5 мс тишины, дальше 32 бита по 1.12 или 2.25 мс. Пока кнопка держится, пульт не пересобирает эти 32 бита, а каждые 108–110 мс шлёт коротыша: вспышка 9 мс, тишина 2.25 мс, стоп-вспышка 560 мкс — и всё, ноль бит. Четверть длины кадра и ноль информации: «я всё ещё держу ту же кнопку».
У многих дешёвых пультов — мой из набора делает ровно так — один повтор досылается даже после короткого нажатия: контроллер пульта попадает в свой 108-миллисекундный такт, пока палец ещё на кнопке. Отсюда классика: читалка печатает две строки на одно касание, toggle-свет мигает, а автор ищет дребезг, которого у пульта нет и быть не может. Лечится не паузой, а чтением флага: повторный кадр IRremote помечает битом IRDATA_FLAGS_IS_REPEAT в decodedIRData.flags, и фильтр по нему пропускает ровно первое нажатие. Полевую механику я разбирал в уроке про кадры-повторы, практические последствия для света — в уроке про управление светом; здесь важна суть: вторая строка в мониторе — не испорченный кадр, а честная половина протокола.
Почему повтор не тащит биты, отвечает цена: 32 бита — это до 85 мс эфира и миллиампры батарейки, коротыш — около 12 мс. Протокол проектировали под пульт с двумя пальчиковыми батарейками, и каждый кадр в нём стоит денег. Заодно это объясняет, почему повтор не спутаешь с полным кадром даже без библиотеки: длина тишины после шапки другая — 2.25 мс против 4.5, декодер различает их первым же сравнением.
Повторы удобно использовать и как тест пульта. Держите кнопку ровно две секунды и считайте строки в мониторе: при периоде 108–110 мс их должно быть около восемнадцати, из них первая — полный кадр. Вышло вдвое меньше — пульт шлёт повторы реже даташитного или садится батарейка; идут сплошные полные кадры по 32 бита — перед вами не NEC или пульт с другим поведением удержания, и флаг IS_REPEAT тут не помощник. Одна подсчитанная секунда экономит вечер гаданий по коду.
Кнопка → 32-битный кадр: AGC, инверсии и repeat видны глазом; включите помехи и поймайте бит, который не прошёл проверку.
Подгружаем интерактив…
- 1. Что вернёт XOR пары «команда 0x43, инверсия 0xBC»?
- 2. В каком порядке идут байты кадра NEC?
- 3. Короткое нажатие — а в мониторе две строки с одной командой. Откуда вторая?
- 4. Сколько бит несёт повторный кадр NEC?
- 5. Библиотека печатает протокол NEC2. Что это значит?
- Кадр NEC — четыре байта: адрес, ~адрес, команда, ~команда; каждая пара в XOR даёт 0xFF
- Биты идут младшим вперёд, поэтому decodedRawData 0x00FF43BC — это адрес 0x00FF и команда 0x43 с зеркалом 0xBC
- (~cmd) & 0xFF — инверсия одной строкой; без маски к числу прилипают единицы знаковых разрядов
- У расширенного NEC2 адрес 16 бит и без зеркала — адресная пара на нём всегда проваливается
- Повтор: вспышка 9 мс, тишина 2.25 мс, стоп-вспышка — ноль бит, каждые 108–110 мс; отличает его от нажатия флаг IRDATA_FLAGS_IS_REPEAT
Кадр разобран, пары посчитаны, второй пакет приручён. Следующий урок — про пульты, для которых всё это не указ: RC5 и RC6 прячут бит в направлении переключения внутри ячейки, и после их таймингов NEC покажется неторопливым великаном. Заодно протрите свою таблицу кодов: команды, снятые без проверки пар, иногда содержат мусор от кадров на грани приёма — после XOR-фильтра таблица обычно худеет на одну-две строки. И привычка держать приёмник на пине 2 тут не мешает: приёму прерывание не нужно, а будущим проектам пригодится.
Частые вопросы по теме урока
- Что вернёт XOR пары «команда 0x43, инверсия 0xBC»?
- 0x43 ^ 0xBC = 0xFF — признак целой пары. Любой другой результат означает сбитый бит и кадр на выброс. 0xFF
- В каком порядке идут байты кадра NEC?
- Четыре байта строго по порядку: адрес, зеркало адреса, команда, зеркало команды. Поэтому decodedRawData 0x00FF43BC читается от старшего к младшему. адрес, инверсия адреса, команда, инверсия команды
- Короткое нажатие — а в мониторе две строки с одной командой. Откуда вторая?
- Многие дешёвые пульты досылают один повтор даже за короткое нажатие. Повтор не несёт бит и помечается флагом IRDATA_FLAGS_IS_REPEAT. кадр-повтор NEC, который пульт дослал после основного
- Сколько бит несёт повторный кадр NEC?
- Повтор — вспышка 9 мс, тишина 2.25 мс и стоп-вспышка 560 мкс. Информации в нём нет вообще: «кнопка всё ещё держится». ноль — только шапка и стоп-вспышка
- Библиотека печатает протокол NEC2. Что это значит?
- Расширенный NEC: два байта адреса без зеркала, команда и её инверсия. Адресную пару у него не проверяют — зеркала нет. адрес шестнадцатибитный и без инверсии