День #3 Адвент-календаря мейкера: Нажимаем кнопки!

День #3 Адвент-календаря мейкера: Нажимаем кнопки!

Добро пожаловать на третий день 12 проектов адвент-календаря Codemas. Сегодня мы добавим физические входы в нашу программу с помощью кнопок!

До этого мы полностью полагались на код, чтобы указывать нашим компонентам и программе, что и когда делать. Теперь мы будем использовать физические кнопки, которые станут триггерами в нашей программе и будут определять, что происходит.

Поехали!

Содержимое коробки #3

В этой коробке вы найдёте:

  • 1x Мини-макетная плата (breadboard)

  • 3x Высокие тактильные кнопки

  • 3x Колпачки для кнопок

  • 7x Соединительные провода «папа–папа»

Комплектующие для третьего дня

Сегодняшний проект

Сегодня мы запрограммируем кнопки из вашей коробки в качестве входных сигналов для нашего кода. Хотя мы легко можем использовать код для того, чтобы программа «делала что-то» (например, считала), компоненты вроде кнопок и переключателей позволяют нам физически взаимодействовать с нашим проектом.

Большинство кнопок очень просты: они просто замыкают цепь, которая затем отправляет сигнал на наш Raspberry Pi Pico через выбранный GPIO-пин. То же самое можно сделать, просто соединив два провода, но кнопки делают это гораздо удобнее и дружелюбнее для пользователя.

Мы также будем использовать мини-макетную плату в качестве небольшой панели управления для нашего проекта, поскольку мы задействуем цепь со светодиодами из вчерашнего дня. Это удобный компонент для вашей растущей коллекции деталей, и он пригодится нам ещё в нескольких других коробках.

Ну а колпачки для кнопок? Это просто маленький приятный бонус, который делает каждый щелчок чуть более приятным! Кнопк-кнопк-кнопк!

Сборка цепи

Как всегда, сначала убедитесь, что ваш Pico отключён от компьютера.

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

Как и на нашей большой макетной плате, этот центральный канал разъединяет одну сторону от другой, что удобно при монтаже проекта, — иначе все ножки кнопок оказались бы соединены вместе (а нам совсем не хочется никакого магического синего дыма !).

Кнопки вставлены в макетную плату

Теперь нам нужно подключить пин 3.3V от Pico к верхней красной шине нашей основной макетной платы. Вставьте провод в пин 3V3 (OUT) на Pico (физический пин 36), затем подключите другой конец к верхней красной шине.

Затем нужно подключить красную шину 3.3V к одной стороне каждой из кнопок — выберите правую сторону, как показано ниже:

Кнопки подключены к 3.3V

Наконец, нам нужно подключить другую сторону кнопок к GPIO-пинам. Вставьте провода в GPIO 13, 8 и 3 (это физические пины 17, 11 и 5). При необходимости сверьтесь с картой пинов Pico или используйте нашу диаграмму ниже.

Кнопки подключены к GPIO-пинам

Важно: GPIO-пин должен находиться на противоположной стороне кнопки, поскольку левая и правая стороны разъединены до тех пор, пока вы не нажмёте кнопку. Если бы провода GPIO и 3.3V находились на одной стороне, кнопка вела бы себя так, как будто она нажата постоянно!

Задание 1: Тестовая программа для кнопки

Сначала запустим простую тестовую программу для одной из кнопок. Каждая кнопка подключена к GPIO-пину (13, 8 и 3), поэтому мы можем установить их в качестве входов, чтобы выводить текст при нажатии.

Мы будем использовать цикл while (с которым познакомились вчера ), чтобы программа постоянно отслеживала нажатие кнопки, и новую конструкцию — оператор if, — чтобы определять, что происходит при нажатии.

Операторы if

Если вы ранее использовали Microsoft Excel или подобные программы, вы, наверное, уже догадались, что делают операторы if.

Операторы if позволяют задавать нашему коду разные результаты в зависимости от значений, входных данных и многого другого, например: «если первая кнопка нажата, зажги красный светодиод». Если условие оператора выполнено, наша программа запустит код внутри этого блока, который записан с отступом под ним.

