embeddedusbmulti-touchhidtouchscreen

USB touchscreen not registering multiple contacts


I'm trying to implement a USB touchscreen on a STM32. I'm making a full-speed device using ST's USB library and I'm testing it with on a Windows 10 host. The first contact registers, but when I try to press another, it doesn't. This happens even when the contacts are being pressed independently.

I'm using the sample report descriptors provided in the Windows Hardware Developer docs, modified such that the X and Y usages only report intended touches. I've also excluded the Device Certification Status Feature Report for simplicity. My report descriptor is as follows:

  /* USER CODE BEGIN 0 */
        0x05, 0x0d,                         // USAGE_PAGE (Digitizers)
            0x09, 0x04,                         // USAGE (Touch Screen)
            0xa1, 0x01,                         // COLLECTION (Application)
            0x85, 0x01,                         //   REPORT_ID (Touch)
            0x09, 0x22,                         //   USAGE (Finger)
            0xa1, 0x02,                         //     COLLECTION (Logical)
            0x09, 0x42,                         //       USAGE (Tip Switch)
            0x15, 0x00,                         //       LOGICAL_MINIMUM (0)
            0x25, 0x01,                         //       LOGICAL_MAXIMUM (1)
            0x75, 0x01,                         //       REPORT_SIZE (1)
            0x95, 0x01,                         //       REPORT_COUNT (1)
            0x81, 0x02,                         //       INPUT (Data,Var,Abs)
            0x95, 0x07,                         //       REPORT_COUNT (7)
            0x81, 0x03,                         //       INPUT (Cnst,Ary,Abs)
            0x75, 0x08,                         //       REPORT_SIZE (8)
            0x09, 0x51,                         //       USAGE (Contact Identifier)
            0x95, 0x01,                         //       REPORT_COUNT (1)
            0x81, 0x02,                         //       INPUT (Data,Var,Abs)
            0x05, 0x01,                         //       USAGE_PAGE (Generic Desk..
            0x26, 0xff, 0x0f,                   //       LOGICAL_MAXIMUM (4095)
            0x75, 0x10,                         //       REPORT_SIZE (16)
            0x55, 0x0e,                         //       UNIT_EXPONENT (-2)
            0x65, 0x13,                         //       UNIT(Inch,EngLinear)
            0x09, 0x30,                         //       USAGE (X)
            0x35, 0x00,                         //       PHYSICAL_MINIMUM (0)
            0x46, 0xb5, 0x04,                   //       PHYSICAL_MAXIMUM (1205)
            0x81, 0x02,                         //       INPUT (Data,Var,Abs)
            0x46, 0x8a, 0x03,                   //       PHYSICAL_MAXIMUM (906)
            0x09, 0x31,                         //       USAGE (Y)
            0x81, 0x02,                         //       INPUT (Data,Var,Abs)
            0x05, 0x0d,                         //       USAGE_PAGE (Digitizers)
            0x09, 0x48,                         //       USAGE (Width)
            0x81, 0x02,                         //       INPUT (Data,Var,Abs)
            0x09, 0x49,                         //       USAGE (Height)
            0x81, 0x02,                         //       INPUT (Data,Var,Abs)
            0x55, 0x0C,                         //       UNIT_EXPONENT (-4)
            0x65, 0x12,                         //       UNIT (Radians,SIROtation)
            0x35, 0x00,                         //       PHYSICAL_MINIMUM (0)
            0x47, 0x6f, 0xf5, 0x00, 0x00,       //       PHYSICAL_MAXIMUM (62831)
            0x15, 0x00,                         //       LOGICAL_MINIMUM (0)
            0x27, 0x6f, 0xf5, 0x00, 0x00,       //       LOGICAL_MAXIMUM (62831)
            0x09, 0x3f,                         //       USAGE (Azimuth[Orientation])
            0x81, 0x02,                         //       INPUT (Data,Var,Abs)
            0xc0,                               //     END_COLLECTION
            0x09, 0x22,                         //   USAGE (Finger)
            0xa1, 0x02,                         //     COLLECTION (Logical)
            0x09, 0x42,                         //       USAGE (Tip Switch)
            0x15, 0x00,                         //       LOGICAL_MINIMUM (0)
            0x25, 0x01,                         //       LOGICAL_MAXIMUM (1)
            0x75, 0x01,                         //       REPORT_SIZE (1)
            0x95, 0x01,                         //       REPORT_COUNT (1)
            0x81, 0x02,                         //       INPUT (Data,Var,Abs)
            0x95, 0x07,                         //       REPORT_COUNT (7)
            0x81, 0x03,                         //       INPUT (Cnst,Ary,Abs)
            0x75, 0x08,                         //       REPORT_SIZE (8)
            0x09, 0x51,                         //       USAGE (Contact Identifier)
            0x95, 0x01,                         //       REPORT_COUNT (1)
            0x81, 0x02,                         //       INPUT (Data,Var,Abs)
            0x05, 0x01,                         //       USAGE_PAGE (Generic Desk..
            0x26, 0xff, 0x0f,                   //       LOGICAL_MAXIMUM (4095)
            0x75, 0x10,                         //       REPORT_SIZE (16)
            0x55, 0x0e,                         //       UNIT_EXPONENT (-2)
            0x65, 0x13,                         //       UNIT(Inch,EngLinear)
            0x09, 0x30,                         //       USAGE (X)
            0x35, 0x00,                         //       PHYSICAL_MINIMUM (0)
            0x46, 0xb5, 0x04,                   //       PHYSICAL_MAXIMUM (1205)
            0x81, 0x02,                         //       INPUT (Data,Var,Abs)
            0x46, 0x8a, 0x03,                   //       PHYSICAL_MAXIMUM (906)
            0x09, 0x31,                         //       USAGE (Y)
            0x81, 0x02,                         //       INPUT (Data,Var,Abs)
            0x05, 0x0d,                         //       USAGE_PAGE (Digitizers)
            0x09, 0x48,                         //       USAGE (Width)
            0x81, 0x02,                         //       INPUT (Data,Var,Abs)
            0x09, 0x49,                         //       USAGE (Height)
            0x81, 0x02,                         //       INPUT (Data,Var,Abs)
            0x55, 0x0C,                         //       UNIT_EXPONENT (-4)
            0x65, 0x12,                         //       UNIT (Radians,SIROtation)
            0x35, 0x00,                         //       PHYSICAL_MINIMUM (0)
            0x47, 0x6f, 0xf5, 0x00, 0x00,       //       PHYSICAL_MAXIMUM (62831)
            0x15, 0x00,                         //       LOGICAL_MINIMUM (0)
            0x27, 0x6f, 0xf5, 0x00, 0x00,       //       LOGICAL_MAXIMUM (62831)
            0x09, 0x3f,                         //       USAGE (Azimuth[Orientation])
            0x81, 0x02,                         //       INPUT (Data,Var,Abs)
            0xc0,                               //     END_COLLECTION
            0x05, 0x0d,                         //   USAGE_PAGE (Digitizers)
            0x55, 0x0C,                         //     UNIT_EXPONENT (-4)
            0x66, 0x01, 0x10,                   //     UNIT (Seconds)
            0x47, 0xff, 0xff, 0x00, 0x00,       //       PHYSICAL_MAXIMUM (65535)
            0x27, 0xff, 0xff, 0x00, 0x00,       //   LOGICAL_MAXIMUM (65535)
            0x75, 0x10,                         //   REPORT_SIZE (16)
            0x95, 0x01,                         //   REPORT_COUNT (1)
            0x09, 0x56,                         //   USAGE (Scan Time)
            0x81, 0x02,                         //   INPUT (Data,Var,Abs)
            0x09, 0x54,                         //   USAGE (Contact count)
            0x25, 0x7f,                         //   LOGICAL_MAXIMUM (127)
            0x95, 0x01,                         //   REPORT_COUNT (1)
            0x75, 0x08,                         //   REPORT_SIZE (8)
            0x81, 0x02,                         //   INPUT (Data,Var,Abs)
            0x85, REPORTID_MAX_COUNT,           //   REPORT_ID (Feature)
            0x09, 0x55,                         //   USAGE(Contact Count Maximum)
            0x95, 0x01,                         //   REPORT_COUNT (1)
            0x25, 0x02,                         //   LOGICAL_MAXIMUM (2)
            0xb1, 0x02,                         //   FEATURE (Data,Var,Abs)
  /* USER CODE END 0 */
  0xC0    /*     END_COLLECTION              */

