felis / USB_Host_Shield_2.0

Revision 2.0 of USB Host Library for Arduino.
https://chome.nerpa.tech
1.79k stars 779 forks source link

HID Joystick #661

Closed ckdo8008 closed 3 years ago

ckdo8008 commented 3 years ago

Env.

Arduino Uno + sparkfun usb host shield + 2axis joystick

HID Desc

Start                                                  
01                          
Device descriptor:          
Descriptor Length:  12      
Descriptor type:    01        
USB version:        0200        
Device class:       00          
Device Subclass:    00        
Device Protocol:    00        
Max.packet size:    08        
Vendor  ID:     068E          
Product ID:     0105          
Revision ID:        0100        
Mfg.string index:   01        
Prod.string index:  02      
Serial number index:    00    
Number of conf.:    01        

Configuration descriptor:   
Total length:       0042        
Num.intf:       02              
Conf.value:     01            
Conf.string:        00          
Attr.:          80              
Max.pwr:        FA              

Interface descriptor:       
Intf.number:        00          
Alt.:           00                
Endpoints:      02            
Intf. Class:        03          
Intf. Subclass:     00        
Intf. Protocol:     00        
Intf.string:        04          
Unknown descriptor:         
Length:     09                
Type:       21                  
Contents:   110121012233000705

Endpoint descriptor:        
Endpoint address:   82        
Attr.:          03              
Max.pkt size:       0040        
Polling interval:   0A        

Endpoint descriptor:        
Endpoint address:   01        
Attr.:          03              
Max.pkt size:       0040        
Polling interval:   0A        

Interface descriptor:       
Intf.number:        01          
Alt.:           00                
Endpoints:      01            
Intf. Class:        03          
Intf. Subclass:     00        
Intf. Protocol:     00        
Intf.string:        03          
Unknown descriptor:         
Length:     09                
Type:       21                  
Contents:   110121012234000705

Endpoint descriptor:        
Endpoint address:   84        
Attr.:          03              
Max.pkt size:       0008        
Polling interval:   0A        

Addr:1(0.0.1)    

On Windows wireshark

request image

response image

apem.h

#ifndef _apem_h_
#define _apem_h_

#include "Usb.h"
#include "usbhid.h"

#define EP_MAXPKTSIZE       0x40

#define APEM_CONTROL_PIPE   0
#define APEM_PIPE_1         1

#define APEM_VID            0x068E
#define APEM_PID            0x0105

#define REPORT_BUFFER_SIZE  0x40

#define MAX_ENDPOINTS       2

enum AnalogHatEnum {
    HatX = 0,
    HatY = 1,
};

class APEM : public USBDeviceConfig {
public:
    APEM(USB *pUsb);

    uint8_t Init(uint8_t parent, uint8_t port, bool lowspeed);
    uint8_t Release();
    uint8_t Poll();
    int16_t getAnalogHat(AnalogHatEnum a);

    virtual uint8_t GetAddress() {
        return bAddress;
    };

    virtual bool isReady() {
        return bPollEnable;
    };  

    virtual bool VIDPIDOK(uint16_t vid, uint16_t pid) {
        return (vid == APEM_VID && pid == APEM_PID);
    };

    void attachOnInit(void (*funcOnInit)(void)) {
        pFuncOnInit = funcOnInit;
    };

    bool APEMConnected;
protected:
    USB *pUsb;
    uint8_t bAddress;
    EpInfo epInfo[MAX_ENDPOINTS];

private:
    void onInit();
    void (*pFuncOnInit)(void);
    bool bPollEnable;

    int16_t hatValue[2];

    uint8_t readBuf[EP_MAXPKTSIZE];
    void readReport();
    void printReport();
};
#endif

apem.cpp

#include "apem.h"

APEM::APEM(USB *p) :
pUsb(p), // pointer to USB class instance - mandatory
bAddress(0), // device address - mandatory
bPollEnable(false) { // don't start polling before dongle is connected
        for(uint8_t i = 0; i < MAX_ENDPOINTS; i++) {
                epInfo[i].epAddr = 0;
                epInfo[i].maxPktSize = (i) ? 0 : 8;
                epInfo[i].bmSndToggle = 0;
                epInfo[i].bmRcvToggle = 0;
                epInfo[i].bmNakPower = (i) ? USB_NAK_NOWAIT : USB_NAK_MAX_POWER;
        }

        if(pUsb) // register in USB subsystem
                pUsb->RegisterDeviceClass(this); //set devConfig[] entry
}

uint8_t APEM::Init(uint8_t parent, uint8_t port, bool lowspeed) {
    uint8_t buf[sizeof (USB_DEVICE_DESCRIPTOR)];
    USB_DEVICE_DESCRIPTOR * udd = reinterpret_cast<USB_DEVICE_DESCRIPTOR*>(buf);
    uint8_t rcode;
    UsbDevice *p = NULL;
    EpInfo *oldep_ptr = NULL;
    uint16_t PID;
    uint16_t VID;

    // USB 장치 주소 풀의 메모리 주소 가져오기
    AddressPool &addrPool = pUsb->GetAddressPool();

    Notify(PSTR("\r\APEM Init"), 0x80);

    // 주소가 이미 인스턴스에 할당되었는지 확인
    if(bAddress) {
#ifdef DEBUG_USB_HOST
        Notify(PSTR("\r\nAddress in use"), 0x80);
#endif
        return USB_ERROR_CLASS_INSTANCE_ALREADY_IN_USE;
    }

    // 주소 0이 할당된 의사 장치에 대한 포인터 가져오기
    p = addrPool.GetUsbDevicePtr(0);

    if(!p) {
#ifdef DEBUG_USB_HOST
        Notify(PSTR("\r\nAddress not found"), 0x80);
#endif
        return USB_ERROR_ADDRESS_NOT_FOUND_IN_POOL;
    }

    if(!p->epinfo) {
#ifdef DEBUG_USB_HOST
        Notify(PSTR("\r\nepinfo is null"), 0x80);
#endif
        return USB_ERROR_EPINFO_IS_NULL;
    }

    // 주소 0의 EP_RECORD에 대한 이전 포인터 저장
    oldep_ptr = p->epinfo;

    // 토글 불일치를 피하기 위해 epInfo에 대한 새 포인터를 p->epinfo에 임시 할당합니다.
    p->epinfo = epInfo;

    p->lowspeed = lowspeed;

    // 장치 설명 가져오기
    rcode = pUsb->getDevDescr(0, 0, sizeof (USB_DEVICE_DESCRIPTOR), (uint8_t*)buf); // Get device descriptor
    // 되돌리기 p->epinfo
    p->epinfo = oldep_ptr;

    if(rcode)
        goto FailGetDevDescr;

    VID = udd->idVendor;
    PID = udd->idProduct;

    if(VID != APEM_VID) // Check VID
        goto FailUnknownDevice;

    if(PID != APEM_PID) // Check PID
        goto FailUnknownDevice;

    // 디바이스 클래스에 따라 새로운 주소 할당
    bAddress = addrPool.AllocAddress(parent, false, port);

    if(!bAddress)
        return USB_ERROR_OUT_OF_ADDRESS_SPACE_IN_POOL;

    // 장치 설명자에서 최대 패킷 크기 추출
    epInfo[0].maxPktSize = udd->bMaxPacketSize0;

    // 장치에 새 주소 할당
    rcode = pUsb->setAddr(0, 0, bAddress);
    if(rcode) {
        p->lowspeed = false;
        addrPool.FreeAddress(bAddress);
        bAddress = 0;
#ifdef DEBUG_USB_HOST
        Notify(PSTR("\r\nsetAddr: "), 0x80);
        D_PrintHex<uint8_t > (rcode, 0x80);
#endif
        return rcode;
    }
#ifdef EXTRADEBUG
    Notify(PSTR("\r\nAddr: "), 0x80);
    D_PrintHex<uint8_t > (bAddress, 0x80);
#endif
    //delay(300); // Spec says you should wait at least 200ms

    p->lowspeed = false;

    // 할당된 주소 레코드에 대한 포인터 가져오기
    p = addrPool.GetUsbDevicePtr(bAddress);
    if(!p)
        return USB_ERROR_ADDRESS_NOT_FOUND_IN_POOL;

    p->lowspeed = lowspeed;

    // epInfo 포인터에 epInfo 할당 - EP0만 알려져 있음
    rcode = pUsb->setEpInfoEntry(bAddress, 1, epInfo);
    if(rcode)
        goto FailSetDevTblEntry;

    epInfo[ APEM_PIPE_1 ].epAddr = 1; 
    epInfo[ APEM_PIPE_1 ].epAttribs = USB_TRANSFER_TYPE_INTERRUPT;
    epInfo[ APEM_PIPE_1 ].bmNakPower = USB_NAK_NOWAIT; // Only poll once for interrupt endpoints
    epInfo[ APEM_PIPE_1 ].maxPktSize = EP_MAXPKTSIZE;
    epInfo[ APEM_PIPE_1 ].bmSndToggle = 0;
    epInfo[ APEM_PIPE_1 ].bmRcvToggle = 0;

    rcode = pUsb->setEpInfoEntry(bAddress, MAX_ENDPOINTS, epInfo);
    if(rcode)
            goto FailSetDevTblEntry;

    delay(200); // Give time for address change

    rcode = pUsb->setConf(bAddress, epInfo[ APEM_CONTROL_PIPE ].epAddr, 1);
    if(rcode)
        goto FailSetConfDescr;

#ifdef DEBUG_USB_HOST
    Notify(PSTR("\r\nAPEM Controller Connected\r\n"), 0x80);
#endif
    onInit();
    APEMConnected = true;
    bPollEnable = true;
    return 0; // Successful configuration

    /* Diagnostic messages */
FailGetDevDescr:
    #ifdef DEBUG_USB_HOST
    NotifyFailGetDevDescr();
    goto Fail;
    #endif

FailSetDevTblEntry:
    #ifdef DEBUG_USB_HOST
    NotifyFailSetDevTblEntry();
    goto Fail;
    #endif

FailSetConfDescr:
    #ifdef DEBUG_USB_HOST
    NotifyFailSetConfDescr();
    #endif
    goto Fail;

FailUnknownDevice:
    #ifdef DEBUG_USB_HOST
    NotifyFailUnknownDevice(VID, PID);
    #endif
    rcode = USB_DEV_CONFIG_ERROR_DEVICE_NOT_SUPPORTED;

Fail:
    #ifdef DEBUG_USB_HOST
    Notify(PSTR("\r\nAPEM Init Failed, error code: "), 0x80);
    NotifyFail(rcode);
    #endif
    Release();
    return rcode;
}

