display: implement power scaling into the display APIs

This commit is contained in:
2026-03-14 12:02:15 +01:00
parent 3f87e22585
commit 2ad22a2632
4 changed files with 26 additions and 9 deletions

View File

@@ -3,6 +3,7 @@ use embassy_time::Duration;
use enum_map::Enum; use enum_map::Enum;
use enumset::EnumSetType; use enumset::EnumSetType;
use figments::liber8tion::interpolate::Fract8; use figments::liber8tion::interpolate::Fract8;
use figments_render::power::Milliwatts;
use nalgebra::{Vector2, Vector3}; use nalgebra::{Vector2, Vector3};
use crate::ego::engine::MotionState; use crate::ego::engine::MotionState;
@@ -38,6 +39,8 @@ pub enum Measurement {
GPS(Option<Vector2<f64>>), GPS(Option<Vector2<f64>>),
// Accelerometer values in body frame where x=forwards // Accelerometer values in body frame where x=forwards
IMU { accel: Vector3<f32>, gyro: Vector3<f32> }, IMU { accel: Vector3<f32>, gyro: Vector3<f32> },
// Power status
ExternalPower(Milliwatts),
// Hardware status updates // Hardware status updates
SensorHardwareStatus(SensorSource, SensorState), SensorHardwareStatus(SensorSource, SensorState),
@@ -73,6 +76,7 @@ pub enum SensorSource {
// Real hardware // Real hardware
IMU, IMU,
GPS, GPS,
ExternalPower,
// Connectivity related // Connectivity related
Wifi, Wifi,

View File

@@ -1,10 +1,11 @@
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal, watch::{Receiver, Watch}}; use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, signal::Signal, watch::{Receiver, Watch}};
use figments::{liber8tion::interpolate::Fract8, prelude::*}; use figments::{liber8tion::interpolate::Fract8, prelude::*};
use portable_atomic::AtomicU32;
use core::{fmt::Debug, ops::Mul, sync::atomic::{AtomicBool, AtomicU8}}; use core::{fmt::Debug, ops::Mul, sync::atomic::{AtomicBool, AtomicU8}};
use alloc::sync::Arc; use alloc::sync::Arc;
use figments_render::{ use figments_render::{
gamma::{GammaCurve, WithGamma}, output::{Brightness, GammaCorrected, Output, OutputAsync}, power::AsMilliwatts, smart_leds::PowerManagedWriter gamma::{GammaCurve, WithGamma}, output::{Brightness, GammaCorrected, Output, OutputAsync}, power::{AsMilliwatts, Milliwatts}, smart_leds::PowerManagedWriter
}; };
use smart_leds::{SmartLedsWrite, SmartLedsWriteAsync}; use smart_leds::{SmartLedsWrite, SmartLedsWriteAsync};
@@ -23,10 +24,10 @@ impl<T, Color> GammaCorrected for BikeOutput<T, Color> {
} }
impl<T, Color: Default + Copy> BikeOutput<T, Color> { impl<T, Color: Default + Copy> BikeOutput<T, Color> {
pub fn new(target: T, max_mw: u32, controls: DisplayControls) -> Self { pub fn new(target: T, controls: DisplayControls) -> Self {
Self { Self {
pixbuf: [Default::default(); NUM_PIXELS], pixbuf: [Default::default(); NUM_PIXELS],
writer: PowerManagedWriter::new(target, max_mw), writer: PowerManagedWriter::new(target, controls.max_power()),
controls controls
} }
} }
@@ -44,6 +45,7 @@ impl<'a, T: SmartLedsWrite + 'a> Output<'a, SegmentSpace> for BikeOutput<T, T::C
fn commit(&mut self) -> Result<(), Self::Error> { fn commit(&mut self) -> Result<(), Self::Error> {
self.writer.controls().set_brightness(self.controls.brightness()); self.writer.controls().set_brightness(self.controls.brightness());
self.writer.controls().set_on(self.controls.is_on()); self.writer.controls().set_on(self.controls.is_on());
self.writer.controls().set_max_power(self.controls.max_power());
critical_section::with(|_| { critical_section::with(|_| {
self.writer.write(&self.pixbuf) self.writer.write(&self.pixbuf)
}) })
@@ -165,7 +167,8 @@ pub const LOW_POWER_FPS: u8 = 16;
struct ControlData { struct ControlData {
on: AtomicBool, on: AtomicBool,
brightness: AtomicU8, brightness: AtomicU8,
fps: AtomicU8 fps: AtomicU8,
max_power_mw: AtomicU32
} }
impl Default for ControlData { impl Default for ControlData {
@@ -173,7 +176,8 @@ impl Default for ControlData {
Self { Self {
on: AtomicBool::new(true), on: AtomicBool::new(true),
brightness: AtomicU8::new(255), brightness: AtomicU8::new(255),
fps: AtomicU8::new(DEFAULT_FPS) fps: AtomicU8::new(DEFAULT_FPS),
max_power_mw: AtomicU32::new(0)
} }
} }
} }
@@ -229,6 +233,14 @@ impl DisplayControls {
while !self.render_run_receiver.changed().await { log::info!("wait for render run") } while !self.render_run_receiver.changed().await { log::info!("wait for render run") }
log::trace!("render says run!"); log::trace!("render says run!");
} }
pub fn set_max_power(&mut self, mw: Milliwatts) {
self.data.max_power_mw.store(mw.0, core::sync::atomic::Ordering::Relaxed);
}
pub fn max_power(&self) -> Milliwatts {
Milliwatts(self.data.max_power_mw.load(core::sync::atomic::Ordering::Relaxed))
}
} }
impl GammaCorrected for DisplayControls { impl GammaCorrected for DisplayControls {
@@ -256,6 +268,7 @@ impl core::fmt::Debug for DisplayControls {
.field("brightness", &self.data.brightness) .field("brightness", &self.data.brightness)
.field("fps", &self.data.fps) .field("fps", &self.data.fps)
.field("render_pause_signaled", &self.display_is_on.signaled()) .field("render_pause_signaled", &self.display_is_on.signaled())
.field("max_power_mw", &self.data.max_power_mw)
.finish() .finish()
} }
} }

