This post explains how to parse the HID **Unit** Global Item as explained by the HID Specification, page 37. The table there is quite confusing and it took me a while to fully understand it (Benjamin Tissoires was really the one who cracked it). I couldn't find any better explanation online which means either I'm incredibly dense and everyone's figured it out or no-one has posted a better explanation. On the off-chance it's the latter [1], here are the instructions on how to parse this item.

We know a HID Report Descriptor consists of a number of items that describe the content of each HID Report (read: an event from a device). These Items include things like Logical Minimum/Maximum for axis ranges, etc. A HID Unit item specifies the physical unit to apply. For example, a Report Descriptor may specify that X and Y axes are in mm which can be quite useful for all the obvious reasons.

Like most HID items, a HID Unit Item consists of a one-byte item tag and 1, 2 or 4 byte payload. The **Unit** item in the Report Descriptor itself has the binary value **0110 01nn** where the **nn** is either 1, 2, or 3 indicating 1, 2 or 4 bytes of payload, respectively. That's standard HID.

The payload is divided into nibbles (4-bit units) and goes from LSB to MSB. The lowest-order 4 bits (*first byte & 0xf*) define the unit **System** to apply: one of **SI Linear**, **SI Rotation**, **English Linear** or **English Rotation** (well, or **None/Reserved**). The rest of the nibbles are in this order: "length", "mass", "time", "temperature", "current", "luminous intensity".
In something resembling code this means:

system = value & 0xf length_exponent = (value & 0xf0) >> 4 mass_exponent = (value & 0xf00) >> 8 time_exponent = (value & 0xf000) >> 12 ...The

**System**defines which unit is used for length (e.g. SILinear means length is in cm). The actual

*value*of each nibble is the

*exponent*for the unit in use [2]. In something resembling code:

switch (system) case SILinear: print("length is in cm^{length_exponent}"); break; case SIRotation: print("length is in rad^{length_exponent}"); break; case EnglishLinear: print("length is in in^{length_exponent}"); break; case EnglishRotation: print("length is in deg^{length_exponent}"); break; case None: case Reserved" print("boo!"); break;

For example, the value **0x321** means "SI Linear" (0x1) so the remaining nibbles represent, in ascending nibble order: Centimeters, Grams, Seconds, Kelvin, Ampere, Candela. The length nibble has a value of 0x2 so it's square cm, the mass nibble has a value of 0x3 so it is cubic grams (well, it's just an example, so...). This means that any report containing this item comes in cm²g³. As a more realistic example: 0xF011 would be cm/s.

If we changed the lowest nibble to English Rotation (0x4), i.e. our value is now **0x324**, the units represent: Degrees, Slug, Seconds, F, Ampere, Candela [3]. The length nibble 0x2 means square degrees, the mass nibble is cubic slugs. As a more realistic example, 0xF014 would be degrees/s.

Any nibble with value 0 means the unit isn't in use, so the example from the spec with value **0x00F0D121** is SI linear, units cm² g s⁻³ A⁻¹, which is... Voltage! Of course you knew that and totally didn't have to double-check with wikipedia.

Because bits are expensive and the base units are of course either too big or too small or otherwise not quite right, HID also provides a **Unit Exponent** item. The **Unit Exponent** item (a separate item to **Unit** in the Report Descriptor) then describes the exponent to be applied to the actual value in the report. For example, a **Unit Eponent** of -3 means 10⁻³ to be applied to the value. If the report descriptor specifies an item of **Unit** 0x00F0D121 (i.e. V) and **Unit Exponent** -3, the value of this item is mV (milliVolt), **Unit Exponent** of 3 would be kV (kiloVolt).

Now, in hindsight all this is pretty obvious and maybe even sensible. It'd have been nice if the spec would've explained it a bit clearer but then I would have nothing to write about, so I guess overall I call it a draw.

[1] This whole adventure was started because there's a touchpad out there that measures touch pressure in radians, so at least one other person out there struggled with the docs...

[2] The nibble value is twos complement (i.e. it's a signed 4-bit integer). Values 0x1-0x7 are exponents 1 to 7, values 0x8-0xf are exponents -8 to -1.

[3] English Linear should've trolled everyone and use Centimetres instead of Centimeters in SI Linear.