Arduino: przyciski podłączane do wejść analogowych

Jeden z projektów wymagał podłączenia do Arduino zestawu 4 przycisków. Chodziło  o zwykłe tact switche. Problem polegał jednak na tym, że do dyspozycji zostały mi dosłownie pojedyncze piny – i to te analogowe.

keys_07Po krótkim poszukiwaniu w sieci znalazłem sposób na podłączenie wielu przycisków do pojedynczego wejścia analogowego (A0…A7).

Na podobne rozwiązanie natknąłem się już przy okazji pracy z rozszerzeniem LCD opisanym tutaj: Moduł LCD z przyciskami dla Arduino. Odczytanie stanu przycisków wymagało wywołania funkcji „analogRead()” na pinie A0. Schemat działającego na tej samej zasadzie układu znalazłem tutaj: Tutorial: Analog input for multiple buttons (part two) – i to na tym właśnie tekście się oparłem (w sieci znajdziecie ich znacznie więcej). Postanowiłem jednak przeanalizować, jak to właściwie działa.

Rozwiązanie

Drobna nieścisłość: na rysunkach znajdziecie rezystory 20kΩ i 10kΩ. Takie były w oryginalnym tekście, na którym oparłem swój post. Ponieważ takich nie miałem – użyłem 22kΩ i 9.8kΩ, jak to jest w obliczeniach.

Rozwiązanie podane w Tutorial: Analog input for multiple buttons (part two) przedstawia się następująco:

keys_00Mamy tu 4 przełączniki S1 do S4. Podłączone są do nich 4 rezystory R1..R4 o wartościach 20kΩ. Między nimi dodano kolejne rezystory R5..R8 o wartościach 10kΩ i 20kΩ (R8). Całość podłączono do zasilania 5v. Wejście A0 Arduino podłączono za rezystorem R1 pierwszego z przełączników.

Zaletą tego układu jest to, że używa tylko 2 typów rezystorów: 10kΩ i 20kΩ.

Dzięki takiemu sposobowi połączenia, do sprawdzenia stanu czterech przycisków wystarczą mi 3 kabelki: 5v, GND i jeden do pinu analogowego (np. A0). „Normalnie” potrzebowałbym 5v, GND oraz 4 piny wejściowe – razem 6.

Rozwiązanie to możecie łatwo złożyć sobie na płytce prototypowej:

keys_07Zamiast przewidzianych w schemacie rezystorów 20kΩ użyłem najbliższych 22kΩ – a zamiast 10kΩ: 9.8kΩ.

Jak to działa?

Gdy nie wciśniecie żadnego przycisku, nie będzie połączenia między 5v a GND – żaden prąd nie popłynie, na A0 będzie wartość 0.

Załóżmy, że wciśnięto klawisz S1:

keys_01Prąd popłynie wtedy przez zamknięty przełącznik S1 oraz rezystor R1. Za rezystorem R1 Arduino zmierzy napięcie. Następnie prąd popłynie przez rezystory R5…R8 aż do masy.

Uprośćmy rysunek usuwając część układu, która nie jest w tym momencie aktywna:

keys_02Wygląda znajomo? Nie? to może bardziej klasycznie:

keys_03Dzielnik napięcia! Po wciśnięciu klawisza S1 (i nie tylko, ale o tym za chwilę), napięcie na pinie A0 będzie miało wartość jak dla dzielnika napięcia o rezystorach R1 i (R5+R6+R7+R8). Rezystory (R5+R6+R7+R8) zastąpię jednym o nazwie R5678:

keys_04Łatwo obliczę napięcie na A0 dla zamkniętego S1:
U_{A0S1} = U_{we} \cdot { {R_{5678}} \over {R_{5678} + R_1} }

Rezystory R5+R6+R7+R8 połączone są szeregowo. Stąd rezystancja zastępcza R5678 będzie sumą rezystancji poszczególnych rezystorów (R5,6 i 7 to 9.8kΩ, R8: 22kΩ). Napięcie Uwe = 5v.

Teraz łatwo obliczę napięcie na A0:
U_{A0S1} = 5 \cdot { {51.4k\Omega} \over {51.4k\Omega + 22k\Omega} } = 5 \cdot { {51.4k\Omega} \over {73.4k\Omega} }=3.5014v

Wartość, jaką oczekujemy na porcie A0 gdy wciśnięto klawisz S1,  można obliczyć z proporcji:
{1023 \over 5v} = {A0S1 \over 3.5014}

Stąd:
{A0S1} = {{ 3.5014 \cdot 1023} \over {5v}} = 716

Więcej o dzielniku napięcia: Arduino, miernik rezystancji w Scratchu.

