Эксперимент 20. Перетягивание каната
← Светильник, управляемый по USB | Оглавление | Конец первой серии экспериментов!
В этом эксперименте мы создаем еще одну игру, на этот раз нужно быстрее соперника нажать кнопку 20 раз.
В старой версии блокнота хакера по ошибке были указаны резисторы на 100 кОм, которых нет в наборе. На самом деле в уроке смело можно и нужно использовать резисторы на 10 кОм из комплекта.
Список деталей для эксперимента
- 1 плата Arduino Uno
- 1 беспаечная макетная плата
- 1 светодиодная шкала
- 10 резисторов номиналом 220 Ом
- 4 резисторов номиналом 10 кОм
- 2 тактовых кнопки
- 2 керамических конденсатора номиналом 100 нФ
- 24 провода «папа-папа»
Для дополнительного задания
- 1 сервопривод
- 1 конденсатор 220 мкФ
Принципиальная схема
Схема на макетке
Обратите внимание
- Схема подключения кнопок с использованием конденсаторов, резисторов и микросхемы 74HC14, которая называется инвертирующий триггер Шмитта, нужна для аппаратного подавления дребезга. Посмотрите видеоурок на эту тему.
- В этом эксперименте нам нужно очень много цифровых портов, поэтому нам пришлось использовать порт 0. Пользоваться им неудобно из-за того, что он соединен с одним из каналов последовательного порта, поэтому перед прошивкой микроконтроллера нам придется отключать провод, идущий к пьезопищалке, а после прошивки подключать его обратно.
Скетч
- p200_button_wrestling.ino
#define BUZZER_PIN 0 #define FIRST_BAR_PIN 4 #define BAR_COUNT 10 #define MAX_SCORE 20 // глобальные переменные, используемые в прерываниях (см. далее) // должны быть отмечены как нестабильные (англ. volatile) volatile int score = 0; void setup() { for (int i = 0; i < BAR_COUNT; ++i) pinMode(i + FIRST_BAR_PIN, OUTPUT); pinMode(BUZZER_PIN, OUTPUT); // Прерывание (англ. interrupt) приостанавливает основную // программу, выполняет заданную функцию, а затем возобновляет // основную программу. Нам нужно прерывание на нажатие кнопки, // т.е. при смене сигнала с высокого на низкий, т.е. на // нисходящем (англ. falling) фронте attachInterrupt(INT1, pushP1, FALLING); // INT1 — это 3-й пин attachInterrupt(INT0, pushP2, FALLING); // INT0 — это 2-й пин } void pushP1() { ++score; } // функция-прерывание 1-го игрока void pushP2() { --score; } // функция-прерывание 2-го игрока void loop() { tone(BUZZER_PIN, 2000, 1000); // даём сигнал к старту. // пока никто из игроков не выиграл, обновляем «канат» while (abs(score) < MAX_SCORE) { int bound = map(score, -MAX_SCORE, MAX_SCORE, 0, BAR_COUNT); int left = min(bound, BAR_COUNT / 2 - 1); int right = max(bound, BAR_COUNT / 2); for (int i = 0; i < BAR_COUNT; ++i) digitalWrite(i + FIRST_BAR_PIN, i >= left && i <= right); } tone(BUZZER_PIN, 4000, 1000); // даём сигнал победы while (true) {} // «подвешиваем» плату до перезагрузки }
Пояснения к коду
- Код нашей обычной программы исполняется инструкция за инструкцией и если мы, например, проверяем состояние датчика, мы к нему обратимся только в те моменты, когда очередь дойдет до соответствующей инструкции. Однако мы можем использовать прерывания:
- по наступлении определенного события
- на определенном порту ход программы будет приостанавливаться для выполнения
- определенной функции, а затем программа продолжит исполняться с того места, где была приостановлена.
- Arduino Uno позволяет делать прерывания на портах 2 и 3.
- В
setup()
прописывается инструкцияattachInterrupt(interrupt, action, event)
, гдеinterrupt
может принимать значенияINT0
илиINT1
для портов 2 и 3 соответственно. Можно задать эти значения и с помощью функцииdigitalPinToInterrupt(pin)
, где вместоpin
указать номер пина.action
— имя функции, которая будет вызываться при наступлении событияevent
— событие, которое мы отслеживаем. Может принимать значениеRISING
(изменение от низкого уровня сигнала к высокому, от 0 к 1),FALLING
(от высокого уровня к низкому, от 1 к 0),CHANGE
(от 0 к 1 или от 1 к 0),LOW
(при низком уровне сигнала).
- Глобальные переменные, к которым мы обращаемся из функции, обрабатывающей прерывания, должны объявляться с использованием ключевого слова
volatile
, как в данном экспериментеvolatile int score = 0
. - Внутри функции, вызываемой по прерыванию, нельзя использовать
delay()
. - Функция
abs(value)
возвращает абсолютное значениеvalue
(значение по модулю). Обратите внимание, что функция может сработать некорректно, если передавать ей выражение, которое еще не вычислено, напримерabs(++a)
, лучше передавать ей просто переменную. - Функция
min(val1, val2)
вернет меньшее изval1
иval2
. - Функция
max(val1, val2)
вернет большее изval1
иval2
. - В данном эксперименте мы вычисляем значение, которое записывается на светодиоды, прямо в
digitalWrite()
- Мы уже знакомы с логическим «и» (
&&
). Нередко нужен оператор «логическое «или»:||
. Он возвращает «истину», если хотя бы один из операндов имеет значение «истина».false || false
вернетfalse
, аtrue || true
,true || false
иfalse || true
вернутtrue
. - Мы использовали
while(true){}
для того, чтобыloop()
остановился после того, как кто-то выиграл: уwhile
всегда истинное условие и он бесконечно ничего не выполняет!
Вопросы для проверки себя
- Каким образом мы подавляем дребезг аппаратно?
- Для чего используются прерывания?
- Каким образом можно включить обработку внешних прерываний?
- О каких нюансах работы с уже известными нам вещами следует помнить при работе с прерываниями?
- Как выбрать максимальное из двух значений? Минимальное?
- Как получить абсолютное значение переменной? Чего следует избегать при использовании этой функции?
- Когда оператор логическое «или» возвращает «ложь»?
Задания для самостоятельного решения
- Вместо светодиодной шкалы подключите сервопривод и измените код таким образом, чтобы перетягивание демонстрировалось путем отклонения сервопривода от среднего положения.
← Светильник, управляемый по USB | Оглавление | Конец первой серии экспериментов!