diff --git a/build_uf2/Pico_Game_Controller.uf2 b/build_uf2/Pico_Game_Controller.uf2 index c97b943..5b0abf8 100644 Binary files a/build_uf2/Pico_Game_Controller.uf2 and b/build_uf2/Pico_Game_Controller.uf2 differ diff --git a/src/controller_config.h b/src/controller_config.h index 03f5e23..4214193 100644 --- a/src/controller_config.h +++ b/src/controller_config.h @@ -6,7 +6,7 @@ #define ENC_GPIO_SIZE 2 // Number of encoders #define ENC_PPR 600 // Encoder PPR #define MOUSE_SENS 1 // Mouse sensitivity multiplier -#define ENC_DEBOUNCE true // Encoder Debouncing +#define ENC_DEBOUNCE false // Encoder Debouncing #define SW_DEBOUNCE_TIME_US 4000 // Switch debounce delay in us #define ENC_PULSE (ENC_PPR * 4) // 4 pulses per PPR #define REACTIVE_TIMEOUT_MAX 1000000 // HID to reactive timeout in us diff --git a/src/pico_game_controller.c b/src/pico_game_controller.c index 84d9b32..9bf19c4 100644 --- a/src/pico_game_controller.c +++ b/src/pico_game_controller.c @@ -17,9 +17,9 @@ #include "hardware/pio.h" #include "pico/multicore.h" #include "pico/stdlib.h" +#include "rgb/rgb_include.h" #include "tusb.h" #include "usb_descriptors.h" -#include "ws2812.pio.h" PIO pio, pio_1; uint32_t enc_val[ENC_GPIO_SIZE]; @@ -33,13 +33,10 @@ bool kbm_report; uint64_t reactive_timeout_timestamp; +void (*ws2812b_mode)(); void (*loop_mode)(); bool joy_mode_check = true; -typedef struct { - uint8_t r, g, b; -} RGB_t; - union { struct { uint8_t buttons[LED_GPIO_SIZE]; @@ -48,54 +45,13 @@ union { 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); -} - -/** - * 768 Color Wheel Picker - * @param wheel_pos Color value, r->g->b->r... - **/ -uint32_t color_wheel(uint16_t wheel_pos) { - wheel_pos %= 768; - if (wheel_pos < 256) { - return urgb_u32(wheel_pos, 255 - wheel_pos, 0); - } else if (wheel_pos < 512) { - wheel_pos -= 256; - return urgb_u32(255 - wheel_pos, 0, wheel_pos); - } else { - wheel_pos -= 512; - return urgb_u32(0, wheel_pos, 255 - wheel_pos); - } -} - -/** - * 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)(768 / WS2812B_LED_SIZE)) % 768)); - } -} - /** * WS2812B Lighting * @param counter Current number of WS2812B cycles **/ void ws2812b_update(uint32_t counter) { if (time_us_64() - reactive_timeout_timestamp >= REACTIVE_TIMEOUT_MAX) { - ws2812b_color_cycle(counter); + ws2812b_mode(counter); } else { for (int i = 0; i < WS2812B_LED_ZONES; i++) { for (int j = 0; j < WS2812B_LEDS_PER_ZONE; j++) { @@ -275,7 +231,7 @@ void init() { channel_config_set_dreq(&c, pio_get_dreq(pio, i, false)); dma_channel_configure(i, &c, - &enc_val[i], // Destinatinon pointer + &enc_val[i], // Destination pointer &pio->rxf[i], // Source pointer 0x10, // Number of transfers true // Start immediately @@ -321,6 +277,13 @@ void init() { joy_mode_check = true; } + // RGB Mode Switching + if (!gpio_get(SW_GPIO[1])) { + ws2812b_mode = &turbocharger_color_cycle; + } else { + ws2812b_mode = &ws2812b_color_cycle; + } + // Disable RGB if (gpio_get(SW_GPIO[8])) { multicore_launch_core1(core1_entry); diff --git a/src/rgb/color_cycle.c b/src/rgb/color_cycle.c new file mode 100644 index 0000000..e5708b6 --- /dev/null +++ b/src/rgb/color_cycle.c @@ -0,0 +1,9 @@ +/** + * Color cycle effect + * @author SpeedyPotato + **/ +void ws2812b_color_cycle(uint32_t counter) { + for (int i = 0; i < WS2812B_LED_SIZE; ++i) { + put_pixel(color_wheel((counter + i * (int)(768 / WS2812B_LED_SIZE)) % 768)); + } +} \ No newline at end of file diff --git a/src/rgb/rgb_include.h b/src/rgb/rgb_include.h new file mode 100644 index 0000000..02d2264 --- /dev/null +++ b/src/rgb/rgb_include.h @@ -0,0 +1,10 @@ +/** + * Simple header file to include all files in the folder + * @author SpeedyPotato + * + * To add a lighting mode, create a function which accepts a uint32_t as a parameter. + * Create lighting mode as desired and then add the #include here. + **/ +#include "ws2812b_util.c" +#include "color_cycle.c" +#include "turbocharger.c" \ No newline at end of file diff --git a/src/rgb/turbocharger.c b/src/rgb/turbocharger.c new file mode 100644 index 0000000..81f2dec --- /dev/null +++ b/src/rgb/turbocharger.c @@ -0,0 +1,111 @@ +/** + * @author 4yn, SpeedyPotato + * Turbocharger chasing laser effect + * + * Move 2 lighting areas around the controller depending on knob input. + * + * For each knob, calculate every 5 ms: + * - Add any knob delta to a counter + * - Clamp counter to some "maximum speed" + * - If counter is far enough from 0, knob is moving + * - If counter is too near 0 and has been there for a while, fade out + * - Move the lighting area in the correct direction at a constant speed + * - Decay the counter + * + * Curent values are tuned for + * - Lights sampled at 200 Hz + * - 0.1 rotations for lights to activate + * - Lighting areas take 0.75s to make one full rotation + * - Movement takes 0.5s to decay to stop + * - Fade out takes another 0.2s samples to disappear + * + * LEDs are positioned as: + * - 0, 1 as dummy leds on top of controller + * - 2-6 LEDs on right edge, top to bottom + * - 7-9 as dummy leds on bottom of controller + * - 10-14 LEDs on left edge, bottom to top + * - 15 as dummy led on top of controller + * + * Lighting areas start at position 0 and will light up the 3 nearest LEDs. + * By strategically positioning led 0 at the top, this avoids lighting areas + * from suddenly appeaering. + **/ +extern uint32_t enc_val[ENC_GPIO_SIZE]; + +#define TURBO_LIGHTS_CLAMP 0.1f +#define TURBO_LIGHTS_THRESHOLD 0.05f +#define TURBO_LIGHTS_DECAY 0.0005f +#define TURBO_LIGHTS_VEL 0.12f +#define TURBO_LIGHTS_MAX (WS2812B_LED_SIZE + 6.0f) +#define TURBO_LIGHTS_FADE 40 +#define TURBO_LIGHTS_FADE_VEL 0.025f + +int i_clamp(int d, int min, int max) { + const int t = d < min ? min : d; + return t > max ? max : t; +} + +float f_clamp(float d, float min, float max) { + const float t = d < min ? min : d; + return t > max ? max : t; +} + +float f_one_mod(float d, float mod) { + const float t = d < 0 ? d + mod : d; + return t > mod ? t - mod : t; +} + +float f_abs(float d) { + return d < 0 ? -d : d; +} + +uint32_t turbo_prev_enc_val[ENC_GPIO_SIZE]; +float turbo_cur_enc_val[ENC_GPIO_SIZE]; +float turbo_lights_pos[ENC_GPIO_SIZE]; +float turbo_lights_brightness[ENC_GPIO_SIZE]; +int turbo_lights_idle[ENC_GPIO_SIZE]; + +void turbocharger_color_cycle(uint32_t unused) { + for (int i = 0; i < ENC_GPIO_SIZE; i++) { + int enc_delta = (enc_val[i] - turbo_prev_enc_val[i]) * (ENC_REV[i] ? 1 : -1); + turbo_prev_enc_val[i] = enc_val[i]; + turbo_cur_enc_val[i] = f_clamp(turbo_cur_enc_val[i] + (float)(enc_delta) / ENC_PULSE, -TURBO_LIGHTS_CLAMP, TURBO_LIGHTS_CLAMP); + + if (turbo_cur_enc_val[i] < -TURBO_LIGHTS_THRESHOLD) { + turbo_lights_idle[i] = 0; + turbo_lights_pos[i] += TURBO_LIGHTS_VEL; + turbo_lights_brightness[i] = 1.0f; + } else if (turbo_cur_enc_val[i] > TURBO_LIGHTS_THRESHOLD) { + turbo_lights_idle[i] = 0; + turbo_lights_pos[i] -= TURBO_LIGHTS_VEL; + turbo_lights_brightness[i] = 1.0f; + } else { + turbo_lights_idle[i]++; + if (turbo_lights_idle[i] > TURBO_LIGHTS_FADE) { + turbo_lights_pos[i] = 0; + } else { + turbo_lights_brightness[i] = f_clamp(turbo_lights_brightness[i] - TURBO_LIGHTS_FADE_VEL, 0.0f, 1.0f); + } + } + + turbo_lights_pos[i] = f_one_mod(turbo_lights_pos[i], TURBO_LIGHTS_MAX); + + if (turbo_cur_enc_val[i] < -TURBO_LIGHTS_DECAY) { + turbo_cur_enc_val[i] += TURBO_LIGHTS_DECAY; + } else if (turbo_cur_enc_val[i] > TURBO_LIGHTS_DECAY) { + turbo_cur_enc_val[i] -= TURBO_LIGHTS_DECAY; + } + } + + for (int i = 0; i < WS2812B_LED_SIZE; i++) { + float pos = 2.0f + i + (i >= WS2812B_LED_SIZE / 2 ? 3.0f : 0.0f); + float l_strength = (1.0f - f_clamp(f_abs(turbo_lights_pos[0] - pos), 0.0f, 2.0f) / 2) * turbo_lights_brightness[0]; + float r_strength = (1.0f - f_clamp(f_abs(turbo_lights_pos[1] - pos), 0.0f, 2.0f) / 2) * turbo_lights_brightness[1]; + + put_pixel(urgb_u32( + i_clamp(l_strength * 70 + r_strength * 250, 0, 255), + i_clamp(l_strength * 230 + r_strength * 60, 0, 255), + i_clamp(l_strength * 250 + r_strength * 200, 0, 255) + )); + } +} diff --git a/src/rgb/ws2812b_util.c b/src/rgb/ws2812b_util.c new file mode 100644 index 0000000..aae7f18 --- /dev/null +++ b/src/rgb/ws2812b_util.c @@ -0,0 +1,41 @@ +/* + * ws2812b utility class + * @author SpeedyPotato + */ +#include "ws2812.pio.h" + +typedef struct { + uint8_t r, g, b; +} RGB_t; + +/** + * 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); +} + +/** + * 768 Color Wheel Picker + * @param wheel_pos Color value, r->g->b->r... + **/ +static inline uint32_t color_wheel(uint16_t wheel_pos) { + wheel_pos %= 768; + if (wheel_pos < 256) { + return urgb_u32(wheel_pos, 255 - wheel_pos, 0); + } else if (wheel_pos < 512) { + wheel_pos -= 256; + return urgb_u32(255 - wheel_pos, 0, wheel_pos); + } else { + wheel_pos -= 512; + return urgb_u32(0, wheel_pos, 255 - wheel_pos); + } +} + +/** + * 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); +} \ No newline at end of file