From 87a2b7acb619eb81b4fe51822199c36a899eff26 Mon Sep 17 00:00:00 2001 From: abratchik Date: Sat, 16 Jan 2021 16:20:37 +0400 Subject: [PATCH] Enhanced HID protocol support for Arduino Leonardo & Pro Micro --- cores/arduino/USBAPI.h | 1 + cores/arduino/USBCore.cpp | 2 +- libraries/HID/src/HID.cpp | 151 +++++++++++++++++++++++++++++++++----- libraries/HID/src/HID.h | 87 ++++++++++++++++------ 4 files changed, 200 insertions(+), 41 deletions(-) diff --git a/cores/arduino/USBAPI.h b/cores/arduino/USBAPI.h index 701a14f78..aa3ae0080 100644 --- a/cores/arduino/USBAPI.h +++ b/cores/arduino/USBAPI.h @@ -196,6 +196,7 @@ bool CDC_Setup(USBSetup& setup); int USB_SendControl(uint8_t flags, const void* d, int len); int USB_RecvControl(void* d, int len); int USB_RecvControlLong(void* d, int len); +bool USB_SendStringDescriptor(const u8*, u8, uint8_t); uint8_t USB_Available(uint8_t ep); uint8_t USB_SendSpace(uint8_t ep); diff --git a/cores/arduino/USBCore.cpp b/cores/arduino/USBCore.cpp index dc6bc387e..14af75f70 100644 --- a/cores/arduino/USBCore.cpp +++ b/cores/arduino/USBCore.cpp @@ -426,7 +426,7 @@ int USB_SendControl(u8 flags, const void* d, int len) // Send a USB descriptor string. The string is stored in PROGMEM as a // plain ASCII string but is sent out as UTF-16 with the correct 2-byte // prefix -static bool USB_SendStringDescriptor(const u8*string_P, u8 string_len, uint8_t flags) { +bool USB_SendStringDescriptor(const u8*string_P, u8 string_len, uint8_t flags) { SendControl(2 + string_len * 2); SendControl(3); bool pgm = flags & TRANSFER_PGM; diff --git a/libraries/HID/src/HID.cpp b/libraries/HID/src/HID.cpp index 21ede269e..ac2fa988d 100644 --- a/libraries/HID/src/HID.cpp +++ b/libraries/HID/src/HID.cpp @@ -1,6 +1,7 @@ /* Copyright (c) 2015, Arduino LLC Original code (pre-library): Copyright (c) 2011, Peter Barrett + Modified code: Copyright (c) 2020, Aleksandr Bratchik Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the @@ -30,18 +31,35 @@ int HID_::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_INTERFACE(pluggedInterface, 2, USB_DEVICE_CLASS_HUMAN_INTERFACE, HID_SUBCLASS_NONE, HID_PROTOCOL_NONE), D_HIDREPORT(descriptorSize), - D_ENDPOINT(USB_ENDPOINT_IN(pluggedEndpoint), USB_ENDPOINT_TYPE_INTERRUPT, USB_EP_SIZE, 0x01) + D_ENDPOINT(USB_ENDPOINT_IN(HID_TX), USB_ENDPOINT_TYPE_INTERRUPT, USB_EP_SIZE, 0x14), + D_ENDPOINT(USB_ENDPOINT_OUT(HID_RX), USB_ENDPOINT_TYPE_INTERRUPT, USB_EP_SIZE, 0x0A) }; return USB_SendControl(0, &hidInterface, sizeof(hidInterface)); } int HID_::getDescriptor(USBSetup& setup) { + + u8 t = setup.wValueH; + + // HID-specific strings + if(USB_STRING_DESCRIPTOR_TYPE == t) { + + // we place all strings in the 0xFF00-0xFFFE range + HIDReport* rep = GetFeature(0xFF00 | setup.wValueL ); + if(rep) { + return USB_SendStringDescriptor((u8*)rep->data, strlen_P((char*)rep->data), TRANSFER_PGM); + } + else { + return 0; + } + } + // 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; } + if (HID_REPORT_DESCRIPTOR_TYPE != t) { return 0; } // In a HID Class Descriptor wIndex cointains the interface number if (setup.wIndex != pluggedInterface) { return 0; } @@ -64,12 +82,24 @@ int HID_::getDescriptor(USBSetup& setup) uint8_t HID_::getShortName(char *name) { + if(serial) { + byte seriallen=min(strlen_P(serial), ISERIAL_MAX_LEN); + for(byte i=0; i< seriallen; i++) { + name[i] = pgm_read_byte_near(serial + i); + } + return seriallen; + } + else { + + // default serial number + name[0] = 'H'; name[1] = 'I'; name[2] = 'D'; name[3] = 'A' + (descriptorSize & 0x0F); name[4] = 'A' + ((descriptorSize >> 4) & 0x0F); return 5; + } } void HID_::AppendDescriptor(HIDSubDescriptor *node) @@ -86,28 +116,102 @@ void HID_::AppendDescriptor(HIDSubDescriptor *node) descriptorSize += node->length; } -int HID_::SendReport(uint8_t id, const void* data, int len) +int HID_::SetFeature(uint16_t id, const void* data, int len) { - auto ret = USB_Send(pluggedEndpoint, &id, 1); + if(!rootReport) { + rootReport = new HIDReport(id, data, len); + } else { + HIDReport* current; + int i=0; + for ( current = rootReport; current; current = current->next, i++) { + if(current->id == id) { + return i; + } + // check if we are on the last report + if(!current->next) { + current->next = new HIDReport(id, data, len); + break; + } + } + + } + + reportCount++; + return reportCount; +} + +int HID_::SetStringFeature(uint8_t id, const uint8_t* index, const char* data) { + + int res = SetFeature(id, index, 1); + + if(res == 0) return 0; + + res += SetFeature(0xFF00 | *index , data, strlen_P(data)); + + return res; +} + +bool HID_::LockFeature(uint16_t id, bool lock) { + if(rootReport) { + HIDReport* current; + for(current = rootReport;current; current=current->next) { + if(current->id == id) { + current->lock = lock; + return true; + } + } + } + return false; +} + + +int HID_::SendReport(uint16_t id, const void* data, int len) +{ + auto ret = USB_Send(HID_TX, &id, 1); if (ret < 0) return ret; - auto ret2 = USB_Send(pluggedEndpoint | TRANSFER_RELEASE, data, len); + auto ret2 = USB_Send(HID_TX | TRANSFER_RELEASE, data, len); if (ret2 < 0) return ret2; return ret + ret2; } -bool HID_::setup(USBSetup& setup) +HIDReport* HID_::GetFeature(uint16_t id) { + HIDReport* current; + int i=0; + for(current=rootReport; current && inext, i++) { + if(id == current->id) { + return current; + } + } + return (HIDReport*) NULL; +} + +bool HID_::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) { - // TODO: HID_GetReport(); + + if(setup.wValueH == HID_REPORT_TYPE_FEATURE) + { + + HIDReport* current = GetFeature(setup.wValueL); + if(current){ + if(USB_SendControl(0, &(current->id), 1)>0 && + USB_SendControl(0, current->data, current->length)>0) + return true; + } + + return false; + + } return true; } if (request == HID_GET_PROTOCOL) { @@ -120,7 +224,7 @@ bool HID_::setup(USBSetup& setup) } if (requestType == REQUEST_HOSTTODEVICE_CLASS_INTERFACE) - { + { if (request == HID_SET_PROTOCOL) { // The USB Host tells us if we are in boot or report mode. // This only works with a real boot compatible device. @@ -133,24 +237,33 @@ bool HID_::setup(USBSetup& setup) } if (request == HID_SET_REPORT) { - //uint8_t reportID = setup.wValueL; - //uint16_t length = setup.wLength; - //uint8_t data[length]; - // Make sure to not read more data than USB_EP_SIZE. - // You can read multiple times through a loop. - // The first byte (may!) contain the reportID on a multreport. - //USB_RecvControl(data, length); + if(setup.wValueH == HID_REPORT_TYPE_FEATURE) + { + + HIDReport* current = GetFeature(setup.wValueL); + if(!current) return false; + if(setup.wLength != current->length + 1) return false; + uint8_t* data = new uint8_t[setup.wLength]; + USB_RecvControl(data, setup.wLength); + if(*data != current->id) return false; + memcpy((uint8_t*)current->data, data+1, current->length); + delete[] data; + return true; + + } + } } return false; } -HID_::HID_(void) : PluggableUSBModule(1, 1, epType), +HID_::HID_(void) : PluggableUSBModule(2, 1, epType), rootNode(NULL), descriptorSize(0), protocol(HID_REPORT_PROTOCOL), idle(1) { epType[0] = EP_TYPE_INTERRUPT_IN; + epType[1] = EP_TYPE_INTERRUPT_OUT; PluggableUSB().plug(this); } diff --git a/libraries/HID/src/HID.h b/libraries/HID/src/HID.h index 93c4bd5b4..94f958db6 100644 --- a/libraries/HID/src/HID.h +++ b/libraries/HID/src/HID.h @@ -1,6 +1,7 @@ /* Copyright (c) 2015, Arduino LLC Original code (pre-library): Copyright (c) 2011, Peter Barrett + Modified code: Copyright (c) 2020, Aleksandr Bratchik Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the @@ -21,7 +22,8 @@ #include #include -#include "PluggableUSB.h" +#include +#include #if defined(USBCON) @@ -59,6 +61,14 @@ #define HID_REPORT_TYPE_OUTPUT 2 #define HID_REPORT_TYPE_FEATURE 3 +#define HID_INTERFACE (CDC_ACM_INTERFACE + CDC_INTERFACE_COUNT) // HID Interface +#define HID_FIRST_ENDPOINT (CDC_FIRST_ENDPOINT + CDC_ENPOINT_COUNT) +#define HID_ENDPOINT_INT (HID_FIRST_ENDPOINT) +#define HID_ENDPOINT_OUT (HID_FIRST_ENDPOINT+1) + +#define HID_TX HID_ENDPOINT_INT +#define HID_RX HID_ENDPOINT_OUT //++ EP HID_RX for ease of use with USB_Available & USB_Rec + typedef struct { uint8_t len; // 9 @@ -77,12 +87,24 @@ typedef struct InterfaceDescriptor hid; HIDDescDescriptor desc; EndpointDescriptor in; + EndpointDescriptor out; //added } HIDDescriptor; +class HIDReport { +public: + HIDReport *next = NULL; + HIDReport(uint16_t i, const void *d, uint8_t l) : id(i), data(d), length(l) {} + + uint16_t id; + const void* data; + uint16_t length; + bool lock; +}; + class HIDSubDescriptor { public: HIDSubDescriptor *next = NULL; - HIDSubDescriptor(const void *d, const uint16_t l) : data(d), length(l) { } + HIDSubDescriptor(const void *d, uint16_t l) : data(d), length(l) { } const void* data; const uint16_t length; @@ -91,26 +113,49 @@ class HIDSubDescriptor { class HID_ : public PluggableUSBModule { public: - HID_(void); - int begin(void); - int SendReport(uint8_t id, const void* data, int len); - void AppendDescriptor(HIDSubDescriptor* node); - + HID_(void); + int begin(void); + int SendReport(uint16_t id, const void* data, int len); + int SetFeature(uint16_t id, const void* data, int len); + int SetStringFeature(uint8_t id, const uint8_t* index, const char* data); + bool LockFeature(uint16_t id, bool lock); + + void AppendDescriptor(HIDSubDescriptor* node); + + void setOutput(Serial_& out) { + dbg = &out; + } + + void setSerial(const char* s) { + serial = s; + } + + HIDReport* GetFeature(uint16_t id); + protected: - // Implementation of the PluggableUSBModule - int getInterface(uint8_t* interfaceCount); - int getDescriptor(USBSetup& setup); - bool setup(USBSetup& setup); - uint8_t getShortName(char* name); - + // Implementation of the PluggableUSBModule + int getInterface(uint8_t* interfaceCount); + int getDescriptor(USBSetup& setup); + bool setup(USBSetup& setup); + uint8_t getShortName(char* name); + private: - uint8_t epType[1]; - - HIDSubDescriptor* rootNode; - uint16_t descriptorSize; - - uint8_t protocol; - uint8_t idle; + uint8_t epType[2]; + + HIDSubDescriptor* rootNode; + uint16_t descriptorSize; + + uint8_t protocol; + uint8_t idle; + + // Buffer pointer to hold the feature data + HIDReport* rootReport; + uint16_t reportCount; + + Serial_ *dbg; + + const char *serial; + }; // Replacement for global singleton. @@ -118,7 +163,7 @@ class HID_ : public PluggableUSBModule // https://isocpp.org/wiki/faq/ctors#static-init-order-on-first-use HID_& HID(); -#define D_HIDREPORT(length) { 9, 0x21, 0x01, 0x01, 0, 1, 0x22, lowByte(length), highByte(length) } +#define D_HIDREPORT(length) { 9, 0x21, 0x01, 0x01, 0x21, 1, 0x22, lowByte(length), highByte(length) } #endif // USBCON