- Введение
- Техническое задание
- Подбор комплектующих
- Электрическая схема
- Сборка
- Программирование
Введение
Платформа Arduino прочно закрепилась среди любителей автоматизированных систем и на её основе уже созданы тысячи всевозможных проектов. Данная статья раскроет ещё одно направление, в котором можно применить эту популярную платформу. Здесь будет описан процесс создания электронной мишени, которая должна вести подсчёт количества попаданий и выводить результат на устройство индикации. Но просто считать очки – это скучно, поэтому в проект целесообразно добавить несколько уровней сложности. Данное устройство поможет оценить меткость и скорость реакции стрелка, например, при сдаче зачёта или просто стать хорошим дополнением для любителей пострелять по мишеням ради развлечения.
Техническое задание
Как правило, для успешной реализации идеи, её необходимо трансформировать в техническое задание. И чем тщательнее оно будет проработано, тем меньше головной боли получим при составлении программного кода. Думаю, рационально начать с требований к проекту и алгоритма его работы, а затем уже переходить к подбору «железа».
Общие требования и алгоритм работы
Итак, электронная мишень должна представлять собой некое устройство, которое считывает попадания пластиковым или металлическим шариком, при этом фиксирует его в виде световой и звуковой индикации, и параллельно выводит данные на дисплей. Так как в проекте предусмотрено несколько режимов работы, то для удобства их выбора необходимо реализовать что-то похожее на меню. Ввиду того, что мишень может быть удалена от стрелка, управление меню должно вестись дистанционно, например, при помощи любого пульта ДУ. Чувствительный элемент, фиксирующий попадания, должен быть выполнен в виде ударопрочной пластины с закреплённым на ней датчиком вибрации. «Мозгом» электронной мишени будет само собой плата Arduino.
Алгоритм работы
В ходе некоторых размышлений был определён общий концепт и алгоритм работы электронной мишени для каждого из режимов работы, а именно:
При подаче питания мигают красный и зелёный светодиоды, бузер дважды издаёт звуковой сигнал. На дисплее появляется главное меню по типу нижеследующего:
Главное меню
На данном этапе пользователь выбирает для себя один из трёх возможных режимов работы с электронной мишенью. Переключение пунктов меню и выбор одного из них производятся соответствующими клавишами «ВВЕРХ», «ВНИЗ» и «ОК» пульта ДУ.
Режим «EASY»
В этом режиме на экран выводится следующая информация:
Система ожидает от пользователя подтверждения о начале игры и как только оно будет получено, отобразиться основной экран соответствующий данному режиму (см. ниже). Бузер издаст секундный писк и загорится красный светодиод, символизирующий начало сканирования мишени.
С этого момента Arduino начинает циклически опрашивать датчик вибрации. Если происходит попадание, бузер в течение 4-х секунд должен пищать на максимально высокой ноте. При этом красный светодиод гаснет и загорается зелёный. В нижней части дисплея вместо нуля появляется цифра «1». Через 4 секунды бузер замолкает и снова загорается красный светодиод, ожидая следующего выстрела. Цикл повторяется.
Режим «MEDIUM»
Этот режим, в отличие от предыдущего, немного интереснее, так как в него будет введён дополнительный параметр «RANDOM TIME». Значение устанавливается в секундах и означает время, в течение которого мишень будет активна для попадания. Пример соответствующего экрана показан ниже.
Допустим, это время будет настраиваться в пределах от 3 до 99 секунд. После выбора необходимого значения клавишами «ВВЕРХ» и «ВНИЗ» пульта ДУ и последующего нажатия «ОК», бузер включается на одну секунду. При этом оба светодиода находятся в погасшем состоянии. Стрелок видит следующий экран:
С этого момента генерируется случайное число из диапазона от 3 секунд до RANDOM TIME, которое установил пользователь. Это и есть тот период, в течение которого необходимо попасть в мишень. Каждый раз он случайный, но всегда лежит в заданном диапазоне.
Итак, далее загорается красный светодиод, что говорит об ожидании выстрела. Если в течение конкретного интервала стрелок успевает поразить мишень, то он получает 1 балл, в противном случае балл отнимается с соответствующим световым и звуковым сопровождением. Цикл повторяется.
Режим «HARD»
В отличие от двух предыдущих режимов, этот будет самый замороченный. Изначально пользователю предоставляется экран предварительных настроек (см. ниже).
Параметр SHOOT TIME отвечает за общее время активности мишени и лежит в диапазоне от 1 до 999 секунд. SHOOT INTERVAL включает в себя 5 параметров, каждый из которых означает промежуток времени, в течение которого мишень будет доступна для поражения. Это время может быть равно от 1 до 99 секунд. Настройка каждого из значений производиться аналогично предыдущим режимам, нажатие кнопки «ОК» на пульте ДУ сохраняет текущий параметр и переводит курсор к следующему. После сохранения последнего параметра, высвечивается главный действующий экран hard-режима, показанный ниже.
В левой части экрана отображается количество совершённых попыток, а в правой количество удачных попаданий. Игра длиться до тех пор, пока не закончится время (SHOOT TIME). Попадание засчитывается, если оно произошло в отведённое время для текущего таймслота SHOOT INTERVAL. Таймслоты, установленные пользователем чередуются по кругу. Если попадание не произошло, баллы в правой части экрана уменьшаются. Таким образом можно получить некоторую статистику.
Также во всех случаях навигации по меню, целесообразно будет предусмотреть кнопку «ВЫХОД», чтобы иметь возможность возврата на верхний уровень.
Подбор комплектующих
После теоретического определения требований к проекту, можно приступать к подбору комплектующих. Начнём с главного элемента – платы Arduino. Исходя из вышеизложенного, с указанной задачей способна справиться любая плата из этой линейки. Пусть это будет Arduino Nano. Она имеет небольшой размер и легко программируется без посторонних средств.
В качестве дисплея будет применён хорошо зарекомендовавший себя графический ЖКИ 84х48 от Nokia5110 на базе контроллера Philips PCD8544.
Светодиоды, фиксирующие готовность мишени или попадания, подойдут любые. Для удобства лучше взять разные цвета: красный и зелёный. Я в своём проекте использую готовый модуль KY-011.
Излучатель звука будет применён пьезоэлектрический, чтобы не нагружать выход микроконтроллера. Важным условием является отсутствие в нём встроенного генератора, что позволит управлять частотой звука.
Для возможности приёма сигнала пульта дистанционного управления с последующей его дешифрацией, в проекте используется модуль ИК-приёмника KY-022 на базе популярной микросхемы TSOP1738.
Одним из основных элементов схемы является датчик удара/вибрации. Мною были протестированы несколько претендентов, но лучший результат по стабильности показал SW-520D, который не дал ни одного сбоя в течение всего периода тестирования.
Для наглядности, ниже приведена таблица с внешним видом всех комплектующих.
Необходимо приобрести модули для Arduino?Купить arduino модули можно в нашем магазине 3DIY с доставкой по всей России!
Электрическая схема
Итак, техническое задание составлено, комплектующие подобраны, осталось все это правильно соединить между собой. Здесь не потребуется прилагать много усилий, так как мы используем довольно распространённые элементы и модули. Единственное, на что следует обратить особое внимание – подключение датчика вибрации. Так как пуля при ударе о мишень даёт очень короткий импульс, то и фиксировать его нужно быстро. Следовательно, датчик удара целесообразно подцепить на вход обработки внешнего прерывания Arduino. Такими входами могут быть пины 2 или 3. Пусть в этом проекте будет использован пин под номером 2. Ниже приведена электрическая схема электронной мишени.
Обратите внимание, что пин D2 платы Arduino Nano подтянут к плюсу питания через резистор 10 кОм. Это необходимо для того, чтобы исключить наводки и как следствие ложные срабатывания датчика. Выводы D6 и D7 управляют работой светодиодов через защитные резисторы, номиналом 330 Ом. ЖКИ дисплей Nokia5110 лучше питать от 3.3В, во избежание выхода его из строя.
На нижеприведенном фото можно наглядно увидеть, как выглядит схема после сборки на макетной плате. На данном этапе датчик удара просто висит в воздухе и впоследствии будет закреплён на ударной части самой мишени.
Сборка чувствительного элемента мишени
Теперь можно приступить к изготовлению той части мишени, по которой непосредственно будет вестись стрельба. Для этого подойдёт любая ударопрочная пластина из твёрдого материала, которая не поглощает силу удара, а равномерно распределяет вибрацию по всей своей поверхности. В моём случае идеально подошёл кусок фольгированного гетинакса, который найдётся практически у каждого радиолюбителя. С тыльной стороны пластины закрепим датчик вибрации, а с лицевой – изображение мишени, распечатанное на бумаге. Чтобы территориально разнести чувствительный элемент и схему можно использовать 4-х жильную витую пару. При наличии экрана в кабеле, его лучше соединить с минусом питания. Схематически это выглядит следующим образом:
Прикрепить датчик удара к пластине можно при помощи пластиковых хомутов. Никакого двухстороннего скотча или термоклея. Крепление должно выдерживать большие встряски.
И ещё один момент. Те, кто работал с датчиками SW-520D, наверняка знают, что сигнал на его выходе может быть разным и зависит от текущего угла наклона. Это обусловлено особенностью конструктивного исполнения. Наглядно это можно понять из нижеприведенной иллюстрации.
В концепции данного проекта не имеет значения, под каким углом будет закреплён датчик на пластите. Программа будет запоминать последнее значение, и принимать его за состояние покоя. Любое изменение этого состояния будет идентифицировано как выстрел. Ниже на фото показан мой вариант исполнения датчика (вид сзади и спереди).
Программирование
Перед тем, как с головой окунутся в составление основного программного кода, необходимо выполнить кое-какие манипуляции. Так как навигация по меню предусмотрена посредствам пульта дистанционного управления, необходимо узнать коды, которые этот самый пульт посылает приёмнику при нажатии той или иной клавиши. В последствие эти коды будут прописаны в основном скетче. Сделать эту процедуру понадобится всего один раз.
Итак, для начала нам понадобится библиотека для работы с приёмником TSOP1738, которая называется « IRremote.h». После её добавления в среду Arduino IDE пишем следующий код:
#include <IRremote.h> int RECV_PIN = 8; // Пин, к которому подключен выход ИК-приёмника IRrecv irrecv(RECV_PIN); decode_results results; void setup() { Serial.begin(9600); Serial.println("Enabling IRin"); irrecv.enableIRIn(); // Активируем ИК-приёмник Serial.println("Enabled IRin"); } void loop() { if (irrecv.decode(&results)) { // Если пришли данные с пульта ДУ Serial.println(results.value, HEX); // Выводим код нажатой клавиши irrecv.resume(); // Принимаем следующее значение } delay(100); }
После этого, загружаем программу в Arduino Nano, открываем монитор серийного порта и поочерёдно нажимаем клавиши «ВВЕРХ», «ВНИЗ», «ВПРАВО», «ВЛЕВО», «ОК», «EXIT». При этом в терминале должен выводиться шестнадцатеричный код текущей клавиши. В моём случае я получил следующую серию кодов:
В зависимости от типа и серии пульта, коды будут соответственно отличаться. Чтобы не забыть что к чему, лучше сразу зафиксировать полученный результат в виде следующей таблицы:
Клавиша пульта ДУ | Код |
Вверх | 0x2FD9867 |
Вниз | 0x2FDB847 |
Вправо | 0x2FDE21D |
Влево | 0x2FD629D |
Ok | 0x2FD50AF |
Exit | 0x2FD22DD |
Теперь, до написания основного кода остался всего один шаг, а именно скачивание библиотек для работы с LCD Nokia5110. Библиотек понадобится всего две: « Adafruit_PCD8544.h» и « Adafruit_GFX.h». Добавляем их в среду Arduino IDE и следом копируем код основной программы, который я постарался максимально закомментировать.
#include <IRremote.h> #define RECV_PIN 8 // Пин для подключения ИК-приёмника IRrecv irrecv(RECV_PIN); decode_results results; // ---------------------------------------- // Коды кнопок с пульта управления // Данные коды необходимо заменить на свои #define UP 0x2FD9867 #define DOWN 0x2FDB847 #define RIGHT 0x2FDE21D #define LEFT 0x2FD629D #define OK 0x2FD50AF // ---------------------------------------- // Библиотеки для работы с дисплеем NOKIA_5110 #include <SPI.h> #include <Adafruit_GFX.h> #include <Adafruit_PCD8544.h> // Подключение дисплея к аппаратному SPI Arduino // SCLK - пин №13 // DIN - пин №11 // D/C - пин №5 // CS - пин №4 // RST - пин №3 Adafruit_PCD8544 display = Adafruit_PCD8544(13, 11, 5, 4, 3); #define pinGreenLed 6 // Пин для подключения зелёного светодиода #define pinRedLed 7 // Пин для подключения красного светодиода #define pinBuzzer 9 // Пин для подключения буззера bool flagDraw = true; // Флаг, разрешающий отрисовку изображения на дисплее volatile int menuCursor = 0; // Номер отображаемого в данный момент экрана volatile uint8_t globalState = 0; // Переменная глобального состояния системы uint32_t ledBlinkTimer = 0; // Таймер для отсчёта периода мигания светодиодов // Макроопределения режимов стрельбы #define EASY 1 #define MEDIUM 2 #define HARD 3 volatile uint8_t shootMode = 0; // Текущий режим стрельбы volatile bool shootFlag = false; // Флаг, разрешающий обработку датчика вибрации в прерывании volatile uint16_t shootScore = 0; // Кол-во баллов/попаданий uint8_t randomTime = 3; // Случайное время, в течение которого можно поразить мишень в режиме MEDIUM uint32_t shootTime = 1; // Общее время работы мишени в режиме HARD int shootInterval[5] = {0, 0, 0, 0, 0}; // Секунды, в течение которых мишень доступна для поражения int hardModeCursor = 0; // Курсор для настройки разных параметров в режиме HARD int TRY = 0; // Кол-во попыток, данных в режиме HARD volatile int shootIntervalCursor = 0; // Курсор переключения между интервалами активности мишени // Переменные для режима MEDIUM uint32_t mm_randomTime; // Случайное время в пределах от 3 сек. до RandomTime, установленном в меню uint32_t mm_randomTimer; // Таймер для отсчёта случайного времени // Переменные для режима HARD uint32_t hm_Time; uint32_t hm_Timer; uint32_t shootTimer; /* Функция подачи звукового сигнала низкого тона */ void beep() { for(int i = 0; i < 500; i++) { digitalWrite(pinBuzzer, HIGH); delay(1); digitalWrite(pinBuzzer, LOW); delay(1); } } /* Функция подачи звукового сигнала высокого тона */ void beep_1() { for(int i = 0; i < 500; i++) { digitalWrite(pinBuzzer, HIGH); delayMicroseconds(500); digitalWrite(pinBuzzer, LOW); delayMicroseconds(500); } } /* Функция предустановок */ void setup() { attachInterrupt(0, shoot, CHANGE); // Внешнее прерывание на выводе D2 pinMode(pinGreenLed, OUTPUT); digitalWrite(pinGreenLed, LOW); pinMode(pinRedLed, OUTPUT); digitalWrite(pinRedLed, LOW); pinMode(pinBuzzer, OUTPUT); Serial.begin(9600); // Инициализация серийного порта irrecv.enableIRIn(); // Инициализация ИК-приёмника display.begin(); // Инициализация дисплея display.setContrast(45); // Установка контраста дисплея display.clearDisplay(); // Очистка дисплея display.setTextSize(1); // Установка мелкого шрифта display.setTextColor(BLACK); // Чёрный цвет текста display.setCursor(18, 0); display.println("E-TARGET"); display.display(); beep(); } /* Функция отображения меню */ void showMenu(uint8_t flagMenu) { if(flagDraw) { display.clearDisplay(); // Выводим одну и ту же надпись для 3-х повторяющихся пунктов if(flagMenu < 3) { display.setTextSize(1); display.setTextColor(BLACK); display.setCursor(18, 0); display.println("E-TARGET"); display.drawLine(0, 8, 84, 8, BLACK); display.setCursor(4, 10); display.println("SHOOTING MODE"); display.drawLine(0, 18, 84, 18, BLACK); } if(flagMenu == 0) { display.fillRoundRect(0, 20, 33, 9, 3, BLACK); display.setTextColor(WHITE); display.setCursor(5, 21); display.println("EASY"); display.setTextColor(BLACK); display.setCursor(5, 30); display.println("MEDIUM"); display.setCursor(5, 39); display.println("HARD"); } else if(flagMenu == 10) { display.setTextSize(1); display.setTextColor(BLACK); display.setCursor(18, 0); display.println("E-TARGET"); display.fillRoundRect(0, 8, 83, 9, 3, BLACK); display.setTextColor(WHITE); display.setCursor(15, 9); display.println("EASY MODE"); display.setTextColor(BLACK); display.setCursor(18, 18); display.println("Press OK"); display.setTextSize(3); display.setCursor(35, 27); display.println("0"); } else if(flagMenu == 100) { display.setTextSize(1); display.setTextColor(BLACK); display.setCursor(18, 0); display.println("E-TARGET"); display.fillRoundRect(0, 8, 83, 9, 3, BLACK); display.setTextColor(WHITE); display.setCursor(15, 9); display.println("EASY MODE"); display.setTextColor(BLACK); display.setCursor(9, 18); display.println("SHOOT SCORE"); display.setTextSize(3); if(shootScore < 10) display.setCursor(35, 27); else if(shootScore > 9 && shootScore < 100) display.setCursor(25, 27); else if(shootScore > 99 && shootScore < 1000) display.setCursor(17, 27); else if(shootScore > 999 && shootScore < 10000) display.setCursor(9, 27); display.println(shootScore); } else if(flagMenu == 1) { display.setTextColor(BLACK); display.setCursor(5, 21); display.println("EASY"); display.fillRoundRect(0, 29, 45, 9, 3, BLACK); display.setTextColor(WHITE); display.setCursor(5, 30); display.println("MEDIUM"); display.setTextColor(BLACK); display.setCursor(5, 39); display.println("HARD"); } else if(flagMenu == 11) { display.setTextSize(1); display.setTextColor(BLACK); display.setCursor(18, 0); display.println("E-TARGET"); display.fillRoundRect(0, 8, 83, 9, 3, BLACK); display.setTextColor(WHITE); display.setCursor(9, 9); display.println("MEDIUM MODE"); display.setTextColor(BLACK); display.setCursor(0, 18); display.print("RANDOM TIME:"); if(randomTime < 10) display.print("0"); display.print(randomTime); display.setTextSize(3); display.setCursor(35, 27); display.println("0"); } else if(flagMenu == 110) { display.setTextSize(1); display.setTextColor(BLACK); display.setCursor(18, 0); display.println("E-TARGET"); display.fillRoundRect(0, 8, 83, 9, 3, BLACK); display.setTextColor(WHITE); display.setCursor(9, 9); display.println("MEDIUM MODE"); display.setTextColor(BLACK); display.setCursor(9, 18); display.println("SHOOT SCORE"); display.setTextSize(3); if(shootScore < 10) display.setCursor(35, 27); else if(shootScore > 9 && shootScore < 100) display.setCursor(25, 27); else if(shootScore > 99 && shootScore < 1000) display.setCursor(17, 27); else if(shootScore > 999 && shootScore < 10000) display.setCursor(9, 27); display.println(shootScore); } else if(flagMenu == 2) { display.setTextColor(BLACK); display.setCursor(5, 21); display.println("EASY"); display.setCursor(5, 30); display.println("MEDIUM"); display.fillRoundRect(0, 38, 33, 9, 3, BLACK); display.setTextColor(WHITE); display.setCursor(5, 39); display.println("HARD"); } else if(flagMenu == 21) { display.setTextSize(1); display.setTextColor(BLACK); display.setCursor(18, 0); display.println("E-TARGET"); display.fillRoundRect(0, 8, 83, 9, 3, BLACK); display.setTextColor(WHITE); display.setCursor(15, 9); display.println("HARD MODE"); display.setTextColor(BLACK); display.setCursor(0, 18); display.print("SHOOT TIME:"); if(shootTime < 10) display.print("00"); else if(shootTime < 100) display.print("0"); display.print(shootTime); // Выводим на дисплей все 5 параметров SHOOT INTERVAL display.setCursor(0, 29); display.print("SHOOT INTERVAL"); display.setCursor(0, 38); for(uint8_t i = 0; i < 5; i++) { if(shootInterval[i] < 10) display.print("0"); display.print(shootInterval[i]); display.print(" "); } // Рисуем курсор под тем параметром, который в данный момент необходимо изменить switch(hardModeCursor) { case 0: display.drawLine(65, 26, 84, 26, BLACK); break; case 1: display.drawLine(0, 46, 10, 46, BLACK); break; case 2: display.drawLine(18, 46, 28, 46, BLACK); break; case 3: display.drawLine(36, 46, 46, 46, BLACK); break; case 4: display.drawLine(54, 46, 64, 46, BLACK); break; case 5: display.drawLine(72, 46, 82, 46, BLACK); break; } } else if(flagMenu == 210) { display.setTextSize(1); display.setTextColor(BLACK); display.setCursor(18, 0); display.println("E-TARGET"); display.fillRoundRect(0, 8, 83, 9, 3, BLACK); display.setTextColor(WHITE); display.setCursor(15, 9); display.println("HARD MODE"); display.setTextColor(BLACK); display.drawLine(42, 18, 42, 48, BLACK); display.drawLine(41, 18, 41, 48, BLACK); display.setCursor(12, 18); display.println("TRY"); display.setCursor(56, 18); display.println("HIT"); display.drawLine(0, 26, 84, 26, BLACK); display.setTextSize(2); display.setCursor(3, 31); if(TRY < 10) display.print("00"); else if((TRY > 9) && (TRY < 100)) display.print("0"); display.print(TRY); display.setCursor(47, 31); if(shootScore < 10) display.print("00"); else if((shootScore > 9) && (shootScore < 100)) display.print("0"); display.println(shootScore); } flagDraw = false; display.display(); } } /* ОСНОВНОЙ ЦИКЛ ПРОГРАММЫ */ void loop() { while(1) { // Если на ИК-приёмник поступил сигнал if (irrecv.decode(&results)) { /* Если на пульте ДУ нажата кнопка "ВНИЗ" */ if(results.value == DOWN) { // Навигация по "ГЛАВНОМУ МЕНЮ" if(menuCursor < 3) { menuCursor++; if(menuCursor > 2) menuCursor = 0; flagDraw = true; } // Уменьшение параметра RANDOM TIME в режиме MEDIUM if(menuCursor == 11) { randomTime--; if(randomTime < 3) randomTime = 99; flagDraw = true; } // Уменьшение одного из параметров в режиме HARD if(menuCursor == 21) { if(hardModeCursor == 0) { // Уменьшаем параметр SHOOT TIME shootTime--; if(shootTime < 1) shootTime = 999; } else if(hardModeCursor > 0) { // Уменьшаем один из параметров SHOOT INTERVAL shootInterval[hardModeCursor - 1]--; if(shootInterval[hardModeCursor-1] < 0) shootInterval[hardModeCursor - 1] = 99; } flagDraw = true; } } /* Если на пульте ДУ нажата кнопка "ВВЕРХ" */ else if(results.value == UP) { // Навигация по "ГЛАВНОМУ МЕНЮ" if(menuCursor < 3) { menuCursor--; if(menuCursor < 0) menuCursor = 2; flagDraw = true; } // Увеличение параметра RANDOM TIME в режиме MEDIUM if(menuCursor == 11) { randomTime++; if(randomTime > 99) randomTime = 3; flagDraw = true; } // Увеличение одного из параметров в режиме HARD if(menuCursor == 21) { if(hardModeCursor == 0) { // Увеличиваем параметр SHOOT TIME shootTime++; if(shootTime > 999) shootTime = 0; } else if(hardModeCursor > 0) { // Увеличиваем один из параметров SHOOT INTERVAL shootInterval[hardModeCursor - 1]++; if(shootInterval[hardModeCursor-1] > 99) shootInterval[hardModeCursor - 1] = 0; } flagDraw = true; } } /* Если на пульте ДУ нажата кнопка "ВПРАВО" */ else if(results.value == RIGHT) { // Перемещаем курсор к следующему параметру в режиме HARD if(menuCursor == 21) { hardModeCursor++; if(hardModeCursor > 5) hardModeCursor = 0; flagDraw = true; } } /* Если на пульте ДУ нажата кнопка "ВЛЕВО" */ else if(results.value == LEFT) { // Перемещаем курсор к предыдущему параметру в режиме HARD if(menuCursor == 21) { hardModeCursor--; if(hardModeCursor < 0) hardModeCursor = 5; flagDraw = true; } } /* Если на пульте ДУ нажата кнопка "ОК" */ else if(results.value == OK) { /* Выход из режима EASY, MEDIUM или HARD*/ if(menuCursor >= 100) { menuCursor = -1; flagDraw = true; globalState = 0; shootMode = 0; shootScore = 0; shootFlag = false; } /* Нажимаем на пульте ОК. Бузер издает сигнал. Включается красный светодиод (нет попадания) */ if(menuCursor == 10) { menuCursor = 100; flagDraw = true; digitalWrite(pinGreenLed, LOW); digitalWrite(pinRedLed, HIGH); beep(); shootMode = EASY; globalState = 3; shootFlag = true; } /* Стреляем в режиме MEDIUM */ if(menuCursor == 11) { menuCursor = 110; flagDraw = true; digitalWrite(pinGreenLed, LOW); digitalWrite(pinRedLed, LOW); beep(); shootMode = MEDIUM; globalState = 5; shootFlag = true; } /* Стреляем в режиме HARD */ if(menuCursor == 21) { menuCursor = 210; flagDraw = true; digitalWrite(pinGreenLed, LOW); digitalWrite(pinRedLed, LOW); beep(); shootMode = HARD; globalState = 7; shootTimer = millis(); shootFlag = true; } // Вход в режим EASY if(menuCursor == 0) { menuCursor = 10; shootScore = 0; flagDraw = true; } // Вход в режим MEDIUM if(menuCursor == 1) { menuCursor = 11; shootScore = 0; flagDraw = true; } // Вход в режим HARD if(menuCursor==2){menuCursor=21;TRY=0;shootScore=0;shootIntervalCursor=0;flagDraw=true;} } irrecv.resume(); } if(globalState == 0) { ledBlinkTimer = millis(); digitalWrite(pinGreenLed, HIGH); digitalWrite(pinRedLed, LOW); globalState = 1; } else if(globalState == 1) { if((millis() - ledBlinkTimer) > 500) { ledBlinkTimer = millis(); digitalWrite(pinGreenLed, LOW); digitalWrite(pinRedLed, HIGH); globalState = 2; } } else if(globalState == 2) { if((millis() - ledBlinkTimer) > 500) globalState = 0; } else if(globalState == 4) { flagDraw = true; showMenu(menuCursor); digitalWrite(pinGreenLed, HIGH); digitalWrite(pinRedLed, LOW); beep(); beep(); digitalWrite(pinGreenLed, LOW); digitalWrite(pinRedLed, HIGH); if(menuCursor == 100) globalState = 3; else if(menuCursor == 110) globalState = 5; else if(menuCursor == 210) { if(shootIntervalCursor < 4) shootIntervalCursor++; globalState = 7; } //flagDraw = true; shootFlag = true; } else if(globalState == 5) { randomSeed(analogRead(0)); mm_randomTime = random(3000, (randomTime * 1000)); Serial.print("randomTime="); Serial.println(mm_randomTime); mm_randomTimer = millis(); digitalWrite(pinRedLed, HIGH); globalState = 6; shootFlag = true; } else if(globalState == 6) { // Если в течение выделенного таймслота нет попадания if((millis() - mm_randomTimer) > mm_randomTime) { if(shootScore > 0) shootScore--; shootFlag = false; beep_1(); digitalWrite(pinRedLed, LOW); delay(200); digitalWrite(pinRedLed, HIGH); delay(200); digitalWrite(pinRedLed, LOW); delay(200); digitalWrite(pinRedLed, HIGH); delay(200); digitalWrite(pinRedLed, LOW); delay(200); flagDraw = true; globalState = 5; } } else if(globalState == 7) { Serial.print("Shoot time:"); Serial.println(shootTime * 1000); hm_Timer = millis(); hm_Time = shootInterval[shootIntervalCursor] * 1000; Serial.print("shootInterval="); Serial.println(hm_Time); digitalWrite(pinRedLed, HIGH); globalState = 8; shootFlag = true; TRY++; flagDraw = true; } else if (globalState == 8) { // Если общее время стрельбы ещё не вышло if((millis() - shootTimer) < (shootTime * 1000)) { // Если в течение выделенного таймслота нет попадания if((millis() - hm_Timer) > hm_Time) { if(shootScore > 0) shootScore--; beep_1(); digitalWrite(pinRedLed, LOW); delay(200); digitalWrite(pinRedLed, HIGH); delay(200); digitalWrite(pinRedLed, LOW); delay(200); digitalWrite(pinRedLed, HIGH); delay(200); digitalWrite(pinRedLed, LOW); delay(200); flagDraw = true; globalState = 7; if(shootIntervalCursor > 0) shootIntervalCursor--; } } else { // Если прошло время, отведённое на стрельбу digitalWrite(pinRedLed, LOW); digitalWrite(pinRedLed, LOW); shootFlag = false; flagDraw = true; globalState = 100; Serial.println("HARD MODE-TIME OUT"); } } showMenu(menuCursor); if(menuCursor == -1) { menuCursor = 0; flagDraw = true; } } } /* Функция-обработчик прерывания */ void shoot() { if(shootMode) { if(shootFlag) { shootFlag = false; globalState = 4; shootScore++; } } }
Если кто был внимателен при прочтении скетча, тот наверняка заменил, что я отказался от кнопки «EXIT», так как посчитал её лишней для данного проекта и выход в верхнее меню осуществляется повторным нажатием кнопки «OK». Ну и наконец, ниже представлены несколько фото работающей мишени, которая доставила массу удовольствия при стрельбе и заставила поработать мозг при её создании.
Режим «EASY»
Режим «MEDIUM»
Режим «HARD»