Отступы снова важны — блок цикла while записывается с отступом, но поскольку цикл while содержит оператор if, мы снова делаем отступ для блока оператора if. Посмотрите на пример ниже, чтобы увидеть это в действии.

Операторы if могут быть очень мощными, и существует множество различных и продвинутых способов их использования. Вы будете часто применять их при написании собственного кода. Начнём с простого примера, но сначала объясним ещё одну новую вещь в нашем коде — подтяжку к нулю (pull down).

Подтяжка к нулю (Pull down)

В нашем коде ниже также используется подтяжка к нулю (pull down) — вы можете видеть это в конце каждого раздела настройки кнопки в виде Pin.PULL_DOWN.

Зачем мы это добавляем? Когда мы используем кнопку, мы отправляем 3.3V на GPIO-пин, чтобы установить его в состояние HIGH (высокий уровень), однако нам нужно убедиться, что сначала пин находится в состоянии LOW (низкий уровень), иначе наш код может не суметь обнаружить изменение и/или срабатывать случайным образом.

Это происходит потому, что GPIO-пины без подтяжки к нулю (или к питанию) могут «плавать» между 0V и 3.3V — «подтягивая» пин к нулю (0V), мы гарантируем, что он всегда начинает с LOW.

Иногда это достигается с помощью физических резисторов в схеме, однако некоторые микроконтроллеры (например, наш Pico) имеют эту функцию встроенной, и мы можем просто включить её в нашем коде. Удобно!

Код

Этот простой начальный код использует только первую кнопку. Код включает наши обычные импорты и настройку пинов, затем запускает цикл while. Цикл while содержит оператор if, который ожидает сигнал HIGH (1) на GPIO 13.

Если кнопка нажата и сигнал отправлен на этот пин, наш код выведет «button 1 pressed».

Мы добавляем небольшую задержку в этот цикл, чтобы дать пальцу шанс отпустить кнопку и не допустить многократного срабатывания кода от одного нажатия (это называется «устранение дребезга»). Значения 0.1 или 0.2 секунды обычно работают хорошо.

Скопируйте пример в Thonny и попробуйте:

# Импорты
from machine import Pin
import time

# Задаём имя кнопки и номер GPIO-пина
# Также устанавливаем пин как вход и используем подтяжку к нулю
button1 = Pin(13, Pin.IN, Pin.PULL_DOWN)

while True: # Бесконечный цикл

    time.sleep(0.2) # Короткая задержка

    if button1.value() == 1: # Если кнопка 1 нажата
        print("Button 1 pressed")

Задание 2: Несколько кнопок в качестве входов

Добавим остальные кнопки в код и изменим наш оператор if, чтобы он отслеживал нажатие любой из них.

Для этого мы добавляем строки настройки для остальных кнопок, а затем добавляем дополнительные операторы if под исходным — наш код просто будет проверять каждое условие по порядку.

Если одно из условий выполнено (если кнопка нажата), код выполнит блок для этой кнопки, а затем продолжит проверять оставшиеся операторы (и будет делать это вечно, потому что всё это находится в цикле while True):

# Импорты
from machine import Pin
import time

# Задаём имена кнопок и номера GPIO-пинов
# Также устанавливаем пины как входы и используем подтяжку к нулю
button1 = Pin(13, Pin.IN, Pin.PULL_DOWN)
button2 = Pin(8, Pin.IN, Pin.PULL_DOWN)
button3 = Pin(3, Pin.IN, Pin.PULL_DOWN)

while True: # Бесконечный цикл

    time.sleep(0.2) # Короткая задержка

    if button1.value() == 1: # Если кнопка 1 нажата

        print("Button 1 pressed")

    if button2.value() == 1: # Если кнопка 2 нажата

        print("Button 2 pressed")

    if button3.value() == 1: # Если кнопка 3 нажата

        print("Button 3 pressed")

Задание 3: Несколько кнопок с elif и else

Что если мы хотим сделать что-то более сложное с нашими кнопками?

