Skip to content

PluggableUSB and PluggableHID howto

Martino Facchin edited this page Jul 21, 2015 · 12 revisions

##An example is worth a thousand words

[MIDIUSB] (https://github.com/arduino-libraries/MIDIUSB) or [HID] (https://github.com/arduino/Arduino/tree/master/hardware/arduino/avr/libraries/HID) libraries are based on PluggableUSB.

[Keyboard] (https://github.com/arduino/Arduino/tree/master/libraries/Keyboard) and [Mouse] (https://github.com/arduino/Arduino/tree/master/libraries/Mouse) libraries are based on PluggableHID and are bundled with the IDE

##Writing a library as a PluggableUSB Module ####(AVR only at the moment)

If you wish to write a library that exposes low-level USB functionality like MIDI or Mass Storage you can take advantage of the new PluggableUSB core.

Attaching to this framework is very simple: first of all your library needs to include PluggableUSB header:

#include "PluggableUSB.h"

Then, you need to declare your endpoints as non-const variables, as their value will be decided at runtime (before main() execution)

static u8 MIDI_ENDPOINT_OUT;
static u8 MIDI_ENDPOINT_IN;

Then, in the constructor, declare something like this:

static uint8_t endpointType[2];

endpointType[0] = EP_TYPE_BULK_OUT_MIDI;	// MIDI_ENDPOINT_OUT	
endpointType[1] = EP_TYPE_BULK_IN_MIDI;		// MIDI_ENDPOINT_IN	

static PUSBCallbacks cb = {
	.setup = &MIDI_Setup,
	.getInterface = &MIDI_GetInterface,
	.getDescriptor = &MIDI_GetDescriptor,
	.numEndpoints = 2,
	.numInterfaces = 2,
	.endpointType = endpointType,
};

static PUSBListNode node(&cb);

MIDI_ENDPOINT_OUT = PUSB_AddFunction(&node, &MIDI_AC_INTERFACE);
MIDI_ENDPOINT_IN =  MIDI_ENDPOINT_OUT + 1;
MIDI_INTERFACE = MIDI_AC_INTERFACE + 1;

The PUSBCallbacks struct contains hooks for the setup, getInterface and getDescriptor functions, plus a couple of informations about the required endpoints.

setup function signature is bool setup(USBSetup& usb_setup, u8 i); it is expected to return true if the request was directed to the module and executed correctly, false otherwise.

If no setup phase is required, simply return false

< examples from HID.cpp - simplified >

bool HID_Setup(USBSetup& setup, u8 i)
{
	if (HID_INTERFACE != i) {
		return false;
	} else {
		u8 r = setup.bRequest;
		u8 requestType = setup.bmRequestType;
		if (REQUEST_DEVICETOHOST_CLASS_INTERFACE == requestType)
		{
			if (HID_GET_REPORT == r)
			{
			//HID_GetReport();
				return true;
			}
		etc etc...

getDescriptor function signature is int getDescriptor(int8_t t); it is expected to return the number of bytes sent if the request was directed to the module, 0 otherwise.

If no device descriptor is required, simply return 0

< examples from HID.cpp - simplified >

int HID_GetDescriptor(int8_t t)
{
	if (HID_REPORT_DESCRIPTOR_TYPE == t) {
		return USB_SendControl(TRANSFER_PGM,_hidReportDescriptor,sizeof(_hidReportDescriptor()));
	} else {
		return 0;
	}
}

getInterface function signature is int getInterface(u8* interfaceNum); it is expected to return the number of bytes sent and increment the interfaceNum variable with the number of interfaces used. This function is compulsory and your module won't work if it's not present

< examples from HID.cpp - simplified >

int HID_GetInterface(u8* interfaceNum)
{
	interfaceNum[0] += 1;	// uses 1
	return USB_SendControl(0,&_hidInterface,sizeof(_hidInterface));
}

Everything is configured, so calling PUSB_AddFunction will do all the magic.

Your library will be plugged when the constructor is called, so you need to pre-instantiate a singleton for your class to make sure to be already plugged when main() is executed.

Then you can perform USB writes calling, for example,

USB_Send(HID_ENDPOINT_INT,data,len);

Writing a library as a PluggableHID Module

####(AVR only at the moment)

If you are interested in writing a library for a specific HID peripheral (Mouse, Keyboard, Touchscreen, Gamepad etc) you can take advantage of the PluggableHID core. No need to explore the darkest corners of USB specifications!

In your library

#include "HID.h" 

in your header file.

If the core you are targeting is pluggable-ready, _USING_HID will be defined.

#ifndef MOUSE_h
#define MOUSE_h

#include "HID.h"

#if !defined(_USING_HID)

#warning "Using legacy HID core (non pluggable)"

#else

etc etc .....

In the cpp file, add a const report descriptor of your choice

static const u8 _hidReportDescriptor[] PROGMEM = {
  
  //  Mouse
  0x05, 0x01,     // USAGE_PAGE (Generic Desktop)  // 54
  0x09, 0x02,     // USAGE (Mouse)
  .
  . etc etc
  .

  }

In the constructor call add the following snippet

Mouse_::Mouse_(void) 
{
    const static HID_Descriptor cb = {
        .length = sizeof(_hidReportDescriptor),
        .descriptor = _hidReportDescriptor,
    };
    static HIDDescriptorListNode node(&cb);
    HID.AppendDescriptor(&node);
}

And you are done 😄