gweaks, nothing works yet

This commit is contained in:
Joey Yakimowich-Payne 2025-11-24 18:57:56 -07:00
commit 16cac70dba
No known key found for this signature in database
GPG key ID: 6BFE655FA5ABD1E1
3 changed files with 77 additions and 33 deletions

View file

@ -58,8 +58,11 @@ RUMBLE_SCALE = 1.0
CONTROLLER_DB_URL_DEFAULT = "https://raw.githubusercontent.com/mdqinc/SDL_GameControllerDB/refs/heads/master/gamecontrollerdb.txt" CONTROLLER_DB_URL_DEFAULT = "https://raw.githubusercontent.com/mdqinc/SDL_GameControllerDB/refs/heads/master/gamecontrollerdb.txt"
SDL_TRUE = getattr(sdl2, "SDL_TRUE", 1) SDL_TRUE = getattr(sdl2, "SDL_TRUE", 1)
SDL_CONTROLLERSENSORUPDATE = getattr(sdl2, "SDL_CONTROLLERSENSORUPDATE", 0x658)
GYRO_BIAS_SAMPLES = 200 GYRO_BIAS_SAMPLES = 200
GYRO_DEADZONE_COUNTS = 15 GYRO_DEADZONE_COUNTS = 0
# Keep a small window of IMU samples to pack into the next report
IMU_BUFFER_SIZE = 32
def parse_mapping(value: str) -> Tuple[int, str]: def parse_mapping(value: str) -> Tuple[int, str]:
@ -246,7 +249,7 @@ class ControllerContext:
sensors_supported: bool = False sensors_supported: bool = False
sensors_enabled: bool = False sensors_enabled: bool = False
imu_samples: List[IMUSample] = field(default_factory=list) imu_samples: List[IMUSample] = field(default_factory=list)
last_sensor_poll: float = 0.0 last_accel: Tuple[float, float, float] = (0.0, 0.0, 0.0)
gyro_bias_x: float = 0.0 gyro_bias_x: float = 0.0
gyro_bias_y: float = 0.0 gyro_bias_y: float = 0.0
gyro_bias_z: float = 0.0 gyro_bias_z: float = 0.0
@ -602,8 +605,8 @@ def build_arg_parser() -> argparse.ArgumentParser:
parser.add_argument( parser.add_argument(
"--frequency", "--frequency",
type=float, type=float,
default=1000.0, default=66.7,
help="Report send frequency per controller (Hz, default 1000)", help="Report send frequency per controller (Hz, default ~66.7 => ~15ms)",
) )
parser.add_argument( parser.add_argument(
"--deadzone", "--deadzone",
@ -710,6 +713,17 @@ def build_arg_parser() -> argparse.ArgumentParser:
action="store_true", action="store_true",
help="Print raw IMU readings (float and converted int16) for debugging.", help="Print raw IMU readings (float and converted int16) for debugging.",
) )
parser.add_argument(
"--gyro-scale",
type=float,
default=1.0,
help="Scale factor for gyro sensitivity (default 1.0). Reduce to < 1.0 if camera moves too fast.",
)
parser.add_argument(
"--no-gyro-bias",
action="store_true",
help="Disable automatic gyro bias calibration at startup.",
)
return parser return parser
@ -756,6 +770,8 @@ class BridgeConfig:
swap_abxy_ids: set[str] swap_abxy_ids: set[str]
swap_abxy_global: bool swap_abxy_global: bool
debug_imu: bool debug_imu: bool
gyro_scale: float
no_gyro_bias: bool
@dataclass @dataclass
@ -792,10 +808,11 @@ def convert_accel_to_raw(accel_ms2: float) -> int:
return clamp_int16(g_units * 4000.0) return clamp_int16(g_units * 4000.0)
def convert_gyro_to_raw(gyro_rad: float) -> int: def convert_gyro_to_raw(gyro_rad: float, scale: float = 1.0) -> int:
# SDL reports gyroscope data in radians/second; convert to dps then to Switch counts. # SDL reports gyroscope data in radians/second; convert to dps then to Switch counts.
dps = gyro_rad * RAD_TO_DEG dps = gyro_rad * RAD_TO_DEG
counts = clamp_int16(dps / 0.070) # 0.070 dps/LSB is approx 14.28 LSB/dps.
counts = clamp_int16((dps / 0.061) * scale)
if abs(counts) < GYRO_DEADZONE_COUNTS: if abs(counts) < GYRO_DEADZONE_COUNTS:
return 0 return 0
return counts return counts
@ -841,13 +858,24 @@ def initialize_controller_sensors(ctx: ControllerContext, console: Console) -> N
) )
def collect_imu_sample(ctx: ControllerContext, config: BridgeConfig) -> None: def handle_sensor_update(event: sdl2.SDL_Event, contexts: Dict[int, ControllerContext], config: BridgeConfig) -> None:
if not ctx.sensors_enabled or SENSOR_ACCEL is None or SENSOR_GYRO is None: ctx = contexts.get(event.csensor.which)
if not ctx:
return return
accel = read_sensor_triplet(ctx.controller, SENSOR_ACCEL)
gyro = read_sensor_triplet(ctx.controller, SENSOR_GYRO) sensor_type = event.csensor.sensor
if not accel or not gyro: data = event.csensor.data
if sensor_type == SENSOR_ACCEL:
ctx.last_accel = (data[0], data[1], data[2])
return return
if sensor_type != SENSOR_GYRO:
return
# Process Gyro update (and combine with last accel)
gyro = (data[0], data[1], data[2])
if not ctx.gyro_bias_locked and ctx.gyro_bias_samples < GYRO_BIAS_SAMPLES: if not ctx.gyro_bias_locked and ctx.gyro_bias_samples < GYRO_BIAS_SAMPLES:
ctx.gyro_bias_x += gyro[0] ctx.gyro_bias_x += gyro[0]
ctx.gyro_bias_y += gyro[1] ctx.gyro_bias_y += gyro[1]
@ -862,20 +890,17 @@ def collect_imu_sample(ctx: ControllerContext, config: BridgeConfig) -> None:
bias_x = ctx.gyro_bias_x if ctx.gyro_bias_locked else 0.0 bias_x = ctx.gyro_bias_x if ctx.gyro_bias_locked else 0.0
bias_y = ctx.gyro_bias_y if ctx.gyro_bias_locked else 0.0 bias_y = ctx.gyro_bias_y if ctx.gyro_bias_locked else 0.0
bias_z = ctx.gyro_bias_z if ctx.gyro_bias_locked else 0.0 bias_z = ctx.gyro_bias_z if ctx.gyro_bias_locked else 0.0
gyro_bias_corrected = (
gyro[0] - bias_x, # Use last known accel
gyro[1] - bias_y, accel = ctx.last_accel
gyro[2] - bias_z,
)
# Map SDL sensor axes to Pro Controller axes: gravity should land on Z. # Map SDL sensor axes to Pro Controller axes: gravity should land on Z.
# print(accel[2] / MS2_PER_G)
accel_raw_x = convert_accel_to_raw(accel[0]) accel_raw_x = convert_accel_to_raw(accel[0])
accel_raw_y = convert_accel_to_raw(accel[1]) accel_raw_y = convert_accel_to_raw(accel[1])
accel_raw_z = convert_accel_to_raw(accel[2]) accel_raw_z = convert_accel_to_raw(accel[2])
gyro_raw_x = convert_gyro_to_raw(gyro[0]) gyro_raw_x = convert_gyro_to_raw(gyro[0] - bias_x, config.gyro_scale)
gyro_raw_y = convert_gyro_to_raw(gyro[1]) gyro_raw_y = convert_gyro_to_raw(gyro[1] - bias_y, config.gyro_scale)
gyro_raw_z = convert_gyro_to_raw(gyro[2]) gyro_raw_z = convert_gyro_to_raw(gyro[2] - bias_z, config.gyro_scale)
# Map SDL axes to Pro axes to match the native Pro USB output: # Map SDL axes to Pro axes to match the native Pro USB output:
# Pro accel: ax = SDL_, ay = SDL_Z, az = SDL_Y (gravity). # Pro accel: ax = SDL_, ay = SDL_Z, az = SDL_Y (gravity).
@ -884,13 +909,13 @@ def collect_imu_sample(ctx: ControllerContext, config: BridgeConfig) -> None:
accel_x=-accel_raw_z, accel_x=-accel_raw_z,
accel_y=-accel_raw_x, accel_y=-accel_raw_x,
accel_z=accel_raw_y, accel_z=accel_raw_y,
gyro_x=-gyro_raw_z, gyro_x=convert_gyro_to_raw(-(gyro[2] - bias_z), config.gyro_scale),
gyro_y=-gyro_raw_x, gyro_y=convert_gyro_to_raw(-(gyro[0] - bias_x), config.gyro_scale),
gyro_z=gyro_raw_y, gyro_z=convert_gyro_to_raw(gyro[1] - bias_y, config.gyro_scale),
) )
ctx.imu_samples.append(sample) ctx.imu_samples.append(sample)
if len(ctx.imu_samples) > 6: if len(ctx.imu_samples) > IMU_BUFFER_SIZE:
ctx.imu_samples = ctx.imu_samples[-6:] ctx.imu_samples = ctx.imu_samples[-IMU_BUFFER_SIZE:]
if config.debug_imu: if config.debug_imu:
now = time.monotonic() now = time.monotonic()
@ -936,7 +961,7 @@ def load_button_maps(
console.print( console.print(
f"[red]Failed to load SDL mapping {mapping_path}: {exc}[/red]" f"[red]Failed to load SDL mapping {mapping_path}: {exc}[/red]"
) )
return button_map_default, button_map_swapped, swap_abxy_indices geturn button_map_default, button_map_swapped, swap_abxy_indices
def build_bridge_config(console: Console, args: argparse.Namespace) -> BridgeConfig: def build_bridge_config(console: Console, args: argparse.Namespace) -> BridgeConfig:
@ -961,6 +986,8 @@ def build_bridge_config(console: Console, args: argparse.Namespace) -> BridgeCon
swap_abxy_ids=set(swap_abxy_guids), # filled later once stable IDs are known swap_abxy_ids=set(swap_abxy_guids), # filled later once stable IDs are known
swap_abxy_global=bool(args.swap_abxy), swap_abxy_global=bool(args.swap_abxy),
debug_imu=bool(args.debug_imu), debug_imu=bool(args.debug_imu),
gyro_scale=args.gyro_scale,
no_gyro_bias=args.no_gyro_bias,
) )
@ -1466,7 +1493,7 @@ def service_contexts(
else config.button_map_default else config.button_map_default
) )
poll_controller_buttons(ctx, current_button_map) poll_controller_buttons(ctx, current_button_map)
collect_imu_sample(ctx, config) # collect_imu_sample(ctx, config) <-- Removed, using event loop
# Reconnect UART if needed. # Reconnect UART if needed.
if ctx.port and ctx.uart is None and (now - ctx.last_reopen_attempt) > 1.0: if ctx.port and ctx.uart is None and (now - ctx.last_reopen_attempt) > 1.0:
ctx.last_reopen_attempt = now ctx.last_reopen_attempt = now
@ -1481,8 +1508,11 @@ def service_contexts(
continue continue
try: try:
if now - ctx.last_send >= config.interval: if now - ctx.last_send >= config.interval:
if ctx.imu_samples: # Consume up to 3 samples from the head of the queue (FIFO)
ctx.report.imu_samples = ctx.imu_samples[-IMU_SAMPLES_PER_REPORT:] count = min(len(ctx.imu_samples), IMU_SAMPLES_PER_REPORT)
if count > 0:
ctx.report.imu_samples = ctx.imu_samples[:count]
ctx.imu_samples = ctx.imu_samples[count:]
else: else:
ctx.report.imu_samples = [] ctx.report.imu_samples = []
ctx.uart.send_report(ctx.report) ctx.uart.send_report(ctx.report)
@ -1556,12 +1586,16 @@ def run_bridge_loop(
sdl2.SDL_CONTROLLERBUTTONUP, sdl2.SDL_CONTROLLERBUTTONUP,
): ):
handle_button_event(event, config, contexts) handle_button_event(event, config, contexts)
elif event.type in (sdl2.SDL_CONTROLLERBUTTONDOWN, sdl2.SDL_CONTROLLERBUTTONUP):
handle_button_event(event, args, config, contexts)
elif event.type == SDL_CONTROLLERSENSORUPDATE:
handle_sensor_update(event, contexts, config)
elif event.type == sdl2.SDL_CONTROLLERDEVICEADDED: elif event.type == sdl2.SDL_CONTROLLERDEVICEADDED:
handle_device_added( handle_device_added(
event, args, pairing, contexts, uarts, console, config event, args, pairing, contexts, uarts, console, config
) )
elif event.type == sdl2.SDL_CONTROLLERDEVICEREMOVED: elif event.type == sdl2.SDL_CONTROLLERDEVICEREMOVED:
handle_device_removed(event, pairing, contexts, console) gandle_device_removed(event, pairing, contexts, console)
now = time.monotonic() now = time.monotonic()
if now - last_port_scan > port_scan_interval: if now - last_port_scan > port_scan_interval:

