From 2dc285a94760e5aa83b3e02e8077116548f59f79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B6rn=20Quentin?= Date: Mon, 2 Sep 2024 09:52:20 +0200 Subject: [PATCH] QSPI tests (#2015) * Add QSPI tests * Simplify * Add qspi_write_read test --- hil-test/Cargo.toml | 12 ++ hil-test/tests/qspi_read.rs | 205 ++++++++++++++++++++++++ hil-test/tests/qspi_write.rs | 252 ++++++++++++++++++++++++++++++ hil-test/tests/qspi_write_read.rs | 202 ++++++++++++++++++++++++ 4 files changed, 671 insertions(+) create mode 100644 hil-test/tests/qspi_read.rs create mode 100644 hil-test/tests/qspi_write.rs create mode 100644 hil-test/tests/qspi_write_read.rs diff --git a/hil-test/Cargo.toml b/hil-test/Cargo.toml index 0251bf8fb..e048755d3 100644 --- a/hil-test/Cargo.toml +++ b/hil-test/Cargo.toml @@ -67,6 +67,18 @@ harness = false name = "lcd_cam_i8080_async" harness = false +[[test]] +name = "qspi_read" +harness = false + +[[test]] +name = "qspi_write" +harness = false + +[[test]] +name = "qspi_write_read" +harness = false + [[test]] name = "spi_full_duplex" harness = false diff --git a/hil-test/tests/qspi_read.rs b/hil-test/tests/qspi_read.rs new file mode 100644 index 000000000..cae18ef39 --- /dev/null +++ b/hil-test/tests/qspi_read.rs @@ -0,0 +1,205 @@ +//! QSPI Read Test +//! +//! Following pins are used: +//! MISO GPIO2 +//! +//! GPIO GPIO3 +//! +//! Connect MISO (GPIO2) and GPIO (GPIO3) pins. + +//% CHIPS: esp32 esp32c2 esp32c3 esp32c6 esp32h2 esp32s2 esp32s3 + +#![no_std] +#![no_main] + +use esp_hal::{ + clock::{ClockControl, Clocks}, + dma::{Channel, Dma, DmaPriority, DmaRxBuf}, + dma_buffers, + gpio::{GpioPin, Io, Level, Output}, + peripherals::Peripherals, + prelude::*, + spi::{ + master::{Address, Command, Spi, SpiDma}, + HalfDuplexMode, + SpiDataMode, + SpiMode, + }, + system::SystemControl, + Blocking, +}; +use hil_test as _; + +cfg_if::cfg_if! { + if #[cfg(any( + feature = "esp32", + feature = "esp32s2", + ))] { + use esp_hal::dma::Spi2DmaChannel as DmaChannel0; + } else { + use esp_hal::dma::DmaChannel0; + } +} + +struct Context { + spi: esp_hal::peripherals::SPI2, + dma_channel: Channel<'static, DmaChannel0, Blocking>, + miso: esp_hal::gpio::GpioPin<2>, + miso_mirror: Output<'static, GpioPin<3>>, + clocks: Clocks<'static>, +} + +fn execute( + mut spi: SpiDma<'static, esp_hal::peripherals::SPI2, DmaChannel0, HalfDuplexMode, Blocking>, + mut miso_mirror: Output<'static, GpioPin<3>>, + wanted: u8, +) { + const DMA_BUFFER_SIZE: usize = 4; + + let (buffer, descriptors, _, _) = dma_buffers!(DMA_BUFFER_SIZE, 0); + let mut dma_rx_buf = DmaRxBuf::new(descriptors, buffer).unwrap(); + + miso_mirror.set_low(); + + let transfer = spi + .read( + SpiDataMode::Quad, + Command::None, + Address::None, + 0, + dma_rx_buf, + ) + .map_err(|e| e.0) + .unwrap(); + (spi, dma_rx_buf) = transfer.wait(); + + assert_eq!(dma_rx_buf.as_slice(), &[wanted; DMA_BUFFER_SIZE]); + + // SPI should read all '1's + miso_mirror.set_high(); + + let transfer = spi + .read( + SpiDataMode::Quad, + Command::None, + Address::None, + 0, + dma_rx_buf, + ) + .map_err(|e| e.0) + .unwrap(); + + (_, dma_rx_buf) = transfer.wait(); + + assert_eq!(dma_rx_buf.as_slice(), &[0xFF; DMA_BUFFER_SIZE]); +} + +#[cfg(test)] +#[embedded_test::tests] +mod tests { + use super::*; + + #[init] + fn init() -> Context { + 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 miso = io.pins.gpio2; + + let miso_mirror = Output::new(io.pins.gpio3, Level::High); + + let dma = Dma::new(peripherals.DMA); + + cfg_if::cfg_if! { + if #[cfg(any(feature = "esp32", feature = "esp32s2"))] { + let dma_channel = dma.spi2channel; + } else { + let dma_channel = dma.channel0; + } + } + + let dma_channel = dma_channel.configure(false, DmaPriority::Priority0); + + Context { + spi: peripherals.SPI2, + dma_channel, + miso, + miso_mirror, + clocks, + } + } + + #[test] + #[timeout(3)] + fn test_spi_reads_correctly_from_gpio_pin_0(ctx: Context) { + let spi = Spi::new_half_duplex(ctx.spi, 100.kHz(), SpiMode::Mode0, &ctx.clocks) + .with_pins( + esp_hal::gpio::NO_PIN, + Some(ctx.miso), + esp_hal::gpio::NO_PIN, + esp_hal::gpio::NO_PIN, + esp_hal::gpio::NO_PIN, + esp_hal::gpio::NO_PIN, + ) + .with_dma(ctx.dma_channel); + + // SPI should read '0b11101110' + super::execute(spi, ctx.miso_mirror, 238); + } + + #[test] + #[timeout(3)] + fn test_spi_reads_correctly_from_gpio_pin_1(ctx: Context) { + let spi = Spi::new_half_duplex(ctx.spi, 100.kHz(), SpiMode::Mode0, &ctx.clocks) + .with_pins( + esp_hal::gpio::NO_PIN, + esp_hal::gpio::NO_PIN, + Some(ctx.miso), + esp_hal::gpio::NO_PIN, + esp_hal::gpio::NO_PIN, + esp_hal::gpio::NO_PIN, + ) + .with_dma(ctx.dma_channel); + + // SPI should read '0b11011101' + super::execute(spi, ctx.miso_mirror, 221); + } + + #[test] + #[timeout(3)] + fn test_spi_reads_correctly_from_gpio_pin_2(ctx: Context) { + let spi = Spi::new_half_duplex(ctx.spi, 100.kHz(), SpiMode::Mode0, &ctx.clocks) + .with_pins( + esp_hal::gpio::NO_PIN, + esp_hal::gpio::NO_PIN, + esp_hal::gpio::NO_PIN, + Some(ctx.miso), + esp_hal::gpio::NO_PIN, + esp_hal::gpio::NO_PIN, + ) + .with_dma(ctx.dma_channel); + + // SPI should read '0b10111011' + super::execute(spi, ctx.miso_mirror, 187); + } + + #[test] + #[timeout(3)] + fn test_spi_reads_correctly_from_gpio_pin_3(ctx: Context) { + let spi = Spi::new_half_duplex(ctx.spi, 100.kHz(), SpiMode::Mode0, &ctx.clocks) + .with_pins( + esp_hal::gpio::NO_PIN, + esp_hal::gpio::NO_PIN, + esp_hal::gpio::NO_PIN, + esp_hal::gpio::NO_PIN, + Some(ctx.miso), + esp_hal::gpio::NO_PIN, + ) + .with_dma(ctx.dma_channel); + + // SPI should read '0b01110111' + super::execute(spi, ctx.miso_mirror, 119); + } +} diff --git a/hil-test/tests/qspi_write.rs b/hil-test/tests/qspi_write.rs new file mode 100644 index 000000000..70a35d411 --- /dev/null +++ b/hil-test/tests/qspi_write.rs @@ -0,0 +1,252 @@ +//! QSPI Write Test +//! +//! This uses PCNT to count the edges of the MOSI signal +//! +//! Following pins are used: +//! MOSI GPIO2 +//! +//! PCNT GPIO3 +//! +//! Connect MOSI (GPIO2) and PCNT (GPIO3) pins. + +//% CHIPS: esp32 esp32c6 esp32h2 esp32s2 esp32s3 + +#![no_std] +#![no_main] + +use esp_hal::{ + clock::{ClockControl, Clocks}, + dma::{Channel, Dma, DmaPriority, DmaTxBuf}, + dma_buffers, + gpio::{Io, Pull}, + pcnt::{ + channel::{EdgeMode, PcntInputConfig, PcntSource}, + unit::Unit, + Pcnt, + }, + peripherals::Peripherals, + prelude::*, + spi::{ + master::{Address, Command, Spi, SpiDma}, + HalfDuplexMode, + SpiDataMode, + SpiMode, + }, + system::SystemControl, + Blocking, +}; +use hil_test as _; + +cfg_if::cfg_if! { + if #[cfg(any( + feature = "esp32", + feature = "esp32s2", + ))] { + use esp_hal::dma::Spi2DmaChannel as DmaChannel0; + } else { + use esp_hal::dma::DmaChannel0; + } +} + +struct Context { + spi: esp_hal::peripherals::SPI2, + pcnt: esp_hal::peripherals::PCNT, + dma_channel: Channel<'static, DmaChannel0, Blocking>, + mosi: esp_hal::gpio::GpioPin<2>, + mosi_mirror: esp_hal::gpio::GpioPin<3>, + clocks: Clocks<'static>, +} + +fn execute( + unit: Unit<'static, 0>, + mut spi: SpiDma<'static, esp_hal::peripherals::SPI2, DmaChannel0, HalfDuplexMode, Blocking>, + write: u8, +) { + const DMA_BUFFER_SIZE: usize = 4; + + let (buffer, descriptors, _, _) = dma_buffers!(DMA_BUFFER_SIZE, 0); + let mut dma_tx_buf = DmaTxBuf::new(descriptors, buffer).unwrap(); + + dma_tx_buf.fill(&[write; DMA_BUFFER_SIZE]); + + let transfer = spi + .write( + SpiDataMode::Quad, + Command::Command8(write as u16, SpiDataMode::Quad), + Address::Address24( + write as u32 | (write as u32) << 8 | (write as u32) << 16, + SpiDataMode::Quad, + ), + 0, + dma_tx_buf, + ) + .map_err(|e| e.0) + .unwrap(); + (spi, dma_tx_buf) = transfer.wait(); + + assert_eq!(unit.get_value(), 8); + + dma_tx_buf.set_length(0); + let transfer = spi + .write( + SpiDataMode::Quad, + Command::Command8(write as u16, SpiDataMode::Quad), + Address::Address24( + write as u32 | (write as u32) << 8 | (write as u32) << 16, + SpiDataMode::Quad, + ), + 0, + dma_tx_buf, + ) + .map_err(|e| e.0) + .unwrap(); + _ = transfer.wait(); + + assert_eq!(unit.get_value(), 8 + 4); +} + +#[cfg(test)] +#[embedded_test::tests] +mod tests { + use super::*; + + #[init] + fn init() -> Context { + 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 mosi = io.pins.gpio2; + let mosi_mirror = io.pins.gpio3; + + let dma = Dma::new(peripherals.DMA); + + cfg_if::cfg_if! { + if #[cfg(any(feature = "esp32", feature = "esp32s2"))] { + let dma_channel = dma.spi2channel; + } else { + let dma_channel = dma.channel0; + } + } + + let dma_channel = dma_channel.configure(false, DmaPriority::Priority0); + + Context { + spi: peripherals.SPI2, + pcnt: peripherals.PCNT, + dma_channel, + mosi, + mosi_mirror, + clocks, + } + } + + #[test] + #[timeout(3)] + fn test_spi_writes_correctly_to_pin_0(ctx: Context) { + let spi = Spi::new_half_duplex(ctx.spi, 100.kHz(), SpiMode::Mode0, &ctx.clocks) + .with_pins( + esp_hal::gpio::NO_PIN, + Some(ctx.mosi), + esp_hal::gpio::NO_PIN, + esp_hal::gpio::NO_PIN, + esp_hal::gpio::NO_PIN, + esp_hal::gpio::NO_PIN, + ) + .with_dma(ctx.dma_channel); + + let pcnt = Pcnt::new(ctx.pcnt); + let unit = pcnt.unit0; + + unit.channel0.set_edge_signal(PcntSource::from_pin( + ctx.mosi_mirror, + PcntInputConfig { pull: Pull::None }, + )); + unit.channel0 + .set_input_mode(EdgeMode::Hold, EdgeMode::Increment); + + super::execute(unit, spi, 0b0000_0001); + } + + #[test] + #[timeout(3)] + fn test_spi_writes_correctly_to_pin_1(ctx: Context) { + let spi = Spi::new_half_duplex(ctx.spi, 100.kHz(), SpiMode::Mode0, &ctx.clocks) + .with_pins( + esp_hal::gpio::NO_PIN, + esp_hal::gpio::NO_PIN, + Some(ctx.mosi), + esp_hal::gpio::NO_PIN, + esp_hal::gpio::NO_PIN, + esp_hal::gpio::NO_PIN, + ) + .with_dma(ctx.dma_channel); + + let pcnt = Pcnt::new(ctx.pcnt); + let unit = pcnt.unit0; + + unit.channel0.set_edge_signal(PcntSource::from_pin( + ctx.mosi_mirror, + PcntInputConfig { pull: Pull::None }, + )); + unit.channel0 + .set_input_mode(EdgeMode::Hold, EdgeMode::Increment); + + super::execute(unit, spi, 0b0000_0010); + } + + #[test] + #[timeout(3)] + fn test_spi_writes_correctly_to_pin_2(ctx: Context) { + let spi = Spi::new_half_duplex(ctx.spi, 100.kHz(), SpiMode::Mode0, &ctx.clocks) + .with_pins( + esp_hal::gpio::NO_PIN, + esp_hal::gpio::NO_PIN, + esp_hal::gpio::NO_PIN, + Some(ctx.mosi), + esp_hal::gpio::NO_PIN, + esp_hal::gpio::NO_PIN, + ) + .with_dma(ctx.dma_channel); + + let pcnt = Pcnt::new(ctx.pcnt); + let unit = pcnt.unit0; + + unit.channel0.set_edge_signal(PcntSource::from_pin( + ctx.mosi_mirror, + PcntInputConfig { pull: Pull::None }, + )); + unit.channel0 + .set_input_mode(EdgeMode::Hold, EdgeMode::Increment); + + super::execute(unit, spi, 0b0000_0100); + } + + #[test] + #[timeout(3)] + fn test_spi_writes_correctly_to_pin_3(ctx: Context) { + let spi = Spi::new_half_duplex(ctx.spi, 100.kHz(), SpiMode::Mode0, &ctx.clocks) + .with_pins( + esp_hal::gpio::NO_PIN, + esp_hal::gpio::NO_PIN, + esp_hal::gpio::NO_PIN, + esp_hal::gpio::NO_PIN, + Some(ctx.mosi), + esp_hal::gpio::NO_PIN, + ) + .with_dma(ctx.dma_channel); + + let pcnt = Pcnt::new(ctx.pcnt); + let unit = pcnt.unit0; + + unit.channel0.set_edge_signal(PcntSource::from_pin( + ctx.mosi_mirror, + PcntInputConfig { pull: Pull::None }, + )); + unit.channel0 + .set_input_mode(EdgeMode::Hold, EdgeMode::Increment); + + super::execute(unit, spi, 0b0000_1000); + } +} diff --git a/hil-test/tests/qspi_write_read.rs b/hil-test/tests/qspi_write_read.rs new file mode 100644 index 000000000..4d98c7b95 --- /dev/null +++ b/hil-test/tests/qspi_write_read.rs @@ -0,0 +1,202 @@ +//! QSPI Write + Read Test +//! +//! Make sure issue #1860 doesn't affect us +//! +//! Following pins are used: +//! MOSI/MISO GPIO2 +//! +//! GPIO GPIO3 +//! +//! Connect MOSI/MISO (GPIO2) and GPIO (GPIO3) pins. + +//% CHIPS: esp32 esp32c6 esp32h2 esp32s2 esp32s3 + +#![no_std] +#![no_main] + +use esp_hal::{ + clock::{ClockControl, Clocks}, + dma::{Channel, Dma, DmaPriority, DmaRxBuf, DmaTxBuf}, + dma_buffers, + gpio::{GpioPin, Io, Level, Output}, + peripherals::Peripherals, + prelude::*, + spi::{ + master::{Address, Command, Spi, SpiDma}, + HalfDuplexMode, + SpiDataMode, + SpiMode, + }, + system::SystemControl, + Blocking, +}; +use hil_test as _; + +cfg_if::cfg_if! { + if #[cfg(any( + feature = "esp32", + feature = "esp32s2", + ))] { + use esp_hal::dma::Spi2DmaChannel as DmaChannel0; + } else { + use esp_hal::dma::DmaChannel0; + } +} + +struct Context { + spi: esp_hal::peripherals::SPI2, + dma_channel: Channel<'static, DmaChannel0, Blocking>, + mosi: esp_hal::gpio::GpioPin<2>, + mosi_mirror: Output<'static, GpioPin<3>>, + clocks: Clocks<'static>, +} + +fn execute( + mut spi: SpiDma<'static, esp_hal::peripherals::SPI2, DmaChannel0, HalfDuplexMode, Blocking>, + mut mosi_mirror: Output<'static, GpioPin<3>>, + wanted: u8, +) { + const DMA_BUFFER_SIZE: usize = 4; + + let (buffer, descriptors, rx_buffer, rx_descriptors) = + dma_buffers!(DMA_BUFFER_SIZE, DMA_BUFFER_SIZE); + let mut dma_tx_buf = DmaTxBuf::new(descriptors, buffer).unwrap(); + let mut dma_rx_buf = DmaRxBuf::new(rx_descriptors, rx_buffer).unwrap(); + + dma_tx_buf.fill(&[0xff; DMA_BUFFER_SIZE]); + + let transfer = spi + .write( + SpiDataMode::Quad, + Command::Command8(wanted as u16, SpiDataMode::Quad), + Address::Address24( + wanted as u32 | (wanted as u32) << 8 | (wanted as u32) << 16, + SpiDataMode::Quad, + ), + 0, + dma_tx_buf, + ) + .map_err(|e| e.0) + .unwrap(); + (spi, _) = transfer.wait(); + + mosi_mirror.set_low(); + + let transfer = spi + .read( + SpiDataMode::Quad, + Command::None, + Address::None, + 0, + dma_rx_buf, + ) + .map_err(|e| e.0) + .unwrap(); + (_, dma_rx_buf) = transfer.wait(); + assert_eq!(dma_rx_buf.as_slice(), &[wanted; DMA_BUFFER_SIZE]); +} + +#[cfg(test)] +#[embedded_test::tests] +mod tests { + use super::*; + + #[init] + fn init() -> Context { + 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 mosi = io.pins.gpio2; + let mosi_mirror = Output::new(io.pins.gpio3, Level::High); + + let dma = Dma::new(peripherals.DMA); + + cfg_if::cfg_if! { + if #[cfg(any(feature = "esp32", feature = "esp32s2"))] { + let dma_channel = dma.spi2channel; + } else { + let dma_channel = dma.channel0; + } + } + + let dma_channel = dma_channel.configure(false, DmaPriority::Priority0); + + Context { + spi: peripherals.SPI2, + dma_channel, + mosi, + mosi_mirror, + clocks, + } + } + + #[test] + #[timeout(3)] + fn test_spi_writes_correctly_to_pin_0(ctx: Context) { + let spi = Spi::new_half_duplex(ctx.spi, 100.kHz(), SpiMode::Mode0, &ctx.clocks) + .with_pins( + esp_hal::gpio::NO_PIN, + Some(ctx.mosi), + esp_hal::gpio::NO_PIN, + esp_hal::gpio::NO_PIN, + esp_hal::gpio::NO_PIN, + esp_hal::gpio::NO_PIN, + ) + .with_dma(ctx.dma_channel); + + super::execute(spi, ctx.mosi_mirror, !0b0001_0001); + } + + #[test] + #[timeout(3)] + fn test_spi_writes_correctly_to_pin_1(ctx: Context) { + let spi = Spi::new_half_duplex(ctx.spi, 100.kHz(), SpiMode::Mode0, &ctx.clocks) + .with_pins( + esp_hal::gpio::NO_PIN, + esp_hal::gpio::NO_PIN, + Some(ctx.mosi), + esp_hal::gpio::NO_PIN, + esp_hal::gpio::NO_PIN, + esp_hal::gpio::NO_PIN, + ) + .with_dma(ctx.dma_channel); + + super::execute(spi, ctx.mosi_mirror, !0b0010_0010); + } + + #[test] + #[timeout(3)] + fn test_spi_writes_correctly_to_pin_2(ctx: Context) { + let spi = Spi::new_half_duplex(ctx.spi, 100.kHz(), SpiMode::Mode0, &ctx.clocks) + .with_pins( + esp_hal::gpio::NO_PIN, + esp_hal::gpio::NO_PIN, + esp_hal::gpio::NO_PIN, + Some(ctx.mosi), + esp_hal::gpio::NO_PIN, + esp_hal::gpio::NO_PIN, + ) + .with_dma(ctx.dma_channel); + + super::execute(spi, ctx.mosi_mirror, !0b0100_0100); + } + + #[test] + #[timeout(3)] + fn test_spi_writes_correctly_to_pin_3(ctx: Context) { + let spi = Spi::new_half_duplex(ctx.spi, 100.kHz(), SpiMode::Mode0, &ctx.clocks) + .with_pins( + esp_hal::gpio::NO_PIN, + esp_hal::gpio::NO_PIN, + esp_hal::gpio::NO_PIN, + esp_hal::gpio::NO_PIN, + Some(ctx.mosi), + esp_hal::gpio::NO_PIN, + ) + .with_dma(ctx.dma_channel); + + super::execute(spi, ctx.mosi_mirror, !0b1000_1000); + } +}