Продолжаем знакомиться с различными протоколами передачи данных по ИК связи, на этот раз разберем протокол NEC, который в настоящее время стал очень популярным среди радиолюбителей. Информация в этом стандарте кодируется паузой разной длины между импульсами, если длительность паузы меньше это лог. ноль, если больше - лог. единица. Длина периода лог. нуля составляет 1,12 мс, а длина лог. единицы 2,25 мс, длина паузы лог. нуля составляет 560 мкс, а длина паузы лог. единицы 1,675 мс.

Команды передаются пакетами. Каждый пакет начинается со стартовой последовательности - импульса длиной 9 мс и паузы длиной 4,5 мс. В каждой посылке передается 8 бит адреса и 8 бит команды, причем адрес и команда передаются дважды - в прямом и инверсном виде (это кроме проверки валидности передачи, делает одинаковой общую длительность любой посылки). Каждый байт пакета передаётся младшим битом вперёд.

При непрерывном нажатии на кнопку, только первая посылка передается как показано на рисунке выше. Все остальные передаются в виде преамбулы с укороченной паузой и завершающим импульсом, с периодом 110мс:


Кодирование бит - длительностью паузы ( 1.6мс или 0.56мс) между импульсами фиксированной длительности (0.56мс):

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

Декодирование сигналов протокола NEC

Мы же разберем пример обработки ИК сигнала с помощью внешнего прерывания и прерывания по таймеру. Микроконтроллер Atmega8 тактируется от внутреннего RC-генератора частотой 1МГц. Демодулированный сигнал поступает на вход INT0. Внешнее прерывание будет происходить по заднему фронту импульса. Таймер/счетчик 1 настроен на прерывание по совпадению, в этом прерывании каждые 560мкс инкрементируется счетчик интервалов signal_time, который сбрасывается в обработчике внешнего прерывания. Если счетчик интервалов успел насчитать больше 9 раз это указывает на начало приема посылки. Если счетчик не успеет обновится, то записанный бит будет равен нулю, если он насчитает больше 4 раз - это значение можно считать лог. единицей. Как только в массив будут записаны все 33 значения, установится флаг окончания приема посылки ir_ok и можно приступать к декодированию принятых команд.

Декодирование происходит по принципу разделения принятого массива данных на 4 байта - байт адреса, байт инвертированного значения адреса, байт команды и байт инвертированного значения команды. На дисплей выводим данные уже в шестнадцатиричном виде, путем преобразования их со значениями из массива HEX.

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

// Декодирование сигналов протокола NEC

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <util/delay.h>
#include "hd44780.h"

const unsigned char HEX[16] PROGMEM="0123456789ABCDEF";

unsigned char signal_time;
static unsigned char ir_ok,ir_decode_ok;
unsigned char ir_code[4]; // Массив для хранения значений адресов и команд
unsigned char ir_signal[33]; // Массив для хранения значений интервалов

// Прерывание по совпадению T1(каждые 560мкс)
ISR(TIMER1_COMPA_vect)
{
signal_time++; // Счетчик интервалов 
}

// Внешнее прерывание по INT0
ISR(INT0_vect)
{
static unsigned char i;
// Определяем начало приема посылки
if(signal_time > 9) // (4,5ms+562us)/560 = 9 
i = 0; // Выбираем первый интервал

ir_signal[i] = signal_time; // Записываем в буфер значения интервалов 
signal_time = 0; // Обнуляем счетчик интервалов
i++; // Следующий интервал 

if(i == 33) // Если все интервалы приняты
ir_ok = 1; // Устанавливаем флаг окончания приема сигнала 
}

// Функция декодирования сигнала
void ir_decode(void)
{ 
unsigned char k = 0;
unsigned char signal_length,value = 0;

for(unsigned char i = 0; i < 4; i++) // Обработка байтов адреса или команды
  {	
  for(unsigned char j = 0; j < 8; j++) // Обработка 8-ми битов адреса или команды
    {
	k++;
	value = value >> 1; // Сдвигаем биты вправо
    signal_length = ir_signal[k]; // Выбираем следущее значение интервала
    if(signal_length > 4) // Если интервал больше (1,675ms+562us)/560 = 4
	  value = value | 0x80; // Добавляем к старшему разряду единицу
    }
  ir_code[i] = value; // Запоминаем в буфере байт адреса или команды
  value = 0; // Обнуляем значение адреса или команды    
  } 
// Производим явное приведение типов и проверяем принятые байты
if(((unsigned char)ir_code[0] == (unsigned char)~ir_code[1]) && 
   ((unsigned char)ir_code[2] == (unsigned char)~ir_code[3]))
  {
  ir_decode_ok = 1; // Устанавливаем флаг окончания декодирования сигнала
  ir_ok = 0; // Сбрасываем флаг окончания приема сигнала
  }
else
  {
  ir_decode_ok = 0; // Сбрасываем флаг окончания декодирования сигнала
  ir_ok = 0; // Сбрасываем флаг окончания приема сигнала
  }
}

