Жалюзи с электроприводом
- Платформы: Arduino Uno
- Языки программирования: Arduino (C++)
- Тэги: дистанционное управление, IR, ИК-пульт, электропривод, сервомотор
Что это?
Автоматизация рутины — одна из самых популярных тем в мире DIY-электроники. На этот раз мы автоматизируем управление жалюзями при помощи Arduino и сервопривода постоянного вращения. В качестве канала связи используем инфракрасный свет, а в качестве пульта — первый попавшийся пульт от телевизора или музыкального центра.
Мы сделаем так, чтобы кнопки открывания и закрывания можно было назначать самостоятельно. Так мы сможем управлять устройством теми кнопками на пульте, которые обычно никак не используются.
Что нам понадобится?
- Пластина крепления сервопривода
- Какая-нибудь пластина для крепления Arduino. Мы использовали акриловую площадку из набора «Матрёшка»
- Пульт дистанционного управления
- Жалюзи со стандартным скобяным креплением
- Запасные скобы крепления жалюзи ×2 шт
Как собрать?
- Установите Arduino Uno на платформу. Мы использовали акриловую площадку из набора «Матрёшка». Так как нам нужна только часть по размерам платы, мы отпилили ненужную половину площадки. А для закрепления скобы мы просверлили отверстие в акриле.
- Установите Troyka Shield на Arduino Uno.
- Установите сервопривод и Arduino Uno на жалюзи.
- Прошейте в Arduino Uno скетч, приведённый ниже.
Алгоритм
Сразу после подачи питания вызывается функция setup
. В её теле
инициализируется ИК-библиотека. Затем подаётся сигнал старта программы (три
коротких сигнала) и запускается процедура «обучения» устройства. Обучение
заключается в запоминании устройством кнопок, которые пользователь использует
для открытия и закрытия жалюзей. Пользователь нажимает кнопку на пульте,
устройство получает код нажатой кнопки, запоминает его и подтверждает это
событие коротким сигналом. Затем всё повторяется для второй кнопки. После того,
как обе кнопки заданы, устройство издаёт длинный звуковой сигнал и переходит в
рабочий цикл (функция loop
).
В рабочем цикле устройство ожидает нажатия кнопки пульта. Как только получен сигнал о нажатии, проверяется было ли нажатие этой же кнопки в недалёком прошлом. Если да, то повторное нажатие воспринимается, как команда «продолжать крутить мотор». Если недавно нажатий не было, запоминаем момент нажатия и включаем привод в нужном направлении.
Исходный код
- irjalousie.ino
// Мы будем использовать библиотеки для работы с ИК и с сервоприводами #include <IRremote.h> #include <Servo.h> // Определим номера используемых пинов #define RECV_PIN 2 #define SERV_PIN 3 #define BUZZER_PIN 4 // Определим тип «действие» enum Command { CMD_NONE = 0, CMD_OPEN = 1, CMD_CLOSE = 2 }; // Создадим объект для пользования ИК-библиотекой IRrecv irrecv(RECV_PIN); decode_results results; // Создадим переменные, которые будут хранить коды кнопок пульта unsigned long codeOpen = 0; unsigned long codeClose = 0; // Определим переменные для хранения полученной команды и времени её получения unsigned long cmdStartTime; unsigned char cmd; // Создадим объект для управления сервоприводом постоянного вращения Servo srv; // Из-за особенностей реализации нельзя использовать библиотеку IRremote // вместе с функцией beep (они используют одно и тоже прерывание). Чтобы // справиться с этой бедой мы, не используя прерываний, реализовали свою версию // функции beep. void beeep(int pin, int freq, unsigned long duration) { unsigned long start; // Переводит пин в выход pinMode(pin, OUTPUT); // Запоминаем момент начала выполнения start = millis(); // В течение duration миллисекунд пищим while (millis() - start < duration) { // Частота писка определяется паузами между изменениями состояния пина digitalWrite(pin, HIGH); delay(1000/freq/2); digitalWrite(pin, LOW); delay(1000/freq/2); } // На всякий случай переводит пин обратно в режим чтения pinMode(pin, INPUT_PULLUP); } // Функция "обучения" устройства (запоминания кодов кнопок открытия/закрытия) void learn(void) { // Ждём получения корректной команды // (условие окончания цикла находится в его теле) while (true) { if (irrecv.decode(&results)) { // Получили какой-то код. Сообщим библиотеке, что мы обработали событие. irrecv.resume(); // Пропускаем коды короче 16 бит (наши пульты давали коды 16 или 32 бита, // остальные считаем ошибочными) if (results.bits >= 16) { // Сохраняем пришедший код как код на открытие codeOpen = results.value; // В знак подтверждения пикаем beeep(BUZZER_PIN, 500, 100); break; } // Ждём 100 мс, чтобы пропустить случайный приём пачки одинаковых кодов delay(100); } } // На всякий случай ждём 200 мс (пользователь явно быстрее не будет на кнопки // нажимать) delay(200); // Таким же методом принимаем второй код (на закрытие) while (true) { if (irrecv.decode(&results)) { irrecv.resume(); if (results.bits >= 16 && results.value != codeOpen) { codeClose = results.value; beeep(BUZZER_PIN, 500, 100); break; } delay(100); } } } // Функция инициализации устройства void setup(void) { // Запускаем библиотеку IRRemote irrecv.enableIRIn(); // Инициализируем команду (не было команды) cmd = CMD_NONE; // Сообщаем пользователю, что мы запустились тройным пиком beeep(BUZZER_PIN, 500, 100); delay(100); beeep(BUZZER_PIN, 500, 100); delay(100); beeep(BUZZER_PIN, 500, 100); delay(100); // Запускаем процедуру "обучения" learn(); // Пищим в знак окончания процедуры обучения beeep(BUZZER_PIN, 2000, 1000); } // Пуск сервопривода постоянного вращения void start(void) { srv.attach(SERV_PIN); srv.write(cmd == CMD_OPEN ? 0 : 120); } // Остановка мотора void stop(void) { // Самый простой способ остановить серву постоянного вращения — отсоединиться // от неё srv.detach(); } // Рабочий цикл программы void loop(void) { unsigned long codeValue; int codeLen; // Запускаем сканирование команды по ИК if (irrecv.decode(&results)) { // Сообщаем библиотеке, что приняли её информацию irrecv.resume(); // Получаем код и длину кода codeValue = results.value; codeLen = results.bits; // Отсеиваем неправильные коды if (codeLen >= 16) { // Если пришёл код на открытие и на данный момент привод не крутится в // противоположную сторону, включаем открытие. if (codeValue == codeOpen && cmd != CMD_CLOSE) { cmd = CMD_OPEN; cmdStartTime = millis(); start(); } else if (codeValue == codeClose && cmd != CMD_OPEN) { // Если пришёл код на закрытие и на данный момент привод не крутится в // противоположную сторону, включаем открытие. cmd = CMD_CLOSE; cmdStartTime = millis(); start(); } // Если приходит команда на резкую смену направления вращения — спасаем // серву от смерти, игнорируя такую плохую команду. } else if (codeLen == 0 && (cmd == CMD_OPEN || cmd == CMD_CLOSE)) { // Некоторые пульты при удержании кнопки в нажатом состоянии посылают код // нулевой длины. Будем считать это повтором команды. cmdStartTime = millis(); } delay(100); } // Здесь мы выключаем привод через 500 мс после последней полученной команды if (cmd == CMD_OPEN || cmd == CMD_CLOSE) { if (millis() - cmdStartTime > 500) { cmd = CMD_NONE; stop(); // Дождёмся полной остановки привода delay(1000); } } }
Пример программы с сенсором освещенности
В данном примере жалюзи открываются и закрываются в зависимости от освещенности. ИК-приёмник нам не понадобиться, но необходимо добавить датчик освещённости (Troyka-модуль) и потенциометр (Troyka-модуль).
- light_jalousie.ino
// подключаем библиотеку для работы с сервоприводами #include <Servo.h> // определим номера используемых пинов // сенсор освещенности #define LIGHT_PIN A0 // чувствительность к свету #define POT_PIN A5 // пин сервопривода #define SERV_PIN 3 // пин пищалки #define BUZZER_PIN 4 // определим тип «действие» enum Command { CMD_NONE = 0, CMD_OPEN = 1, CMD_CLOSE = 2 }; // в начале программы мы не знаем открыты жалюзи или нет int cmd = CMD_NONE; // создадим объект для управления сервоприводом постоянного вращения Servo srv; void setup() { // сообщаем пользователю, что мы запустились тройным пиком tone(BUZZER_PIN, 500, 100); delay(200); tone(BUZZER_PIN, 500, 100); delay(200); tone(BUZZER_PIN, 500, 100); delay(200); } void loop() { int light = analogRead(LIGHT_PIN); int lightSens = analogRead(POT_PIN); Serial.print(light); Serial.print("\t\t"); Serial.print(lightSens); Serial.print("\t\t"); Serial.println(cmd); if (light > lightSens && (cmd == CMD_OPEN || cmd == CMD_NONE)) { srv.attach(SERV_PIN); srv.write(120); // ждём указанное время открытия/закрытие жалюзей delay(2000); // самый простой способ остановить серву постоянного вращения // отсоединиться от неё srv.detach(); cmd = CMD_CLOSE; } else if (light < lightSens && (cmd == CMD_CLOSE || cmd == CMD_NONE)) { srv.attach(SERV_PIN); // ждём указанное время открытия/закрытие жалюзей srv.write(30); delay(2000); // самый простой способ остановить серву постоянного вращения // отсоединиться от неё srv.detach(); cmd = CMD_OPEN; } }
Демонстрация работы устройства
Что дальше?
- Неудобно каждый раз при включении устройства прошивать в него коды кнопок пульта. Но у ATmega328p есть энергонезависимая память — EEPROM, в которой можно сохранить один раз эти коды и загружать их от туда при каждом старте. Для работы с этой памятью есть библиотека EEPROM.
- В нашей конструкции есть изъян: нельзя сделать кнопку, которая автоматически полностью закроет жалюзи. Если установить датчик полного закрытия/открытия, то такую функцию реализовать станет возможным. Для этого можно, например, снять одну полоску и установить вместо неё потенциометр.