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)
|
- Activity colors: green (active), blue (virtual throttle/calibration), orange (holds)
|
||||||
- Warning/error tones (red) and bootloader purple
|
- Warning/error tones (red) and bootloader purple
|
||||||
- Idle heartbeat flashes at half speed once inputs settle
|
- 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
|
## Low-latency firmware path
|
||||||
|
|
||||||
|
|||||||
@ -63,6 +63,7 @@ use rp2040_hal::{
|
|||||||
use status::{StatusLed, StatusMode, SystemState};
|
use status::{StatusLed, StatusMode, SystemState};
|
||||||
use usb_device::class_prelude::*;
|
use usb_device::class_prelude::*;
|
||||||
use usb_device::prelude::*;
|
use usb_device::prelude::*;
|
||||||
|
use usb_device::device::UsbDeviceState;
|
||||||
use usb_joystick_device::JoystickConfig;
|
use usb_joystick_device::JoystickConfig;
|
||||||
use usb_report::get_joystick_report;
|
use usb_report::get_joystick_report;
|
||||||
use usbd_human_interface_device::prelude::*;
|
use usbd_human_interface_device::prelude::*;
|
||||||
@ -230,10 +231,12 @@ 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 usb_initialized: bool = false;
|
let mut usb_initialized: bool = false;
|
||||||
|
let mut usb_suspended: bool = false;
|
||||||
let mut usb_send_pending: bool = false;
|
let mut usb_send_pending: bool = false;
|
||||||
let mut vt_enable: bool = false;
|
let mut vt_enable: bool = false;
|
||||||
let mut idle_mode: bool = false;
|
let mut idle_mode: bool = false;
|
||||||
let mut usb_activity_timeout_count: u32 = 0;
|
let mut usb_activity_timeout_count: u32 = 0;
|
||||||
|
let mut wake_on_input: bool = false;
|
||||||
|
|
||||||
let mut axis_manager = AxisManager::new();
|
let mut axis_manager = AxisManager::new();
|
||||||
let mut button_manager = ButtonManager::new();
|
let mut button_manager = ButtonManager::new();
|
||||||
@ -298,7 +301,47 @@ fn main() -> ! {
|
|||||||
usb_active = true;
|
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)
|
// ## High-Frequency Input Sampling (~5 kHz)
|
||||||
//
|
//
|
||||||
// Sample all inputs at high frequency for responsive control:
|
// 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
|
usb_activity_timeout_count = 0; // Reset timeout on real input activity
|
||||||
idle_mode = false;
|
idle_mode = false;
|
||||||
usb_send_pending = true;
|
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
|
// 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
|
usb_activity_timeout_count = 0; // Reset timeout on real input activity
|
||||||
idle_mode = false;
|
idle_mode = false;
|
||||||
usb_send_pending = true;
|
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)
|
// 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
|
usb_activity_timeout_count = 0; // Reset timeout on real input activity
|
||||||
idle_mode = false;
|
idle_mode = false;
|
||||||
usb_send_pending = true;
|
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 {
|
let system_state = SystemState {
|
||||||
usb_active,
|
usb_active,
|
||||||
usb_initialized,
|
usb_initialized,
|
||||||
|
usb_suspended,
|
||||||
idle_mode,
|
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,
|
||||||
@ -452,9 +514,9 @@ fn main() -> ! {
|
|||||||
// - 8-direction HAT switch state
|
// - 8-direction HAT switch state
|
||||||
// - 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 and not suspended
|
||||||
let usb_tick = usb_update_count_down.wait().is_ok();
|
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 mut send_report = || {
|
||||||
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);
|
||||||
@ -489,7 +551,8 @@ fn main() -> ! {
|
|||||||
} else {
|
} else {
|
||||||
send_report();
|
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;
|
idle_mode = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -34,6 +34,7 @@ pub enum StatusMode {
|
|||||||
Error = 9,
|
Error = 9,
|
||||||
Bootloader = 10,
|
Bootloader = 10,
|
||||||
Power = 11,
|
Power = 11,
|
||||||
|
Suspended = 12,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Aggregate system state for LED status indication.
|
/// Aggregate system state for LED status indication.
|
||||||
@ -41,6 +42,7 @@ pub enum StatusMode {
|
|||||||
pub struct SystemState {
|
pub struct SystemState {
|
||||||
pub usb_active: bool,
|
pub usb_active: bool,
|
||||||
pub usb_initialized: bool,
|
pub usb_initialized: bool,
|
||||||
|
pub usb_suspended: bool,
|
||||||
pub idle_mode: bool,
|
pub idle_mode: bool,
|
||||||
pub calibration_active: bool,
|
pub calibration_active: bool,
|
||||||
pub throttle_hold_enable: 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,
|
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 {
|
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
|
StatusMode::ActivityFlash
|
||||||
} else if !system_state.usb_initialized {
|
} else if !system_state.usb_initialized {
|
||||||
StatusMode::Power
|
StatusMode::Power
|
||||||
@ -349,6 +357,7 @@ mod tests {
|
|||||||
let state = SystemState {
|
let state = SystemState {
|
||||||
usb_active: false,
|
usb_active: false,
|
||||||
usb_initialized: true,
|
usb_initialized: true,
|
||||||
|
usb_suspended: false,
|
||||||
idle_mode: true,
|
idle_mode: true,
|
||||||
calibration_active: false,
|
calibration_active: false,
|
||||||
throttle_hold_enable: false,
|
throttle_hold_enable: false,
|
||||||
@ -381,6 +390,7 @@ mod tests {
|
|||||||
let state = SystemState {
|
let state = SystemState {
|
||||||
usb_active: true,
|
usb_active: true,
|
||||||
usb_initialized: true,
|
usb_initialized: true,
|
||||||
|
usb_suspended: false,
|
||||||
idle_mode: true,
|
idle_mode: true,
|
||||||
calibration_active: true,
|
calibration_active: true,
|
||||||
throttle_hold_enable: false,
|
throttle_hold_enable: false,
|
||||||
@ -428,6 +438,7 @@ mod tests {
|
|||||||
let state = SystemState {
|
let state = SystemState {
|
||||||
usb_active: false,
|
usb_active: false,
|
||||||
usb_initialized: false,
|
usb_initialized: false,
|
||||||
|
usb_suspended: false,
|
||||||
idle_mode: false,
|
idle_mode: false,
|
||||||
calibration_active: false,
|
calibration_active: false,
|
||||||
throttle_hold_enable: false,
|
throttle_hold_enable: false,
|
||||||
@ -436,4 +447,19 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(determine_base_mode(state), StatusMode::Power);
|
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