Почему декодер ИК-пульта NEC не работает

Мне нужно распознавать сигнал от ИК-пульта, а не использовать библиотеку для работы с ИК-пультами/приемниками. Как можно при нажатии на кнопку отображать ее имя. У меня есть микроконтроллер PIC12F615 с этим кодом: code и к нему подключено 5 кнопок и мне нужно распознать какая кнопка нажата. Почему декодер ИК-пульта NEC не работает, он не устанавливает nec_ok в true. Если вывести nec_state на терминал, я получаю 0 1 0 1 0 1 и так далее.

    boolean nec_ok = 0;
byte i, nec_state = 0, command, inv_command;
unsigned int address;
unsigned long nec_code;

void setup() {
Serial.begin(9600);
// Конфигурация модуля Timer1
TCCR1A = 0;
TCCR1B = 0; // Отключить модуль Timer1
TCNT1 = 0; // Установить значение предварительной загрузки Timer1 в 0 (сброс)
TIMSK1 = 1; // включить прерывание по переполнению Timer1
attachInterrupt(digitalPinToInterrupt(2), remote_read, CHANGE); // Включить внешнее прерывание (INT0)
}

void remote_read() {
unsigned int timer_value;
if(nec_state != 0){
timer_value = TCNT1; // Сохраняем значение Timer1
TCNT1 = 0; // Сбросить Таймер1
}
switch(nec_state){
case 0 : // начинаем получать ИК-данные (мы находимся в начале импульса 9 мс)
TCNT1 = 0; // Сбросить Таймер1
TCCR1B = 2; // Включить модуль Timer1 с предделителем 1/8 (2 тика каждые 1 мкс)
nec_state = 1; // Следующее состояние: конец импульса 9 мс (начало интервала 4,5 мс)
i = 0;
return;
case 1 : // Конец импульса длительностью 9 мс
if((timer_value > 19000) || (timer_value < 17000)){ // Недопустимый интервал ==> остановить декодирование и сбросить
nec_state = 0; // Сброс процесса декодирования
TCCR1B = 0; // Отключить модуль Timer1
}
else
nec_state = 2; // Следующее состояние: конец интервала 4,5 мс (начало импульса 562 мкс)
return;
case 2 : // Конец интервала 4,5 мс
if((timer_value > 10000) || (timer_value < 8000)){
nec_state = 0; // Сброс процесса декодирования
TCCR1B = 0; // Отключить модуль Timer1
}
else
nec_state = 3; // Следующее состояние: конец импульса 562 мкс (начало интервала 562 мкс или 1687 мкс)
return;
case 3 : // Конец импульса 562 мкс
if((timer_value > 1400) || (timer_value < 800)){ // Неверный интервал ==> остановить декодирование и сбросить
TCCR1B = 0; // Отключить модуль Timer1
nec_state = 0; // Сброс процесса декодирования
}
else
nec_state = 4; // Следующее состояние: конец интервала 562 мкс или 1687 мкс
return;
case 4 : // Конец интервала 562 мкс или 1687 мкс
if((timer_value > 3600) || (timer_value < 800)){ // Неверный временной интервал ==> остановить декодирование
TCCR1B = 0; // Отключить модуль Timer1
nec_state = 0; // Сброс процесса декодирования
return;
}
if( timer_value > 2000) // Если ширина пробела > 1 мс (короткий пробел)
bitSet(nec_code, (31 - i)); // Записать 1 в бит (31 - i)
else // Если ширина пробела < 1 мс (длинный пробел)
bitClear(nec_code, (31 - i)); // Записать 0 в бит (31 - i)
i++;
if(i > 31){ // Если все биты получены
nec_ok = 1; // Процесс декодирования ОК
detachInterrupt(0); // Запретить внешнее прерывание (INT0)
return;
}
nec_state = 3; // Следующее состояние: конец импульса 562 мкс (начало интервала 562 мкс или 1687 мкс)
}
}

