Gameduino – czyli zróbmy fajną grę

W zasadzie od momentu kiedy w ofercie Nettigo pojawił się pierwszy czujnik przyspieszenia miałem ochotę zrobić pewien hack i wykorzystać go jako główny czujnik do jakiegoś urządzenia sterującego. Myślałem o grze sportowej i podłączeniu czujnika do komputera. Ale, czasu ciągle brakowało, a przynajmniej odnosiłem wrażenie, że hack będzie długi i skomplikowany. Odczyt z czujnika przetworzyć na ciąg wciśnięć klawiszy w komputerze. Arduino, serial, jakaś biblioteka odczytująca serial, potem emulacja wciśnięcia klawisza. Może to wszystko okaże się prostsze niż wygląda, ale sprawiało wrażenie dużej liczby ruchomych części, a co za tym idzie większą szansę niepowodzenia. No cóż, po prostu nie chciało mi się :)

Gdy pojawiło się Arduino UNO, przez chwilę pojawiła się chęć przetestowania klawiaturowego firmware dla ATmega8u2 w ramach tego właśnie hacku, ale projekt nie wystartował. Co UNO zmieniało? Otóż z nowym firmware, podłączone do komputera widziane jest jako klawiatura więc odpada cała komplikacja z odczytem seriala i wysyłania komunikatów do gry. Po prostu Arduino może 'wciskać klawisze’ przez port USB i całość wydaje się znacznie prostsza.

Ale gdy tylko zobaczyłem Gameduino wiedziałem że to jest to czego potrzebowałem. Ostatnie dni deszczowe były, tak więc – do dzieła!

Najpierw – jedno z dem dołączonych do biblioteki to Asteroids – lecisz statkiem przez pole asteroidów i strzelasz do nich. Proste. Rozpakować bibliotekę do sketchbook/libraries, uruchomić Arduino IDE, menu File/Examples/Gameduino/Demos/asteroids. Kompilacja, upload i mamy. Tyle, że nie mamy żadnego kontrolera do sterowania statkiem. Ja do pierwszych testów użyłem Nettigo Keypada, bo go pod ręką miałem.

Wystarczy zmienić kod w controller_sense i zamiast domyślnego kodu przeznaczonego dla game controllera od SparkFun użyć swojego. Ale to mało pasjonujące naciskać na klawisze, byle konsola to ma :)

Czas na Level 2

Podłączyć czujnik przyspieszenia i zrobić jakąś platformę, na której stojąc będzie się sterować statkiem. Przechył w lewo – obrót w lewo. Przechył w prawo – obrót w prawo. Przechył do przodu – lecimy. W tył – strzelamy. Taki Segway. No może bez tego strzelania :)

Pierwsza rzecz jak zrobić samą platformę? Miałem pod ręką kawałek panela podłogowego. Do niego przyklejona płytka stykowa 170 (dzięki swojej taśmie samoprzylepnej). Na niej czujnik przyspieszenia, kabelki zgodnie z opisem w dokumentacji czujnika i możemy ustalić położenie panela. Interesują nas dwie osie – X i Y, Z na razie nam niepotrzebne :)

Czujnik przyspieszenia na płytce stykowej
Czujnik przyspieszenia na płytce stykowej

Pozostaje teraz rozwiązać konstrukcję modułu wychylnego. Musi to być coś co pozwoli naszemu panelowi kiwać się na wszystkie strony, jednocześnie nie za bardzo, tak aby gracz nie spadał z niego. Po wielu testach różnych materiałów i różnych rozwiązań mocowań konsultowanych z NASA nasze finalne rozwiązanie wygląda tak:

Źródło: NASA (just joking)
Źródło: NASA (just joking)

Jak widać wybór padł na starą poduszkę :) Panel położony na niej ma dość swobody aby poruszać się w dwóch osiach na tyle aby czujnik mógł odczytać położenie.

Hard za nami, teraz soft

Teraz trochę kodu, czyli w zasadzie jedyna zmiana w kodzie dema, to nowa funkcja controller_sense:

#define LR_NEUTRAL 330
#define UD_NEUTRAL 345

