Перетягивание каната
В этом эксперименте мы создаем еще одну игру, на этот раз нужно быстрее соперника нажать кнопку 20 раз.
Примечание
В старой версии блокнота хакера по ошибке были указаны резисторы на 100 кОм, которых нет в наборе. На самом деле в уроке смело можно и нужно использовать резисторы на 10 кОм из комплекта.
Список деталей для эксперимента
1 плата Arduino Uno
1 беспаечная макетная плата
1 светодиодная шкала
10 резисторов номиналом 220 Ом
4 резистора номиналом 10 кОм
2 тактовых кнопки
2 керамических конденсатора номиналом 100 нФ
1 пьезопищалка
1 инвертирующий триггер Шмитта
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
всегда истинное условие и он бесконечно ничего не выполняет!
Вопросы для проверки себя
Каким образом мы подавляем дребезг аппаратно?
Для чего используются прерывания?
Каким образом можно включить обработку внешних прерываний?
О каких нюансах работы с уже известными нам вещами следует помнить при работе с прерываниями?
Как выбрать максимальное из двух значений? Минимальное?
Как получить абсолютное значение переменной? Чего следует избегать при использовании этой функции?
Когда оператор логическое «или» возвращает «ложь»?
Задания для самостоятельного решения
Вместо светодиодной шкалы подключите сервопривод и измените код таким образом, чтобы перетягивание демонстрировалось путем отклонения сервопривода от среднего положения.