A dla S3?

Sytuacja trochę zmieni się, jeżeli zamkniemy przełącznik S3:

keys_05Tutaj główną rolę będą grały R3 oraz suma R7 i R8. R5 i R6 ograniczą prąd wchodzący na pin A0 (chociaż samo Arduino radzi sobie z tym dobrze) i nie wpłyną na poziom napięcia.

Stąd:
U_{A0S3} = U_{we} \cdot { {R_{78}} \over {R_{78} + R_3} }

Więc:
U_{A0S3} = 5 \cdot { {31.8k\Omega} \over {31.8k\Omega + 22k\Omega} } = 5 \cdot { {31.8k\Omega} \over {53.8k\Omega} }=2.9553v

A wartość na A0:
{A0S3} = {{ 2.9553 \cdot 1023} \over {5v}} = 604

Dla przycisku S2:
U_{A0S2} = U_{we} \cdot { {R_{678}} \over {R_{678} + R_2} }

Podstawiając:
U_{A0S2} = 5 \cdot { {41.6k\Omega} \over {41.6k\Omega + 22k\Omega} } = 5 \cdot { {41.6k\Omega} \over {63.6k\Omega} }=3.2704v

Na A0 dla zamkniętego S2 powinno pojawić się:
{A0S2} = {{ 3.2704 \cdot 1023} \over {5v}} = 669

Dla S4:
U_{A0S4} = U_{we} \cdot { {R_{8}} \over {R_{8} + R_4} }

Podstawiając wartości rezystorów i napięcia Uwe:
U_{A0S4} = 5 \cdot { {22k\Omega} \over {22k\Omega + 22k\Omega} } = 5 \cdot { {22k\Omega} \over {44k\Omega} }=2.5v

Na A0 powinno pojawić się:
{A0S4} = {{ 2.5 \cdot 1023} \over {5v}} = 511

Układ ten umożliwia również wykrywanie zdarzeń przyciśnięcia kilku klawiszy jednocześnie. Zasadę działania opiszę w osobnym tekście  na ten temat.

Uruchomienie

Na potrzeby testów stworzyłem mały programik:

Ten program odczytuje wartość napięcia na pinie A0 i wyświetla na ekranie: odczytaną wartość (zmienna a0) oraz odpowiadającą jej wartość napięcia.

Funkcja „analogRead()” zwraca wartości od 0 do 1023 (10 bitową). Dla napięcia na pinie analogowym równego 0v – zwraca 0. Dla napięcia równego 5v – zwraca 1023. W praktyce wartości zwracane przez „analogRead()” odnoszą się do napięć na Arduino i mogą trochę różnić się od rzeczywistych.

Dla przykładu, wciśnięcie klawisza na płytce wygenerowało na pinie A0:

Przycisk Teoretycznie Zwrócone przez analogRead()
S1 716 (ok. 3.5v) 715  – 719
S2 669 (ok. 3.27v) 667 – 674
S3 604 (ok. 3v) 555 – 636
S4 511 (2.5v) 510 – 515

Skąd się biorą rozbieżności między wartościami teoretycznymi a rzeczywistymi? Wpływają na nie głównie niedokładności rezystorów. Przykładowo: dla ścieżki R5678, teoretyczna wartość rezystancji to 51.4kΩ. W praktyce: 54.4kΩ. Dodatkowe niedokładności może również wprowadzać sam montaż na płytce stykowej.

Pewien rozrzut wartości wprowadzają same przełączniki, np. nie działają one natychmiastowo. Arduino może próbkować napięcie gdy przełącznik nie do końca „styka” – wtedy stawia dodatkowy opór, który wpływa na końcową wartość napięcia.

Różna jest też może być jakość tych przycisków – co dobrze widać po wartościach zwracanych przy próbkowaniu S3. W ostatecznym montażu zamierzam go pominąć.

Push vs pull

Znając zakresy A0 w odpowiedzi na wciśnięte przyciski, napisanie programu reagującego na klawisze nie powinno być trudne:

Odpowiedzialność za rozpoznanie, który klawisz wciśnięto, przejęła tu funkcja „isKey()”. Jeżeli zwróci kod KEY_NONE – klawisz nie został rozpoznany. W przeciwnym wypadku pętla główna wywołuje kolejną funkcję „onKey()”, w której umieszczone będą procedury obsługi poszczególnych przycisków.

Oczywiście w powyższym kodzie brakuje kilku rzeczy. Przykładowo: Arduino próbkuje dość szybko – może się zdarzyć, że zanim puścicie któryś z przycisków, Arduino kilkakrotnie zgłosi wykrycie jego wciśnięcia.

