Картинка для привлечения внимания :-)

В последнее время мы довольно сильно привыкли к тому, что в разнообразных самоделках на базе Arduino / esp32 используется управление, основанное на радиочастотах. Тем не менее иногда применение такой технологии управления может быть не совсем удобно хотя бы потому, что радиообстановка может быть сложной — например, если вы находитесь в густонаселённом месте (во дворе многоэтажного дома). Однако зачастую совсем без управления «или сложно, или совсем грустно». И в этой статье мы поговорим как раз о том, как можно реализовать альтернативный способ управления вашими самодельными устройствами.

Да, радиоуправление достаточно удобно и позволяет передавать большие объёмы данных, а с появлением плат семейства ESP передача на больших скоростях и на дальние расстояния стала доступна и достаточно проста. Однако в настоящее время совершенно незаслуженно начинает отходить на задний план другой способ управления, который всё меньше используется в самоделках — инфракрасный.

Давайте попробуем рассмотреть, как можно реализовать нечто подобное! Для этого нам необходимо скачать библиотеку IRremote:



После скачивания необходимо перейти в идущие с этой библиотекой примеры и открыть пример под названием IRrecvDemo:

/*
 * IRremote: IRrecvDemo - demonstrates receiving IR codes with IRrecv                            
 * An IR detector/demodulator must be connected to the input RECV_PIN.                            
 * Version 0.1 July, 2009                            
 * Copyright 2009 Ken Shirriff                           
 * http://arcfn.com                           
 */

#include <IRremote.h>

#if defined(ESP32)
int IR_RECEIVE_PIN = 15;
#else
int IR_RECEIVE_PIN = 11;
#endif
IRrecv irrecv(IR_RECEIVE_PIN);

decode_results results;

// On the Zero and others we switch explicitly to SerialUSB
#if defined(ARDUINO_ARCH_SAMD)
#define Serial SerialUSB
#endif

void setup() {
    pinMode(LED_BUILTIN, OUTPUT);

    Serial.begin(115200);
#if defined(__AVR_ATmega32U4__)
    while (!Serial)
        ; //delay for Leonardo, but this loops forever for Maple Serial
#endif
#if defined(SERIAL_USB) || defined(SERIAL_PORT_USBVIRTUAL)
    delay(2000); // To be able to connect Serial monitor after reset and before first printout
#endif
    // Just to know which program is running on my Arduino
    Serial.println(F("START " __FILE__ " from " __DATE__));

    // In case the interrupt driver crashes on setup, give a clue
    // to the user what's going on.
    Serial.println("Enabling IRin");
    irrecv.enableIRIn(); // Start the receiver

    Serial.print(F("Ready to receive IR signals at pin "));
    Serial.println(IR_RECEIVE_PIN);
}

void loop() {
    if (irrecv.decode(&results)) {
        Serial.println(results.value, HEX);
        irrecv.resume(); // Receive the next value
    }
    delay(100);
}

На мой взгляд, этот код содержит много лишнего и его можно существенно урезать, однако и «так сойдёт».

Этот код позволяет сделать следующее: считать коды, которые передаёт ИК-пульт.

Что для этого нужно: сначала подключите приёмник ИК-излучения к плате микроконтроллера, загрузите прошивку и можете понажимать кнопки на пульте. Для этого можно использовать абсолютно любой пульт, какой есть у вас под рукой.

Приёмник ИК-излучения подключаем к тому пину, который прописан у вас в скетче:

image

Пример выше — для подключения к esp32. Но если будут некие эксцессы, т.к. 15 пин выдаёт сигнал ШИМ в момент запуска esp32, согласно этой таблице, просто перекиньте сигнальный провод на другой пин, ориентируясь на приведённую таблицу.

Теперь, если вы наведёте свой пульт на приёмник и нажмёте любую кнопку — у вас в мониторе порта будет написано число в шестнадцатеричном формате, в котором кодирован сигнал конкретной кнопки, которую вы нажали.