Может быть, мы хотим игнорировать кнопки 2 и 3, если нажата кнопка 1? Хотим ли мы, чтобы что-то происходило, если ни одна кнопка не нажата? Может быть, мы хотим, чтобы что-то происходило, если мы одновременно удерживаем две кнопки?

Мы можем сделать всё это и многое другое, используя наш оператор if вместе с дополнительными операторами elif и else. Давайте объясним, как они работают, прежде чем попробовать:

Оператор elif

Оператор elif — это сокращение от «else if» (иначе если). Если ваш первый оператор if не выполняется, ваш код будет проверять каждый оператор elif по порядку, чтобы узнать, выполняется ли хоть один из них.

Если один из операторов elif срабатывает, он выполнит блок кода для этого оператора и прекратит проверку остальных (в отличие от обычных операторов if, которые продолжают проверять каждый из них по очереди, независимо от того, что произошло).

В примере ниже мы используем if, а затем elif, чтобы указать нашему коду проверить, нажата ли кнопка 1, и если нет — проверить кнопку 2, а если кнопка 2 не нажата — проверить кнопку 3.

Совет: всегда должен быть начальный оператор «if» первым.

# Импорты
from machine import Pin
import time

# Задаём имена кнопок и номера GPIO-пинов
# Также устанавливаем пины как входы и используем подтяжку к нулю
button1 = Pin(13, Pin.IN, Pin.PULL_DOWN)
button2 = Pin(8, Pin.IN, Pin.PULL_DOWN)
button3 = Pin(3, Pin.IN, Pin.PULL_DOWN)

while True: # Бесконечный цикл

    time.sleep(0.2) # Короткая задержка

    if button1.value() == 1: # Если кнопка 1 нажата

        print("Button 1 pressed")

    elif button2.value() == 1: # Если кнопка 2 нажата

        print("Button 2 pressed")

    elif button3.value() == 1: # Если кнопка 3 нажата

        print("Button 3 pressed")

Оператор else

Оператор else говорит: «если ни одно из условий выше не выполнено — делай вот это». Он может быть полезен, когда вы хотите, чтобы что-то всегда происходило, если ни одно из операторов if/elif не выполняется.

Вот короткий пример (с использованием только двух кнопок), который можно попробовать: мы всегда выводим «No button presses», если ни одна из кнопок не нажата. Комментарии показывают, где используется оператор else:

# Импорты
from machine import Pin
import time

# Задаём имена кнопок и номера GPIO-пинов
# Также устанавливаем пины как входы и используем подтяжку к нулю
button1 = Pin(13, Pin.IN, Pin.PULL_DOWN)
button2 = Pin(8, Pin.IN, Pin.PULL_DOWN)

while True: # Бесконечный цикл

    time.sleep(0.2) # Короткая задержка

    if button1.value() == 1: # Если кнопка 1 нажата

        print("Button 1 pressed")

    elif button2.value() == 1: # Если кнопка 2 нажата

        print("Button 2 pressed")

    else: # Если ни одна кнопка не нажата

        print("No button presses")

Код

Теперь, когда мы знаем, что делают elif и else, давайте применим их на практике, добавив снова светодиоды в нашу программу.

Код ниже сначала проверяет, удерживаем ли мы кнопки 1 и 2 одновременно. Это делается с помощью оператора if с «and» между двумя условиями, требующего одновременного выполнения обоих. Если обе кнопки удерживаются, загорится зелёный светодиод.

Затем мы используем оператор elif, чтобы проверить, нажата ли кнопка 1 самостоятельно; если так — загорится жёлтый светодиод.

Затем мы используем оператор else, чтобы зажечь красный светодиод (и погасить все остальные), если ничего не нажато. Кнопка 3 в этом примере не используется. Всё это находится в цикле while True, чтобы работать вечно:

# Импорты
from machine import Pin
import time

# Задаём имена кнопок и номера GPIO-пинов
# Также устанавливаем пины как входы и используем подтяжку к нулю
button1 = Pin(13, Pin.IN, Pin.PULL_DOWN)
button2 = Pin(8, Pin.IN, Pin.PULL_DOWN)
button3 = Pin(3, Pin.IN, Pin.PULL_DOWN)