I'm representing the input report using a TouchReport struct, which contains an array of Contact structs. Please note that TOUCHSCREEN_MAX_CONTACTS is 2.

typedef struct __attribute__((packed))
{
    uint8_t report_ID;
    Contact contacts[TOUCHSCREEN_MAX_CONTACTS];
    uint16_t scan_time;
    uint8_t contact_count;
} TouchReport;

typedef struct __attribute__((packed))
{
    uint8_t tip_switch;
    uint8_t contact_ID;
    uint16_t x;
    uint16_t y;
    uint16_t width;
    uint16_t height;
    uint16_t azimuth;
} Contact;

When I set values for the contact with a contact_ID of 0 and send the report, I see a touch press on my screen. In Wireshark, the transaction looks like this:

// DEVICE TO HOST

Frame 30811: 55 bytes on wire (440 bits), 55 bytes captured (440 bits) on interface \\.\USBPcap3, id 0
USB URB
    [Source: 3.26.1]
    [Destination: host]
    USBPcap pseudoheader length: 27
    IRP ID: 0xffff830badc6aa40
    IRP USBD_STATUS: USBD_STATUS_SUCCESS (0x00000000)
    URB Function: URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER (0x0009)
    IRP information: 0x01, Direction: PDO -> FDO
    URB bus id: 3
    Device address: 26
    Endpoint: 0x81, Direction: IN
    URB transfer type: URB_INTERRUPT (0x01)
    Packet Data Length: 28
    [Request in: 26783]
    [Time from request: 15.207822000 seconds]
    [bInterfaceClass: HID (0x03)]
