The USB HID Descriptor Compile Time Compiler can be
used to define USB HID descriptors without any runtime overhead using a
domain specific language and the constexpr
feature in C++14.
- USB HID complaint descriptor encoding
- zero runtime overhead
- parameterizable fields and values
- easy to understand text based declarative language
- compile time syntax and sanity checks
- single, dependency free header file (only requires
stdint.h
andstddef.h
) - PlatformIO compatible
- WASM compatible (see playground below)
Try out the HidDescCTC with C++ here:
https://godbolt.org
The implemented compiler and syntax can also be tried out online using WebAssembly:
https://daniel-starke.github.io/HidDescCTC
This is available as progressive web application (PWA).
Note that all user parameters are replaced with the value 0
here.
The used syntax is very close to the one used in the standard and by the HID Descriptor Tool.
Apply the following rules to get the HidDescCTC usage name from the name in the standard:
- replace leading
+
byPlus
- replace
/second/second
byPerSecondSquared
- remove all non-alphanumeric characters like space and underscore characters
- capitalize words/abbreviations, whereas dimensions count as one word (e.g. Usb3dControl)
- move words with a leading digit behind the first word
- remove second key meaning for the keyboard/keypad usage table entries
- replace non-alphanumeric characters of the keyboard/keypad usage table entries with their spelled out names
Example:
DEF_HID_DESCRIPTOR_AS(
static hidDesc,
(R"(
# Example based on HID 1.11 appendix B.1
UsagePage(GenericDesktop)
Usage(Keyboard)
Collection(Application)
ReportId({id})
ReportSize(1)
ReportCount(8)
UsagePage(Keyboard)
UsageMinimum(224)
UsageMaximum(231)
LogicalMinimum(0)
LogicalMaximum(1)
Input(Data, Var, Abs) # Modifier byte
ReportCount(1)
ReportSize(8)
Input(Cnst) # Reserved byte
ReportCount(5)
ReportSize(1)
UsagePage(LED)
UsageMinimum(1)
UsageMaximum({maxLedId})
Output(Data, Var, Abs) # LED report
ReportCount(1)
ReportSize(3)
Output(Cnst) # LED report padding
ReportCount(6)
ReportSize(8)
LogicalMinimum(0)
LogicalMaximum(255)
UsagePage(Keyboard)
UsageMinimum(0)
UsageMaximum(255)
Input(Data, Ary)
EndCollection
)")
("id", 1)
("maxLedId", 5)
);
This provides the compiled HID descriptor with:
hidDesc.data
asconst uint8_t *
pointing to the compiled datahidDesc.size()
assize_t
with the size of the compiled data
Any number of parameters can be passed and used as seen above.
These, however, need to be compile time evaluable.
Usually, report IDs are defined via enum
and included in the
HID descriptor accordingly for later use in the protocol. These
can be included as parameters in the HID descriptor source.
Define HID_DESCRIPTOR_NO_ERROR_REPORT
to suppress syntax error outputs.
DEF_HID_DESCRIPTOR_AS
can be used in global, namespace and function scope.
If you rather like to avoid using the macro you can compile the HID descriptor like this:
constexpr static const auto hidSrc = hid::fromSource(R"(
# Example based on HID 1.11 appendix B.1
UsagePage(GenericDesktop)
Usage(Keyboard)
Collection(Application)
ReportId({id})
ReportSize(1)
ReportCount(8)
UsagePage(Keyboard)
UsageMinimum(224)
UsageMaximum(231)
LogicalMinimum(0)
LogicalMaximum(1)
Input(Data, Var, Abs) # Modifier byte
ReportCount(1)
ReportSize(8)
Input(Cnst) # Reserved byte
ReportCount(5)
ReportSize(1)
UsagePage(LED)
UsageMinimum(1)
UsageMaximum({maxLedId})
Output(Data, Var, Abs) # LED report
ReportCount(1)
ReportSize(3)
Output(Cnst) # LED report padding
ReportCount(6)
ReportSize(8)
LogicalMinimum(0)
LogicalMaximum(255)
UsagePage(Keyboard)
UsageMinimum(0)
UsageMaximum(255)
Input(Data, Ary)
EndCollection
)")
("id", 1)
("maxLedId", 5)
;
constexpr static const auto hidDesc = hid::Descriptor<hid::compiledSize(hidSrc)>(hidSrc);
This performs the same steps as the macro above but without error reporting. Compile time error reporting can be added with the following code:
constexpr static const hid::Error error = hid::compileError(hidSrc);
constexpr static const size_t dummy = hid::reporter<error.line, error.column, error.message>();
The PlatformIO documentation describes here and here how to add libraries from GitHub directly.
The latest HidDescCTC version can be included by adding the following line to platformio.ini
in your environment section:
lib_deps = HidDescCTC=https://github.com/daniel-starke/HidDescCTC/archive/refs/heads/main.zip
Syntax description according to ISO/IEC 14977.
Exponent = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" ;
SignedExponent = "-", ( Exponent | "8" ) ;
Digit = Exponent | "8" | "9" ;
LowerLetter = "a" | "b" | "c" | "d" | "e" | "f" | "g" "h" | "i" | "j" | "k" | "l" | "m" | "n" "o" | "p" | q" | "r" | "s" | "t" | "u" "v" | "w" | "x" | "y" | "z" ;
UpperLetter = "A" | "B" | "C" | "D" | "E" | "F" | "G" "H" | "I" | "J" | "K" | "L" | "M" | "N" "O" | "P" | Q" | "R" | "S" | "T" | "U" "V" | "W" | "X" | "Y" | "Z" ;
EndOfLine = ? line-feed or carrier-return ? ;
Character = ? all visible characters ? ;
Number = Digit, { Digit } ;
SignedNumber = "-", Number ;
HexNumber = "0x", Digit, { Digit } ;
ItemChar = { "_" | LowerLetter | UpperLetter } ;
ArgChar = { ItemChar | Digit } ;
Parameter = "{", { Character - "}" }, "}" ;
UnitName = "Length" | "Mass" | "Time" | "Temp" | "Current" | "Luminous" ;
BaseUnit = UnitName, [ "^", ( Exponent | SignedExponent ) ] ;
Unit = BaseUnit, { BaseUnit } ;
ArgumentName = ItemChar, { ArgChar } ;
Argument = ArgumentName | Number | SignedNumber | HexNumber | Parameter ;
ArgumentList = Argument, ( ( "(", Unit, ")" ) | { ",", Argument } ) ;
Item = ItemChar, { ItemChar }, [ "(", ArgumentList , ")" ] ;
Comment = ( ";" | "#" ), { Character - EndOfLine } ;
Grammar = { Item | Number | HexNumber | Parameter | Comment } ;
- Deduction of the Usage Page map from a numeric value or user parameter is not supported.
- Semantic checks against usage types as defined in HID 1.11 ch. 3.4 are not yet implemented.
See LICENSE.
No content contributions are accepted. Please file a bug report or feature request instead.
This decision was made in consideration of the used license.