MicroPython Skill Builders — №1: Умные циклы
В этой новой серии MicroPython Skill Builders, которую ведёт Тони Гудхью, мы стремимся помочь вам улучшить навыки программирования на MicroPython, знакомя с новыми компонентами и техниками написания кода — всё это с использованием Raspberry Pi Pico и нескольких доступных, недорогих компонентов.
В первом выпуске мы начнём с управления циклами.
Что вам понадобится
Предполагается, что вы уже установили Thonny на компьютер и настроили Raspberry Pi Pico с актуальной прошивкой MicroPython (UF2). Если нет — ознакомьтесь с руководством по началу работы с Raspberry Pi Pico, где это рассмотрено подробно.
Также может быть полезно вернуться к урокам серии Advent Calendar, посвящённым светодиодам, кнопкам и потенциометрам. Вам понадобится:
Raspberry Pi Pico с контактными разъёмами
Полноразмерная макетная плата (breadboard)
Кабель Micro-USB (для питания и программирования Pico)
Набор перемычек «папа-папа» (смешанный набор также доступен)
Тактильные кнопки (смешанный набор также доступен)
Светодиоды 5 мм (смешанный набор также доступен)
Токоограничивающие резисторы (330 Ом)
Потенциометр 10 кОм (нам может понадобиться несколько штук в будущих выпусках)
Простой цикл while
Скопируйте следующий код в Thonny, сохраните его на компьютере под именем Infinite.py и запустите:
import time
while True:
print("Hello!")
time.sleep(0.2)
Это пример бесконечного цикла: он продолжает выводить строки до тех пор, пока вы не нажмёте кнопку STOP в Thonny, поскольку две строки с отступом повторяются бесконечно.
Цикл while, управляемый кнопкой
Во многих проектах было бы удобно останавливать цикл нажатием кнопки. Сделаем это на макетной плате рядом с Pico. Соберите схему, показанную на рисунке ниже:
Здесь оранжевые провода — 3,3 В, чёрные провода — земля (GND). Они подключены к шинам питания по верхнему и нижнему краям макетной платы (на некоторых больших макетных платах требуются перемычки в центре шин питания). Выводы кнопки подключены к 3,3 В и GP15 коричневым проводом. Катод (-) светодиода, более короткий вывод, идёт на GND, а анод (+), более длинный вывод, подключён к GP16 через резистор 330 Ом синим проводом. Средний вывод потенциометра (движок) подключён к ADC0 на GP26 зелёным проводом, а внешние выводы — к шинам 3,3 В и GND.
Совет: мы будем использовать эту базовую схему в нескольких примерах данной серии уроков, поэтому не разбирайте её слишком рано. По мере продвижения мы будем добавлять другие компоненты.
Код
Скопируйте и вставьте следующую программу в Thonny, сохраните её под именем Loop1.py и запустите.
Цикл запустится автоматически: светодиод включится и в Thonny начнёт отображаться счётчик. Когда вы нажмёте кнопку, светодиод погаснет, подсчёт остановится и выведутся два сообщения:
# Button-controlled loop example
# Button on GP 15 and LED on GP16
# Import the libraries
from machine import Pin
import time
# 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
led = Pin(16, Pin.OUT)
# A button-controlled loop
# Loops until the button is pressed
count = 0
led.value(1) # Turn on LED as looping starts
while (button.value() == 0): # Start looping - Button's normal state is 0
print(count)
count = count + 1 # Increment counter
time.sleep(0.2) # Wait a 1/5 second
led.value(0) # Turn off LED – Looping finished
print("\nButton pressed = HALT")
print("\nCount: " + str(count))
Вот что должно отображаться в Thonny после нажатия кнопки:
>>> %Run -c $EDITOR_CONTENT
0
1
2
3
4
5
6
7
8
9
10
11
Button pressed = HALT
Count: 12
>>>
На что обратить внимание
Перед последними двумя выведенными строками есть пустая строка
Конечное значение счётчика на единицу больше последнего числа, выведенного в цикле
Разберём код подробнее
# Button-controlled loop example
Напоминание о том, что делает программа.
# Button on GP 15 and LED on GP16
Запись о том, какие компоненты подключены к каким выводам GP. Это очень полезно, если вы вернётесь к программе позже и захотите восстановить схему.
# Import the libraries
from machine import Pin
import time
Импорт библиотек, необходимых для работы программы. Pin нужен для кнопки и светодиода, а time — для замедления работы.
# Set up button on GP15 as INPUT with PULL_DOWN
button = Pin(15, Pin.IN, Pin.PULL_DOWN)
Кнопка подключена к GP15 как вход (INPUT) с внутренним подтягивающим резистором к GND (PULL_DOWN). В ненажатом состоянии она удерживается в значении 0 на выводе. При нажатии кнопка соединяет вывод с 3,3 В на шине питания, и значение становится равным 1.
# Set up LED on GP16 on Pi Pico
led = Pin(16, Pin.OUT)
Светодиод подключён к GP16 как выход (OUTPUT). Когда вывод переходит из 0 в 1, светодиод включается.
count = 0
led.value(1) # Turn on LED
Здесь мы инициализируем счётчик значением 0 и включаем светодиод.
while button.value() == 0
Здесь начало цикла. Он начинается с условия проверки: button.value() == 0. Это возвращает логическое значение: True или False. Если True — начинаем цикл. Если False — пропускаем все инструкции цикла и переходим к инструкциям ниже него — с отступом (мы также можем представить False и True числами: False — ноль, True — 1).
while button.value() == 0: # Start looping - Button's normal state is 0
print(count)
count = count + 1
time.sleep(0.2) # Wait a 1/5 second
Равно ли значение кнопки нулю? Если да — делая результат проверки True — мы начинаем цикл while. Нормальное состояние кнопки — ноль, так как она настроена с PULL_DOWN. Если кнопка не нажата, начинается выполнение цикла. При каждом обходе цикла та же проверка выполняется перед повторным запуском кода цикла (обратите внимание: здесь используется двойной знак равенства для сравнения; одиночный знак равенства — для присваивания). В цикле, в коде с отступом после двоеточия, мы выводим текущее значение переменной-счётчика. Затем увеличиваем счётчик (прибавляем к нему 1) и ждём 1/5 секунды. Когда кнопка нажата хотя бы на время одного обхода цикла, значение меняется на 1 — так как она подключена к 3,3 В. Проверка в начале цикла не проходит, поскольку значение больше не равно нулю, и мы выходим из цикла.
led.value(0) # Turn off LED
Ниже цикла мы выключаем светодиод, поскольку цикл завершён, и выводим значение счётчика. Команда \n (обратная косая черта + n) переводит строку в начале вывода.
print("\nButton pressed = HALT")
print("\nCount: " + str(count))
Здесь мы сообщаем пользователю, что кнопка была нажата, и показываем конечный счётчик.
В ходе цикла мы считали с нуля: 0, 1, 2 … если посчитать количество выведенных строк, оно совпадёт со значением count в последней строке. «Компьютеры считают с нуля, а люди — с единицы…». Этот метод управления длиной цикла очень полезен, когда мы хотим иметь возможность прервать циклически выполняемую программу и перейти к следующей части ниже цикла. Пользователь управляет тем, как долго программа остаётся в текущем цикле. Мы можем использовать другую технику для выхода из длинного счётного цикла нажатием кнопки (дополнительные сведения о счётных циклах и базовом синтаксисе Python см. в приложении в конце этого урока).
Использование break в циклах
В следующем примере мы выполняем 15 итераций цикла (от 0 до 14), выводя значения по ходу.
Мы проверяем значение кнопки внутри цикла. Если она нажата, используем инструкцию break, чтобы выйти из цикла, и затем продолжаем выполнение инструкций после него. Если кнопка не нажата, счётный цикл завершается сам по себе, когда счётчик достигает предела.
Скопируйте код ниже в Thonny и попробуйте:
# Button controlled counted loop example
# Button on GP 15 and LED on GP16
# Import the libraries
from machine import Pin
import time
# 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
led = Pin(16, Pin.OUT)
led.value(1) # Turn on LED
# Counted Loop
for count in range(15): # Start looping
print(count)
time.sleep(0.2) # Wait a 1/5 second
if button.value() == 1:
break
led.value(0) # Turn off LED
print("\nCount: " + str(count))
Другие способы использования break
Ещё одна техника позволяет выйти из цикла при наступлении любого из нескольких событий.
Скопируйте новую программу ниже, сохраните её, запустите, а затем читайте дальше, чтобы понять, как она работает:
# Loop control - Multiple events
# Button on GP 15, LED on GP16 and potentiometer on ADC0
# Import the libraries
from machine import Pin, ADC
import time
import random # Random number generator
# === 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
led = Pin(16, Pin.OUT)
# Set up the potentiometer on ADC pin 26, ADC0
potentiometer = ADC(Pin(26))
# === Main ===
count = 0
r = 0
running = True
led.value(1) # Turn on LED
while running: # Start looping – an infinite loop in effect
print(count)
count = count + 1
time.sleep(0.2) # Wait a second
# Check the button - Do we want to stop looping?
if button.value() == 1: # If button is pressed
print("\nButton pressed = HALT")
r = 1
running = False
# Check for a second condition – value too big
count_squared = count * count
if count_squared > 120:
print("\n Value too big")
r = 2
running = False
# Check for a third condition - random event
event = random.randint(0,20)# Generate random number
if event > 18:
print("\Random event " + str(event))
running = False
r = 3
# Check for a fourth condition - pot value too low
# Read the potentiometer value
pot = potentiometer.read_u16()
if pot < 750:
print("\nADC reading too low")
running = False
r = 4
led.value(0) # Turn off LED
print("\nReason: " + str(r))
print("\nCount: " + str(count))
# Flash the built-in LED to indicate the stop method
# Set up LED on GP25 on Pi Pico
led_green = Pin(25, Pin.OUT)
for i in range(r):
led_green.value(1)
time.sleep(0.5)
led_green.value(0)
time.sleep(0.5)
Мы устанавливаем значение переменной running в True и запускаем, по сути, бесконечный цикл. Если изменить значение running на False внутри цикла — цикл остановится. Здесь мы задали четыре разных способа остановки цикла:
Нажатие кнопки
Вычисленное значение становится слишком большим — выходит за допустимый диапазон
Случайное событие — здесь имитируемое, но может быть получено от датчика — слишком горячо?
Показание потенциометра опускается слишком низко — выходит за диапазон
В конце программы мы мигаем зелёным встроенным светодиодом на Pico, чтобы показать, каким образом был остановлен цикл.
event = random.randint(0,20)
Эта строка генерирует случайное целое число от 0 до 20 включительно — отличный инструмент для симуляции.
pot = potentiometer.read_u16()
if pot < 750:
Этот код получает значение от аналого-цифрового преобразователя (ADC0) — целое число в ожидаемом диапазоне от 0 до 65535. Мы проверяем низкое значение показания. Потенциометр подключён к ADC0 и управляет входным значением.
Циклы с ограничением по времени
Инструкция now = time.time() сохраняет текущее время в секундах.
Мы можем использовать это для выполнения циклов в течение заданного времени, как показано в примере ниже. Время одного обхода цикла составляет около 1 секунды, поэтому, возможно, вам придётся удерживать кнопку нажатой не менее секунды для досрочного выхода. Обратите внимание: второй цикл запускается только в том случае, если кнопка не нажата. Если кнопка всё ещё нажата между циклами, пользователю предлагается её отпустить.
# Loop control - Time Controlled Loop
# Button on GP 15, LED on GP16 and potentiometer on ADC0
# Import the libraries
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
led = Pin(16, Pin.OUT)
# === Main ===
now = time.time() # Current time from seconds counter
stop = now + 5 # 5 seconds later
print("First loop - seconds counter\n")
led.value(1) # Turn on LED
while time.time() < stop: # Looping until stop time
print(str(time.time()))
time.sleep(1) # Wait a second
# Check the button - Do we want to stop looping?
if button.value() == 1: # If button is pressed
print("\nButton pressed = HALT")
break
led.value(0) # Turn off LED
print("\nFirst loop finished")
if button.value() == 1:
print("\n=== Release button ===")
# Wait for button to be released
while button.value() == 1: continue
# Start second loop
print("\nSecond loop - Flashing LED")
for i in range(10):
led.value(1) # Turn on LED
time.sleep(0.15)
led.value(0) # Turn off LED
time.sleep(0.15)
print("\nEnd of Program - Bye")
Умные циклы — пройдено!
Надеемся, вам было полезно — это первый выпуск серии уроков, направленных на повышение ваших навыков в MicroPython, которые можно применять с Raspberry Pi Pico и многими другими платами разработчика.
Вам больше не нужно кликать красную кнопку STOP в Thonny — теперь вы можете аккуратно завершить программу несколькими способами и вывести соответствующее сообщение. Мы будем использовать ту же схему в начале следующего урока, поэтому, возможно, вы захотите сохранить её в собранном виде.
Приложение — основные операторы и операции Python
Справочная информация, которая поможет вам в ходе этой серии уроков.
Базовая арифметика в MicroPython
Опер. |
Действие |
Пример |
Результат |
Примечания |
|---|---|---|---|---|
+ |
Сложение |
3 + 5 |
8 |
|
- |
Вычитание |
8 - 3 |
5 |
|
* |
Умножение |
8 * 3 |
24 |
|
/ |
Деление |
9 / 4 |
2.25 |
|
% |
Остаток от деления |
9 % 4 |
1 |
Остаток после деления |
// |
Целочисленное деление |
5 // 4 |
2 |
|
** |
Возведение в степень |
3 ** 2 |
9 |
Возвести в степень |
Помните БОДМАС из уроков математики? (Скобки, Степень, Деление, Умножение, Сложение, Вычитание — порядок выполнения операций.) Именно в таком порядке Python выполняет арифметику — слева направо на одном уровне приоритета.
Вот пример:
10 + (18 – 4)/(3 + 4) — вычитание в первых скобках — первым делом
10 + 14 /(3 + 4) — вторые скобки
10 + 14 /7 — деление перед сложением
10 + 2 — наконец, сложение
12
В Thonny можно выполнить отдельную инструкцию прямо в окне Shell внизу экрана, введя её в строку >>> и нажав Enter:
Целые числа и числа с плавающей точкой
Целые числа без десятичной точки называются целочисленными (integers), а числа с десятичной точкой — числами с плавающей точкой (floats).
Счётные циклы
for c in range(10): # Set up for loop
print(c) # Print current value
print("Done")
Это сокращённая версия очень полезной инструкции:
for x in range(start, stop, step):
Где start — первое значение, stop — граничное условие, а step — шаг между значениями.
for x in range(5): # gives values of 0,1,2,3 and 4 – NOT 5
print(x)
for x in range(2,9): # gives values of 2,3,4,5,6,7 and 8 – NOT 9
for x in range(10,31,5): # gives 10,15,20,25,30 -- steps up in fives
for x in range(5,-7,-2): # gives 5,3,1,-1,-3,-5 -- steps down in twos
Нужно очень внимательно думать об используемом граничном значении, и стоит всегда проверять результат.
Условия Python
Python поддерживает стандартные логические условия из математики:
Равно:
a == bНе равно:
a != bМеньше:
a < bМеньше или равно:
a <= bБольше:
a > bБольше или равно:
a >= b
Оператор if
Краткий пример работы оператора if:
a = 456
b = 378
if b > a:
print("b is greater than a")
elif a == b:
print("a and b are equal")
else:
print("a is greater than b")
Готовы к продолжению? Следующий урок этой серии доступен на нашем сайте!
Об авторе
Эта статья написана Тони Гудхью. Тони — учитель информатики на пенсии, начавший писать код ещё в 1968 году, когда это называлось «программирование» — он начал с FORTRAN IV на IBM 1130! Активный участник сообщества Raspberry Pi, его основные интересы сегодня — программирование на MicroPython, путешествия и фотография.