LinkedIn YouTube Facebook
Szukaj

Newsletter

Proszę czekać.

Dziękujemy za zgłoszenie!

Wstecz
SoM / SBC

[PRZYKŁAD] Obsługa kontrolerów PS2 na Arduino za pomocą nakładki Cytron PS2 Shield

W poniższym artykule prezentujemy model obsługi kontrolerów PS2 na platformie Arduino. W tym celu wykorzystamy nakładkę PS2 Shield z oferty Cytron Technologies. Kod będzie sygnalizował przyciśnięcie przycisku na monochromatycznym ekranie LCD.

Myślę, że każdy nasz czytelnik zetknął się w życiu z kontrolerem gier na konsolę PlayStation, nazywanym także DualShock. W najprostszej wersji zawiera on dwie gałki analogowe z precyzją 8 bitów, 16 przycisków cyfrowych, w tym cyfrowe przyciski kierunkowe oraz przyciski zamontowane na gałkach, a także dwóch silniczków wibracyjnych. Wraz z rozwojem platformy PlayStation, kontroler zyskiwał nowe funkcje, np. już DualShock 2 wyposażono w przyciski wrażliwe na siłę nacisku oraz silniczki o regulowanej sile wibracji. W artykule pozostaniemy jednak przy implementacji funkcjonalności pierwszej generacji kontrolerów.

Rys. 1. Kontroler zgodny z PlayStation

Wraz z rozwojem elektroniki i robotyki, zwłaszcza w wydaniu hobbystycznym, kontrolery zyskały sobie kilka nowych zastosowań. Często bowiem są dostępne pod ręką (również w wersji USB) i można je stosować do sterowania robotami, robotycznymi ramieniami, zdalnie sterowanymi samochodzikami, kamerami, a nawet dronami.

Złącze do podłączenia kontrolera składa się z 9 pinów w trzech przegrodach po trzy wyprowadzenia. Rozkład wyprowadzeń zaprezentowano w tabeli poniżej.

Pin złącza PS (od lewej jak na zdjęciu) Funkcja
1 Dane (z kontrolera do hosta)
2 Komendy (od hosta do kontrolera)
3 Zasilanie silników wibracyjnych (7,2 – 9 V)
4 Masa
5 Zasilanie 3,3 V
6 Attention (podobny do Chip Select)
7 Zegar
8 N/C
9 ACK

Rys. 2. Złącze kontrolera PlayStation – pin 8 (N/C) usunięty

 

Nakładka Cytron PS2 Shield

Do doprowadzenia sygnałów z kontrolera do Arduino wykorzystamy nakładkę Cytron PS2 Shield. Jest to nakładka na Arduino UNO ze złączem na kontroler PlayStation. Za konwersję sygnałów odpowiada prosty mikrokontroler PIC16F1827. Z tego układu dane do hosta są przesyłane za pomocą interfejsu UART. Zworki na płytce pozwalają dobrać zarówno prędkość interfejsu, jak i piny przez które układy się komunikują.

Rys. 3. Nakładka Cytron PS2 Shield

Rys. 4. Cytron PS2 Shield – zbliżenie na zworki programujące prędkość i wyprowadzenia interfejsu UART

Komunikacja Hosta z kontrolerem

Sposób komunikacji hosta z interfejsem jest następujący – należy wysłać komendę, na którą mikrokontroler reaguje wysłaniem stanu przycisku/gałki. Część komend służy również do sterowania silnikiem wibracyjnym. Dostępna jest także komenda umożliwiająca odczyt wszystkich przycisków i gałek jednocześnie.

Poniżej rozpisałem listę komend dla kontrolera:

Tab. 2. Komendy pobierające stan poszczególnego przycisku

Komenda (postać dziesiętna) Przycisk Stan 0 dla przycisku wciśniętego i stan 1 dla przycisku niewciśniętego
0 SELECT
1 Lewa gałka (L3)
2 Prawa gałka (R3)
3 START
4 Góra (D-Pad)
5 Prawo (D-Pad)
6 Dół (D-Pad)
7 Lewo (D-Pad)
8 L2
9 R2
10 L1
11 R1
12 Trójkąt
13 Koło
14 Krzyż
15 Kwadrat
Tab. 3. Komendy pobierające stan gałek analogowych

