From b023ea0d6d26663cc9e648e1f7ddea6f9a2f21e2 Mon Sep 17 00:00:00 2001 From: whowechina Date: Mon, 15 Dec 2025 20:43:35 +0800 Subject: [PATCH] Arcade light support --- firmware/diva_pico.uf2 | Bin 0 -> 121344 bytes firmware/src/CMakeLists.txt | 2 +- firmware/src/lzfx.c | 169 +++++++++++++++++++++++++++++++++ firmware/src/lzfx.h | 18 ++++ firmware/src/main.c | 59 ++++++++---- firmware/src/rgb.c | 74 ++++++++++++--- firmware/src/rgb.h | 3 + firmware/src/slider.c | 5 + firmware/src/slider.h | 2 + firmware/src/tusb_config.h | 3 - firmware/src/usb_descriptors.c | 39 +++----- firmware/src/usb_descriptors.h | 80 +++++++++------- 12 files changed, 365 insertions(+), 89 deletions(-) create mode 100644 firmware/diva_pico.uf2 create mode 100644 firmware/src/lzfx.c create mode 100644 firmware/src/lzfx.h diff --git a/firmware/diva_pico.uf2 b/firmware/diva_pico.uf2 new file mode 100644 index 0000000000000000000000000000000000000000..0918b9b1398484c6ea760e52ff0275b15d95bcb8 GIT binary patch literal 121344 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 z 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 fNP + * This is actually taken from CrazyRedMachine's repo + * + */ + + +#include +#include "lzfx.h" + +typedef unsigned char u8; +typedef const u8 *LZSTATE[LZFX_HSIZE]; + + +/* Guess len. No parameters may be NULL; this is not checked. */ +static int lzfx_getsize(const void *ibuf, unsigned int ilen, unsigned int *olen) +{ + + u8 const *ip = (const u8 *)ibuf; + u8 const *const in_end = ip + ilen; + int tot_len = 0; + + while (ip < in_end) + { + + unsigned int ctrl = *ip++; + + if (ctrl < (1 << 5)) + { + + ctrl++; + + if (ip + ctrl > in_end) + return LZFX_ECORRUPT; + + tot_len += ctrl; + ip += ctrl; + } + else + { + + unsigned int len = (ctrl >> 5); + + if (len == 7) + { /* i.e. format #2 */ + len += *ip++; + } + + len += 2; /* len is now #octets */ + + if (ip >= in_end) + return LZFX_ECORRUPT; + + ip++; /* skip the ref byte */ + + tot_len += len; + } + } + + *olen = tot_len; + + return 0; +} + +/* Decompressor */ +int lzfx_decompress(const void *ibuf, unsigned int ilen, + void *obuf, unsigned int *olen) +{ + + u8 const *ip = (const u8 *)ibuf; + u8 const *const in_end = ip + ilen; + u8 *op = (u8 *)obuf; + u8 const *const out_end = (olen == NULL ? NULL : op + *olen); + + unsigned int remain_len = 0; + int rc; + + if (olen == NULL) + return LZFX_EARGS; + if (ibuf == NULL) + { + if (ilen != 0) + return LZFX_EARGS; + *olen = 0; + return 0; + } + if (obuf == NULL) + { + if (olen != 0) + return LZFX_EARGS; + return lzfx_getsize(ibuf, ilen, olen); + } + + do + { + unsigned int ctrl = *ip++; + + /* Format 000LLLLL: a literal byte string follows, of length L+1 */ + if (ctrl < (1 << 5)) + { + + ctrl++; + + if (fx_expect_false(op + ctrl > out_end)) + { + --ip; /* Rewind to control byte */ + goto guess; + } + if (fx_expect_false(ip + ctrl > in_end)) + return LZFX_ECORRUPT; + + do + *op++ = *ip++; + while (--ctrl); + + /* Format #1 [LLLooooo oooooooo]: backref of length L+1+2 + ^^^^^ ^^^^^^^^ + A B + #2 [111ooooo LLLLLLLL oooooooo] backref of length L+7+2 + ^^^^^ ^^^^^^^^ + A B + In both cases the location of the backref is computed from the + remaining part of the data as follows: + + location = op - A*256 - B - 1 + */ + } + else + { + + unsigned int len = (ctrl >> 5); + u8 *ref = op - ((ctrl & 0x1f) << 8) - 1; + + if (len == 7) + len += *ip++; /* i.e. format #2 */ + + len += 2; /* len is now #octets */ + + if (fx_expect_false(op + len > out_end)) + { + ip -= (len >= 9) ? 2 : 1; /* Rewind to control byte */ + goto guess; + } + if (fx_expect_false(ip >= in_end)) + return LZFX_ECORRUPT; + + ref -= *ip++; + + if (fx_expect_false(ref < (u8 *)obuf)) + return LZFX_ECORRUPT; + + do + *op++ = *ref++; + while (--len); + } + + } while (ip < in_end); + + *olen = op - (u8 *)obuf; + + return 0; + +guess: + rc = lzfx_getsize(ip, ilen - (ip - (u8 *)ibuf), &remain_len); + if (rc >= 0) + *olen = remain_len + (op - (u8 *)obuf); + return rc; +} diff --git a/firmware/src/lzfx.h b/firmware/src/lzfx.h new file mode 100644 index 0000000..761f624 --- /dev/null +++ b/firmware/src/lzfx.h @@ -0,0 +1,18 @@ +/* + * Lzfx decompressor + * WHowe + * This is actually taken from CrazyRedMachine's repo + * + */ + +#define LZFX_ESIZE -1 /* Output buffer too small */ +#define LZFX_ECORRUPT -2 /* Invalid data for decompression */ +#define LZFX_EARGS -3 /* Arguments invalid (NULL) */ +#define LZFX_HLOG 16 +#define LZFX_HSIZE (1 << (LZFX_HLOG)) + +#define fx_expect_false(expr) (expr) +#define fx_expect_true(expr) (expr) + +int lzfx_decompress(const void* ibuf, unsigned int ilen, + void* obuf, unsigned int *olen); \ No newline at end of file diff --git a/firmware/src/main.c b/firmware/src/main.c index 36763a2..9a29d7f 100644 --- a/firmware/src/main.c +++ b/firmware/src/main.c @@ -33,6 +33,7 @@ #include "rgb.h" #include "button.h" #include "hebtn.h" +#include "lzfx.h" struct __attribute__((packed)) { uint16_t buttons; // 16 buttons; see JoystickButtons_t for bit mapping @@ -101,12 +102,8 @@ static void gen_joy_report() } -static uint64_t last_hid_time = 0; -static bool motor_running = false; - static void run_lights() { - uint64_t now = time_us_64(); uint32_t button_colors[] = { rgb32(0, 0xff, 0, false), rgb32(0xe0, 0x10, 0xe0, false), @@ -119,19 +116,27 @@ static void run_lights() for (int i = 0; i < 5; i++) { bool pressed = buttons & (1 << i); uint32_t color = pressed ? button_colors[i] : 0x505050; - if (motor_running && (now / 50000 % 2 == 0)) { - color = 0; - } rgb_button_color(i, color); } + uint64_t now = time_us_64(); + + static int rainbow_level = 0; + int zone_num = slider_zone_num(); uint32_t phase = ((now >> 10) / zone_num) & 0xff; - if (now - last_hid_time >= 1000000) { - for (int i = 0; i < zone_num; i++) { - uint32_t color = rgb32_from_hsv(i * 256 / zone_num + phase, 255, 96); - rgb_slider_color(i, slider_touched(i) ? 0xffffff: color); - } + uint32_t touch_bits = slider_touch_bits(); + + if ((touch_bits) && (rainbow_level > 80)) { + rainbow_level -= (rainbow_level > 168) ? 2 : 1; + } + if ((!touch_bits) && (rainbow_level < 256)) { + rainbow_level += (rainbow_level < 168) ? 2 : 1; + } + + for (int i = 0; i < zone_num; i++) { + uint32_t color = rgb32_from_hsv(i * 256 / zone_num + phase, 255, rainbow_level / 2); + rgb_slider_color(i, touch_bits & (1 << i) ? 0xffffff: color); } } @@ -271,12 +276,30 @@ void tud_hid_set_report_cb(uint8_t itf, uint8_t report_id, hid_report_type_t report_type, uint8_t const *buffer, uint16_t bufsize) { - if (bufsize > 1) { - motor_running = !motor_running; - } if (report_type == HID_REPORT_TYPE_OUTPUT) { - last_hid_time = time_us_64(); - return; - } + static uint8_t obuf[48]; + memcpy(obuf, buffer, bufsize); + if (report_id == REPORT_ID_LED_SLIDER_1) { + rgb_set_hid_slider(0, 16, obuf); + } else if (report_id == REPORT_ID_LED_SLIDER_2) { + rgb_set_hid_slider(16, 16, obuf); + } else if (report_id == REPORT_ID_LED_BUTTON) { + rgb_set_hid_button(obuf); + } + } else if (report_type == HID_REPORT_TYPE_FEATURE) { + if (report_id == REPORT_ID_LED_COMPRESSED) { + static uint8_t fbuf[64]; + memcpy(fbuf, buffer, bufsize); + static uint8_t decomp[100]; + unsigned int olen = sizeof(decomp); + if (lzfx_decompress(fbuf + 1, fbuf[0], decomp, &olen) != 0) { + return; + } + if (olen < 96) { + return; + } + rgb_set_hid_slider(0, 32, decomp); + } + } } diff --git a/firmware/src/rgb.c b/firmware/src/rgb.c index e72d65f..bca7fce 100644 --- a/firmware/src/rgb.c +++ b/firmware/src/rgb.c @@ -20,9 +20,13 @@ #include "board_defs.h" #include "config.h" -static uint32_t rgb_buf[37]; // 4(Main buttons) + 1(Start button) + 32(Max Slider) +#define HID_LIGHT_TIMEOUT_MS 1000 -static int8_t button_rgb[] = RGB_BUTTON_MAP; +static uint32_t main_buf[37]; // 4(Main buttons) + 1(Start button) + 32(Max Slider) +static uint32_t hid_buf[37]; +static uint64_t hid_timeout = 0; + +static int8_t button_rgb_map[] = RGB_BUTTON_MAP; #define _MAP_LED(x) _MAKE_MAPPER(x) #define _MAKE_MAPPER(x) MAP_LED_##x @@ -92,20 +96,23 @@ static void drive_led() } last = now; - for (int i = 0; i < count_of(rgb_buf); i++) { - pio_sm_put_blocking(pio0, 0, rgb_buf[i] << 8u); + bool use_hid = (hid_timeout > 0) && (now < hid_timeout); + const uint32_t *buf = use_hid ? hid_buf : main_buf; + + for (int i = 0; i < count_of(main_buf); i++) { + pio_sm_put_blocking(pio0, 0, buf[i] << 8u); } } void rgb_set_colors(const uint32_t *colors, unsigned index, size_t num) { - if (index >= count_of(rgb_buf)) { + if (index >= count_of(main_buf)) { return; } - if (index + num > count_of(rgb_buf)) { - num = count_of(rgb_buf) - index; + if (index + num > count_of(main_buf)) { + num = count_of(main_buf) - index; } - memcpy(&rgb_buf[index], colors, num * sizeof(*colors)); + memcpy(&main_buf[index], colors, num * sizeof(*colors)); } static inline uint32_t apply_level(uint32_t color, uint8_t level) @@ -123,8 +130,8 @@ static inline uint32_t apply_level(uint32_t color, uint8_t level) void rgb_button_color(unsigned index, uint32_t color) { - if (index < count_of(button_rgb)) { - rgb_buf[button_rgb[index]] = apply_level(color, diva_cfg->light.level.button); + if (index < count_of(button_rgb_map)) { + main_buf[button_rgb_map[index]] = apply_level(color, diva_cfg->light.level.button); } } @@ -133,7 +140,51 @@ void rgb_slider_color(unsigned index, uint32_t color) if (index > 32) { return; } - rgb_buf[5 + index] = apply_level(color, diva_cfg->light.level.slider); + main_buf[5 + index] = apply_level(color, diva_cfg->light.level.slider); +} + +static inline void update_hid_timeout() +{ + hid_timeout = time_us_64() + HID_LIGHT_TIMEOUT_MS * 1000; +} + +static inline uint32_t led_rgb(const uint8_t *grb, int index) +{ + const uint8_t *src = grb + index * 3; + return rgb32(src[1], src[0], src[2], false); +} + +void rgb_set_hid_slider(unsigned index, unsigned num, const uint8_t *grb) +{ + for (int i = 0; i < num; i++) { + if (index + i >= 32) { + return; + } + uint32_t *dest = &hid_buf[5 + index + i]; + *dest = apply_level(led_rgb(grb, i), diva_cfg->light.level.slider); + } + + update_hid_timeout(); +} + +void rgb_set_hid_button(const uint8_t *scale) +{ + uint32_t button_colors[] = { + rgb32(0, 0xff, 0, false), + rgb32(0xe0, 0x10, 0xe0, false), + rgb32(0, 0, 0xff, false), + rgb32(0xff, 0, 0, false), + }; + + for (int i = 0; i < 4; i++) { + int button_led = button_rgb_map[i]; + uint32_t color = apply_level(button_colors[i], scale[i]); + hid_buf[button_led] = apply_level(color, diva_cfg->light.level.button); + } + + hid_buf[4] = main_buf[4]; // HID doesn't have start button light + + update_hid_timeout(); } void rgb_init() @@ -144,6 +195,7 @@ void rgb_init() ws2812_program_init(pio0, 0, pio0_offset, RGB_PIN, 800000, false); } + void rgb_update() { drive_led(); diff --git a/firmware/src/rgb.h b/firmware/src/rgb.h index 178fb95..2056448 100644 --- a/firmware/src/rgb.h +++ b/firmware/src/rgb.h @@ -22,4 +22,7 @@ void rgb_set_colors(const uint32_t *colors, unsigned index, size_t num); void rgb_button_color(unsigned index, uint32_t color); void rgb_slider_color(unsigned index, uint32_t color); +void rgb_set_hid_slider(unsigned index, unsigned num, const uint8_t *grb); +void rgb_set_hid_button(const uint8_t *scale); + #endif diff --git a/firmware/src/slider.c b/firmware/src/slider.c index 94c37dc..d974e71 100644 --- a/firmware/src/slider.c +++ b/firmware/src/slider.c @@ -102,6 +102,11 @@ bool slider_touched(unsigned zone) return touch_bits & (1 << zone); } +uint32_t slider_touch_bits() +{ + return touch_bits; +} + unsigned slider_count(unsigned zone) { if (zone >= zone_num) { diff --git a/firmware/src/slider.h b/firmware/src/slider.h index 1447270..49560f9 100644 --- a/firmware/src/slider.h +++ b/firmware/src/slider.h @@ -13,6 +13,8 @@ void slider_init(); void slider_update(); uint16_t slider_zone_num(); bool slider_touched(unsigned zone); +uint32_t slider_touch_bits(); + const uint16_t *slider_raw(); void slider_update_config(); unsigned slider_count(unsigned zone); diff --git a/firmware/src/tusb_config.h b/firmware/src/tusb_config.h index 610a6c4..bc48905 100644 --- a/firmware/src/tusb_config.h +++ b/firmware/src/tusb_config.h @@ -102,9 +102,6 @@ extern "C" { #define CFG_TUD_MIDI 0 #define CFG_TUD_VENDOR 0 -// HID buffer size Should be sufficient to hold ID (if any) + Data -#define CFG_TUD_HID_EP_BUFSIZE 64 - #define CFG_TUD_VENDOR 0 // HID buffer size Should be sufficient to hold ID (if any) + Data diff --git a/firmware/src/usb_descriptors.c b/firmware/src/usb_descriptors.c index f47b86f..c8b18f2 100644 --- a/firmware/src/usb_descriptors.c +++ b/firmware/src/usb_descriptors.c @@ -78,8 +78,13 @@ uint8_t const desc_hid_report_joy[] = { DIVAPICO_REPORT_DESC_JOYSTICK, }; -uint8_t const desc_hid_report_nkro[] = { - DIVAPICO_REPORT_DESC_NKRO, +uint8_t const desc_hid_report_led[] = { + DIVAPICO_LED_HEADER, + DIVAPICO_REPORT_DESC_LED_SLIDER_1, + DIVAPICO_REPORT_DESC_LED_SLIDER_2, + DIVAPICO_REPORT_DESC_LED_BUTTON, + DIVAPICO_REPORT_DESC_LED_COMPRESSED, + DIVAPICO_LED_FOOTER }; // Invoked when received GET HID REPORT DESCRIPTOR @@ -91,7 +96,7 @@ uint8_t const* tud_hid_descriptor_report_cb(uint8_t itf) case 0: return desc_hid_report_joy; case 1: - return desc_hid_report_nkro; + return desc_hid_report_led; default: return NULL; } @@ -100,7 +105,7 @@ uint8_t const* tud_hid_descriptor_report_cb(uint8_t itf) // Configuration Descriptor //--------------------------------------------------------------------+ -enum { ITF_NUM_JOY, ITF_NUM_NKRO, +enum { ITF_NUM_JOY, ITF_NUM_LED, ITF_NUM_CLI, ITF_NUM_CLI_DATA, ITF_NUM_TOTAL }; @@ -112,8 +117,7 @@ enum { ITF_NUM_JOY, ITF_NUM_NKRO, #define EPNUM_JOY_OUT 0x01 #define EPNUM_JOY_IN 0x81 -//#define EPNUM_LED 0x86 -#define EPNUM_KEY 0x87 +#define EPNUM_LED 0x86 #define EPNUM_CLI_NOTIF 0x89 #define EPNUM_CLI_OUT 0x0a @@ -131,9 +135,9 @@ uint8_t const desc_configuration_joy[] = { sizeof(desc_hid_report_joy), EPNUM_JOY_OUT, EPNUM_JOY_IN, CFG_TUD_HID_EP_BUFSIZE, 1), - TUD_HID_DESCRIPTOR(ITF_NUM_NKRO, 5, HID_ITF_PROTOCOL_NONE, - sizeof(desc_hid_report_nkro), EPNUM_KEY, - CFG_TUD_HID_EP_BUFSIZE, 1), + TUD_HID_DESCRIPTOR(ITF_NUM_LED, 5, HID_ITF_PROTOCOL_NONE, + sizeof(desc_hid_report_led), EPNUM_LED, + CFG_TUD_HID_EP_BUFSIZE, 4), TUD_CDC_DESCRIPTOR(ITF_NUM_CLI, 6, EPNUM_CLI_NOTIF, 8, EPNUM_CLI_OUT, EPNUM_CLI_IN, 64), @@ -157,7 +161,7 @@ const char *string_desc_arr[] = { "Diva Pico Controller", // 2: Product "123456", // 3: Serials, should use chip ID "Diva Pico Joystick", - "Diva Pico NKRO", + "Diva Pico LED", "Diva Pico CLI Port", }; @@ -175,25 +179,12 @@ uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid) } const size_t base_num = sizeof(string_desc_arr) / sizeof(string_desc_arr[0]); - const char *colors[] = {"Blue", "Red", "Green"}; char str[64]; if (index < base_num) { strcpy(str, string_desc_arr[index]); - } else if (index < base_num + 48 + 45) { - const char *names[] = {"Key ", "Splitter "}; - int led = index - base_num; - int id = led / 6 + 1; - int type = led / 3 % 2; - int brg = led % 3; - sprintf(str, "%s%02d %s", names[type], id, colors[brg]); - } else if (index < base_num + 48 + 45 + 18) { - int led = index - base_num - 48 - 45; - int id = led / 3 + 1; - int brg = led % 3; - sprintf(str, "Tower %02d %s", id, colors[brg]); } else { - sprintf(str, "Unknown %d", index); + sprintf(str, "B%02d", index - base_num); } uint8_t chr_count = strlen(str); diff --git a/firmware/src/usb_descriptors.h b/firmware/src/usb_descriptors.h index 01a583c..07f71ac 100644 --- a/firmware/src/usb_descriptors.h +++ b/firmware/src/usb_descriptors.h @@ -6,6 +6,10 @@ enum { REPORT_ID_JOYSTICK = 1, + REPORT_ID_LED_SLIDER_1 = 4, + REPORT_ID_LED_SLIDER_2 = 5, + REPORT_ID_LED_BUTTON = 6, + REPORT_ID_LED_COMPRESSED = 11, }; // because they are missing from tusb_hid.h @@ -58,40 +62,52 @@ enum { HID_OUTPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \ HID_COLLECTION_END -#define DIVAPICO_REPORT_DESC_NKRO \ - HID_USAGE_PAGE(HID_USAGE_PAGE_DESKTOP), \ - HID_USAGE(HID_USAGE_DESKTOP_KEYBOARD), \ +#define DIVAPICO_LED_HEADER \ + HID_USAGE_PAGE(HID_USAGE_PAGE_DESKTOP), HID_USAGE(0x00), \ HID_COLLECTION(HID_COLLECTION_APPLICATION), \ - /* Modifier */ \ - HID_REPORT_SIZE(1), \ - HID_REPORT_COUNT(8), \ - HID_USAGE_PAGE(HID_USAGE_PAGE_KEYBOARD), \ - HID_USAGE_MIN(224), \ - HID_USAGE_MAX(231), \ - HID_LOGICAL_MIN(0), \ - HID_LOGICAL_MAX(1), \ - HID_INPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \ - /* LED output that we don't care */ \ - HID_REPORT_COUNT(5), \ - HID_REPORT_SIZE(1), \ - HID_USAGE_PAGE(HID_USAGE_PAGE_LED), \ - HID_USAGE_MIN(1), \ - HID_USAGE_MAX(5), \ - HID_OUTPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \ - HID_REPORT_COUNT(1), \ - HID_REPORT_SIZE(3), \ - HID_OUTPUT(HID_CONSTANT), \ - /* Full Keyboard Bitmap */ \ - HID_REPORT_SIZE(1), \ - HID_REPORT_COUNT(120), \ - HID_LOGICAL_MIN(0), \ - HID_LOGICAL_MAX(1), \ - HID_USAGE_PAGE(HID_USAGE_PAGE_KEYBOARD), \ - HID_USAGE_MIN(0), \ - HID_USAGE_MAX(119), \ - HID_INPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \ + HID_REPORT_COUNT(1), HID_REPORT_SIZE(8), \ + HID_INPUT(HID_CONSTANT | HID_VARIABLE | HID_ABSOLUTE) + +#define DIVAPICO_LED_FOOTER \ HID_COLLECTION_END -// HID_REPORT_ID(REPORT_ID_NKRO) +// Slider First 16 LEDs (48 rgb zones, BRG order) +#define DIVAPICO_REPORT_DESC_LED_SLIDER_1 \ + HID_REPORT_ID(REPORT_ID_LED_SLIDER_1) \ + HID_REPORT_COUNT(48), HID_REPORT_SIZE(8), \ + HID_LOGICAL_MIN(0x00), HID_LOGICAL_MAX_N(0x00ff, 2), \ + HID_USAGE_PAGE(HID_USAGE_PAGE_ORDINAL), \ + HID_USAGE_MIN(1), HID_USAGE_MAX(48), \ + HID_STRING_MINIMUM(8), HID_STRING_MAXIMUM(55), \ + HID_OUTPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) + +// Slider Remaining 16 LEDs (48 rgb zones, BRG order) +#define DIVAPICO_REPORT_DESC_LED_SLIDER_2 \ + HID_REPORT_ID(REPORT_ID_LED_SLIDER_2) \ + HID_REPORT_COUNT(48), HID_REPORT_SIZE(8), \ + HID_LOGICAL_MIN(0x00), HID_LOGICAL_MAX_N(0x00ff, 2), \ + HID_USAGE_PAGE(HID_USAGE_PAGE_ORDINAL), \ + HID_USAGE_MIN(49), HID_USAGE_MAX(96), \ + HID_STRING_MINIMUM(56), HID_STRING_MAXIMUM(103), /* Delta to previous */ \ + HID_OUTPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) + +// Button LEDs (18 rgb zones, BRG order) +#define DIVAPICO_REPORT_DESC_LED_BUTTON \ + HID_REPORT_ID(REPORT_ID_LED_BUTTON) \ + HID_REPORT_COUNT(4), HID_REPORT_SIZE(8), \ + HID_LOGICAL_MIN(0x00), HID_LOGICAL_MAX_N(0x00ff, 2), \ + HID_USAGE_PAGE(HID_USAGE_PAGE_ORDINAL), \ + HID_USAGE_MIN(97), HID_USAGE_MAX(100), \ + HID_STRING_MINIMUM(104), HID_STRING_MAXIMUM(107), /* Delta to previous */ \ + HID_OUTPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) + +// LEDs Compressed +#define DIVAPICO_REPORT_DESC_LED_COMPRESSED \ + HID_REPORT_ID(REPORT_ID_LED_COMPRESSED) \ + HID_USAGE_PAGE(HID_USAGE_PAGE_ORDINAL), \ + HID_USAGE(0x00), \ + HID_LOGICAL_MIN(0x00), HID_LOGICAL_MAX_N(0x00ff, 2), \ + HID_REPORT_SIZE(8), HID_REPORT_COUNT(63), \ + HID_FEATURE(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) #endif /* USB_DESCRIPTORS_H_ */