Poprawianie dobrego, czyli LCD Shield i Nettigo Keypad

Odświeżałem ostatnio bibliotekę do Keypada, tak by pracowała łatwiej z LCD Shieldem lub innymi niestandardowymi klawiaturami opartymi o dzielnik napięcia. O jakie klawiaturki chodzi?

Klawiatura taka, to szereg rezystorów, każdy kolejny zwarty przez przycisk do masy. Jeśli odczytamy napięcie między rezystorem R1 a R2, to będzie się zmieniało w zależności od tego który przycisk jest załączony. Górny rezystor zostaje cały czas włączony, a liczba dolnych się zmienia. W szczególności gdy zewrzemy pierwszy przycisk (PRAWO), to będzie tam napięcie 0 -bo przycisk zewrze wszystkie rezystory R2-R5 do masy. Gdy wciśniemy drugi przycisk napięcie będzie zależne od dzielnika R1/R2, rezystory R3-R5 będą zwarte. Gdy wciśniemy DÓŁ napięcie zależy od wartości R1/(R2+R3), R4 i R5 – zwarte. Co nam z tego?

Mierząc napięcie wiemy, który przycisk został wciśnięty. Na tej zasadzie działają proste klawiaturki (zwykle 5-cio klawiszowe) jak Nettigo Keypad czy te w LCD Shieldzie. Sęk w tym, że w zależności od użytych rezystorów zmienia się rozkład napięć. Biblioteka Nettigo Keypad od dawna to wspierała (przez NG_Keypad::setBoundaries) ale wartości progowe trzeba było sobie wyznaczać samemu. W sumie nic skomplikowanego, ale pomyślałem czemu tego jakoś nie zautomatyzować. Wydawało się to proste, jednak diabeł tkwi w szczegółach. Oceniając złożoność takiego projektu wydawało się to banalne, ale… gdy masz do dyspozycji skryptowy język (jak Python, Ruby), z konstrukcją typu hash. W C nie jest to takie proste, nie żeby skomplikowane tylko trochę bardziej trzeba pogłówkować. Wkrótce, zagrzebany trochę w C i debugowanie Arduino miałem ochotę odpuścić, bo projekt świata nie zbawia, ale… intencją moją było odświeżyć nieco pamięć jeśli chodzi o C i Arduino, bo ostatnio obowiązki zawodowe nieco za bardzo konsumowały czas. Nim zaatakuję nieco większe i ambitniejsze projekty to warto odświeżyć dawne umiejętności.

Czego o C /C++ można się w takim prostym projekcie nauczyć

Dobrze, wróćmy do projektu (kod można zobaczyć na GitHubie). Najpierw na konsoli Serial Arduino wydaje ciąg poleceń operatorowi,  by zmierzyć napięcie przy naciśnięciu poszczególnych klawiszy. Następnie sortuje je tak by były od najmniejszej do największej wartości. Pozostaje znaleźć graniczne wartości (czyli jeżeli jeden przycisk daje odczyt 0, kolejny daję 100, to graniczna wartość to 50 – w pół drogi) i wypisać na konsoli.

Jak widać – już jest trochę zamieszania – trzeba posortować. Sortowanie jest zrealizowane przez chyba najprostszą metodę bąbelkową. Jest to bardzo nieefektywny algorytm, ale sortujemy tylko 5 czy 6 wartości, więc ważniejsza jest jego prostota implementacji.

Sortujemy odczyty, bo biblioteka wymaga tego, by granice między przyciskami były posortowane rosnąco, jednocześnie robimy to na kopii danych, by później móc odnaleźć odczyt i przypisać do właściwego klawisza. Szkic każde naciskać przyciski w odpowiedniej kolejności i dlatego wiemy który odczyt jest z którego przycisku. Po posortowaniu nie mielibyśmy jak odtworzyć tej informacji.

Oczywiście, można to było zrobić bardziej elegancko – utworzyć strukturę na odczyt klawisza, zawierającą kod klawisza i odczyt i to umieścić w tablicy. Taką tablice możemy sobie już sortować bez obaw o utratę danych.

typedef struct {
  unsigned val;
  byte key;
} reading_type;

reading_type readings5];

OK, dobrze, dość teorii, nie mam ochoty przepisywać kodu, skoro działa ;)

Rezultaty? Proszę oto przykładowy zrzut z sesji z LCD Shieldem:

Take keypad and be prepared....
Press RIGHT and wait for X
.....................X <- here we will read analog input
.....................X
Reading: 0
Press UP and wait for X
.....................X <- here we will read analog input
.....................X
Reading: 130
Press DOWN and wait for X
.....................X <- here we will read analog input
.....................X
Reading: 306
Press LEFT and wait for X
.....................X <- here we will read analog input
.....................X
Reading: 480
Press SELECT and wait for X
.....................X <- here we will read analog input
.....................X
Reading: 720
Results:
0,130,306,480,720,1024,
Sorted results:
0,130,306,480,720,1024,
Val for RIGHT/0 is 0
Val for UP/1 is 130
Val for DOWN/2 is 306
Val for LEFT/3 is 480
Val for SELECT/4 is 720
NG_Keypad keypad(
  NG_Keypad::SELECT,872,
  NG_Keypad::LEFT,600,
  NG_Keypad::DOWN,393,
  NG_Keypad::UP,218,
  NG_Keypad::RIGHT,65);

Czyli wystarczy użyć w swoim szkicu tego konstruktora definiującego obiekt keypad by z niego w pełni korzystać:

NG_Keypad keypad(
  NG_Keypad::SELECT,872,
  NG_Keypad::LEFT,600,
  NG_Keypad::DOWN,393,
  NG_Keypad::UP,218,
  NG_Keypad::RIGHT,65);