204 lines
6.6 KiB
C++
204 lines
6.6 KiB
C++
#include <stdio.h>
|
|
#include "bsp/board.h"
|
|
#include "hardware/uart.h"
|
|
#include "pico/stdlib.h"
|
|
#include "tusb.h"
|
|
#include "switch_pro_driver.h"
|
|
|
|
#ifdef SWITCH_PICO_LOG
|
|
#define LOG_PRINTF(...) printf(__VA_ARGS__)
|
|
#else
|
|
#define LOG_PRINTF(...) ((void)0)
|
|
#endif
|
|
|
|
// UART1 is reserved for external input frames from the host PC.
|
|
#define UART_ID uart1
|
|
#define BAUD_RATE 1000000
|
|
#define UART_TX_PIN 4
|
|
#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() {
|
|
uart_init(UART_ID, BAUD_RATE);
|
|
gpio_set_function(UART_TX_PIN, GPIO_FUNC_UART);
|
|
gpio_set_function(UART_RX_PIN, GPIO_FUNC_UART);
|
|
uart_set_format(UART_ID, 8, 1, UART_PARITY_NONE);
|
|
}
|
|
|
|
static SwitchInputState neutral_input() {
|
|
SwitchInputState state{};
|
|
state.lx = SWITCH_PRO_JOYSTICK_MID;
|
|
state.ly = SWITCH_PRO_JOYSTICK_MID;
|
|
state.rx = SWITCH_PRO_JOYSTICK_MID;
|
|
state.ry = SWITCH_PRO_JOYSTICK_MID;
|
|
return state;
|
|
}
|
|
|
|
// Consume UART bytes and forward complete frames to the Switch Pro driver.
|
|
static void poll_uart_frames() {
|
|
static uint8_t buffer[8];
|
|
static uint8_t index = 0;
|
|
static absolute_time_t last_byte_time = {0};
|
|
static bool has_last_byte = false;
|
|
|
|
while (uart_is_readable(UART_ID)) {
|
|
uint8_t byte = uart_getc(UART_ID);
|
|
|
|
uint64_t now = to_ms_since_boot(get_absolute_time());
|
|
if (has_last_byte && (now - to_ms_since_boot(last_byte_time)) > 20) {
|
|
index = 0; // stale data, restart frame
|
|
}
|
|
last_byte_time = get_absolute_time();
|
|
has_last_byte = true;
|
|
|
|
if (index == 0) {
|
|
if (byte != 0xAA) {
|
|
continue; // wait for start-of-frame marker
|
|
}
|
|
}
|
|
|
|
buffer[index++] = byte;
|
|
if (index >= sizeof(buffer)) {
|
|
SwitchInputState parsed{};
|
|
if (switch_pro_apply_uart_packet(buffer, sizeof(buffer), &parsed)) {
|
|
g_user_state = parsed;
|
|
LOG_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;
|
|
}
|
|
}
|
|
}
|
|
|
|
#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;
|
|
LOG_PRINTF("[USB] %s\n", mounted ? "mounted" : "unmounted");
|
|
}
|
|
if (ready != g_last_ready) {
|
|
g_last_ready = ready;
|
|
LOG_PRINTF("[SWITCH] driver %s\n", ready ? "ready (handshake OK)" : "not ready");
|
|
}
|
|
#ifdef SWITCH_PICO_AUTOTEST
|
|
if (ready && !g_ready_logged) {
|
|
g_ready_logged = true;
|
|
LOG_PRINTF("[AUTO] ready -> autopilot active=%s\n", g_autopilot_active ? "true" : "false");
|
|
}
|
|
#endif
|
|
}
|
|
|
|
int main() {
|
|
board_init();
|
|
stdio_init_all();
|
|
|
|
init_uart_input();
|
|
|
|
tusb_init();
|
|
switch_pro_init();
|
|
g_user_state = neutral_input();
|
|
switch_pro_set_input(g_user_state);
|
|
|
|
LOG_PRINTF("[BOOT] switch-pico starting (UART0 log @ 115200)\n");
|
|
LOG_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) {
|
|
tud_task(); // USB device tasks
|
|
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
|
|
log_usb_state();
|
|
}
|
|
}
|