Тут нужно сделать ремарку: некоторые пульты постоянно меняют по своему алгоритму передающиеся коды кнопок. Я с таким не сталкивался, но слышать приходилось. Поэтому, если вам не повезло и ваш пульт именно такой — лучше взять другой. Или устроить мозговой штурм, выяснить алгоритм смены кодов и встроить его в свой код (но лично мне было бы лениво и я бы просто взял другой пульт :-) )

Этот код в дальнейшем вы можете использовать в своих проектах. То есть по логике «если получили этот код — делаем то-то».

Например, ниже я показал, как это могло бы выглядеть, если бы мы использовали полученный с приёмника код для мигания встроенным светодиодом в esp32 (вы, конечно, понимаете, что это только пример и в реальности это может быть управление чем угодно — включение/выключение электродвигателей, открывание/закрывание штор и т.д. и т. п.):

#include "IRremote.h"

const int LED = 2;// Пин встроенного светодиода на ESP32
IRrecv irrecv(15); // пин ИК-приемника
decode_results results;

void setup() {
  pinMode(LED, OUTPUT);// Устанавливаем встроенный светодиод на ESP32 — как выход
  irrecv.enableIRIn(); // запускаем приём
}

void loop() {
  if ( irrecv.decode( &results )) {
    switch ( results.value ) {
    case 0xABC111:
        digitalWrite( LED, HIGH );
        break;
    case 0xABC222:
        digitalWrite( LED, LOW );
        break;
    }    
    irrecv.resume();
  }
}

Однако и это не всё! Мне видятся наиболее интересными в инфракрасном управлении две его опции:

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

Я думаю, вы уже поняли, к чему я клоню — отказ от популярных плат типа Arduino или esp32 и использование непосредственно только микроконтроллера! Конечно, это есть не совсем правильно, и некая обвязка всё равно требуется для стабильности и надёжной работы, однако теоретически это устройство может быть очень миниатюрным: например, если мы будем использовать чип Attiny13 и инфракрасный приёмник, то всё наше устройство вполне может поместиться на кончике пальца! Отличный результат для самодельщика — как по размерам, так и по цене:



В своё время концепция использования хранимых процедур, создания библиотек и в целом возможность повторного использования уже готового программного обеспечения — совершили революцию в программировании. Поэтому воспользуемся кодом, который был любезно выложен автором.

Как заявляется, код протестирован и оптимизирован для загрузки в весьма ограниченный объём памяти Attiny13.

Прошивка Attiny13
#define IRpin_PIN PINB
#define IRpin 2

#define rLedPin 3
#define gLedPin 4
#define relayPin 1

#define MAXPULSE 5000
#define NUMPULSES 32
#define RESOLUTION 2

#define timeN1 1800000
#define timeN2 3600000

#define timerInterval 500


bool relayState = false;
unsigned long timer = 0;
unsigned long shift = timeN1;//30 min timer by default
unsigned long previousMillis = 0;
bool timerN = false;
byte i = 0;


void setup() {
  //default states
  DDRB |= (1<<relayPin);
  DDRB |= (1<<rLedPin);
  DDRB |= (1<<gLedPin);

  PORTB &= ~(1<<relayPin);//relay off
  PORTB &= ~(1<<rLedPin);//red led off
  PORTB |=  (1<<gLedPin);//green led on

  /*
  //for debug
  Serial.begin(9600);
  Serial.println("Start | "+String(millis()));
  //*/

  /*
  //for debug without ir receiver
  pinMode(5, INPUT);
  pinMode(6, INPUT);
  //*/


}

void shutDown(){
  relayState = true;
  PORTB |=  (1<<relayPin);
  PORTB &= ~(1<<gLedPin);
  PORTB |=  (1<<rLedPin);
  //Serial.println("turining off |"+String(millis()));
}

void startUp(){
  relayState = false;
  PORTB &= ~(1<<relayPin);
  PORTB |=  (1<<gLedPin);
  PORTB &= ~(1<<rLedPin);
  //Serial.println("turining on |"+String(millis()));
}

