Использование пультов ДУ от бытовой техники
Автор ARV   
15.09.2010 г.

пульт DVD BBKВелик соблазн воспользоваться в любительских конструкциях возможностями дистанционного управления при помощи ИК-пультов, которых в настоящее время доступно великое множество. Времена, когда для приема ИК-сигналов приходилось делать многотранзисторный приемник или искать специализированную микросхему, канули в лету, и теперь для этого достаточно приобрести трехвыводный ИК-приемник типа TSOP-хххх или аналогичный - их тоже достаточно много. В сущности, проблема остается только одна: распознать принятый код и как-то на него отреагировать. Об этом и пойдет речь.

Особенностям устройства приемников TSOP уделять внимание нет смысла, т.к. подробные описания их работы давно и свободно имеются в Интернете. Отмечу лишь, что эти приемники выпускаются на различные частоты несущей (от 36 до 51 килогерца), и правильный выбор приемника - залог совместимости разрабатываемой конструкции с имеющимся пультом. Тут может быть два главных подхода: купить пульт и собрать устройство конкретно под него или же собрать устройство по одной из известных схем и затем приобрести подходящий пульт.

В первом подходе камнем преткновения может оказаться определение стандарта, по которому кодируется излучаемый пультом сигнал. Тут каждая фирма-производитель стремится придумать что-то свое, и в итоге мы имеем де-факто стандарты SONY, TOSHIBA, JVC, SAMSUNG, PHILIPS, LG и т.д. Не смотря на некое сходство, это полностью несовместимые между собой стандарты, причем отличаться может все: от временных диаграмм кодирования команд до частоты несущей и ее скважности.

При втором подходе возможности творчества ограничены тем, что имеется в Интернете, а имеются, как правило, узкоспециализированные схемы конкретных устройств и прошивки микроконтроллеров без исходных текстов. Все это не способствует созданию собственных конструкций.

 

Частично решают проблему готовые программные библиотеки для декодирования сигналов разных стандартов, причем наибольшее распространение получили библиотеки приема в стандарте RC5. К сожалению, распространенность библиотек не соответствует распространенности пультов ДУ с этим стандартом: в моем доме скопилось уже 7 различных пультов от аппаратуры, из которых только 1 оказался этого стандарта, да и тот всего с 6-ю кнопками (см. фото) - это пульт от носимой CD-магнитолы PHILIPS. Все прочие пульты и близко не соответствуют стандарту RC5. Кстати, в продаже пультов с маркировкой PHILIPS (некая надежда на соответствие стандарту), так же очень мало - все больше LG, BBK и тому подобных.

В общем, для комплексного решения проблемы было бы желательно иметь некую универсальную библиотечку программного декодирования сигналов с желательно абсолютно любых пультов ДУ.

К сожалению, разница в стандартах кодирования так велика, что действительно универсальная библиотечка окажется весьма объемной и вряд ли оправдает себя в небольших конструкциях на небольших микроконтроллерах. Но некое приближение к идеалу возможно, о чем далее и пойдет речь.

1. Анализ стандартов.

С некоторыми стандартами кодирования информации, используемыми различными производителями для пультов ДУ, можно ознакомиться по следующим ссылкам:

Повторяться смысла нет, поэтому приведу выводы, к которым я пришел после изучения различных источников. Во-первых, нет смысла ориентироваться на стандарты, которые давно вышли из моды или не получили широкого распространения, например, ITT, который, если я не путаю, использовался в ДУ для отечественных телевизоров «Горизонт» и т.п. и нынче является экзотикой. Во-вторых, нет большого смысла для любительских конструкций в обеспечении корректного декодирования всех стандартов, так как в самоделке главное - это уметь отличать одну кнопку пульта от другой, и при этом не реагировать на другие пульты, а будет ли при этом код кнопки соответствовать коду по стандарту или нет - не принципиально. Согласие с этими оговорками значительно упрощает задачу.

