Raspberry Pi Pico и прерывания: внешние и таймерные прерывания (MicroPython)

В этом руководстве мы покажем, как работать с прерываниями на Raspberry Pi Pico с использованием MicroPython, и создадим несколько примеров проектов с кнопкой и PIR-датчиком движения. Мы рассмотрим внешние прерывания и таймерные прерывания.

Raspberry Pi Pico с прерываниями: внешние и таймерные прерывания (MicroPython)

Содержание руководства

В этом руководстве мы рассмотрим следующие темы:

Предварительные требования — прошивка MicroPython

Для работы с этим руководством вам необходимо установить прошивку MicroPython на плату Raspberry Pi Pico. Также вам понадобится IDE для написания и загрузки кода на плату.

Рекомендуемая IDE для MicroPython на Raspberry Pi Pico — Thonny IDE. Следуйте следующему руководству, чтобы узнать, как установить Thonny IDE, прошить прошивку MicroPython и загрузить код на плату.

Введение в прерывания

Прерывания полезны для автоматического выполнения действий в программах микроконтроллеров и могут помочь решить проблемы синхронизации. Прерывания и обработка событий предоставляют механизмы реагирования на внешние события, позволяя вашему Raspberry Pi Pico быстро реагировать на изменения без постоянного опроса (непрерывной проверки текущего значения пина или переменной).

Что такое прерывания?

Прерывания — это сигналы, которые приостанавливают нормальный поток выполнения программы для обработки определённого события. Когда происходит прерывание, процессор останавливает выполнение основной программы для выполнения задачи, а затем возвращается к основной программе. Эта задача также называется подпрограммой обработки прерывания, как показано на рисунке ниже.

Как работают прерывания

Использование прерываний особенно полезно для запуска действия при обнаружении движения или при нажатии кнопки без необходимости постоянной проверки её состояния; или для выполнения чего-либо периодически без постоянной проверки времени.

Типы прерываний

Существуют различные типы прерываний: внешние прерывания и таймерные прерывания.

  1. Внешние прерывания: вызываются внешними сигналами, такими как нажатие кнопки или показания датчика — это аппаратные прерывания, связанные с определённым GPIO-пином. При изменении состояния пина запускается выполнение задачи.

  2. Таймерные прерывания (таймеры): инициируются на основе временных интервалов, что позволяет выполнять периодические действия — используют аппаратный таймер RP2040 (или RP2350 в зависимости от версии Raspberry Pi Pico) для вызова функций обратного вызова через регулярные интервалы.

Внешние прерывания в MicroPython

Чтобы настроить прерывание в MicroPython, необходимо выполнить следующие шаги:

1. Определите функцию обработки прерывания

Функция обработки прерывания должна быть максимально простой, чтобы процессор быстро вернулся к выполнению основной программы. Лучший подход — сигнализировать основному коду о том, что прерывание произошло, используя глобальную переменную.

Функция обработки прерывания должна принимать параметр типа Pin. Этот параметр передаётся в функцию обратного вызова и ссылается на GPIO, вызвавший прерывание. Вы лучше поймёте эти концепции, когда начнём тестировать примеры.

def handle_interrupt(pin):

2. Настройте пин прерывания как вход. Например:

interrupt_pin = Pin(21, Pin.IN)

3. Прикрепите прерывание к этому пину, вызвав метод irq():

interrupt_pin.irq(trigger=Pin.IRQ_RISING, handler=handle_interrupt)

Метод irq() принимает следующие аргументы:

  • trigger: определяет режим срабатывания. Существуют два различных условия:

    • Pin.IRQ_FALLING: для срабатывания прерывания всякий раз, когда пин переходит из HIGH в LOW;

    • Pin.IRQ_RISING: для срабатывания прерывания всякий раз, когда пин переходит из LOW в HIGH.

  • handler: это функция, которая вызывается при обнаружении прерывания, в данном случае — функция handle_interrupt().

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

Режимы прерываний

Внешние прерывания с кнопкой (примеры)

Чтобы продемонстрировать, как работать с прерываниями, мы создадим несколько простых проектов с кнопкой и светодиодом. Подключите кнопку к GPIO 21 и светодиод к GPIO 20.