uint8_t APEM::Release() {
    Notify(PSTR("\r\nAPEM Controller Release\r\n"), 0x80);
    APEMConnected = false;
    pUsb->GetAddressPool().FreeAddress(bAddress);
    bAddress = 0;
    bPollEnable = false;
    return 0;
}

uint8_t APEM::Poll() {
    if(!bPollEnable)
        return 0;

    uint16_t BUFFER_SIZE = EP_MAXPKTSIZE;
    pUsb->inTransfer(bAddress, epInfo[ APEM_PIPE_1 ].epAddr, &BUFFER_SIZE, readBuf); 
    readReport();
//  printReport();
    return 0;
}

void APEM::readReport() {
    if(readBuf == NULL)
        return;
    if(readBuf[0] != 0x00 || readBuf[1] != 0x14) { // Check if it's the correct report - the controller also sends different status reports
        return;
    }

    hatValue[HatX] = (int16_t)(((uint16_t)readBuf[2] << 8) | readBuf[1]);
    hatValue[HatY] = (int16_t)(((uint16_t)readBuf[4] << 8) | readBuf[3]);
}

void APEM::printReport() {
    if(readBuf == NULL)
        return;
    for(uint8_t i = 0; i < REPORT_BUFFER_SIZE; i++) {
        D_PrintHex<uint8_t > (readBuf[i], 0x80);
        Notify(PSTR(" "), 0x80);
    }
    Notify(PSTR("\r\n"), 0x80);
}

int16_t APEM::getAnalogHat(AnalogHatEnum a) {
        return hatValue[a];
}

void APEM::onInit() {
    if(pFuncOnInit)
        pFuncOnInit(); // 사용자 함수 호출
}

main source

#include "apem.h"
#include <SPI.h>

USB Usb;
APEM Apem(&Usb);

void setup() {
  Serial.begin(115200);
#if !defined(__MIPSEL__)
  while (!Serial); // Wait for serial port to connect - used on Leonardo, Teensy and other boards with built-in USB CDC serial connection
#endif
  if (Usb.Init() == -1) {
    Serial.print(F("\r\nOSC did not start"));
    while (1); //halt
  }
  Serial.print(F("\r\nAPEM USB Library Started"));
}

void loop() {
  Usb.Task();
  if (Apem.APEMConnected) {

    if (Apem.getAnalogHat(HatX) > 7500 || Apem.getAnalogHat(HatX) < -7500 || Apem.getAnalogHat(HatY) > 7500 || Apem.getAnalogHat(HatY) < -7500) {
      if (Apem.getAnalogHat(HatX) > 7500 || Apem.getAnalogHat(HatX) < -7500) {
        Serial.print(F("HatX: "));
        Serial.print(Apem.getAnalogHat(HatX));
        Serial.print("\t");
      }
      if (Apem.getAnalogHat(HatY) > 7500 || Apem.getAnalogHat(HatY) < -7500) {
        Serial.print(F("HatY: "));
        Serial.print(Apem.getAnalogHat(HatY));
      }
      Serial.println();
    }
  }
  delay(100);
}

printReport() result: image

How can I get the Axis values?

tmk commented 3 years ago

You have to read HID Report Descriptor to understand how to interpret the Report data content.

Try this example sketch to get the descriptor, first. https://github.com/felis/USB_Host_Shield_2.0/tree/master/examples/HID/USBHID_desc

ckdo8008 commented 3 years ago

Sorry.

Descriptor result

16:50:00.076 -> Start
16:50:00.558 -> HU Init
16:50:00.933 -> Addr:01
16:50:00.933 -> NC:01
16:50:00.933 -> Cnf:01
16:50:00.933 -> NumIface:02
16:50:00.933 -> SetIdle:00
16:50:00.933 -> SetIdle:01
16:50:00.933 -> HU configured
16:50:00.933 -> 0000: 05 01 09 04 A1 01 05 01 09 01 A1 00 09 30 09 31 
16:50:00.933 -> 0010: 15 00 26 FF 0F 75 10 95 02 81 02 09 00 95 01 81 
16:50:00.933 -> 0020: 02 06 00 FF 09 01 15 00 26 FF 00 75 08 95 40 91 
16:50:00.933 -> 0030: 02 C0 C0 
16:50:00.933 -> Usage Page Gen Desktop Ctrls(01)
16:50:00.933 -> Usage Game Pad
16:50:00.933 -> Collection Application
16:50:00.933 -> Usage Page Gen Desktop Ctrls(01)
16:50:00.933 -> Usage Pointer
16:50:00.933 -> Collection Physical
16:50:00.933 -> Usage X
16:50:00.933 -> Usage Y
16:50:00.933 -> Logical Min(00)
16:50:00.933 -> Logical Max(FF0F)
16:50:00.933 -> Report Size(10)
16:50:00.933 -> Report Count(02)
16:50:00.933 -> Input(00000010)
16:50:00.933 -> Usage Undef
16:50:00.933 -> Report Count(01)
16:50:00.933 -> Input(00000010)
16:50:00.933 -> Usage Page Undef(00)
16:50:00.933 -> Usage
16:50:00.933 -> Logical Min(00)
16:50:00.933 -> Logical Max(FF00)
16:50:00.933 -> Report Size(08)
16:50:00.933 -> Report Count(40)
16:50:00.933 -> Output(00000010)
16:50:00.933 -> End Collection
16:50:00.933 -> End Collection
touchgadget commented 3 years ago
struct PACKED joystick_hid_report {
    /*
0xA1, 0x00,        //   Collection (Physical)
0x09, 0x30,        //     Usage (X)
0x09, 0x31,        //     Usage (Y)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0x0F,  //     Logical Maximum (4095)
0x75, 0x10,        //     Report Size (16)
0x95, 0x02,        //     Report Count (2)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
    */
    uint16_t    x_axis;
    uint16_t    y_axis;
    /*
0x09, 0x00,        //     Usage (Undefined)
0x95, 0x01,        //     Report Count (1)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
    */
    uint16_t    z_axis; // Usage(Undefined)????
};

