Kurs ESP8266 & MicroPython #5: Przerwania zewnętrzne, timery i debouncing

Załóżmy, że chciałbyś stworzyć program, który natychmiast zareaguje na wciśnięcie przycisku, a w międzyczasie będzie robił jeszcze coś innego co określony czas. Do tego właśnie służą przerwania i timery, którymi zajmiemy się dzisiaj. Przy okazji pokażę ulepszony względem poprzedniego debouncing.

Trochę teorii
Timer jest licznikiem, odliczającym określoną ilość czasu. W MicroPythonie możemy stworzyć dwa rodzaje timerów – jednorazowe i okresowe. Jak łatwo się domyślić, pierwszy wywoła funkcję (callback) tylko raz, a drugi będzie to robił cały czas w równych odstępach czasu. Przerwania natomiast mają za zadanie natychmiastowe przechwycenie jakiegoś zdarzenia, na przykład wciśnięcia przycisku czy sygnału z przerwania z czujnika. Obsługa timerów i przerwań znajduje się w module machine.

Standardowo zacznijmy od układu, ale szybko przejdziemy do programu. Wszystkie elementy wyjaśniałem w poprzednich lekcjach, więc jeśli nie rozumiesz podłączenia diody lub przycisku – wróć do poprzednich lekcji.

ESP8266 - LED & Button

Program
Program jest dosyć długi w porównaniu z poprzednimi, więc wkleję go od razu w całości i opiszę po kolei.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import machine
 
btn = machine.Pin(4, machine.Pin.IN, machine.Pin.PULL_UP)
rLed = machine.Pin(5, machine.Pin.OUT)
bLed = machine.Pin(16, machine.Pin.OUT)
tim = machine.Timer(-1)
tim2 = machine.Timer(-1)
tim3 = machine.Timer(-1)
stable = 0;
pressed = 0;
 
def rLedOff(p = 0):
    rLed.high()
 
def rLedOn():
    rLed.low()
    tim3.init(period=1000, mode=machine.Timer.ONE_SHOT, callback=rLedOff)
 
def bLedToggle(p):
    bLed.value(not bLed.value())
 
def debounce(p):
    global stable, pressed
    if btn.value() == 1:
        return
    if p == btn:
        stable = 0
    stable += 1
    if stable < 20:
        tim2.init(period=1, mode=machine.Timer.ONE_SHOT, callback=debounce
    else:
        rLedOn()
        pressed += 1
        print(pressed)
 
rLedOff()
btn.irq(trigger=machine.Pin.IRQ_FALLING, handler=debounce)
tim.init(period=100, mode=machine.Timer.PERIODIC, callback=bLedToggle)

Do 6-go wiersza wszystko powinno być jasne. Tworzymy w tym miejscu timer o nazwie tim. W ESP8266 istnieją same wirtualne timery, dlatego wszystkie mają ID równe -1. Kolejną nowością jest definicja funkcji rLedOff z domyślnym parametrem p. Przy wywołaniu takiej funkcji można użyć argumentu, ale nie trzeba i wtedy zostanie on zastąpiony domyślną wartością.
W funkcji rLedOn znajduje się inicjalizacja timera do pracy w trybie „jednorazowym”, czyli po upływie 1000ms wywoła on funkcję rLedOff tylko raz. Funkcje wywoływane przez timery i przerwania muszą przyjmować jeden argument i jest to zdarzenie, które wywołało funkcję – właśnie dlatego funkcja rLedOff przyjmuje jeden argument, chociaż do niczego nie jest on potrzebny.
Zajmijmy się teraz debouncingiem. Jak już zapewne wiesz, po wciśnięciu przycisku może się on zachowywać, jakby został wciśnięty kilka razy. Rozwiązaniem na to może być odczekanie kilkudziesięciu milisekund, lub nieco bardziej skomplikowany sposób, który teraz opiszę. Polega on na tym, żeby sprawdzać stan pinu co 1 milisekundę i jeśli przez określony czas (w tym przypadku 20 milisekund) stan będzie nieprzerwanie niski to znaczy, że przycisk został wciśnięty.
Najpierw deklarujemy, że użyjemy zmiennych globalnych, ponieważ chcemy zatrzymać ich wartość pomiędzy kolejnymi wywołaniami funkcji debounce. Jeśli przycisk nie będzie wciśnięty, nie chcemy, żeby wykonywała się dalsza część kodu. Można to zrobić za pomocą else, ale można też tak jak tutaj zakończyć funkcję poprzez return. Dalej – jeśli funkcja została wywołana poprzez przerwanie zdefiniowane w wierszu 37, wyzerujmy licznik czasu, przez który przycisk był wciśnięty i zacznijmy jego odliczanie. Można by w tym przypadku poczekać w pętli 20 razy po jedną milisekundę, każdorazowo sprawdzając stan pinu, ale w tym przypadku zablokowaliśmy wykonywanie programu w pętli głównej (jeśli jakikolwiek by był – w tym przypadku go nie ma). Zamiast tego można zainicjować timer, który po jednej milisekundzie wywoła tę samą funkcję, co właśnie tutaj jest zrobione. Na koniec, jeśli wartość licznika stable nie jest mniejsza od 20 (czyli jest równa 20, bo nigdy nie przekroczy tej wartości), wywołujemy funkcję rLedOn. Timer tim2 nie jest inicjowany po raz kolejny, dlatego funkcja debounce wywoła się kolejny raz dopiero poprzez przerwanie od przycisku btn.
Na koniec jeszcze słowo wyjaśnienia do btn.irq(trigger=machine.Pin.IRQ_FALLING, handler=debounce). Jeśli chciałbyś, żeby przerwanie następowało w przypadku zbocza narastającego, użyj machine.Pin.IRQ_RISING. Warunki możesz połączyć w ten sposób: machine.Pin.IRQ_RISING | machine.Pin.IRQ_FALLING i wtedy przerwanie nastąpi przy obu zboczach sygnału. Jeśli natomiast chciałbyś pozbyć się timera migającego diodą, użyj tim.deinit().

Jak widzisz, ESP wykonuje kilka rzeczy na raz, a przy tym w ogóle nie ma programu w pętli głównej. Przerwania i timery są bardzo przydatne i warto z nich korzystać. Często czujniki posiadają wyjścia, które sygnalizują jakieś zdarzenie (na przykład swobodny spadek akcelerometru) lub zakończenie pomiaru i wtedy użycie przerwań jest konieczne.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *

Rozwiąż równanie: *