forked from NaxdyOrg/NaxGCC-FW
feat(input): implement input consistency mode
This commit is contained in:
parent
1acb78ec79
commit
35c0f755f2
4 changed files with 81 additions and 18 deletions
|
@ -225,8 +225,12 @@ impl Default for StickConfig {
|
||||||
pub struct ControllerConfig {
|
pub struct ControllerConfig {
|
||||||
#[packed_field(size_bits = "8")]
|
#[packed_field(size_bits = "8")]
|
||||||
pub config_revision: u8,
|
pub config_revision: u8,
|
||||||
|
/// Toggle for input consistency mode. If true, the controller
|
||||||
|
/// will trick the Switch into updating the state every 8.33ms
|
||||||
|
/// instead of every 8ms. The tradeoff is a slight increase in
|
||||||
|
/// input lag.
|
||||||
#[packed_field(size_bits = "8")]
|
#[packed_field(size_bits = "8")]
|
||||||
pub config_version: u8,
|
pub input_consistency_mode: bool,
|
||||||
#[packed_field(size_bytes = "328")]
|
#[packed_field(size_bytes = "328")]
|
||||||
pub astick_config: StickConfig,
|
pub astick_config: StickConfig,
|
||||||
#[packed_field(size_bytes = "328")]
|
#[packed_field(size_bytes = "328")]
|
||||||
|
@ -237,7 +241,7 @@ impl Default for ControllerConfig {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
config_revision: CONTROLLER_CONFIG_REVISION,
|
config_revision: CONTROLLER_CONFIG_REVISION,
|
||||||
config_version: 0,
|
input_consistency_mode: true,
|
||||||
astick_config: StickConfig::default(),
|
astick_config: StickConfig::default(),
|
||||||
cstick_config: StickConfig::default(),
|
cstick_config: StickConfig::default(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,7 @@ use defmt::{debug, info, trace, warn, Format};
|
||||||
use embassy_futures::join::join;
|
use embassy_futures::join::join;
|
||||||
use embassy_rp::{peripherals::USB, usb::Driver};
|
use embassy_rp::{peripherals::USB, usb::Driver};
|
||||||
|
|
||||||
use embassy_time::Instant;
|
use embassy_time::{Duration, Instant, Ticker};
|
||||||
use embassy_usb::{
|
use embassy_usb::{
|
||||||
class::hid::{HidReaderWriter, ReportId, RequestHandler, State},
|
class::hid::{HidReaderWriter, ReportId, RequestHandler, State},
|
||||||
control::OutResponse,
|
control::OutResponse,
|
||||||
|
@ -111,7 +111,7 @@ pub struct Buttons2 {
|
||||||
pub blank1: u8,
|
pub blank1: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Default, PackedStruct, Format)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PackedStruct, Format)]
|
||||||
#[packed_struct(bit_numbering = "msb0", size_bytes = "8")]
|
#[packed_struct(bit_numbering = "msb0", size_bytes = "8")]
|
||||||
pub struct GcReport {
|
pub struct GcReport {
|
||||||
#[packed_field(bits = "0..=7")]
|
#[packed_field(bits = "0..=7")]
|
||||||
|
@ -132,6 +132,21 @@ pub struct GcReport {
|
||||||
pub trigger_r: u8,
|
pub trigger_r: u8,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for GcReport {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
buttons_1: Buttons1::default(),
|
||||||
|
buttons_2: Buttons2::default(),
|
||||||
|
stick_x: 127,
|
||||||
|
stick_y: 127,
|
||||||
|
cstick_x: 127,
|
||||||
|
cstick_y: 127,
|
||||||
|
trigger_l: 0,
|
||||||
|
trigger_r: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||||
#[repr(C, align(8))]
|
#[repr(C, align(8))]
|
||||||
pub struct RawConsoleReport {
|
pub struct RawConsoleReport {
|
||||||
|
@ -234,7 +249,11 @@ impl Handler for MyDeviceHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[embassy_executor::task]
|
#[embassy_executor::task]
|
||||||
pub async fn usb_transfer_task(driver: Driver<'static, USB>, raw_serial: [u8; 8]) {
|
pub async fn usb_transfer_task(
|
||||||
|
driver: Driver<'static, USB>,
|
||||||
|
raw_serial: [u8; 8],
|
||||||
|
input_consistency_mode: bool,
|
||||||
|
) {
|
||||||
let mut serial_buffer = [0u8; 64];
|
let mut serial_buffer = [0u8; 64];
|
||||||
|
|
||||||
let serial = format_no_std::show(
|
let serial = format_no_std::show(
|
||||||
|
@ -294,7 +313,7 @@ pub async fn usb_transfer_task(driver: Driver<'static, USB>, raw_serial: [u8; 8]
|
||||||
let hid_config = embassy_usb::class::hid::Config {
|
let hid_config = embassy_usb::class::hid::Config {
|
||||||
report_descriptor: GCC_REPORT_DESCRIPTOR,
|
report_descriptor: GCC_REPORT_DESCRIPTOR,
|
||||||
request_handler: Some(&request_handler),
|
request_handler: Some(&request_handler),
|
||||||
poll_ms: 8,
|
poll_ms: if input_consistency_mode { 4 } else { 8 },
|
||||||
max_packet_size_in: 37,
|
max_packet_size_in: 37,
|
||||||
max_packet_size_out: 5,
|
max_packet_size_out: 5,
|
||||||
};
|
};
|
||||||
|
@ -318,7 +337,22 @@ pub async fn usb_transfer_task(driver: Driver<'static, USB>, raw_serial: [u8; 8]
|
||||||
let in_fut = async {
|
let in_fut = async {
|
||||||
let mut gcc_subscriber = CHANNEL_GCC_STATE.subscriber().unwrap();
|
let mut gcc_subscriber = CHANNEL_GCC_STATE.subscriber().unwrap();
|
||||||
|
|
||||||
|
let mut ticker = Ticker::every(Duration::from_micros(8333));
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
|
if input_consistency_mode {
|
||||||
|
// This is what we like to call a "hack".
|
||||||
|
// It forces reports to be sent every 8.33ms instead of every 8ms.
|
||||||
|
// 8.33ms is a multiple of the game's frame interval (16.66ms), so if we
|
||||||
|
// send a report every 8.33ms, it should (in theory) ensure (close to)
|
||||||
|
// 100% input accuracy.
|
||||||
|
//
|
||||||
|
// From the console's perspective, we are basically a laggy adapter, taking
|
||||||
|
// a minimum of 333 extra us to send a report every time it's polled, but it
|
||||||
|
// works to our advantage.
|
||||||
|
ticker.next().await;
|
||||||
|
}
|
||||||
|
|
||||||
let state = gcc_subscriber.next_message_pure().await;
|
let state = gcc_subscriber.next_message_pure().await;
|
||||||
let report = get_gcinput_hid_report(&state);
|
let report = get_gcinput_hid_report(&state);
|
||||||
match writer.write(&report).await {
|
match writer.write(&report).await {
|
||||||
|
@ -326,7 +360,7 @@ pub async fn usb_transfer_task(driver: Driver<'static, USB>, raw_serial: [u8; 8]
|
||||||
trace!("Report Written: {:08b}", report);
|
trace!("Report Written: {:08b}", report);
|
||||||
let currtime = Instant::now();
|
let currtime = Instant::now();
|
||||||
let polltime = currtime.duration_since(lasttime);
|
let polltime = currtime.duration_since(lasttime);
|
||||||
trace!("Report written in {}us", polltime.as_micros());
|
debug!("Report written in {}us", polltime.as_micros());
|
||||||
lasttime = currtime;
|
lasttime = currtime;
|
||||||
}
|
}
|
||||||
Err(e) => warn!("Failed to send report: {:?}", e),
|
Err(e) => warn!("Failed to send report: {:?}", e),
|
||||||
|
|
36
src/input.rs
36
src/input.rs
|
@ -21,8 +21,8 @@ use libm::{fmaxf, fminf};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
config::{
|
config::{
|
||||||
ControllerConfig, OverrideStickState, SIGNAL_IS_CALIBRATING, SIGNAL_OVERRIDE_GCC_STATE,
|
ControllerConfig, OverrideGcReportInstruction, OverrideStickState, SIGNAL_IS_CALIBRATING,
|
||||||
SIGNAL_OVERRIDE_STICK_STATE,
|
SIGNAL_OVERRIDE_GCC_STATE, SIGNAL_OVERRIDE_STICK_STATE,
|
||||||
},
|
},
|
||||||
filter::{run_waveshaping, FilterGains, KalmanState, WaveshapingValues, FILTER_GAINS},
|
filter::{run_waveshaping, FilterGains, KalmanState, WaveshapingValues, FILTER_GAINS},
|
||||||
gcc_hid::GcReport,
|
gcc_hid::GcReport,
|
||||||
|
@ -379,6 +379,24 @@ fn update_button_states<
|
||||||
gcc_state.buttons_1.dpad_down = btn_ddown.is_low();
|
gcc_state.buttons_1.dpad_down = btn_ddown.is_low();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[embassy_executor::task]
|
||||||
|
pub async fn input_integrity_benchmark() {
|
||||||
|
loop {
|
||||||
|
SIGNAL_OVERRIDE_GCC_STATE.signal(OverrideGcReportInstruction {
|
||||||
|
report: {
|
||||||
|
let mut report = GcReport::default();
|
||||||
|
report.buttons_1.dpad_up = true;
|
||||||
|
report.cstick_x = 0;
|
||||||
|
report.cstick_y = 0;
|
||||||
|
report
|
||||||
|
},
|
||||||
|
duration_ms: 100,
|
||||||
|
});
|
||||||
|
|
||||||
|
Timer::after_millis(200).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Task responsible for updating the button states.
|
/// Task responsible for updating the button states.
|
||||||
/// Publishes the result to CHANNEL_GCC_STATE.
|
/// Publishes the result to CHANNEL_GCC_STATE.
|
||||||
#[embassy_executor::task]
|
#[embassy_executor::task]
|
||||||
|
@ -405,12 +423,6 @@ pub async fn update_button_state_task(
|
||||||
|
|
||||||
let mut gcc_state = GcReport::default();
|
let mut gcc_state = GcReport::default();
|
||||||
|
|
||||||
// Set the stick states to the center
|
|
||||||
gcc_state.stick_x = 127;
|
|
||||||
gcc_state.stick_y = 127;
|
|
||||||
gcc_state.cstick_x = 127;
|
|
||||||
gcc_state.cstick_y = 127;
|
|
||||||
|
|
||||||
let gcc_publisher = CHANNEL_GCC_STATE.publisher().unwrap();
|
let gcc_publisher = CHANNEL_GCC_STATE.publisher().unwrap();
|
||||||
|
|
||||||
let mut override_stick_state: Option<OverrideStickState> = None;
|
let mut override_stick_state: Option<OverrideStickState> = None;
|
||||||
|
@ -447,8 +459,12 @@ pub async fn update_button_state_task(
|
||||||
|
|
||||||
// check for a gcc state override (usually coming from the config task)
|
// check for a gcc state override (usually coming from the config task)
|
||||||
if let Some(override_gcc_state) = SIGNAL_OVERRIDE_GCC_STATE.try_take() {
|
if let Some(override_gcc_state) = SIGNAL_OVERRIDE_GCC_STATE.try_take() {
|
||||||
gcc_publisher.publish_immediate(override_gcc_state.report);
|
trace!("Overridden gcc state: {:?}", override_gcc_state.report);
|
||||||
Timer::after_millis(override_gcc_state.duration_ms).await;
|
let end_time = Instant::now() + Duration::from_millis(override_gcc_state.duration_ms);
|
||||||
|
while Instant::now() < end_time {
|
||||||
|
gcc_publisher.publish_immediate(override_gcc_state.report);
|
||||||
|
yield_now().await;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(override_state) = &override_stick_state {
|
if let Some(override_state) = &override_stick_state {
|
||||||
|
|
11
src/main.rs
11
src/main.rs
|
@ -35,6 +35,8 @@ use gpio::{Level, Output};
|
||||||
use input::{update_button_state_task, update_stick_states_task};
|
use input::{update_button_state_task, update_stick_states_task};
|
||||||
use static_cell::StaticCell;
|
use static_cell::StaticCell;
|
||||||
|
|
||||||
|
use crate::input::input_integrity_benchmark;
|
||||||
|
|
||||||
use {defmt_rtt as _, panic_probe as _};
|
use {defmt_rtt as _, panic_probe as _};
|
||||||
|
|
||||||
static mut CORE1_STACK: Stack<4096> = Stack::new();
|
static mut CORE1_STACK: Stack<4096> = Stack::new();
|
||||||
|
@ -86,7 +88,14 @@ fn main() -> ! {
|
||||||
let executor1 = EXECUTOR1.init(Executor::new());
|
let executor1 = EXECUTOR1.init(Executor::new());
|
||||||
debug!("Mana");
|
debug!("Mana");
|
||||||
executor1.run(|spawner| {
|
executor1.run(|spawner| {
|
||||||
spawner.spawn(usb_transfer_task(driver, uid)).unwrap();
|
spawner
|
||||||
|
.spawn(usb_transfer_task(
|
||||||
|
driver,
|
||||||
|
uid,
|
||||||
|
controller_config.input_consistency_mode,
|
||||||
|
))
|
||||||
|
.unwrap();
|
||||||
|
// spawner.spawn(input_integrity_benchmark()).unwrap();
|
||||||
spawner
|
spawner
|
||||||
.spawn(update_button_state_task(
|
.spawn(update_button_state_task(
|
||||||
Input::new(AnyPin::from(p.PIN_20), gpio::Pull::Up),
|
Input::new(AnyPin::from(p.PIN_20), gpio::Pull::Up),
|
||||||
|
|
Loading…
Reference in a new issue