#2 Игра “Поймай крота”
В этом эксперименте мы будем подключать к Arduino тактовые кнопки, которые будут управлять светодиодами (включать и выключать их).
Перед выполнением эксперимента прочтите:
СБОРКА УСТРОЙСТВА НА МАКЕТНОЙ ПЛАТЕ
Для сборки устройства нам понадобятся следующие компоненты:
- Arduino Uno;
- USB-кабель для подключения к компьютеру;
- Беспаечная макетная плата;
- 4 светодиода разных цветов;
- 4 резистора номиналом 220 Ом;
- 4 тактовых кнопки;
- 4 резистора номиналом 10 кОм;
- 15 соединительных проводов.
Соберите устройство по следующей схеме:
Схема устройства на макетной плате |
Принципиальная схема устройства |
Обратите внимание:
- Все кнопки мы подключили по схеме со стягивающим резистором. Это необходимо для того, чтобы избежать ложного срабатывания из-за наведённых электромагнитных помех (так называемый эффект антенны);
- В этой схеме задействованы рельсы “+” и “-” макетной платы. Это сделано для уменьшения количества проводов на схеме. Все нижние левые контакты кнопок подключены к “плюсовой” рельсе, на которую подаётся сигнал с пина “5V” Arduino. Кроме этого, как кнопки, так и резисторы подключены к пину “GND” через токоограничивающие резисторы с помощью “минусовых рельс”. В данном случае пришлось задействовать как верхнюю, так и нижнию “минусовые” рельсы. Для этого они соединены между собой и подключены к пину “GND” (чёрные провода);
- Помните, что последовательно со светодиодами, мы подключаем резисторы номиналом 220 Ом, для кнопок-же используются резисторы номиналов в 10 КОм, т.е. 10 000 Ом;
- Помните: длинная ножка светодиода подключается к сигнальному пину Arduino, а короткая – уходит на землю. К какой ножке подключать резистор – значения не имеет.
ПРОГРАММИРОВАНИЕ
Для начала, давайте научимся зажигать светодиод по нажатию кнопки. Для этого мы будем использовать крайнюю левую кнопку и крайний левый (красный) светодиод. Кнопка подключена к 6-му пину Arduino, а светодиод к 11-му. При нажатии кнопки, контакты внутри нее замкнутся и на 6-й пин придёт высокий сигнал с пина “5V”. Проверяя 6-й пин на наличие высокого сигнала, мы и будем определять нажата наша кнопка или нет. Для того, чтобы проверить выполнение того или иного события существует оператор “if”:
if (<условие>) {<команды, которые будут исполнятся, если выполняется условие>}
В нашем случае нам нужно проверить пришёл ли на 6-й цифровой пин высокий цифровой сигнал, и если сигнал пришёл – необходимо зажечь светодиод на 11-м пине:
if (digitalRead(6)==HIGH) { digitalWrite(11, HIGH) }
где:
- digitalRead(6) – возвращает значение сигнала, который пришёл на 6-й цифровой пин;
- == – знак проверки равенства (не путать со знаком “=”, который используется для присваивания значений переменным).
В нашем случае, на 6-м пине может быть зарегистрировано лишь 2 вида сигнала: “HIGH” и “LOW”. Тем не менее, “==” – не единственный оператор сравнения, который используется вместе с оператором “if”. Есть и другие, вот их краткое описание:
- == – равно;
- != – не равно;
- < – меньше;
- > – больше.
Для того, чтобы наша программа заработала, нужно не забыть назначить 11-й пин Arduino, к которому подключён светодиод, в качестве выходного пина с помощью команды “pinMode”. Полный текст нашей программы будет выглядеть так:
void setup() { pinMode(11, OUTPUT); } void loop() { if (digitalRead(6)==HIGH){ digitalWrite(11, HIGH); } }
Подключите плату Arduino к компьютеру, загрузите программу в её память и проверьте её работоспособность. Если Вы всё сделали правильно, то при нажатии кнопки, светодиод, подключённый к 11-му пину должен зажигаться. Как его погасить? Давайте сделаем из нашей кнопки переключатель, который будет не только зажигать светодиод, но и гасить его. При этом, мы можем реализовать функцию переключения для 2-х разных случаев:
- Светодиод должен гореть пока нажата кнопка. При прекращении нажатия на кнопку, светодиод должен гаснуть;
- Светодиод должен загораться при нажатии и гаснуть лишь при повторном нажатии.
Напишем алгоритм для первого случая. Для этого, дополним уже имеющуюся программу:
void setup() { pinMode(11, OUTPUT); } void loop() { if (digitalRead(6)==HIGH){ digitalWrite(11, HIGH); } else { digitalWrite(11, LOW); } }
Обратите внимание, мы дополнили оператор “if” оператором “else”, который позволит нам выполнять какие-либо команды, когда условие записанное в “if” не выполняется. Т.е. если кнопка нажата, то на 11-й пин придёт высокий сигнал (HIGH) и светодиод будет зажжён, в противном случае на 11-й пин придёт низкий сигнал (LOW) и светодиод будет потушен. Возможность такого свечения светодиода можно реализовать намного проще. Можно даже обойтись без операторов “if … else”. Мы просто можем подавать на пин светодиода тот сигнал, который пришёл на пин кнопки:
void setup() { pinMode(11, OUTPUT); } void loop() { digitalWrite(11, digitalRead(6)); }
Обратите внимание на то, как сильно упростилась программа. С помощью всего одной команды
digitalWrite(11, digitalRead(6));
мы сумели прочитать сигнал 6-м пине, к которому подключена кнопка. Для этого мы использовали команду “digitalRead”. И отправили этот сигнал на 11-й пин, к которому подключен светодиод с помощью команды “digitalWrite”.
К сожалению, такой вариант управления устройствами с помощью кнопок используется очень редко. Гораздо чаще кнопка используется в качестве переключателя, т.е. с помощью однократного нажатия на кнопку нагрузка включается, а с помощью повторного однократного нажатия – выключается. Давайте реализуем такой вариант управления. В самом простом виде, это можно реализовать следующим образом:
void setup() { pinMode(11, OUTPUT); } void loop() { if (digitalRead(6)==HIGH) { digitalWrite(11, !digitalRead(11)); } }
Разберём алгоритм подробнее:
- с помощью оператора “if” мы проверяем – пришёл ли на 6-й пин высокий цифровой сигнал;
- далее мы проверяем какой сигнал на данный момент подаётся на 11-й цифровой пин, к которому подключен светодиод. Для этого мы используем команду [digitalRead(11)];
- Добавляя к этой команде знак логического отрицания (не) “!”, мы подаём на 11-й пин сигнал, противоположный тому, который существует на пине в данный момент [!digitalRead(11)].
Таким образом, вкратце наш алгоритм можно описать так: если на пин кнопки пришёл высокий цифровой сигнал, то подать на пин светодиода сигнал противоположный тому, который существует на пине в данный момент.
Загрузите программу в плату Arduino и проверьте её работу.
Вы обратили внимание, что программа работает нестабильно? При нажатии кнопки светодиод может зажечься и тут же погаснуть. Это происходит из-за того, что наш алгоритм повторяется очень-очень быстро и при нажатии мы просто не успеваем отпустить кнопку, а в этом время Arduino считывает сигнал на пине кнопки по второму кругу и успевает отключить только что включённый светодиод. Для того, чтобы обойти этот “баг” необходимо существенно усложнить код, поэтому мы пойдём на хитрость – добавим после команды [digitalWrite] небольшую задержку в 300 мс (позже, мы ещё вернёмся к этому вопросу и разберём код для “правильной” работы кнопки):
void setup() { pinMode(11, OUTPUT); } void loop() { if (digitalRead(6)==HIGH) { digitalWrite(11, !digitalRead(11)); delay(300); } }
Загрузите программу в Arduino и проверьте её работоспособность.
Теперь переключение нашего светодиода происходит намного лучше! Попробуйте самостоятельно дополнить программу таким образом, чтобы оставшиеся кнопку включали и выключали оставшиеся светодиоды (ниже, приведён полный код это программы, но попробуйте не подсматривать и справиться с задачей самостоятельно).
Полный код программы, использующей 4 кнопки для переключения 4-х светодиодов будет выглядеть следующим образом:
void setup() { pinMode(8, OUTPUT); pinMode(9, OUTPUT); pinMode(10, OUTPUT); pinMode(11, OUTPUT); } void loop() { if (digitalRead(6)==HIGH) { digitalWrite(11, !digitalRead(11)); delay(300); } if (digitalRead(5)==HIGH) { digitalWrite(10, !digitalRead(10)); delay(300); } if (digitalRead(4)==HIGH) { digitalWrite(9, !digitalRead(9)); delay(300); } if (digitalRead(3)==HIGH) { digitalWrite(8, !digitalRead(8)); delay(300); } }
ИГРА “ПОЙМАЙ КРОТА”
Игра “Кроты” или “Поймай крота” является классической игрой на проверку реакции и существует, наверное, в десятках и сотнях разнообразных вариантов. Суть игры: есть несколько норок из которых время от времени показываются “кроты”, задача игрока состоит в том, чтобы как можно быстрее “поймать” этого крота. При этом, игра может вести счёт пойманных кротов, или ограничивать время на их поимку.
Мы попробуем создать “цифровую” версию этой игры. В нашем случае, чтобы не усложнять код программы, мы не будем вести счёт пойманных кротов и замерять скорость реакции. Алгоритм нашей игры будет очень прост:
- При включении игры, случайным образом должен загораться один из четырёх светодиодов;
- Игрок, как можно скорее, должен нажать на кнопку, находящуюся под этим светодиодом;
- Если игрок нажал правильную кнопку, то светодиод гаснет, а вместо него зажигается следующий случайный светодиод;
- Игра идёт без ведения счёта и времени, и может продолжаться до тех пор, пока не будет отключено питание.
Приведём код игры целиком, а далее подробно разберём его:
int randLED=random(8,12); void setup() { pinMode(8, OUTPUT); pinMode(9, OUTPUT); pinMode(10, OUTPUT); pinMode(11, OUTPUT); } void loop() { digitalWrite(randLED, HIGH); if (digitalRead(3)==HIGH && randLED==8){ digitalWrite(randLED, LOW); randLED=random(8,12); } if (digitalRead(4)==HIGH && randLED==9){ digitalWrite(randLED, LOW); randLED=random(8,12); } if (digitalRead(5)==HIGH && randLED==10){ digitalWrite(randLED, LOW); randLED=random(8,12); } if (digitalRead(6)==HIGH && randLED==11){ digitalWrite(randLED, LOW); randLED=random(8,12); } }
Вначале программы мы объявили переменную [randLED].
Переменная – это место хранения данных. Она имеет имя, значение и тип. Добавление переменной в программу называется “декларацией”, т.е. говорят “мы задекларировали переменную”. Например, команда:
int pin = 13;
создает переменную с именем [pin], значением [13] и типом [int].
- Имя переменной определяется самим пользователем, это могут быть как отдельные буквы, например: “a”, “b”, “c”, так и целые слова или их сокращения, например: “led”, “pin” и т.д. Кроме буквенных символов имя переменной может содержать цифры и знак нижнего подчёркивания, например: “a_1”, “led13”, “rand_int_numb_7” и т.д. Имена переменных принято писать с маленькой буквы.
- Типы переменной определяют вид информации, который может в ней храниться. Переменные бывают следующих типов:
- int — целочисленный тип данных;
- float — тип данных с плавающей запятой (дробный тип);
- double — тип данных с плавающей запятой двойной точности;
- char — символьный тип данных;
- bool — логический тип данных.
- При декларации переменных, различают объявление переменных и инициализацию переменных. При объявлении переменной, для неё задаётся лишь тип и имя, а при инициализации, также и её значение. Например:
- int a; – объявление переменной a целого типа.
- float b; – объявление переменной b типа данных с плавающей запятой.
- double c = 14.2; // инициализация переменной c типа double.
- char d = ‘s’; // инициализация переменной d типа char.
- bool k = true; // инициализация логической переменной k.
Затем в программе мы можем обратиться к данной переменной через её имя для того, чтобы получить её текущее значение. Например, для того, чтобы переключить режим 7-го пина Arduino, к которому подключён светодиод, на выход, мы можем написать команду:
pinMode(7, OUTPUT);
Или можем сделать это с помощью переменной:
int led = 7; pinMode(led, OUTPUT);
Вернёмся к нашей программе. Мы инициализировали переменную [randLED]:
int randLED=random(8,12);
Мы видим, что:
- Это переменная целого типа (int);
- Переменной присвоено значение random(8, 12); – этак команда генерирует случайное значение в диапазоне [8;12), т.е. 12 – не включается в диапазон. Это значит, что при выполнении этой команды, мы запишем в переменную одной из следующих значений -[8, 9, 10, 11]. Это номера пинов, к которым подключены светодиоды в нашей схеме. Т.е. переменная нужна для того, чтобы хранить значение пина на который мы будем подавать высокий сигнал и зажигать свтодиод.
Далее, в разделе [setup], мы назначаем пины со светодиодами в качестве выходных с помощью команд:
pinMode(8, OUTPUT); pinMode(9, OUTPUT); pinMode(10, OUTPUT); pinMode(11, OUTPUT);
В разделе [loop], мы первым делом зажигаем случайный светодиод командой:
digitalWrite(randLED, HIGH);
Обратите внимание, вместо конкретного номера пина в команде [digitalWrite], мы записываем нашу переменную [randLED], внутри которой и хранится случайное значение пина со светодиодом. Например, если значение переменной будет равно 11, то такая команда будет эквивалентна команде:
digitalWrite(11, HIGH);
Итак, светодиод зажжён. Теперь пользователь должен нажать кнопку, которая находится рядом с этим светодиодом. В нашем случае:
- Для светодиода на 11 пине, необходимо нажать кнопку – на 6-м пине;
- Для светодиода на 10 пине, необходимо нажать кнопку – на 5-м пине;
- Для светодиода на 9 пине, необходимо нажать кнопку – на 4-м пине;
- Для светодиода на 8 пине, необходимо нажать кнопку – на 3-м пине;
В это время программа должна провести проверку: какой из светодиодов зажжён и какая кнопка нажата, и только в случае, если зажённому светодиоду соответсвует нужная нажатая кнопка, программа должна погасить этот светодиод. Проверку мы реализовали с помощью оператора “if”. Разберём алгоритм работы кнопки, которая находится на 6-м пине:
if (digitalRead(6)==HIGH && randLED==11){ digitalWrite(randLED, LOW); randLED=random(8,12); }
В условии мы проверяем одновременно наличие 2-х событий:
- нажатой кнопки – [digitalRead(6)==HIGH];
- номера зажжённого светодиода – [randLED==11].
И только если оба этих условия выполняются, т.е. зажата кнопка на 6-м пине и зажжённ светодиод на 11-м пине, то выполняются команды
digitalWrite(randLED, LOW); // гасит светодиод randLED=random(8,12); // выбирает новое случайное значение переменной randLED
Проверить 2 условия одновременно мы смогли с помощью логического оператора “и”, который имеет обозначение “&&”. При использовании такого оператора, команды в скобках { } выполняются только тогда, когда выполняются все условия, между которыми стоит данный оператор. Кроме логического оператора “и”, есть ещё 2 оператора:
- “или” – обозначается “||”. Команды выполняются тогда, когда выполняется хотя бы одно из условий;
- “не” (логическое отрицание) – обозначается “!”. Команды выполняются только тогда, когда не выполняется не одно из условий.
Теперь, когда Вы знаете как работают операторы условия, Вы без труда дополните программу для проверки остальных светодиодов и кнопок. Полученную программу загрузите на плату Arduino и проверьте её работоспособность.
ЗАДАНИЯ ДЛЯ САМОСТОЯТЕЛЬНОГО ВЫПОЛНЕНИЯ
Задание 1:
Измените схему нашей игры, добавив в неё дополнительно 5-й светодиод и 5-ю кнопку. Дополните код программы, чтобы она работала для 5-ти светодиодов и кнопок.
Задание 2*:
Усовершенствуйте игру. Вначале игры помигайте сразу всеми светодиодами. Добавьте в код программы переменную-счётчик [count]. Инициализируйте его, задав начальное значение [int count=0;]. Увеличивайте значение счётчика каждый раз, когда игрок “словит крота” с помощью команды [count=count+1] или просто [count++]. Когда значение счётчика станет равно 10, ещё раз помигайте всеми светодиодами – это будет означать окончание игры. Не забудьте обнулить счётчик. После данных изменений, появится возможность соревноваться с друзьями в “поимке кротов” на время, для этого достаточно запустить секундомер на вашем смартфоне и измерять время между начальными и конечными подмигиваниями всех светодиодов.