Для своего HTPC я выбрал материнскую плату Intel D2700MUD. Как позже выяснилось — опрометчиво, поскольку встроенный видеоконтроллер GMA 3650, основанный на PowerVR, полностью поддерживается только в 32-битных Windows, а в Linux поддержка очень ограничена — не работает аппаратное декодирование видео. Но меня устраивает работа HTPC под управлением Windows 7 Home Basic. Также эта плата не умеет пробуждаться по сигналу от USB-клавиатуры.
Также у меня уже был пульт (Philips 2422 549 01930), который мне показался подходящим. Но WinLIRC с ним работал крайне нестабильно. Видимо, использовался какой-то необычный протокол.

Первая мысль была такой — подключить к Arduino IR-приемник, питание взять от шины 5VSB блока питания HTPC, а включение питания (и выключение) осуществлять с помощью имитации замыкания пинов на материнской плате, к которым подключается кнопка включения питания, а остальные команды передавать через RS-232. Но Arduino слишком дорога и занимает много места в и без того небольшом корпусе. Поэтому я решил обойтись дешёвым микроконтроллером ATMega8, в который можно зашить бутлоадер Arduino и программировать его как Arduino NG.
Разбор протокола
Для Arduino существует неплохая библиотека для работы с инфракрасными ПДУ — IRRemote, но она не работает с Arduino NG — не хватает памяти. К тому же, мне не удалось заставить эту библиотеку понимать мой пульт даже на Arduino UNO. Как я писал выше, LIRC (и WinLIRC) не очень хорошо работали с этим пультом — часто пропускали нажатия кнопок, иногда неверно определяли нажатую клавишу. Сам пульт работал исправно — с «родным» DVD-плейером проблем не возникало. Поиски информации о протоколе для этого пульта ничего не дали, так что я решил разобраться с ним самостоятельно.
Для этого мне понадобился осциллограф, но его под руками не оказалось, да и взять было негде. Но в случае с сигналами ПДУ вполне подходит линейный вход звуковой карты. К нему я подключил инфракрасный приёмник TSOP 31236.

Далее с помощью любого аудиоредактора можно записать входной сигнал и проанализировать:

Сначала идет длинная посылка, необходимая для установки уровня автоматической регулировки усиления в приёмнике. Затем следуют импульсы различной длины. Я написав промежуточный скетч для Arduino, который определял время между фронтами импульсов в микросекундах и выводил их в терминал через RS232. Полученные данные я загрузил в Excel:

