1
0
Fork 0
forked from NaxdyOrg/NaxGCC-FW
NaxGCC-FW/src/input.rs

594 lines
19 KiB
Rust

use defmt::{debug, info, trace, Format};
use embassy_futures::{join::join, yield_now};
use embassy_rp::{
flash::{Async, Flash},
gpio::{AnyPin, Input, Output, Pin},
peripherals::{
FLASH, PIN_10, PIN_11, PIN_16, PIN_17, PIN_18, PIN_19, PIN_20, PIN_21, PIN_22, PIN_23,
PIN_24, PIN_5, PIN_8, PIN_9, PWM_CH4, PWM_CH6, SPI0, SPI1,
},
pwm::Pwm,
spi::{Blocking, Spi},
};
use embassy_sync::{
blocking_mutex::raw::{CriticalSectionRawMutex, NoopRawMutex, ThreadModeRawMutex},
mutex::Mutex,
pubsub::PubSubChannel,
signal::Signal,
};
use embassy_time::{Duration, Instant, Ticker, Timer};
use libm::{fmaxf, fminf};
use crate::{
config::{
ControllerConfig, 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,
helpers::XyValuePair,
stick::{linearize, notch_remap, StickParams},
FLASH_SIZE,
};
/// Used to send the button state to the usb task and the calibration task
pub static CHANNEL_GCC_STATE: PubSubChannel<CriticalSectionRawMutex, GcReport, 1, 4, 1> =
PubSubChannel::new();
/// Used to send the stick state from the stick task to the main input task
static SIGNAL_STICK_STATE: Signal<CriticalSectionRawMutex, StickState> = Signal::new();
pub static SPI_SHARED: Mutex<ThreadModeRawMutex, Option<Spi<'static, SPI0, Blocking>>> =
Mutex::new(None);
pub static SPI_ACS_SHARED: Mutex<ThreadModeRawMutex, Option<Output<'static, AnyPin>>> =
Mutex::new(None);
pub static SPI_CCS_SHARED: Mutex<ThreadModeRawMutex, Option<Output<'static, AnyPin>>> =
Mutex::new(None);
const STICK_HYST_VAL: f32 = 0.3;
pub const FLOAT_ORIGIN: f32 = 127.5;
#[derive(Clone, Debug, Default, Format)]
pub struct StickState {
pub ax: u8,
pub ay: u8,
pub cx: u8,
pub cy: u8,
}
#[derive(Clone, Debug, Default)]
struct StickPositions {
x: f32,
y: f32,
cx: f32,
cy: f32,
}
#[derive(Clone, Debug, Default, Format)]
struct RawStickValues {
a_linearized: XyValuePair<f32>,
c_linearized: XyValuePair<f32>,
a_raw: XyValuePair<f32>,
c_raw: XyValuePair<f32>,
a_unfiltered: XyValuePair<f32>,
c_unfiltered: XyValuePair<f32>,
}
#[derive(PartialEq, Eq, Debug, Clone, Format, Copy)]
pub enum Stick {
ControlStick,
CStick,
}
#[derive(PartialEq, Eq)]
pub enum StickAxis {
XAxis,
YAxis,
}
#[link_section = ".time_critical.read_ext_adc"]
pub fn read_ext_adc<
'a,
Acs: Pin,
Ccs: Pin,
I: embassy_rp::spi::Instance,
M: embassy_rp::spi::Mode,
>(
which_stick: Stick,
which_axis: StickAxis,
spi: &mut Spi<'a, I, M>,
spi_acs: &mut Output<'a, Acs>,
spi_ccs: &mut Output<'a, Ccs>,
) -> u16 {
let mut buf = [0b11010000; 3];
if which_axis == StickAxis::YAxis {
buf = [0b11110000; 3];
}
if which_stick == Stick::ControlStick {
spi_acs.set_low();
} else {
spi_ccs.set_low();
}
spi.blocking_transfer_in_place(&mut buf).unwrap();
let temp_value =
(((buf[0] & 0b00000111) as u16) << 9) | (buf[1] as u16) << 1 | (buf[2] as u16) >> 7;
if which_stick == Stick::ControlStick {
spi_acs.set_high();
} else {
spi_ccs.set_high();
}
return temp_value;
}
/// 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.
#[link_section = ".time_critical.update_stick_states"]
async fn update_stick_states(
current_stick_state: &StickState,
controlstick_params: &StickParams,
cstick_params: &StickParams,
controller_config: &ControllerConfig,
filter_gains: &FilterGains,
controlstick_waveshaping_values: &mut WaveshapingValues,
cstick_waveshaping_values: &mut WaveshapingValues,
old_stick_pos: &mut StickPositions,
raw_stick_values: &mut RawStickValues,
kalman_state: &mut KalmanState,
is_calibrating: bool,
) -> StickState {
let mut adc_count = 0u32;
let mut ax_sum = 0u32;
let mut ay_sum = 0u32;
let mut cx_sum = 0u32;
let mut cy_sum = 0u32;
let end_time = Instant::now() + Duration::from_micros(200); // this seems kinda magic, and it is, but
let mut spi_unlocked = SPI_SHARED.lock().await;
let mut spi_acs_unlocked = SPI_ACS_SHARED.lock().await;
let mut spi_ccs_unlocked = SPI_CCS_SHARED.lock().await;
let spi = spi_unlocked.as_mut().unwrap();
let spi_acs = spi_acs_unlocked.as_mut().unwrap();
let spi_ccs = spi_ccs_unlocked.as_mut().unwrap();
// "do-while at home"
while {
let loop_start = Instant::now();
adc_count += 1;
ax_sum += read_ext_adc(Stick::ControlStick, StickAxis::XAxis, spi, spi_acs, spi_ccs) as u32;
ay_sum += read_ext_adc(Stick::ControlStick, StickAxis::YAxis, spi, spi_acs, spi_ccs) as u32;
cx_sum += read_ext_adc(Stick::CStick, StickAxis::XAxis, spi, spi_acs, spi_ccs) as u32;
cy_sum += read_ext_adc(Stick::CStick, StickAxis::YAxis, spi, spi_acs, spi_ccs) as u32;
let loop_end = Instant::now();
loop_end < end_time - (loop_end - loop_start)
} {}
trace!("ADC Count: {}", adc_count);
let raw_controlstick = XyValuePair {
x: (ax_sum as f32) / (adc_count as f32) / 4096.0f32,
y: (ay_sum as f32) / (adc_count as f32) / 4096.0f32,
};
let raw_cstick = XyValuePair {
x: (cx_sum as f32) / (adc_count as f32) / 4096.0f32,
y: (cy_sum as f32) / (adc_count as f32) / 4096.0f32,
};
trace!("Raw Control Stick: {}", raw_controlstick);
trace!("Raw CSTICK: {:?}", raw_cstick);
raw_stick_values.a_raw = raw_controlstick;
raw_stick_values.c_raw = raw_cstick;
let x_z = linearize(raw_controlstick.x, &controlstick_params.fit_coeffs.x);
let y_z = linearize(raw_controlstick.y, &controlstick_params.fit_coeffs.y);
let pos_cx = linearize(raw_cstick.x, &cstick_params.fit_coeffs.x);
let pos_cy = linearize(raw_cstick.y, &cstick_params.fit_coeffs.y);
raw_stick_values.a_linearized.x = x_z;
raw_stick_values.a_linearized.y = y_z;
raw_stick_values.c_linearized.x = pos_cx;
raw_stick_values.c_linearized.y = pos_cy;
trace!("Raw Stick Values 001: {:?}", raw_stick_values);
let (x_pos_filt, y_pos_filt) =
kalman_state.run_kalman(x_z, y_z, &controller_config.astick_config, &filter_gains);
let shaped_astick = match run_waveshaping(
x_pos_filt,
y_pos_filt,
controller_config.astick_config.x_waveshaping,
controller_config.astick_config.y_waveshaping,
controlstick_waveshaping_values,
&filter_gains,
) {
(x, y) => XyValuePair { x, y },
};
trace!("Shaped Controlstick: {}", shaped_astick);
let pos_x: f32 = filter_gains.smoothing.x * shaped_astick.x
+ (1.0 - filter_gains.smoothing.x) * old_stick_pos.x;
let pos_y = filter_gains.smoothing.y * shaped_astick.y
+ (1.0 - filter_gains.smoothing.y) * old_stick_pos.y;
old_stick_pos.x = pos_x;
old_stick_pos.y = pos_y;
let shaped_cstick = match run_waveshaping(
pos_cx,
pos_cy,
controller_config.cstick_config.x_waveshaping,
controller_config.cstick_config.y_waveshaping,
cstick_waveshaping_values,
&filter_gains,
) {
(x, y) => XyValuePair { x, y },
};
let old_c_pos = XyValuePair {
x: old_stick_pos.cx,
y: old_stick_pos.cy,
};
old_stick_pos.cx = shaped_cstick.x;
old_stick_pos.cy = shaped_cstick.y;
let x_weight_1 = filter_gains.c_smoothing.x;
let x_weight_2 = 1.0 - x_weight_1;
let y_weight_1 = filter_gains.c_smoothing.y;
let y_weight_2 = 1.0 - y_weight_1;
let pos_cx_filt = x_weight_1 * shaped_cstick.x + x_weight_2 * old_c_pos.x;
let pos_cy_filt = y_weight_1 * shaped_cstick.y + y_weight_2 * old_c_pos.y;
// phob optionally runs a median filter here, but we leave it for now
trace!("Cstick position: {}, {}", pos_cx, pos_cy);
let mut remapped = match notch_remap(
pos_x,
pos_y,
controlstick_params,
&controller_config.astick_config,
is_calibrating,
) {
(x, y) => XyValuePair { x, y },
};
let mut remapped_c = match notch_remap(
pos_cx_filt,
pos_cy_filt,
cstick_params,
&controller_config.cstick_config,
is_calibrating,
) {
(x, y) => XyValuePair { x, y },
};
let remapped_unfiltered = match notch_remap(
raw_stick_values.a_linearized.x,
raw_stick_values.a_linearized.y,
controlstick_params,
&controller_config.astick_config,
is_calibrating,
) {
(x, y) => XyValuePair { x, y },
};
let remapped_c_unfiltered = match notch_remap(
raw_stick_values.c_linearized.x,
raw_stick_values.c_linearized.y,
cstick_params,
&controller_config.cstick_config,
is_calibrating,
) {
(x, y) => XyValuePair { x, y },
};
trace!(
"Remapped Control Stick: {}; C stick: {}",
remapped,
remapped_c
);
remapped = XyValuePair {
x: fminf(125., fmaxf(-125., remapped.x)),
y: fminf(125., fmaxf(-125., remapped.y)),
};
remapped_c = XyValuePair {
x: fminf(125., fmaxf(-125., remapped_c.x)),
y: fminf(125., fmaxf(-125., remapped_c.y)),
};
raw_stick_values.a_unfiltered.x = fminf(125., fmaxf(-125., remapped_unfiltered.x));
raw_stick_values.a_unfiltered.y = fminf(125., fmaxf(-125., remapped_unfiltered.y));
raw_stick_values.c_unfiltered.x = fminf(125., fmaxf(-125., remapped_c_unfiltered.x));
raw_stick_values.c_unfiltered.y = fminf(125., fmaxf(-125., remapped_c_unfiltered.y));
let mut out_stick_state = current_stick_state.clone();
let diff_x = (remapped.x + FLOAT_ORIGIN) - current_stick_state.ax as f32;
if (diff_x > (1.0 + STICK_HYST_VAL)) || (diff_x < -STICK_HYST_VAL) {
out_stick_state.ax = (remapped.x + FLOAT_ORIGIN) as u8;
}
let diff_y = (remapped.y + FLOAT_ORIGIN) - current_stick_state.ay as f32;
if (diff_y > (1.0 + STICK_HYST_VAL)) || (diff_y < -STICK_HYST_VAL) {
out_stick_state.ay = (remapped.y + FLOAT_ORIGIN) as u8;
}
let diff_cx = (remapped_c.x + FLOAT_ORIGIN) - current_stick_state.cx as f32;
if (diff_cx > (1.0 + STICK_HYST_VAL)) || (diff_cx < -STICK_HYST_VAL) {
out_stick_state.cx = (remapped_c.x + FLOAT_ORIGIN) as u8;
}
let diff_cy = (remapped_c.y + FLOAT_ORIGIN) - current_stick_state.cy as f32;
if (diff_cy > (1.0 + STICK_HYST_VAL)) || (diff_cy < -STICK_HYST_VAL) {
out_stick_state.cy = (remapped_c.y + FLOAT_ORIGIN) as u8;
}
trace!(
"Control stick: {}, {}, C-stick: {}, {}",
out_stick_state.ax,
out_stick_state.ay,
out_stick_state.cx,
out_stick_state.cy
);
out_stick_state
}
fn update_button_states<
A: Pin,
B: Pin,
X: Pin,
Y: Pin,
Start: Pin,
L: Pin,
R: Pin,
Z: Pin,
DLeft: Pin,
DRight: Pin,
DUp: Pin,
DDown: Pin,
>(
gcc_state: &mut GcReport,
btn_a: &Input<'_, A>,
btn_b: &Input<'_, B>,
btn_x: &Input<'_, X>,
btn_y: &Input<'_, Y>,
btn_start: &Input<'_, Start>,
btn_l: &Input<'_, L>,
btn_r: &Input<'_, R>,
btn_z: &Input<'_, Z>,
btn_dleft: &Input<'_, DLeft>,
btn_dright: &Input<'_, DRight>,
btn_dup: &Input<'_, DUp>,
btn_ddown: &Input<'_, DDown>,
) {
gcc_state.buttons_1.button_a = btn_a.is_low();
gcc_state.buttons_1.button_b = btn_b.is_low();
gcc_state.buttons_1.button_x = btn_x.is_low();
gcc_state.buttons_1.button_y = btn_y.is_low();
gcc_state.buttons_2.button_z = btn_z.is_low();
gcc_state.buttons_2.button_start = btn_start.is_low();
gcc_state.buttons_2.button_l = btn_l.is_low();
gcc_state.buttons_2.button_r = btn_r.is_low();
gcc_state.buttons_1.dpad_left = btn_dleft.is_low();
gcc_state.buttons_1.dpad_right = btn_dright.is_low();
gcc_state.buttons_1.dpad_up = btn_dup.is_low();
gcc_state.buttons_1.dpad_down = btn_ddown.is_low();
gcc_state.trigger_l = match gcc_state.buttons_2.button_l {
true => 255,
false => 0,
};
gcc_state.trigger_r = match gcc_state.buttons_2.button_r {
true => 255,
false => 0,
};
}
#[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
},
duration_ms: 100,
});
Timer::after_millis(200).await;
}
}
/// Task responsible for updating the button states.
/// Publishes the result to CHANNEL_GCC_STATE.
#[embassy_executor::task]
pub async fn update_button_state_task(
btn_z: Input<'static, AnyPin>,
btn_a: Input<'static, AnyPin>,
btn_b: Input<'static, AnyPin>,
btn_dright: Input<'static, AnyPin>,
btn_dup: Input<'static, AnyPin>,
btn_ddown: Input<'static, AnyPin>,
btn_dleft: Input<'static, AnyPin>,
btn_l: Input<'static, AnyPin>,
btn_r: Input<'static, AnyPin>,
btn_x: Input<'static, AnyPin>,
btn_y: Input<'static, AnyPin>,
btn_start: Input<'static, AnyPin>,
) {
// upon loop entry, we check for the reset combo once
if btn_a.is_low() && btn_x.is_low() && btn_y.is_low() {
info!("Detected reset button press, booting into flash.");
embassy_rp::rom_data::reset_to_usb_boot(0, 0);
loop {}
}
let mut gcc_state = GcReport::default();
let gcc_publisher = CHANNEL_GCC_STATE.publisher().unwrap();
let mut override_stick_state: Option<OverrideStickState> = None;
loop {
update_button_states(
&mut gcc_state,
&btn_a,
&btn_b,
&btn_x,
&btn_y,
&btn_start,
&btn_l,
&btn_r,
&btn_z,
&btn_dleft,
&btn_dright,
&btn_dup,
&btn_ddown,
);
// not every loop pass is going to update the stick state
if let Some(stick_state) = SIGNAL_STICK_STATE.try_take() {
gcc_state.stick_x = stick_state.ax;
gcc_state.stick_y = stick_state.ay;
gcc_state.cstick_x = stick_state.cx;
gcc_state.cstick_y = stick_state.cy;
}
if let Some(override_stick_state_opt) = SIGNAL_OVERRIDE_STICK_STATE.try_take() {
trace!("Overridden stick state: {:?}", override_stick_state_opt);
override_stick_state = override_stick_state_opt;
}
// check for a gcc state override (usually coming from the config task)
if let Some(override_gcc_state) = SIGNAL_OVERRIDE_GCC_STATE.try_take() {
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);
yield_now().await;
}
};
if let Some(override_state) = &override_stick_state {
let mut overriden_gcc_state = gcc_state.clone();
match override_state.which_stick {
Stick::ControlStick => {
overriden_gcc_state.stick_x = override_state.x;
overriden_gcc_state.stick_y = override_state.y;
}
Stick::CStick => {
overriden_gcc_state.cstick_x = override_state.x;
overriden_gcc_state.cstick_y = override_state.y;
}
}
gcc_publisher.publish_immediate(overriden_gcc_state);
} else {
gcc_publisher.publish_immediate(gcc_state);
}
// give other tasks a chance to do something
yield_now().await;
}
}
/// Task responsible for updating the stick states.
/// Publishes the result to STICK_SIGNAL.
///
/// Has to run on core0 because it makes use of SPI0.
#[embassy_executor::task]
#[link_section = ".time_critical.update_stick_states_task"]
pub async fn update_stick_states_task(
spi: Spi<'static, SPI0, embassy_rp::spi::Blocking>,
spi_acs: Output<'static, AnyPin>,
spi_ccs: Output<'static, AnyPin>,
mut controller_config: ControllerConfig,
) {
Timer::after_secs(1).await;
*SPI_SHARED.lock().await = Some(spi);
*SPI_ACS_SHARED.lock().await = Some(spi_acs);
*SPI_CCS_SHARED.lock().await = Some(spi_ccs);
let mut controlstick_params = StickParams::from_stick_config(&controller_config.astick_config);
let mut cstick_params = StickParams::from_stick_config(&controller_config.cstick_config);
let mut filter_gains = FILTER_GAINS.get_normalized_gains(&controller_config);
let mut current_stick_state = StickState {
ax: 127,
ay: 127,
cx: 127,
cy: 127,
};
let mut raw_stick_values = RawStickValues::default();
let mut old_stick_pos = StickPositions::default();
let mut cstick_waveshaping_values = WaveshapingValues::default();
let mut controlstick_waveshaping_values = WaveshapingValues::default();
let mut kalman_state = KalmanState::default();
let mut last_loop_time = Instant::now();
debug!("Entering stick update loop.");
let mut is_calibrating = false;
let mut ticker = Ticker::every(Duration::from_hz(1000));
loop {
current_stick_state = update_stick_states(
&current_stick_state,
&controlstick_params,
&cstick_params,
&controller_config,
&filter_gains,
&mut controlstick_waveshaping_values,
&mut cstick_waveshaping_values,
&mut old_stick_pos,
&mut raw_stick_values,
&mut kalman_state,
is_calibrating,
)
.await;
if let Some(new_calibrating) = SIGNAL_IS_CALIBRATING.try_take() {
is_calibrating = new_calibrating;
if !is_calibrating {
debug!("Reset the ticker.");
ticker.reset();
}
}
match Instant::now() {
n => {
match (n - last_loop_time).as_micros() {
a if a > 1666 => debug!("Loop took {} us", a),
_ => {}
};
last_loop_time = n;
}
};
SIGNAL_STICK_STATE.signal(current_stick_state.clone());
ticker.next().await;
yield_now().await;
if let Some(new_config) = SIGNAL_CONFIG_CHANGE.try_take() {
controller_config = new_config;
controlstick_params = StickParams::from_stick_config(&controller_config.astick_config);
cstick_params = StickParams::from_stick_config(&controller_config.cstick_config);
filter_gains = FILTER_GAINS.get_normalized_gains(&controller_config);
info!("Controlstick params: {:?}", controlstick_params);
info!("CStick params: {:?}", cstick_params);
}
}
}