#define LR_DELTA 8
#define UD_DELTA 7
static byte controller_sense(uint16_t clock)
{
int rd,a1,a2;

rd = 0;
a1 = analogRead(3);
a2 = analogRead(4);

  if (a1 < LR_NEUTRAL - LR_DELTA){
//    Serial.println("Lewo");
    rd = rd | CONTROL_LEFT;
  }  
  else if (a1 > LR_NEUTRAL + LR_DELTA)
  {
    rd = rd | CONTROL_RIGHT;
//    Serial.println("Prawo");
  }
  if (a2 < UD_NEUTRAL - UD_DELTA){
    rd = rd | CONTROL_DOWN;
//    Serial.println("Jazda");
  }  
  else if (a2 > UD_NEUTRAL + UD_DELTA)
  {
    rd = rd | CONTROL_UP;
//    Serial.println("Ognia");
  }
  return rd;

Teraz kilka słów wyjaśnienia. LR_NEUTRAL to wartość jaka jest odczytywana w osi X w pozycji neutralnej. LR jest od Left Right, bo przechył w osi X w naszym ułożeniu czujnika oznacza przechył w lewo/prawo. LR_DELTA to jest margines o jaki może się w każdą stronę zmienić odczyt od pozycji neutralnej zanim zostanie uznany za wciśnięcie lewo lub prawo.

Podobnie jest z UD (od up i down). Oznaczenia te pochodzą od oznaczeń używanych w kodzie dema, nie od kierunku ruchu panela :)

rd jest na początku ustawiane na zero i jeżeli wychył od pozycji neutralnej jest dostatecznie duży odpowiednia wartość CONTROL_ jest ORowana. O co chodzi? Jeśli spojrzeć na definicję wartości CONTROL_ :

#define CONTROL_LEFT  1
#define CONTROL_RIGHT 2
#define CONTROL_UP    4
#define CONTROL_DOWN  8

są to kolejne potęgi 2. W zapisie binarnym odpowiada to kolejnym bitom. 1 to 0001, 2 to 0010, 4 to 0100, itd. Logiczne OR dwóch liczb (w C to symbol tzw pałki | ) zwraca wartość gdzie bity są ustawione na 1 wszędzie tam gdzie choć w jednej z liczb była jedynka. Na przykład:

  • 0000 OR 0001 = 0001
  • 0000 OR 0010 = 0010
  • 0100 OR 0010 = 0110
  • 0100 OR 0100 = 0100

W ten sposób wiedząc że 1 na ostatnim bicie to CONTROL_LEFT a 1 na pierwszym to CONTROL_DOWN, można w jednej liczbie całkowitej przekazać informację, że wciśnięte zostały dwa klawisze jednocześnie: 1001 czyli 9 dziesiętnie. Bardzo często stosowana metoda przekazywania stanu różnych rzeczy przez jedną liczbę całkowitą.

A jak potem łatwo można odczytać czy bit jest ustawiony? Ano z wykorzystaniem AND (znak &):

  • 1001 AND 1000 = 1000
  • 1001 AND 0100 = 0000
  • 1001 AND 0010 = 0000
  • 1001 AND 0001 = 0001

Teraz już chyba jest jasne w jaki sposób przez rd jest zwracana informacja o równocześnie naciśniętych przyciskach.

Co do naszego czujnika – wartości _NEUTRAL oraz _DELTA zostały dobrane eksperymentalnie – obserwując odczyty z czujnika, które na ten czas wysyłane były na Serial. Po ich ustaleniu komendy Serial.print zostały usunięte, bo zajmują cenne takty procesora ;)

Jak to wychodzi w praktyce można zobaczyć na tym krótkim filmiku (nie wiem jak go odwrócić, aparat było mi wygodniej trzymać w pionie):

Ze względu na dynamikę całości :) niezbędne jest osłonięcie czujnika aby zabezpieczyć przed przypadkowym zgnieceniem:

W obudowie
W obudowie

Raport Millera – czyli co można poprawić

Położenie neutralne zmienia się nieco (tzn odczyt z czujnika), w zależności od zasilania Arduino (np mój notebook zamiast 5V na USB ma 4.9, co przy niewielkich zmianach napięcia na wyjściu czujnika może mieć znaczenie – punkt odniesienia się zmienia to i odczyt zwracany przez analogRead się zmienia). Również z czasem czujnik może minimalnie zmienić odczyt. Wówczas zostaje ręczna zmiana wartości w kodzie, kompilacja i wgranie do Arduino.

Lepszym rozwiązaniem byłaby kalibracja – otóż specjalny tryb, zamiast grania wyświetli polecenie: Wychyl się w lewo. Potem odczytuje wartość z czujnika. Następnie Wychyl się w prawo i znowu. W ten sposób szkic jest sam w stanie wyznaczyć sobie wartości _NEUTRAL i _DELTA.

Druga uwaga – dynamika gry jest duża i chyba taki kontroler byłby lepszy dla jakiejś sportowej niż strzelanki. Ale to już lepiej byłoby jednak użyć klawiaturowego firmware i komputera, może innym razem…