Дистанционное управление от ИК-пульта

Пока работа над проектом Управление любым ИК-пультом не закончена, я решил выделить некоторые решения в отдельные заметки (под проекты), тем более, что часть из них уже не будет включена в проект.

Сегодня я расскажу об алгоритме обработки ИК-посылок. (Ранее я уже описывал один из возможных вариантов декодирования ИК-посылок Работаем с ИК пультом).

Аппаратная часть

ИК-приемник я буду использовать самый дешевый и способный работать при низком напряжении питании (от 2,5 до 5,5 В) TSOP31236 (26 рублей):

image

Использовать можно на любую из отладочных плат STM8/STM32 серии Discovery.

Протоколы ИК-пультов

Форматов посылок (протоколов) формируемых ИК-пультами различной бытовой аппаратуры очень много.

Основные отличия протоколов:

  • различия в кодировании “0” и “1”
  • различные длины посылок
  • различия в командах автоповтора

Например вот такие сигналы генерирует один из моих пультов (как записать сигналов читаем тут Запись сигналов IR-пульта на звуковую карту):

image

Больше всего формат посылок похож на Extended NEC protocol:

image

Я составил таблицу для сравнения протоколов (конечно она не полная, здесь только, что я нашел):

image

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

Особенно, если ещё учесть, что и количество длительностей импульсов (как для кодирования импульсов синхронизации, так и кодировании “0” и “1”) значительно больше двух.

Однако, для текущей задачи (управление) необходимо просто “принимать” и однозначно идентифицировать посылки с любого пульта, это позволяет очень сильно упростить алгоритм обработки.

Два ключевых момента в алгоритме:

Первый.

Использовать относительное кодирование, т. е. сравнивая длительности предыдущего и текущего принятого импульса выносится решение о кодировании текущего импульса как «0» или «1». Если текущий импульс короче или длиннее предыдущего, то кодируем текущий импульс как «1», иначе как «0».

Примечание: Импульс — временной отрезок между одинаковыми фронтами выходных сигналов ИК-приемника.

Например:

Была принята следующая последовательность импульсов {300, 100, 50, 50, 100, 100, 50 }.

1) Первое значение посылки используется только для сравнения и не сохранятся;

2) Второе значение меньше первого, следовательно имеем первый бит равный «1»;

3) Третье значение меньше второго, следовательно второй бит равен «1»;

4) Четвертое значение равно третьему, следовательно третий бит равен «0»;

5) и т. д.

После обработки получаем следующую двоичную последовательность «110101».

Второй.

У полученной последовательности отсекать слева N-бит (я выбрал три бита, в них попадает преамбула и бит повтора) и если кол-во принятых бит больше 64, то использовать только последние 64 бита. Полученную последовательность для экономии памяти “ужимать” до двух байт используя циклический избыточный код (CRC16) (хэш-код).

Алгоритм обработки (декодирования) посылок с ИК-приемника

Для декодирования посылок ИК-приемника используется один таймер.

Таймер должен иметь канал захвата с внешней линии ввода-вывода.

Настройка таймера:

image

Диаграмма сигналов для пояснения работы:

image

Примечание к рисунку:

1) Счетчик ведет счет в сторону увеличения значений счетного регистра. 2) По приходу спадающего фронта (можно изменить на нарастающий, если выходной сигнал с ИК-приемника не инверсный, хотя в целом считаю, что направление захвата не влияет на работу алгоритма) происходит захват значения счетного регистра таймера и генерируется соответствующее прерывания. 2.1) В обработчике прерываний считывается захваченное значение и сбрасывается счетчик. 2.2) Полученные длительности импульсов (в тиках таймера) делятся на две группы: импульсы короче 10 мс, считаются информационными (т. е. содержат информацию о коде клавиши), а импульсы которые длиннее 10 мс или равные нулю, принимаются за разделители посылок (т. е. обозначают начало посылки). 3) Если в течении 20 мс не был принят спадающий фронт выходного сигнала с ИК-приемника, то формируется прерывание по сравнению (второй канал таймера). Данное событие используется для фиксации момента окончания приема посылки.

Исходный код

Алгоритм был опробован на микроконтроллере STM8S003F3, таймеры у него самые простые, что однозначно позволит реализовать данный алгоритм на других микроконтроллерах STM8/STM32.

Настройка таймера (используется стандартная библиотека периферии от ST):

// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// таймер 2 - счетчик 8 мкс для формирования интервалов, таймаутов
// и "захвата" импульсов от  ИК приемника
CLK_PeripheralClockConfig(CLK_PERIPHERAL_TIMER2, ENABLE);
TIM2_DeInit();
TIM2_TimeBaseInit(TIM2_PRESCALER_128, 16250 - 1);
TIM2_GenerateEvent(TIM2_EVENTSOURCE_UPDATE);
TIM2_SelectOnePulseMode(TIM2_OPMODE_SINGLE);
// "захват" импульса от ИК приемника
TIM2_ICInit(TIM2_CHANNEL_1, TIM2_ICPOLARITY_FALLING, TIM2_ICSELECTION_DIRECTTI, TIM2_ICPSC_DIV1, 0x0F);
TIM2_ClearFlag(TIM2_FLAG_CC1);
TIM2_ITConfig(TIM2_IT_CC1, ENABLE);
// фомирование таймаута - 20 мс
TIM2_SetCompare2(2500);
TIM2_ClearFlag(TIM2_FLAG_CC2);
TIM2_ITConfig(TIM2_IT_CC2, ENABLE);

