Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0116ce97f8 | ||
|
|
05803ad866 |
Generated
+1
@@ -964,6 +964,7 @@ dependencies = [
|
|||||||
"mipidsi",
|
"mipidsi",
|
||||||
"smart-leds",
|
"smart-leds",
|
||||||
"smart-leds-trait",
|
"smart-leds-trait",
|
||||||
|
"static_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -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
@@ -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,
|
||||||
|
|||||||
+29
-28
@@ -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,13 +73,14 @@ 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 {
|
||||||
|
pub fn new(pins: LcdPins) -> Self {
|
||||||
let mut buffer = [0u8; 4096];
|
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_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 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 (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],
|
|
||||||
) -> Result<(), SpiError> {
|
|
||||||
let x0 = COLUMN_OFFSET;
|
let x0 = COLUMN_OFFSET;
|
||||||
let x1 = COLUMN_OFFSET + WIDTH as u16 - 1;
|
let x1 = COLUMN_OFFSET + WIDTH as u16 - 1;
|
||||||
let y1 = HEIGHT 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.
|
// MIPI DCS: DC low for the command byte, high for its arguments.
|
||||||
dc.set_low();
|
self.dc.set_low();
|
||||||
spi.write_async(&[0x2A]).await?;
|
self.spi.write_async(&[0x2A]).await?;
|
||||||
dc.set_high();
|
self.dc.set_high();
|
||||||
spi.write_async(&[(x0 >> 8) as u8, x0 as u8, (x1 >> 8) as u8, x1 as u8])
|
self.spi.write_async(&[(x0 >> 8) as u8, x0 as u8, (x1 >> 8) as u8, x1 as u8])
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
dc.set_low();
|
self.dc.set_low();
|
||||||
spi.write_async(&[0x2B]).await?;
|
self.spi.write_async(&[0x2B]).await?;
|
||||||
dc.set_high();
|
self.dc.set_high();
|
||||||
spi.write_async(&[0, 0, (y1 >> 8) as u8, y1 as u8]).await?;
|
self.spi.write_async(&[0, 0, (y1 >> 8) as u8, y1 as u8]).await?;
|
||||||
|
|
||||||
dc.set_low();
|
self.dc.set_low();
|
||||||
spi.write_async(&[0x2C]).await?;
|
self.spi.write_async(&[0x2C]).await?;
|
||||||
dc.set_high();
|
self.dc.set_high();
|
||||||
|
|
||||||
spi.write_async(bytes).await?;
|
self.spi.write_async(framebuffer.as_bytes()).await?;
|
||||||
cs.set_high();
|
self.cs.set_high();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+8
-5
@@ -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)?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+37
-24
@@ -14,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;
|
||||||
@@ -24,7 +26,12 @@ const VAL: u8 = 255;
|
|||||||
esp_app_desc!();
|
esp_app_desc!();
|
||||||
|
|
||||||
#[embassy_executor::task]
|
#[embassy_executor::task]
|
||||||
async fn led_task(mut led: led::RgbLed) {
|
async fn led_task(
|
||||||
|
rmt_periph: esp_hal::peripherals::RMT<'static>,
|
||||||
|
led_pin: esp_hal::peripherals::GPIO8<'static>,
|
||||||
|
) {
|
||||||
|
let mut led = led::RgbLed::new(rmt_periph, led_pin).expect("Failed to init RGB LED");
|
||||||
|
|
||||||
let mut frame = 0u32;
|
let mut frame = 0u32;
|
||||||
loop {
|
loop {
|
||||||
let hue = (frame % 256) as u8;
|
let hue = (frame % 256) as u8;
|
||||||
@@ -42,18 +49,19 @@ async fn led_task(mut led: led::RgbLed) {
|
|||||||
#[embassy_executor::task]
|
#[embassy_executor::task]
|
||||||
async fn display_task(
|
async fn display_task(
|
||||||
pins: display::LcdPins,
|
pins: display::LcdPins,
|
||||||
mut fb: framebuffer::Framebuffer,
|
ledc: LEDC<'static>,
|
||||||
timer: esp_hal::ledc::timer::Timer<'static, esp_hal::ledc::LowSpeed>,
|
bl_pin: GPIO22<'static>,
|
||||||
bl_pin: esp_hal::peripherals::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 mut backlight =
|
let mut backlight = backlight::Backlight::new(ledc, bl_pin)
|
||||||
backlight::LcdBacklight::new(&timer, bl_pin).expect("Failed to init backlight");
|
.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");
|
||||||
|
|
||||||
|
let mut fb = framebuffer::Framebuffer;
|
||||||
|
|
||||||
let mut pts = [
|
let mut pts = [
|
||||||
Point::new(86, 20),
|
Point::new(86, 20),
|
||||||
Point::new(61, 50),
|
Point::new(61, 50),
|
||||||
@@ -64,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;
|
||||||
@@ -91,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);
|
||||||
@@ -127,15 +149,7 @@ async fn main(spawner: Spawner) -> ! {
|
|||||||
let timg0 = TimerGroup::new(peripherals.TIMG0);
|
let timg0 = TimerGroup::new(peripherals.TIMG0);
|
||||||
esp_rtos::start(timg0.timer0, peripherals.FROM_CPU_INTR0);
|
esp_rtos::start(timg0.timer0, peripherals.FROM_CPU_INTR0);
|
||||||
|
|
||||||
let led = {
|
spawner.spawn(led_task(peripherals.RMT, peripherals.GPIO8).unwrap());
|
||||||
let rmt = Rmt::new(peripherals.RMT, Rate::from_mhz(80)).expect("Failed to init RMT");
|
|
||||||
led::RgbLed::new(rmt.channel0, peripherals.GPIO8).expect("Failed to init RGB LED")
|
|
||||||
};
|
|
||||||
|
|
||||||
let (_ledc, timer) =
|
|
||||||
backlight::setup_timer(peripherals.LEDC).expect("Failed to init backlight timer");
|
|
||||||
|
|
||||||
spawner.spawn(led_task(led).unwrap());
|
|
||||||
spawner.spawn(display_task(
|
spawner.spawn(display_task(
|
||||||
display::LcdPins {
|
display::LcdPins {
|
||||||
spi2: peripherals.SPI2,
|
spi2: peripherals.SPI2,
|
||||||
@@ -146,8 +160,7 @@ async fn main(spawner: Spawner) -> ! {
|
|||||||
rst: peripherals.GPIO21,
|
rst: peripherals.GPIO21,
|
||||||
dma_ch: peripherals.DMA_CH0,
|
dma_ch: peripherals.DMA_CH0,
|
||||||
},
|
},
|
||||||
framebuffer::Framebuffer,
|
peripherals.LEDC,
|
||||||
timer,
|
|
||||||
peripherals.GPIO22,
|
peripherals.GPIO22,
|
||||||
).unwrap());
|
).unwrap());
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user