mirror of
https://github.com/esp-rs/esp-hal.git
synced 2025-09-28 21:00:59 +00:00
Introduce RGB/DPI driver (#2415)
* Introduce RGB/DPI driver * Choose different pin for HIL test * fail * Use official devkit * merge update * non_exhaustive --------- Co-authored-by: Dominic Fischer <git@dominicfischer.me>
This commit is contained in:
parent
26fd1a40d9
commit
7821968df8
@ -20,6 +20,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- `Pins::steal()` to unsafely obtain GPIO. (#2335)
|
||||
- `I2c::with_timeout` (#2361)
|
||||
- `Spi::half_duplex_read` and `Spi::half_duplex_write` (#2373)
|
||||
- Add RGB/DPI driver (#2415)
|
||||
- Add `DmaLoopBuf` (#2415)
|
||||
- `Cpu::COUNT` and `Cpu::current()` (#2411)
|
||||
- `UartInterrupt` and related functions (#2406)
|
||||
- I2S Parallel output driver for ESP32. (#2348, #2436, #2472)
|
||||
|
@ -1,4 +1,7 @@
|
||||
use core::ptr::null_mut;
|
||||
use core::{
|
||||
ops::{Deref, DerefMut},
|
||||
ptr::null_mut,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
use crate::soc::is_slice_in_dram;
|
||||
@ -1046,3 +1049,91 @@ unsafe impl DmaRxBuffer for EmptyBuf {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/// DMA Loop Buffer
|
||||
///
|
||||
/// This consists of a single descriptor that points to itself and points to a
|
||||
/// single buffer, resulting in the buffer being transmitted over and over
|
||||
/// again, indefinitely.
|
||||
///
|
||||
/// Note: A DMA descriptor is 12 bytes. If your buffer is significantly shorter
|
||||
/// than this, the DMA channel will spend more time reading the descriptor than
|
||||
/// it does reading the buffer, which may leave it unable to keep up with the
|
||||
/// bandwidth requirements of some peripherals at high frequencies.
|
||||
pub struct DmaLoopBuf {
|
||||
descriptor: &'static mut DmaDescriptor,
|
||||
buffer: &'static mut [u8],
|
||||
}
|
||||
|
||||
impl DmaLoopBuf {
|
||||
/// Create a new [DmaLoopBuf].
|
||||
pub fn new(
|
||||
descriptor: &'static mut DmaDescriptor,
|
||||
buffer: &'static mut [u8],
|
||||
) -> Result<DmaLoopBuf, DmaBufError> {
|
||||
if !is_slice_in_dram(buffer) {
|
||||
return Err(DmaBufError::UnsupportedMemoryRegion);
|
||||
}
|
||||
if !is_slice_in_dram(core::slice::from_ref(descriptor)) {
|
||||
return Err(DmaBufError::UnsupportedMemoryRegion);
|
||||
}
|
||||
|
||||
if buffer.len() > max_chunk_size(None) {
|
||||
return Err(DmaBufError::InsufficientDescriptors);
|
||||
}
|
||||
|
||||
descriptor.set_owner(Owner::Dma); // Doesn't matter
|
||||
descriptor.set_suc_eof(false);
|
||||
descriptor.set_length(buffer.len());
|
||||
descriptor.set_size(buffer.len());
|
||||
descriptor.buffer = buffer.as_mut_ptr();
|
||||
descriptor.next = descriptor;
|
||||
|
||||
Ok(Self { descriptor, buffer })
|
||||
}
|
||||
|
||||
/// Consume the buf, returning the descriptor and buffer.
|
||||
pub fn split(self) -> (&'static mut DmaDescriptor, &'static mut [u8]) {
|
||||
(self.descriptor, self.buffer)
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl DmaTxBuffer for DmaLoopBuf {
|
||||
type View = Self;
|
||||
|
||||
fn prepare(&mut self) -> Preparation {
|
||||
Preparation {
|
||||
start: self.descriptor,
|
||||
block_size: None,
|
||||
is_burstable: true,
|
||||
// The DMA must not check the owner bit, as it is never set.
|
||||
check_owner: Some(false),
|
||||
}
|
||||
}
|
||||
|
||||
fn into_view(self) -> Self::View {
|
||||
self
|
||||
}
|
||||
|
||||
fn from_view(view: Self::View) -> Self {
|
||||
view
|
||||
}
|
||||
|
||||
fn length(&self) -> usize {
|
||||
panic!("DmaLoopBuf does not have a length")
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for DmaLoopBuf {
|
||||
type Target = [u8];
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.buffer
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for DmaLoopBuf {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
self.buffer
|
||||
}
|
||||
}
|
||||
|
@ -775,6 +775,30 @@ macro_rules! dma_rx_stream_buffer {
|
||||
}};
|
||||
}
|
||||
|
||||
/// Convenience macro to create a [DmaLoopBuf] from a buffer size.
|
||||
///
|
||||
/// ## Usage
|
||||
/// ```rust,no_run
|
||||
#[doc = crate::before_snippet!()]
|
||||
/// use esp_hal::dma_loop_buffer;
|
||||
///
|
||||
/// let buf = dma_loop_buffer!(2000);
|
||||
/// # }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! dma_loop_buffer {
|
||||
($size:expr) => {{
|
||||
const {
|
||||
::core::assert!($size <= 4095, "size must be <= 4095");
|
||||
::core::assert!($size > 0, "size must be > 0");
|
||||
}
|
||||
|
||||
let (buffer, descriptors) = $crate::dma_buffers_impl!($size, $size, is_circular = false);
|
||||
|
||||
$crate::dma::DmaLoopBuf::new(&mut descriptors[0], buffer).unwrap()
|
||||
}};
|
||||
}
|
||||
|
||||
/// DMA Errors
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
|
826
esp-hal/src/lcd_cam/lcd/dpi.rs
Normal file
826
esp-hal/src/lcd_cam/lcd/dpi.rs
Normal file
@ -0,0 +1,826 @@
|
||||
//! # LCD - RGB/Digital Parallel Interface Mode
|
||||
//!
|
||||
//! ## Overview
|
||||
//!
|
||||
//! The LCD_CAM peripheral Dpi driver provides support for the DPI (commonly
|
||||
//! know as RGB) format/timing. The driver mandates DMA (Direct Memory Access)
|
||||
//! for efficient data transfer.
|
||||
//!
|
||||
//! ## Examples
|
||||
//!
|
||||
//! ### A display
|
||||
//!
|
||||
//! The following example shows how to setup and send a solid frame to a DPI
|
||||
//! display.
|
||||
//!
|
||||
//! ```rust, no_run
|
||||
#![doc = crate::before_snippet!()]
|
||||
//! # use esp_hal::gpio::Level;
|
||||
//! # use esp_hal::lcd_cam::{
|
||||
//! # LcdCam,
|
||||
//! # lcd::{
|
||||
//! # ClockMode, Polarity, Phase,
|
||||
//! # dpi::{Config, Dpi, Format, FrameTiming, self}
|
||||
//! # }
|
||||
//! # };
|
||||
//! # use esp_hal::dma_loop_buffer;
|
||||
//! # use esp_hal::dma::{Dma, DmaPriority};
|
||||
//!
|
||||
//! # let dma = Dma::new(peripherals.DMA);
|
||||
//! # let channel = dma.channel0;
|
||||
//!
|
||||
//! # let mut dma_buf = dma_loop_buffer!(32);
|
||||
//!
|
||||
//! # let channel = channel.configure(
|
||||
//! # false,
|
||||
//! # DmaPriority::Priority0,
|
||||
//! # );
|
||||
//!
|
||||
//! let lcd_cam = LcdCam::new(peripherals.LCD_CAM);
|
||||
//!
|
||||
//! let mut config = dpi::Config::default();
|
||||
//! config.clock_mode = ClockMode {
|
||||
//! polarity: Polarity::IdleLow,
|
||||
//! phase: Phase::ShiftLow,
|
||||
//! };
|
||||
//! config.format = Format {
|
||||
//! enable_2byte_mode: true,
|
||||
//! ..Default::default()
|
||||
//! };
|
||||
//! config.timing = FrameTiming {
|
||||
//! horizontal_active_width: 480,
|
||||
//! horizontal_total_width: 520,
|
||||
//! horizontal_blank_front_porch: 10,
|
||||
//!
|
||||
//! vertical_active_height: 480,
|
||||
//! vertical_total_height: 510,
|
||||
//! vertical_blank_front_porch: 10,
|
||||
//!
|
||||
//! hsync_width: 10,
|
||||
//! vsync_width: 10,
|
||||
//!
|
||||
//! hsync_position: 0,
|
||||
//! };
|
||||
//! config.vsync_idle_level = Level::High;
|
||||
//! config.hsync_idle_level = Level::High;
|
||||
//! config.de_idle_level = Level::Low;
|
||||
//! config.disable_black_region = false;
|
||||
//!
|
||||
//! let mut dpi = Dpi::new(lcd_cam.lcd, channel.tx, 1.MHz(), config)
|
||||
//! .with_vsync(peripherals.GPIO3)
|
||||
//! .with_hsync(peripherals.GPIO46)
|
||||
//! .with_de(peripherals.GPIO17)
|
||||
//! .with_pclk(peripherals.GPIO9)
|
||||
//! // Blue
|
||||
//! .with_data0(peripherals.GPIO10)
|
||||
//! .with_data1(peripherals.GPIO11)
|
||||
//! .with_data2(peripherals.GPIO12)
|
||||
//! .with_data3(peripherals.GPIO13)
|
||||
//! .with_data4(peripherals.GPIO14)
|
||||
//! // Green
|
||||
//! .with_data5(peripherals.GPIO21)
|
||||
//! .with_data6(peripherals.GPIO8)
|
||||
//! .with_data7(peripherals.GPIO18)
|
||||
//! .with_data8(peripherals.GPIO45)
|
||||
//! .with_data9(peripherals.GPIO38)
|
||||
//! .with_data10(peripherals.GPIO39)
|
||||
//! // Red
|
||||
//! .with_data11(peripherals.GPIO40)
|
||||
//! .with_data12(peripherals.GPIO41)
|
||||
//! .with_data13(peripherals.GPIO42)
|
||||
//! .with_data14(peripherals.GPIO2)
|
||||
//! .with_data15(peripherals.GPIO1);
|
||||
//!
|
||||
//! let color: u16 = 0b11111_000000_00000; // RED
|
||||
//! for chunk in dma_buf.chunks_mut(2) {
|
||||
//! chunk.copy_from_slice(&color.to_le_bytes());
|
||||
//! }
|
||||
//!
|
||||
//! let transfer = dpi.send(false, dma_buf).map_err(|e| e.0).unwrap();
|
||||
//! transfer.wait();
|
||||
//! # }
|
||||
//! ```
|
||||
|
||||
use core::{
|
||||
mem::ManuallyDrop,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
use fugit::HertzU32;
|
||||
|
||||
use crate::{
|
||||
clock::Clocks,
|
||||
dma::{ChannelTx, DmaChannelConvert, DmaEligible, DmaError, DmaPeripheral, DmaTxBuffer, Tx},
|
||||
gpio::{interconnect::PeripheralOutput, Level, OutputSignal},
|
||||
lcd_cam::{
|
||||
calculate_clkm,
|
||||
lcd::{ClockMode, DelayMode, Lcd, Phase, Polarity},
|
||||
BitOrder,
|
||||
ByteOrder,
|
||||
},
|
||||
peripheral::{Peripheral, PeripheralRef},
|
||||
peripherals::LCD_CAM,
|
||||
Blocking,
|
||||
Mode,
|
||||
};
|
||||
|
||||
/// Represents the RGB LCD interface.
|
||||
pub struct Dpi<'d> {
|
||||
lcd_cam: PeripheralRef<'d, LCD_CAM>,
|
||||
tx_channel: ChannelTx<'d, Blocking, <LCD_CAM as DmaEligible>::Dma>,
|
||||
}
|
||||
|
||||
impl<'d> Dpi<'d> {
|
||||
/// Create a new instance of the RGB/DPI driver.
|
||||
pub fn new<DM: Mode, CH>(
|
||||
lcd: Lcd<'d, DM>,
|
||||
channel: ChannelTx<'d, Blocking, CH>,
|
||||
frequency: HertzU32,
|
||||
config: Config,
|
||||
) -> Self
|
||||
where
|
||||
CH: DmaChannelConvert<<LCD_CAM as DmaEligible>::Dma>,
|
||||
{
|
||||
let lcd_cam = lcd.lcd_cam;
|
||||
|
||||
let clocks = Clocks::get();
|
||||
// Due to https://www.espressif.com/sites/default/files/documentation/esp32-s3_errata_en.pdf
|
||||
// the LCD_PCLK divider must be at least 2. To make up for this the user
|
||||
// provided frequency is doubled to match.
|
||||
let (i, divider) = calculate_clkm(
|
||||
(frequency.to_Hz() * 2) as _,
|
||||
&[
|
||||
clocks.xtal_clock.to_Hz() as _,
|
||||
clocks.cpu_clock.to_Hz() as _,
|
||||
clocks.crypto_pwm_clock.to_Hz() as _,
|
||||
],
|
||||
);
|
||||
|
||||
lcd_cam.lcd_clock().write(|w| unsafe {
|
||||
// Force enable the clock for all configuration registers.
|
||||
w.clk_en().set_bit();
|
||||
w.lcd_clk_sel().bits((i + 1) as _);
|
||||
w.lcd_clkm_div_num().bits(divider.div_num as _);
|
||||
w.lcd_clkm_div_b().bits(divider.div_b as _);
|
||||
w.lcd_clkm_div_a().bits(divider.div_a as _); // LCD_PCLK = LCD_CLK / 2
|
||||
w.lcd_clk_equ_sysclk().clear_bit();
|
||||
w.lcd_clkcnt_n().bits(2 - 1); // Must not be 0.
|
||||
w.lcd_ck_idle_edge()
|
||||
.bit(config.clock_mode.polarity == Polarity::IdleHigh);
|
||||
w.lcd_ck_out_edge()
|
||||
.bit(config.clock_mode.phase == Phase::ShiftHigh)
|
||||
});
|
||||
lcd_cam.lcd_user().modify(|_, w| w.lcd_reset().set_bit());
|
||||
|
||||
lcd_cam
|
||||
.lcd_rgb_yuv()
|
||||
.write(|w| w.lcd_conv_bypass().clear_bit());
|
||||
|
||||
lcd_cam.lcd_user().modify(|_, w| {
|
||||
if config.format.enable_2byte_mode {
|
||||
w.lcd_8bits_order().bit(false);
|
||||
w.lcd_byte_order()
|
||||
.bit(config.format.byte_order == ByteOrder::Inverted);
|
||||
} else {
|
||||
w.lcd_8bits_order()
|
||||
.bit(config.format.byte_order == ByteOrder::Inverted);
|
||||
w.lcd_byte_order().bit(false);
|
||||
}
|
||||
w.lcd_bit_order()
|
||||
.bit(config.format.bit_order == BitOrder::Inverted);
|
||||
w.lcd_2byte_en().bit(config.format.enable_2byte_mode);
|
||||
|
||||
// Only valid in Intel8080 mode.
|
||||
w.lcd_cmd().clear_bit();
|
||||
w.lcd_dummy().clear_bit();
|
||||
|
||||
// This needs to be explicitly set for RGB mode.
|
||||
w.lcd_dout().set_bit()
|
||||
});
|
||||
|
||||
let timing = &config.timing;
|
||||
lcd_cam.lcd_ctrl().modify(|_, w| unsafe {
|
||||
// Enable RGB mode, and input VSYNC, HSYNC, and DE signals.
|
||||
w.lcd_rgb_mode_en().set_bit();
|
||||
|
||||
w.lcd_hb_front()
|
||||
.bits((timing.horizontal_blank_front_porch as u16).saturating_sub(1));
|
||||
w.lcd_va_height()
|
||||
.bits((timing.vertical_active_height as u16).saturating_sub(1));
|
||||
w.lcd_vt_height()
|
||||
.bits((timing.vertical_total_height as u16).saturating_sub(1))
|
||||
});
|
||||
lcd_cam.lcd_ctrl1().modify(|_, w| unsafe {
|
||||
w.lcd_vb_front()
|
||||
.bits((timing.vertical_blank_front_porch as u8).saturating_sub(1));
|
||||
w.lcd_ha_width()
|
||||
.bits((timing.horizontal_active_width as u16).saturating_sub(1));
|
||||
w.lcd_ht_width()
|
||||
.bits((timing.horizontal_total_width as u16).saturating_sub(1))
|
||||
});
|
||||
lcd_cam.lcd_ctrl2().modify(|_, w| unsafe {
|
||||
w.lcd_vsync_width()
|
||||
.bits((timing.vsync_width as u8).saturating_sub(1));
|
||||
w.lcd_vsync_idle_pol().bit(config.vsync_idle_level.into());
|
||||
w.lcd_de_idle_pol().bit(config.de_idle_level.into());
|
||||
w.lcd_hs_blank_en().bit(config.hs_blank_en);
|
||||
w.lcd_hsync_width()
|
||||
.bits((timing.hsync_width as u8).saturating_sub(1));
|
||||
w.lcd_hsync_idle_pol().bit(config.hsync_idle_level.into());
|
||||
w.lcd_hsync_position().bits(timing.hsync_position as u8)
|
||||
});
|
||||
|
||||
lcd_cam.lcd_misc().modify(|_, w| unsafe {
|
||||
// TODO: Find out what this field actually does.
|
||||
// Set the threshold for Async Tx FIFO full event. (5 bits)
|
||||
w.lcd_afifo_threshold_num().bits((1 << 5) - 1);
|
||||
|
||||
// Doesn't matter for RGB mode.
|
||||
w.lcd_vfk_cyclelen().bits(0);
|
||||
w.lcd_vbk_cyclelen().bits(0);
|
||||
|
||||
// 1: Send the next frame data when the current frame is sent out.
|
||||
// 0: LCD stops when the current frame is sent out.
|
||||
w.lcd_next_frame_en().clear_bit();
|
||||
|
||||
// Enable blank region when LCD sends data out.
|
||||
w.lcd_bk_en().bit(!config.disable_black_region)
|
||||
});
|
||||
lcd_cam.lcd_dly_mode().modify(|_, w| unsafe {
|
||||
w.lcd_de_mode().bits(config.de_mode as u8);
|
||||
w.lcd_hsync_mode().bits(config.hsync_mode as u8);
|
||||
w.lcd_vsync_mode().bits(config.vsync_mode as u8);
|
||||
w
|
||||
});
|
||||
lcd_cam.lcd_data_dout_mode().modify(|_, w| unsafe {
|
||||
w.dout0_mode().bits(config.output_bit_mode as u8);
|
||||
w.dout1_mode().bits(config.output_bit_mode as u8);
|
||||
w.dout2_mode().bits(config.output_bit_mode as u8);
|
||||
w.dout3_mode().bits(config.output_bit_mode as u8);
|
||||
w.dout4_mode().bits(config.output_bit_mode as u8);
|
||||
w.dout5_mode().bits(config.output_bit_mode as u8);
|
||||
w.dout6_mode().bits(config.output_bit_mode as u8);
|
||||
w.dout7_mode().bits(config.output_bit_mode as u8);
|
||||
w.dout8_mode().bits(config.output_bit_mode as u8);
|
||||
w.dout9_mode().bits(config.output_bit_mode as u8);
|
||||
w.dout10_mode().bits(config.output_bit_mode as u8);
|
||||
w.dout11_mode().bits(config.output_bit_mode as u8);
|
||||
w.dout12_mode().bits(config.output_bit_mode as u8);
|
||||
w.dout13_mode().bits(config.output_bit_mode as u8);
|
||||
w.dout14_mode().bits(config.output_bit_mode as u8);
|
||||
w.dout15_mode().bits(config.output_bit_mode as u8)
|
||||
});
|
||||
|
||||
lcd_cam.lcd_user().modify(|_, w| w.lcd_update().set_bit());
|
||||
|
||||
Self {
|
||||
lcd_cam,
|
||||
tx_channel: channel.degrade(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Assign the VSYNC pin for the LCD_CAM.
|
||||
///
|
||||
/// Sets the specified pin to push-pull output and connects it to the VSYNC
|
||||
/// signal.
|
||||
pub fn with_vsync<S: PeripheralOutput>(self, pin: impl Peripheral<P = S> + 'd) -> Self {
|
||||
crate::into_mapped_ref!(pin);
|
||||
pin.set_to_push_pull_output(crate::private::Internal);
|
||||
OutputSignal::LCD_V_SYNC.connect_to(pin);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Assign the HSYNC pin for the LCD_CAM.
|
||||
///
|
||||
/// Sets the specified pin to push-pull output and connects it to the HSYNC
|
||||
/// signal.
|
||||
pub fn with_hsync<S: PeripheralOutput>(self, pin: impl Peripheral<P = S> + 'd) -> Self {
|
||||
crate::into_mapped_ref!(pin);
|
||||
pin.set_to_push_pull_output(crate::private::Internal);
|
||||
OutputSignal::LCD_H_SYNC.connect_to(pin);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Assign the DE pin for the LCD_CAM.
|
||||
///
|
||||
/// Sets the specified pin to push-pull output and connects it to the DE
|
||||
/// signal.
|
||||
pub fn with_de<S: PeripheralOutput>(self, pin: impl Peripheral<P = S> + 'd) -> Self {
|
||||
crate::into_mapped_ref!(pin);
|
||||
pin.set_to_push_pull_output(crate::private::Internal);
|
||||
OutputSignal::LCD_H_ENABLE.connect_to(pin);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Assign the PCLK pin for the LCD_CAM.
|
||||
///
|
||||
/// Sets the specified pin to push-pull output and connects it to the PCLK
|
||||
/// signal.
|
||||
pub fn with_pclk<S: PeripheralOutput>(self, pin: impl Peripheral<P = S> + 'd) -> Self {
|
||||
crate::into_mapped_ref!(pin);
|
||||
pin.set_to_push_pull_output(crate::private::Internal);
|
||||
OutputSignal::LCD_PCLK.connect_to(pin);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Assign the DATA_0 pin for the LCD_CAM.
|
||||
///
|
||||
/// Sets the specified pin to push-pull output and connects it to the DATA_0
|
||||
/// signal.
|
||||
pub fn with_data0<S: PeripheralOutput>(self, pin: impl Peripheral<P = S> + 'd) -> Self {
|
||||
crate::into_mapped_ref!(pin);
|
||||
pin.set_to_push_pull_output(crate::private::Internal);
|
||||
OutputSignal::LCD_DATA_0.connect_to(pin);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Assign the DATA_1 pin for the LCD_CAM.
|
||||
///
|
||||
/// Sets the specified pin to push-pull output and connects it to the DATA_1
|
||||
/// signal.
|
||||
pub fn with_data1<S: PeripheralOutput>(self, pin: impl Peripheral<P = S> + 'd) -> Self {
|
||||
crate::into_mapped_ref!(pin);
|
||||
pin.set_to_push_pull_output(crate::private::Internal);
|
||||
OutputSignal::LCD_DATA_1.connect_to(pin);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Assign the DATA_2 pin for the LCD_CAM.
|
||||
///
|
||||
/// Sets the specified pin to push-pull output and connects it to the DATA_2
|
||||
/// signal.
|
||||
pub fn with_data2<S: PeripheralOutput>(self, pin: impl Peripheral<P = S> + 'd) -> Self {
|
||||
crate::into_mapped_ref!(pin);
|
||||
pin.set_to_push_pull_output(crate::private::Internal);
|
||||
OutputSignal::LCD_DATA_2.connect_to(pin);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Assign the DATA_3 pin for the LCD_CAM.
|
||||
///
|
||||
/// Sets the specified pin to push-pull output and connects it to the DATA_3
|
||||
/// signal.
|
||||
pub fn with_data3<S: PeripheralOutput>(self, pin: impl Peripheral<P = S> + 'd) -> Self {
|
||||
crate::into_mapped_ref!(pin);
|
||||
pin.set_to_push_pull_output(crate::private::Internal);
|
||||
OutputSignal::LCD_DATA_3.connect_to(pin);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Assign the DATA_4 pin for the LCD_CAM.
|
||||
///
|
||||
/// Sets the specified pin to push-pull output and connects it to the DATA_4
|
||||
/// signal.
|
||||
pub fn with_data4<S: PeripheralOutput>(self, pin: impl Peripheral<P = S> + 'd) -> Self {
|
||||
crate::into_mapped_ref!(pin);
|
||||
pin.set_to_push_pull_output(crate::private::Internal);
|
||||
OutputSignal::LCD_DATA_4.connect_to(pin);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Assign the DATA_5 pin for the LCD_CAM.
|
||||
///
|
||||
/// Sets the specified pin to push-pull output and connects it to the DATA_5
|
||||
/// signal.
|
||||
pub fn with_data5<S: PeripheralOutput>(self, pin: impl Peripheral<P = S> + 'd) -> Self {
|
||||
crate::into_mapped_ref!(pin);
|
||||
pin.set_to_push_pull_output(crate::private::Internal);
|
||||
OutputSignal::LCD_DATA_5.connect_to(pin);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Assign the DATA_6 pin for the LCD_CAM.
|
||||
///
|
||||
/// Sets the specified pin to push-pull output and connects it to the DATA_6
|
||||
/// signal.
|
||||
pub fn with_data6<S: PeripheralOutput>(self, pin: impl Peripheral<P = S> + 'd) -> Self {
|
||||
crate::into_mapped_ref!(pin);
|
||||
pin.set_to_push_pull_output(crate::private::Internal);
|
||||
OutputSignal::LCD_DATA_6.connect_to(pin);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Assign the DATA_7 pin for the LCD_CAM.
|
||||
///
|
||||
/// Sets the specified pin to push-pull output and connects it to the DATA_7
|
||||
/// signal.
|
||||
pub fn with_data7<S: PeripheralOutput>(self, pin: impl Peripheral<P = S> + 'd) -> Self {
|
||||
crate::into_mapped_ref!(pin);
|
||||
pin.set_to_push_pull_output(crate::private::Internal);
|
||||
OutputSignal::LCD_DATA_7.connect_to(pin);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Assign the DATA_8 pin for the LCD_CAM.
|
||||
///
|
||||
/// Sets the specified pin to push-pull output and connects it to the DATA_8
|
||||
/// signal.
|
||||
pub fn with_data8<S: PeripheralOutput>(self, pin: impl Peripheral<P = S> + 'd) -> Self {
|
||||
crate::into_mapped_ref!(pin);
|
||||
pin.set_to_push_pull_output(crate::private::Internal);
|
||||
OutputSignal::LCD_DATA_8.connect_to(pin);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Assign the DATA_9 pin for the LCD_CAM.
|
||||
///
|
||||
/// Sets the specified pin to push-pull output and connects it to the DATA_9
|
||||
/// signal.
|
||||
pub fn with_data9<S: PeripheralOutput>(self, pin: impl Peripheral<P = S> + 'd) -> Self {
|
||||
crate::into_mapped_ref!(pin);
|
||||
pin.set_to_push_pull_output(crate::private::Internal);
|
||||
OutputSignal::LCD_DATA_9.connect_to(pin);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Assign the DATA_10 pin for the LCD_CAM.
|
||||
///
|
||||
/// Sets the specified pin to push-pull output and connects it to the
|
||||
/// DATA_10 signal.
|
||||
pub fn with_data10<S: PeripheralOutput>(self, pin: impl Peripheral<P = S> + 'd) -> Self {
|
||||
crate::into_mapped_ref!(pin);
|
||||
pin.set_to_push_pull_output(crate::private::Internal);
|
||||
OutputSignal::LCD_DATA_10.connect_to(pin);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Assign the DATA_11 pin for the LCD_CAM.
|
||||
///
|
||||
/// Sets the specified pin to push-pull output and connects it to the
|
||||
/// DATA_11 signal.
|
||||
pub fn with_data11<S: PeripheralOutput>(self, pin: impl Peripheral<P = S> + 'd) -> Self {
|
||||
crate::into_mapped_ref!(pin);
|
||||
pin.set_to_push_pull_output(crate::private::Internal);
|
||||
OutputSignal::LCD_DATA_11.connect_to(pin);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Assign the DATA_12 pin for the LCD_CAM.
|
||||
///
|
||||
/// Sets the specified pin to push-pull output and connects it to the
|
||||
/// DATA_12 signal.
|
||||
pub fn with_data12<S: PeripheralOutput>(self, pin: impl Peripheral<P = S> + 'd) -> Self {
|
||||
crate::into_mapped_ref!(pin);
|
||||
pin.set_to_push_pull_output(crate::private::Internal);
|
||||
OutputSignal::LCD_DATA_12.connect_to(pin);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Assign the DATA_13 pin for the LCD_CAM.
|
||||
///
|
||||
/// Sets the specified pin to push-pull output and connects it to the
|
||||
/// DATA_13 signal.
|
||||
pub fn with_data13<S: PeripheralOutput>(self, pin: impl Peripheral<P = S> + 'd) -> Self {
|
||||
crate::into_mapped_ref!(pin);
|
||||
pin.set_to_push_pull_output(crate::private::Internal);
|
||||
OutputSignal::LCD_DATA_13.connect_to(pin);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Assign the DATA_14 pin for the LCD_CAM.
|
||||
///
|
||||
/// Sets the specified pin to push-pull output and connects it to the
|
||||
/// DATA_14 signal.
|
||||
pub fn with_data14<S: PeripheralOutput>(self, pin: impl Peripheral<P = S> + 'd) -> Self {
|
||||
crate::into_mapped_ref!(pin);
|
||||
pin.set_to_push_pull_output(crate::private::Internal);
|
||||
OutputSignal::LCD_DATA_14.connect_to(pin);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Assign the DATA_15 pin for the LCD_CAM.
|
||||
///
|
||||
/// Sets the specified pin to push-pull output and connects it to the
|
||||
/// DATA_15 signal.
|
||||
pub fn with_data15<S: PeripheralOutput>(self, pin: impl Peripheral<P = S> + 'd) -> Self {
|
||||
crate::into_mapped_ref!(pin);
|
||||
pin.set_to_push_pull_output(crate::private::Internal);
|
||||
OutputSignal::LCD_DATA_15.connect_to(pin);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Sending out the [DmaTxBuffer] to the RGB/DPI interface.
|
||||
///
|
||||
/// - `next_frame_en`: Automatically send the next frame data when the
|
||||
/// current frame is sent out.
|
||||
pub fn send<TX: DmaTxBuffer>(
|
||||
mut self,
|
||||
next_frame_en: bool,
|
||||
mut buf: TX,
|
||||
) -> Result<DpiTransfer<'d, TX>, (DmaError, Self, TX)> {
|
||||
let result = unsafe {
|
||||
self.tx_channel
|
||||
.prepare_transfer(DmaPeripheral::LcdCam, &mut buf)
|
||||
}
|
||||
.and_then(|_| self.tx_channel.start_transfer());
|
||||
if let Err(err) = result {
|
||||
return Err((err, self, buf));
|
||||
}
|
||||
|
||||
// Reset LCD control unit and Async Tx FIFO
|
||||
self.lcd_cam
|
||||
.lcd_user()
|
||||
.modify(|_, w| w.lcd_reset().set_bit());
|
||||
self.lcd_cam
|
||||
.lcd_misc()
|
||||
.modify(|_, w| w.lcd_afifo_reset().set_bit());
|
||||
|
||||
self.lcd_cam.lcd_misc().modify(|_, w| {
|
||||
// 1: Send the next frame data when the current frame is sent out.
|
||||
// 0: LCD stops when the current frame is sent out.
|
||||
w.lcd_next_frame_en().bit(next_frame_en)
|
||||
});
|
||||
|
||||
// Start the transfer.
|
||||
self.lcd_cam.lcd_user().modify(|_, w| {
|
||||
w.lcd_update().set_bit();
|
||||
w.lcd_start().set_bit()
|
||||
});
|
||||
|
||||
Ok(DpiTransfer {
|
||||
dpi: ManuallyDrop::new(self),
|
||||
buffer_view: ManuallyDrop::new(buf.into_view()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents an ongoing (or potentially finished) transfer using the RGB LCD
|
||||
/// interface
|
||||
pub struct DpiTransfer<'d, BUF: DmaTxBuffer> {
|
||||
dpi: ManuallyDrop<Dpi<'d>>,
|
||||
buffer_view: ManuallyDrop<BUF::View>,
|
||||
}
|
||||
|
||||
impl<'d, BUF: DmaTxBuffer> DpiTransfer<'d, BUF> {
|
||||
/// Returns true when [Self::wait] will not block.
|
||||
pub fn is_done(&self) -> bool {
|
||||
self.dpi
|
||||
.lcd_cam
|
||||
.lcd_user()
|
||||
.read()
|
||||
.lcd_start()
|
||||
.bit_is_clear()
|
||||
}
|
||||
|
||||
/// Stops this transfer on the spot and returns the peripheral and buffer.
|
||||
pub fn stop(mut self) -> (Dpi<'d>, BUF) {
|
||||
self.stop_peripherals();
|
||||
let (dpi, view) = self.release();
|
||||
(dpi, BUF::from_view(view))
|
||||
}
|
||||
|
||||
/// Waits for the transfer to finish and returns the peripheral and buffer.
|
||||
///
|
||||
/// Note: If you specified `next_frame_en` as true in [Dpi::send], you're
|
||||
/// just waiting for a DMA error when you call this.
|
||||
pub fn wait(mut self) -> (Result<(), DmaError>, Dpi<'d>, BUF) {
|
||||
while !self.is_done() {
|
||||
core::hint::spin_loop();
|
||||
}
|
||||
|
||||
// Stop the DMA.
|
||||
//
|
||||
// If the user sends more data to the DMA than the LCD_CAM needs for a single
|
||||
// frame, the DMA will still be running after the LCD_CAM stops.
|
||||
self.dpi.tx_channel.stop_transfer();
|
||||
|
||||
// Note: There is no "done" interrupt to clear.
|
||||
|
||||
let (dpi, view) = self.release();
|
||||
let result = if dpi.tx_channel.has_error() {
|
||||
Err(DmaError::DescriptorError)
|
||||
} else {
|
||||
Ok(())
|
||||
};
|
||||
|
||||
(result, dpi, BUF::from_view(view))
|
||||
}
|
||||
|
||||
fn release(mut self) -> (Dpi<'d>, BUF::View) {
|
||||
// SAFETY: Since forget is called on self, we know that self.dpi and
|
||||
// self.buffer_view won't be touched again.
|
||||
let result = unsafe {
|
||||
let dpi = ManuallyDrop::take(&mut self.dpi);
|
||||
let view = ManuallyDrop::take(&mut self.buffer_view);
|
||||
(dpi, view)
|
||||
};
|
||||
core::mem::forget(self);
|
||||
result
|
||||
}
|
||||
|
||||
fn stop_peripherals(&mut self) {
|
||||
// Stop the LCD_CAM peripheral.
|
||||
self.dpi
|
||||
.lcd_cam
|
||||
.lcd_user()
|
||||
.modify(|_, w| w.lcd_start().clear_bit());
|
||||
|
||||
// Stop the DMA
|
||||
self.dpi.tx_channel.stop_transfer();
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, BUF: DmaTxBuffer> Deref for DpiTransfer<'d, BUF> {
|
||||
type Target = BUF::View;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.buffer_view
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, BUF: DmaTxBuffer> DerefMut for DpiTransfer<'d, BUF> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.buffer_view
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, BUF: DmaTxBuffer> Drop for DpiTransfer<'d, BUF> {
|
||||
fn drop(&mut self) {
|
||||
self.stop_peripherals();
|
||||
|
||||
// SAFETY: This is Drop, we know that self.dpi and self.buf_view
|
||||
// won't be touched again.
|
||||
let view = unsafe {
|
||||
ManuallyDrop::drop(&mut self.dpi);
|
||||
ManuallyDrop::take(&mut self.buffer_view)
|
||||
};
|
||||
let _ = BUF::from_view(view);
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration settings for the RGB/DPI interface.
|
||||
#[non_exhaustive]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Config {
|
||||
/// Specifies the clock mode, including polarity and phase settings.
|
||||
pub clock_mode: ClockMode,
|
||||
|
||||
/// Format of the byte data sent out.
|
||||
pub format: Format,
|
||||
|
||||
/// Timing settings for the peripheral.
|
||||
pub timing: FrameTiming,
|
||||
|
||||
/// The vsync signal level in IDLE state.
|
||||
pub vsync_idle_level: Level,
|
||||
|
||||
/// The hsync signal level in IDLE state.
|
||||
pub hsync_idle_level: Level,
|
||||
|
||||
/// The de signal level in IDLE state.
|
||||
pub de_idle_level: Level,
|
||||
|
||||
/// If enabled, the hsync pulse will be sent out in vertical blanking lines.
|
||||
/// i.e. When no valid data is actually sent out. Otherwise, hysnc
|
||||
/// pulses will only be sent out in active region lines.
|
||||
pub hs_blank_en: bool,
|
||||
|
||||
/// Disables blank region when LCD sends data out.
|
||||
pub disable_black_region: bool,
|
||||
|
||||
/// The output LCD_DE is delayed by module clock LCD_CLK.
|
||||
pub de_mode: DelayMode,
|
||||
/// The output LCD_HSYNC is delayed by module clock LCD_CLK.
|
||||
pub hsync_mode: DelayMode,
|
||||
/// The output LCD_VSYNC is delayed by module clock LCD_CLK.
|
||||
pub vsync_mode: DelayMode,
|
||||
/// The output data bits are delayed by module clock LCD_CLK.
|
||||
pub output_bit_mode: DelayMode,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
fn default() -> Self {
|
||||
Config {
|
||||
clock_mode: Default::default(),
|
||||
format: Default::default(),
|
||||
timing: Default::default(),
|
||||
vsync_idle_level: Level::Low,
|
||||
hsync_idle_level: Level::Low,
|
||||
de_idle_level: Level::Low,
|
||||
hs_blank_en: true,
|
||||
disable_black_region: false,
|
||||
de_mode: Default::default(),
|
||||
hsync_mode: Default::default(),
|
||||
vsync_mode: Default::default(),
|
||||
output_bit_mode: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Controls how the peripheral should treat data received from the DMA.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct Format {
|
||||
/// Configures the bit order for data transmission.
|
||||
pub bit_order: BitOrder,
|
||||
|
||||
/// Configures the byte order for data transmission.
|
||||
///
|
||||
/// - In 8-bit mode, [ByteOrder::Inverted] means every two bytes are
|
||||
/// swapped.
|
||||
/// - In 16-bit mode, this controls the byte order (endianness).
|
||||
pub byte_order: ByteOrder,
|
||||
|
||||
/// If true, the width of the output is 16 bits.
|
||||
/// Otherwise, the width of the output is 8 bits.
|
||||
pub enable_2byte_mode: bool,
|
||||
}
|
||||
|
||||
/// The timing numbers for the driver to follow.
|
||||
///
|
||||
/// Note: The names of the fields in this struct don't match what you
|
||||
/// would typically find in an LCD's datasheet. Carefully read the doc on each
|
||||
/// field to understand what to set it to.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct FrameTiming {
|
||||
/// The horizontal total width of a frame (in units of PCLK).
|
||||
///
|
||||
/// This should be greater than `horizontal_blank_front_porch` +
|
||||
/// `horizontal_active_width`.
|
||||
///
|
||||
/// Max is 4096 (12 bits).
|
||||
pub horizontal_total_width: usize,
|
||||
|
||||
/// The horizontal blank front porch of a frame (in units of PCLK).
|
||||
///
|
||||
/// This is the number of PCLKs between the start of the line and the start
|
||||
/// of active data in the line.
|
||||
///
|
||||
/// Note: This includes `hsync_width`.
|
||||
///
|
||||
/// Max is 2048 (11 bits).
|
||||
pub horizontal_blank_front_porch: usize,
|
||||
|
||||
/// The horizontal active width of a frame. i.e. The number of pixels in a
|
||||
/// line. This is typically the horizontal resolution of the screen.
|
||||
///
|
||||
/// Max is 4096 (12 bits).
|
||||
pub horizontal_active_width: usize,
|
||||
|
||||
/// The vertical total height of a frame (in units of lines).
|
||||
///
|
||||
/// This should be greater than `vertical_blank_front_porch` +
|
||||
/// `vertical_active_height`.
|
||||
///
|
||||
/// Max is 1024 (10 bits).
|
||||
pub vertical_total_height: usize,
|
||||
|
||||
/// The vertical blank front porch height of a frame (in units of lines).
|
||||
///
|
||||
/// This is the number of (blank/invalid) lines before the start of the
|
||||
/// frame.
|
||||
///
|
||||
/// Note: This includes `vsync_width`.
|
||||
///
|
||||
/// Max is 256 (8 bits).
|
||||
pub vertical_blank_front_porch: usize,
|
||||
|
||||
/// The vertical active height of a frame. i.e. The number of lines in a
|
||||
/// frame. This is typically the vertical resolution of the screen.
|
||||
///
|
||||
/// Max is 1024 (10 bits).
|
||||
pub vertical_active_height: usize,
|
||||
|
||||
/// It is the width of LCD_VSYNC active pulse in a line (in units of lines).
|
||||
///
|
||||
/// Max is 128 (7 bits).
|
||||
pub vsync_width: usize,
|
||||
|
||||
/// The width of LCD_HSYNC active pulse in a line (in units of PCLK).
|
||||
///
|
||||
/// This should be less than vertical_blank_front_porch, otherwise the hsync
|
||||
/// pulse will overlap with valid pixel data.
|
||||
///
|
||||
/// Max is 128 (7 bits).
|
||||
pub hsync_width: usize,
|
||||
|
||||
/// It is the position of LCD_HSYNC active pulse in a line (in units of
|
||||
/// PCLK).
|
||||
///
|
||||
/// This should be less than horizontal_total_width.
|
||||
///
|
||||
/// Max is 128 (7 bits).
|
||||
pub hsync_position: usize,
|
||||
}
|
@ -12,6 +12,7 @@
|
||||
|
||||
use crate::{peripheral::PeripheralRef, peripherals::LCD_CAM};
|
||||
|
||||
pub mod dpi;
|
||||
pub mod i8080;
|
||||
|
||||
/// Represents an LCD interface.
|
||||
|
388
examples/src/bin/lcd_dpi.rs
Normal file
388
examples/src/bin/lcd_dpi.rs
Normal file
@ -0,0 +1,388 @@
|
||||
//! Drives the 16-bit parallel RGB display on ESP32-S3-LCD-EV-Board v1.5
|
||||
//!
|
||||
//! This example fills the screen with every color.
|
||||
//!
|
||||
//! The following wiring is assumed:
|
||||
//! - LCD_VSYNC => GPIO3
|
||||
//! - LCD_HSYNC => GPIO46
|
||||
//! - LCD_DE => GPIO17
|
||||
//! - LCD_PCLK => GPIO9
|
||||
//! - LCD_DATA0 => GPIO10
|
||||
//! - LCD_DATA1 => GPIO11
|
||||
//! - LCD_DATA2 => GPIO12
|
||||
//! - LCD_DATA3 => GPIO13
|
||||
//! - LCD_DATA4 => GPIO14
|
||||
//! - LCD_DATA5 => GPIO21
|
||||
//! - LCD_DATA6 => GPIO8
|
||||
//! - LCD_DATA7 => GPIO18
|
||||
//! - LCD_DATA8 => GPIO45
|
||||
//! - LCD_DATA9 => GPIO38
|
||||
//! - LCD_DATA10 => GPIO39
|
||||
//! - LCD_DATA11 => GPIO40
|
||||
//! - LCD_DATA12 => GPIO41
|
||||
//! - LCD_DATA13 => GPIO42
|
||||
//! - LCD_DATA14 => GPIO2
|
||||
//! - LCD_DATA15 => GPIO1
|
||||
|
||||
//% CHIPS: esp32s3
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use core::iter::{empty, once};
|
||||
|
||||
use esp_backtrace as _;
|
||||
use esp_hal::{
|
||||
delay::Delay,
|
||||
dma::{Dma, DmaPriority},
|
||||
dma_loop_buffer,
|
||||
gpio::{Level, Output},
|
||||
i2c,
|
||||
i2c::master::I2c,
|
||||
lcd_cam::{
|
||||
lcd::{
|
||||
dpi::{Config, Dpi, Format, FrameTiming},
|
||||
ClockMode,
|
||||
Phase,
|
||||
Polarity,
|
||||
},
|
||||
LcdCam,
|
||||
},
|
||||
peripherals::Peripherals,
|
||||
prelude::*,
|
||||
Blocking,
|
||||
};
|
||||
use esp_println::println;
|
||||
|
||||
#[entry]
|
||||
fn main() -> ! {
|
||||
esp_println::logger::init_logger_from_env();
|
||||
|
||||
let peripherals: Peripherals = esp_hal::init(esp_hal::Config::default());
|
||||
|
||||
let i2c = I2c::new(
|
||||
peripherals.I2C0,
|
||||
i2c::master::Config {
|
||||
frequency: 400.kHz(),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.with_sda(peripherals.GPIO47)
|
||||
.with_scl(peripherals.GPIO48);
|
||||
|
||||
let dma = Dma::new(peripherals.DMA);
|
||||
let channel = dma.channel2.configure(true, DmaPriority::Priority0);
|
||||
let lcd_cam = LcdCam::new(peripherals.LCD_CAM);
|
||||
|
||||
let mut expander = Tca9554::new(i2c);
|
||||
expander.write_output_reg(0b1111_0011).unwrap();
|
||||
expander.write_direction_reg(0b1111_0001).unwrap();
|
||||
|
||||
let delay = Delay::new();
|
||||
|
||||
println!("Initialising");
|
||||
|
||||
let mut write_byte = |b: u8, is_cmd: bool| {
|
||||
const SCS_BIT: u8 = 0b0000_0010;
|
||||
const SCL_BIT: u8 = 0b0000_0100;
|
||||
const SDA_BIT: u8 = 0b0000_1000;
|
||||
|
||||
let mut output = 0b1111_0001 & !SCS_BIT;
|
||||
expander.write_output_reg(output).unwrap();
|
||||
|
||||
for bit in once(!is_cmd).chain((0..8).map(|i| (b >> i) & 0b1 != 0).rev()) {
|
||||
let prev = output;
|
||||
if bit {
|
||||
output |= SDA_BIT;
|
||||
} else {
|
||||
output &= !SDA_BIT;
|
||||
}
|
||||
if prev != output {
|
||||
expander.write_output_reg(output).unwrap();
|
||||
}
|
||||
|
||||
output &= !SCL_BIT;
|
||||
expander.write_output_reg(output).unwrap();
|
||||
|
||||
output |= SCL_BIT;
|
||||
expander.write_output_reg(output).unwrap();
|
||||
}
|
||||
|
||||
output &= !SCL_BIT;
|
||||
expander.write_output_reg(output).unwrap();
|
||||
|
||||
output &= !SDA_BIT;
|
||||
expander.write_output_reg(output).unwrap();
|
||||
|
||||
output |= SCS_BIT;
|
||||
expander.write_output_reg(output).unwrap();
|
||||
};
|
||||
|
||||
let mut vsync_pin = peripherals.GPIO3;
|
||||
|
||||
let vsync_must_be_high_during_setup = Output::new(&mut vsync_pin, Level::High);
|
||||
for &init in INIT_CMDS.iter() {
|
||||
match init {
|
||||
InitCmd::Cmd(cmd, args) => {
|
||||
write_byte(cmd, true);
|
||||
for &arg in args {
|
||||
write_byte(arg, false);
|
||||
}
|
||||
}
|
||||
InitCmd::Delay(ms) => {
|
||||
delay.delay_millis(ms as _);
|
||||
}
|
||||
}
|
||||
}
|
||||
drop(vsync_must_be_high_during_setup);
|
||||
|
||||
let mut dma_buf = dma_loop_buffer!(2 * 16);
|
||||
|
||||
let mut config = Config::default();
|
||||
config.clock_mode = ClockMode {
|
||||
polarity: Polarity::IdleLow,
|
||||
phase: Phase::ShiftLow,
|
||||
};
|
||||
config.format = Format {
|
||||
enable_2byte_mode: true,
|
||||
..Default::default()
|
||||
};
|
||||
config.timing = FrameTiming {
|
||||
horizontal_active_width: 480,
|
||||
horizontal_total_width: 520,
|
||||
horizontal_blank_front_porch: 10,
|
||||
|
||||
vertical_active_height: 480,
|
||||
vertical_total_height: 510,
|
||||
vertical_blank_front_porch: 10,
|
||||
|
||||
hsync_width: 10,
|
||||
vsync_width: 10,
|
||||
|
||||
hsync_position: 0,
|
||||
};
|
||||
config.vsync_idle_level = Level::High;
|
||||
config.hsync_idle_level = Level::High;
|
||||
config.de_idle_level = Level::Low;
|
||||
config.disable_black_region = false;
|
||||
|
||||
let mut dpi = Dpi::new(lcd_cam.lcd, channel.tx, 16.MHz(), config)
|
||||
.with_vsync(vsync_pin)
|
||||
.with_hsync(peripherals.GPIO46)
|
||||
.with_de(peripherals.GPIO17)
|
||||
.with_pclk(peripherals.GPIO9)
|
||||
// Blue
|
||||
.with_data0(peripherals.GPIO10)
|
||||
.with_data1(peripherals.GPIO11)
|
||||
.with_data2(peripherals.GPIO12)
|
||||
.with_data3(peripherals.GPIO13)
|
||||
.with_data4(peripherals.GPIO14)
|
||||
// Green
|
||||
.with_data5(peripherals.GPIO21)
|
||||
.with_data6(peripherals.GPIO8)
|
||||
.with_data7(peripherals.GPIO18)
|
||||
.with_data8(peripherals.GPIO45)
|
||||
.with_data9(peripherals.GPIO38)
|
||||
.with_data10(peripherals.GPIO39)
|
||||
// Red
|
||||
.with_data11(peripherals.GPIO40)
|
||||
.with_data12(peripherals.GPIO41)
|
||||
.with_data13(peripherals.GPIO42)
|
||||
.with_data14(peripherals.GPIO2)
|
||||
.with_data15(peripherals.GPIO1);
|
||||
|
||||
const MAX_RED: u16 = (1 << 5) - 1;
|
||||
const MAX_GREEN: u16 = (1 << 6) - 1;
|
||||
const MAX_BLUE: u16 = (1 << 5) - 1;
|
||||
|
||||
fn rgb(r: u16, g: u16, b: u16) -> u16 {
|
||||
(r << 11) | (g << 5) | (b << 0)
|
||||
}
|
||||
|
||||
let mut colors = empty()
|
||||
// Start with red and gradually add green
|
||||
.chain((0..=MAX_GREEN).map(|g| rgb(MAX_RED, g, 0)))
|
||||
// Then remove the red
|
||||
.chain((0..=MAX_RED).rev().map(|r| rgb(r, MAX_GREEN, 0)))
|
||||
// Then add blue
|
||||
.chain((0..=MAX_BLUE).map(|b| rgb(0, MAX_GREEN, b)))
|
||||
// Then remove green
|
||||
.chain((0..=MAX_GREEN).rev().map(|g| rgb(0, g, MAX_BLUE)))
|
||||
// Then add red
|
||||
.chain((0..=MAX_RED).map(|r| rgb(r, 0, MAX_BLUE)))
|
||||
// Then remove blue
|
||||
.chain((0..=MAX_BLUE).rev().map(|b| rgb(MAX_RED, 0, b)))
|
||||
// Once we get we have red, and we can start again.
|
||||
.cycle();
|
||||
|
||||
println!("Rendering");
|
||||
loop {
|
||||
let transfer = dpi.send(false, dma_buf).map_err(|e| e.0).unwrap();
|
||||
(_, dpi, dma_buf) = transfer.wait();
|
||||
|
||||
if let Some(color) = colors.next() {
|
||||
for chunk in dma_buf.chunks_mut(2) {
|
||||
chunk.copy_from_slice(&color.to_le_bytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Tca9554 {
|
||||
i2c: I2c<'static, Blocking>,
|
||||
address: u8,
|
||||
}
|
||||
|
||||
impl Tca9554 {
|
||||
pub fn new(i2c: I2c<'static, Blocking>) -> Self {
|
||||
Self { i2c, address: 0x20 }
|
||||
}
|
||||
|
||||
pub fn write_direction_reg(&mut self, value: u8) -> Result<(), i2c::master::Error> {
|
||||
self.i2c.write(self.address, &[0x03, value])
|
||||
}
|
||||
|
||||
pub fn write_output_reg(&mut self, value: u8) -> Result<(), i2c::master::Error> {
|
||||
self.i2c.write(self.address, &[0x01, value])
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
enum InitCmd {
|
||||
Cmd(u8, &'static [u8]),
|
||||
Delay(u8),
|
||||
}
|
||||
|
||||
const INIT_CMDS: &[InitCmd] = &[
|
||||
InitCmd::Cmd(0xf0, &[0x55, 0xaa, 0x52, 0x08, 0x00]),
|
||||
InitCmd::Cmd(0xf6, &[0x5a, 0x87]),
|
||||
InitCmd::Cmd(0xc1, &[0x3f]),
|
||||
InitCmd::Cmd(0xc2, &[0x0e]),
|
||||
InitCmd::Cmd(0xc6, &[0xf8]),
|
||||
InitCmd::Cmd(0xc9, &[0x10]),
|
||||
InitCmd::Cmd(0xcd, &[0x25]),
|
||||
InitCmd::Cmd(0xf8, &[0x8a]),
|
||||
InitCmd::Cmd(0xac, &[0x45]),
|
||||
InitCmd::Cmd(0xa0, &[0xdd]),
|
||||
InitCmd::Cmd(0xa7, &[0x47]),
|
||||
InitCmd::Cmd(0xfa, &[0x00, 0x00, 0x00, 0x04]),
|
||||
InitCmd::Cmd(0x86, &[0x99, 0xa3, 0xa3, 0x51]),
|
||||
InitCmd::Cmd(0xa3, &[0xee]),
|
||||
InitCmd::Cmd(0xfd, &[0x3c, 0x3]),
|
||||
InitCmd::Cmd(0x71, &[0x48]),
|
||||
InitCmd::Cmd(0x72, &[0x48]),
|
||||
InitCmd::Cmd(0x73, &[0x00, 0x44]),
|
||||
InitCmd::Cmd(0x97, &[0xee]),
|
||||
InitCmd::Cmd(0x83, &[0x93]),
|
||||
InitCmd::Cmd(0x9a, &[0x72]),
|
||||
InitCmd::Cmd(0x9b, &[0x5a]),
|
||||
InitCmd::Cmd(0x82, &[0x2c, 0x2c]),
|
||||
InitCmd::Cmd(0xB1, &[0x10]),
|
||||
InitCmd::Cmd(
|
||||
0x6d,
|
||||
&[
|
||||
0x00, 0x1f, 0x19, 0x1a, 0x10, 0x0e, 0x0c, 0x0a, 0x02, 0x07, 0x1e, 0x1e, 0x1e, 0x1e,
|
||||
0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x08, 0x01, 0x09, 0x0b, 0x0d, 0x0f,
|
||||
0x1a, 0x19, 0x1f, 0x00,
|
||||
],
|
||||
),
|
||||
InitCmd::Cmd(
|
||||
0x64,
|
||||
&[
|
||||
0x38, 0x05, 0x01, 0xdb, 0x03, 0x03, 0x38, 0x04, 0x01, 0xdc, 0x03, 0x03, 0x7a, 0x7a,
|
||||
0x7a, 0x7a,
|
||||
],
|
||||
),
|
||||
InitCmd::Cmd(
|
||||
0x65,
|
||||
&[
|
||||
0x38, 0x03, 0x01, 0xdd, 0x03, 0x03, 0x38, 0x02, 0x01, 0xde, 0x03, 0x03, 0x7a, 0x7a,
|
||||
0x7a, 0x7a,
|
||||
],
|
||||
),
|
||||
InitCmd::Cmd(
|
||||
0x66,
|
||||
&[
|
||||
0x38, 0x01, 0x01, 0xdf, 0x03, 0x03, 0x38, 0x00, 0x01, 0xe0, 0x03, 0x03, 0x7a, 0x7a,
|
||||
0x7a, 0x7a,
|
||||
],
|
||||
),
|
||||
InitCmd::Cmd(
|
||||
0x67,
|
||||
&[
|
||||
0x30, 0x01, 0x01, 0xe1, 0x03, 0x03, 0x30, 0x02, 0x01, 0xe2, 0x03, 0x03, 0x7a, 0x7a,
|
||||
0x7a, 0x7a,
|
||||
],
|
||||
),
|
||||
InitCmd::Cmd(
|
||||
0x68,
|
||||
&[
|
||||
0x00, 0x08, 0x15, 0x08, 0x15, 0x7a, 0x7a, 0x08, 0x15, 0x08, 0x15, 0x7a, 0x7a,
|
||||
],
|
||||
),
|
||||
InitCmd::Cmd(0x60, &[0x38, 0x08, 0x7a, 0x7a, 0x38, 0x09, 0x7a, 0x7a]),
|
||||
InitCmd::Cmd(0x63, &[0x31, 0xe4, 0x7a, 0x7a, 0x31, 0xe5, 0x7a, 0x7a]),
|
||||
InitCmd::Cmd(0x69, &[0x04, 0x22, 0x14, 0x22, 0x14, 0x22, 0x08]),
|
||||
InitCmd::Cmd(0x6b, &[0x07]),
|
||||
InitCmd::Cmd(0x7a, &[0x08, 0x13]),
|
||||
InitCmd::Cmd(0x7b, &[0x08, 0x13]),
|
||||
InitCmd::Cmd(
|
||||
0xd1,
|
||||
&[
|
||||
0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35,
|
||||
0x00, 0x47, 0x00, 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7,
|
||||
0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5,
|
||||
0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, 0xff,
|
||||
],
|
||||
),
|
||||
InitCmd::Cmd(
|
||||
0xd2,
|
||||
&[
|
||||
0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35,
|
||||
0x00, 0x47, 0x00, 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7,
|
||||
0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5,
|
||||
0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, 0xff,
|
||||
],
|
||||
),
|
||||
InitCmd::Cmd(
|
||||
0xd3,
|
||||
&[
|
||||
0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35,
|
||||
0x00, 0x47, 0x00, 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7,
|
||||
0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5,
|
||||
0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, 0xff,
|
||||
],
|
||||
),
|
||||
InitCmd::Cmd(
|
||||
0xd4,
|
||||
&[
|
||||
0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35,
|
||||
0x00, 0x47, 0x00, 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7,
|
||||
0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5,
|
||||
0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, 0xff,
|
||||
],
|
||||
),
|
||||
InitCmd::Cmd(
|
||||
0xd5,
|
||||
&[
|
||||
0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35,
|
||||
0x00, 0x47, 0x00, 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7,
|
||||
0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5,
|
||||
0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, 0xff,
|
||||
],
|
||||
),
|
||||
InitCmd::Cmd(
|
||||
0xd6,
|
||||
&[
|
||||
0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35,
|
||||
0x00, 0x47, 0x00, 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7,
|
||||
0x02, 0x36, 0x02, 0xa6, 0x02, 0xee, 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5,
|
||||
0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03, 0xff,
|
||||
],
|
||||
),
|
||||
InitCmd::Cmd(0x3A, &[0x66]),
|
||||
InitCmd::Cmd(0x11, &[]),
|
||||
InitCmd::Delay(120),
|
||||
InitCmd::Cmd(0x29, &[]),
|
||||
InitCmd::Delay(20),
|
||||
];
|
@ -75,6 +75,10 @@ harness = false
|
||||
name = "i2s"
|
||||
harness = false
|
||||
|
||||
[[test]]
|
||||
name = "lcd_cam"
|
||||
harness = false
|
||||
|
||||
[[test]]
|
||||
name = "lcd_cam_i8080"
|
||||
harness = false
|
||||
|
146
hil-test/tests/lcd_cam.rs
Normal file
146
hil-test/tests/lcd_cam.rs
Normal file
@ -0,0 +1,146 @@
|
||||
//! LCD_CAM Camera and DPI tests
|
||||
|
||||
//% CHIPS: esp32s3
|
||||
//% FEATURES: defmt
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use esp_hal::{
|
||||
dma::{Dma, DmaPriority, DmaRxBuf, DmaTxBuf},
|
||||
dma_buffers,
|
||||
gpio::Level,
|
||||
lcd_cam::{
|
||||
cam::{Camera, RxEightBits},
|
||||
lcd::{
|
||||
dpi,
|
||||
dpi::{Dpi, Format, FrameTiming},
|
||||
ClockMode,
|
||||
Phase,
|
||||
Polarity,
|
||||
},
|
||||
LcdCam,
|
||||
},
|
||||
peripherals::Peripherals,
|
||||
};
|
||||
use fugit::RateExtU32;
|
||||
use hil_test as _;
|
||||
|
||||
struct Context {
|
||||
peripherals: Peripherals,
|
||||
dma_tx_buf: DmaTxBuf,
|
||||
dma_rx_buf: DmaRxBuf,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[embedded_test::tests]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[init]
|
||||
fn init() -> Context {
|
||||
let peripherals = esp_hal::init(esp_hal::Config::default());
|
||||
|
||||
let (rx_buffer, rx_descriptors, tx_buffer, tx_descriptors) = dma_buffers!(50 * 50);
|
||||
let dma_rx_buf = DmaRxBuf::new(rx_descriptors, rx_buffer).unwrap();
|
||||
let dma_tx_buf = DmaTxBuf::new(tx_descriptors, tx_buffer).unwrap();
|
||||
|
||||
Context {
|
||||
peripherals,
|
||||
dma_tx_buf,
|
||||
dma_rx_buf,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_camera_can_receive_from_rgb(ctx: Context) {
|
||||
let peripherals = ctx.peripherals;
|
||||
|
||||
let dma = Dma::new(peripherals.DMA);
|
||||
let lcd_cam = LcdCam::new(peripherals.LCD_CAM);
|
||||
|
||||
let channel = dma.channel2.configure(false, DmaPriority::Priority0);
|
||||
|
||||
let (vsync_in, vsync_out) = peripherals.GPIO6.split();
|
||||
let (hsync_in, hsync_out) = peripherals.GPIO7.split();
|
||||
let (de_in, de_out) = peripherals.GPIO14.split();
|
||||
let (pclk_in, pclk_out) = peripherals.GPIO13.split();
|
||||
let (d0_in, d0_out) = peripherals.GPIO11.split();
|
||||
let (d1_in, d1_out) = peripherals.GPIO9.split();
|
||||
let (d2_in, d2_out) = peripherals.GPIO8.split();
|
||||
let (d3_in, d3_out) = peripherals.GPIO47.split();
|
||||
let (d4_in, d4_out) = peripherals.GPIO12.split();
|
||||
let (d5_in, d5_out) = peripherals.GPIO18.split();
|
||||
let (d6_in, d6_out) = peripherals.GPIO17.split();
|
||||
let (d7_in, d7_out) = peripherals.GPIO16.split();
|
||||
|
||||
let mut config = dpi::Config::default();
|
||||
config.clock_mode = ClockMode {
|
||||
polarity: Polarity::IdleHigh,
|
||||
phase: Phase::ShiftLow,
|
||||
};
|
||||
config.format = Format {
|
||||
enable_2byte_mode: false,
|
||||
..Default::default()
|
||||
};
|
||||
// Send a 50x50 video
|
||||
config.timing = FrameTiming {
|
||||
horizontal_total_width: 65,
|
||||
hsync_width: 5,
|
||||
horizontal_blank_front_porch: 10,
|
||||
horizontal_active_width: 50,
|
||||
|
||||
vertical_total_height: 65,
|
||||
vsync_width: 5,
|
||||
vertical_blank_front_porch: 10,
|
||||
vertical_active_height: 50,
|
||||
|
||||
hsync_position: 0,
|
||||
};
|
||||
config.vsync_idle_level = Level::High;
|
||||
config.hsync_idle_level = Level::High;
|
||||
config.de_idle_level = Level::Low;
|
||||
config.disable_black_region = false;
|
||||
|
||||
let dpi = Dpi::new(lcd_cam.lcd, channel.tx, 500u32.kHz(), config)
|
||||
.with_vsync(vsync_out)
|
||||
.with_hsync(hsync_out)
|
||||
.with_de(de_out)
|
||||
.with_pclk(pclk_out)
|
||||
.with_data0(d0_out)
|
||||
.with_data1(d1_out)
|
||||
.with_data2(d2_out)
|
||||
.with_data3(d3_out)
|
||||
.with_data4(d4_out)
|
||||
.with_data5(d5_out)
|
||||
.with_data6(d6_out)
|
||||
.with_data7(d7_out);
|
||||
|
||||
let camera = Camera::new(
|
||||
lcd_cam.cam,
|
||||
channel.rx,
|
||||
RxEightBits::new(d0_in, d1_in, d2_in, d3_in, d4_in, d5_in, d6_in, d7_in),
|
||||
1u32.MHz(),
|
||||
)
|
||||
.with_ctrl_pins_and_de(vsync_in, hsync_in, de_in)
|
||||
.with_pixel_clock(pclk_in);
|
||||
|
||||
let mut dma_tx_buf = ctx.dma_tx_buf;
|
||||
let mut dma_rx_buf = ctx.dma_rx_buf;
|
||||
|
||||
for (i, b) in dma_tx_buf.as_mut_slice().iter_mut().enumerate() {
|
||||
*b = ((i + 0) % 256) as u8;
|
||||
}
|
||||
|
||||
let camera_transfer = camera.receive(dma_rx_buf).map_err(|e| e.0).unwrap();
|
||||
// Note: next_frame_en is not false because the RGB driver doesn't send a VSYNC
|
||||
// at the end of the frame, which means the DMA doesn't flush the last
|
||||
// few bytes it receives.
|
||||
let dpi_transfer = dpi.send(true, dma_tx_buf).map_err(|e| e.0).unwrap();
|
||||
|
||||
(_, _, dma_rx_buf) = camera_transfer.wait();
|
||||
(_, dma_tx_buf) = dpi_transfer.stop();
|
||||
|
||||
assert_eq!(dma_tx_buf.as_slice(), dma_rx_buf.as_slice());
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user