День #4 адвент-календаря «Let it Glow»: блестящие матричные индикаторы!

День 4 адвент-календаря Let it Glow — матричные индикаторы

Наступил день #4 адвент-календаря «Let it Glow»!

Сегодня вы найдёте новый мигающий компонент — один из наших любимых — матричный индикатор (bar graph display). По сути это просто пять светодиодов в ряд, но компактный размер и близкое расположение светодиодов позволяют создавать действительно классные эффекты и проекты. Они отлично подходят для отображения прогресса, счётчиков и других данных.

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

Приступим!

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

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

  • 1x 5-сегментный матричный индикатор

  • 1x Резисторная сборка (network resistor)

  • 6x Провода «папа-папа»

Содержимое коробки 4 адвент-календаря Let it Glow

Задания сегодняшнего дня

Сегодня мы научимся использовать матричный индикатор, в том числе подключать его с помощью резисторной сборки из вашей коробки (через минуту объясним, что это за удобные резисторы).

Мы будем использовать светодиоды для отображения информации из кода, а также покажем, как создавать с ними красивые эффекты.

Сначала расскажем немного подробнее о сегодняшних компонентах…

Как работает матричный индикатор?

Мы уже знаем, что такое светодиод и что у него два вывода: анод (+) и катод (-).

Матричный индикатор работает точно так же — он просто содержит несколько светодиодов, а значит, у него больше выводов!

Вы заметите, что все ножки одинаковой длины, так как же понять, какая из них что значит? Если присмотреться, можно увидеть, что один из углов индикатора имеет срезанный край — он указывает, что выводы на этой стороне являются выводами GND/катода (-). Многие компоненты используют такой подход для обозначения полярности.

Мы просто подключаем каждый вывод светодиода к выводу GPIO, а другой — к GND (через резистор), и можно писать код!

Угол матричного индикатора обозначает GND

Как работает резисторная сборка?

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

В такой ситуации лучше использовать резисторные сборки _(также известные как резисторные массивы, резисторные пакеты или резисторные сети)_. Этот маленький чёрный компонент по сути содержит внутри пять резисторов и один общий вывод для подключения к GND, что избавляет нас от большой работы с отдельными резисторами и проводами.

Обратите внимание, что корпус резисторной сборки имеет точку рядом с буквой A? Эта точка — общий вывод GND. Давайте соберём схему и покажем, куда именно всё подключается.

Сборка схемы

Как всегда, убедитесь, что Pico отключён от USB-кабеля во время работы со схемой.

Подготовка макетной платы

Сначала уберите светодиод, резистор и их провода со схемы и верните их в коробку #2. Для сегодняшних заданий они не понадобятся. Оставьте две кнопки на месте.

Ваша исходная точка должна выглядеть вот так:

Исходная схема для дня 4

Установка матричного индикатора и резисторной сборки

Возьмите матричный индикатор и установите его на макетную плату, перекинув через центральный разрыв, с выводами GND/срезанным углом в нижней части:

Теперь установите резисторную сборку. Найдите точку рядом с буквой «A» — первый вывод здесь не подключается к нашему матричному индикатору, так как это вывод GND.

Установите резистор так, чтобы все остальные выводы были соединены с ножкой нашего матричного индикатора, вот так (также обратите внимание на срезанный край и надпись на индикаторе):

Положение резисторной сборки на макетной плате

Ваша макетная плата должна теперь выглядеть примерно так:

Матричный индикатор и резисторная сборка на макетной плате

Подключение проводов

Теперь подключим всё. Начнём с провода GND — подключите один конец к выводу GND на физическом пине 18, а другой конец — к выводу «точки» на резисторной сборке.

Теперь выводы GPIO. Каждый из сегментов светодиода (все пять) требует отдельного вывода GPIO, чтобы мы могли управлять ими независимо.

Для начала мы использовали GPIO 13, 12, 11 и 10, так как они идут удобным рядом на Pico (физические пины 17, 16, 15, 14). Затем подключили пятый сегмент светодиода к GPIO 9 (физический пин 12). _**Осторожно** — между GPIO 10 и 9 есть вывод GND._

Вот как это выглядит:

Полная схема дня 4