Если внимательно всмотреться в формат импульсной последовательности всех приемлемых протоколов, можно найти в них сходства и отличия. Сходства:

  • используется кодирование методом «длительности промежутка между импульсами» (pulse distance modulation - PDM), т.е. логической единице соответствует долгий интервал между посылками, и логическому нулю - короткий.
  • присутствует некая стартовая последовательность в начале передачи каждого кода.
  • передаваемый импульс (не стартовый) имеет длительность порядка 0,5 мс
  • логическая единица кодируется интервалом примерно в 2 раза большим, чем логический ноль.
  • логический ноль кодируется интервалом около 0,5 мс.

Отличия:

  • несущая частота различна
  • стартовая последовательность различна
  • в некоторых стандартах используется для кодирования метод ШИМ (SONY) или более сложные методы (PHILIPS RC-MM).
  • в стандарте RC5 имеется toggle-бит, который меняется независимо от передаваемых команд
  • в некоторых стандартах при удержании кнопки пульта код полностью передается только один раз, а затем передается только «символ» повторения - один или несколько им пульсов, не укладывающихся в общий формат кода.
  • различно общее число битов в кодах.

2. Формулировка требований к универсальной библиотеке приема ИК-кодов.

Сходства стандартов, отмеченные только что, явно будут способствовать созданию универсального кода, способного принять сигналы от разных пультов - это очевидно. Однако, прежде надо как-то побороть различия.

С разницей в несущих частотах можно смириться потому, что наиболее распространенный приемник TSOP на частоту 36 килогерц успешно способен принимать сигналы на несущей 38 килогерц и наоборот. Правда, при этом возможно снижение чувствительности (т.е. дальности уверенного приема) до 2-х и более раз... В сущности, это не большая беда: пульт передает сигнал, который в условиях обычной комнаты можно принять с расстояния 15 метров и более, а комнаты такой протяженности, согласитесь, скорее редкость, чем норма, поэтому снижение дальности до 7 и даже 5 метров вряд ли заметно снизит комфорт от ДУ. Таким образом, можно брать TSOP на некую среднюю частоту и не мучиться. В качестве средней я бы остановился на 38 кГц. Ну а если есть желание сохранить дальность на предельном уровне - надо будет, купив пульт, определить его несущую частоту и уж затем искать и покупать нужный приемник TSOP. Итак, проблема с несущей - не проблема.

Внимательное рассмотрение стартовых последовательностей различных стандартов позволяет предположить, что при определенных допущениях можно бороться и с этим явлением, причем алгоритмически, т.е. программно. Естественно, при этом есть шанс пропуска кодов или прием кода не с начала, но опасность этого невелика, о чем будет сказано далее.

Если мы решили (а это так и есть) декодировать принимаемые коды по методу PDM, ШИМ-сигнал корректно принять мы сможем вряд ли... Я решил, что проще будет отказаться от совместимости с пультами SONY, чем наоборот, однако, далее я дам некоторые рекомендации, как учесть и этот вариант в универсальной библиотеке - если кому-то это потребуется на самом деле, он сможет сделать это и сам. Итак, отказываемся от совместимости с SONY (тем более что и пульты этой фирмы не из самых дешевых).

С кодировкой RC5 все элементарно: наличие toggle-бита однозначно можно учесть в программе - и это будет сделано.
Символ автоповтора так же можно учесть в алгоритме, хотя некоторые проблемы в связи с этим могут иметь место - об этом далее.

Ну и, наконец, разное число бит в коде. Вообще говоря, это не проблема, если мы не стремимся декодировать код по стандарту, а ограничиваемся просто возможностью отличать один код от другого (именно об этом мы и договорились в самом начале). Достаточно принимать некое заведомо достаточное для однозначного отличия кодов число бит, игнорируя все прочие. Вопрос выбора этого самого достаточного количества не так прост, как кажется на первый взгляд - я посвятил ему целое небольшое исследование, с результатами которого я вас познакомлю далее. Но в целом проблема решаема.

Итак, с учетом изложенных ограничений, возможность создания универсального кода для приема команд с любых пультов не вызывает сомнения.

3. Универсальный код.

Итак, со всеми аппаратными и теоретическими ограничениями мы разобрались, и пора переходить к проблемам чисто программным.

