MicroPython Мастер-класс — #2 Светодиоды и списки

MicroPython Мастер-класс — #2 Светодиоды и списки

Это второй урок нашей серии MicroPython Мастер-класс от Тони Гудхью, цель которой — улучшить ваши навыки программирования на MicroPython, знакомя вас с новыми компонентами и техниками кодирования — с использованием Raspberry Pi Pico!

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

Что вам понадобится

Мы предполагаем, что у вас уже установлен Thonny на компьютере и настроен Raspberry Pi Pico с самой свежей прошивкой MicroPython (UF2). Если нет — сначала ознакомьтесь с руководством по началу работы с Raspberry Pi Pico, где это подробно описано.

Вам понадобится:

  • Raspberry Pi Pico с контактными штырьками

  • Полноразмерная макетная плата

  • Кабель Micro-USB (для питания и программирования Pico)

  • Набор соединительных проводов «папа-папа» и «папа-мама»

  • Тактильные кнопки

  • Потенциометр 10K Ом (он ещё пригодится в будущих уроках)

  • 10 токоограничивающих резисторов 330 Ом

  • 10 светодиодов 5 мм

    • ИЛИ 10-сегментная светодиодная шкала (bar graph)

  • 10 токоограничивающих резисторов 330 Ом

Схема подключения

Мы берём за основу схему из первого урока и добавляем два светодиода с токоограничивающими резисторами 330 Ом на выводах GP17 и GP18.

Следуйте диаграмме ниже, если собираете схему впервые:

Схема подключения для MicroPython Мастер-класса #2

Проверка схемы

Скопируйте программу ниже, вставьте её в Thonny и сохраните как 3 LEDSv1.py.

Запустите программу. В ней есть два цикла. Первый мигает всеми тремя светодиодами одновременно, второй — по одному. Нажатие кнопки прерывает выполнение цикла.

# Flash 3 LEDs - Method 1
# LEDS on GP15, GP16 and GP17
# Button on GP15 and 10K potentiometer on ADC0
from machine import Pin, ADC
import time

# === Set up section ===
# Set up button on GP15 as INPUT with PULL_DOWN
button = Pin(15, Pin.IN, Pin.PULL_DOWN)

# Set up LED on GP16 on Pi Pico
ledA = Pin(16, Pin.OUT)
ledB = Pin(17, Pin.OUT)
ledC = Pin(18, Pin.OUT)

# All LEDs on -- All off – loop 1
while button.value() == 0:
    ledA.value(1)  # LEDs ON
    ledB.value(1)
    ledC.value(1)
    time.sleep(0.5)
    ledA.value(0)  # LEDS OFF
    ledB.value(0)
    ledC.value(0)
    time.sleep(0.5)

# Wait for button to be released
while button.value() == 1:
    continue

# One LED at a time – loop 2
while button.value() == 0:
    ledA.value(1)
    time.sleep(0.3)
    ledA.value(0)
    time.sleep(0.3)
    ledB.value(1)
    time.sleep(0.3)
    ledB.value(0)
    time.sleep(0.3)
    ledC.value(1)
    time.sleep(0.3)
    ledC.value(0)
    time.sleep(0.3)

На что обратить внимание

  • Каждый светодиод имеет своё имя: ledA, ledB и ledC

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

  • В каждом цикле для каждого изменения состояния светодиода или паузы требуется отдельная строка кода

Представьте, насколько длинным был бы код во втором цикле, если бы у нас было десять светодиодов вместо трёх в этой схеме…

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

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

В MicroPython список — это структура, содержащая несколько элементов, к каждому из которых можно обращаться индивидуально с помощью указателя или индекса.

Вот простой пример программы со списком имён друзей. Скопируйте и запустите программу, прежде чем мы разберём её подробно:

# List example - a list of friends
friends = ["Joe", "Anne", "Phil"]
print(friends)
number = len(friends)  # No of elements in the list
print("\n", number, "\n")

# Individual names
for p in range(number):
    print(friends[p], end=' ')
print("\n")

# Individual names - reverse order
for i in range(number - 1, -1, -1):
    print(friends[i], end=' ')
print()

# Add a new friend with .append[] - to the end of the list
friends.append("Jenny")
number = len(friends)  # No of elements in the list
print("\n", number, "\n")

