diff --git a/Readme.md b/Readme.md index d42b9562..418fd66e 100644 --- a/Readme.md +++ b/Readme.md @@ -31,7 +31,8 @@ The idea is to enable enhanced USB functions to almost all 'standard' Arduino bo * Gamepad (32 buttons, 4 16bit axis, 2 8bit axis, 2 D-Pads) * RawHID * SurfaceDial -* Each device is available as single or multi report device (except RawHID) +* Touchscreen (max 10 touches at the same time) +* Each device is available as single or multi report device (except RawHID and Touchscreen) See the [wiki](https://github.com/NicoHood/HID/wiki/Features) for more information about features etc. diff --git a/examples/Touchscreen/Touchscreen.ino b/examples/Touchscreen/Touchscreen.ino new file mode 100644 index 00000000..fe774f5d --- /dev/null +++ b/examples/Touchscreen/Touchscreen.ino @@ -0,0 +1,39 @@ +/* + Copyright (c) 2021 ilufang + See the readme for credit to other people. + + Multi-touch touchscreen example + Draw 7 parallel lines across the screen. + Open Microsoft Whiteboard, select a brush and press the button. + + See HID Project documentation for more infos + https://github.com/NicoHood/HID/wiki +*/ + +#include + +const int pinButton = 2; + +void setup() { + pinMode(pinButton, INPUT_PULLUP); + Touchscreen.begin(); +} + +void loop() { + while(digitalRead(pinButton)); + + int16_t x = 2000, y = 2000; + + for (; x <= 8000; x+=10) { + for (int i = 0; i < 7; i++) { + Touchscreen.setFinger(i, x, y+i*1000, (i+1)*15); + } + Touchscreen.send(); + delay(10); + } + + for (int i = 0; i < 7; i++) { + Touchscreen.releaseFinger(i); + } + Touchscreen.send(); +} diff --git a/src/HID-APIs/TouchscreenAPI.h b/src/HID-APIs/TouchscreenAPI.h new file mode 100644 index 00000000..2edd1bdf --- /dev/null +++ b/src/HID-APIs/TouchscreenAPI.h @@ -0,0 +1,118 @@ +/* +Copyright (c) 2021 ilufang +See the readme for credit to other people. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +// Include guard +#pragma once + +/// Maximum amount of fingers supported +#define HID_TOUCHSCREEN_MAXFINGERS 10 +/// Number of fingers in a single report +#define HID_TOUCHSCREEN_REPORTFINGERS 2 + +// A report will always be the same size, even if you report fewer fingers than +// REPORTFINGERS. The unused finger entries will simply be zero. If more fingers +// are present than REPORTFINGERS, multiple reports will be sent to report all +// fingers. This is know as "Hybrid Mode" on MSDN. The number of supported +// fingers identified by Windows will still be MAXFINGERS. More than MAXFINGERS +// contacts may be ignored by Windows even with hybrid mode. + + +// Bit-mask flags for 'status' in the HID report +#define HID_TOUCHSCREEN_TOUCH_CONTACT 0x01 +#define HID_TOUCHSCREEN_TOUCH_IN_RANGE 0x02 + +typedef struct ATTRIBUTE_PACKED { + uint8_t status; + uint8_t pressure; + uint16_t x; + uint16_t y; +} HID_Touchscreen_Finger_t; + +typedef union ATTRIBUTE_PACKED { + uint8_t whole8[0]; + uint16_t whole16[0]; + uint32_t whole32[0]; + struct ATTRIBUTE_PACKED { + uint8_t count; + struct ATTRIBUTE_PACKED { + uint8_t identifier; + HID_Touchscreen_Finger_t touch; + } contacts[HID_TOUCHSCREEN_REPORTFINGERS]; + }; +} HID_TouchscreenReport_Data_t; + +class TouchscreenAPI +{ +public: + + inline TouchscreenAPI(); + + inline void begin(); + inline void end(); + + /** + * Set contact status for a finger in the internal data structure. You must + * call send manually after setting all fingers to flush them through USB. + * + * @param id Finger id. Must be in the range of 0-MAXFINGERS. Same finger + * must have same id throughout contact. Allocations does not need + * to be continuous. + * @param x, y Coordinates. Range 0-10000. (0,0) is top-left on Windows. + * @param pressure Contact pressure. Range 0-127. When set to 0, the touch + * is reported as hovering (in-range) + * @return 1 if success. 0 if id is out-of-bounds + */ + inline int setFinger(uint8_t id, uint16_t x, uint16_t y, uint8_t pressure=100); + + /** + * Release finger in the internal data structure. You must call send + * manually after setting all fingers to flush them through USB. + * + * @param id Finger id. Must be in the range of 0-MAXFINGERS. Same finger + * must have same id throughout contact. Allocations does not need + * to be continuous. + * @return 1 if success. 0 if id is out-of-bounds + */ + inline int releaseFinger(uint8_t id); + + /** + * Generates an HID report reflecting the currently recorded touch status + * and send through USB. + */ + inline int send(); + + /// Send generated report. Needs to be implemented in a lower level + virtual int sendReport(void *report, int length) = 0; + inline int sendReport(HID_TouchscreenReport_Data_t &report); + +protected: + + /// Internal records of the current touch statuses. Status in this struct + /// is used only internally and differs from the one in the report + HID_Touchscreen_Finger_t _fingers[HID_TOUCHSCREEN_MAXFINGERS]; + /// Number of active contacts, including just release contacts + uint8_t _fingers_count; +}; + +// Implementation is inline +#include "TouchscreenAPI.hpp" diff --git a/src/HID-APIs/TouchscreenAPI.hpp b/src/HID-APIs/TouchscreenAPI.hpp new file mode 100644 index 00000000..99f833d5 --- /dev/null +++ b/src/HID-APIs/TouchscreenAPI.hpp @@ -0,0 +1,128 @@ +/* +Copyright (c) 2021 ilufang +See the readme for credit to other people. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +// Include guard +#pragma once + +enum _finger_status_t { + _MT_STATE_INACTIVE = 0, + _MT_STATE_CONTACT, + _MT_STATE_RELEASED +}; + +TouchscreenAPI::TouchscreenAPI() { + // Empty. Default zero initialization at loading. Do not manually instantiate +} + +void TouchscreenAPI::begin() { + end(); +} + +void TouchscreenAPI::end() { + for (int i = 0; i < HID_TOUCHSCREEN_MAXFINGERS; i++) { + releaseFinger(i); + } + send(); +} + +int TouchscreenAPI::setFinger(uint8_t id, uint16_t x, uint16_t y, uint8_t pressure) { + if (id >= HID_TOUCHSCREEN_MAXFINGERS) { + return 0; + } + if (_fingers[id].status == _MT_STATE_INACTIVE) { + _fingers_count++; + } + _fingers[id].status = _MT_STATE_CONTACT; + _fingers[id].pressure = pressure; + _fingers[id].x = x; + _fingers[id].y = y; + return 1; +} + +int TouchscreenAPI::releaseFinger(uint8_t id) { + if (id >= HID_TOUCHSCREEN_MAXFINGERS) { + return 0; + } + if (_fingers[id].status == _MT_STATE_CONTACT) { + _fingers[id].status = _MT_STATE_RELEASED; + } + return 1; +} + +int TouchscreenAPI::send() { + int ret = 0; + HID_TouchscreenReport_Data_t report; + + // Craft report(s) + report.count = _fingers_count; + + int rptentry=0; + for (int i = 0; i < HID_TOUCHSCREEN_MAXFINGERS; i++) { + if (_fingers[i].status == _MT_STATE_INACTIVE) { + continue; + } + + report.contacts[rptentry].identifier = i; // valid for first report only + + if (_fingers[i].status == _MT_STATE_RELEASED) { + // Released contacts need to be reported once with TipSW=0 + report.contacts[rptentry].touch = {}; + _fingers_count--; + _fingers[i].status = _MT_STATE_INACTIVE; + } else { + // Active contacts must be reported even when not moved + report.contacts[rptentry].touch.status = HID_TOUCHSCREEN_TOUCH_IN_RANGE; + if (_fingers[i].pressure > 0) { + report.contacts[rptentry].touch.status |= HID_TOUCHSCREEN_TOUCH_CONTACT; + } + report.contacts[rptentry].touch.x = _fingers[i].x; + report.contacts[rptentry].touch.y = _fingers[i].y; + report.contacts[rptentry].touch.pressure = _fingers[i].pressure; + } + + rptentry++; + if (rptentry == HID_TOUCHSCREEN_REPORTFINGERS) { + // Report full. Send now. + // If there are more contacts, they will be sent in subsequent + // reports with contact count set to 0 + // See "Hybrid Mode" on MSDN docs + ret += sendReport(report); + rptentry = 0; + report.count = 0; + } + } + + if (rptentry != 0) { + // Send remaining touches + for (; rptentry != HID_TOUCHSCREEN_REPORTFINGERS; rptentry++) { + report.contacts[rptentry] = {}; + } + ret += sendReport(report); + } + + return ret; +} + +int TouchscreenAPI::sendReport(HID_TouchscreenReport_Data_t &report) { + return sendReport(&report, sizeof(HID_TouchscreenReport_Data_t)); +} diff --git a/src/HID-Project.h b/src/HID-Project.h index 2685532f..cd67ad60 100644 --- a/src/HID-Project.h +++ b/src/HID-Project.h @@ -52,5 +52,6 @@ THE SOFTWARE. #include "SingleReport/SingleNKROKeyboard.h" #include "MultiReport/NKROKeyboard.h" #include "MultiReport/SurfaceDial.h" +#include "SingleReport/Touchscreen.h" // Include Teensy HID afterwards to overwrite key definitions if used diff --git a/src/SingleReport/Touchscreen.cpp b/src/SingleReport/Touchscreen.cpp new file mode 100644 index 00000000..d333b2c2 --- /dev/null +++ b/src/SingleReport/Touchscreen.cpp @@ -0,0 +1,200 @@ +/* +Copyright (c) 2021 ilufang +See the readme for credit to other people. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +#include "Touchscreen.h" + +// First part of the descriptor. It appears only once +static const uint8_t _hidReportDescriptorTouchscreen_1[] PROGMEM = { + 0x05, 0x0D, // USAGE_PAGE(Digitizers) + 0x09, 0x04, // USAGE (Touch Screen) + 0xA1, 0x01, // COLLECTION(Application) + + // define the actual amount of fingers that are concurrently touching the screen + 0x09, 0x54, // USAGE (Contact count) + 0x25, 0x7f, // LOGICAL_MAXIMUM (128) + 0x95, 0x01, // REPORT_COUNT(1) + 0x75, 0x08, // REPORT_SIZE (8) + 0x81, 0x02 // INPUT (Data,Var,Abs) +}; + +// Finger definition part of the descriptor. It will repeat REPORTFINGERS times, once for each finger. +// If more actual fingers are present than descriptor fingers, multiple reports will be sent sequentially under "Hybrid Mode" rule (See MSDN) +static const uint8_t _hidReportDescriptorTouchscreen_2[] PROGMEM = { + // declare a finger collection + 0x05, 0x0D, // USAGE_PAGE(Digitizers) + 0x09, 0x22, // USAGE (Finger) + 0xA1, 0x02, // COLLECTION (Logical) + + // declare an identifier for the finger + 0x09, 0x51, // USAGE (Contact Identifier) + 0x25, 0x7f, // LOGICAL_MAXIMUM (127) + 0x75, 0x08, // REPORT_SIZE (8) + 0x95, 0x01, // REPORT_COUNT (1) + 0x81, 0x02, // INPUT (Data,Var,Abs) + + // declare Tip Switch & In range + 0x09, 0x42, // USAGE (Tip Switch) + 0x09, 0x32, // USAGE (In Range) + 0x15, 0x00, // LOGICAL_MINIMUM (0) + 0x25, 0x01, // LOGICAL_MAXIMUM (1) + 0x75, 0x01, // REPORT_SIZE (1) + 0x95, 0x02, // REPORT_COUNT(2) + 0x81, 0x02, // INPUT (Data,Var,Abs) + // declare the 6 padding bits as constant so the driver will ignore them + 0x95, 0x06, // REPORT_COUNT (6) + 0x81, 0x03, // INPUT (Cnst,Ary,Abs) + + // declare pressure + 0x09, 0x30, // USAGE (Pressure) + 0x25, 0x7f, // LOGICAL_MAXIMUM (127) + 0x75, 0x08, // REPORT_SIZE (8) + 0x95, 0x01, // REPORT_COUNT (1) + 0x81, 0x02, // INPUT (Data,Var,Abs) + + // define absolute X and Y coordinates of 16 bit each (percent values multiplied with 100) + 0x05, 0x01, // USAGE_PAGE (Generic Desktop) + 0x09, 0x30, // Usage (X) + 0x09, 0x31, // Usage (Y) + 0x16, 0x00, 0x00, // Logical Minimum (0) + 0x26, 0x10, 0x27, // Logical Maximum (10000) + 0x36, 0x00, 0x00, // Physical Minimum (0) + 0x46, 0x10, 0x27, // Physical Maximum (10000) + 0x66, 0x00, 0x00, // UNIT (None) + 0x75, 0x10, // Report Size (16), + 0x95, 0x02, // Report Count (2), + 0x81, 0x02, // Input (Data,Var,Abs) + 0xC0 // END_COLLECTION +}; + +// Last of the descriptor. It appears only once +static const uint8_t _hidReportDescriptorTouchscreen_3[] PROGMEM = { + // define the maximum amount of fingers that the device supports + 0x05, 0x0D, // USAGE_PAGE(Digitizers) + 0x09, 0x55, // USAGE (Contact Count Maximum) + 0x25, 0x7f, // LOGICAL_MAXIMUM (127) + 0xB1, 0x02, // FEATURE (Data,Var,Abs) + + 0xC0 // END_COLLECTION +}; + + +Touchscreen_::Touchscreen_() : PluggableUSBModule(1, 1, epType), protocol(HID_REPORT_PROTOCOL), idle(1) { + _ccmFeature.contactCountMaximum = HID_TOUCHSCREEN_MAXFINGERS; + epType[0] = EP_TYPE_INTERRUPT_IN; + PluggableUSB().plug(this); +} + +int Touchscreen_::getInterface(uint8_t* interfaceCount) { + *interfaceCount += 1; // uses 1 + HIDDescriptor hidInterface = { + D_INTERFACE(pluggedInterface, 1, USB_DEVICE_CLASS_HUMAN_INTERFACE, HID_SUBCLASS_NONE, HID_PROTOCOL_NONE), + D_HIDREPORT( + sizeof(_hidReportDescriptorTouchscreen_1) + + (sizeof(_hidReportDescriptorTouchscreen_2) * HID_TOUCHSCREEN_REPORTFINGERS) + + sizeof(_hidReportDescriptorTouchscreen_3) + ), + D_ENDPOINT(USB_ENDPOINT_IN(pluggedEndpoint), USB_ENDPOINT_TYPE_INTERRUPT, USB_EP_SIZE, 0x01) + }; + return USB_SendControl(0, &hidInterface, sizeof(hidInterface)); +} + +int Touchscreen_::getDescriptor(USBSetup& setup) { + // Check if this is a HID Class Descriptor request + if (setup.bmRequestType != REQUEST_DEVICETOHOST_STANDARD_INTERFACE) { return 0; } + if (setup.wValueH != HID_REPORT_DESCRIPTOR_TYPE) { return 0; } + + // In a HID Class Descriptor wIndex cointains the interface number + if (setup.wIndex != pluggedInterface) { return 0; } + + // Reset the protocol on reenumeration. Normally the host should not assume the state of the protocol + // due to the USB specs, but Windows and Linux just assumes its in report mode. + protocol = HID_REPORT_PROTOCOL; + + // Transmit HID descriptor. See comments next to the descriptor parts + int ret = 0; + ret += USB_SendControl(TRANSFER_PGM, _hidReportDescriptorTouchscreen_1, sizeof(_hidReportDescriptorTouchscreen_1)); + for (int i = 0; i < HID_TOUCHSCREEN_REPORTFINGERS; i++) { + ret += USB_SendControl(TRANSFER_PGM, _hidReportDescriptorTouchscreen_2, sizeof(_hidReportDescriptorTouchscreen_2)); + } + ret += USB_SendControl(TRANSFER_PGM, _hidReportDescriptorTouchscreen_3, sizeof(_hidReportDescriptorTouchscreen_3)); + + return ret; +} + +bool Touchscreen_::setup(USBSetup& setup) { + if (pluggedInterface != setup.wIndex) { + return false; + } + + uint8_t request = setup.bRequest; + uint8_t requestType = setup.bmRequestType; + + if (requestType == REQUEST_DEVICETOHOST_CLASS_INTERFACE) + { + if (request == HID_GET_REPORT) { + if(setup.wValueH == HID_REPORT_TYPE_FEATURE){ + // The only feature is Contact Count Maximum + USB_SendControl(0, &_ccmFeature, sizeof(_ccmFeature)); + return true; + } + return true; + } + if (request == HID_GET_PROTOCOL) { + // TODO improve + return true; + } + } + + if (requestType == REQUEST_HOSTTODEVICE_CLASS_INTERFACE) + { + if (request == HID_SET_PROTOCOL) { + protocol = setup.wValueL; + return true; + } + if (request == HID_SET_IDLE) { + idle = setup.wValueH; + return true; + } + if (request == HID_SET_REPORT) { + return true; + } + } + + return false; +} + +uint8_t Touchscreen_::getProtocol() { + return protocol; +} + +int Touchscreen_::sendReport(void *report, int length) { + return USB_Send(pluggedEndpoint | TRANSFER_RELEASE, report, length); +} + +void Touchscreen_::wakeupHost() { +#ifdef __AVR__ + USBDevice.wakeupHost(); +#endif +} + +Touchscreen_ Touchscreen; diff --git a/src/SingleReport/Touchscreen.h b/src/SingleReport/Touchscreen.h new file mode 100644 index 00000000..2f8a142d --- /dev/null +++ b/src/SingleReport/Touchscreen.h @@ -0,0 +1,58 @@ +/* +Copyright (c) 2021 ilufang +See the readme for credit to other people. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +// Include guard +#pragma once + +#include +#include "HID.h" +#include "HID-Settings.h" +#include "../HID-APIs/TouchscreenAPI.h" + + +class Touchscreen_ : public PluggableUSBModule, public TouchscreenAPI +{ +public: + Touchscreen_(); + uint8_t getProtocol(); + void wakeupHost(); + + virtual int sendReport(void *report, int length) final; + +protected: + + // Implementation of the PUSBListNode + int getInterface(uint8_t* interfaceCount); + int getDescriptor(USBSetup& setup); + bool setup(USBSetup& setup); + + EPTYPE_DESCRIPTOR_SIZE epType[1]; + uint8_t protocol; + uint8_t idle; + + struct ATTRIBUTE_PACKED { + uint8_t contactCountMaximum; + } _ccmFeature; +}; + +extern Touchscreen_ Touchscreen;