Первая - выбор способа приема кодов. Момент, когда пользователь решит нажать кнопку на пульте, никогда заранее неизвестен, то есть мы должны считать, что поступление в нашу программу кодов осуществляется асинхронно, т.е. в неизвестный момент. С первого взгляда кажется, что наиболее логичным и правильным было бы использование для этого прерываний по перепадам сигнала с датчика TSOP, например. Это действительно так, НО! Во-первых, не всегда есть возможность использовать прерывания, например, из-за ограничений в трассировке печатной платы. Во-вторых, алгоритм декодирования нулей и единиц будет основан на подсчете длительности интервалов времени, а значит, при чисто асинхронном приеме по прерываниям потребуется еще использовать и таймер, что так же далеко не всегда возможно. В-третьих, создание кода с несколькими асинхронными событиями требует известной аккуратности от программиста, и для начинающих может быть затруднительным. Поэтому я решил отказаться от этого подхода и применить синхронный прием, который заключается в следующем: когда программа пожелает, она осуществляет обращение к функции приема кода, которая не завершается до тех пор, пока код не будет принят. Иначе говоря, пока не поступит код, программа будет его ждать в режиме «подвисания».

Это, кстати, не так уж и плохо, если подумать. Во-первых, на самом деле не так часто действительно нужно что-то важное делать между приходом кодов с ДУ. Простой пример - управление усилителем: включили питание - ждем, пока поступит, например, команда изменения громкости, а состояние аппаратуры при этом неизменно, т.е. МК ничего не надо делать. Когда поступила команда - обработали ее и снова можно ждать следующую. При этом можно еще и усыпить МК, если организовано пробуждение по сигналу с TSOP - незаменимо для батарейных конструкций. Во-вторых, хотя прием команд СИНХРОННЫЙ, для всех прочих дел никто не запрещает использовать АСИНХРОННУЮ, т.е. фоновую работу - динамическая индикация, например, прекрасно может при этом функционировать. И опять же, в-третьих, понять логику работы синхронных процессов намного проще начинающему, чем асинхронных, да и сам код попроще, а, главное, компактнее получается.

 

Теперь задумаемся об алгоритме приема PDM-кодированного сигнала с учетом ранее изложенных особенностей и ограничений.

Сначала необходимо дождаться прихода стартовой последовательнсоти, т.е. синхронизироваться с передатчиком. Затем надо ловить конец (т.е. задний фронт) каждого импульса и измерять время до начала (т.е. переднего фронта) следующего. В зависимости от результата этого замера мы имеем три ситуации: если интервал короткий - это принимался логический ноль, если интервал очень длинный - это, скорее всего, конец кода, а если интервал умеренно долгий - это логическая единица. Таким образом, прием битов ведется либо до получения сверхдолгого промежутка, как только что было показано, либо до приема заданного количества битов. Отложим обсуждение количества битов и обратимся к стартовой последовательности.

В большинстве стандартов стартовая последовательность состоит из импульса повышенной длительности (больше длительности логической единицы в коде) и интервала так же увеличенной длительности. Если обобщить форматы всех стандартов, то можно принять следующее: стартовый импульс не короче логической единицы, но и не длиннее учетверенной длительности логической единицы, аналогичные параметры имеет и интервал. Таким образом, можно свести прием стартового сигнала к приему обычного бита, если задать соответствующие длительности в алгоритме. То есть синхронизация ничем не будет отличаться от приема самого кода, просто в результирующем коде часть битов будут всегда неизменными, не значащими. Как уже было не раз сказано, наличие таких лишних битов в коде никак нам помешать не может.

Собственно говоря, уже вполне можно приступать к собственно написанию кода. Я приверженец WinAVR, поэтому проблем с выбором инструментария не будет.

Наша библиотечка будет надолго задерживать основной цикл, поэтому следует предусмотреть во время ожидания сброс WDT, а так же не использовать программных задержек на время, достаточное для срабатывания WDT. Поэтому начало создадим заголовочный файл rc_resive.h и введем в него следующее:

#include <avr/io.h>
#include <avr/wdt.h>
#include <util/delay.h> 

Затем опишем прототипы функций, которые потребуются для наших целей.

static uint8_t delta_t(void);
uint32_t resive_ir(void);

 

 

