more refactor

This commit is contained in:
2026-09-09 12:52:13 -05:00
parent 05803ad866
commit 0116ce97f8
6 changed files with 126 additions and 103 deletions
Generated
+1
View File
@@ -964,6 +964,7 @@ dependencies = [
"mipidsi", "mipidsi",
"smart-leds", "smart-leds",
"smart-leds-trait", "smart-leds-trait",
"static_cell",
] ]
[[package]] [[package]]
+1
View File
@@ -18,6 +18,7 @@ esp-rtos = { version = "0.4", features = ["embassy", "esp32c6"] }
mipidsi = "0.10" mipidsi = "0.10"
smart-leds = "0.4" smart-leds = "0.4"
smart-leds-trait = "0.3" smart-leds-trait = "0.3"
static_cell = { version = "2.1.1" }
[profile.dev] [profile.dev]
codegen-units = 1 codegen-units = 1
+10 -5
View File
@@ -5,6 +5,7 @@ use esp_hal::ledc::timer::{self, Timer, TimerIFace};
use esp_hal::ledc::{LSGlobalClkSource, Ledc, LowSpeed}; use esp_hal::ledc::{LSGlobalClkSource, Ledc, LowSpeed};
use esp_hal::peripherals::LEDC; use esp_hal::peripherals::LEDC;
use esp_hal::time::Rate; use esp_hal::time::Rate;
use static_cell::StaticCell;
/// PWM frequency for the backlight (Hz). /// PWM frequency for the backlight (Hz).
const BACKLIGHT_FREQ: Rate = Rate::from_khz(5); const BACKLIGHT_FREQ: Rate = Rate::from_khz(5);
@@ -13,11 +14,13 @@ const BACKLIGHT_FREQ: Rate = Rate::from_khz(5);
/// running the display above 50% brightness, so `level = 1.0` maps to this. /// running the display above 50% brightness, so `level = 1.0` maps to this.
const MAX_DUTY_PCT: u8 = 50; const MAX_DUTY_PCT: u8 = 50;
static BACKLIGHT_TIMER: StaticCell<(Ledc, Timer<'static, LowSpeed>)> = StaticCell::new();
/// Create and configure the LEDC timer used by the backlight. /// Create and configure the LEDC timer used by the backlight.
/// ///
/// Returns the `Ledc` and `Timer` so the caller can build a [`LcdBacklight`] /// Returns the `Ledc` and `Timer` so the caller can build a [`Backlight`]
/// that borrows the timer. /// that borrows the timer.
pub fn setup_timer( fn setup_timer(
ledc: LEDC<'static>, ledc: LEDC<'static>,
) -> Result<(Ledc<'static>, Timer<'static, LowSpeed>), timer::Error> { ) -> Result<(Ledc<'static>, Timer<'static, LowSpeed>), timer::Error> {
let mut ledc = Ledc::new(ledc); let mut ledc = Ledc::new(ledc);
@@ -37,15 +40,17 @@ pub type Error = channel::Error;
/// Backlight PWM driver. The channel holds a reference to the caller-owned /// Backlight PWM driver. The channel holds a reference to the caller-owned
/// timer, so this type's lifetime is bounded by the timer's lifetime. /// timer, so this type's lifetime is bounded by the timer's lifetime.
pub struct LcdBacklight<'a> { pub struct Backlight<'a> {
channel: Channel<'a, LowSpeed>, channel: Channel<'a, LowSpeed>,
} }
impl<'a> LcdBacklight<'a> { impl<'a> Backlight<'a> {
pub fn new( pub fn new(
timer: &'a dyn TimerIFace<LowSpeed>, ledc: LEDC<'static>,
pin: impl PeripheralOutput<'a>, pin: impl PeripheralOutput<'a>,
) -> Result<Self, Error> { ) -> Result<Self, Error> {
let (_ledc, timer) = BACKLIGHT_TIMER.init(setup_timer(ledc)
.expect("Failed to init backlight timer"));
let mut channel = Channel::new(channel::Number::Channel0, pin); let mut channel = Channel::new(channel::Number::Channel0, pin);
channel.configure(channel::config::Config { channel.configure(channel::config::Config {
timer, timer,
+77 -76
View File
@@ -9,6 +9,7 @@ use mipidsi::interface::SpiInterface;
use mipidsi::models::ST7789; use mipidsi::models::ST7789;
use mipidsi::options::{ColorInversion, ColorOrder, Orientation, Rotation}; use mipidsi::options::{ColorInversion, ColorOrder, Orientation, Rotation};
use mipidsi::Builder; use mipidsi::Builder;
use crate::framebuffer::Framebuffer;
pub const WIDTH: u32 = 172; pub const WIDTH: u32 = 172;
pub const HEIGHT: u32 = 320; pub const HEIGHT: u32 = 320;
@@ -72,93 +73,93 @@ pub struct LcdPins {
} }
/// The raw LCD resources, ready to drive the async framebuffer path. /// The raw LCD resources, ready to drive the async framebuffer path.
pub struct LcdParts { pub struct Display {
pub spi: SpiDmaBus, pub spi: SpiDmaBus,
pub cs: Output<'static>, pub cs: Output<'static>,
pub dc: Output<'static>, pub dc: Output<'static>,
} }
pub fn init_parts(pins: LcdPins) -> LcdParts { impl Display {
let mut buffer = [0u8; 4096]; pub fn new(pins: LcdPins) -> Self {
let dma_tx_buf = esp_hal::dma_tx_buffer!(DMA_BUF_SIZE).expect("Failed to create DMA TX buffer"); let mut buffer = [0u8; 4096];
let dma_rx_buf = esp_hal::dma_rx_buffer!(4).expect("Failed to create DMA RX buffer"); let dma_tx_buf = esp_hal::dma_tx_buffer!(DMA_BUF_SIZE).expect("Failed to create DMA TX buffer");
let dma_rx_buf = esp_hal::dma_rx_buffer!(4).expect("Failed to create DMA RX buffer");
let spi = Spi::new( let spi = Spi::new(
pins.spi2, pins.spi2,
SpiConfig::default().with_frequency(SPI_FREQ).with_mode(Mode::_0), SpiConfig::default().with_frequency(SPI_FREQ).with_mode(Mode::_0),
) )
.expect("Failed to configure SPI") .expect("Failed to configure SPI")
.with_sck(pins.sck) .with_sck(pins.sck)
.with_mosi(pins.mosi) .with_mosi(pins.mosi)
.with_dma(pins.dma_ch) .with_dma(pins.dma_ch)
.with_buffers(dma_rx_buf, dma_tx_buf) .with_buffers(dma_rx_buf, dma_tx_buf)
.into_async(); .into_async();
let cs_pin = Output::new(pins.cs, Level::High, OutputConfig::default()); let cs_pin = Output::new(pins.cs, Level::High, OutputConfig::default());
let device = LcdDevice { let device = LcdDevice {
spi, spi,
cs: cs_pin, cs: cs_pin,
}; };
let dc_pin = Output::new(pins.dc, Level::Low, OutputConfig::default()); let dc_pin = Output::new(pins.dc, Level::Low, OutputConfig::default());
let interface = SpiInterface::new(device, dc_pin, &mut buffer); let interface = SpiInterface::new(device, dc_pin, &mut buffer);
let mut rst_pin = Output::new(pins.rst, Level::High, OutputConfig::default()); let mut rst_pin = Output::new(pins.rst, Level::High, OutputConfig::default());
rst_pin.set_high(); rst_pin.set_high();
let mut delay = esp_hal::delay::Delay::new(); let mut delay = esp_hal::delay::Delay::new();
let lcd = Builder::new(ST7789, interface) let lcd = Builder::new(ST7789, interface)
.display_size(WIDTH as u16, HEIGHT as u16) .display_size(WIDTH as u16, HEIGHT as u16)
.display_offset(COLUMN_OFFSET, 0) .display_offset(COLUMN_OFFSET, 0)
.orientation(Orientation::new().rotate(Rotation::Deg0)) .orientation(Orientation::new().rotate(Rotation::Deg0))
.color_order(ColorOrder::Rgb) .color_order(ColorOrder::Rgb)
.invert_colors(ColorInversion::Inverted) .invert_colors(ColorInversion::Inverted)
.reset_pin(rst_pin) .reset_pin(rst_pin)
.init(&mut delay) .init(&mut delay)
.expect("Failed to init display"); .expect("Failed to init display");
let (interface, _model, _rst) = lcd.release(); let (interface, _model, _rst) = lcd.release();
let (device, dc) = interface.release(); let (device, dc) = interface.release();
let (spi, cs) = device.into_inner(); let (spi, cs) = device.into_inner();
LcdParts { spi, cs, dc } Display { spi, cs, dc }
} }
/// Stream a full framebuffer to the panel asynchronously (DMA). /// Stream a full framebuffer to the panel asynchronously (DMA).
/// ///
/// Sends the address window and RAMWR command, then the pixel bytes as one /// Sends the address window and RAMWR command, then the pixel bytes as one
/// (internally chunked) DMA transfer. Returns when the transfer completes. /// (internally chunked) DMA transfer. Returns when the transfer completes.
pub async fn send_framebuffer( pub async fn send_framebuffer(
spi: &mut SpiDmaBus, &mut self,
dc: &mut Output<'static>, framebuffer: &Framebuffer,
cs: &mut Output<'static>, ) -> Result<(), SpiError> {
bytes: &[u8], let x0 = COLUMN_OFFSET;
) -> Result<(), SpiError> { let x1 = COLUMN_OFFSET + WIDTH as u16 - 1;
let x0 = COLUMN_OFFSET; let y1 = HEIGHT as u16 - 1;
let x1 = COLUMN_OFFSET + WIDTH as u16 - 1;
let y1 = HEIGHT as u16 - 1; self.cs.set_low();
cs.set_low(); // MIPI DCS: DC low for the command byte, high for its arguments.
self.dc.set_low();
// MIPI DCS: DC low for the command byte, high for its arguments. self.spi.write_async(&[0x2A]).await?;
dc.set_low(); self.dc.set_high();
spi.write_async(&[0x2A]).await?; self.spi.write_async(&[(x0 >> 8) as u8, x0 as u8, (x1 >> 8) as u8, x1 as u8])
dc.set_high(); .await?;
spi.write_async(&[(x0 >> 8) as u8, x0 as u8, (x1 >> 8) as u8, x1 as u8])
.await?; self.dc.set_low();
self.spi.write_async(&[0x2B]).await?;
dc.set_low(); self.dc.set_high();
spi.write_async(&[0x2B]).await?; self.spi.write_async(&[0, 0, (y1 >> 8) as u8, y1 as u8]).await?;
dc.set_high();
spi.write_async(&[0, 0, (y1 >> 8) as u8, y1 as u8]).await?; self.dc.set_low();
self.spi.write_async(&[0x2C]).await?;
dc.set_low(); self.dc.set_high();
spi.write_async(&[0x2C]).await?;
dc.set_high(); self.spi.write_async(framebuffer.as_bytes()).await?;
self.cs.set_high();
spi.write_async(bytes).await?;
cs.set_high(); Ok(())
}
Ok(())
} }
+8 -5
View File
@@ -1,9 +1,10 @@
use esp_hal::gpio::interconnect::PeripheralOutput; use esp_hal::gpio::interconnect::PeripheralOutput;
use esp_hal::rmt::TxChannelCreator; use esp_hal::peripherals::RMT;
use esp_hal::rmt::Rmt;
use esp_hal::time::Rate; use esp_hal::time::Rate;
use esp_hal::Blocking; use esp_hal::Blocking;
use esp_hal_smartled::{RmtSmartLeds, WS2812_TIMING, buffer_size, color_order}; use esp_hal_smartled::{buffer_size, color_order, RmtSmartLeds, WS2812_TIMING};
use smart_leds_trait::{RGB8, SmartLedsWrite}; use smart_leds_trait::{SmartLedsWrite, RGB8};
const RMT_FREQ: Rate = Rate::from_mhz(80); const RMT_FREQ: Rate = Rate::from_mhz(80);
@@ -15,11 +16,13 @@ pub struct RgbLed {
impl RgbLed { impl RgbLed {
pub fn new( pub fn new(
channel: impl TxChannelCreator<'static, Blocking>, rmt_periph: RMT<'static>,
pin: impl PeripheralOutput<'static>, pin: impl PeripheralOutput<'static>,
) -> Result<Self, esp_hal_smartled::Error> { ) -> Result<Self, esp_hal_smartled::Error> {
let rmt = Rmt::new(rmt_periph, Rate::from_mhz(80))
.expect("Failed to init RMT");
Ok(Self { Ok(Self {
led: SmartLed::new_with_memsize(WS2812_TIMING, channel, pin, 2, RMT_FREQ)?, led: SmartLed::new_with_memsize(WS2812_TIMING, rmt.channel0, pin, 2, RMT_FREQ)?,
}) })
} }
+28 -16
View File
@@ -5,7 +5,6 @@ mod backlight;
mod display; mod display;
mod framebuffer; mod framebuffer;
mod led; mod led;
pub mod board;
use embedded_graphics::pixelcolor::Rgb565; use embedded_graphics::pixelcolor::Rgb565;
use embedded_graphics::prelude::*; use embedded_graphics::prelude::*;
@@ -15,9 +14,11 @@ use embassy_time::{Instant, Timer};
use esp_backtrace as _; use esp_backtrace as _;
use esp_bootloader_esp_idf::esp_app_desc; use esp_bootloader_esp_idf::esp_app_desc;
use esp_hal::timer::timg::TimerGroup; use esp_hal::timer::timg::TimerGroup;
use esp_hal::{init, rmt::Rmt, time::Rate, Config}; use esp_hal::{init, Config};
use esp_hal::peripherals::{GPIO22, LEDC};
use esp_println::println; use esp_println::println;
use smart_leds::hsv::{hsv2rgb, Hsv}; use smart_leds::hsv::{hsv2rgb, Hsv};
use crate::display::Display;
const SAT: u8 = 255; const SAT: u8 = 255;
const VAL: u8 = 255; const VAL: u8 = 255;
@@ -29,8 +30,7 @@ async fn led_task(
rmt_periph: esp_hal::peripherals::RMT<'static>, rmt_periph: esp_hal::peripherals::RMT<'static>,
led_pin: esp_hal::peripherals::GPIO8<'static>, led_pin: esp_hal::peripherals::GPIO8<'static>,
) { ) {
let rmt = Rmt::new(rmt_periph, Rate::from_mhz(80)).expect("Failed to init RMT"); let mut led = led::RgbLed::new(rmt_periph, led_pin).expect("Failed to init RGB LED");
let mut led = led::RgbLed::new(rmt.channel0, led_pin).expect("Failed to init RGB LED");
let mut frame = 0u32; let mut frame = 0u32;
loop { loop {
@@ -49,16 +49,14 @@ async fn led_task(
#[embassy_executor::task] #[embassy_executor::task]
async fn display_task( async fn display_task(
pins: display::LcdPins, pins: display::LcdPins,
ledc: esp_hal::peripherals::LEDC<'static>, ledc: LEDC<'static>,
bl_pin: esp_hal::peripherals::GPIO22<'static>, bl_pin: GPIO22<'static>,
) { ) {
let mut parts = display::init_parts(pins); let mut display = Display::new(pins);
println!("LCD {}x{} initialized", display::WIDTH, display::HEIGHT); println!("LCD {}x{} initialized", display::WIDTH, display::HEIGHT);
let (_ledc, timer) = let mut backlight = backlight::Backlight::new(ledc, bl_pin)
backlight::setup_timer(ledc).expect("Failed to init backlight timer"); .expect("Failed to init backlight");
let mut backlight =
backlight::LcdBacklight::new(&timer, bl_pin).expect("Failed to init backlight");
backlight.set_level(0.5).unwrap(); backlight.set_level(0.5).unwrap();
println!("backlight set to 0.5"); println!("backlight set to 0.5");
@@ -74,6 +72,10 @@ async fn display_task(
let mut frame = 0u32; let mut frame = 0u32;
let mut fps_window_start = Instant::now(); let mut fps_window_start = Instant::now();
let mut fps_frames = 0u32; let mut fps_frames = 0u32;
let mut window_draw_us: u64 = 0;
let mut window_send_us: u64 = 0;
let mut last_draw_us: u64;
let mut last_send_us: u64;
loop { loop {
let hue = (frame % 256) as u8; let hue = (frame % 256) as u8;
@@ -101,30 +103,40 @@ async fn display_task(
vy = -vy; vy = -vy;
} }
let draw_start = Instant::now();
fb.clear(); fb.clear();
Triangle::new(pts[0], pts[1], pts[2]) Triangle::new(pts[0], pts[1], pts[2])
.into_styled(PrimitiveStyle::with_fill(accent)) .into_styled(PrimitiveStyle::with_fill(accent))
.draw(&mut fb) .draw(&mut fb)
.unwrap(); .unwrap();
last_draw_us = draw_start.elapsed().as_micros() as u64;
display::send_framebuffer(&mut parts.spi, &mut parts.dc, &mut parts.cs, fb.as_bytes()) let send_start = Instant::now();
.await display.send_framebuffer(&fb).await.unwrap();
.unwrap(); last_send_us = send_start.elapsed().as_micros() as u64;
if frame % 32 == 0 { if frame % 32 == 0 {
println!( println!(
"frame {frame}: apex=({}, {}), v=({}, {}), hue={hue}", "frame {frame}: apex=({}, {}), v=({}, {}), hue={hue} | draw {last_draw_us} us, send {last_send_us} us",
pts[0].x, pts[0].y, vx, vy pts[0].x, pts[0].y, vx, vy
); );
} }
fps_frames += 1; fps_frames += 1;
window_draw_us += last_draw_us;
window_send_us += last_send_us;
if fps_frames >= 60 { if fps_frames >= 60 {
let elapsed_ms = fps_window_start.elapsed().as_millis().max(1); let elapsed_ms = fps_window_start.elapsed().as_millis().max(1);
let fps = (fps_frames as u64 * 1000) / elapsed_ms; let fps = (fps_frames as u64 * 1000) / elapsed_ms;
println!("fps: {fps} ({fps_frames} frames in {elapsed_ms} ms)"); let avg_draw_us = window_draw_us / fps_frames as u64;
let avg_send_us = window_send_us / fps_frames as u64;
println!(
"fps: {fps} ({fps_frames} frames in {elapsed_ms} ms) | avg draw {avg_draw_us} us, avg send {avg_send_us} us"
);
fps_frames = 0; fps_frames = 0;
fps_window_start = Instant::now(); fps_window_start = Instant::now();
window_draw_us = 0;
window_send_us = 0;
} }
frame = frame.wrapping_add(1); frame = frame.wrapping_add(1);