It's connecting!!

This commit is contained in:
Joey Yakimowich-Payne 2025-11-20 17:17:47 -07:00
commit 2225e70e5e
No known key found for this signature in database
GPG key ID: 6BFE655FA5ABD1E1
7 changed files with 322 additions and 45 deletions

View file

@ -23,6 +23,7 @@ if (EXISTS ${picoVscode})
include(${picoVscode}) include(${picoVscode})
endif() endif()
# ==================================================================================== # ====================================================================================
option(SWITCH_PICO_AUTOTEST "Auto-play grip-screen connection sequence on startup" OFF)
set(PICO_BOARD pico CACHE STRING "Board type") set(PICO_BOARD pico CACHE STRING "Board type")
# Pull in Raspberry Pi Pico SDK (must be before project) # Pull in Raspberry Pi Pico SDK (must be before project)
@ -44,7 +45,8 @@ pico_set_program_name(switch-pico "switch-pico")
pico_set_program_version(switch-pico "0.1") pico_set_program_version(switch-pico "0.1")
# Modify the below lines to enable/disable output over UART/USB # Modify the below lines to enable/disable output over UART/USB
pico_enable_stdio_uart(switch-pico 0) # UART0 is enabled for debug logging; USB stdio remains off.
pico_enable_stdio_uart(switch-pico 1)
pico_enable_stdio_usb(switch-pico 0) pico_enable_stdio_usb(switch-pico 0)
# Add the standard library to the build # Add the standard library to the build
@ -56,6 +58,10 @@ target_link_libraries(switch-pico
pico_rand pico_rand
) )
if (SWITCH_PICO_AUTOTEST)
target_compile_definitions(switch-pico PRIVATE SWITCH_PICO_AUTOTEST=1)
endif()
# Add the standard include files to the build # Add the standard include files to the build
target_include_directories(switch-pico PRIVATE target_include_directories(switch-pico PRIVATE
${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_LIST_DIR}

33
host_uart_logger.py Normal file
View file

@ -0,0 +1,33 @@
import argparse
import datetime
import sys
try:
import serial
except ImportError:
print("pyserial not installed. Install with: pip install pyserial", file=sys.stderr)
sys.exit(1)
def main():
parser = argparse.ArgumentParser(description="Read debug logs from Pico UART")
parser.add_argument("-p", "--port", required=True, help="Serial port (e.g. /dev/ttyUSB0 or COM3)")
parser.add_argument("-b", "--baud", type=int, default=115200, help="Baud rate (default: 115200)")
args = parser.parse_args()
with serial.Serial(args.port, args.baud, timeout=1) as ser:
print(f"Opened {args.port} @ {args.baud}")
while True:
line = ser.readline()
if not line:
continue
ts = datetime.datetime.now().strftime("%H:%M:%S.%f")[:-3]
try:
text = line.decode(errors="replace").rstrip()
except Exception:
text = repr(line)
print(f"{ts} | {text}")
if __name__ == "__main__":
main()

View file

@ -11,6 +11,20 @@
#define UART_TX_PIN 4 #define UART_TX_PIN 4
#define UART_RX_PIN 5 #define UART_RX_PIN 5
static bool g_last_mounted = false;
static bool g_last_ready = false;
// Track the latest state provided by UART or the autopilot.
static SwitchInputState g_user_state;
#ifdef SWITCH_PICO_AUTOTEST
static bool g_autopilot_active = true;
static uint32_t g_autopilot_counter = 0;
static absolute_time_t g_autopilot_last_tick = {0};
static bool g_uart_activity = false;
static bool g_ready_logged = false;
#endif
static void init_uart_input() { static void init_uart_input() {
uart_init(UART_ID, BAUD_RATE); uart_init(UART_ID, BAUD_RATE);
gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART); gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART);
@ -52,12 +66,104 @@ static void poll_uart_frames() {
buffer[index++] = byte; buffer[index++] = byte;
if (index >= sizeof(buffer)) { if (index >= sizeof(buffer)) {
switch_pro_apply_uart_packet(buffer, sizeof(buffer)); SwitchInputState parsed{};
if (switch_pro_apply_uart_packet(buffer, sizeof(buffer), &parsed)) {
g_user_state = parsed;
printf("[UART] packet buttons=0x%04x hat=%u lx=%u ly=%u rx=%u ry=%u\n",
(parsed.button_a ? SWITCH_PRO_MASK_A : 0) |
(parsed.button_b ? SWITCH_PRO_MASK_B : 0) |
(parsed.button_x ? SWITCH_PRO_MASK_X : 0) |
(parsed.button_y ? SWITCH_PRO_MASK_Y : 0) |
(parsed.button_l ? SWITCH_PRO_MASK_L : 0) |
(parsed.button_r ? SWITCH_PRO_MASK_R : 0) |
(parsed.button_zl ? SWITCH_PRO_MASK_ZL : 0) |
(parsed.button_zr ? SWITCH_PRO_MASK_ZR : 0) |
(parsed.button_plus? SWITCH_PRO_MASK_PLUS: 0) |
(parsed.button_minus?SWITCH_PRO_MASK_MINUS:0) |
(parsed.button_home?SWITCH_PRO_MASK_HOME:0) |
(parsed.button_capture?SWITCH_PRO_MASK_CAPTURE:0) |
(parsed.button_l3 ? SWITCH_PRO_MASK_L3 : 0) |
(parsed.button_r3 ? SWITCH_PRO_MASK_R3 : 0),
parsed.dpad_up ? SWITCH_PRO_HAT_UP :
parsed.dpad_down ? SWITCH_PRO_HAT_DOWN :
parsed.dpad_left ? SWITCH_PRO_HAT_LEFT :
parsed.dpad_right ? SWITCH_PRO_HAT_RIGHT : SWITCH_PRO_HAT_NOTHING,
parsed.lx >> 8, parsed.ly >> 8, parsed.rx >> 8, parsed.ry >> 8);
}
#ifdef SWITCH_PICO_AUTOTEST
g_uart_activity = true;
#endif
index = 0; index = 0;
} }
} }
} }
#ifdef SWITCH_PICO_AUTOTEST
// Replays the Switch-Fightstick grip-screen sequence: press L+R twice, then A twice.
static SwitchInputState autopilot_state(const SwitchInputState& fallback) {
if (!g_autopilot_active || g_uart_activity) {
g_autopilot_active = false;
return fallback;
}
if (!tud_mounted()) {
g_autopilot_counter = 0;
g_autopilot_last_tick = {0};
return fallback;
}
absolute_time_t now = get_absolute_time();
if (!to_us_since_boot(g_autopilot_last_tick)) {
g_autopilot_last_tick = now;
}
// Run at ~1ms cadence similar to the LUFA fightstick timing.
if (absolute_time_diff_us(g_autopilot_last_tick, now) < 1000) {
return fallback;
}
g_autopilot_last_tick = now;
SwitchInputState state = fallback;
state.lx = SWITCH_PRO_JOYSTICK_MID;
state.ly = SWITCH_PRO_JOYSTICK_MID;
state.rx = SWITCH_PRO_JOYSTICK_MID;
state.ry = SWITCH_PRO_JOYSTICK_MID;
// Fire L+R twice then A twice, loop every ~300ms to keep trying.
uint32_t step = g_autopilot_counter % 300;
if (step == 25 || step == 50) {
state.button_l = true;
state.button_r = true;
} else if (step == 75 || step == 100) {
state.button_a = true;
}
g_autopilot_counter++;
return state;
}
#endif
static void log_usb_state() {
bool mounted = tud_mounted();
bool ready = switch_pro_is_ready();
if (mounted != g_last_mounted) {
g_last_mounted = mounted;
printf("[USB] %s\n", mounted ? "mounted" : "unmounted");
}
if (ready != g_last_ready) {
g_last_ready = ready;
printf("[SWITCH] driver %s\n", ready ? "ready (handshake OK)" : "not ready");
}
#ifdef SWITCH_PICO_AUTOTEST
if (ready && !g_ready_logged) {
g_ready_logged = true;
printf("[AUTO] ready -> autopilot active=%s\n", g_autopilot_active ? "true" : "false");
}
#endif
}
int main() { int main() {
board_init(); board_init();
stdio_init_all(); stdio_init_all();
@ -66,11 +172,27 @@ int main() {
tusb_init(); tusb_init();
switch_pro_init(); switch_pro_init();
switch_pro_set_input(neutral_input()); g_user_state = neutral_input();
switch_pro_set_input(g_user_state);
printf("[BOOT] switch-pico starting (UART0 log @ 115200)\n");
printf("[INFO] AUTOTEST=%s UART1 pins TX=%d RX=%d baud=%d\n",
#ifdef SWITCH_PICO_AUTOTEST
"ON"
#else
"OFF"
#endif
, UART_TX_PIN, UART_RX_PIN, BAUD_RATE);
while (true) { while (true) {
tud_task(); // USB device tasks tud_task(); // USB device tasks
poll_uart_frames(); // Pull controller state from UART1 poll_uart_frames(); // Pull controller state from UART1
SwitchInputState state = g_user_state;
#ifdef SWITCH_PICO_AUTOTEST
state = autopilot_state(state);
#endif
switch_pro_set_input(state);
switch_pro_task(); // Push state to the Switch host switch_pro_task(); // Push state to the Switch host
log_usb_state();
} }
} }

View file

@ -322,8 +322,10 @@ typedef struct
} SwitchProOutReport; } SwitchProOutReport;
static const uint8_t switch_pro_string_language[] = { 0x09, 0x04 }; static const uint8_t switch_pro_string_language[] = { 0x09, 0x04 };
static const uint8_t switch_pro_string_manufacturer[] = "Open Stick Community"; static const uint8_t switch_pro_string_manufacturer[] = "Nintendo Co., Ltd.";
static const uint8_t switch_pro_string_product[] = "GP2040-CE (Pro Controller)"; static const uint8_t switch_pro_string_product[] = "Pro Controller";
// static const uint8_t switch_pro_string_manufacturer[] = "Open Stick Community";
// static const uint8_t switch_pro_string_product[] = "GP2040-CE (Pro Controller)";
static const uint8_t switch_pro_string_version[] = "000000000001"; static const uint8_t switch_pro_string_version[] = "000000000001";
static const uint8_t *switch_pro_string_descriptors[] __attribute__((unused)) = static const uint8_t *switch_pro_string_descriptors[] __attribute__((unused)) =
@ -340,8 +342,8 @@ static const uint8_t switch_pro_device_descriptor[] =
0x01, // bDescriptorType (Device) 0x01, // bDescriptorType (Device)
0x00, 0x02, // bcdUSB 2.00 0x00, 0x02, // bcdUSB 2.00
0x00, // bDeviceClass (Use class information in the Interface Descriptors) 0x00, // bDeviceClass (Use class information in the Interface Descriptors)
0x00, // bDeviceSubClass 0x00, // bDeviceSubClass
0x00, // bDeviceProtocol 0x00, // bDeviceProtocol
0x40, // bMaxPacketSize0 64 0x40, // bMaxPacketSize0 64
0x7E, 0x05, // idVendor 0x057E 0x7E, 0x05, // idVendor 0x057E
0x09, 0x20, // idProduct 0x2009 0x09, 0x20, // idProduct 0x2009
@ -394,7 +396,7 @@ static const uint8_t switch_pro_configuration_descriptor[] =
0x07, // bLength 0x07, // bLength
0x05, // bDescriptorType (Endpoint) 0x05, // bDescriptorType (Endpoint)
0x05, // bEndpointAddress (IN/D2H) 0x81, // bEndpointAddress (IN/D2H)
0x03, // bmAttributes (Interrupt) 0x03, // bmAttributes (Interrupt)
0x40, 0x00, // wMaxPacketSize 64 0x40, 0x00, // wMaxPacketSize 64
0x08, // bInterval 8 (unit depends on device speed) 0x08, // bInterval 8 (unit depends on device speed)

