MicroPython: Прерывания по таймеру (Timer Interrupts) с ESP32/ESP8266
В этом руководстве вы узнаете, как использовать прерывания по таймеру (таймеры и обработку событий) с ESP32 и ESP8266, запрограммированными на MicroPython. Прерывания по таймеру позволяют планировать и выполнять определённые задачи через регулярные интервалы или после заданной задержки.
Содержание:
В этом руководстве мы рассмотрим следующие темы:
Предварительные требования
Прежде чем приступить к этому руководству, убедитесь, что выполнены следующие предварительные требования.
Прошивка MicroPython
Для выполнения этого руководства вам необходима прошивка MicroPython, установленная на вашу плату ESP32 или ESP8266. Вам также понадобится IDE для написания и загрузки кода на плату. Мы рекомендуем использовать Thonny IDE или uPyCraft IDE:
Thonny IDE:
uPyCraft IDE:
Если вам нравится программировать в VS Code, есть такой вариант: MicroPython: Программирование ESP32/ESP8266 с VS Code и Pymakr
Необходимые компоненты
Для выполнения примеров из этого руководства вам понадобятся следующие компоненты:
ESP32 или ESP8266
2x светодиода
2x резистора 220 Ом
Кнопка (pushbutton)
Макетная плата (breadboard)
Соединительные провода (jumper wires)
Введение в прерывания
Прерывания полезны для автоматического выполнения действий в программах микроконтроллеров и могут помочь решить проблемы с синхронизацией. Прерывания и обработка событий предоставляют механизмы для реагирования на события, позволяя ESP32/ESP8266 быстро реагировать на изменения без постоянного опроса (непрерывной проверки текущего значения пина, переменной или постоянной проверки прошедшего времени).
Использование прерываний по таймеру особенно полезно для периодического выполнения действий или после заданного периода времени без постоянной проверки прошедшего времени.
Типы прерываний
Существуют различные типы прерываний: внешние прерывания и прерывания по таймеру:
Внешние прерывания: вызываются внешними сигналами, такими как нажатие кнопки или показание датчика — это аппаратный механизм, связанный с определённым выводом GPIO. Когда состояние вывода изменяется, запускается задача — подробнее о внешних прерываниях с ESP32/ESP8266 читайте в статье: MicroPython: Внешние прерывания с ESP32 и ESP8266.
Прерывания по таймеру (таймеры): инициируются на основе временных интервалов, позволяя выполнять периодические действия — используют аппаратный таймер платы для запуска callback-функций через регулярные интервалы. В этой статье мы рассмотрим именно этот тип прерываний.
Класс Timer в MicroPython
Модуль machine в MicroPython содержит класс Timer, который предоставляет методы для выполнения callback-функции периодически в течение заданного периода или однократно после определённой задержки. Это полезно для планирования событий или выполнения периодических задач без постоянной проверки прошедшего времени.
Давайте быстро рассмотрим конструкторы Timer.
Создание таймера
Чтобы создать таймер, вам нужно просто вызвать конструктор Timer() и передать в качестве аргумента идентификатор таймера:
my_timer = Timer(id)
Затем вы инициализируете таймер с помощью метода init() объекта Timer() и передаёте в качестве аргументов режим таймера, период и callback-функцию. Вот пример:
my_timer.init(mode=Timer.PERIODIC, period=1000, callback=timer_callback)
Это инициализирует периодический таймер, который будет запускать функцию timer_callback каждые 1000 миллисекунд (1 секунду). Вы можете изменить параметр period на любой желаемый период.
Вместо периодического вызова callback-функции вы также можете запустить её однократно после заданного времени. Для этого используйте режим Timer.ONE_SHOT:
my_timer.init(mode=Timer.ONE_SHOT, period=1000, callback=timer_callback)
Эта строка кода настраивает таймер (my_timer) для работы в одноразовом режиме, вызывая указанную callback-функцию (timer_callback) через 1000 миллисекунд.
Подробнее о классе Timer в документации MicroPython.
Прерывания по таймеру с ESP32/ESP8266
Теперь рассмотрим различные сценарии применения прерываний по таймеру с ESP32/ESP8266.
Пример 1: Мигание светодиода с помощью таймера
В этом примере вы узнаете, как мигать светодиодом с помощью таймера. Это поможет вам понять, как работают периодические таймеры.
Схема подключения
Мы будем мигать светодиодом, подключённым к GPIO 13. Подключите светодиод к ESP32 или ESP8266 на этот вывод GPIO. Вы можете использовать следующие схемы в качестве справки.
ESP32
Рекомендуемая статья: Распиновка ESP32: какие GPIO пины следует использовать?
ESP8266 NodeMCU
Код
Следующий пример использует класс Timer для мигания светодиодом каждые полсекунды. Этот код совместим с платами ESP32 и ESP8266.
# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/micropython-timer-interrupts-ep32-esp8266/
from machine import Pin, Timer
from time import sleep
# Пин светодиода
led_pin = 13
led = Pin(led_pin, Pin.OUT)
# Callback-функция для таймера
def toggle_led(timer):
led.value(not led.value()) # Переключение состояния светодиода (ВКЛ/ВЫКЛ)
# Создание периодического таймера
blink_timer = Timer(1)
blink_timer.init(mode=Timer.PERIODIC, period=500, callback=toggle_led) # Таймер срабатывает каждые полсекунды
try:
# Основной цикл (опционально)
while True:
print('Main Loop is running')
sleep(2)
except KeyboardInterrupt:
# Произошло прерывание с клавиатуры, деинициализируем таймер
blink_timer.deinit()
print('Timer deinitialized')
# Выключаем светодиод
led.value(0)
В этом коде мы создаём таймер с именем blink_timer:
blink_timer = Timer(1)
Затем мы инициализируем таймер со следующими параметрами:
blink_timer.init(mode=Timer.PERIODIC, period=500, callback=toggle_led)
Это означает, что этот таймер будет вызывать функцию toggle_led каждые 500 мс, бесконечно (или до остановки программы).
Функция toggle_led, как следует из названия, переключает состояние светодиода:
# Callback-функция для таймера
def toggle_led(timer):
led.value(not led.value()) # Переключение состояния светодиода (ВКЛ/ВЫКЛ)
Callback-функции таймера должны иметь один аргумент, который передаётся автоматически объектом Timer при срабатывании события.
С таймерами вы также можете выполнять другие задачи в основном цикле, не мешая друг другу. Например, в нашем случае в основном цикле мы выводим сообщение каждые две секунды.
while True:
print('Main Loop is running')
sleep(2)
Когда пользователь останавливает программу (KeyboardInterrupt), мы деинициализируем таймер с помощью метода deinit() и выключаем светодиод.
except KeyboardInterrupt:
# Произошло прерывание с клавиатуры, деинициализируем таймер
blink_timer.deinit()
print('Timer deinitialized')
# Выключаем светодиод
led_pin.value(0)
Тестирование кода
С подключённым к GPIO 13 светодиодом запустите предыдущий код на вашей плате.
Вы увидите сообщение «Main Loop is running» каждые две секунды в Shell, в то время как светодиод мигает каждые полсекунды одновременно.
Пример 2: Мигание нескольких светодиодов с разной частотой
После тестирования предыдущего примера легко понять, что если создать несколько таймеров, можно выполнять несколько задач с разной частотой. В этом примере мы будем мигать двумя разными светодиодами. Один будет мигать каждые полсекунды, а другой — каждые две секунды.
Схема подключения
Подключите два светодиода к ESP32/ESP8266 (для различения разных светодиодов мы используем разные цвета):
Красный светодиод: GPIO 13 — будет мигать каждые полсекунды;
Жёлтый светодиод: GPIO 12 — будет мигать каждые две секунды.
Вы можете использовать следующие схемы в качестве справки для подключения.
ESP32
ESP8266 NodeMCU
Код
Следующий код использует таймеры для мигания двух разных светодиодов с разной частотой. Код совместим с платами ESP32 и ESP8266.
# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/micropython-timer-interrupts-ep32-esp8266/
from machine import Pin, Timer
from time import sleep
# Светодиоды
red_led_pin = 12
red_led = Pin(red_led_pin, Pin.OUT)
yellow_led_pin = 13
yellow_led = Pin(yellow_led_pin, Pin.OUT)
# Callback-функция для красного таймера
def toggle_red_led(timer):
red_led.value(not red_led.value()) # Переключение состояния светодиода (ВКЛ/ВЫКЛ)
print('red LED is: ', red_led.value())
# Callback-функция для жёлтого таймера
def toggle_yellow_led(timer):
yellow_led.value(not yellow_led.value()) # Переключение состояния светодиода (ВКЛ/ВЫКЛ)
print('yellow LED is: ', yellow_led.value())
# Создание периодических таймеров
red_timer = Timer(1)
yellow_timer = Timer(2)
# Инициализация таймеров
red_timer.init(mode=Timer.PERIODIC, period=500, callback=toggle_red_led) # Таймер срабатывает каждые 0.5 секунды
yellow_timer.init(mode=Timer.PERIODIC, period=2000, callback=toggle_yellow_led) # Таймер срабатывает каждые 2 секунды
try:
# Основной цикл (опционально)
while True:
print('Main Loop is running')
sleep(2)
except KeyboardInterrupt:
# Произошло прерывание с клавиатуры, деинициализируем таймеры
red_timer.deinit()
yellow_timer.deinit()
print('Timers deinitialized')
# Выключаем светодиоды
yellow_led.value(0)
red_led.value(0)
В этом коде мы создаём два разных таймера, по одному для каждого светодиода:
# Создание периодических таймеров
red_timer = Timer(1)
yellow_timer = Timer(2)
Затем мы вызываем соответствующие callback-функции с разными интервалами:
# Инициализация таймеров
red_timer.init(mode=Timer.PERIODIC, period=500, callback=toggle_red_led) # Таймер срабатывает каждые 0.5 секунды
yellow_timer.init(mode=Timer.PERIODIC, period=2000, callback=toggle_yellow_led) # Таймер срабатывает каждые 2 секунды
Callback-функции просто переключают текущее значение светодиода:
# Callback-функция для красного таймера
def toggle_red_led(timer):
red_led.value(not red_led.value()) # Переключение состояния светодиода (ВКЛ/ВЫКЛ)
print('red LED is: ', red_led.value())
# Callback-функция для жёлтого таймера
def toggle_yellow_led(timer):
yellow_led.value(not yellow_led.value()) # Переключение состояния светодиода (ВКЛ/ВЫКЛ)
print('yellow LED is: ', yellow_led.value())
Тестирование кода
Запустите предыдущий код на ESP32 или ESP8266. Вы заметите, что два светодиода мигают с разной частотой.
В то же время вы будете получать сообщение из цикла while каждые две секунды. Это показывает, что другие задачи не мешают нашему циклу.
Пример 3: Дебаунсинг кнопки с помощью таймера
Дребезг контактов (bounce) — это когда кнопка регистрирует больше одного нажатия, хотя на самом деле вы нажали только один раз. Это очень распространённое явление в механических кнопках, таких как тактовые кнопки (pushbutton).
Это происходит потому, что электрические контакты внутри кнопки замыкаются и размыкаются очень быстро перед достижением устойчивого состояния, что заставляет систему регистрировать множественные события нажатия, вызывая неточный подсчёт. Для предотвращения этой проблемы мы можем добавить техники дебаунсинга с использованием задержек или таймеров.
В этом примере мы рассмотрим, как можно использовать таймеры и события для дебаунсинга кнопки.
Схема подключения
Для этого примера подключите светодиод (GPIO 13) и кнопку (GPIO 12) к ESP32 и ESP8266.
Светодиод (GPIO 13)
Кнопка (GPIO 12)
Вы можете использовать следующие принципиальные схемы в качестве справки.
ESP32
ESP8266 NodeMCU
Код
# Rui Santos & Sara Santos - Random Nerd Tutorials
# Complete project details at https://RandomNerdTutorials.com/micropython-timer-interrupts-ep32-esp8266/
from machine import Pin, Timer
import time
led = Pin(13, Pin.OUT)
button = Pin(12, Pin.IN, Pin.PULL_UP)
counter = 0 # Инициализация счётчика нажатий кнопки
debounce_timer = None
def button_pressed(pin):
global counter, debounce_timer # Объявление переменных как глобальных
if debounce_timer is None:
counter += 1
print("Button Pressed! Count: ", counter)
# Переключение светодиода при каждом нажатии кнопки
led.value(not led.value())
# Запуск таймера для периода дебаунсинга (например, 200 миллисекунд)
debounce_timer = Timer(1)
debounce_timer.init(mode=Timer.ONE_SHOT, period=200, callback=debounce_callback)
def debounce_callback(timer):
global debounce_timer
debounce_timer = None
# Привязка прерывания к нарастающему фронту кнопки
button.irq(trigger=Pin.IRQ_RISING, handler=button_pressed)
try:
# Основной цикл (опционально)
while True:
print("Loop is running")
time.sleep(5)
except KeyboardInterrupt:
# Произошло прерывание с клавиатуры, деинициализируем таймер
debounce_timer.deinit()
# Выключаем светодиод
led.value(0)
Как работает код
Давайте быстро рассмотрим, как мы используем таймеры для дебаунсинга кнопки.
Связанная статья: ESP32/ESP8266: Цифровые входы и выходы с MicroPython
В этом примере используется одноразовый таймер (debounce_timer), который инициируется при каждом нажатии кнопки с указанным периодом дебаунсинга — в данном примере 200 миллисекунд. Вы можете увеличить период дебаунсинга, если всё ещё получаете ложные срабатывания.
debounce_timer.init(mode=Timer.ONE_SHOT, period=200, callback=debounce_callback)
Для обнаружения нажатия кнопки мы используем внешние прерывания. Функция button_pressed будет вызвана по нарастающему фронту (при нажатии кнопки).
button.irq(trigger=Pin.IRQ_RISING, handler=button_pressed)
Связанная статья: MicroPython: Прерывания с ESP32 и ESP8266.
При нажатии кнопки вызывается функция button_pressed, которая увеличивает счётчик, переключает светодиод и запускает одноразовый таймер для дебаунсинга.
counter += 1
print("Button Pressed! Count: ", counter)
# Переключение светодиода при каждом нажатии кнопки
led.value(not led.value())
# Запуск таймера для периода дебаунсинга (например, 200 миллисекунд)
debounce_timer = Timer(1)
debounce_timer.init(mode=Timer.ONE_SHOT, period=200, callback=debounce_callback)
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 # Объявление переменных как глобальных
Это лишь один из многих способов дебаунсинга кнопки.
Использование None
В 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(1)
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
Тестирование кода
Запустите код на вашем ESP32/ESP8266. Нажмите кнопку несколько раз. Вы заметите, что ложных срабатываний нет и система подсчитывает количество нажатий кнопки.
Если вы получаете ложные срабатывания, вам нужно увеличить период дебаунсинга в 200 миллисекунд в debounce_timer.
debounce_timer.init(mode=Timer.ONE_SHOT, period=200, callback=debounce_callback)
Заключение
В этом руководстве вы узнали, как использовать прерывания по таймеру с ESP32 и ESP8266, запрограммированными на MicroPython. Использование прерываний по таймеру полезно для периодического выполнения действий без постоянной проверки прошедшего времени.
У нас есть руководство о внешних прерываниях, которое может быть полезным: MicroPython: Прерывания с ESP32 и ESP8266.