Дистанционное управление от ИК-пульта
Пока работа над проектом Управление любым ИК-пультом не закончена, я решил выделить некоторые решения в отдельные заметки (под проекты), тем более, что часть из них уже не будет включена в проект.
Сегодня я расскажу об алгоритме обработки ИК-посылок. (Ранее я уже описывал один из возможных вариантов декодирования ИК-посылок Работаем с ИК пультом).
Аппаратная часть
ИК-приемник я буду использовать самый дешевый и способный работать при низком напряжении питании (от 2,5 до 5,5 В) TSOP31236 (26 рублей):
Использовать можно на любую из отладочных плат STM8/STM32 серии Discovery.
Протоколы ИК-пультов
Форматов посылок (протоколов) формируемых ИК-пультами различной бытовой аппаратуры очень много.
Основные отличия протоколов:
- различия в кодировании “0” и “1”
- различные длины посылок
- различия в командах автоповтора
Например вот такие сигналы генерирует один из моих пультов (как записать сигналов читаем тут Запись сигналов IR-пульта на звуковую карту):
Больше всего формат посылок похож на Extended NEC protocol:
Я составил таблицу для сравнения протоколов (конечно она не полная, здесь только, что я нашел):
Как видим написать программу способную декодировать все протоколы довольно сложно, слишком уж большая разница между ними.
Особенно, если ещё учесть, что и количество длительностей импульсов (как для кодирования импульсов синхронизации, так и кодировании “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) (хэш-код).
Алгоритм обработки (декодирования) посылок с ИК-приемника
Для декодирования посылок ИК-приемника используется один таймер.
Таймер должен иметь канал захвата с внешней линии ввода-вывода.
Настройка таймера:
Диаграмма сигналов для пояснения работы:
Примечание к рисунку:
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;
}
}
Код целиком:
Прием посылок и формирование событий.
На мой взгляд обработка принятых посылок занимает значительно больше времени, чем сам прием (декодирование), что может повлиять на прием посылок (пропуск, ошибки и п.р.).
В связи с чем, код обработки «вынесен» из декодера посылок, в другой обработчик, вызов которого происходит через каждые 10 мс.
Попробую объяснить как это работает (временной масштаб не соблюден):
Самый простой вариант, когда обрабатывается только одна посылка.
После приема устанавливается флаг окончания приема посылки (ir_decoder.is_received).
В обработчике посылок проверяется (и сбрасывается) флаг окончания приема посылки, если он установлен считывается код посылки. Считанный код посылки проверяется с «известными», в случае совпадения генерируется (обрабатывается) событие «кнопка нажата».
По прошествии 130 мс (данное время отсчитывается в самом обработчике) генерируется (обрабатывается) событие «кнопка отжата».
Вариант по сложнее, когда обрабатывается две разных посылки.
После приема первой устанавливается флаг окончания приема посылки «А».
В обработчике посылок проверяется флаг и считывается код посылки, если он поддерживается, то генерируется (обрабатывается) событие «кнопка нажата» «А».
Вторая посылка приходит до завершения периода в 130 мс, благодаря «независимой» обработке она корректно принимается и корректно отрабатываются все события для посылки «А», а именно: событие «кнопка нажата» «Б» и событие «кнопка отжата» «А».
По прошествии 130 мс (данное время отсчитывается в самом обработчике) генерируется (обрабатывается) событие «кнопка отжата» «Б».
Код самого обработчика не привожу.
На этом пока все.
Продолжение следует…
comments powered by Disqus