// Мы будем использовать библиотеки для работы с ИК и с сервоприводами #include #include // Определим номера используемых пинов #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); } } }