void loop() {
  unsigned long irCode = listenForIR(); // Wait for an IR Code
  //Serial.println("ir code: "+String(irCode));
  if(irCode == 3359105948){//green button
    //Serial.println("Pressed green btn |"+String(millis()));
    if(timer == 0){//on off mode
      if(relayState == true){
        startUp();
      }else{
        shutDown();
      }
    }else{//cancel timer mode
      timer = 0;
      PORTB &= ~(1<<rLedPin);//turn off red led
      //Serial.println("timer canceled |"+String(millis()));
    }
  }//end green btn


  if(3359101868 == irCode){//red btn
    //Serial.println("pressed red btn |"+String(millis()));
    if(timer == 0){
      if(relayState == 0){
        timer = millis();
        //Serial.println("timer started |"+String(millis()));
      }/*else{
        Serial.println("already shutdown |"+String(millis()));
      }
      //*/

    }else{//changing time mode
      timerN = !timerN;
      if(timerN){
        //Serial.println("change 30sec |"+String(millis()));
        shift = timeN1;//30 min
      }else{
        //Serial.println("change 60sec |"+String(millis()));
        shift = timeN2;//60 min
      }
    }
  }//end red btn
} // loop end

void checkTimer(){
  unsigned long time = millis();
  if(time - previousMillis >= timerInterval || previousMillis > time ) {
    previousMillis =time;
    timer1();
  }
}

unsigned long  listenForIR() {// IR receive code
  byte currentpulse = 0; // index for pulses we're storing
  unsigned long irCode = 0; // Wait for an IR Code
  irCode = irCode << 1;

  while (true) {
    unsigned int pulse = 0;// temporary storage timing
    //bool true (HIGH)
    while (IRpin_PIN & _BV(IRpin)) { // got a high pulse (99% standby time have HIGH)
      if(++i > 150){//check timer every 150 iterations (high frequency break ir code timing)
        i = 0;
        checkTimer();
      }
      pulse++;
      delayMicroseconds(RESOLUTION);
      if (((pulse >= MAXPULSE) && (currentpulse != 0)) || currentpulse == NUMPULSES ) {
        return irCode;
      }
    }

    //make irCode
    irCode = irCode << 1;
    if ((pulse * RESOLUTION) > 0 && (pulse * RESOLUTION) < 500) {
      irCode |= 0;
    }else {
      irCode |= 1;
    }
    currentpulse++;
    pulse = 0;
        //bool false (LOW)
    while (!(IRpin_PIN & _BV(IRpin))) {//wait before new pulse
      //checkTimer();
      pulse++;
      delayMicroseconds(RESOLUTION);
      if (pulse >= MAXPULSE || currentpulse == NUMPULSES ) {
        //Serial.println(irCode);
        return irCode;
      }
    }
  }//end while(1)
 
}//end listenForIR


//executing every timerInverval
void timer1() {
  if(timer != 0){
    if(timerN == true){//timeN1 or timeN2
      PORTB |= (1<<rLedPin);
    }else{//blinking 30min    
      PORTB ^= (1<<rLedPin);//invert
    }
    //Serial.println(String((timer+shift - millis())/1000));
  }

  if(timer != 0 &&(timer+shift < millis() || timer > millis())){
    timer = 0;
    shutDown();
  }
}

https://www.pvsm.ru/arduino/75674


Более лаконичная версия есть по этой ссылке.

Показанный выше код делает вот что:

В приведённом выше примере используется ИК-приёмник из серии TSOP..., а именно TSOP4838.

Согласно этому источнику, его характеристики следующие:

«TSOP4838 является модулем ИК приёмника для систем дистанционного управления. В своём составе содержит ИК фильтр, PIN-диод и предусилитель. Демодулированный выходной сигнал с ИК приёмника может быть подан непосредственно на микроконтроллер/микропроцессор.»

  • Фотодетектор и предусилитель в одном корпусе.
  • Внутренний фильтр для PCM частоты.
  • Улучшенный экран для защиты от ЭМП.
  • Диапазон напряжения питания от 2.5В до 5.5В.
  • Диапазон передачи 45м.
  • Улучшенная стойкость к внешнему освещению.
  • Частота несущей 38кГц.
  • Направленность 45°.
  • Ток питания 950мкА.
  • Диапазон рабочей температуры от -25°C до 85°C.