Ten sposób nie jest jednak najwygodniejszy. Wymaga stałego odpytywania o stan klawiszy w pętli głównej loop. Możecie tutaj użyć mechanizmu przerwań. Wtedy wykonywanie pętli głównej zostanie przerwane w momencie wykrycia wciśnięcia klawisza.

Przerwanie…

Najniższe napięcie na A0 wystąpi w momencie wciśnięcia przycisku S4: ok. 2.5v. Wartość ta znajduje się powyżej poziomu, jaki Arduino uznaje za logiczną jedynkę (ok. 1.5v). Dzięki temu możemy zmodyfikować nasz układ tak, żeby wciśnięcie klawisza spowodowało wywołanie przerwania.

Połączcie pin analogowy (A0) z wybranym pinem D2 lub D3. Wystarczy do tego zwykły kabelek. Przed pinem A0 zawsze jest rezystor 22kΩ więc płynie przez niego bardzo niewielki prąd – nie zaszkodzi pinowi cyfrowemu.

Zmodyfikujemy teraz nasz program:

Zauważcie: główna pętla „loop()” jest praktycznie… pusta. Za to w „setup()” dodałem nową instrukcję „attachInterrupt()”. W momencie pojawienia się zbocza narastającego na pinie D3, wywoływana jest funkcja „onKeyPressed()” – która teraz sprawdza stan A0.

W ten sposób stan klawiszy sprawdzany jest jedynie w momencie, gdy któryś z nich został faktycznie wciśnięty a nie, jak poprzednio – przy każdym przebiegu pętli.

Niestety w praktyce, takie rozwiązanie może nie być najlepsze…

Hybrydy push & pull

Powyższe rozwiązanie równie nie jest idealne. Procedury obsługi przerwań:

  • Powinny być jak najkrótsze – podobnie jak funkcje, które wywołują,
  • Mogą „gubić” wpisy na porcie szeregowym,
  • Mogą wystąpić konflikty z zapisywaniem do zmiennych globalnych – trzeba deklarować je jako „volatile”,
  • Mogą wystąpić różne dziwne konflikty – np. w przypadku funkcji „analogRead()”.

Jedno z rozwiązań, jakie stosuję jest model „hybrydowy”. Używam przerwania do ustawienia flagi, którą sprawdzam w pętli „loop()”, np:

Zwróćcie uwagę, że w „loop()” używam funkcji „cli()”, która wyłącza przerwania, oraz „sei()” – które je z powrotem włącza. Robię to po to, żeby uniknąć wywołania przerwania w momencie kiedy obsługuję „aKeyHasBeenPressed == true” w pętli głównej „loop()”.

Podsumowanie

Rozwiązanie z przyciskami podłączanymi do wejścia analogowego może być całkiem atrakcyjną alternatywą dla tradycyjnych układów opierających się na wejściach cyfrowych. Wykorzystanie przetwornika analog-cyfra umożliwia znaczne ograniczenie liczby przewodów. Kod jest niewiele bardziej skomplikowany. Słowem: tylko lutować!

keys_08

Źródła

9 komentarzy do “Arduino: przyciski podłączane do wejść analogowych”

  1. CZY W OBLICZENIACH SIE CZASAMI NIE POMYLILES ??
    „Po wciśnięciu klawisza S1 (i nie tylko, ale o tym za chwilę), napięcie na pinie A0 będzie miało wartość jak dla dzielnika napięcia o rezystorach R1 i (R5+R6+R7+R8). Rezystory (R5+R6+R7+R8) zastąpię jednym o nazwie R5678:”
    „Rezystory R5+R6+R7+R8 połączone są szeregowo. Stąd rezystancja zastępcza R5678 będzie sumą rezystancji poszczególnych rezystorów (R5,6 i 7 to 9.8kΩ, R8: 22kΩ). Napięcie Uwe = 5v.”

    JAK TOBIE WYSZLO [( 51,4 )/(51,4)+22) ]
    SKAD TE 51,4 ??
    MOZESZ MI TO WYJASNIC ?

    1. Witaj,
      R5, R6 i R7 mają po 9.8k a R8=22k. Rezystancja zastępcza rezystorów łączonych szeregowo jest sumą rezystancji poszczególnych rezystorów, więc: 9.8k+9.8k+9.8k+22k = 51.4k. Teraz widzę, że na rysunkach są rezystory 10k i 20k. Nie miałem takich, więc wziąłem najbliższe im – poprawię rysunki przy najbliższej okazji – dzięki za uwagę:)
      Pozdrawiam,
      Arek

Dodaj komentarz