Migrate I8080 driver to a move based API (#2191)

* Migrate I8080 driver to a move based API

* fmt

* comments

---------

Co-authored-by: Dominic Fischer <git@dominicfischer.me>
This commit is contained in:
Dominic Fischer 2024-10-02 14:23:10 +01:00 committed by GitHub
parent a3304c6cff
commit 8789ca3c3d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 314 additions and 313 deletions

View File

@ -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<u32>` 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)
@ -46,6 +47,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)
- `Camera` and `AesDma` now support erasing the DMA channel type (#2258)
@ -93,6 +95,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)

View File

@ -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

View File

@ -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<DM>,
}
@ -106,7 +98,6 @@ where
pub fn new<P: TxPins>(
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<u8>` will make this an 8-bit transfer and a
/// `Command<u16>` 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<W: Copy + Into<u16>>(
&mut self,
cmd: impl Into<Command<W>>,
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<u8>` 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<W: Into<u16> + Copy, BUF: DmaTxBuffer>(
mut self,
cmd: impl Into<Command<W>>,
dummy: u8,
data: &'t TXBUF,
) -> Result<DmaTransferTx<'_, Self>, DmaError>
where
W: Copy + Into<u16>,
TXBUF: ReadBuffer,
{
let (ptr, len) = unsafe { data.read_buffer() };
mut data: BUF,
) -> Result<I8080Transfer<'d, BUF, CH, DM>, (DmaError, Self, BUF)> {
let cmd = cmd.into();
self.setup_send(cmd.into(), dummy);
self.start_write_bytes_dma(ptr as _, len)?;
self.start_send();
Ok(DmaTransferTx::new(self))
}
}
impl<'d, CH: DmaChannel> I8080<'d, CH, crate::Async> {
/// Asynchronously sends a command and data to the LCD using DMA.
///
/// Passing a `Command<u8>` will make this an 8-bit transfer and a
/// `Command<u16>` 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<Command<W>>,
dummy: u8,
data: &'t TXBUF,
) -> Result<(), DmaError>
where
W: Copy + Into<u16>,
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<T: Copy + Into<u16>>(&mut self, cmd: Command<T>, 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::<T>() == 2;
let is_2byte_mode = size_of::<W>() == 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,45 +357,11 @@ impl<'d, CH: DmaChannel, DM: Mode> I8080<'d, CH, DM> {
w.lcd_update().set_bit();
w.lcd_start().set_bit()
});
}
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
.lcd_user()
.modify(|_, w| w.lcd_start().clear_bit());
self.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());
} 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)?;
}
self.tx_channel.start_transfer()?;
}
Ok(())
Ok(I8080Transfer {
i8080: ManuallyDrop::new(self),
tx_buf: ManuallyDrop::new(data),
})
}
}
@ -498,6 +371,126 @@ impl<'d, CH: DmaChannel, DM: Mode> core::fmt::Debug for I8080<'d, CH, DM> {
}
}
/// Represents an ongoing (or potentially finished) transfer using the I8080 LCD
/// interface
pub struct I8080Transfer<'d, BUF, CH: DmaChannel, DM: Mode> {
i8080: ManuallyDrop<I8080<'d, CH, DM>>,
tx_buf: ManuallyDrop<BUF>,
}
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()
.read()
.lcd_start()
.bit_is_clear()
}
/// 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());
// 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 {
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<Self::Output> {
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
}
}
}
impl Drop for LcdDoneFuture {
fn drop(&mut self) {
Instance::unlisten_lcd_done();
}
}
LcdDoneFuture {}.await
}
}
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);
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
/// Configuration settings for the I8080 interface.

View File

@ -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<Self::Output> {
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)

View File

@ -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<T>(
&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();
(_, i8080, dma_tx_buf) = i8080.send(0x2Cu8, 0, dma_tx_buf).unwrap().wait();
bytes_left_to_write -= buffer.len();
bytes_left_to_write -= dma_tx_buf.len();
while bytes_left_to_write >= buffer.len() {
let transfer = i8080.send_dma(0x3Cu8, 0, &buffer).unwrap();
transfer.wait().unwrap();
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);

View File

@ -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::<u8>::None, 0, &ctx.tx_buffer)
.unwrap();
xfer.wait().unwrap();
let xfer = i8080.send(Command::<u8>::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::<u8>::None, 0, &ctx.tx_buffer)
.unwrap();
xfer.wait().unwrap();
let xfer = i8080.send(Command::<u8>::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::<u8>::None, 0, &tx_buffer).unwrap();
xfer.wait().unwrap();
let xfer = i8080.send(Command::<u8>::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;
let xfer = i8080.send_dma(Command::<u16>::None, 0, &tx_buffer).unwrap();
xfer.wait().unwrap();
// FIXME: Replace this 16 -> 8 bit copy once DmaTxBuf takes a generic parameter.
// i.e. DmaTxBuf<u16>
// 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(Command::<u16>::None, 0, dma_buf).unwrap();
xfer.wait().0.unwrap();
let actual = [
pcnt.unit0.get_value(),

View File

@ -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::<u8>::None, 0, &ctx.tx_buffer)
.await
.unwrap();
let mut transfer = i8080.send(Command::<u8>::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::<u8>::None, 0, &ctx.tx_buffer)
.await
.unwrap();
let mut transfer = i8080.send(Command::<u8>::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();
}
}