View File

@@ -31,6 +31,9 @@ pub async fn motion_task(src: DynamicReceiver<'static, Measurement>, prediction_
warn!("Sensor {source:?} reports {state:?}!"); warn!("Sensor {source:?} reports {state:?}!");
prediction_sink.publish(Prediction::SensorStatus(source, state)).with_timeout(TIMEOUT).await.expect("Could not update sensor status in time. Is the prediction bus stalled?"); prediction_sink.publish(Prediction::SensorStatus(source, state)).with_timeout(TIMEOUT).await.expect("Could not update sensor status in time. Is the prediction bus stalled?");
}, },
Measurement::ExternalPower(mw) => {
info!("Got external power change to {mw:?}");
},
Measurement::SimulationProgress(source, duration, pct) => debug!("{source:?} simulation time: {} {} / 255", duration.as_secs(), pct), Measurement::SimulationProgress(source, duration, pct) => debug!("{source:?} simulation time: {} {} / 255", duration.as_secs(), pct),
Measurement::Annotation => () Measurement::Annotation => ()
} }

View File

@@ -12,9 +12,6 @@ use log::*;
use crate::graphics::display::NUM_PIXELS; use crate::graphics::display::NUM_PIXELS;
use crate::{graphics::display::{BikeOutput, DisplayControls, Uniforms}, tasks::ui::UiSurfacePool}; use crate::{graphics::display::{BikeOutput, DisplayControls, Uniforms}, tasks::ui::UiSurfacePool};
const POWER_MA : u32 = 300;
const POWER_VOLTS : u32 = 5;
const MAX_POWER_MW : u32 = POWER_VOLTS * POWER_MA;
static SPI_BUFFERS: static_cell::ConstStaticCell<DmaBuffers<u8, {NUM_PIXELS * 12 + 140}>> = static_cell::ConstStaticCell::new(DmaBuffers::new(0)); static SPI_BUFFERS: static_cell::ConstStaticCell<DmaBuffers<u8, {NUM_PIXELS * 12 + 140}>> = static_cell::ConstStaticCell::new(DmaBuffers::new(0));
#[embassy_executor::task] #[embassy_executor::task]
@@ -40,7 +37,7 @@ pub async fn render(spi: AnySpi<'static>, dma: AnyGdmaChannel<'static>, gpio: An
..Uniforms::default() ..Uniforms::default()
}; };
let mut output = BikeOutput::new(target, MAX_POWER_MW, controls.clone()); let mut output = BikeOutput::new(target, controls.clone());
output.set_gamma(GammaCurve::new(1.3)); output.set_gamma(GammaCurve::new(1.3));
info!("Rendering started! {}ms since boot", Instant::now().as_millis()); info!("Rendering started! {}ms since boot", Instant::now().as_millis());