Extract the endpoints from the Xbox One controller instead of hardcoding them

Also cleaned up the code a bit

Fixes #305
This commit is contained in:
Kristian Sloth Lauszus 2017-11-09 18:56:40 +01:00
parent 1308773eb8
commit 7078ea9f32
2 changed files with 147 additions and 69 deletions

View file

@ -27,8 +27,11 @@
XBOXONE::XBOXONE(USB *p) :
pUsb(p), // pointer to USB class instance - mandatory
bAddress(0), // device address - mandatory
bNumEP(1), // If config descriptor needs to be parsed
qNextPollTime(0), // Reset NextPollTime
pollInterval(0),
bPollEnable(false) { // don't start polling before dongle is connected
for(uint8_t i = 0; i < XBOX_MAX_ENDPOINTS; i++) {
for(uint8_t i = 0; i < XBOX_ONE_MAX_ENDPOINTS; i++) {
epInfo[i].epAddr = 0;
epInfo[i].maxPktSize = (i) ? 0 : 8;
epInfo[i].bmSndToggle = 0;
@ -46,8 +49,8 @@ uint8_t XBOXONE::Init(uint8_t parent, uint8_t port, bool lowspeed) {
uint8_t rcode;
UsbDevice *p = NULL;
EpInfo *oldep_ptr = NULL;
uint16_t PID;
uint16_t VID;
uint16_t PID, VID;
uint8_t num_of_conf; // Number of configurations
// get memory address of USB device address pool
AddressPool &addrPool = pUsb->GetAddressPool();
@ -142,31 +145,30 @@ uint8_t XBOXONE::Init(uint8_t parent, uint8_t port, bool lowspeed) {
if(rcode)
goto FailSetDevTblEntry;
/* The application will work in reduced host mode, so we can save program and data
memory space. After verifying the VID we will use known values for the
configuration values for device, interface, endpoints and HID for the XBOXONE Controllers */
num_of_conf = udd->bNumConfigurations; // Number of configurations
/* Initialize data structures for endpoints of device */
epInfo[ XBOX_OUTPUT_PIPE ].epAddr = 0x01; // XBOX one output endpoint
epInfo[ XBOX_OUTPUT_PIPE ].epAttribs = USB_TRANSFER_TYPE_INTERRUPT;
epInfo[ XBOX_OUTPUT_PIPE ].bmNakPower = USB_NAK_NOWAIT; // Only poll once for interrupt endpoints
epInfo[ XBOX_OUTPUT_PIPE ].maxPktSize = EP_MAXPKTSIZE;
epInfo[ XBOX_OUTPUT_PIPE ].bmSndToggle = 0;
epInfo[ XBOX_OUTPUT_PIPE ].bmRcvToggle = 0;
epInfo[ XBOX_INPUT_PIPE ].epAddr = 0x01; // XBOX one input endpoint
epInfo[ XBOX_INPUT_PIPE ].epAttribs = USB_TRANSFER_TYPE_INTERRUPT;
epInfo[ XBOX_INPUT_PIPE ].bmNakPower = USB_NAK_NOWAIT; // Only poll once for interrupt endpoints
epInfo[ XBOX_INPUT_PIPE ].maxPktSize = EP_MAXPKTSIZE;
epInfo[ XBOX_INPUT_PIPE ].bmSndToggle = 0;
epInfo[ XBOX_INPUT_PIPE ].bmRcvToggle = 0;
USBTRACE2("NC:", num_of_conf);
rcode = pUsb->setEpInfoEntry(bAddress, 3, epInfo);
// Check if attached device is a Xbox One controller and fill endpoint data structure
for(uint8_t i = 0; i < num_of_conf; i++) {
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
rcode = pUsb->getConfDescr(bAddress, 0, i, &confDescrParser);
if(rcode) // Check error code
goto FailGetConfDescr;
if(bNumEP >= XBOX_ONE_MAX_ENDPOINTS) // All endpoints extracted
break;
}
if(bNumEP < XBOX_ONE_MAX_ENDPOINTS)
goto FailUnknownDevice;
rcode = pUsb->setEpInfoEntry(bAddress, bNumEP, epInfo);
if(rcode)
goto FailSetDevTblEntry;
delay(200); // Give time for address change
rcode = pUsb->setConf(bAddress, epInfo[ XBOX_CONTROL_PIPE ].epAddr, 1);
rcode = pUsb->setConf(bAddress, epInfo[ XBOX_ONE_CONTROL_PIPE ].epAddr, bConfNum);
if(rcode)
goto FailSetConfDescr;
@ -176,7 +178,8 @@ uint8_t XBOXONE::Init(uint8_t parent, uint8_t port, bool lowspeed) {
delay(200); // let things settle
// initialize the controller for input
// Initialize the controller for input
uint8_t writeBuf[5];
writeBuf[0] = 0x05;
writeBuf[1] = 0x20;
writeBuf[2] = 0x00;
@ -204,6 +207,12 @@ FailSetDevTblEntry:
goto Fail;
#endif
FailGetConfDescr:
#ifdef DEBUG_USB_HOST
NotifyFailGetConfDescr();
goto Fail;
#endif
FailSetConfDescr:
#ifdef DEBUG_USB_HOST
NotifyFailSetConfDescr();
@ -225,11 +234,53 @@ Fail:
return rcode;
}
/* Extracts endpoint information from config descriptor */
void XBOXONE::EndpointXtract(uint8_t conf, uint8_t iface, uint8_t alt, uint8_t proto, const USB_ENDPOINT_DESCRIPTOR *pep) {
bConfNum = conf;
uint8_t index;
if((pep->bmAttributes & bmUSB_TRANSFER_TYPE) == USB_TRANSFER_TYPE_INTERRUPT) { // Interrupt endpoint
index = (pep->bEndpointAddress & 0x80) == 0x80 ? XBOX_ONE_INPUT_PIPE : XBOX_ONE_OUTPUT_PIPE; // Set the endpoint index
} else
return;
// Fill the rest of endpoint data structure
epInfo[index].epAddr = (pep->bEndpointAddress & 0x0F);
epInfo[index].maxPktSize = (uint8_t)pep->wMaxPacketSize;
#ifdef EXTRADEBUG
PrintEndpointDescriptor(pep);
#endif
if(pollInterval < pep->bInterval) // Set the polling interval as the largest polling interval obtained from endpoints
pollInterval = pep->bInterval;
bNumEP++;
}
void XBOXONE::PrintEndpointDescriptor(const USB_ENDPOINT_DESCRIPTOR* ep_ptr) {
#ifdef EXTRADEBUG
Notify(PSTR("\r\nEndpoint descriptor:"), 0x80);
Notify(PSTR("\r\nLength:\t\t"), 0x80);
D_PrintHex<uint8_t > (ep_ptr->bLength, 0x80);
Notify(PSTR("\r\nType:\t\t"), 0x80);
D_PrintHex<uint8_t > (ep_ptr->bDescriptorType, 0x80);
Notify(PSTR("\r\nAddress:\t"), 0x80);
D_PrintHex<uint8_t > (ep_ptr->bEndpointAddress, 0x80);
Notify(PSTR("\r\nAttributes:\t"), 0x80);
D_PrintHex<uint8_t > (ep_ptr->bmAttributes, 0x80);
Notify(PSTR("\r\nMaxPktSize:\t"), 0x80);
D_PrintHex<uint16_t > (ep_ptr->wMaxPacketSize, 0x80);
Notify(PSTR("\r\nPoll Intrv:\t"), 0x80);
D_PrintHex<uint8_t > (ep_ptr->bInterval, 0x80);
#endif
}
/* Performs a cleanup after failed Init() attempt */
uint8_t XBOXONE::Release() {
XboxOneConnected = false;
pUsb->GetAddressPool().FreeAddress(bAddress);
bAddress = 0;
bAddress = 0; // Clear device address
bNumEP = 1; // Must have to be reset to 1
qNextPollTime = 0; // Reset next poll time
pollInterval = 0;
bPollEnable = false;
#ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nXbox One Controller Disconnected\r\n"), 0x80);
@ -238,28 +289,36 @@ uint8_t XBOXONE::Release() {
}
uint8_t XBOXONE::Poll() {
uint8_t rcode = 0;
if(!bPollEnable)
return 0;
uint16_t BUFFER_SIZE = EP_MAXPKTSIZE;
uint8_t rcode = pUsb->inTransfer(bAddress, epInfo[ XBOX_INPUT_PIPE ].epAddr, &BUFFER_SIZE, readBuf);
if (!rcode) {
if((int32_t)((uint32_t)millis() - qNextPollTime) >= 0L) { // Do not poll if shorter than polling interval
qNextPollTime = (uint32_t)millis() + pollInterval; // Set new poll time
uint16_t length = (uint16_t)epInfo[ XBOX_ONE_INPUT_PIPE ].maxPktSize; // Read the maximum packet size from the endpoint
uint8_t rcode = pUsb->inTransfer(bAddress, epInfo[ XBOX_ONE_INPUT_PIPE ].epAddr, &length, readBuf, pollInterval);
if(!rcode) {
readReport();
#ifdef PRINTREPORT
printReport(); // Uncomment "#define PRINTREPORT" to print the report send by the Xbox ONE Controller
#ifdef PRINTREPORT // Uncomment "#define PRINTREPORT" to print the report send by the Xbox ONE Controller
for(uint8_t i = 0; i < length; i++) {
D_PrintHex<uint8_t > (readBuf[i], 0x80);
Notify(PSTR(" "), 0x80);
}
Notify(PSTR("\r\n"), 0x80);
#endif
}
#ifdef DEBUG_USB_HOST
else if (rcode != 0x04) { // not a matter of no update to send
else if(rcode != hrNAK) { // Not a matter of no update to send
Notify(PSTR("\r\nXbox One Poll Failed, error code: "), 0x80);
NotifyFail(rcode);
}
#endif
}
return rcode;
}
void XBOXONE::readReport() {
if(readBuf == NULL)
return;
if(readBuf[0] == 0x07) {
// The XBOX button has a separate message
if(readBuf[4] == 1)
@ -309,18 +368,6 @@ void XBOXONE::readReport() {
triggerValueOld[1] = triggerValue[1];
}
void XBOXONE::printReport() { //Uncomment "#define PRINTREPORT" to print the report send by the Xbox ONE Controller
#ifdef PRINTREPORT
if(readBuf == NULL)
return;
for(uint8_t i = 0; i < XBOX_REPORT_BUFFER_SIZE; i++) {
D_PrintHex<uint8_t > (readBuf[i], 0x80);
Notify(PSTR(" "), 0x80);
}
Notify(PSTR("\r\n"), 0x80);
#endif
}
uint16_t XBOXONE::getButtonPress(ButtonEnum b) {
if(b == L2) // These are analog buttons
return triggerValue[0];
@ -345,7 +392,7 @@ bool XBOXONE::getButtonClick(ButtonEnum b) {
}
uint16_t button = pgm_read_word(&XBOX_BUTTONS[(uint8_t)b]);
bool click = (ButtonClickState & button);
ButtonClickState &= ~button; // clear "click" event
ButtonClickState &= ~button; // Clear "click" event
return click;
}
@ -355,7 +402,7 @@ int16_t XBOXONE::getAnalogHat(AnalogHatEnum a) {
/* Xbox Controller commands */
uint8_t XBOXONE::XboxCommand(uint8_t* data, uint16_t nbytes) {
uint8_t rcode = pUsb->outTransfer(bAddress, epInfo[ XBOX_OUTPUT_PIPE ].epAddr, nbytes, data);
uint8_t rcode = pUsb->outTransfer(bAddress, epInfo[ XBOX_ONE_OUTPUT_PIPE ].epAddr, nbytes, data);
#ifdef DEBUG_USB_HOST
Notify(PSTR("\r\nXboxCommand, Return: "), 0x80);
D_PrintHex<uint8_t > (rcode, 0x80);
@ -364,7 +411,8 @@ uint8_t XBOXONE::XboxCommand(uint8_t* data, uint16_t nbytes) {
}
void XBOXONE::onInit() {
// a short buzz to show the controller is active
// A short buzz to show the controller is active
uint8_t writeBuf[11];
writeBuf[0] = 0x09;
writeBuf[1] = 0x08;
writeBuf[2] = 0x00;

View file

@ -26,15 +26,17 @@
#include "Usb.h"
#include "xboxEnums.h"
/* Data Xbox ONE taken from descriptors */
#define EP_MAXPKTSIZE 32 // max size for data via USB
/* Xbox One data taken from descriptors */
#define XBOX_ONE_EP_MAXPKTSIZE 64 // Max size for data via USB
/* Names we give to the 3 XboxONE pipes */
#define XBOX_CONTROL_PIPE 0
#define XBOX_OUTPUT_PIPE 1
#define XBOX_INPUT_PIPE 2
#define XBOX_ONE_CONTROL_PIPE 0
#define XBOX_ONE_OUTPUT_PIPE 1
#define XBOX_ONE_INPUT_PIPE 2
// PID and VID of the different devices - see: https://github.com/torvalds/linux/blob/master/drivers/input/joystick/xpad.c
#define XBOX_ONE_MAX_ENDPOINTS 3
// PID and VID of the different versions of the controller - see: https://github.com/torvalds/linux/blob/master/drivers/input/joystick/xpad.c
// Official controllers
#define XBOX_VID1 0x045E // Microsoft Corporation
@ -59,12 +61,8 @@
#define XBOX_ONE_PID11 0x542A // Xbox ONE spectra
#define XBOX_ONE_PID12 0x543A // PowerA Xbox One wired controller
#define XBOX_REPORT_BUFFER_SIZE 14 // Size of the input report buffer
#define XBOX_MAX_ENDPOINTS 3
/** This class implements support for a Xbox ONE controller connected via USB. */
class XBOXONE : public USBDeviceConfig {
class XBOXONE : public USBDeviceConfig, public UsbConfigXtracter {
public:
/**
* Constructor for the XBOXONE class.
@ -108,6 +106,14 @@ public:
return bPollEnable;
};
/**
* Read the poll interval taken from the endpoint descriptors.
* @return The poll interval in ms.
*/
uint8_t readPollInterval() {
return pollInterval;
};
/**
* Used by the USB core to check what this driver support.
* @param vid The device's VID.
@ -161,7 +167,32 @@ protected:
/** Device address. */
uint8_t bAddress;
/** Endpoint info structure. */
EpInfo epInfo[XBOX_MAX_ENDPOINTS];
EpInfo epInfo[XBOX_ONE_MAX_ENDPOINTS];
/** Configuration number. */
uint8_t bConfNum;
/** Total number of endpoints in the configuration. */
uint8_t bNumEP;
/** Next poll time based on poll interval taken from the USB descriptor. */
uint32_t qNextPollTime;
/** @name UsbConfigXtracter implementation */
/**
* UsbConfigXtracter implementation, used to extract endpoint information.
* @param conf Configuration value.
* @param iface Interface number.
* @param alt Alternate setting.
* @param proto Interface Protocol.
* @param ep Endpoint Descriptor.
*/
void EndpointXtract(uint8_t conf, uint8_t iface, uint8_t alt, uint8_t proto, const USB_ENDPOINT_DESCRIPTOR *ep);
/**@}*/
/**
* Used to print the USB Endpoint Descriptor.
* @param ep_ptr Pointer to USB Endpoint Descriptor.
*/
void PrintEndpointDescriptor(const USB_ENDPOINT_DESCRIPTOR* ep_ptr);
private:
/**
@ -171,6 +202,7 @@ private:
void onInit();
void (*pFuncOnInit)(void); // Pointer to function called in onInit()
uint8_t pollInterval;
bool bPollEnable;
/* Variables to store the buttons */
@ -184,11 +216,9 @@ private:
bool L2Clicked; // These buttons are analog, so we use we use these bools to check if they where clicked or not
bool R2Clicked;
uint8_t readBuf[EP_MAXPKTSIZE]; // General purpose buffer for input data
uint8_t writeBuf[12]; // General purpose buffer for output data
uint8_t readBuf[XBOX_ONE_EP_MAXPKTSIZE]; // General purpose buffer for input data
void readReport(); // read incoming data
void printReport(); // print incoming date - Uncomment for debugging
void readReport(); // Used to read the incoming data
/* Private commands */
uint8_t XboxCommand(uint8_t* data, uint16_t nbytes);