image
Источник картинки: radioprog.ru

Достаточно подробно вопросы программирования Attiny13 разобраны вот тут, так что можно почитать.

Конечно, программирование Attiny13 является достаточно непростым, производится в стиле языка C, что может быть не совсем «user-friendly», особенно если вы привыкли работать только в среде Arduino IDE. С другой стороны, это даёт более широкие возможности для творчества.

Если у вас всё пройдёт благополучно, то в результате может получиться нечто вроде кораблика, управляемого по инфракрасному каналу:

Ну, вот и всё! Напоследок хочется сказать, что, несмотря на распространение более современных и скоростных способов осуществления связи и управления, инфракрасный способ далеко не изжил себя и может применяться для ряда случаев.

Для общего развития можно прочитать вот эту хорошую обзорную статью про микроконтроллеры семейства AVR.

Ещё одна хорошая статья про 4-х канальное управление есть вот тут.

Успехов в сборке!

P.S. Несколько отступая от рассмотренной темы, хочется сказать, что автор рассматривает в перспективе инфракрасный способ связи для создания любопытной самоделки — «USB-свистка», связь с которым будет осуществляться с инфракрасного пульта, и который будет управлять переключением видеофайлов проигрывателя в компьютере.


НЛО прилетело и оставило здесь промокод для читателей нашего блога:

15% на все тарифы VDS (кроме тарифа Прогрев) — HABRFIRSTVDS.

