diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index 33dcbeb596a..bb42965668e 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - You can now use `Input`, `Output`, `OutputOpenDrain` and `Flex` pins as EXTI and RTCIO wakeup sources (#2095) - Added `Rtc::set_current_time` to allow setting RTC time, and `Rtc::current_time` to getting RTC time while taking into account boot time (#1883) - Added APIs to allow connecting signals through the GPIO matrix. (#2128) +- Allow I8080 transfers to be cancelled on the spot (#2191) - Implement `TryFrom` for `ledc::timer::config::Duty` (#1984) - Expose `RtcClock::get_xtal_freq` and `RtcClock::get_slow_freq` publically for all chips (#2183) - TWAI support for ESP32-H2 (#2199) @@ -45,6 +46,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - ESP32: Added support for touch sensing on GPIO32 and 33 (#2109) - Removed gpio pin generics from I8080 driver type. (#2171) - I8080 driver now decides bus width at transfer time rather than construction time. (#2171) +- Migrate the I8080 driver to a move based API (#2191) - Replaced `AnyPin` with `InputSignal` and `OutputSignal` and renamed `ErasedPin` to `AnyPin` (#2128) - Replaced the `ErasedTimer` enum with the `AnyTimer` struct. (#2144) - Changed the parameters of `Spi::with_pins` to no longer be optional (#2133) @@ -87,6 +89,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Removed the following functions from `GpioPin`: `is_high`, `is_low`, `set_high`, `set_low`, `set_state`, `is_set_high`, `is_set_low`, `toggle`. (#2094) - Removed `Rtc::get_time_raw` (#1883) - Removed `_with_default_pins` UART constructors (#2132) +- Removed transfer methods `send`, `send_dma` and `send_dma_async` from `I8080` (#2191) - Removed `uart::{DefaultRxPin, DefaultTxPin}` (#2132) - Removed `PcntSource` and `PcntInputConfig`. (#2134) - Removed the `place-spi-driver-in-ram` feature, this is now enabled via [esp-config](https://docs.rs/esp-config) (#2156) diff --git a/esp-hal/MIGRATING-0.20.md b/esp-hal/MIGRATING-0.20.md index ea03064eb02..7260d9462a5 100644 --- a/esp-hal/MIGRATING-0.20.md +++ b/esp-hal/MIGRATING-0.20.md @@ -189,14 +189,31 @@ The I8080 driver no longer holds on to pins in its type definition. + let _: I8080<'a, DmaChannel3, Blocking>; ``` -## I8080 start transfer type inference +## I8080 transfer API changes -The I8080 driver now decides bus width at transfer time, which means you don't get inference. +- The I8080 driver now decides bus width at transfer time, which means you don't get type inference anymore. +- Starting a transfer moves the driver into the transfer object, allowing you to store it in a `static` or struct. +- The transfer API no longer works with plain slices, it now works with `DmaTxBuffer`s which allow to bring your own DMA data structure and implement efficient queueing of transfers. +- The three transfer methods (`send`, `send_dma`, `send_dma_async`) have been merged into one `send` method. ```diff -let mut i8080 = I8080::new(....); +let (_, _, tx_buffer, tx_descriptors) = dma_buffers!(0, 32678); ++ let mut dma_buf = DmaTxBuf::new(tx_descriptors, tx_buffer).unwrap(); + +let mut i8080 = I8080::new( + lcd_cam.lcd, + channel.tx, +- tx_descriptors, + tx_pins, + 20.MHz(), + Config::default(), +) + - i8080.send(0x12, 0, &[0, 1, 2, 3, 4]); -+ i8080.send(0x12u8, 0, &[0, 1, 2, 3, 4]); ++ dma_buf.fill(&[0, 1, 2, 3, 4]); ++ let transfer = i8080.send(0x12u8, 0, dma_buf).unwrap(); ++ // transfer.wait_for_done().await; ++ (_, i8080, dma_buf) = transfer.wait(); ``` ### Placing drivers in RAM is now done via esp-config diff --git a/esp-hal/src/lcd_cam/lcd/i8080.rs b/esp-hal/src/lcd_cam/lcd/i8080.rs index 3b38d8cbf30..45fd953a12e 100644 --- a/esp-hal/src/lcd_cam/lcd/i8080.rs +++ b/esp-hal/src/lcd_cam/lcd/i8080.rs @@ -17,14 +17,14 @@ #![doc = crate::before_snippet!()] //! # use esp_hal::gpio::Io; //! # use esp_hal::lcd_cam::{LcdCam, lcd::i8080::{Config, I8080, TxEightBits}}; -//! # use esp_hal::dma_buffers; -//! # use esp_hal::dma::{Dma, DmaPriority}; +//! # use esp_hal::dma_tx_buffer; +//! # use esp_hal::dma::{Dma, DmaPriority, DmaTxBuf}; //! # let io = Io::new(peripherals.GPIO, peripherals.IO_MUX); //! //! # let dma = Dma::new(peripherals.DMA); //! # let channel = dma.channel0; //! -//! # let ( _, rx_descriptors, tx_buffer, tx_descriptors) = dma_buffers!(32678, 0); +//! # let mut dma_buf = dma_tx_buffer!(32678).unwrap(); //! //! # let channel = channel.configure( //! # false, @@ -46,41 +46,34 @@ //! let mut i8080 = I8080::new( //! lcd_cam.lcd, //! channel.tx, -//! tx_descriptors, //! tx_pins, //! 20.MHz(), //! Config::default(), //! ) //! .with_ctrl_pins(io.pins.gpio0, io.pins.gpio47); //! -//! i8080.send(0x3Au8, 0, &[0x55]).unwrap(); // RGB565 +//! dma_buf.fill(&[0x55]); +//! let transfer = i8080.send(0x3Au8, 0, dma_buf).unwrap(); // RGB565 +//! transfer.wait(); //! # } //! ``` -use core::{fmt::Formatter, marker::PhantomData, mem::size_of}; +use core::{ + fmt::Formatter, + marker::PhantomData, + mem::{size_of, ManuallyDrop}, +}; use fugit::HertzU32; use crate::{ clock::Clocks, - dma::{ - dma_private::{DmaSupport, DmaSupportTx}, - ChannelTx, - DescriptorChain, - DmaChannel, - DmaDescriptor, - DmaError, - DmaPeripheral, - DmaTransferTx, - LcdCamPeripheral, - ReadBuffer, - Tx, - }, + dma::{ChannelTx, DmaChannel, DmaError, DmaPeripheral, DmaTxBuffer, LcdCamPeripheral, Tx}, gpio::{OutputSignal, PeripheralOutput}, lcd_cam::{ - asynch::LcdDoneFuture, + asynch::LCD_DONE_WAKER, lcd::{i8080::private::TxPins, ClockMode, DelayMode, Phase, Polarity}, - private::calculate_clkm, + private::{calculate_clkm, Instance}, BitOrder, ByteOrder, Lcd, @@ -94,7 +87,6 @@ use crate::{ pub struct I8080<'d, CH: DmaChannel, DM: Mode> { lcd_cam: PeripheralRef<'d, LCD_CAM>, tx_channel: ChannelTx<'d, CH>, - tx_chain: DescriptorChain, _phantom: PhantomData, } @@ -106,7 +98,6 @@ where pub fn new( lcd: Lcd<'d, DM>, channel: ChannelTx<'d, CH>, - descriptors: &'static mut [DmaDescriptor], mut pins: P, frequency: HertzU32, config: Config, @@ -214,37 +205,11 @@ where Self { lcd_cam, tx_channel: channel, - tx_chain: DescriptorChain::new(descriptors), _phantom: PhantomData, } } } -impl<'d, CH: DmaChannel, DM: Mode> DmaSupport for I8080<'d, CH, DM> { - fn peripheral_wait_dma(&mut self, _is_rx: bool, _is_tx: bool) { - let lcd_user = self.lcd_cam.lcd_user(); - // Wait until LCD_START is cleared by hardware. - while lcd_user.read().lcd_start().bit_is_set() {} - self.tear_down_send(); - } - - fn peripheral_dma_stop(&mut self) { - unreachable!("unsupported") - } -} - -impl<'d, CH: DmaChannel, DM: Mode> DmaSupportTx for I8080<'d, CH, DM> { - type TX = ChannelTx<'d, CH>; - - fn tx(&mut self) -> &mut Self::TX { - &mut self.tx_channel - } - - fn chain(&mut self) -> &mut DescriptorChain { - &mut self.tx_chain - } -} - impl<'d, CH: DmaChannel, DM: Mode> I8080<'d, CH, DM> { /// Configures the byte order for data transmission. pub fn set_byte_order(&mut self, byte_order: ByteOrder) -> &mut Self { @@ -291,33 +256,6 @@ impl<'d, CH: DmaChannel, DM: Mode> I8080<'d, CH, DM> { self } - /// Sends a command and data to the LCD using the I8080 interface. - /// - /// Passing a `Command` will make this an 8-bit transfer and a - /// `Command` will make this a 16-bit transfer. - /// - /// Note: A 16-bit transfer on an 8-bit bus will silently truncate the 2nd - /// byte and an 8-bit transfer on a 16-bit bus will silently pad each - /// byte to 2 bytes. - pub fn send>( - &mut self, - cmd: impl Into>, - dummy: u8, - data: &[W], - ) -> Result<(), DmaError> { - self.setup_send(cmd.into(), dummy); - self.start_write_bytes_dma(data.as_ptr() as _, core::mem::size_of_val(data))?; - self.start_send(); - - let lcd_user = self.lcd_cam.lcd_user(); - // Wait until LCD_START is cleared by hardware. - while lcd_user.read().lcd_start().bit_is_set() {} - - self.tear_down_send(); - - Ok(()) - } - /// Sends a command and data to the LCD using DMA. /// /// Passing a `Command` will make this an 8-bit transfer and a @@ -326,61 +264,14 @@ impl<'d, CH: DmaChannel, DM: Mode> I8080<'d, CH, DM> { /// Note: A 16-bit transfer on an 8-bit bus will silently truncate the 2nd /// byte and an 8-bit transfer on a 16-bit bus will silently pad each /// byte to 2 bytes. - pub fn send_dma<'t, W, TXBUF>( - &'t mut self, + pub fn send + Copy, BUF: DmaTxBuffer>( + mut self, cmd: impl Into>, dummy: u8, - data: &'t TXBUF, - ) -> Result, DmaError> - where - W: Copy + Into, - TXBUF: ReadBuffer, - { - let (ptr, len) = unsafe { data.read_buffer() }; - - self.setup_send(cmd.into(), dummy); - self.start_write_bytes_dma(ptr as _, len)?; - self.start_send(); - - Ok(DmaTransferTx::new(self)) - } -} + mut data: BUF, + ) -> Result, (DmaError, Self, BUF)> { + let cmd = cmd.into(); -impl<'d, CH: DmaChannel> I8080<'d, CH, crate::Async> { - /// Asynchronously sends a command and data to the LCD using DMA. - /// - /// Passing a `Command` will make this an 8-bit transfer and a - /// `Command` will make this a 16-bit transfer. - /// - /// Note: A 16-bit transfer on an 8-bit bus will silently truncate the 2nd - /// byte and an 8-bit transfer on a 16-bit bus will silently pad each - /// byte to 2 bytes. - pub async fn send_dma_async<'t, W, TXBUF>( - &'t mut self, - cmd: impl Into>, - dummy: u8, - data: &'t TXBUF, - ) -> Result<(), DmaError> - where - W: Copy + Into, - TXBUF: ReadBuffer, - { - let (ptr, len) = unsafe { data.read_buffer() }; - - self.setup_send(cmd.into(), dummy); - self.start_write_bytes_dma(ptr as _, len)?; - self.start_send(); - - LcdDoneFuture::new().await; - if self.tx_channel.has_error() { - return Err(DmaError::DescriptorError); - } - Ok(()) - } -} - -impl<'d, CH: DmaChannel, DM: Mode> I8080<'d, CH, DM> { - fn setup_send>(&mut self, cmd: Command, dummy: u8) { // Reset LCD control unit and Async Tx FIFO self.lcd_cam .lcd_user() @@ -417,7 +308,7 @@ impl<'d, CH: DmaChannel, DM: Mode> I8080<'d, CH, DM> { } } - let is_2byte_mode = size_of::() == 2; + let is_2byte_mode = size_of::() == 2; self.lcd_cam.lcd_user().modify(|_, w| unsafe { // Set dummy length @@ -434,9 +325,25 @@ impl<'d, CH: DmaChannel, DM: Mode> I8080<'d, CH, DM> { .lcd_2byte_en() .bit(is_2byte_mode) }); - } - fn start_send(&mut self) { + // Use continous mode for DMA. FROM the S3 TRM: + // > In a continuous output, LCD module keeps sending data till: + // > i. LCD_CAM_LCD_START is cleared; + // > ii. or LCD_CAM_LCD_RESET is set; + // > iii. or all the data in GDMA is sent out. + self.lcd_cam + .lcd_user() + .modify(|_, w| w.lcd_always_out_en().set_bit().lcd_dout().set_bit()); + + let result = unsafe { + self.tx_channel + .prepare_transfer(DmaPeripheral::LcdCam, &mut data) + } + .and_then(|_| self.tx_channel.start_transfer()); + if let Err(err) = result { + return Err((err, self, data)); + } + // Setup interrupts. self.lcd_cam .lc_dma_int_clr() @@ -450,51 +357,137 @@ impl<'d, CH: DmaChannel, DM: Mode> I8080<'d, CH, DM> { w.lcd_update().set_bit(); w.lcd_start().set_bit() }); + + Ok(I8080Transfer { + i8080: ManuallyDrop::new(self), + tx_buf: ManuallyDrop::new(data), + }) } +} - fn tear_down_send(&mut self) { - // This will already be cleared unless the user is trying to cancel, - // which is why this is still here. - self.lcd_cam +impl<'d, CH: DmaChannel, DM: Mode> core::fmt::Debug for I8080<'d, CH, DM> { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + f.debug_struct("I8080").finish() + } +} + +/// Represents an ongoing (or potentially finished) transfer using the I8080 LCD +/// interface +pub struct I8080Transfer<'d, BUF, CH: DmaChannel, DM: Mode> { + i8080: ManuallyDrop>, + tx_buf: ManuallyDrop, +} + +impl<'d, BUF, CH: DmaChannel, DM: Mode> I8080Transfer<'d, BUF, CH, DM> { + /// Returns true when [Self::wait] will not block. + pub fn is_done(&self) -> bool { + self.i8080 + .lcd_cam .lcd_user() - .modify(|_, w| w.lcd_start().clear_bit()); + .read() + .lcd_start() + .bit_is_clear() + } - self.lcd_cam + /// Stops this transfer on the spot and returns the peripheral and buffer. + pub fn cancel(mut self) -> (I8080<'d, CH, DM>, BUF) { + self.stop_peripherals(); + let (_, i8080, buf) = self.wait(); + (i8080, buf) + } + + /// Waits for the transfer to finish and returns the peripheral and buffer. + /// + /// Note: This also clears the transfer interrupt so it can be used in + /// interrupt handlers to "handle" the interrupt. + pub fn wait(mut self) -> (Result<(), DmaError>, I8080<'d, CH, DM>, BUF) { + while !self.is_done() {} + + // Clear "done" interrupt. + self.i8080 + .lcd_cam .lc_dma_int_clr() .write(|w| w.lcd_trans_done_int_clr().set_bit()); - } - fn start_write_bytes_dma(&mut self, ptr: *const u8, len: usize) -> Result<(), DmaError> { - if len == 0 { - // Set transfer length. - self.lcd_cam - .lcd_user() - .modify(|_, w| w.lcd_dout().clear_bit()); + // SAFETY: Since forget is called on self, we know that self.i8080 and + // self.tx_buf won't be touched again. + let (i8080, tx_buf) = unsafe { + let i8080 = ManuallyDrop::take(&mut self.i8080); + let tx_buf = ManuallyDrop::take(&mut self.tx_buf); + core::mem::forget(self); + (i8080, tx_buf) + }; + + let result = if i8080.tx_channel.has_error() { + Err(DmaError::DescriptorError) } else { - // Use continous mode for DMA. FROM the S3 TRM: - // > In a continuous output, LCD module keeps sending data till: - // > i. LCD_CAM_LCD_START is cleared; - // > ii. or LCD_CAM_LCD_RESET is set; - // > iii. or all the data in GDMA is sent out. - self.lcd_cam.lcd_user().modify(|_, w| { - w.lcd_always_out_en().set_bit(); - w.lcd_dout().set_bit() - }); - - unsafe { - self.tx_chain.fill_for_tx(false, ptr, len)?; - self.tx_channel - .prepare_transfer_without_start(DmaPeripheral::LcdCam, &self.tx_chain)?; + Ok(()) + }; + + (result, i8080, tx_buf) + } + + fn stop_peripherals(&mut self) { + // Stop the LCD_CAM peripheral. + self.i8080 + .lcd_cam + .lcd_user() + .modify(|_, w| w.lcd_start().clear_bit()); + + // Stop the DMA + self.i8080.tx_channel.stop_transfer(); + } +} + +impl<'d, BUF, CH: DmaChannel> I8080Transfer<'d, BUF, CH, crate::Async> { + /// Waits for [Self::is_done] to return true. + pub async fn wait_for_done(&mut self) { + use core::{ + future::Future, + pin::Pin, + task::{Context, Poll}, + }; + + struct LcdDoneFuture {} + + impl Future for LcdDoneFuture { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + LCD_DONE_WAKER.register(cx.waker()); + if Instance::is_lcd_done_set() { + // Interrupt bit will be cleared in Self::wait. + // This allows `wait_for_done` to be called more than once. + // + // Instance::clear_lcd_done(); + Poll::Ready(()) + } else { + Instance::listen_lcd_done(); + Poll::Pending + } } - self.tx_channel.start_transfer()?; } - Ok(()) + + impl Drop for LcdDoneFuture { + fn drop(&mut self) { + Instance::unlisten_lcd_done(); + } + } + + LcdDoneFuture {}.await } } -impl<'d, CH: DmaChannel, DM: Mode> core::fmt::Debug for I8080<'d, CH, DM> { - fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { - f.debug_struct("I8080").finish() +impl<'d, BUF, CH: DmaChannel, DM: Mode> Drop for I8080Transfer<'d, BUF, CH, DM> { + fn drop(&mut self) { + self.stop_peripherals(); + + // SAFETY: This is Drop, we know that self.i8080 and self.tx_buf + // won't be touched again. + unsafe { + ManuallyDrop::drop(&mut self.i8080); + ManuallyDrop::drop(&mut self.tx_buf); + } } } diff --git a/esp-hal/src/lcd_cam/mod.rs b/esp-hal/src/lcd_cam/mod.rs index f14f021b3e0..f47b639e6e5 100644 --- a/esp-hal/src/lcd_cam/mod.rs +++ b/esp-hal/src/lcd_cam/mod.rs @@ -118,54 +118,19 @@ pub enum ByteOrder { #[doc(hidden)] pub mod asynch { - use core::task::Poll; - use embassy_sync::waitqueue::AtomicWaker; use procmacros::handler; use super::private::Instance; - static TX_WAKER: AtomicWaker = AtomicWaker::new(); - - #[must_use = "futures do nothing unless you `.await` or poll them"] - pub(crate) struct LcdDoneFuture {} - - impl LcdDoneFuture { - pub(crate) fn new() -> Self { - Self {} - } - } - - impl core::future::Future for LcdDoneFuture { - type Output = (); - - fn poll( - self: core::pin::Pin<&mut Self>, - cx: &mut core::task::Context<'_>, - ) -> core::task::Poll { - TX_WAKER.register(cx.waker()); - if Instance::is_lcd_done_set() { - Instance::clear_lcd_done(); - Poll::Ready(()) - } else { - Instance::listen_lcd_done(); - Poll::Pending - } - } - } - - impl Drop for LcdDoneFuture { - fn drop(&mut self) { - Instance::unlisten_lcd_done(); - } - } + pub(crate) static LCD_DONE_WAKER: AtomicWaker = AtomicWaker::new(); #[handler] pub(crate) fn interrupt_handler() { // TODO: this is a shared interrupt with Camera and here we ignore that! if Instance::is_lcd_done_set() { Instance::unlisten_lcd_done(); - TX_WAKER.wake() + LCD_DONE_WAKER.wake() } } } @@ -199,13 +164,6 @@ mod private { .lcd_trans_done_int_raw() .bit() } - - pub(crate) fn clear_lcd_done() { - let lcd_cam = unsafe { crate::peripherals::LCD_CAM::steal() }; - lcd_cam - .lc_dma_int_clr() - .write(|w| w.lcd_trans_done_int_clr().set_bit()); - } } pub struct ClockDivider { // Integral LCD clock divider value. (8 bits) diff --git a/examples/src/bin/lcd_i8080.rs b/examples/src/bin/lcd_i8080.rs index 4d0ccf0ac8e..a87c1a2ea72 100644 --- a/examples/src/bin/lcd_i8080.rs +++ b/examples/src/bin/lcd_i8080.rs @@ -25,14 +25,15 @@ use esp_backtrace as _; use esp_hal::{ delay::Delay, - dma::{Dma, DmaPriority}, - dma_buffers, + dma::{Dma, DmaChannel0, DmaPriority, DmaTxBuf}, + dma_tx_buffer, gpio::{Input, Io, Level, Output, Pull}, lcd_cam::{ lcd::i8080::{Config, TxEightBits, I8080}, LcdCam, }, prelude::*, + Blocking, }; use esp_println::println; @@ -51,7 +52,7 @@ fn main() -> ! { let dma = Dma::new(peripherals.DMA); let channel = dma.channel0; - let (_, _, tx_buffer, tx_descriptors) = dma_buffers!(0, 32678); + let dma_tx_buf = dma_tx_buffer!(4000).unwrap(); let channel = channel.configure(false, DmaPriority::Priority0); @@ -73,21 +74,50 @@ fn main() -> ! { ); let lcd_cam = LcdCam::new(peripherals.LCD_CAM); - let mut i8080 = I8080::new( + let i8080 = I8080::new( lcd_cam.lcd, channel.tx, - tx_descriptors, tx_pins, 20.MHz(), Config::default(), ) .with_ctrl_pins(lcd_rs, lcd_wr); - // This is here mostly to workaround https://github.com/esp-rs/esp-hal/issues/1532 - let mut send_cmd = |cmd: u8, data: &[u8]| { - let buf = &mut tx_buffer[0..data.len()]; - buf.copy_from_slice(data); - i8080.send(cmd, 0, buf).unwrap(); + // Note: This isn't provided in the HAL since different drivers may require different + // considerations, like how to manage the CS pin, the CD pin, cancellation semantics, + // 8 vs 16 bit, non-native primitives like Rgb565, Rgb888, etc. This Bus is just provided as + // an example of how to implement your own. + struct Bus<'d> { + resources: Option<(I8080<'d, DmaChannel0, Blocking>, DmaTxBuf)>, + } + impl<'d> Bus<'d> { + fn use_resources( + &mut self, + func: impl FnOnce( + I8080<'d, DmaChannel0, Blocking>, + DmaTxBuf, + ) -> (T, I8080<'d, DmaChannel0, Blocking>, DmaTxBuf), + ) -> T { + let (i8080, buf) = self.resources.take().unwrap(); + let (result, i8080, buf) = func(i8080, buf); + self.resources = Some((i8080, buf)); + result + } + + pub fn send(&mut self, cmd: u8, data: &[u8]) { + self.use_resources(|i8080, mut buf| { + buf.fill(data); + match i8080.send(cmd, 0, buf) { + Ok(transfer) => transfer.wait(), + Err((result, i8080, buf)) => (Err(result), i8080, buf), + } + }) + .unwrap(); + } + } + + let mut bus = Bus { + resources: Some((i8080, dma_tx_buf)), }; { @@ -116,10 +146,10 @@ fn main() -> ! { const CMD_DOCA: u8 = 0xE8; // Display Output Ctrl Adjust const CMD_CSCON: u8 = 0xF0; // Command Set Control - send_cmd(CMD_CSCON, &[0xC3]); // Enable extension command 2 part I - send_cmd(CMD_CSCON, &[0x96]); // Enable extension command 2 part II - send_cmd(CMD_INVCTR, &[0x01]); // 1-dot inversion - send_cmd( + bus.send(CMD_CSCON, &[0xC3]); // Enable extension command 2 part I + bus.send(CMD_CSCON, &[0x96]); // Enable extension command 2 part II + bus.send(CMD_INVCTR, &[0x01]); // 1-dot inversion + bus.send( CMD_DFUNCTR, &[ 0x80, // Display Function Control //Bypass @@ -128,7 +158,7 @@ fn main() -> ! { 0x3B, ], ); // LCD Drive Line=8*(59+1) - send_cmd( + bus.send( CMD_DOCA, &[ 0x40, 0x8A, 0x00, 0x00, 0x29, // Source eqaulizing period time= 22.5 us @@ -137,43 +167,44 @@ fn main() -> ! { 0x33, ], ); - send_cmd(CMD_PWCTR2, &[0x06]); // Power control2 //VAP(GVDD)=3.85+( vcom+vcom offset), VAN(GVCL)=-3.85+( + bus.send(CMD_PWCTR2, &[0x06]); // Power control2 //VAP(GVDD)=3.85+( vcom+vcom offset), VAN(GVCL)=-3.85+( // vcom+vcom offset) - send_cmd(CMD_PWCTR3, &[0xA7]); // Power control 3 //Source driving current level=low, Gamma driving current + bus.send(CMD_PWCTR3, &[0xA7]); // Power control 3 //Source driving current level=low, Gamma driving current // level=High - send_cmd(CMD_VMCTR, &[0x18]); // VCOM Control //VCOM=0.9 + bus.send(CMD_VMCTR, &[0x18]); // VCOM Control //VCOM=0.9 delay.delay_micros(120_000); - send_cmd( + bus.send( CMD_GMCTRP1, &[ 0xF0, 0x09, 0x0B, 0x06, 0x04, 0x15, 0x2F, 0x54, 0x42, 0x3C, 0x17, 0x14, 0x18, 0x1B, ], ); - send_cmd( + bus.send( CMD_GMCTRN1, &[ 0xE0, 0x09, 0x0B, 0x06, 0x04, 0x03, 0x2B, 0x43, 0x42, 0x3B, 0x16, 0x14, 0x17, 0x1B, ], ); delay.delay_micros(120_000); - send_cmd(CMD_CSCON, &[0x3C]); // Command Set control // Disable extension command 2 partI - send_cmd(CMD_CSCON, &[0x69]); // Command Set control // Disable + bus.send(CMD_CSCON, &[0x3C]); // Command Set control // Disable extension command 2 partI + bus.send(CMD_CSCON, &[0x69]); // Command Set control // Disable // extension command 2 partII - send_cmd(0x11, &[]); // ExitSleepMode + bus.send(0x11, &[]); // ExitSleepMode delay.delay_micros(130_000); - send_cmd(0x38, &[]); // ExitIdleMode - send_cmd(0x29, &[]); // SetDisplayOn + bus.send(0x38, &[]); // ExitIdleMode + bus.send(0x29, &[]); // SetDisplayOn - send_cmd(0x21, &[]); // SetInvertMode(ColorInversion::Inverted) + bus.send(0x21, &[]); // SetInvertMode(ColorInversion::Inverted) // let madctl = SetAddressMode::from(options); - // send_cmd(madctl)?; + // bus.send(madctl)?; - send_cmd(0x3A, &[0x55]); // RGB565 + bus.send(0x3A, &[0x55]); // RGB565 } - send_cmd(0x35, &[0]); // Tear Effect Line On + // Tearing Effect Line On + bus.send(0x35, &[0]); let width = 320u16; let height = 480u16; @@ -182,12 +213,8 @@ fn main() -> ! { let width_b = width.to_be_bytes(); let height_b = height.to_be_bytes(); - i8080 - .send(0x2A, 0, &[0, 0, width_b[0], width_b[1]]) - .unwrap(); // CASET - i8080 - .send(0x2B, 0, &[0, 0, height_b[0], height_b[1]]) - .unwrap(); // PASET + bus.send(0x2A, &[0, 0, width_b[0], width_b[1]]); // CASET + bus.send(0x2B, &[0, 0, height_b[0], height_b[1]]) // PASET } println!("Drawing"); @@ -200,11 +227,13 @@ fn main() -> ! { let total_pixels = width as usize * height as usize; let total_bytes = total_pixels * 2; - let buffer = tx_buffer; + let (mut i8080, mut dma_tx_buf) = bus.resources.take().unwrap(); + + dma_tx_buf.set_length(dma_tx_buf.capacity()); for color in [RED, BLUE].iter().cycle() { let color = color.to_be_bytes(); - for chunk in buffer.chunks_mut(2) { + for chunk in dma_tx_buf.as_mut_slice().chunks_mut(2) { chunk.copy_from_slice(&color); } @@ -222,20 +251,18 @@ fn main() -> ! { let mut bytes_left_to_write = total_bytes; - let transfer = i8080.send_dma(0x2Cu8, 0, &buffer).unwrap(); - transfer.wait().unwrap(); - - bytes_left_to_write -= buffer.len(); + (_, i8080, dma_tx_buf) = i8080.send(0x2Cu8, 0, dma_tx_buf).unwrap().wait(); - while bytes_left_to_write >= buffer.len() { - let transfer = i8080.send_dma(0x3Cu8, 0, &buffer).unwrap(); - transfer.wait().unwrap(); + bytes_left_to_write -= dma_tx_buf.len(); - bytes_left_to_write -= buffer.len(); + while bytes_left_to_write >= dma_tx_buf.len() { + (_, i8080, dma_tx_buf) = i8080.send(0x3Cu8, 0, dma_tx_buf).unwrap().wait(); + bytes_left_to_write -= dma_tx_buf.len(); } if bytes_left_to_write > 0 { - let transfer = i8080.send_dma(0x3Cu8, 0, &buffer).unwrap(); - transfer.wait().unwrap(); + dma_tx_buf.set_length(bytes_left_to_write); + (_, i8080, dma_tx_buf) = i8080.send(0x3Cu8, 0, dma_tx_buf).unwrap().wait(); + dma_tx_buf.set_length(dma_tx_buf.capacity()); } delay.delay_millis(1_000); diff --git a/hil-test/tests/lcd_cam_i8080.rs b/hil-test/tests/lcd_cam_i8080.rs index fe739dada93..85d5c8493b5 100644 --- a/hil-test/tests/lcd_cam_i8080.rs +++ b/hil-test/tests/lcd_cam_i8080.rs @@ -6,7 +6,7 @@ #![no_main] use esp_hal::{ - dma::{Dma, DmaDescriptor, DmaPriority}, + dma::{Dma, DmaPriority, DmaTxBuf}, dma_buffers, gpio::{Io, NoPin}, lcd_cam::{ @@ -21,7 +21,6 @@ use esp_hal::{ prelude::*, }; use hil_test as _; -use static_cell::ConstStaticCell; const DATA_SIZE: usize = 1024 * 10; @@ -30,8 +29,7 @@ struct Context<'d> { pcnt: Pcnt<'d>, io: Io, dma: Dma<'d>, - tx_buffer: &'static mut [u8], - tx_descriptors: &'static mut [DmaDescriptor], + dma_buf: DmaTxBuf, } #[cfg(test)] @@ -47,14 +45,14 @@ mod tests { let pcnt = Pcnt::new(peripherals.PCNT); let io = Io::new(peripherals.GPIO, peripherals.IO_MUX); let (_, _, tx_buffer, tx_descriptors) = dma_buffers!(0, DATA_SIZE); + let dma_buf = DmaTxBuf::new(tx_descriptors, tx_buffer).unwrap(); Context { lcd_cam, dma, pcnt, io, - tx_buffer, - tx_descriptors, + dma_buf, } } @@ -64,19 +62,16 @@ mod tests { let pins = TxEightBits::new(NoPin, NoPin, NoPin, NoPin, NoPin, NoPin, NoPin, NoPin); - let mut i8080 = I8080::new( + let i8080 = I8080::new( ctx.lcd_cam.lcd, channel.tx, - ctx.tx_descriptors, pins, 20.MHz(), Config::default(), ); - let xfer = i8080 - .send_dma(Command::::None, 0, &ctx.tx_buffer) - .unwrap(); - xfer.wait().unwrap(); + let xfer = i8080.send(Command::::None, 0, ctx.dma_buf).unwrap(); + xfer.wait().0.unwrap(); } #[test] @@ -87,19 +82,16 @@ mod tests { .configure_for_async(false, DmaPriority::Priority0); let pins = TxEightBits::new(NoPin, NoPin, NoPin, NoPin, NoPin, NoPin, NoPin, NoPin); - let mut i8080 = I8080::new( + let i8080 = I8080::new( ctx.lcd_cam.lcd, channel.tx, - ctx.tx_descriptors, pins, 20.MHz(), Config::default(), ); - let xfer = i8080 - .send_dma(Command::::None, 0, &ctx.tx_buffer) - .unwrap(); - xfer.wait().unwrap(); + let xfer = i8080.send(Command::::None, 0, ctx.dma_buf).unwrap(); + xfer.wait().0.unwrap(); } #[test] @@ -163,7 +155,6 @@ mod tests { let mut i8080 = I8080::new( ctx.lcd_cam.lcd, channel.tx, - ctx.tx_descriptors, pins, 20.MHz(), Config::default(), @@ -202,12 +193,12 @@ mod tests { 0b1000_0000, ]; - let tx_buffer = ctx.tx_buffer; - tx_buffer.fill(0); - tx_buffer[..data_to_send.len()].copy_from_slice(&data_to_send); + let mut dma_buf = ctx.dma_buf; + dma_buf.as_mut_slice().fill(0); + dma_buf.as_mut_slice()[..data_to_send.len()].copy_from_slice(&data_to_send); - let xfer = i8080.send_dma(Command::::None, 0, &tx_buffer).unwrap(); - xfer.wait().unwrap(); + let xfer = i8080.send(Command::::None, 0, dma_buf).unwrap(); + xfer.wait().0.unwrap(); let actual = [ pcnt.unit0.get_value(), @@ -289,7 +280,6 @@ mod tests { let mut i8080 = I8080::new( ctx.lcd_cam.lcd, channel.tx, - ctx.tx_descriptors, pins, 20.MHz(), Config::default(), @@ -316,7 +306,7 @@ mod tests { pcnt.unit3.resume(); let data_to_send = [ - 0b0000_0000_0000_0000, + 0b0000_0000_0000_0000u16, 0b0001_0000_0001_0000, 0b0000_0001_0001_0000, 0b0001_0001_0001_0000, @@ -328,12 +318,20 @@ mod tests { 0b0001_0000_0000_0000, ]; - static TX_BUF: ConstStaticCell<[u16; 10]> = ConstStaticCell::new([0; 10]); - let tx_buffer = TX_BUF.take(); - tx_buffer.copy_from_slice(&data_to_send); + let mut dma_buf = ctx.dma_buf; + + // FIXME: Replace this 16 -> 8 bit copy once DmaTxBuf takes a generic parameter. + // i.e. DmaTxBuf + + // Copy 16 bit array into 8 bit buffer. + dma_buf + .as_mut_slice() + .iter_mut() + .zip(data_to_send.iter().flat_map(|&d| d.to_ne_bytes())) + .for_each(|(d, s)| *d = s); - let xfer = i8080.send_dma(Command::::None, 0, &tx_buffer).unwrap(); - xfer.wait().unwrap(); + let xfer = i8080.send(Command::::None, 0, dma_buf).unwrap(); + xfer.wait().0.unwrap(); let actual = [ pcnt.unit0.get_value(), diff --git a/hil-test/tests/lcd_cam_i8080_async.rs b/hil-test/tests/lcd_cam_i8080_async.rs index 6a8079305ee..c5c36d209d7 100644 --- a/hil-test/tests/lcd_cam_i8080_async.rs +++ b/hil-test/tests/lcd_cam_i8080_async.rs @@ -7,7 +7,7 @@ #![no_main] use esp_hal::{ - dma::{Dma, DmaDescriptor, DmaPriority}, + dma::{Dma, DmaPriority, DmaTxBuf}, dma_buffers, gpio::NoPin, lcd_cam::{ @@ -23,8 +23,7 @@ const DATA_SIZE: usize = 1024 * 10; struct Context<'d> { lcd_cam: LcdCam<'d, esp_hal::Async>, dma: Dma<'d>, - tx_buffer: &'static [u8], - tx_descriptors: &'static mut [DmaDescriptor], + dma_buf: DmaTxBuf, } #[cfg(test)] @@ -38,13 +37,13 @@ mod tests { let dma = Dma::new(peripherals.DMA); let lcd_cam = LcdCam::new_async(peripherals.LCD_CAM); - let (_, _, tx_buffer, tx_descriptors) = dma_buffers!(DATA_SIZE, 0); + let (_, _, tx_buffer, tx_descriptors) = dma_buffers!(0, DATA_SIZE); + let dma_buf = DmaTxBuf::new(tx_descriptors, tx_buffer).unwrap(); Context { lcd_cam, dma, - tx_buffer, - tx_descriptors, + dma_buf, } } @@ -53,19 +52,22 @@ mod tests { let channel = ctx.dma.channel0.configure(false, DmaPriority::Priority0); let pins = TxEightBits::new(NoPin, NoPin, NoPin, NoPin, NoPin, NoPin, NoPin, NoPin); - let mut i8080 = I8080::new( + let i8080 = I8080::new( ctx.lcd_cam.lcd, channel.tx, - ctx.tx_descriptors, pins, 20.MHz(), Config::default(), ); - i8080 - .send_dma_async(Command::::None, 0, &ctx.tx_buffer) - .await - .unwrap(); + let mut transfer = i8080.send(Command::::None, 0, ctx.dma_buf).unwrap(); + + transfer.wait_for_done().await; + + // This should not block forever and should immediately return. + transfer.wait_for_done().await; + + transfer.wait().0.unwrap(); } #[test] @@ -76,18 +78,21 @@ mod tests { .configure_for_async(false, DmaPriority::Priority0); let pins = TxEightBits::new(NoPin, NoPin, NoPin, NoPin, NoPin, NoPin, NoPin, NoPin); - let mut i8080 = I8080::new( + let i8080 = I8080::new( ctx.lcd_cam.lcd, channel.tx, - ctx.tx_descriptors, pins, 20.MHz(), Config::default(), ); - i8080 - .send_dma_async(Command::::None, 0, &ctx.tx_buffer) - .await - .unwrap(); + let mut transfer = i8080.send(Command::::None, 0, ctx.dma_buf).unwrap(); + + transfer.wait_for_done().await; + + // This should not block forever and should immediately return. + transfer.wait_for_done().await; + + transfer.wait().0.unwrap(); } }