int main(void)
{
MCUCR |= (1 << ISC01); // Внешнее прерывание по заднему фронту
GICR |= (1 << INT0); // Разрешение внешнего прерывния по INT0

TCCR1B |= (1 << WGM12)|(1 << CS10); // Режим CTC, предделитель 1 
OCR1A = 279; // 1000000/2*(279+1) = 1786Hz(560us)
TIMSK |= (1 << OCIE1A); // Разрешаем прерывание по совпадению Т1 

lcd_init(); // Инициализация ЖК дисплея
sei(); // Глобально разрешаем прерывания

lcd_gotoxy(0, 0);
lcd_string("NEC PROTOCOL    ",16);
lcd_gotoxy(0, 1);
lcd_string("DECODER         ",16);
_delay_ms(250);
_delay_ms(250);
_delay_ms(250);
_delay_ms(250);
lcd_clr(); // Очищаем дисплей
lcd_gotoxy(0, 0);
lcd_string("Adr: 00  Inv: 00",16);
lcd_gotoxy(0, 1);
lcd_string("Com: 00  Inv: 00",16);

while(1)
{
  if(ir_ok) ir_decode(); // Если сигнал принят, декодируем его
	if(ir_decode_ok) // Если обработка сигнала завершена, выводим данные на дисплей
	{
      lcd_gotoxy(5, 0);
      lcd_data(pgm_read_byte(HEX+ir_code[0]/16)); // Выводим данные в шестнадцатиричном виде
	  lcd_data(pgm_read_byte(HEX+ir_code[0]%16));
      lcd_gotoxy(14, 0);
	  lcd_data(pgm_read_byte(HEX+ir_code[1]/16));
	  lcd_data(pgm_read_byte(HEX+ir_code[1]%16));
      lcd_gotoxy(5, 1);	    
	  lcd_data(pgm_read_byte(HEX+ir_code[2]/16));
	  lcd_data(pgm_read_byte(HEX+ir_code[2]%16));
      lcd_gotoxy(14, 1);	    
	  lcd_data(pgm_read_byte(HEX+ir_code[3]/16));
	  lcd_data(pgm_read_byte(HEX+ir_code[3]%16));
      ir_decode_ok = 0; // Сбрасываем флаг окончания декодирования сигнала
	}
   }
}


Архив для статьи "Декодирование сигналов протокола NEC"
Описание: Проект AVRStudio4
Размер файла: 33.63 KB Количество загрузок: 1 013 Скачать

Комментарии  

0 #1 evild64 28.09.2017 18:16
биты ISC01 и INT0 вроде как относятся к регистрам EICRA и EIMSK, разве нет?
Сообщить модератору
0 #2 AntonChip 29.09.2017 08:37
Цитирую evild64:
биты ISC01 и INT0 вроде как относятся к регистрам EICRA и EIMSK, разве нет?
EICRA и EIMSK это названия регистров в МК таких как Atmega328 и т.п.
Сообщить модератору
0 #3 Алексейbird 10.03.2018 21:12
Проверял этот код на Меге8 - работает норм, а вот перенос кода на Мегу168 с ходу не получился, не декодирует сигналы. Затык, как мне кажется, в куске кода "Производим явное приведение типов и проверяем принятые байты" потому как первое условие не отрабатывается, т.е. я добавил в это условие свои флаги(включение светодиода) и срабатывает условие которое "иначе", а вот основное нет. К сожалению дисплея нет и понять, что там декодируется, нет возможности. Есть какие-нибудь мысли?
Сообщить модератору
+1 #4 Sergey F 20.10.2018 01:39
Отличная статья, только не хватает функции обработки кода повторения. Не могли бы вы добавить эту функцию, пожалуйста.
Сообщить модератору
0 #5 Redfern89 09.12.2018 14:16
Народ, а как пересчитать этот таймер под частоту 16mhz? :-) :-) :-)
Сообщить модератору
+1 #6 AntonChip 15.12.2018 21:40
Цитирую Redfern89:
Народ, а как пересчитать этот таймер под частоту 16mhz? :-) :-) :-)

Если настроить предделитель таймера на 8, то OCR1A будет равен 559,
16000000/2*8*(1+559) = 1786 Hz
Сообщить модератору
0 #7 Zanis Dukalskis 04.10.2019 11:39
Мне попался пульт в котором адрес 16 битный и его ошибочно проверять. Для универсальности можно поставить условие ИЛИ Код:if(((unsigned char)ir_code[0] == (unsigned char)~ir_code[1]) ||
((unsigned char)ir_code[2] == (unsigned char)~ir_code[3]))
Сообщить модератору