mirror of
https://github.com/speedypotato/Pico-Game-Controller.git
synced 2026-10-11 16:48:12 +03:00
WS2812B Implementation on multicore with HID
This commit is contained in:
@@ -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
|
||||
- KyubiFox for bringing clkdiv to my attention for encoder debouncing
|
||||
|
||||
Binary file not shown.
@@ -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
|
||||
|
||||
+107
-13
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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<pin_base+pin_count; i++) {
|
||||
pio_gpio_init(pio, i);
|
||||
}
|
||||
pio_sm_set_consecutive_pindirs(pio, sm, pin_base, pin_count, true);
|
||||
|
||||
pio_sm_config c = ws2812_parallel_program_get_default_config(offset);
|
||||
sm_config_set_out_shift(&c, true, true, 32);
|
||||
sm_config_set_out_pins(&c, pin_base, pin_count);
|
||||
sm_config_set_set_pins(&c, pin_base, pin_count);
|
||||
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
|
||||
|
||||
int cycles_per_bit = ws2812_parallel_T1 + ws2812_parallel_T2 + ws2812_parallel_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);
|
||||
}
|
||||
%}
|
||||
Reference in New Issue
Block a user