Изчитане на бутон с Raspberry Pi Pico чрез EMA филтър

Надеждно изчитане на бутон с Raspberry Pi Pico чрез EMA филтър

Въведение

В предходната статия разгледахме как се извършва цифрово изчитане на бутон с Raspberry Pi Pico и MicroPython. Видяхме също, че механичните бутони не превключват моментално, а при натискане или отпускане контактите могат да генерират множество краткотрайни импулси. Това явление, известно като Button Bounce, често води до многократно регистриране на едно единствено натискане.

Най-разпространеният начин за решаване на този проблем е използването на програмно закъснение (delay), но този метод има своите недостатъци. Той забавя реакцията на програмата и не винаги успява да елиминира всички паразитни импулси.

В тази статия ще използваме разработена библиотека, която реализира EMA (Exponential Moving Average) филтър за цифрови входове. Вместо да разчита на фиксирани закъснения, библиотеката анализира последователните измервания и определя кога състоянието на бутона е действително стабилно. Получава се надежден software debounce, който е бърз, устойчив на електрически шум и лесен за използване както с един, така и с множество бутони. С този вид филтър може също да изчитате и “стабилизирате” аналогови сигнали. Може да прочетете как се използва EMA филтър за изчитането на аналогови сигнали в статията ни: Изглаждане на данни с EMA филтър на ESP32 с MicroPython


Как работи EMA Debounce?

EMA (Exponential Moving Average) представлява експоненциално движещо се осредняване.

При всяко ново измерване алгоритъмът не приема веднага новото състояние, а постепенно го доближава до предишното филтрирано състояние. По този начин кратките паразитни импулси почти не оказват влияние върху крайния резултат.

След филтрирането библиотеката използва два прага (хистерезис), които работят подобно на Шмит тригер:

  • Low Threshold – определя момента, в който бутонът се счита за натиснат.
  • High Threshold – определя момента, в който бутонът се счита за отпуснат.

Благодарение на тези два прага се избягват непрекъснатите превключвания около граничната стойност и се осигурява стабилна работа дори при наличие на електрически шум.


Библиотека ema_debounce.py

import machine

class EmaButton:
    def __init__(self, pin_num, alpha=0.2, low_threshold=0.2, high_threshold=0.8):
        """Инициализира бутон с ЕМА филтър за дебаунс.

        
        :param pin_num: Номер на GPIO пина.
        :param alpha: Скорост на филтриране (0.0 - 1.0). По-ниско = по-бавно/по-стабилно.
        :param low_threshold: Праг за засичане на натискане (0).
        :param high_threshold: Праг за засичане на пускане (1).
        """
        # Инициализиране на пина с вграден pull-up
        self.pin = machine.Pin(pin_num, machine.Pin.IN, machine.Pin.PULL_UP)
        
        self.alpha = alpha
        self.low_threshold = low_threshold
        self.high_threshold = high_threshold
        
        # Начално филтрирано и стабилно състояние (1.0 поради PULL_UP)
        self.digital_filter = 1.0
        self.stable_state = 1

    def update(self):
        """Сканира състоянието на бутона и прилага ЕМА филтъра.

        Трябва да се вика периодично в главния цикъл.
        Връща True само в момента на преход (натискане или пускане), иначе връща False.
        """
        # 1. Четем текущото физическо състояние
        raw_state = self.pin.value()
        
        # 2. Прилагане на ЕМА формулата
        self.digital_filter = (self.alpha * raw_state) + ((1.0 - self.alpha) * self.digital_filter)
        
        # 3. Прагов детектор (Шмит тригер логика)
        if self.digital_filter < self.low_threshold and self.stable_state == 1:
            self.stable_state = 0
            return True # Състоянието се е променило (натиснат)
            
        elif self.digital_filter > self.high_threshold and self.stable_state == 0:
            self.stable_state = 1
            return True # Състоянието се е променило (пуснат)
            
        return False # Няма промяна в стабилното състояние

    def is_pressed(self):
        """Връща True, ако текущото стабилно състояние е натиснато (0)"""
        return self.stable_state == 0


Обяснение на библиотеката

Инициализация

При създаването на обекта автоматично се конфигурира GPIO пинът като вход с вътрешен Pull-Up резистор.

self.pin = machine.Pin(pin_num, machine.Pin.IN, machine.Pin.PULL_UP)

Освен номера на GPIO могат да бъдат зададени три параметъра:

  • alpha – определя скоростта на филтриране;
  • low_threshold – праг за натискане;
  • high_threshold – праг за отпускане.

Колкото по-малка е стойността на alpha, толкова по-бавно реагира филтърът, но толкова по-добре потиска шума.


EMA филтър

Сърцето на библиотеката е редът:

self.digital_filter = (self.alpha * raw_state) + ((1.0 - self.alpha) * self.digital_filter)

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

Така единични паразитни импулси почти не влияят върху крайния резултат.


Прагов детектор

След филтрирането се проверяват двата прага.

Ако филтрираната стойност стане по-малка от Low Threshold, бутонът се счита за натиснат.

Ако стане по-голяма от High Threshold, бутонът се счита за отпуснат.

Този метод практически елиминира трептенията около граничните стойности.


Методът update()

Методът трябва да се извиква периодично в главния цикъл.

button.update()

Той връща True само когато стабилното състояние действително се е променило.

Това позволява програмата да реагира само при реални натискания и отпускания.


Методът is_pressed()

