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. Соберите схему, показанную на рисунке ниже:

Схема подключения на макетной плате для MicroPython Skill Builder 1

Здесь оранжевые провода — 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:

Пример работы в Shell-окне Thonny

Целые числа и числа с плавающей точкой

Целые числа без десятичной точки называются целочисленными (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, путешествия и фотография.