Вот список компонентов, необходимых для этих примеров:

  • Raspberry Pi Pico или Raspberry Pi Pico 2

  • Светодиод 5 мм

  • Резистор 220 Ом

  • Тактовая кнопка

  • Макетная плата

  • Соединительные провода

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

Для примеров этого раздела подключите светодиод к GPIO20 и кнопку к GPIO21. Вы можете использовать следующую схему в качестве справки.

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

Пример 1: Обнаружение нажатия кнопки

Это один из самых простых примеров для демонстрации работы прерываний. Скопируйте следующий код в Thonny IDE.

# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/raspberry-pi-pico-interrupts-micropython/

from machine import Pin

button = Pin(21, Pin.IN, Pin.PULL_DOWN)

def button_pressed(pin):
    print("Button Pressed!")

# Attach the interrupt to the button's rising edge
button.irq(trigger=Pin.IRQ_RISING, handler=button_pressed)

В этом примере вы создаёте входной объект с именем button.

button = Pin(21, Pin.IN, Pin.PULL_DOWN)

Затем вы добавляете прерывание к этому пину следующим образом:

button.irq(trigger=Pin.IRQ_RISING, handler=button_pressed)

Эта строка означает, что когда сигнал кнопки переходит в режим RISING (из LOW в HIGH), будет вызвана функция button_pressed.

Функция button_pressed просто выводит в Shell сообщение о том, что кнопка была нажата.

def button_pressed(pin):
    print("Button Pressed!")

Обратите внимание, что определение функции должно быть до создания прерывания. В MicroPython любая пользовательская функция должна быть определена перед её вызовом.

Тестирование кода

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

Raspberry Pi Pico обнаружение нажатия кнопки Raspberry Pi Pico обнаружение нажатия кнопки — MicroPython Shell Thonny IDE

Пример 2: Подсчёт количества нажатий кнопки

В этом примере мы будем подсчитывать количество нажатий кнопки с помощью глобальной переменной. Это поможет вам лучше понять, как работают глобальные переменные.

# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/raspberry-pi-pico-interrupts-micropython/

from machine import Pin

button = Pin(21, Pin.IN, Pin.PULL_DOWN)
counter = 0  # Initialize the button press count

def button_pressed(pin):
    global counter # Declare variable as global
    counter +=1
    print("Button Pressed! Count: ", counter)

# Attach the interrupt to the button's rising edge
button.irq(trigger=Pin.IRQ_RISING, handler=button_pressed)

В этом примере мы создаём глобальную переменную с именем counter — она объявлена и инициализирована вне функции button_pressed.

counter = 0  # Initialize the button press count

Чтобы использовать эту переменную внутри функции, нам нужно использовать ключевое слово global внутри функции — таким образом, переменная counter определена глобально, что делает её доступной и изменяемой как внутри, так и вне функции.

Когда кнопка нажата и вызывается функция button_pressed, глобальная переменная counter увеличивается, и её обновлённое значение выводится на экран.

global counter # Declare variable as global
counter +=1
print("Button Pressed! Count: ", counter)

Примечание: counter +=1 — это сокращение для counter = counter +1

Тестирование кода

Запустите предыдущий код на вашем Raspberry Pi Pico. Обратите внимание, что в Shell будет отображаться количество нажатий кнопки.

Счётчик нажатий кнопки Raspberry Pi Pico MicroPython Thonny IDE

Пример 3: Переключение светодиода при каждом нажатии кнопки

Этот пример аналогичен предыдущему, но мы также переключаем светодиод при нажатии кнопки.

# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/raspberry-pi-pico-interrupts-micropython/

from machine import Pin

led = Pin(20, Pin.OUT)
button = Pin(21, Pin.IN, Pin.PULL_DOWN)
counter = 0   # Initialize the button press count

def button_pressed(pin):
    global counter # Declare variable as global
    counter +=1
    print("Button Pressed! Count: ", counter)

    # Toggle the LED on each button press
    led.value(not led.value())

# Attach the interrupt to the button's rising edge
button.irq(trigger=Pin.IRQ_RISING, handler=button_pressed)

В этом примере нам нужно лишь объявить новый объект Pin для светодиода, в данном случае на GPIO 20.

led = Pin(20, Pin.OUT)

Затем мы добавляем инструкцию для переключения светодиода внутри функции button_pressed.

