Added USB suspend support

This commit is contained in:
Christoffer Martinsson 2025-09-18 11:54:26 +02:00
parent fa841fda65
commit a629a3e94d
3 changed files with 100 additions and 5 deletions

View File

@ -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

View File

@ -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;
} }
} }

View File

@ -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);
}
} }