Teensy w roli myszki

Witajcie ponownie! W dzisiejszym artykule weźmiemy pod lupę Teensy w roli urządzenia typu HID (Human Interface Device) jakim jest mysz komputerowa. Zbudujemy sobie układ składający się z Teensy oraz modułu joysticka z przyciskiem.

Co będzie potrzebne?

Składamy układ

Schemat połączeń jak zwykle wręcz banalnie prosty. Bardzo ważne jest, aby pin zasilania joysticka podłączyć pod wyjście 3V3 Teensy (w żadnym wypadku 5V, gdyż uszkodzimy piny Teensy, do których podłączone będą wyjścia modułu).

Najlepiej schemat połączeń zobrazuje poniższa grafika:

Kod źródłowy

Skoro mamy już podłączone peryferia to możemy zabrać się za analizę kodu źródłowego, który dostępny jest do pobrania tutaj.

Na samym początku jak zwykle definiujemy sobie kilka stałych, które będą przechowywały numery pinów do których podłączone są poszczególne wyjścia modułu:

const int AXIS_X = A0;
const int AXIS_Y = A1;
const int BTN = A2;

Następnie tworzymy zmienne globalne, które będziemy wykorzystywali do sprawdzania zmiany stanu joysticka.

bool lastBtnState, currentBtnState;
int norm_x, norm_y, pos_x, pos_y;

Już same te nazwy wiele nam mówią co będzie przechowywane w danej zmiennej.

Wartości boolowskie lastBtnState oraz currentBtnState będą zawierały informacje o tym, czy został wciśnięty przycisk (jeżeli lastBtnState != currentBtnState to wiemy, że stan uległ zmianie od ostatniego sprawdzenia).

Wartości całkowitoliczbowe norm_x oraz norm_y będą przechowywały położenie gałki w obu osiach bez żadnego wychylenia (w idealnym świecie byłoby to 512 (zakładając, że mamy do dyspozycji 10-bitowy przetwornik ADC), ale jak wiadomo nic nie jest idealne). Wartości pos_x oraz pos_y będą z kolei przechowywały aktualną wartość wychylenia w obu osiach.

Teraz przyjrzyjmy się funkcji setup():

void setup() {
  pinMode(AXIS_X, INPUT);
  pinMode(AXIS_Y, INPUT);
  pinMode(BTN, INPUT_PULLUP);
  lastBtnState = digitalRead(BTN);
  currentBtnState = lastBtnState;
  norm_x = map(analogRead(AXIS_X), 0, 1023, 0, 254);
  norm_y = map(analogRead(AXIS_Y), 0, 1023, 0, 254);
}

Myślę, że w tym przypadku nie trzeba wiele tłumaczyć. Jedyne linijki, które wymagają kilku słów omówienia to przypisanie wartości do norm_x oraz norm_y. Z dokumentacji klasy udającej myszkę w Teensy możemy dowiedzieć się, że metoda do poruszania kursorem przyjmuje 2 parametry (przesunięcie x oraz y) w zakresie od -127 do 127. Musimy więc przekonwertować sobie wartość zmierzoną analogowo w zakresie od 0 do 1023 na wartość z zakresu 0 do 2*127. Dlaczego tak? Wyjaśni się to za chwilkę kiedy będziemy omawiali funkcję loop():

void loop() {
  pos_x = map(analogRead(AXIS_X), 0, 1023, 0, 254) - norm_x;
  pos_y = map(analogRead(AXIS_Y), 0, 1023, 0, 254) - norm_y;


  if(abs(pos_x) < 10) pos_x = 0;
  if(abs(pos_y) < 10) pos_y = 0;
  
  currentBtnState = digitalRead(BTN);
  if(currentBtnState != lastBtnState) {
    Mouse.set_buttons(!currentBtnState, 0, 0);
  }

  Mouse.move(pos_x, pos_y);
  
  lastBtnState = currentBtnState;
  delay(25);
}

Teraz już może się trochę rozjaśnić dlaczego konwertujemy na wartość 0-254.

Załóżmy że w normalnym położeniu będziemy mieli wartości 512. Przeliczając je na wartość z zakresu 0-254 powinniśmy uzyskać 127. Teraz jeżeli joystick będzie w normalnym położeniu to wartość pos_x będzie 127 – 127 = 0. Przy maksymalnym wychyleniu będzie to 254 – 127 = 127, a przy wychyleniu maksymalnym w przeciwną stronę 0 – 127 = -127.

Analizując dalej kod natrafiamy na dwie instrukcje warunkowe. Jest to nic innego jak zabezpieczenie przed poruszaniem się kursora przy braku interakcji z naszej strony (wychylenia drążka) (bo jak już wiemy nic nie jest idealne).

Następnie sprawdzamy jaki jest aktualny stan przycisku, jeżeli jest inny niż stan poprzedni to nastąpiło naciśnięcie lub zwolnienie przycisku. Ustawiamy więc przyciski (metoda set_buttons przyjmuje po kolei w parametrach stan lewego przycisku myszy, środkowego oraz prawego, gdzie 0 to brak wciśnięcia, a 1 to wciśnięcie). Z racji tego, że pin do którego podłączony jest przycisk jest w trybie INPUT_PULLUP, a przycisk przy wciśnięciu zwiera do masy to kiedy przycisk będzie wciśnięty currentBtnState będzie równy 0 (false), a w stanie normalnym będzie 1 (true). Z tego też powodu negujemy tą wartość w metodzie (moglibyśmy też wpisać tam lastBtnState bez negacji gdyż wiemy już, że na pewno jest przeciwne do currentBtnState).

Pozostało nam już tylko poruszyć odpowiednio kursorem wywołując metodę move(), przypisać aktualny stan przycisku do poprzedniego stanu przycisku oraz odczekać 25 ms (aby kursor nie leciał zbyt szybko).

Przed wgraniem kodu na płytkę należy pamiętać, aby zmienić tryb USB na taki, który zawiera w sobie słówko Mouse. W przeciwnym wypadku kod się nie skompiluje.

Teensy może także udawać klawiaturę, więcej o tym można poczytać w dokumentacji na stronie producenta: