USB Host Shield 2.0
Loading...
Searching...
No Matches
XBOXONE.cpp
Go to the documentation of this file.
1/* Copyright (C) 2012 Kristian Lauszus, TKJ Electronics. All rights reserved.
2 Copyright (C) 2015 guruthree
3
4 This software may be distributed and modified under the terms of the GNU
5 General Public License version 2 (GPL2) as published by the Free Software
6 Foundation and appearing in the file GPL2.TXT included in the packaging of
7 this file. Please note that GPL2 Section 2[b] requires that all works based
8 on this software must also be made publicly available under the terms of
9 the GPL2 ("Copyleft").
10
11 Contact information
12 -------------------
13
14 Kristian Lauszus, TKJ Electronics
15 Web : http://www.tkjelectronics.com
16 e-mail : kristianl@tkjelectronics.com
17
18 guruthree
19 Web : https://github.com/guruthree/
20 */
21
22#include "XBOXONE.h"
23// To enable serial debugging see "settings.h"
24//#define EXTRADEBUG // Uncomment to get even more debugging data
25//#define PRINTREPORT // Uncomment to print the report send by the Xbox ONE Controller
26
28pUsb(p), // pointer to USB class instance - mandatory
29bAddress(0), // device address - mandatory
30bNumEP(1), // If config descriptor needs to be parsed
31qNextPollTime(0), // Reset NextPollTime
32pollInterval(0),
33bPollEnable(false) { // don't start polling before dongle is connected
34 for(uint8_t i = 0; i < XBOX_ONE_MAX_ENDPOINTS; i++) {
35 epInfo[i].epAddr = 0;
36 epInfo[i].maxPktSize = (i) ? 0 : 8;
37 epInfo[i].bmSndToggle = 0;
38 epInfo[i].bmRcvToggle = 0;
40 }
41
42 if(pUsb) // register in USB subsystem
43 pUsb->RegisterDeviceClass(this); //set devConfig[] entry
44}
45
48 USB_DEVICE_DESCRIPTOR * udd = reinterpret_cast<USB_DEVICE_DESCRIPTOR*>(buf);
50 UsbDevice *p = NULL;
52 uint16_t PID, VID;
53 uint8_t num_of_conf; // Number of configurations
54
55 // get memory address of USB device address pool
56 AddressPool &addrPool = pUsb->GetAddressPool();
57#ifdef EXTRADEBUG
58 Notify(PSTR("\r\nXBOXONE Init"), 0x80);
59#endif
60 // check if address has already been assigned to an instance
61 if(bAddress) {
62#ifdef DEBUG_USB_HOST
63 Notify(PSTR("\r\nAddress in use"), 0x80);
64#endif
66 }
67
68 // Get pointer to pseudo device with address 0 assigned
69 p = addrPool.GetUsbDevicePtr(0);
70
71 if(!p) {
72#ifdef DEBUG_USB_HOST
73 Notify(PSTR("\r\nAddress not found"), 0x80);
74#endif
76 }
77
78 if(!p->epinfo) {
79#ifdef DEBUG_USB_HOST
80 Notify(PSTR("\r\nepinfo is null"), 0x80);
81#endif
83 }
84
85 // Save old pointer to EP_RECORD of address 0
86 oldep_ptr = p->epinfo;
87
88 // Temporary assign new pointer to epInfo to p->epinfo in order to avoid toggle inconsistence
89 p->epinfo = epInfo;
90
91 p->lowspeed = lowspeed;
92
93 // Get device descriptor
94 rcode = pUsb->getDevDescr(0, 0, sizeof (USB_DEVICE_DESCRIPTOR), (uint8_t*)buf); // Get device descriptor - addr, ep, nbytes, data
95 // Restore p->epinfo
96 p->epinfo = oldep_ptr;
97
98 if(rcode)
99 goto FailGetDevDescr;
100
101 VID = udd->idVendor;
102 PID = udd->idProduct;
103
104 if(!VIDPIDOK(VID, PID)) // Check VID
106
107 // Allocate new address according to device class
108 bAddress = addrPool.AllocAddress(parent, false, port);
109
110 if(!bAddress)
112
113 // Extract Max Packet Size from device descriptor
114 epInfo[0].maxPktSize = udd->bMaxPacketSize0;
115
116 // Assign new address to the device
117 rcode = pUsb->setAddr(0, 0, bAddress);
118 if(rcode) {
119 p->lowspeed = false;
120 addrPool.FreeAddress(bAddress);
121 bAddress = 0;
122#ifdef DEBUG_USB_HOST
123 Notify(PSTR("\r\nsetAddr: "), 0x80);
125#endif
126 return rcode;
127 }
128#ifdef EXTRADEBUG
129 Notify(PSTR("\r\nAddr: "), 0x80);
131#endif
132 //delay(300); // Spec says you should wait at least 200ms
133
134 p->lowspeed = false;
135
136 //get pointer to assigned address record
137 p = addrPool.GetUsbDevicePtr(bAddress);
138 if(!p)
140
141 p->lowspeed = lowspeed;
142
143 // Assign epInfo to epinfo pointer - only EP0 is known
145 if(rcode)
147
148 num_of_conf = udd->bNumConfigurations; // Number of configurations
149
150 USBTRACE2("NC:", num_of_conf);
151
152 // Check if attached device is a Xbox One controller and fill endpoint data structure
153 for(uint8_t i = 0; i < num_of_conf; i++) {
154 ConfigDescParser<0, 0, 0, 0> confDescrParser(this); // Allow all devices, as we have already verified that it is a Xbox One controller from the VID and PID
156 if(rcode) // Check error code
157 goto FailGetConfDescr;
158 if(bNumEP >= XBOX_ONE_MAX_ENDPOINTS) // All endpoints extracted
159 break;
160 }
161
164
166 if(rcode)
168
169 delay(200); // Give time for address change
170
172 if(rcode)
173 goto FailSetConfDescr;
174
175#ifdef DEBUG_USB_HOST
176 Notify(PSTR("\r\nXbox One Controller Connected\r\n"), 0x80);
177#endif
178
179 delay(200); // let things settle
180
181 // Initialize the controller for input
182 cmdCounter = 0; // Reset the counter used when sending out the commands
183 uint8_t writeBuf[5];
184 writeBuf[0] = 0x05;
185 writeBuf[1] = 0x20;
186 // Byte 2 is set in "XboxCommand"
187 writeBuf[3] = 0x01;
188 writeBuf[4] = 0x00;
189 rcode = XboxCommand(writeBuf, 5);
190 if (rcode)
191 goto Fail;
192
193 onInit();
194 XboxOneConnected = true;
195 bPollEnable = true;
196 return 0; // Successful configuration
197
198 /* Diagnostic messages */
200#ifdef DEBUG_USB_HOST
202 goto Fail;
203#endif
204
206#ifdef DEBUG_USB_HOST
208 goto Fail;
209#endif
210
212#ifdef DEBUG_USB_HOST
214 goto Fail;
215#endif
216
218#ifdef DEBUG_USB_HOST
220#endif
221 goto Fail;
222
224#ifdef DEBUG_USB_HOST
225 NotifyFailUnknownDevice(VID, PID);
226#endif
228
229Fail:
230#ifdef DEBUG_USB_HOST
231 Notify(PSTR("\r\nXbox One Init Failed, error code: "), 0x80);
233#endif
234 Release();
235 return rcode;
236}
237
238/* Extracts endpoint information from config descriptor */
240 uint8_t iface __attribute__((unused)),
241 uint8_t alt __attribute__((unused)),
242 uint8_t proto __attribute__((unused)),
244{
245
246 bConfNum = conf;
247 uint8_t index;
248
249 if((pep->bmAttributes & bmUSB_TRANSFER_TYPE) == USB_TRANSFER_TYPE_INTERRUPT) { // Interrupt endpoint
250 index = (pep->bEndpointAddress & 0x80) == 0x80 ? XBOX_ONE_INPUT_PIPE : XBOX_ONE_OUTPUT_PIPE; // Set the endpoint index
251 } else
252 return;
253
254 // Fill the rest of endpoint data structure
255 epInfo[index].epAddr = (pep->bEndpointAddress & 0x0F);
256 epInfo[index].maxPktSize = (uint8_t)pep->wMaxPacketSize;
257#ifdef EXTRADEBUG
259#endif
260 if(pollInterval < pep->bInterval) // Set the polling interval as the largest polling interval obtained from endpoints
261 pollInterval = pep->bInterval;
262 bNumEP++;
263}
264
266 __attribute__((unused)))
267{
268#ifdef EXTRADEBUG
269 Notify(PSTR("\r\nEndpoint descriptor:"), 0x80);
270 Notify(PSTR("\r\nLength:\t\t"), 0x80);
271 D_PrintHex<uint8_t > (ep_ptr->bLength, 0x80);
272 Notify(PSTR("\r\nType:\t\t"), 0x80);
273 D_PrintHex<uint8_t > (ep_ptr->bDescriptorType, 0x80);
274 Notify(PSTR("\r\nAddress:\t"), 0x80);
275 D_PrintHex<uint8_t > (ep_ptr->bEndpointAddress, 0x80);
276 Notify(PSTR("\r\nAttributes:\t"), 0x80);
277 D_PrintHex<uint8_t > (ep_ptr->bmAttributes, 0x80);
278 Notify(PSTR("\r\nMaxPktSize:\t"), 0x80);
279 D_PrintHex<uint16_t > (ep_ptr->wMaxPacketSize, 0x80);
280 Notify(PSTR("\r\nPoll Intrv:\t"), 0x80);
281 D_PrintHex<uint8_t > (ep_ptr->bInterval, 0x80);
282#endif
283}
284
285/* Performs a cleanup after failed Init() attempt */
287 XboxOneConnected = false;
289 bAddress = 0; // Clear device address
290 bNumEP = 1; // Must have to be reset to 1
291 qNextPollTime = 0; // Reset next poll time
292 pollInterval = 0;
293 bPollEnable = false;
294#ifdef DEBUG_USB_HOST
295 Notify(PSTR("\r\nXbox One Controller Disconnected\r\n"), 0x80);
296#endif
297 return 0;
298}
299
301 uint8_t rcode = 0;
302
303 if(!bPollEnable)
304 return 0;
305
306 if((int32_t)((uint32_t)millis() - qNextPollTime) >= 0L) { // Do not poll if shorter than polling interval
307 qNextPollTime = (uint32_t)millis() + pollInterval; // Set new poll time
308 uint16_t length = (uint16_t)epInfo[ XBOX_ONE_INPUT_PIPE ].maxPktSize; // Read the maximum packet size from the endpoint
309 uint8_t rcode = pUsb->inTransfer(bAddress, epInfo[ XBOX_ONE_INPUT_PIPE ].epAddr, &length, readBuf, pollInterval);
310 if(!rcode) {
311 readReport();
312#ifdef PRINTREPORT // Uncomment "#define PRINTREPORT" to print the report send by the Xbox ONE Controller
313 for(uint8_t i = 0; i < length; i++) {
314 D_PrintHex<uint8_t > (readBuf[i], 0x80);
315 Notify(PSTR(" "), 0x80);
316 }
317 Notify(PSTR("\r\n"), 0x80);
318#endif
319 }
320#ifdef DEBUG_USB_HOST
321 else if(rcode != hrNAK) { // Not a matter of no update to send
322 Notify(PSTR("\r\nXbox One Poll Failed, error code: "), 0x80);
324 }
325#endif
326 }
327 return rcode;
328}
329
330void XBOXONE::readReport() {
331 if(readBuf[0] == 0x07) {
332 // The XBOX button has a separate message
333 if(readBuf[4] == 1)
334 ButtonState |= pgm_read_word(&XBOX_BUTTONS[ButtonIndex(XBOX)]);
335 else
336 ButtonState &= ~pgm_read_word(&XBOX_BUTTONS[ButtonIndex(XBOX)]);
337
338 if(ButtonState != OldButtonState) {
339 ButtonClickState = ButtonState & ~OldButtonState; // Update click state variable
340 OldButtonState = ButtonState;
341 }
342 }
343 if(readBuf[0] != 0x20) { // Check if it's the correct report, otherwise return - the controller also sends different status reports
344#ifdef EXTRADEBUG
345 Notify(PSTR("\r\nXbox Poll: "), 0x80);
346 D_PrintHex<uint8_t > (readBuf[0], 0x80); // 0x03 is a heart beat report!
347#endif
348 return;
349 }
350
351 uint16_t xbox = ButtonState & pgm_read_word(&XBOX_BUTTONS[ButtonIndex(XBOX)]); // Since the XBOX button is separate, save it and add it back in
352 // xbox button from before, dpad, abxy, start/back, sync, stick click, shoulder buttons
353 ButtonState = xbox | (((uint16_t)readBuf[5] & 0xF) << 8) | (readBuf[4] & 0xF0) | (((uint16_t)readBuf[4] & 0x0C) << 10) | ((readBuf[4] & 0x01) << 3) | (((uint16_t)readBuf[5] & 0xC0) << 8) | ((readBuf[5] & 0x30) >> 4);
354
355 triggerValue[0] = (uint16_t)(((uint16_t)readBuf[7] << 8) | readBuf[6]);
356 triggerValue[1] = (uint16_t)(((uint16_t)readBuf[9] << 8) | readBuf[8]);
357
358 hatValue[LeftHatX] = (int16_t)(((uint16_t)readBuf[11] << 8) | readBuf[10]);
359 hatValue[LeftHatY] = (int16_t)(((uint16_t)readBuf[13] << 8) | readBuf[12]);
360 hatValue[RightHatX] = (int16_t)(((uint16_t)readBuf[15] << 8) | readBuf[14]);
361 hatValue[RightHatY] = (int16_t)(((uint16_t)readBuf[17] << 8) | readBuf[16]);
362
363 // Read and store share button separately
364 const bool newShare = (readBuf[22] & 0x01) ? 1 : 0;
365 shareClicked = ((sharePressed != newShare) && newShare) ? 1 : 0;
366 sharePressed = newShare;
367
368 //Notify(PSTR("\r\nButtonState"), 0x80);
369 //PrintHex<uint16_t>(ButtonState, 0x80);
370
371 if(ButtonState != OldButtonState) {
372 ButtonClickState = ButtonState & ~OldButtonState; // Update click state variable
373 OldButtonState = ButtonState;
374 }
375
376 // Handle click detection for triggers
377 if(triggerValue[0] != 0 && triggerValueOld[0] == 0)
378 L2Clicked = true;
379 triggerValueOld[0] = triggerValue[0];
380 if(triggerValue[1] != 0 && triggerValueOld[1] == 0)
381 R2Clicked = true;
382 triggerValueOld[1] = triggerValue[1];
383}
384
386 // special handling for 'SHARE' button due to index collision with 'BACK',
387 // since the 'SHARE' value originally came from the PS4 controller and
388 // the 'SHARE' button was added to Xbox later with the Series S/X controllers
389 if (b == SHARE) return sharePressed;
390
391 const int8_t index = getButtonIndexXbox(b); if (index < 0) return 0;
392 if(index == ButtonIndex(L2)) // These are analog buttons
393 return triggerValue[0];
394 else if(index == ButtonIndex(R2))
395 return triggerValue[1];
396 return (bool)(ButtonState & ((uint16_t)pgm_read_word(&XBOX_BUTTONS[index])));
397}
398
400 // special handling for 'SHARE' button, ibid the above
401 if (b == SHARE) {
402 if (shareClicked) {
403 shareClicked = false;
404 return true;
405 }
406 return false;
407 }
408
409 const int8_t index = getButtonIndexXbox(b); if (index < 0) return 0;
410 if(index == ButtonIndex(L2)) {
411 if(L2Clicked) {
412 L2Clicked = false;
413 return true;
414 }
415 return false;
416 } else if(index == ButtonIndex(R2)) {
417 if(R2Clicked) {
418 R2Clicked = false;
419 return true;
420 }
421 return false;
422 }
424 bool click = (ButtonClickState & button);
425 ButtonClickState &= ~button; // Clear "click" event
426 return click;
427}
428
430 return hatValue[a];
431}
432
433/* Xbox Controller commands */
434uint8_t XBOXONE::XboxCommand(uint8_t* data, uint16_t nbytes) {
435 data[2] = cmdCounter++; // Increment the output command counter
437#ifdef DEBUG_USB_HOST
438 if(rcode) {
439 Notify(PSTR("\r\nXboxCommand failed. Return: "), 0x80);
441 }
442#endif
443 return rcode;
444}
445
446// The Xbox One packets are described at: https://github.com/quantus/xbox-one-controller-protocol
447void XBOXONE::onInit() {
448 // A short buzz to show the controller is active
449 uint8_t writeBuf[13];
450
451 // Activate rumble
452 writeBuf[0] = 0x09;
453 writeBuf[1] = 0x00;
454 // Byte 2 is set in "XboxCommand"
455
456 // Single rumble effect
457 writeBuf[3] = 0x09; // Substructure (what substructure rest of this packet has)
458 writeBuf[4] = 0x00; // Mode
459 writeBuf[5] = 0x0F; // Rumble mask (what motors are activated) (0000 lT rT L R)
460 writeBuf[6] = 0x04; // lT force
461 writeBuf[7] = 0x04; // rT force
462 writeBuf[8] = 0x20; // L force
463 writeBuf[9] = 0x20; // R force
464 writeBuf[10] = 0x80; // Length of pulse
465 writeBuf[11] = 0x00; // Off period
466 writeBuf[12] = 0x00; // Repeat count
467 XboxCommand(writeBuf, 13);
468
469 if(pFuncOnInit)
470 pFuncOnInit(); // Call the user function
471}
472
474 uint8_t writeBuf[13];
475
476 // Activate rumble
477 writeBuf[0] = 0x09;
478 writeBuf[1] = 0x00;
479 // Byte 2 is set in "XboxCommand"
480
481 // Continuous rumble effect
482 writeBuf[3] = 0x09; // Substructure (what substructure rest of this packet has)
483 writeBuf[4] = 0x00; // Mode
484 writeBuf[5] = 0x0F; // Rumble mask (what motors are activated) (0000 lT rT L R)
485 writeBuf[6] = 0x00; // lT force
486 writeBuf[7] = 0x00; // rT force
487 writeBuf[8] = 0x00; // L force
488 writeBuf[9] = 0x00; // R force
489 writeBuf[10] = 0x00; // On period
490 writeBuf[11] = 0x00; // Off period
491 writeBuf[12] = 0x00; // Repeat count
492 XboxCommand(writeBuf, 13);
493}
494
496 uint8_t writeBuf[13];
497
498 // Activate rumble
499 writeBuf[0] = 0x09;
500 writeBuf[1] = 0x00;
501 // Byte 2 is set in "XboxCommand"
502
503 // Continuous rumble effect
504 writeBuf[3] = 0x09; // Substructure (what substructure rest of this packet has)
505 writeBuf[4] = 0x00; // Mode
506 writeBuf[5] = 0x0F; // Rumble mask (what motors are activated) (0000 lT rT L R)
507 writeBuf[6] = leftTrigger; // lT force
508 writeBuf[7] = rightTrigger; // rT force
509 writeBuf[8] = leftMotor; // L force
510 writeBuf[9] = rightMotor; // R force
511 writeBuf[10] = 0xFF; // On period
512 writeBuf[11] = 0x00; // Off period
513 writeBuf[12] = 0xFF; // Repeat count
514 XboxCommand(writeBuf, 13);
515}
#define USB_ERROR_OUT_OF_ADDRESS_SPACE_IN_POOL
Definition UsbCore.h:103
#define USB_ERROR_CLASS_INSTANCE_ALREADY_IN_USE
Definition UsbCore.h:108
#define USB_DEV_CONFIG_ERROR_DEVICE_NOT_SUPPORTED
Definition UsbCore.h:100
#define USB_ERROR_EPINFO_IS_NULL
Definition UsbCore.h:106
#define USB_ERROR_ADDRESS_NOT_FOUND_IN_POOL
Definition UsbCore.h:105
#define XBOX_ONE_OUTPUT_PIPE
Definition XBOXONE.h:34
#define XBOX_ONE_MAX_ENDPOINTS
Definition XBOXONE.h:37
#define XBOX_ONE_CONTROL_PIPE
Definition XBOXONE.h:33
#define XBOX_ONE_INPUT_PIPE
Definition XBOXONE.h:35
#define USB_NAK_MAX_POWER
Definition address.h:34
#define USB_NAK_NOWAIT
Definition address.h:36
virtual void FreeAddress(uint8_t addr)=0
virtual UsbDevice * GetUsbDevicePtr(uint8_t addr)=0
virtual uint8_t AllocAddress(uint8_t parent, bool is_hub=false, uint8_t port=0)=0
Definition UsbCore.h:220
AddressPool & GetAddressPool()
Definition UsbCore.h:236
uint8_t getDevDescr(uint8_t addr, uint8_t ep, uint16_t nbytes, uint8_t *dataptr)
defined(USB_METHODS_INLINE)
Definition Usb.cpp:801
uint8_t setConf(uint8_t addr, uint8_t ep, uint8_t conf_value)
Definition Usb.cpp:850
uint8_t setAddr(uint8_t oldaddr, uint8_t ep, uint8_t newaddr)
Definition Usb.cpp:841
uint8_t RegisterDeviceClass(USBDeviceConfig *pdev)
Definition UsbCore.h:240
uint8_t getConfDescr(uint8_t addr, uint8_t ep, uint16_t nbytes, uint8_t conf, uint8_t *dataptr)
Definition Usb.cpp:806
uint8_t setEpInfoEntry(uint8_t addr, uint8_t epcount, EpInfo *eprecord_ptr)
Definition Usb.cpp:64
uint8_t inTransfer(uint8_t addr, uint8_t ep, uint16_t *nbytesptr, uint8_t *data, uint8_t bInterval=0)
Definition Usb.cpp:209
uint8_t outTransfer(uint8_t addr, uint8_t ep, uint16_t nbytes, uint8_t *data)
Definition Usb.cpp:303
EpInfo epInfo[XBOX_ONE_MAX_ENDPOINTS]
Definition XBOXONE.h:185
void PrintEndpointDescriptor(const USB_ENDPOINT_DESCRIPTOR *ep_ptr)
Definition XBOXONE.cpp:265
XBOXONE(USB *pUsb)
Definition XBOXONE.cpp:27
void EndpointXtract(uint8_t conf, uint8_t iface, uint8_t alt, uint8_t proto, const USB_ENDPOINT_DESCRIPTOR *ep)
Definition XBOXONE.cpp:239
uint32_t qNextPollTime
Definition XBOXONE.h:192
void setRumbleOff()
Definition XBOXONE.cpp:473
virtual bool VIDPIDOK(uint16_t vid, uint16_t pid)
Definition XBOXONE.h:125
USB * pUsb
Definition XBOXONE.h:181
void setRumbleOn(uint8_t leftTrigger, uint8_t rightTrigger, uint8_t leftMotor, uint8_t rightMotor)
Definition XBOXONE.cpp:495
uint8_t bAddress
Definition XBOXONE.h:183
uint8_t bNumEP
Definition XBOXONE.h:190
bool getButtonClick(ButtonEnum b)
Definition XBOXONE.cpp:399
virtual uint8_t Release()
Definition XBOXONE.cpp:286
bool XboxOneConnected
Definition XBOXONE.h:177
virtual uint8_t Poll()
Definition XBOXONE.cpp:300
uint8_t bConfNum
Definition XBOXONE.h:188
uint16_t getButtonPress(ButtonEnum b)
Definition XBOXONE.cpp:385
virtual uint8_t Init(uint8_t parent, uint8_t port, bool lowspeed)
Definition XBOXONE.cpp:46
int16_t getAnalogHat(AnalogHatEnum a)
Definition XBOXONE.cpp:429
constexpr int8_t ButtonIndex(ButtonEnum key)
AnalogHatEnum
@ LeftHatX
@ RightHatY
@ RightHatX
@ LeftHatY
ButtonEnum
@ SHARE
@ XBOX
#define USBTRACE2(s, r)
Definition macros.h:84
#define hrNAK
Definition max3421e.h:218
#define NotifyFailSetConfDescr(...)
Definition message.h:60
#define NotifyFailUnknownDevice(...)
Definition message.h:61
#define NotifyFail(...)
Definition message.h:62
#define Notify(...)
Definition message.h:51
#define NotifyFailGetConfDescr(...)
Definition message.h:56
#define NotifyFailSetDevTblEntry(...)
Definition message.h:55
#define NotifyFailGetDevDescr(...)
Definition message.h:54
uint8_t bmNakPower
Definition address.h:49
uint8_t bmRcvToggle
Definition address.h:48
uint8_t epAddr
Definition address.h:40
uint8_t maxPktSize
Definition address.h:41
uint8_t bmSndToggle
Definition address.h:47
#define bmUSB_TRANSFER_TYPE
Definition usb_ch9.h:94
#define USB_TRANSFER_TYPE_INTERRUPT
Definition usb_ch9.h:93
#define PSTR(str)
#define pgm_read_word(addr)
const uint16_t XBOX_BUTTONS[]
Definition xboxEnums.h:41
int8_t getButtonIndexXbox(ButtonEnum b)
Definition xboxEnums.h:65