Skip to main content
Associate
July 3, 2026
Question

USB Host PID D-Input Rumble: Block Load Feature Report returns all zeros on PXN V9 Gen2 using STM32 USBH

  • July 3, 2026
  • 5 replies
  • 78 views
  • Device Under Test: PXN V9 Gen2 Steering Wheel (operating in native D-Input mode)
  • Hardware Platform: STM32F446RE Nucleo Board acting as a USB Host.

  • Toolchain: STM32CubeIDE.

  • Driver Framework: Custom embedded USB Host HID stack implemented by extending and adapting the native usbh_hid.c library provided by the STM32Cube firmware package.

  • Analysis Tools: Wireshark with a USBPcap

I am attempting to initialize a force feedback (rumble) effect sequence from the STM32 host. According to the USB PID specification, the transaction sequence rules:

  1. Send Create New Effect Report (Report ID 0x05) via a Control Out transfer.

  2. Read PID Block Load Report (Report ID 0x06) via a Control In transfer to retrieve the allocated Effect Block Index.

The Symptom: When performing the Control In transfer to fetch Report ID 0x06, the data payload returned to the STM32 memory buffer is entirely filled with 0x00 bytes instead of a valid allocation status or a non-zero block index handle. No asynchronous data packets are visible or triggered on the Interrupt IN pipe either

The Result

DEBUG : [FFB] Actuators enabled 
DEBUG : [FFB] Create Effect sent
[FFB] Block Load: 00 00 00 00
[FFB] Index=0 Status=0
ERROR: [FFB] Error state

 

what i expect

Upon receiving a valid Create New Effect command, the steering wheel's internal PID state engine should populate the PID Block Load Report (0x06) with a Block Load Success status flag (logical value mapping within 1–3) and provide a valid Effect Block Index handle (between 1 and 14). This token is required so the host application can proceed with downloading force parameters.
 

  • Why not use X-Input? While X-Input handles rumble seamlessly out of the box via simple Xbox packet emulation, the hardware has a strict limitation in that mode: the external H-pattern gear shifter attachment fails to transmit correct positional data. In X-Input, it only flags center-top and bottom inputs, missing the left-top and right-top gates entirely. Native D-Input implementation is required to read the full layout of the shifter.

  • Descriptor Verification: Prior to writing the firmware, I audited the HID report descriptor (extract attached below) captured via Wireshark. I confirmed that both Report ID 0x05 and Report ID 0x06 are strictly classified as Feature Reports.

Capture HID Report

Usage (Create New Effect Report)
Collection (Logical)
    Report ID (0x05)
    Usage (Effect Type)
    Collection (Logical)
        Usage (ET Constant Force)
        Usage (ET Ramp)
        Usage (ET Square)
        Usage (ET Sine)
        Usage (ET Triangle)
        Usage (ET Sawtooth Up)
        Usage (ET Sawtooth Down)
        Usage (ET Spring)
        Usage (ET Damper)
        Usage (ET Inertia)
        Usage (ET Friction)
        Usage (ET Custom Force Data)
        Logical Maximum (12)
        Logical Minimum (1)
        Physical Minimum (1)
        Physical Maximum (12)
        Report Size (8)
        Report Count (1)
        Feature (Data,Array,Abs)
        End Collection
        Usage Page (Generic Desktop Controls)
        Usage (Byte Count)
        Logical Minimum (0)
        Logical Maximum (511)
        Physical Minimum (0)
        Physical Maximum (511)
        Report Size (10)
        Report Count (1)
        Feature (Data,Var,Abs)
        Report Size (6)
        Feature (Const,Array,Abs)
        End Collection

Usage Page (Physical Interface Device (PID))
Usage (PID Block Load Report)
Collection (Logical)
    Report ID (0x06)
    Usage (Effect Block Index)
    Logical Maximum (14)
    Logical Minimum (1)
    Physical Minimum (1)
    Physical Maximum (14)
    Report Size (8)
    Report Count (1)
    Feature (Data,Var,Abs)
    Usage (Block Load Status)
    Collection (Logical)
        Usage (Block Load Success)
        Usage (Block Load Full)
        Usage (Block Load Error)
        Logical Maximum (3)
        Logical Minimum (1)
        Physical Minimum (1)
        Physical Maximum (3)
        Report Size (8)
        Report Count (1)
        Feature (Data,Array,Abs)
        End Collection
        Usage (RAM Pool Available)
        Logical Minimum (0)
        Logical Maximum (65535)
        Physical Minimum (0)
        Physical Maximum (65535)
        Report Size (16)
        Report Count (1)
        Feature (Data,Array,Abs)
   End Collection

 