Первая функция будет измерять интервал между импульсами в коде, а вторая - собственно заниматься приемом кода. Важно, что первая функция объявлена статической: она не потребуется вне модуля нашей библиотечки, и потому префикс static позволит компилятору при необходимости проинлайнить ее в коде, сократив тем самым объем итоговой прошивки. Вторая же функция должна быть видимой в других модулях программы, и поэтому не может быть статической. Если вы пишите программу методом copy-paste, т.е. не оформляете эту библиотеку в виде отдельного модуля, то обе эти функции (равно как и все прочие, кроме main) следует сделать статическими.

 

Теперь определим все нужные в программе макросы:

 

#define LOG0_100us           10
#define LOG1_100us           20
#define END_100us            100
#define is_1()               (PINB & _BV(0))
#define is_0()               (!is_1())
#define do_nothing()         wdt_reset()
#define MAX_BIT_CNT          33

 

Первые три константы определяют интервалы для отличия принимаемой единицы от нуля и от конца кода, причем задаются они 100-микросекундными шагами. Такая точность определения интервалов достаточна для декодирования по принятым нами соглашениям, и в то же время она невелика, чтобы волноваться за WDT. Кроме того, выбор такого интервального шага позволит для подсчета достаточно больших интервалов использовать однобайтные переменные, что предпочтительно для скорости и объема кода.

Макросы-функции is_1() и is_0() служат для определения уровня на выводе порта, к котоому подключен выход датчика TSOP. Обращаю ваше внимание на то, что в программе мы оперируем физическими уровнями сигналов, в то время как ранее при рассуждениях оперировали логическими. Разница между ними в том, что логические уровни после приема датчиком TSOP инвертируются, превращаясь в физические. То есть в то время, когда по протоколу пульт молчит и на его выходе низкий логический уровень, на выходе TSOP присутствует логическая единица. В программе мы работаем именно с этим сигналом.

Макрос do_nothing() предназначен для сброса WDT - если вы в своей программе его не активируете, то этот макрос можно сделать пустым, и он не помешает остальному коду.

Наконец, последний макрос задает максимальное количество принимаемых битов. О том, почему именно такое значение выбрано и какие другие значения допустимо (и при каких условиях) использовать, мы поговорим позже.

Теперь создайте файл rc_resive.c и в нем введите следующее:

uint32_t resive_ir(void){
      uint32_t code = 0;
      static uint32_t oldcode = 0;
      while(is_1()) do_nothing();// синхронизация с началом импульса
       // ввод не более заданного кол-ва битов
      for(uint8_t i = MAX_BIT_CNT; i; i--){
            code <<= 1;                  // готовим очередное место бита
            uint16_t delta = delta_t();  // измеряем длительность 1 в линии
            if((delta >= END_100us)) 
                break;                   // если слишком долго - конец приема            
            if(delta > LOG0_100us)
                code |= 1;             // если прием 1 - заносим в бит 1      
      }
      if((code > 0) && (code < 5))
            return oldcode;      
      oldcode = code;      
      return code;                       // возвращаем, что накопилось
} 

Для приема используется локальная переменная code, обнуляемая при каждом вызове функции. Вторая локальная переменная oldcode - статическая, то есть сохраняет свое значение от вызова к вызову функции, обнуляется она только при самом первом обращении. Эта переменная хранит предыдущий принятый код - он потребуется в том случае, если вместо полного кода пульт передаст только символ повторения.

В соответствии с ранее озвученным алгоритмом, начинается прием с синхронизации - мы ожидаем, пока на выходе TSOP не появится низкий уровень. Во время ожидания постоянно сбрасывается WDT (если нужно, конечно). Как только обнаружено начало кода - переходим к циклу приема битов. Прием осуществляется с условно старшего бита, т.е. первый принятый бит оказывается самым старшим в результирующем коде. Опять же, как уже было сказано, мы осуществляем замер очередного интервала при помощи функции delta_t() и по нему либо «задвигаем» в code единичку, либо завершаем работу цикла. Нолик, как это видно по коду, и так задвинется при необходимости.

После выхода из цикла мы должны проверить код на допустимость. В моих экспериментах символ автоповтора получался равным 2. Для страховки я решил считать таковыми любые коды от 1 до 4 включительно, то и отражает оператор сравнения. Если принят такой маленький код - вместо него выдается предыдущий принятый. А вот если принят код «нормального размера» - он запоминается и возвращается, как результат функции. Возможно, вам придется поэкспериментировать с диапазоном значений символа автоповтора - сложно предсказать, все ли пульты будут одинаково его передавать...

