From 8d8eabc56ba0c3674403e2d811b6870929165579 Mon Sep 17 00:00:00 2001 From: Ross Andrews Date: Mon, 7 Sep 2026 23:33:05 -0500 Subject: [PATCH] dma --- Cargo.lock | 396 +++++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 4 + src/display.rs | 151 +++++++++++++---- src/framebuffer.rs | 38 ++--- src/main.rs | 124 +++++++++----- 5 files changed, 626 insertions(+), 87 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index de2a827..2f10ab5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,21 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c583acf993cf4245c4acb0a2cc2ab1f9cc097de73411bb6d3647ff6af2b1013d" + [[package]] name = "autocfg" version = "1.5.1" @@ -70,12 +85,38 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "cc" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "005ec2760ca554fae18df7a11195552ec576cd665632a881bc011d5bb2fd4d80" +dependencies = [ + "find-msvc-tools", + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" +[[package]] +name = "const-default" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b396d1f76d455557e1218ec8066ae14bba60b4b36ecd55577ba979f5db7ecaa" + +[[package]] +name = "cordyceps" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b9ab7e0ca1d179628fa0172b2b97203c7fa0cd81be2448bd446fb9559ca9261" +dependencies = [ + "loom", + "tracing", +] + [[package]] name = "cpufeatures" version = "0.3.1" @@ -110,6 +151,16 @@ dependencies = [ "hybrid-array", ] +[[package]] +name = "darling" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" +dependencies = [ + "darling_core 0.20.11", + "darling_macro 0.20.11", +] + [[package]] name = "darling" version = "0.21.3" @@ -130,6 +181,20 @@ dependencies = [ "darling_macro 0.24.1", ] +[[package]] +name = "darling_core" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.119", +] + [[package]] name = "darling_core" version = "0.21.3" @@ -156,6 +221,17 @@ dependencies = [ "syn 3.0.5", ] +[[package]] +name = "darling_macro" +version = "0.20.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" +dependencies = [ + "darling_core 0.20.11", + "quote", + "syn 2.0.119", +] + [[package]] name = "darling_macro" version = "0.21.3" @@ -266,6 +342,37 @@ dependencies = [ "nb 1.1.0", ] +[[package]] +name = "embassy-executor" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0d3b15c9d7dc4fec1d8cb77112472fb008b3b28c51ad23838d83587a6d2f1e" +dependencies = [ + "cordyceps", + "critical-section", + "document-features", + "embassy-executor-macros", + "embassy-executor-timer-queue", +] + +[[package]] +name = "embassy-executor-macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d11a246f53de5f97a387f40ac24726817cd0b6f833e7603baac784f29d6ff276" +dependencies = [ + "darling 0.20.11", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "embassy-executor-timer-queue" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fc328bf943af66b80b98755db9106bf7e7471b0cf47dc8559cd9a6be504cc9c" + [[package]] name = "embassy-futures" version = "0.1.2" @@ -323,6 +430,41 @@ dependencies = [ "heapless 0.9.3", ] +[[package]] +name = "embassy-time" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "592b0c143ec626e821d4d90da51a2bd91d559d6c442b7c74a47d368c9e23d97a" +dependencies = [ + "cfg-if", + "critical-section", + "document-features", + "embassy-time-driver", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "futures-core", +] + +[[package]] +name = "embassy-time-driver" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ee71af1b3a0deaa53eaf2d39252f83504c853646e472400b763060389b9fcc9" +dependencies = [ + "document-features", +] + +[[package]] +name = "embassy-time-queue-utils" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168297bf80aaf114b3c9ad589bf38b01b3009b9af7f97cd18086c5bbf96f5693" +dependencies = [ + "embassy-executor-timer-queue", + "heapless 0.9.3", +] + [[package]] name = "embedded-can" version = "0.4.1" @@ -462,6 +604,22 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" +[[package]] +name = "esp-alloc" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f73e8d355019c740f6a5a78429633040617d28a378c271464caadc52c78216e3" +dependencies = [ + "allocator-api2", + "document-features", + "enumset", + "esp-config", + "esp-metadata-generated", + "esp-sync", + "linked_list_allocator", + "rlsf", +] + [[package]] name = "esp-backtrace" version = "0.20.0" @@ -634,6 +792,29 @@ dependencies = [ "esp32c6", ] +[[package]] +name = "esp-rtos" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f8e99f512a839c78a77c2e55e1f0d208851cfa48935847d5e0b5e8b2c5a6289" +dependencies = [ + "document-features", + "embassy-executor", + "embassy-sync 0.8.0", + "embassy-time-driver", + "embassy-time-queue-utils", + "esp-alloc", + "esp-config", + "esp-hal", + "esp-hal-procmacros", + "esp-metadata-generated", + "esp-rom-sys", + "esp-sync", + "portable-atomic", + "riscv", + "xtensa-lx", +] + [[package]] name = "esp-storage" version = "0.10.0" @@ -769,18 +950,28 @@ dependencies = [ name = "espboard" version = "0.1.0" dependencies = [ + "embassy-executor", + "embassy-time", "embedded-graphics", + "embedded-hal 1.0.0", "embedded-hal-bus", "esp-backtrace", "esp-bootloader-esp-idf", "esp-hal", "esp-hal-smartled", "esp-println", + "esp-rtos", "mipidsi", "smart-leds", "smart-leds-trait", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d" + [[package]] name = "float-cmp" version = "0.9.0" @@ -840,6 +1031,21 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a" +[[package]] +name = "generator" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3b854b0e584ead1a33f18b2fcad7cf7be18b3875c78816b753639aa501513ae" +dependencies = [ + "cc", + "cfg-if", + "libc", + "log", + "rustversion", + "windows-link", + "windows-result", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -980,12 +1186,24 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.189" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" +[[package]] +name = "linked_list_allocator" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b23ac50abb8261cb38c6e2a7192d3302e0836dac1628f6a93b82b4fad185897" + [[package]] name = "litrs" version = "1.0.0" @@ -998,6 +1216,28 @@ version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" +[[package]] +name = "loom" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "419e0dc8046cb947daa77eb95ae174acfbddb7673b4151f56d1eed8e93fbfaca" +dependencies = [ + "cfg-if", + "generator", + "scoped-tls", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + [[package]] name = "memchr" version = "2.8.3" @@ -1036,6 +1276,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -1054,6 +1303,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + [[package]] name = "paste" version = "1.0.15" @@ -1126,6 +1381,23 @@ version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + [[package]] name = "rgb" version = "0.8.53" @@ -1191,6 +1463,18 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1376b15f3ff160e9b1e8ea564ce427f2f6fcf77528cc0a8bf405cb476f9cea7" +[[package]] +name = "rlsf" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07393724337be2ee43a9d86164df4505746874a3fa65913374bc6d6a92314362" +dependencies = [ + "cfg-if", + "const-default", + "libc", + "rustversion", +] + [[package]] name = "rustversion" version = "1.0.23" @@ -1203,6 +1487,12 @@ version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "serde" version = "1.0.229" @@ -1257,6 +1547,27 @@ dependencies = [ "digest 0.11.3", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "smallvec" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9be42f50aa861c555654aa3a37f52f4b1074bacf4e48fe0ef7fa584e80f1f0f" + [[package]] name = "smart-leds" version = "0.4.0" @@ -1394,6 +1705,15 @@ dependencies = [ "syn 3.0.5", ] +[[package]] +name = "thread_local" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" +dependencies = [ + "cfg-if", +] + [[package]] name = "toml_datetime" version = "1.1.1+spec-1.1.0" @@ -1424,6 +1744,67 @@ dependencies = [ "winnow", ] +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + [[package]] name = "typenum" version = "1.20.1" @@ -1448,6 +1829,12 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "vcell" version = "0.1.3" @@ -1481,6 +1868,15 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-sys" version = "0.61.2" diff --git a/Cargo.toml b/Cargo.toml index af52398..244264b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,12 +5,16 @@ edition = "2024" [dependencies] embedded-graphics = "0.8" +embedded-hal = "1.0" embedded-hal-bus = "0.3" +embassy-executor = "0.10" +embassy-time = "0.5" esp-backtrace = { version = "0.20.0", features = ["esp32c6", "println", "panic-handler"] } esp-bootloader-esp-idf = { version = "0.6.0", features = ["esp32c6"] } esp-hal = { version = "1.2.0", features = ["esp32c6", "unstable"] } 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-rtos = { version = "0.4", features = ["embassy", "esp32c6"] } mipidsi = "0.10" smart-leds = "0.4" smart-leds-trait = "0.3" diff --git a/src/display.rs b/src/display.rs index 5510818..aa8c10c 100644 --- a/src/display.rs +++ b/src/display.rs @@ -1,11 +1,10 @@ -use embedded_hal_bus::spi::{ExclusiveDevice, NoDelay}; -use esp_hal::delay::Delay; +use embedded_hal::spi::{ErrorType, Operation, SpiDevice}; 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::peripherals::{DMA_CH0, GPIO14, GPIO15, GPIO21, GPIO6, GPIO7, SPI2}; +use esp_hal::spi::master::{Config as SpiConfig, Spi, SpiDma}; +use esp_hal::spi::{Error as SpiError, Mode}; use esp_hal::time::Rate; -use esp_hal::Blocking; +use esp_hal::Async; use mipidsi::interface::SpiInterface; use mipidsi::models::ST7789; use mipidsi::options::{ColorInversion, ColorOrder, Orientation, Rotation}; @@ -14,43 +13,97 @@ use mipidsi::{Builder, Display}; pub const WIDTH: u32 = 172; pub const HEIGHT: u32 = 320; -const SPI_FREQ: Rate = Rate::from_mhz(40); +const SPI_FREQ: Rate = Rate::from_mhz(60); /// Column offset of the 172-px-wide panel within the ST7789's 240-px framebuffer. const COLUMN_OFFSET: u16 = 34; -type Device = ExclusiveDevice, Output<'static>, NoDelay>; -type Interface<'a> = SpiInterface<'a, Device, Output<'static>>; +/// Size of the internal DMA copy buffer used by `write_async`. +const DMA_BUF_SIZE: usize = 32000; -/// The mipidsi [`Display`], which implements embedded-graphics' [`DrawTarget`]. -/// -/// The lifetime is the borrow of the pixel buffer passed to [`init`]. +pub type SpiDmaBus = SpiDma<'static, Async>; + +/// SPI bus + CS, presented to mipidsi as an `SpiDevice` for init, while keeping +/// the raw pieces recoverable for the async framebuffer path. +pub struct LcdDevice { + spi: SpiDmaBus, + cs: Output<'static>, +} + +impl LcdDevice { + pub fn into_inner(self) -> (SpiDmaBus, Output<'static>) { + (self.spi, self.cs) + } +} + +impl ErrorType for LcdDevice { + type Error = SpiError; +} + +impl SpiDevice for LcdDevice { + fn transaction(&mut self, operations: &mut [Operation<'_, u8>]) -> Result<(), Self::Error> { + self.cs.set_low(); + let result = (|| { + for operation in operations { + match operation { + Operation::Write(buf) => self.spi.write(buf)?, + Operation::Read(buf) => self.spi.read(buf)?, + Operation::Transfer(read, write) => self.spi.transfer(read, write)?, + Operation::TransferInPlace(buf) => self.spi.transfer_in_place(buf)?, + Operation::DelayNs(_) => {}, + } + } + Ok(()) + })(); + self.cs.set_high(); + result + } +} + +type Interface<'a> = SpiInterface<'a, LcdDevice, Output<'static>>; + +/// The mipidsi [`Display`] used to initialize the panel. pub type Lcd<'a> = Display, 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); +/// All the peripheral resources the LCD needs. +pub struct LcdPins { + pub spi2: SPI2<'static>, + pub sck: GPIO7<'static>, + pub mosi: GPIO6<'static>, + pub dc: GPIO15<'static>, + pub cs: GPIO14<'static>, + pub rst: GPIO21<'static>, + pub dma_ch: DMA_CH0<'static>, +} - 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"); +pub fn init<'a>(pins: LcdPins, buffer: &'a mut [u8]) -> Lcd<'a> { + 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 dc_pin = Output::new(dc, Level::Low, OutputConfig::default()); + let spi = Spi::new( + pins.spi2, + SpiConfig::default().with_frequency(SPI_FREQ).with_mode(Mode::_0), + ) + .expect("Failed to configure SPI") + .with_sck(pins.sck) + .with_mosi(pins.mosi) + .with_dma(pins.dma_ch) + .with_buffers(dma_rx_buf, dma_tx_buf) + .into_async(); + + let cs_pin = Output::new(pins.cs, Level::High, OutputConfig::default()); + let device = LcdDevice { + spi, + cs: cs_pin, + }; + + let dc_pin = Output::new(pins.dc, Level::Low, OutputConfig::default()); let interface = SpiInterface::new(device, dc_pin, buffer); - let mut rst_pin = Output::new(rst, Level::High, OutputConfig::default()); + let mut rst_pin = Output::new(pins.rst, Level::High, OutputConfig::default()); rst_pin.set_high(); - let mut delay = Delay::new(); + let mut delay = esp_hal::delay::Delay::new(); Builder::new(ST7789, interface) .display_size(WIDTH as u16, HEIGHT as u16) @@ -61,4 +114,42 @@ pub fn init<'a>( .reset_pin(rst_pin) .init(&mut delay) .expect("Failed to init display") +} + +/// 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> { + let x0 = COLUMN_OFFSET; + let x1 = COLUMN_OFFSET + WIDTH as u16 - 1; + let y1 = HEIGHT as u16 - 1; + + 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]) + .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?; + + dc.set_low(); + spi.write_async(&[0x2C]).await?; + dc.set_high(); + + spi.write_async(bytes).await?; + cs.set_high(); + + Ok(()) } \ No newline at end of file diff --git a/src/framebuffer.rs b/src/framebuffer.rs index e510884..36f59ac 100644 --- a/src/framebuffer.rs +++ b/src/framebuffer.rs @@ -1,32 +1,37 @@ use embedded_graphics::draw_target::DrawTarget; use embedded_graphics::geometry::{OriginDimensions, Size}; -use embedded_graphics::pixelcolor::{Rgb565, RgbColor}; +use embedded_graphics::pixelcolor::Rgb565; +use embedded_graphics::prelude::IntoStorage; use embedded_graphics::Pixel; use crate::display::{HEIGHT, WIDTH}; const PIXEL_COUNT: usize = (WIDTH * HEIGHT) as usize; +const BYTE_COUNT: usize = PIXEL_COUNT * 2; -/// The full-screen back buffer (172 × 320 × 2 bytes ≈ 110 KB). -static mut PIXELS: [Rgb565; PIXEL_COUNT] = [Rgb565::BLACK; PIXEL_COUNT]; +/// Full-screen back buffer (172 × 320 × 2 bytes ≈ 110 KB). +/// +/// Pixels are stored as RGB565 **big-endian** byte pairs, which is the wire +/// format the ST7789 expects, so the buffer can be handed straight to DMA. +static mut PIXELS: [u8; BYTE_COUNT] = [0; BYTE_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). +/// safe to use from a single task (the display task). 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); + // SAFETY: only used from the single display task. + unsafe { &mut *core::ptr::addr_of_mut!(PIXELS) }.fill(0); } - /// 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. + /// The pixels as bytes in the display's wire format (RGB565 big-endian). + pub fn as_bytes(&self) -> &'static [u8] { + // SAFETY: the buffer lives for the whole program and is only read for + // the duration of the (blocking-await) blit. unsafe { &*core::ptr::addr_of!(PIXELS) } } } @@ -39,13 +44,16 @@ impl DrawTarget for Framebuffer { where I: IntoIterator>, { - // SAFETY: only used from the single main loop. + // SAFETY: only used from the single display task. 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; + let idx = (y as usize) * WIDTH as usize + (x as usize); + let value = pixel.1.into_storage(); + buffer[idx * 2] = (value >> 8) as u8; + buffer[idx * 2 + 1] = value as u8; } } @@ -57,10 +65,4 @@ impl OriginDimensions for Framebuffer { fn size(&self) -> Size { Size::new(WIDTH, HEIGHT) } -} - -impl Default for Framebuffer { - fn default() -> Self { - Self - } } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index b04f0f9..c49c90c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -6,55 +6,53 @@ mod display; mod framebuffer; 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 embedded_graphics::primitives::{PrimitiveStyle, Triangle}; +use embassy_executor::Spawner; +use embassy_time::Timer; use esp_backtrace as _; use esp_bootloader_esp_idf::esp_app_desc; +use esp_hal::gpio::Output; +use esp_hal::timer::timg::TimerGroup; use esp_hal::{init, rmt::Rmt, time::Rate, Config}; use esp_println::println; use smart_leds::hsv::{hsv2rgb, Hsv}; -use smart_leds::RGB8; -const SAT: u8 = 200; -const VAL: u8 = 200; +const SAT: u8 = 255; +const VAL: u8 = 255; esp_app_desc!(); -#[esp_hal::main] -fn main() -> ! { - let peripherals = init(Config::default()); +#[embassy_executor::task] +async fn led_task(mut led: led::RgbLed) { + let mut frame = 0u32; + loop { + let hue = (frame % 256) as u8; + let lit = hsv2rgb(Hsv { + hue, + sat: SAT, + val: VAL, + }); + led.set_rgb(lit).unwrap(); + Timer::after_millis(30).await; + frame = frame.wrapping_add(1); + } +} - let mut led = { - 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"); +#[embassy_executor::task] +async fn display_task( + mut spi: display::SpiDmaBus, + mut dc: Output<'static>, + mut cs: Output<'static>, + mut fb: framebuffer::Framebuffer, + timer: esp_hal::ledc::timer::Timer<'static, esp_hal::ledc::LowSpeed>, + bl_pin: esp_hal::peripherals::GPIO22<'static>, +) { let mut backlight = - backlight::LcdBacklight::new(&timer, peripherals.GPIO22).expect("Failed to init backlight"); + backlight::LcdBacklight::new(&timer, bl_pin).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(); - - println!("LCD {}x{} initialized", display::WIDTH, display::HEIGHT); - lcd.clear(Rgb565::BLACK).unwrap(); - - let mut fb = framebuffer::Framebuffer; - let screen = Rectangle::new(Point::zero(), Size::new(display::WIDTH, display::HEIGHT)); + println!("backlight set to 0.5"); let mut pts = [ Point::new(86, 20), @@ -72,8 +70,7 @@ fn main() -> ! { sat: SAT, val: VAL, }); - led.set_rgb(RGB8::new(0,0,0)).unwrap(); - let accent = Rgb565::new(lit.r / 8, lit.g / 4, lit.b / 8); + let accent = Rgb565::new(lit.r/8, lit.g/4, lit.b/8); for p in &mut pts { p.x += vx; @@ -98,7 +95,8 @@ fn main() -> ! { .draw(&mut fb) .unwrap(); - lcd.fill_contiguous(&screen, fb.pixels().iter().cloned()) + display::send_framebuffer(&mut spi, &mut dc, &mut cs, fb.as_bytes()) + .await .unwrap(); if frame % 32 == 0 { @@ -109,6 +107,54 @@ fn main() -> ! { } frame = frame.wrapping_add(1); - //delay.delay_millis(30); + } +} + +#[esp_rtos::main] +async fn main(spawner: Spawner) -> ! { + let peripherals = init(Config::default()); + let timg0 = TimerGroup::new(peripherals.TIMG0); + esp_rtos::start(timg0.timer0, peripherals.FROM_CPU_INTR0); + + let led = { + 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"); + + let mut buffer = [0u8; 4096]; + let lcd = display::init( + display::LcdPins { + spi2: peripherals.SPI2, + sck: peripherals.GPIO7, + mosi: peripherals.GPIO6, + dc: peripherals.GPIO15, + cs: peripherals.GPIO14, + rst: peripherals.GPIO21, + dma_ch: peripherals.DMA_CH0, + }, + &mut buffer, + ); + + let (interface, _model, _rst) = lcd.release(); + let (device, dc) = interface.release(); + let (spi, cs) = device.into_inner(); + + println!("LCD {}x{} initialized", display::WIDTH, display::HEIGHT); + + spawner.spawn(led_task(led).unwrap()); + spawner.spawn(display_task( + spi, + dc, + cs, + framebuffer::Framebuffer, + timer, + peripherals.GPIO22, + ).unwrap()); + + loop { + Timer::after_millis(1000).await; } } \ No newline at end of file