HID Data: 010100ff07ff070000000000000001000000000000000000002a5b01
    Report ID: 0x01

    .... ...1 = Usage: Tip Switch: 1
    Padding: 00
    0000 0000 = Usage: Contact Identifier: 0
    0000 0111  1111 1111 = X Axis: 2047
    0000 0111  1111 1111 = Y Axis: 2047
    0000 0000  0000 0000 = Usage: Width: 0
    0000 0000  0000 0000 = Usage: Height: 0
    0000 0000  0000 0000 = Usage: Azimuth: 0

    .... ...0 = Usage: Tip Switch: 0
    Padding: 00
    0000 0001 = Usage: Contact Identifier: 1
    0000 0000  0000 0000 = X Axis: 0
    0000 0000  0000 0000 = Y Axis: 0
    0000 0000  0000 0000 = Usage: Width: 0
    0000 0000  0000 0000 = Usage: Height: 0
    0000 0000  0000 0000 = Usage: Azimuth: 0

    0101 1011  0010 1010 = Usage: Scan Time: 23338
    0000 0001 = Usage: Contact Count: 1

// HOST TO DEVICE

Frame 30812: 27 bytes on wire (216 bits), 27 bytes captured (216 bits) on interface \\.\USBPcap3, id 0
USB URB
    [Source: host]
    [Destination: 3.26.1]
    USBPcap pseudoheader length: 27
    IRP ID: 0xffff830badc6aa40
    IRP USBD_STATUS: USBD_STATUS_SUCCESS (0x00000000)
    URB Function: URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER (0x0009)
    IRP information: 0x00, Direction: FDO -> PDO
    URB bus id: 3
    Device address: 26
    Endpoint: 0x81, Direction: IN
    URB transfer type: URB_INTERRUPT (0x01)
    Packet Data Length: 0
    [Response in: 30939]
    [bInterfaceClass: HID (0x03)]

However, when I try the same thing for the second contact, with a contact_ID of 1, no touch registers on my screen. Here's how this transaction looks:

// DEVICE TO HOST

Frame 30401: 55 bytes on wire (440 bits), 55 bytes captured (440 bits) on interface \\.\USBPcap3, id 0
USB URB
    [Source: 3.28.1]
    [Destination: host]
    USBPcap pseudoheader length: 27
    IRP ID: 0xffff830bb0eb5010
    IRP USBD_STATUS: USBD_STATUS_SUCCESS (0x00000000)
    URB Function: URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER (0x0009)
    IRP information: 0x01, Direction: PDO -> FDO
    URB bus id: 3
    Device address: 28
    Endpoint: 0x81, Direction: IN
    URB transfer type: URB_INTERRUPT (0x01)
    Packet Data Length: 28
    [Request in: 18793]
    [Time from request: 36.682296000 seconds]
    [bInterfaceClass: HID (0x03)]
HID Data: 010000000000000000000000000101ff07ff07000000000000eaa101
    Report ID: 0x01
    
    .... ...0 = Usage: Tip Switch: 0
    Padding: 00
    0000 0000 = Usage: Contact Identifier: 0
    0000 0000  0000 0000 = X Axis: 0
    0000 0000  0000 0000 = Y Axis: 0
    0000 0000  0000 0000 = Usage: Width: 0
    0000 0000  0000 0000 = Usage: Height: 0
    0000 0000  0000 0000 = Usage: Azimuth: 0

    .... ...1 = Usage: Tip Switch: 1
    Padding: 00
    0000 0001 = Usage: Contact Identifier: 1
    0000 0111  1111 1111 = X Axis: 2047
    0000 0111  1111 1111 = Y Axis: 2047
    0000 0000  0000 0000 = Usage: Width: 0
    0000 0000  0000 0000 = Usage: Height: 0
    0000 0000  0000 0000 = Usage: Azimuth: 0

    1010 0001  1110 1010 = Usage: Scan Time: 41450
    0000 0001 = Usage: Contact Count: 1

// HOST TO DEVICE

