
Картинка для привлечения внимания 🙂
В последнее время мы довольно сильно привыкли к тому, что в разнообразных самоделках на базе 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); }
На мой взгляд, этот код содержит много лишнего и его можно существенно урезать, однако и «так сойдёт».
Этот код позволяет сделать следующее: считать коды, которые передаёт ИК-пульт.
Что для этого нужно: сначала подключите приёмник ИК-излучения к плате микроконтроллера, загрузите прошивку и можете понажимать кнопки на пульте. Для этого можно использовать абсолютно любой пульт, какой есть у вас под рукой.
Приёмник ИК-излучения подключаем к тому пину, который прописан у вас в скетче:

Пример выше — для подключения к 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.
#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.

Источник картинки: radioprog.ru
Достаточно подробно вопросы программирования Attiny13 разобраны вот тут, так что можно почитать.
Конечно, программирование Attiny13 является достаточно непростым, производится в стиле языка C, что может быть не совсем «user-friendly», особенно если вы привыкли работать только в среде Arduino IDE. С другой стороны, это даёт более широкие возможности для творчества.
Если у вас всё пройдёт благополучно, то в результате может получиться нечто вроде кораблика, управляемого по инфракрасному каналу:
Ну, вот и всё! Напоследок хочется сказать, что, несмотря на распространение более современных и скоростных способов осуществления связи и управления, инфракрасный способ далеко не изжил себя и может применяться для ряда случаев.
Для общего развития можно прочитать вот эту хорошую обзорную статью про микроконтроллеры семейства AVR.
Ещё одна хорошая статья про 4-х канальное управление есть вот тут.
Успехов в сборке!
P.S. Несколько отступая от рассмотренной темы, хочется сказать, что автор рассматривает в перспективе инфракрасный способ связи для создания любопытной самоделки — «USB-свистка», связь с которым будет осуществляться с инфракрасного пульта, и который будет управлять переключением видеофайлов проигрывателя в компьютере.
НЛО прилетело и оставило здесь промокод для читателей нашего блога:
— 15% на все тарифы VDS (кроме тарифа Прогрев) — HABRFIRSTVDS.
ссылка на оригинал статьи https://habr.com/ru/company/first/blog/658945/
Добавить комментарий