Несколько советов

  • Здесь используется больше проводов, а значит, больше шансов что-то перепутать! Тщательно проверьте схему перед подключением Pico.

  • Основное, что нужно проверить:

    • Срезанный край на индикаторе находится на той же стороне, что и резисторная сборка

    • Вывод «точки» резисторной сборки не подключён к индикатору (должен подключаться только к нашему проводу GND)

    • Правильность подключения со стороны Pico, включая разрыв между первыми четырьмя выводами GPIO и последним

Всё хорошо? Снова подключите USB-кабель Pico и продолжим!

Задание 1: Тест сегментов

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

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

Помните, что мы говорили, что индикатор — это по сути просто пять светодиодов? Именно так мы его и программируем: настраиваем пять выводов GPIO как светодиоды, как в день #2.

Скрипт ниже должен зажигать сегменты по одному, а затем выключить их все — если этого не происходит, вернитесь назад и проверьте схему (иногда помогает начать заново, если вы не уверены).

from machine import Pin
import time

# Настройка выводов светодиодов
seg1 = Pin(13, Pin.OUT)
seg2 = Pin(12, Pin.OUT)
seg3 = Pin(11, Pin.OUT)
seg4 = Pin(10, Pin.OUT)
seg5 = Pin(9, Pin.OUT)

# Включаем каждый светодиод по очереди
seg1.value(1)
time.sleep(1)

seg2.value(1)
time.sleep(1)

seg3.value(1)
time.sleep(1)

seg4.value(1)
time.sleep(1)

seg5.value(1)
time.sleep(1)

# Выключаем все светодиоды
seg1.value(0)
seg2.value(0)
seg3.value(0)
seg4.value(0)
seg5.value(0)

Задание 2: Управление сегментами с помощью списков

Вы заметили, насколько длинным получился код только для того, чтобы включить и выключить светодиоды? Это потому, что теперь у нас пять разных светодиодов и выводов GPIO для управления ими — много строк!

Существуют более удобные способы управления большим количеством элементов, например, использование списка в цикле for.

Мы рассматривали циклы for в день #2 с функцией range, но на этот раз будем использовать циклы for со списком.

Что такое список в MicroPython?

В MicroPython список — это место для хранения нескольких _вещей_ (например, наших светодиодов), на которые мы можем ссылаться в коде по имени списка.

Перейдём сразу к примеру.

Код ниже настраивает выводы светодиодов, а затем добавляет их все в список под названием «segments». Затем мы обращаемся к этому списку в нашем цикле for, который будет включать каждый светодиод из списка с задержкой в одну секунду.

Программа продолжает проходить по списку в этом цикле for до последнего элемента, а затем переходит к любому коду ниже.

Наконец, мы запускаем ещё один цикл for для выключения всех светодиодов (без задержки).

Помните: не обязательно использовать «**for led*». Можно написать «for i» или «for x» или что-то другое. Это не имеет значения — можно даже написать «for santa», если хотите!*

Только убедитесь, что используете то же имя в коде с отступом, то есть «**santa*.value(1)»*

Вот пример кода:

from machine import Pin
import time

# Настройка выводов светодиодов
seg1 = Pin(13, Pin.OUT)
seg2 = Pin(12, Pin.OUT)
seg3 = Pin(11, Pin.OUT)
seg4 = Pin(10, Pin.OUT)
seg5 = Pin(9, Pin.OUT)

# Создаём список наших светодиодов
segments = [seg1, seg2, seg3, seg4, seg5]

# Цикл for для поочерёдного включения каждого светодиода
for led in segments:

    led.value(1)
    time.sleep(1)

# Цикл for для выключения всех светодиодов

for led in segments:

    led.value(0)

Задание 3: Сканер светодиодов

Время немного повеселиться с миганием! Мы создадим сканер светодиодов, который бегает от одного края к другому. Известный пример — машина из сериала Knight Rider, классического телешоу 80-х, где у автомобиля был сканирующий красный огонь спереди.

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

Начальный код тот же: настраиваем выводы светодиодов и помещаем их в список. Затем запускаем цикл _**while True**_ (работает бесконечно).

Внутри этого цикла while запускаем _**цикл for**_, который проходит по каждому светодиоду и включает, а затем выключает их по одному с задержкой, аналогично тому, что мы делали раньше.

