Files
speedypotato_Pico-Game-Cont…/src/pico_game_controller.c
T

468 lines
13 KiB
C

/*
* Pico Game Controller
* @author SpeedyPotato
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#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 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,
HID_KEY_C, HID_KEY_M, HID_KEY_A, HID_KEY_B,
HID_KEY_1, HID_KEY_E, HID_KEY_G};
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,
};
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_1;
uint32_t enc_val[ENC_GPIO_SIZE];
uint32_t prev_enc_val[ENC_GPIO_SIZE];
int cur_enc_val[ENC_GPIO_SIZE];
bool enc_changed;
bool sw_val[SW_GPIO_SIZE];
bool prev_sw_val[SW_GPIO_SIZE];
bool sw_changed;
bool leds_changed;
unsigned long reactive_timeout_count = REACTIVE_TIMEOUT_MAX;
void (*loop_mode)();
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);
}
/**
* 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 (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
**/
void update_lights() {
if (reactive_timeout_count < REACTIVE_TIMEOUT_MAX) {
reactive_timeout_count++;
}
if (leds_changed) {
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);
} else {
gpio_put(LED_GPIO[i], 0);
}
} else {
if (lights_report.lights.buttons[i] == 0) {
gpio_put(LED_GPIO[i], 0);
} else {
gpio_put(LED_GPIO[i], 1);
}
}
}
leds_changed = false;
}
}
struct report {
uint16_t buttons;
uint8_t joy0;
uint8_t joy1;
} report;
/**
* Gamepad Mode
**/
void joy_mode() {
if (tud_hid_ready()) {
bool send_report = false;
if (sw_changed) {
send_report = true;
uint16_t translate_buttons = 0;
for (int i = SW_GPIO_SIZE - 1; i >= 0; i--) {
translate_buttons = (translate_buttons << 1) | (sw_val[i] ? 1 : 0);
prev_sw_val[i] = sw_val[i];
}
report.buttons = translate_buttons;
sw_changed = false;
}
if (enc_changed) {
send_report = true;
// find the delta between previous and current enc_val
for (int i = 0; i < ENC_GPIO_SIZE; i++) {
int delta;
int changeType; // -1 for negative 1 for positive
if (enc_val[i] > prev_enc_val[i]) { // if the new value is bigger its
// a positive change
delta = enc_val[i] - prev_enc_val[i];
changeType = 1;
} else { // otherwise its a negative change
delta = prev_enc_val[i] - enc_val[i];
changeType = -1;
}
// Overflow / Underflow
if (delta > ENC_ROLLOVER) {
// Reverse the change type due to overflow / underflow
changeType *= -1;
delta = UINT32_MAX - delta + 1; // this should give us how much we
// overflowed / underflowed by
}
cur_enc_val[i] =
cur_enc_val[i] + ((ENC_REV[i] ? 1 : -1) * delta * changeType);
while (cur_enc_val[i] < 0) {
cur_enc_val[i] = ENC_PULSE - cur_enc_val[i];
}
prev_enc_val[i] = enc_val[i];
}
report.joy0 = ((double)cur_enc_val[0] / ENC_PULSE) * 256;
report.joy1 = ((double)cur_enc_val[1] / ENC_PULSE) * 256;
enc_changed = false;
}
if (send_report) {
tud_hid_n_report(0x00, REPORT_ID_JOYSTICK, &report, sizeof(report));
}
}
}
/**
* Keyboard Mode
**/
void key_mode() {
if (tud_hid_ready()) {
/*------------- Keyboard -------------*/
if (sw_changed) {
uint8_t nkro_report[32] = {0};
for (int i = 0; i < SW_GPIO_SIZE; i++) {
if (sw_val[i]) {
uint8_t bit = SW_KEYCODE[i] % 8;
uint8_t byte = (SW_KEYCODE[i] / 8) + 1;
if (SW_KEYCODE[i] >= 240 && SW_KEYCODE[i] <= 247) {
nkro_report[0] |= (1 << bit);
} else if (byte > 0 && byte <= 31) {
nkro_report[byte] |= (1 << bit);
}
prev_sw_val[i] = sw_val[i];
}
}
// Send key report
tud_hid_n_report(0x00, REPORT_ID_KEYBOARD, &nkro_report,
sizeof(nkro_report));
sw_changed = false;
}
/*------------- Mouse -------------*/
if (enc_changed) {
// Delay if needed before attempt to send mouse report
while (!tud_hid_ready()) {
board_delay(1);
}
// find the delta between previous and current enc_val
int delta[ENC_GPIO_SIZE] = {0};
for (int i = 0; i < ENC_GPIO_SIZE; i++) {
int changeType; // -1 for negative 1 for positive
if (enc_val[i] > prev_enc_val[i]) { // if the new value is bigger its
// a positive change
delta[i] = enc_val[i] - prev_enc_val[i];
changeType = 1;
} else { // otherwise its a negative change
delta[i] = prev_enc_val[i] - enc_val[i];
changeType = -1;
}
// Overflow / Underflow
if (delta[i] > ENC_ROLLOVER) {
// Reverse the change type due to overflow / underflow
changeType *= -1;
delta[i] =
UINT32_MAX - delta[i] + 1; // this should give us how much we
// overflowed / underflowed by
}
delta[i] *= changeType * (ENC_REV[i] ? 1 : -1); // set direction
prev_enc_val[i] = enc_val[i];
}
tud_hid_mouse_report(REPORT_ID_MOUSE, 0x00, delta[0], delta[1], 0, 0);
enc_changed = false;
}
}
}
/**
* Update Input States
**/
void update_inputs() {
// Encoder Flag
for (int i = 0; i < ENC_GPIO_SIZE; i++) {
if (enc_val[i] != prev_enc_val[i]) {
enc_changed = true;
break;
}
}
// Switch Update & Flag
for (int i = 0; i < SW_GPIO_SIZE; i++) {
if (gpio_get(SW_GPIO[i])) {
sw_val[i] = false;
} else {
sw_val[i] = true;
}
if (!sw_changed && sw_val[i] != prev_sw_val[i]) {
sw_changed = true;
}
}
// Update LEDs if input changed while in reactive mode
if (sw_changed && reactive_timeout_count >= REACTIVE_TIMEOUT_MAX)
leds_changed = true;
}
/**
* DMA Encoder Logic For 2 Encoders
**/
void dma_handler() {
uint i = 1;
int interrupt_channel = 0;
while ((i & dma_hw->ints0) == 0) {
i = i << 1;
++interrupt_channel;
}
dma_hw->ints0 = 1u << interrupt_channel;
if (interrupt_channel < 4) {
dma_channel_set_read_addr(interrupt_channel, &pio->rxf[interrupt_channel],
true);
}
}
/**
* Initialize Board Pins
**/
void init() {
// LED Pin on when connected
gpio_init(25);
gpio_set_dir(25, GPIO_OUT);
gpio_put(25, 1);
// 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;
prev_enc_val[i] = 0;
cur_enc_val[i] = 0;
encoders_program_init(pio, i, offset, ENC_GPIO[i], ENC_DEBOUNCE);
dma_channel_config c = dma_channel_get_default_config(i);
channel_config_set_read_increment(&c, false);
channel_config_set_write_increment(&c, false);
channel_config_set_dreq(&c, pio_get_dreq(pio, i, false));
dma_channel_configure(i, &c,
&enc_val[i], // Destinatinon pointer
&pio->rxf[i], // Source pointer
0x10, // Number of transfers
true // Start immediately
);
irq_set_exclusive_handler(DMA_IRQ_0, dma_handler);
irq_set_enabled(DMA_IRQ_0, true);
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;
prev_sw_val[i] = false;
gpio_init(SW_GPIO[i]);
gpio_set_function(SW_GPIO[i], GPIO_FUNC_SIO);
gpio_set_dir(SW_GPIO[i], GPIO_IN);
gpio_pull_up(SW_GPIO[i]);
}
// Setup LED GPIO
for (int i = 0; i < LED_GPIO_SIZE; i++) {
gpio_init(LED_GPIO[i]);
gpio_set_dir(LED_GPIO[i], GPIO_OUT);
}
// Set listener bools
enc_changed = false;
sw_changed = false;
leds_changed = false;
// Joy/KB Mode Switching
if (gpio_get(SW_GPIO[0])) {
loop_mode = &joy_mode;
} else {
loop_mode = &key_mode;
}
}
/**
* Second Core Runnable
**/
void core1_entry() {
uint32_t counter = 0;
while (1) {
ws2812b_update(++counter);
sleep_ms(5);
}
}
/**
* Main Loop Function
**/
int main(void) {
board_init();
tusb_init();
init();
multicore_launch_core1(core1_entry);
while (1) {
tud_task(); // tinyusb device task
update_inputs();
loop_mode();
update_lights();
}
return 0;
}
// Invoked when received GET_REPORT control request
// Application must fill buffer report's content and return its length.
// Return zero will cause the stack to STALL request
uint16_t tud_hid_get_report_cb(uint8_t itf, uint8_t report_id,
hid_report_type_t report_type, uint8_t* buffer,
uint16_t reqlen) {
// TODO not Implemented
(void)itf;
(void)report_id;
(void)report_type;
(void)buffer;
(void)reqlen;
return 0;
}
// Invoked when received SET_REPORT control request or
// received data on OUT endpoint ( Report ID = 0, Type = 0 )
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) {
(void)itf;
if (report_id == 2 && report_type == HID_REPORT_TYPE_OUTPUT &&
buffer[0] == 2 && bufsize >= sizeof(lights_report)) // light data
{
size_t i = 0;
for (i; i < sizeof(lights_report); i++) {
lights_report.raw[i] = buffer[i + 1];
}
reactive_timeout_count = 0;
leds_changed = true;
}
}