View file

@ -22,11 +22,14 @@ static uint8_t last_report[SWITCH_PRO_ENDPOINT_SIZE] = {};
static SwitchProReport switch_report{}; static SwitchProReport switch_report{};
static uint8_t last_report_counter = 0; static uint8_t last_report_counter = 0;
static uint32_t last_report_timer = 0; static uint32_t last_report_timer = 0;
static uint32_t last_host_activity_ms = 0;
static bool is_ready = false; static bool is_ready = false;
static bool is_initialized = false; static bool is_initialized = false;
static bool is_report_queued = false; static bool is_report_queued = false;
static bool report_sent = false; static bool report_sent = false;
static uint8_t queued_report_id = 0; static uint8_t queued_report_id = 0;
static bool forced_ready = false;
static bool host_sent_out = false;
static uint8_t handshake_counter = 0; static uint8_t handshake_counter = 0;
static SwitchDeviceInfo device_info{}; static SwitchDeviceInfo device_info{};
@ -47,31 +50,31 @@ static const uint8_t factory_config_data[0xEFF] = {
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF,
// device type // device type
SWITCH_TYPE_PRO_CONTROLLER, SWITCH_TYPE_PRO_CONTROLLER,
// unknown // unknown
0xA0, 0xA0,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// color options // color options
0x02, 0x02,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// config & calibration 1 // config & calibration 1
0xE3, 0xFF, 0x39, 0xFF, 0xED, 0x01, 0x00, 0x40, 0xE3, 0xFF, 0x39, 0xFF, 0xED, 0x01, 0x00, 0x40,
0x00, 0x40, 0x00, 0x40, 0x09, 0x00, 0xEA, 0xFF, 0x00, 0x40, 0x00, 0x40, 0x09, 0x00, 0xEA, 0xFF,
0xA1, 0xFF, 0x3B, 0x34, 0x3B, 0x34, 0x3B, 0x34, 0xA1, 0xFF, 0x3B, 0x34, 0x3B, 0x34, 0x3B, 0x34,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
// config & calibration 2 // config & calibration 2
// left stick // left stick
0xa4, 0x46, 0x6a, 0x00, 0x08, 0x80, 0xa4, 0x46, 0xa4, 0x46, 0x6a, 0x00, 0x08, 0x80, 0xa4, 0x46,
0x6a, 0x6a,
// right stick // right stick
@ -92,32 +95,32 @@ static const uint8_t factory_config_data[0xEFF] = {
// right grip color // right grip color
0xEC, 0x00, 0x8C, 0xEC, 0x00, 0x8C,
0x01, 0x01,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x50, 0xFD, 0x00, 0x00, 0xC6, 0x0F, 0x50, 0xFD, 0x00, 0x00, 0xC6, 0x0F,
0x0F, 0x30, 0x61, 0xAE, 0x90, 0xD9, 0xD4, 0x14, 0x0F, 0x30, 0x61, 0xAE, 0x90, 0xD9, 0xD4, 0x14,
0x54, 0x41, 0x15, 0x54, 0xC7, 0x79, 0x9C, 0x33, 0x54, 0x41, 0x15, 0x54, 0xC7, 0x79, 0x9C, 0x33,
0x36, 0x63, 0x36, 0x63,
0x0F, 0x30, 0x61, 0xAE, 0x90, 0xD9, 0xD4, 0x14, 0x0F, 0x30, 0x61, 0xAE, 0x90, 0xD9, 0xD4, 0x14,
0x54, 0x41, 0x15, 0x54, 0x54, 0x41, 0x15, 0x54,
0xC7, 0xC7,
0x79, 0x79,
0x9C, 0x9C,
0x33,
0x36,
0x33,
0x36,
0x63, 0xFF, 0xFF, 0xFF, 0x63, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF 0xFF, 0xFF, 0xFF
}; };
@ -125,9 +128,9 @@ static const uint8_t factory_config_data[0xEFF] = {
static const uint8_t user_calibration_data[0x3F] = { static const uint8_t user_calibration_data[0x3F] = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
// Left Stick // Left Stick
0xB2, 0xA1, 0xa4, 0x46, 0x6a, 0x00, 0x08, 0x80, 0xB2, 0xA1, 0xa4, 0x46, 0x6a, 0x00, 0x08, 0x80,
0xa4, 0x46, 0x6a, 0xa4, 0x46, 0x6a,
// Right Stick // Right Stick
@ -176,6 +179,9 @@ static bool send_report(uint8_t reportID, const void* reportData, uint16_t repor
} else { } else {
last_report_counter = 0; last_report_counter = 0;
} }
if (!result) {
printf("[HID] send_report failed id=%u len=%u\n", reportID, reportLength);
}
return result; return result;
} }
@ -194,21 +200,26 @@ static void read_spi_flash(uint8_t* dest, uint32_t address, uint8_t size) {
static void handle_config_report(uint8_t switchReportID, uint8_t switchReportSubID, const uint8_t *reportData, uint16_t reportLength) { static void handle_config_report(uint8_t switchReportID, uint8_t switchReportSubID, const uint8_t *reportData, uint16_t reportLength) {
bool canSend = false; bool canSend = false;
last_host_activity_ms = to_ms_since_boot(get_absolute_time());
host_sent_out = true;
switch (switchReportSubID) { switch (switchReportSubID) {
case IDENTIFY: case IDENTIFY:
send_identify(); send_identify();
canSend = true; canSend = true;
printf("[HID] CONFIG IDENTIFY\n");
break; break;
case HANDSHAKE: case HANDSHAKE:
report_buffer[0] = REPORT_USB_INPUT_81; report_buffer[0] = REPORT_USB_INPUT_81;
report_buffer[1] = HANDSHAKE; report_buffer[1] = HANDSHAKE;
canSend = true; canSend = true;
printf("[HID] CONFIG HANDSHAKE\n");
break; break;
case BAUD_RATE: case BAUD_RATE:
report_buffer[0] = REPORT_USB_INPUT_81; report_buffer[0] = REPORT_USB_INPUT_81;
report_buffer[1] = BAUD_RATE; report_buffer[1] = BAUD_RATE;
canSend = true; canSend = true;
printf("[HID] CONFIG BAUD_RATE\n");
break; break;
case DISABLE_USB_TIMEOUT: case DISABLE_USB_TIMEOUT:
report_buffer[0] = REPORT_OUTPUT_30; report_buffer[0] = REPORT_OUTPUT_30;
@ -219,16 +230,19 @@ static void handle_config_report(uint8_t switchReportID, uint8_t switchReportSub
is_ready = true; is_ready = true;
//} //}
canSend = true; canSend = true;
printf("[HID] CONFIG DISABLE_USB_TIMEOUT -> ready\n");
break; break;
case ENABLE_USB_TIMEOUT: case ENABLE_USB_TIMEOUT:
report_buffer[0] = REPORT_OUTPUT_30; report_buffer[0] = REPORT_OUTPUT_30;
report_buffer[1] = switchReportSubID; report_buffer[1] = switchReportSubID;
canSend = true; canSend = true;
printf("[HID] CONFIG ENABLE_USB_TIMEOUT\n");
break; break;
default: default:
report_buffer[0] = REPORT_OUTPUT_30; report_buffer[0] = REPORT_OUTPUT_30;
report_buffer[1] = switchReportSubID; report_buffer[1] = switchReportSubID;
canSend = true; canSend = true;
printf("[HID] CONFIG unknown subid=0x%02x\n", switchReportSubID);
break; break;
} }
@ -240,6 +254,8 @@ static void handle_feature_report(uint8_t switchReportID, uint8_t switchReportSu
uint32_t spiReadAddress = 0; uint32_t spiReadAddress = 0;
uint8_t spiReadSize = 0; uint8_t spiReadSize = 0;
bool canSend = false; bool canSend = false;
last_host_activity_ms = to_ms_since_boot(get_absolute_time());
host_sent_out = true;
report_buffer[0] = REPORT_OUTPUT_21; report_buffer[0] = REPORT_OUTPUT_21;
report_buffer[1] = last_report_counter; report_buffer[1] = last_report_counter;
@ -251,18 +267,21 @@ static void handle_feature_report(uint8_t switchReportID, uint8_t switchReportSu
report_buffer[14] = commandID; report_buffer[14] = commandID;
report_buffer[15] = 0x03; report_buffer[15] = 0x03;
canSend = true; canSend = true;
printf("[HID] FEATURE GET_CONTROLLER_STATE\n");
break; break;
case BLUETOOTH_PAIR_REQUEST: case BLUETOOTH_PAIR_REQUEST:
report_buffer[13] = 0x81; report_buffer[13] = 0x81;
report_buffer[14] = commandID; report_buffer[14] = commandID;
report_buffer[15] = 0x03; report_buffer[15] = 0x03;
canSend = true; canSend = true;
printf("[HID] FEATURE BLUETOOTH_PAIR_REQUEST\n");
break; break;
case REQUEST_DEVICE_INFO: case REQUEST_DEVICE_INFO:
report_buffer[13] = 0x82; report_buffer[13] = 0x82;
report_buffer[14] = 0x02; report_buffer[14] = 0x02;
memcpy(&report_buffer[15], &device_info, sizeof(device_info)); memcpy(&report_buffer[15], &device_info, sizeof(device_info));
canSend = true; canSend = true;
printf("[HID] FEATURE REQUEST_DEVICE_INFO\n");
break; break;
case SET_MODE: case SET_MODE:
input_mode = reportData[11]; input_mode = reportData[11];
@ -270,16 +289,19 @@ static void handle_feature_report(uint8_t switchReportID, uint8_t switchReportSu
report_buffer[14] = 0x03; report_buffer[14] = 0x03;
report_buffer[15] = input_mode; report_buffer[15] = input_mode;
canSend = true; canSend = true;
printf("[HID] FEATURE SET_MODE 0x%02x\n", input_mode);
break; break;
case TRIGGER_BUTTONS: case TRIGGER_BUTTONS:
report_buffer[13] = 0x83; report_buffer[13] = 0x83;
report_buffer[14] = 0x04; report_buffer[14] = 0x04;
canSend = true; canSend = true;
printf("[HID] FEATURE TRIGGER_BUTTONS\n");
break; break;
case SET_SHIPMENT: case SET_SHIPMENT:
report_buffer[13] = 0x80; report_buffer[13] = 0x80;
report_buffer[14] = commandID; report_buffer[14] = commandID;
canSend = true; canSend = true;
printf("[HID] FEATURE SET_SHIPMENT\n");
break; break;
case SPI_READ: case SPI_READ:
spiReadAddress = (reportData[14] << 24) | (reportData[13] << 16) | (reportData[12] << 8) | (reportData[11]); spiReadAddress = (reportData[14] << 24) | (reportData[13] << 16) | (reportData[12] << 8) | (reportData[11]);
@ -293,22 +315,26 @@ static void handle_feature_report(uint8_t switchReportID, uint8_t switchReportSu
report_buffer[19] = reportData[15]; report_buffer[19] = reportData[15];
read_spi_flash(&report_buffer[20], spiReadAddress, spiReadSize); read_spi_flash(&report_buffer[20], spiReadAddress, spiReadSize);
canSend = true; canSend = true;
printf("[HID] FEATURE SPI_READ addr=0x%08lx size=%u\n", (unsigned long)spiReadAddress, spiReadSize);
break; break;
case SET_NFC_IR_CONFIG: case SET_NFC_IR_CONFIG:
report_buffer[13] = 0x80; report_buffer[13] = 0x80;
report_buffer[14] = commandID; report_buffer[14] = commandID;
canSend = true; canSend = true;
printf("[HID] FEATURE SET_NFC_IR_CONFIG\n");
break; break;
case SET_NFC_IR_STATE: case SET_NFC_IR_STATE:
report_buffer[13] = 0x80; report_buffer[13] = 0x80;
report_buffer[14] = commandID; report_buffer[14] = commandID;
canSend = true; canSend = true;
printf("[HID] FEATURE SET_NFC_IR_STATE\n");
break; break;
case SET_PLAYER_LIGHTS: case SET_PLAYER_LIGHTS:
player_id = reportData[11]; player_id = reportData[11];
report_buffer[13] = 0x80; report_buffer[13] = 0x80;
report_buffer[14] = commandID; report_buffer[14] = commandID;
canSend = true; canSend = true;
printf("[HID] FEATURE SET_PLAYER_LIGHTS player=%u\n", player_id);
break; break;
case GET_PLAYER_LIGHTS: case GET_PLAYER_LIGHTS:
player_id = reportData[11]; player_id = reportData[11];
@ -316,18 +342,21 @@ static void handle_feature_report(uint8_t switchReportID, uint8_t switchReportSu
report_buffer[14] = commandID; report_buffer[14] = commandID;
report_buffer[15] = player_id; report_buffer[15] = player_id;
canSend = true; canSend = true;
printf("[HID] FEATURE GET_PLAYER_LIGHTS player=%u\n", player_id);
break; break;
case COMMAND_UNKNOWN_33: case COMMAND_UNKNOWN_33:
report_buffer[13] = 0x80; report_buffer[13] = 0x80;
report_buffer[14] = commandID; report_buffer[14] = commandID;
report_buffer[15] = 0x03; report_buffer[15] = 0x03;
canSend = true; canSend = true;
printf("[HID] FEATURE COMMAND_UNKNOWN_33\n");
break; break;
case SET_HOME_LIGHT: case SET_HOME_LIGHT:
report_buffer[13] = 0x80; report_buffer[13] = 0x80;
report_buffer[14] = commandID; report_buffer[14] = commandID;
report_buffer[15] = 0x00; report_buffer[15] = 0x00;
canSend = true; canSend = true;
printf("[HID] FEATURE SET_HOME_LIGHT\n");
break; break;
case TOGGLE_IMU: case TOGGLE_IMU:
is_imu_enabled = reportData[11]; is_imu_enabled = reportData[11];
@ -335,11 +364,13 @@ static void handle_feature_report(uint8_t switchReportID, uint8_t switchReportSu
report_buffer[14] = commandID; report_buffer[14] = commandID;
report_buffer[15] = 0x00; report_buffer[15] = 0x00;
canSend = true; canSend = true;
printf("[HID] FEATURE TOGGLE_IMU %u\n", is_imu_enabled);
break; break;
case IMU_SENSITIVITY: case IMU_SENSITIVITY:
report_buffer[13] = 0x80; report_buffer[13] = 0x80;
report_buffer[14] = commandID; report_buffer[14] = commandID;
canSend = true; canSend = true;
printf("[HID] FEATURE IMU_SENSITIVITY\n");
break; break;
case ENABLE_VIBRATION: case ENABLE_VIBRATION:
is_vibration_enabled = reportData[11]; is_vibration_enabled = reportData[11];
@ -347,6 +378,7 @@ static void handle_feature_report(uint8_t switchReportID, uint8_t switchReportSu
report_buffer[14] = commandID; report_buffer[14] = commandID;
report_buffer[15] = 0x00; report_buffer[15] = 0x00;
canSend = true; canSend = true;
printf("[HID] FEATURE ENABLE_VIBRATION %u\n", is_vibration_enabled);
break; break;
case READ_IMU: case READ_IMU:
report_buffer[13] = 0xC0; report_buffer[13] = 0xC0;
@ -354,6 +386,7 @@ static void handle_feature_report(uint8_t switchReportID, uint8_t switchReportSu
report_buffer[15] = reportData[11]; report_buffer[15] = reportData[11];
report_buffer[16] = reportData[12]; report_buffer[16] = reportData[12];
canSend = true; canSend = true;
printf("[HID] FEATURE READ_IMU addr=%u size=%u\n", reportData[11], reportData[12]);
break; break;
case GET_VOLTAGE: case GET_VOLTAGE:
report_buffer[13] = 0xD0; report_buffer[13] = 0xD0;
@ -361,12 +394,14 @@ static void handle_feature_report(uint8_t switchReportID, uint8_t switchReportSu
report_buffer[15] = 0x83; report_buffer[15] = 0x83;
report_buffer[16] = 0x06; report_buffer[16] = 0x06;
canSend = true; canSend = true;
printf("[HID] FEATURE GET_VOLTAGE\n");
break; break;
default: default:
report_buffer[13] = 0x80; report_buffer[13] = 0x80;
report_buffer[14] = commandID; report_buffer[14] = commandID;
report_buffer[15] = 0x03; report_buffer[15] = 0x03;
canSend = true; canSend = true;
printf("[HID] FEATURE unknown cmd=0x%02x\n", commandID);
break; break;
} }
@ -404,7 +439,7 @@ static void update_switch_report_from_state() {
uint16_t scaleLeftStickY = scale16To12(g_input_state.ly); uint16_t scaleLeftStickY = scale16To12(g_input_state.ly);
uint16_t scaleRightStickX = scale16To12(g_input_state.rx); uint16_t scaleRightStickX = scale16To12(g_input_state.rx);
uint16_t scaleRightStickY = scale16To12(g_input_state.ry); uint16_t scaleRightStickY = scale16To12(g_input_state.ry);
switch_report.inputs.leftStick.setX(std::min(std::max(scaleLeftStickX,leftMinX), leftMaxX)); switch_report.inputs.leftStick.setX(std::min(std::max(scaleLeftStickX,leftMinX), leftMaxX));
switch_report.inputs.leftStick.setY(-std::min(std::max(scaleLeftStickY,leftMinY), leftMaxY)); switch_report.inputs.leftStick.setY(-std::min(std::max(scaleLeftStickY,leftMinY), leftMaxY));
switch_report.inputs.rightStick.setX(std::min(std::max(scaleRightStickX,rightMinX), rightMaxX)); switch_report.inputs.rightStick.setX(std::min(std::max(scaleRightStickX,rightMinX), rightMaxX));
@ -421,6 +456,8 @@ void switch_pro_init() {
is_initialized = false; is_initialized = false;
is_report_queued = false; is_report_queued = false;
report_sent = false; report_sent = false;
forced_ready = false;
host_sent_out = false;
device_info = { device_info = {
.majorVersion = 0x04, .majorVersion = 0x04,
@ -437,8 +474,8 @@ void switch_pro_init() {
.timestamp = 0, .timestamp = 0,
.inputs { .inputs {
.connectionInfo = 0, .connectionInfo = 0x08, // wired connection
.batteryLevel = 0x08, .batteryLevel = 0x0F, // full battery
.buttonY = 0, .buttonY = 0,
.buttonX = 0, .buttonX = 0,
@ -475,6 +512,7 @@ void switch_pro_init() {
}; };
last_report_timer = to_ms_since_boot(get_absolute_time()); last_report_timer = to_ms_since_boot(get_absolute_time());
last_host_activity_ms = last_report_timer;
factory_config->leftStickCalibration.getRealMin(leftMinX, leftMinY); factory_config->leftStickCalibration.getRealMin(leftMinX, leftMinY);
factory_config->leftStickCalibration.getCenter(leftCenX, leftCenY); factory_config->leftStickCalibration.getCenter(leftCenX, leftCenY);
@ -508,7 +546,8 @@ void switch_pro_task() {
report_sent = true; report_sent = true;
} }
if (is_ready && !report_sent) { // If the host never sends feature/config reports (seen on Switch), force readiness after a timeout.
if (is_ready && !report_sent && host_sent_out) {
if ((now - last_report_timer) > SWITCH_PRO_KEEPALIVE_TIMER) { if ((now - last_report_timer) > SWITCH_PRO_KEEPALIVE_TIMER) {
switch_report.timestamp = last_report_counter; switch_report.timestamp = last_report_counter;
void * inputReport = &switch_report; void * inputReport = &switch_report;
@ -535,7 +574,7 @@ void switch_pro_task() {
} }
} }
bool switch_pro_apply_uart_packet(const uint8_t* packet, uint8_t length) { bool switch_pro_apply_uart_packet(const uint8_t* packet, uint8_t length, SwitchInputState* out_state) {
// Packet format: 0xAA, buttons(2 LE), hat, lx, ly, rx, ry // Packet format: 0xAA, buttons(2 LE), hat, lx, ly, rx, ry
if (length < 8 || packet[0] != 0xAA) { if (length < 8 || packet[0] != 0xAA) {
return false; return false;
@ -587,18 +626,33 @@ bool switch_pro_apply_uart_packet(const uint8_t* packet, uint8_t length) {
state.rx = expand_axis(out.rx); state.rx = expand_axis(out.rx);
state.ry = expand_axis(out.ry); state.ry = expand_axis(out.ry);
switch_pro_set_input(state); if (out_state) {
*out_state = state;
} else {
switch_pro_set_input(state);
}
return true; return true;
} }
bool switch_pro_is_ready() {
return is_ready;
}
void switch_pro_mark_host_active() {
host_sent_out = true;
}
// HID callbacks // HID callbacks
uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t *buffer, uint16_t reqlen) { uint16_t tud_hid_get_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t *buffer, uint16_t reqlen) {
(void)instance; (void)instance;
(void)report_id; printf("[HID] get_report id=%u type=%u len=%u\n", report_id, report_type, reqlen);
(void)report_type; if (!buffer) return 0;
(void)buffer;
(void)reqlen; // Serve the current input report for any GET_REPORT request.
return 0; uint16_t report_size = sizeof(switch_report);
if (reqlen < report_size) report_size = reqlen;
memcpy(buffer, &switch_report, report_size);
return report_size;
} }
void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t const *buffer, uint16_t bufsize) { void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_t report_type, uint8_t const *buffer, uint16_t bufsize) {
@ -609,7 +663,12 @@ void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_
uint8_t switchReportID = buffer[0]; uint8_t switchReportID = buffer[0];
uint8_t switchReportSubID = buffer[1]; uint8_t switchReportSubID = buffer[1];
printf("[HID] set_report type=%d id=%u switchRID=0x%02x sub=0x%02x len=%u\n",
report_type, report_id, switchReportID, switchReportSubID, bufsize);
host_sent_out = true;
if (switchReportID == REPORT_OUTPUT_00) { if (switchReportID == REPORT_OUTPUT_00) {
// No-op, just acknowledge to clear any stalls.
return;
} else if (switchReportID == REPORT_FEATURE) { } else if (switchReportID == REPORT_FEATURE) {
queued_report_id = report_id; queued_report_id = report_id;
handle_feature_report(switchReportID, switchReportSubID, buffer, bufsize); handle_feature_report(switchReportID, switchReportSubID, buffer, bufsize);
@ -620,6 +679,26 @@ void tud_hid_set_report_cb(uint8_t instance, uint8_t report_id, hid_report_type_
} }
} }
void tud_hid_report_received_cb(uint8_t instance, uint8_t report_id, uint8_t const* buffer, uint16_t bufsize) {
(void)instance;
// Host sent data on interrupt OUT; mirror the control path handling.
memset(report_buffer, 0x00, bufsize);
uint8_t switchReportID = buffer[0];
uint8_t switchReportSubID = buffer[1];
printf("[HID] report_received id=%u switchRID=0x%02x sub=0x%02x len=%u\n",
report_id, switchReportID, switchReportSubID, bufsize);
host_sent_out = true;
if (switchReportID == REPORT_OUTPUT_00) {
return;
} else if (switchReportID == REPORT_FEATURE) {
queued_report_id = report_id;
handle_feature_report(switchReportID, switchReportSubID, buffer, bufsize);
} else if (switchReportID == REPORT_CONFIGURATION) {
queued_report_id = report_id;
handle_config_report(switchReportID, switchReportSubID, buffer, bufsize);
}
}
uint8_t const * tud_hid_descriptor_report_cb(uint8_t itf) { uint8_t const * tud_hid_descriptor_report_cb(uint8_t itf) {
(void)itf; (void)itf;
return switch_pro_report_descriptor; return switch_pro_report_descriptor;
@ -634,6 +713,30 @@ uint8_t const * tud_descriptor_configuration_cb(uint8_t index) {
return switch_pro_configuration_descriptor; return switch_pro_configuration_descriptor;
} }
bool tud_control_request_cb(uint8_t rhport, tusb_control_request_t const * request) {
(void)rhport;
printf("[CTRL] bmReq=0x%02x bReq=0x%02x wValue=0x%04x wIndex=0x%04x wLen=%u\n",
request->bmRequestType, request->bRequest, request->wValue, request->wIndex, request->wLength);
return false; // let TinyUSB handle it normally
}
void tud_mount_cb(void) {
printf("[USB] mount_cb\n");
last_host_activity_ms = to_ms_since_boot(get_absolute_time());
forced_ready = false;
is_ready = false;
is_initialized = false;
host_sent_out = false;
}
void tud_umount_cb(void) {
printf("[USB] umount_cb\n");
forced_ready = false;
is_ready = false;
is_initialized = false;
host_sent_out = false;
}
static uint16_t desc_str[32]; static uint16_t desc_str[32];
uint16_t const * tud_descriptor_string_cb(uint8_t index, uint16_t langid) { uint16_t const * tud_descriptor_string_cb(uint8_t index, uint16_t langid) {

View file

@ -47,4 +47,11 @@ void switch_pro_set_input(const SwitchInputState& state);
void switch_pro_task(); void switch_pro_task();
// Convert a packed UART message into controller state (returns true if parsed). // Convert a packed UART message into controller state (returns true if parsed).
bool switch_pro_apply_uart_packet(const uint8_t* packet, uint8_t length); // 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();
// Mark that the host has sent any OUT traffic (allows starting IN reports).
void switch_pro_mark_host_active();

View file

@ -28,6 +28,10 @@ extern "C" {
#define CFG_TUD_MSC 0 #define CFG_TUD_MSC 0
#define CFG_TUD_MIDI 0 #define CFG_TUD_MIDI 0
#define CFG_TUD_VENDOR 0 #define CFG_TUD_VENDOR 0
#ifdef CFG_TUSB_DEBUG
#undef CFG_TUSB_DEBUG
#endif
#define CFG_TUSB_DEBUG 2
#define CFG_TUD_HID_EP_BUFSIZE 64 #define CFG_TUD_HID_EP_BUFSIZE 64