Frame 30402: 27 bytes on wire (216 bits), 27 bytes captured (216 bits) on interface \\.\USBPcap3, id 0
USB URB
    [Source: host]
    [Destination: 3.28.1]
    USBPcap pseudoheader length: 27
    IRP ID: 0xffff830bb0eb5010
    IRP USBD_STATUS: USBD_STATUS_SUCCESS (0x00000000)
    URB Function: URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER (0x0009)
    IRP information: 0x00, Direction: FDO -> PDO
    URB bus id: 3
    Device address: 28
    Endpoint: 0x81, Direction: IN
    URB transfer type: URB_INTERRUPT (0x01)
    Packet Data Length: 0
    [Response in: 30585]
    [bInterfaceClass: HID (0x03)]

If it wasn't already evident, I'm reporting packets in parallel mode. As far as I can tell, the data from the input reports is being transmitted successfully because the HID data is pretty much identical except for which contact is being pressed.

The next place where things could go awry is with feature reporting. I represented the Contact Count Maximum Feature Report that Windows requires with the following array:

uint8_t maxCountFeatureReport[2] = {REPORTID_MAX_COUNT, TOUCHSCREEN_MAX_CONTACTS};

Please note that TOUCHSCREEN_MAX_CONTACTS is 2. During the setup process, a Get_Report transaction occurs, as the data from Wireshark shows:

// HOST TO DEVICE (REQUEST)

Frame 18799: 36 bytes on wire (288 bits), 36 bytes captured (288 bits) on interface \\.\USBPcap3, id 0
USB URB
    [Source: host]
    [Destination: 3.28.0]
    USBPcap pseudoheader length: 28
    IRP ID: 0xffff830bb56f7700
    IRP USBD_STATUS: USBD_STATUS_SUCCESS (0x00000000)
    URB Function: URB_FUNCTION_CLASS_INTERFACE (0x001b)
    IRP information: 0x00, Direction: FDO -> PDO
    URB bus id: 3
    Device address: 28
    Endpoint: 0x80, Direction: IN
    URB transfer type: URB_CONTROL (0x02)
    Packet Data Length: 8
    [Response in: 18800]
    Control transfer stage: Setup (0)
    [bInterfaceClass: HID (0x03)]
Setup Data
    bmRequestType: 0xa1
        1... .... = Direction: Device-to-host
        .01. .... = Type: Class (0x1)
        ...0 0001 = Recipient: Interface (0x01)
    bRequest: GET_REPORT (0x01)
    wValue: 0x0302
        ReportID: 2
        ReportType: Feature (3)
    wIndex: 0
    wLength: 2

// DEVICE TO HOST (RESPONSE)

Frame 18800: 30 bytes on wire (240 bits), 30 bytes captured (240 bits) on interface \\.\USBPcap3, id 0
USB URB
    [Source: 3.28.0]
    [Destination: host]
    USBPcap pseudoheader length: 28
    IRP ID: 0xffff830bb56f7700
    IRP USBD_STATUS: USBD_STATUS_SUCCESS (0x00000000)
    URB Function: URB_FUNCTION_CONTROL_TRANSFER (0x0008)
    IRP information: 0x01, Direction: PDO -> FDO
    URB bus id: 3
    Device address: 28
    Endpoint: 0x80, Direction: IN
    URB transfer type: URB_CONTROL (0x02)
    Packet Data Length: 2
    [Request in: 18799]
    [Time from request: 0.000207000 seconds]
    Control transfer stage: Complete (3)
    [bInterfaceClass: HID (0x03)]

Since it isn't decoded by Wireshark, here's the hex dump for the response packet:

0000   1c 00 00 77 6f b5 0b 83 ff ff 00 00 00 00 08 00
0010   01 03 00 1c 00 80 02 02 00 00 00 03 02 02

As far as I can tell, something here is correct since the literal values of the feature report array is {2, 2} and the last two bytes of the packet are 02 02. But I feel like this was the trickiest part, so hopefully there's something in this transaction that someone sees is wrong.

I guess the last place where there could be a problem is with drivers, but I really doubt that's the case because it says everything is ok and the driver is installed in Device Manager.

So does anyone have any idea what could be going on?


Solution

  • The issue was with the ordering of contacts reported. I had assumed that I could just treat the array of contacts reported like a map where the index of each contact is also the contact ID. This isn't the case. The array of contacts reported needs to be filled from first contact reported to last contact reported, without any gaps.

    So for example, something like this is vaild:

    Contacts[0]: contact with contact_ID=2, tip_switch=1
    Contacts[1]: empty
    Contacts[2]: empty
    

    But something like this is not:

    Contacts[0]: empty
    Contacts[1]: empty
    Contacts[2]: contact with contact_ID=2, tip_switch=1
    

    I also realized that I had made a mistake in my post. It turned out that pressing 2/2 contacts at the same time was actually working, but pressing them independently was not.