Add LCD_CAM Camera driver (#1483)
* Add LCD_CAM Camera driver * comments * 16 bit * Expose most config * Module documentation * Remove GPIO generics * Fix breaking change * Remove Pin generics from Camera type * Fix unsafe * Another breaking change * fmt
This commit is contained in:
parent
157ac17be1
commit
d5d3f1f46b
@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
- ESP32-PICO-V3-02: Initial support (#1155)
|
||||
- `time::current_time` API (#1503)
|
||||
- ESP32-S3: Add LCD_CAM Camera driver (#1483)
|
||||
|
||||
### Fixed
|
||||
|
||||
|
||||
@ -1,5 +1,581 @@
|
||||
use crate::{peripheral::PeripheralRef, peripherals::LCD_CAM};
|
||||
//! # Camera - Master or Slave Mode
|
||||
//!
|
||||
//! ## Overview
|
||||
//! The LCD_CAM peripheral supports receiving 8/16 bit DVP signals in either
|
||||
//! master or slave mode. 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
|
||||
//! Following code shows how to receive some bytes from an 8 bit DVP stream in
|
||||
//! master mode.
|
||||
//!
|
||||
//! ```no_run
|
||||
//! let mclk_pin = io.pins.gpio15;
|
||||
//! let vsync_pin = io.pins.gpio6;
|
||||
//! let href_pin = io.pins.gpio7;
|
||||
//! let pclk_pin = io.pins.gpio13;
|
||||
//! let data_pins = RxEightBits::new(
|
||||
//! io.pins.gpio11,
|
||||
//! io.pins.gpio9,
|
||||
//! io.pins.gpio8,
|
||||
//! io.pins.gpio10,
|
||||
//! io.pins.gpio12,
|
||||
//! io.pins.gpio18,
|
||||
//! io.pins.gpio17,
|
||||
//! io.pins.gpio16,
|
||||
//! );
|
||||
//!
|
||||
//! let lcd_cam = LcdCam::new(peripherals.LCD_CAM);
|
||||
//! let mut camera = Camera::new(lcd_cam.cam, channel.rx, data_pins, 20u32.MHz(), &clocks)
|
||||
//! .with_master_clock(mclk_pin) // Remove this for slave mode.
|
||||
//! .with_ctrl_pins(vsync_pin, href_pin, pclk_pin);
|
||||
//! ```
|
||||
|
||||
use core::mem::size_of;
|
||||
|
||||
use embedded_dma::WriteBuffer;
|
||||
use fugit::HertzU32;
|
||||
|
||||
use crate::{
|
||||
clock::Clocks,
|
||||
dma::{
|
||||
ChannelRx,
|
||||
ChannelTypes,
|
||||
DmaError,
|
||||
DmaPeripheral,
|
||||
DmaTransfer,
|
||||
LcdCamPeripheral,
|
||||
RegisterAccess,
|
||||
Rx,
|
||||
RxChannel,
|
||||
RxPrivate,
|
||||
},
|
||||
gpio::{InputPin, InputSignal, OutputPin, OutputSignal},
|
||||
i2s::Error,
|
||||
lcd_cam::{cam::private::RxPins, private::calculate_clkm, BitOrder, ByteOrder},
|
||||
peripheral::{Peripheral, PeripheralRef},
|
||||
peripherals::LCD_CAM,
|
||||
};
|
||||
|
||||
/// 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 {
|
||||
One,
|
||||
Two,
|
||||
Three,
|
||||
Four,
|
||||
Five,
|
||||
Six,
|
||||
Seven,
|
||||
Eight,
|
||||
}
|
||||
|
||||
pub struct Cam<'d> {
|
||||
pub(crate) _lcd_cam: PeripheralRef<'d, LCD_CAM>,
|
||||
pub(crate) lcd_cam: PeripheralRef<'d, LCD_CAM>,
|
||||
}
|
||||
|
||||
pub struct Camera<'d, RX> {
|
||||
lcd_cam: PeripheralRef<'d, LCD_CAM>,
|
||||
rx_channel: RX,
|
||||
// 1 or 2
|
||||
bus_width: usize,
|
||||
}
|
||||
|
||||
impl<'d, T, R> Camera<'d, ChannelRx<'d, T, R>>
|
||||
where
|
||||
T: RxChannel<R>,
|
||||
R: ChannelTypes + RegisterAccess,
|
||||
R::P: LcdCamPeripheral,
|
||||
{
|
||||
pub fn new<P: RxPins>(
|
||||
cam: Cam<'d>,
|
||||
mut channel: ChannelRx<'d, T, R>,
|
||||
_pins: P,
|
||||
frequency: HertzU32,
|
||||
clocks: &Clocks,
|
||||
) -> Self {
|
||||
let lcd_cam = cam.lcd_cam;
|
||||
|
||||
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 _)
|
||||
.cam_clkm_div_num()
|
||||
.bits(divider.div_num as _)
|
||||
.cam_clkm_div_b()
|
||||
.bits(divider.div_b as _)
|
||||
.cam_clkm_div_a()
|
||||
.bits(divider.div_a as _)
|
||||
.cam_vsync_filter_thres()
|
||||
.bits(0)
|
||||
.cam_vs_eof_en()
|
||||
.set_bit()
|
||||
.cam_line_int_en()
|
||||
.clear_bit()
|
||||
.cam_stop_en()
|
||||
.clear_bit()
|
||||
}
|
||||
});
|
||||
lcd_cam.cam_ctrl1().write(|w| unsafe {
|
||||
w.cam_vh_de_mode_en()
|
||||
.set_bit()
|
||||
.cam_rec_data_bytelen()
|
||||
.bits(0)
|
||||
.cam_line_int_num()
|
||||
.bits(0)
|
||||
.cam_vsync_filter_en()
|
||||
.clear_bit()
|
||||
.cam_2byte_en()
|
||||
.clear_bit()
|
||||
.cam_clk_inv()
|
||||
.clear_bit()
|
||||
.cam_de_inv()
|
||||
.clear_bit()
|
||||
.cam_hsync_inv()
|
||||
.clear_bit()
|
||||
.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());
|
||||
|
||||
channel.init_channel();
|
||||
|
||||
Self {
|
||||
lcd_cam,
|
||||
rx_channel: channel,
|
||||
bus_width: P::BUS_WIDTH,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'d, RX: Rx> Camera<'d, RX> {
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
pub fn with_master_clock<MCLK: OutputPin>(self, mclk: impl Peripheral<P = MCLK> + 'd) -> Self {
|
||||
crate::into_ref!(mclk);
|
||||
mclk.set_to_push_pull_output()
|
||||
.connect_peripheral_to_output(OutputSignal::CAM_CLK);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn with_ctrl_pins<VSYNC: InputPin, HENABLE: InputPin, PCLK: InputPin>(
|
||||
self,
|
||||
vsync: impl Peripheral<P = VSYNC> + 'd,
|
||||
h_enable: impl Peripheral<P = HENABLE> + 'd,
|
||||
pclk: impl Peripheral<P = PCLK> + 'd,
|
||||
) -> Self {
|
||||
crate::into_ref!(vsync);
|
||||
crate::into_ref!(h_enable);
|
||||
crate::into_ref!(pclk);
|
||||
|
||||
vsync
|
||||
.set_to_input()
|
||||
.connect_input_to_peripheral(InputSignal::CAM_V_SYNC);
|
||||
h_enable
|
||||
.set_to_input()
|
||||
.connect_input_to_peripheral(InputSignal::CAM_H_ENABLE);
|
||||
pclk.set_to_input()
|
||||
.connect_input_to_peripheral(InputSignal::CAM_PCLK);
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
// Reset Camera control unit and Async Rx FIFO
|
||||
fn reset_unit_and_fifo(&self) {
|
||||
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 the Camera unit to listen for incoming DVP stream.
|
||||
fn start_unit(&self) {
|
||||
self.lcd_cam
|
||||
.cam_ctrl()
|
||||
.modify(|_, w| w.cam_update().set_bit());
|
||||
self.lcd_cam
|
||||
.cam_ctrl1()
|
||||
.modify(|_, w| w.cam_start().set_bit());
|
||||
}
|
||||
|
||||
fn start_dma<RXBUF: WriteBuffer>(
|
||||
&mut self,
|
||||
circular: bool,
|
||||
buf: &mut RXBUF,
|
||||
) -> Result<(), DmaError> {
|
||||
let (ptr, len) = unsafe { buf.write_buffer() };
|
||||
|
||||
assert_eq!(self.bus_width, size_of::<RXBUF::Word>());
|
||||
|
||||
unsafe {
|
||||
self.rx_channel.prepare_transfer_without_start(
|
||||
circular,
|
||||
DmaPeripheral::LcdCam,
|
||||
ptr as _,
|
||||
len * size_of::<RXBUF::Word>(),
|
||||
)?;
|
||||
}
|
||||
self.rx_channel.start_transfer()
|
||||
}
|
||||
|
||||
pub fn read_dma<'t, RXBUF: WriteBuffer>(
|
||||
&'t mut self,
|
||||
buf: &'t mut RXBUF,
|
||||
) -> Result<Transfer<'t, 'd, RX>, DmaError> {
|
||||
self.reset_unit_and_fifo();
|
||||
// Start DMA to receive incoming transfer.
|
||||
self.start_dma(false, buf)?;
|
||||
self.start_unit();
|
||||
|
||||
Ok(Transfer { instance: self })
|
||||
}
|
||||
|
||||
pub fn read_dma_circular<'t, RXBUF: WriteBuffer>(
|
||||
&'t mut self,
|
||||
buf: &'t mut RXBUF,
|
||||
) -> Result<Transfer<'t, 'd, RX>, DmaError> {
|
||||
self.reset_unit_and_fifo();
|
||||
// Start DMA to receive incoming transfer.
|
||||
self.start_dma(true, buf)?;
|
||||
self.start_unit();
|
||||
|
||||
Ok(Transfer { instance: self })
|
||||
}
|
||||
}
|
||||
|
||||
/// An in-progress transfer
|
||||
#[must_use]
|
||||
pub struct Transfer<'t, 'd, RX: Rx> {
|
||||
instance: &'t mut Camera<'d, RX>,
|
||||
}
|
||||
|
||||
impl<'t, 'd, RX: Rx> Transfer<'t, 'd, RX> {
|
||||
/// Amount of bytes which can be popped
|
||||
pub fn available(&mut self) -> usize {
|
||||
self.instance.rx_channel.available()
|
||||
}
|
||||
|
||||
pub fn pop(&mut self, data: &mut [u8]) -> Result<usize, Error> {
|
||||
Ok(self.instance.rx_channel.pop(data)?)
|
||||
}
|
||||
|
||||
/// Wait for the DMA transfer to complete.
|
||||
/// Length of the received data is returned
|
||||
#[allow(clippy::type_complexity)]
|
||||
pub fn wait_receive(self, dst: &mut [u8]) -> Result<usize, (DmaError, usize)> {
|
||||
// Wait for DMA transfer to finish.
|
||||
while !self.is_done() {}
|
||||
|
||||
let len = self
|
||||
.instance
|
||||
.rx_channel
|
||||
.drain_buffer(dst)
|
||||
.map_err(|e| (e, 0))?;
|
||||
|
||||
if self.instance.rx_channel.has_error() {
|
||||
Err((DmaError::DescriptorError, len))
|
||||
} else {
|
||||
Ok(len)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'t, 'd, RX: Rx> DmaTransfer for Transfer<'t, 'd, RX> {
|
||||
fn wait(self) -> Result<(), DmaError> {
|
||||
// Wait for DMA transfer to finish.
|
||||
while !self.is_done() {}
|
||||
|
||||
let ch = &self.instance.rx_channel;
|
||||
if ch.has_error() {
|
||||
Err(DmaError::DescriptorError)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn is_done(&self) -> bool {
|
||||
let ch = &self.instance.rx_channel;
|
||||
// Wait for IN_SUC_EOF (i.e. VSYNC)
|
||||
ch.is_done() ||
|
||||
// Or for IN_DSCR_EMPTY (i.e. No more buffer space)
|
||||
ch.has_dscr_empty_error() ||
|
||||
// Or for IN_DSCR_ERR (i.e. bad descriptor)
|
||||
ch.has_error()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'t, 'd, RX: Rx> Drop for Transfer<'t, 'd, RX> {
|
||||
fn drop(&mut self) {
|
||||
self.instance
|
||||
.lcd_cam
|
||||
.cam_ctrl1()
|
||||
.modify(|_, w| w.cam_start().clear_bit());
|
||||
// TODO: Stop DMA?? self.instance.rx_channel.stop_transfer();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct RxEightBits {
|
||||
_pins: (),
|
||||
}
|
||||
|
||||
impl RxEightBits {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new<'d, P0, P1, P2, P3, P4, P5, P6, P7>(
|
||||
pin_0: impl Peripheral<P = P0> + 'd,
|
||||
pin_1: impl Peripheral<P = P1> + 'd,
|
||||
pin_2: impl Peripheral<P = P2> + 'd,
|
||||
pin_3: impl Peripheral<P = P3> + 'd,
|
||||
pin_4: impl Peripheral<P = P4> + 'd,
|
||||
pin_5: impl Peripheral<P = P5> + 'd,
|
||||
pin_6: impl Peripheral<P = P6> + 'd,
|
||||
pin_7: impl Peripheral<P = P7> + 'd,
|
||||
) -> Self
|
||||
where
|
||||
P0: InputPin,
|
||||
P1: InputPin,
|
||||
P2: InputPin,
|
||||
P3: InputPin,
|
||||
P4: InputPin,
|
||||
P5: InputPin,
|
||||
P6: InputPin,
|
||||
P7: InputPin,
|
||||
{
|
||||
crate::into_ref!(pin_0);
|
||||
crate::into_ref!(pin_1);
|
||||
crate::into_ref!(pin_2);
|
||||
crate::into_ref!(pin_3);
|
||||
crate::into_ref!(pin_4);
|
||||
crate::into_ref!(pin_5);
|
||||
crate::into_ref!(pin_6);
|
||||
crate::into_ref!(pin_7);
|
||||
|
||||
pin_0
|
||||
.set_to_input()
|
||||
.connect_input_to_peripheral(InputSignal::CAM_DATA_0);
|
||||
pin_1
|
||||
.set_to_input()
|
||||
.connect_input_to_peripheral(InputSignal::CAM_DATA_1);
|
||||
pin_2
|
||||
.set_to_input()
|
||||
.connect_input_to_peripheral(InputSignal::CAM_DATA_2);
|
||||
pin_3
|
||||
.set_to_input()
|
||||
.connect_input_to_peripheral(InputSignal::CAM_DATA_3);
|
||||
pin_4
|
||||
.set_to_input()
|
||||
.connect_input_to_peripheral(InputSignal::CAM_DATA_4);
|
||||
pin_5
|
||||
.set_to_input()
|
||||
.connect_input_to_peripheral(InputSignal::CAM_DATA_5);
|
||||
pin_6
|
||||
.set_to_input()
|
||||
.connect_input_to_peripheral(InputSignal::CAM_DATA_6);
|
||||
pin_7
|
||||
.set_to_input()
|
||||
.connect_input_to_peripheral(InputSignal::CAM_DATA_7);
|
||||
|
||||
Self { _pins: () }
|
||||
}
|
||||
}
|
||||
|
||||
impl RxPins for RxEightBits {
|
||||
const BUS_WIDTH: usize = 1;
|
||||
}
|
||||
|
||||
pub struct RxSixteenBits {
|
||||
_pins: (),
|
||||
}
|
||||
|
||||
impl RxSixteenBits {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new<'d, P0, P1, P2, P3, P4, P5, P6, P7, P8, P9, P10, P11, P12, P13, P14, P15>(
|
||||
pin_0: impl Peripheral<P = P0> + 'd,
|
||||
pin_1: impl Peripheral<P = P1> + 'd,
|
||||
pin_2: impl Peripheral<P = P2> + 'd,
|
||||
pin_3: impl Peripheral<P = P3> + 'd,
|
||||
pin_4: impl Peripheral<P = P4> + 'd,
|
||||
pin_5: impl Peripheral<P = P5> + 'd,
|
||||
pin_6: impl Peripheral<P = P6> + 'd,
|
||||
pin_7: impl Peripheral<P = P7> + 'd,
|
||||
pin_8: impl Peripheral<P = P8> + 'd,
|
||||
pin_9: impl Peripheral<P = P9> + 'd,
|
||||
pin_10: impl Peripheral<P = P10> + 'd,
|
||||
pin_11: impl Peripheral<P = P11> + 'd,
|
||||
pin_12: impl Peripheral<P = P12> + 'd,
|
||||
pin_13: impl Peripheral<P = P13> + 'd,
|
||||
pin_14: impl Peripheral<P = P14> + 'd,
|
||||
pin_15: impl Peripheral<P = P15> + 'd,
|
||||
) -> Self
|
||||
where
|
||||
P0: InputPin,
|
||||
P1: InputPin,
|
||||
P2: InputPin,
|
||||
P3: InputPin,
|
||||
P4: InputPin,
|
||||
P5: InputPin,
|
||||
P6: InputPin,
|
||||
P7: InputPin,
|
||||
P8: InputPin,
|
||||
P9: InputPin,
|
||||
P10: InputPin,
|
||||
P11: InputPin,
|
||||
P12: InputPin,
|
||||
P13: InputPin,
|
||||
P14: InputPin,
|
||||
P15: InputPin,
|
||||
{
|
||||
crate::into_ref!(pin_0);
|
||||
crate::into_ref!(pin_1);
|
||||
crate::into_ref!(pin_2);
|
||||
crate::into_ref!(pin_3);
|
||||
crate::into_ref!(pin_4);
|
||||
crate::into_ref!(pin_5);
|
||||
crate::into_ref!(pin_6);
|
||||
crate::into_ref!(pin_7);
|
||||
crate::into_ref!(pin_8);
|
||||
crate::into_ref!(pin_9);
|
||||
crate::into_ref!(pin_10);
|
||||
crate::into_ref!(pin_11);
|
||||
crate::into_ref!(pin_12);
|
||||
crate::into_ref!(pin_13);
|
||||
crate::into_ref!(pin_14);
|
||||
crate::into_ref!(pin_15);
|
||||
|
||||
pin_0
|
||||
.set_to_input()
|
||||
.connect_input_to_peripheral(InputSignal::CAM_DATA_0);
|
||||
pin_1
|
||||
.set_to_input()
|
||||
.connect_input_to_peripheral(InputSignal::CAM_DATA_1);
|
||||
pin_2
|
||||
.set_to_input()
|
||||
.connect_input_to_peripheral(InputSignal::CAM_DATA_2);
|
||||
pin_3
|
||||
.set_to_input()
|
||||
.connect_input_to_peripheral(InputSignal::CAM_DATA_3);
|
||||
pin_4
|
||||
.set_to_input()
|
||||
.connect_input_to_peripheral(InputSignal::CAM_DATA_4);
|
||||
pin_5
|
||||
.set_to_input()
|
||||
.connect_input_to_peripheral(InputSignal::CAM_DATA_5);
|
||||
pin_6
|
||||
.set_to_input()
|
||||
.connect_input_to_peripheral(InputSignal::CAM_DATA_6);
|
||||
pin_7
|
||||
.set_to_input()
|
||||
.connect_input_to_peripheral(InputSignal::CAM_DATA_7);
|
||||
pin_8
|
||||
.set_to_input()
|
||||
.connect_input_to_peripheral(InputSignal::CAM_DATA_8);
|
||||
pin_9
|
||||
.set_to_input()
|
||||
.connect_input_to_peripheral(InputSignal::CAM_DATA_9);
|
||||
pin_10
|
||||
.set_to_input()
|
||||
.connect_input_to_peripheral(InputSignal::CAM_DATA_10);
|
||||
pin_11
|
||||
.set_to_input()
|
||||
.connect_input_to_peripheral(InputSignal::CAM_DATA_11);
|
||||
pin_12
|
||||
.set_to_input()
|
||||
.connect_input_to_peripheral(InputSignal::CAM_DATA_12);
|
||||
pin_13
|
||||
.set_to_input()
|
||||
.connect_input_to_peripheral(InputSignal::CAM_DATA_13);
|
||||
pin_14
|
||||
.set_to_input()
|
||||
.connect_input_to_peripheral(InputSignal::CAM_DATA_14);
|
||||
pin_15
|
||||
.set_to_input()
|
||||
.connect_input_to_peripheral(InputSignal::CAM_DATA_15);
|
||||
|
||||
Self { _pins: () }
|
||||
}
|
||||
}
|
||||
|
||||
impl RxPins for RxSixteenBits {
|
||||
const BUS_WIDTH: usize = 2;
|
||||
}
|
||||
|
||||
mod private {
|
||||
pub trait RxPins {
|
||||
const BUS_WIDTH: usize;
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ impl<'d> LcdCam<'d> {
|
||||
lcd_cam: unsafe { lcd_cam.clone_unchecked() },
|
||||
},
|
||||
cam: Cam {
|
||||
_lcd_cam: unsafe { lcd_cam.clone_unchecked() },
|
||||
lcd_cam: unsafe { lcd_cam.clone_unchecked() },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
423
examples/src/bin/lcd_cam_ov2640.rs
Normal file
423
examples/src/bin/lcd_cam_ov2640.rs
Normal file
@ -0,0 +1,423 @@
|
||||
//! Drives the camera on a Freenove ESP32-S3 WROOM (also works as is on the ESP32S3-EYE)
|
||||
//!
|
||||
//! This example reads a JPEG from an OV2640 and writes it to the console as hex.
|
||||
//!
|
||||
//! Pins used:
|
||||
//! SIOD GPIO4
|
||||
//! SIOC GPIO5
|
||||
//! XCLK GPIO15
|
||||
//! VSYNC GPIO6
|
||||
//! HREF GPIO7
|
||||
//! PCLK GPIO13
|
||||
//! D2 GPIO11
|
||||
//! D3 GPIO9
|
||||
//! D4 GPIO8
|
||||
//! D5 GPIO10
|
||||
//! D6 GPIO12
|
||||
//! D7 GPIO18
|
||||
//! D8 GPIO17
|
||||
//! D9 GPIO16
|
||||
|
||||
//% CHIPS: esp32s3
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use esp_backtrace as _;
|
||||
use esp_hal::{
|
||||
clock::ClockControl,
|
||||
delay::Delay,
|
||||
dma::{Dma, DmaPriority},
|
||||
dma_buffers,
|
||||
gpio::Io,
|
||||
i2c,
|
||||
i2c::I2C,
|
||||
lcd_cam::{
|
||||
cam::{Camera, RxEightBits},
|
||||
LcdCam,
|
||||
},
|
||||
peripherals::Peripherals,
|
||||
prelude::*,
|
||||
system::SystemControl,
|
||||
Blocking,
|
||||
};
|
||||
use esp_println::println;
|
||||
|
||||
#[entry]
|
||||
fn main() -> ! {
|
||||
let peripherals = Peripherals::take();
|
||||
let system = SystemControl::new(peripherals.SYSTEM);
|
||||
let clocks = ClockControl::boot_defaults(system.clock_control).freeze();
|
||||
|
||||
let io = Io::new(peripherals.GPIO, peripherals.IO_MUX);
|
||||
|
||||
let dma = Dma::new(peripherals.DMA);
|
||||
let channel = dma.channel0;
|
||||
|
||||
let (_, mut tx_descriptors, rx_buffer, mut rx_descriptors) = dma_buffers!(0, 32678);
|
||||
|
||||
let channel = channel.configure(
|
||||
false,
|
||||
&mut tx_descriptors,
|
||||
&mut rx_descriptors,
|
||||
DmaPriority::Priority0,
|
||||
);
|
||||
|
||||
let cam_siod = io.pins.gpio4;
|
||||
let cam_sioc = io.pins.gpio5;
|
||||
let cam_xclk = io.pins.gpio15;
|
||||
let cam_vsync = io.pins.gpio6;
|
||||
let cam_href = io.pins.gpio7;
|
||||
let cam_pclk = io.pins.gpio13;
|
||||
let cam_data_pins = RxEightBits::new(
|
||||
io.pins.gpio11,
|
||||
io.pins.gpio9,
|
||||
io.pins.gpio8,
|
||||
io.pins.gpio10,
|
||||
io.pins.gpio12,
|
||||
io.pins.gpio18,
|
||||
io.pins.gpio17,
|
||||
io.pins.gpio16,
|
||||
);
|
||||
|
||||
let lcd_cam = LcdCam::new(peripherals.LCD_CAM);
|
||||
let mut camera = Camera::new(lcd_cam.cam, channel.rx, cam_data_pins, 20u32.MHz(), &clocks)
|
||||
.with_master_clock(cam_xclk)
|
||||
.with_ctrl_pins(cam_vsync, cam_href, cam_pclk);
|
||||
|
||||
let delay = Delay::new(&clocks);
|
||||
|
||||
let mut buffer = rx_buffer;
|
||||
buffer.fill(0u8);
|
||||
|
||||
delay.delay_millis(500u32);
|
||||
|
||||
let i2c = I2C::new(
|
||||
peripherals.I2C0,
|
||||
cam_siod,
|
||||
cam_sioc,
|
||||
100u32.kHz(),
|
||||
&clocks,
|
||||
None,
|
||||
);
|
||||
|
||||
let mut sccb = Sccb::new(i2c);
|
||||
|
||||
// Checking camera address
|
||||
sccb.probe(OV2640_ADDRESS).unwrap();
|
||||
println!("Probe successful!");
|
||||
|
||||
sccb.write(OV2640_ADDRESS, 0xFF, 0x01).unwrap(); // bank sensor
|
||||
let pid = sccb.read(OV2640_ADDRESS, 0x0A).unwrap();
|
||||
println!("Found PID of {:#02X}, and was expecting 0x26", pid);
|
||||
|
||||
// Start waiting for camera before initialising it to prevent missing the first few bytes.
|
||||
// This can be improved with a VSYNC interrupt but would complicate this example.
|
||||
let transfer = camera.read_dma(&mut buffer).unwrap();
|
||||
|
||||
for (reg, value) in FIRST_BLOCK {
|
||||
sccb.write(OV2640_ADDRESS, *reg, *value).unwrap();
|
||||
}
|
||||
delay.delay_millis(10u32);
|
||||
for (reg, value) in SECOND_BLOCK {
|
||||
sccb.write(OV2640_ADDRESS, *reg, *value).unwrap();
|
||||
if *reg == 0xDD && *value == 0x7F {
|
||||
delay.delay_millis(10u32);
|
||||
}
|
||||
}
|
||||
|
||||
transfer.wait().unwrap();
|
||||
|
||||
// Note: JPEGs starts with "FF, D8, FF, E0" and end with "FF, D9"
|
||||
|
||||
let index_of_end = buffer.windows(2).position(|c| c[0] == 0xFF && c[1] == 0xD9);
|
||||
let index_of_end = if let Some(idx) = index_of_end {
|
||||
idx + 2
|
||||
} else {
|
||||
println!("Failed to find JPEG terminator");
|
||||
buffer.len()
|
||||
};
|
||||
|
||||
println!("Frame data (parse with `xxd -r -p <uart>.txt image.jpg`):");
|
||||
println!("{:02X?}", &buffer[..index_of_end]);
|
||||
|
||||
loop {}
|
||||
}
|
||||
|
||||
pub const OV2640_ADDRESS: u8 = 0x30;
|
||||
|
||||
pub struct Sccb<'d, T> {
|
||||
i2c: I2C<'d, T, Blocking>,
|
||||
}
|
||||
|
||||
impl<'d, T> Sccb<'d, T>
|
||||
where
|
||||
T: i2c::Instance,
|
||||
{
|
||||
pub fn new(i2c: I2C<'d, T, Blocking>) -> Self {
|
||||
Self { i2c }
|
||||
}
|
||||
|
||||
pub fn probe(&mut self, address: u8) -> Result<(), i2c::Error> {
|
||||
self.i2c.write(address, &[])
|
||||
}
|
||||
|
||||
pub fn read(&mut self, address: u8, reg: u8) -> Result<u8, i2c::Error> {
|
||||
self.i2c.write(address, &[reg])?;
|
||||
|
||||
let mut bytes = [0u8; 1];
|
||||
self.i2c.read(address, &mut bytes)?;
|
||||
Ok(bytes[0])
|
||||
}
|
||||
|
||||
pub fn write(&mut self, address: u8, reg: u8, data: u8) -> Result<(), i2c::Error> {
|
||||
self.i2c.write(address, &[reg, data])
|
||||
}
|
||||
}
|
||||
|
||||
const FIRST_BLOCK: &[(u8, u8)] = &[(0xFF, 0x01), (0x12, 0x80)];
|
||||
|
||||
const SECOND_BLOCK: &[(u8, u8)] = &[
|
||||
(0xFF, 0x00),
|
||||
(0x2C, 0xFF),
|
||||
(0x2E, 0xDF),
|
||||
(0xFF, 0x01),
|
||||
(0x3C, 0x32),
|
||||
(0x11, 0x01),
|
||||
(0x09, 0x02),
|
||||
(0x04, 0x28),
|
||||
(0x13, 0xE5),
|
||||
(0x14, 0x48),
|
||||
(0x2C, 0x0C),
|
||||
(0x33, 0x78),
|
||||
(0x3A, 0x33),
|
||||
(0x3B, 0xFB),
|
||||
(0x3E, 0x00),
|
||||
(0x43, 0x11),
|
||||
(0x16, 0x10),
|
||||
(0x39, 0x92),
|
||||
(0x35, 0xDA),
|
||||
(0x22, 0x1A),
|
||||
(0x37, 0xC3),
|
||||
(0x23, 0x00),
|
||||
(0x34, 0xC0),
|
||||
(0x06, 0x88),
|
||||
(0x07, 0xC0),
|
||||
(0x0D, 0x87),
|
||||
(0x0E, 0x41),
|
||||
(0x4C, 0x00),
|
||||
(0x4A, 0x81),
|
||||
(0x21, 0x99),
|
||||
(0x24, 0x40),
|
||||
(0x25, 0x38),
|
||||
(0x26, 0x82),
|
||||
(0x5C, 0x00),
|
||||
(0x63, 0x00),
|
||||
(0x61, 0x70),
|
||||
(0x62, 0x80),
|
||||
(0x7C, 0x05),
|
||||
(0x20, 0x80),
|
||||
(0x28, 0x30),
|
||||
(0x6C, 0x00),
|
||||
(0x6D, 0x80),
|
||||
(0x6E, 0x00),
|
||||
(0x70, 0x02),
|
||||
(0x71, 0x94),
|
||||
(0x73, 0xC1),
|
||||
(0x3D, 0x34),
|
||||
(0x5A, 0x57),
|
||||
(0x4F, 0xBB),
|
||||
(0x50, 0x9C),
|
||||
(0x12, 0x20),
|
||||
(0x17, 0x11),
|
||||
(0x18, 0x43),
|
||||
(0x19, 0x00),
|
||||
(0x1A, 0x25),
|
||||
(0x32, 0x89),
|
||||
(0x37, 0xC0),
|
||||
(0x4F, 0xCA),
|
||||
(0x50, 0xA8),
|
||||
(0x6D, 0x00),
|
||||
(0x3D, 0x38),
|
||||
(0xFF, 0x00),
|
||||
(0xE5, 0x7F),
|
||||
(0xF9, 0xC0),
|
||||
(0x41, 0x24),
|
||||
(0xE0, 0x14),
|
||||
(0x76, 0xFF),
|
||||
(0x33, 0xA0),
|
||||
(0x42, 0x20),
|
||||
(0x43, 0x18),
|
||||
(0x4C, 0x00),
|
||||
(0x87, 0x50),
|
||||
(0x88, 0x3F),
|
||||
(0xD7, 0x03),
|
||||
(0xD9, 0x10),
|
||||
(0xD3, 0x82),
|
||||
(0xC8, 0x08),
|
||||
(0xC9, 0x80),
|
||||
(0x7C, 0x00),
|
||||
(0x7D, 0x00),
|
||||
(0x7C, 0x03),
|
||||
(0x7D, 0x48),
|
||||
(0x7D, 0x48),
|
||||
(0x7C, 0x08),
|
||||
(0x7D, 0x20),
|
||||
(0x7D, 0x10),
|
||||
(0x7D, 0x0E),
|
||||
(0x90, 0x00),
|
||||
(0x91, 0x0E),
|
||||
(0x91, 0x1A),
|
||||
(0x91, 0x31),
|
||||
(0x91, 0x5A),
|
||||
(0x91, 0x69),
|
||||
(0x91, 0x75),
|
||||
(0x91, 0x7E),
|
||||
(0x91, 0x88),
|
||||
(0x91, 0x8F),
|
||||
(0x91, 0x96),
|
||||
(0x91, 0xA3),
|
||||
(0x91, 0xAF),
|
||||
(0x91, 0xC4),
|
||||
(0x91, 0xD7),
|
||||
(0x91, 0xE8),
|
||||
(0x91, 0x20),
|
||||
(0x92, 0x00),
|
||||
(0x93, 0x06),
|
||||
(0x93, 0xE3),
|
||||
(0x93, 0x05),
|
||||
(0x93, 0x05),
|
||||
(0x93, 0x00),
|
||||
(0x93, 0x04),
|
||||
(0x93, 0x00),
|
||||
(0x93, 0x00),
|
||||
(0x93, 0x00),
|
||||
(0x93, 0x00),
|
||||
(0x93, 0x00),
|
||||
(0x93, 0x00),
|
||||
(0x93, 0x00),
|
||||
(0x96, 0x00),
|
||||
(0x97, 0x08),
|
||||
(0x97, 0x19),
|
||||
(0x97, 0x02),
|
||||
(0x97, 0x0C),
|
||||
(0x97, 0x24),
|
||||
(0x97, 0x30),
|
||||
(0x97, 0x28),
|
||||
(0x97, 0x26),
|
||||
(0x97, 0x02),
|
||||
(0x97, 0x98),
|
||||
(0x97, 0x80),
|
||||
(0x97, 0x00),
|
||||
(0x97, 0x00),
|
||||
(0xA4, 0x00),
|
||||
(0xA8, 0x00),
|
||||
(0xC5, 0x11),
|
||||
(0xC6, 0x51),
|
||||
(0xBF, 0x80),
|
||||
(0xC7, 0x10),
|
||||
(0xB6, 0x66),
|
||||
(0xB8, 0xA5),
|
||||
(0xB7, 0x64),
|
||||
(0xB9, 0x7C),
|
||||
(0xB3, 0xAF),
|
||||
(0xB4, 0x97),
|
||||
(0xB5, 0xFF),
|
||||
(0xB0, 0xC5),
|
||||
(0xB1, 0x94),
|
||||
(0xB2, 0x0F),
|
||||
(0xC4, 0x5C),
|
||||
(0xC3, 0xFD),
|
||||
(0x7F, 0x00),
|
||||
(0xE5, 0x1F),
|
||||
(0xE1, 0x67),
|
||||
(0xDD, 0x7F),
|
||||
(0xDA, 0x00),
|
||||
(0xE0, 0x00),
|
||||
(0x05, 0x00),
|
||||
(0x05, 0x01),
|
||||
(0xFF, 0x01),
|
||||
(0x12, 0x40),
|
||||
(0x03, 0x0A),
|
||||
(0x32, 0x09),
|
||||
(0x17, 0x11),
|
||||
(0x18, 0x43),
|
||||
(0x19, 0x00),
|
||||
(0x1A, 0x4B),
|
||||
(0x37, 0xC0),
|
||||
(0x4F, 0xCA),
|
||||
(0x50, 0xA8),
|
||||
(0x5A, 0x23),
|
||||
(0x6D, 0x00),
|
||||
(0x3D, 0x38),
|
||||
(0x39, 0x92),
|
||||
(0x35, 0xDA),
|
||||
(0x22, 0x1A),
|
||||
(0x37, 0xC3),
|
||||
(0x23, 0x00),
|
||||
(0x34, 0xC0),
|
||||
(0x06, 0x88),
|
||||
(0x07, 0xC0),
|
||||
(0x0D, 0x87),
|
||||
(0x0E, 0x41),
|
||||
(0x42, 0x03),
|
||||
(0x4C, 0x00),
|
||||
(0xFF, 0x00),
|
||||
(0xE0, 0x04),
|
||||
(0xC0, 0x64),
|
||||
(0xC1, 0x4B),
|
||||
(0x8C, 0x00),
|
||||
(0x51, 0xC8),
|
||||
(0x52, 0x96),
|
||||
(0x53, 0x00),
|
||||
(0x54, 0x00),
|
||||
(0x55, 0x00),
|
||||
(0x57, 0x00),
|
||||
(0x86, 0x3D),
|
||||
(0x50, 0x80),
|
||||
(0x51, 0xC8),
|
||||
(0x52, 0x96),
|
||||
(0x53, 0x00),
|
||||
(0x54, 0x00),
|
||||
(0x55, 0x00),
|
||||
(0x57, 0x00),
|
||||
(0x5A, 0xA0),
|
||||
(0x5B, 0x78),
|
||||
(0x5C, 0x00),
|
||||
(0xFF, 0x01),
|
||||
(0x11, 0x00),
|
||||
(0xFF, 0x00),
|
||||
(0xD3, 0x10),
|
||||
(0x05, 0x00),
|
||||
(0xE0, 0x14),
|
||||
(0xDA, 0x12),
|
||||
(0xD7, 0x03),
|
||||
(0xE1, 0x77),
|
||||
(0xE5, 0x1F),
|
||||
(0xD9, 0x10),
|
||||
(0xDF, 0x80),
|
||||
(0x33, 0x80),
|
||||
(0x3C, 0x10),
|
||||
(0xEB, 0x30),
|
||||
(0xDD, 0x7F),
|
||||
(0xE0, 0x00),
|
||||
(0xE0, 0x14),
|
||||
(0xDA, 0x12),
|
||||
(0xD7, 0x03),
|
||||
(0xE1, 0x77),
|
||||
(0xE5, 0x1F),
|
||||
(0xD9, 0x10),
|
||||
(0xDF, 0x80),
|
||||
(0x33, 0x80),
|
||||
(0x3C, 0x10),
|
||||
(0xEB, 0x30),
|
||||
(0xDD, 0x7F),
|
||||
(0xE0, 0x00),
|
||||
(0xFF, 0x01),
|
||||
(0x14, 0x08),
|
||||
(0xFF, 0x00),
|
||||
(0x87, 0x50),
|
||||
(0x87, 0x10),
|
||||
(0xC3, 0xFD),
|
||||
(0x44, 0x0C),
|
||||
];
|
||||
Loading…
Reference in New Issue
Block a user