Compare commits

..

1 commit

Author SHA1 Message Date
1d51c12bc1
feat(hid): implement lower-latency consistency algo 2024-04-19 14:20:14 +02:00
9 changed files with 35 additions and 59 deletions

View file

@ -1,7 +0,0 @@
This is mainly a QoL and performance update for the NaxGCC. The following changes have been made:
- Added a MSOS descriptor to the USB device, allowing the NaxGCC to be immediately recognized on Windows, without the need for a custom driver. This should make it plug-and-play for Dolphin on Windows.
- Improved the "rate limiting" of SuperHack mode, which should make it more consistent and less likely to drop inputs compared to "Consistency" mode.
- Added a new "PC" mode which polls at 1000Hz, for use on PC or other consoles that don't have any issues with 1000Hz polling. The "OG" mode now polls at 125Hz always, regardless of which device it is connected to.
To update your firmware, plug in your controller while keeping the `A+X+Y` buttons held. Then drag & drop the `.uf2` file (found below, under Downloads) onto the storage device that appears.

View file

@ -1 +0,0 @@
v1.1.0.md

2
Cargo.lock generated
View file

@ -875,7 +875,7 @@ checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149"
[[package]] [[package]]
name = "naxgcc-fw" name = "naxgcc-fw"
version = "1.1.1" version = "1.0.0"
dependencies = [ dependencies = [
"cortex-m", "cortex-m",
"cortex-m-rt", "cortex-m-rt",

View file

@ -1,6 +1,6 @@
[package] [package]
name = "naxgcc-fw" name = "naxgcc-fw"
version = "1.1.1" version = "1.0.0"
edition = "2021" edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -58,7 +58,7 @@ codegen-units = 1
debug = 2 debug = 2
debug-assertions = true debug-assertions = true
incremental = false incremental = false
opt-level = 3 opt-level = 1
lto = "fat" lto = "fat"
overflow-checks = true overflow-checks = true
@ -71,7 +71,7 @@ incremental = false
lto = 'fat' lto = 'fat'
# opt level needs to be benchmarked after every major feature # opt level needs to be benchmarked after every major feature
# due to the changes in binary size and alignment # due to the changes in binary size and alignment
opt-level = 3 opt-level = 1
overflow-checks = false overflow-checks = false
# do not optimize proc-macro crates = faster builds from scratch # do not optimize proc-macro crates = faster builds from scratch

View file

@ -34,7 +34,7 @@ use embassy_sync::{
pubsub::Subscriber, pubsub::Subscriber,
signal::Signal, signal::Signal,
}; };
use embassy_time::Timer; use embassy_time::{Duration, Ticker, Timer};
use crate::{gcc_hid::GcReport, input::CHANNEL_GCC_STATE}; use crate::{gcc_hid::GcReport, input::CHANNEL_GCC_STATE};
@ -558,9 +558,6 @@ pub enum InputConsistencyMode {
/// to something your opponent does. /// to something your opponent does.
/// The name is not meant to imply that this is a hack that is super, but rather that this is super hacky. /// The name is not meant to imply that this is a hack that is super, but rather that this is super hacky.
SuperHack = 2, SuperHack = 2,
/// Transmit inputs every 1 ms, for use on PC or other devices that are not garbage.
/// This is not recommended for use on the Switch!
PC = 3,
} }
#[derive(Debug, Clone, Format, PackedStruct)] #[derive(Debug, Clone, Format, PackedStruct)]
@ -1057,6 +1054,8 @@ impl<'a> StickCalibrationProcess<'a> {
Timer::after_millis(100).await; Timer::after_millis(100).await;
let mut ticker = Ticker::every(Duration::from_millis(20));
let notch_idx = NOTCH_ADJUSTMENT_ORDER let notch_idx = NOTCH_ADJUSTMENT_ORDER
[self.calibration_step as usize - NO_OF_CALIBRATION_POINTS]; [self.calibration_step as usize - NO_OF_CALIBRATION_POINTS];
@ -1098,7 +1097,7 @@ impl<'a> StickCalibrationProcess<'a> {
None => self.adjust_notch(NotchAdjustmentType::None), None => self.adjust_notch(NotchAdjustmentType::None),
}; };
Timer::after_millis(1).await; ticker.next().await;
yield_now().await; yield_now().await;
} }
}; };
@ -1690,8 +1689,7 @@ async fn configuration_main_loop<
// input consistency toggle // input consistency toggle
37 => { 37 => {
final_config.input_consistency_mode = match final_config.input_consistency_mode { final_config.input_consistency_mode = match final_config.input_consistency_mode {
InputConsistencyMode::Original => InputConsistencyMode::PC, InputConsistencyMode::Original => InputConsistencyMode::ConsistencyHack,
InputConsistencyMode::PC => InputConsistencyMode::ConsistencyHack,
InputConsistencyMode::ConsistencyHack => InputConsistencyMode::SuperHack, InputConsistencyMode::ConsistencyHack => InputConsistencyMode::SuperHack,
InputConsistencyMode::SuperHack => InputConsistencyMode::Original, InputConsistencyMode::SuperHack => InputConsistencyMode::Original,
}; };
@ -1715,7 +1713,6 @@ async fn configuration_main_loop<
stick_y: (127_i8 stick_y: (127_i8
+ match final_config.input_consistency_mode { + match final_config.input_consistency_mode {
InputConsistencyMode::Original => -69, InputConsistencyMode::Original => -69,
InputConsistencyMode::PC => -42,
InputConsistencyMode::ConsistencyHack => 42, InputConsistencyMode::ConsistencyHack => 42,
InputConsistencyMode::SuperHack => 69, InputConsistencyMode::SuperHack => 69,
}) as u8, }) as u8,

View file

@ -179,7 +179,7 @@ pub struct KalmanState {
} }
impl KalmanState { impl KalmanState {
#[inline(never)] // runs kalman filter
#[link_section = ".time_critical.run_kalman"] #[link_section = ".time_critical.run_kalman"]
pub fn run_kalman( pub fn run_kalman(
&mut self, &mut self,
@ -292,7 +292,6 @@ impl KalmanState {
/// output at the rim longer when released. /// output at the rim longer when released.
/// ///
/// Output is a tuple of the x and y positions. /// Output is a tuple of the x and y positions.
#[inline(never)]
#[link_section = ".time_critical.run_waveshaping"] #[link_section = ".time_critical.run_waveshaping"]
pub fn run_waveshaping( pub fn run_waveshaping(
x_pos: f32, x_pos: f32,

View file

@ -17,7 +17,6 @@ use embassy_time::{Duration, Instant, Timer};
use embassy_usb::{ use embassy_usb::{
class::hid::{HidReaderWriter, ReportId, RequestHandler, State}, class::hid::{HidReaderWriter, ReportId, RequestHandler, State},
control::OutResponse, control::OutResponse,
msos::{self, windows_version},
Builder, Handler, Builder, Handler,
}; };
use libm::powf; use libm::powf;
@ -37,9 +36,6 @@ pub static MUTEX_INPUT_CONSISTENCY_MODE: Mutex<
Option<InputConsistencyMode>, Option<InputConsistencyMode>,
> = Mutex::new(None); > = Mutex::new(None);
/// Vendor-defined property data
const DEVICE_INTERFACE_GUID: &str = "{ecceff35-146c-4ff3-acd9-8f992d09acdd}";
#[rustfmt::skip] #[rustfmt::skip]
pub const GCC_REPORT_DESCRIPTOR: &[u8] = &[ pub const GCC_REPORT_DESCRIPTOR: &[u8] = &[
0x05, 0x01, // Usage Page (Generic Desktop Ctrls) 0x05, 0x01, // Usage Page (Generic Desktop Ctrls)
@ -306,7 +302,6 @@ pub async fn usb_transfer_task(raw_serial: [u8; 8], driver: Driver<'static, USB>
InputConsistencyMode::Original => "NaxGCC (OG Mode)", InputConsistencyMode::Original => "NaxGCC (OG Mode)",
InputConsistencyMode::ConsistencyHack => "NaxGCC (Consistency Mode)", InputConsistencyMode::ConsistencyHack => "NaxGCC (Consistency Mode)",
InputConsistencyMode::SuperHack => "NaxGCC (SuperHack Mode)", InputConsistencyMode::SuperHack => "NaxGCC (SuperHack Mode)",
InputConsistencyMode::PC => "NaxGCC (PC Mode)",
}); });
usb_config.serial_number = Some(serial); usb_config.serial_number = Some(serial);
usb_config.max_power = 200; usb_config.max_power = 200;
@ -338,26 +333,12 @@ pub async fn usb_transfer_task(raw_serial: [u8; 8], driver: Driver<'static, USB>
&mut control_buf, &mut control_buf,
); );
builder.msos_descriptor(windows_version::WIN8_1, 2);
let msos_writer = builder.msos_writer();
msos_writer.device_feature(msos::CompatibleIdFeatureDescriptor::new("WINUSB", ""));
msos_writer.device_feature(msos::RegistryPropertyFeatureDescriptor::new(
"DeviceInterfaceGUID",
msos::PropertyData::Sz(DEVICE_INTERFACE_GUID),
));
builder.handler(&mut device_handler); builder.handler(&mut device_handler);
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: match input_consistency_mode { poll_ms: 1,
InputConsistencyMode::Original => 8,
InputConsistencyMode::ConsistencyHack
| InputConsistencyMode::SuperHack
| InputConsistencyMode::PC => 1,
},
max_packet_size_in: 37, max_packet_size_in: 37,
max_packet_size_out: 5, max_packet_size_out: 5,
}; };
@ -380,7 +361,8 @@ pub async fn usb_transfer_task(raw_serial: [u8; 8], driver: Driver<'static, USB>
let mut gcc_subscriber = CHANNEL_GCC_STATE.subscriber().unwrap(); let mut gcc_subscriber = CHANNEL_GCC_STATE.subscriber().unwrap();
let mut last_report_time = Instant::now(); let mut last_report_time = Instant::now();
let mut rate_limit_end_time = Instant::now(); let mut frame_ready_time = Instant::now() + Duration::from_micros(8333);
let mut usb_ready_time = Instant::now() + Duration::from_millis(8);
loop { loop {
// This is what we like to call a "hack". // This is what we like to call a "hack".
@ -393,11 +375,29 @@ pub async fn usb_transfer_task(raw_serial: [u8; 8], driver: Driver<'static, USB>
// a minimum of 333 extra us to send a report every time it's polled, but it // a minimum of 333 extra us to send a report every time it's polled, but it
// works to our advantage. // works to our advantage.
match input_consistency_mode { match input_consistency_mode {
InputConsistencyMode::SuperHack | InputConsistencyMode::ConsistencyHack => { InputConsistencyMode::SuperHack => {
// "Ticker at home", so we can use this for both consistency and SuperHack mode // In SuperHack mode, we send reports only if the state changes, but
Timer::at(rate_limit_end_time).await; // in order to not mess up very fast inputs (like sticks travelling, for example),
// we still need to "rate limit" the reports to every 8.33ms at most.
// This does rate limit it to ~8.33ms fairly well, my only
// gripe with it is that I hate it :)
Timer::at(last_report_time + Duration::from_micros(8100)).await;
} }
InputConsistencyMode::Original | InputConsistencyMode::PC => {} InputConsistencyMode::ConsistencyHack => {
// Ensure we report in multiples of 1 (not in decimals, like 8.33ms)
Timer::at(usb_ready_time).await;
while Instant::now() < frame_ready_time {
Timer::after_micros(1000).await;
}
// has to be done this way in case we're behind through e.g. suspends
while frame_ready_time < Instant::now() {
frame_ready_time += Duration::from_micros(8333);
}
while usb_ready_time < Instant::now() {
usb_ready_time += Duration::from_millis(8);
}
}
InputConsistencyMode::Original => {}
} }
match writer match writer
@ -414,13 +414,6 @@ pub async fn usb_transfer_task(raw_serial: [u8; 8], driver: Driver<'static, USB>
let polltime = currtime.duration_since(last_report_time); let polltime = currtime.duration_since(last_report_time);
let micros = polltime.as_micros(); let micros = polltime.as_micros();
debug!("Report written in {}us", micros); debug!("Report written in {}us", micros);
if input_consistency_mode != InputConsistencyMode::Original
&& input_consistency_mode != InputConsistencyMode::PC
{
while rate_limit_end_time < currtime {
rate_limit_end_time += Duration::from_micros(8333);
}
}
last_report_time = currtime; last_report_time = currtime;
} }
Err(e) => warn!("Failed to send report: {:?}", e), Err(e) => warn!("Failed to send report: {:?}", e),

View file

@ -82,7 +82,6 @@ pub enum StickAxis {
YAxis, YAxis,
} }
#[inline(never)]
#[link_section = ".time_critical.read_ext_adc"] #[link_section = ".time_critical.read_ext_adc"]
pub fn read_ext_adc< pub fn read_ext_adc<
'a, 'a,
@ -126,7 +125,6 @@ pub fn read_ext_adc<
/// Gets the average stick state over a 1ms interval in a non-blocking fashion. /// Gets the average stick state over a 1ms interval in a non-blocking fashion.
/// Will wait until end_time is reached before continuing after reading the ADCs. /// Will wait until end_time is reached before continuing after reading the ADCs.
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
#[inline(never)]
#[link_section = ".time_critical.update_stick_states"] #[link_section = ".time_critical.update_stick_states"]
async fn update_stick_states( async fn update_stick_states(
current_stick_state: &StickState, current_stick_state: &StickState,
@ -542,7 +540,6 @@ pub async fn update_button_state_task(
/// ///
/// Has to run on core0 because it makes use of SPI0. /// Has to run on core0 because it makes use of SPI0.
#[embassy_executor::task] #[embassy_executor::task]
#[inline(never)]
#[link_section = ".time_critical.update_stick_states_task"] #[link_section = ".time_critical.update_stick_states_task"]
pub async fn update_stick_states_task( pub async fn update_stick_states_task(
spi: Spi<'static, SPI0, embassy_rp::spi::Blocking>, spi: Spi<'static, SPI0, embassy_rp::spi::Blocking>,

View file

@ -882,7 +882,6 @@ pub fn calc_stick_values(angle: f32) -> (f32, f32) {
(x, y) (x, y)
} }
#[inline(never)]
#[link_section = ".time_critical.linearize"] #[link_section = ".time_critical.linearize"]
pub fn linearize(point: f32, coefficients: &[f32; NUM_COEFFS]) -> f32 { pub fn linearize(point: f32, coefficients: &[f32; NUM_COEFFS]) -> f32 {
coefficients[0] * (point * point * point) coefficients[0] * (point * point * point)
@ -891,7 +890,6 @@ pub fn linearize(point: f32, coefficients: &[f32; NUM_COEFFS]) -> f32 {
+ coefficients[3] + coefficients[3]
} }
#[inline(never)]
#[link_section = ".time_critical.notch_remap"] #[link_section = ".time_critical.notch_remap"]
pub fn notch_remap( pub fn notch_remap(
x_in: f32, x_in: f32,