Note the values range from 0-4095 so there are only 12 bits of valid data in a 16 bit int.

In one of the wireshark screen caps, there are 6 bytes like this:

HID Data: ff07ff077ff07

This results in x_axis = 0x07ff, y_axis = 0x07ff, z_axis = 0x07ff. I am guessing the Usage(Undefined) is the z axis. Or maybe throttle.

0x07ff=2047 is half way between 0 and 4095 so this is expected when the stick is centered. Moving the stick should show the values changing between 0 and 4095.

ckdo8008 commented 3 years ago
struct PACKED joystick_hid_report {
    /*
0xA1, 0x00,        //   Collection (Physical)
0x09, 0x30,        //     Usage (X)
0x09, 0x31,        //     Usage (Y)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0x0F,  //     Logical Maximum (4095)
0x75, 0x10,        //     Report Size (16)
0x95, 0x02,        //     Report Count (2)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
    */
    uint16_t    x_axis;
    uint16_t    y_axis;
    /*
0x09, 0x00,        //     Usage (Undefined)
0x95, 0x01,        //     Report Count (1)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
    */
    uint16_t    z_axis; // Usage(Undefined)????
};

값 범위는 0-4095이므로 16비트 int에는 12비트의 유효한 데이터만 있습니다.

wireshark 화면 캡 중 하나에는 다음과 같은 6바이트가 있습니다.

HID Data: ff07ff077ff07

결과적으로 x_axis = 0x07ff, y_axis = 0x07ff, z_axis = 0x07ff가 됩니다. Usage(Undefined) 가 z 축이라고 생각합니다. 아니면 스로틀.

0x07ff=2047은 0과 4095 사이의 중간이므로 스틱이 중앙에 있을 때 예상됩니다. 스틱을 움직이면 0과 4095 사이에서 변하는 값이 표시되어야 합니다.

In the "Poll" method

uint8_t APEM::Poll() {
    if(!bPollEnable)
        return 0;

    uint16_t BUFFER_SIZE = EP_MAXPKTSIZE;
    pUsb->inTransfer(bAddress, epInfo[ APEM_PIPE_1 ].epAddr, &BUFFER_SIZE, readBuf); 
    readReport();
//  printReport();
    return 0;
}

Receive data using "void APEM::readReport()". I referenced the xbox controller example.

APEM_PIPE_1 : 1 When data is called, it is answered with "00".

ckdo8008 commented 3 years ago
struct PACKED joystick_hid_report {
    /*
0xA1, 0x00,        //   Collection (Physical)
0x09, 0x30,        //     Usage (X)
0x09, 0x31,        //     Usage (Y)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0x0F,  //     Logical Maximum (4095)
0x75, 0x10,        //     Report Size (16)
0x95, 0x02,        //     Report Count (2)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
    */
    uint16_t    x_axis;
    uint16_t    y_axis;
    /*
0x09, 0x00,        //     Usage (Undefined)
0x95, 0x01,        //     Report Count (1)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
    */
    uint16_t    z_axis; // Usage(Undefined)????
};

Note the values range from 0-4095 so there are only 12 bits of valid data in a 16 bit int.

In one of the wireshark screen caps, there are 6 bytes like this:

HID Data: ff07ff077ff07

This results in x_axis = 0x07ff, y_axis = 0x07ff, z_axis = 0x07ff. I am guessing the Usage(Undefined) is the z axis. Or maybe throttle.

0x07ff=2047 is half way between 0 and 4095 so this is expected when the stick is centered. Moving the stick should show the values changing between 0 and 4095.

I've been working with your suggestion, but nothing comes out.

/* Simplified Thrustmaster T.16000M FCS Joystick Report Parser */

#include <usbhid.h>
#include <hiduniversal.h>
#include <usbhub.h>

// Satisfy the IDE, which needs to see the include statment in the ino too.
#ifdef dobogusinclude
#include <spi4teensy3.h>
#endif
#include <SPI.h>

// Thrustmaster T.16000M HID report
struct GamePadEventData
{
    /*
0xA1, 0x00,        //   Collection (Physical)
0x09, 0x30,        //     Usage (X)
0x09, 0x31,        //     Usage (Y)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0x0F,  //     Logical Maximum (4095)
0x75, 0x10,        //     Report Size (16)
0x95, 0x02,        //     Report Count (2)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
    */
    uint16_t    x_axis;
    uint16_t    y_axis;
    /*
0x09, 0x00,        //     Usage (Undefined)
0x95, 0x01,        //     Report Count (1)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
    */
    uint16_t    z_axis; // Usage(Undefined)????
}__attribute__((packed));

class JoystickEvents
{
public:
    virtual void OnGamePadChanged(const GamePadEventData *evt);
};

#define RPT_GAMEPAD_LEN sizeof(GamePadEventData)

class JoystickReportParser : public HIDReportParser
{
    JoystickEvents      *joyEvents;

  uint8_t oldPad[RPT_GAMEPAD_LEN];

public:
    JoystickReportParser(JoystickEvents *evt);

    virtual void Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf);
};

JoystickReportParser::JoystickReportParser(JoystickEvents *evt) :
    joyEvents(evt)
{}

void JoystickReportParser::Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf)
{
    // Checking if there are changes in report since the method was last called
  bool match = (sizeof(oldPad) == len) && (memcmp(oldPad, buf, len) == 0);

  // Calling Game Pad event handler
    if (!match && joyEvents) {
        joyEvents->OnGamePadChanged((const GamePadEventData*)buf);
    memcpy(oldPad, buf, len);
    }
}

void JoystickEvents::OnGamePadChanged(const GamePadEventData *evt)
{
    Serial.print("X: ");
    PrintHex<uint16_t>(evt->x_axis, 0x80);
    Serial.print(" Y: ");
    PrintHex<uint16_t>(evt->y_axis, 0x80);
    Serial.println();
}

USB                                             Usb;
USBHub                                          Hub(&Usb);
HIDUniversal                                    Hid(&Usb);
JoystickEvents                                  JoyEvents;
JoystickReportParser                            Joy(&JoyEvents);

void setup()
{
  Serial.begin( 115200 );
#if !defined(__MIPSEL__)
  while (!Serial); // Wait for serial port to connect - used on Leonardo, Teensy and other boards with built-in USB CDC serial connection
#endif
  Serial.println("Start");

  if (Usb.Init() == -1)
      Serial.println("OSC did not start.");

  delay( 200 );

  if (!Hid.SetReportParser(0, &Joy))
      ErrorMessage<uint8_t>(PSTR("SetReportParser"), 1  );
}

void loop()
{
    Usb.Task();
}
touchgadget commented 3 years ago
void JoystickReportParser::Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf)
{
  Serial.print("len="); Serial.println(len);
  for (int i = 0; i < 6; i++) {
    Serial.print(buf[i], HEX);
    Serial.print(' ');
  }
  Serial.println();

See if Parse is being called. If so, print the incoming parameters such as len and buf[].

ckdo8008 commented 3 years ago

Serial.print("len="); Serial.println(len); for (int i = 0; i < 6; i++) { Serial.print(buf[i], HEX); Serial.print(' '); } Serial.println();

11:48:10.144 -> Start
11:48:10.732 -> HU Init
11:48:11.002 -> Addr:01
11:48:11.002 -> NC:01
11:48:11.002 -> Cnf:01
11:48:11.002 -> NumIface:02
11:48:11.002 -> SetIdle:00
11:48:11.002 -> SetIdle:01
11:48:11.002 -> HU configured

Not called "JoystickReportParser::Parse"

HIDUniversal::Poll() 37 Line

                        uint8_t rcode = pUsb->inTransfer(bAddress, epInfo[index].epAddr, &read, buf);

                        if(rcode) {
                                if(rcode != hrNAK)
                                        USBTRACE3("(hiduniversal.h) Poll:", rcode, 0x81);
                                return rcode;
                        }

return.... (T_T)

xxxajk commented 3 years ago

}attribute((packed)); <--- that's the better way to pack a structure, at least for readability. UHS2's parser is pretty wonky too. It tries to read the same things over and over for each full device, which while is technically OK, is a hack to keep the heap usage low. That's where so many of the problems are in UHS2, unless you only ever plan on connecting one device, and have compiled a single device driver, some devices will get confused and refuse to enumerate. That said, If you are using a device that has MORE ram than the wimpy Atmel chips, and are using something like ESP, ARM, MIPS, etc, with more RAM, consider using UHS3. Yes, There's still bits and pieces like joysticks that do need to be worked out, however HID-RAW is already provided, allowing you to use it as you see fit. Adding joystick is trivial, just take the data provided by the HID-RAW driver, which even identifies the VID/PID and everything else you would require to identify the HID device, and simply process the data that came in. A perfect included example of this is the keyboard and mouse examples. These were originally developed within the sketch, then moved into the library. :-)

