Skip to content

USB++ a C++14 template library for handy descriptor definition

Overview

USB++ (usbplusplus) is a C++14 template library that gives the user a simple and error-prone way for defining USB descriptors, suitable for embedded projects.

Motivation

Despite C++ is not in favor among embedded developers for historical reasons, the existing cross-compilers already provide good support for new versions of C++ and there is a noticeable trend in community driven project, such as Arduino, Particle and others, for wider use of C++ in embedded projects. However, as it seems, there is no open source C++ library that would address topic of USB descriptors. This project makes an attempt to fill this lacuna

Goals

This project aims creation of a C++ template library that would assist the user in creation USB descriptors with the following characteristics:

  1. verbose and easy to read sources
  2. as close as possible to the published specifications
  3. resistant to developer’s errors
  4. type-safe data fields
  5. implicit endianness handling
  6. compiler-assisted string index lookup
  7. compiler-assisted entries replications

Design Rationales

1. Fixed length descriptors are implemented as C++ structures

2. Variable length descriptors, such as Configuration, implemented as templates, accepting a collection of data types as its parameters. Two data type collections are provided:

  1. Array – a homogenous collection of given type and length
  2. List – heterogeneous collection of given data types (implemented up to 8 items).

3. Every field is declared with it specific type. This approach allows to handle endiannes of multi-byte fields and bit-level defined fields

4. All members that, for a given descriptor instance have only one valid value (such as bLength, bDescriptorType, bNumInterfaces, etc.), are encapsulated in a class-depended type with default constructor using the right value.

5. Bitmasks with enumerated subfileds values are first defined as one or more enums and then those enums are used for constructing the bitmask value.

6. Strings have two dedicated data types: compile-time strings (UTF-16, native endian, fixed length of 64 characters) and run-time (UTF-16, little endian, fixed length of N characters, per length of the source string)

7. String resources (collection of all string descriptors) have two forms – monolingual and multilingual and are implemented as templates
7.1. Monolingual template accepts a language ID and a list of compile time string variables
7.2. Multilingual template accepts a template-list of language IDs and a list of lists of compile time string variables
7.3. Both these templates expose indexof method, returning one-based index of a string in the collection of string descriptors. This method is evaluated at compile time and can be used to set values in other descriptors, eliminating the need of maintaining index/string correspondence.
7.4. Also, these templates implement method get that returns pointer to a string descriptor, including String Descriptor Zero.

Results

The USB++ library is available on github: https://github.com/hutorny/usbplusplus and licensed under MIT License.

Usage

USB++ is a header-only library. To use include usbplusplus.hpp, instantiate and initialize descriptors defined in the library.

Fixed length descriptors

Define a constant variable of a descriptor type and provide an initializer, as in this example:

#include "usbplusplus.hpp"
using namespace usbplusplus;
using namespace usbplusplus::usb2;

const Device deviceDescriptor = {
	.bLength 		= {},
	.bDescriptorType 	= {},
	.bcdUsb 		= 2.00_bcd,
	.bDeviceClass 	= DeviceClass::Defined_in_the_Interface_Descriptors,
	.bDeviceSubClass 	= 0,
	.bDeviceProtocol 	= 0,
	.bMaxPacketSize0 	= MaxPacketSize0_t::_64,
	.idVendor 		= 0x0102,
	.idProduct 		= 0x0304,
	.bcdDevice 		= 1.00_bcd,
	.iManufacturer 	= 0,
	.iProduct 		= 0,
	.iSerialNumber 	= 0,
	.bNumConfigurations = 1 
};
Or, as in this:
const Device deviceDescriptor = {
	Length<Device>(),
	DescriptorType<Device>(),
	2.00_bcd,
	DeviceClass::Defined_in_the_Interface_Descriptors,
	DeviceSubClass(0),
	DeviceProtocol(0),
	MaxPacketSize0_t::_64,
	IDVendor(0x0102),
	IDProduct(0x0304),
	1.00_bcd,
	Manufacturer(0),
	Product(0),
	SerialNumber(0),
	NumConfigurations(1)
};
Note: fields bLength and bDescriptor are initialized with the default value.

Variable length descriptors

Define the descriptor type with a template, such as Interface or Configuration, providing types of nested descriptors via variadic template List, plain template Array, or stub Empty. Instantiate a descriptor of that time and provide initializer.
For example:

#include "usbplusplus.hpp"
using namespace usbplusplus;
using namespace usbplusplus::usb2;
using MyInterface1 = Interface<Empty>;
using MyInterface2 = Interface<List<Endpoint>>;

