// Подключаем библиотеку для работы с мелодиями в формате RTTTL #include // Подключаем библиотеку для работы с ИК-приёмником #include // Подключаем библиотеку для работы с сервоприводом #include // Подключаем библиотеку для работы с таймером millis #include // Подключаем библиотеку для работы со светодиодными матрицами #include // Даём понятное имя пину 3 с пищалкой constexpr uint8_t BUZZER_PIN = 3; // Даём понятное имя пину 2 с ИК-приёмником constexpr uint8_t IR_RECEIVE_PIN = 2; // Даём понятное имя пину A3 с сервоприводом constexpr uint8_t SERVO_YAW_PIN = A3; // Даём понятное имя пину A0 с сервоприводом constexpr uint8_t SERVO_PITCH_PIN = A0; // Создаём объект сервопривода влево-вправо Servo servoYaw; // Создаём объект сервопривода вверх-вниз Servo servoPitch; // Создаём объект для работы с таймером TimerMs timer; // Создаём объект матрицы левого глаза // на шине I²C с адресом 0x60 (указан по умолчанию) TroykaLedMatrix matrixL; // Создаём объект матрицы правого глаза // на шине I²C с адресом 0x63 TroykaLedMatrix matrixR(0x63); // Создаём константу для хранения базовой частоты constexpr int FREQUENCY = 2000; // Создаём константы для хранения минимальной и максимальной частоты constexpr int MIN_FREQUENCY = FREQUENCY - (0.25 * FREQUENCY); constexpr int MAX_FREQUENCY = FREQUENCY + (0.25 * FREQUENCY); // Задаём максимально доступные углы поворота сервопривода головы влево-вправо constexpr uint8_t MAX_ANGLE_YAW_R = 0; constexpr uint8_t MAX_ANGLE_YAW_L = 180; // Задаём максимально доступные углы поворота сервопривода головы вверх-вниз constexpr uint8_t MAX_ANGLE_PITCH_DOWN = 60; constexpr uint8_t MAX_ANGLE_PITCH_UP = 120; // Вычисляем средний угол поворота сервопривода головы влево-вправо constexpr uint8_t MID_ANGLE_YAW = (MAX_ANGLE_YAW_R + MAX_ANGLE_YAW_L) / 2; // Вычисляем средний угол поворота сервопривода головы вверх-вниз constexpr uint8_t MID_ANGLE_PITCH = (MAX_ANGLE_PITCH_DOWN + MAX_ANGLE_PITCH_UP) / 2; // Создаём переменную для хранения текущего положения сервопривода головы влево-вправо uint8_t angleYaw = MID_ANGLE_YAW; // Создаём переменную для хранения текущего положения сервопривода головы вверх-вниз uint8_t anglePitch = MID_ANGLE_PITCH; // Создаём константу для хранения паузы между поворотом вала сервопривода constexpr uint8_t ANGLE_RANGE = 2; // Создаём иконку «Взгляд прямо» в шестнадцатеричной системе HEX // Иконка для левого и правого глаза одинаковая // Эмоции рисуем в редакторе изображений для LED-матрицы // https://amperka.github.io/led-matrix-editor/ constexpr uint8_t ICON_EYE_STRAIGHT[] PROGMEM { 0x7e, 0x81, 0x81, 0x99, 0x99, 0x81, 0x81, 0x7e }; // Создаём иконку «Взгляд влево» в шестнадцатеричной системе HEX // Иконка для левого и правого глаза одинаковая constexpr uint8_t ICON_EYE_LEFT[] PROGMEM { 0x7e, 0x81, 0x81, 0xe1, 0xe1, 0x81, 0x81, 0x7e }; // Создаём иконку «Взгляд вправо» в шестнадцатеричной системе HEX // Иконка для левого и правого глаза одинаковая constexpr uint8_t ICON_EYE_RIGHT[] PROGMEM { 0x7e, 0x81, 0x81, 0x87, 0x87, 0x81, 0x81, 0x7e }; // Создаём иконку «Взгляд вверх» в шестнадцатеричной системе HEX // Иконка для левого и правого глаза одинаковая constexpr uint8_t ICON_EYE_UP[] PROGMEM { 0x7e, 0x99, 0x99, 0x81, 0x81, 0x81, 0x81, 0x7e }; // Создаём иконку «Взгляд вниз» в шестнадцатеричной системе HEX // Иконка для левого и правого глаза одинаковая constexpr uint8_t ICON_EYE_DOWN[] PROGMEM { 0x7e, 0x81, 0x81, 0x81, 0x81, 0x99, 0x99, 0x7e }; // Создаём иконку «Взгляд вверх-влево» в шестнадцатеричной системе HEX // Иконка для левого и правого глаза одинаковая constexpr uint8_t ICON_EYE_UP_LEFT[] PROGMEM { 0x7e, 0xe1, 0xe1, 0x81, 0x81, 0x81, 0x81, 0x7e }; // Создаём иконку «Взгляд вверх-вправо» в шестнадцатеричной системе HEX // Иконка для левого и правого глаза одинаковая constexpr uint8_t ICON_EYE_UP_RIGHT[] PROGMEM { 0x7e, 0x87, 0x87, 0x81, 0x81, 0x81, 0x81, 0x7e }; // Создаём иконку «Взгляд вниз-влево» в шестнадцатеричной системе HEX // Иконка для левого и правого глаза одинаковая constexpr uint8_t ICON_EYE_DOWN_LEFT[] PROGMEM { 0x7e, 0x81, 0x81, 0x81, 0x81, 0xe1, 0xe1, 0x7e }; // Создаём иконку «Взгляд вниз-вправо» в шестнадцатеричной системе HEX // Иконка для левого и правого глаза одинаковая constexpr uint8_t ICON_EYE_DOWN_RIGHT[] PROGMEM { 0x7e, 0x81, 0x81, 0x81, 0x81, 0x87, 0x87, 0x7e }; // Создаём иконку «Глаза выключены» в шестнадцатеричной системе HEX // Иконка для левого и правого глаза одинаковая constexpr uint8_t ICON_EYE_OFF[] PROGMEM { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; // Создаём константу для хранения статуса слежения глазами за поворотом робоголовы constexpr bool STATE_LOOK_TURN = false; // Создаём перечисление состояний робота с соответствующей переменной enum { ROBOT_ON, // Робот включен ROBOT_OFF, // Робот выключен } robotState; // Создаём перечисление кодов и ИК-пульта с соответствующей переменной enum CODE { POWER = 0x0B, LEFT = 0x08, RIGHT = 0x19, UP = 0x06, DOWN = 0x1D, UP_LEFT = 0x0A, UP_RIGHT = 0x1A, DOWN_LEFT = 0x18, DOWN_RIGHT = 0x04, RED = 0x00, GREEN = 0x03, BLUE = 0x01 } code; void setup() { // Подключаем сервомоторы головы влево-вправо и вниз-вверх servoYaw.attach(SERVO_YAW_PIN); servoPitch.attach(SERVO_PITCH_PIN); // Приравниваем текущим положениям вала сервоприводов // влево-вправо и вверх-вниз среднее значение angleYaw = MID_ANGLE_YAW; anglePitch = MID_ANGLE_PITCH; // Устанавливаем углы сервоприводов по умолчанию servoYaw.write(angleYaw); servoPitch.write(anglePitch); // Инициализируем ИК-приёмник IrReceiver.begin(IR_RECEIVE_PIN); // Инициализируем матрицы matrixL.begin(); matrixR.begin(); // Очищаем матрицы matrixL.clear(); matrixR.clear(); // Отображаем на матрицах иконку «Взгляд прямо» drawIcon(ICON_EYE_STRAIGHT, ICON_EYE_STRAIGHT); // Устанавливаем таймер в режим остановки после срабатывания timer.setTimerMode(); // Передаём случайное число с пина A1 // для последующей генерации случайных чисел randomSeed(analogRead(A1)); // Устанавливаем режим «Робот включен» robotState = ROBOT_ON; } void loop() { // Если робот включен if (robotState == ROBOT_ON) { // Переходим в функцию обработки режима «Робот включен» handleRobotOn(); } // Если робот выключен if (robotState == ROBOT_OFF) { // Переходим в функцию обработки режима «Робот выключен» handleRobotOff(); } } // Функция отображения иконки поворотов на матрицах void drawIconTurn(uint8_t* iconEyeL, uint8_t* iconEyeR) { // Если статус активности слежение глаз за поворотом включен if (STATE_LOOK_TURN) { // Отображаем на матрицах полученные иконки matrixL.drawBitmapF(iconEyeL); matrixR.drawBitmapF(iconEyeR); } } // Функция отображения иконки на матрицах void drawIcon(uint8_t* iconEyeL, uint8_t* iconEyeR) { // Отображаем на матрицах полученные иконки matrixL.drawBitmapF(iconEyeL); matrixR.drawBitmapF(iconEyeR); } // Функция обработки режима «Робот выключен» void handleRobotOff() { // Выключаем глаза drawIcon(ICON_EYE_OFF, ICON_EYE_OFF); // Если робот выключен while (robotState == ROBOT_OFF) { // Переходим в функцию обработки ИК-приёмника в режиме «Робот выключен» remoteHandlerRobotOff(); } } // Функция обработки режима «Робот включен» void handleRobotOn() { // Включаем глаза drawIcon(ICON_EYE_STRAIGHT, ICON_EYE_STRAIGHT); // Если робот включен while (robotState == ROBOT_ON) { // Переходим в функцию обработки ИК-приёмника в режиме «Робот включен» remoteHandlerRobotOn(); } } // Функция обработки ИК-приёмника в режиме «Робот выключен» void remoteHandlerRobotOff() { // Если пришёл новый сигнал на ИК-приёмник, декодируем его if (IrReceiver.decode()) { // Создаём переменную и присваиваем ей декодируемый код кнопки uint32_t code = IrReceiver.decodedIRData.command; // Если нажата кнопка «Включение/выключение» if (code == POWER) { // Даём роботу 500 мс на включение delay(500); // Включаем робота robotState = ROBOT_ON; } // Разрешаем обрабатывать следующий сигнал IrReceiver.resume(); } } // Функция обработки ИК-приёмника в режиме «Робот включен» void remoteHandlerRobotOn() { // Если пришёл новый сигнал на ИК-приёмник, декодируем его if (IrReceiver.decode()) { // Создаём переменную и присваиваем ей декодируемый код кнопки uint32_t code = IrReceiver.decodedIRData.command; // Устанавливаем в таймер значение счетчика 500 timer.setTime(500); // Запускаем таймер timer.start(); // Выбираем действие в зависимости от текущего режима игры switch (code) { // Если нажата кнопка «Включение/выключение» case POWER: // Даём роботу 500 мс на выключение delay(500); // Выключаем робота robotState = ROBOT_OFF; break; // Если нажата кнопка «Влево» case LEFT: // Обновляем переменную положение головы влево-вправо angleYaw = angleYaw + ANGLE_RANGE; // Обновляем положения вала влево-вправо servoYaw.write(angleYaw); // Отображаем на матрицах иконку «Взгляд влево» drawIconTurn(ICON_EYE_LEFT, ICON_EYE_LEFT); break; // Если нажата кнопка «Вправо» case RIGHT: // Обновляем переменную положение головы влево-вправо angleYaw = angleYaw - ANGLE_RANGE; // Обновляем положения вала влево-вправо servoYaw.write(angleYaw); // Отображаем на матрицах иконку «Взгляд вправо» drawIconTurn(ICON_EYE_RIGHT, ICON_EYE_RIGHT); break; // Если нажата кнопка «Вверх» case UP: // Обновляем переменную положение головы вверх-вниз anglePitch = anglePitch + ANGLE_RANGE; // Обновляем положения вала вверх-вниз servoPitch.write(anglePitch); // Отображаем на матрицах иконку «Взгляд вверх» drawIconTurn(ICON_EYE_UP, ICON_EYE_UP); break; // Если нажата кнопка «Вниз» case DOWN: // Обновляем переменную положение головы вверх-вниз anglePitch = anglePitch - ANGLE_RANGE; // Обновляем положения вала вверх-вниз servoPitch.write(anglePitch); // Отображаем на матрицах иконку «Взгляд вниз» drawIconTurn(ICON_EYE_DOWN, ICON_EYE_DOWN); break; // Если нажата кнопка «Вверх-влево» case UP_LEFT: // Обновляем переменную положение головы влево-вправо angleYaw = angleYaw + ANGLE_RANGE; // Обновляем переменную положение головы вверх-вниз anglePitch = anglePitch + ANGLE_RANGE; // Обновляем положения вала влево-вправо servoYaw.write(angleYaw); // Обновляем положения вала вверх-вниз servoPitch.write(anglePitch); // Отображаем на матрицах иконку «Взгляд вверх-влево» drawIconTurn(ICON_EYE_UP_LEFT, ICON_EYE_UP_LEFT); break; // Если нажата кнопка «Вверх-вправо» case UP_RIGHT: // Обновляем переменную положение головы влево-вправо angleYaw = angleYaw - ANGLE_RANGE; // Обновляем переменную положение головы вверх-вниз anglePitch = anglePitch + ANGLE_RANGE; // Обновляем положения вала влево-вправо servoYaw.write(angleYaw); // Обновляем положения вала вверх-вниз servoPitch.write(anglePitch); // Отображаем на матрицах иконку «Взгляд вверх-влево» drawIconTurn(ICON_EYE_UP_RIGHT, ICON_EYE_UP_RIGHT); break; // Если нажата кнопка «Вниз-влево» case DOWN_LEFT: // Обновляем переменную положение головы влево-вправо angleYaw = angleYaw + ANGLE_RANGE; // Обновляем переменную положение головы вверх-вниз anglePitch = anglePitch - ANGLE_RANGE; // Обновляем положения вала влево-вправо servoYaw.write(angleYaw); // Обновляем положения вала вверх-вниз servoPitch.write(anglePitch); // Отображаем на матрицах иконку «Взгляд вниз-влево» drawIconTurn(ICON_EYE_DOWN_LEFT, ICON_EYE_DOWN_LEFT); break; // Если нажата кнопка «Вниз-вправо» case DOWN_RIGHT: // Обновляем переменную положение головы влево-вправо angleYaw = angleYaw - ANGLE_RANGE; // Обновляем переменную положение головы вверх-вниз anglePitch = anglePitch - ANGLE_RANGE; // Обновляем положения вала влево-вправо servoYaw.write(angleYaw); // Обновляем положения вала вверх-вниз servoPitch.write(anglePitch); // Отображаем на матрицах иконку «Взгляд вниз-влево» drawIconTurn(ICON_EYE_DOWN_RIGHT, ICON_EYE_DOWN_RIGHT); break; case RED: // Отключаем ИК-приёмник IrReceiver.stop(); // Вызываем функцию генератора последовательных длинных семплов toneLongSamples(); // Вызываем функцию генератора непоследовательных коротких семплов toneShortSamples(); // Запускаем ИК-приёмник IrReceiver.start(); break; case GREEN: // Отключаем ИК-приёмник IrReceiver.stop(); // Вызываем функцию генератора последовательных длинных семплов toneLongSamples(); // Вызываем функцию генератора непоследовательных коротких семплов toneShortSamples(); // Запускаем ИК-приёмник IrReceiver.start(); break; case BLUE: // Отключаем ИК-приёмник IrReceiver.stop(); // Вызываем функцию генератора последовательных длинных семплов toneLongSamples(); // Вызываем функцию генератора непоследовательных коротких семплов toneShortSamples(); // Запускаем ИК-приёмник IrReceiver.start(); break; } // Разрешаем обрабатывать следующий сигнал IrReceiver.resume(); } // Если таймер досчитал до конца if (timer.tick()) { // Отображаем на матрицах иконку «Взгляд прямо» drawIcon(ICON_EYE_STRAIGHT, ICON_EYE_STRAIGHT); } } // Функция генерации последовательных длинных семплов void toneLongSamples() { // Генерируем случайное количество семплов от 1 до 6 int countSamples = random(1, 7); // Перебираем нумерацию семплов в цикле for (int i = 0; i < countSamples; i++) { // Генерируем случайное число: true или false bool typeSamples = random(0, 2); // Если тип семпла true if (typeSamples) { // Вызываем функцию генерации тона toneSlowDownFastUp: // Сначала медленный убывающий тон со случайными задержками // Затем быстрый возрастающий тон со случайными задержками toneSlowDownFastUp(); } else { // Если тип семпла false // Вызываем функцию генерации тона toneSlowUpFastDown: // Сначала медленный возрастающий тон со случайными задержками // Затем быстрый убывающий тон со случайными задержками toneSlowUpFastDown(); } } } // Функция генерации непоследовательных коротких семплов void toneShortSamples() { // Генерируем случайное количество семплов от 3 до 9 int countSamples = random(3, 10); // Генерируем случайную частоту на базе FREQUENCY int frequency = random(MIN_FREQUENCY, MAX_FREQUENCY); for (int i = 0; i <= countSamples; i++) { // Активируем глаза drawIcon(ICON_EYE_STRAIGHT, ICON_EYE_STRAIGHT); // Генерируем случайный интервал на базе FREQUENCY int range = random(-FREQUENCY, FREQUENCY); tone(BUZZER_PIN, frequency + range); // Выполняем случайную задержку от 70 до 170 мс delay(random(70, 170)); // Деактивируем глаза drawIcon(ICON_EYE_OFF, ICON_EYE_OFF); // Выключаем звук noTone(BUZZER_PIN); // Выполняем случайную задержку от 0 до 30 мс delay(random(0, 30)); } } // Функция генерации звуковой последовательности // Сначала медленный убывающий тон со случайными задержками // Затем быстрый возрастающий тон со случайными задержками void toneSlowDownFastUp() { // Генерируем случайную частоту на базе FREQUENCY int frequency = random(MIN_FREQUENCY, MAX_FREQUENCY); // Активируем глаза drawIcon(ICON_EYE_STRAIGHT, ICON_EYE_STRAIGHT); // Генерируем медленный понижающий тон со случайными задержками for (int range = 0; range <= random(100, 1000); range++) { tone(BUZZER_PIN, frequency - (range * 2)); delayMicroseconds(random(0, 1000)); } // Деактивируем глаза drawIcon(ICON_EYE_OFF, ICON_EYE_OFF); // Генерируем быстрый повышающий тон со случайными задержками for (int range = 0; range <= random(100, 1000); range++) { tone(BUZZER_PIN, frequency + (range * 10)); delayMicroseconds(random(0, 1000)); } } // Функция генерации звуковой последовательности // Сначала медленный возрастающий тон со случайными задержками // Затем быстрый убывающий тон со случайными задержками void toneSlowUpFastDown() { // Генерируем случайную частоту на базе FREQUENCY int frequency = random(MIN_FREQUENCY, MAX_FREQUENCY); // Активируем глаза drawIcon(ICON_EYE_STRAIGHT, ICON_EYE_STRAIGHT); // Генерируем медленный возрастающий тон со случайными задержками for (int range = 0; range <= random(100, 1000); range++) { tone(BUZZER_PIN, frequency + (range * 2)); delayMicroseconds(random(0, 1000)); } // Деактивируем глаза drawIcon(ICON_EYE_OFF, ICON_EYE_OFF); // Генерируем быстрый понижающий тон со случайными задержками for (int range = 0; range <= random(100, 1000); range++) { tone(BUZZER_PIN, frequency - (range * 10)); delayMicroseconds(random(0, 1000)); } }