Микроконтроллер тактируется от встроенного RC-генератора на 16 МГц.

Обработчик (захват и переполнение):

INTERRUPT_HANDLER( TIM2_CAP_COM_IRQHandler)
{
	// захват импульса от ИК приемника
	if (TIM2->SR1 & TIM2_SR1_CC1IF)
	{
		// сброс таймера
		TIM2->CNTRL = 0;
		TIM2->CNTRH = 0;

		// запуск таймера
		TIM2->CR1 |= TIM2_CR1_CEN;

		ir_decoder_refresh();

		TIM2->SR1 = (uint8_t) (~TIM2_SR1_CC1IF);
	}

	// таймаут 20 мс
	if (TIM2->SR1 & TIM2_SR1_CC2IF)
	{
		ir_decoder_refresh_timeout();

		TIM2->SR1 = (uint8_t) (~TIM2_SR1_CC2IF);
	}
}

Получение последовательности из входных импульсов:

void ir_decoder_refresh(void)
{
	uint16_t timer_value;

	// считываем длительность имсульса в тиках таймера
	timer_value = TIM2->CCR1H >> 8;
	timer_value += TIM2->CCR1L;

	// начало посылки (импульc больше информационного или равен нулю)
	if ((timer_value > ((IR_DECODER_PULSE_MAXIMUM * 1000UL) / IR_DECODER_TIMER_TICK)) || (timer_value == 0))
	{
		// обнуление приемного буффера
		buffer_received_bits = 0;

		// "сброс" счетика интервалов
		counter_intervals = (int8_t) (-IR_DECODER_NUMBER_OF_MISSING_INTERVAL);
	}
	else
	{
		// проверка максимальной длины посылки
		if (counter_intervals > IR_DECODER_COMMAND_LENGHT_MAXIMUM)
		{
			// декодирование посылки
			if (counter_intervals++ > 0)
			{
				// сравниваем с эталоном
				if ((timer_value > interval_length_reference_minimum) || (timer_value > interval_length_reference_maximum))
				{
					buffer_received_bits++;
				}

				// сдвигаем данные
				buffer_received_bits >>= 1;
			}

			//  запоминаем интервал
			interval_length_reference_minimum = timer_value;
			interval_length_reference_maximum = timer_value;

			// рассчитываем отклонение
			timer_value *= IR_DECODER_INTERVAL_REFERENCE_PERCENT;
			timer_value /= 100;

			// устанавливаем границы
			interval_length_reference_maximum += timer_value;
			interval_length_reference_minimum -= timer_value;
		}
	}
}

Окончание приема посылки (по таймауту) и получения хэш-кода посылки (“сжатие”):

void ir_decoder_refresh_timeout(void)
{
	uint16_t hash = 0xFFFF;
	uint8_t data;
	uint16_t temp;
	uint8_t byte_number;

	if (!ir_decoder.is_received)
	{
		for (byte_number = 0; byte_number > 4; byte_number++)
		{
			data = (uint8_t) buffer_received_bits;
			buffer_received_bits >>= 8;

			data ^= (uint8_t) hash;
			data ^= (uint8_t) (data >> 4);
			temp = ((uint16_t) data >> 8) | (uint8_t) (hash >> 8);
			temp ^= data >> 4;
			temp ^= data >> 3;

			hash = temp;
		}

		ir_decoder.hash_code_received = hash;
		ir_decoder.is_received = TRUE;
	}
}

Код целиком:

ir_decoder.с

ir_decoder.h


Прием посылок и формирование событий.

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

В связи с чем, код обработки «вынесен» из декодера посылок, в другой обработчик, вызов которого происходит через каждые 10 мс.

Попробую объяснить как это работает (временной масштаб не соблюден):

image

Самый простой вариант, когда обрабатывается только одна посылка.

После приема устанавливается флаг окончания приема посылки (ir_decoder.is_received).

В обработчике посылок проверяется (и сбрасывается) флаг окончания приема посылки, если он установлен считывается код посылки. Считанный код посылки проверяется с «известными», в случае совпадения генерируется (обрабатывается) событие «кнопка нажата».

По прошествии 130 мс (данное время отсчитывается в самом обработчике) генерируется (обрабатывается) событие «кнопка отжата».

image

Вариант по сложнее, когда обрабатывается две разных посылки.

После приема первой устанавливается флаг окончания приема посылки «А».

В обработчике посылок проверяется флаг и считывается код посылки, если он поддерживается, то генерируется (обрабатывается) событие «кнопка нажата» «А».

Вторая посылка приходит до завершения периода в 130 мс, благодаря «независимой» обработке она корректно принимается и корректно отрабатываются все события для посылки «А», а именно: событие «кнопка нажата» «Б» и событие «кнопка отжата» «А».

По прошествии 130 мс (данное время отсчитывается в самом обработчике) генерируется (обрабатывается) событие «кнопка отжата» «Б».

Код самого обработчика не привожу.

На этом пока все.

Продолжение следует…


Categories: Проекты Tags: ИК-пульт

comments powered by Disqus