From aa003575ca0afdbfe4a5dd61c26ae51ee7825a56 Mon Sep 17 00:00:00 2001 From: Ralph Ursprung Date: Sat, 1 Mar 2025 18:33:02 +0100 Subject: [PATCH 1/2] move mod `i2s::master` to own folder --- esp-hal/src/i2s/{master.rs => master/mod.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename esp-hal/src/i2s/{master.rs => master/mod.rs} (100%) diff --git a/esp-hal/src/i2s/master.rs b/esp-hal/src/i2s/master/mod.rs similarity index 100% rename from esp-hal/src/i2s/master.rs rename to esp-hal/src/i2s/master/mod.rs From a988b9cf7e45ba1291c9e4483b211bd38cda677a Mon Sep 17 00:00:00 2001 From: dominaezzz Date: Wed, 5 Mar 2025 17:38:11 +0100 Subject: [PATCH 2/2] add I2S camera driver this implements the I2S camera functionality. it has been tested using an AI Thinker ESP32-CAM board with an OV2640 camera as well as a Makerfabs ESP32 3.2" TFT LCD with Camera with the same camera model. the qa-test is based on the latter board. this connection can be found on ESP32 boards. note that this is independent of `lcd_cam::cam` module which is a different kind of camera. `DataFormat::DualChannel32` is not currently available as part of this PR since it's also not present in `esp32-camera` and it's thus unclear what its behaviour would be and whether that should be supported. while this is currently under `i2s::master` it should theoretically belong to `i2s::slave` - but it relies heavily on traits which need to be moved elsewhere so that they're accessible (currently they're under `i2s::master::private`). this refactoring will be done at a later stage by the maintainers :) Co-Authored-By: Ralph Ursprung --- esp-hal/CHANGELOG.md | 1 + esp-hal/src/i2s/master/camera.rs | 470 ++++++++++++++++++++++++++++ esp-hal/src/i2s/master/mod.rs | 198 ++++++++++++ qa-test/src/bin/i2s_camera.rs | 504 +++++++++++++++++++++++++++++++ 4 files changed, 1173 insertions(+) create mode 100644 esp-hal/src/i2s/master/camera.rs create mode 100644 qa-test/src/bin/i2s_camera.rs diff --git a/esp-hal/CHANGELOG.md b/esp-hal/CHANGELOG.md index 4126ad82e32..67a1b353102 100644 --- a/esp-hal/CHANGELOG.md +++ b/esp-hal/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Support for `rand_core` 0.9 (#3211) - `ESP_HAL_CONFIG_STACK_GUARD_OFFSET` and `ESP_HAL_CONFIG_STACK_GUARD_VALUE` to configure Rust's [Stack smashing protection](https://doc.rust-lang.org/rustc/exploit-mitigations.html#stack-smashing-protection) (#3203) +- ESP32: I2S Camera mode driver (#3219) ### Changed diff --git a/esp-hal/src/i2s/master/camera.rs b/esp-hal/src/i2s/master/camera.rs new file mode 100644 index 00000000000..a2a16d16c8d --- /dev/null +++ b/esp-hal/src/i2s/master/camera.rs @@ -0,0 +1,470 @@ +//! # I2S in Camera Slave receiving mode. +//! +//! ## Overview +//! The I2S peripheral supports a camera slave mode for high-speed data +//! transfer from external camera modules. +//! The driver mandates DMA for efficient data transfer. +//! +//! ## Examples +//! ```rust, no_run +#![doc = crate::before_snippet!()] +//! # use esp_hal::i2s::master::camera::{Camera, Config}; +//! # use esp_hal::dma_rx_stream_buffer; +//! +//! # const BUF_SIZE: usize = 20 * 1000; +//! # let dma_rx_buf = dma_rx_stream_buffer!(BUF_SIZE, 1000); +//! +//! let cam_siod = peripherals.GPIO26; +//! let cam_sioc = peripherals.GPIO27; +//! let cam_xclk = peripherals.GPIO32; +//! +//! let camera = Camera::new( +//! peripherals.I2S0, +//! Config::default(), +//! peripherals.DMA_I2S0, +//! ) +//! .with_ws(peripherals.GPIO22) +//! .with_vsync(peripherals.GPIO25) +//! .with_hsync(peripherals.GPIO23) +//! .with_data_pins( +//! peripherals.GPIO5, +//! peripherals.GPIO18, +//! peripherals.GPIO19, +//! peripherals.GPIO21, +//! peripherals.GPIO36, +//! peripherals.GPIO39, +//! peripherals.GPIO34, +//! peripherals.GPIO35, +//! ); +//! +//! let mut transfer = camera.receive(dma_rx_buf, BUF_SIZE).unwrap(); +//! +//! # Ok(()) +//! # } +//! ``` + +use core::{ + mem::ManuallyDrop, + ops::{Deref, DerefMut}, +}; + +use crate::{ + dma::{ChannelRx, DmaEligible, DmaError, DmaRxBuffer, PeripheralRxChannel, Rx, RxChannelFor}, + gpio::{InputPin, Level, Pull}, + i2s::master::{Error, ExtendedSignals, RegisterAccess}, + peripheral::{Peripheral, PeripheralRef}, + system::PeripheralClockControl, + Blocking, +}; + +/// Supported data formats +#[derive(Debug, Clone, Copy, PartialEq)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +pub enum DataFormat { + /// camera sends byte sequence: s1, s2, s3, s4, ... + /// fifo receives: 00 s1 00 s2, 00 s2 00 s3, 00 s3 00 s4, ... + DualChannel16 = 0, + /// camera sends byte sequence: s1, s2, s3, s4, ... + /// fifo receives: 00 s1 00 s2, 00 s3 00 s4, ... + SingleChannel16 = 1, + // DualChannel32 = 2, // not present in https://github.com/espressif/esp32-camera/blob/4467667b71f22a4c7db226f24105a9350abe7a05/target/esp32/ll_cam.c#L66 + /// camera sends byte sequence: s1, s2, s3, s4, ... + /// fifo receives: 00 s1 00 00, 00 s2 00 00, 00 s3 00 00, ... + SingleChannel32 = 3, +} + +/// Represents the camera interface. +pub struct Camera<'d, I2S: DmaEligible> { + _i2s: PeripheralRef<'d, I2S>, + rx_channel: ChannelRx<'d, Blocking, PeripheralRxChannel>, +} + +impl<'d, I2S> Camera<'d, I2S> +where + I2S: RegisterAccess + ExtendedSignals, +{ + /// Creates a new `Camera` instance with DMA support. + pub fn new>( + i2s: impl Peripheral

+ 'd, + config: Config, + channel: impl Peripheral

+ 'd, + ) -> Self { + crate::into_ref!(i2s); + + PeripheralClockControl::enable(i2s.peripheral()); + + let regs = i2s.regs(); + + // Configuration and start/stop bits + regs.conf().modify(|_, w| { + // Enable slave receiver mode + w.rx_slave_mod() + .set_bit() + // Receive left-channel data first. + .rx_right_first() + .clear_bit() + // Place left-channel data at the MSB in the FIFO + .rx_msb_right() + .clear_bit() + // Do not enable receiver in Philips standard mode + .rx_msb_shift() + .clear_bit() + // Do not enable receiver’s mono mode in PCM standard mode + .rx_mono() + .clear_bit() + // Do not enable receiver in PCM standard mode + .rx_short_sync() + .clear_bit() + }); + + // ADC/LCD/camera configuration register + regs.conf2().modify(|_, w| { + // Enable LCD mode. + w.lcd_en() + .set_bit() + // Enable camera mode. + .camera_en() + .set_bit() + }); + + // Configure clock divider + regs.clkm_conf().modify(|_, w| unsafe { + w.clkm_div_a() + .bits(0) // Fractional clock divider’s denominator value (6 bits) + .clkm_div_b() + .bits(0) // Fractional clock divider’s numerator value. (6 bits) + .clkm_div_num() + .bits(2) // I2S clock divider’s integral value. (8 bits) + }); + + regs.fifo_conf().modify(|_, w| unsafe { + // Enable I2S DMA mode. + w.dscr_en() + .set_bit() + // Receive FIFO mode configuration bit. (3 bits) + .rx_fifo_mod() + .bits(config.data_format as _) + // The bit should always be set to 1. + .rx_fifo_mod_force_en() + .set_bit() + }); + + regs.conf_chan().modify(|_, w| unsafe { + // I2S receiver channel mode configuration bit. (2 bits) + w.rx_chan_mod().bits(1) + }); + + // Configure the bit length of I2S receiver channel. (6 bits) + regs.sample_rate_conf() + .modify(|_, w| unsafe { w.rx_bits_mod().bits(16) }); + + // Synchronize signals into the receiver in double sync method. + regs.timing().write(|w| w.rx_dsync_sw().set_bit()); + + // Default these signals to high in case user doesn't provide them. + I2S::v_sync_signal().connect_to(Level::High); + I2S::h_sync_signal().connect_to(Level::High); + I2S::h_enable_signal().connect_to(Level::High); + + let rx_channel = ChannelRx::new(channel.map(|ch| ch.degrade())); + + Self { + _i2s: i2s, + rx_channel, + } + } +} + +impl Camera<'_, I2S> +where + I2S: RegisterAccess + ExtendedSignals, +{ + /// Configures the data pins for the camera interface. + /// Use either [`Self::with_data_pins`] *or* + /// [`Self::with_d0`]..[`Self::with_d7`]. + #[allow(clippy::too_many_arguments)] + pub fn with_data_pins< + D0: InputPin, + D1: InputPin, + D2: InputPin, + D3: InputPin, + D4: InputPin, + D5: InputPin, + D6: InputPin, + D7: InputPin, + >( + self, + d0: impl Peripheral

