В предыдущей главе мы успешно считывали состояние кнопки, но делали это внутри бесконечного цикла while True
, постоянно опрашивая пин с помощью GPIO.input()
. Этот метод, известный как опрос (polling), имеет серьезный недостаток: он непрерывно загружает ядро процессора, даже когда ничего не происходит.
Для создания эффективных и отзывчивых программ применяется более совершенный подход — обработка событий, основанная на механизме аппаратных прерываний.
От опроса к событиям: Философия подхода
- Модель опроса: Ваша программа постоянно и активно спрашивает у системы: «Состояние изменилось? А сейчас? А сейчас?». 99.9% ответов будут "нет", но процессор все равно тратит на это ресурсы.
- Событийная модель: Ваша программа говорит системе: «Пожалуйста, следи за этим пином. Когда на нем произойдет нужное мне событие, сообщи мне об этом. А до тех пор я буду заниматься другими делами (или просто спать)».
Этот подход кардинально снижает нагрузку на процессор и позволяет системе реагировать на внешние сигналы практически мгновенно.
Как это реализовано в RepkaPi.GPIO
Библиотека RepkaPi.GPIO
использует стандартные и высокоэффективные механизмы ядра Linux для реализации событийной модели.
- Интерфейс
sysfs
: Когда вы просите библиотеку отслеживать событие, она записывает тип нужного триггера (например,rising
илиfalling
) в специальный файл/sys/class/gpio/gpioX/edge
. - Системный вызов
epoll
: Вместо бесконечного цикла, библиотека использует системный вызовepoll
. Он позволяет процессу "уснуть" и передать ядру Linux задачу по наблюдению за файловым дескриптором, связанным с GPIO. - Аппаратное прерывание: Когда на пине происходит физическое событие, аппаратура SoC генерирует прерывание. Ядро Linux перехватывает его и, видя, что за этим пином велось наблюдение, "будит" вашу программу.
- Функция обратного вызова (Callback): Пробудившись, библиотека вызывает предоставленную вами Python-функцию — обработчик, в котором и находится логика реакции на событие.
Весь этот сложный процесс происходит в фоновом потоке, что позволяет вашему основному коду выполняться, не блокируясь в ожидании.
Практическое применение: Реагируем на нажатие кнопки
Давайте модернизируем наш предыдущий пример. Теперь программа не будет постоянно проверять кнопку. Вместо этого мы "попросим" систему сообщить нам, когда кнопка будет нажата, и автоматически вызовем функцию для переключения светодиода.
1. Аппаратная часть и схема: Используем ту же самую схему, что и в предыдущей главе, с внешним стягивающим (pull-down) резистором.
- Светодиод подключен к пину №11.
- Кнопка подключена к пину №7.
- Состояние кнопки по умолчанию —
LOW
, при нажатии —HIGH
. Следовательно, мы будем отслеживать нарастающий фронт (GPIO.RISING
).
2. Ключевые функции API:
GPIO.add_event_detect(pin, edge, callback, bouncetime)
: Основная функция. Настраивает отслеживание событияedge
(RISING, FALLING, BOTH) на пинеpin
и привязывает к нему функциюcallback
.bouncetime
(в миллисекундах): Крайне полезный параметр для подавления "дребезга контактов" — ложных срабатываний, возникающих при физическом замыкании/размыкании механической кнопки. Библиотека будет игнорировать последующие события в течение указанного времени после первого срабатывания.
3. Программный код (event_driven.py
)
# -*- coding: utf-8 -*-
import RepkaPi.GPIO as GPIO
from time import sleep
# --- 1. Настройка ---
GPIO.setmode(GPIO.BOARD)
BUTTON_PIN = 7
LED_PIN = 11
# Настраиваем пины на вход и выход
GPIO.setup(LED_PIN, GPIO.OUT)
GPIO.setup(BUTTON_PIN, GPIO.IN)
# --- 2. Функция обратного вызова (Callback) ---
# Эта функция будет автоматически вызвана в фоновом потоке, когда произойдет событие.
def toggle_led(channel):
"""
Переключает состояние светодиода.
Параметр 'channel' обязателен - библиотека передает в него номер пина,
на котором произошло событие.
"""
print(f"Событие обнаружено на канале {channel}!")
# Считываем текущее состояние светодиода и инвертируем его
current_state = GPIO.input(LED_PIN)
GPIO.output(LED_PIN, not current_state)
# --- 3. Регистрация события и основной цикл ---
try:
print("Программа запущена. Нажимайте на кнопку, чтобы переключать светодиод.")
print("Для выхода нажмите CTRL+C.")
# Регистрируем отслеживание события:
# - на пине BUTTON_PIN
# - по нарастающему фронту (RISING)
# - с вызовом функции toggle_led
# - с защитой от дребезга в 200 мс
GPIO.add_event_detect(BUTTON_PIN, GPIO.RISING, callback=toggle_led, bouncetime=200)
# Основной поток программы теперь свободен.
# Он может выполнять другие задачи или просто спать, не тратя ресурсы.
while True:
# Здесь могла бы быть другая полезная работа...
sleep(1) # Программа просто ждет, пока ее не прервут.
# --- 4. Очистка ---
except KeyboardInterrupt:
print("\nЗавершение работы.")
finally:
# Эта команда не только сбросит настройки пинов, но и корректно
# остановит фоновый поток отслеживания событий.
GPIO.cleanup()
4. Запуск и результат
Запустите скрипт: python3 event_driven.py
. Программа выведет сообщение и "затихнет". Теперь каждое нажатие на кнопку будет вызывать функцию toggle_led
, которая переключит состояние светодиода. При этом основной цикл программы не выполняет никаких проверок — вся работа происходит асинхронно, по событию от аппаратуры.