Читалка из урока про приём кодов работала безупречно ровно до вечера, когда я направил на макетку пульт от старого кондиционера. Типовой пульт из набора печатает аккуратные строки: NEC, команда, адрес. Кондиционер выплюнул UNKNOWN и число, из которого не было понятно ничего. Пульт от люстры тоже повёл себя странно: команды плясали, часть кнопок молчала, а в мониторе иногда всплывали кадры, которых я не нажимал.
Виноват оказался не пульт, а моя привычка читать только command. decodedIRData — это целый паспорт кадра: протокол, число бит, адрес, флаги, сырые данные. Без этих полей я не отличал первое нажатие от повтора и не понимал, почему половина пультов в доме живёт вне моей таблицы кодов. Признаюсь честно: к тому вечеру я три месяца работал с библиотекой и был уверен, что знаю её вдоль и поперёк. Структура декодера оказалась глубже: кадр несёт печать того, как он ехал по воздуху — сколько в нём бит, кто его отправил и не был ли он повтором предыдущего. Научившись читать эти печати, я перестал гадать по монитору и начал видеть.
Здесь разберём, что именно decode() кладёт в результат: как кадр доходит от вспышки света до структуры данных, какие поля стоят того, чтобы их печатать, и почему между decode() и resume() плата на секунду глохнет. Этот словарь пригодится в каждом следующем уроке раздела.
Конвейер: от вспышки до структуры decodedIRData
Пока скетч занят своими делами, за кадр отвечает прерывание. IrReceiver.begin() цепляет его на пин приёмника, и каждое изменение линии ловит не loop(), а аппаратный механизм внутри библиотеки: длительность каждого импульса и каждой паузы меряет таймер, интервалы складываются в буфер. Как устроены такие обработчики под капотом — отдельная история, мы разбирали её в уроке про прерывания; здесь достаточно факта: пока идёт приём, ваш скетч ничего не делает и ничего не замечает.
decode() — это кассир на выходе конвейера. Когда в буфере лежит целая посылка, он разбирает интервалы в структуру decodedIRData и возвращает true. Структура одна на весь объект: следующий кадр перезапишет её целиком. Отсюда вечный сценарий — decode() вернул true, вы прочитали поля, вызвали resume(), буфер свободен. Забытый resume() мы ловили в первом уроке раздела: после него decode() навсегда молчит, потому что кассир остаётся занятым.
У той же медали есть оборот, о котором в туториалах не пишут: между decode() и resume() приёмник глух. Всё, что пульт шлёт в этом окне, теряется безвозвратно. Числа для наглядности: строка печати в 30 символов при 9600 бод занимает около 30 мс, кадр NEC тянется 50–85 мс, повтор приходит каждые 108–110 мс. Пара длинных печатей внутри decode-блока — и окно глухоты съедает следующий кадр целиком.
Один нюанс стоит проговорить сразу: буфер интервалов и структура результата — вещи разные. Пока приём идёт, длительности копятся в очереди внутри библиотеки; decode() только перекладывает готовую посылку в decodedIRData. Поэтому долгая обработка внутри decode-блока не портит текущий кадр — он уже разобран, — но блокирует следующий. Вся хитрость в том, чтобы делать в этом окне минимум работы: сохранить пару чисел, выставить флаг, отпустить буфер. Всё остальное — после resume().
#include <IRremote.hpp> const int IR_PIN = 2; void setup() { Serial.begin(9600); IrReceiver.begin(IR_PIN, ENABLE_LED_FEEDBACK); Serial.println("Наведите пульт и жмите кнопки");} void loop() { if (IrReceiver.decode()) { // протокол, адрес, команда и биты — одной строкой IrReceiver.printIRResultShort(&Serial); Serial.print("cmd="); Serial.println(IrReceiver.decodedIRData.command); Serial.print("bits="); Serial.println(IrReceiver.decodedIRData.numberOfBits); IrReceiver.resume(); }}printIRResultShort — быстрая сводка, но руками интересны пять полей. command — команда кнопки, байт от 0 до 255. address — паспорт пульта, по которому плата отличает свой пульт от телевизора. numberOfBits — сколько бит в кадре: у NEC всегда 32, у Sony 12, 15 или 20, у RC5 — 14; это число подсказывает протокол быстрее любой таблицы. flags — битовая маска состояния: у повторного кадра поднят бит IRDATA_FLAGS_IS_REPEAT, мы уже вешали на него различение нажатия и удержания. Наконец, protocol — номер протокола из внутреннего списка библиотеки, а getProtocolString() переводит его в читаемое имя.
Отдельно есть decodedRawData — тот же кадр, но одним 32-битным словом. Для NEC вида 0x00FF43BC оно читается на глаз: старшие байты — адрес 0x00FF, младшие — команда 0x43 и её инверсия 0xBC. Печатаю такие числа в hex — привычка из урока про bin, hex и dec: команда 0x43 находится в таблице даташита мгновенно, а её десятичная версия 67 — нет. А в проектах, где таблицу кодов снимает пользователь, печатаю оба представления подряд: hex — для человека, dec — для сверки с таблицами из других уроков.
Моё правило для отладки: в loop печатать только пары вроде cmd= и name=, а полный паспорт — по нажатию специально выбранной кнопки. Без этого монитор превращается в поток строк, в котором повтор кадра от удержания не отличить от нового нажатия, а глаза перестают цепляться за то, что действительно изменилось.
UNKNOWN: когда декодер пожимает плечами
Если интервалы не похожи ни на один известный шаблон, библиотека честно печатает UNKNOWN. Поле protocol получает соответствующий номер, command остаётся пустым, но данные никуда не деваются: decodedRawData хранит сырые слова, а подробный дамп умеет печатать сам декодер. Кадр при этом никто не подделывал: интервалы честные, просто ни один шаблон не сошёлся. Три типовых виновника, с которыми я встречался: климатическая техника, которая шлёт кадры в два-три раза длиннее NEC; пульты с кодировками вне списка библиотеки; и умирающая батарейка — интервалы плывут, и шаблон перестаёт сходиться.
С батарейкой отдельная история: у меня пульт от вентилятора два дня изображал помехи — UNKNOWN вылетал на каждое третье нажатие, и я уже подозревал приёмник. Свежая батарейка вылечила всё за секунду. Так что при загадочных кадрах первым делом заменяю питание пульта и только потом лезу в настройки декодера.
Для кадров, которые библиотека не узнала, есть режим диктофона: печать всех длительностей импульсов и пауз как есть. Он не понимает, что записал, зато позволяет воспроизвести кадр бит в бит — этим мы займёмся в третьем уроке этой тройки. А пока запомните порядок действий: сначала protocol и numberOfBits, потом RAW-дамп, и только затем мысли о собственном декодере.
| Поле decodedIRData | Что содержит | Пример для NEC-пульта |
|---|---|---|
| command | команда кнопки, байт 0–255 | 67 (0x43) — кнопка PLAY |
| address | адрес пульта, байт | 0x00 у пультов из наборов Arduino |
| flags | битовая маска состояния кадра | бит IS_REPEAT у удержания кнопки |
| numberOfBits | сколько бит в пришедшем кадре | 32 у NEC; 12–20 у Sony; 14 у RC5 |
| protocol | номер протокола из списка библиотеки | NEC; UNKNOWN для экзотов |
| decodedRawData | сырое 32-битное слово кадра | 0x00FF43BC: адрес и команда с инверсией |
Симулятор: чтение команд и разбор через switch
Кнопка → 32-битный кадр: AGC, инверсии и repeat видны глазом; включите помехи и поймайте бит, который не прошёл проверку.
Подгружаем интерактив…
- decode() разбирает буфер в decodedIRData; структура одна на объект — прочитали, что нужно, и сразу resume()
- Между decode() и resume() приёмник глух: печать 30 символов при 9600 бод — это 30 мс, за которые теряется повторный кадр
- Пять полей на каждый день: command, address, flags, numberOfBits, protocol; паспорт одной строкой печатает printIRResultShort
- numberOfBits подсказывает протокол: 32 — NEC, 12/15/20 — Sony, 14 — RC5
- decodedRawData хранит кадр одним 32-битным словом: 0x00FF43BC читается как адрес и команда с инверсией
- UNKNOWN — не приговор: дамп длительностей сохраняет кадр целиком для повторного разбора
- 1. decode() вернул true, команду прочитали, а resume() забыли. Что будет дальше?
- 2. Что покажет numberOfBits у типового пульта NEC?
- 3. Пульт от кондиционера печатает UNKNOWN. Где искать подробности кадра?
- 4. Почему при удержании кнопки часть кадров пропадает, если внутри decode-блока стоит тяжёлая печать?
- 5. Поле flags — это…
Теперь в decodedIRData нет тёмных мест: вы знаете, что лежит в каждом поле и почему кадры исчезают в паузах между decode и resume. Следующий урок разбирает самый ходовой протокол по битам — анатомию кадра NEC: лидер на 9 мс, биты длиной паузы и та самая инверсия, которая делает 0x43 и 0xBC парой. А если ваши проекты тормозят длинными печатями в loop — держите под рукой урок про millis вместо delay: без блокировок кадры теряться перестают.
Частые вопросы по теме урока
- decode() вернул true, команду прочитали, а resume() забыли. Что будет дальше?
- Структура результата считается занятой до resume(). Библиотека не перезапишет её, поэтому decode() больше не возвращает true — классический симптом «первая кнопка работает, остальные молчат». decode() вернёт false: новая посылка не разбирается, пока буфер не освобождён
- Что покажет numberOfBits у типового пульта NEC?
- Кадр NEC — всегда 32 бита: адрес, его инверсия, команда, инверсия команды. Отсюда быстрое правило: 32 бита — почти наверняка NEC. 32 бита
- Пульт от кондиционера печатает UNKNOWN. Где искать подробности кадра?
- При UNKNOWN команда не разбирается, но сырые данные и интервалы живы — их печатают дампом и потом воспроизводят передатчиком. В decodedRawData и дампе длительностей через printIRResultRawlinated
- Почему при удержании кнопки часть кадров пропадает, если внутри decode-блока стоит тяжёлая печать?
- Строка в 30 символов печатается около 30 мс при 9600 бод. Повторный кадр приходит каждые 108–110 мс, и окно глухоты успевает его съесть. Между decode() и resume() приёмник глух, а печать при 9600 бод занимает десятки миллисекунд
- Поле flags — это…
- Флаги — побитовые признакы кадра. Самый ходовой — IS_REPEAT: именно по нему скетч отличает первое нажатие от автоповтора NEC. Битовая маска состояния: у повторного кадра поднят бит IRDATA_FLAGS_IS_REPEAT