mirror of
https://github.com/esp-rs/esp-hal.git
synced 2025-09-28 21:00:59 +00:00
Support PSRAM in DmaTxBuf (#2161)
* support psram in DmaTxBuf * add example that sometimes works :-( * fmt * cleanups * allow chunk_size upto (including) 4095 * this test is passing for me now * remove chunk_size and compute based on block_size * return error in `prepare_transfer` if psram is found on non-esp32s3 add `dma_tx_buffer` macro * missing parens * changelog * default 4092 for esp32 & fmt * no errors anymode * use block_size is_some to flag invalid psram in prepare_transfer * drop block_size from macro, the buffer allocation was not being aligned - its not needed for dram anyway. * missed macro example * use defmt::Format that decodes owner like Debug * fix typo * DmaTxBuf: its an error if buffer is in psram and block_size is none * DmaTxBuf: its an error if buffer is in psram and block_size is none * update for PSRAM feature changes * address alignment comments add simple test * fmt * better alignment test * revert alignment test --------- Co-authored-by: Juraj Sadel <juraj.sadel@espressif.com>
This commit is contained in:
parent
826754c482
commit
cf9050d5d7
@ -53,6 +53,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Allow handling interrupts while trying to lock critical section on multi-core chips. (#2197)
|
||||
- Removed the PS-RAM related features, replaced by `quad-psram`/`octal-psram`, `init_psram` takes a configuration parameter, it's now possible to auto-detect PS-RAM size (#2178)
|
||||
- `EspTwaiFrame` constructors now accept any type that converts into `esp_hal::twai::Id` (#2207)
|
||||
- Change `DmaTxBuf` to support PSRAM on `esp32s3` (#2161)
|
||||
|
||||
### Fixed
|
||||
|
||||
|
@ -205,7 +205,6 @@ where
|
||||
bitfield::bitfield! {
|
||||
#[doc(hidden)]
|
||||
#[derive(Clone, Copy)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct DmaDescriptorFlags(u32);
|
||||
|
||||
u16;
|
||||
@ -226,6 +225,20 @@ impl Debug for DmaDescriptorFlags {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "defmt")]
|
||||
impl defmt::Format for DmaDescriptorFlags {
|
||||
fn format(&self, fmt: defmt::Formatter<'_>) {
|
||||
defmt::write!(
|
||||
fmt,
|
||||
"DmaDescriptorFlags {{ size: {}, length: {}, suc_eof: {}, owner: {} }}",
|
||||
self.size(),
|
||||
self.length(),
|
||||
self.suc_eof(),
|
||||
if self.owner() { "DMA" } else { "CPU" }
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A DMA transfer descriptor.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
@ -286,6 +299,8 @@ use enumset::{EnumSet, EnumSetType};
|
||||
pub use self::gdma::*;
|
||||
#[cfg(pdma)]
|
||||
pub use self::pdma::*;
|
||||
#[cfg(esp32s3)]
|
||||
use crate::soc::is_slice_in_psram;
|
||||
use crate::{interrupt::InterruptHandler, soc::is_slice_in_dram, Mode};
|
||||
|
||||
#[cfg(gdma)]
|
||||
@ -558,7 +573,7 @@ macro_rules! dma_circular_buffers_chunk_size {
|
||||
macro_rules! dma_descriptors_chunk_size {
|
||||
($rx_size:expr, $tx_size:expr, $chunk_size:expr) => {{
|
||||
// these will check for size at compile time
|
||||
const _: () = ::core::assert!($chunk_size <= 4092, "chunk size must be <= 4092");
|
||||
const _: () = ::core::assert!($chunk_size <= 4095, "chunk size must be <= 4095");
|
||||
const _: () = ::core::assert!($chunk_size > 0, "chunk size must be > 0");
|
||||
|
||||
static mut RX_DESCRIPTORS: [$crate::dma::DmaDescriptor;
|
||||
@ -593,7 +608,7 @@ macro_rules! dma_descriptors_chunk_size {
|
||||
macro_rules! dma_circular_descriptors_chunk_size {
|
||||
($rx_size:expr, $tx_size:expr, $chunk_size:expr) => {{
|
||||
// these will check for size at compile time
|
||||
const _: () = ::core::assert!($chunk_size <= 4092, "chunk size must be <= 4092");
|
||||
const _: () = ::core::assert!($chunk_size <= 4095, "chunk size must be <= 4095");
|
||||
const _: () = ::core::assert!($chunk_size > 0, "chunk size must be > 0");
|
||||
|
||||
const rx_descriptor_len: usize = if $rx_size > $chunk_size * 2 {
|
||||
@ -620,6 +635,33 @@ macro_rules! dma_circular_descriptors_chunk_size {
|
||||
};
|
||||
}
|
||||
|
||||
/// Convenience macro to create a DmaTxBuf from buffer size. The buffer and
|
||||
/// descriptors are statically allocated and used to create the `DmaTxBuf`.
|
||||
///
|
||||
/// ## Usage
|
||||
/// ```rust,no_run
|
||||
#[doc = crate::before_snippet!()]
|
||||
/// use esp_hal::dma_tx_buffer;
|
||||
/// use esp_hal::dma::DmaBufBlkSize;
|
||||
///
|
||||
/// let tx_buf =
|
||||
/// dma_tx_buffer!(32000);
|
||||
/// # }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! dma_tx_buffer {
|
||||
($tx_size:expr) => {{
|
||||
const TX_DESCRIPTOR_LEN: usize =
|
||||
$crate::dma::DmaTxBuf::compute_descriptor_count($tx_size, None);
|
||||
$crate::declare_aligned_dma_buffer!(TX_BUFFER, $tx_size);
|
||||
static mut TX_DESCRIPTORS: [$crate::dma::DmaDescriptor; TX_DESCRIPTOR_LEN] =
|
||||
[$crate::dma::DmaDescriptor::EMPTY; TX_DESCRIPTOR_LEN];
|
||||
let tx_buffer = $crate::as_mut_byte_array!(TX_BUFFER, $tx_size);
|
||||
let tx_descriptors = unsafe { &mut TX_DESCRIPTORS };
|
||||
$crate::dma::DmaTxBuf::new(tx_descriptors, tx_buffer)
|
||||
}};
|
||||
}
|
||||
|
||||
/// DMA Errors
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
@ -1001,6 +1043,16 @@ pub enum DmaExtMemBKSize {
|
||||
Size64 = 2,
|
||||
}
|
||||
|
||||
impl From<DmaBufBlkSize> for DmaExtMemBKSize {
|
||||
fn from(size: DmaBufBlkSize) -> Self {
|
||||
match size {
|
||||
DmaBufBlkSize::Size16 => DmaExtMemBKSize::Size16,
|
||||
DmaBufBlkSize::Size32 => DmaExtMemBKSize::Size32,
|
||||
DmaBufBlkSize::Size64 => DmaExtMemBKSize::Size64,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct TxCircularState {
|
||||
write_offset: usize,
|
||||
write_descr_ptr: *mut DmaDescriptor,
|
||||
@ -1417,7 +1469,6 @@ where
|
||||
if des.buffer as usize % alignment != 0 && des.size() % alignment != 0 {
|
||||
return Err(DmaError::InvalidAlignment);
|
||||
}
|
||||
// TODO: make this optional?
|
||||
crate::soc::cache_invalidate_addr(des.buffer as u32, des.size() as u32);
|
||||
}
|
||||
}
|
||||
@ -1635,6 +1686,7 @@ where
|
||||
peri: DmaPeripheral,
|
||||
chain: &DescriptorChain,
|
||||
) -> Result<(), DmaError> {
|
||||
// TODO: based on the ESP32-S3 TRM the alignment check is not needed for TX!
|
||||
// for esp32s3 we check each descriptor buffer that points to psram for
|
||||
// alignment and writeback the cache for that buffer
|
||||
#[cfg(esp32s3)]
|
||||
@ -1660,7 +1712,19 @@ where
|
||||
buffer: &mut BUF,
|
||||
) -> Result<(), DmaError> {
|
||||
let preparation = buffer.prepare();
|
||||
|
||||
cfg_if::cfg_if!(
|
||||
if #[cfg(esp32s3)] {
|
||||
if let Some(block_size) = preparation.block_size {
|
||||
self.set_ext_mem_block_size(block_size.into());
|
||||
}
|
||||
} else {
|
||||
// we insure that block_size is some only for PSRAM addresses
|
||||
if preparation.block_size.is_some() {
|
||||
return Err(DmaError::UnsupportedMemoryRegion);
|
||||
}
|
||||
}
|
||||
);
|
||||
// TODO: Get burst mode from DmaBuf.
|
||||
self.tx_impl
|
||||
.prepare_transfer_without_start(preparation.start, peri)
|
||||
}
|
||||
@ -1817,6 +1881,10 @@ where
|
||||
/// Holds all the information needed to configure a DMA channel for a transfer.
|
||||
pub struct Preparation {
|
||||
start: *mut DmaDescriptor,
|
||||
/// block size for PSRAM transfers (TODO: enable burst mode for non external
|
||||
/// memory?)
|
||||
#[cfg_attr(not(esp32s3), allow(dead_code))]
|
||||
block_size: Option<DmaBufBlkSize>,
|
||||
// burst_mode, alignment, check_owner, etc.
|
||||
}
|
||||
|
||||
@ -1859,22 +1927,41 @@ pub trait DmaRxBuffer {
|
||||
|
||||
/// Error returned from Dma[Rx|Tx|RxTx]Buf operations.
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum DmaBufError {
|
||||
/// More descriptors are needed for the buffer size
|
||||
InsufficientDescriptors,
|
||||
/// Descriptors or buffers are not located in a supported memory region
|
||||
UnsupportedMemoryRegion,
|
||||
/// Buffer is not aligned to the required size
|
||||
InvalidAlignment,
|
||||
/// Invalid chunk size: must be > 0 and <= 4095
|
||||
InvalidChunkSize,
|
||||
}
|
||||
|
||||
/// DMA buffer allignments
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub enum DmaBufBlkSize {
|
||||
/// 16 bytes
|
||||
Size16 = 16,
|
||||
/// 32 bytes
|
||||
Size32 = 32,
|
||||
/// 64 bytes
|
||||
Size64 = 64,
|
||||
}
|
||||
|
||||
/// DMA transmit buffer
|
||||
///
|
||||
/// This is a contiguous buffer linked together by DMA descriptors of length
|
||||
/// 4092. It can only be used for transmitting data to a peripheral's FIFO.
|
||||
/// See [DmaRxBuf] for receiving data.
|
||||
/// 4095 at most. It can only be used for transmitting data to a peripheral's
|
||||
/// FIFO. See [DmaRxBuf] for receiving data.
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
|
||||
pub struct DmaTxBuf {
|
||||
descriptors: &'static mut [DmaDescriptor],
|
||||
buffer: &'static mut [u8],
|
||||
block_size: Option<DmaBufBlkSize>,
|
||||
}
|
||||
|
||||
impl DmaTxBuf {
|
||||
@ -1884,23 +1971,87 @@ impl DmaTxBuf {
|
||||
/// Each descriptor can handle 4092 bytes worth of buffer.
|
||||
///
|
||||
/// Both the descriptors and buffer must be in DMA-capable memory.
|
||||
/// Only DRAM is supported.
|
||||
/// Only DRAM is supported for descriptors.
|
||||
pub fn new(
|
||||
descriptors: &'static mut [DmaDescriptor],
|
||||
buffer: &'static mut [u8],
|
||||
) -> Result<Self, DmaBufError> {
|
||||
let min_descriptors = buffer.len().div_ceil(CHUNK_SIZE);
|
||||
Self::new_with_block_size(descriptors, buffer, None)
|
||||
}
|
||||
|
||||
/// Compute max chunk size based on block size
|
||||
pub const fn compute_chunk_size(block_size: Option<DmaBufBlkSize>) -> usize {
|
||||
match block_size {
|
||||
Some(size) => 4096 - size as usize,
|
||||
#[cfg(esp32)]
|
||||
None => 4092, // esp32 requires 4 byte alignment
|
||||
#[cfg(not(esp32))]
|
||||
None => 4095,
|
||||
}
|
||||
}
|
||||
|
||||
/// Compute the number of descriptors required for a given block size and
|
||||
/// buffer size
|
||||
pub const fn compute_descriptor_count(
|
||||
buffer_size: usize,
|
||||
block_size: Option<DmaBufBlkSize>,
|
||||
) -> usize {
|
||||
buffer_size.div_ceil(Self::compute_chunk_size(block_size))
|
||||
}
|
||||
|
||||
/// Creates a new [DmaTxBuf] from some descriptors and a buffer.
|
||||
///
|
||||
/// There must be enough descriptors for the provided buffer.
|
||||
/// Each descriptor can handle at most 4095 bytes worth of buffer.
|
||||
/// Optionally, a block size can be provided for PSRAM & Burst transfers.
|
||||
///
|
||||
/// Both the descriptors and buffer must be in DMA-capable memory.
|
||||
/// Only DRAM is supported for descriptors.
|
||||
pub fn new_with_block_size(
|
||||
descriptors: &'static mut [DmaDescriptor],
|
||||
buffer: &'static mut [u8],
|
||||
block_size: Option<DmaBufBlkSize>,
|
||||
) -> Result<Self, DmaBufError> {
|
||||
let chunk_size = Self::compute_chunk_size(block_size);
|
||||
let min_descriptors = Self::compute_descriptor_count(buffer.len(), block_size);
|
||||
if descriptors.len() < min_descriptors {
|
||||
return Err(DmaBufError::InsufficientDescriptors);
|
||||
}
|
||||
|
||||
if !is_slice_in_dram(descriptors) || !is_slice_in_dram(buffer) {
|
||||
// descriptors are required to be in DRAM
|
||||
if !is_slice_in_dram(descriptors) {
|
||||
return Err(DmaBufError::UnsupportedMemoryRegion);
|
||||
}
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(esp32s3)] {
|
||||
// buffer can be either DRAM or PSRAM (if supported)
|
||||
if !is_slice_in_dram(buffer) && !is_slice_in_psram(buffer) {
|
||||
return Err(DmaBufError::UnsupportedMemoryRegion);
|
||||
}
|
||||
// if its PSRAM, the block_size/alignment must be specified
|
||||
if is_slice_in_psram(buffer) && block_size.is_none() {
|
||||
return Err(DmaBufError::InvalidAlignment);
|
||||
}
|
||||
} else {
|
||||
#[cfg(any(esp32,esp32s2))]
|
||||
if buffer.len() % 4 != 0 && buffer.as_ptr() as usize % 4 != 0 {
|
||||
// ESP32 requires word alignment for DMA buffers.
|
||||
// ESP32-S2 technically supports byte-aligned DMA buffers, but the
|
||||
// transfer ends up writing out of bounds if the buffer's length
|
||||
// is 2 or 3 (mod 4).
|
||||
return Err(DmaBufError::InvalidAlignment);
|
||||
}
|
||||
// buffer can only be DRAM
|
||||
if !is_slice_in_dram(buffer) {
|
||||
return Err(DmaBufError::UnsupportedMemoryRegion);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Setup size and buffer pointer as these will not change for the remainder of
|
||||
// this object's lifetime
|
||||
let chunk_iter = descriptors.iter_mut().zip(buffer.chunks_mut(CHUNK_SIZE));
|
||||
let chunk_iter = descriptors.iter_mut().zip(buffer.chunks_mut(chunk_size));
|
||||
for (desc, chunk) in chunk_iter {
|
||||
desc.set_size(chunk.len());
|
||||
desc.buffer = chunk.as_mut_ptr();
|
||||
@ -1909,9 +2060,13 @@ impl DmaTxBuf {
|
||||
let mut buf = Self {
|
||||
descriptors,
|
||||
buffer,
|
||||
block_size,
|
||||
};
|
||||
buf.set_length(buf.capacity());
|
||||
|
||||
// no need for block size if the buffer is in DRAM
|
||||
if is_slice_in_dram(buf.buffer) {
|
||||
buf.block_size = None;
|
||||
}
|
||||
Ok(buf)
|
||||
}
|
||||
|
||||
@ -1947,7 +2102,7 @@ impl DmaTxBuf {
|
||||
assert!(len <= self.buffer.len());
|
||||
|
||||
// Get the minimum number of descriptors needed for this length of data.
|
||||
let descriptor_count = len.div_ceil(CHUNK_SIZE).max(1);
|
||||
let descriptor_count = len.div_ceil(self.descriptors[0].size()).max(1);
|
||||
let required_descriptors = &mut self.descriptors[0..descriptor_count];
|
||||
|
||||
// Link up the relevant descriptors.
|
||||
@ -2008,8 +2163,19 @@ impl DmaTxBuffer for DmaTxBuf {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(esp32s3)]
|
||||
if crate::soc::is_valid_psram_address(self.buffer.as_ptr() as u32) {
|
||||
unsafe {
|
||||
crate::soc::cache_writeback_addr(
|
||||
self.buffer.as_ptr() as u32,
|
||||
self.buffer.len() as u32,
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
Preparation {
|
||||
start: self.descriptors.as_mut_ptr(),
|
||||
block_size: self.block_size,
|
||||
}
|
||||
}
|
||||
|
||||
@ -2246,6 +2412,7 @@ impl DmaRxBuffer for DmaRxBuf {
|
||||
|
||||
Preparation {
|
||||
start: self.descriptors.as_mut_ptr(),
|
||||
block_size: None,
|
||||
}
|
||||
}
|
||||
|
||||
@ -2438,6 +2605,7 @@ impl DmaTxBuffer for DmaRxTxBuf {
|
||||
|
||||
Preparation {
|
||||
start: self.tx_descriptors.as_mut_ptr(),
|
||||
block_size: None, // TODO: support block size!
|
||||
}
|
||||
}
|
||||
|
||||
@ -2467,6 +2635,7 @@ impl DmaRxBuffer for DmaRxTxBuf {
|
||||
|
||||
Preparation {
|
||||
start: self.rx_descriptors.as_mut_ptr(),
|
||||
block_size: None, // TODO: support block size!
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -105,6 +105,13 @@ pub(crate) fn is_valid_psram_address(address: u32) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) fn is_slice_in_psram<T>(slice: &[T]) -> bool {
|
||||
let start = slice.as_ptr() as u32;
|
||||
let end = start + slice.len() as u32;
|
||||
is_valid_psram_address(start) && is_valid_psram_address(end)
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
pub(crate) fn is_valid_memory_address(address: u32) -> bool {
|
||||
is_valid_ram_address(address) || is_valid_psram_address(address)
|
||||
|
132
examples/src/bin/spi_loopback_dma_psram.rs
Normal file
132
examples/src/bin/spi_loopback_dma_psram.rs
Normal file
@ -0,0 +1,132 @@
|
||||
//! SPI loopback test using DMA - send from PSRAM receive to internal RAM
|
||||
//!
|
||||
//! The following wiring is assumed:
|
||||
//! - SCLK => GPIO42
|
||||
//! - MISO => (loopback to MOSI via peripheral_input())
|
||||
//! - MOSI => GPIO48
|
||||
//! - CS => GPIO38
|
||||
//!
|
||||
//! Depending on your target and the board you are using you have to change the
|
||||
//! pins.
|
||||
//!
|
||||
//! This example transfers data via SPI.
|
||||
//! Connect MISO and MOSI pins to see the outgoing data is read as incoming
|
||||
//! data.
|
||||
//!
|
||||
//! If your module is quad PSRAM then you need to change the `psram` feature in the
|
||||
//! in the features line below to `quad-psram`.
|
||||
|
||||
//% FEATURES: esp-hal/log esp-hal/octal-psram
|
||||
//% CHIPS: esp32s3
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
|
||||
use esp_backtrace as _;
|
||||
use esp_hal::{
|
||||
delay::Delay,
|
||||
dma::{Dma, DmaBufBlkSize, DmaPriority, DmaRxBuf, DmaTxBuf},
|
||||
gpio::Io,
|
||||
prelude::*,
|
||||
spi::{master::Spi, SpiMode},
|
||||
};
|
||||
extern crate alloc;
|
||||
use log::*;
|
||||
|
||||
macro_rules! dma_alloc_buffer {
|
||||
($size:expr, $align:expr) => {{
|
||||
let layout = core::alloc::Layout::from_size_align($size, $align).unwrap();
|
||||
unsafe {
|
||||
let ptr = alloc::alloc::alloc(layout);
|
||||
if ptr.is_null() {
|
||||
error!("dma_alloc_buffer: alloc failed");
|
||||
alloc::alloc::handle_alloc_error(layout);
|
||||
}
|
||||
core::slice::from_raw_parts_mut(ptr, $size)
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
const DMA_BUFFER_SIZE: usize = 8192;
|
||||
const DMA_ALIGNMENT: DmaBufBlkSize = DmaBufBlkSize::Size64;
|
||||
const DMA_CHUNK_SIZE: usize = 4096 - DMA_ALIGNMENT as usize;
|
||||
|
||||
#[entry]
|
||||
fn main() -> ! {
|
||||
esp_println::logger::init_logger(log::LevelFilter::Info);
|
||||
info!("Starting SPI loopback test");
|
||||
let peripherals = esp_hal::init(esp_hal::Config::default());
|
||||
esp_alloc::psram_allocator!(peripherals.PSRAM, esp_hal::psram);
|
||||
let delay = Delay::new();
|
||||
|
||||
let io = Io::new(peripherals.GPIO, peripherals.IO_MUX);
|
||||
let sclk = io.pins.gpio42;
|
||||
let mosi = io.pins.gpio48;
|
||||
let miso = mosi.peripheral_input();
|
||||
let cs = io.pins.gpio38;
|
||||
|
||||
let dma = Dma::new(peripherals.DMA);
|
||||
let dma_channel = dma.channel0;
|
||||
|
||||
let (_, tx_descriptors) =
|
||||
esp_hal::dma_descriptors_chunk_size!(0, DMA_BUFFER_SIZE, DMA_CHUNK_SIZE);
|
||||
let tx_buffer = dma_alloc_buffer!(DMA_BUFFER_SIZE, DMA_ALIGNMENT as usize);
|
||||
info!(
|
||||
"TX: {:p} len {} ({} descripters)",
|
||||
tx_buffer.as_ptr(),
|
||||
tx_buffer.len(),
|
||||
tx_descriptors.len()
|
||||
);
|
||||
let mut dma_tx_buf =
|
||||
DmaTxBuf::new_with_block_size(tx_descriptors, tx_buffer, Some(DMA_ALIGNMENT)).unwrap();
|
||||
let (rx_buffer, rx_descriptors, _, _) = esp_hal::dma_buffers!(DMA_BUFFER_SIZE, 0);
|
||||
info!(
|
||||
"RX: {:p} len {} ({} descripters)",
|
||||
rx_buffer.as_ptr(),
|
||||
rx_buffer.len(),
|
||||
rx_descriptors.len()
|
||||
);
|
||||
let mut dma_rx_buf = DmaRxBuf::new(rx_descriptors, rx_buffer).unwrap();
|
||||
let mut spi = Spi::new(peripherals.SPI2, 100.kHz(), SpiMode::Mode0)
|
||||
.with_pins(sclk, mosi, miso, cs)
|
||||
.with_dma(dma_channel.configure(false, DmaPriority::Priority0));
|
||||
|
||||
delay.delay_millis(100); // delay to let the above messages display
|
||||
|
||||
for (i, v) in dma_tx_buf.as_mut_slice().iter_mut().enumerate() {
|
||||
*v = (i % 256) as u8;
|
||||
}
|
||||
|
||||
let mut i = 0;
|
||||
|
||||
loop {
|
||||
dma_tx_buf.as_mut_slice()[0] = i;
|
||||
*dma_tx_buf.as_mut_slice().last_mut().unwrap() = i;
|
||||
i = i.wrapping_add(1);
|
||||
|
||||
let transfer = spi
|
||||
.dma_transfer(dma_rx_buf, dma_tx_buf)
|
||||
.map_err(|e| e.0)
|
||||
.unwrap();
|
||||
|
||||
(spi, (dma_rx_buf, dma_tx_buf)) = transfer.wait();
|
||||
for (i, v) in dma_tx_buf.as_mut_slice().iter_mut().enumerate() {
|
||||
if dma_rx_buf.as_slice()[i] != *v {
|
||||
error!(
|
||||
"Mismatch at index {}: expected {}, got {}",
|
||||
i,
|
||||
*v,
|
||||
dma_rx_buf.as_slice()[i]
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
info!(
|
||||
"{:0x?} .. {:0x?}",
|
||||
&dma_rx_buf.as_slice()[..10],
|
||||
&dma_rx_buf.as_slice().last_chunk::<10>().unwrap()
|
||||
);
|
||||
dma_tx_buf.as_mut_slice().reverse();
|
||||
delay.delay_millis(1000);
|
||||
}
|
||||
}
|
@ -91,6 +91,10 @@ harness = false
|
||||
name = "spi_half_duplex_write"
|
||||
harness = false
|
||||
|
||||
[[test]]
|
||||
name = "spi_half_duplex_write_psram"
|
||||
harness = false
|
||||
|
||||
[[test]]
|
||||
name = "systimer"
|
||||
harness = false
|
||||
@ -175,6 +179,7 @@ embedded-hal = "1.0.0"
|
||||
embedded-hal-02 = { version = "0.2.7", package = "embedded-hal", features = ["unproven"] }
|
||||
embedded-hal-async = "1.0.0"
|
||||
embedded-hal-nb = { version = "1.0.0", optional = true }
|
||||
esp-alloc = { path = "../esp-alloc", optional = true }
|
||||
esp-backtrace = { path = "../esp-backtrace", default-features = false, features = ["exception-handler", "panic-handler", "defmt", "semihosting"] }
|
||||
esp-hal = { path = "../esp-hal", features = ["defmt", "digest"], optional = true }
|
||||
esp-hal-embassy = { path = "../esp-hal-embassy", optional = true }
|
||||
@ -203,7 +208,7 @@ esp-metadata = { path = "../esp-metadata" }
|
||||
[features]
|
||||
default = ["embassy"]
|
||||
|
||||
defmt = ["dep:defmt-rtt"]
|
||||
defmt = ["dep:defmt-rtt", "embedded-test/defmt"]
|
||||
|
||||
# Device support (required!):
|
||||
esp32 = [
|
||||
@ -240,6 +245,7 @@ generic-queue = [
|
||||
integrated-timers = [
|
||||
"esp-hal-embassy/integrated-timers",
|
||||
]
|
||||
octal-psram = ["esp-hal/octal-psram", "dep:esp-alloc"]
|
||||
|
||||
# https://doc.rust-lang.org/cargo/reference/profiles.html#test
|
||||
# Test and bench profiles inherit from dev and release respectively.
|
||||
|
@ -201,4 +201,25 @@ mod tests {
|
||||
compute_circular_size(TX_SIZE, CHUNK_SIZE)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dma_tx_buffer() {
|
||||
use esp_hal::dma::{DmaBufError, DmaTxBuf};
|
||||
const TX_SIZE: usize = DATA_SIZE;
|
||||
|
||||
fn check(result: Result<DmaTxBuf, DmaBufError>, size: usize) {
|
||||
match result {
|
||||
Ok(tx_buf) => {
|
||||
assert_eq!(tx_buf.len(), size);
|
||||
}
|
||||
Err(_) => {
|
||||
panic!("Failed to create DmaTxBuf");
|
||||
}
|
||||
}
|
||||
}
|
||||
check(esp_hal::dma_tx_buffer!(TX_SIZE), TX_SIZE);
|
||||
check(esp_hal::dma_tx_buffer!(TX_SIZE + 1), TX_SIZE + 1);
|
||||
check(esp_hal::dma_tx_buffer!(TX_SIZE + 2), TX_SIZE + 2);
|
||||
check(esp_hal::dma_tx_buffer!(TX_SIZE + 3), TX_SIZE + 3);
|
||||
}
|
||||
}
|
||||
|
192
hil-test/tests/spi_half_duplex_write_psram.rs
Normal file
192
hil-test/tests/spi_half_duplex_write_psram.rs
Normal file
@ -0,0 +1,192 @@
|
||||
//! SPI Half Duplex Write Test
|
||||
//% FEATURES: octal-psram
|
||||
//% CHIPS: esp32s3
|
||||
|
||||
#![no_std]
|
||||
#![no_main]
|
||||
use esp_alloc as _;
|
||||
use esp_hal::{
|
||||
dma::{Dma, DmaBufBlkSize, DmaPriority, DmaRxBuf, DmaTxBuf},
|
||||
dma_buffers,
|
||||
dma_descriptors_chunk_size,
|
||||
gpio::{interconnect::InputSignal, Io},
|
||||
pcnt::{channel::EdgeMode, unit::Unit, Pcnt},
|
||||
peripherals::SPI2,
|
||||
prelude::*,
|
||||
spi::{
|
||||
master::{Address, Command, HalfDuplexReadWrite, Spi, SpiDma},
|
||||
HalfDuplexMode,
|
||||
SpiDataMode,
|
||||
SpiMode,
|
||||
},
|
||||
Blocking,
|
||||
};
|
||||
use hil_test as _;
|
||||
extern crate alloc;
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(any(
|
||||
feature = "esp32",
|
||||
feature = "esp32s2",
|
||||
))] {
|
||||
use esp_hal::dma::Spi2DmaChannel as DmaChannel0;
|
||||
} else {
|
||||
use esp_hal::dma::DmaChannel0;
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! dma_alloc_buffer {
|
||||
($size:expr, $align:expr) => {{
|
||||
let layout = core::alloc::Layout::from_size_align($size, $align).unwrap();
|
||||
unsafe {
|
||||
let ptr = alloc::alloc::alloc(layout);
|
||||
if ptr.is_null() {
|
||||
error!("dma_alloc_buffer: alloc failed");
|
||||
alloc::alloc::handle_alloc_error(layout);
|
||||
}
|
||||
core::slice::from_raw_parts_mut(ptr, $size)
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
struct Context {
|
||||
spi: SpiDma<'static, SPI2, DmaChannel0, HalfDuplexMode, Blocking>,
|
||||
pcnt_unit: Unit<'static, 0>,
|
||||
pcnt_source: InputSignal,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[embedded_test::tests]
|
||||
mod tests {
|
||||
// defmt::* is load-bearing, it ensures that the assert in dma_buffers! is not
|
||||
// using defmt's non-const assert. Doing so would result in a compile error.
|
||||
#[allow(unused_imports)]
|
||||
use defmt::{assert_eq, *};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[init]
|
||||
fn init() -> Context {
|
||||
let peripherals = esp_hal::init(esp_hal::Config::default());
|
||||
esp_alloc::psram_allocator!(peripherals.PSRAM, esp_hal::psram);
|
||||
|
||||
let io = Io::new(peripherals.GPIO, peripherals.IO_MUX);
|
||||
let sclk = io.pins.gpio0;
|
||||
let (mosi, _) = hil_test::common_test_pins!(io);
|
||||
|
||||
let pcnt = Pcnt::new(peripherals.PCNT);
|
||||
let dma = Dma::new(peripherals.DMA);
|
||||
|
||||
let dma_channel = dma.channel0;
|
||||
|
||||
let mosi_loopback = mosi.peripheral_input();
|
||||
|
||||
let spi = Spi::new_half_duplex(peripherals.SPI2, 100.kHz(), SpiMode::Mode0)
|
||||
.with_sck(sclk)
|
||||
.with_mosi(mosi)
|
||||
.with_dma(dma_channel.configure(false, DmaPriority::Priority0));
|
||||
|
||||
Context {
|
||||
spi,
|
||||
pcnt_unit: pcnt.unit0,
|
||||
pcnt_source: mosi_loopback,
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[timeout(3)]
|
||||
fn test_spi_writes_are_correctly_by_pcnt(ctx: Context) {
|
||||
const DMA_BUFFER_SIZE: usize = 4;
|
||||
const DMA_ALIGNMENT: DmaBufBlkSize = DmaBufBlkSize::Size32;
|
||||
const DMA_CHUNK_SIZE: usize = 4096 - DMA_ALIGNMENT as usize;
|
||||
|
||||
let (_, descriptors) = dma_descriptors_chunk_size!(0, DMA_BUFFER_SIZE, DMA_CHUNK_SIZE);
|
||||
let buffer = dma_alloc_buffer!(DMA_BUFFER_SIZE, DMA_ALIGNMENT as usize);
|
||||
let mut dma_tx_buf =
|
||||
DmaTxBuf::new_with_block_size(descriptors, buffer, Some(DMA_ALIGNMENT)).unwrap();
|
||||
|
||||
let unit = ctx.pcnt_unit;
|
||||
let mut spi = ctx.spi;
|
||||
|
||||
unit.channel0.set_edge_signal(ctx.pcnt_source);
|
||||
unit.channel0
|
||||
.set_input_mode(EdgeMode::Hold, EdgeMode::Increment);
|
||||
|
||||
// Fill the buffer where each byte has 3 pos edges.
|
||||
dma_tx_buf.fill(&[0b0110_1010; DMA_BUFFER_SIZE]);
|
||||
let transfer = spi
|
||||
.write(
|
||||
SpiDataMode::Single,
|
||||
Command::None,
|
||||
Address::None,
|
||||
0,
|
||||
dma_tx_buf,
|
||||
)
|
||||
.map_err(|e| e.0)
|
||||
.unwrap();
|
||||
(spi, dma_tx_buf) = transfer.wait();
|
||||
|
||||
assert_eq!(unit.get_value(), (3 * DMA_BUFFER_SIZE) as _);
|
||||
|
||||
let transfer = spi
|
||||
.write(
|
||||
SpiDataMode::Single,
|
||||
Command::None,
|
||||
Address::None,
|
||||
0,
|
||||
dma_tx_buf,
|
||||
)
|
||||
.map_err(|e| e.0)
|
||||
.unwrap();
|
||||
transfer.wait();
|
||||
|
||||
assert_eq!(unit.get_value(), (6 * DMA_BUFFER_SIZE) as _);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[timeout(3)]
|
||||
fn test_spidmabus_writes_are_correctly_by_pcnt(ctx: Context) {
|
||||
const DMA_BUFFER_SIZE: usize = 4;
|
||||
const DMA_ALIGNMENT: DmaBufBlkSize = DmaBufBlkSize::Size32; // matches dcache line size
|
||||
const DMA_CHUNK_SIZE: usize = 4096 - DMA_ALIGNMENT as usize; // 64 byte aligned
|
||||
|
||||
let (_, descriptors) = dma_descriptors_chunk_size!(0, DMA_BUFFER_SIZE, DMA_CHUNK_SIZE);
|
||||
let buffer = dma_alloc_buffer!(DMA_BUFFER_SIZE, DMA_ALIGNMENT as usize);
|
||||
let dma_tx_buf =
|
||||
DmaTxBuf::new_with_block_size(descriptors, buffer, Some(DMA_ALIGNMENT)).unwrap();
|
||||
|
||||
let (rx, rxd, _, _) = dma_buffers!(1, 0);
|
||||
let dma_rx_buf = DmaRxBuf::new(rxd, rx).unwrap();
|
||||
|
||||
let unit = ctx.pcnt_unit;
|
||||
let mut spi = ctx.spi.with_buffers(dma_rx_buf, dma_tx_buf);
|
||||
|
||||
unit.channel0.set_edge_signal(ctx.pcnt_source);
|
||||
unit.channel0
|
||||
.set_input_mode(EdgeMode::Hold, EdgeMode::Increment);
|
||||
|
||||
let buffer = [0b0110_1010; DMA_BUFFER_SIZE];
|
||||
// Write the buffer where each byte has 3 pos edges.
|
||||
spi.write(
|
||||
SpiDataMode::Single,
|
||||
Command::None,
|
||||
Address::None,
|
||||
0,
|
||||
&buffer,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(unit.get_value(), (3 * DMA_BUFFER_SIZE) as _);
|
||||
|
||||
spi.write(
|
||||
SpiDataMode::Single,
|
||||
Command::None,
|
||||
Address::None,
|
||||
0,
|
||||
&buffer,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(unit.get_value(), (6 * DMA_BUFFER_SIZE) as _);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user