feat: add display UI with splash screen, dashboard, and RSSI sparkline
Implement full SSD1306 OLED display support: - Splash screen on boot (1.5s) with project name, version, board - Dashboard showing radio state, config, counters, RSSI/SNR - RSSI history sparkline (64-sample ring buffer, 2px-wide bars) - DisplayOn/DisplayOff host commands routed via dedicated channel - Pure render functions against generic DrawTarget (no hardware coupling) - Display on/off control bypasses radio task entirely Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
17624 files changed
+4564928
-38
No files matched your search
Vendored
+611
@@ -0,0 +1,611 @@
|
||||
//! # Camera - Master or Slave Mode
|
||||
//!
|
||||
//! ## Overview
|
||||
//! The camera module is designed to receive parallel video data signals, and
|
||||
//! its bus supports DVP 8-/16-bit modes in master or slave mode.
|
||||
//!
|
||||
//! ## Configuration
|
||||
//! In master mode, the peripheral provides the master clock to drive the
|
||||
//! camera, in slave mode it does not. This is configured with the
|
||||
//! `with_master_clock` method on the camera driver. The driver (due to the
|
||||
//! peripheral) mandates DMA (Direct Memory Access) for efficient data transfer.
|
||||
//!
|
||||
//! ## Examples
|
||||
//! ## Master Mode
|
||||
//! Following code shows how to receive some bytes from an 8 bit DVP stream in
|
||||
//! master mode.
|
||||
//! ```rust, no_run
|
||||
#![doc = crate::before_snippet!()]
|
||||
//! # use esp_hal::lcd_cam::{cam::{Camera, RxEightBits}, LcdCam};
|
||||
//! # use fugit::RateExtU32;
|
||||
//! # use esp_hal::dma_rx_stream_buffer;
|
||||
//! # use esp_hal::dma::{Dma, DmaPriority};
|
||||
//!
|
||||
//! # let dma = Dma::new(peripherals.DMA);
|
||||
//! # let channel = dma.channel0;
|
||||
//!
|
||||
//! # let dma_buf = dma_rx_stream_buffer!(20 * 1000, 1000);
|
||||
//!
|
||||
//! # let channel = channel.configure(
|
||||
//! # false,
|
||||
//! # DmaPriority::Priority0,
|
||||
//! # );
|
||||
//!
|
||||
//! let mclk_pin = peripherals.GPIO15;
|
||||
//! let vsync_pin = peripherals.GPIO6;
|
||||
//! let href_pin = peripherals.GPIO7;
|
||||
//! let pclk_pin = peripherals.GPIO13;
|
||||
//! let data_pins = RxEightBits::new(
|
||||
//! peripherals.GPIO11,
|
||||
//! peripherals.GPIO9,
|
||||
//! peripherals.GPIO8,
|
||||
//! peripherals.GPIO10,
|
||||
//! peripherals.GPIO12,
|
||||
//! peripherals.GPIO18,
|
||||
//! peripherals.GPIO17,
|
||||
//! peripherals.GPIO16,
|
||||
//! );
|
||||
//!
|
||||
//! let lcd_cam = LcdCam::new(peripherals.LCD_CAM);
|
||||
//! let mut camera = Camera::new(
|
||||
//! lcd_cam.cam,
|
||||
//! channel.rx,
|
||||
//! data_pins,
|
||||
//! 20u32.MHz(),
|
||||
//! )
|
||||
//! // Remove this for slave mode.
|
||||
//! .with_master_clock(mclk_pin)
|
||||
//! .with_pixel_clock(pclk_pin)
|
||||
//! .with_ctrl_pins(vsync_pin, href_pin);
|
||||
//!
|
||||
//! let transfer = camera.receive(dma_buf).map_err(|e| e.0).unwrap();
|
||||
//!
|
||||
//! # }
|
||||
//! ```
|
||||
|
||||
use core::{
|
||||
mem::ManuallyDrop,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
use fugit::HertzU32;
|
||||
|
||||
use crate::{
|
||||
clock::Clocks,
|
||||
dma::{ChannelRx, DmaChannelConvert, DmaEligible, DmaError, DmaPeripheral, DmaRxBuffer, Rx},
|
||||
gpio::{
|
||||
interconnect::{PeripheralInput, PeripheralOutput},
|
||||
InputSignal,
|
||||
OutputSignal,
|
||||
Pull,
|
||||
},
|
||||
lcd_cam::{calculate_clkm, BitOrder, ByteOrder},
|
||||
peripheral::{Peripheral, PeripheralRef},
|
||||
peripherals::LCD_CAM,
|
||||
Blocking,
|
||||
};
|
||||
|
||||
/// Generation of GDMA SUC EOF
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum EofMode {
|
||||
/// Generate GDMA SUC EOF by data byte length
|
||||
ByteLen,
|
||||
/// Generate GDMA SUC EOF by the vsync signal
|
||||
VsyncSignal,
|
||||
}
|
||||
|
||||
/// Vsync Filter Threshold
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum VsyncFilterThreshold {
|
||||
/// Requires 1 valid VSYNC pulse to trigger synchronization.
|
||||
One,
|
||||
/// Requires 2 valid VSYNC pulse to trigger synchronization.
|
||||
Two,
|
||||
/// Requires 3 valid VSYNC pulse to trigger synchronization.
|
||||
Three,
|
||||
/// Requires 4 valid VSYNC pulse to trigger synchronization.
|
||||
Four,
|
||||
/// Requires 5 valid VSYNC pulse to trigger synchronization.
|
||||
Five,
|
||||
/// Requires 6 valid VSYNC pulse to trigger synchronization.
|
||||
Six,
|
||||
/// Requires 7 valid VSYNC pulse to trigger synchronization.
|
||||
Seven,
|
||||
/// Requires 8 valid VSYNC pulse to trigger synchronization.
|
||||
Eight,
|
||||
}
|
||||
|
||||
/// Represents the camera interface.
|
||||
pub struct Cam<'d> {
|
||||
/// The LCD_CAM peripheral reference for managing the camera functionality.
|
||||
pub(crate) lcd_cam: PeripheralRef<'d, LCD_CAM>,
|
||||
}
|
||||
|
||||
/// Represents the camera interface with DMA support.
|
||||
pub struct Camera<'d> {
|
||||
lcd_cam: PeripheralRef<'d, LCD_CAM>,
|
||||
rx_channel: ChannelRx<'d, Blocking, <LCD_CAM as DmaEligible>::Dma>,
|
||||
}
|
||||
|
||||
impl<'d> Camera<'d> {
|
||||
/// Creates a new `Camera` instance with DMA support.
|
||||
pub fn new<P, CH>(
|
||||
cam: Cam<'d>,
|
||||
channel: ChannelRx<'d, Blocking, CH>,
|
||||
_pins: P,
|
||||
frequency: HertzU32,
|
||||
) -> Self
|
||||
where
|
||||
CH: DmaChannelConvert<<LCD_CAM as DmaEligible>::Dma>,
|
||||
P: RxPins,
|
||||
{
|
||||
let lcd_cam = cam.lcd_cam;
|
||||
|
||||
let clocks = Clocks::get();
|
||||
let (i, divider) = calculate_clkm(
|
||||
frequency.to_Hz() as _,
|
||||
&[
|
||||
clocks.xtal_clock.to_Hz() as _,
|
||||
clocks.cpu_clock.to_Hz() as _,
|
||||
clocks.crypto_pwm_clock.to_Hz() as _,
|
||||
],
|
||||
);
|
||||
|
||||
lcd_cam.cam_ctrl().write(|w| {
|
||||
// Force enable the clock for all configuration registers.
|
||||
unsafe {
|
||||
w.cam_clk_sel().bits((i + 1) as _);
|
||||
w.cam_clkm_div_num().bits(divider.div_num as _);
|
||||
w.cam_clkm_div_b().bits(divider.div_b as _);
|
||||
w.cam_clkm_div_a().bits(divider.div_a as _);
|
||||
w.cam_vsync_filter_thres().bits(0);
|
||||
w.cam_vs_eof_en().set_bit();
|
||||
w.cam_line_int_en().clear_bit();
|
||||
w.cam_stop_en().clear_bit()
|
||||
}
|
||||
});
|
||||
lcd_cam.cam_ctrl1().write(|w| unsafe {
|
||||
w.cam_vh_de_mode_en().set_bit();
|
||||
w.cam_rec_data_bytelen().bits(0);
|
||||
w.cam_line_int_num().bits(0);
|
||||
w.cam_vsync_filter_en().clear_bit();
|
||||
w.cam_2byte_en().bit(P::BUS_WIDTH == 2);
|
||||
w.cam_clk_inv().clear_bit();
|
||||
w.cam_de_inv().clear_bit();
|
||||
w.cam_hsync_inv().clear_bit();
|
||||
w.cam_vsync_inv().clear_bit()
|
||||
});
|
||||
|
||||
lcd_cam
|
||||
.cam_rgb_yuv()
|
||||
.write(|w| w.cam_conv_bypass().clear_bit());
|
||||
|
||||
lcd_cam.cam_ctrl().modify(|_, w| w.cam_update().set_bit());
|
||||
|
||||
Self {
|
||||
lcd_cam,
|
||||
rx_channel: channel.degrade(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> Camera<'d> {
|
||||
/// Configures the byte order for the camera data.
|
||||
pub fn set_byte_order(&mut self, byte_order: ByteOrder) -> &mut Self {
|
||||
self.lcd_cam
|
||||
.cam_ctrl()
|
||||
.modify(|_, w| w.cam_byte_order().bit(byte_order != ByteOrder::default()));
|
||||
self
|
||||
}
|
||||
|
||||
/// Configures the bit order for the camera data.
|
||||
pub fn set_bit_order(&mut self, bit_order: BitOrder) -> &mut Self {
|
||||
self.lcd_cam
|
||||
.cam_ctrl()
|
||||
.modify(|_, w| w.cam_bit_order().bit(bit_order != BitOrder::default()));
|
||||
self
|
||||
}
|
||||
|
||||
/// Configures the VSYNC filter threshold.
|
||||
pub fn set_vsync_filter(&mut self, threshold: Option<VsyncFilterThreshold>) -> &mut Self {
|
||||
if let Some(threshold) = threshold {
|
||||
let value = match threshold {
|
||||
VsyncFilterThreshold::One => 0,
|
||||
VsyncFilterThreshold::Two => 1,
|
||||
VsyncFilterThreshold::Three => 2,
|
||||
VsyncFilterThreshold::Four => 3,
|
||||
VsyncFilterThreshold::Five => 4,
|
||||
VsyncFilterThreshold::Six => 5,
|
||||
VsyncFilterThreshold::Seven => 6,
|
||||
VsyncFilterThreshold::Eight => 7,
|
||||
};
|
||||
|
||||
self.lcd_cam
|
||||
.cam_ctrl()
|
||||
.modify(|_, w| unsafe { w.cam_vsync_filter_thres().bits(value) });
|
||||
self.lcd_cam
|
||||
.cam_ctrl1()
|
||||
.modify(|_, w| w.cam_vsync_filter_en().set_bit());
|
||||
} else {
|
||||
self.lcd_cam
|
||||
.cam_ctrl1()
|
||||
.modify(|_, w| w.cam_vsync_filter_en().clear_bit());
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Configures the master clock (MCLK) pin for the camera interface.
|
||||
pub fn with_master_clock<MCLK: PeripheralOutput>(
|
||||
self,
|
||||
mclk: impl Peripheral<P = MCLK> + 'd,
|
||||
) -> Self {
|
||||
crate::into_mapped_ref!(mclk);
|
||||
|
||||
mclk.set_to_push_pull_output(crate::private::Internal);
|
||||
OutputSignal::CAM_CLK.connect_to(mclk);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Configures the pixel clock (PCLK) pin for the camera interface.
|
||||
pub fn with_pixel_clock<PCLK: PeripheralInput>(
|
||||
self,
|
||||
pclk: impl Peripheral<P = PCLK> + 'd,
|
||||
) -> Self {
|
||||
crate::into_mapped_ref!(pclk);
|
||||
|
||||
pclk.init_input(Pull::None, crate::private::Internal);
|
||||
InputSignal::CAM_PCLK.connect_to(pclk);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Configures the control pins for the camera interface (VSYNC and
|
||||
/// HENABLE).
|
||||
pub fn with_ctrl_pins<VSYNC: PeripheralInput, HENABLE: PeripheralInput>(
|
||||
self,
|
||||
vsync: impl Peripheral<P = VSYNC> + 'd,
|
||||
h_enable: impl Peripheral<P = HENABLE> + 'd,
|
||||
) -> Self {
|
||||
crate::into_mapped_ref!(vsync, h_enable);
|
||||
|
||||
vsync.init_input(Pull::None, crate::private::Internal);
|
||||
InputSignal::CAM_V_SYNC.connect_to(vsync);
|
||||
h_enable.init_input(Pull::None, crate::private::Internal);
|
||||
InputSignal::CAM_H_ENABLE.connect_to(h_enable);
|
||||
|
||||
self.lcd_cam
|
||||
.cam_ctrl1()
|
||||
.modify(|_, w| w.cam_vh_de_mode_en().clear_bit());
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Configures the control pins for the camera interface (VSYNC, HSYNC, and
|
||||
/// HENABLE) with DE (data enable).
|
||||
pub fn with_ctrl_pins_and_de<
|
||||
VSYNC: PeripheralInput,
|
||||
HSYNC: PeripheralInput,
|
||||
HENABLE: PeripheralInput,
|
||||
>(
|
||||
self,
|
||||
vsync: impl Peripheral<P = VSYNC> + 'd,
|
||||
hsync: impl Peripheral<P = HSYNC> + 'd,
|
||||
h_enable: impl Peripheral<P = HENABLE> + 'd,
|
||||
) -> Self {
|
||||
crate::into_mapped_ref!(vsync, hsync, h_enable);
|
||||
|
||||
vsync.init_input(Pull::None, crate::private::Internal);
|
||||
InputSignal::CAM_V_SYNC.connect_to(vsync);
|
||||
hsync.init_input(Pull::None, crate::private::Internal);
|
||||
InputSignal::CAM_H_SYNC.connect_to(hsync);
|
||||
h_enable.init_input(Pull::None, crate::private::Internal);
|
||||
InputSignal::CAM_H_ENABLE.connect_to(h_enable);
|
||||
|
||||
self.lcd_cam
|
||||
.cam_ctrl1()
|
||||
.modify(|_, w| w.cam_vh_de_mode_en().set_bit());
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Starts a DMA transfer to receive data from the camera peripheral.
|
||||
pub fn receive<BUF: DmaRxBuffer>(
|
||||
mut self,
|
||||
mut buf: BUF,
|
||||
) -> Result<CameraTransfer<'d, BUF>, (DmaError, Self, BUF)> {
|
||||
// Reset Camera control unit and Async Rx FIFO
|
||||
self.lcd_cam
|
||||
.cam_ctrl1()
|
||||
.modify(|_, w| w.cam_reset().set_bit());
|
||||
self.lcd_cam
|
||||
.cam_ctrl1()
|
||||
.modify(|_, w| w.cam_reset().clear_bit());
|
||||
self.lcd_cam
|
||||
.cam_ctrl1()
|
||||
.modify(|_, w| w.cam_afifo_reset().set_bit());
|
||||
self.lcd_cam
|
||||
.cam_ctrl1()
|
||||
.modify(|_, w| w.cam_afifo_reset().clear_bit());
|
||||
|
||||
// Start DMA to receive incoming transfer.
|
||||
let result = unsafe {
|
||||
self.rx_channel
|
||||
.prepare_transfer(DmaPeripheral::LcdCam, &mut buf)
|
||||
.and_then(|_| self.rx_channel.start_transfer())
|
||||
};
|
||||
|
||||
if let Err(e) = result {
|
||||
return Err((e, self, buf));
|
||||
}
|
||||
|
||||
// Start the Camera unit to listen for incoming DVP stream.
|
||||
self.lcd_cam.cam_ctrl().modify(|_, w| {
|
||||
// Automatically stops the camera unit once the GDMA Rx FIFO is full.
|
||||
w.cam_stop_en().set_bit();
|
||||
|
||||
w.cam_update().set_bit()
|
||||
});
|
||||
self.lcd_cam
|
||||
.cam_ctrl1()
|
||||
.modify(|_, w| w.cam_start().set_bit());
|
||||
|
||||
Ok(CameraTransfer {
|
||||
camera: ManuallyDrop::new(self),
|
||||
buffer_view: ManuallyDrop::new(buf.into_view()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents an ongoing (or potentially stopped) transfer from the Camera to a
|
||||
/// DMA buffer.
|
||||
pub struct CameraTransfer<'d, BUF: DmaRxBuffer> {
|
||||
camera: ManuallyDrop<Camera<'d>>,
|
||||
buffer_view: ManuallyDrop<BUF::View>,
|
||||
}
|
||||
|
||||
impl<'d, BUF: DmaRxBuffer> CameraTransfer<'d, BUF> {
|
||||
/// Returns true when [Self::wait] will not block.
|
||||
pub fn is_done(&self) -> bool {
|
||||
// This peripheral doesn't really "complete". As long the camera (or anything
|
||||
// pretending to be :D) sends data, it will receive it and pass it to the DMA.
|
||||
// This implementation of is_done is an opinionated one. When the transfer is
|
||||
// started, the CAM_STOP_EN bit is set, which tells the LCD_CAM to stop
|
||||
// itself when the DMA stops emptying its async RX FIFO. This will
|
||||
// typically be because the DMA ran out descriptors but there could be other
|
||||
// reasons as well.
|
||||
|
||||
// In the future, a user of esp_hal may not want this behaviour, which would be
|
||||
// a reasonable ask. At which point is_done and wait would go away, and
|
||||
// the driver will stop pretending that this peripheral has some kind of
|
||||
// finish line.
|
||||
|
||||
// For now, most people probably want this behaviour, so it shall be kept for
|
||||
// the sake of familiarity and similarity with other drivers.
|
||||
|
||||
self.camera
|
||||
.lcd_cam
|
||||
.cam_ctrl1()
|
||||
.read()
|
||||
.cam_start()
|
||||
.bit_is_clear()
|
||||
}
|
||||
|
||||
/// Stops this transfer on the spot and returns the peripheral and buffer.
|
||||
pub fn stop(mut self) -> (Camera<'d>, BUF) {
|
||||
self.stop_peripherals();
|
||||
let (camera, view) = self.release();
|
||||
(camera, BUF::from_view(view))
|
||||
}
|
||||
|
||||
/// Waits for the transfer to stop and returns the peripheral and buffer.
|
||||
///
|
||||
/// Note: The camera doesn't really "finish" its transfer, so what you're
|
||||
/// really waiting for here is a DMA Error. You typically just want to
|
||||
/// call [Self::stop] once you have the data you need.
|
||||
pub fn wait(mut self) -> (Result<(), DmaError>, Camera<'d>, BUF) {
|
||||
while !self.is_done() {}
|
||||
|
||||
// Stop the DMA as it doesn't know that the camera has stopped.
|
||||
self.camera.rx_channel.stop_transfer();
|
||||
|
||||
// Note: There is no "done" interrupt to clear.
|
||||
|
||||
let (camera, view) = self.release();
|
||||
|
||||
let result = if camera.rx_channel.has_error() {
|
||||
Err(DmaError::DescriptorError)
|
||||
} else {
|
||||
Ok(())
|
||||
};
|
||||
|
||||
(result, camera, BUF::from_view(view))
|
||||
}
|
||||
|
||||
fn release(mut self) -> (Camera<'d>, BUF::View) {
|
||||
// SAFETY: Since forget is called on self, we know that self.camera and
|
||||
// self.buffer_view won't be touched again.
|
||||
let result = unsafe {
|
||||
let camera = ManuallyDrop::take(&mut self.camera);
|
||||
let view = ManuallyDrop::take(&mut self.buffer_view);
|
||||
(camera, view)
|
||||
};
|
||||
core::mem::forget(self);
|
||||
result
|
||||
}
|
||||
|
||||
fn stop_peripherals(&mut self) {
|
||||
// Stop the LCD_CAM peripheral.
|
||||
self.camera
|
||||
.lcd_cam
|
||||
.cam_ctrl1()
|
||||
.modify(|_, w| w.cam_start().clear_bit());
|
||||
|
||||
// Stop the DMA
|
||||
self.camera.rx_channel.stop_transfer();
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, BUF: DmaRxBuffer> Deref for CameraTransfer<'d, BUF> {
|
||||
type Target = BUF::View;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.buffer_view
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, BUF: DmaRxBuffer> DerefMut for CameraTransfer<'d, BUF> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.buffer_view
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, BUF: DmaRxBuffer> Drop for CameraTransfer<'d, BUF> {
|
||||
fn drop(&mut self) {
|
||||
self.stop_peripherals();
|
||||
|
||||
// SAFETY: This is Drop, we know that self.camera and self.buffer_view
|
||||
// won't be touched again.
|
||||
unsafe {
|
||||
ManuallyDrop::drop(&mut self.camera);
|
||||
ManuallyDrop::drop(&mut self.buffer_view);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents an 8-bit wide camera data bus.
|
||||
/// Is used to configure the camera interface to receive 8-bit data.
|
||||
pub struct RxEightBits {
|
||||
_pins: (),
|
||||
}
|
||||
|
||||
impl RxEightBits {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
/// Creates a new instance of `RxEightBits`, configuring the specified pins
|
||||
/// as the 8-bit data bus.
|
||||
pub fn new<'d>(
|
||||
pin_0: impl Peripheral<P = impl PeripheralInput> + 'd,
|
||||
pin_1: impl Peripheral<P = impl PeripheralInput> + 'd,
|
||||
pin_2: impl Peripheral<P = impl PeripheralInput> + 'd,
|
||||
pin_3: impl Peripheral<P = impl PeripheralInput> + 'd,
|
||||
pin_4: impl Peripheral<P = impl PeripheralInput> + 'd,
|
||||
pin_5: impl Peripheral<P = impl PeripheralInput> + 'd,
|
||||
pin_6: impl Peripheral<P = impl PeripheralInput> + 'd,
|
||||
pin_7: impl Peripheral<P = impl PeripheralInput> + 'd,
|
||||
) -> Self {
|
||||
crate::into_mapped_ref!(pin_0);
|
||||
crate::into_mapped_ref!(pin_1);
|
||||
crate::into_mapped_ref!(pin_2);
|
||||
crate::into_mapped_ref!(pin_3);
|
||||
crate::into_mapped_ref!(pin_4);
|
||||
crate::into_mapped_ref!(pin_5);
|
||||
crate::into_mapped_ref!(pin_6);
|
||||
crate::into_mapped_ref!(pin_7);
|
||||
|
||||
let pairs = [
|
||||
(pin_0, InputSignal::CAM_DATA_0),
|
||||
(pin_1, InputSignal::CAM_DATA_1),
|
||||
(pin_2, InputSignal::CAM_DATA_2),
|
||||
(pin_3, InputSignal::CAM_DATA_3),
|
||||
(pin_4, InputSignal::CAM_DATA_4),
|
||||
(pin_5, InputSignal::CAM_DATA_5),
|
||||
(pin_6, InputSignal::CAM_DATA_6),
|
||||
(pin_7, InputSignal::CAM_DATA_7),
|
||||
];
|
||||
|
||||
for (pin, signal) in pairs.into_iter() {
|
||||
pin.init_input(Pull::None, crate::private::Internal);
|
||||
signal.connect_to(pin);
|
||||
}
|
||||
|
||||
Self { _pins: () }
|
||||
}
|
||||
}
|
||||
|
||||
impl RxPins for RxEightBits {
|
||||
const BUS_WIDTH: usize = 1;
|
||||
}
|
||||
|
||||
/// Represents a 16-bit wide camera data bus.
|
||||
/// Is used to configure the camera interface to receive 16-bit data.
|
||||
pub struct RxSixteenBits {
|
||||
_pins: (),
|
||||
}
|
||||
|
||||
impl RxSixteenBits {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
/// Creates a new instance of `RxSixteenBits`, configuring the specified
|
||||
/// pins as the 16-bit data bus.
|
||||
pub fn new<'d>(
|
||||
pin_0: impl Peripheral<P = impl PeripheralInput> + 'd,
|
||||
pin_1: impl Peripheral<P = impl PeripheralInput> + 'd,
|
||||
pin_2: impl Peripheral<P = impl PeripheralInput> + 'd,
|
||||
pin_3: impl Peripheral<P = impl PeripheralInput> + 'd,
|
||||
pin_4: impl Peripheral<P = impl PeripheralInput> + 'd,
|
||||
pin_5: impl Peripheral<P = impl PeripheralInput> + 'd,
|
||||
pin_6: impl Peripheral<P = impl PeripheralInput> + 'd,
|
||||
pin_7: impl Peripheral<P = impl PeripheralInput> + 'd,
|
||||
pin_8: impl Peripheral<P = impl PeripheralInput> + 'd,
|
||||
pin_9: impl Peripheral<P = impl PeripheralInput> + 'd,
|
||||
pin_10: impl Peripheral<P = impl PeripheralInput> + 'd,
|
||||
pin_11: impl Peripheral<P = impl PeripheralInput> + 'd,
|
||||
pin_12: impl Peripheral<P = impl PeripheralInput> + 'd,
|
||||
pin_13: impl Peripheral<P = impl PeripheralInput> + 'd,
|
||||
pin_14: impl Peripheral<P = impl PeripheralInput> + 'd,
|
||||
pin_15: impl Peripheral<P = impl PeripheralInput> + 'd,
|
||||
) -> Self {
|
||||
crate::into_mapped_ref!(pin_0);
|
||||
crate::into_mapped_ref!(pin_1);
|
||||
crate::into_mapped_ref!(pin_2);
|
||||
crate::into_mapped_ref!(pin_3);
|
||||
crate::into_mapped_ref!(pin_4);
|
||||
crate::into_mapped_ref!(pin_5);
|
||||
crate::into_mapped_ref!(pin_6);
|
||||
crate::into_mapped_ref!(pin_7);
|
||||
crate::into_mapped_ref!(pin_8);
|
||||
crate::into_mapped_ref!(pin_9);
|
||||
crate::into_mapped_ref!(pin_10);
|
||||
crate::into_mapped_ref!(pin_11);
|
||||
crate::into_mapped_ref!(pin_12);
|
||||
crate::into_mapped_ref!(pin_13);
|
||||
crate::into_mapped_ref!(pin_14);
|
||||
crate::into_mapped_ref!(pin_15);
|
||||
|
||||
let pairs = [
|
||||
(pin_0, InputSignal::CAM_DATA_0),
|
||||
(pin_1, InputSignal::CAM_DATA_1),
|
||||
(pin_2, InputSignal::CAM_DATA_2),
|
||||
(pin_3, InputSignal::CAM_DATA_3),
|
||||
(pin_4, InputSignal::CAM_DATA_4),
|
||||
(pin_5, InputSignal::CAM_DATA_5),
|
||||
(pin_6, InputSignal::CAM_DATA_6),
|
||||
(pin_7, InputSignal::CAM_DATA_7),
|
||||
(pin_8, InputSignal::CAM_DATA_8),
|
||||
(pin_9, InputSignal::CAM_DATA_9),
|
||||
(pin_10, InputSignal::CAM_DATA_10),
|
||||
(pin_11, InputSignal::CAM_DATA_11),
|
||||
(pin_12, InputSignal::CAM_DATA_12),
|
||||
(pin_13, InputSignal::CAM_DATA_13),
|
||||
(pin_14, InputSignal::CAM_DATA_14),
|
||||
(pin_15, InputSignal::CAM_DATA_15),
|
||||
];
|
||||
|
||||
for (pin, signal) in pairs.into_iter() {
|
||||
pin.init_input(Pull::None, crate::private::Internal);
|
||||
signal.connect_to(pin);
|
||||
}
|
||||
|
||||
Self { _pins: () }
|
||||
}
|
||||
}
|
||||
|
||||
impl RxPins for RxSixteenBits {
|
||||
const BUS_WIDTH: usize = 2;
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub trait RxPins {
|
||||
const BUS_WIDTH: usize;
|
||||
}
|
||||
+826
@@ -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,
|
||||
}
|
||||
+722
@@ -0,0 +1,722 @@
|
||||
//! # LCD - I8080/MOTO6800 Mode.
|
||||
//!
|
||||
//! ## Overview
|
||||
//!
|
||||
//! The LCD_CAM peripheral I8080 driver provides support for the I8080
|
||||
//! format/timing. The driver mandates DMA (Direct Memory Access) for
|
||||
//! efficient data transfer.
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
//! ### MIPI-DSI Display
|
||||
//!
|
||||
//! The following example shows how to send a command to a MIPI-DSI display over
|
||||
//! the I8080 protocol.
|
||||
//!
|
||||
//! ```rust, no_run
|
||||
#![doc = crate::before_snippet!()]
|
||||
//! # use esp_hal::lcd_cam::{LcdCam, lcd::i8080::{Config, I8080, TxEightBits}};
|
||||
//! # use esp_hal::dma_tx_buffer;
|
||||
//! # use esp_hal::dma::{Dma, DmaPriority, DmaTxBuf};
|
||||
//!
|
||||
//! # let dma = Dma::new(peripherals.DMA);
|
||||
//! # let channel = dma.channel0;
|
||||
//!
|
||||
//! # let mut dma_buf = dma_tx_buffer!(32678).unwrap();
|
||||
//!
|
||||
//! # let channel = channel.configure(
|
||||
//! # false,
|
||||
//! # DmaPriority::Priority0,
|
||||
//! # );
|
||||
//!
|
||||
//! let tx_pins = TxEightBits::new(
|
||||
//! peripherals.GPIO9,
|
||||
//! peripherals.GPIO46,
|
||||
//! peripherals.GPIO3,
|
||||
//! peripherals.GPIO8,
|
||||
//! peripherals.GPIO18,
|
||||
//! peripherals.GPIO17,
|
||||
//! peripherals.GPIO16,
|
||||
//! peripherals.GPIO15,
|
||||
//! );
|
||||
//! let lcd_cam = LcdCam::new(peripherals.LCD_CAM);
|
||||
//!
|
||||
//! let mut i8080 = I8080::new(
|
||||
//! lcd_cam.lcd,
|
||||
//! channel.tx,
|
||||
//! tx_pins,
|
||||
//! 20.MHz(),
|
||||
//! Config::default(),
|
||||
//! )
|
||||
//! .with_ctrl_pins(peripherals.GPIO0, peripherals.GPIO47);
|
||||
//!
|
||||
//! 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, ManuallyDrop},
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
use fugit::HertzU32;
|
||||
|
||||
use crate::{
|
||||
clock::Clocks,
|
||||
dma::{ChannelTx, DmaChannelConvert, DmaEligible, DmaError, DmaPeripheral, DmaTxBuffer, Tx},
|
||||
gpio::{
|
||||
interconnect::{OutputConnection, PeripheralOutput},
|
||||
OutputSignal,
|
||||
},
|
||||
lcd_cam::{
|
||||
calculate_clkm,
|
||||
lcd::{ClockMode, DelayMode, Phase, Polarity},
|
||||
BitOrder,
|
||||
ByteOrder,
|
||||
Instance,
|
||||
Lcd,
|
||||
LCD_DONE_WAKER,
|
||||
},
|
||||
peripheral::{Peripheral, PeripheralRef},
|
||||
peripherals::LCD_CAM,
|
||||
Blocking,
|
||||
Mode,
|
||||
};
|
||||
|
||||
/// Represents the I8080 LCD interface.
|
||||
pub struct I8080<'d, DM: Mode> {
|
||||
lcd_cam: PeripheralRef<'d, LCD_CAM>,
|
||||
tx_channel: ChannelTx<'d, Blocking, <LCD_CAM as DmaEligible>::Dma>,
|
||||
_mode: PhantomData<DM>,
|
||||
}
|
||||
|
||||
impl<'d, DM> I8080<'d, DM>
|
||||
where
|
||||
DM: Mode,
|
||||
{
|
||||
/// Creates a new instance of the I8080 LCD interface.
|
||||
pub fn new<P, CH>(
|
||||
lcd: Lcd<'d, DM>,
|
||||
channel: ChannelTx<'d, Blocking, CH>,
|
||||
mut pins: P,
|
||||
frequency: HertzU32,
|
||||
config: Config,
|
||||
) -> Self
|
||||
where
|
||||
CH: DmaChannelConvert<<LCD_CAM as DmaEligible>::Dma>,
|
||||
P: TxPins,
|
||||
{
|
||||
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_ctrl()
|
||||
.write(|w| w.lcd_rgb_mode_en().clear_bit());
|
||||
lcd_cam
|
||||
.lcd_rgb_yuv()
|
||||
.write(|w| w.lcd_conv_bypass().clear_bit());
|
||||
|
||||
lcd_cam.lcd_user().modify(|_, w| {
|
||||
w.lcd_8bits_order().bit(false);
|
||||
w.lcd_bit_order().bit(false);
|
||||
w.lcd_byte_order().bit(false);
|
||||
w.lcd_2byte_en().bit(false)
|
||||
});
|
||||
lcd_cam.lcd_misc().write(|w| unsafe {
|
||||
// Set the threshold for Async Tx FIFO full event. (5 bits)
|
||||
w.lcd_afifo_threshold_num().bits(0);
|
||||
// Configure the setup cycles in LCD non-RGB mode. Setup cycles
|
||||
// expected = this value + 1. (6 bit)
|
||||
w.lcd_vfk_cyclelen()
|
||||
.bits(config.setup_cycles.saturating_sub(1) as _);
|
||||
// Configure the hold time cycles in LCD non-RGB mode. Hold
|
||||
// cycles expected = this value + 1.
|
||||
w.lcd_vbk_cyclelen()
|
||||
.bits(config.hold_cycles.saturating_sub(1) as _);
|
||||
// 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().set_bit();
|
||||
// 1: LCD_CD = !LCD_CAM_LCD_CD_IDLE_EDGE when LCD is in DOUT phase.
|
||||
// 0: LCD_CD = LCD_CAM_LCD_CD_IDLE_EDGE.
|
||||
w.lcd_cd_data_set()
|
||||
.bit(config.cd_data_edge != config.cd_idle_edge);
|
||||
// 1: LCD_CD = !LCD_CAM_LCD_CD_IDLE_EDGE when LCD is in DUMMY phase.
|
||||
// 0: LCD_CD = LCD_CAM_LCD_CD_IDLE_EDGE.
|
||||
w.lcd_cd_dummy_set()
|
||||
.bit(config.cd_dummy_edge != config.cd_idle_edge);
|
||||
// 1: LCD_CD = !LCD_CAM_LCD_CD_IDLE_EDGE when LCD is in CMD phase.
|
||||
// 0: LCD_CD = LCD_CAM_LCD_CD_IDLE_EDGE.
|
||||
w.lcd_cd_cmd_set()
|
||||
.bit(config.cd_cmd_edge != config.cd_idle_edge);
|
||||
// The default value of LCD_CD
|
||||
w.lcd_cd_idle_edge().bit(config.cd_idle_edge)
|
||||
});
|
||||
lcd_cam
|
||||
.lcd_dly_mode()
|
||||
.write(|w| unsafe { w.lcd_cd_mode().bits(config.cd_mode as u8) });
|
||||
lcd_cam.lcd_data_dout_mode().write(|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());
|
||||
|
||||
pins.configure();
|
||||
|
||||
Self {
|
||||
lcd_cam,
|
||||
tx_channel: channel.degrade(),
|
||||
_mode: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Configures the byte order for data transmission in 16-bit mode.
|
||||
/// This must be set to [ByteOrder::default()] when transmitting in 8-bit
|
||||
/// mode.
|
||||
pub fn set_byte_order(&mut self, byte_order: ByteOrder) -> &mut Self {
|
||||
let is_inverted = byte_order != ByteOrder::default();
|
||||
self.lcd_cam
|
||||
.lcd_user()
|
||||
.modify(|_, w| w.lcd_byte_order().bit(is_inverted));
|
||||
self
|
||||
}
|
||||
|
||||
/// Configures the byte order for data transmission in 8-bit mode.
|
||||
/// This must be set to [ByteOrder::default()] when transmitting in 16-bit
|
||||
/// mode.
|
||||
pub fn set_8bits_order(&mut self, byte_order: ByteOrder) -> &mut Self {
|
||||
let is_inverted = byte_order != ByteOrder::default();
|
||||
self.lcd_cam
|
||||
.lcd_user()
|
||||
.modify(|_, w| w.lcd_8bits_order().bit(is_inverted));
|
||||
self
|
||||
}
|
||||
|
||||
/// Configures the bit order for data transmission.
|
||||
pub fn set_bit_order(&mut self, bit_order: BitOrder) -> &mut Self {
|
||||
self.lcd_cam
|
||||
.lcd_user()
|
||||
.modify(|_, w| w.lcd_bit_order().bit(bit_order != BitOrder::default()));
|
||||
self
|
||||
}
|
||||
|
||||
/// Associates a CS pin with the I8080 interface.
|
||||
pub fn with_cs<CS: PeripheralOutput>(self, cs: impl Peripheral<P = CS> + 'd) -> Self {
|
||||
crate::into_mapped_ref!(cs);
|
||||
cs.set_to_push_pull_output(crate::private::Internal);
|
||||
OutputSignal::LCD_CS.connect_to(cs);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// Configures the control pins for the I8080 interface.
|
||||
pub fn with_ctrl_pins<DC: PeripheralOutput, WRX: PeripheralOutput>(
|
||||
self,
|
||||
dc: impl Peripheral<P = DC> + 'd,
|
||||
wrx: impl Peripheral<P = WRX> + 'd,
|
||||
) -> Self {
|
||||
crate::into_mapped_ref!(dc, wrx);
|
||||
|
||||
dc.set_to_push_pull_output(crate::private::Internal);
|
||||
OutputSignal::LCD_DC.connect_to(dc);
|
||||
|
||||
wrx.set_to_push_pull_output(crate::private::Internal);
|
||||
OutputSignal::LCD_PCLK.connect_to(wrx);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
/// 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 fn send<W: Into<u16> + Copy, BUF: DmaTxBuffer>(
|
||||
mut self,
|
||||
cmd: impl Into<Command<W>>,
|
||||
dummy: u8,
|
||||
mut data: BUF,
|
||||
) -> Result<I8080Transfer<'d, BUF, DM>, (DmaError, Self, BUF)> {
|
||||
let cmd = cmd.into();
|
||||
|
||||
// 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());
|
||||
|
||||
// Set cmd value
|
||||
match cmd {
|
||||
Command::None => {
|
||||
self.lcd_cam
|
||||
.lcd_user()
|
||||
.modify(|_, w| w.lcd_cmd().clear_bit());
|
||||
}
|
||||
Command::One(value) => {
|
||||
self.lcd_cam.lcd_user().modify(|_, w| {
|
||||
w.lcd_cmd().set_bit();
|
||||
w.lcd_cmd_2_cycle_en().clear_bit()
|
||||
});
|
||||
self.lcd_cam
|
||||
.lcd_cmd_val()
|
||||
.write(|w| unsafe { w.lcd_cmd_value().bits(value.into() as _) });
|
||||
}
|
||||
Command::Two(first, second) => {
|
||||
self.lcd_cam.lcd_user().modify(|_, w| {
|
||||
w.lcd_cmd().set_bit();
|
||||
w.lcd_cmd_2_cycle_en().set_bit()
|
||||
});
|
||||
let cmd = first.into() as u32 | (second.into() as u32) << 16;
|
||||
self.lcd_cam
|
||||
.lcd_cmd_val()
|
||||
.write(|w| unsafe { w.lcd_cmd_value().bits(cmd) });
|
||||
}
|
||||
}
|
||||
|
||||
let is_2byte_mode = size_of::<W>() == 2;
|
||||
|
||||
self.lcd_cam.lcd_user().modify(|_, w| unsafe {
|
||||
// Set dummy length
|
||||
if dummy > 0 {
|
||||
// Enable DUMMY phase in LCD sequence when LCD starts.
|
||||
w.lcd_dummy()
|
||||
.set_bit()
|
||||
// Configure DUMMY cycles. DUMMY cycles = this value + 1. (2 bits)
|
||||
.lcd_dummy_cyclelen()
|
||||
.bits((dummy - 1) as _)
|
||||
} else {
|
||||
w.lcd_dummy().clear_bit()
|
||||
}
|
||||
.lcd_2byte_en()
|
||||
.bit(is_2byte_mode)
|
||||
});
|
||||
|
||||
// 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()
|
||||
.write(|w| w.lcd_trans_done_int_clr().set_bit());
|
||||
|
||||
// Before issuing lcd_start need to wait shortly for fifo to get data
|
||||
// Otherwise, some garbage data will be sent out
|
||||
crate::rom::ets_delay_us(1);
|
||||
|
||||
self.lcd_cam.lcd_user().modify(|_, w| {
|
||||
w.lcd_update().set_bit();
|
||||
w.lcd_start().set_bit()
|
||||
});
|
||||
|
||||
Ok(I8080Transfer {
|
||||
i8080: ManuallyDrop::new(self),
|
||||
buf_view: ManuallyDrop::new(data.into_view()),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, DM: Mode> core::fmt::Debug for I8080<'d, 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: DmaTxBuffer, DM: Mode> {
|
||||
i8080: ManuallyDrop<I8080<'d, DM>>,
|
||||
buf_view: ManuallyDrop<BUF::View>,
|
||||
}
|
||||
|
||||
impl<'d, BUF: DmaTxBuffer, DM: Mode> I8080Transfer<'d, BUF, 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, 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, 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.buf_view won't be touched again.
|
||||
let (i8080, view) = unsafe {
|
||||
let i8080 = ManuallyDrop::take(&mut self.i8080);
|
||||
let view = ManuallyDrop::take(&mut self.buf_view);
|
||||
core::mem::forget(self);
|
||||
(i8080, view)
|
||||
};
|
||||
|
||||
let result = if i8080.tx_channel.has_error() {
|
||||
Err(DmaError::DescriptorError)
|
||||
} else {
|
||||
Ok(())
|
||||
};
|
||||
|
||||
(result, i8080, BUF::from_view(view))
|
||||
}
|
||||
|
||||
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: DmaTxBuffer, DM: Mode> Deref for I8080Transfer<'d, BUF, DM> {
|
||||
type Target = BUF::View;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.buf_view
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, BUF: DmaTxBuffer, DM: Mode> DerefMut for I8080Transfer<'d, BUF, DM> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.buf_view
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, BUF: DmaTxBuffer> I8080Transfer<'d, BUF, 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: DmaTxBuffer, DM: Mode> Drop for I8080Transfer<'d, BUF, DM> {
|
||||
fn drop(&mut self) {
|
||||
self.stop_peripherals();
|
||||
|
||||
// SAFETY: This is Drop, we know that self.i8080 and self.buf_view
|
||||
// won't be touched again.
|
||||
let view = unsafe {
|
||||
ManuallyDrop::drop(&mut self.i8080);
|
||||
ManuallyDrop::take(&mut self.buf_view)
|
||||
};
|
||||
let _ = BUF::from_view(view);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
/// Configuration settings for the I8080 interface.
|
||||
pub struct Config {
|
||||
/// Specifies the clock mode, including polarity and phase settings.
|
||||
pub clock_mode: ClockMode,
|
||||
|
||||
/// Setup cycles expected, must be at least 1. (6 bits)
|
||||
pub setup_cycles: usize,
|
||||
|
||||
/// Hold cycles expected, must be at least 1. (13 bits)
|
||||
pub hold_cycles: usize,
|
||||
|
||||
/// The default value of LCD_CD.
|
||||
pub cd_idle_edge: bool,
|
||||
/// The value of LCD_CD during CMD phase.
|
||||
pub cd_cmd_edge: bool,
|
||||
/// The value of LCD_CD during dummy phase.
|
||||
pub cd_dummy_edge: bool,
|
||||
/// The value of LCD_CD during data phase.
|
||||
pub cd_data_edge: bool,
|
||||
|
||||
/// The output LCD_CD is delayed by module clock LCD_CLK.
|
||||
pub cd_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 {
|
||||
Self {
|
||||
clock_mode: Default::default(),
|
||||
setup_cycles: 1,
|
||||
hold_cycles: 1,
|
||||
cd_idle_edge: false,
|
||||
cd_cmd_edge: false,
|
||||
cd_dummy_edge: false,
|
||||
cd_data_edge: true,
|
||||
cd_mode: Default::default(),
|
||||
output_bit_mode: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// LCD_CAM I8080 command.
|
||||
///
|
||||
/// Can be [Command::None] if command phase should be suppressed.
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum Command<T> {
|
||||
/// Suppresses the command phase. No command is sent.
|
||||
None,
|
||||
/// Sends a single-word command.
|
||||
One(T),
|
||||
/// Sends a two-word command.
|
||||
Two(T, T),
|
||||
}
|
||||
|
||||
impl From<u8> for Command<u8> {
|
||||
fn from(value: u8) -> Self {
|
||||
Command::One(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u16> for Command<u16> {
|
||||
fn from(value: u16) -> Self {
|
||||
Command::One(value)
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a group of 8 output pins configured for 8-bit parallel data
|
||||
/// transmission.
|
||||
pub struct TxEightBits<'d> {
|
||||
pins: [PeripheralRef<'d, OutputConnection>; 8],
|
||||
}
|
||||
|
||||
impl<'d> TxEightBits<'d> {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
/// Creates a new `TxEightBits` instance with the provided output pins.
|
||||
pub fn new(
|
||||
pin_0: impl Peripheral<P = impl PeripheralOutput> + 'd,
|
||||
pin_1: impl Peripheral<P = impl PeripheralOutput> + 'd,
|
||||
pin_2: impl Peripheral<P = impl PeripheralOutput> + 'd,
|
||||
pin_3: impl Peripheral<P = impl PeripheralOutput> + 'd,
|
||||
pin_4: impl Peripheral<P = impl PeripheralOutput> + 'd,
|
||||
pin_5: impl Peripheral<P = impl PeripheralOutput> + 'd,
|
||||
pin_6: impl Peripheral<P = impl PeripheralOutput> + 'd,
|
||||
pin_7: impl Peripheral<P = impl PeripheralOutput> + 'd,
|
||||
) -> Self {
|
||||
crate::into_mapped_ref!(pin_0);
|
||||
crate::into_mapped_ref!(pin_1);
|
||||
crate::into_mapped_ref!(pin_2);
|
||||
crate::into_mapped_ref!(pin_3);
|
||||
crate::into_mapped_ref!(pin_4);
|
||||
crate::into_mapped_ref!(pin_5);
|
||||
crate::into_mapped_ref!(pin_6);
|
||||
crate::into_mapped_ref!(pin_7);
|
||||
|
||||
Self {
|
||||
pins: [pin_0, pin_1, pin_2, pin_3, pin_4, pin_5, pin_6, pin_7],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> TxPins for TxEightBits<'d> {
|
||||
fn configure(&mut self) {
|
||||
const SIGNALS: [OutputSignal; 8] = [
|
||||
OutputSignal::LCD_DATA_0,
|
||||
OutputSignal::LCD_DATA_1,
|
||||
OutputSignal::LCD_DATA_2,
|
||||
OutputSignal::LCD_DATA_3,
|
||||
OutputSignal::LCD_DATA_4,
|
||||
OutputSignal::LCD_DATA_5,
|
||||
OutputSignal::LCD_DATA_6,
|
||||
OutputSignal::LCD_DATA_7,
|
||||
];
|
||||
|
||||
for (pin, signal) in self.pins.iter_mut().zip(SIGNALS.into_iter()) {
|
||||
pin.set_to_push_pull_output(crate::private::Internal);
|
||||
signal.connect_to(pin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a group of 16 output pins configured for 16-bit parallel data
|
||||
/// transmission.
|
||||
pub struct TxSixteenBits<'d> {
|
||||
pins: [PeripheralRef<'d, OutputConnection>; 16],
|
||||
}
|
||||
|
||||
impl<'d> TxSixteenBits<'d> {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
/// Creates a new `TxSixteenBits` instance with the provided output pins.
|
||||
pub fn new(
|
||||
pin_0: impl Peripheral<P = impl PeripheralOutput> + 'd,
|
||||
pin_1: impl Peripheral<P = impl PeripheralOutput> + 'd,
|
||||
pin_2: impl Peripheral<P = impl PeripheralOutput> + 'd,
|
||||
pin_3: impl Peripheral<P = impl PeripheralOutput> + 'd,
|
||||
pin_4: impl Peripheral<P = impl PeripheralOutput> + 'd,
|
||||
pin_5: impl Peripheral<P = impl PeripheralOutput> + 'd,
|
||||
pin_6: impl Peripheral<P = impl PeripheralOutput> + 'd,
|
||||
pin_7: impl Peripheral<P = impl PeripheralOutput> + 'd,
|
||||
pin_8: impl Peripheral<P = impl PeripheralOutput> + 'd,
|
||||
pin_9: impl Peripheral<P = impl PeripheralOutput> + 'd,
|
||||
pin_10: impl Peripheral<P = impl PeripheralOutput> + 'd,
|
||||
pin_11: impl Peripheral<P = impl PeripheralOutput> + 'd,
|
||||
pin_12: impl Peripheral<P = impl PeripheralOutput> + 'd,
|
||||
pin_13: impl Peripheral<P = impl PeripheralOutput> + 'd,
|
||||
pin_14: impl Peripheral<P = impl PeripheralOutput> + 'd,
|
||||
pin_15: impl Peripheral<P = impl PeripheralOutput> + 'd,
|
||||
) -> Self {
|
||||
crate::into_mapped_ref!(
|
||||
pin_0, pin_1, pin_2, pin_3, pin_4, pin_5, pin_6, pin_7, pin_8, pin_9, pin_10, pin_11,
|
||||
pin_12, pin_13, pin_14, pin_15
|
||||
);
|
||||
|
||||
Self {
|
||||
pins: [
|
||||
pin_0, pin_1, pin_2, pin_3, pin_4, pin_5, pin_6, pin_7, pin_8, pin_9, pin_10,
|
||||
pin_11, pin_12, pin_13, pin_14, pin_15,
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> TxPins for TxSixteenBits<'d> {
|
||||
fn configure(&mut self) {
|
||||
const SIGNALS: [OutputSignal; 16] = [
|
||||
OutputSignal::LCD_DATA_0,
|
||||
OutputSignal::LCD_DATA_1,
|
||||
OutputSignal::LCD_DATA_2,
|
||||
OutputSignal::LCD_DATA_3,
|
||||
OutputSignal::LCD_DATA_4,
|
||||
OutputSignal::LCD_DATA_5,
|
||||
OutputSignal::LCD_DATA_6,
|
||||
OutputSignal::LCD_DATA_7,
|
||||
OutputSignal::LCD_DATA_8,
|
||||
OutputSignal::LCD_DATA_9,
|
||||
OutputSignal::LCD_DATA_10,
|
||||
OutputSignal::LCD_DATA_11,
|
||||
OutputSignal::LCD_DATA_12,
|
||||
OutputSignal::LCD_DATA_13,
|
||||
OutputSignal::LCD_DATA_14,
|
||||
OutputSignal::LCD_DATA_15,
|
||||
];
|
||||
|
||||
for (pin, signal) in self.pins.iter_mut().zip(SIGNALS.into_iter()) {
|
||||
pin.set_to_push_pull_output(crate::private::Internal);
|
||||
signal.connect_to(pin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub trait TxPins {
|
||||
fn configure(&mut self);
|
||||
}
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
//! LCD
|
||||
//!
|
||||
//! ## Overview
|
||||
//! The LCD module is designed to send parallel video data signals, and its bus
|
||||
//! supports RGB, MOTO6800, and I8080 interface timing.
|
||||
//!
|
||||
//! For more information on these modes, please refer to the documentation in
|
||||
//! their respective modules.
|
||||
//!
|
||||
//! ## Implementation State
|
||||
//! - RGB is not supported yet
|
||||
|
||||
use crate::{peripheral::PeripheralRef, peripherals::LCD_CAM};
|
||||
|
||||
pub mod dpi;
|
||||
pub mod i8080;
|
||||
|
||||
/// Represents an LCD interface.
|
||||
pub struct Lcd<'d, DM: crate::Mode> {
|
||||
/// The `LCD_CAM` peripheral reference for managing the LCD functionality.
|
||||
pub(crate) lcd_cam: PeripheralRef<'d, LCD_CAM>,
|
||||
|
||||
/// A marker for the mode of operation (blocking or asynchronous).
|
||||
pub(crate) _mode: core::marker::PhantomData<DM>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
/// Represents the clock mode configuration for the LCD interface.
|
||||
pub struct ClockMode {
|
||||
/// The polarity of the clock signal (idle high or low).
|
||||
pub polarity: Polarity,
|
||||
|
||||
/// The phase of the clock signal (shift on the rising or falling edge).
|
||||
pub phase: Phase,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
/// Represents the polarity of the clock signal for the LCD interface.
|
||||
pub enum Polarity {
|
||||
/// The clock signal is low when idle.
|
||||
#[default]
|
||||
IdleLow,
|
||||
|
||||
/// The clock signal is high when idle.
|
||||
IdleHigh,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
/// Represents the phase of the clock signal for the LCD interface.
|
||||
pub enum Phase {
|
||||
/// Data is shifted on the low (falling) edge of the clock signal.
|
||||
#[default]
|
||||
ShiftLow,
|
||||
|
||||
/// Data is shifted on the high (rising) edge of the clock signal.
|
||||
ShiftHigh,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
/// Represents the delay mode for the LCD signal output.
|
||||
pub enum DelayMode {
|
||||
/// Output without delay.
|
||||
#[default]
|
||||
None = 0,
|
||||
/// Delayed by the rising edge of LCD_CLK.
|
||||
RaisingEdge = 1,
|
||||
/// Delayed by the falling edge of LCD_CLK.
|
||||
FallingEdge = 2,
|
||||
}
|
||||
Vendored
+307
@@ -0,0 +1,307 @@
|
||||
//! # LCD and Camera
|
||||
//!
|
||||
//! ## Overview
|
||||
//! This peripheral consists of an LCD module and a Camera module, which can be
|
||||
//! used simultaneously. For more information on these modules, please refer to
|
||||
//! the documentation in their respective modules.
|
||||
|
||||
pub mod cam;
|
||||
pub mod lcd;
|
||||
|
||||
use core::marker::PhantomData;
|
||||
|
||||
use embassy_sync::waitqueue::AtomicWaker;
|
||||
|
||||
use crate::{
|
||||
interrupt::InterruptHandler,
|
||||
lcd_cam::{cam::Cam, lcd::Lcd},
|
||||
macros::handler,
|
||||
peripheral::Peripheral,
|
||||
peripherals::{Interrupt, LCD_CAM},
|
||||
system::{self, PeripheralClockControl},
|
||||
Async,
|
||||
Blocking,
|
||||
Cpu,
|
||||
InterruptConfigurable,
|
||||
};
|
||||
|
||||
/// Represents a combined LCD and Camera interface.
|
||||
pub struct LcdCam<'d, DM: crate::Mode> {
|
||||
/// The LCD interface.
|
||||
pub lcd: Lcd<'d, DM>,
|
||||
/// The Camera interface.
|
||||
pub cam: Cam<'d>,
|
||||
}
|
||||
|
||||
impl<'d> LcdCam<'d, Blocking> {
|
||||
/// Creates a new `LcdCam` instance.
|
||||
pub fn new(lcd_cam: impl Peripheral<P = LCD_CAM> + 'd) -> Self {
|
||||
crate::into_ref!(lcd_cam);
|
||||
|
||||
PeripheralClockControl::reset(system::Peripheral::LcdCam);
|
||||
PeripheralClockControl::enable(system::Peripheral::LcdCam);
|
||||
|
||||
Self {
|
||||
lcd: Lcd {
|
||||
lcd_cam: unsafe { lcd_cam.clone_unchecked() },
|
||||
_mode: PhantomData,
|
||||
},
|
||||
cam: Cam { lcd_cam },
|
||||
}
|
||||
}
|
||||
|
||||
/// Reconfigures the peripheral for asynchronous operation.
|
||||
pub fn into_async(mut self) -> LcdCam<'d, Async> {
|
||||
self.set_interrupt_handler(interrupt_handler);
|
||||
LcdCam {
|
||||
lcd: Lcd {
|
||||
lcd_cam: self.lcd.lcd_cam,
|
||||
_mode: PhantomData,
|
||||
},
|
||||
cam: self.cam,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::private::Sealed for LcdCam<'_, Blocking> {}
|
||||
// TODO: This interrupt is shared with the Camera module, we should handle this
|
||||
// in a similar way to the gpio::IO
|
||||
impl InterruptConfigurable for LcdCam<'_, Blocking> {
|
||||
fn set_interrupt_handler(&mut self, handler: InterruptHandler) {
|
||||
for core in crate::Cpu::other() {
|
||||
crate::interrupt::disable(core, Interrupt::LCD_CAM);
|
||||
}
|
||||
unsafe { crate::interrupt::bind_interrupt(Interrupt::LCD_CAM, handler.handler()) };
|
||||
unwrap!(crate::interrupt::enable(
|
||||
Interrupt::LCD_CAM,
|
||||
handler.priority()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d> LcdCam<'d, Async> {
|
||||
/// Reconfigures the peripheral for blocking operation.
|
||||
pub fn into_blocking(self) -> LcdCam<'d, Blocking> {
|
||||
crate::interrupt::disable(Cpu::current(), Interrupt::LCD_CAM);
|
||||
LcdCam {
|
||||
lcd: Lcd {
|
||||
lcd_cam: self.lcd.lcd_cam,
|
||||
_mode: PhantomData,
|
||||
},
|
||||
cam: self.cam,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// LCD_CAM bit order
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum BitOrder {
|
||||
/// Do not change bit order.
|
||||
#[default]
|
||||
Native = 0,
|
||||
/// Invert bit order.
|
||||
Inverted = 1,
|
||||
}
|
||||
|
||||
/// LCD_CAM byte order
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Default)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum ByteOrder {
|
||||
/// Do not change byte order.
|
||||
#[default]
|
||||
Native = 0,
|
||||
/// Invert byte order.
|
||||
Inverted = 1,
|
||||
}
|
||||
|
||||
pub(crate) static LCD_DONE_WAKER: AtomicWaker = AtomicWaker::new();
|
||||
|
||||
#[handler]
|
||||
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();
|
||||
LCD_DONE_WAKER.wake()
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Instance;
|
||||
|
||||
// NOTE: the LCD_CAM interrupt registers are shared between LCD and Camera and
|
||||
// this is only implemented for the LCD side, when the Camera is implemented a
|
||||
// CriticalSection will be needed to protect these shared registers.
|
||||
impl Instance {
|
||||
pub(crate) fn listen_lcd_done() {
|
||||
let lcd_cam = unsafe { LCD_CAM::steal() };
|
||||
lcd_cam
|
||||
.lc_dma_int_ena()
|
||||
.modify(|_, w| w.lcd_trans_done_int_ena().set_bit());
|
||||
}
|
||||
|
||||
pub(crate) fn unlisten_lcd_done() {
|
||||
let lcd_cam = unsafe { LCD_CAM::steal() };
|
||||
lcd_cam
|
||||
.lc_dma_int_ena()
|
||||
.modify(|_, w| w.lcd_trans_done_int_ena().clear_bit());
|
||||
}
|
||||
|
||||
pub(crate) fn is_lcd_done_set() -> bool {
|
||||
let lcd_cam = unsafe { LCD_CAM::steal() };
|
||||
lcd_cam
|
||||
.lc_dma_int_raw()
|
||||
.read()
|
||||
.lcd_trans_done_int_raw()
|
||||
.bit()
|
||||
}
|
||||
}
|
||||
pub(crate) struct ClockDivider {
|
||||
// Integral LCD clock divider value. (8 bits)
|
||||
// Value 0 is treated as 256
|
||||
// Value 1 is treated as 2
|
||||
// Value N is treated as N
|
||||
pub div_num: usize,
|
||||
|
||||
// Fractional clock divider numerator value. (6 bits)
|
||||
pub div_b: usize,
|
||||
|
||||
// Fractional clock divider denominator value. (6 bits)
|
||||
pub div_a: usize,
|
||||
}
|
||||
|
||||
pub(crate) fn calculate_clkm(
|
||||
desired_frequency: usize,
|
||||
source_frequencies: &[usize],
|
||||
) -> (usize, ClockDivider) {
|
||||
let mut result_freq = 0;
|
||||
let mut result = None;
|
||||
|
||||
for (i, &source_frequency) in source_frequencies.iter().enumerate() {
|
||||
let div = calculate_closest_divider(source_frequency, desired_frequency);
|
||||
if let Some(div) = div {
|
||||
let freq = calculate_output_frequency(source_frequency, &div);
|
||||
if result.is_none() || freq > result_freq {
|
||||
result = Some((i, div));
|
||||
result_freq = freq;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.expect("Desired frequency was too low for the dividers to divide to")
|
||||
}
|
||||
|
||||
fn calculate_output_frequency(source_frequency: usize, divider: &ClockDivider) -> usize {
|
||||
let n = match divider.div_num {
|
||||
0 => 256,
|
||||
1 => 2,
|
||||
_ => divider.div_num.min(256),
|
||||
};
|
||||
|
||||
if divider.div_b != 0 && divider.div_a != 0 {
|
||||
// OUTPUT = SOURCE / (N + B/A)
|
||||
// OUTPUT = SOURCE / ((NA + B)/A)
|
||||
// OUTPUT = (SOURCE * A) / (NA + B)
|
||||
|
||||
// u64 is required to fit the numbers from this arithmetic.
|
||||
|
||||
let source = source_frequency as u64;
|
||||
let n = n as u64;
|
||||
let a = divider.div_b as u64;
|
||||
let b = divider.div_a as u64;
|
||||
|
||||
((source * a) / (n * a + b)) as _
|
||||
} else {
|
||||
source_frequency / n
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_closest_divider(
|
||||
source_frequency: usize,
|
||||
desired_frequency: usize,
|
||||
) -> Option<ClockDivider> {
|
||||
let div_num = source_frequency / desired_frequency;
|
||||
if div_num < 2 {
|
||||
// Source clock isn't fast enough to reach the desired frequency.
|
||||
// Return max output.
|
||||
return Some(ClockDivider {
|
||||
div_num: 1,
|
||||
div_b: 0,
|
||||
div_a: 0,
|
||||
});
|
||||
}
|
||||
if div_num > 256 {
|
||||
// Source is too fast to divide to the desired frequency. Return None.
|
||||
return None;
|
||||
}
|
||||
|
||||
let div_num = if div_num == 256 { 0 } else { div_num };
|
||||
|
||||
let div_fraction = {
|
||||
let div_remainder = source_frequency % desired_frequency;
|
||||
let gcd = hcf(div_remainder, desired_frequency);
|
||||
Fraction {
|
||||
numerator: div_remainder / gcd,
|
||||
denominator: desired_frequency / gcd,
|
||||
}
|
||||
};
|
||||
|
||||
let divider = if div_fraction.numerator == 0 {
|
||||
ClockDivider {
|
||||
div_num,
|
||||
div_b: 0,
|
||||
div_a: 0,
|
||||
}
|
||||
} else {
|
||||
let target = div_fraction;
|
||||
let closest = farey_sequence(63)
|
||||
.find(|curr| {
|
||||
// https://en.wikipedia.org/wiki/Fraction#Adding_unlike_quantities
|
||||
|
||||
let new_curr_num = curr.numerator * target.denominator;
|
||||
let new_target_num = target.numerator * curr.denominator;
|
||||
new_curr_num >= new_target_num
|
||||
})
|
||||
.expect("The fraction must be between 0 and 1");
|
||||
|
||||
ClockDivider {
|
||||
div_num,
|
||||
div_b: closest.numerator,
|
||||
div_a: closest.denominator,
|
||||
}
|
||||
};
|
||||
Some(divider)
|
||||
}
|
||||
|
||||
// https://en.wikipedia.org/wiki/Euclidean_algorithm
|
||||
const fn hcf(a: usize, b: usize) -> usize {
|
||||
if b != 0 {
|
||||
hcf(b, a % b)
|
||||
} else {
|
||||
a
|
||||
}
|
||||
}
|
||||
|
||||
struct Fraction {
|
||||
pub numerator: usize,
|
||||
pub denominator: usize,
|
||||
}
|
||||
|
||||
// https://en.wikipedia.org/wiki/Farey_sequence#Next_term
|
||||
fn farey_sequence(denominator: usize) -> impl Iterator<Item = Fraction> {
|
||||
let mut a = 0;
|
||||
let mut b = 1;
|
||||
let mut c = 1;
|
||||
let mut d = denominator;
|
||||
core::iter::from_fn(move || {
|
||||
if a > denominator {
|
||||
return None;
|
||||
}
|
||||
let next = Fraction {
|
||||
numerator: a,
|
||||
denominator: b,
|
||||
};
|
||||
let k = (denominator + b) / d;
|
||||
(a, b, c, d) = (c, d, k * c - a, k * d - b);
|
||||
Some(next)
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user