Методът връща текущото стабилно състояние на бутона.

button.is_pressed()

Така не се налага потребителят да работи директно с GPIO входовете.


Изчитане на бутон с Raspberry Pi Pico и EMA Debounce

Изчитане на бутон с Raspberry Pi Pico и EMA Debounce
import time
from ema_debounce import EmaButton

# Инициализираме бутона на GPIO 0 с вашите параметри по подразбиране
button = EmaButton(pin_num=0, alpha=0.2)

print("Програмата СТАРТИРА (ЕМА Дебаунс Библиотека)")

while True:
    # Обновяваме филтъра и проверяваме за промяна в състоянието
    if button.update():
        # Ако update() върне True, проверяваме какво е новото стабилно състояние
        if button.is_pressed():
            print("Бутонът е НАТИСНАТ стабилно!")
        else:
            print("Бутонът е ПУСНАТ стабилно!")
            
    # Запазваме оригиналната честота на сканиране (всеки 5ms)
    time.sleep(0.005)

Обяснение на примера

Първо създаваме един обект от класа EmaButton.

button = EmaButton(pin_num=0)

След това в главния цикъл извикваме:

button.update()

Библиотеката сама извършва:

  • прочитане на GPIO;
  • EMA филтриране;
  • debounce;
  • определяне на стабилното състояние.

Ако има промяна, проверяваме:

button.is_pressed()

и извеждаме дали бутонът е натиснат или отпуснат.


Изчитане на няколко бутона с Raspberry Pi Pico и EMA Debounce

Изчитане на няколко бутона с Raspberry Pi Pico и EMA Debounce
import time
from ema_debounce import EmaButton

# 1. ИНИЦИАЛИЗИРАНЕ НА БУТОНИТЕ
# Всеки обект си има собствен пин и независим филтър
btn_up    = EmaButton(pin_num=0, alpha=0.2) # Бутон за НАГОРЕ на GPIO0
btn_down  = EmaButton(pin_num=1, alpha=0.2) # Бутон за НАДОЛУ на GPIO1
btn_enter = EmaButton(pin_num=2, alpha=0.2) # Бутон за ИЗБОР  на GPIO2

print("Програмата СТАРТИРА с 3 независими ЕМА бутона")

while True:
    # 2. СКАНИРАНЕ И ПРОВЕРКА НА БУТОН 1 (UP)
    if btn_up.update():
        if btn_up.is_pressed():
            print("[Бутон НАГОРЕ] -> НАТИСНАТ")
        else:
            print("[Бутон НАГОРЕ] -> ПУСНАТ")

    # 3. СКАНИРАНЕ И ПРОВЕРКА НА БУТОН 2 (DOWN)
    if btn_down.update():
        if btn_down.is_pressed():
            print("[Бутон НАДОЛУ] -> НАТИСНАТ")
        else:
            print("[Бутон НАДОЛУ] -> ПУСНАТ")

    # 4. СКАНИРАНЕ И ПРОВЕРКА НА БУТОН 3 (ENTER)
    if btn_enter.update():
        if btn_enter.is_pressed():
            print("[Бутон ИЗБОР]  -> НАТИСНАТ")
        else:
            print("[Бутон ИЗБОР]  -> ПУСНАТ")

    # Период на сканиране за всички филтри наведнъж (5ms)
    time.sleep(0.005)

Обяснение на примера с три бутона

Едно от най-големите предимства на библиотеката е, че всеки бутон представлява отделен обект.

Например:

btn_up    = EmaButton(pin_num=0, alpha=0.2) # Бутон за НАГОРЕ на GPIO0
btn_down  = EmaButton(pin_num=1, alpha=0.2) # Бутон за НАДОЛУ на GPIO1
btn_enter = EmaButton(pin_num=2, alpha=0.2) # Бутон за ИЗБОР  на GPIO2

Всеки обект има собствен:

  • EMA филтър;
  • филтрирана стойност;
  • стабилно състояние;
  • прагов детектор.

Това означава, че всички бутони могат да работят едновременно, без да си влияят взаимно.

Главният цикъл просто извиква последователно:

btn_up.update()
btn_down.update()
btn_enter.update()

и реагира само ако има реална промяна в състоянието на съответния бутон.

Този подход позволява лесно изграждане на клавиатури, менюта, контролни панели и потребителски интерфейси с голям брой бутони.


Обобщение

EMA Debounce представлява значително по-модерен подход за обработка на механични бутони в сравнение с класическите програмни закъснения. Вместо да блокира изпълнението на програмата, библиотеката използва експоненциално осредняване и прагов детектор, за да разпознава само стабилните промени в състоянието на бутона.

Освен че елиминира механичния bounce, този подход потиска и краткотрайните електрически смущения, което го прави подходящ за приложения, при които надеждността е от съществено значение.


Заключение

Разработената библиотека ema_debounce.py предоставя лесен и ефективен начин за надеждно изчитане на механични бутони с Raspberry Pi Pico и MicroPython. Благодарение на използването на EMA филтър и хистерезис чрез два прага, тя успешно елиминира паразитните импулси и осигурява стабилно разпознаване на натисканията и отпусканията.

Допълнително предимство е обектно-ориентираният подход, който позволява всеки бутон да работи независимо със собствен филтър. Това прави библиотеката подходяща както за прости проекти с един бутон, така и за по-сложни потребителски интерфейси с множество бутони, без да се увеличава значително сложността на програмния код.

Translate »