Sygnalizator revisited czyli przyciski i debouncing

Dawno temu pisałem o tym jak można korzystać z przycisków (tactile switch, pushbutton) na przykładzie kartonowego sygnalizatora. Wróćmy do tematu, tym razem na poważniej zajmując się przyciskami.

Jeśli spojrzycie na kod tamtego sygnalizatora, możecie dostrzec następującą pętlę loop:

void loop()
{
  val = digitalRead(buttonPin);
  if (val == HIGH && prev == LOW) {
    next_status();
  }
  prev = val;
  display_status();
  delay(50);

}

Wykrywanie naciśnięcia odbywa się przez porównanie bieżącej wartości wejścia cyfrowego z poprzednią. Jeżeli aktualna wartość to HIGH a poprzednia to LOW, to wykonujemy akcję next_status(), która zmienia stan sygnalizatora. I wszystko działa. Ale nie ma problemów, tylko dzięki ostatniej linii kodu w loop: delay(50);.

Przycisk jest urządzeniem mechanicznym i włączenie lub wyłączenie nie jest jednoznaczne, jeżeli będziemy stan przycisku badać dostatecznie często. W momencie dociskania/zwalniania przycisku jest taki moment, w którym styk już łapie/puszcza przez co włączenie wyłączenie nie jest czystym przełączeniem między LOW a HIGH tylko migotaniem. Angielska Wikipedia przy haśle contact bounce (bo tak się zjawisko to nazywa) ma ten piękny obrazek ilustrujący ten problem:

Ilustracja migotania przełącznika (CC Wikipedia)
Ilustracja migotania przełącznika (CC Wikipedia)

Opóźnienie w loop załatwia nam ten problem, bowiem jeżeli zauważymy zmianę stanu klawisza odczekanie 50 ms przed następnym odczytem zapewnia margines wykluczający błędny odczyt. Gdyby nie ono, wówczas w następnym przebiegach moglibyśmy odczytać migotanie jako kolejne przyciśnięcia. Rezultat? Z punktu widzenia użytkownika – on nacisnął przycisk jeden raz, a sygnalizator przeskoczył o 1 lub więcej stanów za jednym razem.

Rozwiązanie z użyciem delay nie zawsze jest dopuszczalne, bo może nasze Arduino musi robić coś więcej niż tylko czekać na naciśnięcie klawisza.

Jak się zabezpieczyć przed złymi odczytami?


Najlepszą metodą jest zapamiętanie czasu w którym odczytaliśmy zmianę stanu klawisza i przez następne kilkanaście, kilkadziesiąt milisekund nie reagować na zmiany stanu wejścia. Ale wówczas kod znacznie się komplikuje, a taki jest znacznie bardziej podatny na błędy. Na szczęście jest gotowe rozwiązanie w postaci biblioteki Bounce.

Instalujemy bibliotekę w sketchbook/libraries i teraz możemy korzystać z dobrodziejstw klasy Bounce. Zamiast odczytywać wartość przez digitalRead i samemu starać się rozszyfrować co znaczy odczyt w zależności od poprzedniej wartości i czasu który upłynął od ostatniej zmiany, definiujemy obiekt w naszym szkicu:

int buttonPin = 4;
Bounce button(buttonPin,20);

Od teraz cyfrowe wejście nr 4 będzie obsługiwane przez Bounce, z 20-sto milisekundowym marginesem. W czasie 20 milisekund od wykrycia zmiany stanu wejścia jego następne zmiany będą ignorowane. Dopiero po upływie tego czasu obiekt może zmienić swój stan.

Pierwszym krokiem jest uaktualnienie stanu przycisku przez funkcję update(). To właśnie w tym momencie jest odczytywana wartość na wejściu:

  button.update();

update zwraca wartość true lub false – w zależności czy stan przycisku uległ zmianie czy jest taki jak poprzednio.

Po tej operacji możemy sprawdzić czy przycisk jest wciśnięty lub nie (read()) albo jeżeli interesuje nas tylko czy został właśnie wciśnięty lub zwolniony informacji dostarczy nam risingEdge() lub fallingEdge(). W wypadku sygnalizatora nas interesuje kiedy został wciśnięty klawisz:

  if (button.risingEdge()) {
    next_status();
  };

next_status zostanie wywołany tylko kiedy zostanie wykryta zmiana ze stanu LOW na HIGH. W następnym przebiegu, jeżeli przycisk jest nadal wciśnięty, risingEdge() już zwróci false.

Biblioteka ma sobie jeszcze kilka przydatnych funkcji, pozwalających określić jak długo już przycisk jest w aktualnym stanie, albo przydatnych do generowania wirtualnych 'przyciśnięć’ (to drugie świetnie się sprawdza do powtarzania akcji gdy przycisk jest ciągle wciśnięty).

To jeszcze nie koniec o sygnalizatorze, bo on nadal istnieje. Oczywiście kartonowy v1 został zastąpiony przez v2 ze spienionego PCW, które jest świetnym materiałem do budowy prostych konstrukcji. Sygnalizator dorobił się dodatku w postaci zapory napędzanej przez serwomechanizm, co widać też w kodzie.

O tym jak korzystać z serwomechanizmów na przykładzie zapory i tego szkicu postaram się napisać już niedługo.

Cały kod sygnalizatora v2.