The Question

  • Is there an explicit vendor-defined handshake or standard state command (such as sending a PID Device Control on Report ID 0x0c with DC Enable Actuators) that must be dispatched by the STM32 host before the internal allocation mechanism responds on Report ID 0x06?

5 replies

ST Technical Moderator
July 3, 2026

Hello ​@noobgineer 
and welcome to STCommunity.
Could you please share your extended usbh_hid.c?
as a start, one effective way to find the root cause is to capture USB traffic with Wireshark using the original PC driver, then compare it with the STM32 host traffic, including descriptors, report IDs, and control requests.
this is an interesting case, waiting for your feedback.
BR
Gyessine

In order to give better visibility on the answered topics, please click on 'Best answer' on the reply which solved your issue or answered your question.
Associate
July 3, 2026
static USBH_StatusTypeDef PXN_Process(USBH_HandleTypeDef *phost) {

    uint32_t XferSize;
    USBH_StatusTypeDef status = USBH_OK;
    USBH_URBStateTypeDef inURBState;
    USBH_URBStateTypeDef outURBState;
    PXN_HandleTypeDef *PXN_Handle = (PXN_HandleTypeDef *)phost->pActiveClass->pData;

    if (PXN_Handle == NULL) {
        return USBH_OK;
    }

    if (PXN_Handle->initialized == 0U) {

        outURBState = USBH_LL_GetURBState(phost, PXN_Handle->OutPipe);
        if ((outURBState == USBH_URB_IDLE) || (outURBState == USBH_URB_DONE)) {
            PXN_Handle->initialized = 1U;
            PXN_Handle->timer = phost->Timer;
            PXN_Handle->ffb.state = FFB_STATE_RESET;
            USBH_DbgLog("PXN Out pipe ready\r\n");

            Rumble_Stop(PXN_Handle);
        }

        return USBH_OK;
    }

    FFB_Processs(phost);
//    Rumble_Process(phost, PXN_Handle);

    if (PXN_Handle->DataReady == 0U) {
        return USBH_OK;
    }

    PXN_Handle->DataReady = 0U;

    inURBState = USBH_LL_GetURBState(phost, PXN_Handle->InPipe);

    switch(inURBState)
    {

    case USBH_URB_DONE:
        XferSize = USBH_LL_GetLastXferSize(phost, PXN_Handle->InPipe);
        if (XferSize > 0 && XferSize <= PXN_Handle->InEpSize)
        {
            PXN_ProcessInput(phost, PXN_Handle->IN_buf, XferSize);
        }
        USBH_InterruptReceiveData(phost, PXN_Handle->IN_buf, PXN_Handle->InEpSize, PXN_Handle->InPipe);
    break;

    case USBH_URB_IDLE:
    case USBH_URB_NOTREADY:
         USBH_InterruptReceiveData(phost, PXN_Handle->IN_buf, PXN_Handle->InEpSize, PXN_Handle->InPipe);
    break;

    case USBH_URB_STALL:
        USBH_LL_SetToggle(phost, PXN_Handle->InPipe, 0);
        USBH_InterruptReceiveData(phost, PXN_Handle->IN_buf, PXN_Handle->InEpSize, PXN_Handle->InPipe);
        break;

    case USBH_URB_ERROR:
        USBH_ErrLog("USB Receive Error");
        status = USBH_FAIL;
        break;
    default:
        break;
    }

    return status;
}

This is major change in usbh_hid.c the other function is similar 
 