Вот такая простая функция получилась у нас. Теперь уточним, что представляет из себя вторая функция.

static uint8_t delta_t(void){
      uint8_t i = END_100us;
 
      while(is_0()) do_nothing();  // ждем появления 1 в линии
      for( ; i; i--){              // ждем не бесконечно!!!
            _delay_us(100);         // кусочками по 100 мкс
            do_nothing();           // при этом сбрасываем WDT
            if(is_0()) break;       // прекращаем ждать при 0 на входе
      }
      return END_100us - i;        // возвращаем интервал
}

Эта функция так же очень проста, и так же начинается с ожидания, только на этот раз ждать надо «конца» импульса, или, иначе говоря, начала кодового интервала. У нас никогда не может быть бесконечного нуля на выходе TSOP (если он исправен, конечно), поэтому цикл ожидания не ограничен по времени, а вот цикл измерения - ограничен значением END_100us - ведь после передачи последнего бита высокий уровень на выходе TSOP может длиться вечно. В общем-то, кроме комментариев, и добавить-то про эту функцию нечего...

На этом создание библиотечки можно считать завершенной. Как же ею пользоваться? Элементарно! Подключаете в свой проект этот файл, и делаете главный файл, например, main.c следующего вида:

#include <avr/io.h>
#include <stdio.h>
#include "lcd.h"
#include "rc_resive.h"
 
int main(void){
      char str[32];
      uint32_t code;
      uint8_t cnt = 0;
      lcd_init(LCD_DISP_ON);
      lcd_puts_P("READY");
      while(1){
            lcd_gotoxy(0,1);
            code = resive_ir();
            sprintf(str,"%8.8lx:%3.3d",code,cnt++);
            lcd_puts(str);
      }
}

Этот демонстрационный пример всего-навсего принимает коды с пульта и отображает их на ЖКИ. Именно для работы с ЖКИ и организации стандартного вывода подключаются модули lcd.h и stdio.h. ЖКИ в этом примере используется двухстрочный, подключается он по 4-проводному варианту к порту С микроконтроллера (см. содержимое lcd.h - там расписаны все пины порта).

В программе нет никаких особенностей, разве что переменная cnt, которая просто показывает количество принятых кодов - это удобно при наблюдении за удержанием кнопки пульта. С помощью этой демонстрационной программы можно, наконец, выяснить пределы количества принимаемых символов, о чем уже не раз упоминалось.

Тестируя свои пульты этой программой, вы можете узнать, какие биты меняются, а какие от кода к коду остаются неизменными. Если времени на это вам жаль - воспользуйтесь результатами моих исследований.

 

Пульт DVD BBK Пульт TV/SAT LG Пульт магнитолы PHILIPS Пульт видика SAMSUNG-Электроника Пульт сплит-системы LG Пульт TV TOHIBA

 

Я протестировал 6 пультов от имеющейся в моем доме аппаратуры (на фото выше) и свел результаты в Excel-табличку. Я не буду приводить ее здесь - вы можете ее скачать и посмотреть сами: одинаковым цветом выделены коды, которые совпадают в пультах разных типов. Для всех пультов, кроме RC5 и пульта от Сплит-системы, характерно наличие неизменной части в коде (старшие 2 байта), которые указаны в таблице, и меняющейся части (младшие 2 байта). Пульт от Сплит-системы имеет особенности, которые вряд ли позволят использовать его в поделках: во-первых, он передает не коды кнопок, а коды режимов работы кондиционера, поэтому при нажатии, например, кнопки «POWER» могут передаваться разные коды; во-вторых, пульт не посылает коды многократно при удержании кнопки нажатой; в-третьих, если пульт выключен (дисплей не светится), он не передает никакие коды, кроме кнопки «POWER». Собственно говоря, его можно из рассмотрения исключить.

