MicroPython Skill Builders — #3 Светодиоды и процедуры
Это третий выпуск серии MicroPython Skill Builders от Тони Гудью, цель которой — улучшить ваши навыки программирования на MicroPython, попутно знакомя вас с новыми компонентами и техниками написания кода — с использованием Raspberry Pi Pico!
В этом выпуске Тони продемонстрирует использование процедур на примере 10-сегментного светодиодного индикатора.
Не читали предыдущие выпуски? Ознакомьтесь с первым и вторым эпизодами, в которых рассматривается использование циклов и списков в MicroPython.
Что вам понадобится
Предполагается, что вы уже установили Thonny на свой компьютер и настроили Raspberry Pi Pico с актуальной прошивкой MicroPython (UF2). Если нет, ознакомьтесь с руководством по началу работы с Raspberry Pi Pico, где это описано подробно.
Также предполагается, что вы прочитали и прошли первые два руководства серии (см. ссылки выше, если нет).
Вам понадобится:
Raspberry Pi Pico с контактными штырями
Макетная плата полного размера
Кабель Micro-USB (для питания и программирования Pico)
Набор соединительных проводов папа-папа и папа-мама
Потенциометр 10 кОм
10 токоограничивающих резисторов (330 Ом)
Тактовые кнопки
10 светодиодов ИЛИ 10-сегментный светодиодный индикатор
Начальная точка
Мы продолжим работу с той же схемой со светодиодным индикатором, что и в предыдущем руководстве. Если вам нужны инструкции по сборке схемы, вернитесь ко второму руководству серии.
Демонстрация процедур
Давайте сразу перейдём к примеру кода, который покажет несколько процедур в действии.
Когда схема готова, скопируйте приведённый ниже код в Thonny, запустите его, а затем прочитайте ниже объяснение того, как он работает:
# Демонстрирует ПРОЦЕДУРЫ
# 10-сегментный светодиодный индикатор или 10 светодиодов
# Автор: Tony Goodhew, 2 мая 2023
# Светодиоды с резисторами 330 Ом на контактах GP2–GP11
# Кнопка на GP15 - Pull Down
# Потенциометр 10 кОм на ADC0 = GP26
# Импорт библиотек
from machine import Pin, ADC
import time
import random
# Настройка кнопки на GP 15 с Pull Down
button = Pin(15,Pin.IN, Pin.PULL_DOWN)
# Настройка светодиодов с помощью списка
leds = [] # Пустой список
for i in range(10):
led = Pin(2 + i, Pin.OUT) # Настройка 1 светодиода
leds.append(led) # Добавление нового светодиода в список
leds[i].value(0) # Выключить его
# ====== Начало определений ======
def on(): # Включить ВСЕ светодиоды
for i in range(10):
leds[i].value(1)
def off(): # Выключить ВСЕ светодиоды
for i in range(10):
leds[i].value(0)
def odd(): # Включить светодиоды с НЕЧЁТНЫМИ номерами
for i in range(0,10,2):
leds[i].value(1)
def even(): # Включить светодиоды с ЧЁТНЫМИ номерами
for i in range(1,10,2):
leds[i].value(1)
def on_n_L(n): # Включить n светодиодов слева
for i in range(n):
leds[i].value(1)
def on_n_R(n): # Включить n светодиодов справа
for i in range(n):
leds[9-i].value(1)
def flash(pp, delay): # pp — указатель на светодиод, delay — время включения
leds[pp].value(1) # Включить светодиод
time.sleep(delay)
leds[pp].value(0) # Выключить светодиод
def random_LEDs(n,delay): # Мигнуть n случайными светодиодами
for c in range(n):
p = random.randint(1,9)
flash(p,delay)
# ========= Конец определений =========
# Мигать всеми светодиодами
while button.value() == 0:
on()
time.sleep(0.3)
off()
time.sleep(0.3)
while button.value() == 1:continue
# Покачивание дисплея светодиодов
while button.value() == 0:
odd()
time.sleep(0.2)
off()
even()
time.sleep(0.2)
off()
while button.value() == 1:continue
random_LEDs(30,0.2)
on_n_R(4) # 4 светодиода включены у ПРАВОГО конца индикатора
time.sleep(0.9)
off()
on_n_L(2) # светодиоды включены у ЛЕВОГО конца индикатора
time.sleep(0.9)
off()
Первый блок программы настраивает список светодиодов и кнопку для управления циклами — это мы разбирали в двух предыдущих руководствах, поэтому повторять не будем.
Следующий блок определяет несколько простых процедур. Но что они из себя представляют?
Что такое процедура в MicroPython?
Процедура — это набор инструкций для выполнения одной задачи.
Она начинается с ключевого слова def, за которым следуют имя процедуры, пара скобок и двоеточие. Строки с отступом после строки def — это обычный код с отступами, такой же, какой мы пишем в цикле.
Возьмём первую процедуру из демонстрационного кода выше в качестве примера. Она очень простая и просто запускает цикл, включающий светодиоды по одному с максимальной скоростью:
def on(): # Включить ВСЕ светодиоды
for i in range(10):
leds[i].value(1)
Это стандартная простая задача со светодиодами, которую теперь можно запрограммировать одной инструкцией on(), не прописывая код каждый раз.
Аналогично можно определить ещё более полезную процедуру off(), чтобы выключать все светодиоды:
def off(): # Выключить ВСЕ светодиоды
for i in range(10):
leds[i].value(0)
Больше вариаций этого паттерна вы найдёте в нашем демонстрационном коде. odd() включает светодиоды с нечётными номерами, а even() — только чётные (мы разберём, как они работают, в другой раз, когда подробнее рассмотрим функцию range).
Переменные внутри процедур
Следующие процедуры из нашей демонстрации включают переменную в части в скобках. Это называется параметром и используется для внесения изменений внутри процедуры при каждом её вызове.
def on_n_L(n): # Включить n светодиодов слева
for i in range(n):
leds[i].value(1)
Она позволяет включить нужное количество светодиодов, начиная с левого конца индикатора. Посмотрим на пример вызова:
on_n_L(3)
Это включит только три левых светодиода на индикаторе.
Параметры и аргументы в MicroPython
Когда мы вызываем процедуру с аргументом (или значением) между скобками, это значение используется как параметр при выполнении процедуры:
Параметр — это переменная, указанная внутри скобок в определении функции
Аргумент — это значение, которое передаётся в функцию при её вызове
Хороший пример этого — оператор print(). Когда вы используете print(), вы используете процедуру. В примере ниже print — это имя процедуры, а «Answer is» и x — аргументы:
print("Answer is", x)
Последние два определения имеют параметры и при вызове потребуют передачи аргументов:
def flash(pp, delay):
def random_LEDs(n,delay):
Использование наших определений
В нашем примере кода, ниже определений, находится ОСНОВНАЯ часть программы, которая вызывает заранее определённые процедуры для выполнения полезных задач (помните, что определения ничего не делают сами по себе — их нужно вызвать в коде):
Мигать всеми светодиодами до нажатия кнопки
«Качать» светодиоды до нажатия кнопки
Случайным образом зажигать светодиоды по всему индикатору
Зажигать ряд светодиодов с любого конца
Ещё один пример определений в MicroPython
Давайте добавим ещё несколько определений и немного изменим код, чтобы программа делала с светодиодами что-то новое!
Добавьте эти процедуры в конец блока определений:
def zero_L(delay): # 1.1.......
leds[0].value(1)
leds[2].value(1)
time.sleep(delay)
def zero_R(delay): # .......1.1
leds[7].value(1)
leds[9].value(1)
time.sleep(delay)
Эти новые определения добавляют два светодиодных паттерна для обозначения нуля на каждом конце индикатора.
Чтобы использовать эти определения, нам нужно их вызвать. Замените основной блок кода в конце программы приведённым ниже кодом, затем сохраните программу как proc_counting1.py:
# ========= Конец определений =========
wait = 0.7
# Считаем от 0 до 35 на светодиодном индикаторе
for count in range(36):
tens = count // 10
units = count % 10
print(tens, units) # Только для проверки
if tens == 0:
zero_L(wait)
off()
else:
on_n_L(tens)
time.sleep(wait)
off()
if units == 0:
zero_R(wait)
off()
else:
on_n_R(units)
time.sleep(wait+0.2)
off()
time.sleep(wait)
# Нажмите кнопку, чтобы выйти из цикла
if button.value() == 1:
print("Остановлено кнопкой")
break
Запустите программу! Она считает от нуля до 35 довольно необычным способом: два горящих светодиода с пробелом между ними обозначают ноль — но это работает. Обратите внимание, как мы извлекаем значения десятков и единиц из значения count с помощью // и %.
Почему процедуры так полезны?
Повторное использование — Если у вас есть проверенная процедура для выполнения конкретной задачи, вы можете с уверенностью использовать её в других программах, не «изобретая велосипед» заново и не перетестируя код. Просто скопируйте и вставьте из старой программы в новую и вызовите её.
Структура и ясность — Вы можете разбить очень сложную задачу на серию более мелких задач — процедур, которые можно проектировать, писать и тестировать отдельно. Затем они объединяются вместе и вызываются по очереди из основной программы.
Иерархия — Вы можете выстроить иерархию процедур, где процедуры высокого уровня вызывают процедуры низкого уровня. Мы продемонстрируем это в следующей программе.
Иерархия процедур в MicroPython
Здесь мы создаём процедуру высокого уровня def show_number(z): для отображения двузначных значений на 10-сегментном светодиодном индикаторе. Для этого она вызывает процедуры низкого уровня.
Вернёмся к примеру кода в Thonny. Измените конец программы так, чтобы он выглядел следующим образом:
def zero_R(delay):
leds[7].value(1)
leds[9].value(1)
time.sleep(delay)
def show_number(z): # Показать 1 число: Диапазон: от 0 до 99 включительно
wait = 1
tens = z // 10
units = z % 10
print(tens, units) # Только для проверки
if tens == 0:
zero_L(wait)
off()
else:
on_n_L(tens)
time.sleep(wait)
off()
time.sleep(0.2)
if units == 0:
zero_R(wait)
off()
else:
on_n_R(units)
time.sleep(wait+0.2)
off()
time.sleep(wait)
# ========= Конец определений =========
# Список тестовых значений
test_values = [33, 19, 0, 20, 13, 91, 6, 24, 66, 99]
for v in test_values:
show_number(v)
time.sleep(0.5)
# Нажмите кнопку, чтобы досрочно выйти из цикла
if button.value() == 1:
print("Остановлено кнопкой")
break
print("Готово")
На что стоит обратить внимание
Мы можем использовать список для предоставления тестовых значений. При тестировании нового кода всегда полезно включать граничные значения: ноль и 99 в данном случае.
for v in test_values: извлекает значения из списка и настраивает цикл, не требуя от нас знания длины списка.
Что попробовать
Почему бы не попробовать использовать датчик температуры вместе с процедурами для отображения уровня температуры через светодиоды?
Если у вас есть датчик температуры, совместимый с DS18B20, подключите его к GP1 и протестируйте. Они достаточно точны, водонепроницаемы и очень полезны.
В противном случае смоделируйте набор показаний температуры (от 0 до 42 — возможно, нас ждёт ещё одно жаркое лето!) с помощью списка и протестируйте свой код.
Создайте процедуру для отображения температуры с помощью нескольких вспышек на каждом конце светодиодного индикатора. 14 градусов — это одна вспышка слева и 4 вспышки справа. 7 градусов — это просто 7 вспышек справа, а десятки — одна вспышка слева. Подсказка: def flash_temp(t): — это первая строка определения процедуры.
Как насчёт чисел выше 99?
Как можно обозначить отрицательную температуру? Добавьте ваше решение в код.
В следующем руководстве мы рассмотрим использование другого типа подпрограмм — функции, и устраним незначительную проблему при чтении значений, близких к нулю, с помощью до трёх потенциометров на 10 кОм.
Об авторе
Эта статья написана Тони Гудью. Тони — учитель информатики на пенсии, начавший писать код ещё в 1968 году, когда это называлось программированием — он начинал с FORTRAN IV на IBM 1130! Активный участник сообщества Raspberry Pi, его главные интересы сейчас — программирование на MicroPython, путешествия и фотография.