led.value(not led.value())
Raspberry Pi Pico кнопка и светодиод включён

Дребезг кнопки

Вы наверняка заметили, что иногда кнопка регистрирует более одного нажатия, хотя на самом деле вы нажали только один раз. Это называется механическим дребезгом, и это очень распространённое явление для механических кнопок, таких как тактовые кнопки.

Дребезг кнопки

Это происходит потому, что электрические контакты внутри кнопки соединяются и разъединяются очень быстро, прежде чем достичь стабильного состояния, что заставляет систему регистрировать несколько событий нажатия, вызывая неточный подсчёт.

Чтобы предотвратить эту проблему, мы можем добавить методы устранения дребезга с использованием задержек или таймерных прерываний. Мы рассмотрим таймерные прерывания и события в следующем разделе, где вы узнаете, как устранить дребезг кнопки.

Другие применения

Помимо кнопок, внешние прерывания можно использовать для самых разных приложений на Raspberry Pi Pico, включая входные сигналы датчиков, энкодеры вращения и другие сценарии, управляемые событиями в реальном времени.

Одно из наиболее распространённых применений — PIR-датчики движения. Эти датчики переходят в активное состояние (HIGH) на несколько секунд после обнаружения движения. Поэтому использование прерывания в режиме rising — отличный способ обнаружить, когда датчик фиксирует движение.

Raspberry Pi Pico с PIR-датчиком движения

Существуют также другие датчики, такие как датчики дыма, дождя, уровня воды и звука, среди прочих, которые отправляют цифровой сигнал, когда показания превышают определённый порог. Обнаружение момента отправки такого сигнала датчиком также может быть реализовано с помощью прерываний, поскольку мы можем обнаружить это состояние без необходимости постоянно опрашивать значение на определённом GPIO.


Таймерные прерывания

Использование таймерных прерываний особенно полезно для периодического выполнения действий или выполнения после заданного периода времени без постоянной проверки прошедшего времени.

Таймерные прерывания

Класс Timer в MicroPython

Модуль machine в MicroPython содержит класс Timer, который предоставляет методы для периодического выполнения функции обратного вызова с заданным периодом или однократного выполнения после заданной задержки. Это полезно для планирования событий или выполнения периодических задач без необходимости постоянно проверять прошедшее время.

Давайте кратко рассмотрим конструкторы Timer.

Создание таймера

Чтобы создать таймер, вызовите конструктор Timer() следующим образом:

my_timer = Timer()

Затем инициализируйте таймер с помощью метода init() объекта Timer() и передайте в качестве аргументов режим таймера, период и функцию обратного вызова. Вот пример:

my_timer.init(mode=Timer.PERIODIC, period=1000, callback=timer_callback)

Это инициализирует периодический таймер, который будет вызывать функцию timer_callback каждые 1000 миллисекунд (1 секунду). Вы можете изменить параметр period на любой желаемый период.

Вместо периодического вызова функции обратного вызова вы также можете захотеть вызвать её однократно после заданного времени. Для этого можно использовать режим Timer.ONE_SHOT следующим образом:

my_timer.init(mode=Timer.ONE_SHOT, period=1000, callback=timer_callback)

Эта строка кода настраивает таймер (my_timer) для работы в режиме однократного срабатывания, вызывая указанную функцию обратного вызова (timer_callback) через 1000 миллисекунд.

Пример 1: Мигание светодиодом с помощью таймера

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

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

Мы будем мигать светодиодом, подключённым к GPIO 20. Подключите светодиод к вашему Raspberry Pi Pico на этом GPIO. Вы можете использовать следующую схему в качестве справки.

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

Код

Следующий пример использует класс Timer для мигания светодиодом каждые полсекунды.

# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/raspberry-pi-pico-interrupts-micropython/

from machine import Pin, Timer
from time import sleep

# LED pin
led_pin = 20
led = Pin(led_pin, Pin.OUT)

# Callback function for the timer
def toggle_led(timer):
    led.value(not led.value())  # Toggle the LED state (ON/OFF)

# Create a periodic timer
blink_timer = Timer()
blink_timer.init(mode=Timer.PERIODIC, period=500, callback=toggle_led)  # Timer repeats every half second

# Main loop (optional)
while True:
    print('Main Loop is running')
    sleep(2)