# Individual names
for p in range(number):
    print(friends[p], end=' ')
print("\n")

# Individual names - reverse order
# Index moves from last to first
for i in range(number - 1, -1, -1):
    print(friends[i], end=' ')
print("\n")

print("My 2nd friend is " + friends[1])  # Index/Pointer starts at 1

После запуска вывод программы должен выглядеть следующим образом:

>>> %Run -c $EDITOR_CONTENT
['Joe', 'Anne', 'Phil']

 3

Joe Anne Phil
Phil Anne Joe

 4

Joe Anne Phil Jenny
Jenny Phil Anne Joe

My 2nd friend is Anne
>>>

Разбираем код подробнее

# List example - a list of friends
friends = ["Joe", "Anne", "Phil"]
print(friends)
number = len(friends)  # No of elements in the list
print("\n", number, "\n")

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

Мы выводим список, и он появляется как одна строка — с квадратными скобками, кавычками и запятыми.

Следующая строка определяет количество элементов в списке с помощью функции len(), подсчитывая их. Наконец, мы печатаем это число, добавляя несколько переводов строки с помощью строк n.

# Individual names
for p in range(number):
    print(friends[p], end=' ')
print("\n")

Здесь мы используем указатель p для обращения к каждому элементу списка и его вывода.

Параметр end=“ „ в конце оператора print предотвращает обычный перевод строки, генерируемый командой print, и имена выводятся на одной строке с единственным пробелом между ними. Последний print генерирует два перевода строки.

# Individual names - reverse order
for i in range(number - 1, -1, -1):
    print(friends[i], end=' ')
print()

На этот раз мы перемещаем указатель в обратном направлении и выводим имена в обратном порядке.

(n – 1) указывает на последний элемент списка из n элементов, тогда как 0 указывает на первый элемент.

# Add a new friend with .append[] - to the end of the list
friends.append("Jenny")

Эта строка добавляет новое имя в конец списка friends с помощью метода .append().

number = len(friends)  # No of elements in the list
print("\n", number, "\n")

Здесь мы обновляем переменную, хранящую длину списка, и выводим её.

# Individual names
for p in range(number):
    print(friends[p], end=' ')
print("\n")

# Individual names - reverse order
# Index moves from last to first
for i in range(number - 1, -1, -1):
    print(friends[i], end=' ')
print("\n")

Затем мы выводим расширенный список в прямом и обратном порядке.

print("My 2nd friend is " + friends[1])  # Index/Pointer starts at 1

Эта строка демонстрирует, что индекс или указатель считается с нуля. Индекс, равный 1, указывает на второй элемент списка.

Список светодиодов

Теперь мы воспользуемся нашим кодом для управления десятью светодиодами в ряд. Можно использовать как обычные круглые светодиоды, так и удобный компонент с десятью прямоугольными светодиодами — светодиодную шкалу (Light Bar / Bar Graph).

Каждый светодиод потребует собственного резистора 330 Ом и отдельного соединения с выводом Pico. Мы будем использовать выводы GP2–GP11 для управления светодиодами.

Вот фотография нашей схемы. У нашей 10-сегментной светодиодной шкалы аноды расположены со стороны, где нанесена маркировка, а катоды — с чистой стороны:

Схема подключения 10 светодиодов к Raspberry Pi Pico

На Raspberry Pi Pico номера выводов удобно напечатаны на нижней стороне, что значительно облегчает подключение перемычек к нужным контактам.

Кнопку мы оставляем на GP15, а потенциометр — на ADC0 (GP26).

Аноды светодиодов подключены напрямую к выводам GPIO Pico (GP2–GP11), тогда как катоды подключены к шине GND через резисторы 330 Ом.

Хотите вспомнить, что такое анод и катод? Вернитесь к первому уроку, где это подробно объясняется.

Проверка светодиодов

Скопируйте приведённый ниже код в окно Thonny и запустите его — объяснение принципа работы будет дальше:

# 10 Segment LED Bar Graph or 10 LEDs
# Author: Tony Goodhew 27 April 2023
# LEDs with 330 Ohm resistors on pins GP2 to GP11
# Button switch on GP15 - Pull Down

