Skip to content

Commit

Permalink
Enhanced HID protocol support for Arduino Leonardo & Pro Micro
Browse files Browse the repository at this point in the history
  • Loading branch information
abratchik authored and abratchik committed Jun 1, 2021
1 parent 60f0d0b commit 87a2b7a
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 41 deletions.
1 change: 1 addition & 0 deletions cores/arduino/USBAPI.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion cores/arduino/USBCore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
151 changes: 132 additions & 19 deletions libraries/HID/src/HID.cpp
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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; }
Expand All @@ -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)
Expand All @@ -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 && i<reportCount; current=current->next, 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) {
Expand All @@ -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.
Expand All @@ -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);
}

Expand Down
87 changes: 66 additions & 21 deletions libraries/HID/src/HID.h
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -21,7 +22,8 @@

#include <stdint.h>
#include <Arduino.h>
#include "PluggableUSB.h"
#include <HardwareSerial.h>
#include <PluggableUSB.h>

#if defined(USBCON)

Expand Down Expand Up @@ -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
Expand All @@ -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;
Expand All @@ -91,34 +113,57 @@ 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.
// This function prevents static-initialization-order-fiasco
// 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

Expand Down

0 comments on commit 87a2b7a

Please sign in to comment.