ISR(TIMER1_OVF_vect) { // Подпрограмма обслуживания прерывания Timer1 (ISR)
nec_state = 0; // Сброс процесса декодирования
TCCR1B = 0; // Отключить модуль Timer1
}

void loop() {
Serial.println(nec_ok);
if(nec_ok){ // Если микроконтроллер получает сообщение NEC с успешным
nec_ok = 0; // Сброс процесса декодирования
nec_state = 0;
TCCR1B = 0; // Отключить модуль Timer1
address = nec_code >> 16;
command = nec_code >> 8;
inv_command = nec_code;


attachInterrupt(digitalPinToInterrupt(2), remote_read, CHANGE); // Включить внешнее прерывание (INT0)
}
}

Схема:

, 👍0

Обсуждение

Есть некоторые проблемы с кодированием, которые нужно решить, прежде чем двигаться дальше. Переменные, установленные в ISR, но используемые в loop(), должны быть изменчивыми, например, nec_ok. Также убедитесь, что в среде IDE включены предупреждения компилятора, и при необходимости выполните очистку. Вероятно, есть и другие проблемы., @6v6gt

изменены переменные на volatile, если отображается предупреждение: предупреждение: 'timer_value' может использоваться неинициализированным в этой функции, примечание: здесь было объявлено 'timer_value' беззнаковое целое timer_value;, @Good York

unsigned int timer_value; сделайте это статическим, так как static unsigned int timer_value = 0; . Как статический, он инициализируется только один раз и сохраняет свое значение между вызовами вызывающей процедуры (что, вероятно, вам здесь и нужно)., @6v6gt

ничего не изменилось, та же проблема, @Good York

Я думаю, что у вас есть по крайней мере одна основная проблема, заключающаяся в том, что timer1 (16 бит) с предделителем /8 имеет период ~ 32 мс. Типичный поток данных NEC в два раза больше. В векторе переполнения timer1 вы все сбрасываете. В любом случае, вместо того, чтобы манипулировать Timer1, вы можете просто использовать micros(). При входе в подпрограмму рассчитайте длину последнего импульса и сохраните время (в микросекундах), которое вы ввели в подпрограмму, для следующего ввода. Что-то вроде pulseLengthLastUs = micros() - enterAtUs; enterAtUs = микро() ;, @6v6gt

Чтобы узнать, как далеко вы продвинулись, создайте глобальную переменную ошибки, скажем, volatile uint32_t errFlag Установите ее, когда значение выходит за пределы диапазона, скажем, errFlag = 2000000 + timer_value, где 2000000 представляет случай 2. Остановите программу как как только появится ошибка, выполнив что-то вроде этого в цикле(): if (errFlag > 0) { Serial.println(errFlag) ; пока (правда) ; }, @6v6gt