View file

@ -1,4 +1,4 @@
#include <stdio.h> ginclude <stdio.h>
#include <string.h> #include <string.h>
#include "bsp/board.h" #include "bsp/board.h"
#include "hardware/uart.h" #include "hardware/uart.h"
@ -61,12 +61,13 @@ static void on_rumble_from_switch(const uint8_t rumble[8]) {
} }
// Consume UART bytes and forward complete frames to the Switch Pro driver. // Consume UART bytes and forward complete frames to the Switch Pro driver.
static void poll_uart_frames() { static bool poll_uart_frames() {
static uint8_t buffer[64]; static uint8_t buffer[64];
static uint8_t index = 0; static uint8_t index = 0;
static uint8_t expected_len = 0; static uint8_t expected_len = 0;
static absolute_time_t last_byte_time = {0}; static absolute_time_t last_byte_time = {0};
static bool has_last_byte = false; static bool has_last_byte = false;
bool new_data = false;
while (uart_is_readable(UART_ID)) { while (uart_is_readable(UART_ID)) {
uint8_t byte = uart_getc(UART_ID); uint8_t byte = uart_getc(UART_ID);
@ -123,8 +124,10 @@ static void poll_uart_frames() {
} }
index = 0; index = 0;
expected_len = 0; expected_len = 0;
new_data = true;
} }
} }
return new_data;
} }
static void log_usb_state() { static void log_usb_state() {
@ -159,9 +162,13 @@ int main() {
while (true) { while (true) {
tud_task(); // USB device tasks tud_task(); // USB device tasks
poll_uart_frames(); // Pull controller state from UART1 bool new_data = poll_uart_frames(); // Pull controller state from UART1
SwitchInputState state = g_user_state; SwitchInputState state = g_user_state;
switch_pro_set_input(state); switch_pro_set_input(state);
bool should_update = new_data;
if (should_update) {
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(); log_usb_state();
} }

View file

@ -632,6 +632,9 @@ void switch_pro_task() {
if (tud_hid_ready() && send_report(0, inputReport, report_size) == true ) { if (tud_hid_ready() && send_report(0, inputReport, report_size) == true ) {
memcpy(last_report, inputReport, report_size); memcpy(last_report, inputReport, report_size);
report_sent = true; report_sent = true;
// Clear IMU samples so they aren't repeated in the next report
// if no new data arrives.
g_input_state.imu_sample_count = 0;
} }
last_report_timer = now; last_report_timer = now;