Wire protocol modes
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
parent
01cf6ed26f
commit
05e04b2632
6 changed files with 53 additions and 203 deletions
|
|
@ -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")
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,39 +9,10 @@
|
|||
|
||||
#include <stdint.h>
|
||||
|
||||
#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,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#include <stdio.h>
|
||||
#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<uint16_t>(packet[3]) | (static_cast<uint16_t>(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<uint16_t>(8u + static_cast<uint16_t>(imu_count) * 12u);
|
||||
if (payload_len < required_payload_len) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto expand_axis = [](uint8_t v) -> uint16_t {
|
||||
return static_cast<uint16_t>(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<int16_t>(static_cast<uint16_t>(src[0]) | (static_cast<uint16_t>(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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,46 +8,8 @@
|
|||
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue