Skip to content

Enhanced HID protocol support for Arduino Leonardo & Pro Micro #387

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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