, + d1: impl Peripheral

, + d2: impl Peripheral

, + d3: impl Peripheral

, + d4: impl Peripheral

, + d5: impl Peripheral

, + d6: impl Peripheral

, + d7: impl Peripheral

, + ) -> Self { + self.with_d0(d0) + .with_d1(d1) + .with_d2(d2) + .with_d3(d3) + .with_d4(d4) + .with_d5(d5) + .with_d6(d6) + .with_d7(d7) + } + + /// Configures the d0 pin. + /// Use either [`Self::with_data_pins`] *or* + /// [`Self::with_d0`]..[`Self::with_d7`]. + pub fn with_d0(self, pin: impl Peripheral

) -> Self { + crate::into_mapped_ref!(pin); + pin.init_input(Pull::None); + I2S::din0_signal().connect_to(pin); + self + } + + /// Configures the d1 pin. + /// Use either [`Self::with_data_pins`] *or* + /// [`Self::with_d0`]..[`Self::with_d7`]. + pub fn with_d1(self, pin: impl Peripheral

) -> Self { + crate::into_mapped_ref!(pin); + pin.init_input(Pull::None); + I2S::din1_signal().connect_to(pin); + self + } + + /// Configures the d2 pin. + /// Use either [`Self::with_data_pins`] *or* + /// [`Self::with_d0`]..[`Self::with_d7`]. + pub fn with_d2(self, pin: impl Peripheral

) -> Self { + crate::into_mapped_ref!(pin); + pin.init_input(Pull::None); + I2S::din2_signal().connect_to(pin); + self + } + + /// Configures the d3 pin. + /// Use either [`Self::with_data_pins`] *or* + /// [`Self::with_d0`]..[`Self::with_d7`]. + pub fn with_d3(self, pin: impl Peripheral

) -> Self { + crate::into_mapped_ref!(pin); + pin.init_input(Pull::None); + I2S::din3_signal().connect_to(pin); + self + } + + /// Configures the d4 pin. + /// Use either [`Self::with_data_pins`] *or* + /// [`Self::with_d0`]..[`Self::with_d7`]. + pub fn with_d4(self, pin: impl Peripheral

) -> Self { + crate::into_mapped_ref!(pin); + pin.init_input(Pull::None); + I2S::din4_signal().connect_to(pin); + self + } + + /// Configures the d5 pin. + /// Use either [`Self::with_data_pins`] *or* + /// [`Self::with_d0`]..[`Self::with_d7`]. + pub fn with_d5(self, pin: impl Peripheral

) -> Self { + crate::into_mapped_ref!(pin); + pin.init_input(Pull::None); + I2S::din5_signal().connect_to(pin); + self + } + + /// Configures the d6 pin. + /// Use either [`Self::with_data_pins`] *or* + /// [`Self::with_d0`]..[`Self::with_d7`]. + pub fn with_d6(self, pin: impl Peripheral

) -> Self { + crate::into_mapped_ref!(pin); + pin.init_input(Pull::None); + I2S::din6_signal().connect_to(pin); + self + } + + /// Configures the d7 pin. + /// Use either [`Self::with_data_pins`] *or* + /// [`Self::with_d0`]..[`Self::with_d7`]. + pub fn with_d7(self, pin: impl Peripheral

) -> Self { + crate::into_mapped_ref!(pin); + pin.init_input(Pull::None); + I2S::din7_signal().connect_to(pin); + self + } + + /// Configures the pixel clock pin for the camera interface. + pub fn with_ws(self, pin: impl Peripheral

) -> Self { + crate::into_mapped_ref!(pin); + pin.init_input(Pull::None); + I2S::ws_in_signal().connect_to(pin); + self + } + + /// Configures the vertical sync (VSYNC) pin for the camera interface. + pub fn with_vsync(self, pin: impl Peripheral

) -> Self { + crate::into_mapped_ref!(pin); + pin.init_input(Pull::None); + I2S::v_sync_signal().connect_to(pin); + self + } + + /// Configures the horizontal sync (HSYNC) pin for the camera interface. + pub fn with_hsync(self, pin: impl Peripheral

) -> Self { + crate::into_mapped_ref!(pin); + pin.init_input(Pull::None); + I2S::h_sync_signal().connect_to(pin); + self + } + + /// Configures the horizontal sync enable pin for the camera interface. + pub fn with_henable(self, pin: impl Peripheral

) -> Self { + crate::into_mapped_ref!(pin); + pin.init_input(Pull::None); + I2S::h_enable_signal().connect_to(pin); + self + } +} + +impl<'d, I2S> Camera<'d, I2S> +where + I2S: RegisterAccess, +{ + /// Starts a DMA transfer to receive data from the camera peripheral. + pub fn receive( + mut self, + mut buf: BUF, + len: usize, + ) -> Result, Error> { + if len % 4 != 0 { + return Err(Error::IllegalArgument); + } + + // Reset RX unit and RX FIFO + self._i2s.reset_rx(); + + // configure DMA outlink + unsafe { + self.rx_channel + .prepare_transfer(self._i2s.dma_peripheral(), &mut buf) + .and_then(|_| self.rx_channel.start_transfer())?; + } + + // start: set I2S_RX_START + self._i2s.rx_start(len); + + 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, I2S: DmaEligible + RegisterAccess, BUF: DmaRxBuffer> { + camera: ManuallyDrop>, + buffer_view: ManuallyDrop, +} + +impl<'d, I2S: RegisterAccess, BUF: DmaRxBuffer> CameraTransfer<'d, I2S, BUF> { + /// Returns true when [Self::wait] will not block. + pub fn is_done(&self) -> bool { + self.camera.rx_channel.has_dscr_empty_error() // IN_DSCR_EMPTY (i.e. No more buffer space) + || self.camera.rx_channel.has_error() // IN_DSCR_ERR (i.e. bad + // descriptor) + } + + /// Stops this transfer on the spot and returns the peripheral and buffer. + pub fn stop(mut self) -> (Camera<'d, I2S>, 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, I2S>, BUF) { + while !self.is_done() {} + + // Stop the DMA as it doesn't know that the camera has stopped. + self.stop_peripherals(); + + // 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, I2S>, 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) { + self.camera.rx_channel.stop_transfer(); + self.camera._i2s.rx_stop(); + } +} + +impl Deref for CameraTransfer<'_, I2S, BUF> { + type Target = BUF::View; + + fn deref(&self) -> &Self::Target { + &self.buffer_view + } +} + +impl DerefMut + for CameraTransfer<'_, I2S, BUF> +{ + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.buffer_view + } +} + +impl Drop for CameraTransfer<'_, I2S, 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); + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, procmacros::BuilderLite)] +#[cfg_attr(feature = "defmt", derive(defmt::Format))] +/// Configuration settings for the Camera interface. +pub struct Config { + /// Format used for the data transfer. + data_format: DataFormat, +} + +impl Default for Config { + fn default() -> Self { + Self { + data_format: DataFormat::DualChannel16, + } + } +} diff --git a/esp-hal/src/i2s/master/mod.rs b/esp-hal/src/i2s/master/mod.rs index 9e2dc19630a..fa21c4b6464 100644 --- a/esp-hal/src/i2s/master/mod.rs +++ b/esp-hal/src/i2s/master/mod.rs @@ -108,6 +108,9 @@ use crate::{ DriverMode, }; +#[cfg(esp32)] +pub mod camera; + #[derive(Debug, EnumSetType)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] /// Represents the various interrupt types for the I2S peripheral. @@ -836,6 +839,30 @@ mod private { fn din_signal(&self) -> InputSignal; } + #[cfg(esp32)] + pub trait ExtendedSignals: Signals { + fn ws_in_signal() -> InputSignal; + fn h_sync_signal() -> InputSignal; + fn v_sync_signal() -> InputSignal; + fn h_enable_signal() -> InputSignal; + fn din0_signal() -> InputSignal; + fn din1_signal() -> InputSignal; + fn din2_signal() -> InputSignal; + fn din3_signal() -> InputSignal; + fn din4_signal() -> InputSignal; + fn din5_signal() -> InputSignal; + fn din6_signal() -> InputSignal; + fn din7_signal() -> InputSignal; + fn din8_signal() -> InputSignal; + fn din9_signal() -> InputSignal; + fn din10_signal() -> InputSignal; + fn din11_signal() -> InputSignal; + fn din12_signal() -> InputSignal; + fn din13_signal() -> InputSignal; + fn din14_signal() -> InputSignal; + fn din15_signal() -> InputSignal; + } + #[cfg(any(esp32, esp32s2))] pub trait RegisterAccessPrivate: Signals + RegBlock { fn set_interrupt_handler(&self, handler: InterruptHandler); @@ -1058,6 +1085,11 @@ mod private { self.regs().conf().modify(|_, w| w.rx_start().set_bit()); } + fn rx_stop(&self) { + let i2s = self.regs(); + i2s.conf().modify(|_, w| w.rx_start().clear_bit()); + } + fn wait_for_rx_done(&self) { while self.regs().int_raw().read().in_suc_eof().bit_is_clear() { // wait @@ -1615,6 +1647,89 @@ mod private { } } + #[cfg(esp32)] + impl ExtendedSignals for crate::peripherals::I2S0 { + fn ws_in_signal() -> InputSignal { + InputSignal::I2S0I_WS + } + + fn h_sync_signal() -> InputSignal { + InputSignal::I2S0I_H_SYNC + } + + fn v_sync_signal() -> InputSignal { + InputSignal::I2S0I_V_SYNC + } + + fn h_enable_signal() -> InputSignal { + InputSignal::I2S0I_H_ENABLE + } + + fn din0_signal() -> InputSignal { + InputSignal::I2S0I_DATA_0 + } + + fn din1_signal() -> InputSignal { + InputSignal::I2S0I_DATA_1 + } + + fn din2_signal() -> InputSignal { + InputSignal::I2S0I_DATA_2 + } + + fn din3_signal() -> InputSignal { + InputSignal::I2S0I_DATA_3 + } + + fn din4_signal() -> InputSignal { + InputSignal::I2S0I_DATA_4 + } + + fn din5_signal() -> InputSignal { + InputSignal::I2S0I_DATA_5 + } + + fn din6_signal() -> InputSignal { + InputSignal::I2S0I_DATA_6 + } + + fn din7_signal() -> InputSignal { + InputSignal::I2S0I_DATA_7 + } + + fn din8_signal() -> InputSignal { + InputSignal::I2S0I_DATA_8 + } + + fn din9_signal() -> InputSignal { + InputSignal::I2S0I_DATA_9 + } + + fn din10_signal() -> InputSignal { + InputSignal::I2S0I_DATA_10 + } + + fn din11_signal() -> InputSignal { + InputSignal::I2S0I_DATA_11 + } + + fn din12_signal() -> InputSignal { + InputSignal::I2S0I_DATA_12 + } + + fn din13_signal() -> InputSignal { + InputSignal::I2S0I_DATA_13 + } + + fn din14_signal() -> InputSignal { + InputSignal::I2S0I_DATA_14 + } + + fn din15_signal() -> InputSignal { + InputSignal::I2S0I_DATA_15 + } + } + #[cfg(i2s1)] impl RegBlock for I2S1 { fn regs(&self) -> &RegisterBlock { @@ -1689,6 +1804,89 @@ mod private { } } + #[cfg(esp32)] + impl ExtendedSignals for crate::peripherals::I2S1 { + fn ws_in_signal() -> InputSignal { + InputSignal::I2S1I_WS + } + + fn h_sync_signal() -> InputSignal { + InputSignal::I2S1I_H_SYNC + } + + fn v_sync_signal() -> InputSignal { + InputSignal::I2S1I_V_SYNC + } + + fn h_enable_signal() -> InputSignal { + InputSignal::I2S1I_H_ENABLE + } + + fn din0_signal() -> InputSignal { + InputSignal::I2S1I_DATA_0 + } + + fn din1_signal() -> InputSignal { + InputSignal::I2S1I_DATA_1 + } + + fn din2_signal() -> InputSignal { + InputSignal::I2S1I_DATA_2 + } + + fn din3_signal() -> InputSignal { + InputSignal::I2S1I_DATA_3 + } + + fn din4_signal() -> InputSignal { + InputSignal::I2S1I_DATA_4 + } + + fn din5_signal() -> InputSignal { + InputSignal::I2S1I_DATA_5 + } + + fn din6_signal() -> InputSignal { + InputSignal::I2S1I_DATA_6 + } + + fn din7_signal() -> InputSignal { + InputSignal::I2S1I_DATA_7 + } + + fn din8_signal() -> InputSignal { + InputSignal::I2S1I_DATA_8 + } + + fn din9_signal() -> InputSignal { + InputSignal::I2S1I_DATA_9 + } + + fn din10_signal() -> InputSignal { + InputSignal::I2S1I_DATA_10 + } + + fn din11_signal() -> InputSignal { + InputSignal::I2S1I_DATA_11 + } + + fn din12_signal() -> InputSignal { + InputSignal::I2S1I_DATA_12 + } + + fn din13_signal() -> InputSignal { + InputSignal::I2S1I_DATA_13 + } + + fn din14_signal() -> InputSignal { + InputSignal::I2S1I_DATA_14 + } + + fn din15_signal() -> InputSignal { + InputSignal::I2S1I_DATA_15 + } + } + impl RegBlock for super::AnyI2s { fn regs(&self) -> &RegisterBlock { match &self.0 { diff --git a/qa-test/src/bin/i2s_camera.rs b/qa-test/src/bin/i2s_camera.rs new file mode 100644 index 00000000000..690bf7a7671 --- /dev/null +++ b/qa-test/src/bin/i2s_camera.rs @@ -0,0 +1,504 @@ +#![feature(coroutines)] +#![feature(iter_from_coroutine)] + +//! This shows how to continuously receive data from a Camera via I2S, +//! using a "Makerfabs ESP32 3.2" TFT LCD with Camera" +//! +//! This example reads a JPEG from an OV2640 and writes it to the console as +//! hex. +//! +//! Pins used: +//! XCLK GPIO32 +//! SIOD GPIO26 +//! SIOC GPIO27 +//! PCLK GPIO22 +//! VSYNC GPIO25 +//! HREF GPIO23 +//! Y2 GPIO5 +//! Y3 GPIO18 +//! Y4 GPIO19 +//! Y5 GPIO21 +//! Y6 GPIO36 +//! Y7 GPIO39 +//! Y8 GPIO34 +//! Y9 GPIO35 + +//% CHIPS: esp32 + +#![no_std] +#![no_main] + +use esp_backtrace as _; +use esp_hal::{ + delay::Delay, + dma_rx_stream_buffer, + i2c::{ + self, + master::{Config as I2cConfig, I2c}, + }, + i2s::master::camera::{Camera, Config}, + ledc::{ + channel::{self, config::PinConfig, ChannelIFace}, + timer, + timer::{config::Duty, LSClockSource, Number, TimerIFace}, + LSGlobalClkSource, + Ledc, + LowSpeed, + }, + main, + time::Rate, + Blocking, +}; +use esp_println::println; + +#[main] +fn main() -> ! { + let peripherals = esp_hal::init(esp_hal::Config::default()); + + const BUF_SIZE: usize = 20 * 1000; + let dma_rx_buf = dma_rx_stream_buffer!(BUF_SIZE, 1000); + + let delay = Delay::new(); + + let cam_siod = peripherals.GPIO26; + let cam_sioc = peripherals.GPIO27; + let cam_xclk = peripherals.GPIO32; + + let camera = Camera::new(peripherals.I2S0, Config::default(), peripherals.DMA_I2S0) + .with_ws(peripherals.GPIO22) + .with_vsync(peripherals.GPIO25) + .with_hsync(peripherals.GPIO23) + .with_data_pins( + peripherals.GPIO5, + peripherals.GPIO18, + peripherals.GPIO19, + peripherals.GPIO21, + peripherals.GPIO36, + peripherals.GPIO39, + peripherals.GPIO34, + peripherals.GPIO35, + ); + + let mut ledc = Ledc::new(peripherals.LEDC); + ledc.set_global_slow_clock(LSGlobalClkSource::APBClk); + + let mut timer = ledc.timer::(Number::Timer0); + timer + .configure(timer::config::Config { + duty: Duty::Duty1Bit, + clock_source: LSClockSource::APBClk, + frequency: Rate::from_mhz(20), + }) + .unwrap(); + + let mut channel = ledc.channel(channel::Number::Channel0, cam_xclk); + channel + .configure(channel::config::Config { + timer: &timer, + duty_pct: 50, + pin_config: PinConfig::PushPull, + }) + .unwrap(); + + delay.delay_millis(500u32); + + let i2c = I2c::new(peripherals.I2C0, I2cConfig::default()) + .unwrap() + .with_sda(cam_siod) + .with_scl(cam_sioc); + + 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); + + 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); + } + } + + // Start receiving data from the camera. + let mut transfer = camera.receive(dma_rx_buf, BUF_SIZE).unwrap(); + + let mut byte_stream = core::iter::from_coroutine( + #[coroutine] + || { + loop { + let (data, _ends_with_eof) = transfer.peek_until_eof(); + if data.is_empty() { + if transfer.is_done() { + panic!("We were too slow to read from the DMA"); + } + } else { + let bytes_peeked = data.len(); + // Convert [FF, 00, FF, 00, D8, 00, D8, 00, FF, 00, FF, 00, E0, 00, E0] to [FF, + // D8, FF, E0] + for i in 0..(bytes_peeked / 4) { + yield data[i * 4] + } + transfer.consume(bytes_peeked); + } + } + }, + ); + + let mut jpegs_skipped = 0; + let mut bytes_skipped = 0; + loop { + let byte: u8 = byte_stream.next().unwrap(); + if byte != 0xFF { + bytes_skipped += 1; + continue; + } + let byte: u8 = byte_stream.next().unwrap(); + if byte != 0xD8 { + bytes_skipped += 2; + continue; + } + let byte: u8 = byte_stream.next().unwrap(); + if byte != 0xFF { + bytes_skipped += 3; + continue; + } + let byte: u8 = byte_stream.next().unwrap(); + if byte != 0xE0 { + bytes_skipped += 4; + continue; + } + + // We've found the starting JPEG marker of FF, D8, FF, E0. + + // We want to skip the first 10 of these as they're likely to be garbage. + if jpegs_skipped < 10 { + bytes_skipped += 4; + jpegs_skipped += 1; + continue; + } + + // Read the rest of the JPEG below + break; + } + + // Note: JPEGs starts with "FF, D8, FF, E0" and end with "FF, D9" + + println!("Skipped {} bytes worth of image data", bytes_skipped); + println!("Frame data (parse with `xxd -r -p .txt image.jpg`):"); + + let mut frame = [0; 65472]; + frame[0] = 0xFF; + frame[1] = 0xD8; + frame[2] = 0xFF; + frame[3] = 0xE0; + let mut idx = 4; + + loop { + let byte: u8 = byte_stream.next().unwrap(); + frame[idx] = byte; + idx += 1; + if byte != 0xFF { + continue; + } + let byte: u8 = byte_stream.next().unwrap(); + frame[idx] = byte; + idx += 1; + if byte != 0xD9 { + continue; + } + + // We've found the ending JPEG marker of FF, D9. + break; + } + + println!("{:02X?}", &frame[..idx]); + + // Cancel transfer before halting. + drop(transfer); + + loop {} +} + +pub const OV2640_ADDRESS: u8 = 0x30; + +pub struct Sccb<'d> { + i2c: I2c<'d, Blocking>, +} + +impl<'d> Sccb<'d> { + pub fn new(i2c: I2c<'d, Blocking>) -> Self { + Self { i2c } + } + + pub fn probe(&mut self, address: u8) -> Result<(), i2c::master::Error> { + self.i2c.write(address, &[]) + } + + pub fn read(&mut self, address: u8, reg: u8) -> Result { + 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::master::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), +];