# Import libraries
from machine import Pin
import utime
import random

# Set up button on GP 15 with Pull Down
button = Pin(15, Pin.IN, Pin.PULL_DOWN)

# Set up the LEDs with a list
leds = []  # An empty list
for i in range(10):
    led = Pin(2 + i, Pin.OUT)  # Set up 1 LED
    leds.append(led)           # Add the new led to the list
    leds[i].value(0)           # Turn it off

# Turn LEDs ON, one at a time
for i in range(10):
    leds[i].value(1)  # Turn on a LED
    utime.sleep(0.1)
utime.sleep(0.5)  # Short delay

# Turn LEDs OFF, one at a time
for i in range(10):
    leds[i].value(0)  # Turn off a LED
    utime.sleep(0.1)

# Flash, one at a time UP
for i in range(10):
    leds[i].value(1)  # Turn on a LED
    utime.sleep(0.3)
    leds[i].value(0)  # Turn off a LED
    utime.sleep(0.3)
utime.sleep(0.7)

# Flash, one at a time DOWN
for i in range(9, -1, -1):
    leds[i].value(1)  # Turn on a LED
    utime.sleep(0.3)
    leds[i].value(0)  # Turn off a LED
    utime.sleep(0.3)
utime.sleep(0.7)

# Random LEDs
for c in range(30):
    p = random.randint(1, 9)
    leds[p].value(1)  # Turn LED on
    utime.sleep(0.1)
    leds[p].value(0)  # Turn LED off
    utime.sleep(0.1)

Как это работает

Первый блок импортирует необходимые библиотеки и настраивает кнопку. Затем мы настраиваем список светодиодов следующим образом:

# Set up the LEDs with a list
leds = []  # An empty list
for i in range(10):
    led = Pin(2 + i, Pin.OUT)  # Set up 1 LED
    leds.append(led)           # Add the new led to the list
    leds[i].value(0)           # Turn it off

Мы начинаем с пустого списка leds, используя leds = [] # An empty list.

У нас десять светодиодов, поэтому мы можем использовать счётный цикл для повторения блока кода десять раз. В цикле мы настраиваем новый светодиод на выводе GP 2 + i, где i — счётчик цикла, идущий от 0 до 9. Изначально это вывод GP2, в конце — GP11.

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

После настройки светодиодов мы начинаем включать и выключать их в разных режимах:

  • Включить все по одному

  • Выключить все по одному в обратном направлении

  • Мигать по одному вверх и вниз по ряду

  • Мигать в случайных позициях 30 раз

Управление списком светодиодов с помощью потенциометра

Давайте используем потенциометр (pot) в нашей схеме для управления светодиодами. Скопируйте приведённый ниже код в Thonny, запустите его, а затем прочитайте объяснение принципа работы:

# 10 Segment LED Bar Graph or 10 LEDs
# Author: Tony Goodhew 27 April 2023
# LEDs with 330 Ohm resistors on pins GP2 to GP11
# Button switch on GP15 - Pull Down
# 10K Ohm potentiometer on ADC0 = GP26

# Import libraries
from machine import Pin, ADC
import utime
import random

# Set up button on GP 15 with Pull Down
button = Pin(15, Pin.IN, Pin.PULL_DOWN)

# Set up potentiometer on ADC0 = GP26
pot = machine.ADC(26)

# Set up the LEDs with a list
leds = []  # An empty list
for i in range(10):
    led = Pin(2 + i, Pin.OUT)  # Set up 1 LED
    leds.append(led)           # Add the new led to the list
    leds[i].value(0)           # Turn it off

p = 0    # Set pointer at first LED
dir = 1  # pointer direction of movement

# Loop UP and DOWN until button pressed
while button.value() == 0:
    leds[p].value(1)  # Turn LED on
    utime.sleep(0.1)
    leds[p].value(0)  # Turn LED off
    utime.sleep(0.1)
    p = p + dir
    if p == 10:   # Over the upper end
        p = 8     # Set next position – back 2 places
        dir = -1  # Reverse direction = move down
    if p == -1:   # Below the bottom end
        p = 1     # Set next position – up 2 places
        dir = 1   # Reverse direction = move up

# Wait for button release
while button.value() == 1: continue