Анализ же остальных кодов говорит о том, что прием менее 32 бит кода не позволит гарантировать отсутствия совпадений кодов у разных пультов. Если пульт у вас только один - можно принимать 16 бит, и при этом изменить тип всех переменных и функций uint32_t в нашей библиотечке на uint16_t - это даст заметную экономию объема кода. Использовать прием менее 16 бит нежелательно, особенно это касается приема одного байта - в этом случае коды разных кнопок даже от одного и того же пульта могут детектироваться одинаково.

Почему же я указал в библиотечке не 32, а 33 бита в максимуме? Большинство пультов передают либо 24, либо 32 бита (RC5 передает 14 бит), однако в совокупности со стартовыми битами их число может быть и больше. Кроме того, некоторые пульты передают именно 33 бита кода. Использование 32-битовой переменной для сохранения 33 битов не является нонсенсом, т.к. мы уже выяснили, что потеря старших битов, связанных со стартовой последовательностью, нас не пугает, младшие для нас важнее. Вот потому я и выбрал 33 бита - этого достаточно для всех моих пультов, и я смею надеяться, будет достаточно для любых ваших.

В реальной программе принятый код по идее должен сравниваться с заранее определенными (назначенными) кодами для выполнения тех или иных функций. Вот тут-то как раз и придет на помощи только что приведенный тестовый пример: составив с его помощью табличку соответствия кодов кнопкам вашего пульта, вы сможете в настоящей программе использовать конкретные числовые константы. Но в некоторых проектах можно обойтись и без этого, используя принцип обучения, заключающийся в следующем.

Изначально устройство содержит в EEPROM незаполненный массив кодов для разных функций устройства. При первом включении (или при каких-то специальных действиях, например, при установке какой-то перемычки) программа входит в режим обучения: принимает код с пульта и сохраняет его в массиве. В нормальном режиме каждый принятый код сравнивается с имеющимися в массиве, и, если найдено совпадение кодов, выполняется требуемая функция. По этому принципу построены известные конструкции диммеров, управляемых от пульта ДУ. И вот здесь имеется небольшой подводный камень, о котором надо помнить и учитывать его в реальных конструкциях.

Даже в тестовой программе можно увидеть, что иногда TSOP выдает импульс на выходе без активности пульта. Это возможно из-за воздействия различных электрических помех, а так же из-за случайной его засветки «подходящими» частотами: например, зажигание лампы дневного света может сопровождаться всплеском излучения, которое приведет к появлению случайного импульса на выходе датчика. В нашем случае это приведет к тому, что функция resive_ir() может вернуть нулевое значение или, если не повезет, принятое в прошлый раз. Отфильтроваться от автоповторного кода можно изменением диапазона допустимых кодов, о чем говорилось ранее, а вот нулевой код надо просто игнорировать в основной программе. Кроме того, можно использовать определенные особенности некоторых пультов для дополнительной проверки кода на корректность: из подготовленной мною таблицы хорошо видно, что большинство пультов передает информацию двумя последними байтами, причем второй есть инверсия первого. Если ваш пульт действует так же, эту особенность можно использовать для уточнения достоверности принятого кода.

 

Иногда может случиться так, что вызов функции resive_ir() произойдет как раз во время передачи кода, и в этом случае будут приняты только последние биты посылки. Это ситуация ложной синхронизации нашего простейшего алгоритма, ведь мы решили не определять длительность стартового импульса... Но это совсем не страшно, так как практически со 100% гарантией можно утверждать, что принятый таким способом «хвост» кода не совпадет ни с одним из заданных значений.

Надеюсь, столь обширная статья поможет разобраться вам в методах приема сигналов с любых пультов, и станет хорошим дополнением ваших разработок. Буду рад замечаниям и предложениям по теме.

Вы можете скачать:

Обсудить материал на форуме. (1 сообщений)

 


Добавить в любимые (0) | Просмотров: 44917

  Коментарии (1)
 1 Написал(а) Atabek, в 00:32 21.04.2013
здравствуйте. как получить сигнал от ИК приемника(38 КГц) его data подключен в PB0 у. 
не знаю как настроит таймеры . 
помогите пожалуйста. 
за ранние спасибо.

Только зарегистрированные пользователи могут оставлять коментарии.
Пожалуйста зарегистрируйтесь или войдите в ваш аккаунт.