Skip to content

PluggableUSB and PluggableHID howto

Martino Facchin edited this page Oct 14, 2015 · 9 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

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 implement a PluggableUSBModule, so the constructor will look like

/* we need 2 endpoints and 2 interfaces */
MIDI_::MIDI_(void) : PluggableUSBModule(2, 2, epType)
{
	epType[0] = EP_TYPE_BULK_OUT_MIDI;	// MIDI_ENDPOINT_OUT
	epType[1] = EP_TYPE_BULK_IN_MIDI;	// MIDI_ENDPOINT_IN
	PluggableUSB().plug(this);
}

The PluggableUSBModule must implement setup, getInterface and getDescriptor functions and declare how many endpoints and interfaces it needs to allocate

setup function signature is bool setup(USBSetup& usb_setup); 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)
{
	if (pluggedInterface != setup.wIndex) {
		return false;
	}

	uint8_t request = setup.bRequest;
	uint8_t requestType = setup.bmRequestType;
        etc etc...

getDescriptor function signature is int getDescriptor(USBSetup& setup); it is expected to return the number of bytes sent if the request was directed to the module, 0 if the request has not been served or -1 if errors has been encountered.

If no device descriptor is required, simply return 0

< examples from HID.cpp - simplified >

int HID_::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; }

	int total = 0;
	HIDSubDescriptor* node;
	for (node = rootNode; node; node = node->next) {
		int res = USB_SendControl(TRANSFER_PGM, node->data, node->length);
		if (res == -1)
			return -1;
		total += res;
	}
	return total;
}

getInterface function signature is int getInterface(uint8_t* interfaceCount); it is expected to return the number of bytes sent and increment the interfaceNum variable with the number of interfaces used.

< examples from HID.cpp - simplified >

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_HIDREPORT(descriptorSize),
		D_ENDPOINT(USB_ENDPOINT_IN(pluggedEndpoint), USB_ENDPOINT_TYPE_INTERRUPT, USB_EP_SIZE, 0x01)
	};
	return USB_SendControl(0, &hidInterface, sizeof(hidInterface));
}

Everything is configured, so calling PluggableUSB().plug(this) 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(pluggedEndpoint, data, len);

Writing a library as a PluggableHID Module

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) : _buttons(0)
{
    static HIDSubDescriptor node(_hidReportDescriptor, sizeof(_hidReportDescriptor));
    HID().AppendDescriptor(&node);
}

And you are done 😄