ckdo8008 commented 3 years ago

I looked at HIDUniversal and USB sources. When calling endpoint 02, data is not received from InTransfer of USB.

usb.cpp (Lib)

Line 242
                rcode = dispatchPkt(tokIN, pep->epAddr, nak_limit); //IN packet to EP-'endpoint'. Function takes care of NAKS.
        USBTRACE3("(USB::InTransfer) rcode ", rcode, 0x80);
        if(rcode == hrTOGERR) {
                        // yes, we flip it wrong here so that next time it is actually correct!
                        pep->bmRcvToggle = (regRd(rHRSL) & bmRCVTOGRD) ? 0 : 1;
                        regWr(rHCTL, (pep->bmRcvToggle) ? bmRCVTOG1 : bmRCVTOG0); //set toggle value
                        continue;
                }
                if(rcode) {
                        //printf(">>>>>>>> Problem! dispatchPkt %2.2x\r\n", rcode);
                        USBTRACE("Problem! dispatchPkt!\r\n");
                        break; //should be 0, indicating ACK. Else return error code.
                }

hiduniversal.cpp(Lib)

Line 37
                        uint8_t rcode = pUsb->inTransfer(bAddress, epInfo[index].epAddr, &read, buf);
            USBTRACE3("(HIDUniversal::Poll) pUsb->inTransfer bAddress ", bAddress, 0x80);
            USBTRACE3("(HIDUniversal::Poll) pUsb->inTransfer epInfo[index].epAddr ", epInfo[index].epAddr, 0x80);

Serial Output

20:21:56.393 -> (HIDUniversal::Poll) pUsb->inTransfer bAddress 01
20:21:56.393 -> (HIDUniversal::Poll) pUsb->inTransfer epInfo[index].epAddr 02
20:21:56.393 -> (USB::InTransfer) rcode 06
20:21:56.393 -> (USB::InTransfer) rcode 04
20:21:56.393 -> Problem! dispatchPkt!
20:21:56.393 -> (HIDUniversal::Poll) pUsb->inTransfer bAddress 01
20:21:56.393 -> (HIDUniversal::Poll) pUsb->inTransfer epInfo[index].epAddr 02
20:21:56.393 -> (USB::InTransfer) rcode 06
20:21:56.393 -> (USB::InTransfer) rcode 04
20:21:56.393 -> Problem! dispatchPkt!
20:21:56.393 -> (HIDUniversal::Poll) pUsb->inTransfer bAddress 01
20:21:56.393 -> (HIDUniversal::Poll) pUsb->inTransfer epInfo[index].epAddr 02
20:22:01.394 -> (USB::InTransfer) rcode 03
20:22:01.394 -> Problem! dispatchPkt!

dose not run.