В этом коде мы создаём таймер с именем blink_timer:

blink_timer = Timer()

Затем мы инициализируем таймер со следующими параметрами:

blink_timer.init(mode=Timer.PERIODIC, period=500, callback=toggle_led)

Это означает, что этот таймер будет вызывать функцию toggle_led каждые 500 миллисекунд, бесконечно (или пока вы не остановите программу).

Функция toggle_led, как следует из названия, переключает состояние светодиода:

# Callback function for the timer
def toggle_led(timer):
    led.value(not led.value())  # Toggle the LED state (ON/OFF)

Функции обратного вызова для события таймера должны принимать один аргумент (def toggle_led( timer):). Объект Timer автоматически передаётся в качестве аргумента этой функции при срабатывании события. Вы должны указать этот аргумент, даже если не используете его.

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

while True:
    print('Main Loop is running')
    sleep(2)

Тестирование кода

С подключённым к GPIO 20 светодиодом запустите предыдущий код на вашей плате.

Вы будете получать сообщение «Main Loop is running» каждые две секунды в Shell, в то время как светодиод мигает каждые полсекунды одновременно.

RPi Pico основной цикл работает в Thonny IDE RPi Pico мигающий светодиод RPi Pico мигающий светодиод

Пример 2: Мигание несколькими светодиодами с разной частотой

После тестирования предыдущего примера легко понять, что если создать несколько таймеров, можно выполнять несколько задач с разной частотой. В этом примере мы будем мигать двумя разными светодиодами: одним каждые полсекунды, а другим каждые две секунды.

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

Подключите два светодиода к Raspberry Pi Pico (чтобы различать светодиоды, мы будем использовать разные цвета):

  • Синий светодиод: GPIO 19 > будет мигать каждые полсекунды.

  • Зелёный светодиод: GPIO 20 > будет мигать каждые две секунды;

Вы можете использовать следующую схему для подключения цепи.

Raspberry Pi Pico подключение двух светодиодов

Код

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

# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/raspberry-pi-pico-interrupts-micropython/

from machine import Pin, Timer
from time import sleep

# LEDs
green_led_pin = 19
green_led = Pin(green_led_pin, Pin.OUT)
blue_led_pin = 20
blue_led = Pin(blue_led_pin, Pin.OUT)

# Callback function for the green timer
def toggle_green_led(timer):
    green_led.toggle()  # Toggle the LED state (ON/OFF)
    print('Green LED is: ', green_led.value())

# Callback function for the blue timer
def toggle_blue_led(timer):
    blue_led.toggle()  # Toggle the LED state (ON/OFF)
    print('Blue LED is: ', blue_led.value())

# Create periodic timers
green_timer = Timer()
blue_timer = Timer()

# Init the timers
green_timer.init(mode=Timer.PERIODIC, period=500, callback=toggle_green_led)  # Timer repeats every 0.5 second
blue_timer.init(mode=Timer.PERIODIC, period=2000, callback=toggle_blue_led)  # Timer repeats every 2 seconds

# Main loop (optional)
while True:
    print('Main Loop is running')
    sleep(2)

В этом коде мы создаём два разных таймера, по одному для каждого светодиода:

green_timer = Timer()
blue_timer = Timer()

Затем мы вызываем соответствующие функции обратного вызова с разными интервалами:

# Timer repeats every 0.5 second
green_timer.init(mode=Timer.PERIODIC, period=500, callback=toggle_green_led)
# Timer repeats every 2 seconds
blue_timer.init(mode=Timer.PERIODIC, period=2000, callback=toggle_blue_led)

Функции обратного вызова просто переключают текущее значение светодиода:

# Callback function for the green timer
def toggle_green_led(timer):
    green_led.toggle()  # Toggle the LED state (ON/OFF)
    print('Green LED is: ', green_led.value())

# Callback function for the blue timer
def toggle_blue_led(timer):
    blue_led.toggle()  # Toggle the LED state (ON/OFF)
    print('Blue LED is: ', blue_led.value())

Функция toggle(): в MicroPython существует метод toggle(), который можно использовать для выходных объектов Pin, таких как светодиоды. Этот метод изменяет состояние цифрового пина с текущего на противоположное.

Тестирование кода