Komenda Znaczenie Odpowiedź kontrolera
16 Gałka lewa oś X Stan osi – liczba w zakresie od 0 do 255
17 Gałka lewa oś Y
18 Gałka prawa oś X
19 Gałka prawa oś Y
20 Gałka lewa – góra Stan wychylenia w konkretnym kierunku– liczba od 0 do 100
21 Gałka lewa – dół
22 Gałka lewa – lewo
23 Gałka lewa – prawo
24 Gałka prawa – góra
25 Gałka prawa – dół
26 Gałka prawa – lewo
27 Gałka prawa – prawo
Tab. 4. Pozostałe komendy
Komenda Znaczenie
28 Status kontrolera 1 – podłączony, 0 – niepodłączony
29 Obsługa silniczka 1 (prawego) Konieczne wysłanie parametru: 1 – włącz silnik, 0 – wyłącz silnik
30 Konieczne wysłanie parametru: od 0 do 255 – szybkość wibracji
31 Odczyt wszystkich przycisków i gałek Kontroler wysyła 6 bajtów – opis poniżej

Pobieranie danych o stanie przycisków i gałek

W naszym przykładzie wykorzystamy funkcję readAllButtons, która działa właśnie wysyłając ostatnią komendę odczytującą wszystkie przyciski i gałki jednocześnie. Po wykonaniu funkcji odpowiedź kontrolera zapisywana jest w tablicy ps_data. Poniżej zaprezentowano znaczenie poszczególnych bajtów w tablicy.

  • Bajt 1: Stan przycisków (1):
    • Bit 7 (MSB) – Lewo (D-Pad),
    • 6 – Dół (D-Pad),
    • 5 – Prawo (D-Pad),
    • 4 – Góra (D-Pad),
    • 3 – Start,
    • 2 – Prawa Gałka (R3),
    • 1 – Lewa Gałka (L3),
    • 0 (LSB) – Select.
  • Bajt 2: Stan przycisków (2):
    • Bit 7 (MSB) – Kwadrat,
    • 6 – Krzyż,
    • 5 – Koło,
    • 4 – Trójkąt,
    • 3 – R1,
    • 2 – L1,
    • 1 – R2,
    • 0 (LSB) – L2.
  • Bajt 3: Gałka prawa – oś X,
  • 4: Gałka prawa – oś Y,
  • 5: Gałka lewa – oś X,
  • 6: Gałka lewa – oś Y.

Pozostałe elementy projektu

Jako płytkę główną projektu wykorzystam klona Arduino UNO – Cytron Maker UNO, ale oczywiście równie dobrze można wykorzystać wszystkie płytki zgodne z tym standardem, a także sam oryginał. Rolę nakładki z ekranem i przyciskami pełni znana już z kilku moich tekstów płytka LCD Keypad Shield z wyświetlaczem alfanumerycznym 2×16 i przyciskami analogowymi.

Natomiast w roli kontrolera wystąpi Bezprzewodowy kontroler PS2 z odbiornikiem. Kontroler komunikuje się bezprzewodowo w paśmie 2,4 GHz w zasięgu ok. 8 m. Ma rozkład przycisków znany z padów DualShock. Jest zasilany z dwóch baterii AAA. Można go także podłączyć bezpośrednio do hosta (np. Arduino) do interfejsu SPI. Jest on również dystrybuowany przez Cytron Technologies.

Rys. 5. Bezprzewodowy kontroler PS2 Cytron Technologies

Procedura łączenia elementów projektu jest następująca – na płytkę główną nakładamy PS2 Shield, a następnie na samą górę nakładkę z ekranem LCD. W porcie PS2 umieszczamy odbiornik kontrolera. Połączony projekt zaprezentowano na poniższym zdjęciu.

Rys. 6. Połączone elementy projektu

Kod projektu

Projekt pracuje w sposób następujący: na górnej linii wyświetlacza prezentowane są aktualnie wciśnięte przyciski w postaci numerów takich, jak w tabeli 2. Natomiast na dolej linii prezentowane są odczyty obu osi prawej gałki analogowej.

Na początek należy zaimportować niezbędne biblioteki. W tym przypadku są to SoftwareSerial do obsługi programowego portu szeregowego, Cytron_PS2Shield czyli biblioteka producenta do celów obsługi kontrolera, a także LiquidCrystal do sterowania wyświetlacza.

#include <SoftwareSerial.h>
#include <Cytron_PS2Shield.h>
#include <LiquidCrystal.h>

