Konfiguracja i programowanie Gamepad'a PlayStation2 na mikrokontrolerze STM32 Autor: Dawid Lubomski Data opracowania streszczenia (wersja 2): 23.04.2018 Data wygłoszenia referatu: 06.04.2018 Wprowadzenie W moim referacie przedstawię jak podłączyć odbiornik bezprzewodowego Gamepad'a Playstation2 (DualShock 2, PS2) do mikrokontrolera STM32F411RE, skonfigurować środowisko STM32CubeMX oraz prawidłowo obsłużyć komunikaty aby móc kontrolować inne urządzenia. Gamepad oprócz standardowego zastosowania, jakim jest kontrola konsoli PlayStation, może mieć również inne wykorzystanie np. sterowanie robotów. Program STM32CubeMX generuje projekt, który później należy otworzyć w edytorze System Workbench for STM32, opartym na popularnej platformie Eclipse. Mikrokontroler programuje się w języku C/C++. Opis Gamepad'a PlayStation2 Standardowy kontroler konsoli wygląda następująco: Posiada on 2 gałki analogowe oraz 15 przycisków funkcyjnych. 4 z nich to tzw. D-pad czyli przyciski odpowiadające czterem kierunkom. Przyciski L1, L2, R1, R2, trójkąt, kółko, krzyżyk, kwadrat służą do grania. Zawiera on również, przyciski takie jak: Select, Start i Mode (zamienna nazwa Analog), których funkcje to dodatkowe ustawienia oraz włącznik. Kontroler ten ma w sobie 2 serwomechanizmy z niesymetrycznymi ciężarkami na osiach wzbudzające wstrząsy o różnym nasileniu (drgania Gamepad'a w ręku użytkownika). Drgania występują w zależności od tego, co dzieje się w grze w celu zwiększenia doznań. 1
W celu uruchomienia silników, w wersji bezprzewodowej kontrolera należy wysłać odpowiedni ciąg bajtów, co spowoduje zmianę trybu pracy urządzenia. Odbiornik PS2 posiada następujące wyjścia oraz wejścia: - Pin,,dane - przesyła informacje do kontrolera (wyjście); - Pin,,komendy - odbiera informacje od kontrolera (wejście); - Pin,,zasilanie silników powodujących wstrząsy - musi być zasilany napięciem 7V-11V, ma zastosowanie tylko w przewodowej wersji Gamepad'a. Modele bezprzewodowe nie używają tego pinu; - Pin,,uziemienie - pin techniczny (masa); - Pin,,zasilanie - pin zasilający odbiornik napięciem 3.3V; - Pin,,attention - pin umożliwiający wybór odbiornika danego Gamepad'a, jest to również pin załączający (należy podać stan wysoki przed wysyłaniem komend); - Pin,,zegar - na ten pin należy generować sygnał zegarowy; -,,Pin wolny - do tego pinu nie trzeba niczego podłączać; - Pin,,ACK (ang. acknowledge) - pin, który przybiera stan wysoki kiedy odbiornik poprawnie odebrał pakiet informacji. Krótkie przedstawienie połączeń Aby poprawnie podłączyć odbiornik do mikrokontrolera, należy przeanalizować nazwy pinów używanych w STM32CubeMX z poniższego schematu, oraz dopasować je do nazw na płytce. pł Źródło: https://encrypted-tbn0.gstatic.com/images?q=tbn:and9g crdxkipya_sff2kqs2rsimedeob2-avf54ohxsl6c4wbhktfj7w0q Źródło: https://botland.com.pl/12291-thickbox_default/st m32-nucleo-f401re-stm32f401re-arm-cortex-m4.jpg Niezbędne jest wykorzystanie pinów,,sck/d13 (zegar, PA5),,,MISO/D12 (dane, PA6),,,PWM/MOSI/D11 (komendy, PA7), dowolnego uziemienia (GND) oraz dowolnego pinu, jako,,attention (pin załączający) - innego wolnego pinu np. PA4. Konfiguracja projektu w STM32CubeMX Tworzymy nowy projekt i konfigurujemy piny o nazwach: PA4 jako CS (Attention), PA5 jako SCK, PA6 jako MISO oraz PA7 jako MOSI. Nadane nazwy mogą być zupełnie przypadkowe, jednak należy o nich pamiętać później w trakcie pisania programu. 2
Piny PA nie mogą zostać wybrane przypadkowo i w trakcie konfiguracji, trzeba nadać im odpowiednie funkcje. SPI (ang. Serial Peripheral Interface) to szeregowy interfejs urządzeń peryferyjnych stosowany w komunikacji pomiędzy mikroprocesorem a innym układem. W momencie kiedy wybierzemy opcję,,full-duplex Master z bocznego menu, piny PA5, PA6 i PA7 automatycznie się skonfigurują jako zegar, wejście oraz wyjście. Pin PA4 należy ręcznie ustawić jako wyjście mikrokontrolera. Następnie ustawiamy taktowania zegarów. Dzięki ustaleniu odpowiednich wartości możemy poprawnie komunikować się z odbiornikiem PS2. Wymaga on prędkości przesyłu 125KBps (dane zawarte w dokumentacji Gamepad'a). Aby uzyskać taką wartość musimy w konfiguracji głównego zegara ustawić następujące wartości: APB1 Timer clocks(mhz) jest to magistrala typu low-speed. Może być taktowana maksymalnie z częstotliwością 50MHz. Na tej magistrali znajduje się interfejs SPI1. W konfiguracji SPI1 ustawiamy wielkość przesyłanych pakietów na 8 bitów. Pierwszy bit znaczący zaczyna się od lewej strony - jest to konieczne aby poprawnie połączyć się z odbiornikiem. Stanowi on o stronie od której zacznie się transmisja bajtu. W tym przypadku od bitu [0] do bitu [7]. Prescaler (dzielnik częstotliwości) ustawiamy na 64 aby otrzymać 125kHz (prędkość przesyłu 125KBps). Dzielnik ten, wpływa na magistralę APB1. 8Mhz podzielone przez 64 da 125kHz. Kolejne wartości jak CPOL, CPHA (polaryzacja i faza zegara) pobieramy z dokumentacji. Ustawienia SPI. 3
Kolejnym ważnym aspektem jest ustawienie timer'ów. Timer TIM3 w programie umożliwia wykonanie odstępów czasowych co 15μs - niezbędnych do komunikacji z odbiornikiem. Wartości timera przelicza się według "wzoru generowania przerwania impulsu": FREQ = TIM_CLK / ((ARR+1)(PSC+1)(CKD+1)) gdzie: FREQ - częstotliwość generowanego przerwania (ilość okresów w sekundzie); TIM_CLK - częstotliwość taktowania magistrali, na której umieszczony jest timer; PSC+1 - dzielnik częstotliwości wewnętrznego zegara timera (rejestr); ARR+1 - poziom wypełnienia impulsu, wartość do której zlicza timer; CKD+1 - dzielnik częstotliwości zegara timera (rejestr dodatkowy). Źródło: własne, ustawienia licznika TIM3 Timer działa w następujący sposób: na początku wartość rejestru licznika wynosi 0. Każdy cykl zliczenia odbywa się z częstotliwością zadaną przez taktowanie zegara timera, która podzielona jest przez wartość PSC oraz CKD. Kiedy wartość liczona będzie równa ARR następuje przerwanie i rejestr licznika jest zerowany. Zatem aby otrzymać opóźnienie 15μs należy: - policzyć ile w 1 sekundzie odbywa się takich cykli - jest to ok. 66 667, wartość do której będzie zliczał timer. Niestety takiej wartości nie można ustawić jako ARR bo maksymalna wartość to 65535 (16 bitów). Założyłem że 60 000 będzie wartością optymalną. - podstawić do przekształconego wzoru dane: (PSC+1) = TIM_CLK / FREQ (ARR+1)(CKD+1) (PSC+1)= 8 000 000Hz / 60 000 (15+1)(0+1) (PSC+1)= 8 000 000Hz/ 960 000 (PSC+1) 8,33 PSC=7,33 - ja wpisałem wartość 7 więc opóźnienie wynosi wtedy ok. 16,7μs - tak również jest dobrze gdyż prawdopodobnie istnieje jakiś zakres tolerancji. Programowanie w System Workbench for STM32 Aby rozpocząć programowanie trzeba zaimportować projekt do programu, podłączyć płytkę STM32 do komputera kablem MiniUSB oraz odpowiednio połączyć odbiornik z mikrokontrolerem. W pliku main.c najpierw powinno się zadeklarować zmienne prywatne dla ustalania poziomu wysokiego lub niskiego na pinie CS w celu "otwierania drogi" wysyłanym bajtom: Definiowanie zmiennych. Funkcja HAL_GPIO_WritePin umożliwia ustalanie na danym pinie stanu wysokiego lub niskiego. 4
Funkcja delay_us, to funkcja umożliwiająca tworzenie opóźnień: Funkcja tworząca opóźnienie. volatile - wyłączenie optymalizacji w stosunku do zmiennej, tej składni używa się w przypadku gdy zmienna modyfikowana jest przez funkcję przerwania. uint32_t - typ liczb całkowitych gdzie maksymalna wartość liczby może być zapisana na 32 bitach. Funkcja wysyłająca i odbierająca komendy(1 bajt) poprzez protokół SPI: Funkcja wysyłająca komendy protokołem SPI. uint8_t - typ liczb całkowitych gdzie maksymalna wartość liczby może być zapisana na 8 bitach. HAL_SPI_GetState(&hspi1) - funkcja służąca do sprawdzenia czy transmisja bajtu została zakończona na interfejsie SPI1. HAL_SPI_TransmitReceive - funkcja, która służy do wysyłania bajtu danych i jednocześnie odbiera wartości liczbowe z odbiornika do tablicy interfejsem SPI1 (hspi1). Funkcja Get_PS2Dat wysyła ciąg bajtów co ok.15μs. Wartości zwrotne, które otrzymujemy zapisywane są w tablicy. Dany indeks tablicy odpowiada kilku wciśniętym przyciskom na padzie (różne wartości odebrane): Funkcja wysyłająca ciąg bajtów. Następnie w funkcji main należy ręcznie uruchomić zegar dla SPI1: 5
Główną częścią programu jest nieskończona pętla (przedstawia poniższy obrazek), która obsługuje przyciski z pada. Główna pętla programu. PS2buf jest tutaj zmienną tablicową przyjmującą określone wartości z zakresu 0-255. Pobrana liczba oznacza wciśnięty przycisk na kontrolerze PS2. Po wysłaniu informacji: 0x01 0x42 0x00 0x00 0x00 otrzymujemy: 0xFF 0x41 0x5A 0xFF 0xFF w przypadku kiedy żaden przycisk nie jest wciśnięty. Pierwszy bajt odpowiada przyciskowi mode. Na 3 i 4 bajcie pojawiają się informacje odnośnie wciśniętych klawiszy. Bajty: 5,6,7,8 - to działanie analogów. W celu uruchomienia silników pada, należy w pierwszej kolejności, wysłać odpowiedni ciąg bajtów aby przestawić tryb działania pad'a. Wartości odebrane znajdują się wtedy na podobnych indeksach tablicy. 6