# Single LED under pot control until button pressed
while button.value() == 0:
    pot_in = int(pot.read_u16() / 6500)
    if pot_in != 0:
        leds[pot_in - 1].value(1)  # Turn LED on
        utime.sleep(0.1)
        leds[pot_in - 1].value(0)  # Turn LED off

# Clear LEDS
for p in range(10):
    leds[p].value(0)  # Clear LEDS

# Wait for button release
while button.value() == 1: continue

# Bar graph under pot control until button pressed
while button.value() == 0:
    pot_in = int(pot.read_u16() / 6500)  # Values 0 to 10
    print(pot_in)
    # Clear LEDS
    for p in range(10):
        leds[p].value(0)  # OFF
    for p in range(pot_in):  # Values
        leds[p].value(1)
    utime.sleep(0.1)

# Clear LEDS
for p in range(10):
    leds[p].value(0)  # Clear LEDS

Горящий светодиод должен казаться движущимся вверх и вниз по ряду.

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

Как это работает

Рассмотрим два основных блока кода:

# Single LED under pot control until button pressed
while button.value() == 0:
    pot_in = int(pot.read_u16() / 6500)
    if pot_in != 0:
        leds[pot_in - 1].value(1)  # Turn LED on
        utime.sleep(0.1)
        leds[pot_in - 1].value(0)  # Turn LED off

Сначала один светодиод движется по мере поворота потенциометра.

# Bar graph under pot control until button pressed
while button.value() == 0:
    pot_in = int(pot.read_u16() / 6500)  # Values 0 to 10
    print(pot_in)
    # Clear LEDS
    for p in range(10):
        leds[p].value(0)  # OFF
    for p in range(pot_in):  # Values
        leds[p].value(1)
    utime.sleep(0.1)
# Clear LEDS
for p in range(10):
    leds[p].value(0)  # OFF

После повторного нажатия кнопки мы получаем сплошную шкалу (bar graph), управляемую потенциометром. Ещё одно нажатие кнопки завершает программу и очищает дисплей.

Этот цикл выполняется, пока кнопка отпущена. Первая строка в цикле вычисляет целое значение от 0 до 10 включительно из значения, полученного от потенциометра.

Если значение равно нулю, мы хотим, чтобы дисплей был ВЫКЛЮЧЕН, поэтому не включаем ни одного светодиода. Мы вычисляем значение указателя, вычитая 1 из pot_in, получая значения 0–9, необходимые для 1–10 светодиодов. Текущий светодиод горит только 1/10 секунды в каждом цикле, но мы не видим мерцания благодаря инерции зрения.

Это пример вложенных циклов — циклов, выполняющихся внутри внешнего цикла. Здесь внешний цикл управляется кнопкой — он продолжает выполняться до нажатия кнопки. Внутри цикла мы получаем необработанное значение от потенциометра, масштабируем его до диапазона 0–10 включительно и выводим в окно Shell.

Первый внутренний цикл выключает все светодиоды. Указатель принимает значения: 0, 1, 2, 3, 4, 5, 6, 7, 8 и 9.

Второй цикл, который зажигает некоторые светодиоды, выполняется только если масштабированное значение от потенциометра больше 0. Если pot_in равно нулю, цикл не выполняется. В противном случае он зажигает количество светодиодов, равное pot_in. Если pot_in равно 4, нам нужно зажечь 4 светодиода, и указатель p принимает значения 0, 1, 2, 3, зажигая первые четыре светодиода (всё дело в том, что люди считают с единицы, а компьютеры — с нуля).

Что попробовать

  • Заставьте пару соседних светодиодов «прыгать» вверх и вниз по дисплею — всегда должны гореть 2 светодиода

  • Заставьте два горящих светодиода двигаться одновременно, начиная с противоположных концов дисплея

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

Об авторе

Эта статья написана Тони Гудхью. Тони — ушедший на пенсию учитель информатики, начавший писать код ещё в 1968 году, когда это называлось «программированием» — он начинал с FORTRAN IV на IBM 1130! Активный участник сообщества Raspberry Pi, его основные интересы сейчас — программирование на MicroPython, путешествия и фотография.

Для создания схем подключения на макетной плате в этой статье использовался Fritzing.