From 3e579358df17b2f100133f85497615a2e4c7c6f2 Mon Sep 17 00:00:00 2001 From: Nicholas G Date: Mon, 30 Aug 2021 15:08:50 -0400 Subject: [PATCH] WS2812B Implementation on multicore with HID --- README.md | 10 +-- build_uf2/Pico_Game_Controller.uf2 | Bin 71168 -> 74752 bytes src/CMakeLists.txt | 2 + src/pico_game_controller.c | 120 +++++++++++++++++++++++++---- src/usb_descriptors.h | 6 +- src/ws2812.pio | 85 ++++++++++++++++++++ 6 files changed, 202 insertions(+), 21 deletions(-) create mode 100644 src/ws2812.pio diff --git a/README.md b/README.md index fc4495b..68d7381 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,14 @@ # Pico-Game-Controller -WIP code for a rhythm game controller using a Raspberry Pi Pico. Intended for SDVX or IIDX but is capable of handling 11 buttons, 11 LEDs, and 2 encoders. +WIP code for a rhythm game controller using a Raspberry Pi Pico. Intended for SDVX or IIDX but is capable of handling 11 buttons, 10 LEDs, 1 WS2812B RGB strip, and 2 encoders. Currently working/fixed: - Gamepad mode - default boot mode - NKRO Keyboard & Mouse Mode - hold first button to enter kb mode - HID LEDs with Reactive LED fallback +- ws2812b rgb on second core +- 2 ws2812b hid descriptor zones - sdvx/iidx spoof - Tested on EAC - 1000hz polling - Reversable Encoders with debouncing @@ -17,9 +19,7 @@ TODO: - store configuration settings in a text file? consider implementing littlefs https://github.com/littlefs-project/littlefs https://www.raspberrypi.org/forums/viewtopic.php?t=313009 https://www.raspberrypi.org/forums/viewtopic.php?p=1894014#p1894014 - Store last mode in flash memory https://www.raspberrypi.org/forums/viewtopic.php?t=305570 -- debounce -- ws2812b rgb on second core? -- split input updating into pi pico's second core? +- debounce on switches How to Use: @@ -39,4 +39,4 @@ Thanks to: - https://github.com/Drewol/rp2040-gamecon for usb gamepad descriptor info. - https://github.com/veroxzik/arduino-konami-spoof for konami spoof usb descriptor info. - https://github.com/veroxzik/roxy-firmware for nkro descriptor and logic info. -- KyubiFox for bringing clkdiv to my attention for encoder debouncing \ No newline at end of file +- KyubiFox for bringing clkdiv to my attention for encoder debouncing diff --git a/build_uf2/Pico_Game_Controller.uf2 b/build_uf2/Pico_Game_Controller.uf2 index 87b216cc8917c8d03c003399bb0ac0d857cabf29..7b69800fd55ff5a459bb95e024d81e1cddcc5dbb 100644 GIT binary patch delta 12858 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 delta 11226 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 b=O8!)<{;*MVC5WGJA%0YPgNDHdqwv@L`@dz diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c5cc490..14091d5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -5,9 +5,11 @@ add_executable(Pico_Game_Controller target_include_directories(Pico_Game_Controller PRIVATE ${CMAKE_CURRENT_LIST_DIR}) pico_generate_pio_header(Pico_Game_Controller ${CMAKE_CURRENT_LIST_DIR}/encoders.pio) +pico_generate_pio_header(Pico_Game_Controller ${CMAKE_CURRENT_LIST_DIR}/ws2812.pio) target_sources(Pico_Game_Controller PRIVATE pico_game_controller.c) target_link_libraries(Pico_Game_Controller PRIVATE + pico_multicore pico_stdlib tinyusb_device tinyusb_board diff --git a/src/pico_game_controller.c b/src/pico_game_controller.c index aa6848c..6ecc322 100644 --- a/src/pico_game_controller.c +++ b/src/pico_game_controller.c @@ -9,21 +9,28 @@ #include "bsp/board.h" #include "encoders.pio.h" +#include "hardware/clocks.h" #include "hardware/dma.h" #include "hardware/irq.h" #include "hardware/pio.h" +#include "pico/multicore.h" #include "pico/stdlib.h" #include "tusb.h" #include "usb_descriptors.h" +#include "ws2812.pio.h" -#define SW_GPIO_SIZE 11 // Number of switches -#define ENC_GPIO_SIZE 2 // Number of encoders -#define ENC_PPR 600 // Encoder PPR -#define ENC_DEBOUNCE true // Encoder Debouncing - +#define SW_GPIO_SIZE 11 // Number of switches +#define LED_GPIO_SIZE 10 // Number of switches +#define ENC_GPIO_SIZE 2 // Number of encoders +#define ENC_PPR 600 // Encoder PPR +#define ENC_DEBOUNCE true // Encoder Debouncing #define ENC_PULSE (ENC_PPR * 4) // 4 pulses per PPR #define ENC_ROLLOVER (ENC_PULSE * 2) // Delta Rollover threshold #define REACTIVE_TIMEOUT_MAX 100000 // Cycles before HID falls back to reactive +#define WS2812B_LED_SIZE 10 // Number of WS2812B LEDs +#define WS2812B_LED_ZONES 2 // Number of WS2812B LED Zones +#define WS2812B_LEDS_PER_ZONE \ + WS2812B_LED_SIZE / WS2812B_LED_ZONES // Number of LEDs per zone // MODIFY KEYBINDS HERE, MAKE SURE LENGTHS MATCH SW_GPIO_SIZE const uint8_t SW_KEYCODE[] = {HID_KEY_D, HID_KEY_F, HID_KEY_J, HID_KEY_K, @@ -33,12 +40,13 @@ const uint8_t SW_GPIO[] = { 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 27, }; const uint8_t LED_GPIO[] = { - 5, 7, 9, 11, 13, 15, 17, 19, 21, 26, 28, + 5, 7, 9, 11, 13, 15, 17, 19, 21, 26, }; const uint8_t ENC_GPIO[] = {0, 2}; // L_ENC(0, 1); R_ENC(2, 3) const bool ENC_REV[] = {false, false}; // Reverse Encoders +const uint8_t WS2812B_GPIO = 28; -PIO pio; +PIO pio, pio_1; uint32_t enc_val[ENC_GPIO_SIZE]; uint32_t prev_enc_val[ENC_GPIO_SIZE]; int cur_enc_val[ENC_GPIO_SIZE]; @@ -53,10 +61,76 @@ unsigned long reactive_timeout_count = REACTIVE_TIMEOUT_MAX; void (*loop_mode)(); -struct lights_report { - uint8_t buttons[11]; +typedef struct { + uint8_t r, g, b; +} RGB_t; + +union { + struct { + uint8_t buttons[LED_GPIO_SIZE]; + RGB_t rgb[WS2812B_LED_ZONES]; + } lights; + uint8_t raw[LED_GPIO_SIZE + WS2812B_LED_ZONES * 3]; } lights_report; +/** + * WS2812B RGB Assignment + * @param pixel_grb The pixel color to set + **/ +static inline void put_pixel(uint32_t pixel_grb) { + pio_sm_put_blocking(pio1, ENC_GPIO_SIZE, pixel_grb << 8u); +} + +/** + * WS2812B RGB Format Helper + **/ +static inline uint32_t urgb_u32(uint8_t r, uint8_t g, uint8_t b) { + return ((uint32_t)(r) << 8) | ((uint32_t)(g) << 16) | (uint32_t)(b); +} + +/** + * Color Wheel Picker + * @param wheel_pos Color value from 0-255, r->g->b->r... + **/ +uint32_t color_wheel(uint8_t wheel_pos) { + if (wheel_pos < 85) { + return urgb_u32(wheel_pos * 3, 255 - wheel_pos * 3, 0); + } else if (wheel_pos < 170) { + wheel_pos -= 85; + return urgb_u32(255 - wheel_pos * 3, 0, wheel_pos * 3); + } else { + wheel_pos -= 170; + return urgb_u32(0, wheel_pos * 3, 255 - wheel_pos * 3); + } +} + +/** + * Color cycle effect + **/ +void ws2812b_color_cycle(uint32_t counter) { + for (int i = 0; i < WS2812B_LED_SIZE; ++i) { + put_pixel(color_wheel((counter + i * (int)(255 / WS2812B_LED_SIZE)) % 256)); + } +} + +/** + * WS2812B Lighting + * @param counter Current number of WS2812B cycles + **/ +void ws2812b_update(uint32_t counter) { + if (reactive_timeout_count >= REACTIVE_TIMEOUT_MAX) { + ws2812b_color_cycle(counter); + } else { + for (int i = 0; i < WS2812B_LED_ZONES; i++) { + for (int j = 0; j < WS2812B_LEDS_PER_ZONE; j++) { + put_pixel(urgb_u32(lights_report.lights.rgb[i].r, + lights_report.lights.rgb[i].g, + lights_report.lights.rgb[i].b)); + } + } + } +} + /** * HID/Reactive Lights **/ @@ -65,7 +139,7 @@ void update_lights() { reactive_timeout_count++; } if (leds_changed) { - for (int i = 0; i < SW_GPIO_SIZE; i++) { + for (int i = 0; i < LED_GPIO_SIZE; i++) { if (reactive_timeout_count >= REACTIVE_TIMEOUT_MAX) { if (sw_val[i]) { gpio_put(LED_GPIO[i], 1); @@ -73,7 +147,7 @@ void update_lights() { gpio_put(LED_GPIO[i], 0); } } else { - if (lights_report.buttons[i] == 0) { + if (lights_report.lights.buttons[i] == 0) { gpio_put(LED_GPIO[i], 0); } else { gpio_put(LED_GPIO[i], 1); @@ -267,6 +341,7 @@ void init() { // Set up the state machine for encoders pio = pio0; uint offset = pio_add_program(pio, &encoders_program); + // Setup Encoders for (int i = 0; i < ENC_GPIO_SIZE; i++) { enc_val[i] = 0; @@ -290,6 +365,12 @@ void init() { dma_channel_set_irq0_enabled(i, true); } + // Set up WS2812B + pio_1 = pio1; + uint offset2 = pio_add_program(pio_1, &ws2812_program); + ws2812_program_init(pio_1, ENC_GPIO_SIZE, offset2, WS2812B_GPIO, 800000, + false); + // Setup Button GPIO for (int i = 0; i < SW_GPIO_SIZE; i++) { sw_val[i] = false; @@ -301,7 +382,7 @@ void init() { } // Setup LED GPIO - for (int i = 0; i < SW_GPIO_SIZE; i++) { + for (int i = 0; i < LED_GPIO_SIZE; i++) { gpio_init(LED_GPIO[i]); gpio_set_dir(LED_GPIO[i], GPIO_OUT); } @@ -319,6 +400,17 @@ void init() { } } +/** + * Second Core Runnable + **/ +void core1_entry() { + uint32_t counter = 0; + while (1) { + ws2812b_update(++counter); + sleep_ms(10); + } +} + /** * Main Loop Function **/ @@ -327,6 +419,8 @@ int main(void) { tusb_init(); init(); + multicore_launch_core1(core1_entry); + while (1) { tud_task(); // tinyusb device task update_inputs(); @@ -364,7 +458,7 @@ void tud_hid_set_report_cb(uint8_t itf, uint8_t report_id, { size_t i = 0; for (i; i < sizeof(lights_report); i++) { - lights_report.buttons[i] = buffer[i + 1]; + lights_report.raw[i] = buffer[i + 1]; } reactive_timeout_count = 0; leds_changed = true; diff --git a/src/usb_descriptors.h b/src/usb_descriptors.h index 7b65e53..112196e 100644 --- a/src/usb_descriptors.h +++ b/src/usb_descriptors.h @@ -35,11 +35,11 @@ enum { #define GAMECON_REPORT_DESC_LIGHTS(...) \ HID_USAGE_PAGE(HID_USAGE_PAGE_DESKTOP), HID_USAGE(0x00), \ HID_COLLECTION(HID_COLLECTION_APPLICATION), \ - __VA_ARGS__ HID_REPORT_COUNT(11), /*11 button lights*/ \ + __VA_ARGS__ HID_REPORT_COUNT(16), /*10 button lights + 2 RGB Sets*/ \ 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(11), HID_OUTPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \ - HID_REPORT_COUNT(1), HID_REPORT_SIZE(40), /*Padding*/ \ + HID_USAGE_MAX(16), HID_OUTPUT(HID_DATA | HID_VARIABLE | HID_ABSOLUTE), \ + HID_REPORT_COUNT(1), HID_REPORT_SIZE(8), /*Padding*/ \ HID_INPUT(HID_CONSTANT | HID_VARIABLE | HID_ABSOLUTE), \ HID_COLLECTION_END diff --git a/src/ws2812.pio b/src/ws2812.pio new file mode 100644 index 0000000..3c31fd6 --- /dev/null +++ b/src/ws2812.pio @@ -0,0 +1,85 @@ +; +; Copyright (c) 2020 Raspberry Pi (Trading) Ltd. +; +; SPDX-License-Identifier: BSD-3-Clause +; + +.program ws2812 +.side_set 1 + +.define public T1 2 +.define public T2 5 +.define public T3 3 + +.lang_opt python sideset_init = pico.PIO.OUT_HIGH +.lang_opt python out_init = pico.PIO.OUT_HIGH +.lang_opt python out_shiftdir = 1 + +.wrap_target +bitloop: + out x, 1 side 0 [T3 - 1] ; Side-set still takes place when instruction stalls + jmp !x do_zero side 1 [T1 - 1] ; Branch on the bit we shifted out. Positive pulse +do_one: + jmp bitloop side 1 [T2 - 1] ; Continue driving high, for a long pulse +do_zero: + nop side 0 [T2 - 1] ; Or drive low, for a short pulse +.wrap + +% c-sdk { +#include "hardware/clocks.h" + +static inline void ws2812_program_init(PIO pio, uint sm, uint offset, uint pin, float freq, bool rgbw) { + + pio_gpio_init(pio, pin); + pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true); + + pio_sm_config c = ws2812_program_get_default_config(offset); + sm_config_set_sideset_pins(&c, pin); + sm_config_set_out_shift(&c, false, true, rgbw ? 32 : 24); + sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); + + int cycles_per_bit = ws2812_T1 + ws2812_T2 + ws2812_T3; + float div = clock_get_hz(clk_sys) / (freq * cycles_per_bit); + sm_config_set_clkdiv(&c, div); + + pio_sm_init(pio, sm, offset, &c); + pio_sm_set_enabled(pio, sm, true); +} +%} + +.program ws2812_parallel + +.define public T1 2 +.define public T2 5 +.define public T3 3 + +.wrap_target + out x, 32 + mov pins, !null [T1-1] + mov pins, x [T2-1] + mov pins, null [T3-2] +.wrap + +% c-sdk { +#include "hardware/clocks.h" + +static inline void ws2812_parallel_program_init(PIO pio, uint sm, uint offset, uint pin_base, uint pin_count, float freq) { + for(uint i=pin_base; i