Я перешел на микро, но все еще не работает [код](https://pastecode.io/s/fm2ak7xc), @Good York

Это выглядит намного проще, но (а) помните переменные volatile volatile boolean nec_ok = 0; (б) настраивайте время для нас, а не для удвоенных тиков таймера (в) добавьте предлагаемые отладочные материалы, чтобы вы знали, где они впервые терпят неудачу., @6v6gt

Если выводить nec_state на терминал, я получаю 0 1 0 1 0 1 и так далее, если выводить pulseLengthUs на терминал, я получаю 338020 380 176 200 180 196 176 200 176 200 180 196 180 964 1148 1056 1404 2680 2676 804 1164 1656 528 1552 39964 304 200 176 200 176 200 176 840 2800 за один клик, @Good York

у вас должно быть не менее 67 временных интервалов ... прочитайте это https://techdocs.altium.com/display/FPGA/NEC%2bInfrared%2bTransmission%2bProtocol ... или это https://www.digikey.ca/en /maker/blogs/2021/understanding-the-basics-of-infrared-коммуникаций ... и это https://circuitdigest.com/microcontroller-projects/build-your-own-ir-remote-decoder-using-tsop -и-pic-микроконтроллер, @jsotola

Если использовать таймер, у меня есть это время: 0 122 122 96 96 32 32 33 33 28 28 33 33 32 32 33 33 31 31 1827 г. 1827 г. 1736 1736 133 133 3422 3422 1072 1072 1072 112 112 95 95 32 32 29 29 150 150, @Good York

у вас не хватает временных интервалов... те что есть, не в правильных пропорциях, @jsotola

тогда что не так?, @Good York

Пожалуйста, покажите ваш последний код. Возможно, используйте ту же ссылку, что и раньше., @6v6gt

[код](https://pastecode.io/s/eyqjv1ri), @Good York

начните с изучения пульта дистанционного управления... он отправляет правильный код? ... также, пожалуйста, очистите свой код ... это беспорядок ... правильно сделайте отступ и уберите комментарии из кода ... комментарии не являются частью кода, @jsotola

Я использовал осциллограф [ссылка](https://imgur.com/t7bStKQ), желтый контакт 5, синий контакт 2, и это решение работает [решение](https://arduinoprosto.ru/q/93087/how -может-при-нажатии-кнопки-отображать-его-название), @Good York

почему вы снова публикуете этот вопрос?, @jsotola

вот еще вопрос и код, @Good York

похоже тот же вопрос, @jsotola


1 ответ


Лучший ответ:

1

Внешняя версия прерывания декодера протокола NEC, использующая micros() для синхронизации вместо Таймера 1. Отбрасывает повторяющиеся коды NEC, В противном случае, в стиле кода в вопросе.

volatile boolean nec_ok = 0;
byte command, inv_command;
volatile byte nec_state = 0 ;
unsigned int address;
volatile unsigned long nec_code;
volatile uint32_t errFlag = 0 ;
volatile uint32_t decodeStartAtMs = 0 ;


void setup() {
  Serial.begin(9600);
  pinMode( 2, INPUT_PULLUP ) ;
  attachInterrupt(digitalPinToInterrupt(2), remote_read, CHANGE); // Включить внешнее прерывание (INT0)
}

void remote_read() {

  static byte i = 0 ;
  static bool wasSpace;
  static unsigned long enteredAtUs = 0 ;
  static unsigned long pulseLengthLastUs ;

  wasSpace = ! digitalRead( 2 ) ;
  pulseLengthLastUs = micros() - enteredAtUs; // Расчет длины импульса
  enteredAtUs = micros(); // Сохраняем текущее время для следующей записи


  switch (nec_state) {

    case 0 : // Подождите здесь, пока не будет найден импульс 9 мс )
      if ( pulseLengthLastUs > 8000 && pulseLengthLastUs < 10000 ) {  // получен импульс длительностью 9 мс
        decodeStartAtMs = millis() ;  // для тайм-аута
        nec_state = 1 ;
        nec_code = 0 ;
        i = 0 ;
      }
      break;

    case 1 : // обработка 4,5 мс пробела
      /*
      if ((pulseLengthLastUs > 2000) && (pulseLengthLastUs < 2500)) {
          // беззвучно отбрасываем повторяющиеся коды !!!
          nec_state = 0; // Сброс процесса декодирования
      }
      else 
      */
      if ((pulseLengthLastUs > 5500) || (pulseLengthLastUs < 3500)) {
        errFlag = 10000000 + pulseLengthLastUs ;
        nec_state = 0; // Сброс процесса декодирования
      }
      else
        nec_state = 2; // Следующее состояние: конец импульса 562 мкс (начало интервала 562 мкс или 1687 мкс)
      break;


    case 2 : // обрабатывать пространство 562 мкс или 1687 мкс
      if ((pulseLengthLastUs > 1900) || (pulseLengthLastUs < 400)) { // Недопустимый временной интервал ==> остановить декодирование
        errFlag = 20000000 + pulseLengthLastUs ;
        nec_state = 0; // Сброс процесса декодирования
        break;
      }

      if ( wasSpace ) {
        // нас интересует только длина пробела
        if ( pulseLengthLastUs > 1000) // Если ширина пробела > 1 мс (короткий пробел)
          bitSet(nec_code, (31 - i)); // Записать 1 в бит (31 - i)
        else // Если ширина пробела < 1 мс (длинный пробел)
          bitClear(nec_code, (31 - i)); // Записать 0 в бит (31 - i)
        i++;
        if (i > 31) { // Если все биты получены
          i = 0 ;
          nec_ok = 1; // Процесс декодирования ОК
          detachInterrupt(0); // Запретить внешнее прерывание (INT0)
        }
      }
      break;
  }
}

void loop() {

  if ( errFlag > 0 ) {
    Serial.print( "Error: " ) ;
    Serial.println( errFlag ) ;
    errFlag = 0 ;
  }

  if ( nec_state > 0 && millis() - decodeStartAtMs > 90 ) {
    Serial.println( "Timeout" ) ;
    nec_state = 0 ;
  }


  if (nec_ok) {
    Serial.print( "Code received = " ) ;
    Serial.println( nec_code, HEX ) ;

    address = (nec_code >> 16) & 0xFF; // Извлекаем адрес из полученного кода
    inv_command = (nec_code >> 8) & 0xFF; // Извлечь инвертированную команду из полученного кода
    command = nec_code & 0xFF; // Извлечь команду из полученного кода
    if (command == ((~inv_command) & 0xFF)) { // Проверка правильности команды
      Serial.print("Received valid NEC code: ");
      Serial.print(address, HEX); Serial.print(", ");
      Serial.print(command, HEX); Serial.print(", ");
      Serial.println(inv_command, HEX);
    }
    else {
      Serial.println("Received invalid NEC code!");
    }
    nec_ok = 0; // Сброс процесса декодирования
    nec_state = 0; // Сбросить состояние декодирования
    attachInterrupt(digitalPinToInterrupt(2), remote_read, CHANGE); // Повторно разрешить внешнее прерывание (INT0)
  }

}

Эту утилиту можно использовать для проверки выходных данных ИК-приемника, показывающих синхронизацию импульсов и промежутков с разрешением 4 мкс.

// утилита анализатора протоколов
// предназначен для передачи таймингов на последовательную консоль, например, из демодулированных IR-кодов NEC
// Подключите выход ИК-приемника к контакту 2


const uint8_t DATA_SIZE = 100 ;

struct intervalRead_t {
  uint32_t durationUs ;
  bool space ;
} ;

intervalRead_t intervalRead[ DATA_SIZE ] ;

volatile unsigned long enteredAtUs = 0 ;
volatile uint8_t index = 0 ;
volatile bool firstPulse = true ;


void saveTimes() {

  bool wasSpace;
  uint32_t pulseLengthLastUs ;

  wasSpace = ! digitalRead( 2 ) ;
  pulseLengthLastUs = micros() - enteredAtUs; // Расчет длины импульса
  enteredAtUs = micros(); // Сохраняем текущее время для следующей записи

  if ( firstPulse ) {
    firstPulse = false ;
    index = 0 ;
  }
  else {
    if ( index < DATA_SIZE  )  {
      intervalRead[ index ].durationUs = pulseLengthLastUs ;
      intervalRead[ index ].space = wasSpace ;
      index++ ;
    }
  }
}

void setup() {
  Serial.begin(9600);
  pinMode( 2, INPUT_PULLUP ) ;
  attachInterrupt(digitalPinToInterrupt(2), saveTimes, CHANGE); // Включить внешнее прерывание (INT0)
}

void loop() {
  if ( !firstPulse && micros() - enteredAtUs > 500000UL  ) {
    for ( uint8_t i = 0; i < index ; i++ ) {
      Serial.print( i ) ;
      Serial.print( "\tinterval= " ) ;
      Serial.print( intervalRead[ i ].durationUs ) ;
      Serial.print( "us  \t") ;
      Serial.print( intervalRead[ i ].space ? "space" : "pulse" ) ;
      Serial.println( ) ;
    }
    Serial.println( ) ;
    firstPulse = true ;
  }
}

,

Почему-то ничего не выводит в консоль, @Good York

nec_state всегда ноль и pulseLengthLastUs 238000 380 176 196 176 196 184 192 184 192 176 196 184 960 1072 1132 376 804 2292 2676 1552 2676 1500 1604 40988 304 196 176 196 176 196 176 836 2800, @Good York

Если вы используете свой [код](https://arduinoprosto.ru/q/93087/how-can-when-the-button-is-clicked-to-display-its-name?noredirect=1&;lq=1), печать из консоли при нажатии кнопки: нет кода нет кода 1С0 1ДФ 3 Е нет кода 1, @Good York

Как вы получили эти цифры? Вы не можете поместить оператор print() в функцию remote_read(), потому что это ISR и в любом случае испортит тайминги, особенно при такой медленной печати со скоростью 9600 бод. Программное обеспечение PIC, по-видимому, генерирует повторный код NEC, если кнопка удерживается в нажатом состоянии. Код, который я предоставил, игнорирует их. Попробуйте кратковременно нажать кнопку при тестировании., @6v6gt

Я только на мгновение нажимаю кнопку при тестировании, но ничего не печатает, @Good York

я изменил ваш обновленный код, но все еще не работает, @Good York

Вы удалили все операторы печати, добавленные в ISR? Вы вообще видели какое-нибудь сообщение на консоли? Вы можете попробовать временно изменить 31 здесь, чтобы сказать 30 или 29, просто чтобы увидеть, отсутствует ли конечный импульс. if (i > 31) { // Если все биты получены. Если это ничего не даст, вам придется разобраться, как использовать логический анализатор proteus. Разрешение осциллографа слишком низкое. Я проверил этот код с помощью пульта дистанционного управления NEC, прежде чем публиковать его, но не изменения., @6v6gt

pulseLengthLastUs всегда ложно в этом условии: pulseLengthLastUs > 8000 && pulseLengthLastUs < 10000 при нажатии любой кнопки, @Good York

Затем возникает несоответствие между кодом https://github.com/circuitvalley/IR-Remote-Control/blob/master/NEC%20IR%20Transmitter%20Remote/main.c (NEC) и его производительностью на виртуальной PIC в эта среда Proteus. Тот факт, что не удалось найти заголовок NEC длительностью 9 мс, является явным признаком проблемы синхронизации. С помощью логического анализатора/(осциллографа) проверьте форму сигнала и убедитесь, что частота несущей составляет 38 кГц. Несущая частота не имеет решающего значения, но может указывать на то, что PIC или среда настроены неправильно. Возможно, просто для тестирования используйте библиотеку Arduino IR., @6v6gt

Результаты не выглядят как правильные коды NEC, и программа заблокирует ISR на длительный период, что может привести к другим проблемам. Имеет ли это значение — другой вопрос. Во всяком случае, я добавил дополнительный скетч в конец того, который я дал в ответе. Он распечатает тайминги потока ИК-данных и может быть использован для определения проблем с таймингами., @6v6gt

отображает следующее, когда я нажимаю кнопку: [экран](https://pastecode.io/s/yo5qu9yz), @Good York

Это сигнал ~ 8 кГц с рабочим циклом 33%. Это четверть ожидаемой скорости. Похоже, что скорость слишком низкая для того компонента ИК-приемника, который вы используете для его демодуляции, и пропускаете его напрямую, если вы измерили его на выводе 2 Arduino. В любом случае, без надлежащего использования инструментов этого симулятора для проверки таймингов это упражнение похоже на простукивание в темноте., @6v6gt

но частота на компоненте ирлинк 38кГц, @Good York