const Configuration<List<MyInterface1,MyInterface2>> myConfiguration = {
		.bLength = {},
		.bDescriptorType = {},
		.wTotalLength = {},
		.bNumInterfaces = {},
		.bConfigurationValue = 1,
		.iConfiguration = 1,
		.bmAttributes = ConfigurationCharacteristics_t::Self_powered,
		.bMaxPower = 100_mA,
		{{
			.bLength 		= {},
			.bDescriptorType 	= {},
			.bInterfaceNumber	= 0,
			.bAlternateSetting	= 0,
			.bNumEndpoints 	= {},
			InterfaceClass::CDC,
			.bInterfaceSubClass = 0,
			.bInterfaceProtocol = 0,
			.iInterface 		= 0
		},
		{
			.bLength 		= {},
			.bDescriptorType	= {},
			.bInterfaceNumber	= 1,
			.bAlternateSetting	= 0,
			.bNumEndpoints	= {},
			InterfaceClass::CDC,
			.bInterfaceSubClass	= 0,
			.bInterfaceProtocol	= 0,
			.iInterface 		= 1,
			{{
				.bLength	= {},
				.bDescriptorType= {},
				EndpointAddress(1, EndpointDirection_t::IN),
				.bmAttributes		= TransferType_t::Isochronous,
				.wMaxPacketSize	= 512,
				.bInterval		= 1,
			}}
		}}
};
Note: nested items are enclosed in with outer curve brackets { }. The examples emphasize it with {{ … }}

Replicating descriptors

Sometimes, there is a need to define a sequence of similar descriptors that differs only in some fields. With C++ this task can be accomplished with a consexpr factory function, returning descriptors.
For example:

constexpr MyInterface2 myInterface(InterfaceNumber interfaceNumber,
		uint8_t endpointNumber) {
	return  MyInterface2 {
		.bLength 			= {},
		.bDescriptorType	= {},
		.bInterfaceNumber	= interfaceNumber,
		.bAlternateSetting	= 1,
		.bNumEndpoints = {},
		InterfaceClass::CDC,
		.bInterfaceSubClass = 0,
		.bInterfaceProtocol = 0,
		.iInterface 		= MyStrings::indexof(sInterface),
		{{
			.bLength		= {},
			.bDescriptorType= {},
			EndpointAddress(endpointNumber, EndpointDirection_t::IN),
			.bmAttributes = TransferType_t::Isochronous,
			.wMaxPacketSize = 512,
			.bInterval		= 1,
		}}
	};
}

String Resources

All strings in the device has to be declared upfront, as constexpr ustring variables;
For example:

#include "usbplusplus.hpp"
using namespace usbplusplus;
using namespace usbplusplus::usb2;

constexpr ustring sManufacturer = u"MegaCool Corp.";
constexpr ustring sProduct      = u"SuperPuper device";
constexpr ustring sInterface    = u"Interface";
constexpr ustring sSerialNumber = u"SN-12C55F2";

Once the strings are declared, they can be used in the definition of string resources.

Monolingual resources

If support for multiple languages is not needed, the string resources can be defined with the Strings template.
For example:

using MyStrings = Strings<LanguageIdentifier::English_United_States,
	sManufacturer,
	sProduct,
	sInterface,
	sSerialNumber>;

Multilingual resources

Define strings for every language the same way:

constexpr ustring deManufacturer = u"MegaKool Korp.";
constexpr ustring deProduct      = u"SuperPuper Gerät";
constexpr ustring deInterface    = u"Das Interface";
constexpr ustring uaManufacturer = u"МегаКруть корпорація";
constexpr ustring uaProduct      = u"СуперПупер пристрій";
constexpr ustring uaInterface    = u"Інтерфейс";
Then define the string resources with MultiStrings and Strings templates, as in the following example:
using MyStrings = MultiStrings<
	Strings<LanguageIdentifier::English_United_States,
					sManufacturer,  sProduct,  sInterface,  sSerialNumber>,
	Strings<LanguageIdentifier::English_United_Kingdom,
					sManufacturer,  sProduct,  sInterface,  sSerialNumber>,
	Strings<LanguageIdentifier::English_Canadian,
					sManufacturer,  sProduct,  sInterface,  sSerialNumber>,
	Strings<LanguageIdentifier::German_Standard,
					deManufacturer, deProduct, deInterface, sSerialNumber>,
	Strings<LanguageIdentifier::Ukrainian,
					uaManufacturer, uaProduct, uaInterface, sSerialNumber>
>;
where each Strings defines resources for given language.

String index

Both Strings and MultiStrings templates implement indexof method that returns one-based index of a string, passed as a parameter.
Note: MultiStrings just forwards the call to the first Strings in the list.

String descriptor

Strings and MultiStrings templates implement method get(index, lang) that returns a pointer to the string descriptor. If index is equal zero, get returns pointer to String Descriptor Zero with a list of supported languages.

Post a Comment

Your email is never published nor shared. Required fields are marked *
*
*

*