void JoystickReportParser::Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf)
{
  Serial.print("len="); Serial.println(len);
  for (int i = 0; i < 6; i++) {
    Serial.print(buf[i], HEX);
    Serial.print(' ');
  }
  Serial.println();
touchgadget commented 3 years ago

Joysticks like the Thrustmaster t16k has only on USB interface. Your stick has two interfaces.

First interface

Interface descriptor:       
Intf.number:        00          
Alt.:           00                
Endpoints:      02            
Intf. Class:        03          
Intf. Subclass:     00        
Intf. Protocol:     00        
Intf.string:        04          
Unknown descriptor:         
Length:     09                
Type:       21                  
Contents:   110121012233000705

Endpoint descriptor:        
Endpoint address:   82        
Attr.:          03              
Max.pkt size:       0040        
Polling interval:   0A        

Endpoint descriptor:        
Endpoint address:   01        
Attr.:          03              
Max.pkt size:       0040        
Polling interval:   0A        

Second interface

Interface descriptor:       
Intf.number:        01          
Alt.:           00                
Endpoints:      01            
Intf. Class:        03          
Intf. Subclass:     00        
Intf. Protocol:     00        
Intf.string:        03          
Unknown descriptor:         
Length:     09                
Type:       21                  
Contents:   110121012234000705

Endpoint descriptor:        
Endpoint address:   84        
Attr.:          03              
Max.pkt size:       0008        
Polling interval:   0A        

You may have to go into the enumeration code to make UHS2 claim the second HID interface. Note the second interface specifies endpoint 84 which should result in IN transfers on endpoint number 4. The first interface specifies 82 (IN transfers on endpoint 2).

The first interface may be a vendor specific interface. The second looks more like a generic USB HID interface.

ckdo8008 commented 3 years ago

Joysticks like the Thrustmaster t16k has only on USB interface. Your stick has two interfaces.

First interface

Interface descriptor:       
Intf.number:      00          
Alt.:         00                
Endpoints:        02            
Intf. Class:      03          
Intf. Subclass:       00        
Intf. Protocol:       00        
Intf.string:      04          
Unknown descriptor:         
Length:       09                
Type:     21                  
Contents: 110121012233000705

Endpoint descriptor:        
Endpoint address: 82        
Attr.:            03              
Max.pkt size:     0040        
Polling interval: 0A        

Endpoint descriptor:        
Endpoint address: 01        
Attr.:            03              
Max.pkt size:     0040        
Polling interval: 0A        

Second interface

Interface descriptor:       
Intf.number:      01          
Alt.:         00                
Endpoints:        01            
Intf. Class:      03          
Intf. Subclass:       00        
Intf. Protocol:       00        
Intf.string:      03          
Unknown descriptor:         
Length:       09                
Type:     21                  
Contents: 110121012234000705

Endpoint descriptor:        
Endpoint address: 84        
Attr.:            03              
Max.pkt size:     0008        
Polling interval: 0A        

You may have to go into the enumeration code to make UHS2 claim the second HID interface. Note the second interface specifies endpoint 84 which should result in IN transfers on endpoint number 4. The first interface specifies 82 (IN transfers on endpoint 2).

The first interface may be a vendor specific interface. The second looks more like a generic USB HID interface.

Do you have a reference source for multi-interface devices? I tried adding an EndPoint (2, 4) but it failed. I want to know how to use the second interface. Modify initialization? What should I do?

#include "apem.h"

APEM::APEM(USB *p) :
pUsb(p), // pointer to USB class instance - mandatory
bAddress(0), // device address - mandatory
bPollEnable(false) { // don't start polling before dongle is connected
        for(uint8_t i = 0; i < MAX_ENDPOINTS; i++) {
                epInfo[i].epAddr = 0;
                epInfo[i].maxPktSize = (i) ? 0 : 8;
                epInfo[i].bmSndToggle = 0;
                epInfo[i].bmRcvToggle = 0;
                epInfo[i].bmNakPower = (i) ? USB_NAK_NOWAIT : USB_NAK_MAX_POWER;
        }

        if(pUsb) // register in USB subsystem
                pUsb->RegisterDeviceClass(this); //set devConfig[] entry
}

uint8_t APEM::Init(uint8_t parent, uint8_t port, bool lowspeed) {
    uint8_t buf[sizeof (USB_DEVICE_DESCRIPTOR)];
    USB_DEVICE_DESCRIPTOR * udd = reinterpret_cast<USB_DEVICE_DESCRIPTOR*>(buf);
    uint8_t rcode;
    UsbDevice *p = NULL;
    EpInfo *oldep_ptr = NULL;
    uint16_t PID;
    uint16_t VID;

    // USB 장치 주소 풀의 메모리 주소 가져오기
    AddressPool &addrPool = pUsb->GetAddressPool();

#ifdef EXTRADEBUG
    Notify(PSTR("\r\APEM Init"), 0x80);
#endif

    // 주소가 이미 인스턴스에 할당되었는지 확인
    if(bAddress) {
#ifdef DEBUG_USB_HOST
        Notify(PSTR("\r\nAddress in use"), 0x80);
#endif
        return USB_ERROR_CLASS_INSTANCE_ALREADY_IN_USE;
    }

    // 주소 0이 할당된 의사 장치에 대한 포인터 가져오기
    p = addrPool.GetUsbDevicePtr(1);

    if(!p) {
#ifdef DEBUG_USB_HOST
        Notify(PSTR("\r\nAddress not found"), 0x80);
#endif
        return USB_ERROR_ADDRESS_NOT_FOUND_IN_POOL;
    }

    if(!p->epinfo) {
#ifdef DEBUG_USB_HOST
        Notify(PSTR("\r\nepinfo is null"), 0x80);
#endif
        return USB_ERROR_EPINFO_IS_NULL;
    }

    // 주소 0의 EP_RECORD에 대한 이전 포인터 저장
    oldep_ptr = p->epinfo;

    // 토글 불일치를 피하기 위해 epInfo에 대한 새 포인터를 p->epinfo에 임시 할당합니다.
    p->epinfo = epInfo;

    p->lowspeed = lowspeed;

    // 장치 설명 가져오기
    rcode = pUsb->getDevDescr(0, 0, sizeof (USB_DEVICE_DESCRIPTOR), (uint8_t*)buf); // Get device descriptor
    // 되돌리기 p->epinfo
    p->epinfo = oldep_ptr;

    if(rcode)
        goto FailGetDevDescr;

    VID = udd->idVendor;
    PID = udd->idProduct;

    if(VID != APEM_VID) // Check VID
        goto FailUnknownDevice;

    if(PID != APEM_PID) // Check PID
        goto FailUnknownDevice;

    // 디바이스 클래스에 따라 새로운 주소 할당
    bAddress = addrPool.AllocAddress(parent, false, port);

    if(!bAddress)
        return USB_ERROR_OUT_OF_ADDRESS_SPACE_IN_POOL;

    // 장치 설명자에서 최대 패킷 크기 추출
    epInfo[0].maxPktSize = udd->bMaxPacketSize0;

    // 장치에 새 주소 할당
    rcode = pUsb->setAddr(0, 0, bAddress);
    if(rcode) {
        p->lowspeed = false;
        addrPool.FreeAddress(bAddress);
        bAddress = 0;
#ifdef DEBUG_USB_HOST
        Notify(PSTR("\r\nsetAddr: "), 0x80);
        D_PrintHex<uint8_t > (rcode, 0x80);
#endif
        return rcode;
    }
    #ifdef EXTRADEBUG
    Notify(PSTR("\r\nAddr: "), 0x80);
    D_PrintHex<uint8_t > (bAddress, 0x80);
    #endif
    //delay(300); // Spec says you should wait at least 200ms

    p->lowspeed = false;

    // 할당된 주소 레코드에 대한 포인터 가져오기
    p = addrPool.GetUsbDevicePtr(bAddress);
    if(!p)
        return USB_ERROR_ADDRESS_NOT_FOUND_IN_POOL;

    p->lowspeed = lowspeed;

    // epInfo 포인터에 epInfo 할당 - EP0만 알려져 있음
    rcode = pUsb->setEpInfoEntry(bAddress, 1, epInfo);
    if(rcode)
        goto FailSetDevTblEntry;

    epInfo[ APEM_PIPE_1 ].epAddr = 0x02; 
    epInfo[ APEM_PIPE_1 ].epAttribs = USB_TRANSFER_TYPE_INTERRUPT;
    epInfo[ APEM_PIPE_1 ].bmNakPower = USB_NAK_NOWAIT; // Only poll once for interrupt endpoints
    epInfo[ APEM_PIPE_1 ].maxPktSize = 0x40;
    epInfo[ APEM_PIPE_1 ].bmSndToggle = 0;
    epInfo[ APEM_PIPE_1 ].bmRcvToggle = 0;

  epInfo[ APEM_PIPE_2 ].epAddr = 0x04; 
  epInfo[ APEM_PIPE_2 ].epAttribs = USB_TRANSFER_TYPE_INTERRUPT;
  epInfo[ APEM_PIPE_2 ].bmNakPower = USB_NAK_NOWAIT; // Only poll once for interrupt endpoints
  epInfo[ APEM_PIPE_2 ].maxPktSize = 0x8;
  epInfo[ APEM_PIPE_2 ].bmSndToggle = 0;
  epInfo[ APEM_PIPE_2 ].bmRcvToggle = 0;

    rcode = pUsb->setEpInfoEntry(bAddress, MAX_ENDPOINTS, epInfo);
    if(rcode)
            goto FailSetDevTblEntry;

    delay(200); // Give time for address change

    rcode = pUsb->setConf(bAddress, epInfo[ APEM_CONTROL_PIPE ].epAddr, 1);
    if(rcode)
        goto FailSetConfDescr;

#ifdef DEBUG_USB_HOST
    Notify(PSTR("\r\nAPEM Controller Connected\r\n"), 0x80);
#endif
    onInit();
    APEMConnected = true;
    bPollEnable = true;
    return 0; // Successful configuration

    /* Diagnostic messages */
FailGetDevDescr:
    #ifdef DEBUG_USB_HOST
    NotifyFailGetDevDescr();
    goto Fail;
    #endif

FailSetDevTblEntry:
    #ifdef DEBUG_USB_HOST
    NotifyFailSetDevTblEntry();
    goto Fail;
    #endif

FailSetConfDescr:
    #ifdef DEBUG_USB_HOST
    NotifyFailSetConfDescr();
    #endif
    goto Fail;

FailUnknownDevice:
    #ifdef DEBUG_USB_HOST
    NotifyFailUnknownDevice(VID, PID);
    #endif
    rcode = USB_DEV_CONFIG_ERROR_DEVICE_NOT_SUPPORTED;

Fail:
    #ifdef DEBUG_USB_HOST
    Notify(PSTR("\r\nAPEM Init Failed, error code: "), 0x80);
    NotifyFail(rcode);
    #endif
    Release();
    return rcode;
}

uint8_t APEM::Release() {
    Notify(PSTR("\r\nAPEM Controller Release\r\n"), 0x80);
    APEMConnected = false;
    pUsb->GetAddressPool().FreeAddress(bAddress);
    bAddress = 0;
    bPollEnable = false;
    return 0;
}

uint8_t APEM::Poll() {
    if(!bPollEnable)
        return 0;

    uint16_t BUFFER_SIZE = EP_MAXPKTSIZE;
    pUsb->inTransfer(bAddress, epInfo[ APEM_PIPE_1 ].epAddr, &BUFFER_SIZE, readBuf); 
//  pUsb->inTransfer(bAddress, 0X01, &BUFFER_SIZE, readBuf); 
//  readReport();
    printReport();
    return 0;
}

void APEM::readReport() {
    if(readBuf == NULL)
        return;
    if(readBuf[0] != 0x00 || readBuf[1] != 0x14) { // Check if it's the correct report - the controller also sends different status reports
        return;
    }

    hatValue[HatX] = (int16_t)(((uint16_t)readBuf[2] << 8) | readBuf[1]);
    hatValue[HatY] = (int16_t)(((uint16_t)readBuf[4] << 8) | readBuf[3]);
}

void APEM::printReport() {
    if(readBuf == NULL)
        return;
    for(uint8_t i = 0; i < REPORT_BUFFER_SIZE; i++) {
        D_PrintHex<uint8_t > (readBuf[i], 0x80);
        Notify(PSTR(" "), 0x80);
    }
    Notify(PSTR("\r\n"), 0x80);
}

int16_t APEM::getAnalogHat(AnalogHatEnum a) {
        return hatValue[a];
}

void APEM::onInit() {
    if(pFuncOnInit)
        pFuncOnInit(); // 사용자 함수 호출
}
ckdo8008 commented 3 years ago

}attribute((packed)); <--- that's the better way to pack a structure, at least for readability. UHS2's parser is pretty wonky too. It tries to read the same things over and over for each full device, which while is technically OK, is a hack to keep the heap usage low. That's where so many of the problems are in UHS2, unless you only ever plan on connecting one device, and have compiled a single device driver, some devices will get confused and refuse to enumerate. That said, If you are using a device that has MORE ram than the wimpy Atmel chips, and are using something like ESP, ARM, MIPS, etc, with more RAM, consider using UHS3. Yes, There's still bits and pieces like joysticks that do need to be worked out, however HID-RAW is already provided, allowing you to use it as you see fit. Adding joystick is trivial, just take the data provided by the HID-RAW driver, which even identifies the VID/PID and everything else you would require to identify the HID device, and simply process the data that came in. A perfect included example of this is the keyboard and mouse examples. These were originally developed within the sketch, then moved into the library. :-)