Комментарии (12)


  1. Jury_78
    13.04.2022 13:34
    +2

    Да, радиоуправление достаточно удобно и позволяет передавать большие
    объёмы данных, а с появлением плат семейства ESP передача на больших
    скоростях и на дальние расстояния...

    Лучше разделить управление и передачу больших объемов. При управление передают команды и они фиксированы. Существует IrDA - протокол передачи данных, по моему это интересней чем односторонняя передача команд.

    для создания любопытной самоделки — «USB-свистка»

    Если только для изучения, т.к. готовые устройства существуют и их можно приобрести.


  1. serafims
    13.04.2022 17:38

    Ни слова о том, что пульты работают на разных частотах несущей. И для передачи сигнала надо не только протокол пульта знать, но и частоту несущей. Впрочем, приемников на 36 и 38 кГц как грязи, чаще эти частоты используются.

    Радует, что приведен код для attiny13, на благородном чистом коде, на самом деле это простой микроконтроллер, с ним играться и в симуляторе можно типа proteus.


  1. Almighty_Goose
    13.04.2022 19:16

    Насчет "создания любопытной самоделки — «USB-свистка»"...

    Это делается довольно элементарно, например, на Attiny85 (DigiSpark) или Atmega32u4 (Arduino Leonardo, Arduino Pro micro/mini) - оба умеют притворяться клавиатурой и мышкой. DigiSpark покомпактнее будет и можно платку сразу в USB вставлять. Я так на DigiSpark-e делал себе джойстик через эмуляцию мыши. А тут нужно только мультимедиа клавиши эмулировать.

    Но, вы все равно делайте!

    А, вообще, помню мечтал когда-то о КПК с IrDA или FIR, ведь там были программы, чтобы работать в качестве универсального пульта от телевизора, видеомагнитофона и других приборов!!

    Сейчас смартфон есть теперь уже почти у каждого, но вот инфракрасного порта там уже нет. Вот, если бы были модули для USB-type-C с FIR/IrDA... Пока я только видел китайские штуковины вставляемые в миниДжек 3.5мм, но сейчас и миниДжеки в смартфонах убирают.

    Еще говорят китайские модули для 3.5мм не всегда работают. Думаю такую штуку можно и самому спаять. Если использовать доп. питание, то кроме инфракрасного светодиода, понадобится mosfet (2N7000) и батарейка. Всё довольно не дорогое. А команды = акустические файлы.


    1. d2d8
      13.04.2022 20:46

      >Сейчас смартфон есть теперь уже почти у каждого, но вот инфракрасного порта там уже нет.
      Xiaomi, Honor, Huawei и прочие китайцы комплектуются регулярно.


      1. Almighty_Goose
        14.04.2022 02:09

        В мае заканчивается поддержка моего Google Pixel 3A. Пиксель нормальный телефон, но гугл уже успел в марте сломать в нем Mir Pay. Пока не наступит стабильность, апгрейд смартфона совсем не в приоритете.

        При выборе нового смартфона будет предпочтение моделям с тепловизором. Телевизор-то я смотрю раз в год (значит и пульт не использую), а вот тепловизор по важности и полезности сильно перевешивает.

        Конечно, здорово было бы исполнить детскую мечту. Но китаец с IrDA/FIR -- это совсем другое. Интереснее сделать себе модуль USB-type-C с IR-парой. Думаю, скоро появятся модули по типу DigiSpark, но только type-C или уже есть: на них сделать уже свой плагин гораздо проще. Или использовать переходник USB-A <-> type-C, а в него уже втыкать digispark/leonardo.

        Для 3.5мм-аудио можно фотодиод подключить на вход микрофона, инфракрасный светодиод - на левый или правый канал. И отлаживать можно на ПК, т.е. не обязательно нужен смартфон. Кажется, что в этой идее есть потенциал.


  1. Lexsus333
    13.04.2022 20:54

    На базе Arduino, простого ик-приемника, радио-передатчика и умного пульта яндекса "подключил" к умному дому с Алисой простые радио-розетки 433 Mhz. Получилась вот такая самоделка. Arduino получает сигнал с ИК-пульта яндекса, и в зависимости от кода включает/отключает 2 розетки.

    1. Arduino.

    2. Передатчик 433 Mhz.

    3. ИК-приемник.

    На начальной стадии для считывания кодов радио-розеток и ИК-пульта конечно еще нужен приемник 433 Mhz (и соответствующая библиотека) и ИК-приемник.

    Минус на мой взгляд, что обучать надо такую самоделку с компьютера, а не непосредственно с самой самоделки. Пока думаю как это обойти - прикрутить пару кнопок, экранчик и, может быть с использованием SD-карты, обходиться вообще без компьютера. С нового года эта самоделка в тестовом режиме работает, все нет времени облагородить как-то в коробочку и перевести на arduino-micro.


    1. mkvmaks
      14.04.2022 21:48

      Аналогично, только еще все это воткнул в бышую тв приставку yuxing с часиками которая. Теперь так же выглядит - пульт от ТВ, управляет люстрой + отображение часов )))


  1. wAgo
    14.04.2022 01:24

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


  1. moroz69off
    15.04.2022 03:16

    После прочтения статьи сразу подружил "ардуину-нану" с пультом от "тв-лыжы". Было весело играться (потом, когда звёзды сошлись, и всё заработало). Цифры на пульте зажигают лампочки, каждая свою. Спасибо за статью!


    Смотрел на ваш код "для примера" и долго чесал ум на лбу: почему не работает?
    Оказалось (когда заглянул на тип results.value), что у вас хексы в свитче, а велью в интах.
    Вот вам и пример. Чик...
    Стыдно только, что сразу не увидел в коде ошибку, сто^пицот раз кнопки на пульте перепроверял.


  1. andrey_ssh
    15.04.2022 14:34

    оптический канал не глушится в условиях сложной радиосреды (или даже специального подавления);

    Да, но успешно глушится солнечным светом.


    1. wAgo
      15.04.2022 14:40

      Вовсе не успешно, есть такое развлечение лазертаг называется - прекрасно работает в солнечный день, а расстояния там по 100 и более метров


      1. andrey_ssh
        15.04.2022 14:47

        Тут автор предлагает пульт от телевизора использовать, а не лазерное ружьё.

        А ещё от ориентации относительно солнечного света зависит.