lcd driver

This commit is contained in:
2026-09-07 22:13:21 -05:00
parent 9141629a58
commit 3ad59eac09
5 changed files with 278 additions and 22 deletions
Generated
+68
View File
@@ -8,6 +8,12 @@ version = "1.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
[[package]]
name = "az"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973"
[[package]] [[package]]
name = "bitfield" name = "bitfield"
version = "0.19.5" version = "0.19.5"
@@ -326,6 +332,29 @@ dependencies = [
"nb 1.1.0", "nb 1.1.0",
] ]
[[package]]
name = "embedded-graphics"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e8da660bb0c829b34a56a965490597f82a55e767b91f9543be80ce8ccb416fe"
dependencies = [
"az",
"byteorder",
"embedded-graphics-core",
"float-cmp",
"micromath",
]
[[package]]
name = "embedded-graphics-core"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95743bef3ff70fcba3930246c4e6872882bbea0dcc6da2ca860112e0cd4bd09f"
dependencies = [
"az",
"byteorder",
]
[[package]] [[package]]
name = "embedded-hal" name = "embedded-hal"
version = "0.2.7" version = "0.2.7"
@@ -351,6 +380,16 @@ dependencies = [
"embedded-hal 1.0.0", "embedded-hal 1.0.0",
] ]
[[package]]
name = "embedded-hal-bus"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "513e0b3a8fb7d3013a8ae17a834283f170deaf7d0eeab0a7c1a36ad4dd356d22"
dependencies = [
"critical-section",
"embedded-hal 1.0.0",
]
[[package]] [[package]]
name = "embedded-io" name = "embedded-io"
version = "0.6.1" version = "0.6.1"
@@ -730,15 +769,27 @@ dependencies = [
name = "espboard" name = "espboard"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"embedded-graphics",
"embedded-hal-bus",
"esp-backtrace", "esp-backtrace",
"esp-bootloader-esp-idf", "esp-bootloader-esp-idf",
"esp-hal", "esp-hal",
"esp-hal-smartled", "esp-hal-smartled",
"esp-println", "esp-println",
"mipidsi",
"smart-leds", "smart-leds",
"smart-leds-trait", "smart-leds-trait",
] ]
[[package]]
name = "float-cmp"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98de4bbd547a563b716d8dfa9aad1cb19bfab00f4fa09a6a4ed21dbcf44ce9c4"
dependencies = [
"num-traits",
]
[[package]] [[package]]
name = "fnv" name = "fnv"
version = "1.0.7" version = "1.0.7"
@@ -953,6 +1004,23 @@ version = "2.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98"
[[package]]
name = "micromath"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3c8dda44ff03a2f238717214da50f65d5a53b45cd213a7370424ffdb6fae815"
[[package]]
name = "mipidsi"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "790ebd28bd67addbccf41b1c0c188c26bb9f5bdcd91d4d6da9bd558e20d97a1d"
dependencies = [
"embedded-graphics-core",
"embedded-hal 1.0.0",
"heapless 0.8.0",
]
[[package]] [[package]]
name = "nb" name = "nb"
version = "0.1.3" version = "0.1.3"
+3
View File
@@ -4,11 +4,14 @@ version = "0.1.0"
edition = "2024" edition = "2024"
[dependencies] [dependencies]
embedded-graphics = "0.8"
embedded-hal-bus = "0.3"
esp-backtrace = { version = "0.20.0", features = ["esp32c6", "println", "panic-handler"] } esp-backtrace = { version = "0.20.0", features = ["esp32c6", "println", "panic-handler"] }
esp-bootloader-esp-idf = { version = "0.6.0", features = ["esp32c6"] } esp-bootloader-esp-idf = { version = "0.6.0", features = ["esp32c6"] }
esp-hal = { version = "1.2.0", features = ["esp32c6", "unstable"] } esp-hal = { version = "1.2.0", features = ["esp32c6", "unstable"] }
esp-hal-smartled = { version = "0.18", default-features = false, features = ["esp32c6"] } esp-hal-smartled = { version = "0.18", default-features = false, features = ["esp32c6"] }
esp-println = { version = "0.18.0", default-features = false, features = ["esp32c6", "jtag-serial"] } esp-println = { version = "0.18.0", default-features = false, features = ["esp32c6", "jtag-serial"] }
mipidsi = "0.10"
smart-leds = "0.4" smart-leds = "0.4"
smart-leds-trait = "0.3" smart-leds-trait = "0.3"
+64
View File
@@ -0,0 +1,64 @@
use embedded_hal_bus::spi::{ExclusiveDevice, NoDelay};
use esp_hal::delay::Delay;
use esp_hal::gpio::{Level, Output, OutputConfig};
use esp_hal::peripherals::{GPIO14, GPIO15, GPIO21, GPIO6, GPIO7, SPI2};
use esp_hal::spi::master::{Config as SpiConfig, Spi};
use esp_hal::spi::Mode;
use esp_hal::time::Rate;
use esp_hal::Blocking;
use mipidsi::interface::SpiInterface;
use mipidsi::models::ST7789;
use mipidsi::options::{ColorInversion, ColorOrder, Orientation, Rotation};
use mipidsi::{Builder, Display};
pub const WIDTH: u32 = 172;
pub const HEIGHT: u32 = 320;
const SPI_FREQ: Rate = Rate::from_mhz(40);
/// Column offset of the 172-px-wide panel within the ST7789's 240-px framebuffer.
const COLUMN_OFFSET: u16 = 34;
type Device = ExclusiveDevice<Spi<'static, Blocking>, Output<'static>, NoDelay>;
type Interface<'a> = SpiInterface<'a, Device, Output<'static>>;
/// The mipidsi [`Display`], which implements embedded-graphics' [`DrawTarget`].
///
/// The lifetime is the borrow of the pixel buffer passed to [`init`].
pub type Lcd<'a> = Display<Interface<'a>, ST7789, Output<'static>>;
pub fn init<'a>(
spi2: SPI2<'static>,
sck: GPIO7<'static>,
mosi: GPIO6<'static>,
dc: GPIO15<'static>,
cs: GPIO14<'static>,
rst: GPIO21<'static>,
buffer: &'a mut [u8],
) -> Lcd<'a> {
let spi = Spi::new(spi2, SpiConfig::default().with_frequency(SPI_FREQ).with_mode(Mode::_0))
.expect("Failed to configure SPI")
.with_sck(sck)
.with_mosi(mosi);
let cs_pin = Output::new(cs, Level::High, OutputConfig::default());
let device = ExclusiveDevice::new_no_delay(spi, cs_pin).expect("Failed to create SPI device");
let dc_pin = Output::new(dc, Level::Low, OutputConfig::default());
let interface = SpiInterface::new(device, dc_pin, buffer);
let mut rst_pin = Output::new(rst, Level::High, OutputConfig::default());
rst_pin.set_high();
let mut delay = Delay::new();
Builder::new(ST7789, interface)
.display_size(WIDTH as u16, HEIGHT as u16)
.display_offset(COLUMN_OFFSET, 0)
.orientation(Orientation::new().rotate(Rotation::Deg0))
.color_order(ColorOrder::Rgb)
.invert_colors(ColorInversion::Inverted)
.reset_pin(rst_pin)
.init(&mut delay)
.expect("Failed to init display")
}
+66
View File
@@ -0,0 +1,66 @@
use embedded_graphics::draw_target::DrawTarget;
use embedded_graphics::geometry::{OriginDimensions, Size};
use embedded_graphics::pixelcolor::{Rgb565, RgbColor};
use embedded_graphics::Pixel;
use crate::display::{HEIGHT, WIDTH};
const PIXEL_COUNT: usize = (WIDTH * HEIGHT) as usize;
/// The full-screen back buffer (172 × 320 × 2 bytes ≈ 110 KB).
static mut PIXELS: [Rgb565; PIXEL_COUNT] = [Rgb565::BLACK; PIXEL_COUNT];
/// A full-frame framebuffer that implements embedded-graphics' [`DrawTarget`].
///
/// Draw primitives into it, then blit the whole thing to the display once per
/// frame to avoid flicker. The backing memory is a `static`, so this is only
/// safe to use from a single thread (which is all this application has).
pub struct Framebuffer;
impl Framebuffer {
pub fn clear(&mut self) {
// SAFETY: only used from the single main loop.
unsafe { &mut *core::ptr::addr_of_mut!(PIXELS) }.fill(Rgb565::BLACK);
}
/// The raw pixels, ready to be sent to the display.
pub fn pixels(&self) -> &'static [Rgb565] {
// SAFETY: the buffer lives for the whole program and is only borrowed
// for the duration of the (blocking) blit.
unsafe { &*core::ptr::addr_of!(PIXELS) }
}
}
impl DrawTarget for Framebuffer {
type Color = Rgb565;
type Error = core::convert::Infallible;
fn draw_iter<I>(&mut self, pixels: I) -> Result<(), Self::Error>
where
I: IntoIterator<Item = Pixel<Self::Color>>,
{
// SAFETY: only used from the single main loop.
let buffer = unsafe { &mut *core::ptr::addr_of_mut!(PIXELS) };
for pixel in pixels {
let (x, y) = (pixel.0.x, pixel.0.y);
if x >= 0 && y >= 0 && x < WIDTH as i32 && y < HEIGHT as i32 {
buffer[(y as usize) * WIDTH as usize + (x as usize)] = pixel.1;
}
}
Ok(())
}
}
impl OriginDimensions for Framebuffer {
fn size(&self) -> Size {
Size::new(WIDTH, HEIGHT)
}
}
impl Default for Framebuffer {
fn default() -> Self {
Self
}
}
+74 -19
View File
@@ -2,14 +2,23 @@
#![no_main] #![no_main]
mod backlight; mod backlight;
mod display;
mod framebuffer;
mod led; mod led;
use embedded_graphics::draw_target::DrawTarget;
use embedded_graphics::pixelcolor::Rgb565;
use embedded_graphics::prelude::*;
use embedded_graphics::primitives::{PrimitiveStyle, Rectangle, Triangle};
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::{init, rmt::Rmt, time::Rate, Config}; use esp_hal::{init, rmt::Rmt, time::Rate, Config};
use esp_println::println; use esp_println::println;
use smart_leds::hsv::{hsv2rgb, Hsv}; use smart_leds::hsv::{hsv2rgb, Hsv};
use smart_leds::{brightness, gamma}; use smart_leds::RGB8;
const SAT: u8 = 200;
const VAL: u8 = 200;
esp_app_desc!(); esp_app_desc!();
@@ -26,34 +35,80 @@ fn main() -> ! {
backlight::setup_timer(peripherals.LEDC).expect("Failed to init backlight timer"); backlight::setup_timer(peripherals.LEDC).expect("Failed to init backlight timer");
let mut backlight = let mut backlight =
backlight::LcdBacklight::new(&timer, peripherals.GPIO22).expect("Failed to init backlight"); backlight::LcdBacklight::new(&timer, peripherals.GPIO22).expect("Failed to init backlight");
backlight.set_level(0.5).unwrap();
let mut buffer = [0u8; 4096];
let mut lcd = display::init(
peripherals.SPI2,
peripherals.GPIO7,
peripherals.GPIO6,
peripherals.GPIO15,
peripherals.GPIO14,
peripherals.GPIO21,
&mut buffer,
);
let delay = esp_hal::delay::Delay::new(); let delay = esp_hal::delay::Delay::new();
let levels = [1.0f32, 0.75, 0.5, 0.25, 0.0, 0.5]; println!("LCD {}x{} initialized", display::WIDTH, display::HEIGHT);
let mut level_idx = 0; lcd.clear(Rgb565::BLACK).unwrap();
let mut fb = framebuffer::Framebuffer;
let screen = Rectangle::new(Point::zero(), Size::new(display::WIDTH, display::HEIGHT));
let mut pts = [
Point::new(86, 20),
Point::new(61, 50),
Point::new(111, 50),
];
let mut vx: i32 = 3;
let mut vy: i32 = 2;
let mut frame = 0u32;
loop { loop {
for hue in 0..=255 { let hue = (frame % 256) as u8;
let rgb = hsv2rgb(Hsv { let lit = hsv2rgb(Hsv {
hue, hue,
sat: 255, sat: SAT,
val: 255, val: VAL,
}); });
let lit = brightness(gamma(core::iter::once(rgb)), 40) led.set_rgb(RGB8::new(0,0,0)).unwrap();
.next() let accent = Rgb565::new(lit.r / 8, lit.g / 4, lit.b / 8);
for p in &mut pts {
p.x += vx;
p.y += vy;
}
let min_x = pts.iter().map(|p| p.x).min().unwrap();
let max_x = pts.iter().map(|p| p.x).max().unwrap();
let min_y = pts.iter().map(|p| p.y).min().unwrap();
let max_y = pts.iter().map(|p| p.y).max().unwrap();
if min_x <= 0 || max_x >= display::WIDTH as i32 {
vx = -vx;
}
if min_y <= 0 || max_y >= display::HEIGHT as i32 {
vy = -vy;
}
fb.clear();
Triangle::new(pts[0], pts[1], pts[2])
.into_styled(PrimitiveStyle::with_fill(accent))
.draw(&mut fb)
.unwrap(); .unwrap();
led.set_rgb(lit).unwrap();
if hue % 32 == 0 { lcd.fill_contiguous(&screen, fb.pixels().iter().cloned())
println!("hue {hue}: ({}, {}, {})", lit.r, lit.g, lit.b); .unwrap();
if frame % 32 == 0 {
println!(
"frame {frame}: apex=({}, {}), v=({}, {}), hue={hue}",
pts[0].x, pts[0].y, vx, vy
);
} }
delay.delay_millis(10); frame = frame.wrapping_add(1);
} //delay.delay_millis(30);
let level = levels[level_idx % levels.len()];
backlight.set_level(level).unwrap();
println!("backlight level: {level}");
level_idx += 1;
} }
} }