90 lines
3.8 KiB
Rust
90 lines
3.8 KiB
Rust
use embassy_time::{Duration, Instant, Timer};
|
|
use esp_hal::{gpio::AnyPin, rmt::Rmt, time::Rate, timer::timg::Wdt};
|
|
use esp_hal_smartled::{buffer_size_async, SmartLedsAdapterAsync};
|
|
use figments::{prelude::*, surface::Surfaces};
|
|
use figments_render::gamma::GammaCurve;
|
|
use figments_render::output::{GammaCorrected, OutputAsync};
|
|
use log::{info, warn};
|
|
use micromath::F32Ext;
|
|
|
|
use crate::display::NUM_PIXELS;
|
|
use crate::{display::{BikeOutput, DisplayControls, Uniforms}, tasks::ui::UiSurfacePool};
|
|
|
|
#[embassy_executor::task]
|
|
pub async fn render(rmt: esp_hal::peripherals::RMT<'static>, gpio: AnyPin<'static>, surfaces: UiSurfacePool, safety_surfaces: UiSurfacePool, mut controls: DisplayControls, mut wdt: Wdt<esp_hal::peripherals::TIMG0<'static>>) {
|
|
let frequency: Rate = Rate::from_mhz(80);
|
|
let rmt = Rmt::new(rmt, frequency)
|
|
.expect("Failed to initialize RMT").into_async();
|
|
let rmt_channel = rmt.channel0;
|
|
|
|
let rmt_buffer = [0u32; buffer_size_async(NUM_PIXELS)];
|
|
|
|
//let target = SmartLedsAdapterAsync::new(rmt_channel, gpio, rmt_buffer);
|
|
let target = SmartLedsAdapterAsync::new(rmt_channel, gpio, rmt_buffer);
|
|
|
|
// Change this to adjust the power available; the USB spec says 500ma is the standard limit, but sometimes you can draw more from a power brick
|
|
const POWER_MA : u32 = 500;
|
|
|
|
// You probably don't need to change these values, unless your LED strip is somehow not 5 volts
|
|
const POWER_VOLTS : u32 = 5;
|
|
|
|
const MAX_POWER_MW : u32 = if cfg!(feature="max-usb-power") { u32::MAX } else { POWER_VOLTS * POWER_MA };
|
|
|
|
// This value is used as the 'seed' for rendering each frame, allowing us to do things like run the animation backwards, frames for double FPS, or even use system uptime for more human-paced animations
|
|
let mut uniforms = Uniforms {
|
|
primary_color: Hsv::new(210, 255, 255),
|
|
..Uniforms::default()
|
|
};
|
|
|
|
let mut output = BikeOutput::new(target, MAX_POWER_MW, controls.clone());
|
|
output.set_gamma(GammaCurve::new(1.3));
|
|
|
|
info!("Rendering started! {}ms since boot", Instant::now().as_millis());
|
|
controls.notify_render_is_running(true);
|
|
|
|
const FPS: u64 = 80;
|
|
const RENDER_BUDGET: Duration = Duration::from_millis(1000 / FPS);
|
|
|
|
loop {
|
|
// FIXME: need to put the rendering loop into a deep sleep when the display is off
|
|
let start = Instant::now();
|
|
|
|
output.blank();
|
|
|
|
surfaces.render_to(&mut output, &uniforms);
|
|
// TODO: We should split up the safety layers so they always have full power
|
|
safety_surfaces.render_to(&mut output, &uniforms);
|
|
|
|
// Finally, write out the rendered frame
|
|
output.commit_async().await.expect("Failed to commit frame");
|
|
|
|
let render_duration = Instant::now() - start;
|
|
|
|
if !controls.is_on() {
|
|
warn!("Renderer is sleeping zzzz");
|
|
controls.notify_render_is_running(false);
|
|
output.blank();
|
|
wdt.disable();
|
|
controls.wait_until_display_is_on().await;
|
|
wdt.feed();
|
|
wdt.enable();
|
|
warn!("Renderer is awake !!!!");
|
|
controls.notify_render_is_running(true);
|
|
}
|
|
|
|
if render_duration < RENDER_BUDGET {
|
|
let remaining_budget = RENDER_BUDGET - render_duration;
|
|
uniforms.frame += 1;
|
|
Timer::after(remaining_budget).await;
|
|
} else {
|
|
let dropped_count = (render_duration.as_ticks() as f32 / RENDER_BUDGET.as_ticks() as f32).ceil() as usize;
|
|
warn!("Render stall! Frame took {}ms, dropping {dropped_count} frame(s)", render_duration.as_millis());
|
|
// If we took longer than the budget, we need to drop some frames to catch up
|
|
uniforms.frame += dropped_count;
|
|
}
|
|
|
|
uniforms.primary_color.hue = uniforms.primary_color.hue.wrapping_add(1);
|
|
|
|
wdt.feed();
|
|
}
|
|
} |