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",
"smart-leds",
"smart-leds-trait",
"static_cell",
]
[[package]]
+1
View File
@@ -18,6 +18,7 @@ esp-rtos = { version = "0.4", features = ["embassy", "esp32c6"] }
mipidsi = "0.10"
smart-leds = "0.4"
smart-leds-trait = "0.3"
static_cell = { version = "2.1.1" }
[profile.dev]
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::peripherals::LEDC;
use esp_hal::time::Rate;
use static_cell::StaticCell;
/// PWM frequency for the backlight (Hz).
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.
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.
///
/// 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.
pub fn setup_timer(
fn setup_timer(
ledc: LEDC<'static>,
) -> Result<(Ledc<'static>, Timer<'static, LowSpeed>), timer::Error> {
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
/// 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>,
}
impl<'a> LcdBacklight<'a> {
impl<'a> Backlight<'a> {
pub fn new(
timer: &'a dyn TimerIFace<LowSpeed>,
ledc: LEDC<'static>,
pin: impl PeripheralOutput<'a>,
) -> 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);
channel.configure(channel::config::Config {
timer,
+29 -28
View File
@@ -9,6 +9,7 @@ use mipidsi::interface::SpiInterface;
use mipidsi::models::ST7789;
use mipidsi::options::{ColorInversion, ColorOrder, Orientation, Rotation};
use mipidsi::Builder;
use crate::framebuffer::Framebuffer;
pub const WIDTH: u32 = 172;
pub const HEIGHT: u32 = 320;
@@ -72,13 +73,14 @@ pub struct LcdPins {
}
/// The raw LCD resources, ready to drive the async framebuffer path.
pub struct LcdParts {
pub struct Display {
pub spi: SpiDmaBus,
pub cs: Output<'static>,
pub dc: Output<'static>,
}
pub fn init_parts(pins: LcdPins) -> LcdParts {
impl Display {
pub fn new(pins: LcdPins) -> Self {
let mut buffer = [0u8; 4096];
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");
@@ -122,43 +124,42 @@ pub fn init_parts(pins: LcdPins) -> LcdParts {
let (device, dc) = interface.release();
let (spi, cs) = device.into_inner();
LcdParts { spi, cs, dc }
}
Display { spi, cs, dc }
}
/// Stream a full framebuffer to the panel asynchronously (DMA).
///
/// Sends the address window and RAMWR command, then the pixel bytes as one
/// (internally chunked) DMA transfer. Returns when the transfer completes.
pub async fn send_framebuffer(
spi: &mut SpiDmaBus,
dc: &mut Output<'static>,
cs: &mut Output<'static>,
bytes: &[u8],
) -> Result<(), SpiError> {
/// Stream a full framebuffer to the panel asynchronously (DMA).
///
/// Sends the address window and RAMWR command, then the pixel bytes as one
/// (internally chunked) DMA transfer. Returns when the transfer completes.
pub async fn send_framebuffer(
&mut self,
framebuffer: &Framebuffer,
) -> Result<(), SpiError> {
let x0 = COLUMN_OFFSET;
let x1 = COLUMN_OFFSET + WIDTH as u16 - 1;
let y1 = HEIGHT as u16 - 1;
cs.set_low();
self.cs.set_low();
// MIPI DCS: DC low for the command byte, high for its arguments.
dc.set_low();
spi.write_async(&[0x2A]).await?;
dc.set_high();
spi.write_async(&[(x0 >> 8) as u8, x0 as u8, (x1 >> 8) as u8, x1 as u8])
self.dc.set_low();
self.spi.write_async(&[0x2A]).await?;
self.dc.set_high();
self.spi.write_async(&[(x0 >> 8) as u8, x0 as u8, (x1 >> 8) as u8, x1 as u8])
.await?;
dc.set_low();
spi.write_async(&[0x2B]).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(&[0x2B]).await?;
self.dc.set_high();
self.spi.write_async(&[0, 0, (y1 >> 8) as u8, y1 as u8]).await?;
dc.set_low();
spi.write_async(&[0x2C]).await?;
dc.set_high();
self.dc.set_low();
self.spi.write_async(&[0x2C]).await?;
self.dc.set_high();
spi.write_async(bytes).await?;
cs.set_high();
self.spi.write_async(framebuffer.as_bytes()).await?;
self.cs.set_high();
Ok(())
}
}
+8 -5
View File
@@ -1,9 +1,10 @@
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::Blocking;
use esp_hal_smartled::{RmtSmartLeds, WS2812_TIMING, buffer_size, color_order};
use smart_leds_trait::{RGB8, SmartLedsWrite};
use esp_hal_smartled::{buffer_size, color_order, RmtSmartLeds, WS2812_TIMING};
use smart_leds_trait::{SmartLedsWrite, RGB8};
const RMT_FREQ: Rate = Rate::from_mhz(80);
@@ -15,11 +16,13 @@ pub struct RgbLed {
impl RgbLed {
pub fn new(
channel: impl TxChannelCreator<'static, Blocking>,
rmt_periph: RMT<'static>,
pin: impl PeripheralOutput<'static>,
) -> Result<Self, esp_hal_smartled::Error> {
let rmt = Rmt::new(rmt_periph, Rate::from_mhz(80))
.expect("Failed to init RMT");
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 framebuffer;
mod led;
pub mod board;
use embedded_graphics::pixelcolor::Rgb565;
use embedded_graphics::prelude::*;
@@ -15,9 +14,11 @@ use embassy_time::{Instant, Timer};
use esp_backtrace as _;
use esp_bootloader_esp_idf::esp_app_desc;
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 smart_leds::hsv::{hsv2rgb, Hsv};
use crate::display::Display;
const SAT: u8 = 255;
const VAL: u8 = 255;
@@ -29,8 +30,7 @@ async fn led_task(
rmt_periph: esp_hal::peripherals::RMT<'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.channel0, led_pin).expect("Failed to init RGB LED");
let mut led = led::RgbLed::new(rmt_periph, led_pin).expect("Failed to init RGB LED");
let mut frame = 0u32;
loop {
@@ -49,16 +49,14 @@ async fn led_task(
#[embassy_executor::task]
async fn display_task(
pins: display::LcdPins,
ledc: esp_hal::peripherals::LEDC<'static>,
bl_pin: esp_hal::peripherals::GPIO22<'static>,
ledc: LEDC<'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);
let (_ledc, timer) =
backlight::setup_timer(ledc).expect("Failed to init backlight timer");
let mut backlight =
backlight::LcdBacklight::new(&timer, bl_pin).expect("Failed to init backlight");
let mut backlight = backlight::Backlight::new(ledc, bl_pin)
.expect("Failed to init backlight");
backlight.set_level(0.5).unwrap();
println!("backlight set to 0.5");
@@ -74,6 +72,10 @@ async fn display_task(
let mut frame = 0u32;
let mut fps_window_start = Instant::now();
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 {
let hue = (frame % 256) as u8;
@@ -101,30 +103,40 @@ async fn display_task(
vy = -vy;
}
let draw_start = Instant::now();
fb.clear();
Triangle::new(pts[0], pts[1], pts[2])
.into_styled(PrimitiveStyle::with_fill(accent))
.draw(&mut fb)
.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())
.await
.unwrap();
let send_start = Instant::now();
display.send_framebuffer(&fb).await.unwrap();
last_send_us = send_start.elapsed().as_micros() as u64;
if frame % 32 == 0 {
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
);
}
fps_frames += 1;
window_draw_us += last_draw_us;
window_send_us += last_send_us;
if fps_frames >= 60 {
let elapsed_ms = fps_window_start.elapsed().as_millis().max(1);
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_window_start = Instant::now();
window_draw_us = 0;
window_send_us = 0;
}
frame = frame.wrapping_add(1);