Запустите предыдущий код на Raspberry Pi Pico. Вы заметите, что два светодиода мигают с разной частотой.

RPi Pico светодиоды мигают с разной частотой RPi Pico светодиоды мигают с разной частотой RPi Pico светодиоды мигают с разной частотой RPi Pico светодиоды мигают с разной частотой

Одновременно вы будете получать сообщение из цикла while каждые две секунды. Это показывает, что другие задачи не мешают работе нашего цикла.

RPi Pico несколько таймеров и цикл работают одновременно

Пример 3: Устранение дребезга кнопки с помощью таймера

Как мы упоминали ранее, мы можем использовать таймеры для устранения дребезга кнопки, чтобы избежать ложных нажатий. Существует множество способов устранения дребезга кнопки, приведённый ниже пример — лишь один из множества вариантов.

# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/raspberry-pi-pico-interrupts-micropython/

from machine import Pin, Timer

led = Pin(20, Pin.OUT)
button = Pin(21, Pin.IN, Pin.PULL_DOWN)
counter = 0  # Initialize the button press count
debounce_timer = None

def button_pressed(pin):
    global counter, debounce_timer  # Declare variables as global

    if debounce_timer is None:
        counter += 1
        print("Button Pressed! Count: ", counter)

        # Toggle the LED on each button press
        led.toggle()

        # Start a timer for debounce period (e.g., 200 milliseconds)
        debounce_timer = Timer()
        debounce_timer.init(mode=Timer.ONE_SHOT, period=200, callback=debounce_callback)

def debounce_callback(timer):
    global debounce_timer
    debounce_timer = None

# Attach the interrupt to the button's rising edge
button.irq(trigger=Pin.IRQ_RISING, handler=button_pressed)

Вы уже должны быть знакомы с большей частью кода, поэтому мы рассмотрим только ключевые части этого примера.

Этот пример использует одноразовый таймер (debounce_timer), инициализируемый при каждом нажатии кнопки с указанным периодом подавления дребезга, в данном примере 200 миллисекунд.

debounce_timer.init(mode=Timer.ONE_SHOT, period=200, callback=debounce_callback)

Как и в предыдущих примерах, мы привязываем прерывание к пину кнопки, которое вызовет функцию button_pressed в режиме rising (при нажатии кнопки).

button.irq(trigger=Pin.IRQ_RISING, handler=button_pressed)

При нажатии кнопки вызывается функция button_pressed, которая увеличивает счётчик, переключает светодиод и запускает одноразовый таймер для подавления дребезга.

counter += 1
print("Button Pressed! Count: ", counter)
# Toggle the LED on each button press
led.toggle()
# Start a timer for debounce period (e.g., 200 milliseconds)
debounce_timer = Timer()
debounce_timer.init(mode=Timer.ONE_SHOT, period=200, callback=debounce_callback)

Функция debounce_callback таймера вызывается, когда одноразовый таймер истекает, сбрасывая debounce_timer в None.

def debounce_callback(timer):
    global debounce_timer
    debounce_timer = None

Если таймер ещё не истёк и обнаружено ещё одно нажатие кнопки, оно не будет учтено, потому что debounce_timer не был сброшен в None:

if debounce_timer is None:

Обратите внимание, что мы используем глобальные переменные, чтобы иметь к ним доступ во всех частях кода, включая внутри функций:

global counter, debounce_timer  # Declare variables as global

Это лишь один из многих способов устранения дребезга кнопки.

В Python/MicroPython значение None часто используется как заполнитель или значение по умолчанию для обозначения отсутствия осмысленного значения. В предыдущем примере None помогает нам управлять состоянием переменной debounce_timer.

Переменная debounce_timer изначально установлена в None, чтобы указать, что в данный момент нет активного таймера подавления дребезга.

debounce_timer = None

В функции button_pressed следующее условие проверяет, нет ли активного таймера подавления дребезга.

if debounce_timer is None:

Если активного таймера нет (None), выполняются действия по нажатию кнопки, и инициируется новый одноразовый таймер debounce_timer.

debounce_timer = Timer()
debounce_timer.init(mode=Timer.ONE_SHOT, period=200, callback=debounce_callback)

Когда одноразовый таймер истекает, вызывается функция debounce_callback, которая устанавливает debounce_timer обратно в None, указывая, что период подавления дребезга завершён.