Когда я только начал изучать протокол, моей главной ошибкой было то, что я считал длительности самих импульсов, но игнорировал длительность интервалов между ними. После того, как я начал считать интервалы между фронтами все встало на свои места. Как видно, большинство импульсов интервалов между фронтами имеют длительности в пределах 350-550 мкс и 700-1000 мкс. Значит так обозначаются значения передаваемых битов — «0» и «1». В протоколе ПДУ используется toggle-бит. Это означает что при нескольких последовательных нажатиях одной и той же клавиши на пульте, в коде будет меняться один бит (иногда несколько). В моем случае toggle-бит имеет необычную длительность — 1200-1400 мкс. К тому же число фронтов в «четных» и «нечетных» посылках отличается. Позже я пришёл к выводу, что интервал 1200-1400 мкс это сумма их двух битов с одинаковым уровнем, но различной длительностью (400+800). В коде я такую посылку обозначил как последовательность из двух бит — «01». Тогда количество бит на выходе стало постоянным.
Скетч для Arduino
Затем я написал скетч, который считал время между изменениями уровня на входе и выводил полученное число в uart. С помощью несложной отладки я добился стабильности определения кодов, добавил сложение с маской для игнорирования toggle-бита (у меня не было функций, в которых он пригодился бы). Потом я жестко закодил функцию, чтобы при получении кода кнопки «Power» микроконтроллер прижимал к земле один из «цифровых» пинов Arduino (в моем случае — 12).
// Заранее прошу прощения за индусский код. Я знаю что можно было сделать намного правильнее и лучше, // но для меня было важным решить задачу, а на изящность решения я не претендовал. byte a = 0; // Текущий уровень на входе byte b = 0; // Сохраненный уровень на входе byte bc = 0; // Счетчик битов const boolean dbg=false; // Режим отладки =) boolean start=false; // Запущен ли отсчет импульсов unsigned int usecs = 0; // Таймер 1 - определяет длительность фронтов unsigned int usecs2 = 0; // Таймер 2 - определяет паузы между посылками unsigned int plen = 0; // Длительность интервала между фронтами unsigned int plen2 = 0; // Длительность паузы между посылками String bits; // Представление буфера в виде единиц и нулей, для отладки unsigned int buffer; // Буфер. Собственно, сюда помещается код нажатой кнопки void setup() { // Initialize the digital pin as an output. // Pin 13 has an LED connected on most Arduino boards pinMode(13, OUTPUT); // Ок, пусть светодиод там и будет. pinMode(12, OUTPUT); // А пин 12 будет подключаться к разъему кнопки питания на мат.плате. digitalWrite(13,HIGH); // Включаем светодиод. digitalWrite(12,HIGH); // Настраиваем на выходе высокий уровень, чтобы плата считала что контакт разомкнут. pinMode(7, INPUT); // Сюда подаем сигнал от ИК-приемника TSOP. Serial.begin(115200); Serial.println("READY"); usecs = micros(); // Инициализируем индусский таймер usecs2 = micros(); } void loop() { a = digitalRead(7); // Считываем значение уровня на входе if(a != b) { // Уровень изменился! Фронт импульса. start=true; // Отсчет импульсов считаем начатым. b = a; // Сбрасываем детектор фронта plen = micros() - usecs; // Замеряем прошедшее время между предыдущим и текущим фронтом usecs = micros(); // Сбрасываем таймер usecs2 = micros(); if (plen<2000) { // фильтруем стартовый импульс и длительность между импульсами if (plen>200 && plen < 620) { // ноль bits += "0"; buffer = buffer << 1; buffer = buffer | 0; bc++; } if (plen>620 && plen < 1150) { // единица bits += "1"; buffer = buffer << 1; buffer = buffer | 1; bc++; } if (plen>1150 && plen < 1600) { // тоггл-бит bits += "01"; buffer = buffer << 2; // Тут сдвигаем на два бита! buffer = buffer | 1; bc++; bc++; } if(dbg==true){ Serial.print(plen); // если включена отладка - выводим длительности импульсов Serial.print(";"); } } } else { // Если в этом цикле изменения уровня на входе не произошло plen2 = micros() - usecs2; // Определяем длительность паузы if(plen2 > 5000 ) { // ЕСЛИ ПАУЗА БОЛЬШЕ 5 МИЛЛИСЕКУНД, ЭТО ЗНАЧИТ ЧТО ПОСЫЛКА ЗАКОНЧИЛАСЬ ИЛИ ЕЩЕ НЕ НАЧИНАЛАСЬ usecs2 = micros(); // сбрасываем Таймер 2 if(start==true){ // Если до этого был запущен отсчет импульсов, значит у нас в буфере должен был скопиться код клавиши. usecs = micros(); // сбрасываем таймер 1 while(bc<40){ // выравниваем вывод. честно говоря, я уже не помню почему так сделал, но раз сделал - значит так надо. bits += "0"; buffer = buffer << 1; bc++; } buffer = buffer & 1048575; // Использование magic-numbers плохо влияет на карму. Это маска для toggle-бита. Serial.println(buffer); // ВЫВОДИМ КОД! if (dbg){ // если включена отладка Serial.print("bits="); // выводим код в виде единиц и нулей Serial.println(bc); // выводим число битов } digitalWrite(13,LOW); // Мигаем светодиодом в знак того, delay(100); // что код от пульта был digitalWrite(13,HIGH); // принят и распознан. switch(buffer){ // Прочие действия при нажатии некоторых кнопок. case 8448: // Кнопка Power на моём пульте. Serial.println("POWER"); // Выводим сообщение POWER помимо кода кнопки. Удобно при разборе логов EventGhost. digitalWrite(12,LOW); // Прижимаем к земле пин кнопки питания на мат.плате delay(300); // На 300мс digitalWrite(12,HIGH); // И отпускаем default: break; } buffer=0; // Обнуляем буффер bits=""; // Обнуляем отладочную строку с битами start=false; // Сбрасываем признак отсчета bc=0; // Сбрасываем количество бит } } } }
А что дальше?
А дальше приложение EventGhost считывает коды из порта RS-232 и «дёргает за ниточки» всем известной оболочки XBMC. Настройка того и другого индивидуальна и в то же время проста, поэтому не заслуживает внимания.

Результат
Устройство работает уже несколько месяцев. Пользуется им в основном мой отец, далекий от техники, поэтому минимально необходимое количество кнопок на пульте является достаточно удобным обстоятельством. Питание включается и отключается, ошибочных срабатываний не бывает.
ссылка на оригинал статьи http://habrahabr.ru/post/204906/
Добавить комментарий