Przyciski, buttony, switche – jak obsłużyć?

tactW dzisiejszym wpisie zajmiemy się obsługą podstawowego sposobu wprowadzania informacji przez użytkownika „z zewnątrz” – przycisków.

W artykule wykorzystam standardowy przycisk monostabilny (czyli taki o jednej „stabilnej” pozycji – po puszczeniu wraca do stanu początkowego).

Przycisk podłączony będzie do modułu microBOARD M8, a jego obsługa zaprogramowana będzie przy pomocy Arduino IDE. Informacje o tym, jak zaprogramować ten moduł przy pomocy Arduino znajdziecie TUTAJ.

Rozpoczniemy od napisania prostego kodu sprawdzającego stan przycisku, potem przejdziemy do kodu trochę bardziej rozbudowanego, żeby ostatecznie przejść kawałka programu pozwalającego na wykrycie krótkich i długich wciśnięć przycisku.

Na początek krótkie wprowadzenie elektroniczne.

Jak wiemy, mikrokontroler ATmega8 na pinach cyfrowych jest w stanie wykryć dwa stany – wysoki (czyli zbliżony do napięcia zasilania, zazwyczaj 5V) oraz niski (czyli zbliżony potencjałem do masy układu GND). Oznacza to, że w celu umożliwienia obsługi przycisku musimy zapewnić zmianę napięcia w momencie wciśnięcia przycisku. Można to zrobić na kilka sposobów.

Najpopularniejszym sposobem jest podłączenie jednej strony przycisku do masy układu, a drugiego do wyprowadzenia mikrokontrolera tak, aby w momencie wciśnięcia przycisku na pin „podawany” był potencjał masy układu. Osiągamy w ten sposób połowę sukcesu, ponieważ nadal nie zapewniliśmy wymuszenia wysokiego potencjału na pinie (logicznej “1”) – wiemy tylko, że potencjał ten będzie zbliżony do zera przy przycisku wciśniętym. Można by dojść do wniosku, że wystarczy pin podłączyć do napięcia zasilania, wymuszając w ten sposób wysoki stan w przypadku „puszczonego” przycisku. Rozwiązanie takie jest jednak NIEPRAWIDŁOWE.

Owszem, zapewniamy w ten sposób stan wysoki w przypadku braku wciśnięcia przycisku, ale zastanówmy się co się stanie po wciśnięciu przycisku. Jednak w momencie wciśnięcia przycisku następuje ZWARCIE napięcia zasilania do masy układu, ponieważ pomiędzy potencjałem napięcia zasilania a masą nie będzie żadnego „odbiornika”. W najlepszym przypadku mocno nagrzeje nam się stabilizator napięcia. W najgorszym – spalimy któryś z układów w wyniku przepływu prądu o bardzo dużej wartości. Ponadto – nasz układ wyłączy się w wyniku spadku napięcia zasilającego, i “odżyje” dopiero po puszczeniu przycisku, oczywiście program wystartuje od początku, a nie od miejsca, w którym wcisnęliśmy przycisk.

Co w takim razie należy zrobić? Jeżeli podstawowym problemem jest brak „odbiornika” na linii napięcie zasilania-masa, to umieśćmy tam odbiornik w postaci rezystora, np. o wartości 10kOhm. Dzięki temu prąd płynący wtedy przez linię będzie znikomy (z prawa Ohma dla napięcia 5V i rezystancji 10000Ohm – I=U/R – I=0,5mA). Ale rozwiązujemy w ten sposób nie tylko problem dużego prądu! Ze względu na dodanie rezystora o dużej wartości, w momencie wciśnięcia przycisku na zacisku tworzy się dzielnik rezystancyjny, o rezystancji przy wyższym potencjale praktycznie nieskończenie większym niż przy potencjale niższym (10000Ohm, wobec rezystancji przewodu o wartości prawdopodobnie mniejszej od 1 Ohma). Dzięki temu napięcie na tym zacisku będzie praktycznie równe masie układu, czyli osiągnęliśmy swój cel!

Wprowadzając trochę fachowego nazewnictwa należy wspomnieć iż taki rezystor nazywany jest popularnie rezystorem pull-up (czyli rezystorem „podciągającym” stan pinu). Czasami stosuje się odwrotną logikę – przycisk podłącza pin do napięcia zasilania, a masa zwarta jest przez rezystor – w takim przypadku mówimy o rezystorze pull-down.

Na szczęście, producenci mikrokontrolerów (a przynajmniej firma ATMEL, odpowiedzialna za mikrokontrolery ATmega) wiedzą, że rezystory pullup są często potrzebne w projekcie, dlatego są one zawarte bezpośrednio w mikrokontrolerze. Istnieje prosta możliwość włączenia tego rezystora. Przy programowaniu w „czystym” C należy ustawić pin jako wejście, a następnie ustawić jego stan na wysoki. W Arduino procedura ta jest jeszcze prostsza – przy deklarowaniu trybu pracy pinu poleceniem pinMode(), jako tryb pinu należy wpisać polecenie INPUT_PULLUP.