def debounce_callback(timer):
    global debounce_timer
    debounce_timer = None

Тестирование кода

Запустите код на вашем Raspberry Pi Pico. Нажмите кнопку несколько раз. Вы заметите, что ложных срабатываний больше не будет. Если они всё же есть, вам нужно увеличить период подавления дребезга в 200 миллисекунд для debounce_timer.

debounce_timer.init(mode=Timer.ONE_SHOT, period=200, callback=debounce_callback)
Raspberry Pi Pico кнопка и светодиод включён RPi Pico устранение дребезга кнопки с таймером

Пример 4: Прерывания и таймеры с PIR-датчиком движения

В этом примере мы продемонстрируем использование одноразовых таймеров с PIR-датчиком движения. Мы создадим простой проект с PIR-датчиком движения. При обнаружении движения мы включим светодиод на 20 секунд. После этого времени светодиод выключится.

Использование PIR-датчика движения для управления светодиодом

PIR-датчик движения

Пассивный инфракрасный (PIR) датчик движения предназначен для обнаружения инфракрасного излучения, испускаемого объектами в его поле зрения. PIR-датчики обнаруживают изменения инфракрасного излучения, преимущественно испускаемого живыми существами. Когда в зоне действия датчика происходит движение, возникают колебания в обнаруженном инфракрасном излучении. На следующем изображении показан популярный датчик движения HC-SR501.

PIR-датчик движения HC-SR501

Для более подробного руководства по PIR-датчику движения с Raspberry Pi Pico мы рекомендуем прочитать следующее руководство:

Датчик выдаёт сигнал HIGH при обнаружении движения или LOW при отсутствии движения. Цифровой выход PIR-датчика может быть считан GPIO-пином Raspberry Pi Pico, что позволяет программировать определённые действия на основе обнаруженного статуса движения. В этом примере мы просто включим светодиод, но его можно использовать для полезных приложений, таких как отправка электронной почты, включение сигнализации и т.д.

Важная концепция PIR-датчиков движения — время удержания (время сброса или задержка датчика) — это продолжительность, в течение которой выход PIR-датчика движения остаётся в состоянии HIGH после обнаружения движения, прежде чем вернуться в состояние LOW.

Принцип работы PIR-датчика движения

Необходимые компоненты

Для этого примера нам понадобятся следующие компоненты:

  • Raspberry Pi Pico или Raspberry Pi Pico 2

  • Светодиод 5 мм

  • Резистор 220 Ом

  • PIR-датчик движения (Mini AM312)

  • Макетная плата

  • Соединительные провода

Примечание: PIR-датчик движения Mini AM312, который мы используем в этом проекте, работает при напряжении 3.3 В. Однако, если вы используете другой PIR-датчик, например HC-SR501, он работает при 5 В. Вы можете либо модифицировать его для работы при 3.3 В, либо просто запитать его через пин VBUS, который выдаёт 5 В, если вы питаете Raspberry Pi Pico через USB напряжением 5 В.

Этот пример также можно реализовать с кнопкой.

На рисунке ниже мы приводим распиновку PIR-датчика движения Mini AM312. Если вы используете другой датчик движения, проверьте его распиновку перед сборкой схемы.

Распиновка мини PIR-датчика движения

Следующая таблица показывает подключения между PIR-датчиком движения и Raspberry Pi Pico:

PIR-датчик движения

Raspberry Pi Pico

GND

GND

Data

Любой цифровой пин (например, GPIO 21)

3.3V

3V3(OUT)

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

Мы подключим вывод данных PIR-датчика движения к GPIO 21 и светодиод к GPIO 20.

Схема подключения PIR-датчика и светодиода к RPi Pico

Код

Следующий код установит PIR-датчик движения в качестве прерывания. При обнаружении движения светодиод включится на 20 секунд после последнего обнаруженного движения. По истечении этого периода светодиод выключится, и датчик движения снова сможет обнаруживать движение.

В этом примере вы увидите ещё одно применение одноразовых таймеров, а также узнаете об обнаружении движения с помощью PIR-датчиков.

# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/raspberry-pi-pico-interrupts-micropython/

from machine import Pin, Timer
import time

# PIR sensor pin
pir_pin = 21
# LED pin
led_pin = 20