Когда первый цикл for исчерпывает элементы в списке, он завершается и переходит к следующему. Здесь мы запускаем _**обратную**_ версию этого цикла, то есть он будет проходить по списку в обратном порядке. Для этого просто используем слово «_**reversed**_» и помещаем имя списка в скобки после него: _**for led in reversed(segments):**_.

Попробуйте этот код:

from machine import Pin
import time

# Настройка выводов светодиодов
seg1 = Pin(13, Pin.OUT)
seg2 = Pin(12, Pin.OUT)
seg3 = Pin(11, Pin.OUT)
seg4 = Pin(10, Pin.OUT)
seg5 = Pin(9, Pin.OUT)

# Создаём список наших светодиодов
segments = [seg1, seg2, seg3, seg4, seg5]

while True:

    # Цикл for для поочерёдного включения и выключения каждого светодиода в порядке списка
    for led in segments:

        led.value(1)
        time.sleep(0.08)
        led.value(0)

    # Цикл for в обратном порядке, проходящий по списку назад
    for led in reversed (segments):

        led.value(1)
        time.sleep(0.08)
        led.value(0)

Задание 4: Случайные светодиоды

Ещё один интересный приём — модуль random. Этот модуль является генератором случайных чисел (ГСЧ, RNG), который мы можем использовать для случайного выбора светодиода для включения.

Чтобы использовать модуль random, нам сначала нужно добавить его как import в начале скрипта. Затем, после обычной настройки выводов светодиодов и создания списка, мы сразу переходим в цикл while True для многократного выполнения кода.

Внутри нашего цикла while сначала создаём переменную r с помощью r = random.randint(0,4). Эта строка говорит: «_дай мне случайное число (целое) от 0 до **4**_». Мы делаем это, потому что у нас пять светодиодов, и мы хотим случайным образом выбрать один из них для включения.

Вы можете спросить: «_почему тогда не от 1 до 5?_». MicroPython всегда начинает с нуля для списков и подобных вещей. Это называется «_индекс_», и индексация в MicroPython всегда начинается с 0.

Чтобы сделать это понятнее, наш индекс при использовании списка из пяти светодиодов выглядит так:

  • 0 = seg1

  • 1 = seg2

  • 2 = seg3

  • 3 = seg4

  • 4 = seg5

Последняя часть нашей программы указывает коду зажечь соответствующий светодиод. Мы используем _**segments[r].value(1)**_ — это зажигает светодиод из нашего списка (segments) с номером/индексом (r), который был выбран случайным образом.

Тот же r используется для выключения того же светодиода после задержки. Когда код возвращается в начало, будет сгенерировано новое случайное число.

Попробуйте этот код и убедитесь сами:

from machine import Pin
import time
import random

# Настройка выводов светодиодов
seg1 = Pin(13, Pin.OUT)
seg2 = Pin(12, Pin.OUT)
seg3 = Pin(11, Pin.OUT)
seg4 = Pin(10, Pin.OUT)
seg5 = Pin(9, Pin.OUT)

# Создаём список наших светодиодов
segments = [seg1, seg2, seg3, seg4, seg5]

while True:

    r = random.randint(0,4) # устанавливаем r в случайное число от 0 до 4

    segments[r].value(1) # зажигаем сегмент из списка с индексом r

    time.sleep(0.1)

    segments[r].value(0) # выключаем тот же светодиод

Задание 5: Счётчик нажатий кнопки с светодиодами

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

Для этого нам нужно вернуться к операторам if из дня #3 и узнать о них немного больше. Нам нужно, чтобы код считал каждое нажатие каждой кнопки, отслеживал это изменяющееся число и зажигал нужное количество светодиодов. Также нужно сообщить коду, что делать при достижении минимального/максимального значения счётчика.

В примере ниже используются вложенные операторы if, а также оператор else. Что же это такое?

Вложенные операторы if

Вложенный оператор if — это просто оператор if внутри другого оператора if. Они очень удобны, так как позволяют задать ещё одно условие для проверки после того, как первое уже выполнено.

Правила отступов применяются и здесь, поэтому необходимо продолжать делать отступ для вложенного оператора if. Через минуту мы покажем это на примере, чтобы всё стало понятно.

