From 67d415db50ca513ee25efd49bdd21beeb92fbe4c Mon Sep 17 00:00:00 2001 From: whowechina Date: Sat, 9 Sep 2023 22:23:55 +0800 Subject: [PATCH] Firmware done, I call it beta --- Production/Firmware/chu_pico.uf2 | Bin 0 -> 81408 bytes firmware/src/CMakeLists.txt | 2 +- firmware/src/air.c | 41 +++++++- firmware/src/air.h | 3 +- firmware/src/lzfx.c | 169 +++++++++++++++++++++++++++++++ firmware/src/lzfx.h | 18 ++++ firmware/src/main.c | 151 +++++++++++++++++---------- firmware/src/rgb.c | 23 ++++- firmware/src/rgb.h | 3 + firmware/src/slider.c | 6 +- firmware/src/usb_descriptors.c | 104 ++++++++++--------- firmware/src/usb_descriptors.h | 169 +++++++++++++++---------------- 12 files changed, 492 insertions(+), 197 deletions(-) create mode 100644 Production/Firmware/chu_pico.uf2 create mode 100644 firmware/src/lzfx.c create mode 100644 firmware/src/lzfx.h diff --git a/Production/Firmware/chu_pico.uf2 b/Production/Firmware/chu_pico.uf2 new file mode 100644 index 0000000000000000000000000000000000000000..4bf1518b7d7ce95c2af7c746563ad9c34a1436a6 GIT binary patch literal 81408 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 6) { + return 0; + } + + return 1 << index; +} + +uint8_t air_bitmap() +{ + uint8_t bitmap = 0; + for (int i = 0; i < sizeof(TOF_LIST); i++) { + bitmap |= air_bits(distances[i], offset_a); + bitmap |= air_bits(distances[i], offset_b); + } + return bitmap; +} + +unsigned air_value(uint8_t index) { if (index >= sizeof(TOF_LIST)) { return 0; } - uint16_t dist = distances[index] >> 6; - return dist < 63 ? dist : 0; + uint8_t bitmap = air_bits(distances[index], offset_a) | + air_bits(distances[index], offset_b); + + for (int i = 0; i < 6; i++) { + if (bitmap & (1 << i)) { + return i + 1; + } + } + + return 0; } void air_update() diff --git a/firmware/src/air.h b/firmware/src/air.h index 4f32628..748bdd2 100644 --- a/firmware/src/air.h +++ b/firmware/src/air.h @@ -11,7 +11,8 @@ void air_init(); size_t air_num(); -uint16_t air_value(uint8_t index); +unsigned air_value(uint8_t index); +uint8_t air_bitmap(); void air_update(); #endif diff --git a/firmware/src/lzfx.c b/firmware/src/lzfx.c new file mode 100644 index 0000000..63dddeb --- /dev/null +++ b/firmware/src/lzfx.c @@ -0,0 +1,169 @@ +/* + * Lzfx decompressor + * WHowe + * 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 c453445..d94fcc8 100644 --- a/firmware/src/main.c +++ b/firmware/src/main.c @@ -7,10 +7,11 @@ #include #include +#include "pico/stdio.h" +#include "pico/stdlib.h" #include "bsp/board.h" #include "pico/multicore.h" #include "pico/bootrom.h" -#include "pico/stdio.h" #include "hardware/gpio.h" #include "hardware/sync.h" @@ -23,6 +24,7 @@ #include "slider.h" #include "air.h" #include "rgb.h" +#include "lzfx.h" /* Measure the time of a function call */ #define RUN_TIME(func) \ @@ -39,7 +41,6 @@ struct __attribute__((packed)) { void report_usb_hid() { if (tud_hid_ready()) { - hid_joy.buttons = 0; hid_joy.HAT = 0; hid_joy.VendorSpec = 0; tud_hid_n_report(0x00, REPORT_ID_JOYSTICK, &hid_joy, sizeof(hid_joy)); @@ -56,32 +57,72 @@ static void pause_core1(bool pause) } } -static void fps_count() +static int fps[2] = {0}; + +static int get_fps(int core) { - static uint64_t last = 0; - static int fps_counter = 0; + return fps[core]; +} - fps_counter++; +static void fps_count(int core) +{ + static uint32_t last[2] = {0}; + static int counter[2] = {0}; - uint64_t now = time_us_64(); - if (now - last < 1000000) { + counter[core]++; + + uint32_t now = time_us_32(); + if (now - last[core] < 1000000) { + return; + } + last[core] = now; + fps[core] = counter[core]; + counter[core] = 0; +} + +static void print_fps() +{ + static uint32_t last = 0; + uint32_t now = time_us_32(); + if (now - last < 5000000) { return; } last = now; - printf("FPS: %d\n", fps_counter); - fps_counter = 0; + printf("FPS: %d %d\n", get_fps(0), get_fps(1)); } -static void core1_loop() +static void gen_hid_report() { - while (1) { - rgb_update(); - sleep_ms(1); + hid_joy.axis = 0; + for (int i = 0; i < 16; i++) { + if (slider_touched(i * 2)) { + hid_joy.axis |= 1 << (30 - i * 2); + } + if (slider_touched(i * 2 + 1)) { + hid_joy.axis |= 1 << (31 - i * 2); + } + } + hid_joy.axis ^= 0x80808080; // some magic number from CrazyRedMachine + hid_joy.buttons = air_bitmap(); } -static void core0_loop() +static uint64_t last_hid_time = 0; + +static void run_lights() { + uint64_t now = time_us_64(); + if (now - last_hid_time < 1000000) { + return; + } + + const uint32_t colors[] = {0x000000, 0x0000ff, 0xff0000, 0xffff00, + 0x00ff00, 0x00ffff, 0xffffff}; + for (int i = 0; i < air_num(); i++) { + int d = air_value(i); + rgb_set_color(31 + i, colors[d]); + } + for (int i = 0; i < 15; i++) { int x = 15 - i; uint8_t r = (x & 0x01) ? 10 : 0; @@ -90,50 +131,44 @@ static void core0_loop() rgb_gap_color(i, rgb32(r, g, b, false)); } + for (int i = 0; i < 16; i++) { + bool r = slider_touched(i * 2); + bool g = slider_touched(i * 2 + 1); + rgb_set_color(30 - i * 2, rgb32(r ? 80 : 0, g ? 80 : 0, 0, false)); + } +} + +static void core1_loop() +{ + while (1) { + rgb_update(); + fps_count(1); + sleep_ms(1); + } +} + +static void core0_loop() +{ while(1) { tud_task(); - report_usb_hid(); slider_update(); air_update(); -#if 0 - static uint16_t old_touch = 0; - uint16_t touch = slider_hw_touch(0); - if (touch != old_touch) { - printf("Touch: %04x\n", touch); - old_touch = touch; - } -#endif - hid_joy.axis = 0; - for (int i = 0; i < 16; i++) { - bool k1 = slider_touched(i * 2); - bool k2 = slider_touched(i * 2 + 1); + gen_hid_report(); + report_usb_hid(); - if (k1) { - hid_joy.axis |= 1 << (30 - i * 2); - } - if (k2) { - hid_joy.axis |= 1 << (31 - i * 2); - } - - uint8_t r = k1 ? 255 : 0; - uint8_t g = k2 ? 255 : 0; - rgb_key_color(i, rgb32(r, g, g, false)); - } - hid_joy.axis ^= 0x80808080; // some magic number from CrazyRedMachine - for (int i = 0; i < air_num(); i++) { - uint8_t v = air_value(i) << 2; - rgb_set_color(31 + i, rgb32(v, v, v, false)); - } + run_lights(); slider_update_baseline(); - fps_count(); + fps_count(0); + print_fps(); } } void init() { sleep_ms(200); +// set_sys_clock_khz(166000, true); board_init(); tusb_init(); stdio_init_all(); @@ -168,14 +203,26 @@ void tud_hid_set_report_cb(uint8_t itf, uint8_t report_id, uint16_t bufsize) { if (report_type == HID_REPORT_TYPE_OUTPUT) { - if (report_id == REPORT_ID_LED_SLIDER_15) { - printf("Slider 16: %d\n", bufsize); - } else if (report_id == REPORT_ID_LED_SLIDER_16) { - printf("Slider 15: %d\n", bufsize); + if (report_id == REPORT_ID_LED_SLIDER_16) { + rgb_set_brg(0, buffer, bufsize / 3); + } else if (report_id == REPORT_ID_LED_SLIDER_15) { + rgb_set_brg(16, buffer, bufsize / 3); } else if (report_id == REPORT_ID_LED_TOWER_6) { - printf("Tower 6: %d\n", bufsize); - } else { - printf("Report: %d - %d\n", report_id, bufsize); + rgb_set_brg(31, buffer, bufsize / 3); } + last_hid_time = time_us_64(); + return; + } + + if (report_type == HID_REPORT_TYPE_FEATURE) { + if (report_id == REPORT_ID_LED_COMPRESSED) { + uint8_t buf[(48 + 45 + 6) * 3]; + unsigned int olen = sizeof(buf); + if (lzfx_decompress(buffer + 1, buffer[0], buf, &olen) == 0) { + rgb_set_brg(0, buf, olen / 3); + } + } + last_hid_time = time_us_64(); + return; } } diff --git a/firmware/src/rgb.c b/firmware/src/rgb.c index 6720c79..987324c 100644 --- a/firmware/src/rgb.c +++ b/firmware/src/rgb.c @@ -22,7 +22,7 @@ #define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) -static uint32_t rgb_buf[48]; // 32 + 16(maximum) ToF indicators +static uint32_t rgb_buf[47]; // 16(Keys) + 15(Gaps) + 16(maximum ToF indicators) #define _MAP_LED(x) _MAKE_MAPPER(x) #define _MAKE_MAPPER(x) MAP_LED_##x @@ -61,7 +61,10 @@ static void drive_led() } last = now; - for (int i = 0; i < ARRAY_SIZE(rgb_buf); i++) { + for (int i = 30; i >= 0; i--) { + pio_sm_put_blocking(pio0, 0, rgb_buf[i] << 8u); + } + for (int i = 31; i < ARRAY_SIZE(rgb_buf); i++) { pio_sm_put_blocking(pio0, 0, rgb_buf[i] << 8u); } } @@ -101,6 +104,22 @@ void rgb_gap_color(unsigned index, uint32_t color) rgb_buf[index * 2 + 1] = color; } +void rgb_set_brg(unsigned index, const uint8_t *brg_array, size_t num) +{ + if (index >= ARRAY_SIZE(rgb_buf)) { + return; + } + if (index + num > ARRAY_SIZE(rgb_buf)) { + num = ARRAY_SIZE(rgb_buf) - index; + } + for (int i = 0; i < num; i++) { + uint8_t b = brg_array[i * 3 + 0]; + uint8_t r = brg_array[i * 3 + 1]; + uint8_t g = brg_array[i * 3 + 2]; + rgb_buf[index + i] = rgb32(r, g, b, false); + } +} + void rgb_init() { diff --git a/firmware/src/rgb.h b/firmware/src/rgb.h index 67c2568..903cca0 100644 --- a/firmware/src/rgb.h +++ b/firmware/src/rgb.h @@ -22,4 +22,7 @@ void rgb_set_color(unsigned index, uint32_t color); void rgb_key_color(unsigned index, uint32_t color); void rgb_gap_color(unsigned index, uint32_t color); +/* num of the rgb leds, num*3 bytes in the array */ +void rgb_set_brg(unsigned index, const uint8_t *brg_array, size_t num); + #endif diff --git a/firmware/src/slider.c b/firmware/src/slider.c index 8aaf396..e8f6288 100644 --- a/firmware/src/slider.c +++ b/firmware/src/slider.c @@ -90,7 +90,6 @@ void slider_update() void slider_update_baseline() { static int iteration = 0; - iteration++; for (int i = 0; i < 32; i++) { int16_t delta = readout[i] - baseline[i]; @@ -100,21 +99,18 @@ void slider_update_baseline() error[i] += delta; } + iteration++; if (iteration > 100) { iteration = 0; for (int i = 0; i < 32; i++) { if (error[i] > 100) { baseline[i] ++; - printf("+"); } else if (error[i] < -100) { baseline[i] --; - printf("-"); } else { - printf(" "); } error[i] = 0; } - printf("\n"); } } diff --git a/firmware/src/usb_descriptors.c b/firmware/src/usb_descriptors.c index f9ba2ef..47d20da 100644 --- a/firmware/src/usb_descriptors.c +++ b/firmware/src/usb_descriptors.c @@ -36,8 +36,8 @@ */ #define _PID_MAP(itf, n) ((CFG_TUD_##itf) << (n)) #define USB_PID \ - (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \ - _PID_MAP(MIDI, 3) | _PID_MAP(VENDOR, 4)) + (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \ + _PID_MAP(MIDI, 3) | _PID_MAP(VENDOR, 4)) //--------------------------------------------------------------------+ // Device Descriptors @@ -67,7 +67,7 @@ tusb_desc_device_t desc_device_joy = { // Invoked when received GET DEVICE DESCRIPTOR // Application return pointer to descriptor uint8_t const* tud_descriptor_device_cb(void) { - return (uint8_t const*)&desc_device_joy; + return (uint8_t const*)&desc_device_joy; } //--------------------------------------------------------------------+ @@ -75,15 +75,17 @@ uint8_t const* tud_descriptor_device_cb(void) { //--------------------------------------------------------------------+ uint8_t const desc_hid_report_joy[] = { - GAMECON_REPORT_DESC_JOYSTICK(HID_REPORT_ID(REPORT_ID_JOYSTICK)), + GAMECON_REPORT_DESC_JOYSTICK, }; uint8_t const desc_hid_report_led[] = { - GAMECON_REPORT_DESC_LED_SLIDER_16(HID_REPORT_ID(REPORT_ID_LED_SLIDER_16)), - GAMECON_REPORT_DESC_LED_SLIDER_15(HID_REPORT_ID(REPORT_ID_LED_SLIDER_15)), - GAMECON_REPORT_DESC_LED_TOWER_6(HID_REPORT_ID(REPORT_ID_LED_TOWER_6)), - GAMECON_REPORT_DESC_LED_COMPRESSED(HID_REPORT_ID(REPORT_ID_LED_COMPRESSED)), + GAMECON_LED_HEADER, + GAMECON_REPORT_DESC_LED_SLIDER_16, + GAMECON_REPORT_DESC_LED_SLIDER_15, + GAMECON_REPORT_DESC_LED_TOWER_6, + GAMECON_REPORT_DESC_LED_COMPRESSED, + GAMECON_LED_FOOTER }; // Invoked when received GET HID REPORT DESCRIPTOR @@ -128,19 +130,18 @@ uint8_t const desc_configuration_joy[] = { TUD_HID_DESCRIPTOR(ITF_NUM_LED, 5, HID_ITF_PROTOCOL_NONE, sizeof(desc_hid_report_led), EPNUM_LED, - CFG_TUD_HID_EP_BUFSIZE, 1), + CFG_TUD_HID_EP_BUFSIZE, 4), TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 6, EPNUM_CDC_NOTIF, 8, EPNUM_CDC_OUT, EPNUM_CDC_IN, 64) - }; +}; // Invoked when received GET CONFIGURATION DESCRIPTOR // Application return pointer to descriptor // Descriptor contents must exist long enough for transfer to complete uint8_t const* tud_descriptor_configuration_cb(uint8_t index) { - (void)index; // for multiple configurations - return desc_configuration_joy; + return desc_configuration_joy; } //--------------------------------------------------------------------+ @@ -153,47 +154,58 @@ const char *string_desc_arr[] = { "WHowe" , // 1: Manufacturer "Chu Pico Controller", // 2: Product "123456", // 3: Serials, should use chip ID - "Joystick Interface", - "LED Interface", - "Serial Port", + "Chu Pico Joystick", + "Chu Pico LED", + "Chu Pico Serial Port", }; -static uint16_t _desc_str[64]; - // Invoked when received GET STRING DESCRIPTOR request // Application return pointer to descriptor, whose contents must exist long // enough for transfer to complete -uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid) { - (void)langid; +uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid) +{ + static uint16_t _desc_str[64]; - uint8_t chr_count; - - if (index == 0) { - memcpy(&_desc_str[1], string_desc_arr[0], 2); - chr_count = 1; - } else { - // Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors. - // https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors - - if (index < sizeof(string_desc_arr) / sizeof(string_desc_arr[0])) { - const char* str = string_desc_arr[index]; - - // Cap at max char - chr_count = strlen(str); - if (chr_count > 63) chr_count = 63; - - // Convert ASCII string into UTF-16 - for (uint8_t i = 0; i < chr_count; i++) { - _desc_str[1 + i] = str[i]; - } - } else { - _desc_str[1] = 'X'; - chr_count = 1; + if (index == 0) { + memcpy(&_desc_str[1], string_desc_arr[0], 2); + _desc_str[0] = (TUSB_DESC_STRING << 8) | (2 + 2); + return _desc_str; } - } + + const size_t base_num = sizeof(string_desc_arr) / sizeof(string_desc_arr[0]); + const char *colors[] = {"Blue", "Red", "Green"}; + char str[64]; - // first byte is length (including header), second byte is string type - _desc_str[0] = (TUSB_DESC_STRING << 8) | (2 * chr_count + 2); + 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); + } - return _desc_str; + uint8_t chr_count = strlen(str); + if (chr_count > 63) { + chr_count = 63; + } + + // Convert ASCII string into UTF-16 + for (uint8_t i = 0; i < chr_count; i++) { + _desc_str[1 + i] = str[i]; + } + + // first byte is length (including header), second byte is string type + _desc_str[0] = (TUSB_DESC_STRING << 8) | (2 * chr_count + 2); + + return _desc_str; } diff --git a/firmware/src/usb_descriptors.h b/firmware/src/usb_descriptors.h index 591e024..9e82075 100644 --- a/firmware/src/usb_descriptors.h +++ b/firmware/src/usb_descriptors.h @@ -5,11 +5,11 @@ #include "device/usbd.h" enum { - REPORT_ID_JOYSTICK = 1, - REPORT_ID_LED_SLIDER_16 = 4, - REPORT_ID_LED_SLIDER_15 = 5, - REPORT_ID_LED_TOWER_6 = 6, - REPORT_ID_LED_COMPRESSED = 11, + REPORT_ID_JOYSTICK = 1, + REPORT_ID_LED_SLIDER_16 = 4, + REPORT_ID_LED_SLIDER_15 = 5, + REPORT_ID_LED_TOWER_6 = 6, + REPORT_ID_LED_COMPRESSED = 11, }; // because they are missing from tusb_hid.h @@ -22,94 +22,89 @@ enum { // Joystick Report Descriptor Template - Based off Drewol/rp2040-gamecon // Button Map | X | Y -#define GAMECON_REPORT_DESC_JOYSTICK(...) \ - HID_USAGE_PAGE(HID_USAGE_PAGE_DESKTOP), \ - HID_USAGE(HID_USAGE_DESKTOP_JOYSTICK), \ - HID_COLLECTION(HID_COLLECTION_APPLICATION), \ - __VA_ARGS__ HID_USAGE_PAGE(HID_USAGE_PAGE_BUTTON), \ - HID_USAGE_MIN(1), HID_USAGE_MAX(16), \ - HID_LOGICAL_MIN(0), HID_LOGICAL_MAX(1), \ - HID_REPORT_COUNT(16), HID_REPORT_SIZE(1), \ - HID_INPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \ +#define GAMECON_REPORT_DESC_JOYSTICK \ + HID_USAGE_PAGE(HID_USAGE_PAGE_DESKTOP), \ + HID_USAGE(HID_USAGE_DESKTOP_JOYSTICK), \ + HID_COLLECTION(HID_COLLECTION_APPLICATION), \ + HID_REPORT_ID(REPORT_ID_JOYSTICK) \ + HID_USAGE_PAGE(HID_USAGE_PAGE_BUTTON), \ + HID_USAGE_MIN(1), HID_USAGE_MAX(16), \ + HID_LOGICAL_MIN(0), HID_LOGICAL_MAX(1), \ + HID_REPORT_COUNT(16), HID_REPORT_SIZE(1), \ + HID_INPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \ \ - HID_USAGE_PAGE(HID_USAGE_PAGE_DESKTOP), \ - HID_USAGE(HID_USAGE_DESKTOP_HAT_SWITCH), \ - HID_LOGICAL_MIN(1), HID_LOGICAL_MAX(8), \ - HID_PHYSICAL_MIN(0), HID_PHYSICAL_MAX_N(315, 2), \ - HID_REPORT_SIZE(8), HID_REPORT_COUNT(1), \ - HID_INPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \ + HID_USAGE_PAGE(HID_USAGE_PAGE_DESKTOP), \ + HID_USAGE(HID_USAGE_DESKTOP_HAT_SWITCH), \ + HID_LOGICAL_MIN(1), HID_LOGICAL_MAX(8), \ + HID_PHYSICAL_MIN(0), HID_PHYSICAL_MAX_N(315, 2), \ + HID_REPORT_SIZE(8), HID_REPORT_COUNT(1), \ + HID_INPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \ \ - HID_USAGE_PAGE(HID_USAGE_PAGE_DESKTOP), \ - HID_USAGE(HID_USAGE_DESKTOP_X), HID_USAGE(HID_USAGE_DESKTOP_Y), \ - HID_USAGE(HID_USAGE_DESKTOP_Z), HID_USAGE(HID_USAGE_DESKTOP_RX), \ - HID_LOGICAL_MIN(0x00), HID_LOGICAL_MAX(0xff), /* Analog */ \ - HID_REPORT_SIZE(8), HID_REPORT_COUNT(4), \ - HID_INPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \ + HID_USAGE_PAGE(HID_USAGE_PAGE_DESKTOP), \ + HID_USAGE(HID_USAGE_DESKTOP_X), HID_USAGE(HID_USAGE_DESKTOP_Y), \ + HID_USAGE(HID_USAGE_DESKTOP_Z), HID_USAGE(HID_USAGE_DESKTOP_RX), \ + HID_LOGICAL_MIN(0x00), HID_LOGICAL_MAX(0xff), /* Analog */ \ + HID_REPORT_SIZE(8), HID_REPORT_COUNT(4), \ + HID_INPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \ \ - HID_USAGE_PAGE_N(HID_USAGE_PAGE_VENDOR, 2), \ - HID_USAGE(0), \ - HID_LOGICAL_MIN(0x00), HID_LOGICAL_MAX(0xff), \ - HID_REPORT_SIZE(8), HID_REPORT_COUNT(1), \ - HID_INPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \ - \ - HID_COLLECTION_END + HID_USAGE_PAGE_N(HID_USAGE_PAGE_VENDOR, 2), \ + HID_USAGE(0), \ + HID_LOGICAL_MIN(0x00), HID_LOGICAL_MAX(0xff), \ + HID_REPORT_SIZE(8), HID_REPORT_COUNT(1), \ + HID_INPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \ + HID_COLLECTION_END + +#define GAMECON_LED_HEADER \ + HID_USAGE_PAGE(HID_USAGE_PAGE_DESKTOP), HID_USAGE(0x00), \ + HID_COLLECTION(HID_COLLECTION_APPLICATION), \ + HID_REPORT_COUNT(1), HID_REPORT_SIZE(8), \ + HID_INPUT(HID_CONSTANT | HID_VARIABLE | HID_ABSOLUTE) + +#define GAMECON_LED_FOOTER \ + HID_COLLECTION_END + +// Slider First 16 LEDs (48 rgb zones, BRG order) +#define GAMECON_REPORT_DESC_LED_SLIDER_16 \ + HID_REPORT_ID(REPORT_ID_LED_SLIDER_16) \ + 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(7), HID_STRING_MAXIMUM(54), \ + HID_OUTPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) + +// Slider Remaining 15 LEDs (45 rgb zones, BRG order) +#define GAMECON_REPORT_DESC_LED_SLIDER_15 \ + HID_REPORT_ID(REPORT_ID_LED_SLIDER_15) \ + HID_REPORT_COUNT(45), 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(93), \ + HID_STRING_MINIMUM(7), HID_STRING_MAXIMUM(51), /* Delta to previous */ \ + HID_OUTPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) + +// Tower LEDs (18 rgb zones, BRG order) +#define GAMECON_REPORT_DESC_LED_TOWER_6 \ + HID_REPORT_ID(REPORT_ID_LED_TOWER_6) \ + HID_REPORT_COUNT(18), HID_REPORT_SIZE(8), \ + HID_LOGICAL_MIN(0x00), HID_LOGICAL_MAX_N(0x00ff, 2), \ + HID_USAGE_PAGE(HID_USAGE_PAGE_ORDINAL), \ + HID_USAGE_MIN(94), HID_USAGE_MAX(111), \ + HID_STRING_MINIMUM(7), HID_STRING_MAXIMUM(24), /* Delta to previous */ \ + HID_OUTPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE) /* - report id 4 : slider first 48 leds (16 rgb zones, brg order) - report id 5 : slider remaining 45 leds (15 rgb zones, brg order) - report id 6 : tower 18 leds (6 rgb, brg order) + HID_STRING_MINIMUM(56), HID_STRING_MAXIMUM(100), \ + HID_STRING_MINIMUM(101), HID_STRING_MAXIMUM(117), \ */ -// Slider First 16 LEDs -#define GAMECON_REPORT_DESC_LED_SLIDER_16(...) \ - HID_USAGE_PAGE(HID_USAGE_PAGE_DESKTOP), HID_USAGE(0x00), \ - HID_COLLECTION(HID_COLLECTION_APPLICATION), \ - __VA_ARGS__ 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_OUTPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \ - HID_REPORT_COUNT(1), HID_REPORT_SIZE(8), \ - HID_INPUT(HID_CONSTANT | HID_VARIABLE | HID_ABSOLUTE), \ - HID_COLLECTION_END - -// Slider Remaining 15 LEDs -#define GAMECON_REPORT_DESC_LED_SLIDER_15(...) \ - HID_USAGE_PAGE(HID_USAGE_PAGE_DESKTOP), HID_USAGE(0x00), \ - HID_COLLECTION(HID_COLLECTION_APPLICATION), \ - __VA_ARGS__ HID_REPORT_COUNT(45), 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(45), \ - HID_OUTPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \ - HID_REPORT_COUNT(1), HID_REPORT_SIZE(8), \ - HID_INPUT(HID_CONSTANT | HID_VARIABLE | HID_ABSOLUTE), \ - HID_COLLECTION_END - -// Tower LEDs -#define GAMECON_REPORT_DESC_LED_TOWER_6(...) \ - HID_USAGE_PAGE(HID_USAGE_PAGE_DESKTOP), HID_USAGE(0x00), \ - HID_COLLECTION(HID_COLLECTION_APPLICATION), \ - __VA_ARGS__ HID_REPORT_COUNT(18), 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(18), \ - HID_OUTPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \ - HID_REPORT_COUNT(1), HID_REPORT_SIZE(8), \ - HID_INPUT(HID_CONSTANT | HID_VARIABLE | HID_ABSOLUTE), \ - HID_COLLECTION_END - // LEDs Compressed -#define GAMECON_REPORT_DESC_LED_COMPRESSED(...) \ - HID_USAGE_PAGE(HID_USAGE_PAGE_DESKTOP), HID_USAGE(0x00), \ - HID_COLLECTION(HID_COLLECTION_APPLICATION), \ - __VA_ARGS__ HID_REPORT_COUNT(63), 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(63), \ - HID_OUTPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \ - HID_REPORT_COUNT(1), HID_REPORT_SIZE(8), \ - HID_INPUT(HID_CONSTANT | HID_VARIABLE | HID_ABSOLUTE), \ - HID_COLLECTION_END +#define GAMECON_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_ */ \ No newline at end of file +#endif /* USB_DESCRIPTORS_H_ */