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.
Следуйте диаграмме ниже, если собираете схему впервые:
Проверка схемы
Скопируйте программу ниже, вставьте её в 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-сегментной светодиодной шкалы аноды расположены со стороны, где нанесена маркировка, а катоды — с чистой стороны:
На 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.