Оператор else

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

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

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

greenbutton = Pin(3, Pin.IN, Pin.PULL_DOWN)

while True:

    time.sleep(0.2) # Небольшая задержка

    if greenbutton.value() == 1:

        print("Зелёная кнопка нажата")

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

        print("Нет нажатия кнопки")

Код

В примере ниже снова используется наш список светодиодов и создаётся переменная «count», установленная в минус 1 (-1) для начала. Почему мы используем -1? Потому что, если вы помните из предыдущего, MicroPython всегда начинает с нуля для списков (индексов) — поэтому наш первый светодиод технически имеет индекс 0. Если бы мы начали счётчик с нуля, первый светодиод загорелся бы сразу (вы увидите почему через минуту).

Наш код запускает цикл while, затем использует два оператора if, проверяющих, нажата ли одна из наших кнопок.

Когда одна из кнопок нажата (когда одно из начальных условий if истинно), управление передаётся вложенному оператору if ниже.

Взяв в качестве примера первую кнопку (красную), вложенный оператор if спрашивает: «счётчик (индекс) равен 4?» (все пять светодиодов горят?). Если да, мы говорим коду pass — это просто способ сказать MicroPython не делать ничего.

Если счётчик не равен 4, выполнится оператор else. Наш оператор else добавляет +1 к счётчику, затем зажигает светодиод, соответствующий нашему новому значению счётчика, используя segments[count].value(1). Переменная «count» «подставляется» в код с помощью [count].

Посмотрите на другой раздел для зелёной кнопки — он делает противоположное: выключает светодиоды и уменьшает счётчик.

Скопируйте код ниже в Thonny и попробуйте сами:

from machine import Pin
import time

# Настройка входных выводов
redbutton = Pin(2, Pin.IN, Pin.PULL_DOWN)
greenbutton = Pin(3, Pin.IN, Pin.PULL_DOWN)

# Настройка выводов светодиодов
seg1 = Pin(13, Pin.OUT)
seg2 = Pin(12, Pin.OUT)
seg3 = Pin(11, Pin.OUT)
seg4 = Pin(10, Pin.OUT)
seg5 = Pin(9, Pin.OUT)

# Создаём список наших светодиодов
segments = [seg1, seg2, seg3, seg4, seg5]

# Устанавливаем начальное значение счётчика для индекса
count = -1

# Выключаем все светодиоды в начале
seg1.value(0)
seg2.value(0)
seg3.value(0)
seg4.value(0)
seg5.value(0)

while True:

    time.sleep(0.01) # Небольшая задержка, чтобы программа не работала слишком быстро

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

        if count == 4: # Если счётчик уже равен 4
            pass # Ничего не делаем

        else:
            count = count + 1 # Добавляем 1 к счётчику
            segments[count].value(1) # Зажигаем светодиод с индексом счётчика
            time.sleep(0.2)

    if greenbutton.value() == 1: # Если нажата зелёная кнопка

        if count == -1: # Если счётчик уже равен -1
            pass # Ничего не делаем

        else:
            segments[count].value(0) # Выключаем светодиод с индексом счётчика
            time.sleep(0.2)
            count = count -1 # Вычитаем 1 из счётчика

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

Молодцы, мастера, ещё один день мигающего веселья завершён!

Мы знаем, что последнее задание могло немного вас запутать, но если вы не уверены в чём-то, вернитесь к началу дня и пройдите всё снова. Иногда помогает просто читать код, а не описания — это может помочь всему «встать на своё место».

Мы начинаем показывать, как можно программировать интересные/декоративные мигающие проекты (модуль random отлично подходит для этого), и будем делать это на протяжении всего оставшегося календаря.

Повторение — что мы рассмотрели в коробке сегодняшнего дня?

  • Подключение матричного индикатора

  • Использование резисторных сборок

  • Использование списков в MicroPython

  • Использование метода «reversed» со списками

  • Модуль random

  • Вложенные операторы if

  • Операторы else

  • Использование «pass»

Как всегда, по возможности оставьте схему как есть, и увидимся завтра утром!

Диаграммы подключения на макетной плате на этой странице созданы с помощью Fritzing.