Po tym przydługim wstępie – zabieramy się do pracy. Podłączamy układ wg następującego schematu.

schemat

Przerywanymi liniami oznaczyłem rezystor pullup, który włączymy wewnątrz mikrokontrolera, także nie trzeba go fizycznie umieszczać na płytce stykowej. W schemacie znalazł się on jedynie w celu przypomnienia.

Tworzymy prosty program w Arduino IDE.

Listing programu ZaznaczPokaż

Program ten będzie wypisywał komunikat „PRZYCISK” przez cały czas wciskania przycisku. Jest to najprostszy program wykorzystujący przycisk i jak to często bywa z tego typu programami nie wykorzystuje się go praktycznie nigdy w końcowej wersji projektu. Przede wszystkim z prostego powodu – w przypadku wykonywania innych czynności w trakcie działania programu łatwo „przegapić” wciśnięcie przycisku przez użytkownika. Pierwszą myślą w takim momencie jest oczywiście wykorzystanie przerwań. Przerwanie oznacza (jak sama nazwa wskazuje) przerwanie aktualnie wykonywanego programu, przeskoczenie do innej procedury (obsługi przerwania) i po jej wykonaniu – powrocie do dotychczas wykonywanego kodu.

ATmega8A, a więc i nasz moduł M8 posiada dwa piny, na których podłączyć możemy zewnętrzne źródła przerwania – są to piny D2 i D3. Na poprzednim schemacie uwzględniłem już ten fakt, także nie musimy zmieniać w układzie.

Nowy program wyglądać będzie następująco:

Listing programu ZaznaczPokaż

Widzimy tutaj pojawienie się nowej procedury – „przycisk”. Procedura ta jest obsługą przerwania i zgodnie z linijką „attachInterrupt(…);” będzie ona wywoływana za każdym razem, gdy na zerowym pinie przerwań zewnętrznych (pierwszy parametr – 0 to pin D2, 1 to D3) pojawi się zbocze opadające sygnału (trzeci parametr – FALLING to zbocze opadające, oprócz tego dostępne są opcje RISING – zbocze opadające, CHANGE – zmiana stanu pinu oraz LOW – stan niski na pinie). Efekt działania programu będzie (a raczej powinien – o różnicy napiszę za chwilę) taki, że przy każdym wciśnięciu przycisku pojawi się jeden raz komunikat „PRZYCISK”.

Niestety po wgraniu programu i parokrotnym wciśnięciu przycisku okaże się, że komunikat pojawia się parę razy na każde wciśnięcie przycisku. Wyjaśnieniem tego faktu jest zjawisko bouncingu. Zjawisko to polega na tym, że przycisk w momencie wciśnięcia potrzebuje pewnego czasu na ustabilizowanie swojego stanu. Zanim to nastąpi styki przycisku „drgają” –  parokrotnie wahają się pomiędzy stanem wysokim a niskim. Dlatego właśnie mikrokontroler wychwytuje w momencie wciskania przycisku kilka zboczy opadających. Oczywiście – jest to efekt wysoce niepożądany. Jego przeciwdziałanie określane jest jako debouncing i w najprostszym ujęciu polega na odczekania krótkiej chwili (dającym stykom czas na ustabilizowanie swojej wartości) i po tym czasie sprawdzeniu, czy przycisk faktycznie został wciśnięty, czy było to niepożądane drganie.

Najprostsza implementacja debouncingu w poprzednim kodzie zmienia procedurę obsługi przerwania do poniższej postaci.

Listing programu ZaznaczPokaż

Takie rozwiązanie wykorzystać można w prostych projektach, w których szybkość wykonywania kodu nie ma kluczowego znaczenia.  W bardziej zaawansowanych projektach nieakceptowana jest obsługa przerwania powodująca wstrzymanie (polecenie delay(…)) działania programu – należy wtedy wykorzystać bardziej zaawansowane metody programowego deboucingu, lub metody sprzętowe (zazwyczaj wykorzystujące fakt filtrowania „szpilek” przez kondensatory ceramiczne).

Na koniec wpisu zaprezentuję Wam wycinek kodu, który stworzyłem do projektu który aktualnie wykonuję. Ze względu na estetykę projektu zdecydowałem, że w projekcie znajdzie się tylko jeden przycisk, który pozwoli na wywołanie dwóch różnych funkcji w zależności od czasu jego wciśnięcia. Wymagało to zmiany podejścia do sposobu sprawdzania stanu przycisku – wykorzystałem w tym celu przerwanie cyklicznie wywoływane przez 8-bitowy timer wbudowany w mikrokontroler ATmega8.

Listing programu ZaznaczPokaż

Kod jest bogato opatrzony komentarzami, a jego działanie polega na ciągłym (a dokładniej występującym co około 20ms) sprawdzaniu stanu przycisku, co z jednej strony powoduje nieczułość na drgania styków przycisku, a z drugiej pozwala na łatwe monitorowanie czasu wciśnięcia przycisku i wychwycenie wciśnięcia dłuższego niż czas określony przez ustaloną wartość.

Comments are closed.

Post Navigation