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]]
name = "naxgcc-fw"
version = "1.1.1"
version = "1.0.0"
dependencies = [
"cortex-m",
"cortex-m-rt",

View file

@ -1,6 +1,6 @@
[package]
name = "naxgcc-fw"
version = "1.1.1"
version = "1.0.0"
edition = "2021"
# 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-assertions = true
incremental = false
opt-level = 3
opt-level = 1
lto = "fat"
overflow-checks = true
@ -71,7 +71,7 @@ incremental = false
lto = 'fat'
# opt level needs to be benchmarked after every major feature
# due to the changes in binary size and alignment
opt-level = 3
opt-level = 1
overflow-checks = false
# do not optimize proc-macro crates = faster builds from scratch

View file

@ -34,7 +34,7 @@ use embassy_sync::{
pubsub::Subscriber,
signal::Signal,
};
use embassy_time::Timer;
use embassy_time::{Duration, Ticker, Timer};
use crate::{gcc_hid::GcReport, input::CHANNEL_GCC_STATE};
@ -558,9 +558,6 @@ pub enum InputConsistencyMode {
/// 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.
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)]
@ -1057,6 +1054,8 @@ impl<'a> StickCalibrationProcess<'a> {
Timer::after_millis(100).await;
let mut ticker = Ticker::every(Duration::from_millis(20));
let notch_idx = NOTCH_ADJUSTMENT_ORDER
[self.calibration_step as usize - NO_OF_CALIBRATION_POINTS];
@ -1098,7 +1097,7 @@ impl<'a> StickCalibrationProcess<'a> {
None => self.adjust_notch(NotchAdjustmentType::None),
};
Timer::after_millis(1).await;
ticker.next().await;
yield_now().await;
}
};
@ -1690,8 +1689,7 @@ async fn configuration_main_loop<
// input consistency toggle
37 => {
final_config.input_consistency_mode = match final_config.input_consistency_mode {
InputConsistencyMode::Original => InputConsistencyMode::PC,
InputConsistencyMode::PC => InputConsistencyMode::ConsistencyHack,
InputConsistencyMode::Original => InputConsistencyMode::ConsistencyHack,
InputConsistencyMode::ConsistencyHack => InputConsistencyMode::SuperHack,
InputConsistencyMode::SuperHack => InputConsistencyMode::Original,
};
@ -1715,7 +1713,6 @@ async fn configuration_main_loop<
stick_y: (127_i8
+ match final_config.input_consistency_mode {
InputConsistencyMode::Original => -69,
InputConsistencyMode::PC => -42,
InputConsistencyMode::ConsistencyHack => 42,
InputConsistencyMode::SuperHack => 69,
}) as u8,

View file

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

View file

@ -17,7 +17,6 @@ use embassy_time::{Duration, Instant, Timer};
use embassy_usb::{
class::hid::{HidReaderWriter, ReportId, RequestHandler, State},
control::OutResponse,
msos::{self, windows_version},
Builder, Handler,
};
use libm::powf;
@ -37,9 +36,6 @@ pub static MUTEX_INPUT_CONSISTENCY_MODE: Mutex<
Option<InputConsistencyMode>,
> = Mutex::new(None);
/// Vendor-defined property data
const DEVICE_INTERFACE_GUID: &str = "{ecceff35-146c-4ff3-acd9-8f992d09acdd}";
#[rustfmt::skip]
pub const GCC_REPORT_DESCRIPTOR: &[u8] = &[
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::ConsistencyHack => "NaxGCC (Consistency Mode)",
InputConsistencyMode::SuperHack => "NaxGCC (SuperHack Mode)",
InputConsistencyMode::PC => "NaxGCC (PC Mode)",
});
usb_config.serial_number = Some(serial);
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,
);
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);
let hid_config = embassy_usb::class::hid::Config {
report_descriptor: GCC_REPORT_DESCRIPTOR,
request_handler: Some(&request_handler),
poll_ms: match input_consistency_mode {
InputConsistencyMode::Original => 8,
InputConsistencyMode::ConsistencyHack
| InputConsistencyMode::SuperHack
| InputConsistencyMode::PC => 1,
},
poll_ms: 1,
max_packet_size_in: 37,
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 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 {
// 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
// works to our advantage.
match input_consistency_mode {
InputConsistencyMode::SuperHack | InputConsistencyMode::ConsistencyHack => {
// "Ticker at home", so we can use this for both consistency and SuperHack mode
Timer::at(rate_limit_end_time).await;
InputConsistencyMode::SuperHack => {
// In SuperHack mode, we send reports only if the state changes, but
// 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
@ -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 micros = polltime.as_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;
}
Err(e) => warn!("Failed to send report: {:?}", e),

View file

@ -82,7 +82,6 @@ pub enum StickAxis {
YAxis,
}
#[inline(never)]
#[link_section = ".time_critical.read_ext_adc"]
pub fn read_ext_adc<
'a,
@ -126,7 +125,6 @@ pub fn read_ext_adc<
/// 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.
#[allow(clippy::too_many_arguments)]
#[inline(never)]
#[link_section = ".time_critical.update_stick_states"]
async fn update_stick_states(
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.
#[embassy_executor::task]
#[inline(never)]
#[link_section = ".time_critical.update_stick_states_task"]
pub async fn update_stick_states_task(
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)
}
#[inline(never)]
#[link_section = ".time_critical.linearize"]
pub fn linearize(point: f32, coefficients: &[f32; NUM_COEFFS]) -> f32 {
coefficients[0] * (point * point * point)
@ -891,7 +890,6 @@ pub fn linearize(point: f32, coefficients: &[f32; NUM_COEFFS]) -> f32 {
+ coefficients[3]
}
#[inline(never)]
#[link_section = ".time_critical.notch_remap"]
pub fn notch_remap(
x_in: f32,