void FFB_Processs(USBH_HandleTypeDef *phost) {

USBH_StatusTypeDef status = USBH_OK;
PXN_HandleTypeDef *handle = (PXN_HandleTypeDef *)phost->pActiveClass->pData;

switch(handle->ffb.state) {

case FFB_STATE_RESET:
status = FFB_DeviceControl(phost, PID_DC_DEVICE_RESET);
if (status == USBH_OK)
{
USBH_DbgLog("[FFB] Device reset\r\n");
handle->ffb.state = FFB_STATE_CREATE_EFFECT;
} else if (status == USBH_NOT_SUPPORTED)
{
handle->ffb.state = FFB_STATE_ERROR;
}
break;

case FFB_STATE_ENABLE_ACTUATORS:
status = FFB_DeviceControl(phost, PID_DC_ENABLE_ACTUATORS);
if (status == USBH_OK)
{

USBH_DbgLog("[FFB] Actuators enabled");
handle->ffb.state = FFB_STATE_CREATE_EFFECT;
}
else if (status == USBH_NOT_SUPPORTED)
{
handle->ffb.state = FFB_STATE_ERROR;
}
break;
static USBH_StatusTypeDef FFB_DeviceControl(USBH_HandleTypeDef *phost, PID_DC_EnumTypes device_control) {

PXN_HandleTypeDef *handle = (PXN_HandleTypeDef *)phost->pActiveClass->pData;
handle->OUT_buf[0] = device_control;

return USBH_HID_SetReport(phost, HID_REPORT_OUTPUT, FFB_REPORT_DEVICE_CONTROL, handle->OUT_buf, 1U);
}

I captured the HID report descriptor by sniffing the USB traffic when plugging the wheel into my laptop (PXN has no official software to test rumble). My code can successfully send data to the OUT pipe and receive data from the IN pipe. I know X-Input mode can make this wheel rumble easily, so D-Input PID should be able to do it too, even though it is much harder.

I try to send a Set_Report or read a Get_Report (even after updating the format, report type, and report ID), the device always returns all zeros (0x00). It acts as if it isn't responding at all. 

 

ST Technical Moderator
July 3, 2026

Hi ​@noobgineer 

About Is a preceding PID Device Control / DC Enable Actuators required by spec, or by common PID implementations, before PID Block Load becomes valid?
I’m raising the question to dev team, and will get back to you. Internal ticket number : CDM0064074

To give better visibility on the answered topics, please click on "Best answer" on the reply which solved your issue or answered your question.Best regards,FBL
Associate
July 7, 2026

I have an update. I plugged in the PXN V9 Gen2 and it works with Euro Truck 2. I saw the data in Wireshark and tried to follow it.

static USBH_StatusTypeDef FFB_GetPool(USBH_HandleTypeDef *phost) {

PXN_HandleTypeDef *handle = (PXN_HandleTypeDef *)phost->pActiveClass->pData;
(void) USBH_memset(handle->ffb.get_buf, 0, sizeof(handle->ffb.get_buf));

return USBH_HID_GetReport(phost, 0x03U, 0x07U, handle->ffb.get_buf, 5U);
}

And then I call the function here.

	case FFB_STATE_GET_POOL:
status = FFB_GetPool(phost);
if (status == USBH_OK)
{
USBH_DbgLog("[FFB] Get Pool Report (USHB OK)\r\n");
for(int i=0;i<4;i++) {
printf("%02X ", handle->ffb.get_buf[i]);
}
printf("\n");
handle->ffb.state = FFB_STATE_RESET;
}
else if (status == USBH_NOT_SUPPORTED)
{
USBH_DbgLog("[FFB] Get Pool Report (USBH NOT SUPPORTED)\r\n");
for(int i=0;i<4;i++) {
printf("%02X ", handle->ffb.get_buf[i]);
}
printf("\n");
handle->ffb.state = FFB_STATE_RESET;
}

else
{
}
break;

This is the result

PID: 4001h
VID: 36e6h
Address (#1) assigned.
Manufacturer : LiteStar
Product : V9GEN2
Serial Number : 516628157777
Enumeration done.
This device has only 1 configuration.
Default configuration set.
PXN_DINPUT class started.
DEBUG : PXN Out pipe ready

DEBUG : [FFB] Get Pool Report (USHB OK)

00 00 00 00
DEBUG : [FFB] Device reset

I don't know why I'm getting all zeros. It should show some data, just like it does in Wireshark.

GET_REPORT Request

 

GET_REPORT Response

You can see it has data like 07 58 02 0A 03. Even though my data shouldn't be exactly the same, I don't know why mine is all zeros. Did I do something wrong?

ST Technical Moderator
July 9, 2026

Hi ​@noobgineer 

I assume no Interrupt IN notification is required for the PID Block Load flow. The PID spec defines Block Load as a Feature report returned via Get_Report on the Control pipe, not as an interrupt event. So the absence of async INT traffic is not itself a fault.

You need to separate device to host PID reports are expected to be delivered over the Interrupt IN path when the device exposes that endpoint and cannot be handled through control endpoint.

The Control “pipe” (called in host mode) Get_Report  is a separate host initiated retrieval path.

Your GetReport matches the request format. But can you print full 5 bytes response since wLength = 5?

for(int i=0;i<5;i++) {
printf("%02X ", handle->ffb.get_buf[i]);
}

The device is not refusing GetReport in general. The remaining issue is host side implementation, buffer handling, or both…

You must match the exact report ID, report length, interface number, required PID state sequence, and the STM32 host control transfer handling.

To give better visibility on the answered topics, please click on "Best answer" on the reply which solved your issue or answered your question.Best regards,FBL