diff --git a/CMakeLists.txt b/CMakeLists.txt index 10a816f..ebca586 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,8 +24,29 @@ if (EXISTS ${picoVscode}) endif() # ==================================================================================== option(SWITCH_PICO_LOG "Enable UART debug logging" OFF) +set(SWITCH_PICO_PROTOCOL "legacy" CACHE STRING "USB protocol: legacy or switch2") +set_property(CACHE SWITCH_PICO_PROTOCOL PROPERTY STRINGS legacy switch2) set(PICO_BOARD pico CACHE STRING "Board type") +if (SWITCH_PICO_PROTOCOL STREQUAL "legacy") + set(SWITCH_PICO_PROTOCOL_SOURCES + switch_pro_driver.cpp + switch_legacy_protocol.cpp + ) + set(SWITCH_PICO_PROTOCOL_DEFINITION SWITCH_PICO_PROTOCOL_LEGACY=1) +elseif (SWITCH_PICO_PROTOCOL STREQUAL "switch2") + set(SWITCH_PICO_PROTOCOL_SOURCES + switch2_driver.cpp + switch2_descriptors.cpp + switch2_reports.cpp + switch2_commands.cpp + ) + set(SWITCH_PICO_PROTOCOL_DEFINITION SWITCH_PICO_PROTOCOL_SWITCH2=1) +else() + message(FATAL_ERROR + "Invalid SWITCH_PICO_PROTOCOL='${SWITCH_PICO_PROTOCOL}'. Expected legacy or switch2.") +endif() + # Pull in Raspberry Pi Pico SDK (must be before project) include(pico_sdk_import.cmake) @@ -38,9 +59,12 @@ pico_sdk_init() add_executable(switch-pico switch-pico.cpp - switch_pro_driver.cpp + switch_uart_protocol.cpp + ${SWITCH_PICO_PROTOCOL_SOURCES} ) +target_compile_definitions(switch-pico PRIVATE ${SWITCH_PICO_PROTOCOL_DEFINITION}) + pico_set_program_name(switch-pico "switch-pico") pico_set_program_version(switch-pico "0.1") diff --git a/switch-pico.cpp b/switch-pico.cpp index 22beaef..dc93a67 100644 --- a/switch-pico.cpp +++ b/switch-pico.cpp @@ -4,7 +4,9 @@ #include "hardware/uart.h" #include "pico/stdlib.h" #include "tusb.h" -#include "switch_pro_driver.h" +#include "switch_input.h" +#include "switch_protocol.h" +#include "switch_uart_protocol.h" #ifdef SWITCH_PICO_LOG #define LOG_PRINTF(...) printf(__VA_ARGS__) @@ -60,7 +62,7 @@ static void on_rumble_from_switch(const uint8_t rumble[8]) { send_rumble_uart_frame(rumble); } -// Consume UART bytes and forward complete frames to the Switch Pro driver. +// Consume UART bytes and decode complete input frames. static bool poll_uart_frames() { static uint8_t buffer[64]; static uint8_t index = 0; @@ -103,7 +105,7 @@ static bool poll_uart_frames() { if (expected_len > 0 && index >= expected_len) { SwitchInputState parsed{}; - if (switch_pro_apply_uart_packet(buffer, expected_len, &parsed)) { + if (switch_uart_decode_input_frame(buffer, expected_len, &parsed)) { g_user_state = parsed; new_data = true; LOG_PRINTF("[UART] packet buttons=0x%04x hat=%u lx=%u ly=%u rx=%u ry=%u\n", @@ -137,7 +139,7 @@ static bool poll_uart_frames() { static void log_usb_state() { bool mounted = tud_mounted(); - bool ready = switch_pro_is_ready(); + bool ready = switch_protocol_is_ready(); if (mounted != g_last_mounted) { g_last_mounted = mounted; @@ -156,10 +158,10 @@ int main() { init_uart_input(); tusb_init(); - switch_pro_init(); - switch_pro_set_rumble_callback(on_rumble_from_switch); + switch_protocol_init(); + switch_protocol_set_rumble_callback(on_rumble_from_switch); g_user_state = neutral_input(); - switch_pro_set_input(g_user_state); + switch_protocol_set_input(g_user_state); LOG_PRINTF("[BOOT] switch-pico starting (UART0 log @ 115200)\n"); LOG_PRINTF("[INFO] UART1 pins TX=%d RX=%d baud=%d\n", @@ -170,8 +172,8 @@ int main() { bool new_data = poll_uart_frames(); // Pull controller state from UART1 (void)new_data; SwitchInputState state = g_user_state; - switch_pro_set_input(state); - switch_pro_task(); // Push state to the Switch host + switch_protocol_set_input(state); + switch_protocol_task(); // Push state to the Switch host log_usb_state(); } } diff --git a/switch_pro_descriptors.h b/switch_pro_descriptors.h index 2ec3444..236b8b3 100644 --- a/switch_pro_descriptors.h +++ b/switch_pro_descriptors.h @@ -9,39 +9,10 @@ #include +#include "switch_input.h" + #define SWITCH_PRO_ENDPOINT_SIZE 64 -// HAT report (4 bits) -#define SWITCH_PRO_HAT_UP 0x00 -#define SWITCH_PRO_HAT_UPRIGHT 0x01 -#define SWITCH_PRO_HAT_RIGHT 0x02 -#define SWITCH_PRO_HAT_DOWNRIGHT 0x03 -#define SWITCH_PRO_HAT_DOWN 0x04 -#define SWITCH_PRO_HAT_DOWNLEFT 0x05 -#define SWITCH_PRO_HAT_LEFT 0x06 -#define SWITCH_PRO_HAT_UPLEFT 0x07 -#define SWITCH_PRO_HAT_NOTHING 0x08 - -#define SWITCH_PRO_MASK_Y (1U << 0) -#define SWITCH_PRO_MASK_B (1U << 1) -#define SWITCH_PRO_MASK_A (1U << 2) -#define SWITCH_PRO_MASK_X (1U << 3) -#define SWITCH_PRO_MASK_L (1U << 4) -#define SWITCH_PRO_MASK_R (1U << 5) -#define SWITCH_PRO_MASK_ZL (1U << 6) -#define SWITCH_PRO_MASK_ZR (1U << 7) - -#define SWITCH_PRO_MASK_MINUS (1U << 8) -#define SWITCH_PRO_MASK_PLUS (1U << 9) -#define SWITCH_PRO_MASK_L3 (1U << 10) -#define SWITCH_PRO_MASK_R3 (1U << 11) -#define SWITCH_PRO_MASK_HOME (1U << 12) -#define SWITCH_PRO_MASK_CAPTURE (1U << 13) - -#define SWITCH_PRO_JOYSTICK_MIN 0x0000 -#define SWITCH_PRO_JOYSTICK_MID 0x7FFF -#define SWITCH_PRO_JOYSTICK_MAX 0xFFFF - typedef enum { REPORT_OUTPUT_00 = 0x00, REPORT_FEATURE = 0x01, diff --git a/switch_pro_driver.cpp b/switch_pro_driver.cpp index ee68f68..57580a9 100644 --- a/switch_pro_driver.cpp +++ b/switch_pro_driver.cpp @@ -6,6 +6,7 @@ #include #include "pico/rand.h" #include "pico/time.h" +#include "switch_pro_descriptors.h" #include "tusb.h" #ifdef SWITCH_PICO_LOG @@ -210,16 +211,6 @@ static void fill_imu_report_data(const SwitchInputState& state) { } } -static SwitchInputState make_neutral_state() { - SwitchInputState s{}; - s.lx = SWITCH_PRO_JOYSTICK_MID; - s.ly = SWITCH_PRO_JOYSTICK_MID; - s.rx = SWITCH_PRO_JOYSTICK_MID; - s.ry = SWITCH_PRO_JOYSTICK_MID; - s.imu_sample_count = 0; - return s; -} - static void send_identify() { memset(report_buffer, 0x00, sizeof(report_buffer)); report_buffer[0] = REPORT_USB_INPUT_81; @@ -643,112 +634,6 @@ void switch_pro_task() { } } -bool switch_pro_apply_uart_packet(const uint8_t* packet, uint8_t length, SwitchInputState* out_state) { - // v2 format: 0xAA + 0x02 + payload_len + payload... + checksum - if (length < 12) { - return false; - } - if (packet[0] != 0xAA) { - return false; - } - if (packet[1] != 0x02) { - return false; - } - - uint8_t payload_len = packet[2]; - if ((uint16_t)payload_len + 4u != length) { - return false; - } - - uint16_t sum = 0; - for (uint16_t i = 0; i < (uint16_t)(3u + payload_len); ++i) { - sum += packet[i]; - } - if ((sum & 0xFF) != packet[length - 1]) { - return false; - } - - // payload: buttons(2 LE), hat, lx, ly, rx, ry, imu_count, [imu_samples...] - if (payload_len < 8) { - return false; - } - - SwitchProOutReport out{}; - out.buttons = static_cast(packet[3]) | (static_cast(packet[4]) << 8); - out.hat = packet[5]; - out.lx = packet[6]; - out.ly = packet[7]; - out.rx = packet[8]; - out.ry = packet[9]; - uint8_t imu_count = packet[10]; - if (imu_count > 3) { - imu_count = 3; - } - - uint16_t required_payload_len = static_cast(8u + static_cast(imu_count) * 12u); - if (payload_len < required_payload_len) { - return false; - } - - auto expand_axis = [](uint8_t v) -> uint16_t { - return static_cast(v) << 8 | v; - }; - - SwitchInputState state = make_neutral_state(); - state.imu_sample_count = imu_count; - - auto read_int16 = [](const uint8_t* src) -> int16_t { - return static_cast(static_cast(src[0]) | (static_cast(src[1]) << 8)); - }; - for (uint8_t i = 0; i < imu_count; ++i) { - const uint8_t* base = &packet[11 + i * 12]; - state.imu_samples[i].accel_x = read_int16(base + 0); - state.imu_samples[i].accel_y = read_int16(base + 2); - state.imu_samples[i].accel_z = read_int16(base + 4); - state.imu_samples[i].gyro_x = read_int16(base + 6); - state.imu_samples[i].gyro_y = read_int16(base + 8); - state.imu_samples[i].gyro_z = read_int16(base + 10); - } - - switch (out.hat) { - case SWITCH_PRO_HAT_UP: state.dpad_up = true; break; - case SWITCH_PRO_HAT_UPRIGHT: state.dpad_up = true; state.dpad_right = true; break; - case SWITCH_PRO_HAT_RIGHT: state.dpad_right = true; break; - case SWITCH_PRO_HAT_DOWNRIGHT: state.dpad_down = true; state.dpad_right = true; break; - case SWITCH_PRO_HAT_DOWN: state.dpad_down = true; break; - case SWITCH_PRO_HAT_DOWNLEFT: state.dpad_down = true; state.dpad_left = true; break; - case SWITCH_PRO_HAT_LEFT: state.dpad_left = true; break; - case SWITCH_PRO_HAT_UPLEFT: state.dpad_up = true; state.dpad_left = true; break; - default: break; - } - - state.button_y = out.buttons & SWITCH_PRO_MASK_Y; - state.button_x = out.buttons & SWITCH_PRO_MASK_X; - state.button_b = out.buttons & SWITCH_PRO_MASK_B; - state.button_a = out.buttons & SWITCH_PRO_MASK_A; - state.button_r = out.buttons & SWITCH_PRO_MASK_R; - state.button_zr = out.buttons & SWITCH_PRO_MASK_ZR; - state.button_plus = out.buttons & SWITCH_PRO_MASK_PLUS; - state.button_minus = out.buttons & SWITCH_PRO_MASK_MINUS; - state.button_r3 = out.buttons & SWITCH_PRO_MASK_R3; - state.button_l3 = out.buttons & SWITCH_PRO_MASK_L3; - state.button_home = out.buttons & SWITCH_PRO_MASK_HOME; - state.button_capture = out.buttons & SWITCH_PRO_MASK_CAPTURE; - state.button_zl = out.buttons & SWITCH_PRO_MASK_ZL; - state.button_l = out.buttons & SWITCH_PRO_MASK_L; - - state.lx = expand_axis(out.lx); - state.ly = expand_axis(out.ly); - state.rx = expand_axis(out.rx); - state.ry = expand_axis(out.ry); - - if (!out_state) { - return false; - } - *out_state = state; - return true; -} - void switch_pro_set_rumble_callback(SwitchRumbleCallback cb) { rumble_callback = cb; } diff --git a/switch_pro_driver.h b/switch_pro_driver.h index 951d68b..fe3c4f5 100644 --- a/switch_pro_driver.h +++ b/switch_pro_driver.h @@ -8,46 +8,8 @@ #include #include -#include "switch_pro_descriptors.h" -typedef struct { - int16_t accel_x; - int16_t accel_y; - int16_t accel_z; - int16_t gyro_x; - int16_t gyro_y; - int16_t gyro_z; -} SwitchImuSample; - -typedef struct { - bool dpad_up; - bool dpad_down; - bool dpad_left; - bool dpad_right; - - bool button_a; - bool button_b; - bool button_x; - bool button_y; - bool button_l; - bool button_r; - bool button_zl; - bool button_zr; - bool button_plus; - bool button_minus; - bool button_home; - bool button_capture; - bool button_l3; - bool button_r3; - - uint16_t lx; // 0-65535 - uint16_t ly; - uint16_t rx; - uint16_t ry; - - uint8_t imu_sample_count; // 0-3 - SwitchImuSample imu_samples[3]; -} SwitchInputState; +#include "switch_protocol.h" // Initialize USB state and calibration before entering the main loop. void switch_pro_init(); @@ -58,13 +20,8 @@ void switch_pro_set_input(const SwitchInputState& state); // Drive the Switch Pro USB state machine; call this frequently in the main loop. void switch_pro_task(); -// Convert a packed UART message into controller state (returns true if parsed). -// If out_state is null the parsed state is written directly to the driver. -bool switch_pro_apply_uart_packet(const uint8_t* packet, uint8_t length, SwitchInputState* out_state = nullptr); - // Driver state helpers bool switch_pro_is_ready(); // Optional callback fired when the host sends a rumble payload (the raw 8 rumble bytes). -typedef void (*SwitchRumbleCallback)(const uint8_t rumble_data[8]); void switch_pro_set_rumble_callback(SwitchRumbleCallback cb); diff --git a/tusb_config.h b/tusb_config.h index 7f291ec..3c25f26 100644 --- a/tusb_config.h +++ b/tusb_config.h @@ -1,8 +1,11 @@ -// TinyUSB configuration tailored for a single Switch Pro style HID interface. -// Data is derived from TinyUSB examples and tuned for a 64-byte HID endpoint. +// TinyUSB configuration for the selected 64-byte controller protocol. #ifndef _TUSB_CONFIG_H_ #define _TUSB_CONFIG_H_ +#if (defined(SWITCH_PICO_PROTOCOL_LEGACY) + defined(SWITCH_PICO_PROTOCOL_SWITCH2)) != 1 +#error "Define exactly one Switch Pico USB protocol" +#endif + #ifdef __cplusplus extern "C" { #endif @@ -27,7 +30,15 @@ extern "C" { #define CFG_TUD_CDC 0 #define CFG_TUD_MSC 0 #define CFG_TUD_MIDI 0 +#define CFG_TUD_AUDIO 0 +#if defined(SWITCH_PICO_PROTOCOL_SWITCH2) +#define CFG_TUD_VENDOR 1 +#define CFG_TUD_VENDOR_EPSIZE 64 +#define CFG_TUD_VENDOR_RX_BUFSIZE 64 +#define CFG_TUD_VENDOR_TX_BUFSIZE 64 +#else #define CFG_TUD_VENDOR 0 +#endif // Always enable TinyUSB debug at level 2; LOG_PRINTF controls user-facing logs. #ifdef CFG_TUSB_DEBUG #undef CFG_TUSB_DEBUG