# Задаём имена светодиодов и номера GPIO-пинов
red = Pin(18, Pin.OUT)
amber = Pin(19, Pin.OUT)
green = Pin(20, Pin.OUT)

while True: # Бесконечный цикл

    time.sleep(0.2) # Короткая задержка

    if button1.value() == 1 and button2.value() == 1: # Если нажаты кнопки 1 и 2 одновременно

        print("Buttons 1 and 2 pressed")
        green.value(1) # зелёный светодиод включён
        red.value(0) # красный светодиод выключен

    elif button1.value() == 1: # Если нажата кнопка 1

        print("Button 1 pressed")
        amber.value(1) # жёлтый светодиод включён
        red.value(0) # красный светодиод выключен

    else: # Если ни одна кнопка не нажата

        red.value(1) # красный светодиод включён
        amber.value(0) # жёлтый светодиод выключен
        green.value(0) # зелёный светодиод выключен

Задание 4: Эта кнопка или та кнопка?

Ещё один приём, который мы можем использовать с кнопками и операторами if — это «or» (или) между условиями, чтобы проверить, нажата ли одна из наших кнопок, но при этом все они давали одинаковый результат.

Мы добавили пример ниже, который проверяет, нажата ли либо кнопка 1, либо кнопка 2.

Попробовав его, почему бы не попробовать добавить кнопку 3 обратно в код и заставить все три кнопки управлять светодиодом? (Подсказка: просто добавьте ещё одно «or» в ту же строку таким же образом):

# Импорты
from machine import Pin
import time

# Задаём имена кнопок и номера GPIO-пинов
# Также устанавливаем пины как входы и используем подтяжку к нулю
button1 = Pin(13, Pin.IN, Pin.PULL_DOWN)
button2 = Pin(8, Pin.IN, Pin.PULL_DOWN)

# Задаём имена светодиодов и номера GPIO-пинов
green = Pin(20, Pin.OUT)

while True: # Бесконечный цикл

    time.sleep(0.2) # Короткая задержка

    if button1.value() == 1 or button2.value() == 1: # Если нажата кнопка 1 ИЛИ кнопка 2

        print("Button 1 or 2 pressed")

        green.value(1) # зелёный светодиод включён
        time.sleep(2)
        green.value(0) # зелёный светодиод выключен

Задание 5: Переключение с помощью кнопок

Ещё одна удобная функция, которую мы можем использовать с GPIO-пинами — это «toggle» (переключение). Как вы, наверное, догадались, это переключает состояние пина с HIGH на LOW, и обратно на HIGH и так далее.

Пример ниже в основном такой же, но использует кнопку 1 для переключения красного светодиода вкл/выкл.

Попробовав код ниже, почему бы не добавить остальные кнопки и светодиоды обратно, переключая каждый из них?

# Импорты
from machine import Pin
import time

# Задаём имена кнопок и номера GPIO-пинов
# Также устанавливаем пины как входы и используем подтяжку к нулю
button1 = Pin(13, Pin.IN, Pin.PULL_DOWN)

# Задаём имена светодиодов и номера GPIO-пинов
red = Pin(18, Pin.OUT)

while True: # Бесконечный цикл

    time.sleep(0.5) # Короткая задержка

    if button1.value() == 1: # Если кнопка 1 нажата

        print("Button 1 pressed")

        red.toggle() # Переключить красный светодиод вкл/выкл

День #3 завершён!

Отличная работа, мейкеры — теперь вы умеете использовать физические входы с MicroPython (что пригодится на протяжении всего остального календаря) и можете программировать операторы if самыми разными интересными способами!

Сегодня вы узнали:

  • Как создать схему с физическими входами

  • Как программировать физические входы с помощью MicroPython

  • Что такое операторы if и как их использовать

  • Как использовать elif и else внутри операторов if

  • Как использовать «and» и «or» в условиях операторов if

  • Как комбинировать физические входы с физическими выходами

Как всегда, пожалуйста, не разбирайте схему до завтра, где мы исследуем следующий интересный компонент — до встречи!


Для создания диаграмм монтажа на макетной плате использовалась программа Fritzing.