forked from NaxdyOrg/NaxGCC-FW
Compare commits
2 commits
main
...
work/super
Author | SHA1 | Date | |
---|---|---|---|
7f675c895d | |||
64ec44c933 |
9 changed files with 130 additions and 59 deletions
|
@ -13,6 +13,10 @@ jobs:
|
|||
echo "extra-trusted-public-keys = attic:05LdE8Nav5Qd1E+KOJqSwdr+WE1z8AUmSb3oKL7s8dk=" >> /etc/nix/nix.conf
|
||||
nix profile install nixpkgs#nodejs "github:zhaofengli/attic?ref=6eabc3f02fae3683bffab483e614bebfcd476b21"
|
||||
echo "PATH=/nix/var/nix/profiles/per-user/root/profile/bin:$PATH" >> "$GITHUB_ENV"
|
||||
- name: Set up attic binary cache
|
||||
run: |
|
||||
attic login "${{ vars.BINARY_CACHE_NAME }}" "${{ vars.BINARY_CACHE_URL }}" "${{ secrets.BINARY_CACHE_AUTH_KEY }}"
|
||||
attic use "${{ vars.BINARY_CACHE_NAME }}"
|
||||
- uses: actions/checkout@v4
|
||||
- name: Run Clippy
|
||||
run: |
|
||||
|
|
|
@ -21,8 +21,12 @@ jobs:
|
|||
echo "PATH=/nix/var/nix/profiles/per-user/root/profile/bin:$PATH" >> "$GITHUB_ENV"
|
||||
- name: Set up attic binary cache
|
||||
run: |
|
||||
attic login "${{ vars.PUBLIC_BINARY_CACHE_NAME }}" "${{ vars.BINARY_CACHE_URL }}" "${{ secrets.PUBLIC_BINARY_CACHE_AUTH_KEY }}"
|
||||
attic use "${{ vars.PUBLIC_BINARY_CACHE_NAME }}"
|
||||
attic login "${{ vars.BINARY_CACHE_NAME }}" "${{ vars.BINARY_CACHE_URL }}" "${{ secrets.BINARY_CACHE_AUTH_KEY }}"
|
||||
attic use "${{ vars.BINARY_CACHE_NAME }}"
|
||||
- name: Prepare SSH key
|
||||
run: |
|
||||
mkdir -p dist && mkdir -p ~/.ssh
|
||||
ssh-keyscan git.naxdy.org >> ~/.ssh/known_hosts
|
||||
- uses: actions/checkout@v4
|
||||
- name: Build firmware image
|
||||
run: |
|
||||
|
@ -30,7 +34,7 @@ jobs:
|
|||
- name: Push derivations to binary cache
|
||||
run: |
|
||||
cd /nix/store
|
||||
attic push "${{ vars.PUBLIC_BINARY_CACHE_NAME }}" $(ls /nix/store --ignore='*.drv' --ignore='*fake_nixpkgs*')
|
||||
attic push "${{ vars.BINARY_CACHE_NAME }}" $(ls /nix/store --ignore='*.drv' --ignore='*fake_nixpkgs*')
|
||||
- name: (Re-)generate tag
|
||||
run: |
|
||||
git config --global user.email "noreply@naxdy.org"
|
||||
|
@ -47,9 +51,5 @@ jobs:
|
|||
tag_name: nightly
|
||||
prerelease: true
|
||||
name: "Nightly Release"
|
||||
body: >
|
||||
This is an automatically generated nightly release.
|
||||
|
||||
**WARNING:** This release may contain untested changes and could potentially break your configuration. Use at your own risk. **Do not report issues you encounter with nightly releases.**
|
||||
files: |
|
||||
dist/bin/*
|
||||
|
|
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -1,6 +1,5 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
# A Comment
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
|
|
|
@ -9,12 +9,12 @@
|
|||
"rev": "2ee4657727b9679998d941de00b43e1754f570bf",
|
||||
"revCount": 6754,
|
||||
"type": "git",
|
||||
"url": "https://gitea@git.naxdy.org/NaxdyOrg/embassy"
|
||||
"url": "ssh://gitea@git.naxdy.org/NaxdyOrg/embassy"
|
||||
},
|
||||
"original": {
|
||||
"ref": "naxgcc-fw",
|
||||
"type": "git",
|
||||
"url": "https://gitea@git.naxdy.org/NaxdyOrg/embassy"
|
||||
"url": "ssh://gitea@git.naxdy.org/NaxdyOrg/embassy"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
flake-utils.url = "github:numtide/flake-utils";
|
||||
|
||||
embassy-rs-patched = {
|
||||
url = "git+https://gitea@git.naxdy.org/NaxdyOrg/embassy?ref=naxgcc-fw";
|
||||
url = "git+ssh://gitea@git.naxdy.org/NaxdyOrg/embassy?ref=naxgcc-fw";
|
||||
flake = false;
|
||||
};
|
||||
|
||||
|
|
|
@ -10,12 +10,13 @@ use embassy_rp::{
|
|||
flash::{Async, Flash, ERASE_SIZE},
|
||||
peripherals::FLASH,
|
||||
};
|
||||
use packed_struct::{derive::PackedStruct, PackedStruct};
|
||||
use packed_struct::{
|
||||
derive::{PackedStruct, PrimitiveEnum_u8},
|
||||
PackedStruct,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
gcc_hid::{
|
||||
Buttons1, Buttons2, SIGNAL_CHANGE_RUMBLE_STRENGTH, SIGNAL_INPUT_CONSISTENCY_MODE_STATUS,
|
||||
},
|
||||
gcc_hid::{Buttons1, Buttons2, MUTEX_INPUT_CONSISTENCY_MODE, SIGNAL_CHANGE_RUMBLE_STRENGTH},
|
||||
helpers::{PackedFloat, ToPackedFloatArray, ToRegularArray, XyValuePair},
|
||||
input::{
|
||||
read_ext_adc, Stick, StickAxis, FLOAT_ORIGIN, SPI_ACS_SHARED, SPI_CCS_SHARED, SPI_SHARED,
|
||||
|
@ -523,6 +524,18 @@ impl Default for StickConfig {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Format, PrimitiveEnum_u8, PartialEq, Eq)]
|
||||
pub enum InputConsistencyMode {
|
||||
/// Transmit inputs every 8ms, same as the original GCC adapter (and any other).
|
||||
Original = 0,
|
||||
/// Forcibly delay transmissions to be 8.33ms apart, to better align with the game's frame rate.
|
||||
ConsistencyHack = 1,
|
||||
/// Transmit inputs _at most_ every 8.33ms, but don't transmit anything at all if the controller state doesn't change.
|
||||
/// This has the potential to drastically improve latency in certain situations, such as when you are waiting to react
|
||||
/// to something your opponent does.
|
||||
SuperHack = 2,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Format, PackedStruct)]
|
||||
#[packed_struct(endian = "msb")]
|
||||
pub struct ControllerConfig {
|
||||
|
@ -532,8 +545,8 @@ pub struct ControllerConfig {
|
|||
/// 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")]
|
||||
pub input_consistency_mode: bool,
|
||||
#[packed_field(size_bits = "8", ty = "enum")]
|
||||
pub input_consistency_mode: InputConsistencyMode,
|
||||
#[packed_field(size_bits = "8")]
|
||||
pub rumble_strength: u8,
|
||||
#[packed_field(size_bytes = "328")]
|
||||
|
@ -546,7 +559,7 @@ impl Default for ControllerConfig {
|
|||
fn default() -> Self {
|
||||
Self {
|
||||
config_revision: CONTROLLER_CONFIG_REVISION,
|
||||
input_consistency_mode: true,
|
||||
input_consistency_mode: InputConsistencyMode::ConsistencyHack,
|
||||
astick_config: StickConfig::default(),
|
||||
rumble_strength: 9,
|
||||
cstick_config: StickConfig::default(),
|
||||
|
@ -1621,7 +1634,11 @@ async fn configuration_main_loop<
|
|||
}
|
||||
// input consistency toggle
|
||||
37 => {
|
||||
final_config.input_consistency_mode = !final_config.input_consistency_mode;
|
||||
final_config.input_consistency_mode = match final_config.input_consistency_mode {
|
||||
InputConsistencyMode::Original => InputConsistencyMode::ConsistencyHack,
|
||||
InputConsistencyMode::ConsistencyHack => InputConsistencyMode::SuperHack,
|
||||
InputConsistencyMode::SuperHack => InputConsistencyMode::Original,
|
||||
};
|
||||
|
||||
override_gcc_state_and_wait(&OverrideGcReportInstruction {
|
||||
report: GcReport {
|
||||
|
@ -1641,8 +1658,9 @@ async fn configuration_main_loop<
|
|||
stick_x: 127,
|
||||
stick_y: (127_i8
|
||||
+ match final_config.input_consistency_mode {
|
||||
true => 69,
|
||||
false => -69,
|
||||
InputConsistencyMode::Original => -69,
|
||||
InputConsistencyMode::ConsistencyHack => 42,
|
||||
InputConsistencyMode::SuperHack => 69,
|
||||
}) as u8,
|
||||
cstick_x: 127,
|
||||
cstick_y: 127,
|
||||
|
@ -1682,7 +1700,11 @@ pub async fn config_task(mut flash: Flash<'static, FLASH, Async, FLASH_SIZE>) {
|
|||
|
||||
let mut current_config = ControllerConfig::from_flash_memory(&mut flash).unwrap();
|
||||
|
||||
SIGNAL_INPUT_CONSISTENCY_MODE_STATUS.signal(current_config.input_consistency_mode);
|
||||
{
|
||||
let mut m_input_consistency = MUTEX_INPUT_CONSISTENCY_MODE.lock().await;
|
||||
*m_input_consistency = Some(current_config.input_consistency_mode);
|
||||
}
|
||||
|
||||
SIGNAL_CHANGE_RUMBLE_STRENGTH.signal(current_config.rumble_strength);
|
||||
SIGNAL_CONFIG_CHANGE.signal(current_config.clone());
|
||||
|
||||
|
|
|
@ -12,8 +12,8 @@ use embassy_rp::{
|
|||
usb::Driver,
|
||||
};
|
||||
|
||||
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal};
|
||||
use embassy_time::{Duration, Instant, Ticker};
|
||||
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex, signal::Signal};
|
||||
use embassy_time::{Duration, Instant, Ticker, Timer};
|
||||
use embassy_usb::{
|
||||
class::hid::{HidReaderWriter, ReportId, RequestHandler, State},
|
||||
control::OutResponse,
|
||||
|
@ -22,7 +22,7 @@ use embassy_usb::{
|
|||
use libm::powf;
|
||||
use packed_struct::{derive::PackedStruct, PackedStruct};
|
||||
|
||||
use crate::input::CHANNEL_GCC_STATE;
|
||||
use crate::{config::InputConsistencyMode, input::CHANNEL_GCC_STATE};
|
||||
|
||||
static SIGNAL_RUMBLE: Signal<CriticalSectionRawMutex, bool> = Signal::new();
|
||||
|
||||
|
@ -31,8 +31,10 @@ static SIGNAL_RUMBLE: Signal<CriticalSectionRawMutex, bool> = Signal::new();
|
|||
pub static SIGNAL_CHANGE_RUMBLE_STRENGTH: Signal<CriticalSectionRawMutex, u8> = Signal::new();
|
||||
|
||||
/// Only dispatched ONCE after powerup, to determine how to advertise itself via USB.
|
||||
pub static SIGNAL_INPUT_CONSISTENCY_MODE_STATUS: Signal<CriticalSectionRawMutex, bool> =
|
||||
Signal::new();
|
||||
pub static MUTEX_INPUT_CONSISTENCY_MODE: Mutex<
|
||||
CriticalSectionRawMutex,
|
||||
Option<InputConsistencyMode>,
|
||||
> = Mutex::new(None);
|
||||
|
||||
#[rustfmt::skip]
|
||||
pub const GCC_REPORT_DESCRIPTOR: &[u8] = &[
|
||||
|
@ -266,7 +268,12 @@ impl Handler for MyDeviceHandler {
|
|||
|
||||
#[embassy_executor::task]
|
||||
pub async fn usb_transfer_task(raw_serial: [u8; 8], driver: Driver<'static, USB>) {
|
||||
let input_consistency_mode = SIGNAL_INPUT_CONSISTENCY_MODE_STATUS.wait().await;
|
||||
let input_consistency_mode = {
|
||||
while MUTEX_INPUT_CONSISTENCY_MODE.lock().await.is_none() {
|
||||
Timer::after(Duration::from_millis(100)).await;
|
||||
}
|
||||
MUTEX_INPUT_CONSISTENCY_MODE.lock().await.unwrap()
|
||||
};
|
||||
|
||||
let mut serial_buffer = [0u8; 64];
|
||||
|
||||
|
@ -291,10 +298,10 @@ pub async fn usb_transfer_task(raw_serial: [u8; 8], driver: Driver<'static, USB>
|
|||
trace!("Start of config");
|
||||
let mut usb_config = embassy_usb::Config::new(0x057e, 0x0337);
|
||||
usb_config.manufacturer = Some("Naxdy");
|
||||
usb_config.product = Some(if input_consistency_mode {
|
||||
"NaxGCC (Consistency Mode)"
|
||||
} else {
|
||||
"NaxGCC (OG Mode)"
|
||||
usb_config.product = Some(match input_consistency_mode {
|
||||
InputConsistencyMode::Original => "NaxGCC (OG Mode)",
|
||||
InputConsistencyMode::ConsistencyHack => "NaxGCC (Consistency Mode)",
|
||||
InputConsistencyMode::SuperHack => "NaxGCC (SuperHack Mode)",
|
||||
});
|
||||
usb_config.serial_number = Some(serial);
|
||||
usb_config.max_power = 200;
|
||||
|
@ -331,7 +338,7 @@ pub async fn usb_transfer_task(raw_serial: [u8; 8], driver: Driver<'static, USB>
|
|||
let hid_config = embassy_usb::class::hid::Config {
|
||||
report_descriptor: GCC_REPORT_DESCRIPTOR,
|
||||
request_handler: Some(&request_handler),
|
||||
poll_ms: if input_consistency_mode { 4 } else { 8 },
|
||||
poll_ms: 8,
|
||||
max_packet_size_in: 37,
|
||||
max_packet_size_out: 5,
|
||||
};
|
||||
|
@ -350,25 +357,37 @@ pub async fn usb_transfer_task(raw_serial: [u8; 8], driver: Driver<'static, USB>
|
|||
|
||||
let (mut reader, mut writer) = hid.split();
|
||||
|
||||
let mut lasttime = Instant::now();
|
||||
|
||||
let in_fut = async {
|
||||
let mut gcc_subscriber = CHANNEL_GCC_STATE.subscriber().unwrap();
|
||||
|
||||
let mut last_report_time = Instant::now();
|
||||
let mut ticker = Ticker::every(Duration::from_micros(8333));
|
||||
|
||||
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;
|
||||
// This is what we like to call a "hack".
|
||||
// It forces reports to be sent at least 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.
|
||||
match input_consistency_mode {
|
||||
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::ConsistencyHack => {
|
||||
// Ticker better maintains a consistent interval than Timer, so
|
||||
// we prefer it for consistency mode, where we send reports regularly.
|
||||
ticker.next().await;
|
||||
}
|
||||
InputConsistencyMode::Original => {}
|
||||
}
|
||||
|
||||
match writer
|
||||
|
@ -382,15 +401,17 @@ pub async fn usb_transfer_task(raw_serial: [u8; 8], driver: Driver<'static, USB>
|
|||
{
|
||||
Ok(()) => {
|
||||
let currtime = Instant::now();
|
||||
let polltime = currtime.duration_since(lasttime);
|
||||
let polltime = currtime.duration_since(last_report_time);
|
||||
let micros = polltime.as_micros();
|
||||
trace!("Report written in {}us", micros);
|
||||
// If we're sending reports too fast, reset the ticker.
|
||||
debug!("Report written in {}us", micros);
|
||||
// If we're sending reports too fast in regular consistency mode, reset the ticker.
|
||||
// This might happen right after plug-in, or after suspend.
|
||||
if micros < 8150 {
|
||||
ticker.reset();
|
||||
if input_consistency_mode == InputConsistencyMode::ConsistencyHack
|
||||
&& micros < 8150
|
||||
{
|
||||
ticker.reset()
|
||||
}
|
||||
lasttime = currtime;
|
||||
last_report_time = currtime;
|
||||
}
|
||||
Err(e) => warn!("Failed to send report: {:?}", e),
|
||||
}
|
||||
|
|
35
src/input.rs
35
src/input.rs
|
@ -16,11 +16,12 @@ use libm::{fmaxf, fminf};
|
|||
|
||||
use crate::{
|
||||
config::{
|
||||
ControllerConfig, OverrideGcReportInstruction, OverrideStickState, SIGNAL_CONFIG_CHANGE,
|
||||
SIGNAL_IS_CALIBRATING, SIGNAL_OVERRIDE_GCC_STATE, SIGNAL_OVERRIDE_STICK_STATE,
|
||||
ControllerConfig, InputConsistencyMode, OverrideGcReportInstruction, OverrideStickState,
|
||||
SIGNAL_CONFIG_CHANGE, SIGNAL_IS_CALIBRATING, SIGNAL_OVERRIDE_GCC_STATE,
|
||||
SIGNAL_OVERRIDE_STICK_STATE,
|
||||
},
|
||||
filter::{run_waveshaping, FilterGains, KalmanState, WaveshapingValues, FILTER_GAINS},
|
||||
gcc_hid::GcReport,
|
||||
gcc_hid::{GcReport, MUTEX_INPUT_CONSISTENCY_MODE},
|
||||
helpers::XyValuePair,
|
||||
input_filter::{DummyFilter, InputFilter},
|
||||
stick::{linearize, notch_remap, StickParams},
|
||||
|
@ -439,6 +440,15 @@ pub async fn update_button_state_task(
|
|||
loop {}
|
||||
}
|
||||
|
||||
let input_consistency_mode = {
|
||||
while MUTEX_INPUT_CONSISTENCY_MODE.lock().await.is_none() {
|
||||
Timer::after(Duration::from_millis(100)).await;
|
||||
}
|
||||
MUTEX_INPUT_CONSISTENCY_MODE.lock().await.unwrap()
|
||||
};
|
||||
|
||||
let mut previous_state = GcReport::default();
|
||||
|
||||
let mut gcc_state = GcReport::default();
|
||||
|
||||
let gcc_publisher = CHANNEL_GCC_STATE.publisher().unwrap();
|
||||
|
@ -483,7 +493,15 @@ pub async fn update_button_state_task(
|
|||
trace!("Overridden gcc state: {:?}", override_gcc_state.report);
|
||||
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);
|
||||
if input_consistency_mode == InputConsistencyMode::SuperHack {
|
||||
if override_gcc_state.report != previous_state {
|
||||
gcc_publisher.publish_immediate(override_gcc_state.report);
|
||||
previous_state = override_gcc_state.report;
|
||||
}
|
||||
} else {
|
||||
gcc_publisher.publish_immediate(override_gcc_state.report);
|
||||
}
|
||||
|
||||
yield_now().await;
|
||||
}
|
||||
};
|
||||
|
@ -503,7 +521,14 @@ pub async fn update_button_state_task(
|
|||
gcc_publisher.publish_immediate(overriden_gcc_state);
|
||||
} else {
|
||||
input_filter.apply_filter(&mut gcc_state);
|
||||
gcc_publisher.publish_immediate(gcc_state);
|
||||
if input_consistency_mode == InputConsistencyMode::SuperHack {
|
||||
if gcc_state != previous_state {
|
||||
gcc_publisher.publish_immediate(gcc_state);
|
||||
previous_state = gcc_state.clone();
|
||||
}
|
||||
} else {
|
||||
gcc_publisher.publish_immediate(gcc_state);
|
||||
}
|
||||
}
|
||||
|
||||
// give other tasks a chance to do something
|
||||
|
|
|
@ -30,8 +30,8 @@ use gpio::{Level, Output};
|
|||
use input::{update_button_state_task, update_stick_states_task};
|
||||
use static_cell::StaticCell;
|
||||
|
||||
use crate::config::enter_config_mode_task;
|
||||
use crate::gcc_hid::rumble_task;
|
||||
use crate::{config::enter_config_mode_task, input::input_integrity_benchmark};
|
||||
|
||||
use {defmt_rtt as _, panic_probe as _};
|
||||
|
||||
|
@ -84,7 +84,7 @@ fn main() -> ! {
|
|||
spawner
|
||||
.spawn(rumble_task(p.PIN_25, p.PIN_29, p.PWM_CH4, p.PWM_CH6))
|
||||
.unwrap();
|
||||
// spawner.spawn(input_integrity_benchmark()).unwrap();
|
||||
spawner.spawn(input_integrity_benchmark()).unwrap();
|
||||
spawner
|
||||
.spawn(update_button_state_task(
|
||||
Input::new(AnyPin::from(p.PIN_20), gpio::Pull::Up),
|
||||
|
|
Loading…
Reference in a new issue