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?
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.