# Set up LED and PIR sensor
led = Pin(led_pin, Pin.OUT)
pir = Pin(pir_pin, Pin.IN)

# Create timer
motion_timer = Timer()

# Create timer
motion = False
motion_printed = False

def handle_motion(pin):
    global motion
    motion = True
    motion_timer.init(mode=Timer.ONE_SHOT, period=20000, callback=turn_off_led)

def turn_off_led(timer):
    global motion, motion_printed
    led.off()  # Turn off the LED
    print('Motion stopped!')
    motion = False
    motion_printed = False

# Attach interrupt to the PIR motion sensor
pir.irq(trigger=Pin.IRQ_RISING, handler=handle_motion)

while True:
    if motion and not motion_printed:
        print('Motion detected!')
        led.on()  # Turn on the LED
        motion_printed = True
    elif not motion:
        time.sleep(0.1)
        # Other tasks that you might need to do in the loop

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

Мы начинаем с объявления светодиода и PIR-датчика движения на соответствующих GPIO-пинах, к которым они подключены.

# PIR sensor pin
pir_pin = 21
# LED pin
led_pin = 20
# Set up LED and PIR sensor
led = Pin(led_pin, Pin.OUT)
pir = Pin(pir_pin, Pin.IN)

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

# Create timer
motion_timer = Timer()

Мы создаём две глобальные переменные: motion и motion_printed. Переменная motion используется для сигнализации во всём коде о том, было ли обнаружено движение или нет. Переменная motion_printed позволяет нам проверить, было ли уже выведено сообщение «Motion detected!» в Shell.

# Create global variables
motion = False
motion_printed = False

Мы привязываем прерывание к PIR-датчику движения в режиме rising. Это означает, что при обнаружении движения будет вызвана функция handle_motion.

pir.irq(trigger=Pin.IRQ_RISING, handler=handle_motion)

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

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

Хорошей практикой является создание ISR как можно более компактными, чтобы процессор быстро вернулся к выполнению основной программы. Лучший подход — сигнализировать основному коду о том, что прерывание произошло, используя флаг или счётчик, например. Здесь мы используем переменную motion в качестве флага для указания на обнаружение движения. Затем основной код должен содержать все действия, которые мы хотим выполнить при обнаружении движения, такие как включение светодиода и вывод сообщения в Shell.

Более подробная информация о написании ISR доступна здесь.

Продолжим рассмотрение кода. Внутри функции handle_motion мы устанавливаем глобальную переменную motion в True, чтобы во всех частях кода было известно, что движение обнаружено, и инициализируем одноразовый таймер, который выключит светодиод через 20 секунд, вызвав функцию turn_off_led().

def handle_motion(pin):
    global motion
    motion = True
    motion_timer.init(mode=Timer.ONE_SHOT, period=20000, callback=turn_off_led)

В цикле While мы проверяем, было ли обнаружено движение или нет. Если движение было обнаружено и сообщение ещё не было выведено в Shell, мы выводим сообщение, включаем светодиод и устанавливаем переменную motion_printed в True.

while True:
    if motion and not motion_printed:
        print('Motion detected!')
        led.on()  # Turn on the LED
        motion_printed = True

Через 20000 миллисекунд выполнится функция turn_off_led, которая выключит светодиод и установит глобальные переменные motion и motion_printed в False.

def turn_off_led(timer):
    global motion, motion_printed
    led.off()  # Turn off the LED
    print('Motion stopped!')
    motion = False
    motion_printed = False

Переменная motion может снова стать True только при обнаружении движения и вызове функции handle_motion.

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

Тестирование кода

Запустите предыдущий код на вашем Raspberry Pi Pico. Помашите рукой перед PIR-датчиком движения.

RPi Pico тестирование PIR-датчика движения

В Shell будет выведено сообщение, и светодиод включится на 20 секунд.

RPi Pico — тестирование PIR-датчика движения — Shell Thonny IDE

Через 20 секунд светодиод выключится.

RPi Pico тестирование PIR-датчика движения — светодиод выключен

Заключение

В этом руководстве вы узнали о прерываниях на Raspberry Pi Pico, программируемом с помощью MicroPython. Мы рассмотрели внешние прерывания и таймерные прерывания.

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