I have sparkfun's USB Host Shield. and I have arduino DUE. However, the DUE board requires a connecting cable. There is no OTG cable.

tmk commented 3 years ago

examples/HID/USBHID_desc is hard-coded to display Report Descriptor only for Interface 0. You will have to modify its code to get descriptor for sencond interface.

Or use this sketch which tries to dump all of the descriptors as possible. https://github.com/tmk/USB_Host_Shield_2.0/tree/upstream_add_desc_dump/examples/USB_desc_dump

20:21:56.393 -> (HIDUniversal::Poll) pUsb->inTransfer bAddress 01
20:21:56.393 -> (HIDUniversal::Poll) pUsb->inTransfer epInfo[index].epAddr 02
20:21:56.393 -> (USB::InTransfer) rcode 06
20:21:56.393 -> (USB::InTransfer) rcode 04
20:21:56.393 -> Problem! dispatchPkt!

I don't know where this debug print came from, but rcode 06 means hrTOGERR probably. You may try this patch for the Toggle Error. https://github.com/tmk/USB_Host_Shield_2.0/commit/4a35a0364007d6821513b06e8600797aefa6a47c

ckdo8008 commented 3 years ago

examples/HID/USBHID_desc is hard-coded to display Report Descriptor only for Interface 0. You will have to modify its code to get descriptor for sencond interface.

Or use this sketch which tries to dump all of the descriptors as possible. https://github.com/tmk/USB_Host_Shield_2.0/tree/upstream_add_desc_dump/examples/USB_desc_dump

20:21:56.393 -> (HIDUniversal::Poll) pUsb->inTransfer bAddress 01
20:21:56.393 -> (HIDUniversal::Poll) pUsb->inTransfer epInfo[index].epAddr 02
20:21:56.393 -> (USB::InTransfer) rcode 06
20:21:56.393 -> (USB::InTransfer) rcode 04
20:21:56.393 -> Problem! dispatchPkt!

I don't know where this debug print came from, but rcode 06 means hrTOGERR probably. You may try this patch for the Toggle Error. tmk@4a35a03

Thanks for your advice, I know that I have to ignore the second interface. I added debug info. I'll take a look at the patch.

Start
usb_state: 12
Start
usb_state: 20
usb_state: 40
usb_state: 50
usb_state: 51
usb_state: 90

//////////////////////////////////////////////////////////////////////
// USB_desc_dump
// Address: 01
// Lowspeed: 00

// Devicer dump:
12 01 00 02 00 00 00 08 8E 06 05 01 00 01 01 02
00 01

// Device:
bLength:        12
bDescriptorType:    01
bcdUSB:         0200
bDeviceClass:       00
bDeviceSubClass:    00
bDeviceProtocol:    00
bMaxPacketSize0:    08
idVendor:       068E
idProduct:      0105
bcdDevice:      0100
iManufacturer:      01
iProduct:       02
iSerialNumber:      00
bNumConfigurations: 01

// String Zero: len: 04
// 04 03 09 04
// LangId: 0409

// iManufacturer: String1(0409): len: 18
// 18 03 43 00 48 00 20 00 50 00 72 00 6F 00 64 00
// 75 00 63 00 74 00 73 00
// CH Products

// iProduct: String2(0409): len: 42
// 42 03 41 00 50 00 45 00 4D 00 20 00 48 00 46 00
// 20 00 4A 00 6F 00 79 00 73 00 74 00 69 00 63 00
// 6B 00 20 00 20 00 20 00 20 00 20 00 20 00 20 00
// 20 00 20 00 20 00 20 00 20 00 20 00 20 00 20 00
// 20 00
// APEM HF Joystick                

// Config0 dump:    len: 0042
09 02 42 00 02 01 00 80 FA 09 04 00 00 02 03 00
00 04 09 21 11 01 21 01 22 33 00 07 05 82 03 40
00 0A 07 05 01 03 40 00 0A 09 04 01 00 01 03 00
00 03 09 21 11 01 21 01 22 34 00 07 05 84 03 08
00 0A

// Config:
bLength:        09
bDescriptorType:    02
wTotalLength:       0042
bNumInterfaces:     02
bConfigurationValue:    01
iConfiguration:     00
bmAttributes:       80
bMaxPower:      FA

// Interface0.0:
bLength:        09
bDescriptorType:    04
bInterfaceNumber:   00
bAlternateSetting:  00
bNumEndpoints:      02
bInterfaceClass:    03
bInterfaceSubClass: 00
bInterfaceProtocol: 00
iInterface:     04

// iInterface: String4(0409): len: 42
// 42 03 48 00 46 00 20 00 4A 00 6F 00 79 00 73 00
// 74 00 69 00 63 00 6B 00 20 00 20 00 20 00 20 00
// 20 00 20 00 20 00 20 00 20 00 20 00 20 00 20 00
// 20 00 20 00 20 00 20 00 20 00 20 00 20 00 20 00
// 20 00
// HF Joystick                     

// HID: 
bLength:        09
bDescriptorType:    21
bcdHID:         0111
bCountryCode:       21
bNumDescriptors:    01
bDescrType:     22
wDescriptorLength:  0033

// Report0 dump:    len: 0033
05 01 09 04 A1 01 05 01 09 01 A1 00 09 30 09 31
15 00 26 FF 0F 75 10 95 02 81 02 09 00 95 01 81
02 06 00 FF 09 01 15 00 26 FF 00 75 08 95 40 91
02 C0 C0

// Report:
Usage Page Gen Desktop Ctrls(01)
Usage Game Pad
Collection Application
Usage Page Gen Desktop Ctrls(01)
Usage Pointer
Collection Physical
Usage X
Usage Y
Logical Min(00)
Logical Max(FF0F)
Report Size(10)
Report Count(02)
Input(00000010)
Usage Undef
Report Count(01)
Input(00000010)
Usage Page Undef(00)
Usage
Logical Min(00)
Logical Max(FF00)
Report Size(08)
Report Count(40)
Output(00000010)
End Collection
End Collection

// Endpoint:
bLength:        07
bDescriptorType:    05
bEndpointAddress:   82
bmAttributes:       03
wMaxPacketSize:     0040
bInterval:      0A

// Endpoint:
bLength:        07
bDescriptorType:    05
bEndpointAddress:   01
bmAttributes:       03
wMaxPacketSize:     0040
bInterval:      0A

// Interface1.0:
bLength:        09
bDescriptorType:    04
bInterfaceNumber:   01
bAlternateSetting:  00
bNumEndpoints:      01
bInterfaceClass:    03
bInterfaceSubClass: 00
bInterfaceProtocol: 00
iInterface:     03

// iInterface: String3(0409): len: 42
// 42 03 41 00 50 00 45 00 4D 00 20 00 4D 00 6F 00
// 75 00 73 00 65 00 20 00 20 00 20 00 20 00 20 00
// 20 00 20 00 20 00 20 00 20 00 20 00 20 00 20 00
// 20 00 20 00 20 00 20 00 20 00 20 00 20 00 20 00
// 20 00
// APEM Mouse                      

// HID: 
bLength:        09
bDescriptorType:    21
bcdHID:         0111
bCountryCode:       21
bNumDescriptors:    01
bDescrType:     22
wDescriptorLength:  0034

// Report0 dump:    len: 0034
05 01 09 02 A1 01 09 01 A1 00 05 09 19 01 29 03
15 00 25 01 95 03 75 01 81 02 95 01 75 05 81 01
05 01 09 30 09 31 09 38 15 81 25 7F 75 08 95 03
81 06 C0 C0

// Report:
Usage Page Gen Desktop Ctrls(01)
Usage Mouse
Collection Application
Usage Pointer
Collection Physical
Usage Page Button(09)
Usage Min(01)
Usage Max(03)
Logical Min(00)
Logical Max(01)
Report Count(03)
Report Size(01)
Input(00000010)
Report Count(01)
Report Size(05)
Input(00000001)
Usage Page Gen Desktop Ctrls(01)
Usage X
Usage Y
Usage Wheel
Logical Min(81)
Logical Max(7F)
Report Size(08)
Report Count(03)
Input(00000110)
End Collection
End Collection

