W poprzednich odcinkach (część 1 oraz część 2) udało mi się przekompilować firmware czipu AtMega16U2 komunikującego Arduino UNO z komputerem. Teraz spróbuję zamienić Arduino z nałożnym rozszerzeniem Funduino w… kontroler do gier dla RetroPie:)
W tym celu wymienię firmware AtMega16U2 na taki, dzięki któremu Arduino będzie rozpoznane jako akcesorium USB typu joystick z przyciskami.
Funduino
Funduino to rozszerzenie dla Arduino UNO/Mega, które ma na sobie joystick i 7 przycisków. Joystick działa w 2 osiach. Reaguje również na wciśnięcie (przycisk K). Na płytce są 4 kolorowe przyciski (A, B,C, D) oraz 2 mniejsze tact-switche E i F.
Osie joysticka podłączono do wejść analogowych A0 i A1. Przyciski podłączono do pinów cyfrowych:
Przycisk | PoRT |
---|---|
A | 2 |
B | 3 |
C | 4 |
D | 5 |
E | 6 |
F | 7 |
Oś X | A0 |
Oś Y | A1 |
Całkiem fajny kod do obsługi znalazłem na blogu All About Circuits. Czytanie z joystika nie jest problemem – wystarczy tu funkcja analogRead(), np:
1 2 |
int x = analogRead(0); int y = analogRead(1); |
Kolejną przydatną funkcją jest „map()”. Funkcja „map()” skaluje wartość zmiennej z jednego przedziału na wartość z innego przedziału. Funkcja „analogRead()” zwraca wartości z przedziału 0..1023. Załóżmy, że pozycja joysticka zawiera się w granicach -100…100. Wtedy:
1 2 3 4 |
int x = analogRead(0); int y = analogRead(1); int xMap = map(x, 0, 1023, -100, 100); int yMap = map(y, 0, 1023, -100, 100); |
Dlaczego akurat-100 i 100? Do tego dojdziemy za chwilę.
A co z czytaniem przycisków. Schemat postępowania jest następujący:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
//Button UP connected to D2 #define BUTTON_UP 2 //.. void setup() { Serial.begin(115200); //Set pin input pinMode(BUTTON_UP, INPUT); //pull it up digitalWrite(BUTTON_UP, HIGH); //... } void loop(){ if (digitalRead(BUTTON_UP) == LOW) { //Report button press //... } } |
Kilka szczegółów:
- W funkcji „setup()”, port. do którego podłączony jest klawisz, ustawiamy jako wejściowy (pinMode(…,INPUT);),
- Wywołanie „digitalWrite(…,HIGH)” powoduje włączenie na porcie rezystora podciągającego do stanu wysokiego. w ten sposób, gdy przycisk jest rozłączony – pin nie „pływa” (jest w stanie wysokim). Jeżeli przycisk jest wciśnięty – zwracana jest wartość niska – stąd porównanie do LOW,
- W funkcji loop() sprawdzamy stan portu funkcją digitalRead() – jeżeli jest niski, wciśnięto przycisk.
Tą samą procedurę należy zastosować dla każdego z przycisków.
Jeszcze jedna uwaga konstrukcyjna: Funduino nałożone na Arduino wystaje poza jego krawędź. Jeżeli myślicie nad jakąś sprytną obudową – weźcie to pod uwagę.
Firmware AtMega16U2
Cały potrzebny mi kod znalazłem na githubie harlequin-tech. Przykłady zbudowano na Lightweight USB Framework for AVRs – LUFA. W największym skrócie, LUFA to takie specjalne oprogramowanie, które pozwala zamieniać kontrolery AVR w urządzenia USB.
Przykłady dostarczone przez harlequin-tech zbudowano na dość starej wersji LUFA:100807. W tej chwili na stronie tego projektu LUFA wystawiono wersję 151115. W porównaniu do 100807 zaimplementowano wiele zmian – również w samym API. Z tego powodu przykłady z harlequin-tech wymagają kilku poprawek. Możecie to zrobić, ja (po krótkim, acz nieudanym teście) postanowiłem pozostać przy wersji 100807. Zwłaszcza, że jeżeli spojrzycie do katalogu Arduino IDE: „.hardware/arduino/avr/firmwares/atmegaxxu2/” (a więc źródła dla AtMega16U2 zamontowanego na płytce Arduino), w pliku „readme.txt” znajdziecie: should be compiled against LUFA 100807. Dlatego ta wersja wydaje mi się dobrym pomysłem.
Kilka poprawek do firmware…
Tutaj oparłem się (prawie) całkowicie na kodzie z forum Arduino. oraz kodzie z githuba harlequin-tech. Będę operował na plikach w katalogu ’./firmwares/arduino-joystick’. Trzeba wykonać kilka zmian.
W makefile:
1 2 3 4 5 |
MCU = atmega16u2 MCU_AVRDUDE = atmega16u2 MCU_DFU = atmega16u2 ARDUINO_MODEL_PID = 0x0043 BOARD=USER |
Załóżmy teraz, że do przesyłania danych z Funduino, użyjemy takiej struktury:
1 2 3 4 5 |
struct { int8_t x; int8_t y; uint32_t buttons; } joyReport; |
Zmienne x i y dotyczą joysticka. Zmienna buttons dotyczy klawiszy. Każdy klawisz to 1 bit w buttons. Ustawiony na „1” oznacza „wciśniśnięty”, 0 – zwolniony. Zamieńcie oryginalną strukturę USB_JoystickReport_Data_t w pliku Arduino_joystick.h na tą podaną powyżej.
W pliku Arduino_joystick.c podmieńcie funkcję na poniższą:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
bool CALLBACK_HID_Device_CreateHIDReport( USB_ClassInfo_HID_Device_t* const HIDInterfaceInfo, uint8_t* const ReportID, const uint8_t ReportType, void* ReportData, uint16_t* const ReportSize) { USB_JoystickReport_Data_t *reportp = (USB_JoystickReport_Data_t*)ReportData; RingBuff_Count_t BufferCount = RingBuffer_GetCount(&USARTtoUSB_Buffer); if (BufferCount >= sizeof(joyReport)) { uint8_t ind; for (ind = 0; ind < sizeof(joyReport); ind++) { ((uint8_t *)&joyReport)[ind] = RingBuffer_Remove(&USARTtoUSB_Buffer); } LEDs_TurnOnLEDs(LEDS_LED1); led1_ticks = LED_ON_TICKS; } *reportp = joyReport; *ReportSize = sizeof(USB_JoystickReport_Data_t); return false; } |
Oryginalny przykład obsługuje tylko 2 przyciski – Funduino ma 7. W związku z tym, w pliku descriptors.c trzeba zmienić zawartość tablicy JoystickReport na taki:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
const USB_Descriptor_HIDReport_Datatype_t PROGMEM JoystickReport[] = { 0x05, 0x01, /* Usage Page (Generic Desktop)*/ 0x09, 0x04, /* Usage (Joystick) */ 0xa1, 0x01, /* Collection (Application) */ 0x09, 0x01, /* Usage (Pointer) */ 0xa1, 0x00, /* Collection (Physical) */ 0x05, 0x01, /* Usage Page (Generic Desktop)*/ 0x95, 0x02, /* Report Count (2) */ 0x75, 0x08, /* Report Size (8) */ 0x09, 0x30, /* Usage (X) */ 0x09, 0x31, /* Usage (Y) */ 0x15, 0x9c, /* Logical Minimum (-100) */ 0x25, 0x64, /* Logical Maximum (100) */ 0x81, 0x82, /* Input (Data, Variable, Absolute, Volatile)*/ 0xc0, /* End Collection */ 0x05, 0x09, /* Usage Page (Button) */ 0x95, 0x07, /* Report Count:7 */ 0x75, 0x01, /* Report Size: 1 bit per report */ 0x19, 0x01, /* Usage Minimum (Button 1) */ 0x29, 0x07, /* Usage Maximum (Button 7) */ 0x15, 0x00, /* Logical Minimum (0) */ 0x25, 0x01, /* Logical Maximum (1) */ 0x81, 0x02, /* Input (Data, Variable, Absolute)/ 0x95, 0x01, /* Report Count (1) */ 0x75, 0x19, /* Report Size (25, fill to 32 */ 0x81, 0x01, /* Input (Constant) */ 0xc0 /* End Collection */ }; |
Zadeklarowaliśmy właśnie struktury, w jakich wysyłane będą dane z Arduino. Przyznam się szczerze, że nie chciało mi się przebijać przez dokumentację do HID i kod zmieniłem… trochę metodą prób i błędów. Każda para liczb to kod poszczególnych deskryptorów i ich wartości. Poprawne ustawienie tych deskryptorów jest warunkiem, żeby Arduino zostało prawidłowo rozpoznane a podłączone do niego urządzenie – zrozumiało nadawane przez niego dane.
Znaczenie niektórych wpisów (0x oznacza liczby heksadecymalne):
- Report size (0x75): rozmiar danych wysyłanych do hosta, tutaj: w bitach. Pozycję joysticka będziemy wysyłać po bajcie (int8_t, 8 bitów), stąd dla joysticka 0x75, 8 w górnej sekcji; każdy klawisz zajmie 1 bit, stąd 0x75,1 w dolnej sekcji;
- Report count (0x95): w wolnym tłumaczeniu: ilość raportowanych zmiennych; dla joysticka to 2 (pozycja x i y) – stąd 0x95,2; dla przycisków wysyłam 7 bitów (po jednym na przycisk) – 0x95,7; ALE UWAGA: dla przycisków muszę dopełnić te 7 bitów do 32 – rozmiaru zmiennej buttons w strukturze. Stąd na dole dodatkowy raport (jeden; 0x75,1) o rozmiarze 25 bitów (bo 7+25=32) – 0x95,25
- Usage minimum (0x19): minimalna ilość przycisków: 1
- Usage maximum (0x21): maksymalna ilość przycisków: 7
- Logical Minimum (0x15): minimalna wartość zmiennej w raporcie; dla przycisku to będzie 0 (0x15,0) – dla joysticka… no właśnie: pozostawiłem -100 jak w oryginale,
- Logical Maximum (0x25): maksymalna wartość zmiennej; dla przycisku: 1 (wciśnięty) a joysticka 100 (0x25,0x64).
Autor posta użył 32 bitowej zmiennej buttons po to, żebyście mogli wygodnie dodawać więcej przycisków. Przykładowo dla 10, dolna sekcja mogłaby wyglądać tak:
1 2 3 4 5 6 7 8 9 10 11 |
0x05, 0x09, /* Usage Page (Button) */ 0x95, 0x0A, /* Report Count:10 */ 0x75, 0x01, /* Report Size: 1 bit per report */ 0x19, 0x01, /* Usage Minimum (Button 1) */ 0x29, 0x0A, /* Usage Maximum (Button 10) */ 0x15, 0x00, /* Logical Minimum (0) */ 0x25, 0x01, /* Logical Maximum (1) */ 0x81, 0x02, /* Input (Data, Variable, Absolute) */ 0x95, 0x01, /* Report Count (1) */ 0x75, 0x16, /* Report Size (fill to 32) */ 0x81, 0x01, /* Input (Constant) */ |
Teraz pozostało skompilować. Wejdźcie do katalogu ./firmwares/arduino-joystik i po prostu wykonajcie komendę najpierw „make clean” a potem „make”.
Ja używam linuxa – ale w poprzednim tekście na ten temat (tutaj) podałem sposób na kompilację pod windows.
Powinno się udać. W wyniku kompilacji powinniście uzyskać plik Arduino-joystick.hex.
Teraz podłączcie atmega16u2 do programatora (szpilki bliżej gniazda USB) i wgrajcie na niego nowy firmware:
1 |
avrdude -c usbasp -v -p m16u2 -U flash:w:Arduino-joystick.hex |
Szkic
Teraz czas na szkic. Niestety musicie go wgrywać przy pomocy programatora – przecież właśnie przeprogramowaliście czip USB:)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
/* Arduino USB Joystick HID demo * / /* Author: Darran Hunt Released into the public domain. Updated with Funduino by Arek Merta */ struct { int8_t x; int8_t y; uint32_t buttons; } joyReport; #define PIN_ANALOG_X 0 #define PIN_ANALOG_Y 1 #define BUTTON_UP 2 #define BUTTON_RIGHT 3 #define BUTTON_DOWN 4 #define BUTTON_LEFT 5 #define BUTTON_E 6 #define BUTTON_F 7 #define BUTTON_K 8 #define NO_BUTTON 0 void setup() { Serial.begin(115200); pinMode(BUTTON_UP, INPUT); digitalWrite(BUTTON_UP, HIGH); pinMode(BUTTON_RIGHT, INPUT); digitalWrite(BUTTON_RIGHT, HIGH); pinMode(BUTTON_DOWN, INPUT); digitalWrite(BUTTON_DOWN, HIGH); pinMode(BUTTON_LEFT, INPUT); digitalWrite(BUTTON_LEFT, HIGH); pinMode(BUTTON_E, INPUT); digitalWrite(BUTTON_E, HIGH); pinMode(BUTTON_F, INPUT); digitalWrite(BUTTON_F, HIGH); pinMode(BUTTON_K, INPUT); digitalWrite(BUTTON_K, HIGH); } void reportMe(int x, int y, int button) { uint32_t buttons = 0; if ( button != NO_BUTTON ) { buttons = 1 << ( button - 2); } joyReport.buttons = buttons; joyReport.x = map( x, 0, 1023, -100, 100); joyReport.y = map( y, 0, 1023, -100, 100); Serial.write((uint8_t *)&joyReport, sizeof(joyReport)); } void loop() { int a1 = analogRead(PIN_ANALOG_X); int a2 = analogRead(PIN_ANALOG_Y); if (digitalRead(BUTTON_UP) == LOW) { reportMe( a1, a2, BUTTON_UP); } else if (digitalRead(BUTTON_RIGHT) == LOW) { reportMe( a1, a2, BUTTON_RIGHT); } else if (digitalRead(BUTTON_DOWN) == LOW) { reportMe( a1, a2, BUTTON_DOWN); } else if (digitalRead(BUTTON_LEFT) == LOW) { reportMe( a1, a2, BUTTON_LEFT); } else if (digitalRead(BUTTON_E) == LOW) { reportMe( a1, a2, BUTTON_E); } else if (digitalRead(BUTTON_F) == LOW) { reportMe( a1, a2, BUTTON_F); } else if (digitalRead(BUTTON_K) == LOW) { reportMe( a1, a2, BUTTON_K); } else { reportMe( a1, a2, NO_BUTTON); } delay(50); } |
Pod Windows…
Możecie sprawdzić działanie Funduino na Windows. Podłączcie Arduino z Funduino do portu USB i w Windowsie:
- Wciśnijcie klawisz 'Windows’ i wpiszcie 'USB’
- Z listy wybierzcie: Skonfiguruj kontrolery gier podłączane przez USB:
- Otworzy się okno 'Kontrolery gier’:
- Kliknijcie 'Właściwości’ – będziecie mogli przetestować działanie joystika:
I demo:
Źródła
- http://forum.arduino.cc/index.php?topic=99.0
- https://github.com/NicoHood/HoodLoader2
- http://hunt.net.nz/users/darran/blog/
- http://smartinteractionlab.com/lufa-example-turning-your-arduino-into-a-keyboard/
- http://forum.arduino.cc/index.php?topic=111.30
- http://hunt.net.nz/users/darran/