Added USB idle timeout
This commit is contained in:
parent
de138354b1
commit
3324de3e9d
@ -309,8 +309,7 @@ fn update_button_press_type(button: &mut Button, current_time: u32) {
|
|||||||
!button.pressed && elapsed >= USB_MIN_HOLD_MS
|
!button.pressed && elapsed >= USB_MIN_HOLD_MS
|
||||||
};
|
};
|
||||||
|
|
||||||
if button.usb_press_active && should_release
|
if button.usb_press_active && should_release {
|
||||||
{
|
|
||||||
button.usb_changed = true;
|
button.usb_changed = true;
|
||||||
button.usb_press_active = false;
|
button.usb_press_active = false;
|
||||||
button.active_usb_button = 0;
|
button.active_usb_button = 0;
|
||||||
|
|||||||
@ -11,7 +11,7 @@
|
|||||||
//! behind simple `read_fn`/`write_fn` closures so the code can be unit‑tested on
|
//! behind simple `read_fn`/`write_fn` closures so the code can be unit‑tested on
|
||||||
//! a host without hardware.
|
//! a host without hardware.
|
||||||
|
|
||||||
use crate::axis::{GIMBAL_MODE_M7, GIMBAL_MODE_M10, GimbalAxis};
|
use crate::axis::{GimbalAxis, GIMBAL_MODE_M10, GIMBAL_MODE_M7};
|
||||||
use crate::hardware::NBR_OF_GIMBAL_AXIS;
|
use crate::hardware::NBR_OF_GIMBAL_AXIS;
|
||||||
use crate::storage;
|
use crate::storage;
|
||||||
use dyn_smooth::DynamicSmootherEcoI32;
|
use dyn_smooth::DynamicSmootherEcoI32;
|
||||||
@ -148,10 +148,8 @@ impl CalibrationManager {
|
|||||||
/// Load per‑axis calibration data from EEPROM.
|
/// Load per‑axis calibration data from EEPROM.
|
||||||
///
|
///
|
||||||
/// Updates the provided axes with loaded values; retains defaults on error.
|
/// Updates the provided axes with loaded values; retains defaults on error.
|
||||||
pub fn load_axis_calibration<F>(
|
pub fn load_axis_calibration<F>(axes: &mut [GimbalAxis; NBR_OF_GIMBAL_AXIS], read_fn: &mut F)
|
||||||
axes: &mut [GimbalAxis; NBR_OF_GIMBAL_AXIS],
|
where
|
||||||
read_fn: &mut F,
|
|
||||||
) where
|
|
||||||
F: FnMut(u32) -> Result<u8, ()>,
|
F: FnMut(u32) -> Result<u8, ()>,
|
||||||
{
|
{
|
||||||
for (index, axis) in axes.iter_mut().enumerate() {
|
for (index, axis) in axes.iter_mut().enumerate() {
|
||||||
@ -176,7 +174,6 @@ impl CalibrationManager {
|
|||||||
storage::read_gimbal_mode(read_fn).unwrap_or(GIMBAL_MODE_M10)
|
storage::read_gimbal_mode(read_fn).unwrap_or(GIMBAL_MODE_M10)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// Reset each axis calibration to its current smoothed center.
|
/// Reset each axis calibration to its current smoothed center.
|
||||||
fn reset_axis_calibration(
|
fn reset_axis_calibration(
|
||||||
&self,
|
&self,
|
||||||
@ -342,11 +339,6 @@ mod tests {
|
|||||||
assert_eq!(manager.get_gimbal_mode(), GIMBAL_MODE_M10);
|
assert_eq!(manager.get_gimbal_mode(), GIMBAL_MODE_M10);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_load_axis_calibration_success() {
|
fn test_load_axis_calibration_success() {
|
||||||
let mut axes = [GimbalAxis::new(); NBR_OF_GIMBAL_AXIS];
|
let mut axes = [GimbalAxis::new(); NBR_OF_GIMBAL_AXIS];
|
||||||
|
|||||||
@ -95,6 +95,9 @@ pub mod timers {
|
|||||||
|
|
||||||
/// USB HID report interval (ms).
|
/// USB HID report interval (ms).
|
||||||
pub const USB_UPDATE_INTERVAL_MS: u32 = 10;
|
pub const USB_UPDATE_INTERVAL_MS: u32 = 10;
|
||||||
|
|
||||||
|
/// USB activity timeout (ms) - stop sending reports after this period of inactivity.
|
||||||
|
pub const USB_ACTIVITY_TIMEOUT_MS: u32 = 10_000; // 10 seconds
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== USB DEVICE CONFIGURATION ====================
|
// ==================== USB DEVICE CONFIGURATION ====================
|
||||||
|
|||||||
@ -8,8 +8,6 @@
|
|||||||
|
|
||||||
/// Axis processing for gimbal and virtual axes (smoothing, expo, holds).
|
/// Axis processing for gimbal and virtual axes (smoothing, expo, holds).
|
||||||
pub mod axis;
|
pub mod axis;
|
||||||
/// Button-to-USB mapping tables and HAT constants.
|
|
||||||
pub mod mapping;
|
|
||||||
/// Row/column scanned button matrix driver with debouncing.
|
/// Row/column scanned button matrix driver with debouncing.
|
||||||
pub mod button_matrix;
|
pub mod button_matrix;
|
||||||
/// Button state machine (short/long press, timing, special actions).
|
/// Button state machine (short/long press, timing, special actions).
|
||||||
@ -20,6 +18,8 @@ pub mod calibration;
|
|||||||
pub mod expo;
|
pub mod expo;
|
||||||
/// Hardware constants, pin definitions, timing, and helper macros.
|
/// Hardware constants, pin definitions, timing, and helper macros.
|
||||||
pub mod hardware;
|
pub mod hardware;
|
||||||
|
/// Button-to-USB mapping tables and HAT constants.
|
||||||
|
pub mod mapping;
|
||||||
/// WS2812 status LED driver and status model.
|
/// WS2812 status LED driver and status model.
|
||||||
pub mod status;
|
pub mod status;
|
||||||
/// EEPROM storage layout and read/write helpers.
|
/// EEPROM storage layout and read/write helpers.
|
||||||
@ -32,9 +32,9 @@ pub mod usb_report;
|
|||||||
/// Re-exports for convenient access in `main` and downstream consumers.
|
/// Re-exports for convenient access in `main` and downstream consumers.
|
||||||
pub use axis::{AxisManager, GimbalAxis, VirtualAxis};
|
pub use axis::{AxisManager, GimbalAxis, VirtualAxis};
|
||||||
pub use calibration::CalibrationManager;
|
pub use calibration::CalibrationManager;
|
||||||
pub use expo::{ExpoLUT, apply_expo_curve, constrain, generate_expo_lut};
|
pub use expo::{apply_expo_curve, constrain, generate_expo_lut, ExpoLUT};
|
||||||
pub use storage::{read_axis_calibration, read_gimbal_mode, write_calibration_data, StorageError};
|
pub use storage::{read_axis_calibration, read_gimbal_mode, write_calibration_data, StorageError};
|
||||||
pub use usb_report::{get_joystick_report, axis_12bit_to_i16};
|
pub use usb_report::{axis_12bit_to_i16, get_joystick_report};
|
||||||
|
|
||||||
/// Common ADC range constants used across modules.
|
/// Common ADC range constants used across modules.
|
||||||
pub const ADC_MIN: u16 = 0;
|
pub const ADC_MIN: u16 = 0;
|
||||||
|
|||||||
@ -233,6 +233,8 @@ fn main() -> ! {
|
|||||||
let mut usb_activity: bool = false;
|
let mut usb_activity: bool = false;
|
||||||
let mut usb_active: bool = false;
|
let mut usb_active: bool = false;
|
||||||
let mut vt_enable: bool = false;
|
let mut vt_enable: bool = false;
|
||||||
|
let mut idle_mode: bool = false;
|
||||||
|
let mut usb_activity_timeout_count: u32 = 0;
|
||||||
|
|
||||||
let mut axis_manager = AxisManager::new();
|
let mut axis_manager = AxisManager::new();
|
||||||
let mut button_manager = ButtonManager::new();
|
let mut button_manager = ButtonManager::new();
|
||||||
@ -285,6 +287,11 @@ fn main() -> ! {
|
|||||||
|
|
||||||
// Handle USB device polling and maintain connection state
|
// Handle USB device polling and maintain connection state
|
||||||
if usb_dev.poll(&mut [&mut usb_hid_joystick]) {
|
if usb_dev.poll(&mut [&mut usb_hid_joystick]) {
|
||||||
|
if !usb_active {
|
||||||
|
usb_activity = true; // Force initial report
|
||||||
|
idle_mode = false;
|
||||||
|
usb_activity_timeout_count = 0;
|
||||||
|
}
|
||||||
usb_active = true;
|
usb_active = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -329,6 +336,7 @@ fn main() -> ! {
|
|||||||
|
|
||||||
let system_state = SystemState {
|
let system_state = SystemState {
|
||||||
usb_active,
|
usb_active,
|
||||||
|
idle_mode,
|
||||||
calibration_active: calibration_manager.is_active(),
|
calibration_active: calibration_manager.is_active(),
|
||||||
throttle_hold_enable: axis_manager.throttle_hold_enable,
|
throttle_hold_enable: axis_manager.throttle_hold_enable,
|
||||||
vt_enable,
|
vt_enable,
|
||||||
@ -424,16 +432,22 @@ fn main() -> ! {
|
|||||||
// Process gimbal axes through calibration, expo curves, and scaling
|
// Process gimbal axes through calibration, expo curves, and scaling
|
||||||
if axis_manager.process_axis_values(&smoother, &expo_lut) {
|
if axis_manager.process_axis_values(&smoother, &expo_lut) {
|
||||||
usb_activity = true;
|
usb_activity = true;
|
||||||
|
usb_activity_timeout_count = 0; // Reset timeout on real input activity
|
||||||
|
idle_mode = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update virtual axes based on front button states
|
// Update virtual axes based on front button states
|
||||||
if axis_manager.update_virtual_axes(button_manager.buttons(), vt_enable) {
|
if axis_manager.update_virtual_axes(button_manager.buttons(), vt_enable) {
|
||||||
usb_activity = true;
|
usb_activity = true;
|
||||||
|
usb_activity_timeout_count = 0; // Reset timeout on real input activity
|
||||||
|
idle_mode = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process button logic (press types, timing, USB mapping)
|
// Process button logic (press types, timing, USB mapping)
|
||||||
if button_manager.process_button_logic_with_timer(&timer) {
|
if button_manager.process_button_logic_with_timer(&timer) {
|
||||||
usb_activity = true;
|
usb_activity = true;
|
||||||
|
usb_activity_timeout_count = 0; // Reset timeout on real input activity
|
||||||
|
idle_mode = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -450,7 +464,16 @@ fn main() -> ! {
|
|||||||
// - Virtual throttle mode handling
|
// - Virtual throttle mode handling
|
||||||
|
|
||||||
// Only transmit USB reports when input activity is detected
|
// Only transmit USB reports when input activity is detected
|
||||||
if usb_update_count_down.wait().is_ok() && usb_activity {
|
let usb_tick = usb_update_count_down.wait().is_ok();
|
||||||
|
|
||||||
|
if usb_tick && usb_activity {
|
||||||
|
// Check if we've exceeded the activity timeout
|
||||||
|
usb_activity_timeout_count += timers::USB_UPDATE_INTERVAL_MS;
|
||||||
|
if usb_activity_timeout_count >= timers::USB_ACTIVITY_TIMEOUT_MS {
|
||||||
|
usb_activity = false; // Stop sending reports after timeout
|
||||||
|
usb_activity_timeout_count = 0;
|
||||||
|
idle_mode = true;
|
||||||
|
} else {
|
||||||
let virtual_ry_value = axis_manager.get_virtual_ry_value(&expo_lut_virtual);
|
let virtual_ry_value = axis_manager.get_virtual_ry_value(&expo_lut_virtual);
|
||||||
let virtual_rz_value = axis_manager.get_virtual_rz_value(&expo_lut_virtual);
|
let virtual_rz_value = axis_manager.get_virtual_rz_value(&expo_lut_virtual);
|
||||||
|
|
||||||
@ -468,7 +491,11 @@ fn main() -> ! {
|
|||||||
core::panic!("Failed to write joystick report: {:?}", e)
|
core::panic!("Failed to write joystick report: {:?}", e)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
usb_activity = false;
|
}
|
||||||
|
} else if usb_tick && usb_active {
|
||||||
|
idle_mode = true;
|
||||||
|
} else if usb_tick {
|
||||||
|
idle_mode = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -91,7 +91,6 @@ pub const USB_HAT_RIGHT: usize = 34;
|
|||||||
pub const USB_HAT_DOWN: usize = 35;
|
pub const USB_HAT_DOWN: usize = 35;
|
||||||
pub const USB_HAT_LEFT: usize = 36;
|
pub const USB_HAT_LEFT: usize = 36;
|
||||||
|
|
||||||
|
|
||||||
use crate::buttons::Button;
|
use crate::buttons::Button;
|
||||||
|
|
||||||
/// Configure USB button mappings for all buttons.
|
/// Configure USB button mappings for all buttons.
|
||||||
|
|||||||
@ -6,9 +6,9 @@
|
|||||||
|
|
||||||
use rp2040_hal::{
|
use rp2040_hal::{
|
||||||
gpio::AnyPin,
|
gpio::AnyPin,
|
||||||
pio::{PIO, PIOExt, StateMachineIndex, UninitStateMachine},
|
pio::{PIOExt, StateMachineIndex, UninitStateMachine, PIO},
|
||||||
};
|
};
|
||||||
use smart_leds::{RGB8, SmartLedsWrite};
|
use smart_leds::{SmartLedsWrite, RGB8};
|
||||||
use ws2812_pio::Ws2812Direct;
|
use ws2812_pio::Ws2812Direct;
|
||||||
|
|
||||||
/// Status LED modes with clear semantics.
|
/// Status LED modes with clear semantics.
|
||||||
@ -20,29 +20,32 @@ pub enum StatusMode {
|
|||||||
NormalFlash = 2,
|
NormalFlash = 2,
|
||||||
Activity = 3,
|
Activity = 3,
|
||||||
ActivityFlash = 4,
|
ActivityFlash = 4,
|
||||||
Other = 5,
|
Idle = 5,
|
||||||
OtherFlash = 6,
|
Other = 6,
|
||||||
Warning = 7,
|
OtherFlash = 7,
|
||||||
Error = 8,
|
Warning = 8,
|
||||||
Bootloader = 9,
|
Error = 9,
|
||||||
|
Bootloader = 10,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Aggregate system state for LED status indication.
|
/// Aggregate system state for LED status indication.
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub struct SystemState {
|
pub struct SystemState {
|
||||||
pub usb_active: bool,
|
pub usb_active: bool,
|
||||||
|
pub idle_mode: bool,
|
||||||
pub calibration_active: bool,
|
pub calibration_active: bool,
|
||||||
pub throttle_hold_enable: bool,
|
pub throttle_hold_enable: bool,
|
||||||
pub vt_enable: bool,
|
pub vt_enable: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Color definitions for different status modes.
|
/// Color definitions for different status modes.
|
||||||
const LED_COLORS: [RGB8; 10] = [
|
const LED_COLORS: [RGB8; 11] = [
|
||||||
RGB8 { r: 0, g: 0, b: 0 }, // Off
|
RGB8 { r: 0, g: 0, b: 0 }, // Off
|
||||||
RGB8 { r: 10, g: 7, b: 0 }, // Normal (Green)
|
RGB8 { r: 10, g: 7, b: 0 }, // Normal (Green)
|
||||||
RGB8 { r: 10, g: 7, b: 0 }, // NormalFlash (Green)
|
RGB8 { r: 10, g: 7, b: 0 }, // NormalFlash (Green)
|
||||||
RGB8 { r: 10, g: 4, b: 10 }, // Activity (Blue)
|
RGB8 { r: 10, g: 4, b: 10 }, // Activity (Blue)
|
||||||
RGB8 { r: 10, g: 4, b: 10 }, // ActivityFlash (Blue)
|
RGB8 { r: 10, g: 4, b: 10 }, // ActivityFlash (Blue)
|
||||||
|
RGB8 { r: 10, g: 7, b: 0 }, // Idle (Green flash)
|
||||||
RGB8 { r: 5, g: 10, b: 0 }, // Other (Orange)
|
RGB8 { r: 5, g: 10, b: 0 }, // Other (Orange)
|
||||||
RGB8 { r: 5, g: 10, b: 0 }, // OtherFlash (Orange)
|
RGB8 { r: 5, g: 10, b: 0 }, // OtherFlash (Orange)
|
||||||
RGB8 { r: 2, g: 20, b: 0 }, // Warning (Red)
|
RGB8 { r: 2, g: 20, b: 0 }, // Warning (Red)
|
||||||
@ -50,6 +53,24 @@ const LED_COLORS: [RGB8; 10] = [
|
|||||||
RGB8 { r: 0, g: 10, b: 10 }, // Bootloader (Purple)
|
RGB8 { r: 0, g: 10, b: 10 }, // Bootloader (Purple)
|
||||||
];
|
];
|
||||||
|
|
||||||
|
fn determine_mode(system_state: SystemState) -> StatusMode {
|
||||||
|
if system_state.calibration_active {
|
||||||
|
StatusMode::ActivityFlash
|
||||||
|
} else if system_state.idle_mode {
|
||||||
|
StatusMode::Idle
|
||||||
|
} else if !system_state.usb_active {
|
||||||
|
StatusMode::NormalFlash
|
||||||
|
} else if system_state.vt_enable {
|
||||||
|
StatusMode::Activity
|
||||||
|
} else if system_state.throttle_hold_enable {
|
||||||
|
StatusMode::Other
|
||||||
|
} else if system_state.usb_active {
|
||||||
|
StatusMode::Normal
|
||||||
|
} else {
|
||||||
|
StatusMode::Off
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Status LED driver with self-contained state management.
|
/// Status LED driver with self-contained state management.
|
||||||
pub struct StatusLed<P, SM, I>
|
pub struct StatusLed<P, SM, I>
|
||||||
where
|
where
|
||||||
@ -93,20 +114,7 @@ where
|
|||||||
|
|
||||||
/// Update LED based on system state and current time (ms).
|
/// Update LED based on system state and current time (ms).
|
||||||
pub fn update_from_system_state(&mut self, system_state: SystemState, current_time: u32) {
|
pub fn update_from_system_state(&mut self, system_state: SystemState, current_time: u32) {
|
||||||
let desired_mode = if system_state.calibration_active {
|
let desired_mode = determine_mode(system_state);
|
||||||
StatusMode::ActivityFlash
|
|
||||||
} else if !system_state.usb_active {
|
|
||||||
StatusMode::NormalFlash
|
|
||||||
} else if system_state.usb_active && system_state.vt_enable {
|
|
||||||
StatusMode::Activity
|
|
||||||
} else if system_state.usb_active && system_state.throttle_hold_enable {
|
|
||||||
StatusMode::Other
|
|
||||||
} else if system_state.usb_active {
|
|
||||||
StatusMode::Normal
|
|
||||||
} else {
|
|
||||||
StatusMode::Off
|
|
||||||
};
|
|
||||||
|
|
||||||
self.set_mode(desired_mode, current_time);
|
self.set_mode(desired_mode, current_time);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -133,6 +141,7 @@ where
|
|||||||
// Flashing modes - toggle between on and off
|
// Flashing modes - toggle between on and off
|
||||||
StatusMode::NormalFlash
|
StatusMode::NormalFlash
|
||||||
| StatusMode::ActivityFlash
|
| StatusMode::ActivityFlash
|
||||||
|
| StatusMode::Idle
|
||||||
| StatusMode::OtherFlash
|
| StatusMode::OtherFlash
|
||||||
| StatusMode::Warning => {
|
| StatusMode::Warning => {
|
||||||
if self.flash_state {
|
if self.flash_state {
|
||||||
@ -143,7 +152,7 @@ where
|
|||||||
self.write_color(LED_COLORS[StatusMode::Off as usize]);
|
self.write_color(LED_COLORS[StatusMode::Off as usize]);
|
||||||
}
|
}
|
||||||
self.flash_state = !self.flash_state;
|
self.flash_state = !self.flash_state;
|
||||||
},
|
}
|
||||||
// Solid modes - just show the color
|
// Solid modes - just show the color
|
||||||
_ => {
|
_ => {
|
||||||
self.write_color(LED_COLORS[self.current_mode as usize]);
|
self.write_color(LED_COLORS[self.current_mode as usize]);
|
||||||
@ -167,11 +176,10 @@ where
|
|||||||
match self.current_mode {
|
match self.current_mode {
|
||||||
StatusMode::NormalFlash
|
StatusMode::NormalFlash
|
||||||
| StatusMode::ActivityFlash
|
| StatusMode::ActivityFlash
|
||||||
|
| StatusMode::Idle
|
||||||
| StatusMode::OtherFlash
|
| StatusMode::OtherFlash
|
||||||
| StatusMode::Warning => {
|
| StatusMode::Warning => current_time.saturating_sub(last_time) >= 500,
|
||||||
current_time.saturating_sub(last_time) >= 500
|
_ => false, // Non-flashing modes don't need periodic updates
|
||||||
},
|
|
||||||
_ => false // Non-flashing modes don't need periodic updates
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -195,3 +203,34 @@ where
|
|||||||
self.set_mode(mode, 0);
|
self.set_mode(mode, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(all(test, feature = "std"))]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn idle_mode_selected_when_usb_idle() {
|
||||||
|
let state = SystemState {
|
||||||
|
usb_active: true,
|
||||||
|
idle_mode: true,
|
||||||
|
calibration_active: false,
|
||||||
|
throttle_hold_enable: false,
|
||||||
|
vt_enable: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(determine_mode(state), StatusMode::Idle);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn calibration_has_priority_over_idle() {
|
||||||
|
let state = SystemState {
|
||||||
|
usb_active: true,
|
||||||
|
idle_mode: true,
|
||||||
|
calibration_active: true,
|
||||||
|
throttle_hold_enable: false,
|
||||||
|
vt_enable: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(determine_mode(state), StatusMode::ActivityFlash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -28,7 +28,7 @@ pub enum StorageError {
|
|||||||
/// Returns (min, max, center) values as u16.
|
/// Returns (min, max, center) values as u16.
|
||||||
pub fn read_axis_calibration(
|
pub fn read_axis_calibration(
|
||||||
read_byte_fn: &mut dyn FnMut(u32) -> Result<u8, ()>,
|
read_byte_fn: &mut dyn FnMut(u32) -> Result<u8, ()>,
|
||||||
axis_index: usize
|
axis_index: usize,
|
||||||
) -> Result<(u16, u16, u16), StorageError> {
|
) -> Result<(u16, u16, u16), StorageError> {
|
||||||
// Original format uses: base = axis_index * 6, addresses = base+1,2,3,4,5,6
|
// Original format uses: base = axis_index * 6, addresses = base+1,2,3,4,5,6
|
||||||
let base = axis_index as u32 * 6;
|
let base = axis_index as u32 * 6;
|
||||||
@ -42,10 +42,9 @@ pub fn read_axis_calibration(
|
|||||||
|
|
||||||
/// Read gimbal mode from EEPROM.
|
/// Read gimbal mode from EEPROM.
|
||||||
pub fn read_gimbal_mode(
|
pub fn read_gimbal_mode(
|
||||||
read_byte_fn: &mut dyn FnMut(u32) -> Result<u8, ()>
|
read_byte_fn: &mut dyn FnMut(u32) -> Result<u8, ()>,
|
||||||
) -> Result<u8, StorageError> {
|
) -> Result<u8, StorageError> {
|
||||||
read_byte_fn(GIMBAL_MODE_OFFSET)
|
read_byte_fn(GIMBAL_MODE_OFFSET).map_err(|_| StorageError::ReadError)
|
||||||
.map_err(|_| StorageError::ReadError)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write all calibration data and gimbal mode to EEPROM.
|
/// Write all calibration data and gimbal mode to EEPROM.
|
||||||
@ -53,7 +52,7 @@ pub fn read_gimbal_mode(
|
|||||||
pub fn write_calibration_data(
|
pub fn write_calibration_data(
|
||||||
write_page_fn: &mut dyn FnMut(u32, &[u8]) -> Result<(), ()>,
|
write_page_fn: &mut dyn FnMut(u32, &[u8]) -> Result<(), ()>,
|
||||||
axis_data: &[(u16, u16, u16)],
|
axis_data: &[(u16, u16, u16)],
|
||||||
gimbal_mode: u8
|
gimbal_mode: u8,
|
||||||
) -> Result<(), StorageError> {
|
) -> Result<(), StorageError> {
|
||||||
let mut eeprom_data: [u8; EEPROM_DATA_LENGTH] = [0; EEPROM_DATA_LENGTH];
|
let mut eeprom_data: [u8; EEPROM_DATA_LENGTH] = [0; EEPROM_DATA_LENGTH];
|
||||||
|
|
||||||
@ -73,28 +72,23 @@ pub fn write_calibration_data(
|
|||||||
eeprom_data[EEPROM_DATA_LENGTH - 1] = gimbal_mode;
|
eeprom_data[EEPROM_DATA_LENGTH - 1] = gimbal_mode;
|
||||||
|
|
||||||
// Write entire page to EEPROM
|
// Write entire page to EEPROM
|
||||||
write_page_fn(0x01, &eeprom_data)
|
write_page_fn(0x01, &eeprom_data).map_err(|_| StorageError::WriteError)
|
||||||
.map_err(|_| StorageError::WriteError)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ==================== HELPER FUNCTIONS ====================
|
// ==================== HELPER FUNCTIONS ====================
|
||||||
|
|
||||||
|
|
||||||
/// Read a u16 value from EEPROM in little‑endian (low then high byte) format.
|
/// Read a u16 value from EEPROM in little‑endian (low then high byte) format.
|
||||||
fn read_u16_with_closure(
|
fn read_u16_with_closure(
|
||||||
read_byte_fn: &mut dyn FnMut(u32) -> Result<u8, ()>,
|
read_byte_fn: &mut dyn FnMut(u32) -> Result<u8, ()>,
|
||||||
low_addr: u32,
|
low_addr: u32,
|
||||||
high_addr: u32
|
high_addr: u32,
|
||||||
) -> Result<u16, StorageError> {
|
) -> Result<u16, StorageError> {
|
||||||
let low_byte = read_byte_fn(low_addr)
|
let low_byte = read_byte_fn(low_addr).map_err(|_| StorageError::ReadError)? as u16;
|
||||||
.map_err(|_| StorageError::ReadError)? as u16;
|
let high_byte = read_byte_fn(high_addr).map_err(|_| StorageError::ReadError)? as u16;
|
||||||
let high_byte = read_byte_fn(high_addr)
|
|
||||||
.map_err(|_| StorageError::ReadError)? as u16;
|
|
||||||
|
|
||||||
Ok(low_byte | (high_byte << 8))
|
Ok(low_byte | (high_byte << 8))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ==================== TESTS ====================
|
// ==================== TESTS ====================
|
||||||
|
|
||||||
#[cfg(all(test, feature = "std"))]
|
#[cfg(all(test, feature = "std"))]
|
||||||
@ -147,7 +141,12 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_calibration_data_format() {
|
fn test_calibration_data_format() {
|
||||||
// Test that calibration data format matches original (addresses base+1,2,3,4,5,6)
|
// Test that calibration data format matches original (addresses base+1,2,3,4,5,6)
|
||||||
let test_data = [(100, 3900, 2000), (150, 3850, 2048), (200, 3800, 1900), (250, 3750, 2100)];
|
let test_data = [
|
||||||
|
(100, 3900, 2000),
|
||||||
|
(150, 3850, 2048),
|
||||||
|
(200, 3800, 1900),
|
||||||
|
(250, 3750, 2100),
|
||||||
|
];
|
||||||
let mut buffer = [0u8; EEPROM_DATA_LENGTH];
|
let mut buffer = [0u8; EEPROM_DATA_LENGTH];
|
||||||
|
|
||||||
// Pack data using original format
|
// Pack data using original format
|
||||||
|
|||||||
@ -182,12 +182,10 @@ pub struct JoystickConfig<'a> {
|
|||||||
impl Default for JoystickConfig<'_> {
|
impl Default for JoystickConfig<'_> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new(
|
Self::new(
|
||||||
unwrap!(
|
unwrap!(unwrap!(InterfaceBuilder::new(JOYSTICK_DESCRIPTOR))
|
||||||
unwrap!(InterfaceBuilder::new(JOYSTICK_DESCRIPTOR))
|
|
||||||
.boot_device(InterfaceProtocol::None)
|
.boot_device(InterfaceProtocol::None)
|
||||||
.description("Joystick")
|
.description("Joystick")
|
||||||
.in_endpoint(10.millis())
|
.in_endpoint(10.millis()))
|
||||||
)
|
|
||||||
.without_out_endpoint()
|
.without_out_endpoint()
|
||||||
.build(),
|
.build(),
|
||||||
)
|
)
|
||||||
|
|||||||
@ -4,10 +4,13 @@
|
|||||||
//! that matches the HID descriptor defined in `usb_joystick_device.rs`.
|
//! that matches the HID descriptor defined in `usb_joystick_device.rs`.
|
||||||
//! Also contains support for virtual throttle mode and HAT directions.
|
//! Also contains support for virtual throttle mode and HAT directions.
|
||||||
|
|
||||||
use crate::axis::{GimbalAxis, remap, GIMBAL_AXIS_LEFT_X, GIMBAL_AXIS_LEFT_Y, GIMBAL_AXIS_RIGHT_X, GIMBAL_AXIS_RIGHT_Y};
|
use crate::axis::{
|
||||||
|
remap, GimbalAxis, GIMBAL_AXIS_LEFT_X, GIMBAL_AXIS_LEFT_Y, GIMBAL_AXIS_RIGHT_X,
|
||||||
|
GIMBAL_AXIS_RIGHT_Y,
|
||||||
|
};
|
||||||
use crate::buttons::{Button, TOTAL_BUTTONS};
|
use crate::buttons::{Button, TOTAL_BUTTONS};
|
||||||
use crate::mapping::{USB_HAT_UP, USB_HAT_LEFT};
|
use crate::hardware::{ADC_MAX, ADC_MIN, AXIS_CENTER};
|
||||||
use crate::hardware::{ADC_MIN, ADC_MAX, AXIS_CENTER};
|
use crate::mapping::{USB_HAT_LEFT, USB_HAT_UP};
|
||||||
use crate::usb_joystick_device::JoystickReport;
|
use crate::usb_joystick_device::JoystickReport;
|
||||||
|
|
||||||
// ==================== USB REPORT GENERATION ====================
|
// ==================== USB REPORT GENERATION ====================
|
||||||
@ -130,7 +133,6 @@ pub fn get_joystick_report(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// ==================== TESTS ====================
|
// ==================== TESTS ====================
|
||||||
|
|
||||||
#[cfg(all(test, feature = "std"))]
|
#[cfg(all(test, feature = "std"))]
|
||||||
@ -195,7 +197,8 @@ mod tests {
|
|||||||
let virtual_rz = 2000;
|
let virtual_rz = 2000;
|
||||||
let vt_enable = false;
|
let vt_enable = false;
|
||||||
|
|
||||||
let report = get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable);
|
let report =
|
||||||
|
get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable);
|
||||||
|
|
||||||
// Verify axis conversions
|
// Verify axis conversions
|
||||||
assert_eq!(report.x, axis_12bit_to_i16(2048));
|
assert_eq!(report.x, axis_12bit_to_i16(2048));
|
||||||
@ -221,7 +224,8 @@ mod tests {
|
|||||||
let virtual_rz = 0;
|
let virtual_rz = 0;
|
||||||
let vt_enable = true;
|
let vt_enable = true;
|
||||||
|
|
||||||
let report = get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable);
|
let report =
|
||||||
|
get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable);
|
||||||
|
|
||||||
// In VT mode, z should be 0
|
// In VT mode, z should be 0
|
||||||
assert_eq!(report.z, 0);
|
assert_eq!(report.z, 0);
|
||||||
@ -243,7 +247,8 @@ mod tests {
|
|||||||
let virtual_rz = 0;
|
let virtual_rz = 0;
|
||||||
let vt_enable = true;
|
let vt_enable = true;
|
||||||
|
|
||||||
let report = get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable);
|
let report =
|
||||||
|
get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable);
|
||||||
|
|
||||||
// In VT mode, z should be 0
|
// In VT mode, z should be 0
|
||||||
assert_eq!(report.z, 0);
|
assert_eq!(report.z, 0);
|
||||||
@ -269,7 +274,8 @@ mod tests {
|
|||||||
let virtual_rz = 0;
|
let virtual_rz = 0;
|
||||||
let vt_enable = false;
|
let vt_enable = false;
|
||||||
|
|
||||||
let report = get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable);
|
let report =
|
||||||
|
get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable);
|
||||||
|
|
||||||
// Check button bits are set correctly
|
// Check button bits are set correctly
|
||||||
assert_eq!(report.buttons & (1 << 0), 1 << 0); // Button 1
|
assert_eq!(report.buttons & (1 << 0), 1 << 0); // Button 1
|
||||||
@ -290,7 +296,8 @@ mod tests {
|
|||||||
let virtual_rz = 0;
|
let virtual_rz = 0;
|
||||||
let vt_enable = false;
|
let vt_enable = false;
|
||||||
|
|
||||||
let report = get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable);
|
let report =
|
||||||
|
get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable);
|
||||||
|
|
||||||
// Check HAT direction is set correctly
|
// Check HAT direction is set correctly
|
||||||
let expected_hat = (USB_HAT_UP as u8 - USB_HAT_UP as u8) * 2; // Should be 0 (up)
|
let expected_hat = (USB_HAT_UP as u8 - USB_HAT_UP as u8) * 2; // Should be 0 (up)
|
||||||
@ -311,7 +318,8 @@ mod tests {
|
|||||||
let virtual_rz = 0;
|
let virtual_rz = 0;
|
||||||
let vt_enable = false;
|
let vt_enable = false;
|
||||||
|
|
||||||
let report = get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable);
|
let report =
|
||||||
|
get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable);
|
||||||
|
|
||||||
// Check long press button is handled
|
// Check long press button is handled
|
||||||
assert_eq!(report.buttons & (1 << 2), 1 << 2); // Button 3
|
assert_eq!(report.buttons & (1 << 2), 1 << 2); // Button 3
|
||||||
@ -350,7 +358,8 @@ mod tests {
|
|||||||
let virtual_rz = 0;
|
let virtual_rz = 0;
|
||||||
let vt_enable = false;
|
let vt_enable = false;
|
||||||
|
|
||||||
let report = get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable);
|
let report =
|
||||||
|
get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable);
|
||||||
|
|
||||||
// Check HAT left direction
|
// Check HAT left direction
|
||||||
let expected_hat = (USB_HAT_LEFT as u8 - USB_HAT_UP as u8) * 2;
|
let expected_hat = (USB_HAT_LEFT as u8 - USB_HAT_UP as u8) * 2;
|
||||||
@ -374,7 +383,8 @@ mod tests {
|
|||||||
let virtual_rz = 0;
|
let virtual_rz = 0;
|
||||||
let vt_enable = false;
|
let vt_enable = false;
|
||||||
|
|
||||||
let report = get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable);
|
let report =
|
||||||
|
get_joystick_report(&mut buttons, &mut axes, virtual_ry, virtual_rz, &vt_enable);
|
||||||
|
|
||||||
// Check both regular buttons and HAT
|
// Check both regular buttons and HAT
|
||||||
assert_eq!(report.buttons & (1 << 0), 1 << 0); // Button 1
|
assert_eq!(report.buttons & (1 << 0), 1 << 0); // Button 1
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user