// Endpoint:
bLength:        07
bDescriptorType:    05
bEndpointAddress:   84
bmAttributes:       03
wMaxPacketSize:     0008
bInterval:      0A

// Parse data here: http://eleccelerator.com/usbdescreqparser/
usb_state: 12
ckdo8008 commented 3 years ago

examples/HID/USBHID_desc is hard-coded to display Report Descriptor only for Interface 0. You will have to modify its code to get descriptor for sencond interface.

Or use this sketch which tries to dump all of the descriptors as possible. https://github.com/tmk/USB_Host_Shield_2.0/tree/upstream_add_desc_dump/examples/USB_desc_dump

20:21:56.393 -> (HIDUniversal::Poll) pUsb->inTransfer bAddress 01
20:21:56.393 -> (HIDUniversal::Poll) pUsb->inTransfer epInfo[index].epAddr 02
20:21:56.393 -> (USB::InTransfer) rcode 06
20:21:56.393 -> (USB::InTransfer) rcode 04
20:21:56.393 -> Problem! dispatchPkt!

I don't know where this debug print came from, but rcode 06 means hrTOGERR probably. You may try this patch for the Toggle Error. tmk@4a35a03

Wow lol.

11:43:13.921 -> (APEM::Poll) BUFFER_SIZE: 0000
11:43:13.921 -> (APEM::Poll) pUsb->inTransfer: 03
11:43:19.042 -> (APEM::Poll) BUFFER_SIZE: 0000
11:43:19.042 -> (APEM::Poll) pUsb->inTransfer: 03
11:43:24.132 -> (APEM::Poll) BUFFER_SIZE: 0000
11:43:24.132 -> (APEM::Poll) pUsb->inTransfer: 03
11:43:29.249 -> (APEM::Poll) BUFFER_SIZE: 0000
11:43:29.249 -> (APEM::Poll) pUsb->inTransfer: 03
11:43:35.740 -> 
11:43:35.740 -> APEM USB Library Started
11:43:35.740 -> V 0000
11:43:36.835 -> APEM Controller Connected
11:43:37.203 -> (APEM::Poll) BUFFER_SIZE: 0006
11:43:37.203 -> (APEM::Poll) pUsb->inTransfer: 00
11:43:37.203 -> FF 07 FF 07 FF 07 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
11:43:37.341 -> (APEM::Poll) BUFFER_SIZE: 0006
11:43:37.341 -> (APEM::Poll) pUsb->inTransfer: 00
11:43:37.341 -> FF 07 FF 07 FF 07 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
11:43:37.432 -> (APEM::Poll) BUFFER_SIZE: 0006
11:43:37.432 -> (APEM::Poll) pUsb->inTransfer: 00
11:43:37.432 -> FF 07 FF 07 FF 07 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
11:43:37.523 -> (APEM::Poll) BUFFER_SIZE: 0006
11:43:37.523 -> (APEM::Poll) pUsb->inTransfer: 00
11:43:37.523 -> FF 0F FF 0F FF 07 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
11:43:37.661 -> (APEM::Poll) BUFFER_SIZE: 0006
11:43:37.661 -> (APEM::Poll) pUsb->inTransfer: 00
11:43:37.661 -> 00 00 FF 0F FF 07 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
11:43:37.752 -> (APEM::Poll) BUFFER_SIZE: 0006
11:43:37.752 -> (APEM::Poll) pUsb->inTransfer: 00
11:43:37.752 -> FF 0F FF 0F FF 07 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
11:43:37.887 -> (APEM::Poll) BUFFER_SIZE: 0006
11:43:37.887 -> (APEM::Poll) pUsb->inTransfer: 00
11:43:37.887 -> 00 00 FF 0F FF 07 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
11:43:37.977 -> (APEM::Poll) BUFFER_SIZE: 0006
11:43:37.977 -> (APEM::Poll) pUsb->inTransfer: 00
11:43:37.977 -> FF 0F FF 0F FF 07 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
11:43:38.068 -> (APEM::Poll) BUFFER_SIZE: 0006
11:43:38.068 -> (APEM::Poll) pUsb->inTransfer: 00
11:43:38.068 -> 00 00 FF 0F FF 07 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
11:43:38.205 -> (APEM::Poll) BUFFER_SIZE: 0006
11:43:38.205 -> (APEM::Poll) pUsb->inTransfer: 00
11:43:38.205 -> FF 0F FF 0F FF 07 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
11:43:38.296 -> (APEM::Poll) BUFFER_SIZE: 0006
11:43:38.296 -> (APEM::Poll) pUsb->inTransfer: 00
11:43:38.296 -> 00 00 FF 0F FF 07 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
11:43:38.387 -> (APEM::Poll) BUFFER_SIZE: 0006
11:43:38.387 -> (APEM::Poll) pUsb->inTransfer: 00
11:43:38.387 -> FF 0F FF 0F FF 07 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
11:43:38.525 -> (APEM::Poll) BUFFER_SIZE: 0006
11:43:38.525 -> (APEM::Poll) pUsb->inTransfer: 00
11:43:38.525 -> 00 00 FF 0F FF 07 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
11:43:38.616 -> (APEM::Poll) BUFFER_SIZE: 0006
11:43:38.616 -> (APEM::Poll) pUsb->inTransfer: 00
11:43:38.616 -> FF 0F FF 0F FF 07 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
11:43:38.753 -> (APEM::Poll) BUFFER_SIZE: 0006
11:43:38.753 -> (APEM::Poll) pUsb->inTransfer: 00
11:43:38.753 -> 00 00 FF 0F FF 07 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
11:43:38.844 -> (APEM::Poll) BUFFER_SIZE: 0006
11:43:38.844 -> (APEM::Poll) pUsb->inTransfer: 00
11:43:38.844 -> FF 0F FF 0F FF 07 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
11:43:38.935 -> (APEM::Poll) BUFFER_SIZE: 0000
11:43:38.935 -> (APEM::Poll) pUsb->inTransfer: 0D
11:43:38.935 -> 
11:43:38.935 -> APEM Controller Release

However, even when the joystick is moved, the data remains constant. FF 0F FF 0F FF 07 00 00 FF 0F FF 07

tmk commented 3 years ago

what is exact model number of your joystick? It seems to be a product from CH Products Inc/APEM, the manufacturer may have datasheet or useful technical information.

This is report descriptor of first interface. Note that it has vendor specific Output pipe, you may need to send a command to setup joystick?

0x05, 0x01,        // Usage Page (Generic Desktop Ctrls)
0x09, 0x04,        // Usage (Joystick)
0xA1, 0x01,        // Collection (Application)
0x05, 0x01,        //   Usage Page (Generic Desktop Ctrls)
0x09, 0x01,        //   Usage (Pointer)
0xA1, 0x00,        //   Collection (Physical)
0x09, 0x30,        //     Usage (X)
0x09, 0x31,        //     Usage (Y)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0x0F,  //     Logical Maximum (4095)
0x75, 0x10,        //     Report Size (16)
0x95, 0x02,        //     Report Count (2)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x09, 0x00,        //     Usage (Undefined)
0x95, 0x01,        //     Report Count (1)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x06, 0x00, 0xFF,  //     Usage Page (Vendor Defined 0xFF00)
0x09, 0x01,        //     Usage (0x01)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0x00,  //     Logical Maximum (255)
0x75, 0x08,        //     Report Size (8)
0x95, 0x40,        //     Report Count (64)
0x91, 0x02,        //     Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0xC0,              //   End Collection
0xC0,              // End Collection

// 51 bytes

Sencond interface is simple mouse with 3button and wheel, and it is not what you are interested.

