mirror of
https://github.com/speedypotato/Pico-Game-Controller.git
synced 2026-10-09 15:10:12 +03:00
382 lines
11 KiB
C
382 lines
11 KiB
C
/*
|
|
* Pico Game Controller
|
|
* @author SpeedyPotato
|
|
*
|
|
* Based off tinyusb's hid_composite, mdxtinkernick/pico_encoders, and
|
|
* Drewol/rp2040-gamecon
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "bsp/board.h"
|
|
#include "encoders.pio.h"
|
|
#include "hardware/dma.h"
|
|
#include "hardware/irq.h"
|
|
#include "hardware/pio.h"
|
|
#include "pico/stdlib.h"
|
|
#include "tusb.h"
|
|
#include "usb_descriptors.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_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
|
|
|
|
// 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_C, HID_KEY_D};
|
|
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,
|
|
};
|
|
const uint8_t ENC_GPIO[] = {0, 2}; // L_ENC(0, 1); R_ENC(2, 3)
|
|
const bool ENC_REV[] = {false, false}; // Reverse Encoders
|
|
|
|
PIO pio;
|
|
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)();
|
|
|
|
struct lights_report {
|
|
uint8_t buttons[11];
|
|
} lights_report;
|
|
|
|
/**
|
|
* HID/Reactive Lights
|
|
**/
|
|
void update_lights() {
|
|
if (reactive_timeout_count < REACTIVE_TIMEOUT_MAX) {
|
|
reactive_timeout_count++;
|
|
}
|
|
if (leds_changed) {
|
|
for (int i = 0; i < SW_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.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) {
|
|
bool is_pressed = false;
|
|
int keycode_idx = 0;
|
|
uint8_t keycode[6] = {0}; // looks like we are limited to 6kro?
|
|
for (int i = 0; i < SW_GPIO_SIZE; i++) {
|
|
if (sw_val[i]) {
|
|
// use to avoid send multiple consecutive zero report for keyboard
|
|
keycode[keycode_idx] = SW_KEYCODE[i];
|
|
keycode_idx = ++keycode_idx % SW_GPIO_SIZE;
|
|
is_pressed = true;
|
|
|
|
prev_sw_val[i] = sw_val[i];
|
|
// Reactive Lighting On
|
|
gpio_put(LED_GPIO[i], 1);
|
|
} else {
|
|
// Reactive Lighting Off
|
|
gpio_put(LED_GPIO[i], 0);
|
|
}
|
|
}
|
|
if (is_pressed) {
|
|
// Send key report
|
|
tud_hid_keyboard_report(REPORT_ID_KEYBOARD, 0, keycode);
|
|
} else {
|
|
// Send empty key report if previously has key pressed
|
|
tud_hid_keyboard_report(REPORT_ID_KEYBOARD, 0, NULL);
|
|
}
|
|
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]);
|
|
|
|
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);
|
|
}
|
|
|
|
// 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 < SW_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;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Main Loop Function
|
|
**/
|
|
int main(void) {
|
|
board_init();
|
|
tusb_init();
|
|
init();
|
|
|
|
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.buttons[i] = buffer[i + 1];
|
|
}
|
|
reactive_timeout_count = 0;
|
|
leds_changed = true;
|
|
}
|
|
}
|