Added USB suspend support
This commit is contained in:
parent
fa841fda65
commit
a629a3e94d
@ -85,6 +85,12 @@ Config Layer (holding CONFIG button)
|
||||
- Activity colors: green (active), blue (virtual throttle/calibration), orange (holds)
|
||||
- Warning/error tones (red) and bootloader purple
|
||||
- Idle heartbeat flashes at half speed once inputs settle
|
||||
- LED turns off during USB suspend for power savings
|
||||
- Power management for USB suspend/resume
|
||||
- Automatic power saving when USB host suspends device
|
||||
- Reduced input scanning frequency (10x slower) during suspend
|
||||
- Wake-on-input detection for gimbals and buttons
|
||||
- Immediate resume response when inputs detected
|
||||
|
||||
## Low-latency firmware path
|
||||
|
||||
|
||||
@ -63,6 +63,7 @@ use rp2040_hal::{
|
||||
use status::{StatusLed, StatusMode, SystemState};
|
||||
use usb_device::class_prelude::*;
|
||||
use usb_device::prelude::*;
|
||||
use usb_device::device::UsbDeviceState;
|
||||
use usb_joystick_device::JoystickConfig;
|
||||
use usb_report::get_joystick_report;
|
||||
use usbd_human_interface_device::prelude::*;
|
||||
@ -230,10 +231,12 @@ fn main() -> ! {
|
||||
let mut usb_activity: bool = false;
|
||||
let mut usb_active: bool = false;
|
||||
let mut usb_initialized: bool = false;
|
||||
let mut usb_suspended: bool = false;
|
||||
let mut usb_send_pending: bool = false;
|
||||
let mut vt_enable: bool = false;
|
||||
let mut idle_mode: bool = false;
|
||||
let mut usb_activity_timeout_count: u32 = 0;
|
||||
let mut wake_on_input: bool = false;
|
||||
|
||||
let mut axis_manager = AxisManager::new();
|
||||
let mut button_manager = ButtonManager::new();
|
||||
@ -298,7 +301,47 @@ fn main() -> ! {
|
||||
usb_active = true;
|
||||
}
|
||||
|
||||
if scan_count_down.wait().is_ok() {
|
||||
// Check USB device state for suspend/resume handling
|
||||
let usb_state = usb_dev.state();
|
||||
let was_suspended = usb_suspended;
|
||||
usb_suspended = usb_state == UsbDeviceState::Suspend;
|
||||
|
||||
// Handle USB resume transition
|
||||
if was_suspended && !usb_suspended {
|
||||
// Device was suspended and is now resumed
|
||||
usb_activity = true;
|
||||
idle_mode = false;
|
||||
usb_activity_timeout_count = 0;
|
||||
usb_send_pending = true;
|
||||
wake_on_input = false;
|
||||
}
|
||||
|
||||
// Handle USB suspend transition
|
||||
if !was_suspended && usb_suspended {
|
||||
// Device has just been suspended - enter power saving mode
|
||||
idle_mode = true;
|
||||
usb_activity = false;
|
||||
usb_send_pending = false;
|
||||
wake_on_input = true;
|
||||
|
||||
// Reduce LED update frequency to save power when suspended
|
||||
// LED will be off anyway (Suspended mode), so slow updates are fine
|
||||
}
|
||||
|
||||
// Skip high-frequency scanning when suspended to save power
|
||||
// Only scan periodically to detect wake-up inputs
|
||||
let should_scan = if usb_suspended {
|
||||
// When suspended, reduce scan frequency by factor of 10 (every ~2ms instead of 200μs)
|
||||
static mut SUSPENDED_SCAN_COUNTER: u8 = 0;
|
||||
unsafe {
|
||||
SUSPENDED_SCAN_COUNTER = (SUSPENDED_SCAN_COUNTER + 1) % 10;
|
||||
SUSPENDED_SCAN_COUNTER == 0
|
||||
}
|
||||
} else {
|
||||
true
|
||||
};
|
||||
|
||||
if scan_count_down.wait().is_ok() && should_scan {
|
||||
// ## High-Frequency Input Sampling (~5 kHz)
|
||||
//
|
||||
// Sample all inputs at high frequency for responsive control:
|
||||
@ -398,6 +441,12 @@ fn main() -> ! {
|
||||
usb_activity_timeout_count = 0; // Reset timeout on real input activity
|
||||
idle_mode = false;
|
||||
usb_send_pending = true;
|
||||
|
||||
// Wake from USB suspend if input detected
|
||||
if wake_on_input && usb_suspended {
|
||||
// TODO: Implement remote wakeup if supported by host
|
||||
wake_on_input = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Update virtual axes based on front button states
|
||||
@ -406,6 +455,12 @@ fn main() -> ! {
|
||||
usb_activity_timeout_count = 0; // Reset timeout on real input activity
|
||||
idle_mode = false;
|
||||
usb_send_pending = true;
|
||||
|
||||
// Wake from USB suspend if input detected
|
||||
if wake_on_input && usb_suspended {
|
||||
// TODO: Implement remote wakeup if supported by host
|
||||
wake_on_input = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Process button logic (press types, timing, USB mapping)
|
||||
@ -414,6 +469,12 @@ fn main() -> ! {
|
||||
usb_activity_timeout_count = 0; // Reset timeout on real input activity
|
||||
idle_mode = false;
|
||||
usb_send_pending = true;
|
||||
|
||||
// Wake from USB suspend if input detected
|
||||
if wake_on_input && usb_suspended {
|
||||
// TODO: Implement remote wakeup if supported by host
|
||||
wake_on_input = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -430,6 +491,7 @@ fn main() -> ! {
|
||||
let system_state = SystemState {
|
||||
usb_active,
|
||||
usb_initialized,
|
||||
usb_suspended,
|
||||
idle_mode,
|
||||
calibration_active: calibration_manager.is_active(),
|
||||
throttle_hold_enable: axis_manager.throttle_hold_enable,
|
||||
@ -452,9 +514,9 @@ fn main() -> ! {
|
||||
// - 8-direction HAT switch state
|
||||
// - Virtual throttle mode handling
|
||||
|
||||
// Only transmit USB reports when input activity is detected
|
||||
// Only transmit USB reports when input activity is detected and not suspended
|
||||
let usb_tick = usb_update_count_down.wait().is_ok();
|
||||
if usb_activity && (usb_tick || usb_send_pending) {
|
||||
if usb_activity && (usb_tick || usb_send_pending) && !usb_suspended {
|
||||
let mut send_report = || {
|
||||
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);
|
||||
@ -489,7 +551,8 @@ fn main() -> ! {
|
||||
} else {
|
||||
send_report();
|
||||
}
|
||||
} else if usb_tick && usb_active {
|
||||
} else if usb_tick && usb_active && !usb_suspended {
|
||||
// Only update idle mode for non-suspended devices
|
||||
idle_mode = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -34,6 +34,7 @@ pub enum StatusMode {
|
||||
Error = 9,
|
||||
Bootloader = 10,
|
||||
Power = 11,
|
||||
Suspended = 12,
|
||||
}
|
||||
|
||||
/// Aggregate system state for LED status indication.
|
||||
@ -41,6 +42,7 @@ pub enum StatusMode {
|
||||
pub struct SystemState {
|
||||
pub usb_active: bool,
|
||||
pub usb_initialized: bool,
|
||||
pub usb_suspended: bool,
|
||||
pub idle_mode: bool,
|
||||
pub calibration_active: bool,
|
||||
pub throttle_hold_enable: bool,
|
||||
@ -187,6 +189,10 @@ const fn descriptor_for(mode: StatusMode, base_mode: StatusMode) -> ModeDescript
|
||||
period_ms: HEARTBEAT_POWER_MS,
|
||||
},
|
||||
},
|
||||
StatusMode::Suspended => ModeDescriptor {
|
||||
color: COLOR_OFF,
|
||||
effect: LedEffect::Solid,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@ -200,7 +206,9 @@ fn scale_color(base: RGB8, brightness: u8) -> RGB8 {
|
||||
}
|
||||
|
||||
fn determine_base_mode(system_state: SystemState) -> StatusMode {
|
||||
if system_state.calibration_active {
|
||||
if system_state.usb_suspended {
|
||||
StatusMode::Suspended
|
||||
} else if system_state.calibration_active {
|
||||
StatusMode::ActivityFlash
|
||||
} else if !system_state.usb_initialized {
|
||||
StatusMode::Power
|
||||
@ -349,6 +357,7 @@ mod tests {
|
||||
let state = SystemState {
|
||||
usb_active: false,
|
||||
usb_initialized: true,
|
||||
usb_suspended: false,
|
||||
idle_mode: true,
|
||||
calibration_active: false,
|
||||
throttle_hold_enable: false,
|
||||
@ -381,6 +390,7 @@ mod tests {
|
||||
let state = SystemState {
|
||||
usb_active: true,
|
||||
usb_initialized: true,
|
||||
usb_suspended: false,
|
||||
idle_mode: true,
|
||||
calibration_active: true,
|
||||
throttle_hold_enable: false,
|
||||
@ -428,6 +438,7 @@ mod tests {
|
||||
let state = SystemState {
|
||||
usb_active: false,
|
||||
usb_initialized: false,
|
||||
usb_suspended: false,
|
||||
idle_mode: false,
|
||||
calibration_active: false,
|
||||
throttle_hold_enable: false,
|
||||
@ -436,4 +447,19 @@ mod tests {
|
||||
|
||||
assert_eq!(determine_base_mode(state), StatusMode::Power);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usb_suspend_takes_priority() {
|
||||
let state = SystemState {
|
||||
usb_active: true,
|
||||
usb_initialized: true,
|
||||
usb_suspended: true,
|
||||
idle_mode: false,
|
||||
calibration_active: true, // Even with calibration active
|
||||
throttle_hold_enable: false,
|
||||
vt_enable: false,
|
||||
};
|
||||
|
||||
assert_eq!(determine_base_mode(state), StatusMode::Suspended);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user