0x05, 0x01,        // Usage Page (Generic Desktop Ctrls)
0x09, 0x02,        // Usage (Mouse)
0xA1, 0x01,        // Collection (Application)
0x09, 0x01,        //   Usage (Pointer)
0xA1, 0x00,        //   Collection (Physical)
0x05, 0x09,        //     Usage Page (Button)
0x19, 0x01,        //     Usage Minimum (0x01)
0x29, 0x03,        //     Usage Maximum (0x03)
0x15, 0x00,        //     Logical Minimum (0)
0x25, 0x01,        //     Logical Maximum (1)
0x95, 0x03,        //     Report Count (3)
0x75, 0x01,        //     Report Size (1)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x01,        //     Report Count (1)
0x75, 0x05,        //     Report Size (5)
0x81, 0x01,        //     Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x01,        //     Usage Page (Generic Desktop Ctrls)
0x09, 0x30,        //     Usage (X)
0x09, 0x31,        //     Usage (Y)
0x09, 0x38,        //     Usage (Wheel)
0x15, 0x81,        //     Logical Minimum (-127)
0x25, 0x7F,        //     Logical Maximum (127)
0x75, 0x08,        //     Report Size (8)
0x95, 0x03,        //     Report Count (3)
0x81, 0x06,        //     Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              //   End Collection
0xC0,              // End Collection

// 52 bytes
ckdo8008 commented 3 years ago

what is exact model number of your joystick? It seems to be a product from CH Products Inc/APEM, the manufacturer may have datasheet or useful technical information.

This is report descriptor of first interface. Note that it has vendor specific Output pipe, you may need to send a command to setup joystick?

0x05, 0x01,        // Usage Page (Generic Desktop Ctrls)
0x09, 0x04,        // Usage (Joystick)
0xA1, 0x01,        // Collection (Application)
0x05, 0x01,        //   Usage Page (Generic Desktop Ctrls)
0x09, 0x01,        //   Usage (Pointer)
0xA1, 0x00,        //   Collection (Physical)
0x09, 0x30,        //     Usage (X)
0x09, 0x31,        //     Usage (Y)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0x0F,  //     Logical Maximum (4095)
0x75, 0x10,        //     Report Size (16)
0x95, 0x02,        //     Report Count (2)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x09, 0x00,        //     Usage (Undefined)
0x95, 0x01,        //     Report Count (1)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x06, 0x00, 0xFF,  //     Usage Page (Vendor Defined 0xFF00)
0x09, 0x01,        //     Usage (0x01)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0x00,  //     Logical Maximum (255)
0x75, 0x08,        //     Report Size (8)
0x95, 0x40,        //     Report Count (64)
0x91, 0x02,        //     Output (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position,Non-volatile)
0xC0,              //   End Collection
0xC0,              // End Collection

// 51 bytes

Sencond interface is simple mouse with 3button and wheel, and it is not what you are interested.

0x05, 0x01,        // Usage Page (Generic Desktop Ctrls)
0x09, 0x02,        // Usage (Mouse)
0xA1, 0x01,        // Collection (Application)
0x09, 0x01,        //   Usage (Pointer)
0xA1, 0x00,        //   Collection (Physical)
0x05, 0x09,        //     Usage Page (Button)
0x19, 0x01,        //     Usage Minimum (0x01)
0x29, 0x03,        //     Usage Maximum (0x03)
0x15, 0x00,        //     Logical Minimum (0)
0x25, 0x01,        //     Logical Maximum (1)
0x95, 0x03,        //     Report Count (3)
0x75, 0x01,        //     Report Size (1)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x95, 0x01,        //     Report Count (1)
0x75, 0x05,        //     Report Size (5)
0x81, 0x01,        //     Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position)
0x05, 0x01,        //     Usage Page (Generic Desktop Ctrls)
0x09, 0x30,        //     Usage (X)
0x09, 0x31,        //     Usage (Y)
0x09, 0x38,        //     Usage (Wheel)
0x15, 0x81,        //     Logical Minimum (-127)
0x25, 0x7F,        //     Logical Maximum (127)
0x75, 0x08,        //     Report Size (8)
0x95, 0x03,        //     Report Count (3)
0x81, 0x06,        //     Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position)
0xC0,              //   End Collection
0xC0,              // End Collection

// 52 bytes

APEM HF11S10U https://www.apem.com/int/hf-series-42.html

ckdo8008 commented 3 years ago

Thanks. main

#include <usbhid.h>
#include <hiduniversal.h>

// Satisfy IDE, which only needs to see the include statment in the ino.
#ifdef dobogusinclude
#include <spi4teensy3.h>
#endif
#include <SPI.h>

#include "hidjoystickrptparser.h"

USB Usb;
HIDUniversal Hid(&Usb);
JoystickEvents JoyEvents;
JoystickReportParser Joy(&JoyEvents);

void setup() {
        Serial.begin(115200);
#if !defined(__MIPSEL__)
        while (!Serial); // Wait for serial port to connect - used on Leonardo, Teensy and other boards with built-in USB CDC serial connection
#endif
        Serial.println("Start");

        if (Usb.Init() == -1)
                Serial.println("OSC did not start.");

        delay(200);

        if (!Hid.SetReportParser(0, &Joy))
                ErrorMessage<uint8_t > (PSTR("SetReportParser"), 1);
}

void loop() {
        Usb.Task();
}

hidjoystickrptparser.cpp

#include "hidjoystickrptparser.h"

JoystickReportParser::JoystickReportParser(JoystickEvents *evt) :
joyEvents(evt),
oldHat(0xDE),
oldButtons(0) {
        for (uint8_t i = 0; i < RPT_GEMEPAD_LEN; i++)
                oldPad[i] = 0xD;
}

void JoystickReportParser::Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf) {
        bool match = true;

        // Checking if there are changes in report since the method was last called
        for (uint8_t i = 0; i < RPT_GEMEPAD_LEN; i++)
                if (buf[i] != oldPad[i]) {
                        match = false;
                        break;
                }

        // Calling Game Pad event handler
        if (!match && joyEvents) {
                joyEvents->OnGamePadChanged((const GamePadEventData*)buf);

                for (uint8_t i = 0; i < RPT_GEMEPAD_LEN; i++) oldPad[i] = buf[i];
        }

        uint8_t hat = (buf[5] & 0xF);

        // Calling Hat Switch event handler
        if (hat != oldHat && joyEvents) {
                joyEvents->OnHatSwitch(hat);
                oldHat = hat;
        }
}

void JoystickEvents::OnGamePadChanged(const GamePadEventData *evt) {
        Serial.print("X1: ");
        PrintHex<uint16_t > (evt->x_axis, 0x80);
        Serial.print("\tY1: ");
        PrintHex<uint16_t > (evt->y_axis, 0x80);
        Serial.print("\tX2: ");
        PrintHex<uint16_t > (evt->z_axis, 0x80);
        Serial.println("");
}

void JoystickEvents::OnHatSwitch(uint16_t hat) {
        Serial.print("Hat Switch: ");
        PrintHex<uint16_t > (hat, 0x80);
        Serial.println("");
}

hidjoystickrptparser.h

#if !defined(__HIDJOYSTICKRPTPARSER_H__)
#define __HIDJOYSTICKRPTPARSER_H__

#include <usbhid.h>

struct GamePadEventData {
    /*
0xA1, 0x00,        //   Collection (Physical)
0x09, 0x30,        //     Usage (X)
0x09, 0x31,        //     Usage (Y)
0x15, 0x00,        //     Logical Minimum (0)
0x26, 0xFF, 0x0F,  //     Logical Maximum (4095)
0x75, 0x10,        //     Report Size (16)
0x95, 0x02,        //     Report Count (2)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
    */
    uint16_t    x_axis;
    uint16_t    y_axis;
    /*
0x09, 0x00,        //     Usage (Undefined)
0x95, 0x01,        //     Report Count (1)
0x81, 0x02,        //     Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position)
    */
    uint16_t    z_axis; // Usage(Undefined)????
};

class JoystickEvents {
public:
        virtual void OnGamePadChanged(const GamePadEventData *evt);
        virtual void OnHatSwitch(uint16_t hat);
};

#define RPT_GEMEPAD_LEN     3

class JoystickReportParser : public HIDReportParser {
        JoystickEvents *joyEvents;

        uint16_t oldPad[RPT_GEMEPAD_LEN];
        uint16_t oldHat;
        uint16_t oldButtons;

public:
        JoystickReportParser(JoystickEvents *evt);

        virtual void Parse(USBHID *hid, bool is_rpt_id, uint8_t len, uint8_t *buf);
};

#endif // __HIDJOYSTICKRPTPARSER_H__

You may try this patch for the Toggle Error. tmk@4a35a03