Урок 20. Перетягивание каната


В этом уроке мы создаем еще одну игру, на этот раз нужно быстрее соперника нажать кнопку 20 раз.

Список деталей для эксперимента

Для дополнительного задания

  • сервопривод
  • конденсатор 220 мкФ

Принципиальная схема

Схема на макетке

Обратите внимание

  • Схема подключения кнопок с использованием конденсаторов, резисторов и микросхемы 74HC17, которая называется инвертирующий триггер Шмитта, нужна для аппаратного подавления дребезга. Посмотрите видеоурок на эту тему.
  • В этом эксперименте нам нужно очень много цифровых портов, поэтому нам пришлось использовать порт 0. Пользоваться им неудобно из-за того, что он соединен с одним из каналов последовательного порта, поэтому перед прошивкой микроконтроллера нам придется отключать провод, идущий к пьезопищалке, а после прошивки подключать его обратно.

Скетч

  1.     #define BUZZER_PIN     0
    
  2.     #define FIRST_BAR_PIN  4
    
  3.     #define BAR_COUNT      10
    
  4.     #define MAX_SCORE      20
    
  5.     // глобальные переменные, используемые в прерываниях (см. далее)
    
  6.     // должны быть отмечены как нестабильные (англ. volatile)
    
  7.     volatile int score = 0
    ;
    
  8.  
  9.     void setup(
    )
    
  10.     {
    
  11.       for (
    int i = 0
    ; i < BAR_COUNT; ++i)
    
  12.         pinMode(i + FIRST_BAR_PIN, OUTPUT)
    ;
    
  13.       pinMode(BUZZER_PIN, OUTPUT)
    ;
    
  14.       // Прерывание (англ. interrupt) приостанавливает основную
    
  15.       // программу, выполняет заданную функцию, а затем возобновляет
    
  16.       // основную программу. Нам нужно прерывание на нажатие кнопки,
    
  17.       // т.е. при смене сигнала с высокого на низкий, т.е. на
    
  18.       // нисходящем (англ. falling) фронте
    
  19.       attachInterrupt(INT1, pushP1, FALLING)
    ; // INT1 — это 3-й пин
    
  20.       attachInterrupt(INT0, pushP2, FALLING)
    ; // INT0 — это 2-й пин
    
  21.     }
    
  22.  
  23.     void pushP1(
    ) { ++score; } // функция-прерывание 1-го игрока
    
  24.     void pushP2(
    ) { --score; } // функция-прерывание 2-го игрока
    
  25.     void loop(
    )
    
  26.     {
    
  27.       tone(BUZZER_PIN, 2000, 1000
    )
    ; // даём сигнал к старту.
    
  28.       // пока никто из игроков не выиграл, обновляем «канат»
    
  29.       while (
    abs
    (score) < MAX_SCORE) {
    
  30.         int bound = map(score, -MAX_SCORE, MAX_SCORE, 0, BAR_COUNT)
    ;
    
  31.         int left = min(bound, BAR_COUNT / 2 - 1
    )
    ;
    
  32.         int right = max(bound, BAR_COUNT / 2
    )
    ;
    
  33.         for (
    int i = 0
    ; i < BAR_COUNT; ++i)
    
  34.           digitalWrite(i + FIRST_BAR_PIN, i >= left && i <= right)
    ;
    
  35.       }
    
  36.       tone(BUZZER_PIN, 4000, 1000
    )
    ; // даём сигнал победы
    
  37.       while (
    true
    ) {
    } // «подвешиваем» плату до перезагрузки
    
  38.     }
    

Пояснения к коду

  • Код нашей обычной программы исполняется инструкция за инструкцией и если мы, например, проверяем состояние датчика, мы к нему обратимся только в те моменты, когда очередь дойдет до соответствующей инструкции. Однако мы можем использовать прерывания:
    • по наступлении определенного события
    • на определенном порту ход программы будет приостанавливаться для выполнения
    • определенной функции, а затем программа продолжит исполняться с того места, где была приостановлена.
  • Arduino Uno позволяет делать прерывания на портах 2 и 3.
  • В setup() прописывается инструкция attachInterrupt(interrupt, action, event), где
    • interrupt может принимать значения INT0 или INT1 для портов 2 и 3 соответственно
    • 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 всегда истинное условие и он бесконечно ничего не выполняет!

Вопросы для проверки себя

  • Каким образом мы подавляем дребезг аппаратно?
  • Для чего используются прерывания?
  • Каким образом можно включить обработку внешних прерываний?
  • О каких нюансах работы с уже известными нам вещами следует помнить при работе с прерываниями?
  • Как выбрать максимальное из двух значений? Минимальное?
  • Как получить абсолютное значение переменной? Чего следует избегать при использовании этой функции?
  • Когда оператор логическое «или» возвращает «ложь»?

Задания для самостоятельного решения

  • Вместо светодиодной шкалы подключите сервопривод и измените код таким образом, чтобы перетягивание демонстрировалось путем отклонения сервопривода от среднего положения.

 

 

 

Теги: 
Источник: 
wiki.amperka.ru