Декодирование сигналов инфракрасного пульта дистанционного управления
29/05/2011 - 17:36
Pavel Bobkov
Введение
Однажды мне понадобилось управлять устройством с помощью пульта дистанционного управления. Дистанция требовалась небольшая (до 10 метров) и я решил использовать для этих целей ИК приемник и инфракрасный пульт от бытовой аппаратуры.
Для инфракрасных пультов дистанционного управления (ИК ПДУ) существует несколько протоколов передачи данных. Они отличаются способами кодирования битов, длительностями сигналов, несущей частотой и количеством передаваемых данных. Один из самых распространенных протоколов, судя по интернету, – это RC5. Изначально я думал, что придется разбираться с ним, но ни одного пульта с таким протоколом у меня под рукой не оказалось. Зато нашлись три пульта от DVD плееров, использующих протокол фирмы NEC.
Схема
Принцип передачи ИК сигналов заключается в следующем - электрический сигнал несущей частоты модулируется передаваемыми данными и с помощью инфракрасного светодиода излучается в пространство.
Передача сигнала уже реализована в пульте, этот вопрос мы не затрагиваем, хотя ничего сложного там нет.
Для приема сигнала используется специальная микросхема, объединяющая в себе фотодиод, усилитель с полосовым фильтром, настроенным на определенную несущую частоту, и детектор, выделяющий огибающую сигнала. Как правило, микросхема имеет три вывода – два питающих и один сигнальный. Последний подключен к коллектору выходного транзистора микросхемы, из-за чего сигнал на выходе оказывается инвертированным.
Микросхемы этого семейства объединены общей аббревиатурой— TSOP.
Для того чтобы узнать, какой протокол используется в пульте дистанционного управления, нужно собрать схемку с ИК приемником, снять осциллограмму сигнала и сравнить ее с известными протоколами.
Правда есть один момент, ИК приемник рассчитан на работу с одной несущей частотой, а в некоторых протоколах значения несущих частот отличаются - одни используют 38 кГц (например, протокол фирмы Nec), другие 36 или 40. Поэтому одна и та же схема не сможет работать со всеми без исключения ИК пультами дистанционного управления.
Схема, которую я использовал при написании программы, приведена на рисунке ниже. Прием ИК сигналов осуществлялся микросхемой TSOP2138, рассчитанной на частоту 38 кГц.
Описание протокола Nec
Для передачи данных используется несущая частота 38 кГц. Посылка состоит из стартового импульса и четырех байтов данных – адрес, инвертированное значение адреса, команда, инвертированное значение команды. Адрес и команда передаются дважды для повышения надежности.
Данные передаются младшим битом вперед. Каждый бит начинается с пачки импульсов несущей частоты. Длительность пачки равна 560 мкс. Путем изменением временного интервала между пачками импульсов осуществляется кодирование нулей и единиц. При передаче логической единицы интервал от начала текущей до начала следующей пачки импульсов составляет 2.25 мс, а при передаче логического нуля – 1.12 мс.
Стартовый импульс представляет собой пачку импульсов несущей частоты длительностью 9 мс. После подачи стартового импульса следует пауза в 4.5 мс.
Посылка, содержащая адрес и команду передается однократно, но при удержании кнопки пульта дистанционного управления через каждые 110 мс излучается код повтора – пачка импульсов несущей частоты длительностью 9мс, пауза в 2.25 мс и пачка импульсов длительностью 560 мкс.
Существует расширенная версия протокола Nec, в которой используется 16-ти разрядные адреса.
Алгоритм декодирования сигналов ПДУ
Для декодирования ИК сигналов, соответствующих протоколу Nec, требуется измерять время между передаваемыми пачками импульсов. Для этого можно использовать схему захвата, внешнее прерывание или банальный опрос вывода микроконтроллера. Я остановил свой выбор на схеме захвата.
Рассмотрим алгоритм работы программы, декодирующей сигналы ПДУ. (Код не привожу, чтобы не загромождать описание.)
В начале функции main() выполняется инициализация таймера Т1 - разрешается прерывание по событию «захват», устанавливается работа схемы захвата по спадающему фронту (потому что сигнал на выходе ИК приемника инвертированный), нормальный режим работы таймера и коэффициент предделителя, позволяющий вычислять длительность между пачками импульсов без многократных переполнений счетного регистра. Инициализируется lcd дисплей, используемый для отладки. Устанавливается флаг глобального разрешения/запрещения прерываний.
Далее микроконтроллер «попадает» в бесконечный цикл while, в котором вызываются функции обработки и вывода данных – TIM_Handle() и TIM_Display() соответственно.
Параллельно выполнению основного кода, работает таймер Т1 и его схема захвата. Как только логический уровень вывода ICP(PD6) меняется с «1» на «0» выполнение основного кода прекращается и вызывается обработчик прерывания. В обработчике микроконтроллер сохраняет текущее и предыдущее значение счетчика таймера и устанавливается флаг, сигнализирующий функции TIM_Handle() о захвате нового значения.
Функция TIM_Handle() представляет собой конечный автомат с двумя состояниями – IDLE(ожидание) и RESEIVE(прием). В начале функции проверяется установка программного флага и в случае выполнения условия, вычисляется длительность временного интервала и выполняется код автомата.
Начальное состояние автомата –IDLE. В этом состоянии автомат ожидает появление стартового импульса или кода повтора. Если длительность временного интервала меньше максимально возможного значения, но больше порогового - принят стартовый импульс. Автомат инициализирует переменные и меняет свое состояние на RESEIVE. Если значение меньше порогового – принят импульс повтора, выполняется инкремент счетчика повторов (пятый байт массива buf[]).
Переменные, используемые автоматом:
currentState – текущее состояние автомата,
period – длительность временного интервала,
data – приемный «сдвиговый регистр»,
countBit – счетчик принятых битов,
countByte – счетчик принятых байтов,
buf[] – пяти байтовый массив данных - адрес, инвертированное значение адреса, команда, инвертированное значение команды, количество повторов
В состоянии RESEIVE автомат сравнивает длительность временного интервала с максимально возможным значением и с пороговым. Если интервал больше порогового значения – значит, принята единица, если меньше – то ноль.
В случае приема единицы в переменной data устанавливается 7 разряд. Далее проверяются счетчики битов и байт, с помощью которых контролируется «укладка» битовой последовательности в массив.
Как только будет принято четыре байта, автомат установит флаг RESEIVE_OK и сменит состояние на IDLE.
Флаг RESEIVE_OK сигнализирует функции TIM_Display() о выводе принятых данных на символьный lcd. В данном проекте дисплей использовался исключительно в целях отладки.
Comments
спасибо за статью
Что случилось?
бывает кто?
столкнулся с такой бедой:
тсоп 4838 при подключении по схеме как у вас в холостую выдает 1,28в, а при подаче импульса с пульта показывает 1,08в. В чем беда? что я мог сделать не так? при чем у меня где то 5 разных тсопов и все ведут себя одинаково(((
как такое может быт?
вроде все так как надо)))
ну по логике, в холостом состоянии должно быть 5в а не 1,5в?
Мультиметр видит сигнал, если он присутствует около 0,5сек. А длительности команд пульста - несколько милисекунд! Конечно мультиметр не увидит!
вопрос: какие Fuse mega8535, т.к. программатор USBASP AVRDUDE из хекса брать не умеет.
Code:
//порт, к которому подключена шина данных
#define PORT_DATA PORTC
#define PIN_DATA PINC
#define DDRX_DATA DDRC
//порт, к которому подключены управляющие выводы
#define PORT_SIG PORTC
#define PIN_SIG PINC
#define DDRX_SIG DDRC
//управляющие выводы
#define RS 0
#define RW 1
#define EN 2
Микроконтроллер работает от внешнего кварца на 16 МГц. Нужно выставить фуз биты соответствующие этому режиму..
Схему я скоро поправлю.
или графический 64х128
Code:
//прерывание по событию захват
interrupt [TIM1_CAPT] void Timer1Capt(void)
{
#asm("cli")
if (flag==1) {
interval = TCNT1;
}
else
{
interval=0;
TCNT1=0;
flag=1;
#asm("sei")
LED=OFF;
return;
}
switch (state) {
//ждем стартовый импульс
case WAIT:
TCNT1=0;
if (interval < START_BIT_INT) {
if (interval > REPEAT_BIT_INT){
data = 0;
countBit = 0;
countByte = 0;
buffer[NUM_REPEAT] = 0;
state = RECEIVE;
LED=ON;
}
else {
LED=ON;
buffer[NUM_REPEAT]++;
flag=0;
delay_ms(10);
LED=OFF;
}
}
interval=0;
break;
//прием посылки
case RECEIVE:
TCNT1=0;
if (interval < HIGH_BIT_INT){
if (interval > LOW_BIT_INT){
SetBit(data, countBit);
}
countBit++;
if (countBit == 8){
buffer[countByte] = data;
countBit = 0;
data = 0;
countByte++;
if (countByte == (MAX_SIZE - 1)){
flag=0;
state = WAIT;
LED=OFF;
break;
}
}
}
else
{
flag=0;
state=WAIT;
LED=OFF;
}
interval=0;
break;
default:
break;
}
#asm("sei")
}
Code:
icr1 = icr2;
icr2 = ((unsigned int)ICR1H<<8)|ICR1L;
чтобы было вот так
Code:
icr1 = icr2;
icr2 = ICR1L;
icr2 |= ((unsigned int)ICR1H<<8);
Попробуй, должно заработать.
PS. первый раз неправильно написал. не выспался.
Code:
#include <mega32.h>
#include <alcd.h>
#include <delay.h>
#include <stdio.h>
#include <bits_macros.h>
#include <alcd.h>
#define LED PORTD.4
#define START_BIT_INT 3500
#define REPEAT_BIT_INT 3000
#define LOW_BIT_INT 300
#define HIGH_BIT_INT 600
#define RECEIVE 1
#define WAIT 0
#define ON 1
#define OFF 0
#define NUM_REPEAT 4
#define MAX_SIZE 5
unsigned char state;
unsigned char flag = 0;
unsigned char buffer[MAX_SIZE];
unsigned char lcd_buffer[20];
static unsigned int interval;
static unsigned char data;
static unsigned char countBit, countByte;
Code:
void main(void)
{
PORTA=0xFF;
DDRA=0x00;
PORTB=0xFF;
DDRB=0x00;
PORTC=0xFF;
DDRC=0x00;
PORTD=0xCF;
DDRD=0x30;
TCCR0=0x00;
TCNT0=0x00;
OCR0=0x00;
TCCR1A=0x00;
TCCR1B=0x03;
TCNT1H=0x00;
TCNT1L=0x00;
ICR1H=0x00;
ICR1L=0x00;
OCR1AH=0x00;
OCR1AL=0x00;
OCR1BH=0x00;
OCR1BL=0x00;
ASSR=0x00;
TCCR2=0x00;
TCNT2=0x00;
OCR2=0x00;
MCUCR=0x00;
MCUCSR=0x00;
TIMSK=0x20;
UCSRB=0x00;
ACSR=0x80;
SFIOR=0x00;
ADCSRA=0x00;
SPCR=0x00;
TWCR=0x00;
// Alphanumeric LCD initialization
// Connections are specified in the
// Project|Configure|C Compiler|Libraries|Alphanumeric LCD menu:
// RS - PORTA Bit 4
// RD - PORTA Bit 5
// EN - PORTA Bit 6
// D4 - PORTB Bit 4
// D5 - PORTB Bit 5
// D6 - PORTB Bit 6
// D7 - PORTB Bit 7
// Characters/line: 20
lcd_init(20);
lcd_puts("READY");
#asm("sei")
while (1)
{
// Place your code here
if ((flag==0)&&(state==WAIT)) {ShowResult();}
}
}
Code:
void ShowResult()
{
lcd_gotoxy(0,1);
sprintf(lcd_buffer,"%u %u %u %u \n",buffer[0],buffer[1],buffer[2],buffer[3]);
lcd_puts(lcd_buffer);
sprintf(lcd_buffer,"%u ",buffer[4]);
lcd_puts(lcd_buffer);
}
Вот и весь кодю ;-) Да, у меня атмега32, для других надо соответсвенно менять инициализацию оборудования в майн функции.
Библиотека RC5 использована - kibermaster.net/priem-komand-s-pulta-distantsionnogo-upravleniya/.После инициализации таймера и разрешения прерываний включается захват, и микроконтроллер начинает обрабатывать данные, принятые с пульта. Если получен пакет данных, то активируется флаг rc5.dataOK, а принятая команда записывается с переменную структуры rc5.command.
Сам код который я юзаю являет собой вкл/отключение четырех каналов клавишами 1-4 пульта
Сам код здесь radiomanoff.at.ua/index/distancionnoe_upravlenie_na_atmega8_protokol_rc_5/0-27
Все бы хорошо,но вот если нажатую кнопку пульта "передержать" соответствующий канал включается/выкл ючается.А хотелось чтобы канал включился,и контролер ждал отпускания кнопки пульта,а уже потом обрабатывал следующее нажатие.
Пробовал вставлять
Code:
...
_delay_ms(700); //задержка 700 мс
rc5.dataOK = 0; // сбрасываем флаг
while (rc5.dataOK) //ждем отпускания кнопки
{
_delay_ms(200); //задержка 70 мс
rc5.dataOK = 0; // сбрасываем флаг
кнопки
Как бы проверка отпускания кнопки.Тестирую на реальном "железе"не идет
Code:
_delay_ms(200); //задержка 700 мс
rc5.dataOK = 0; // сбрасываем флаг
_delay_ms(20);
while (rc5.dataOK) //ждем отпускания кнопки
{
//_delay_ms(20); //задержка 70 мс
rc5.dataOK = 0; // сбрасываем флаг
_delay_ms(200);
}
Я так подумал : каналы у меня включает четко,никаких нареканий нет.Но если кнопку пульта держать постоянно состояние канала меняется вкл/выкл.Поэтом у я вогнал в цикл while проверку флага rc5.dataOK с постоянным его збросом rc5.dataOK = 0;- и таким образом жду окончания передачи с пульта.Ведь канал который мне нужен уже включен.На реальном железе получилось очень даже ничего,впринцип е то чего я и добивался.
Я понимаю что скорей всего это не есть правильно,ведь во время когда кнопка нажата ,другие действия с МК я производить несмогу.Но меня устраивает.
Почему пишу под этой статьей?Вы знаете что"прогуглив" сеть,только здесь TSOP подвешен на ногу ICP микроконтролера .Правда я использовал Atmega8 c заводскими фьюзами,но код с пультом RC5 работает, а это я думаю главное.
Вот небольшой "опус" по этому поводу,обработк а RC5& матричная клавиатураradiomanoff.at.ua/index/distancionnoe_upravlenie_na_atmega8_protokol_rc_5_matrichnaja_klaviatura/0-35.Может кому пригодится.
дело в том что необходимо чтобы при длительном нажатии производилось какое то действие(как например при регулировании громкости)
Почему при работе со стартовым импульсом, мы оперируем с числами 15000 и 12000? (START_IMP_MAX и START_IMP_TH)
Ведь первые 9мс состоят из импульсов частотой 38кГц, т.е. на этом промежутке между двумя прерываниями period будет равен 6, и по сути стартовый импульс определяется не периодом в 13,5 мс (откуда и взяты, как я понимаю, эти значения), а паузой в 4,5 мс. Т.е. START_IMP_MAX и START_IMP_TH должны быть что-то около 5000*FCPU/PRE и 4000*FCPU/PRE соответственно. Где я ошибся?
RSS feed for comments to this post