Następnie inicjalizujemy obiekty klas do obsługi nakładki PS2 oraz wyświetlacza. W przypadku klasy Cytron_PS2Shield można skorzystać z portu sprzętowego (inicjalizacja bez parametrów) lub z portu programowego. Z racji, że port sprzętowy jest już wykorzystywany do wgrywania kodu do pamięci procesora, polecam skorzystać z portu programowego. Wyprowadzenia można zmieniać przestawiając zworki na nakładce (por. z rysunkiem 4). Ja skorzystam z wyprowadzeń D2 i D3 i takie właśnie piny podaję w parametrach konstruktora.

Z kolei w konstruktorze obiektu odpowiedzialnego za obsługę wyświetlacza podaję wyprowadzenia płytki Arduino podłączone do ekranu na nakładce. Ten zestaw jest właściwy dla tej konkretnej nakładki i jeśli chcemy stosować inne rozwiązanie np. podłączyć wyświetlacz bezpośrednio do Arduino, trzeba zmodyfikować podany pinout.

Cytron_PS2Shield ps2(2, 3);
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);

Następnie inicjujemy niezbędne zmienne. Zmienna przyciski ma być dwubajtową reprezentacją stanu przycisków kontrolera. Natomiast tepTimer ma za zadanie przechowywać aktualny czas pracy programu w milisekundach.

uint16_t przyciski;
unsigned long tepTimer;

Funkcja setup

W funkcji setup inicjujemy pracę nakładki PS2 i wyświetlacza. Komunikacja z nakładką przebiega przez port szeregowy o przepływności 9600. Przepływność również można regulować za pomocą zworki na nakładce i oczywiście obie wartości – w kodzie i na nakładce – muszą być jednakowe (por. z rysunkiem 4).

void setup() {
  ps2.begin(9600);
  lcd.begin(16, 2);
}

Pętla główna programu

Główna pętla programu wykonuje się co 100 ms. Osiągamy to dzięki funkcji millis oraz aktualizacji zmiennej tepTimer. Następnie dzięki funkcji while wstrzymujemy działanie programu do momentu pobrania wszystkich danych o stanie kontrolera. W kolejnym kroku „składamy” dwa pierwsze bajty tablicy ps_data, co daje nam pełną informację o stanie przycisków.

if(millis() - tepTimer > 100){

  tepTimer = millis();

  while (ps2.readAllButton()==false);

  przyciski = (ps2.ps_data[1]<<8 | ps2.ps_data[0]);

Następnie czyścimy wyświetlacz LCD i ustawiamy kursor w pozycję początkową.

lcd.clear();
lcd.setCursor (0,0);

W pętli for przeglądamy zawartość zmiennej przyciski i w przypadku, gdy bit jest równy 0 (przycisk wciśnięty), to na pierwszej linii wyświetlacza pokazuje się numer przycisku. Kod bez problemu radzi sobie z wykrywaniem jednoczesnego przyciśnięcia wielu przycisków, aczkolwiek ograniczenie w długości linii spowoduje, że być może nie wszystkie będą sygnalizowane na ekranie.

Algorytm jest prosty – kod sprawdza czy zmienna jest parzysta (czy ostatni bit jest równy 0). Jeśli tak – numer przycisku jest wypisywany na ekran. Następnie zmienna jest przesuwana bitowo w prawo i pętla wykonuje kolejny obrót aż do sprawdzenia wszystkich bitów.

for (int i = 0; i<=15; ++i){
  if (przyciski%2==0) {
    lcd.print (i);
    lcd.print (" ");
  }

  przyciski = przyciski >>1;
}

Na koniec kod wypisuje w drugiej linii aktualny odczyt stanu prawej gałki analogowej na osi X oraz Y.

lcd.setCursor (0,1);
lcd.print("X: ");
lcd.print(ps2.ps_data[2]);
 
lcd.setCursor (8,1);
lcd.print("Y: ");
lcd.print(ps2.ps_data[3]);

Pełen kod programu jest dostępny w sekcji Do pobrania. Może on służyć jako baza do implementacji sterowania urządzeniem jak, np. robotem za pomocą kontrolera PS2.

Efekt działania kodu zaprezentowano na poniższej fotografii.

Rys. 7. Efekt pracy przykładu obsługi kontrolera PS2

Wszystkie elementy projektu otrzymaliśmy dzięki uprzejmości sklepu Kamami.pl

Do pobrania