Skip to content

Instantly share code, notes, and snippets.

@SelvinPL
Last active March 10, 2024 04:45
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save SelvinPL/99fd9af4566e759b6553e912b6a163f9 to your computer and use it in GitHub Desktop.
Save SelvinPL/99fd9af4566e759b6553e912b6a163f9 to your computer and use it in GitHub Desktop.
modified LUFA hid parser for simple use with tinyusb - gamepad example
/*
* The MIT License (MIT)
*
* Copyright (c) 2023 Selvin
*
*/
#include "ch32v20x.h" //include your board
#include "tusb.h"
#include <stdbool.h>
#include "hidparser.h"
#define INVALID_REPORT_ID -1
// means 1/X of half range of analog would be dead zone
#define DEAD_ZONE 4U
static const char *const BUTTON_NAMES[] = {"NONE", "UP", "RIGHT", "DOWN", "LEFT", "A", "B"};
//(hat format, 8 is released, 0=N, 1=NE, 2=E, 3=SE, 4=S, 5=SW, 6=W, 7=NW)
static const uint8_t HAT_SWITCH_TO_DIRECTION_BUTTONS[] = {0b0001, 0b0011, 0b0010, 0b0110, 0b0100, 0b1100, 0b1000, 0b1001, 0b0000};
typedef union
{
struct
{
bool up : 1;
bool right : 1;
bool down : 1;
bool left : 1;
bool button1 : 1;
bool button2 : 1;
};
struct
{
uint8_t all_direction : 4;
uint8_t all_buttons : 2;
};
uint8_t value : 8;
} pad_buttons;
HID_ReportInfo_t *info = NULL;
int16_t reportID;
pad_buttons previous = {0};
uint8_t g_dev_addr = 255;
uint8_t g_instance = 0;
void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *desc_report, uint16_t desc_len)
{
uint16_t vid, pid;
tuh_vid_pid_get(dev_addr, &vid, &pid);
uint8_t ret = USB_ProcessHIDReport(desc_report, desc_len, &info);
printf("HID device address = %d, instance = %d is mounted\r\n", dev_addr, instance);
printf("VID = %04x, PID = %04x\r\n", vid, pid);
if(ret == HID_PARSE_Successful)
{
g_dev_addr = dev_addr;
g_instance = instance;
}
else
{
printf("Error: USB_ProcessHIDReport failed: %d\r\n", ret);
}
}
void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance)
{
printf("HID device address = %d, instance = %d is unmounted\r\n", dev_addr, instance);
reportID = INVALID_REPORT_ID;
USB_FreeReportInfo(info);
info = NULL;
previous.value = 0;
g_dev_addr = 255;
}
//called from parser for filtering report items
bool CALLBACK_HIDParser_FilterHIDReportItem(HID_ReportItem_t *const CurrentItem)
{
if (CurrentItem->ItemType != HID_REPORT_ITEM_In)
return false;
if (reportID == INVALID_REPORT_ID)
{
reportID = CurrentItem->ReportID;
}
switch (CurrentItem->Attributes.Usage.Page)
{
case HID_USAGE_PAGE_DESKTOP:
switch (CurrentItem->Attributes.Usage.Usage)
{
case HID_USAGE_DESKTOP_X:
case HID_USAGE_DESKTOP_Y:
case HID_USAGE_DESKTOP_HAT_SWITCH:
case HID_USAGE_DESKTOP_DPAD_UP:
case HID_USAGE_DESKTOP_DPAD_DOWN:
case HID_USAGE_DESKTOP_DPAD_LEFT:
case HID_USAGE_DESKTOP_DPAD_RIGHT:
return true;
}
return false;
case HID_USAGE_PAGE_BUTTON:
return true;
}
return false;
}
static inline bool USB_GetHIDReportItemInfoWithReportId(const uint8_t *ReportData, HID_ReportItem_t *const ReportItem)
{
if (ReportItem->ReportID)
{
if (ReportItem->ReportID != ReportData[0])
return false;
ReportData++;
}
return USB_GetHIDReportItemInfo(ReportItem->ReportID, ReportData, ReportItem);
}
void parse_report(uint8_t const *report, uint16_t len)
{
pad_buttons current = {0};
current.value = 0;
HID_ReportItem_t *item = info->FirstReportItem;
//iterate filtered reports info to match report from data
while (item)
{
if (USB_GetHIDReportItemInfoWithReportId(report, item))
{
switch (item->Attributes.Usage.Page)
{
case HID_USAGE_PAGE_DESKTOP:
switch (item->Attributes.Usage.Usage)
{
case HID_USAGE_DESKTOP_X:
{
uint32_t range_half = (item->Attributes.Logical.Maximum - item->Attributes.Logical.Minimum) / 2;
uint32_t dead_zone_range = range_half / DEAD_ZONE;
if (item->Value < (range_half - dead_zone_range))
{
current.left |= 1;
}
else if (item->Value > (range_half + dead_zone_range))
{
current.right |= 1;
}
}
break;
case HID_USAGE_DESKTOP_Y:
{
uint32_t range_half = (item->Attributes.Logical.Maximum - item->Attributes.Logical.Minimum) / 2;
uint32_t dead_zone_range = range_half / DEAD_ZONE;
if (item->Value < (range_half - dead_zone_range))
{
current.up |= 1;
}
else if (item->Value > (range_half + dead_zone_range))
{
current.down |= 1;
}
}
break;
case HID_USAGE_DESKTOP_HAT_SWITCH:
current.all_direction |= HAT_SWITCH_TO_DIRECTION_BUTTONS[item->Value];
break;
case HID_USAGE_DESKTOP_DPAD_UP:
current.up |= 1;
break;
case HID_USAGE_DESKTOP_DPAD_RIGHT:
current.right |= 1;
break;
case HID_USAGE_DESKTOP_DPAD_DOWN:
current.down |= 1;
break;
case HID_USAGE_DESKTOP_DPAD_LEFT:
current.left |= 1;
break;
}
break;
case HID_USAGE_PAGE_BUTTON:
{
uint8_t usage = item->Attributes.Usage.Usage;
if (usage == 1)
{
if (item->Value)
{
current.button1 = 1;
}
}
else if (usage == 2)
{
if (item->Value)
{
current.button2 = 1;
}
}
}
break;
}
}
item = item->Next;
}
if (previous.value != current.value)
{
//of course you can use GPIO here for hiven buttons
uint8_t value = current.value;
if (!value)
{
printf(BUTTON_NAMES[0]);
}
else
{
bool first = true;
for (int i = 1; i <= 6; i++)
{
if (value & 1)
{
if (first)
{
printf("%s", BUTTON_NAMES[i]);
first = false;
}
else
{
printf(", %s", BUTTON_NAMES[i]);
}
}
value >>= 1;
}
}
printf("\n");
previous.value = current.value;
}
}
void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const *report, uint16_t len)
{
parse_report(report, len);
g_dev_addr = dev_addr;
g_instance = instance;
}
int main()
{
//init board your board
Delay_Init();
USART_Printf_Init(115200);
printf("SystemClk:%d\r\n", (int)SystemCoreClock);
//init end
tusb_init();
while(true)
{
tuh_task();
if(g_dev_addr != 255)
{
if (!tuh_hid_receive_report(g_dev_addr, g_instance))
{
printf("Error: cannot request to receive report(tuh_hid_report_received_cb)\r\n");
}
g_dev_addr = 255;
}
}
}
/****************************************************************************
* Adapted from the LUFA Library:
*
* Copyright 2011 Dean Camera (dean [at] fourwalledcubicle [dot] com)
* dean [at] fourwalledcubicle [dot] com, www.lufa-lib.org
*
* Permission to use, copy, modify, distribute, and sell this
* software and its documentation for any purpose is hereby granted
* without fee, provided that the above copyright notice appear in
* all copies and that both that the copyright notice and this
* permission notice and warranty disclaimer appear in supporting
* documentation, and that the name of the author not be used in
* advertising or publicity pertaining to distribution of the
* software without specific, written prior permission.
*
* The author disclaim all warranties with regard to this
* software, including all implied warranties of merchantability
* and fitness. In no event shall the author be liable for any
* special, indirect or consequential damages or any damages
* whatsoever resulting from loss of use, data or profits, whether
* in an action of contract, negligence or other tortious action,
* arising out of or in connection with the use or performance of
* this software.
*
****************************************************************************/
#include "hidparser.h"
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#ifdef DYNAMIC
#include <malloc.h>
#define ACQUIRE_AND_RELEASE(structure, size) \
__attribute__ ((always_inline)) static inline structure##_t* acquire_##structure() { return malloc(sizeof(structure##_t)); } \
__attribute__ ((always_inline)) static inline void release_##structure(structure##_t* pointer) { free(pointer); }
#else
#define ACQUIRE_AND_RELEASE(structure, size) \
enum { MAX_##structure = size }; \
static structure##_t structure##s[MAX_##structure]; \
static bool structure##sAcquired[MAX_##structure]; \
static structure##_t* acquire_##structure() \
{\
for(uint8_t i=0; i < MAX_##structure; i++) \
{\
if(!structure##sAcquired[i])\
{\
structure##sAcquired[i] = true;\
return &structure##s[i];\
}\
}\
assert(false);\
return NULL;\
}\
static void release_##structure(structure##_t* pointer)\
{\
structure##sAcquired[pointer - structure##s] = false;\
}
#endif
ACQUIRE_AND_RELEASE(HID_ReportSizeInfo, 100);
ACQUIRE_AND_RELEASE(HID_CollectionPath, 25);
ACQUIRE_AND_RELEASE(HID_ReportInfo, 1);
ACQUIRE_AND_RELEASE(HID_ReportItem, 50);
void USB_FreeReportInfo(HID_ReportInfo_t *ReportInfo)
{
if (ReportInfo)
{
HID_ReportItem_t *item = ReportInfo->FirstReportItem;
while (item)
{
HID_ReportItem_t *current = item;
item = item->Next;
release_HID_ReportItem(current);
}
release_HID_ReportInfo(ReportInfo);
}
}
uint8_t USB_ProcessHIDReport(const uint8_t *ReportData,
uint16_t ReportSize,
HID_ReportInfo_t **ParserDataOut)
{
HID_ReportSizeInfo_t *FirstReportIDSize = acquire_HID_ReportSizeInfo();
HID_CollectionPath_t *FirstCollectionPath = acquire_HID_CollectionPath();
memset(FirstCollectionPath, 0, sizeof(HID_CollectionPath_t));
HID_ReportInfo_t *ParserData = acquire_HID_ReportInfo();
HID_StateTable_t StateTable[HID_STATETABLE_STACK_DEPTH];
HID_StateTable_t *CurrStateTable = &StateTable[0];
HID_CollectionPath_t *CurrCollectionPath = NULL;
HID_ReportSizeInfo_t *CurrReportIDInfo = FirstReportIDSize;
uint16_t UsageList[HID_USAGE_STACK_DEPTH];
uint8_t UsageListSize = 0;
HID_MinMax_t UsageMinMax = {0, 0};
memset(ParserData, 0x00, sizeof(HID_ReportInfo_t));
memset(CurrStateTable, 0x00, sizeof(HID_StateTable_t));
memset(CurrReportIDInfo, 0x00, sizeof(HID_ReportSizeInfo_t));
ParserData->TotalDeviceReports = 1;
uint8_t Result = HID_PARSE_Successful;
while (ReportSize)
{
uint8_t HIDReportItem = *ReportData;
uint32_t ReportItemData;
ReportData++;
ReportSize--;
switch (HIDReportItem & HID_RI_DATA_SIZE_MASK)
{
case HID_RI_DATA_BITS_32:
ReportItemData = (((uint32_t)ReportData[3] << 24) | ((uint32_t)ReportData[2] << 16) |
((uint16_t)ReportData[1] << 8) | ReportData[0]);
ReportSize -= 4;
ReportData += 4;
break;
case HID_RI_DATA_BITS_16:
ReportItemData = (((uint16_t)ReportData[1] << 8) | (ReportData[0]));
ReportSize -= 2;
ReportData += 2;
break;
case HID_RI_DATA_BITS_8:
ReportItemData = ReportData[0];
ReportSize -= 1;
ReportData += 1;
break;
default:
ReportItemData = 0;
break;
}
switch (HIDReportItem & (HID_RI_TYPE_MASK | HID_RI_TAG_MASK))
{
case HID_RI_PUSH(0):
if (CurrStateTable == &StateTable[HID_STATETABLE_STACK_DEPTH - 1])
{
Result = HID_PARSE_HIDStackOverflow;
break;
}
memcpy((CurrStateTable + 1),
CurrStateTable,
sizeof(HID_StateTable_t));
CurrStateTable++;
break;
case HID_RI_POP(0):
if (CurrStateTable == &StateTable[0])
{
Result = HID_PARSE_HIDStackUnderflow;
break;
}
CurrStateTable--;
break;
case HID_RI_USAGE_PAGE(0):
CurrStateTable->Attributes.Usage.Page = ReportItemData;
break;
case HID_RI_LOGICAL_MINIMUM(0):
CurrStateTable->Attributes.Logical.Minimum = ReportItemData;
break;
case HID_RI_LOGICAL_MAXIMUM(0):
CurrStateTable->Attributes.Logical.Maximum = ReportItemData;
break;
case HID_RI_PHYSICAL_MINIMUM(0):
CurrStateTable->Attributes.Physical.Minimum = ReportItemData;
break;
case HID_RI_PHYSICAL_MAXIMUM(0):
CurrStateTable->Attributes.Physical.Maximum = ReportItemData;
break;
case HID_RI_UNIT_EXPONENT(0):
CurrStateTable->Attributes.Unit.Exponent = ReportItemData;
break;
case HID_RI_UNIT(0):
CurrStateTable->Attributes.Unit.Type = ReportItemData;
break;
case HID_RI_REPORT_SIZE(0):
CurrStateTable->Attributes.BitSize = ReportItemData;
break;
case HID_RI_REPORT_COUNT(0):
CurrStateTable->ReportCount = ReportItemData;
break;
case HID_RI_REPORT_ID(0):
CurrStateTable->ReportID = ReportItemData;
if (ParserData->UsingReportIDs)
{
CurrReportIDInfo = NULL;
HID_ReportSizeInfo_t *iterator = FirstReportIDSize;
while (true)
{
if (iterator->ReportID == CurrStateTable->ReportID)
{
CurrReportIDInfo = iterator;
break;
}
if (!iterator->Next)
break;
iterator = iterator->Next;
}
if (CurrReportIDInfo == NULL)
{
ParserData->TotalDeviceReports++;
iterator->Next = CurrReportIDInfo = acquire_HID_ReportSizeInfo();
memset(CurrReportIDInfo, 0x00, sizeof(HID_ReportSizeInfo_t));
}
}
ParserData->UsingReportIDs = true;
CurrReportIDInfo->ReportID = CurrStateTable->ReportID;
break;
case HID_RI_USAGE(0):
if (UsageListSize == HID_USAGE_STACK_DEPTH)
{
Result = HID_PARSE_UsageListOverflow;
break;
}
if ((HIDReportItem & HID_RI_DATA_SIZE_MASK) == HID_RI_DATA_BITS_32)
CurrStateTable->Attributes.Usage.Page = (ReportItemData >> 16);
UsageList[UsageListSize++] = ReportItemData;
break;
case HID_RI_USAGE_MINIMUM(0):
UsageMinMax.Minimum = ReportItemData;
break;
case HID_RI_USAGE_MAXIMUM(0):
UsageMinMax.Maximum = ReportItemData;
break;
case HID_RI_COLLECTION(0):
if (CurrCollectionPath == NULL)
{
CurrCollectionPath = FirstCollectionPath;
}
else
{
HID_CollectionPath_t *ParentCollectionPath = CurrCollectionPath;
CurrCollectionPath = FirstCollectionPath;
while (CurrCollectionPath->Next)
{
CurrCollectionPath = CurrCollectionPath->Next;
}
HID_CollectionPath_t *NewCollectionPath = acquire_HID_CollectionPath();
CurrCollectionPath->Next = NewCollectionPath;
CurrCollectionPath = NewCollectionPath;
memset(CurrCollectionPath, 0, sizeof(HID_CollectionPath_t));
CurrCollectionPath->Parent = ParentCollectionPath;
}
CurrCollectionPath->Type = ReportItemData;
CurrCollectionPath->Usage.Page = CurrStateTable->Attributes.Usage.Page;
if (UsageListSize)
{
CurrCollectionPath->Usage.Usage = UsageList[0];
for (uint8_t i = 1; i < UsageListSize; i++)
UsageList[i - 1] = UsageList[i];
UsageListSize--;
}
else if (UsageMinMax.Minimum <= UsageMinMax.Maximum)
{
CurrCollectionPath->Usage.Usage = UsageMinMax.Minimum++;
}
break;
case HID_RI_END_COLLECTION(0):
if (CurrCollectionPath == NULL)
{
Result = HID_PARSE_UnexpectedEndCollection;
break;
}
CurrCollectionPath = CurrCollectionPath->Parent;
if (CurrCollectionPath)
{
release_HID_CollectionPath(CurrCollectionPath->Next);
CurrCollectionPath->Next = NULL;
}
break;
case HID_RI_INPUT(0):
case HID_RI_OUTPUT(0):
case HID_RI_FEATURE(0):
for (uint8_t ReportItemNum = 0; ReportItemNum < CurrStateTable->ReportCount; ReportItemNum++)
{
HID_ReportItem_t NewReportItem;
memcpy(&NewReportItem.Attributes,
&CurrStateTable->Attributes,
sizeof(HID_ReportItem_Attributes_t));
NewReportItem.ItemFlags = ReportItemData;
NewReportItem.ReportID = CurrStateTable->ReportID;
if (UsageListSize)
{
NewReportItem.Attributes.Usage.Usage = UsageList[0];
for (uint8_t i = 1; i < UsageListSize; i++)
UsageList[i - 1] = UsageList[i];
UsageListSize--;
}
else if (UsageMinMax.Minimum <= UsageMinMax.Maximum)
{
NewReportItem.Attributes.Usage.Usage = UsageMinMax.Minimum++;
}
uint8_t ItemTypeTag = (HIDReportItem & (HID_RI_TYPE_MASK | HID_RI_TAG_MASK));
if (ItemTypeTag == HID_RI_INPUT(0))
NewReportItem.ItemType = HID_REPORT_ITEM_In;
else if (ItemTypeTag == HID_RI_OUTPUT(0))
NewReportItem.ItemType = HID_REPORT_ITEM_Out;
else
NewReportItem.ItemType = HID_REPORT_ITEM_Feature;
NewReportItem.BitOffset = CurrReportIDInfo->ReportSizeBits[NewReportItem.ItemType];
CurrReportIDInfo->ReportSizeBits[NewReportItem.ItemType] += CurrStateTable->Attributes.BitSize;
ParserData->LargestReportSizeBits = MAX(ParserData->LargestReportSizeBits, CurrReportIDInfo->ReportSizeBits[NewReportItem.ItemType]);
if (!(ReportItemData & HID_IOF_CONSTANT) && CALLBACK_HIDParser_FilterHIDReportItem(&NewReportItem))
{
if (!ParserData->FirstReportItem)
{
ParserData->FirstReportItem = acquire_HID_ReportItem();
ParserData->LastReportItem = ParserData->FirstReportItem;
}
else
{
ParserData->LastReportItem->Next = acquire_HID_ReportItem();
ParserData->LastReportItem = ParserData->LastReportItem->Next;
}
memcpy(ParserData->LastReportItem, &NewReportItem, sizeof(HID_ReportItem_t));
ParserData->LastReportItem->Next = NULL;
ParserData->TotalReportItems++;
}
}
break;
default:
break;
}
if (Result != HID_PARSE_Successful)
{
break;
}
if ((HIDReportItem & HID_RI_TYPE_MASK) == HID_RI_TYPE_MAIN)
{
UsageMinMax.Minimum = 0;
UsageMinMax.Maximum = 0;
UsageListSize = 0;
}
}
if (!(ParserData->TotalReportItems))
Result = HID_PARSE_NoUnfilteredReportItems;
if (Result != HID_PARSE_Successful)
{
USB_FreeReportInfo(ParserData);
}
else
{
*ParserDataOut = ParserData;
}
HID_ReportSizeInfo_t *iteratorReportIDSizes = FirstReportIDSize;
while (iteratorReportIDSizes)
{
HID_ReportSizeInfo_t *temp = iteratorReportIDSizes->Next;
release_HID_ReportSizeInfo(iteratorReportIDSizes);
iteratorReportIDSizes = temp;
}
HID_CollectionPath_t *iteratorCollectionPaths = FirstCollectionPath;
while (iteratorCollectionPaths)
{
HID_CollectionPath_t *temp = iteratorCollectionPaths->Next;
release_HID_CollectionPath(iteratorCollectionPaths);
iteratorCollectionPaths = temp;
}
return Result;
}
bool USB_GetHIDReportItemInfo(uint16_t report_id, const uint8_t *ReportData,
HID_ReportItem_t *const ReportItem)
{
if (ReportItem == NULL)
{
return false;
}
if (ReportItem->ReportID != report_id)
{
return false;
}
uint16_t DataBitsRem = ReportItem->Attributes.BitSize;
uint16_t CurrentBit = ReportItem->BitOffset;
uint32_t BitMask = (1 << 0);
ReportItem->PreviousValue = ReportItem->Value;
ReportItem->Value = 0;
while (DataBitsRem--)
{
if (ReportData[CurrentBit / 8] & (1 << (CurrentBit % 8)))
ReportItem->Value |= BitMask;
CurrentBit++;
BitMask <<= 1;
}
return true;
}
/****************************************************************************
* Adapted from the LUFA Library:
*
* Copyright 2011 Dean Camera (dean [at] fourwalledcubicle [dot] com)
* dean [at] fourwalledcubicle [dot] com, www.lufa-lib.org
*
* Permission to use, copy, modify, distribute, and sell this
* software and its documentation for any purpose is hereby granted
* without fee, provided that the above copyright notice appear in
* all copies and that both that the copyright notice and this
* permission notice and warranty disclaimer appear in supporting
* documentation, and that the name of the author not be used in
* advertising or publicity pertaining to distribution of the
* software without specific, written prior permission.
*
* The author disclaim all warranties with regard to this
* software, including all implied warranties of merchantability
* and fitness. In no event shall the author be liable for any
* special, indirect or consequential damages or any damages
* whatsoever resulting from loss of use, data or profits, whether
* in an action of contract, negligence or other tortious action,
* arising out of or in connection with the use or performance of
* this software.
*
****************************************************************************/
/** \file
* \brief USB Human Interface Device (HID) Class report descriptor parser.
*
* This file allows for the easy parsing of complex HID report descriptors, which describes the data that
* a HID device transmits to the host. It also provides an easy API for extracting and processing the data
* elements inside a HID report sent from an attached HID device.
*/
/** \ingroup Group_USB
* \defgroup Group_HIDParser HID Report Parser
* \brief USB Human Interface Device (HID) Class report descriptor parser.
*
* \section Sec_HIDParser_Dependencies Module Source Dependencies
* The following files must be built with any user project that uses this module:
* - LUFA/Drivers/USB/Class/Host/HIDParser.c <i>(Makefile source module name: LUFA_SRC_USB)</i>
*
* \section Sec_HIDParser_ModDescription Module Description
* Human Interface Device (HID) class report descriptor parser. This module implements a parser than is
* capable of processing a complete HID report descriptor, and outputting a flat structure containing the
* contents of the report in an a more friendly format. The parsed data may then be further processed and used
* within an application to process sent and received HID reports to and from an attached HID device.
*
* A HID report descriptor consists of a set of HID report items, which describe the function and layout
* of data exchanged between a HID device and a host, including both the physical encoding of each item
* (such as a button, key press or joystick axis) in the sent and received data packets - known as "reports" -
* as well as other information about each item such as the usages, data range, physical location and other
* characteristics. In this way a HID device can retain a high degree of flexibility in its capabilities, as it
* is not forced to comply with a given report layout or feature-set.
*
* This module also contains routines for the processing of data in an actual HID report, using the parsed report
* descriptor data as a guide for the encoding.
*
* @{
*/
#pragma once
/* Includes: */
#include <stdint.h>
#include <stdbool.h>
/* Enable C linkage for C++ Compilers: */
#if defined(__cplusplus)
extern "C"
{
#endif
/* Macros: */
#if !defined(HID_STATETABLE_STACK_DEPTH) || defined(__DOXYGEN__)
/** Constant indicating the maximum stack depth of the state table. A larger state table
* allows for more PUSH/POP report items to be nested, but consumes more memory. By default
* this is set to 2 levels (allowing non-nested PUSH items) but this can be overridden by
* defining \c HID_STATETABLE_STACK_DEPTH to another value in the user project makefile, passing the
* define to the compiler using the -D compiler switch.
*/
#define HID_STATETABLE_STACK_DEPTH 2
#endif
#if !defined(HID_USAGE_STACK_DEPTH) || defined(__DOXYGEN__)
/** Constant indicating the maximum stack depth of the usage table. A larger usage table
* allows for more USAGE items to be indicated sequentially for REPORT COUNT entries of more than
* one, but requires more stack space. By default this is set to 8 levels (allowing for a report
* item with a count of 8) but this can be overridden by defining \c HID_USAGE_STACK_DEPTH to another
* value in the user project makefile, passing the define to the compiler using the -D compiler
* switch.
*/
#define HID_USAGE_STACK_DEPTH 8
#endif
/** Returns the value a given HID report item (once its value has been fetched via \ref USB_GetHIDReportItemInfo())
* left-aligned to the given data type. This allows for signed data to be interpreted correctly, by shifting the data
* leftwards until the data's sign bit is in the correct position.
*
* \param[in] ReportItem HID Report Item whose retrieved value is to be aligned.
* \param[in] Type Data type to align the HID report item's value to.
*
* \return Left-aligned data of the given report item's pre-retrieved value for the given datatype.
*/
#define HID_ALIGN_DATA(ReportItem, Type) ((Type)(ReportItem->Value << ((8 * sizeof(Type)) - ReportItem->Attributes.BitSize)))
/** Convenience macro to determine the larger of two values.
*
* \attention This macro should only be used with operands that do not have side effects from being evaluated
* multiple times.
*
* \param[in] x First value to compare
* \param[in] y First value to compare
*
* \return The larger of the two input parameters
*/
#if !defined(MAX) || defined(__DOXYGEN__)
#define MAX(x, y) (((x) > (y)) ? (x) : (y))
#endif
/** Convenience macro to determine the smaller of two values.
*
* \attention This macro should only be used with operands that do not have side effects from being evaluated
* multiple times.
*
* \param[in] x First value to compare.
* \param[in] y First value to compare.
*
* \return The smaller of the two input parameters
*/
#if !defined(MIN) || defined(__DOXYGEN__)
#define MIN(x, y) (((x) < (y)) ? (x) : (y))
#endif
#if !defined(CONCAT) || defined(__DOXYGEN__)
/** Concatenates the given input into a single token, via the C Preprocessor.
*
* \param[in] x First item to concatenate.
* \param[in] y Second item to concatenate.
*
* \return Concatenated version of the input.
*/
#define CONCAT(x, y) x##y
/** CConcatenates the given input into a single token after macro expansion, via the C Preprocessor.
*
* \param[in] x First item to concatenate.
* \param[in] y Second item to concatenate.
*
* \return Concatenated version of the expanded input.
*/
#define CONCAT_EXPANDED(x, y) CONCAT(x, y)
#endif
/* Public Interface - May be used in end-application: */
/* Enums: */
/** Enum for the possible error codes in the return value of the \ref USB_ProcessHIDReport() function. */
enum HID_Parse_ErrorCodes_t
{
HID_PARSE_Successful = 0, /**< Successful parse of the HID report descriptor, no error. */
HID_PARSE_HIDStackOverflow = 1, /**< More than \ref HID_STATETABLE_STACK_DEPTH nested PUSHes in the report. */
HID_PARSE_HIDStackUnderflow = 2, /**< A POP was found when the state table stack was empty. */
HID_PARSE_UnexpectedEndCollection = 3, /**< An END COLLECTION item found without matching COLLECTION item. */
HID_PARSE_UsageListOverflow = 4, /**< More than \ref HID_USAGE_STACK_DEPTH usages listed in a row. */
HID_PARSE_NoUnfilteredReportItems = 5, /**< All report items from the device were filtered by the filtering callback routine. */
};
/* Private Interface - For use in library only: */
#if !defined(__DOXYGEN__)
/* Macros: */
#define HID_RI_DATA_SIZE_MASK 0x03
#define HID_RI_TYPE_MASK 0x0C
#define HID_RI_TAG_MASK 0xF0
#define HID_RI_TYPE_MAIN 0x00
#define HID_RI_TYPE_GLOBAL 0x04
#define HID_RI_TYPE_LOCAL 0x08
#define HID_RI_DATA_BITS_0 0x00
#define HID_RI_DATA_BITS_8 0x01
#define HID_RI_DATA_BITS_16 0x02
#define HID_RI_DATA_BITS_32 0x03
#define HID_RI_DATA_BITS(DataBits) CONCAT_EXPANDED(HID_RI_DATA_BITS_, DataBits)
#define _HID_RI_ENCODE_0(Data)
#define _HID_RI_ENCODE_8(Data) , (Data & 0xFF)
#define _HID_RI_ENCODE_16(Data) \
_HID_RI_ENCODE_8(Data) \
_HID_RI_ENCODE_8(Data >> 8)
#define _HID_RI_ENCODE_32(Data) \
_HID_RI_ENCODE_16(Data) \
_HID_RI_ENCODE_16(Data >> 16)
#define _HID_RI_ENCODE(DataBits, ...) CONCAT_EXPANDED(_HID_RI_ENCODE_, DataBits(__VA_ARGS__))
#define _HID_RI_ENTRY(Type, Tag, DataBits, ...) (Type | Tag | HID_RI_DATA_BITS(DataBits)) _HID_RI_ENCODE(DataBits, (__VA_ARGS__))
#endif
/* Public Interface - May be used in end-application: */
/* Macros: */
/** \name HID Input, Output and Feature Report Descriptor Item Flags */
/**@{*/
#define HID_IOF_CONSTANT (1 << 0)
#define HID_IOF_DATA (0 << 0)
#define HID_IOF_VARIABLE (1 << 1)
#define HID_IOF_ARRAY (0 << 1)
#define HID_IOF_RELATIVE (1 << 2)
#define HID_IOF_ABSOLUTE (0 << 2)
#define HID_IOF_WRAP (1 << 3)
#define HID_IOF_NO_WRAP (0 << 3)
#define HID_IOF_NON_LINEAR (1 << 4)
#define HID_IOF_LINEAR (0 << 4)
#define HID_IOF_NO_PREFERRED_STATE (1 << 5)
#define HID_IOF_PREFERRED_STATE (0 << 5)
#define HID_IOF_NULLSTATE (1 << 6)
#define HID_IOF_NO_NULL_POSITION (0 << 6)
#define HID_IOF_VOLATILE (1 << 7)
#define HID_IOF_NON_VOLATILE (0 << 7)
#define HID_IOF_BUFFERED_BYTES (1 << 8)
#define HID_IOF_BITFIELD (0 << 8)
/**@}*/
/** \name HID Report Descriptor Item Macros */
/**@{*/
#define HID_RI_INPUT(DataBits, ...) _HID_RI_ENTRY(HID_RI_TYPE_MAIN, 0x80, DataBits, __VA_ARGS__)
#define HID_RI_OUTPUT(DataBits, ...) _HID_RI_ENTRY(HID_RI_TYPE_MAIN, 0x90, DataBits, __VA_ARGS__)
#define HID_RI_COLLECTION(DataBits, ...) _HID_RI_ENTRY(HID_RI_TYPE_MAIN, 0xA0, DataBits, __VA_ARGS__)
#define HID_RI_FEATURE(DataBits, ...) _HID_RI_ENTRY(HID_RI_TYPE_MAIN, 0xB0, DataBits, __VA_ARGS__)
#define HID_RI_END_COLLECTION(DataBits, ...) _HID_RI_ENTRY(HID_RI_TYPE_MAIN, 0xC0, DataBits, __VA_ARGS__)
#define HID_RI_USAGE_PAGE(DataBits, ...) _HID_RI_ENTRY(HID_RI_TYPE_GLOBAL, 0x00, DataBits, __VA_ARGS__)
#define HID_RI_LOGICAL_MINIMUM(DataBits, ...) _HID_RI_ENTRY(HID_RI_TYPE_GLOBAL, 0x10, DataBits, __VA_ARGS__)
#define HID_RI_LOGICAL_MAXIMUM(DataBits, ...) _HID_RI_ENTRY(HID_RI_TYPE_GLOBAL, 0x20, DataBits, __VA_ARGS__)
#define HID_RI_PHYSICAL_MINIMUM(DataBits, ...) _HID_RI_ENTRY(HID_RI_TYPE_GLOBAL, 0x30, DataBits, __VA_ARGS__)
#define HID_RI_PHYSICAL_MAXIMUM(DataBits, ...) _HID_RI_ENTRY(HID_RI_TYPE_GLOBAL, 0x40, DataBits, __VA_ARGS__)
#define HID_RI_UNIT_EXPONENT(DataBits, ...) _HID_RI_ENTRY(HID_RI_TYPE_GLOBAL, 0x50, DataBits, __VA_ARGS__)
#define HID_RI_UNIT(DataBits, ...) _HID_RI_ENTRY(HID_RI_TYPE_GLOBAL, 0x60, DataBits, __VA_ARGS__)
#define HID_RI_REPORT_SIZE(DataBits, ...) _HID_RI_ENTRY(HID_RI_TYPE_GLOBAL, 0x70, DataBits, __VA_ARGS__)
#define HID_RI_REPORT_ID(DataBits, ...) _HID_RI_ENTRY(HID_RI_TYPE_GLOBAL, 0x80, DataBits, __VA_ARGS__)
#define HID_RI_REPORT_COUNT(DataBits, ...) _HID_RI_ENTRY(HID_RI_TYPE_GLOBAL, 0x90, DataBits, __VA_ARGS__)
#define HID_RI_PUSH(DataBits, ...) _HID_RI_ENTRY(HID_RI_TYPE_GLOBAL, 0xA0, DataBits, __VA_ARGS__)
#define HID_RI_POP(DataBits, ...) _HID_RI_ENTRY(HID_RI_TYPE_GLOBAL, 0xB0, DataBits, __VA_ARGS__)
#define HID_RI_USAGE(DataBits, ...) _HID_RI_ENTRY(HID_RI_TYPE_LOCAL, 0x00, DataBits, __VA_ARGS__)
#define HID_RI_USAGE_MINIMUM(DataBits, ...) _HID_RI_ENTRY(HID_RI_TYPE_LOCAL, 0x10, DataBits, __VA_ARGS__)
#define HID_RI_USAGE_MAXIMUM(DataBits, ...) _HID_RI_ENTRY(HID_RI_TYPE_LOCAL, 0x20, DataBits, __VA_ARGS__)
/**@}*/
/* Type Defines: */
/** \brief HID Parser Report Item Min/Max Structure.
*
* Type define for an attribute with both minimum and maximum values (e.g. Logical Min/Max).
*/
typedef struct
{
uint32_t Minimum; /**< Minimum value for the attribute. */
uint32_t Maximum; /**< Maximum value for the attribute. */
} HID_MinMax_t;
/** \brief HID Parser Report Item Unit Structure.
*
* Type define for the Unit attributes of a report item.
*/
typedef struct
{
uint32_t Type; /**< Unit type (refer to HID specifications for details). */
uint8_t Exponent; /**< Unit exponent (refer to HID specifications for details). */
} HID_Unit_t;
/** \brief HID Parser Report Item Usage Structure.
*
* Type define for the Usage attributes of a report item.
*/
typedef struct
{
uint16_t Page; /**< Usage page of the report item. */
uint16_t Usage; /**< Usage of the report item. */
} HID_Usage_t;
/** \brief HID Parser Report Item Collection Path Structure.
*
* Type define for a COLLECTION object. Contains the collection attributes and a reference to the
* parent collection if any.
*/
typedef struct HID_CollectionPath
{
uint8_t Type; /**< Collection type (e.g. "Generic Desktop"). */
HID_Usage_t Usage; /**< Collection usage. */
struct HID_CollectionPath *Parent; /**< Reference to parent collection, or \c NULL if root collection. */
struct HID_CollectionPath *Next; /**< Reference to parent collection, or \c NULL if root collection. */
} HID_CollectionPath_t;
/** \brief HID Parser Report Item Attributes Structure.
*
* Type define for all the data attributes of a report item, except flags.
*/
typedef struct
{
uint8_t BitSize; /**< Size in bits of the report item's data. */
HID_Usage_t Usage; /**< Usage of the report item. */
HID_Unit_t Unit; /**< Unit type and exponent of the report item. */
HID_MinMax_t Logical; /**< Logical minimum and maximum of the report item. */
HID_MinMax_t Physical; /**< Physical minimum and maximum of the report item. */
} HID_ReportItem_Attributes_t;
/** \brief HID Parser Report Item Details Structure.
*
* Type define for a report item (IN, OUT or FEATURE) layout attributes and other details.
*/
typedef struct HID_ReportItem_s
{
uint16_t BitOffset; /**< Bit offset in the IN, OUT or FEATURE report of the item. */
uint8_t ItemType; /**< Report item type, a value in \ref HID_ReportItemTypes_t. */
uint16_t ItemFlags; /**< Item data flags, a mask of \c HID_IOF_* constants. */
uint8_t ReportID; /**< Report ID this item belongs to, or 0x00 if device has only one report */
HID_ReportItem_Attributes_t Attributes; /**< Report item attributes. */
uint32_t Value; /**< Current value of the report item - use \ref HID_ALIGN_DATA() when processing
* a retrieved value so that it is aligned to a specific type.
*/
uint32_t PreviousValue; /**< Previous value of the report item. */
struct HID_ReportItem_s* Next;
} HID_ReportItem_t;
/** \brief HID Parser Report Size Structure.
*
* Type define for a report item size information structure, to retain the size of a device's reports by ID.
*/
typedef struct HID_ReportSizeInfo_s
{
uint8_t ReportID; /**< Report ID of the report within the HID interface. */
uint16_t ReportSizeBits[3]; /**< Total number of bits in each report type for the given Report ID,
* indexed by the \ref HID_ReportItemTypes_t enum.
*/
struct HID_ReportSizeInfo_s* Next;
} HID_ReportSizeInfo_t;
/** \brief HID Parser State Structure.
*
* Type define for a complete processed HID report, including all report item data and collections.
*/
typedef struct
{
uint8_t TotalReportItems; /**< Total number of report items stored in the \c ReportItems array. */
HID_ReportItem_t* FirstReportItem; /**< Report items array, including all IN, OUT
* and FEATURE items.
*/
uint8_t TotalDeviceReports; /**< Number of reports within the HID interface */
uint16_t LargestReportSizeBits; /**< Largest report that the attached device will generate, in bits */
bool UsingReportIDs; /**< Indicates if the device has at least one REPORT ID
* element in its HID report descriptor.
*/
HID_ReportItem_t* LastReportItem;
} HID_ReportInfo_t;
/* Function Prototypes: */
/** Function to process a given HID report returned from an attached device, and store it into a given
* \ref HID_ReportInfo_t structure.
*
* \param[in] ReportData Buffer containing the device's HID report table.
* \param[in] ReportSize Size in bytes of the HID report table.
* \param[out] ParserData Pointer to a \ref HID_ReportInfo_t instance for the parser output.
*
* \return A value in the \ref HID_Parse_ErrorCodes_t enum.
*/
uint8_t USB_ProcessHIDReport(const uint8_t *ReportData,
uint16_t ReportSize,
HID_ReportInfo_t **ParserData);
void USB_FreeReportInfo(HID_ReportInfo_t *ReportInfo);
/** Extracts the given report item's value out of the given HID report and places it into the Value
* member of the report item's \ref HID_ReportItem_t structure.
*
* When called on a report with an item that exists in that report, this copies the report item's \c Value
* to its \c PreviousValue element for easy checking to see if an item's value has changed before processing
* a report. If the given item does not exist in the report, the function does not modify the report item's
* data.
*
* \param[in] ReportData Buffer containing an IN or FEATURE report from an attached device.
* \param[in,out] ReportItem Pointer to the report item of interest in a \ref HID_ReportInfo_t ReportItem array.
*
* \returns Boolean \c true if the item to retrieve was located in the given report, \c false otherwise.
*/
bool USB_GetHIDReportItemInfo(uint16_t report_id, const uint8_t *ReportData,
HID_ReportItem_t *const ReportItem);
/** Callback routine for the HID Report Parser. This callback <b>must</b> be implemented by the user code when
* the parser is used, to determine what report IN, OUT and FEATURE item's information is stored into the user
* \ref HID_ReportInfo_t structure. This can be used to filter only those items the application will be using, so that
* no RAM is wasted storing the attributes for report items which will never be referenced by the application.
*
* Report item pointers passed to this callback function may be cached by the user application for later use
* when processing report items. This provides faster report processing in the user application than would
* a search of the entire parsed report item table for each received or sent report.
*
* \param[in] CurrentItem Pointer to the current report item for user checking.
*
* \return Boolean \c true if the item should be stored into the \ref HID_ReportInfo_t structure, \c false if
* it should be ignored.
*/
bool CALLBACK_HIDParser_FilterHIDReportItem(HID_ReportItem_t *const CurrentItem);
/** Enum for the different types of HID reports. */
enum HID_ReportItemTypes_t
{
HID_REPORT_ITEM_In = 0, /**< Indicates that the item is an IN report type. */
HID_REPORT_ITEM_Out = 1, /**< Indicates that the item is an OUT report type. */
HID_REPORT_ITEM_Feature = 2, /**< Indicates that the item is a FEATURE report type. */
};
/* Private Interface - For use in library only: */
#if !defined(__DOXYGEN__)
/* Type Defines: */
typedef struct
{
HID_ReportItem_Attributes_t Attributes;
uint8_t ReportCount;
uint8_t ReportID;
} HID_StateTable_t;
#endif
/* Disable C linkage for C++ Compilers: */
#if defined(__cplusplus)
}
#endif
/** @} */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment