From 876249d1a49c8615f5feddf3b28c7c02af32cc32 Mon Sep 17 00:00:00 2001 From: Richard Meadows <962920+richardeoin@users.noreply.github.com> Date: Sat, 17 Oct 2020 22:09:41 +0200 Subject: [PATCH 01/41] Add DMA and examples for DMA1/DMA2 --- Cargo.toml | 9 + examples/dma.rs | 105 +++++ examples/spi_dma.rs | 123 ++++++ src/dma.rs | 472 ---------------------- src/dma/dma.rs | 565 ++++++++++++++++++++++++++ src/dma/macros.rs | 26 ++ src/dma/mod.rs | 935 ++++++++++++++++++++++++++++++++++++++++++++ src/dma/traits.rs | 229 +++++++++++ src/lib.rs | 4 - src/spi.rs | 19 + 10 files changed, 2011 insertions(+), 476 deletions(-) create mode 100644 examples/dma.rs create mode 100644 examples/spi_dma.rs delete mode 100644 src/dma.rs create mode 100644 src/dma/dma.rs create mode 100644 src/dma/macros.rs create mode 100644 src/dma/mod.rs create mode 100644 src/dma/traits.rs diff --git a/Cargo.toml b/Cargo.toml index ee2c371f..68d74ffe 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,6 +25,7 @@ targets = ["thumbv7em-none-eabihf"] [dependencies] embedded-hal = "0.2.4" +embedded-dma = "0.1.2" cortex-m = "^0.6.2" cortex-m-rt = "^0.6.12" stm32h7 = "^0.12.1" @@ -172,4 +173,12 @@ required-features = ["rt", "rtc"] [[example]] name = "sai-i2s-passthru" +required-features = ["rm0433"] + +[[example]] +name = "dma" +required-features = ["rm0433"] + +[[example]] +name = "spi_dma" required-features = ["rm0433"] \ No newline at end of file diff --git a/examples/dma.rs b/examples/dma.rs new file mode 100644 index 00000000..9f5c0c98 --- /dev/null +++ b/examples/dma.rs @@ -0,0 +1,105 @@ +//! Example of Memory to Memory Transfer with the DMA + +#![deny(warnings)] +#![no_main] +#![no_std] + +use core::{mem, mem::MaybeUninit}; + +use cortex_m_rt::entry; +#[macro_use] +mod utilities; +use stm32h7xx_hal::{pac, prelude::*}; + +use stm32h7xx_hal::dma::{ + config::DmaConfig, dma::StreamsTuple, traits::Direction, MemoryToMemory, + Transfer, +}; + +use log::info; + +// DMA1/DMA2 cannot interact with our stack. Instead, buffers for use with the +// DMA must be placed somewhere that DMA1/DMA2 can access. +// +// The runtime does not initialise these SRAM banks. +#[link_section = ".sram4.buffers"] +static mut SOURCE_BUFFER: MaybeUninit<[u32; 20]> = MaybeUninit::uninit(); +#[link_section = ".axisram.buffers"] +static mut TARGET_BUFFER: MaybeUninit<[u32; 20]> = MaybeUninit::uninit(); + +#[entry] +fn main() -> ! { + utilities::logger::init(); + let dp = pac::Peripherals::take().unwrap(); + + // Constrain and Freeze power + info!("Setup PWR... "); + let pwr = dp.PWR.constrain(); + let pwrcfg = example_power!(pwr).freeze(); + + // Constrain and Freeze clock + info!("Setup RCC... "); + let rcc = dp.RCC.constrain(); + let ccdr = rcc + .sys_ck(400.mhz()) + .pll1_q_ck(200.mhz()) + .freeze(pwrcfg, &dp.SYSCFG); + + // True RNG + let mut rng = dp.RNG.constrain(ccdr.peripheral.RNG, &ccdr.clocks); + + info!(""); + info!("stm32h7xx-hal example - Memory to Memory DMA"); + info!(""); + + // Initialise the source buffer with truly random data, without taking any + // references to uninitialisated memory + let source_buffer: &'static mut [u32; 20] = { + let buf: &mut [MaybeUninit; 20] = + unsafe { mem::transmute(&mut SOURCE_BUFFER) }; + + for value in buf.iter_mut() { + unsafe { + value.as_mut_ptr().write(rng.gen().unwrap()); + } + } + unsafe { mem::transmute(buf) } + }; + // Save a copy on the stack so we can check it later + let source_buffer_cloned = source_buffer.clone(); + + // Setup DMA + // + // We need to specify the transfer size with a type annotation + + let streams = StreamsTuple::new(dp.DMA1, ccdr.peripheral.DMA1); + + let config = DmaConfig::default() + .memory_increment(true) // destination mem + .peripheral_increment(true) // source mem + .fifo_enable(true); + + let mut transfer: Transfer<_, _, MemoryToMemory, _> = Transfer::init( + streams.4, + MemoryToMemory::new(), + unsafe { mem::transmute(&mut TARGET_BUFFER) }, // Uninitialised memory + Some(source_buffer), + config, + ); + + transfer.start(|_| {}); + + // Wait for transfer to complete + while transfer.get_transfer_complete_flag() == false {} + + // Now the target memory is actually initialised + let target_buffer: &'static mut [u32; 20] = + unsafe { mem::transmute(&mut TARGET_BUFFER) }; + + // Comparison check + assert_eq!(&source_buffer_cloned, target_buffer); + + info!("Memory to Memory DMA completed successfully"); + + loop {} +} diff --git a/examples/spi_dma.rs b/examples/spi_dma.rs new file mode 100644 index 00000000..26d61e49 --- /dev/null +++ b/examples/spi_dma.rs @@ -0,0 +1,123 @@ +//! Example that transmits SPI data using the DMA + +#![deny(warnings)] +#![no_main] +#![no_std] + +use core::{mem, mem::MaybeUninit}; + +use cortex_m_rt::entry; +#[macro_use] +mod utilities; +use stm32h7xx_hal::{pac, prelude::*, spi}; + +use stm32h7xx_hal::dma::{ + config::DmaConfig, dma::StreamsTuple, MemoryToPeripheral, Transfer, +}; + +use log::info; + +// DMA1/DMA2 cannot interact with our stack. Instead, buffers for use with the +// DMA must be placed somewhere that DMA1/DMA2 can access. In this case we use +// AXI SRAM. +// +// The runtime does not initialise these SRAM banks +#[link_section = ".axisram.buffers"] +static mut BUFFER: MaybeUninit<[u8; 10]> = MaybeUninit::uninit(); + +#[entry] +fn main() -> ! { + utilities::logger::init(); + let dp = pac::Peripherals::take().unwrap(); + + // Constrain and Freeze power + info!("Setup PWR... "); + let pwr = dp.PWR.constrain(); + let pwrcfg = example_power!(pwr).freeze(); + + // Constrain and Freeze clock + info!("Setup RCC... "); + let rcc = dp.RCC.constrain(); + let ccdr = rcc + .sys_ck(400.mhz()) + .pll1_q_ck(200.mhz()) + .freeze(pwrcfg, &dp.SYSCFG); + + // Acquire the GPIOC peripheral. This also enables the clock for + // GPIOC in the RCC register. + let gpioa = dp.GPIOA.split(ccdr.peripheral.GPIOA); + let gpioc = dp.GPIOC.split(ccdr.peripheral.GPIOC); + + let sck = gpioa.pa12.into_alternate_af5(); + let miso = gpioc.pc2.into_alternate_af5(); + let mosi = gpioc.pc3.into_alternate_af5(); + let _nss = gpioa.pa11.into_alternate_af5(); + + info!(""); + info!("stm32h7xx-hal example - SPI DMA"); + info!(""); + + // Initialise the SPI peripheral. + let spi: spi::Spi<_, _, u8> = dp.SPI2.spi( + (sck, miso, mosi), + spi::MODE_0, + 1.mhz(), + ccdr.peripheral.SPI2, + &ccdr.clocks, + ); + + // SPI must be disabled to configure DMA + let mut spi = spi.disable(); + + // Initialise the source buffer, without taking any references to + // uninitialisated memory + let buffer: &'static mut [u8; 10] = { + let buf: &mut [MaybeUninit; 10] = + unsafe { mem::transmute(&mut BUFFER) }; + + for (i, value) in buf.iter_mut().enumerate() { + unsafe { + value.as_mut_ptr().write(i as u8 + 96); // 0x60, 0x61, 0x62... + } + } + unsafe { mem::transmute(buf) } + }; + + // Setup the DMA transfer on stream 0 + // + // We need to specify the direction with a type annotation, since DMA + // transfers both to and from the SPI are possible + let streams = StreamsTuple::new(dp.DMA1, ccdr.peripheral.DMA1); + + let config = DmaConfig::default().memory_increment(true); + + let mut transfer: Transfer<_, _, MemoryToPeripheral, _> = Transfer::init( + streams.0, + spi.inner_mut(), // Mutable reference to SPI register block + buffer, + None, + config, + ); + + transfer.start(|spi| { + // This closure runs right after enabling the stream + + // Enable DMA Tx buffer by setting the TXDMAEN bit in the SPI_CFG1 + // register + spi.cfg1.modify(|_, w| w.txdmaen().enabled()); + + // Enable the SPI by setting the SPE bit + spi.cr1 + .write(|w| w.ssi().slave_not_selected().spe().enabled()); + + // write CSTART to start a transaction in master mode + spi.cr1.modify(|_, w| w.cstart().started()); + }); + + // Wait for transfer to complete + while transfer.get_transfer_complete_flag() == false {} + + info!("Transfer complete!"); + + loop {} +} diff --git a/src/dma.rs b/src/dma.rs deleted file mode 100644 index 7fce1c10..00000000 --- a/src/dma.rs +++ /dev/null @@ -1,472 +0,0 @@ -//! Direct Memory Access Engine -// Adapted from -// https://github.com/stm32-rs/stm32l4xx-hal/blob/fad6d807b1a0a10bdf22549513d20b34ce2487fe/src/dma.rs -// #![allow(dead_code)] -use crate::stm32::dma1 as dmareg; - -#[derive(Debug)] -pub enum Error { - Overrun, - BufferError, - #[doc(hidden)] - _Extensible, -} - -pub enum Event { - HalfTransfer, - TransferComplete, -} - -#[derive(Clone, Copy, PartialEq)] -pub enum Half { - First, - Second, -} - -pub enum Direction { - PeripherialToMemory, - MemoryToPeripherial, - MemoryToMemory, -} - -/// Chanel level methods with defaults based on DmaInternal -pub trait DmaChannel: DmaInternal { - fn set_peripheral_address(&mut self, address: u32, inc: bool) { - self.par().write(|w| unsafe { w.pa().bits(address) }); - self.cr().modify(|_, w| w.pinc().bit(inc)); - } - - fn set_memory_address(&mut self, address: u32, inc: bool) { - unsafe { self.m0ar().write(|w| w.bits(address)) }; - self.cr().modify(|_, w| w.minc().bit(inc)); - } - - fn set_transfer_length(&mut self, len: u16) { - self.ndtr().write(|w| w.ndt().bits(len)); - } - - fn start(&mut self) { - self.cr().modify(|_, w| w.en().set_bit()); - } - - fn stop(&mut self) { - self.cr().modify(|_, w| w.en().clear_bit()); - } - - fn clear_all_interrupts(&self); - - fn in_progress(&self) -> bool; - - fn listen(&mut self, event: Event) { - match event { - Event::HalfTransfer => self.cr().modify(|_, w| w.htie().set_bit()), - Event::TransferComplete => { - self.cr().modify(|_, w| w.tcie().set_bit()) - } - } - } - - fn unlisten(&mut self, event: Event) { - match event { - Event::HalfTransfer => { - self.cr().modify(|_, w| w.htie().clear_bit()) - } - Event::TransferComplete => { - self.cr().modify(|_, w| w.tcie().clear_bit()) - } - } - } - - fn set_direction(&mut self, dir: Direction) { - match dir { - Direction::PeripherialToMemory => { - self.cr().modify(|_, w| w.dir().peripheral_to_memory()) - } - Direction::MemoryToPeripherial => { - self.cr().modify(|_, w| w.dir().memory_to_peripheral()) - } - Direction::MemoryToMemory => { - self.cr().modify(|_, w| w.dir().memory_to_memory()) - } - }; - } -} - -pub trait DmaInternal { - fn cr(&mut self) -> &dmareg::st::CR; - fn ndtr(&mut self) -> &dmareg::st::NDTR; - fn par(&mut self) -> &dmareg::st::PAR; - fn m0ar(&mut self) -> &dmareg::st::M0AR; - fn m1ar(&mut self) -> &dmareg::st::M1AR; - fn fcr(&mut self) -> &dmareg::st::FCR; - fn get_ndtr(&self) -> u32; - fn dmamux(&mut self) -> &MUX; -} - -pub trait DmaExt { - type Channels; - - fn split(self) -> Self::Channels; -} - -macro_rules! dma { - ($($DMAX:ident: ($DMAMUXX:ident, $dmamuxx:ident, $dmaX:ident, { - $($CX:ident: ( - $dmaXindex:literal, - $isrX:ident, - $ISR:ty, - $ifcrX:ident, - $IFCR:ty, - $htifX:ident, - $tcifX:ident, - $dmifX:ident, - $feifX:ident, - $teifX:ident, - $chtifX:ident, - $ctcifX:ident, - $cdmeifX:ident, - $cfeifX:ident, - $cteifX:ident, - ),)+ - }),)+) => { - $( - pub mod $dmaX { - use crate::stm32::{$DMAX, $DMAMUXX, dma1, $dmamuxx}; - use crate::dma::{DmaExt, DmaInternal, DmaChannel}; - - pub struct Channels( $(pub $CX),+); - - $( - pub struct $CX; - - impl $CX { - #[inline] - fn isr(&self) -> $ISR { - // NOTE(unsafe) atomic read with no side effects - unsafe { (*$DMAX::ptr()).$isrX.read() } - } - - #[inline] - fn ifcr(&self) -> &$IFCR { - unsafe { &(*$DMAX::ptr()).$ifcrX } - } - } - - impl DmaInternal<$dmamuxx::CCR> for $CX { - #[inline] - fn cr(&mut self) -> &dma1::st::CR { - unsafe { &(*$DMAX::ptr()).st[$dmaXindex].cr } - } - - #[inline] - fn ndtr(&mut self) -> &dma1::st::NDTR { - unsafe { &(*$DMAX::ptr()).st[$dmaXindex].ndtr } - } - - #[inline] - fn par(&mut self) -> &dma1::st::PAR { - unsafe { &(*$DMAX::ptr()).st[$dmaXindex].par} - } - - #[inline] - fn m0ar(&mut self) -> &dma1::st::M0AR { - unsafe { &(*$DMAX::ptr()).st[$dmaXindex].m0ar } - } - - #[inline] - fn m1ar(&mut self) -> &dma1::st::M1AR { - unsafe { &(*$DMAX::ptr()).st[$dmaXindex].m1ar } - } - - #[inline] - fn fcr(&mut self) -> &dma1::st::FCR { - unsafe { &(*$DMAX::ptr()).st[$dmaXindex].fcr } - } - - #[inline] - fn get_ndtr(&self) -> u32 { - // NOTE(unsafe) atomic read with no side effects - unsafe { (*$DMAX::ptr()).st[$dmaXindex].ndtr.read().bits() } - } - - #[inline] - fn dmamux(&mut self) -> &$dmamuxx::CCR { - unsafe { &(*$DMAMUXX::ptr()).ccr[$dmaXindex] } - } - } - - impl DmaChannel<$dmamuxx::CCR> for $CX { - fn in_progress(&self) -> bool { - self.isr().$tcifX().bit_is_clear() - } - - fn clear_all_interrupts(&self) { - self.ifcr().write(|w| { - w.$chtifX().set_bit() - .$ctcifX().set_bit() - .$cdmeifX().set_bit() - .$cfeifX().set_bit() - .$cteifX().set_bit() - }); - } - } - )+ - - impl DmaExt for $DMAX { - type Channels = Channels; - - fn split(self) -> Channels { - Channels( $($CX { }),+) - } - } - } - )+ - } -} - -dma! { - DMA1: (DMAMUX1, dmamux1, dma1, { - C0: ( - 0, - lisr, dma1::lisr::R, - lifcr, dma1::LIFCR, - htif0, - tcif0, - dmif0, - feif0, - teif0, - chtif0, - ctcif0, - cdmeif0, - cfeif0, - cteif0, - ), - C1: ( - 1, - lisr, dma1::lisr::R, - lifcr, dma1::LIFCR, - htif1, - tcif1, - dmif1, - feif1, - teif1, - chtif1, - ctcif1, - cdmeif1, - cfeif1, - cteif1, - ), - C2: ( - 2, - lisr, dma1::lisr::R, - lifcr, dma1::LIFCR, - htif2, - tcif2, - dmif2, - feif2, - teif2, - chtif2, - ctcif2, - cdmeif2, - cfeif2, - cteif2, - ), - C3: ( - 3, - lisr, dma1::lisr::R, - lifcr, dma1::LIFCR, - htif3, - tcif3, - dmif3, - feif3, - teif3, - chtif3, - ctcif3, - cdmeif3, - cfeif3, - cteif3, - ), - C4: ( - 4, - hisr, dma1::hisr::R, - hifcr, dma1::HIFCR, - htif4, - tcif4, - dmif4, - feif4, - teif4, - chtif4, - ctcif4, - cdmeif4, - cfeif4, - cteif4, - ), - C5: ( - 5, - hisr, dma1::hisr::R, - hifcr, dma1::HIFCR, - htif5, - tcif5, - dmif5, - feif5, - teif5, - chtif5, - ctcif5, - cdmeif5, - cfeif5, - cteif5, - ), - C6: ( - 6, - hisr, dma1::hisr::R, - hifcr, dma1::HIFCR, - htif6, - tcif6, - dmif6, - feif6, - teif6, - chtif6, - ctcif6, - cdmeif6, - cfeif6, - cteif6, - ), - C7: ( - 7, - hisr, dma1::hisr::R, - hifcr, dma1::HIFCR, - htif7, - tcif7, - dmif7, - feif7, - teif7, - chtif7, - ctcif7, - cdmeif7, - cfeif7, - cteif7, - ), - }), - DMA2: (DMAMUX2, dmamux2, dma2, { - C0: ( - 0, - lisr, dma1::lisr::R, - lifcr, dma1::LIFCR, - htif0, - tcif0, - dmif0, - feif0, - teif0, - chtif0, - ctcif0, - cdmeif0, - cfeif0, - cteif0, - ), - C1: ( - 1, - lisr, dma1::lisr::R, - lifcr, dma1::LIFCR, - htif1, - tcif1, - dmif1, - feif1, - teif1, - chtif1, - ctcif1, - cdmeif1, - cfeif1, - cteif1, - ), - C2: ( - 2, - lisr, dma1::lisr::R, - lifcr, dma1::LIFCR, - htif2, - tcif2, - dmif2, - feif2, - teif2, - chtif2, - ctcif2, - cdmeif2, - cfeif2, - cteif2, - ), - C3: ( - 3, - lisr, dma1::lisr::R, - lifcr, dma1::LIFCR, - htif3, - tcif3, - dmif3, - feif3, - teif3, - chtif3, - ctcif3, - cdmeif3, - cfeif3, - cteif3, - ), - C4: ( - 4, - hisr, dma1::hisr::R, - hifcr, dma1::HIFCR, - htif4, - tcif4, - dmif4, - feif4, - teif4, - chtif4, - ctcif4, - cdmeif4, - cfeif4, - cteif4, - ), - C5: ( - 5, - hisr, dma1::hisr::R, - hifcr, dma1::HIFCR, - htif5, - tcif5, - dmif5, - feif5, - teif5, - chtif5, - ctcif5, - cdmeif5, - cfeif5, - cteif5, - ), - C6: ( - 6, - hisr, dma1::hisr::R, - hifcr, dma1::HIFCR, - htif6, - tcif6, - dmif6, - feif6, - teif6, - chtif6, - ctcif6, - cdmeif6, - cfeif6, - cteif6, - ), - C7: ( - 7, - hisr, dma1::hisr::R, - hifcr, dma1::HIFCR, - htif7, - tcif7, - dmif7, - feif7, - teif7, - chtif7, - ctcif7, - cdmeif7, - cfeif7, - cteif7, - ), - }), -} diff --git a/src/dma/dma.rs b/src/dma/dma.rs new file mode 100644 index 00000000..873134cb --- /dev/null +++ b/src/dma/dma.rs @@ -0,0 +1,565 @@ +//! DMA1 and DMA2 + +use super::{ + config, + traits::sealed::{Bits, Sealed}, + traits::*, + CurrentBuffer, FifoLevel, MemoryToPeripheral, PeripheralToMemory, +}; +use core::marker::PhantomData; + +use crate::{ + pac::{self, DMA1, DMA2, DMAMUX1}, + rcc::{rec, rec::ResetEnable}, + //serial::{Rx, Tx}, +}; + +use core::ops::Deref; + +impl Sealed for DMA1 {} +impl Sealed for DMA2 {} + +/// Type aliases for register blocks +pub type DMARegisterBlock = pac::dma1::RegisterBlock; +pub type DMAMUXRegisterBlock = pac::dmamux1::RegisterBlock; + +/// Trait that represents an instance of a DMA peripheral +pub trait Instance: Deref + Sealed { + type Rec: ResetEnable; + + /// Gives a pointer to the RegisterBlock. + fn ptr() -> *const DMARegisterBlock; + + /// Gives a pointer to the DMAMUX used for this DMA. + fn mux_ptr() -> *const DMAMUXRegisterBlock; + + const DMA_MUX_STREAM_OFFSET: usize; +} + +// DMA1 channels 0 to 7 are connected to DMAMUX1 channels 0 to 7 +impl Instance for DMA1 { + type Rec = rec::Dma1; + + #[inline(always)] + fn ptr() -> *const DMARegisterBlock { + DMA1::ptr() + } + + #[inline(always)] + fn mux_ptr() -> *const DMAMUXRegisterBlock { + DMAMUX1::ptr() + } + + const DMA_MUX_STREAM_OFFSET: usize = 0; +} + +// DMA2 channels 0 to 7 are connected to DMAMUX1 channels 8 to 15 +impl Instance for DMA2 { + type Rec = rec::Dma2; + + #[inline(always)] + fn ptr() -> *const DMARegisterBlock { + DMA2::ptr() + } + + #[inline(always)] + fn mux_ptr() -> *const DMAMUXRegisterBlock { + DMAMUX1::ptr() + } + const DMA_MUX_STREAM_OFFSET: usize = 8; +} + +/// Stream 0 on DMA1/2 +pub struct Stream0 { + _dma: PhantomData, +} +/// Stream 1 on DMA1/2 +pub struct Stream1 { + _dma: PhantomData, +} +/// Stream 2 on DMA1/2 +pub struct Stream2 { + _dma: PhantomData, +} +/// Stream 3 on DMA1/2 +pub struct Stream3 { + _dma: PhantomData, +} +/// Stream 4 on DMA1/2 +pub struct Stream4 { + _dma: PhantomData, +} +/// Stream 5 on DMA1/2 +pub struct Stream5 { + _dma: PhantomData, +} +/// Stream 6 on DMA1/2 +pub struct Stream6 { + _dma: PhantomData, +} +/// Stream 7 on DMA1/2 +pub struct Stream7 { + _dma: PhantomData, +} + +impl Sealed for Stream0 {} +impl Sealed for Stream1 {} +impl Sealed for Stream2 {} +impl Sealed for Stream3 {} +impl Sealed for Stream4 {} +impl Sealed for Stream5 {} +impl Sealed for Stream6 {} +impl Sealed for Stream7 {} + +/// Alias for a tuple with all DMA streams. +pub struct StreamsTuple( + pub Stream0, + pub Stream1, + pub Stream2, + pub Stream3, + pub Stream4, + pub Stream5, + pub Stream6, + pub Stream7, +); + +impl StreamsTuple { + /// Splits the DMA peripheral into streams. + pub fn new(_regs: I, prec: I::Rec) -> Self { + prec.enable().reset(); + Self( + Stream0 { _dma: PhantomData }, + Stream1 { _dma: PhantomData }, + Stream2 { _dma: PhantomData }, + Stream3 { _dma: PhantomData }, + Stream4 { _dma: PhantomData }, + Stream5 { _dma: PhantomData }, + Stream6 { _dma: PhantomData }, + Stream7 { _dma: PhantomData }, + ) + } +} + +// Macro that creates a struct representing a stream on either DMA controller +// +// The implementation does the heavy lifting of mapping to the right fields on +// the stream +macro_rules! dma_stream { + ($(($name:ident, $number:expr ,$ifcr:ident, $tcif:ident, $htif:ident, + $teif:ident, $dmeif:ident, $feif:ident, $isr:ident, $tcisr:ident, + $htisr:ident) + ),+$(,)*) => { + $( + impl Stream for $name { + + const NUMBER: usize = $number; + + #[inline(always)] + fn clear_interrupts(&mut self) { + //NOTE(unsafe) Atomic write with no side-effects and we only access the bits + // that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.$ifcr.write(|w| w + .$tcif().set_bit() //Clear transfer complete interrupt flag + .$htif().set_bit() //Clear half transfer interrupt flag + .$teif().set_bit() //Clear transfer error interrupt flag + .$dmeif().set_bit() //Clear direct mode error interrupt flag + .$feif().set_bit() //Clear fifo error interrupt flag + ); + } + + #[inline(always)] + fn clear_transfer_complete_interrupt(&mut self) { + //NOTE(unsafe) Atomic write with no side-effects and we only access the bits + // that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.$ifcr.write(|w| w.$tcif().set_bit()); + } + + #[inline(always)] + fn clear_half_transfer_interrupt(&mut self) { + //NOTE(unsafe) Atomic write with no side-effects and we only access the bits + // that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.$ifcr.write(|w| w.$htif().set_bit()); + } + + #[inline(always)] + fn clear_transfer_error_interrupt(&mut self) { + //NOTE(unsafe) Atomic write with no side-effects and we only access the bits + // that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.$ifcr.write(|w| w.$teif().set_bit()); + } + + #[inline(always)] + fn clear_direct_mode_error_interrupt(&mut self) { + //NOTE(unsafe) Atomic write with no side-effects and we only access the bits + // that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.$ifcr.write(|w| w.$dmeif().set_bit()); + } + + #[inline(always)] + fn clear_fifo_error_interrupt(&mut self) { + //NOTE(unsafe) Atomic write with no side-effects and we only access the bits + // that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.$ifcr.write(|w| w.$feif().set_bit()); + } + + #[inline(always)] + fn get_transfer_complete_flag() -> bool { + //NOTE(unsafe) Atomic read with no side effects + let dma = unsafe { &*I::ptr() }; + dma.$isr.read().$tcisr().bit_is_set() + } + + #[inline(always)] + fn get_half_transfer_flag() -> bool { + //NOTE(unsafe) Atomic read with no side effects + let dma = unsafe { &*I::ptr() }; + dma.$isr.read().$htisr().bit_is_set() + } + + #[inline(always)] + unsafe fn set_peripheral_address(&mut self, value: u32) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = &*I::ptr(); + dma.st[Self::NUMBER].par.write(|w| w.pa().bits(value)); + } + + #[inline(always)] + unsafe fn set_memory_address(&mut self, value: u32) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = &*I::ptr(); + dma.st[Self::NUMBER].m0ar.write(|w| w.m0a().bits(value)); + } + + #[inline(always)] + fn get_memory_address(&self) -> u32 { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.st[Self::NUMBER].m0ar.read().m0a().bits() + } + + #[inline(always)] + unsafe fn set_memory_double_buffer_address(&mut self, value: u32) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = &*I::ptr(); + dma.st[Self::NUMBER].m1ar.write(|w| w.m1a().bits(value)); + } + + #[inline(always)] + fn get_memory_double_buffer_address(&self) -> u32 { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.st[Self::NUMBER].m1ar.read().m1a().bits() + } + + #[inline(always)] + fn set_number_of_transfers(&mut self, value: u16) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.st[Self::NUMBER].ndtr.write(|w| w.ndt().bits(value)); + } + + #[inline(always)] + fn get_number_of_transfers() -> u16 { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.st[Self::NUMBER].ndtr.read().ndt().bits() + } + + #[inline(always)] + unsafe fn enable(&mut self) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = &*I::ptr(); + dma.st[Self::NUMBER].cr.modify(|_, w| w.en().set_bit()); + } + + #[inline(always)] + fn is_enabled() -> bool { + //NOTE(unsafe) Atomic read with no side effects + let dma = unsafe { &*I::ptr() }; + dma.st[Self::NUMBER].cr.read().en().bit_is_set() + } + + fn disable(&mut self) { + if Self::is_enabled() { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + + // Aborting an on-going transfer might cause interrupts to fire, disable + // them + let (tc, ht, te, dm) = Self::get_interrupts_enable(); + self + .set_interrupts_enable(false, false, false, false); + + dma.st[Self::NUMBER].cr.modify(|_, w| w.en().clear_bit()); + while Self::is_enabled() {} + + self.clear_interrupts(); + self.set_interrupts_enable(tc, ht, te, dm); + } + } + + #[inline(always)] + fn set_request_line(&mut self, request_line: u8) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dmamux = unsafe { &*I::mux_ptr() }; + unsafe { + dmamux.ccr[Self::NUMBER + I::DMA_MUX_STREAM_OFFSET] + .modify(|_, w| w.dmareq_id().bits(request_line)); + } + } + + #[inline(always)] + fn set_priority(&mut self, priority: config::Priority) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.st[Self::NUMBER].cr.modify(|_, w| w.pl().bits(priority.bits())); + } + + #[inline(always)] + unsafe fn set_memory_size(&mut self, size: u8) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = &*I::ptr(); + dma.st[Self::NUMBER].cr.modify(|_, w| w.msize().bits(size)); + } + + #[inline(always)] + unsafe fn set_peripheral_size(&mut self, size: u8) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = &*I::ptr(); + dma.st[Self::NUMBER].cr.modify(|_, w| w.psize().bits(size)); + } + + #[inline(always)] + fn set_memory_increment(&mut self, increment: bool) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.st[Self::NUMBER].cr.modify(|_, w| w.minc().bit(increment)); + } + + #[inline(always)] + fn set_peripheral_increment(&mut self, increment: bool) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.st[Self::NUMBER].cr.modify(|_, w| w.pinc().bit(increment)); + } + + #[inline(always)] + fn set_direction(&mut self, direction: D) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.st[Self::NUMBER].cr.modify(|_, w| unsafe { w.dir().bits(direction.bits()) }); + } + + #[inline(always)] + #[cfg(not(feature = "rm0455"))] + fn set_trbuff(&mut self, trbuff: bool) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.st[Self::NUMBER].cr.modify(|_, w| w.trbuff().bit(trbuff)); + } + + #[inline(always)] + fn set_interrupts_enable( + &mut self, + transfer_complete: bool, + half_transfer: bool, + transfer_error: bool, + direct_mode_error: bool, + ) + { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.st[Self::NUMBER].cr.modify(|_, w| w + .tcie().bit(transfer_complete) + .htie().bit(half_transfer) + .teie().bit(transfer_error) + .dmeie().bit(direct_mode_error) + ); + } + + #[inline(always)] + fn get_interrupts_enable() -> (bool, bool, bool, bool) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + let cr = dma.st[Self::NUMBER].cr.read(); + (cr.tcie().bit_is_set(), cr.htie().bit_is_set(), + cr.teie().bit_is_set(), cr.dmeie().bit_is_set()) + } + + #[inline(always)] + fn set_transfer_complete_interrupt_enable(&mut self, transfer_complete_interrupt: bool) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.st[Self::NUMBER].cr.modify(|_, w| w.tcie().bit(transfer_complete_interrupt)); + } + + #[inline(always)] + fn set_half_transfer_interrupt_enable(&mut self, half_transfer_interrupt: bool) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.st[Self::NUMBER].cr.modify(|_, w| w.htie().bit(half_transfer_interrupt)); + } + + #[inline(always)] + fn set_transfer_error_interrupt_enable(&mut self, transfer_error_interrupt: bool) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.st[Self::NUMBER].cr.modify(|_, w| w.teie().bit(transfer_error_interrupt)); + } + + #[inline(always)] + fn set_direct_mode_error_interrupt_enable(&mut self, direct_mode_error_interrupt: bool) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.st[Self::NUMBER].cr.modify(|_, w| w.dmeie().bit(direct_mode_error_interrupt)); + } + + #[inline(always)] + fn set_fifo_error_interrupt_enable(&mut self, fifo_error_interrupt: bool) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.st[Self::NUMBER].fcr.modify(|_, w| w.feie().bit(fifo_error_interrupt)); + } + + #[inline(always)] + fn set_double_buffer(&mut self, double_buffer: bool) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.st[Self::NUMBER].cr.modify(|_, w| w.dbm().bit(double_buffer)); + } + + #[inline(always)] + fn set_fifo_threshold(&mut self, fifo_threshold: config::FifoThreshold) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.st[Self::NUMBER].fcr.modify(|_, w| w.fth().bits(fifo_threshold.bits())); + } + + #[inline(always)] + fn set_fifo_enable(&mut self, fifo_enable: bool) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + //Register is actually direct mode disable rather than fifo enable + dma.st[Self::NUMBER].fcr.modify(|_, w| w.dmdis().bit(fifo_enable)); + } + + #[inline(always)] + fn set_memory_burst(&mut self, memory_burst: config::BurstMode) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.st[Self::NUMBER].cr.modify(|_, w| w.mburst().bits(memory_burst.bits())); + } + + #[inline(always)] + fn set_peripheral_burst(&mut self, peripheral_burst: config::BurstMode) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.st[Self::NUMBER].cr.modify(|_, w| w.pburst().bits(peripheral_burst.bits())); + } + + #[inline(always)] + fn fifo_level() -> FifoLevel { + //NOTE(unsafe) Atomic read with no side effects + let dma = unsafe { &*I::ptr() }; + dma.st[Self::NUMBER].fcr.read().fs().bits().into() + } + + fn current_buffer() -> CurrentBuffer { + //NOTE(unsafe) Atomic read with no side effects + let dma = unsafe { &*I::ptr() }; + if dma.st[Self::NUMBER].cr.read().ct().bit_is_set() { + CurrentBuffer::DoubleBuffer + } else { + CurrentBuffer::FirstBuffer + } + } + } + )+ + }; +} + +dma_stream!( + ( + Stream0, 0, lifcr, ctcif0, chtif0, cteif0, cdmeif0, cfeif0, lisr, + tcif0, htif0 + ), + ( + Stream1, 1, lifcr, ctcif1, chtif1, cteif1, cdmeif1, cfeif1, lisr, + tcif1, htif1 + ), + ( + Stream2, 2, lifcr, ctcif2, chtif2, cteif2, cdmeif2, cfeif2, lisr, + tcif2, htif2 + ), + ( + Stream3, 3, lifcr, ctcif3, chtif3, cteif3, cdmeif3, cfeif3, lisr, + tcif3, htif3 + ), + ( + Stream4, 4, hifcr, ctcif4, chtif4, cteif4, cdmeif4, cfeif4, hisr, + tcif4, htif4 + ), + ( + Stream5, 5, hifcr, ctcif5, chtif5, cteif5, cdmeif5, cfeif5, hisr, + tcif5, htif5 + ), + ( + Stream6, 6, hifcr, ctcif6, chtif6, cteif6, cdmeif6, cfeif6, hisr, + tcif6, htif6 + ), + ( + Stream7, 7, hifcr, ctcif7, chtif7, cteif7, cdmeif7, cfeif7, hisr, + tcif7, htif7 + ), +); + +macro_rules! peripheral_register_markers { + ($($name:ident),+ $(,)*) => { + $( + /// Wrapper type that indicates which register of the contained + /// peripheral to use for DMA. + pub struct $name (pub T); + + impl Deref for $name { + type Target = T; + + #[inline(always)] + fn deref(&self) -> &T { + &self.0 + } + } + )+ + }; +} + +peripheral_register_markers!(CCR1, CCR2, CCR3, CCR4, DMAR, ARR); + +/// Type alias for the DMA Request Multiplexer +pub type DMAReq = pac::dmamux1::ccr::DMAREQ_ID_A; + +type P2M = PeripheralToMemory; +type M2P = MemoryToPeripheral; + +peripheral_target_address!( + (pac::SPI1, rxdr, u8, P2M, DMAReq::SPI1_RX_DMA), + (pac::SPI1, txdr, u8, M2P, DMAReq::SPI1_TX_DMA), + (pac::SPI2, rxdr, u8, P2M, DMAReq::SPI2_RX_DMA), + (pac::SPI2, txdr, u8, M2P, DMAReq::SPI2_TX_DMA), + (pac::SPI3, rxdr, u8, P2M, DMAReq::SPI3_RX_DMA), + (pac::SPI3, txdr, u8, M2P, DMAReq::SPI3_TX_DMA), + (pac::SPI4, rxdr, u8, P2M, DMAReq::SPI4_RX_DMA), + (pac::SPI4, txdr, u8, M2P, DMAReq::SPI4_TX_DMA), + (pac::SPI5, rxdr, u8, P2M, DMAReq::SPI5_RX_DMA), + (pac::SPI5, txdr, u8, M2P, DMAReq::SPI5_TX_DMA) +); + +peripheral_target_address!( + (pac::USART1, rdr(TRBUFF), u8, P2M, DMAReq::USART1_RX_DMA), + (pac::USART1, tdr(TRBUFF), u8, M2P, DMAReq::USART1_TX_DMA), +); diff --git a/src/dma/macros.rs b/src/dma/macros.rs new file mode 100644 index 00000000..0ebf1d75 --- /dev/null +++ b/src/dma/macros.rs @@ -0,0 +1,26 @@ +//! Macros used for implementing DMAs + +// Convenience macro for implementing target addresses on peripherals +macro_rules! peripheral_target_address { + ($( + ($peripheral:ty, $register:ident $(($TRBUFF:ident))*, $size:ty, + $dir:ty $(, $mux:expr)*) + ),+ $(,)*) => { + $( + unsafe impl TargetAddress<$dir> for &mut $peripheral { + #[inline(always)] + fn address(&self) -> u32 { + &self.$register as *const _ as u32 + } + + type MemSize = $size; + $( + const REQUEST_LINE: Option = Some($mux as u8); + )* + $( + const $TRBUFF: bool = true; + )* + } + )+ + }; +} diff --git a/src/dma/mod.rs b/src/dma/mod.rs new file mode 100644 index 00000000..e19b8bbd --- /dev/null +++ b/src/dma/mod.rs @@ -0,0 +1,935 @@ +//! Direct Memory Access. +//! +//! [Transfer::init](struct.Transfer.html#method.init) is only implemented for +//! valid combinations of peripheral-stream-channel-direction, providing compile +//! time checking. +//! +//! This module implements Memory To Memory, Peripheral To Memory and Memory to +//! Peripheral transfers, double buffering is supported only for Peripheral To +//! Memory and Memory to Peripheral transfers. +//! +//! Adapted from +//! https://github.com/stm32-rs/stm32f4xx-hal/blob/master/src/dma/mod.rs + +use core::{ + fmt::{self, Debug, Formatter}, + marker::PhantomData, + mem, + ops::Not, + ptr, + sync::atomic::{compiler_fence, Ordering}, +}; +use embedded_dma::WriteBuffer; + +#[macro_use] +mod macros; + +#[cfg(not(feature = "rm0455"))] // Remove when fixed upstream +pub mod dma; // DMA1 and DMA2 + +pub mod traits; +use traits::{sealed::Bits, Direction, Stream, TargetAddress}; + +/// Errors. +#[derive(PartialEq)] +pub enum DMAError { + /// DMA not ready to change buffers. + NotReady(T), + /// The user provided a buffer that is not big enough while double buffering. + SmallBuffer(T), + /// Overrun during a double buffering or circular transfer. + Overrun(T), +} + +// Manually implement `Debug`, so we can have debug information even with a +// buffer `T` that doesn't implement `Debug`. `T` is always a buffer type chosen +// by the user, because of that the debug information can be helpful even +// without knowing the inner type +impl Debug for DMAError { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + DMAError::NotReady(_) => f.debug_tuple("NotReady").finish(), + DMAError::SmallBuffer(_) => f.debug_tuple("SmallBuffer").finish(), + DMAError::Overrun(_) => f.debug_tuple("Overrun").finish(), + } + } +} + +/// Possible DMA's directions. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum DmaDirection { + /// Memory to Memory transfer. + MemoryToMemory, + /// Peripheral to Memory transfer. + PeripheralToMemory, + /// Memory to Peripheral transfer. + MemoryToPeripheral, +} + +/// DMA from a peripheral to a memory location. +#[derive(Debug, Clone, Copy)] +pub struct PeripheralToMemory; + +impl Bits for PeripheralToMemory { + #[inline(always)] + fn bits(self) -> u8 { + 0 + } +} + +impl Direction for PeripheralToMemory { + fn new() -> Self { + PeripheralToMemory + } + #[inline(always)] + fn direction() -> DmaDirection { + DmaDirection::PeripheralToMemory + } +} + +/// DMA from one memory location to another memory location. +#[derive(Debug, Clone, Copy)] +pub struct MemoryToMemory { + _data: PhantomData, +} + +impl Bits for MemoryToMemory { + #[inline(always)] + fn bits(self) -> u8 { + 2 + } +} + +impl Direction for MemoryToMemory { + fn new() -> Self { + Self { _data: PhantomData } + } + #[inline(always)] + fn direction() -> DmaDirection { + DmaDirection::MemoryToMemory + } +} + +/// DMA from a memory location to a peripheral. +#[derive(Debug, Clone, Copy)] +pub struct MemoryToPeripheral; + +impl Bits for MemoryToPeripheral { + #[inline(always)] + fn bits(self) -> u8 { + 1 + } +} + +impl Direction for MemoryToPeripheral { + fn new() -> Self { + MemoryToPeripheral + } + fn direction() -> DmaDirection { + DmaDirection::MemoryToPeripheral + } +} + +unsafe impl TargetAddress for MemoryToMemory { + fn address(&self) -> u32 { + unimplemented!() + } + type MemSize = u8; +} + +unsafe impl TargetAddress for MemoryToMemory { + fn address(&self) -> u32 { + unimplemented!() + } + type MemSize = u16; +} + +unsafe impl TargetAddress for MemoryToMemory { + fn address(&self) -> u32 { + unimplemented!() + } + type MemSize = u32; +} + +/// How full the DMA stream's fifo is. +#[derive(Debug, Clone, Copy)] +pub enum FifoLevel { + /// 0 < fifo_level < 1/4. + GtZeroLtQuarter, + /// 1/4 <= fifo_level < 1/2. + GteQuarterLtHalf, + /// 1/2 <= fifo_level < 3/4. + GteHalfLtThreeQuarter, + /// 3/4 <= fifo_level < full. + GteThreeQuarterLtFull, + /// Fifo is empty. + Empty, + /// Fifo is full. + Full, + /// Invalid value. + Invalid, +} + +impl From for FifoLevel { + fn from(value: u8) -> Self { + match value { + 0 => FifoLevel::GtZeroLtQuarter, + 1 => FifoLevel::GteQuarterLtHalf, + 2 => FifoLevel::GteHalfLtThreeQuarter, + 3 => FifoLevel::GteThreeQuarterLtFull, + 4 => FifoLevel::Empty, + 5 => FifoLevel::Full, + _ => FifoLevel::Invalid, + } + } +} + +/// Which DMA buffer is in use. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum CurrentBuffer { + /// The first buffer (m0ar) is in use. + FirstBuffer, + /// The second buffer (m1ar) is in use. + DoubleBuffer, +} + +impl Not for CurrentBuffer { + type Output = CurrentBuffer; + + fn not(self) -> Self::Output { + if self == CurrentBuffer::FirstBuffer { + CurrentBuffer::DoubleBuffer + } else { + CurrentBuffer::FirstBuffer + } + } +} + +/// Contains types related to DMA configuration. +pub mod config { + use super::Bits; + + /// Priority of the DMA stream, defaults to `Medium`. If two requests have + /// the same software priority level, the stream with the lower number takes + /// priority over the stream with the higher number. For example, Stream 2 + /// takes priority over Stream 4. + #[derive(Debug, Clone, Copy)] + pub enum Priority { + /// Low priority. + Low, + /// Medium priority. + Medium, + /// High priority. + High, + /// Very high priority. + VeryHigh, + } + + impl Bits for Priority { + fn bits(self) -> u8 { + match self { + Priority::Low => 0, + Priority::Medium => 1, + Priority::High => 2, + Priority::VeryHigh => 3, + } + } + } + + /// The level to fill the fifo to before performing the transaction. + #[derive(Debug, Clone, Copy)] + pub enum FifoThreshold { + /// 1/4 full. + QuarterFull, + /// 1/2 full. + HalfFull, + /// 3/4 full. + ThreeQuarterFull, + /// Full. + Full, + } + + impl Bits for FifoThreshold { + fn bits(self) -> u8 { + match self { + FifoThreshold::QuarterFull => 0, + FifoThreshold::HalfFull => 1, + FifoThreshold::ThreeQuarterFull => 2, + FifoThreshold::Full => 3, + } + } + } + + /// How burst transfers are done, requires fifo enabled. Check datasheet for + /// valid combinations. + #[derive(Debug, Clone, Copy)] + pub enum BurstMode { + /// Single transfer, no burst. + NoBurst, + /// Burst transfer of 4 beats. + Burst4, + /// Burst transfer of 8 beats. + Burst8, + /// Burst transfer of 16 beats. + Burst16, + } + + impl Bits for BurstMode { + fn bits(self) -> u8 { + match self { + BurstMode::NoBurst => 0, + BurstMode::Burst4 => 1, + BurstMode::Burst8 => 2, + BurstMode::Burst16 => 3, + } + } + } + + /// Contains the complete set of configuration for a DMA stream. + #[derive(Debug, Clone, Copy)] + pub struct DmaConfig { + pub(crate) priority: Priority, + pub(crate) memory_increment: bool, + pub(crate) peripheral_increment: bool, + pub(crate) transfer_complete_interrupt: bool, + pub(crate) half_transfer_interrupt: bool, + pub(crate) transfer_error_interrupt: bool, + pub(crate) direct_mode_error_interrupt: bool, + pub(crate) fifo_error_interrupt: bool, + pub(crate) double_buffer: bool, + pub(crate) fifo_threshold: FifoThreshold, + pub(crate) fifo_enable: bool, + pub(crate) memory_burst: BurstMode, + pub(crate) peripheral_burst: BurstMode, + } + + impl Default for DmaConfig { + fn default() -> Self { + Self { + priority: Priority::Medium, + memory_increment: false, + peripheral_increment: false, + transfer_complete_interrupt: false, + half_transfer_interrupt: false, + transfer_error_interrupt: false, + direct_mode_error_interrupt: false, + fifo_error_interrupt: false, + double_buffer: false, + fifo_threshold: FifoThreshold::QuarterFull, + fifo_enable: false, + memory_burst: BurstMode::NoBurst, + peripheral_burst: BurstMode::NoBurst, + } + } + } + + impl DmaConfig { + /// Set the priority. + #[inline(always)] + pub fn priority(mut self, priority: Priority) -> Self { + self.priority = priority; + self + } + + /// Set the memory_increment. + #[inline(always)] + pub fn memory_increment(mut self, memory_increment: bool) -> Self { + self.memory_increment = memory_increment; + self + } + /// Set the peripheral_increment. + #[inline(always)] + pub fn peripheral_increment( + mut self, + peripheral_increment: bool, + ) -> Self { + self.peripheral_increment = peripheral_increment; + self + } + /// Set the transfer_complete_interrupt. + #[inline(always)] + pub fn transfer_complete_interrupt( + mut self, + transfer_complete_interrupt: bool, + ) -> Self { + self.transfer_complete_interrupt = transfer_complete_interrupt; + self + } + /// Set the half_transfer_interrupt. + #[inline(always)] + pub fn half_transfer_interrupt( + mut self, + half_transfer_interrupt: bool, + ) -> Self { + self.half_transfer_interrupt = half_transfer_interrupt; + self + } + /// Set the transfer_error_interrupt. + #[inline(always)] + pub fn transfer_error_interrupt( + mut self, + transfer_error_interrupt: bool, + ) -> Self { + self.transfer_error_interrupt = transfer_error_interrupt; + self + } + /// Set the direct_mode_error_interrupt. + #[inline(always)] + pub fn direct_mode_error_interrupt( + mut self, + direct_mode_error_interrupt: bool, + ) -> Self { + self.direct_mode_error_interrupt = direct_mode_error_interrupt; + self + } + /// Set the fifo_error_interrupt. + #[inline(always)] + pub fn fifo_error_interrupt( + mut self, + fifo_error_interrupt: bool, + ) -> Self { + self.fifo_error_interrupt = fifo_error_interrupt; + self + } + /// Set the double_buffer. + #[inline(always)] + pub fn double_buffer(mut self, double_buffer: bool) -> Self { + self.double_buffer = double_buffer; + self + } + /// Set the fifo_threshold. + #[inline(always)] + pub fn fifo_threshold(mut self, fifo_threshold: FifoThreshold) -> Self { + self.fifo_threshold = fifo_threshold; + self + } + /// Set the fifo_enable. + #[inline(always)] + pub fn fifo_enable(mut self, fifo_enable: bool) -> Self { + self.fifo_enable = fifo_enable; + self + } + /// Set the memory_burst. + #[inline(always)] + pub fn memory_burst(mut self, memory_burst: BurstMode) -> Self { + self.memory_burst = memory_burst; + self + } + /// Set the peripheral_burst. + #[inline(always)] + pub fn peripheral_burst(mut self, peripheral_burst: BurstMode) -> Self { + self.peripheral_burst = peripheral_burst; + self + } + } +} + +/// DMA Transfer. +pub struct Transfer +where + STREAM: Stream, + PERIPHERAL: TargetAddress, + DIR: Direction, + BUF: WriteBuffer>::MemSize> + + 'static, +{ + stream: STREAM, + peripheral: PERIPHERAL, + _direction: PhantomData, + buf: Option, + double_buf: Option, + // Used when double buffering + transfer_length: u16, +} + +impl Transfer +where + STREAM: Stream, + DIR: Direction, + PERIPHERAL: TargetAddress, + BUF: WriteBuffer>::MemSize> + + 'static, +{ + /// Applies all fields in DmaConfig. + fn apply_config(&mut self, config: config::DmaConfig) { + let msize = + mem::size_of::<>::MemSize>() / 2; + + self.stream.clear_interrupts(); + self.stream.set_priority(config.priority); + // NOTE(unsafe) These values are correct because of the invariants of TargetAddress + unsafe { + self.stream.set_memory_size(msize as u8); + self.stream.set_peripheral_size(msize as u8); + } + self.stream.set_memory_increment(config.memory_increment); + self.stream + .set_peripheral_increment(config.peripheral_increment); + self.stream.set_transfer_complete_interrupt_enable( + config.transfer_complete_interrupt, + ); + self.stream + .set_half_transfer_interrupt_enable(config.half_transfer_interrupt); + self.stream.set_transfer_error_interrupt_enable( + config.transfer_error_interrupt, + ); + self.stream.set_direct_mode_error_interrupt_enable( + config.direct_mode_error_interrupt, + ); + self.stream + .set_fifo_error_interrupt_enable(config.fifo_error_interrupt); + self.stream.set_double_buffer(config.double_buffer); + self.stream.set_fifo_threshold(config.fifo_threshold); + self.stream.set_fifo_enable(config.fifo_enable); + self.stream.set_memory_burst(config.memory_burst); + self.stream.set_peripheral_burst(config.peripheral_burst); + } + + /// Configures the DMA source and destination and applies supplied + /// configuration. In a memory to memory transfer, the `double_buf` argument + /// is the source of the data. If double buffering is enabled, the number of + /// transfers will be the minimum length of `memory` and `double_buf`. + /// + /// # Panics + /// + /// * When the FIFO is disabled or double buffering is enabled in + /// `DmaConfig` while initializing a memory to memory transfer. + /// * When double buffering is enabled but the `double_buf` argument is + /// `None`. + pub fn init( + mut stream: STREAM, + peripheral: PERIPHERAL, + mut memory: BUF, + mut double_buf: Option, + config: config::DmaConfig, + ) -> Self { + stream.disable(); + + // Set peripheral to memory mode + stream.set_direction(DIR::new()); + + // Enable bufferable transfers + #[cfg(not(feature = "rm0455"))] + if PERIPHERAL::TRBUFF { + stream.set_trbuff(true); + } + + // NOTE(unsafe) We now own this buffer and we won't call any &mut + // methods on it until the end of the DMA transfer + let (buf_ptr, buf_len) = unsafe { memory.write_buffer() }; + + // Set the memory address + // + // # Safety + // + // Must be a valid memory address + unsafe { + stream.set_memory_address(buf_ptr as u32); + } + + let is_mem2mem = DIR::direction() == DmaDirection::MemoryToMemory; + if is_mem2mem { + // Fifo must be enabled for memory to memory + if !config.fifo_enable { + panic!("Fifo disabled."); + } else if config.double_buffer { + panic!("Double buffering enabled."); + } + } else { + // Set the peripheral address + // + // # Safety + // + // Must be a valid peripheral address + unsafe { + stream.set_peripheral_address(peripheral.address()); + } + } + + let db_len = if let Some(ref mut db) = double_buf { + // NOTE(unsafe) We now own this buffer and we won't call any &mut + // methods on it until the end of the DMA transfer + + let (db_ptr, db_len) = unsafe { db.write_buffer() }; + unsafe { + if is_mem2mem { + // Double buffer is the source in mem2mem mode + stream.set_peripheral_address(db_ptr as u32); + } else { + stream.set_memory_double_buffer_address(db_ptr as u32); + } + } + Some(db_len) + } else { + // Double buffer mode must not be enabled if we haven't been given a + // second buffer + if config.double_buffer { + panic!("No second buffer."); + } + None + }; + + let n_transfers = if let Some(db) = db_len { + buf_len.min(db) as u16 + } else { + buf_len as u16 + }; + stream.set_number_of_transfers(n_transfers); + + // Set the DMAMUX request line if needed + if let Some(request_line) = PERIPHERAL::REQUEST_LINE { + stream.set_request_line(request_line); + } + + let mut transfer = Self { + stream, + //_channel: PhantomData, + peripheral, + _direction: PhantomData, + buf: Some(memory), + double_buf, + transfer_length: n_transfers, + }; + transfer.apply_config(config); + + transfer + } + + /// Starts the transfer, the closure will be executed right after enabling + /// the stream. + pub fn start(&mut self, f: F) + where + F: FnOnce(&mut PERIPHERAL), + { + // "Preceding reads and writes cannot be moved past subsequent writes" + compiler_fence(Ordering::Release); + + unsafe { + self.stream.enable(); + } + f(&mut self.peripheral); + } + + /// Pauses the dma stream, the closure will be executed right before + /// disabling the stream. + pub fn pause(&mut self, f: F) + where + F: FnOnce(&mut PERIPHERAL), + { + f(&mut self.peripheral); + self.stream.disable() + } + + /// Changes the buffer and restarts or continues a double buffer + /// transfer. This must be called immediately after a transfer complete + /// event. Returns the old buffer together with its `CurrentBuffer`. If an + /// error occurs, this method will return the new buffer with the error. + /// + /// This method will clear the transfer complete flag on entry, it will also + /// clear it again if an overrun occurs during its execution. Moreover, if + /// an overrun occurs, the stream will be disabled and the transfer error + /// flag will be set. This method can be called before the end of an ongoing + /// transfer only if not using double buffering, in that case, the current + /// transfer will be canceled and a new one will be started. A `NotReady` + /// error will be returned if this method is called before the end of a + /// transfer while double buffering. + pub fn next_transfer( + &mut self, + mut new_buf: BUF, + ) -> Result<(BUF, CurrentBuffer), DMAError> { + if self.double_buf.is_some() + && DIR::direction() != DmaDirection::MemoryToMemory + { + if !STREAM::get_transfer_complete_flag() { + return Err(DMAError::NotReady(new_buf)); + } + self.stream.clear_transfer_complete_interrupt(); + // NOTE(unsafe) We now own this buffer and we won't call any &mut + // methods on it until the end of the DMA transfer + let (new_buf_ptr, new_buf_len) = unsafe { new_buf.write_buffer() }; + + // We can't change the transfer length while double buffering + if new_buf_len < usize::from(self.transfer_length) { + return Err(DMAError::SmallBuffer(new_buf)); + } + + if STREAM::current_buffer() == CurrentBuffer::DoubleBuffer { + unsafe { + self.stream.set_memory_address(new_buf_ptr as u32); + } + // Check if an overrun occurred, the buffer address won't be + // updated in that case + if self.stream.get_memory_address() != new_buf_ptr as u32 { + self.stream.clear_transfer_complete_interrupt(); + return Err(DMAError::Overrun(new_buf)); + } + + // "Subsequent reads and writes cannot be moved ahead of + // preceding reads" + compiler_fence(Ordering::Acquire); + + let old_buf = self.buf.replace(new_buf); + + // We always have a buffer, so unwrap can't fail + return Ok((old_buf.unwrap(), CurrentBuffer::FirstBuffer)); + } else { + unsafe { + self.stream + .set_memory_double_buffer_address(new_buf_ptr as u32); + } + // Check if an overrun occurred, the buffer address won't be + // updated in that case + if self.stream.get_memory_double_buffer_address() + != new_buf_ptr as u32 + { + self.stream.clear_transfer_complete_interrupt(); + return Err(DMAError::Overrun(new_buf)); + } + + // "Subsequent reads and writes cannot be moved ahead of + // preceding reads" + compiler_fence(Ordering::Acquire); + + let old_buf = self.double_buf.replace(new_buf); + + // double buffering, unwrap can never fail + return Ok((old_buf.unwrap(), CurrentBuffer::DoubleBuffer)); + } + } + self.stream.disable(); + self.stream.clear_transfer_complete_interrupt(); + + // "No re-ordering of reads and writes across this point is allowed" + compiler_fence(Ordering::SeqCst); + + // NOTE(unsafe) We now own this buffer and we won't call any &mut + // methods on it until the end of the DMA transfer + let buf_len = unsafe { + let (buf_ptr, buf_len) = new_buf.write_buffer(); + + self.stream.set_memory_address(buf_ptr as u32); + buf_len + }; + self.stream.set_number_of_transfers(buf_len as u16); + let old_buf = self.buf.replace(new_buf); + + // "Preceding reads and writes cannot be moved past subsequent writes" + compiler_fence(Ordering::Release); + + unsafe { + self.stream.enable(); + } + + Ok((old_buf.unwrap(), CurrentBuffer::FirstBuffer)) + } + + /// Stops the stream and returns the underlying resources. + pub fn free(mut self) -> (STREAM, PERIPHERAL, BUF, Option) { + self.stream.disable(); + compiler_fence(Ordering::SeqCst); + self.stream.clear_interrupts(); + + unsafe { + let stream = ptr::read(&self.stream); + let peripheral = ptr::read(&self.peripheral); + let buf = ptr::read(&self.buf); + let double_buf = ptr::read(&self.double_buf); + mem::forget(self); + (stream, peripheral, buf.unwrap(), double_buf) + } + } + + /// Clear all interrupts for the DMA stream. + #[inline(always)] + pub fn clear_interrupts(&mut self) { + self.stream.clear_interrupts(); + } + + /// Clear transfer complete interrupt (tcif) for the DMA stream. + #[inline(always)] + pub fn clear_transfer_complete_interrupt(&mut self) { + self.stream.clear_transfer_complete_interrupt(); + } + + /// Clear half transfer interrupt (htif) for the DMA stream. + #[inline(always)] + pub fn clear_half_transfer_interrupt(&mut self) { + self.stream.clear_half_transfer_interrupt(); + } + + /// Clear transfer error interrupt (teif) for the DMA stream. + #[inline(always)] + pub fn clear_transfer_error_interrupt(&mut self) { + self.stream.clear_transfer_error_interrupt(); + } + + /// Clear direct mode error interrupt (dmeif) for the DMA stream. + #[inline(always)] + pub fn clear_direct_mode_error_interrupt(&mut self) { + self.stream.clear_direct_mode_error_interrupt(); + } + + /// Clear fifo error interrupt (feif) for the DMA stream. + #[inline(always)] + pub fn clear_fifo_error_interrupt(&mut self) { + self.stream.clear_fifo_error_interrupt(); + } + + /// Get the underlying stream of the transfer. + /// + /// # Safety + /// + /// This implementation relies on several configurations points in order to + /// be sound, this method can void that. The use of this method is + /// discouraged. + pub unsafe fn get_stream(&mut self) -> &mut STREAM { + &mut self.stream + } + + #[inline(always)] + pub fn get_transfer_complete_flag(&self) -> bool { + STREAM::get_transfer_complete_flag() + } + + /// Changes the buffer and restarts or continues a double buffer + /// transfer. This must be called immediately after a transfer complete + /// event. The closure must return `(BUF, T)` where `BUF` is the new buffer + /// to be used. This method can be called before the end of an ongoing + /// transfer only if not using double buffering, in that case, the current + /// transfer will be canceled and a new one will be started. A `NotReady` + /// error will be returned if this method is called before the end of a + /// transfer while double buffering and the closure won't be executed. + /// + /// # Panics + /// + /// This method will panic when double buffering and one or both of the + /// following conditions happen: + /// + /// * The new buffer's length is smaller than the one used in the `init` method. + /// * The closure `f` takes too long to return and a buffer overrun occurs. + /// + /// # Safety + /// + /// Memory corruption might occur in the previous buffer, the one passed to + /// the closure, if an overrun occurs in double buffering mode. + /// + /// # Panics + /// + /// This method will panic if an overrun is detected while double buffering. + pub unsafe fn next_transfer_with( + &mut self, + f: F, + ) -> Result> + where + F: FnOnce(BUF, CurrentBuffer) -> (BUF, T), + { + if self.double_buf.is_some() + && DIR::direction() != DmaDirection::MemoryToMemory + { + if !STREAM::get_transfer_complete_flag() { + return Err(DMAError::NotReady(())); + } + self.stream.clear_transfer_complete_interrupt(); + + let current_buffer = STREAM::current_buffer(); + // double buffering, unwrap can never fail + let db = if current_buffer == CurrentBuffer::DoubleBuffer { + self.buf.take().unwrap() + } else { + self.double_buf.take().unwrap() + }; + let r = f(db, !current_buffer); + let mut new_buf = r.0; + let (new_buf_ptr, new_buf_len) = new_buf.write_buffer(); + + // We can't change the transfer length while double buffering + assert!( + new_buf_len >= usize::from(self.transfer_length), + "Second Buffer not big enough" + ); + + // We don't know how long the closure took to complete, we might + // have changed the current buffer twice (or any even number of + // times) and got back to the same buffer we had in the beginning of + // the method, check for that + if STREAM::get_transfer_complete_flag() { + // If this is true, then RAM corruption might have occurred, + // there's nothing we can do apart from panicking. TODO: Is + // this the best solution ? The closure based approach seems + // necessary if we want to support BBqueue. + panic!("Overrun"); + } + + if current_buffer == CurrentBuffer::DoubleBuffer { + self.stream.set_memory_address(new_buf_ptr as u32); + + // Check again if an overrun occurred, the buffer address won't + // be updated in that case + if self.stream.get_memory_address() != new_buf_ptr as u32 { + panic!("Overrun"); + } + + // "Subsequent reads and writes cannot be moved ahead of + // preceding reads" + compiler_fence(Ordering::Acquire); + + self.buf.replace(new_buf); + return Ok(r.1); + } else { + self.stream + .set_memory_double_buffer_address(new_buf_ptr as u32); + if self.stream.get_memory_double_buffer_address() + != new_buf_ptr as u32 + { + panic!("Overrun"); + } + + // "Subsequent reads and writes cannot be moved ahead of + // preceding reads" + compiler_fence(Ordering::Acquire); + + self.double_buf.replace(new_buf); + return Ok(r.1); + } + } + self.stream.disable(); + self.stream.clear_transfer_complete_interrupt(); + + // "No re-ordering of reads and writes across this point is allowed" + compiler_fence(Ordering::SeqCst); + + // Can never fail, we never let the Transfer without a buffer + let old_buf = self.buf.take().unwrap(); + let r = f(old_buf, CurrentBuffer::FirstBuffer); + let mut new_buf = r.0; + + let (buf_ptr, buf_len) = new_buf.write_buffer(); + self.stream.set_memory_address(buf_ptr as u32); + self.stream.set_number_of_transfers(buf_len as u16); + self.buf.replace(new_buf); + + // "Preceding reads and writes cannot be moved past subsequent writes" + compiler_fence(Ordering::Release); + + self.stream.enable(); + + Ok(r.1) + } +} + +impl Drop + for Transfer +where + STREAM: Stream, + PERIPHERAL: TargetAddress, + DIR: Direction, + BUF: WriteBuffer>::MemSize> + + 'static, +{ + fn drop(&mut self) { + self.stream.disable(); + + // "No re-ordering of reads and writes across this point is allowed" + compiler_fence(Ordering::SeqCst); + } +} diff --git a/src/dma/traits.rs b/src/dma/traits.rs new file mode 100644 index 00000000..6d0efd4e --- /dev/null +++ b/src/dma/traits.rs @@ -0,0 +1,229 @@ +//! Traits for DMA types +//! +//! Adapted from +//! https://github.com/stm32-rs/stm32f4xx-hal/blob/master/src/dma/traits.rs + +use super::*; + +pub(crate) mod sealed { + /// Converts value to bits for setting a register value. + pub trait Bits { + /// Returns the bit value. + fn bits(self) -> T; + } + pub trait Sealed {} +} +use sealed::{Bits, Sealed}; + +/// Trait for DMA streams types. +pub trait Stream: Sealed { + /// Number of the register stream. + const NUMBER: usize; + + /// Clear all interrupts for the DMA stream. + fn clear_interrupts(&mut self); + + /// Clear transfer complete interrupt (tcif) for the DMA stream. + fn clear_transfer_complete_interrupt(&mut self); + + /// Clear half transfer interrupt (htif) for the DMA stream. + fn clear_half_transfer_interrupt(&mut self); + + /// Clear transfer error interrupt (teif) for the DMA stream. + fn clear_transfer_error_interrupt(&mut self); + + /// Clear direct mode error interrupt (dmeif) for the DMA stream. + fn clear_direct_mode_error_interrupt(&mut self); + + /// Clear fifo error interrupt (feif) for the DMA stream. + fn clear_fifo_error_interrupt(&mut self); + + /// Get transfer complete flag. + fn get_transfer_complete_flag() -> bool; + + /// Get half transfer flag. + fn get_half_transfer_flag() -> bool; + + /// Set the peripheral address (par) for the DMA stream. + unsafe fn set_peripheral_address(&mut self, value: u32); + + /// Set the memory address (m0ar) for the DMA stream. + unsafe fn set_memory_address(&mut self, value: u32); + + /// Get the memory address (m0ar) for the DMA stream. + fn get_memory_address(&self) -> u32; + + /// Set the double buffer address (m1ar) for the DMA stream. + unsafe fn set_memory_double_buffer_address(&mut self, value: u32); + + /// Get the double buffer address (m1ar) for the DMA stream. + fn get_memory_double_buffer_address(&self) -> u32; + + /// Set the number of transfers (ndt) for the DMA stream. + fn set_number_of_transfers(&mut self, value: u16); + + /// Get the number of transfers (ndt) for the DMA stream. + fn get_number_of_transfers() -> u16; + + /// Enable the DMA stream. + /// + /// # Safety + /// + /// The user must ensure that all registers are properly configured. + unsafe fn enable(&mut self); + + /// Returns the state of the DMA stream. + fn is_enabled() -> bool; + + /// Disable the DMA stream. + /// + /// Disabling the stream during an on-going transfer needs to be performed + /// in a certain way to prevent problems if the stream is to be re-enabled + /// shortly after, because of that, this method will also clear all the + /// stream's interrupt flags if the stream is active. + fn disable(&mut self); + + /// Sets the corresponding DMAMUX request line for this stream + fn set_request_line(&mut self, request_line: u8); + + /// Set the priority (pl) the DMA stream. + fn set_priority(&mut self, priority: config::Priority); + + /// Set the memory size (msize) for the DMA stream. + /// + /// # Safety + /// + /// This must have the same alignment of the buffer used in the transfer. + /// + /// Valid values: + /// * 0 -> byte + /// * 1 -> half word + /// * 2 -> word + unsafe fn set_memory_size(&mut self, size: u8); + + /// Set the peripheral memory size (psize) for the DMA stream. + /// + /// # Safety + /// + /// This must have the same alignment of the peripheral data used in the + /// transfer. + /// + /// Valid values: + /// * 0 -> byte + /// * 1 -> half word + /// * 2 -> word + unsafe fn set_peripheral_size(&mut self, size: u8); + + /// Enable/disable memory increment (minc) for the DMA stream. + fn set_memory_increment(&mut self, increment: bool); + + /// Enable/disable peripheral increment (pinc) for the DMA stream. + fn set_peripheral_increment(&mut self, increment: bool); + + /// Set the direction (dir) of the DMA stream. + fn set_direction(&mut self, direction: D); + + #[cfg(not(feature = "rm0455"))] + /// Enable bufferable transfers + fn set_trbuff(&mut self, trbuff: bool); + + /// Convenience method to configure the 4 common interrupts for the DMA stream. + fn set_interrupts_enable( + &mut self, + transfer_complete: bool, + half_transfer: bool, + transfer_error: bool, + direct_mode_error: bool, + ); + + /// Convenience method to get the value of the 4 common interrupts for the + /// DMA stream. + /// + /// The order of the returns are: `transfer_complete`, `half_transfer`, + /// `transfer_error` and `direct_mode_error`. + fn get_interrupts_enable() -> (bool, bool, bool, bool); + + /// Enable/disable the transfer complete interrupt (tcie) of the DMA stream. + fn set_transfer_complete_interrupt_enable( + &mut self, + transfer_complete_interrupt: bool, + ); + + /// Enable/disable the half transfer interrupt (htie) of the DMA stream. + fn set_half_transfer_interrupt_enable( + &mut self, + half_transfer_interrupt: bool, + ); + + /// Enable/disable the transfer error interrupt (teie) of the DMA stream. + fn set_transfer_error_interrupt_enable( + &mut self, + transfer_error_interrupt: bool, + ); + + /// Enable/disable the direct mode error interrupt (dmeie) of the DMA stream. + fn set_direct_mode_error_interrupt_enable( + &mut self, + direct_mode_error_interrupt: bool, + ); + + /// Enable/disable the fifo error interrupt (feie) of the DMA stream. + fn set_fifo_error_interrupt_enable(&mut self, fifo_error_interrupt: bool); + + /// Enable/disable the double buffer (dbm) of the DMA stream. + fn set_double_buffer(&mut self, double_buffer: bool); + + /// Set the fifo threshold (fcr.fth) of the DMA stream. + fn set_fifo_threshold(&mut self, fifo_threshold: config::FifoThreshold); + + /// Enable/disable the fifo (dmdis) of the DMA stream. + fn set_fifo_enable(&mut self, fifo_enable: bool); + + /// Set memory burst mode (mburst) of the DMA stream. + fn set_memory_burst(&mut self, memory_burst: config::BurstMode); + + /// Set peripheral burst mode (pburst) of the DMA stream. + fn set_peripheral_burst(&mut self, peripheral_burst: config::BurstMode); + + /// Get the current fifo level (fs) of the DMA stream. + fn fifo_level() -> FifoLevel; + + /// Get which buffer is currently in use by the DMA. + fn current_buffer() -> CurrentBuffer; +} + +/// DMA direction. +pub trait Direction: Bits { + /// Creates a new instance of the type. + fn new() -> Self; + + /// Returns the `DmaDirection` of the type. + fn direction() -> DmaDirection; +} + +/// Mark a target that the DMA can use. This is a peripheral (PeripheralToMemory +/// or MemoryToPeripheral) or a memory (MemoryToMemory) +/// +/// This trait is generic over transfer direction, so a given target can +/// implement this trait multiple times for different directions. +/// +/// Each implementation has an associated memory size (u32/u16/u8) and +/// optionally an associated request line in the DMA's DMAMUX. +/// +/// # Safety +/// +/// Both the memory size and the address must be correct for the memory region +/// and for the DMA. +pub unsafe trait TargetAddress { + /// Memory size of the target address + type MemSize; + + /// The address to be used by the DMA stream + fn address(&self) -> u32; + + /// An optional associated request line + const REQUEST_LINE: Option = None; + + /// Mark that the TRBUFF bit must be set for this target + const TRBUFF: bool = false; +} diff --git a/src/lib.rs b/src/lib.rs index 891a3a87..19b0e460 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -134,10 +134,6 @@ pub mod adc; pub mod dac; #[cfg(feature = "device-selected")] pub mod delay; -#[deprecated( - since = "0.8.0", - note = "The DMA API will be completely replaced in a future release" -)] #[cfg(feature = "device-selected")] pub mod dma; #[cfg(all( diff --git a/src/spi.rs b/src/spi.rs index 3c4453eb..125d852f 100644 --- a/src/spi.rs +++ b/src/spi.rs @@ -536,6 +536,20 @@ macro_rules! spi { } } + /// Enables the Rx DMA stream. If the DMA Rx is used, the + /// reference manual recommends that this is enabled before + /// enabling the DMA + pub fn enable_dma_rx(&mut self) { + self.spi.cfg1.modify(|_,w| w.rxdmaen().enabled()); + } + + /// Enables the Tx DMA stream. If the DMA Tx is used, the + /// reference manual recommends that this is enabled after + /// enablign the DMA + pub fn enable_dma_tx(&mut self) { + self.spi.cfg1.modify(|_,w| w.txdmaen().enabled()); + } + /// Deconstructs the SPI peripheral and returns the component parts. pub fn free(self) -> ($SPIX, rec::$Rec) { (self.spi, rec::$Rec { _marker: PhantomData }) @@ -544,6 +558,11 @@ macro_rules! spi { impl Spi<$SPIX, EN, $TY> { + /// Returns a mutable reference to the inner peripheral + pub fn inner_mut(&mut self) -> &mut $SPIX { + &mut self.spi + } + /// Enable interrupts for the given `event`: /// - Received data ready to be read (RXP) /// - Transmit data register empty (TXP) From b111beea30d6b3d08fc8ec9c9c08ad048726e63d Mon Sep 17 00:00:00 2001 From: Richard Meadows <962920+richardeoin@users.noreply.github.com> Date: Sat, 17 Oct 2020 23:07:54 +0200 Subject: [PATCH 02/41] Mark AXISRAM as NOLOAD (as well as SRAM3/4) Even though some debug probes might load it, we certainly shouldn't assume that in the examples. --- memory.x | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/memory.x b/memory.x index 3af6352a..e10ddffe 100644 --- a/memory.x +++ b/memory.x @@ -46,7 +46,7 @@ _stack_start = ORIGIN(RAM) + LENGTH(RAM); /* These sections are used for some of the examples */ SECTIONS { - .axisram : ALIGN(8) { + .axisram (NOLOAD) : ALIGN(8) { *(.axisram .axisram.*); . = ALIGN(8); } > AXISRAM From 0cca09e4d30f8b1dfdfcdbff0679ec0b00431b95 Mon Sep 17 00:00:00 2001 From: Richard Meadows <962920+richardeoin@users.noreply.github.com> Date: Sun, 18 Oct 2020 23:18:31 +0200 Subject: [PATCH 03/41] Add BDMA, work in progress The BDMA does not support all of the functionality in Stream. For now unsupported methods are just left as no-ops. --- src/dma/bdma.rs | 481 ++++++++++++++++++++++++++++++++++++++++++++++ src/dma/dma.rs | 13 +- src/dma/mod.rs | 26 +-- src/dma/traits.rs | 6 +- 4 files changed, 498 insertions(+), 28 deletions(-) create mode 100644 src/dma/bdma.rs diff --git a/src/dma/bdma.rs b/src/dma/bdma.rs new file mode 100644 index 00000000..074e5063 --- /dev/null +++ b/src/dma/bdma.rs @@ -0,0 +1,481 @@ +//! BDMA + +use super::{ + config, + traits::sealed::{Bits, Sealed}, + traits::*, + CurrentBuffer, DmaDirection, FifoLevel, MemoryToPeripheral, + PeripheralToMemory, +}; +use core::marker::PhantomData; + +use crate::{ + pac::{self, BDMA, DMAMUX2}, + rcc::{rec, rec::ResetEnable}, + //serial::{Rx, Tx}, +}; + +use core::ops::Deref; + +impl Sealed for BDMA {} + +/// Type aliases for register blocks +pub type BDMARegisterBlock = pac::bdma::RegisterBlock; +pub type DMAMUXRegisterBlock = pac::dmamux2::RegisterBlock; + +/// Trait that represents an instance of a BDMA peripheral +pub trait Instance: Deref + Sealed { + type Rec: ResetEnable; + + /// Gives a pointer to the RegisterBlock. + fn ptr() -> *const BDMARegisterBlock; + + /// Gives a pointer to the DMAMUX used for this DMA. + fn mux_ptr() -> *const DMAMUXRegisterBlock; + + const DMA_MUX_STREAM_OFFSET: usize; +} + +impl Instance for BDMA { + type Rec = rec::Bdma; + + #[inline(always)] + fn ptr() -> *const BDMARegisterBlock { + BDMA::ptr() + } + + #[inline(always)] + fn mux_ptr() -> *const DMAMUXRegisterBlock { + DMAMUX2::ptr() + } + + const DMA_MUX_STREAM_OFFSET: usize = 0; +} + +/// Stream 0 on BDMA +pub struct Stream0 { + _dma: PhantomData, +} +/// Stream 1 on BDMA +pub struct Stream1 { + _dma: PhantomData, +} +/// Stream 2 on BDMA +pub struct Stream2 { + _dma: PhantomData, +} +/// Stream 3 on BDMA +pub struct Stream3 { + _dma: PhantomData, +} +/// Stream 4 on BDMA +pub struct Stream4 { + _dma: PhantomData, +} +/// Stream 5 on BDMA +pub struct Stream5 { + _dma: PhantomData, +} +/// Stream 6 on BDMA +pub struct Stream6 { + _dma: PhantomData, +} +/// Stream 7 on BDMA +pub struct Stream7 { + _dma: PhantomData, +} + +impl Sealed for Stream0 {} +impl Sealed for Stream1 {} +impl Sealed for Stream2 {} +impl Sealed for Stream3 {} +impl Sealed for Stream4 {} +impl Sealed for Stream5 {} +impl Sealed for Stream6 {} +impl Sealed for Stream7 {} + +/// Alias for a tuple with all DMA streams. +pub struct StreamsTuple( + pub Stream0, + pub Stream1, + pub Stream2, + pub Stream3, + pub Stream4, + pub Stream5, + pub Stream6, + pub Stream7, +); + +impl StreamsTuple { + /// Splits the DMA peripheral into streams. + pub fn new(_regs: I, prec: I::Rec) -> Self { + prec.enable().reset(); + Self( + Stream0 { _dma: PhantomData }, + Stream1 { _dma: PhantomData }, + Stream2 { _dma: PhantomData }, + Stream3 { _dma: PhantomData }, + Stream4 { _dma: PhantomData }, + Stream5 { _dma: PhantomData }, + Stream6 { _dma: PhantomData }, + Stream7 { _dma: PhantomData }, + ) + } +} + +// Macro that creates a struct representing a stream on either DMA controller +// +// The implementation does the heavy lifting of mapping to the right fields on +// the stream +macro_rules! bdma_stream { + ($(($name:ident, $number:expr, + $ifcr:ident, $tcif:ident, $htif:ident, $teif:ident, $gif:ident, + $isr:ident, $tcisr:ident, $htisr:ident) + ),+$(,)*) => { + $( + impl Stream for $name { + + const NUMBER: usize = $number; + + #[inline(always)] + fn clear_interrupts(&mut self) { + //NOTE(unsafe) Atomic write with no side-effects and we only access the bits + // that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.$ifcr.write(|w| w + .$tcif().set_bit() //Clear transfer complete interrupt flag + .$htif().set_bit() //Clear half transfer interrupt flag + .$teif().set_bit() //Clear transfer error interrupt flag + .$gif().set_bit() //Clear global interrupt flag + ); + } + + #[inline(always)] + fn clear_transfer_complete_interrupt(&mut self) { + //NOTE(unsafe) Atomic write with no side-effects and we only access the bits + // that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.$ifcr.write(|w| w.$tcif().set_bit()); + } + + #[inline(always)] + fn clear_half_transfer_interrupt(&mut self) { + //NOTE(unsafe) Atomic write with no side-effects and we only access the bits + // that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.$ifcr.write(|w| w.$htif().set_bit()); + } + + #[inline(always)] + fn clear_transfer_error_interrupt(&mut self) { + //NOTE(unsafe) Atomic write with no side-effects and we only access the bits + // that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.$ifcr.write(|w| w.$teif().set_bit()); + } + + #[inline(always)] + fn clear_direct_mode_error_interrupt(&mut self) { + + } + + #[inline(always)] + fn clear_fifo_error_interrupt(&mut self) { + + } + + #[inline(always)] + fn get_transfer_complete_flag() -> bool { + //NOTE(unsafe) Atomic read with no side effects + let dma = unsafe { &*I::ptr() }; + dma.$isr.read().$tcisr().bit_is_set() + } + + #[inline(always)] + fn get_half_transfer_flag() -> bool { + //NOTE(unsafe) Atomic read with no side effects + let dma = unsafe { &*I::ptr() }; + dma.$isr.read().$htisr().bit_is_set() + } + + #[inline(always)] + unsafe fn set_peripheral_address(&mut self, value: u32) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = &*I::ptr(); + dma.ch[Self::NUMBER].par.write(|w| w.pa().bits(value)); + } + + #[inline(always)] + unsafe fn set_memory_address(&mut self, value: u32) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = &*I::ptr(); + dma.ch[Self::NUMBER].m0ar.write(|w| w.ma().bits(value)); + } + + #[inline(always)] + fn get_memory_address(&self) -> u32 { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.ch[Self::NUMBER].m0ar.read().ma().bits() + } + + #[inline(always)] + unsafe fn set_memory_double_buffer_address(&mut self, value: u32) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = &*I::ptr(); + dma.ch[Self::NUMBER].m1ar.write(|w| w.ma().bits(value)); + } + + #[inline(always)] + fn get_memory_double_buffer_address(&self) -> u32 { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.ch[Self::NUMBER].m1ar.read().ma().bits() + } + + #[inline(always)] + fn set_number_of_transfers(&mut self, value: u16) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.ch[Self::NUMBER].ndtr.write(|w| w.ndt().bits(value)); + } + + #[inline(always)] + fn get_number_of_transfers() -> u16 { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.ch[Self::NUMBER].ndtr.read().ndt().bits() + } + + #[inline(always)] + unsafe fn enable(&mut self) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = &*I::ptr(); + dma.ch[Self::NUMBER].cr.modify(|_, w| w.en().set_bit()); + } + + #[inline(always)] + fn is_enabled() -> bool { + //NOTE(unsafe) Atomic read with no side effects + let dma = unsafe { &*I::ptr() }; + dma.ch[Self::NUMBER].cr.read().en().bit_is_set() + } + + fn disable(&mut self) { + if Self::is_enabled() { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + + // Aborting an on-going transfer might cause interrupts to fire, disable + // them + let (tc, ht, te, dm) = Self::get_interrupts_enable(); + self + .set_interrupts_enable(false, false, false, false); + + dma.ch[Self::NUMBER].cr.modify(|_, w| w.en().clear_bit()); + while Self::is_enabled() {} + + self.clear_interrupts(); + self.set_interrupts_enable(tc, ht, te, dm); + } + } + + #[inline(always)] + fn set_request_line(&mut self, request_line: u8) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dmamux = unsafe { &*I::mux_ptr() }; + unsafe { + dmamux.ccr[Self::NUMBER + I::DMA_MUX_STREAM_OFFSET] + .modify(|_, w| w.dmareq_id().bits(request_line)); + } + } + + #[inline(always)] + fn set_priority(&mut self, priority: config::Priority) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.ch[Self::NUMBER].cr.modify(|_, w| w.pl().bits(priority.bits())); + } + + #[inline(always)] + unsafe fn set_memory_size(&mut self, size: u8) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = &*I::ptr(); + dma.ch[Self::NUMBER].cr.modify(|_, w| w.msize().bits(size)); + } + + #[inline(always)] + unsafe fn set_peripheral_size(&mut self, size: u8) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = &*I::ptr(); + dma.ch[Self::NUMBER].cr.modify(|_, w| w.psize().bits(size)); + } + + #[inline(always)] + fn set_memory_increment(&mut self, increment: bool) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.ch[Self::NUMBER].cr.modify(|_, w| w.minc().bit(increment)); + } + + #[inline(always)] + fn set_peripheral_increment(&mut self, increment: bool) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.ch[Self::NUMBER].cr.modify(|_, w| w.pinc().bit(increment)); + } + + #[inline(always)] + fn set_direction(&mut self, direction: DmaDirection) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.ch[Self::NUMBER].cr.modify(|_, w| { + match direction { + DmaDirection::PeripheralToMemory => + w.dir().peripheral_to_memory().mem2mem().disabled(), + DmaDirection::MemoryToPeripheral => + w.dir().memory_to_peripheral().mem2mem().disabled(), + DmaDirection::MemoryToMemory => + w.mem2mem().enabled().dir().clear_bit(), + } + }); + } + + #[inline(always)] + #[cfg(not(feature = "rm0455"))] + fn set_trbuff(&mut self, _trbuff: bool) { + + } + + #[inline(always)] + fn set_interrupts_enable( + &mut self, + transfer_complete: bool, + half_transfer: bool, + transfer_error: bool, + _direct_mode_error: bool, + ) + { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.ch[Self::NUMBER].cr.modify(|_, w| w + .tcie().bit(transfer_complete) + .htie().bit(half_transfer) + .teie().bit(transfer_error) + // No dmeie + ); + } + + #[inline(always)] + fn get_interrupts_enable() -> (bool, bool, bool, bool) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + let cr = dma.ch[Self::NUMBER].cr.read(); + (cr.tcie().bit_is_set(), cr.htie().bit_is_set(), + cr.teie().bit_is_set(), false) // No dmeie + } + + #[inline(always)] + fn set_transfer_complete_interrupt_enable(&mut self, transfer_complete_interrupt: bool) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.ch[Self::NUMBER].cr.modify(|_, w| w.tcie().bit(transfer_complete_interrupt)); + } + + #[inline(always)] + fn set_half_transfer_interrupt_enable(&mut self, half_transfer_interrupt: bool) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.ch[Self::NUMBER].cr.modify(|_, w| w.htie().bit(half_transfer_interrupt)); + } + + #[inline(always)] + fn set_transfer_error_interrupt_enable(&mut self, transfer_error_interrupt: bool) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.ch[Self::NUMBER].cr.modify(|_, w| w.teie().bit(transfer_error_interrupt)); + } + + #[inline(always)] + fn set_direct_mode_error_interrupt_enable(&mut self, _direct_mode_error_interrupt: bool) { + + } + + #[inline(always)] + fn set_fifo_error_interrupt_enable(&mut self, _fifo_error_interrupt: bool) { + + } + + #[inline(always)] + fn set_double_buffer(&mut self, double_buffer: bool) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.ch[Self::NUMBER].cr.modify(|_, w| w.dbm().bit(double_buffer)); + } + + #[inline(always)] + fn set_fifo_threshold(&mut self, _fifo_threshold: config::FifoThreshold) { + + } + + #[inline(always)] + fn set_fifo_enable(&mut self, _fifo_enable: bool) { + + } + + #[inline(always)] + fn set_memory_burst(&mut self, _memory_burst: config::BurstMode) { + + } + + #[inline(always)] + fn set_peripheral_burst(&mut self, _peripheral_burst: config::BurstMode) { + + } + + #[inline(always)] + fn fifo_level() -> FifoLevel { + FifoLevel::Invalid + } + + fn current_buffer() -> CurrentBuffer { + //NOTE(unsafe) Atomic read with no side effects + let dma = unsafe { &*I::ptr() }; + if dma.ch[Self::NUMBER].cr.read().ct().bit_is_set() { + CurrentBuffer::DoubleBuffer + } else { + CurrentBuffer::FirstBuffer + } + } + } + )+ + }; +} + +bdma_stream!( + // Note: the field names start from one, unlike the RM where they start from + // zero. May need updating if it gets fixed upstream. + (Stream0, 0, ifcr, ctcif1, chtif1, cteif1, cgif1, isr, tcif1, htif1), + (Stream1, 1, ifcr, ctcif2, chtif2, cteif2, cgif2, isr, tcif2, htif2), + (Stream2, 2, ifcr, ctcif3, chtif3, cteif3, cgif3, isr, tcif3, htif3), + (Stream3, 3, ifcr, ctcif4, chtif4, cteif4, cgif4, isr, tcif4, htif4), + (Stream4, 4, ifcr, ctcif5, chtif5, cteif5, cgif5, isr, tcif5, htif5), + (Stream5, 5, ifcr, ctcif6, chtif6, cteif6, cgif6, isr, tcif6, htif6), + (Stream6, 6, ifcr, ctcif7, chtif7, cteif7, cgif7, isr, tcif7, htif7), + (Stream7, 7, ifcr, ctcif8, chtif8, cteif8, cgif8, isr, tcif8, htif8), +); + +/// Type alias for the DMA Request Multiplexer +pub type DMAReq = pac::dmamux2::ccr::DMAREQ_ID_A; + +type P2M = PeripheralToMemory; +type M2P = MemoryToPeripheral; + +peripheral_target_address!( + (pac::LPUART1, rdr, u8, P2M, DMAReq::LPUART1_RX_DMA), + (pac::LPUART1, tdr, u8, M2P, DMAReq::LPUART1_TX_DMA), + (pac::SPI6, rxdr, u8, P2M, DMAReq::SPI6_RX_DMA), + (pac::SPI6, txdr, u8, M2P, DMAReq::SPI6_TX_DMA), + (pac::I2C4, rxdr, u8, P2M, DMAReq::I2C4_RX_DMA), + (pac::I2C4, txdr, u8, M2P, DMAReq::I2C4_TX_DMA), +); diff --git a/src/dma/dma.rs b/src/dma/dma.rs index 873134cb..764761ea 100644 --- a/src/dma/dma.rs +++ b/src/dma/dma.rs @@ -4,7 +4,8 @@ use super::{ config, traits::sealed::{Bits, Sealed}, traits::*, - CurrentBuffer, FifoLevel, MemoryToPeripheral, PeripheralToMemory, + CurrentBuffer, DmaDirection, FifoLevel, MemoryToPeripheral, + PeripheralToMemory, }; use core::marker::PhantomData; @@ -350,10 +351,16 @@ macro_rules! dma_stream { } #[inline(always)] - fn set_direction(&mut self, direction: D) { + fn set_direction(&mut self, direction: DmaDirection) { //NOTE(unsafe) We only access the registers that belongs to the StreamX let dma = unsafe { &*I::ptr() }; - dma.st[Self::NUMBER].cr.modify(|_, w| unsafe { w.dir().bits(direction.bits()) }); + dma.st[Self::NUMBER].cr.modify(|_, w| unsafe { + match direction { + DmaDirection::PeripheralToMemory => w.dir().bits(0), + DmaDirection::MemoryToPeripheral => w.dir().bits(1), + DmaDirection::MemoryToMemory => w.dir().bits(2), + } + }); } #[inline(always)] diff --git a/src/dma/mod.rs b/src/dma/mod.rs index e19b8bbd..d60c499c 100644 --- a/src/dma/mod.rs +++ b/src/dma/mod.rs @@ -27,6 +27,9 @@ mod macros; #[cfg(not(feature = "rm0455"))] // Remove when fixed upstream pub mod dma; // DMA1 and DMA2 +#[cfg(not(feature = "rm0455"))] // Remove when fixed upstream +pub mod bdma; + pub mod traits; use traits::{sealed::Bits, Direction, Stream, TargetAddress}; @@ -70,13 +73,6 @@ pub enum DmaDirection { #[derive(Debug, Clone, Copy)] pub struct PeripheralToMemory; -impl Bits for PeripheralToMemory { - #[inline(always)] - fn bits(self) -> u8 { - 0 - } -} - impl Direction for PeripheralToMemory { fn new() -> Self { PeripheralToMemory @@ -93,13 +89,6 @@ pub struct MemoryToMemory { _data: PhantomData, } -impl Bits for MemoryToMemory { - #[inline(always)] - fn bits(self) -> u8 { - 2 - } -} - impl Direction for MemoryToMemory { fn new() -> Self { Self { _data: PhantomData } @@ -114,13 +103,6 @@ impl Direction for MemoryToMemory { #[derive(Debug, Clone, Copy)] pub struct MemoryToPeripheral; -impl Bits for MemoryToPeripheral { - #[inline(always)] - fn bits(self) -> u8 { - 1 - } -} - impl Direction for MemoryToPeripheral { fn new() -> Self { MemoryToPeripheral @@ -506,7 +488,7 @@ where stream.disable(); // Set peripheral to memory mode - stream.set_direction(DIR::new()); + stream.set_direction(DIR::direction()); // Enable bufferable transfers #[cfg(not(feature = "rm0455"))] diff --git a/src/dma/traits.rs b/src/dma/traits.rs index 6d0efd4e..2bde670f 100644 --- a/src/dma/traits.rs +++ b/src/dma/traits.rs @@ -13,7 +13,7 @@ pub(crate) mod sealed { } pub trait Sealed {} } -use sealed::{Bits, Sealed}; +use sealed::Sealed; /// Trait for DMA streams types. pub trait Stream: Sealed { @@ -121,7 +121,7 @@ pub trait Stream: Sealed { fn set_peripheral_increment(&mut self, increment: bool); /// Set the direction (dir) of the DMA stream. - fn set_direction(&mut self, direction: D); + fn set_direction(&mut self, direction: DmaDirection); #[cfg(not(feature = "rm0455"))] /// Enable bufferable transfers @@ -193,7 +193,7 @@ pub trait Stream: Sealed { } /// DMA direction. -pub trait Direction: Bits { +pub trait Direction { /// Creates a new instance of the type. fn new() -> Self; From e74deb2111bb38e5c9514d5e2b07dfa2f315a210 Mon Sep 17 00:00:00 2001 From: Richard Meadows <962920+richardeoin@users.noreply.github.com> Date: Sun, 18 Oct 2020 23:22:20 +0200 Subject: [PATCH 04/41] Experiment with implementing TargetAddress on a HAL structure As well as the PAC structure. Also add the option to include an extra layer of indirection for the target register --- src/dma/bdma.rs | 3 +++ src/dma/macros.rs | 58 +++++++++++++++++++++++++++++++++++++++-------- src/i2c.rs | 5 ++++ 3 files changed, 56 insertions(+), 10 deletions(-) diff --git a/src/dma/bdma.rs b/src/dma/bdma.rs index 074e5063..116c9847 100644 --- a/src/dma/bdma.rs +++ b/src/dma/bdma.rs @@ -10,6 +10,7 @@ use super::{ use core::marker::PhantomData; use crate::{ + i2c::I2c, pac::{self, BDMA, DMAMUX2}, rcc::{rec, rec::ResetEnable}, //serial::{Rx, Tx}, @@ -478,4 +479,6 @@ peripheral_target_address!( (pac::SPI6, txdr, u8, M2P, DMAReq::SPI6_TX_DMA), (pac::I2C4, rxdr, u8, P2M, DMAReq::I2C4_RX_DMA), (pac::I2C4, txdr, u8, M2P, DMAReq::I2C4_TX_DMA), + (INNER, I2c, rxdr, u8, P2M, DMAReq::I2C4_RX_DMA), + (INNER, I2c, txdr, u8, M2P, DMAReq::I2C4_TX_DMA), ); diff --git a/src/dma/macros.rs b/src/dma/macros.rs index 0ebf1d75..dd4a8a8c 100644 --- a/src/dma/macros.rs +++ b/src/dma/macros.rs @@ -3,24 +3,62 @@ // Convenience macro for implementing target addresses on peripherals macro_rules! peripheral_target_address { ($( - ($peripheral:ty, $register:ident $(($TRBUFF:ident))*, $size:ty, - $dir:ty $(, $mux:expr)*) + $peripheral:tt ),+ $(,)*) => { $( - unsafe impl TargetAddress<$dir> for &mut $peripheral { - #[inline(always)] - fn address(&self) -> u32 { - &self.$register as *const _ as u32 - } + peripheral_target_instance!($peripheral); + )+ + }; +} +macro_rules! peripheral_target_instance { + (($peripheral:ty, $register:ident $(($TRBUFF:ident))*, $size:ty, + $dir:ty $(, $mux:expr)*)) => { + unsafe impl TargetAddress<$dir> for &mut $peripheral { + #[inline(always)] + fn address(&self) -> u32 { + &self.$register as *const _ as u32 + } - type MemSize = $size; + type MemSize = $size; + $( + const REQUEST_LINE: Option = Some($mux as u8); + )* $( - const REQUEST_LINE: Option = Some($mux as u8); + const $TRBUFF: bool = true; )* + } + }; + + ((INNER, $peripheral:ty, $register:ident $(($TRBUFF:ident))*, $size:ty, + $dir:ty $(, $mux:expr)*)) => { + unsafe impl TargetAddress<$dir> for &mut $peripheral { + #[inline(always)] + fn address(&self) -> u32 { + &self.inner().$register as *const _ as u32 + } + + type MemSize = $size; + $( + const REQUEST_LINE: Option = Some($mux as u8); + )* $( const $TRBUFF: bool = true; )* + } + }; + + (($peripheral:ty, $channel:ident.$register:ident, $size:ty, + $dir:ty $(, $mux:expr)*)) => { + unsafe impl TargetAddress<$dir> for &mut $peripheral { + #[inline(always)] + fn address(&self) -> u32 { + &self.$channel.$register as *const _ as u32 } - )+ + + type MemSize = $size; + $( + const REQUEST_LINE: Option = Some($mux as u8); + )* + } }; } diff --git a/src/i2c.rs b/src/i2c.rs index 8b303e00..2d8d207b 100644 --- a/src/i2c.rs +++ b/src/i2c.rs @@ -313,6 +313,11 @@ macro_rules! i2c { I2c { i2c } } + /// Returns a reference to the inner peripheral + pub fn inner(&self) -> &$I2CX { + &self.i2c + } + /// Start listening for `event` pub fn listen(&mut self, event: Event) { self.i2c.cr1.modify(|_,w| { From b178f3a11f9a8b611a0467e1e0f27383a12ee946 Mon Sep 17 00:00:00 2001 From: Richard Meadows <962920+richardeoin@users.noreply.github.com> Date: Sun, 18 Oct 2020 23:44:38 +0200 Subject: [PATCH 05/41] Experiment with DMA methods for I2C, add example with I2C4 and BDMA --- Cargo.toml | 4 ++ examples/i2c4_bdma.rs | 127 ++++++++++++++++++++++++++++++++++++++++++ src/i2c.rs | 29 +++++++--- 3 files changed, 151 insertions(+), 9 deletions(-) create mode 100644 examples/i2c4_bdma.rs diff --git a/Cargo.toml b/Cargo.toml index 68d74ffe..1bc53162 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -179,6 +179,10 @@ required-features = ["rm0433"] name = "dma" required-features = ["rm0433"] +[[example]] +name = "i2c4_bdma" +required-features = ["rm0433"] + [[example]] name = "spi_dma" required-features = ["rm0433"] \ No newline at end of file diff --git a/examples/i2c4_bdma.rs b/examples/i2c4_bdma.rs new file mode 100644 index 00000000..6a8df64d --- /dev/null +++ b/examples/i2c4_bdma.rs @@ -0,0 +1,127 @@ +//! I2C4 in low power mode. +//! +//! + +#![allow(clippy::transmute_ptr_to_ptr)] +#![deny(warnings)] +#![no_std] +#![no_main] + +use core::{mem, mem::MaybeUninit}; + +#[macro_use] +mod utilities; + +use stm32h7xx_hal::dma::{ + bdma::StreamsTuple, config::DmaConfig, PeripheralToMemory, Transfer, +}; + +use stm32h7xx_hal::prelude::*; +use stm32h7xx_hal::{i2c, pac, pac::interrupt, rcc::LowPowerMode}; + +use cortex_m_rt::entry; + +use log::info; + +// The BDMA can only interact with SRAM4. +// +// The runtime does not initialise this SRAM bank +#[link_section = ".sram4.buffers"] +static mut BUFFER: MaybeUninit<[u8; 10]> = MaybeUninit::uninit(); + +#[entry] +fn main() -> ! { + utilities::logger::init(); + let cp = cortex_m::Peripherals::take().unwrap(); + let dp = pac::Peripherals::take().expect("Cannot take peripherals"); + + // Run D3 / SRD domain + dp.PWR.cpucr.modify(|_, w| w.run_d3().set_bit()); + + let pwr = dp.PWR.constrain(); + let pwrcfg = example_power!(pwr).freeze(); + + // RCC + let rcc = dp.RCC.constrain(); + let ccdr = rcc + .sys_ck(400.mhz()) + // D3 / SRD domain + .hclk(200.mhz()) // rcc_hclk4 + .pclk4(50.mhz()) // rcc_pclk4 + .freeze(pwrcfg, &dp.SYSCFG); + + // GPIO + let gpiod = dp.GPIOD.split(ccdr.peripheral.GPIOD); + + // Configure the SCL and the SDA pin for our I2C bus + let scl = gpiod.pd12.into_alternate_af4().set_open_drain(); + let sda = gpiod.pd13.into_alternate_af4().set_open_drain(); + + let mut i2c = dp.I2C4.i2c( + (scl, sda), + 100.khz(), + ccdr.peripheral.I2C4.low_power(LowPowerMode::Autonomous), + &ccdr.clocks, + ); + + // Use RX DMA + i2c.rx_dma(true); + + // Listen for the end of i2c transactions + i2c.clear_irq(i2c::Event::Stop); + i2c.listen(i2c::Event::Stop); + unsafe { + cortex_m::peripheral::NVIC::unmask(pac::Interrupt::I2C4_EV); + } + + // Setup the DMA transfer on stream 0 + // + // We need to specify the direction with a type annotation + let streams = StreamsTuple::new( + dp.BDMA, + ccdr.peripheral.BDMA.low_power(LowPowerMode::Autonomous), + ); + + let config = DmaConfig::default().memory_increment(true); + + let mut transfer: Transfer<_, _, PeripheralToMemory, _> = Transfer::init( + streams.0, + &mut i2c, // Mutable reference to I2C HAL + unsafe { &mut BUFFER }, // uninitialised memory + None, + config, + ); + + transfer.start(|i2c| { + // This closure runs right after enabling the stream + + // Issue the first part of the I2C transaction + // + // We use a dummy buffer to tell the I2C HAL the length of the + // transaction + + let mut pt: [u8; 10] = [0; 10]; + // Read data from a random touchscreen + i2c.write_read(0x28 >> 1, &[0x41, 0xE4], &mut pt).unwrap(); + }); + + // Enter CStop mode on wfi + let mut scb = cp.SCB; + scb.set_sleepdeep(); + + loop { + cortex_m::asm::wfi(); + } +} + +#[interrupt] +fn I2C4_EV() { + info!("I2C transfer complete!"); + + // Look at BUFFER, which we expect to be initialised + let buffer: &'static mut [u8; 10] = unsafe { mem::transmute(&mut BUFFER) }; + + assert_eq!(buffer[0], 0xBE); + + loop {} +} diff --git a/src/i2c.rs b/src/i2c.rs index 2d8d207b..40e2db2e 100644 --- a/src/i2c.rs +++ b/src/i2c.rs @@ -82,6 +82,7 @@ where #[derive(Debug)] pub struct I2c { i2c: I2C, + rx_dma_enable: bool, } pub trait I2cExt: Sized { @@ -310,7 +311,7 @@ macro_rules! i2c { // Enable the peripheral i2c.cr1.write(|w| w.pe().set_bit()); - I2c { i2c } + I2c { i2c, rx_dma_enable: false } } /// Returns a reference to the inner peripheral @@ -318,6 +319,12 @@ macro_rules! i2c { &self.i2c } + /// Enable the DMA mode for reception + pub fn rx_dma(&mut self, enable: bool) { + self.rx_dma_enable = enable; + self.i2c.cr1.modify(|_,w| w.rxdmaen().bit(enable)); + } + /// Start listening for `event` pub fn listen(&mut self, event: Event) { self.i2c.cr1.modify(|_,w| { @@ -529,11 +536,13 @@ macro_rules! i2c { .automatic() }); - for byte in buffer { - // Wait until we have received something - busy_wait!(self.i2c, rxne, is_not_empty); + if !self.rx_dma_enable { + for byte in buffer { + // Wait until we have received something + busy_wait!(self.i2c, rxne, is_not_empty); - *byte = self.i2c.rxdr.read().rxdata().bits(); + *byte = self.i2c.rxdr.read().rxdata().bits(); + } } // automatic STOP @@ -574,11 +583,13 @@ macro_rules! i2c { .automatic() }); - for byte in buffer { - // Wait until we have received something - busy_wait!(self.i2c, rxne, is_not_empty); + if !self.rx_dma_enable { + for byte in buffer { + // Wait until we have received something + busy_wait!(self.i2c, rxne, is_not_empty); - *byte = self.i2c.rxdr.read().rxdata().bits(); + *byte = self.i2c.rxdr.read().rxdata().bits(); + } } // automatic STOP From a1a719c66af92dade87712b0da08d3a6c6bb2d1e Mon Sep 17 00:00:00 2001 From: Richard Meadows <962920+richardeoin@users.noreply.github.com> Date: Sun, 18 Oct 2020 23:46:03 +0200 Subject: [PATCH 06/41] Tidy clippy warnings Allow use of mem::transmute to elide the intermediate types --- examples/dma.rs | 1 + examples/spi_dma.rs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/dma.rs b/examples/dma.rs index 9f5c0c98..1731e41c 100644 --- a/examples/dma.rs +++ b/examples/dma.rs @@ -1,5 +1,6 @@ //! Example of Memory to Memory Transfer with the DMA +#![allow(clippy::transmute_ptr_to_ptr)] #![deny(warnings)] #![no_main] #![no_std] diff --git a/examples/spi_dma.rs b/examples/spi_dma.rs index 26d61e49..ecc316cb 100644 --- a/examples/spi_dma.rs +++ b/examples/spi_dma.rs @@ -1,5 +1,6 @@ //! Example that transmits SPI data using the DMA +#![allow(clippy::transmute_ptr_to_ptr)] #![deny(warnings)] #![no_main] #![no_std] @@ -115,7 +116,7 @@ fn main() -> ! { }); // Wait for transfer to complete - while transfer.get_transfer_complete_flag() == false {} + while !transfer.get_transfer_complete_flag() {} info!("Transfer complete!"); From a8d58b5f526b34e715f18b1fe8b9c6ebcf335d74 Mon Sep 17 00:00:00 2001 From: Richard Meadows <962920+richardeoin@users.noreply.github.com> Date: Wed, 21 Oct 2020 21:52:54 +0200 Subject: [PATCH 07/41] Refactor I2C to expose infallible methods for parts of transcations These are useful for DMA. --- examples/i2c4_bdma.rs | 16 +-- src/i2c.rs | 278 ++++++++++++++++++++++++++---------------- 2 files changed, 181 insertions(+), 113 deletions(-) diff --git a/examples/i2c4_bdma.rs b/examples/i2c4_bdma.rs index 6a8df64d..7309e2bd 100644 --- a/examples/i2c4_bdma.rs +++ b/examples/i2c4_bdma.rs @@ -95,14 +95,14 @@ fn main() -> ! { transfer.start(|i2c| { // This closure runs right after enabling the stream - // Issue the first part of the I2C transaction - // - // We use a dummy buffer to tell the I2C HAL the length of the - // transaction - - let mut pt: [u8; 10] = [0; 10]; - // Read data from a random touchscreen - i2c.write_read(0x28 >> 1, &[0x41, 0xE4], &mut pt).unwrap(); + // Issue the first part of an I2C transaction to read data from a + // touchscreen + + // Write register index + i2c.write(0xBA >> 1, &[0x41, 0xE4]).unwrap(); + + // Start a read of 10 bytes + i2c.master_read(0xBA >> 1, 10, i2c::Stop::Automatic); }); // Enter CStop mode on wfi diff --git a/src/i2c.rs b/src/i2c.rs index 40e2db2e..9f8c4c78 100644 --- a/src/i2c.rs +++ b/src/i2c.rs @@ -18,7 +18,7 @@ use cast::u16; /// I2C Events /// -/// Each event is a possible interrupt sources, if enabled +/// Each event is a possible interrupt source, if enabled #[derive(Copy, Clone, PartialEq)] pub enum Event { /// (TXIE) @@ -35,6 +35,18 @@ pub enum Event { NotAcknowledge, } +/// I2C Stop Configuration +/// +/// Peripheral options for generating the STOP condition +#[derive(Copy, Clone, PartialEq)] +pub enum Stop { + /// Software end mode: Must write register to generate STOP condition + Software, + /// Automatic end mode: A STOP condition is automatically generated once the + /// configured number of bytes have been transferred + Automatic, +} + /// I2C error #[derive(Debug)] pub enum Error { @@ -82,7 +94,6 @@ where #[derive(Debug)] pub struct I2c { i2c: I2C, - rx_dma_enable: bool, } pub trait I2cExt: Sized { @@ -311,7 +322,7 @@ macro_rules! i2c { // Enable the peripheral i2c.cr1.write(|w| w.pe().set_bit()); - I2c { i2c, rx_dma_enable: false } + I2c { i2c } } /// Returns a reference to the inner peripheral @@ -319,12 +330,16 @@ macro_rules! i2c { &self.i2c } - /// Enable the DMA mode for reception + /// Enable or disable the DMA mode for reception pub fn rx_dma(&mut self, enable: bool) { - self.rx_dma_enable = enable; self.i2c.cr1.modify(|_,w| w.rxdmaen().bit(enable)); } + /// Enable or disable the DMA mode for transmission + pub fn tx_dma(&mut self, enable: bool) { + self.i2c.cr1.modify(|_,w| w.txdmaen().bit(enable)); + } + /// Start listening for `event` pub fn listen(&mut self, event: Event) { self.i2c.cr1.modify(|_,w| { @@ -368,13 +383,133 @@ macro_rules! i2c { }); } - /// Releases the I2C peripheral pub fn free(self) -> ($I2CX, rec::$Rec) { (self.i2c, rec::$Rec { _marker: PhantomData }) } } + /// Master controller methods + /// + /// These infallible methods are used to begin or end parts of + /// transactions, but do __not__ read or write the data + /// registers. If you want to perform an entire transcation see the + /// [Read](I2c#impl-Read) and [Write](I2c#impl-Write) + /// implementations. + /// + /// If a previous transcation is still in progress, then these + /// methods will block until that transcation is complete. A + /// previous transaction can still be "in progress" up to 50% of a + /// bus cycle after a ACK/NACK event. Otherwise these methods return + /// immediately. + impl I2c<$I2CX> { + /// Master read + /// + /// Perform an I2C start and prepare to receive `length` bytes. + /// + /// ``` + /// Master: ST SAD+R ... (SP) + /// Slave: ... + /// ``` + pub fn master_read(&mut self, addr: u8, length: usize, stop: Stop) { + assert!(length < 256 && length > 0); + + // Wait for any previous address sequence to end + // automatically. This could be up to 50% of a bus + // cycle (ie. up to 0.5/freq) + while self.i2c.cr2.read().start().bit_is_set() {}; + + // Set START and prepare to receive bytes into + // `buffer`. The START bit can be set even if the bus + // is BUSY or I2C is in slave mode. + self.i2c.cr2.write(|w| { + w.sadd() + .bits((addr << 1 | 0) as u16) + .rd_wrn() + .read() + .nbytes() + .bits(length as u8) + .start() + .set_bit() + .autoend() + .bit(stop == Stop::Automatic) + }); + } + /// Master write + /// + /// Perform an I2C start and prepare to send `length` bytes. + /// + /// ``` + /// Master: ST SAD+W ... (SP) + /// Slave: ... + /// ``` + pub fn master_write(&mut self, addr: u8, length: usize, stop: Stop) { + assert!(length < 256 && length > 0); + + // Wait for any previous address sequence to end + // automatically. This could be up to 50% of a bus + // cycle (ie. up to 0.5/freq) + while self.i2c.cr2.read().start().bit_is_set() {}; + + // Set START and prepare to send `bytes`. The + // START bit can be set even if the bus is BUSY or + // I2C is in slave mode. + self.i2c.cr2.write(|w| { + w.start() + .set_bit() + .sadd() + .bits(u16(addr << 1 | 0)) + .add10().clear_bit() + .rd_wrn() + .write() + .nbytes() + .bits(length as u8) + .autoend() + .bit(stop == Stop::Automatic) + }); + } + + /// Master restart + /// + /// Performs an I2C restart following a write phase and prepare + /// to receive `length` bytes. The I2C peripheral is configured + /// to provide an automatic stop. + /// + /// ``` + /// Master: ... SR SAD+R ... (SP) + /// Slave: ... ... + /// ``` + pub fn master_re_start(&mut self, addr: u8, length: usize, stop: Stop) { + assert!(length < 256 && length > 0); + + self.i2c.cr2.write(|w| { + w.sadd() + .bits(u16(addr << 1 | 1)) + .add10().clear_bit() + .rd_wrn() + .read() + .nbytes() + .bits(length as u8) + .start() + .set_bit() + .autoend() + .bit(stop == Stop::Automatic) + }); + } + + /// Master stop + /// + /// Generate a stop condition. + /// + /// ``` + /// Master: ... SP + /// Slave: ... + /// ``` + pub fn master_stop(&mut self) { + self.i2c.cr2.write(|w| w.stop().set_bit()); + } + } + impl I2cExt<$I2CX> for $I2CX { type Rec = rec::$Rec; @@ -432,27 +567,10 @@ macro_rules! i2c { // TODO support transfers of more than 255 bytes assert!(bytes.len() < 256 && bytes.len() > 0); - // Wait for any previous address sequence to end - // automatically. This could be up to 50% of a bus - // cycle (ie. up to 0.5/freq) - while self.i2c.cr2.read().start().bit_is_set() {}; - - // Set START and prepare to send `bytes`. The - // START bit can be set even if the bus is BUSY or - // I2C is in slave mode. - self.i2c.cr2.write(|w| { - w.start() - .set_bit() - .sadd() - .bits(u16(addr << 1 | 0)) - .add10().clear_bit() - .rd_wrn() - .write() - .nbytes() - .bits(bytes.len() as u8) - .autoend() - .software() - }); + // I2C start + // + // ST SAD+W + self.master_write(addr, bytes.len(), Stop::Software); for byte in bytes { // Wait until we are allowed to send data @@ -468,7 +586,7 @@ macro_rules! i2c { busy_wait!(self.i2c, tc, is_complete); // Stop - self.i2c.cr2.write(|w| w.stop().set_bit()); + self.master_stop(); Ok(()) } @@ -487,27 +605,10 @@ macro_rules! i2c { assert!(bytes.len() < 256 && bytes.len() > 0); assert!(buffer.len() < 256 && buffer.len() > 0); - // Wait for any previous address sequence to end - // automatically. This could be up to 50% of a bus - // cycle (ie. up to 0.5/freq) - while self.i2c.cr2.read().start().bit_is_set() {}; - - // Set START and prepare to send `bytes`. The - // START bit can be set even if the bus is BUSY or - // I2C is in slave mode. - self.i2c.cr2.write(|w| { - w.start() - .set_bit() - .sadd() - .bits(u16(addr << 1 | 0)) - .add10().clear_bit() - .rd_wrn() - .write() - .nbytes() - .bits(bytes.len() as u8) - .autoend() - .software() - }); + // I2C start + // + // ST SAD+W + self.master_write(addr, bytes.len(), Stop::Software); for byte in bytes { // Wait until we are allowed to send data @@ -521,28 +622,16 @@ macro_rules! i2c { // Wait until the write finishes before beginning to read. busy_wait!(self.i2c, tc, is_complete); - // reSTART and prepare to receive bytes into `buffer` - self.i2c.cr2.write(|w| { - w.sadd() - .bits(u16(addr << 1 | 1)) - .add10().clear_bit() - .rd_wrn() - .read() - .nbytes() - .bits(buffer.len() as u8) - .start() - .set_bit() - .autoend() - .automatic() - }); + // I2C re-start + // + // SR SAD+R + self.master_re_start(addr, buffer.len(), Stop::Automatic); - if !self.rx_dma_enable { - for byte in buffer { - // Wait until we have received something - busy_wait!(self.i2c, rxne, is_not_empty); + for byte in buffer { + // Wait until we have received something + busy_wait!(self.i2c, rxne, is_not_empty); - *byte = self.i2c.rxdr.read().rxdata().bits(); - } + *byte = self.i2c.rxdr.read().rxdata().bits(); } // automatic STOP @@ -552,50 +641,29 @@ macro_rules! i2c { } impl Read for I2c<$I2CX> { - type Error = Error; - - fn read( - &mut self, - addr: u8, - buffer: &mut [u8], - ) -> Result<(), Error> { - // TODO support transfers of more than 255 bytes - assert!(buffer.len() < 256 && buffer.len() > 0); - - // Wait for any previous address sequence to end - // automatically. This could be up to 50% of a bus - // cycle (ie. up to 0.5/freq) - while self.i2c.cr2.read().start().bit_is_set() {}; - - // Set START and prepare to receive bytes into - // `buffer`. The START bit can be set even if the bus - // is BUSY or I2C is in slave mode. - self.i2c.cr2.write(|w| { - w.sadd() - .bits((addr << 1 | 0) as u16) - .rd_wrn() - .read() - .nbytes() - .bits(buffer.len() as u8) - .start() - .set_bit() - .autoend() - .automatic() - }); - - if !self.rx_dma_enable { + type Error = Error; + + fn read( + &mut self, + addr: u8, + buffer: &mut [u8], + ) -> Result<(), Error> { + // TODO support transfers of more than 255 bytes + assert!(buffer.len() < 256 && buffer.len() > 0); + + self.master_read(addr, buffer.len(), Stop::Automatic); + for byte in buffer { // Wait until we have received something busy_wait!(self.i2c, rxne, is_not_empty); *byte = self.i2c.rxdr.read().rxdata().bits(); } - } - // automatic STOP + // automatic STOP - Ok(()) - } + Ok(()) + } } )+ }; From 91de4eecb3dceacfb3331d1e388665fec374c8bc Mon Sep 17 00:00:00 2001 From: Richard Meadows <962920+richardeoin@users.noreply.github.com> Date: Wed, 21 Oct 2020 22:03:31 +0200 Subject: [PATCH 08/41] Tidy more clippy lints --- examples/dma.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/dma.rs b/examples/dma.rs index 1731e41c..c496e857 100644 --- a/examples/dma.rs +++ b/examples/dma.rs @@ -67,7 +67,7 @@ fn main() -> ! { unsafe { mem::transmute(buf) } }; // Save a copy on the stack so we can check it later - let source_buffer_cloned = source_buffer.clone(); + let source_buffer_cloned = *source_buffer; // Setup DMA // @@ -91,7 +91,7 @@ fn main() -> ! { transfer.start(|_| {}); // Wait for transfer to complete - while transfer.get_transfer_complete_flag() == false {} + while !transfer.get_transfer_complete_flag() {} // Now the target memory is actually initialised let target_buffer: &'static mut [u32; 20] = From a86c5b61afc784eea628752489bb26090b7ccd94 Mon Sep 17 00:00:00 2001 From: Richard Meadows <962920+richardeoin@users.noreply.github.com> Date: Thu, 22 Oct 2020 20:36:10 +0200 Subject: [PATCH 09/41] Add DMBs before and after the stream read/writes normal memory Without barrier instructions, the Cortex-M7 core can reorder the execution of transfers to normal and device memory with respect to each other. This does not correspond to the compiler_fence calls, which are concerned with generating the correct _program order_. The Cortex-M7 core does not reorder the transfers to device memory (the DMA configuration) with respect to each other. --- src/dma/mod.rs | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/dma/mod.rs b/src/dma/mod.rs index d60c499c..19622722 100644 --- a/src/dma/mod.rs +++ b/src/dma/mod.rs @@ -8,6 +8,11 @@ //! Peripheral transfers, double buffering is supported only for Peripheral To //! Memory and Memory to Peripheral transfers. //! +//! Given that the Cortex-M7 core is capable of reordering accesses between +//! normal and device memory, we insert DMB instructions to ensure correct +//! operation. See ARM DAI 0321A, Section 3.2 which discusses the use of DMB +//! instructions in DMA controller configuration. +//! //! Adapted from //! https://github.com/stm32-rs/stm32f4xx-hal/blob/master/src/dma/mod.rs @@ -586,6 +591,14 @@ where // "Preceding reads and writes cannot be moved past subsequent writes" compiler_fence(Ordering::Release); + // Ensure that all transfers to normal memory complete before + // subsequent memory transfers. + // + // The memory buffer is almost certainly in normal memory, so we ensure + // that all transfers there complete before proceeding to enable + // the stream + cortex_m::asm::dmb(); + unsafe { self.stream.enable(); } @@ -695,6 +708,14 @@ where self.stream.set_number_of_transfers(buf_len as u16); let old_buf = self.buf.replace(new_buf); + // Ensure that all transfers to normal memory complete before + // subsequent memory transfers. + // + // The new memory buffer is almost certainly in normal memory, so we + // ensure that all transfers there complete before proceeding to enable + // the stream + cortex_m::asm::dmb(); + // "Preceding reads and writes cannot be moved past subsequent writes" compiler_fence(Ordering::Release); @@ -709,6 +730,11 @@ where pub fn free(mut self) -> (STREAM, PERIPHERAL, BUF, Option) { self.stream.disable(); compiler_fence(Ordering::SeqCst); + + // Ensure that the transfer to device memory that disables the stream is + // complete before subsequent memory transfers + cortex_m::asm::dmb(); + self.stream.clear_interrupts(); unsafe { @@ -890,6 +916,14 @@ where self.stream.set_number_of_transfers(buf_len as u16); self.buf.replace(new_buf); + // Ensure that all transfers to normal memory complete before + // subsequent memory transfers. + // + // The new memory buffer is almost certainly in normal memory, so we + // ensure that all transfers there complete before proceeding to enable + // the stream + cortex_m::asm::dmb(); + // "Preceding reads and writes cannot be moved past subsequent writes" compiler_fence(Ordering::Release); @@ -913,5 +947,9 @@ where // "No re-ordering of reads and writes across this point is allowed" compiler_fence(Ordering::SeqCst); + + // Ensure that the transfer to device memory that disables the stream is + // complete before subsequent memory transfers + cortex_m::asm::dmb(); } } From 5ce5fc9cad0108e3a1c0cd5efe2c8eb98bacb3f0 Mon Sep 17 00:00:00 2001 From: Richard Meadows <962920+richardeoin@users.noreply.github.com> Date: Fri, 23 Oct 2020 22:08:40 +0200 Subject: [PATCH 10/41] Refactor Stream trait into two parts, add config struct for BDMA The remaining core Stream trait should apply to MDMA streams also, although implementing this is future work --- examples/dma.rs | 5 +- examples/i2c4_bdma.rs | 5 +- examples/spi_dma.rs | 3 +- src/dma/bdma.rs | 370 +++++++++++++++++++-------------- src/dma/dma.rs | 473 ++++++++++++++++++++++++++++++------------ src/dma/mod.rs | 211 ++----------------- src/dma/traits.rs | 179 +++++++--------- 7 files changed, 666 insertions(+), 580 deletions(-) diff --git a/examples/dma.rs b/examples/dma.rs index c496e857..9d16be7f 100644 --- a/examples/dma.rs +++ b/examples/dma.rs @@ -13,8 +13,9 @@ mod utilities; use stm32h7xx_hal::{pac, prelude::*}; use stm32h7xx_hal::dma::{ - config::DmaConfig, dma::StreamsTuple, traits::Direction, MemoryToMemory, - Transfer, + dma::{DmaConfig, StreamsTuple}, + traits::Direction, + MemoryToMemory, Transfer, }; use log::info; diff --git a/examples/i2c4_bdma.rs b/examples/i2c4_bdma.rs index 7309e2bd..d161e476 100644 --- a/examples/i2c4_bdma.rs +++ b/examples/i2c4_bdma.rs @@ -13,7 +13,8 @@ use core::{mem, mem::MaybeUninit}; mod utilities; use stm32h7xx_hal::dma::{ - bdma::StreamsTuple, config::DmaConfig, PeripheralToMemory, Transfer, + bdma::{BdmaConfig, StreamsTuple}, + PeripheralToMemory, Transfer, }; use stm32h7xx_hal::prelude::*; @@ -82,7 +83,7 @@ fn main() -> ! { ccdr.peripheral.BDMA.low_power(LowPowerMode::Autonomous), ); - let config = DmaConfig::default().memory_increment(true); + let config = BdmaConfig::default().memory_increment(true); let mut transfer: Transfer<_, _, PeripheralToMemory, _> = Transfer::init( streams.0, diff --git a/examples/spi_dma.rs b/examples/spi_dma.rs index ecc316cb..654d6d62 100644 --- a/examples/spi_dma.rs +++ b/examples/spi_dma.rs @@ -13,7 +13,8 @@ mod utilities; use stm32h7xx_hal::{pac, prelude::*, spi}; use stm32h7xx_hal::dma::{ - config::DmaConfig, dma::StreamsTuple, MemoryToPeripheral, Transfer, + dma::{DmaConfig, StreamsTuple}, + MemoryToPeripheral, Transfer, }; use log::info; diff --git a/src/dma/bdma.rs b/src/dma/bdma.rs index 116c9847..754dcd26 100644 --- a/src/dma/bdma.rs +++ b/src/dma/bdma.rs @@ -4,8 +4,7 @@ use super::{ config, traits::sealed::{Bits, Sealed}, traits::*, - CurrentBuffer, DmaDirection, FifoLevel, MemoryToPeripheral, - PeripheralToMemory, + CurrentBuffer, DmaDirection, MemoryToPeripheral, PeripheralToMemory, }; use core::marker::PhantomData; @@ -53,6 +52,92 @@ impl Instance for BDMA { const DMA_MUX_STREAM_OFFSET: usize = 0; } +/// BDMA interrupts +#[derive(Debug, Clone, Copy)] +pub struct BdmaInterrupts { + transfer_complete: bool, + transfer_error: bool, + half_transfer: bool, +} + +/// Contains the complete set of configuration for a DMA stream. +#[derive(Debug, Default, Clone, Copy)] +pub struct BdmaConfig { + pub(crate) priority: config::Priority, + pub(crate) memory_increment: bool, + pub(crate) peripheral_increment: bool, + pub(crate) transfer_complete_interrupt: bool, + pub(crate) half_transfer_interrupt: bool, + pub(crate) transfer_error_interrupt: bool, + pub(crate) double_buffer: bool, +} + +impl DoubleBufferedConfig for BdmaConfig { + #[inline(always)] + fn is_double_buffered(&self) -> bool { + self.double_buffer + } + + #[inline(always)] + fn is_fifo_enabled(&self) -> bool { + false // No FIFO for BDMA + } +} + +impl BdmaConfig { + /// Set the priority. + #[inline(always)] + pub fn priority(mut self, priority: config::Priority) -> Self { + self.priority = priority; + self + } + /// Set the memory_increment. + #[inline(always)] + pub fn memory_increment(mut self, memory_increment: bool) -> Self { + self.memory_increment = memory_increment; + self + } + /// Set the peripheral_increment. + #[inline(always)] + pub fn peripheral_increment(mut self, peripheral_increment: bool) -> Self { + self.peripheral_increment = peripheral_increment; + self + } + /// Set the transfer_complete_interrupt. + #[inline(always)] + pub fn transfer_complete_interrupt( + mut self, + transfer_complete_interrupt: bool, + ) -> Self { + self.transfer_complete_interrupt = transfer_complete_interrupt; + self + } + /// Set the half_transfer_interrupt. + #[inline(always)] + pub fn half_transfer_interrupt( + mut self, + half_transfer_interrupt: bool, + ) -> Self { + self.half_transfer_interrupt = half_transfer_interrupt; + self + } + /// Set the transfer_error_interrupt. + #[inline(always)] + pub fn transfer_error_interrupt( + mut self, + transfer_error_interrupt: bool, + ) -> Self { + self.transfer_error_interrupt = transfer_error_interrupt; + self + } + /// Set the double_buffer. + #[inline(always)] + pub fn double_buffer(mut self, double_buffer: bool) -> Self { + self.double_buffer = double_buffer; + self + } +} + /// Stream 0 on BDMA pub struct Stream0 { _dma: PhantomData, @@ -124,7 +209,7 @@ impl StreamsTuple { } } -// Macro that creates a struct representing a stream on either DMA controller +// Macro that creates a struct representing a stream on either BDMA controller // // The implementation does the heavy lifting of mapping to the right fields on // the stream @@ -137,6 +222,22 @@ macro_rules! bdma_stream { impl Stream for $name { const NUMBER: usize = $number; + type Config = BdmaConfig; + type Interrupts = BdmaInterrupts; + + fn apply_config(&mut self, config: BdmaConfig) { + self.set_priority(config.priority); + self.set_memory_increment(config.memory_increment); + self.set_peripheral_increment(config.peripheral_increment); + self.set_transfer_complete_interrupt_enable( + config.transfer_complete_interrupt + ); + self.set_half_transfer_interrupt_enable(config.half_transfer_interrupt); + self.set_transfer_error_interrupt_enable( + config.transfer_error_interrupt + ); + self.set_double_buffer(config.double_buffer); + } #[inline(always)] fn clear_interrupts(&mut self) { @@ -144,10 +245,10 @@ macro_rules! bdma_stream { // that belongs to the StreamX let dma = unsafe { &*I::ptr() }; dma.$ifcr.write(|w| w - .$tcif().set_bit() //Clear transfer complete interrupt flag - .$htif().set_bit() //Clear half transfer interrupt flag - .$teif().set_bit() //Clear transfer error interrupt flag - .$gif().set_bit() //Clear global interrupt flag + .$tcif().set_bit() //Clear transfer complete interrupt flag + .$htif().set_bit() //Clear half transfer interrupt flag + .$teif().set_bit() //Clear transfer error interrupt flag + .$gif().set_bit() //Clear global interrupt flag ); } @@ -159,14 +260,6 @@ macro_rules! bdma_stream { dma.$ifcr.write(|w| w.$tcif().set_bit()); } - #[inline(always)] - fn clear_half_transfer_interrupt(&mut self) { - //NOTE(unsafe) Atomic write with no side-effects and we only access the bits - // that belongs to the StreamX - let dma = unsafe { &*I::ptr() }; - dma.$ifcr.write(|w| w.$htif().set_bit()); - } - #[inline(always)] fn clear_transfer_error_interrupt(&mut self) { //NOTE(unsafe) Atomic write with no side-effects and we only access the bits @@ -175,16 +268,6 @@ macro_rules! bdma_stream { dma.$ifcr.write(|w| w.$teif().set_bit()); } - #[inline(always)] - fn clear_direct_mode_error_interrupt(&mut self) { - - } - - #[inline(always)] - fn clear_fifo_error_interrupt(&mut self) { - - } - #[inline(always)] fn get_transfer_complete_flag() -> bool { //NOTE(unsafe) Atomic read with no side effects @@ -192,62 +275,6 @@ macro_rules! bdma_stream { dma.$isr.read().$tcisr().bit_is_set() } - #[inline(always)] - fn get_half_transfer_flag() -> bool { - //NOTE(unsafe) Atomic read with no side effects - let dma = unsafe { &*I::ptr() }; - dma.$isr.read().$htisr().bit_is_set() - } - - #[inline(always)] - unsafe fn set_peripheral_address(&mut self, value: u32) { - //NOTE(unsafe) We only access the registers that belongs to the StreamX - let dma = &*I::ptr(); - dma.ch[Self::NUMBER].par.write(|w| w.pa().bits(value)); - } - - #[inline(always)] - unsafe fn set_memory_address(&mut self, value: u32) { - //NOTE(unsafe) We only access the registers that belongs to the StreamX - let dma = &*I::ptr(); - dma.ch[Self::NUMBER].m0ar.write(|w| w.ma().bits(value)); - } - - #[inline(always)] - fn get_memory_address(&self) -> u32 { - //NOTE(unsafe) We only access the registers that belongs to the StreamX - let dma = unsafe { &*I::ptr() }; - dma.ch[Self::NUMBER].m0ar.read().ma().bits() - } - - #[inline(always)] - unsafe fn set_memory_double_buffer_address(&mut self, value: u32) { - //NOTE(unsafe) We only access the registers that belongs to the StreamX - let dma = &*I::ptr(); - dma.ch[Self::NUMBER].m1ar.write(|w| w.ma().bits(value)); - } - - #[inline(always)] - fn get_memory_double_buffer_address(&self) -> u32 { - //NOTE(unsafe) We only access the registers that belongs to the StreamX - let dma = unsafe { &*I::ptr() }; - dma.ch[Self::NUMBER].m1ar.read().ma().bits() - } - - #[inline(always)] - fn set_number_of_transfers(&mut self, value: u16) { - //NOTE(unsafe) We only access the registers that belongs to the StreamX - let dma = unsafe { &*I::ptr() }; - dma.ch[Self::NUMBER].ndtr.write(|w| w.ndt().bits(value)); - } - - #[inline(always)] - fn get_number_of_transfers() -> u16 { - //NOTE(unsafe) We only access the registers that belongs to the StreamX - let dma = unsafe { &*I::ptr() }; - dma.ch[Self::NUMBER].ndtr.read().ndt().bits() - } - #[inline(always)] unsafe fn enable(&mut self) { //NOTE(unsafe) We only access the registers that belongs to the StreamX @@ -269,15 +296,14 @@ macro_rules! bdma_stream { // Aborting an on-going transfer might cause interrupts to fire, disable // them - let (tc, ht, te, dm) = Self::get_interrupts_enable(); - self - .set_interrupts_enable(false, false, false, false); + let interrupts = Self::get_interrupts_enable(); + self.disable_interrupts(); dma.ch[Self::NUMBER].cr.modify(|_, w| w.en().clear_bit()); while Self::is_enabled() {} self.clear_interrupts(); - self.set_interrupts_enable(tc, ht, te, dm); + self.enable_interrupts(interrupts); } } @@ -299,146 +325,162 @@ macro_rules! bdma_stream { } #[inline(always)] - unsafe fn set_memory_size(&mut self, size: u8) { + fn disable_interrupts(&mut self) { //NOTE(unsafe) We only access the registers that belongs to the StreamX - let dma = &*I::ptr(); - dma.ch[Self::NUMBER].cr.modify(|_, w| w.msize().bits(size)); + let dma = unsafe { &*I::ptr() }; + dma.ch[Self::NUMBER].cr.modify(|_, w| w + .tcie().clear_bit() + .teie().clear_bit() + .htie().clear_bit() + ); } #[inline(always)] - unsafe fn set_peripheral_size(&mut self, size: u8) { + fn enable_interrupts(&mut self, interrupt: Self::Interrupts) { //NOTE(unsafe) We only access the registers that belongs to the StreamX - let dma = &*I::ptr(); - dma.ch[Self::NUMBER].cr.modify(|_, w| w.psize().bits(size)); + let dma = unsafe { &*I::ptr() }; + dma.ch[Self::NUMBER].cr.modify(|_, w| w + .tcie().bit(interrupt.transfer_complete) + .teie().bit(interrupt.transfer_error) + .htie().bit(interrupt.half_transfer) + ); } #[inline(always)] - fn set_memory_increment(&mut self, increment: bool) { + fn get_interrupts_enable() -> Self::Interrupts { //NOTE(unsafe) We only access the registers that belongs to the StreamX let dma = unsafe { &*I::ptr() }; - dma.ch[Self::NUMBER].cr.modify(|_, w| w.minc().bit(increment)); + let cr = dma.ch[Self::NUMBER].cr.read(); + + BdmaInterrupts { + transfer_complete: cr.tcie().bit_is_set(), + half_transfer: cr.htie().bit_is_set(), + transfer_error: cr.teie().bit_is_set() + } } #[inline(always)] - fn set_peripheral_increment(&mut self, increment: bool) { + fn set_transfer_complete_interrupt_enable(&mut self, transfer_complete_interrupt: bool) { //NOTE(unsafe) We only access the registers that belongs to the StreamX let dma = unsafe { &*I::ptr() }; - dma.ch[Self::NUMBER].cr.modify(|_, w| w.pinc().bit(increment)); + dma.ch[Self::NUMBER].cr.modify(|_, w| w.tcie().bit(transfer_complete_interrupt)); } #[inline(always)] - fn set_direction(&mut self, direction: DmaDirection) { + fn set_transfer_error_interrupt_enable(&mut self, transfer_error_interrupt: bool) { //NOTE(unsafe) We only access the registers that belongs to the StreamX let dma = unsafe { &*I::ptr() }; - dma.ch[Self::NUMBER].cr.modify(|_, w| { - match direction { - DmaDirection::PeripheralToMemory => - w.dir().peripheral_to_memory().mem2mem().disabled(), - DmaDirection::MemoryToPeripheral => - w.dir().memory_to_peripheral().mem2mem().disabled(), - DmaDirection::MemoryToMemory => - w.mem2mem().enabled().dir().clear_bit(), - } - }); + dma.ch[Self::NUMBER].cr.modify(|_, w| w.teie().bit(transfer_error_interrupt)); } + } + impl DoubleBufferedStream for $name { #[inline(always)] - #[cfg(not(feature = "rm0455"))] - fn set_trbuff(&mut self, _trbuff: bool) { + unsafe fn set_peripheral_address(&mut self, value: u32) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = &*I::ptr(); + dma.ch[Self::NUMBER].par.write(|w| w.pa().bits(value)); + } + #[inline(always)] + unsafe fn set_memory_address(&mut self, value: u32) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = &*I::ptr(); + dma.ch[Self::NUMBER].m0ar.write(|w| w.ma().bits(value)); } #[inline(always)] - fn set_interrupts_enable( - &mut self, - transfer_complete: bool, - half_transfer: bool, - transfer_error: bool, - _direct_mode_error: bool, - ) - { + fn get_memory_address(&self) -> u32 { //NOTE(unsafe) We only access the registers that belongs to the StreamX let dma = unsafe { &*I::ptr() }; - dma.ch[Self::NUMBER].cr.modify(|_, w| w - .tcie().bit(transfer_complete) - .htie().bit(half_transfer) - .teie().bit(transfer_error) - // No dmeie - ); + dma.ch[Self::NUMBER].m0ar.read().ma().bits() } #[inline(always)] - fn get_interrupts_enable() -> (bool, bool, bool, bool) { + unsafe fn set_memory_double_buffer_address(&mut self, value: u32) { //NOTE(unsafe) We only access the registers that belongs to the StreamX - let dma = unsafe { &*I::ptr() }; - let cr = dma.ch[Self::NUMBER].cr.read(); - (cr.tcie().bit_is_set(), cr.htie().bit_is_set(), - cr.teie().bit_is_set(), false) // No dmeie + let dma = &*I::ptr(); + dma.ch[Self::NUMBER].m1ar.write(|w| w.ma().bits(value)); } #[inline(always)] - fn set_transfer_complete_interrupt_enable(&mut self, transfer_complete_interrupt: bool) { + fn get_memory_double_buffer_address(&self) -> u32 { //NOTE(unsafe) We only access the registers that belongs to the StreamX let dma = unsafe { &*I::ptr() }; - dma.ch[Self::NUMBER].cr.modify(|_, w| w.tcie().bit(transfer_complete_interrupt)); + dma.ch[Self::NUMBER].m1ar.read().ma().bits() } #[inline(always)] - fn set_half_transfer_interrupt_enable(&mut self, half_transfer_interrupt: bool) { + fn set_number_of_transfers(&mut self, value: u16) { //NOTE(unsafe) We only access the registers that belongs to the StreamX let dma = unsafe { &*I::ptr() }; - dma.ch[Self::NUMBER].cr.modify(|_, w| w.htie().bit(half_transfer_interrupt)); + dma.ch[Self::NUMBER].ndtr.write(|w| w.ndt().bits(value)); } #[inline(always)] - fn set_transfer_error_interrupt_enable(&mut self, transfer_error_interrupt: bool) { + fn get_number_of_transfers() -> u16 { //NOTE(unsafe) We only access the registers that belongs to the StreamX let dma = unsafe { &*I::ptr() }; - dma.ch[Self::NUMBER].cr.modify(|_, w| w.teie().bit(transfer_error_interrupt)); + dma.ch[Self::NUMBER].ndtr.read().ndt().bits() } - #[inline(always)] - fn set_direct_mode_error_interrupt_enable(&mut self, _direct_mode_error_interrupt: bool) { - + unsafe fn set_memory_size(&mut self, size: u8) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = &*I::ptr(); + dma.ch[Self::NUMBER].cr.modify(|_, w| w.msize().bits(size)); } #[inline(always)] - fn set_fifo_error_interrupt_enable(&mut self, _fifo_error_interrupt: bool) { - + unsafe fn set_peripheral_size(&mut self, size: u8) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = &*I::ptr(); + dma.ch[Self::NUMBER].cr.modify(|_, w| w.psize().bits(size)); } #[inline(always)] - fn set_double_buffer(&mut self, double_buffer: bool) { + fn set_memory_increment(&mut self, increment: bool) { //NOTE(unsafe) We only access the registers that belongs to the StreamX let dma = unsafe { &*I::ptr() }; - dma.ch[Self::NUMBER].cr.modify(|_, w| w.dbm().bit(double_buffer)); + dma.ch[Self::NUMBER].cr.modify(|_, w| w.minc().bit(increment)); } #[inline(always)] - fn set_fifo_threshold(&mut self, _fifo_threshold: config::FifoThreshold) { - + fn set_peripheral_increment(&mut self, increment: bool) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.ch[Self::NUMBER].cr.modify(|_, w| w.pinc().bit(increment)); } #[inline(always)] - fn set_fifo_enable(&mut self, _fifo_enable: bool) { - + fn set_direction(&mut self, direction: DmaDirection) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.ch[Self::NUMBER].cr.modify(|_, w| { + match direction { + DmaDirection::PeripheralToMemory => + w.dir().peripheral_to_memory().mem2mem().disabled(), + DmaDirection::MemoryToPeripheral => + w.dir().memory_to_peripheral().mem2mem().disabled(), + DmaDirection::MemoryToMemory => + w.mem2mem().enabled().dir().clear_bit(), + } + }); } #[inline(always)] - fn set_memory_burst(&mut self, _memory_burst: config::BurstMode) { - + #[cfg(not(feature = "rm0455"))] + fn set_trbuff(&mut self, _trbuff: bool) { + // BDMA does not have a TRBUFF bit } #[inline(always)] - fn set_peripheral_burst(&mut self, _peripheral_burst: config::BurstMode) { - + fn set_double_buffer(&mut self, double_buffer: bool) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.ch[Self::NUMBER].cr.modify(|_, w| w.dbm().bit(double_buffer)); } #[inline(always)] - fn fifo_level() -> FifoLevel { - FifoLevel::Invalid - } - fn current_buffer() -> CurrentBuffer { //NOTE(unsafe) Atomic read with no side effects let dma = unsafe { &*I::ptr() }; @@ -449,6 +491,30 @@ macro_rules! bdma_stream { } } } + + impl $name { + #[inline(always)] + pub fn clear_half_transfer_interrupt(&mut self) { + //NOTE(unsafe) Atomic write with no side-effects and we only access the bits + // that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.$ifcr.write(|w| w.$htif().set_bit()); + } + + #[inline(always)] + pub fn get_half_transfer_flag() -> bool { + //NOTE(unsafe) Atomic read with no side effects + let dma = unsafe { &*I::ptr() }; + dma.$isr.read().$htisr().bit_is_set() + } + + #[inline(always)] + pub fn set_half_transfer_interrupt_enable(&mut self, half_transfer_interrupt: bool) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.ch[Self::NUMBER].cr.modify(|_, w| w.htie().bit(half_transfer_interrupt)); + } + } )+ }; } diff --git a/src/dma/dma.rs b/src/dma/dma.rs index 764761ea..8e52f5ef 100644 --- a/src/dma/dma.rs +++ b/src/dma/dma.rs @@ -70,6 +70,166 @@ impl Instance for DMA2 { const DMA_MUX_STREAM_OFFSET: usize = 8; } +/// DMA interrupts +#[derive(Debug, Clone, Copy)] +pub struct DmaInterrupts { + transfer_complete: bool, + transfer_error: bool, + half_transfer: bool, + direct_mode_error: bool, + fifo_error: bool, +} + +/// Contains configuration for a DMA stream +#[derive(Debug, Clone, Copy)] +pub struct DmaConfig { + pub(crate) priority: config::Priority, + pub(crate) memory_increment: bool, + pub(crate) peripheral_increment: bool, + pub(crate) transfer_complete_interrupt: bool, + pub(crate) half_transfer_interrupt: bool, + pub(crate) transfer_error_interrupt: bool, + pub(crate) direct_mode_error_interrupt: bool, + pub(crate) fifo_error_interrupt: bool, + pub(crate) double_buffer: bool, + pub(crate) fifo_threshold: config::FifoThreshold, + pub(crate) fifo_enable: bool, + pub(crate) memory_burst: config::BurstMode, + pub(crate) peripheral_burst: config::BurstMode, +} + +impl Default for DmaConfig { + fn default() -> Self { + Self { + priority: config::Priority::Medium, + memory_increment: false, + peripheral_increment: false, + transfer_complete_interrupt: false, + half_transfer_interrupt: false, + transfer_error_interrupt: false, + direct_mode_error_interrupt: false, + fifo_error_interrupt: false, + double_buffer: false, + fifo_threshold: config::FifoThreshold::QuarterFull, + fifo_enable: false, + memory_burst: config::BurstMode::NoBurst, + peripheral_burst: config::BurstMode::NoBurst, + } + } +} + +impl DoubleBufferedConfig for DmaConfig { + #[inline(always)] + fn is_double_buffered(&self) -> bool { + self.double_buffer + } + + #[inline(always)] + fn is_fifo_enabled(&self) -> bool { + self.fifo_enable + } +} + +impl DmaConfig { + /// Set the priority. + #[inline(always)] + pub fn priority(mut self, priority: config::Priority) -> Self { + self.priority = priority; + self + } + + /// Set the memory_increment. + #[inline(always)] + pub fn memory_increment(mut self, memory_increment: bool) -> Self { + self.memory_increment = memory_increment; + self + } + /// Set the peripheral_increment. + #[inline(always)] + pub fn peripheral_increment(mut self, peripheral_increment: bool) -> Self { + self.peripheral_increment = peripheral_increment; + self + } + /// Set the transfer_complete_interrupt. + #[inline(always)] + pub fn transfer_complete_interrupt( + mut self, + transfer_complete_interrupt: bool, + ) -> Self { + self.transfer_complete_interrupt = transfer_complete_interrupt; + self + } + /// Set the half_transfer_interrupt. + #[inline(always)] + pub fn half_transfer_interrupt( + mut self, + half_transfer_interrupt: bool, + ) -> Self { + self.half_transfer_interrupt = half_transfer_interrupt; + self + } + /// Set the transfer_error_interrupt. + #[inline(always)] + pub fn transfer_error_interrupt( + mut self, + transfer_error_interrupt: bool, + ) -> Self { + self.transfer_error_interrupt = transfer_error_interrupt; + self + } + /// Set the direct_mode_error_interrupt. + #[inline(always)] + pub fn direct_mode_error_interrupt( + mut self, + direct_mode_error_interrupt: bool, + ) -> Self { + self.direct_mode_error_interrupt = direct_mode_error_interrupt; + self + } + /// Set the fifo_error_interrupt. + #[inline(always)] + pub fn fifo_error_interrupt(mut self, fifo_error_interrupt: bool) -> Self { + self.fifo_error_interrupt = fifo_error_interrupt; + self + } + /// Set the double_buffer. + #[inline(always)] + pub fn double_buffer(mut self, double_buffer: bool) -> Self { + self.double_buffer = double_buffer; + self + } + /// Set the fifo_threshold. + #[inline(always)] + pub fn fifo_threshold( + mut self, + fifo_threshold: config::FifoThreshold, + ) -> Self { + self.fifo_threshold = fifo_threshold; + self + } + /// Set the fifo_enable. + #[inline(always)] + pub fn fifo_enable(mut self, fifo_enable: bool) -> Self { + self.fifo_enable = fifo_enable; + self + } + /// Set the memory_burst. + #[inline(always)] + pub fn memory_burst(mut self, memory_burst: config::BurstMode) -> Self { + self.memory_burst = memory_burst; + self + } + /// Set the peripheral_burst. + #[inline(always)] + pub fn peripheral_burst( + mut self, + peripheral_burst: config::BurstMode, + ) -> Self { + self.peripheral_burst = peripheral_burst; + self + } +} + /// Stream 0 on DMA1/2 pub struct Stream0 { _dma: PhantomData, @@ -154,6 +314,34 @@ macro_rules! dma_stream { impl Stream for $name { const NUMBER: usize = $number; + type Config = DmaConfig; + type Interrupts = DmaInterrupts; + + fn apply_config(&mut self, config: DmaConfig) { + self.set_priority(config.priority); + self.set_memory_increment(config.memory_increment); + self + .set_peripheral_increment(config.peripheral_increment); + self.set_transfer_complete_interrupt_enable( + config.transfer_complete_interrupt + ); + self.set_half_transfer_interrupt_enable( + config.half_transfer_interrupt + ); + self.set_transfer_error_interrupt_enable( + config.transfer_error_interrupt + ); + self.set_direct_mode_error_interrupt_enable( + config.direct_mode_error_interrupt + ); + self + .set_fifo_error_interrupt_enable(config.fifo_error_interrupt); + self.set_double_buffer(config.double_buffer); + self.set_fifo_threshold(config.fifo_threshold); + self.set_fifo_enable(config.fifo_enable); + self.set_memory_burst(config.memory_burst); + self.set_peripheral_burst(config.peripheral_burst); + } #[inline(always)] fn clear_interrupts(&mut self) { @@ -161,11 +349,11 @@ macro_rules! dma_stream { // that belongs to the StreamX let dma = unsafe { &*I::ptr() }; dma.$ifcr.write(|w| w - .$tcif().set_bit() //Clear transfer complete interrupt flag - .$htif().set_bit() //Clear half transfer interrupt flag - .$teif().set_bit() //Clear transfer error interrupt flag - .$dmeif().set_bit() //Clear direct mode error interrupt flag - .$feif().set_bit() //Clear fifo error interrupt flag + .$tcif().set_bit() //Clear transfer complete interrupt flag + .$htif().set_bit() //Clear half transfer interrupt flag + .$teif().set_bit() //Clear transfer error interrupt flag + .$dmeif().set_bit() //Clear direct mode error interrupt flag + .$feif().set_bit() //Clear fifo error interrupt flag ); } @@ -177,14 +365,6 @@ macro_rules! dma_stream { dma.$ifcr.write(|w| w.$tcif().set_bit()); } - #[inline(always)] - fn clear_half_transfer_interrupt(&mut self) { - //NOTE(unsafe) Atomic write with no side-effects and we only access the bits - // that belongs to the StreamX - let dma = unsafe { &*I::ptr() }; - dma.$ifcr.write(|w| w.$htif().set_bit()); - } - #[inline(always)] fn clear_transfer_error_interrupt(&mut self) { //NOTE(unsafe) Atomic write with no side-effects and we only access the bits @@ -194,134 +374,168 @@ macro_rules! dma_stream { } #[inline(always)] - fn clear_direct_mode_error_interrupt(&mut self) { - //NOTE(unsafe) Atomic write with no side-effects and we only access the bits - // that belongs to the StreamX + fn get_transfer_complete_flag() -> bool { + //NOTE(unsafe) Atomic read with no side effects let dma = unsafe { &*I::ptr() }; - dma.$ifcr.write(|w| w.$dmeif().set_bit()); + dma.$isr.read().$tcisr().bit_is_set() } #[inline(always)] - fn clear_fifo_error_interrupt(&mut self) { - //NOTE(unsafe) Atomic write with no side-effects and we only access the bits - // that belongs to the StreamX - let dma = unsafe { &*I::ptr() }; - dma.$ifcr.write(|w| w.$feif().set_bit()); + unsafe fn enable(&mut self) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = &*I::ptr(); + dma.st[Self::NUMBER].cr.modify(|_, w| w.en().set_bit()); } #[inline(always)] - fn get_transfer_complete_flag() -> bool { + fn is_enabled() -> bool { //NOTE(unsafe) Atomic read with no side effects let dma = unsafe { &*I::ptr() }; - dma.$isr.read().$tcisr().bit_is_set() + dma.st[Self::NUMBER].cr.read().en().bit_is_set() } - #[inline(always)] - fn get_half_transfer_flag() -> bool { - //NOTE(unsafe) Atomic read with no side effects - let dma = unsafe { &*I::ptr() }; - dma.$isr.read().$htisr().bit_is_set() + fn disable(&mut self) { + if Self::is_enabled() { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + + // Aborting an on-going transfer might cause interrupts to fire, disable + // them + let interrupts = Self::get_interrupts_enable(); + self.disable_interrupts(); + + dma.st[Self::NUMBER].cr.modify(|_, w| w.en().clear_bit()); + while Self::is_enabled() {} + + self.clear_interrupts(); + self.enable_interrupts(interrupts); + } } #[inline(always)] - unsafe fn set_peripheral_address(&mut self, value: u32) { + fn set_request_line(&mut self, request_line: u8) { //NOTE(unsafe) We only access the registers that belongs to the StreamX - let dma = &*I::ptr(); - dma.st[Self::NUMBER].par.write(|w| w.pa().bits(value)); + let dmamux = unsafe { &*I::mux_ptr() }; + unsafe { + dmamux.ccr[Self::NUMBER + I::DMA_MUX_STREAM_OFFSET] + .modify(|_, w| w.dmareq_id().bits(request_line)); + } } #[inline(always)] - unsafe fn set_memory_address(&mut self, value: u32) { + fn set_priority(&mut self, priority: config::Priority) { //NOTE(unsafe) We only access the registers that belongs to the StreamX - let dma = &*I::ptr(); - dma.st[Self::NUMBER].m0ar.write(|w| w.m0a().bits(value)); + let dma = unsafe { &*I::ptr() }; + dma.st[Self::NUMBER].cr.modify(|_, w| w.pl().bits(priority.bits())); } #[inline(always)] - fn get_memory_address(&self) -> u32 { + fn disable_interrupts(&mut self) { //NOTE(unsafe) We only access the registers that belongs to the StreamX let dma = unsafe { &*I::ptr() }; - dma.st[Self::NUMBER].m0ar.read().m0a().bits() + dma.st[Self::NUMBER].cr.modify(|_, w| w + .tcie().clear_bit() + .teie().clear_bit() + .htie().clear_bit() + .dmeie().clear_bit() + ); + dma.st[Self::NUMBER].fcr.modify(|_, w| w.feie().clear_bit()); } #[inline(always)] - unsafe fn set_memory_double_buffer_address(&mut self, value: u32) { + fn enable_interrupts(&mut self, interrupt: Self::Interrupts) { //NOTE(unsafe) We only access the registers that belongs to the StreamX - let dma = &*I::ptr(); - dma.st[Self::NUMBER].m1ar.write(|w| w.m1a().bits(value)); + let dma = unsafe { &*I::ptr() }; + dma.st[Self::NUMBER].cr.modify(|_, w| w + .tcie().bit(interrupt.transfer_complete) + .htie().bit(interrupt.half_transfer) + .teie().bit(interrupt.transfer_error) + .dmeie().bit(interrupt.direct_mode_error) + ); + dma.st[Self::NUMBER].fcr.modify(|_, w| w.feie().bit(interrupt.fifo_error)); } #[inline(always)] - fn get_memory_double_buffer_address(&self) -> u32 { + fn get_interrupts_enable() -> Self::Interrupts { //NOTE(unsafe) We only access the registers that belongs to the StreamX let dma = unsafe { &*I::ptr() }; - dma.st[Self::NUMBER].m1ar.read().m1a().bits() + let cr = dma.st[Self::NUMBER].cr.read(); + let fcr = dma.st[Self::NUMBER].fcr.read(); + + DmaInterrupts { + transfer_complete: cr.tcie().bit_is_set(), + half_transfer: cr.htie().bit_is_set(), + transfer_error: cr.teie().bit_is_set(), + direct_mode_error: cr.dmeie().bit_is_set(), + fifo_error: fcr.feie().bit_is_set() + } } #[inline(always)] - fn set_number_of_transfers(&mut self, value: u16) { + fn set_transfer_complete_interrupt_enable(&mut self, transfer_complete_interrupt: bool) { //NOTE(unsafe) We only access the registers that belongs to the StreamX let dma = unsafe { &*I::ptr() }; - dma.st[Self::NUMBER].ndtr.write(|w| w.ndt().bits(value)); + dma.st[Self::NUMBER].cr.modify(|_, w| w.tcie().bit(transfer_complete_interrupt)); } #[inline(always)] - fn get_number_of_transfers() -> u16 { + fn set_transfer_error_interrupt_enable(&mut self, transfer_error_interrupt: bool) { //NOTE(unsafe) We only access the registers that belongs to the StreamX let dma = unsafe { &*I::ptr() }; - dma.st[Self::NUMBER].ndtr.read().ndt().bits() + dma.st[Self::NUMBER].cr.modify(|_, w| w.teie().bit(transfer_error_interrupt)); } + } + + impl DoubleBufferedStream for $name { #[inline(always)] - unsafe fn enable(&mut self) { + unsafe fn set_peripheral_address(&mut self, value: u32) { //NOTE(unsafe) We only access the registers that belongs to the StreamX let dma = &*I::ptr(); - dma.st[Self::NUMBER].cr.modify(|_, w| w.en().set_bit()); + dma.st[Self::NUMBER].par.write(|w| w.pa().bits(value)); } #[inline(always)] - fn is_enabled() -> bool { - //NOTE(unsafe) Atomic read with no side effects - let dma = unsafe { &*I::ptr() }; - dma.st[Self::NUMBER].cr.read().en().bit_is_set() + unsafe fn set_memory_address(&mut self, value: u32) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = &*I::ptr(); + dma.st[Self::NUMBER].m0ar.write(|w| w.m0a().bits(value)); } - fn disable(&mut self) { - if Self::is_enabled() { - //NOTE(unsafe) We only access the registers that belongs to the StreamX - let dma = unsafe { &*I::ptr() }; - - // Aborting an on-going transfer might cause interrupts to fire, disable - // them - let (tc, ht, te, dm) = Self::get_interrupts_enable(); - self - .set_interrupts_enable(false, false, false, false); - - dma.st[Self::NUMBER].cr.modify(|_, w| w.en().clear_bit()); - while Self::is_enabled() {} + #[inline(always)] + fn get_memory_address(&self) -> u32 { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.st[Self::NUMBER].m0ar.read().m0a().bits() + } - self.clear_interrupts(); - self.set_interrupts_enable(tc, ht, te, dm); - } + #[inline(always)] + unsafe fn set_memory_double_buffer_address(&mut self, value: u32) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = &*I::ptr(); + dma.st[Self::NUMBER].m1ar.write(|w| w.m1a().bits(value)); } #[inline(always)] - fn set_request_line(&mut self, request_line: u8) { + fn get_memory_double_buffer_address(&self) -> u32 { //NOTE(unsafe) We only access the registers that belongs to the StreamX - let dmamux = unsafe { &*I::mux_ptr() }; - unsafe { - dmamux.ccr[Self::NUMBER + I::DMA_MUX_STREAM_OFFSET] - .modify(|_, w| w.dmareq_id().bits(request_line)); - } + let dma = unsafe { &*I::ptr() }; + dma.st[Self::NUMBER].m1ar.read().m1a().bits() } #[inline(always)] - fn set_priority(&mut self, priority: config::Priority) { + fn set_number_of_transfers(&mut self, value: u16) { //NOTE(unsafe) We only access the registers that belongs to the StreamX let dma = unsafe { &*I::ptr() }; - dma.st[Self::NUMBER].cr.modify(|_, w| w.pl().bits(priority.bits())); + dma.st[Self::NUMBER].ndtr.write(|w| w.ndt().bits(value)); } + #[inline(always)] + fn get_number_of_transfers() -> u16 { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.st[Self::NUMBER].ndtr.read().ndt().bits() + } #[inline(always)] unsafe fn set_memory_size(&mut self, size: u8) { //NOTE(unsafe) We only access the registers that belongs to the StreamX @@ -372,119 +586,110 @@ macro_rules! dma_stream { } #[inline(always)] - fn set_interrupts_enable( - &mut self, - transfer_complete: bool, - half_transfer: bool, - transfer_error: bool, - direct_mode_error: bool, - ) - { + fn set_double_buffer(&mut self, double_buffer: bool) { //NOTE(unsafe) We only access the registers that belongs to the StreamX let dma = unsafe { &*I::ptr() }; - dma.st[Self::NUMBER].cr.modify(|_, w| w - .tcie().bit(transfer_complete) - .htie().bit(half_transfer) - .teie().bit(transfer_error) - .dmeie().bit(direct_mode_error) - ); + dma.st[Self::NUMBER].cr.modify(|_, w| w.dbm().bit(double_buffer)); } - #[inline(always)] - fn get_interrupts_enable() -> (bool, bool, bool, bool) { - //NOTE(unsafe) We only access the registers that belongs to the StreamX + fn current_buffer() -> CurrentBuffer { + //NOTE(unsafe) Atomic read with no side effects let dma = unsafe { &*I::ptr() }; - let cr = dma.st[Self::NUMBER].cr.read(); - (cr.tcie().bit_is_set(), cr.htie().bit_is_set(), - cr.teie().bit_is_set(), cr.dmeie().bit_is_set()) + if dma.st[Self::NUMBER].cr.read().ct().bit_is_set() { + CurrentBuffer::DoubleBuffer + } else { + CurrentBuffer::FirstBuffer + } } + } + impl $name { #[inline(always)] - fn set_transfer_complete_interrupt_enable(&mut self, transfer_complete_interrupt: bool) { + fn set_fifo_threshold(&mut self, fifo_threshold: config::FifoThreshold) { //NOTE(unsafe) We only access the registers that belongs to the StreamX let dma = unsafe { &*I::ptr() }; - dma.st[Self::NUMBER].cr.modify(|_, w| w.tcie().bit(transfer_complete_interrupt)); + dma.st[Self::NUMBER].fcr.modify(|_, w| w.fth().bits(fifo_threshold.bits())); } #[inline(always)] - fn set_half_transfer_interrupt_enable(&mut self, half_transfer_interrupt: bool) { + fn set_fifo_enable(&mut self, fifo_enable: bool) { //NOTE(unsafe) We only access the registers that belongs to the StreamX let dma = unsafe { &*I::ptr() }; - dma.st[Self::NUMBER].cr.modify(|_, w| w.htie().bit(half_transfer_interrupt)); + //Register is actually direct mode disable rather than fifo enable + dma.st[Self::NUMBER].fcr.modify(|_, w| w.dmdis().bit(fifo_enable)); } #[inline(always)] - fn set_transfer_error_interrupt_enable(&mut self, transfer_error_interrupt: bool) { + fn set_memory_burst(&mut self, memory_burst: config::BurstMode) { //NOTE(unsafe) We only access the registers that belongs to the StreamX let dma = unsafe { &*I::ptr() }; - dma.st[Self::NUMBER].cr.modify(|_, w| w.teie().bit(transfer_error_interrupt)); + dma.st[Self::NUMBER].cr.modify(|_, w| w.mburst().bits(memory_burst.bits())); } #[inline(always)] - fn set_direct_mode_error_interrupt_enable(&mut self, direct_mode_error_interrupt: bool) { + fn set_peripheral_burst(&mut self, peripheral_burst: config::BurstMode) { //NOTE(unsafe) We only access the registers that belongs to the StreamX let dma = unsafe { &*I::ptr() }; - dma.st[Self::NUMBER].cr.modify(|_, w| w.dmeie().bit(direct_mode_error_interrupt)); + dma.st[Self::NUMBER].cr.modify(|_, w| w.pburst().bits(peripheral_burst.bits())); } + } + impl $name { #[inline(always)] - fn set_fifo_error_interrupt_enable(&mut self, fifo_error_interrupt: bool) { - //NOTE(unsafe) We only access the registers that belongs to the StreamX + pub fn fifo_level() -> FifoLevel { + //NOTE(unsafe) Atomic read with no side effects let dma = unsafe { &*I::ptr() }; - dma.st[Self::NUMBER].fcr.modify(|_, w| w.feie().bit(fifo_error_interrupt)); + dma.st[Self::NUMBER].fcr.read().fs().bits().into() } #[inline(always)] - fn set_double_buffer(&mut self, double_buffer: bool) { - //NOTE(unsafe) We only access the registers that belongs to the StreamX + pub fn clear_half_transfer_interrupt(&mut self) { + //NOTE(unsafe) Atomic write with no side-effects and we only access the bits + // that belongs to the StreamX let dma = unsafe { &*I::ptr() }; - dma.st[Self::NUMBER].cr.modify(|_, w| w.dbm().bit(double_buffer)); + dma.$ifcr.write(|w| w.$htif().set_bit()); } - #[inline(always)] - fn set_fifo_threshold(&mut self, fifo_threshold: config::FifoThreshold) { - //NOTE(unsafe) We only access the registers that belongs to the StreamX + pub fn clear_direct_mode_error_interrupt(&mut self) { + //NOTE(unsafe) Atomic write with no side-effects and we only access the bits + // that belongs to the StreamX let dma = unsafe { &*I::ptr() }; - dma.st[Self::NUMBER].fcr.modify(|_, w| w.fth().bits(fifo_threshold.bits())); + dma.$ifcr.write(|w| w.$dmeif().set_bit()); } - #[inline(always)] - fn set_fifo_enable(&mut self, fifo_enable: bool) { - //NOTE(unsafe) We only access the registers that belongs to the StreamX + pub fn clear_fifo_error_interrupt(&mut self) { + //NOTE(unsafe) Atomic write with no side-effects and we only access the bits + // that belongs to the StreamX let dma = unsafe { &*I::ptr() }; - //Register is actually direct mode disable rather than fifo enable - dma.st[Self::NUMBER].fcr.modify(|_, w| w.dmdis().bit(fifo_enable)); + dma.$ifcr.write(|w| w.$feif().set_bit()); } #[inline(always)] - fn set_memory_burst(&mut self, memory_burst: config::BurstMode) { - //NOTE(unsafe) We only access the registers that belongs to the StreamX + pub fn get_half_transfer_flag() -> bool { + //NOTE(unsafe) Atomic read with no side effects let dma = unsafe { &*I::ptr() }; - dma.st[Self::NUMBER].cr.modify(|_, w| w.mburst().bits(memory_burst.bits())); + dma.$isr.read().$htisr().bit_is_set() } #[inline(always)] - fn set_peripheral_burst(&mut self, peripheral_burst: config::BurstMode) { + pub fn set_half_transfer_interrupt_enable(&mut self, half_transfer_interrupt: bool) { //NOTE(unsafe) We only access the registers that belongs to the StreamX let dma = unsafe { &*I::ptr() }; - dma.st[Self::NUMBER].cr.modify(|_, w| w.pburst().bits(peripheral_burst.bits())); + dma.st[Self::NUMBER].cr.modify(|_, w| w.htie().bit(half_transfer_interrupt)); } #[inline(always)] - fn fifo_level() -> FifoLevel { - //NOTE(unsafe) Atomic read with no side effects + pub fn set_direct_mode_error_interrupt_enable(&mut self, direct_mode_error_interrupt: bool) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX let dma = unsafe { &*I::ptr() }; - dma.st[Self::NUMBER].fcr.read().fs().bits().into() + dma.st[Self::NUMBER].cr.modify(|_, w| w.dmeie().bit(direct_mode_error_interrupt)); } - fn current_buffer() -> CurrentBuffer { - //NOTE(unsafe) Atomic read with no side effects + #[inline(always)] + pub fn set_fifo_error_interrupt_enable(&mut self, fifo_error_interrupt: bool) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX let dma = unsafe { &*I::ptr() }; - if dma.st[Self::NUMBER].cr.read().ct().bit_is_set() { - CurrentBuffer::DoubleBuffer - } else { - CurrentBuffer::FirstBuffer - } + dma.st[Self::NUMBER].fcr.modify(|_, w| w.feie().bit(fifo_error_interrupt)); } } )+ diff --git a/src/dma/mod.rs b/src/dma/mod.rs index 19622722..96278163 100644 --- a/src/dma/mod.rs +++ b/src/dma/mod.rs @@ -36,7 +36,10 @@ pub mod dma; // DMA1 and DMA2 pub mod bdma; pub mod traits; -use traits::{sealed::Bits, Direction, Stream, TargetAddress}; +use traits::{ + sealed::Bits, Direction, DoubleBufferedConfig, DoubleBufferedStream, + Stream, TargetAddress, +}; /// Errors. #[derive(PartialEq)] @@ -211,6 +214,11 @@ pub mod config { /// Very high priority. VeryHigh, } + impl Default for Priority { + fn default() -> Self { + Priority::Medium + } + } impl Bits for Priority { fn bits(self) -> u8 { @@ -271,144 +279,6 @@ pub mod config { } } } - - /// Contains the complete set of configuration for a DMA stream. - #[derive(Debug, Clone, Copy)] - pub struct DmaConfig { - pub(crate) priority: Priority, - pub(crate) memory_increment: bool, - pub(crate) peripheral_increment: bool, - pub(crate) transfer_complete_interrupt: bool, - pub(crate) half_transfer_interrupt: bool, - pub(crate) transfer_error_interrupt: bool, - pub(crate) direct_mode_error_interrupt: bool, - pub(crate) fifo_error_interrupt: bool, - pub(crate) double_buffer: bool, - pub(crate) fifo_threshold: FifoThreshold, - pub(crate) fifo_enable: bool, - pub(crate) memory_burst: BurstMode, - pub(crate) peripheral_burst: BurstMode, - } - - impl Default for DmaConfig { - fn default() -> Self { - Self { - priority: Priority::Medium, - memory_increment: false, - peripheral_increment: false, - transfer_complete_interrupt: false, - half_transfer_interrupt: false, - transfer_error_interrupt: false, - direct_mode_error_interrupt: false, - fifo_error_interrupt: false, - double_buffer: false, - fifo_threshold: FifoThreshold::QuarterFull, - fifo_enable: false, - memory_burst: BurstMode::NoBurst, - peripheral_burst: BurstMode::NoBurst, - } - } - } - - impl DmaConfig { - /// Set the priority. - #[inline(always)] - pub fn priority(mut self, priority: Priority) -> Self { - self.priority = priority; - self - } - - /// Set the memory_increment. - #[inline(always)] - pub fn memory_increment(mut self, memory_increment: bool) -> Self { - self.memory_increment = memory_increment; - self - } - /// Set the peripheral_increment. - #[inline(always)] - pub fn peripheral_increment( - mut self, - peripheral_increment: bool, - ) -> Self { - self.peripheral_increment = peripheral_increment; - self - } - /// Set the transfer_complete_interrupt. - #[inline(always)] - pub fn transfer_complete_interrupt( - mut self, - transfer_complete_interrupt: bool, - ) -> Self { - self.transfer_complete_interrupt = transfer_complete_interrupt; - self - } - /// Set the half_transfer_interrupt. - #[inline(always)] - pub fn half_transfer_interrupt( - mut self, - half_transfer_interrupt: bool, - ) -> Self { - self.half_transfer_interrupt = half_transfer_interrupt; - self - } - /// Set the transfer_error_interrupt. - #[inline(always)] - pub fn transfer_error_interrupt( - mut self, - transfer_error_interrupt: bool, - ) -> Self { - self.transfer_error_interrupt = transfer_error_interrupt; - self - } - /// Set the direct_mode_error_interrupt. - #[inline(always)] - pub fn direct_mode_error_interrupt( - mut self, - direct_mode_error_interrupt: bool, - ) -> Self { - self.direct_mode_error_interrupt = direct_mode_error_interrupt; - self - } - /// Set the fifo_error_interrupt. - #[inline(always)] - pub fn fifo_error_interrupt( - mut self, - fifo_error_interrupt: bool, - ) -> Self { - self.fifo_error_interrupt = fifo_error_interrupt; - self - } - /// Set the double_buffer. - #[inline(always)] - pub fn double_buffer(mut self, double_buffer: bool) -> Self { - self.double_buffer = double_buffer; - self - } - /// Set the fifo_threshold. - #[inline(always)] - pub fn fifo_threshold(mut self, fifo_threshold: FifoThreshold) -> Self { - self.fifo_threshold = fifo_threshold; - self - } - /// Set the fifo_enable. - #[inline(always)] - pub fn fifo_enable(mut self, fifo_enable: bool) -> Self { - self.fifo_enable = fifo_enable; - self - } - /// Set the memory_burst. - #[inline(always)] - pub fn memory_burst(mut self, memory_burst: BurstMode) -> Self { - self.memory_burst = memory_burst; - self - } - /// Set the peripheral_burst. - #[inline(always)] - pub fn peripheral_burst(mut self, peripheral_burst: BurstMode) -> Self { - self.peripheral_burst = peripheral_burst; - self - } - } } /// DMA Transfer. @@ -429,47 +299,30 @@ where transfer_length: u16, } -impl Transfer +impl + Transfer where - STREAM: Stream, + STREAM: DoubleBufferedStream + Stream, + CONFIG: DoubleBufferedConfig, DIR: Direction, PERIPHERAL: TargetAddress, BUF: WriteBuffer>::MemSize> + 'static, { /// Applies all fields in DmaConfig. - fn apply_config(&mut self, config: config::DmaConfig) { + fn apply_config(&mut self, config: CONFIG) { let msize = mem::size_of::<>::MemSize>() / 2; self.stream.clear_interrupts(); - self.stream.set_priority(config.priority); + // NOTE(unsafe) These values are correct because of the invariants of TargetAddress unsafe { self.stream.set_memory_size(msize as u8); self.stream.set_peripheral_size(msize as u8); } - self.stream.set_memory_increment(config.memory_increment); - self.stream - .set_peripheral_increment(config.peripheral_increment); - self.stream.set_transfer_complete_interrupt_enable( - config.transfer_complete_interrupt, - ); - self.stream - .set_half_transfer_interrupt_enable(config.half_transfer_interrupt); - self.stream.set_transfer_error_interrupt_enable( - config.transfer_error_interrupt, - ); - self.stream.set_direct_mode_error_interrupt_enable( - config.direct_mode_error_interrupt, - ); - self.stream - .set_fifo_error_interrupt_enable(config.fifo_error_interrupt); - self.stream.set_double_buffer(config.double_buffer); - self.stream.set_fifo_threshold(config.fifo_threshold); - self.stream.set_fifo_enable(config.fifo_enable); - self.stream.set_memory_burst(config.memory_burst); - self.stream.set_peripheral_burst(config.peripheral_burst); + + self.stream.apply_config(config); } /// Configures the DMA source and destination and applies supplied @@ -488,7 +341,7 @@ where peripheral: PERIPHERAL, mut memory: BUF, mut double_buf: Option, - config: config::DmaConfig, + config: CONFIG, ) -> Self { stream.disable(); @@ -517,9 +370,9 @@ where let is_mem2mem = DIR::direction() == DmaDirection::MemoryToMemory; if is_mem2mem { // Fifo must be enabled for memory to memory - if !config.fifo_enable { + if !config.is_fifo_enabled() { panic!("Fifo disabled."); - } else if config.double_buffer { + } else if config.is_double_buffered() { panic!("Double buffering enabled."); } } else { @@ -548,9 +401,8 @@ where } Some(db_len) } else { - // Double buffer mode must not be enabled if we haven't been given a - // second buffer - if config.double_buffer { + if config.is_double_buffered() { + // Error if we expected a double buffer but none was specified panic!("No second buffer."); } None @@ -570,7 +422,6 @@ where let mut transfer = Self { stream, - //_channel: PhantomData, peripheral, _direction: PhantomData, buf: Some(memory), @@ -759,30 +610,12 @@ where self.stream.clear_transfer_complete_interrupt(); } - /// Clear half transfer interrupt (htif) for the DMA stream. - #[inline(always)] - pub fn clear_half_transfer_interrupt(&mut self) { - self.stream.clear_half_transfer_interrupt(); - } - /// Clear transfer error interrupt (teif) for the DMA stream. #[inline(always)] pub fn clear_transfer_error_interrupt(&mut self) { self.stream.clear_transfer_error_interrupt(); } - /// Clear direct mode error interrupt (dmeif) for the DMA stream. - #[inline(always)] - pub fn clear_direct_mode_error_interrupt(&mut self) { - self.stream.clear_direct_mode_error_interrupt(); - } - - /// Clear fifo error interrupt (feif) for the DMA stream. - #[inline(always)] - pub fn clear_fifo_error_interrupt(&mut self) { - self.stream.clear_fifo_error_interrupt(); - } - /// Get the underlying stream of the transfer. /// /// # Safety diff --git a/src/dma/traits.rs b/src/dma/traits.rs index 2bde670f..3dd7a6fe 100644 --- a/src/dma/traits.rs +++ b/src/dma/traits.rs @@ -15,56 +15,32 @@ pub(crate) mod sealed { } use sealed::Sealed; -/// Trait for DMA streams types. +/// Minimal trait for DMA streams pub trait Stream: Sealed { - /// Number of the register stream. + /// Number of the stream register const NUMBER: usize; + /// Configuration structure for this stream. + type Config: Copy + Debug; + + /// Structure representing interrupts + type Interrupts: Copy + Debug; + + /// Apply the configation structure to this stream. + fn apply_config(&mut self, config: Self::Config); + /// Clear all interrupts for the DMA stream. fn clear_interrupts(&mut self); /// Clear transfer complete interrupt (tcif) for the DMA stream. fn clear_transfer_complete_interrupt(&mut self); - /// Clear half transfer interrupt (htif) for the DMA stream. - fn clear_half_transfer_interrupt(&mut self); - /// Clear transfer error interrupt (teif) for the DMA stream. fn clear_transfer_error_interrupt(&mut self); - /// Clear direct mode error interrupt (dmeif) for the DMA stream. - fn clear_direct_mode_error_interrupt(&mut self); - - /// Clear fifo error interrupt (feif) for the DMA stream. - fn clear_fifo_error_interrupt(&mut self); - /// Get transfer complete flag. fn get_transfer_complete_flag() -> bool; - /// Get half transfer flag. - fn get_half_transfer_flag() -> bool; - - /// Set the peripheral address (par) for the DMA stream. - unsafe fn set_peripheral_address(&mut self, value: u32); - - /// Set the memory address (m0ar) for the DMA stream. - unsafe fn set_memory_address(&mut self, value: u32); - - /// Get the memory address (m0ar) for the DMA stream. - fn get_memory_address(&self) -> u32; - - /// Set the double buffer address (m1ar) for the DMA stream. - unsafe fn set_memory_double_buffer_address(&mut self, value: u32); - - /// Get the double buffer address (m1ar) for the DMA stream. - fn get_memory_double_buffer_address(&self) -> u32; - - /// Set the number of transfers (ndt) for the DMA stream. - fn set_number_of_transfers(&mut self, value: u16); - - /// Get the number of transfers (ndt) for the DMA stream. - fn get_number_of_transfers() -> u16; - /// Enable the DMA stream. /// /// # Safety @@ -83,12 +59,63 @@ pub trait Stream: Sealed { /// stream's interrupt flags if the stream is active. fn disable(&mut self); - /// Sets the corresponding DMAMUX request line for this stream + /// Sets the request or trigger line for this stream fn set_request_line(&mut self, request_line: u8); - /// Set the priority (pl) the DMA stream. + /// Set the priority the DMA stream. fn set_priority(&mut self, priority: config::Priority); + /// Disable all interrupts for the DMA stream. + fn disable_interrupts(&mut self); + + /// Configure interrupts for the DMA stream + fn enable_interrupts(&mut self, interrupts: Self::Interrupts); + + /// Get the value of all the interrupts for this DMA stream + fn get_interrupts_enable() -> Self::Interrupts; + + /// Enable/disable the transfer complete interrupt (tcie) of the DMA stream. + fn set_transfer_complete_interrupt_enable( + &mut self, + transfer_complete_interrupt: bool, + ); + + /// Enable/disable the transfer error interrupt (teie) of the DMA stream. + fn set_transfer_error_interrupt_enable( + &mut self, + transfer_error_interrupt: bool, + ); +} + +/// Trait for Double-Buffered DMA streams +pub trait DoubleBufferedStream: Stream + Sealed { + /// Set the peripheral address (par) for the DMA stream. + unsafe fn set_peripheral_address(&mut self, value: u32); + + /// Set the memory address (m0ar) for the DMA stream. + unsafe fn set_memory_address(&mut self, value: u32); + + /// Get the memory address (m0ar) for the DMA stream. + fn get_memory_address(&self) -> u32; + + /// Set the double buffer address (m1ar) for the DMA stream. + unsafe fn set_memory_double_buffer_address(&mut self, value: u32); + + /// Get the double buffer address (m1ar) for the DMA stream. + fn get_memory_double_buffer_address(&self) -> u32; + + /// Enable/disable memory increment (minc) for the DMA stream. + fn set_memory_increment(&mut self, increment: bool); + + /// Enable/disable peripheral increment (pinc) for the DMA stream. + fn set_peripheral_increment(&mut self, increment: bool); + + /// Set the number of transfers (ndt) for the DMA stream. + fn set_number_of_transfers(&mut self, value: u16); + + /// Get the number of transfers (ndt) for the DMA stream. + fn get_number_of_transfers() -> u16; + /// Set the memory size (msize) for the DMA stream. /// /// # Safety @@ -99,6 +126,7 @@ pub trait Stream: Sealed { /// * 0 -> byte /// * 1 -> half word /// * 2 -> word + /// * 3 -> double workd unsafe fn set_memory_size(&mut self, size: u8); /// Set the peripheral memory size (psize) for the DMA stream. @@ -114,12 +142,6 @@ pub trait Stream: Sealed { /// * 2 -> word unsafe fn set_peripheral_size(&mut self, size: u8); - /// Enable/disable memory increment (minc) for the DMA stream. - fn set_memory_increment(&mut self, increment: bool); - - /// Enable/disable peripheral increment (pinc) for the DMA stream. - fn set_peripheral_increment(&mut self, increment: bool); - /// Set the direction (dir) of the DMA stream. fn set_direction(&mut self, direction: DmaDirection); @@ -127,69 +149,26 @@ pub trait Stream: Sealed { /// Enable bufferable transfers fn set_trbuff(&mut self, trbuff: bool); - /// Convenience method to configure the 4 common interrupts for the DMA stream. - fn set_interrupts_enable( - &mut self, - transfer_complete: bool, - half_transfer: bool, - transfer_error: bool, - direct_mode_error: bool, - ); - - /// Convenience method to get the value of the 4 common interrupts for the - /// DMA stream. - /// - /// The order of the returns are: `transfer_complete`, `half_transfer`, - /// `transfer_error` and `direct_mode_error`. - fn get_interrupts_enable() -> (bool, bool, bool, bool); - - /// Enable/disable the transfer complete interrupt (tcie) of the DMA stream. - fn set_transfer_complete_interrupt_enable( - &mut self, - transfer_complete_interrupt: bool, - ); - - /// Enable/disable the half transfer interrupt (htie) of the DMA stream. - fn set_half_transfer_interrupt_enable( - &mut self, - half_transfer_interrupt: bool, - ); - - /// Enable/disable the transfer error interrupt (teie) of the DMA stream. - fn set_transfer_error_interrupt_enable( - &mut self, - transfer_error_interrupt: bool, - ); - - /// Enable/disable the direct mode error interrupt (dmeie) of the DMA stream. - fn set_direct_mode_error_interrupt_enable( - &mut self, - direct_mode_error_interrupt: bool, - ); - - /// Enable/disable the fifo error interrupt (feie) of the DMA stream. - fn set_fifo_error_interrupt_enable(&mut self, fifo_error_interrupt: bool); - /// Enable/disable the double buffer (dbm) of the DMA stream. fn set_double_buffer(&mut self, double_buffer: bool); - /// Set the fifo threshold (fcr.fth) of the DMA stream. - fn set_fifo_threshold(&mut self, fifo_threshold: config::FifoThreshold); - - /// Enable/disable the fifo (dmdis) of the DMA stream. - fn set_fifo_enable(&mut self, fifo_enable: bool); - - /// Set memory burst mode (mburst) of the DMA stream. - fn set_memory_burst(&mut self, memory_burst: config::BurstMode); + /// Get which buffer is currently in use by the DMA. + fn current_buffer() -> CurrentBuffer; +} - /// Set peripheral burst mode (pburst) of the DMA stream. - fn set_peripheral_burst(&mut self, peripheral_burst: config::BurstMode); +/// Trait for Master DMA streams +/// +/// TODO +#[allow(unused)] +pub trait MasterStream: Stream + Sealed {} - /// Get the current fifo level (fs) of the DMA stream. - fn fifo_level() -> FifoLevel; +/// Trait for the configuration of Double-Buffered DMA streams +pub trait DoubleBufferedConfig { + /// Returns if the Double Buffer is enabled in the configuration + fn is_double_buffered(&self) -> bool; - /// Get which buffer is currently in use by the DMA. - fn current_buffer() -> CurrentBuffer; + /// Returns if the FIFO is enabled in the configuation + fn is_fifo_enabled(&self) -> bool; } /// DMA direction. From 7d5d843087ae7424e446a442a29fbb2964b9bc3b Mon Sep 17 00:00:00 2001 From: Richard Meadows <962920+richardeoin@users.noreply.github.com> Date: Fri, 23 Oct 2020 22:20:59 +0200 Subject: [PATCH 11/41] Remove unnecessary trait bound --- src/dma/traits.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dma/traits.rs b/src/dma/traits.rs index 3dd7a6fe..d4284090 100644 --- a/src/dma/traits.rs +++ b/src/dma/traits.rs @@ -21,7 +21,7 @@ pub trait Stream: Sealed { const NUMBER: usize; /// Configuration structure for this stream. - type Config: Copy + Debug; + type Config; /// Structure representing interrupts type Interrupts: Copy + Debug; From af88ee0ee8532eabce1d5e5639560279ef084fa8 Mon Sep 17 00:00:00 2001 From: Richard Meadows <962920+richardeoin@users.noreply.github.com> Date: Tue, 27 Oct 2020 21:40:52 +0100 Subject: [PATCH 12/41] Implement TargetAddress directly on the PAC/HAL struct itself Rather than on a mutable reference --- examples/i2c4_bdma.rs | 2 +- examples/spi_dma.rs | 18 +++++++----------- src/dma/bdma.rs | 4 ++-- src/dma/dma.rs | 17 +++++++++++++++++ src/dma/macros.rs | 8 ++++---- src/spi.rs | 5 +++++ 6 files changed, 36 insertions(+), 18 deletions(-) diff --git a/examples/i2c4_bdma.rs b/examples/i2c4_bdma.rs index d161e476..963fa56b 100644 --- a/examples/i2c4_bdma.rs +++ b/examples/i2c4_bdma.rs @@ -87,7 +87,7 @@ fn main() -> ! { let mut transfer: Transfer<_, _, PeripheralToMemory, _> = Transfer::init( streams.0, - &mut i2c, // Mutable reference to I2C HAL + i2c, unsafe { &mut BUFFER }, // uninitialised memory None, config, diff --git a/examples/spi_dma.rs b/examples/spi_dma.rs index 654d6d62..296f0984 100644 --- a/examples/spi_dma.rs +++ b/examples/spi_dma.rs @@ -69,7 +69,7 @@ fn main() -> ! { ); // SPI must be disabled to configure DMA - let mut spi = spi.disable(); + let spi = spi.disable(); // Initialise the source buffer, without taking any references to // uninitialisated memory @@ -93,27 +93,23 @@ fn main() -> ! { let config = DmaConfig::default().memory_increment(true); - let mut transfer: Transfer<_, _, MemoryToPeripheral, _> = Transfer::init( - streams.0, - spi.inner_mut(), // Mutable reference to SPI register block - buffer, - None, - config, - ); + let mut transfer: Transfer<_, _, MemoryToPeripheral, _> = + Transfer::init(streams.0, spi, buffer, None, config); transfer.start(|spi| { // This closure runs right after enabling the stream // Enable DMA Tx buffer by setting the TXDMAEN bit in the SPI_CFG1 // register - spi.cfg1.modify(|_, w| w.txdmaen().enabled()); + spi.inner_mut().cfg1.modify(|_, w| w.txdmaen().enabled()); // Enable the SPI by setting the SPE bit - spi.cr1 + spi.inner_mut() + .cr1 .write(|w| w.ssi().slave_not_selected().spe().enabled()); // write CSTART to start a transaction in master mode - spi.cr1.modify(|_, w| w.cstart().started()); + spi.inner_mut().cr1.modify(|_, w| w.cstart().started()); }); // Wait for transfer to complete diff --git a/src/dma/bdma.rs b/src/dma/bdma.rs index 754dcd26..641f2489 100644 --- a/src/dma/bdma.rs +++ b/src/dma/bdma.rs @@ -545,6 +545,6 @@ peripheral_target_address!( (pac::SPI6, txdr, u8, M2P, DMAReq::SPI6_TX_DMA), (pac::I2C4, rxdr, u8, P2M, DMAReq::I2C4_RX_DMA), (pac::I2C4, txdr, u8, M2P, DMAReq::I2C4_TX_DMA), - (INNER, I2c, rxdr, u8, P2M, DMAReq::I2C4_RX_DMA), - (INNER, I2c, txdr, u8, M2P, DMAReq::I2C4_TX_DMA), + (INNER: I2c, rxdr, u8, P2M, DMAReq::I2C4_RX_DMA), + (INNER: I2c, txdr, u8, M2P, DMAReq::I2C4_TX_DMA), ); diff --git a/src/dma/dma.rs b/src/dma/dma.rs index 8e52f5ef..12a6acc0 100644 --- a/src/dma/dma.rs +++ b/src/dma/dma.rs @@ -13,6 +13,7 @@ use crate::{ pac::{self, DMA1, DMA2, DMAMUX1}, rcc::{rec, rec::ResetEnable}, //serial::{Rx, Tx}, + spi, }; use core::ops::Deref; @@ -763,6 +764,22 @@ peripheral_target_address!( (pac::SPI1, txdr, u8, M2P, DMAReq::SPI1_TX_DMA), (pac::SPI2, rxdr, u8, P2M, DMAReq::SPI2_RX_DMA), (pac::SPI2, txdr, u8, M2P, DMAReq::SPI2_TX_DMA), + ( + // SPI peripheral must be disabled to configure DMA + INNER: spi::Spi, + rxdr, + u8, + P2M, + DMAReq::SPI2_RX_DMA + ), + ( + // SPI peripheral must be disabled to configure DMA + INNER: spi::Spi, + txdr, + u8, + M2P, + DMAReq::SPI2_TX_DMA + ), (pac::SPI3, rxdr, u8, P2M, DMAReq::SPI3_RX_DMA), (pac::SPI3, txdr, u8, M2P, DMAReq::SPI3_TX_DMA), (pac::SPI4, rxdr, u8, P2M, DMAReq::SPI4_RX_DMA), diff --git a/src/dma/macros.rs b/src/dma/macros.rs index dd4a8a8c..5ce4abe0 100644 --- a/src/dma/macros.rs +++ b/src/dma/macros.rs @@ -13,7 +13,7 @@ macro_rules! peripheral_target_address { macro_rules! peripheral_target_instance { (($peripheral:ty, $register:ident $(($TRBUFF:ident))*, $size:ty, $dir:ty $(, $mux:expr)*)) => { - unsafe impl TargetAddress<$dir> for &mut $peripheral { + unsafe impl TargetAddress<$dir> for $peripheral { #[inline(always)] fn address(&self) -> u32 { &self.$register as *const _ as u32 @@ -29,9 +29,9 @@ macro_rules! peripheral_target_instance { } }; - ((INNER, $peripheral:ty, $register:ident $(($TRBUFF:ident))*, $size:ty, + ((INNER: $peripheral:ty, $register:ident $(($TRBUFF:ident))*, $size:ty, $dir:ty $(, $mux:expr)*)) => { - unsafe impl TargetAddress<$dir> for &mut $peripheral { + unsafe impl TargetAddress<$dir> for $peripheral { #[inline(always)] fn address(&self) -> u32 { &self.inner().$register as *const _ as u32 @@ -49,7 +49,7 @@ macro_rules! peripheral_target_instance { (($peripheral:ty, $channel:ident.$register:ident, $size:ty, $dir:ty $(, $mux:expr)*)) => { - unsafe impl TargetAddress<$dir> for &mut $peripheral { + unsafe impl TargetAddress<$dir> for $peripheral { #[inline(always)] fn address(&self) -> u32 { &self.$channel.$register as *const _ as u32 diff --git a/src/spi.rs b/src/spi.rs index 125d852f..a620676c 100644 --- a/src/spi.rs +++ b/src/spi.rs @@ -558,6 +558,11 @@ macro_rules! spi { impl Spi<$SPIX, EN, $TY> { + /// Returns a mutable reference to the inner peripheral + pub fn inner(&self) -> &$SPIX { + &self.spi + } + /// Returns a mutable reference to the inner peripheral pub fn inner_mut(&mut self) -> &mut $SPIX { &mut self.spi From d22689130fe0f396a9a4a4c5a8d87dd2de649c39 Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Sat, 31 Oct 2020 15:09:46 +0200 Subject: [PATCH 13/41] Fix a few typos in the sai module's comments. --- src/sai/i2s.rs | 6 +++--- src/sai/mod.rs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/sai/i2s.rs b/src/sai/i2s.rs index 683959b6..ce3dc81e 100644 --- a/src/sai/i2s.rs +++ b/src/sai/i2s.rs @@ -323,7 +323,7 @@ pub struct I2S { } impl INTERFACE for I2S {} -/// Trait to extend SAI periperhals +/// Trait to extend SAI peripherals pub trait SaiI2sExt: Sized { type Rec: ResetEnable; fn i2s_ch_a( @@ -444,7 +444,7 @@ macro_rules! i2s { .try_into() .expect("SAI kernel clock is out of range for required MCLK"); - // Configure SAI peripeheral + // Configure SAI peripheral let mut per_sai = Sai { rb: sai, master_channel: SaiChannel::ChannelA, @@ -511,7 +511,7 @@ macro_rules! i2s { .try_into() .expect("SAI kernel clock is out of range for required MCLK"); - // Configure SAI peripeheral + // Configure SAI peripheral let mut per_sai = Sai { rb: sai, master_channel: SaiChannel::ChannelB, diff --git a/src/sai/mod.rs b/src/sai/mod.rs index 2d5c85b3..d9fe34cd 100644 --- a/src/sai/mod.rs +++ b/src/sai/mod.rs @@ -239,7 +239,7 @@ macro_rules! sai_hal { }; } - /// Used to operate the audio block(s) with an external SAI for synchoniozation + /// Used to operate the audio block(s) with an external SAI for synchronization /// Refer to RM0433 rev 7 section 51.4.4 for valid values /// /// In short 0-3 maps SAI1-4 with the ones pointing to self being reserved. @@ -249,7 +249,7 @@ macro_rules! sai_hal { unsafe { &self.rb.gcr.modify(|_, w| w.syncout().bits(selection)) }; } - /// Synchoniazation output for other SAI blocks + /// Synchronization output for other SAI blocks pub fn set_sync_output(&mut self, channel: Option) { match channel { Some(SaiChannel::ChannelA) => unsafe { &self.rb.gcr.modify(|_, w| w.syncout().bits(0b01) ) }, From a1b9a2728fcd6fac3046e7b0200958eaf62a6745 Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Sat, 31 Oct 2020 17:08:09 +0200 Subject: [PATCH 14/41] Add enable_dma() method to SAI peripheral --- src/sai/mod.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/sai/mod.rs b/src/sai/mod.rs index d9fe34cd..cc3c43d2 100644 --- a/src/sai/mod.rs +++ b/src/sai/mod.rs @@ -258,6 +258,14 @@ macro_rules! sai_hal { }; } + /// Enable DMA for the SAI peripheral. + pub fn enable_dma(&mut self, channel: SaiChannel) { + match channel { + SaiChannel::ChannelA => self.rb.cha.cr1.modify(|_, w| w.dmaen().enabled()), + SaiChannel::ChannelB => self.rb.chb.cr1.modify(|_, w| w.dmaen().enabled()), + }; + } + /// Releases the SAI peripheral pub fn free(self) -> ($SAIX, rec::$Rec) { // Refer to RM0433 Rev 7 51.4.15 Disabling the SAI From 5d2b2a85f9fc497a05a2eea3a4e0e9a53951c147 Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Sat, 31 Oct 2020 17:11:44 +0200 Subject: [PATCH 15/41] Implement peripheral_target_address macro for SAI1-4 Add support to DMA Stream for the half transfer interrupt Add support to DMA Config for enabling/disabling circular buffering --- examples/sai_dma_passthru.rs | 224 +++++++++++++++++++++++++++++++++++ src/dma/bdma.rs | 31 +++++ src/dma/dma.rs | 47 ++++++++ src/dma/mod.rs | 11 ++ src/dma/traits.rs | 15 +++ 5 files changed, 328 insertions(+) create mode 100644 examples/sai_dma_passthru.rs diff --git a/examples/sai_dma_passthru.rs b/examples/sai_dma_passthru.rs new file mode 100644 index 00000000..62fc6a26 --- /dev/null +++ b/examples/sai_dma_passthru.rs @@ -0,0 +1,224 @@ +// This demo code runs on the Electro Smith Daisy Seed board +// https://www.electro-smith.com/daisy + +#![allow(unused_macros)] +//#![deny(warnings)] +//#![deny(unsafe_code)] + +#![no_main] +#![no_std] + +use cortex_m::asm; + +use cortex_m_rt::{entry}; + +use stm32h7xx_hal as hal; +use hal::stm32; +use hal::{ prelude::*, pac }; +use hal::hal::digital::v2::OutputPin; +use hal::rcc::{ rec::Sai1ClkSel }; +use hal::sai::{ self, SaiI2sExt, SaiChannel }; +use hal::dma; +use hal::time::Hertz; + +use pac::interrupt; + +use log::info; + +#[macro_use] +mod utilities; + + +// = global constants ========================================================= + +// 32 samples * 2 audio channels * 2 buffers +const DMA_BUFFER_LENGTH:usize = 32 * 2 * 2; + +const AUDIO_SAMPLE_HZ: Hertz = Hertz(48_000); + +// Using PLL3_P for SAI1 clock +// The rate should be equal to sample rate * 256 +// But not less than so targetting 257 +const PLL3_P_HZ: Hertz = Hertz(AUDIO_SAMPLE_HZ.0 * 257); + + +// = static data ============================================================== + +#[link_section = ".sram3"] +static mut TX_BUFFER: [u32; DMA_BUFFER_LENGTH] = [0; DMA_BUFFER_LENGTH]; +#[link_section = ".sram3"] +static mut RX_BUFFER: [u32; DMA_BUFFER_LENGTH] = [0; DMA_BUFFER_LENGTH]; + + +// = entry ==================================================================== + +#[entry] +fn main() -> ! { + utilities::logger::init(); + + // - initialize power & clocks ---------------------------------------- + + let dp = hal::pac::Peripherals::take().unwrap(); + let pwr = dp.PWR.constrain(); + let vos = pwr.freeze(); + let ccdr = dp.RCC.constrain() + .use_hse(16.mhz()) + .sys_ck(400.mhz()) + .pll3_p_ck(PLL3_P_HZ) + .freeze(vos, &dp.SYSCFG); + + // enable sai1 peripheral and set clock to pll3 + let sai1_rec = ccdr.peripheral.SAI1.kernel_clk_mux(Sai1ClkSel::PLL3_P); + + + // - configure pins --------------------------------------------------- + + let gpiob = dp.GPIOB.split(ccdr.peripheral.GPIOB); + let mut ak4556_reset = gpiob.pb11.into_push_pull_output(); + + let gpioe = dp.GPIOE.split(ccdr.peripheral.GPIOE); + let sai1_pins = ( + gpioe.pe2.into_alternate_af6(), // MCLK_A + gpioe.pe5.into_alternate_af6(), // SCK_A + gpioe.pe4.into_alternate_af6(), // FS_A + gpioe.pe6.into_alternate_af6(), // SD_A + Some(gpioe.pe3.into_alternate_af6()), // SD_B + ); + + + // - configure dma1 ------------------------------------------------------- + + let dma1_streams = dma::dma::StreamsTuple::new(dp.DMA1, ccdr.peripheral.DMA1); + + // dma1 stream 0 + let tx_buffer: &'static mut [u32; DMA_BUFFER_LENGTH] = unsafe { &mut TX_BUFFER }; + let dma_config = dma::dma::DmaConfig::default() + .priority(dma::config::Priority::High) + .memory_increment(true) + .peripheral_increment(false) + .circular_buffer(true) + .fifo_enable(false); + let mut dma1_str0: dma::Transfer<_, _, dma::MemoryToPeripheral, _> = dma::Transfer::init( + dma1_streams.0, + unsafe { pac::Peripherals::steal().SAI1 }, + tx_buffer, + None, + dma_config, + ); + + // dma1 stream 1 + let rx_buffer: &'static mut [u32; DMA_BUFFER_LENGTH] = unsafe { &mut RX_BUFFER }; + let dma_config = dma_config.transfer_complete_interrupt(true) + .half_transfer_interrupt(true); + let mut dma1_str1: dma::Transfer<_, _, dma::PeripheralToMemory, _> = dma::Transfer::init( + dma1_streams.1, + unsafe { pac::Peripherals::steal().SAI1 }, + rx_buffer, + None, + dma_config, + ); + + // - configure sai ---------------------------------------------------- + + // configure sai for FS: 48 KHz, bits: 24, Data Format: MSB Justified, LRCK Order: Hi/Lo + let sai1_tx_config = sai::I2SChanConfig::new(sai::I2SDir::Tx) + .set_frame_sync_active_high(true) + .set_clock_strobe(sai::I2SClockStrobe::Falling); + + let sai1_rx_config = sai::I2SChanConfig::new(sai::I2SDir::Rx) + .set_sync_type(sai::I2SSync::Internal) + .set_frame_sync_active_high(true) + .set_clock_strobe(sai::I2SClockStrobe::Rising); + + let mut sai1 = dp.SAI1.i2s_ch_a( + sai1_pins, + AUDIO_SAMPLE_HZ, + sai::I2SDataSize::BITS_24, + sai1_rec, + &ccdr.clocks, + sai1_tx_config, + Some(sai1_rx_config), + ); + + + // - reset ak4556 codec ----------------------------------------------- + + ak4556_reset.set_low().unwrap(); + asm::delay(480_000); // ~ 1ms (datasheet specifies minimum 150ns) + ak4556_reset.set_high().unwrap(); + + + // - start audio ------------------------------------------------------ + + // unmask interrupt handler for dma 1, stream 1 + unsafe { pac::NVIC::unmask(pac::Interrupt::DMA1_STR1); } + + dma1_str1.start(|_sai1_rb| { + sai1.enable_dma(SaiChannel::ChannelB); + }); + + dma1_str0.start(|sai1_rb| { + sai1.enable_dma(SaiChannel::ChannelA); + + // wait until sai1's fifo starts to receive data + info!("sai1 fifo waiting to receive data"); + while sai1_rb.cha.sr.read().flvl().is_empty() { } + info!("audio started"); + + sai1.enable(); + }); + + + // - dma1 stream 1 interrupt handler -------------------------------------- + + type TransferDma1Str1 = dma::Transfer, + stm32::SAI1, + dma::PeripheralToMemory, + &'static mut [u32; 128]>; + + static mut TRANSFER_DMA1_STR1: Option = None; + unsafe { + TRANSFER_DMA1_STR1 = Some(dma1_str1); + } + + #[interrupt] + fn DMA1_STR1() { + let tx_buffer: &'static mut [u32; DMA_BUFFER_LENGTH] = unsafe { &mut TX_BUFFER }; + let rx_buffer: &'static mut [u32; DMA_BUFFER_LENGTH] = unsafe { &mut RX_BUFFER }; + + let stereo_block_length = tx_buffer.len() / 2; + + if let Some(transfer) = unsafe { &mut TRANSFER_DMA1_STR1 } { + let skip = if transfer.get_half_transfer_flag() { + transfer.clear_half_transfer_interrupt(); + (0, stereo_block_length) + } else if transfer.get_transfer_complete_flag() { + transfer.clear_transfer_complete_interrupt(); + (stereo_block_length, 0) + } else { + return; + }; + + // pass thru + let mut index = 0; + while index < stereo_block_length { + let tx0 = index + skip.0; + let tx1 = tx0 + 1; + let rx0 = index + skip.1; + let rx1 = rx0 + 1; + + tx_buffer[tx0] = rx_buffer[rx0]; + tx_buffer[tx1] = rx_buffer[rx1]; + + index += 2; + } + } + } + + + // - main loop ------------------------------------------------------------ + + loop { + asm::wfi(); + } +} diff --git a/src/dma/bdma.rs b/src/dma/bdma.rs index 641f2489..6bb9030f 100644 --- a/src/dma/bdma.rs +++ b/src/dma/bdma.rs @@ -252,6 +252,14 @@ macro_rules! bdma_stream { ); } + #[inline(always)] + fn clear_half_transfer_interrupt(&mut self) { + //NOTE(unsafe) Atomic write with no side-effects and we only access the bits + // that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.$ifcr.write(|w| w.$htif().set_bit()); + } + #[inline(always)] fn clear_transfer_complete_interrupt(&mut self) { //NOTE(unsafe) Atomic write with no side-effects and we only access the bits @@ -268,6 +276,13 @@ macro_rules! bdma_stream { dma.$ifcr.write(|w| w.$teif().set_bit()); } + #[inline(always)] + fn get_half_transfer_flag() -> bool { + //NOTE(unsafe) Atomic read with no side effects + let dma = unsafe { &*I::ptr() }; + dma.$isr.read().$htisr().bit_is_set() + } + #[inline(always)] fn get_transfer_complete_flag() -> bool { //NOTE(unsafe) Atomic read with no side effects @@ -359,6 +374,13 @@ macro_rules! bdma_stream { } } + #[inline(always)] + fn set_half_transfer_interrupt_enable(&mut self, half_transfer_interrupt: bool) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.ch[Self::NUMBER].cr.modify(|_, w| w.htie().bit(half_transfer_interrupt)); + } + #[inline(always)] fn set_transfer_complete_interrupt_enable(&mut self, transfer_complete_interrupt: bool) { //NOTE(unsafe) We only access the registers that belongs to the StreamX @@ -473,6 +495,13 @@ macro_rules! bdma_stream { // BDMA does not have a TRBUFF bit } + #[inline(always)] + fn set_circular_buffer(&mut self, circular_buffer: bool) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.ch[Self::NUMBER].cr.modify(|_, w| w.circ().bit(circular_buffer)); + } + #[inline(always)] fn set_double_buffer(&mut self, double_buffer: bool) { //NOTE(unsafe) We only access the registers that belongs to the StreamX @@ -547,4 +576,6 @@ peripheral_target_address!( (pac::I2C4, txdr, u8, M2P, DMAReq::I2C4_TX_DMA), (INNER: I2c, rxdr, u8, P2M, DMAReq::I2C4_RX_DMA), (INNER: I2c, txdr, u8, M2P, DMAReq::I2C4_TX_DMA), + (pac::SAI4, cha.dr, u32, M2P, DMAReq::SAI4_A_DMA), + (pac::SAI4, chb.dr, u32, P2M, DMAReq::SAI4_B_DMA), ); diff --git a/src/dma/dma.rs b/src/dma/dma.rs index 12a6acc0..448e77f2 100644 --- a/src/dma/dma.rs +++ b/src/dma/dma.rs @@ -92,6 +92,7 @@ pub struct DmaConfig { pub(crate) transfer_error_interrupt: bool, pub(crate) direct_mode_error_interrupt: bool, pub(crate) fifo_error_interrupt: bool, + pub(crate) circular_buffer: bool, pub(crate) double_buffer: bool, pub(crate) fifo_threshold: config::FifoThreshold, pub(crate) fifo_enable: bool, @@ -110,6 +111,7 @@ impl Default for DmaConfig { transfer_error_interrupt: false, direct_mode_error_interrupt: false, fifo_error_interrupt: false, + circular_buffer: false, double_buffer: false, fifo_threshold: config::FifoThreshold::QuarterFull, fifo_enable: false, @@ -193,6 +195,12 @@ impl DmaConfig { self.fifo_error_interrupt = fifo_error_interrupt; self } + /// Set the circular_buffer. + #[inline(always)] + pub fn circular_buffer(mut self, circular_buffer: bool) -> Self { + self.circular_buffer = circular_buffer; + self + } /// Set the double_buffer. #[inline(always)] pub fn double_buffer(mut self, double_buffer: bool) -> Self { @@ -337,6 +345,7 @@ macro_rules! dma_stream { ); self .set_fifo_error_interrupt_enable(config.fifo_error_interrupt); + self.set_circular_buffer(config.circular_buffer); self.set_double_buffer(config.double_buffer); self.set_fifo_threshold(config.fifo_threshold); self.set_fifo_enable(config.fifo_enable); @@ -358,6 +367,14 @@ macro_rules! dma_stream { ); } + #[inline(always)] + fn clear_half_transfer_interrupt(&mut self) { + //NOTE(unsafe) Atomic write with no side-effects and we only access the bits + // that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.$ifcr.write(|w| w.$htif().set_bit()); + } + #[inline(always)] fn clear_transfer_complete_interrupt(&mut self) { //NOTE(unsafe) Atomic write with no side-effects and we only access the bits @@ -374,6 +391,13 @@ macro_rules! dma_stream { dma.$ifcr.write(|w| w.$teif().set_bit()); } + #[inline(always)] + fn get_half_transfer_flag() -> bool { + //NOTE(unsafe) Atomic read with no side effects + let dma = unsafe { &*I::ptr() }; + dma.$isr.read().$htisr().bit_is_set() + } + #[inline(always)] fn get_transfer_complete_flag() -> bool { //NOTE(unsafe) Atomic read with no side effects @@ -472,6 +496,13 @@ macro_rules! dma_stream { } } + #[inline(always)] + fn set_half_transfer_interrupt_enable(&mut self, half_transfer_interrupt: bool) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.st[Self::NUMBER].cr.modify(|_, w| w.htie().bit(half_transfer_interrupt)); + } + #[inline(always)] fn set_transfer_complete_interrupt_enable(&mut self, transfer_complete_interrupt: bool) { //NOTE(unsafe) We only access the registers that belongs to the StreamX @@ -586,6 +617,13 @@ macro_rules! dma_stream { dma.st[Self::NUMBER].cr.modify(|_, w| w.trbuff().bit(trbuff)); } + #[inline(always)] + fn set_circular_buffer(&mut self, circular_buffer: bool) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.st[Self::NUMBER].cr.modify(|_, w| w.circ().bit(circular_buffer)); + } + #[inline(always)] fn set_double_buffer(&mut self, double_buffer: bool) { //NOTE(unsafe) We only access the registers that belongs to the StreamX @@ -792,3 +830,12 @@ peripheral_target_address!( (pac::USART1, rdr(TRBUFF), u8, P2M, DMAReq::USART1_RX_DMA), (pac::USART1, tdr(TRBUFF), u8, M2P, DMAReq::USART1_TX_DMA), ); + +peripheral_target_address!( + (pac::SAI1, cha.dr, u32, M2P, DMAReq::SAI1A_DMA), + (pac::SAI1, chb.dr, u32, P2M, DMAReq::SAI1B_DMA), + (pac::SAI2, cha.dr, u32, M2P, DMAReq::SAI2A_DMA), + (pac::SAI2, chb.dr, u32, P2M, DMAReq::SAI2B_DMA), + (pac::SAI3, cha.dr, u32, M2P, DMAReq::SAI3_A_DMA), + (pac::SAI3, chb.dr, u32, P2M, DMAReq::SAI3_B_DMA), +); diff --git a/src/dma/mod.rs b/src/dma/mod.rs index 96278163..5e5e5016 100644 --- a/src/dma/mod.rs +++ b/src/dma/mod.rs @@ -604,6 +604,12 @@ where self.stream.clear_interrupts(); } + /// Clear half transfer interrupt (htif) for the DMA stream. + #[inline(always)] + pub fn clear_half_transfer_interrupt(&mut self) { + self.stream.clear_half_transfer_interrupt(); + } + /// Clear transfer complete interrupt (tcif) for the DMA stream. #[inline(always)] pub fn clear_transfer_complete_interrupt(&mut self) { @@ -627,6 +633,11 @@ where &mut self.stream } + #[inline(always)] + pub fn get_half_transfer_flag(&self) -> bool { + STREAM::get_half_transfer_flag() + } + #[inline(always)] pub fn get_transfer_complete_flag(&self) -> bool { STREAM::get_transfer_complete_flag() diff --git a/src/dma/traits.rs b/src/dma/traits.rs index d4284090..bdab1bf4 100644 --- a/src/dma/traits.rs +++ b/src/dma/traits.rs @@ -32,12 +32,18 @@ pub trait Stream: Sealed { /// Clear all interrupts for the DMA stream. fn clear_interrupts(&mut self); + /// Clear half transfer interrupt (htif) for the DMA stream. + fn clear_half_transfer_interrupt(&mut self); + /// Clear transfer complete interrupt (tcif) for the DMA stream. fn clear_transfer_complete_interrupt(&mut self); /// Clear transfer error interrupt (teif) for the DMA stream. fn clear_transfer_error_interrupt(&mut self); + /// Get half transfer flag. + fn get_half_transfer_flag() -> bool; + /// Get transfer complete flag. fn get_transfer_complete_flag() -> bool; @@ -74,6 +80,12 @@ pub trait Stream: Sealed { /// Get the value of all the interrupts for this DMA stream fn get_interrupts_enable() -> Self::Interrupts; + /// Enable/disable the half transfer interrupt (htie) of the DMA stream. + fn set_half_transfer_interrupt_enable( + &mut self, + transfer_complete_interrupt: bool, + ); + /// Enable/disable the transfer complete interrupt (tcie) of the DMA stream. fn set_transfer_complete_interrupt_enable( &mut self, @@ -149,6 +161,9 @@ pub trait DoubleBufferedStream: Stream + Sealed { /// Enable bufferable transfers fn set_trbuff(&mut self, trbuff: bool); + /// Enable/disable circular buffering for the DMA stream. + fn set_circular_buffer(&mut self, circular_buffer: bool); + /// Enable/disable the double buffer (dbm) of the DMA stream. fn set_double_buffer(&mut self, double_buffer: bool); From e2db677e1bebf692adf2b23f8d1ec57a02bdb067 Mon Sep 17 00:00:00 2001 From: Antoine van Gelder Date: Sat, 31 Oct 2020 17:17:18 +0200 Subject: [PATCH 16/41] Add a second iteration of the SAI passthrough example which uses the embedded_hal DMA implementation --- Cargo.toml | 4 ++++ examples/sai_dma_passthru.rs | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1bc53162..abc2ec01 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -175,6 +175,10 @@ required-features = ["rt", "rtc"] name = "sai-i2s-passthru" required-features = ["rm0433"] +[[example]] +name = "sai_dma_passthru" +required-features = ["rm0433"] + [[example]] name = "dma" required-features = ["rm0433"] diff --git a/examples/sai_dma_passthru.rs b/examples/sai_dma_passthru.rs index 62fc6a26..3946e366 100644 --- a/examples/sai_dma_passthru.rs +++ b/examples/sai_dma_passthru.rs @@ -2,7 +2,7 @@ // https://www.electro-smith.com/daisy #![allow(unused_macros)] -//#![deny(warnings)] +#![deny(warnings)] //#![deny(unsafe_code)] #![no_main] From 8d0605ee7166294a819eb34133a2fd4c1a4843fd Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Tue, 3 Nov 2020 08:54:44 +0100 Subject: [PATCH 17/41] Updating DMA macro for SPI types --- src/dma/dma.rs | 31 +++++------------------------ src/dma/macros.rs | 50 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 26 deletions(-) diff --git a/src/dma/dma.rs b/src/dma/dma.rs index 448e77f2..7d219e3b 100644 --- a/src/dma/dma.rs +++ b/src/dma/dma.rs @@ -798,32 +798,11 @@ type P2M = PeripheralToMemory; type M2P = MemoryToPeripheral; peripheral_target_address!( - (pac::SPI1, rxdr, u8, P2M, DMAReq::SPI1_RX_DMA), - (pac::SPI1, txdr, u8, M2P, DMAReq::SPI1_TX_DMA), - (pac::SPI2, rxdr, u8, P2M, DMAReq::SPI2_RX_DMA), - (pac::SPI2, txdr, u8, M2P, DMAReq::SPI2_TX_DMA), - ( - // SPI peripheral must be disabled to configure DMA - INNER: spi::Spi, - rxdr, - u8, - P2M, - DMAReq::SPI2_RX_DMA - ), - ( - // SPI peripheral must be disabled to configure DMA - INNER: spi::Spi, - txdr, - u8, - M2P, - DMAReq::SPI2_TX_DMA - ), - (pac::SPI3, rxdr, u8, P2M, DMAReq::SPI3_RX_DMA), - (pac::SPI3, txdr, u8, M2P, DMAReq::SPI3_TX_DMA), - (pac::SPI4, rxdr, u8, P2M, DMAReq::SPI4_RX_DMA), - (pac::SPI4, txdr, u8, M2P, DMAReq::SPI4_TX_DMA), - (pac::SPI5, rxdr, u8, P2M, DMAReq::SPI5_RX_DMA), - (pac::SPI5, txdr, u8, M2P, DMAReq::SPI5_TX_DMA) + (SPI: pac::SPI1, rxdr, txdr, [u8, u16], DMAReq::SPI1_RX_DMA, DMAReq::SPI1_TX_DMA), + (SPI: pac::SPI2, rxdr, txdr, [u8, u16], DMAReq::SPI2_RX_DMA, DMAReq::SPI2_TX_DMA), + (SPI: pac::SPI3, rxdr, txdr, [u8, u16], DMAReq::SPI3_RX_DMA, DMAReq::SPI3_TX_DMA), + (SPI: pac::SPI4, rxdr, txdr, [u8, u16], DMAReq::SPI4_RX_DMA, DMAReq::SPI4_TX_DMA), + (SPI: pac::SPI5, rxdr, txdr, [u8, u16], DMAReq::SPI5_RX_DMA, DMAReq::SPI5_TX_DMA) ); peripheral_target_address!( diff --git a/src/dma/macros.rs b/src/dma/macros.rs index 5ce4abe0..55f667cf 100644 --- a/src/dma/macros.rs +++ b/src/dma/macros.rs @@ -29,6 +29,56 @@ macro_rules! peripheral_target_instance { } }; + ((SPI: $peripheral:ty, $rxreg:ident, $txreg:ident, [$($size:ty),+], $rxmux:expr, $txmux:expr)) => { + // Access via PAC peripheral structures implies u8 sizing, as the sizing is unknown. + unsafe impl TargetAddress for $peripheral { + #[inline(always)] + fn address(&self) -> u32 { + &self.$txreg as *const _ as u32 + } + + type MemSize = u8; + + const REQUEST_LINE: Option = Some($txmux as u8); + } + + unsafe impl TargetAddress for $peripheral { + #[inline(always)] + fn address(&self) -> u32 { + &self.$rxreg as *const _ as u32 + } + + type MemSize = u8; + + const REQUEST_LINE: Option = Some($rxmux as u8); + } + + // For each size + $( + unsafe impl TargetAddress for spi::Spi<$peripheral, spi::Disabled, $size> { + #[inline(always)] + fn address(&self) -> u32 { + &self.inner().$txreg as *const _ as u32 + } + + type MemSize = $size; + + const REQUEST_LINE: Option = Some($txmux as u8); + } + + unsafe impl TargetAddress for spi::Spi<$peripheral, spi::Disabled, $size> { + #[inline(always)] + fn address(&self) -> u32 { + &self.inner().$rxreg as *const _ as u32 + } + + type MemSize = $size; + + const REQUEST_LINE: Option = Some($rxmux as u8); + } + )+ + }; + ((INNER: $peripheral:ty, $register:ident $(($TRBUFF:ident))*, $size:ty, $dir:ty $(, $mux:expr)*)) => { unsafe impl TargetAddress<$dir> for $peripheral { From 0c37c70cc0c8fd2a4b970a75287b3c006f4d0eb9 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Tue, 3 Nov 2020 09:38:01 +0100 Subject: [PATCH 18/41] Formatting --- src/dma/dma.rs | 45 ++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 5 deletions(-) diff --git a/src/dma/dma.rs b/src/dma/dma.rs index 7d219e3b..54b398b7 100644 --- a/src/dma/dma.rs +++ b/src/dma/dma.rs @@ -798,11 +798,46 @@ type P2M = PeripheralToMemory; type M2P = MemoryToPeripheral; peripheral_target_address!( - (SPI: pac::SPI1, rxdr, txdr, [u8, u16], DMAReq::SPI1_RX_DMA, DMAReq::SPI1_TX_DMA), - (SPI: pac::SPI2, rxdr, txdr, [u8, u16], DMAReq::SPI2_RX_DMA, DMAReq::SPI2_TX_DMA), - (SPI: pac::SPI3, rxdr, txdr, [u8, u16], DMAReq::SPI3_RX_DMA, DMAReq::SPI3_TX_DMA), - (SPI: pac::SPI4, rxdr, txdr, [u8, u16], DMAReq::SPI4_RX_DMA, DMAReq::SPI4_TX_DMA), - (SPI: pac::SPI5, rxdr, txdr, [u8, u16], DMAReq::SPI5_RX_DMA, DMAReq::SPI5_TX_DMA) + ( + SPI: pac::SPI1, + rxdr, + txdr, + [u8, u16], + DMAReq::SPI1_RX_DMA, + DMAReq::SPI1_TX_DMA + ), + ( + SPI: pac::SPI2, + rxdr, + txdr, + [u8, u16], + DMAReq::SPI2_RX_DMA, + DMAReq::SPI2_TX_DMA + ), + ( + SPI: pac::SPI3, + rxdr, + txdr, + [u8, u16], + DMAReq::SPI3_RX_DMA, + DMAReq::SPI3_TX_DMA + ), + ( + SPI: pac::SPI4, + rxdr, + txdr, + [u8, u16], + DMAReq::SPI4_RX_DMA, + DMAReq::SPI4_TX_DMA + ), + ( + SPI: pac::SPI5, + rxdr, + txdr, + [u8, u16], + DMAReq::SPI5_RX_DMA, + DMAReq::SPI5_TX_DMA + ) ); peripheral_target_address!( From 0575235411e5babbf46aa71e329faadfba6a09fe Mon Sep 17 00:00:00 2001 From: Richard Meadows <962920+richardeoin@users.noreply.github.com> Date: Tue, 3 Nov 2020 21:51:26 +0100 Subject: [PATCH 19/41] rustfmt --- examples/sai_dma_passthru.rs | 98 +++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 47 deletions(-) diff --git a/examples/sai_dma_passthru.rs b/examples/sai_dma_passthru.rs index 3946e366..0fa6442a 100644 --- a/examples/sai_dma_passthru.rs +++ b/examples/sai_dma_passthru.rs @@ -4,22 +4,21 @@ #![allow(unused_macros)] #![deny(warnings)] //#![deny(unsafe_code)] - #![no_main] #![no_std] use cortex_m::asm; -use cortex_m_rt::{entry}; +use cortex_m_rt::entry; -use stm32h7xx_hal as hal; -use hal::stm32; -use hal::{ prelude::*, pac }; -use hal::hal::digital::v2::OutputPin; -use hal::rcc::{ rec::Sai1ClkSel }; -use hal::sai::{ self, SaiI2sExt, SaiChannel }; use hal::dma; +use hal::hal::digital::v2::OutputPin; +use hal::rcc::rec::Sai1ClkSel; +use hal::sai::{self, SaiChannel, SaiI2sExt}; +use hal::stm32; use hal::time::Hertz; +use hal::{pac, prelude::*}; +use stm32h7xx_hal as hal; use pac::interrupt; @@ -28,11 +27,10 @@ use log::info; #[macro_use] mod utilities; - // = global constants ========================================================= // 32 samples * 2 audio channels * 2 buffers -const DMA_BUFFER_LENGTH:usize = 32 * 2 * 2; +const DMA_BUFFER_LENGTH: usize = 32 * 2 * 2; const AUDIO_SAMPLE_HZ: Hertz = Hertz(48_000); @@ -41,7 +39,6 @@ const AUDIO_SAMPLE_HZ: Hertz = Hertz(48_000); // But not less than so targetting 257 const PLL3_P_HZ: Hertz = Hertz(AUDIO_SAMPLE_HZ.0 * 257); - // = static data ============================================================== #[link_section = ".sram3"] @@ -49,7 +46,6 @@ static mut TX_BUFFER: [u32; DMA_BUFFER_LENGTH] = [0; DMA_BUFFER_LENGTH]; #[link_section = ".sram3"] static mut RX_BUFFER: [u32; DMA_BUFFER_LENGTH] = [0; DMA_BUFFER_LENGTH]; - // = entry ==================================================================== #[entry] @@ -61,7 +57,9 @@ fn main() -> ! { let dp = hal::pac::Peripherals::take().unwrap(); let pwr = dp.PWR.constrain(); let vos = pwr.freeze(); - let ccdr = dp.RCC.constrain() + let ccdr = dp + .RCC + .constrain() .use_hse(16.mhz()) .sys_ck(400.mhz()) .pll3_p_ck(PLL3_P_HZ) @@ -70,7 +68,6 @@ fn main() -> ! { // enable sai1 peripheral and set clock to pll3 let sai1_rec = ccdr.peripheral.SAI1.kernel_clk_mux(Sai1ClkSel::PLL3_P); - // - configure pins --------------------------------------------------- let gpiob = dp.GPIOB.split(ccdr.peripheral.GPIOB); @@ -85,38 +82,43 @@ fn main() -> ! { Some(gpioe.pe3.into_alternate_af6()), // SD_B ); - // - configure dma1 ------------------------------------------------------- - let dma1_streams = dma::dma::StreamsTuple::new(dp.DMA1, ccdr.peripheral.DMA1); + let dma1_streams = + dma::dma::StreamsTuple::new(dp.DMA1, ccdr.peripheral.DMA1); // dma1 stream 0 - let tx_buffer: &'static mut [u32; DMA_BUFFER_LENGTH] = unsafe { &mut TX_BUFFER }; + let tx_buffer: &'static mut [u32; DMA_BUFFER_LENGTH] = + unsafe { &mut TX_BUFFER }; let dma_config = dma::dma::DmaConfig::default() .priority(dma::config::Priority::High) .memory_increment(true) .peripheral_increment(false) .circular_buffer(true) .fifo_enable(false); - let mut dma1_str0: dma::Transfer<_, _, dma::MemoryToPeripheral, _> = dma::Transfer::init( - dma1_streams.0, - unsafe { pac::Peripherals::steal().SAI1 }, - tx_buffer, - None, - dma_config, - ); + let mut dma1_str0: dma::Transfer<_, _, dma::MemoryToPeripheral, _> = + dma::Transfer::init( + dma1_streams.0, + unsafe { pac::Peripherals::steal().SAI1 }, + tx_buffer, + None, + dma_config, + ); // dma1 stream 1 - let rx_buffer: &'static mut [u32; DMA_BUFFER_LENGTH] = unsafe { &mut RX_BUFFER }; - let dma_config = dma_config.transfer_complete_interrupt(true) - .half_transfer_interrupt(true); - let mut dma1_str1: dma::Transfer<_, _, dma::PeripheralToMemory, _> = dma::Transfer::init( - dma1_streams.1, - unsafe { pac::Peripherals::steal().SAI1 }, - rx_buffer, - None, - dma_config, - ); + let rx_buffer: &'static mut [u32; DMA_BUFFER_LENGTH] = + unsafe { &mut RX_BUFFER }; + let dma_config = dma_config + .transfer_complete_interrupt(true) + .half_transfer_interrupt(true); + let mut dma1_str1: dma::Transfer<_, _, dma::PeripheralToMemory, _> = + dma::Transfer::init( + dma1_streams.1, + unsafe { pac::Peripherals::steal().SAI1 }, + rx_buffer, + None, + dma_config, + ); // - configure sai ---------------------------------------------------- @@ -140,18 +142,18 @@ fn main() -> ! { Some(sai1_rx_config), ); - // - reset ak4556 codec ----------------------------------------------- ak4556_reset.set_low().unwrap(); - asm::delay(480_000); // ~ 1ms (datasheet specifies minimum 150ns) + asm::delay(480_000); // ~ 1ms (datasheet specifies minimum 150ns) ak4556_reset.set_high().unwrap(); - // - start audio ------------------------------------------------------ // unmask interrupt handler for dma 1, stream 1 - unsafe { pac::NVIC::unmask(pac::Interrupt::DMA1_STR1); } + unsafe { + pac::NVIC::unmask(pac::Interrupt::DMA1_STR1); + } dma1_str1.start(|_sai1_rb| { sai1.enable_dma(SaiChannel::ChannelB); @@ -162,19 +164,20 @@ fn main() -> ! { // wait until sai1's fifo starts to receive data info!("sai1 fifo waiting to receive data"); - while sai1_rb.cha.sr.read().flvl().is_empty() { } + while sai1_rb.cha.sr.read().flvl().is_empty() {} info!("audio started"); sai1.enable(); }); - // - dma1 stream 1 interrupt handler -------------------------------------- - type TransferDma1Str1 = dma::Transfer, - stm32::SAI1, - dma::PeripheralToMemory, - &'static mut [u32; 128]>; + type TransferDma1Str1 = dma::Transfer< + dma::dma::Stream1, + stm32::SAI1, + dma::PeripheralToMemory, + &'static mut [u32; 128], + >; static mut TRANSFER_DMA1_STR1: Option = None; unsafe { @@ -183,8 +186,10 @@ fn main() -> ! { #[interrupt] fn DMA1_STR1() { - let tx_buffer: &'static mut [u32; DMA_BUFFER_LENGTH] = unsafe { &mut TX_BUFFER }; - let rx_buffer: &'static mut [u32; DMA_BUFFER_LENGTH] = unsafe { &mut RX_BUFFER }; + let tx_buffer: &'static mut [u32; DMA_BUFFER_LENGTH] = + unsafe { &mut TX_BUFFER }; + let rx_buffer: &'static mut [u32; DMA_BUFFER_LENGTH] = + unsafe { &mut RX_BUFFER }; let stereo_block_length = tx_buffer.len() / 2; @@ -215,7 +220,6 @@ fn main() -> ! { } } - // - main loop ------------------------------------------------------------ loop { From 624c87e382e238baea88c6277b84c833292e691f Mon Sep 17 00:00:00 2001 From: Richard Meadows <962920+richardeoin@users.noreply.github.com> Date: Tue, 3 Nov 2020 22:06:28 +0100 Subject: [PATCH 20/41] Add BDMA2 support for RM0455 parts This means there is at least some DMA support for these parts, even if the main DMAs aren't supported yet --- Cargo.toml | 4 --- examples/i2c4_bdma.rs | 9 ++++-- src/dma/bdma.rs | 65 +++++++++++++++++++++++++++++++++++++++++-- src/dma/mod.rs | 1 - 4 files changed, 69 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index abc2ec01..9bfa6a86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -183,10 +183,6 @@ required-features = ["rm0433"] name = "dma" required-features = ["rm0433"] -[[example]] -name = "i2c4_bdma" -required-features = ["rm0433"] - [[example]] name = "spi_dma" required-features = ["rm0433"] \ No newline at end of file diff --git a/examples/i2c4_bdma.rs b/examples/i2c4_bdma.rs index 963fa56b..5b311a7d 100644 --- a/examples/i2c4_bdma.rs +++ b/examples/i2c4_bdma.rs @@ -76,15 +76,20 @@ fn main() -> ! { } // Setup the DMA transfer on stream 0 - // - // We need to specify the direction with a type annotation + #[cfg(not(feature = "rm0455"))] let streams = StreamsTuple::new( dp.BDMA, ccdr.peripheral.BDMA.low_power(LowPowerMode::Autonomous), ); + #[cfg(feature = "rm0455")] + let streams = StreamsTuple::new( + dp.BDMA2, + ccdr.peripheral.BDMA.low_power(LowPowerMode::Autonomous), + ); let config = BdmaConfig::default().memory_increment(true); + // We need to specify the direction with a type annotation let mut transfer: Transfer<_, _, PeripheralToMemory, _> = Transfer::init( streams.0, i2c, diff --git a/src/dma/bdma.rs b/src/dma/bdma.rs index 6bb9030f..c11bc99d 100644 --- a/src/dma/bdma.rs +++ b/src/dma/bdma.rs @@ -1,4 +1,6 @@ //! BDMA +//! +//! For RM0455 parts, only BDMA2 is implemented use super::{ config, @@ -10,17 +12,30 @@ use core::marker::PhantomData; use crate::{ i2c::I2c, - pac::{self, BDMA, DMAMUX2}, + pac, rcc::{rec, rec::ResetEnable}, //serial::{Rx, Tx}, + spi, }; +#[cfg(not(feature = "rm0455"))] +use crate::pac::{BDMA, DMAMUX2}; + +#[cfg(feature = "rm0455")] +use crate::pac::{BDMA2, DMAMUX2}; + use core::ops::Deref; +#[cfg(not(feature = "rm0455"))] impl Sealed for BDMA {} +#[cfg(feature = "rm0455")] +impl Sealed for BDMA2 {} /// Type aliases for register blocks +#[cfg(not(feature = "rm0455"))] pub type BDMARegisterBlock = pac::bdma::RegisterBlock; +#[cfg(feature = "rm0455")] +pub type BDMARegisterBlock = pac::bdma2::RegisterBlock; pub type DMAMUXRegisterBlock = pac::dmamux2::RegisterBlock; /// Trait that represents an instance of a BDMA peripheral @@ -36,6 +51,7 @@ pub trait Instance: Deref + Sealed { const DMA_MUX_STREAM_OFFSET: usize; } +#[cfg(not(feature = "rm0455"))] impl Instance for BDMA { type Rec = rec::Bdma; @@ -52,6 +68,23 @@ impl Instance for BDMA { const DMA_MUX_STREAM_OFFSET: usize = 0; } +#[cfg(feature = "rm0455")] +impl Instance for BDMA2 { + type Rec = rec::Bdma; + + #[inline(always)] + fn ptr() -> *const BDMARegisterBlock { + BDMA2::ptr() + } + + #[inline(always)] + fn mux_ptr() -> *const DMAMUXRegisterBlock { + DMAMUX2::ptr() + } + + const DMA_MUX_STREAM_OFFSET: usize = 0; +} + /// BDMA interrupts #[derive(Debug, Clone, Copy)] pub struct BdmaInterrupts { @@ -562,20 +595,46 @@ bdma_stream!( ); /// Type alias for the DMA Request Multiplexer +/// +/// TODO: Needs fixing upstream for RM0455 +#[cfg(not(feature = "rm0455"))] pub type DMAReq = pac::dmamux2::ccr::DMAREQ_ID_A; type P2M = PeripheralToMemory; type M2P = MemoryToPeripheral; +#[cfg(not(feature = "rm0455"))] peripheral_target_address!( (pac::LPUART1, rdr, u8, P2M, DMAReq::LPUART1_RX_DMA), (pac::LPUART1, tdr, u8, M2P, DMAReq::LPUART1_TX_DMA), - (pac::SPI6, rxdr, u8, P2M, DMAReq::SPI6_RX_DMA), - (pac::SPI6, txdr, u8, M2P, DMAReq::SPI6_TX_DMA), + ( + SPI: pac::SPI6, + rxdr, + txdr, + [u8, u16], + DMAReq::SPI6_RX_DMA, + DMAReq::SPI6_TX_DMA + ), (pac::I2C4, rxdr, u8, P2M, DMAReq::I2C4_RX_DMA), (pac::I2C4, txdr, u8, M2P, DMAReq::I2C4_TX_DMA), (INNER: I2c, rxdr, u8, P2M, DMAReq::I2C4_RX_DMA), (INNER: I2c, txdr, u8, M2P, DMAReq::I2C4_TX_DMA), +); + +#[cfg(not(feature = "rm0455"))] +peripheral_target_address!( (pac::SAI4, cha.dr, u32, M2P, DMAReq::SAI4_A_DMA), (pac::SAI4, chb.dr, u32, P2M, DMAReq::SAI4_B_DMA), ); + +// TODO: Remove when fixed upstream +#[cfg(feature = "rm0455")] +peripheral_target_address!( + (pac::LPUART1, rdr, u8, P2M, 9), + (pac::LPUART1, tdr, u8, M2P, 10), + (SPI: pac::SPI6, rxdr, txdr, [u8, u16], 11, 12), + (pac::I2C4, rxdr, u8, P2M, 13), + (pac::I2C4, txdr, u8, M2P, 14), + (INNER: I2c, rxdr, u8, P2M, 13), + (INNER: I2c, txdr, u8, M2P, 14), +); diff --git a/src/dma/mod.rs b/src/dma/mod.rs index 5e5e5016..c063054c 100644 --- a/src/dma/mod.rs +++ b/src/dma/mod.rs @@ -32,7 +32,6 @@ mod macros; #[cfg(not(feature = "rm0455"))] // Remove when fixed upstream pub mod dma; // DMA1 and DMA2 -#[cfg(not(feature = "rm0455"))] // Remove when fixed upstream pub mod bdma; pub mod traits; From 6c738b8650afae96a97fb3a00e86a3f21bd234f4 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Sat, 7 Nov 2020 11:20:33 +0100 Subject: [PATCH 21/41] Adding SPI DMA example using RTIC --- Cargo.toml | 6 +- examples/spi-dma-rtic.rs | 141 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 examples/spi-dma-rtic.rs diff --git a/Cargo.toml b/Cargo.toml index 9bfa6a86..88c7633e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -185,4 +185,8 @@ required-features = ["rm0433"] [[example]] name = "spi_dma" -required-features = ["rm0433"] \ No newline at end of file +required-features = ["rm0433"] + +[[example]] +name = "spi-dma-rtic" +required-features = ["rm0433"] diff --git a/examples/spi-dma-rtic.rs b/examples/spi-dma-rtic.rs new file mode 100644 index 00000000..ca850e8c --- /dev/null +++ b/examples/spi-dma-rtic.rs @@ -0,0 +1,141 @@ +//! Demo for STM32H747I-NUCLEO eval board using the Real Time Interrupt-driven Concurrency (RTIC) +//! framework. +//! +//! This example demonstrates using DMA to continuously write a data stream over SPI. +#![deny(warnings)] +#![no_main] +#![no_std] + +use core::{mem, mem::MaybeUninit}; + +use embedded_hal::digital::v2::OutputPin; +use rtic::app; + +#[macro_use] +#[allow(unused)] +mod utilities; +use log::info; + +use hal::prelude::*; +use stm32h7xx_hal as hal; + +// The number of bytes to transfer. +const BUFFER_SIZE: usize = 100; + +// DMA1/DMA2 cannot interact with our stack. Instead, buffers for use with the +// DMA must be placed somewhere that DMA1/DMA2 can access. In this case we use +// AXI SRAM. +// +// The runtime does not initialise these SRAM banks +#[link_section = ".axisram.buffers"] +static mut BUFFER: MaybeUninit<[u8; BUFFER_SIZE]> = MaybeUninit::uninit(); + +#[app(device = stm32h7xx_hal::stm32, peripherals = true)] +const APP: () = { + struct Resources { + transfer: hal::dma::Transfer< + hal::dma::dma::Stream0, + hal::spi::Spi, + hal::dma::MemoryToPeripheral, + &'static mut [u8; BUFFER_SIZE], + >, + } + + #[init] + fn init(ctx: init::Context) -> init::LateResources { + utilities::logger::init(); + + // Initialise power... + let pwr = ctx.device.PWR.constrain(); + let pwrcfg = example_power!(pwr).freeze(); + + // Initialise clocks... + let rcc = ctx.device.RCC.constrain(); + let ccdr = rcc + .sys_ck(200.mhz()) + .hclk(200.mhz()) + .freeze(pwrcfg, &ctx.device.SYSCFG); + + let gpiob = ctx.device.GPIOB.split(ccdr.peripheral.GPIOB); + + // Initialize a SPI transmitter on SPI2. + let spi = { + let mosi = gpiob.pb15.into_alternate_af5(); + let sck = gpiob.pb13.into_alternate_af5(); + let config = hal::spi::Config::new(hal::spi::MODE_0) + .communication_mode(hal::spi::CommunicationMode::Transmitter); + + let spi: hal::spi::Spi<_, _, u8> = ctx.device.SPI2.spi( + (sck, hal::spi::NoMiso, mosi), + config, + 3.mhz(), + ccdr.peripheral.SPI2, + &ccdr.clocks, + ); + + spi.disable() + }; + + let mut cs = gpiob.pb12.into_push_pull_output(); + cs.set_high().unwrap(); + + // Initialize our transmit buffer. + let buffer: &'static mut [u8; BUFFER_SIZE] = { + let buf: &mut [MaybeUninit; BUFFER_SIZE] = + unsafe { mem::transmute(&mut BUFFER) }; + + for (i, value) in buf.iter_mut().enumerate() { + unsafe { + value.as_mut_ptr().write(i as u8 + 0x60); // 0x60, 0x61, 0x62... + } + } + + unsafe { mem::transmute(buf) } + }; + + let streams = hal::dma::dma::StreamsTuple::new( + ctx.device.DMA1, + ccdr.peripheral.DMA1, + ); + + // Configure the DMA stream to increment the memory address and generate a transfer complete + // interrupt so we know when transmission is done. + let config = hal::dma::dma::DmaConfig::default() + .memory_increment(true) + .transfer_complete_interrupt(true); + + let mut transfer: hal::dma::Transfer< + _, + _, + hal::dma::MemoryToPeripheral, + _, + > = hal::dma::Transfer::init(streams.0, spi, buffer, None, config); + + transfer.start(|spi| { + // For this example, we will always drive CSn as we are simply streaming data over SPI. + cs.set_low().unwrap(); + + // Enable TX DMA support, enable the SPI peripheral, and start the transaction. + spi.inner_mut().cfg1.modify(|_, w| w.txdmaen().enabled()); + spi.inner_mut().cr1.modify(|_, w| w.spe().enabled()); + spi.inner_mut().cr1.modify(|_, w| w.cstart().started()); + + // The transaction immediately begins as the TX FIFO is now being filled by DMA. + }); + + init::LateResources { transfer } + } + + #[task(binds=DMA1_STR0, resources=[transfer])] + fn dma_complete(ctx: dma_complete::Context) { + info!("DMA transmission completed!"); + + // If desired, the transfer can scheduled again here to continue transmitting. + ctx.resources.transfer.clear_transfer_complete_interrupt(); + } + + #[idle] + fn idle(_c: idle::Context) -> ! { + loop {} + } +}; From d8cb6fa5099282665f5e5068a9dcdc9ebaa63240 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Sun, 8 Nov 2020 09:34:03 +0100 Subject: [PATCH 22/41] Updating sample after testing --- Cargo.toml | 2 +- examples/spi-dma-rtic.rs | 72 +++++++++++++++++++++++++--------------- 2 files changed, 46 insertions(+), 28 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 88c7633e..df39d2de 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -189,4 +189,4 @@ required-features = ["rm0433"] [[example]] name = "spi-dma-rtic" -required-features = ["rm0433"] +required-features = ["rm0433","rt"] diff --git a/examples/spi-dma-rtic.rs b/examples/spi-dma-rtic.rs index ca850e8c..5e813cbc 100644 --- a/examples/spi-dma-rtic.rs +++ b/examples/spi-dma-rtic.rs @@ -1,20 +1,20 @@ //! Demo for STM32H747I-NUCLEO eval board using the Real Time Interrupt-driven Concurrency (RTIC) //! framework. //! -//! This example demonstrates using DMA to continuously write a data stream over SPI. +//! This example demonstrates using DMA to write data over a TX-only SPI interface. #![deny(warnings)] #![no_main] #![no_std] use core::{mem, mem::MaybeUninit}; +use cortex_m; use embedded_hal::digital::v2::OutputPin; use rtic::app; #[macro_use] #[allow(unused)] mod utilities; -use log::info; use hal::prelude::*; use stm32h7xx_hal as hal; @@ -34,11 +34,12 @@ static mut BUFFER: MaybeUninit<[u8; BUFFER_SIZE]> = MaybeUninit::uninit(); const APP: () = { struct Resources { transfer: hal::dma::Transfer< - hal::dma::dma::Stream0, + hal::dma::dma::Stream1, hal::spi::Spi, hal::dma::MemoryToPeripheral, &'static mut [u8; BUFFER_SIZE], >, + cs: hal::gpio::gpiob::PB12>, } #[init] @@ -54,14 +55,21 @@ const APP: () = { let ccdr = rcc .sys_ck(200.mhz()) .hclk(200.mhz()) + .pll1_q_ck(200.mhz()) .freeze(pwrcfg, &ctx.device.SYSCFG); let gpiob = ctx.device.GPIOB.split(ccdr.peripheral.GPIOB); // Initialize a SPI transmitter on SPI2. let spi = { - let mosi = gpiob.pb15.into_alternate_af5(); - let sck = gpiob.pb13.into_alternate_af5(); + let mosi = gpiob + .pb15 + .into_alternate_af5() + .set_speed(hal::gpio::Speed::VeryHigh); + let sck = gpiob + .pb13 + .into_alternate_af5() + .set_speed(hal::gpio::Speed::VeryHigh); let config = hal::spi::Config::new(hal::spi::MODE_0) .communication_mode(hal::spi::CommunicationMode::Transmitter); @@ -76,7 +84,10 @@ const APP: () = { spi.disable() }; - let mut cs = gpiob.pb12.into_push_pull_output(); + let mut cs = gpiob + .pb12 + .into_push_pull_output() + .set_speed(hal::gpio::Speed::VeryHigh); cs.set_high().unwrap(); // Initialize our transmit buffer. @@ -104,38 +115,45 @@ const APP: () = { .memory_increment(true) .transfer_complete_interrupt(true); - let mut transfer: hal::dma::Transfer< + let transfer: hal::dma::Transfer< _, _, hal::dma::MemoryToPeripheral, _, - > = hal::dma::Transfer::init(streams.0, spi, buffer, None, config); + > = hal::dma::Transfer::init(streams.1, spi, buffer, None, config); - transfer.start(|spi| { - // For this example, we will always drive CSn as we are simply streaming data over SPI. - cs.set_low().unwrap(); - - // Enable TX DMA support, enable the SPI peripheral, and start the transaction. - spi.inner_mut().cfg1.modify(|_, w| w.txdmaen().enabled()); - spi.inner_mut().cr1.modify(|_, w| w.spe().enabled()); - spi.inner_mut().cr1.modify(|_, w| w.cstart().started()); - - // The transaction immediately begins as the TX FIFO is now being filled by DMA. - }); - - init::LateResources { transfer } + init::LateResources { transfer, cs } } - #[task(binds=DMA1_STR0, resources=[transfer])] + #[task(binds=DMA1_STR1, resources=[transfer, cs], priority=2)] fn dma_complete(ctx: dma_complete::Context) { - info!("DMA transmission completed!"); - // If desired, the transfer can scheduled again here to continue transmitting. + ctx.resources.cs.set_high().unwrap(); ctx.resources.transfer.clear_transfer_complete_interrupt(); } - #[idle] - fn idle(_c: idle::Context) -> ! { - loop {} + #[idle(resources=[transfer, cs])] + fn idle(mut ctx: idle::Context) -> ! { + // Start the DMA transfer over SPI. + let mut cs = ctx.resources.cs; + ctx.resources.transfer.lock(|transfer| { + cs.lock(|cs| { + transfer.start(|spi| { + // Set CS low for the transfer. + cs.set_low().unwrap(); + + // Enable TX DMA support, enable the SPI peripheral, and start the transaction. + spi.enable_dma_tx(); + spi.inner_mut().cr1.modify(|_, w| w.spe().enabled()); + spi.inner_mut().cr1.modify(|_, w| w.cstart().started()); + + // The transaction immediately begins as the TX FIFO is now being filled by DMA. + }); + }); + }); + + loop { + cortex_m::asm::nop(); + } } }; From 62f7fb7ff4f35821a4cd9d142587b48545114a78 Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Wed, 11 Nov 2020 12:40:18 +0100 Subject: [PATCH 23/41] Cleaning up example --- examples/spi-dma-rtic.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/examples/spi-dma-rtic.rs b/examples/spi-dma-rtic.rs index 5e813cbc..fe8110d0 100644 --- a/examples/spi-dma-rtic.rs +++ b/examples/spi-dma-rtic.rs @@ -8,7 +8,6 @@ use core::{mem, mem::MaybeUninit}; -use cortex_m; use embedded_hal::digital::v2::OutputPin; use rtic::app; @@ -67,7 +66,7 @@ const APP: () = { .into_alternate_af5() .set_speed(hal::gpio::Speed::VeryHigh); let sck = gpiob - .pb13 + .pb10 .into_alternate_af5() .set_speed(hal::gpio::Speed::VeryHigh); let config = hal::spi::Config::new(hal::spi::MODE_0) @@ -126,10 +125,16 @@ const APP: () = { } #[task(binds=DMA1_STR1, resources=[transfer, cs], priority=2)] - fn dma_complete(ctx: dma_complete::Context) { + fn dma_complete(mut ctx: dma_complete::Context) { // If desired, the transfer can scheduled again here to continue transmitting. - ctx.resources.cs.set_high().unwrap(); + let cs = &mut ctx.resources.cs; ctx.resources.transfer.clear_transfer_complete_interrupt(); + ctx.resources.transfer.pause(|spi| { + // At this point, the DMA transfer is done, but the data is still in the SPI output + // FIFO. Wait for it to complete before disabling CS. + while spi.inner().sr.read().txc().bit_is_clear() {} + cs.set_high().unwrap(); + }); } #[idle(resources=[transfer, cs])] From 002123ce8a05a72cd3275a593378b431a44a3b1a Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Wed, 11 Nov 2020 12:55:49 +0100 Subject: [PATCH 24/41] Fixing clippy --- examples/spi-dma-rtic.rs | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/examples/spi-dma-rtic.rs b/examples/spi-dma-rtic.rs index fe8110d0..78df10a4 100644 --- a/examples/spi-dma-rtic.rs +++ b/examples/spi-dma-rtic.rs @@ -6,7 +6,7 @@ #![no_main] #![no_std] -use core::{mem, mem::MaybeUninit}; +use core::mem::MaybeUninit; use embedded_hal::digital::v2::OutputPin; use rtic::app; @@ -91,8 +91,10 @@ const APP: () = { // Initialize our transmit buffer. let buffer: &'static mut [u8; BUFFER_SIZE] = { - let buf: &mut [MaybeUninit; BUFFER_SIZE] = - unsafe { mem::transmute(&mut BUFFER) }; + let buf: &mut [MaybeUninit; BUFFER_SIZE] = unsafe { + &mut *(&mut BUFFER as *mut MaybeUninit<[u8; BUFFER_SIZE]> + as *mut [MaybeUninit; BUFFER_SIZE]) + }; for (i, value) in buf.iter_mut().enumerate() { unsafe { @@ -100,7 +102,10 @@ const APP: () = { } } - unsafe { mem::transmute(buf) } + unsafe { + &mut *(buf as *mut [MaybeUninit; BUFFER_SIZE] + as *mut [u8; BUFFER_SIZE]) + } }; let streams = hal::dma::dma::StreamsTuple::new( From d77e835ca95dc21061adfd61d45660331b0f56c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Fri, 27 Nov 2020 16:06:11 +0100 Subject: [PATCH 25/41] dma: streamline double buffer address handling --- src/dma/bdma.rs | 32 ++++++++++++------------------- src/dma/dma.rs | 32 ++++++++++++------------------- src/dma/mod.rs | 48 +++++++++++++++++++++++------------------------ src/dma/traits.rs | 12 +++--------- 4 files changed, 51 insertions(+), 73 deletions(-) diff --git a/src/dma/bdma.rs b/src/dma/bdma.rs index c11bc99d..e33f4192 100644 --- a/src/dma/bdma.rs +++ b/src/dma/bdma.rs @@ -438,31 +438,23 @@ macro_rules! bdma_stream { } #[inline(always)] - unsafe fn set_memory_address(&mut self, value: u32) { + unsafe fn set_memory_address(&mut self, buffer: CurrentBuffer, value: u32) { //NOTE(unsafe) We only access the registers that belongs to the StreamX let dma = &*I::ptr(); - dma.ch[Self::NUMBER].m0ar.write(|w| w.ma().bits(value)); - } - - #[inline(always)] - fn get_memory_address(&self) -> u32 { - //NOTE(unsafe) We only access the registers that belongs to the StreamX - let dma = unsafe { &*I::ptr() }; - dma.ch[Self::NUMBER].m0ar.read().ma().bits() - } - - #[inline(always)] - unsafe fn set_memory_double_buffer_address(&mut self, value: u32) { - //NOTE(unsafe) We only access the registers that belongs to the StreamX - let dma = &*I::ptr(); - dma.ch[Self::NUMBER].m1ar.write(|w| w.ma().bits(value)); + match buffer { + CurrentBuffer::Buffer0 => dma.ch[Self::NUMBER].m0ar.write(|w| w.ma().bits(value)), + CurrentBuffer::Buffer1 => dma.ch[Self::NUMBER].m1ar.write(|w| w.ma().bits(value)), + } } #[inline(always)] - fn get_memory_double_buffer_address(&self) -> u32 { + fn get_memory_address(&self, buffer: CurrentBuffer) -> u32 { //NOTE(unsafe) We only access the registers that belongs to the StreamX let dma = unsafe { &*I::ptr() }; - dma.ch[Self::NUMBER].m1ar.read().ma().bits() + match buffer { + CurrentBuffer::Buffer0 => dma.ch[Self::NUMBER].m0ar.read().ma().bits(), + CurrentBuffer::Buffer1 => dma.ch[Self::NUMBER].m1ar.read().ma().bits(), + } } #[inline(always)] @@ -547,9 +539,9 @@ macro_rules! bdma_stream { //NOTE(unsafe) Atomic read with no side effects let dma = unsafe { &*I::ptr() }; if dma.ch[Self::NUMBER].cr.read().ct().bit_is_set() { - CurrentBuffer::DoubleBuffer + CurrentBuffer::Buffer1 } else { - CurrentBuffer::FirstBuffer + CurrentBuffer::Buffer0 } } } diff --git a/src/dma/dma.rs b/src/dma/dma.rs index 54b398b7..07509570 100644 --- a/src/dma/dma.rs +++ b/src/dma/dma.rs @@ -528,31 +528,23 @@ macro_rules! dma_stream { } #[inline(always)] - unsafe fn set_memory_address(&mut self, value: u32) { + unsafe fn set_memory_address(&mut self, buffer: CurrentBuffer, value: u32) { //NOTE(unsafe) We only access the registers that belongs to the StreamX let dma = &*I::ptr(); - dma.st[Self::NUMBER].m0ar.write(|w| w.m0a().bits(value)); - } - - #[inline(always)] - fn get_memory_address(&self) -> u32 { - //NOTE(unsafe) We only access the registers that belongs to the StreamX - let dma = unsafe { &*I::ptr() }; - dma.st[Self::NUMBER].m0ar.read().m0a().bits() - } - - #[inline(always)] - unsafe fn set_memory_double_buffer_address(&mut self, value: u32) { - //NOTE(unsafe) We only access the registers that belongs to the StreamX - let dma = &*I::ptr(); - dma.st[Self::NUMBER].m1ar.write(|w| w.m1a().bits(value)); + match buffer { + CurrentBuffer::Buffer0 => dma.st[Self::NUMBER].m0ar.write(|w| w.m0a().bits(value)), + CurrentBuffer::Buffer1 => dma.st[Self::NUMBER].m1ar.write(|w| w.m1a().bits(value)), + } } #[inline(always)] - fn get_memory_double_buffer_address(&self) -> u32 { + fn get_memory_address(&self, buffer: CurrentBuffer) -> u32 { //NOTE(unsafe) We only access the registers that belongs to the StreamX let dma = unsafe { &*I::ptr() }; - dma.st[Self::NUMBER].m1ar.read().m1a().bits() + match buffer { + CurrentBuffer::Buffer0 => dma.st[Self::NUMBER].m0ar.read().m0a().bits(), + CurrentBuffer::Buffer1 => dma.st[Self::NUMBER].m1ar.read().m1a().bits(), + } } #[inline(always)] @@ -635,9 +627,9 @@ macro_rules! dma_stream { //NOTE(unsafe) Atomic read with no side effects let dma = unsafe { &*I::ptr() }; if dma.st[Self::NUMBER].cr.read().ct().bit_is_set() { - CurrentBuffer::DoubleBuffer + CurrentBuffer::Buffer1 } else { - CurrentBuffer::FirstBuffer + CurrentBuffer::Buffer0 } } } diff --git a/src/dma/mod.rs b/src/dma/mod.rs index c063054c..cfa082cf 100644 --- a/src/dma/mod.rs +++ b/src/dma/mod.rs @@ -177,19 +177,19 @@ impl From for FifoLevel { #[derive(Debug, Clone, Copy, PartialEq)] pub enum CurrentBuffer { /// The first buffer (m0ar) is in use. - FirstBuffer, + Buffer0, /// The second buffer (m1ar) is in use. - DoubleBuffer, + Buffer1, } impl Not for CurrentBuffer { type Output = CurrentBuffer; fn not(self) -> Self::Output { - if self == CurrentBuffer::FirstBuffer { - CurrentBuffer::DoubleBuffer + if self == CurrentBuffer::Buffer0 { + CurrentBuffer::Buffer1 } else { - CurrentBuffer::FirstBuffer + CurrentBuffer::Buffer0 } } } @@ -363,7 +363,7 @@ where // // Must be a valid memory address unsafe { - stream.set_memory_address(buf_ptr as u32); + stream.set_memory_address(CurrentBuffer::Buffer0, buf_ptr as u32); } let is_mem2mem = DIR::direction() == DmaDirection::MemoryToMemory; @@ -395,7 +395,7 @@ where // Double buffer is the source in mem2mem mode stream.set_peripheral_address(db_ptr as u32); } else { - stream.set_memory_double_buffer_address(db_ptr as u32); + stream.set_memory_address(CurrentBuffer::Buffer1, db_ptr as u32); } } Some(db_len) @@ -498,13 +498,13 @@ where return Err(DMAError::SmallBuffer(new_buf)); } - if STREAM::current_buffer() == CurrentBuffer::DoubleBuffer { + if STREAM::current_buffer() == CurrentBuffer::Buffer1 { unsafe { - self.stream.set_memory_address(new_buf_ptr as u32); + self.stream.set_memory_address(CurrentBuffer::Buffer0, new_buf_ptr as u32); } // Check if an overrun occurred, the buffer address won't be // updated in that case - if self.stream.get_memory_address() != new_buf_ptr as u32 { + if self.stream.get_memory_address(CurrentBuffer::Buffer0) != new_buf_ptr as u32 { self.stream.clear_transfer_complete_interrupt(); return Err(DMAError::Overrun(new_buf)); } @@ -516,15 +516,15 @@ where let old_buf = self.buf.replace(new_buf); // We always have a buffer, so unwrap can't fail - return Ok((old_buf.unwrap(), CurrentBuffer::FirstBuffer)); + return Ok((old_buf.unwrap(), CurrentBuffer::Buffer0)); } else { unsafe { self.stream - .set_memory_double_buffer_address(new_buf_ptr as u32); + .set_memory_address(CurrentBuffer::Buffer1, new_buf_ptr as u32); } // Check if an overrun occurred, the buffer address won't be // updated in that case - if self.stream.get_memory_double_buffer_address() + if self.stream.get_memory_address(CurrentBuffer::Buffer1) != new_buf_ptr as u32 { self.stream.clear_transfer_complete_interrupt(); @@ -538,7 +538,7 @@ where let old_buf = self.double_buf.replace(new_buf); // double buffering, unwrap can never fail - return Ok((old_buf.unwrap(), CurrentBuffer::DoubleBuffer)); + return Ok((old_buf.unwrap(), CurrentBuffer::Buffer1)); } } self.stream.disable(); @@ -552,7 +552,7 @@ where let buf_len = unsafe { let (buf_ptr, buf_len) = new_buf.write_buffer(); - self.stream.set_memory_address(buf_ptr as u32); + self.stream.set_memory_address(CurrentBuffer::Buffer0, buf_ptr as u32); buf_len }; self.stream.set_number_of_transfers(buf_len as u16); @@ -573,7 +573,7 @@ where self.stream.enable(); } - Ok((old_buf.unwrap(), CurrentBuffer::FirstBuffer)) + Ok((old_buf.unwrap(), CurrentBuffer::Buffer0)) } /// Stops the stream and returns the underlying resources. @@ -684,7 +684,7 @@ where let current_buffer = STREAM::current_buffer(); // double buffering, unwrap can never fail - let db = if current_buffer == CurrentBuffer::DoubleBuffer { + let db = if current_buffer == CurrentBuffer::Buffer1 { self.buf.take().unwrap() } else { self.double_buf.take().unwrap() @@ -711,12 +711,12 @@ where panic!("Overrun"); } - if current_buffer == CurrentBuffer::DoubleBuffer { - self.stream.set_memory_address(new_buf_ptr as u32); + if current_buffer == CurrentBuffer::Buffer1 { + self.stream.set_memory_address(CurrentBuffer::Buffer0, new_buf_ptr as u32); // Check again if an overrun occurred, the buffer address won't // be updated in that case - if self.stream.get_memory_address() != new_buf_ptr as u32 { + if self.stream.get_memory_address(CurrentBuffer::Buffer0) != new_buf_ptr as u32 { panic!("Overrun"); } @@ -728,8 +728,8 @@ where return Ok(r.1); } else { self.stream - .set_memory_double_buffer_address(new_buf_ptr as u32); - if self.stream.get_memory_double_buffer_address() + .set_memory_address(CurrentBuffer::Buffer1, new_buf_ptr as u32); + if self.stream.get_memory_address(CurrentBuffer::Buffer1) != new_buf_ptr as u32 { panic!("Overrun"); @@ -751,11 +751,11 @@ where // Can never fail, we never let the Transfer without a buffer let old_buf = self.buf.take().unwrap(); - let r = f(old_buf, CurrentBuffer::FirstBuffer); + let r = f(old_buf, CurrentBuffer::Buffer0); let mut new_buf = r.0; let (buf_ptr, buf_len) = new_buf.write_buffer(); - self.stream.set_memory_address(buf_ptr as u32); + self.stream.set_memory_address(CurrentBuffer::Buffer0, buf_ptr as u32); self.stream.set_number_of_transfers(buf_len as u16); self.buf.replace(new_buf); diff --git a/src/dma/traits.rs b/src/dma/traits.rs index bdab1bf4..afa4e164 100644 --- a/src/dma/traits.rs +++ b/src/dma/traits.rs @@ -105,16 +105,10 @@ pub trait DoubleBufferedStream: Stream + Sealed { unsafe fn set_peripheral_address(&mut self, value: u32); /// Set the memory address (m0ar) for the DMA stream. - unsafe fn set_memory_address(&mut self, value: u32); + unsafe fn set_memory_address(&mut self, buffer: CurrentBuffer, value: u32); /// Get the memory address (m0ar) for the DMA stream. - fn get_memory_address(&self) -> u32; - - /// Set the double buffer address (m1ar) for the DMA stream. - unsafe fn set_memory_double_buffer_address(&mut self, value: u32); - - /// Get the double buffer address (m1ar) for the DMA stream. - fn get_memory_double_buffer_address(&self) -> u32; + fn get_memory_address(&self, buffer: CurrentBuffer) -> u32; /// Enable/disable memory increment (minc) for the DMA stream. fn set_memory_increment(&mut self, increment: bool); @@ -138,7 +132,7 @@ pub trait DoubleBufferedStream: Stream + Sealed { /// * 0 -> byte /// * 1 -> half word /// * 2 -> word - /// * 3 -> double workd + /// * 3 -> double word unsafe fn set_memory_size(&mut self, size: u8); /// Set the peripheral memory size (psize) for the DMA stream. From 558521098b9dd9c0586071cf90d87323e61f3670 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Fri, 27 Nov 2020 16:13:09 +0100 Subject: [PATCH 26/41] dma: StaticWriteBuffer --- src/dma/mod.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/dma/mod.rs b/src/dma/mod.rs index cfa082cf..1e671516 100644 --- a/src/dma/mod.rs +++ b/src/dma/mod.rs @@ -24,7 +24,7 @@ use core::{ ptr, sync::atomic::{compiler_fence, Ordering}, }; -use embedded_dma::WriteBuffer; +use embedded_dma::StaticWriteBuffer; #[macro_use] mod macros; @@ -286,8 +286,7 @@ where STREAM: Stream, PERIPHERAL: TargetAddress, DIR: Direction, - BUF: WriteBuffer>::MemSize> - + 'static, + BUF: StaticWriteBuffer>::MemSize>, { stream: STREAM, peripheral: PERIPHERAL, @@ -305,8 +304,7 @@ where CONFIG: DoubleBufferedConfig, DIR: Direction, PERIPHERAL: TargetAddress, - BUF: WriteBuffer>::MemSize> - + 'static, + BUF: StaticWriteBuffer>::MemSize>, { /// Applies all fields in DmaConfig. fn apply_config(&mut self, config: CONFIG) { @@ -782,8 +780,7 @@ where STREAM: Stream, PERIPHERAL: TargetAddress, DIR: Direction, - BUF: WriteBuffer>::MemSize> - + 'static, + BUF: StaticWriteBuffer>::MemSize>, { fn drop(&mut self) { self.stream.disable(); From 301e16b60a980139ce3792e34d44860b9f4ded12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Fri, 27 Nov 2020 16:23:20 +0100 Subject: [PATCH 27/41] dma: buf array --- src/dma/mod.rs | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/dma/mod.rs b/src/dma/mod.rs index 1e671516..61d8e399 100644 --- a/src/dma/mod.rs +++ b/src/dma/mod.rs @@ -291,8 +291,7 @@ where stream: STREAM, peripheral: PERIPHERAL, _direction: PhantomData, - buf: Option, - double_buf: Option, + buf: [Option; 2], // Used when double buffering transfer_length: u16, } @@ -421,8 +420,7 @@ where stream, peripheral, _direction: PhantomData, - buf: Some(memory), - double_buf, + buf: [Some(memory), double_buf], transfer_length: n_transfers, }; transfer.apply_config(config); @@ -480,7 +478,7 @@ where &mut self, mut new_buf: BUF, ) -> Result<(BUF, CurrentBuffer), DMAError> { - if self.double_buf.is_some() + if self.buf[1].is_some() && DIR::direction() != DmaDirection::MemoryToMemory { if !STREAM::get_transfer_complete_flag() { @@ -511,7 +509,7 @@ where // preceding reads" compiler_fence(Ordering::Acquire); - let old_buf = self.buf.replace(new_buf); + let old_buf = self.buf[0].replace(new_buf); // We always have a buffer, so unwrap can't fail return Ok((old_buf.unwrap(), CurrentBuffer::Buffer0)); @@ -533,7 +531,7 @@ where // preceding reads" compiler_fence(Ordering::Acquire); - let old_buf = self.double_buf.replace(new_buf); + let old_buf = self.buf[1].replace(new_buf); // double buffering, unwrap can never fail return Ok((old_buf.unwrap(), CurrentBuffer::Buffer1)); @@ -554,7 +552,7 @@ where buf_len }; self.stream.set_number_of_transfers(buf_len as u16); - let old_buf = self.buf.replace(new_buf); + let old_buf = self.buf[0].replace(new_buf); // Ensure that all transfers to normal memory complete before // subsequent memory transfers. @@ -588,8 +586,8 @@ where unsafe { let stream = ptr::read(&self.stream); let peripheral = ptr::read(&self.peripheral); - let buf = ptr::read(&self.buf); - let double_buf = ptr::read(&self.double_buf); + let buf = ptr::read(&self.buf[0]); + let double_buf = ptr::read(&self.buf[1]); mem::forget(self); (stream, peripheral, buf.unwrap(), double_buf) } @@ -672,7 +670,7 @@ where where F: FnOnce(BUF, CurrentBuffer) -> (BUF, T), { - if self.double_buf.is_some() + if self.buf[1].is_some() && DIR::direction() != DmaDirection::MemoryToMemory { if !STREAM::get_transfer_complete_flag() { @@ -683,9 +681,9 @@ where let current_buffer = STREAM::current_buffer(); // double buffering, unwrap can never fail let db = if current_buffer == CurrentBuffer::Buffer1 { - self.buf.take().unwrap() + self.buf[0].take().unwrap() } else { - self.double_buf.take().unwrap() + self.buf[1].take().unwrap() }; let r = f(db, !current_buffer); let mut new_buf = r.0; @@ -722,7 +720,7 @@ where // preceding reads" compiler_fence(Ordering::Acquire); - self.buf.replace(new_buf); + self.buf[0].replace(new_buf); return Ok(r.1); } else { self.stream @@ -737,7 +735,7 @@ where // preceding reads" compiler_fence(Ordering::Acquire); - self.double_buf.replace(new_buf); + self.buf[1].replace(new_buf); return Ok(r.1); } } @@ -748,14 +746,14 @@ where compiler_fence(Ordering::SeqCst); // Can never fail, we never let the Transfer without a buffer - let old_buf = self.buf.take().unwrap(); + let old_buf = self.buf[0].take().unwrap(); let r = f(old_buf, CurrentBuffer::Buffer0); let mut new_buf = r.0; let (buf_ptr, buf_len) = new_buf.write_buffer(); self.stream.set_memory_address(CurrentBuffer::Buffer0, buf_ptr as u32); self.stream.set_number_of_transfers(buf_len as u16); - self.buf.replace(new_buf); + self.buf[0].replace(new_buf); // Ensure that all transfers to normal memory complete before // subsequent memory transfers. From 44253232028f5fb53b6e0064ff8dfbc8660626e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Fri, 27 Nov 2020 18:12:02 +0100 Subject: [PATCH 28/41] dma: streamline double buffer --- src/dma/dma.rs | 23 +++++++-- src/dma/mod.rs | 127 +++++++++++++++++++--------------------------- src/dma/traits.rs | 7 ++- 3 files changed, 75 insertions(+), 82 deletions(-) diff --git a/src/dma/dma.rs b/src/dma/dma.rs index 07509570..be0afddb 100644 --- a/src/dma/dma.rs +++ b/src/dma/dma.rs @@ -623,13 +623,28 @@ macro_rules! dma_stream { dma.st[Self::NUMBER].cr.modify(|_, w| w.dbm().bit(double_buffer)); } - fn current_buffer() -> CurrentBuffer { + fn set_current_buffer(buffer: CurrentBuffer) { //NOTE(unsafe) Atomic read with no side effects let dma = unsafe { &*I::ptr() }; - if dma.st[Self::NUMBER].cr.read().ct().bit_is_set() { - CurrentBuffer::Buffer1 + dma.st[Self::NUMBER].cr.modify(|_, w| w.ct().bit( + match buffer { + CurrentBuffer::Buffer0 => false, + CurrentBuffer::Buffer1 => true, + })) + } + + fn get_current_buffer() -> Option { + //NOTE(unsafe) Atomic read with no side effects + let dma = unsafe { &*I::ptr() }; + let cr = dma.st[Self::NUMBER].cr.read(); + if cr.dbm().bit_is_set() { + Some(if cr.ct().bit_is_set() { + CurrentBuffer::Buffer1 + } else { + CurrentBuffer::Buffer0 + }) } else { - CurrentBuffer::Buffer0 + None } } } diff --git a/src/dma/mod.rs b/src/dma/mod.rs index 61d8e399..62480dea 100644 --- a/src/dma/mod.rs +++ b/src/dma/mod.rs @@ -32,7 +32,7 @@ mod macros; #[cfg(not(feature = "rm0455"))] // Remove when fixed upstream pub mod dma; // DMA1 and DMA2 -pub mod bdma; +// pub mod bdma; pub mod traits; use traits::{ @@ -177,9 +177,9 @@ impl From for FifoLevel { #[derive(Debug, Clone, Copy, PartialEq)] pub enum CurrentBuffer { /// The first buffer (m0ar) is in use. - Buffer0, + Buffer0 = 0, /// The second buffer (m1ar) is in use. - Buffer1, + Buffer1 = 1, } impl Not for CurrentBuffer { @@ -478,98 +478,73 @@ where &mut self, mut new_buf: BUF, ) -> Result<(BUF, CurrentBuffer), DMAError> { - if self.buf[1].is_some() - && DIR::direction() != DmaDirection::MemoryToMemory - { + // NOTE(unsafe) We now own this buffer and we won't call any &mut + // methods on it until the end of the DMA transfer + let (new_buf_ptr, new_buf_len) = unsafe { new_buf.write_buffer() }; + + if let Some(buf) = STREAM::get_current_buffer() { + // Double buffer mode, swap the inactive buffer. + let buf = !buf; + if !STREAM::get_transfer_complete_flag() { return Err(DMAError::NotReady(new_buf)); } self.stream.clear_transfer_complete_interrupt(); - // NOTE(unsafe) We now own this buffer and we won't call any &mut - // methods on it until the end of the DMA transfer - let (new_buf_ptr, new_buf_len) = unsafe { new_buf.write_buffer() }; // We can't change the transfer length while double buffering - if new_buf_len < usize::from(self.transfer_length) { + if new_buf_len != usize::from(self.transfer_length) { return Err(DMAError::SmallBuffer(new_buf)); } - if STREAM::current_buffer() == CurrentBuffer::Buffer1 { - unsafe { - self.stream.set_memory_address(CurrentBuffer::Buffer0, new_buf_ptr as u32); - } - // Check if an overrun occurred, the buffer address won't be - // updated in that case - if self.stream.get_memory_address(CurrentBuffer::Buffer0) != new_buf_ptr as u32 { - self.stream.clear_transfer_complete_interrupt(); - return Err(DMAError::Overrun(new_buf)); - } - - // "Subsequent reads and writes cannot be moved ahead of - // preceding reads" - compiler_fence(Ordering::Acquire); + unsafe { + self.stream.set_memory_address(buf, new_buf_ptr as u32); + } + // Check if an overrun occurred, the buffer address won't be + // updated in that case + if self.stream.get_memory_address(buf) != new_buf_ptr as u32 { + self.stream.clear_transfer_complete_interrupt(); + return Err(DMAError::Overrun(new_buf)); + } - let old_buf = self.buf[0].replace(new_buf); + // "Subsequent reads and writes cannot be moved ahead of + // preceding reads" + compiler_fence(Ordering::Acquire); - // We always have a buffer, so unwrap can't fail - return Ok((old_buf.unwrap(), CurrentBuffer::Buffer0)); - } else { - unsafe { - self.stream - .set_memory_address(CurrentBuffer::Buffer1, new_buf_ptr as u32); - } - // Check if an overrun occurred, the buffer address won't be - // updated in that case - if self.stream.get_memory_address(CurrentBuffer::Buffer1) - != new_buf_ptr as u32 - { - self.stream.clear_transfer_complete_interrupt(); - return Err(DMAError::Overrun(new_buf)); - } + let old_buf = self.buf[buf as usize].replace(new_buf); - // "Subsequent reads and writes cannot be moved ahead of - // preceding reads" - compiler_fence(Ordering::Acquire); + // We always have a buffer, so unwrap can't fail + Ok((old_buf.unwrap(), buf)) + } else { + self.stream.disable(); + self.stream.clear_transfer_complete_interrupt(); - let old_buf = self.buf[1].replace(new_buf); + // "No re-ordering of reads and writes across this point is allowed" + compiler_fence(Ordering::SeqCst); - // double buffering, unwrap can never fail - return Ok((old_buf.unwrap(), CurrentBuffer::Buffer1)); + unsafe { + self.stream.set_memory_address(CurrentBuffer::Buffer0, new_buf_ptr as u32); } - } - self.stream.disable(); - self.stream.clear_transfer_complete_interrupt(); + self.stream.set_number_of_transfers(new_buf_len as u16); + + let old_buf = self.buf[0].replace(new_buf); - // "No re-ordering of reads and writes across this point is allowed" - compiler_fence(Ordering::SeqCst); + // Ensure that all transfers to normal memory complete before + // subsequent memory transfers. + // + // The new memory buffer is almost certainly in normal memory, so we + // ensure that all transfers there complete before proceeding to enable + // the stream + cortex_m::asm::dmb(); - // NOTE(unsafe) We now own this buffer and we won't call any &mut - // methods on it until the end of the DMA transfer - let buf_len = unsafe { - let (buf_ptr, buf_len) = new_buf.write_buffer(); + // "Preceding reads and writes cannot be moved past subsequent writes" + compiler_fence(Ordering::Release); - self.stream.set_memory_address(CurrentBuffer::Buffer0, buf_ptr as u32); - buf_len - }; - self.stream.set_number_of_transfers(buf_len as u16); - let old_buf = self.buf[0].replace(new_buf); - - // Ensure that all transfers to normal memory complete before - // subsequent memory transfers. - // - // The new memory buffer is almost certainly in normal memory, so we - // ensure that all transfers there complete before proceeding to enable - // the stream - cortex_m::asm::dmb(); - - // "Preceding reads and writes cannot be moved past subsequent writes" - compiler_fence(Ordering::Release); + unsafe { + self.stream.enable(); + } - unsafe { - self.stream.enable(); + Ok((old_buf.unwrap(), CurrentBuffer::Buffer0)) } - - Ok((old_buf.unwrap(), CurrentBuffer::Buffer0)) } /// Stops the stream and returns the underlying resources. @@ -678,7 +653,7 @@ where } self.stream.clear_transfer_complete_interrupt(); - let current_buffer = STREAM::current_buffer(); + let current_buffer = STREAM::get_current_buffer().unwrap(); // double buffering, unwrap can never fail let db = if current_buffer == CurrentBuffer::Buffer1 { self.buf[0].take().unwrap() diff --git a/src/dma/traits.rs b/src/dma/traits.rs index afa4e164..93c4a857 100644 --- a/src/dma/traits.rs +++ b/src/dma/traits.rs @@ -161,8 +161,11 @@ pub trait DoubleBufferedStream: Stream + Sealed { /// Enable/disable the double buffer (dbm) of the DMA stream. fn set_double_buffer(&mut self, double_buffer: bool); - /// Get which buffer is currently in use by the DMA. - fn current_buffer() -> CurrentBuffer; + /// Set the current buffer. + fn set_current_buffer(buffer: CurrentBuffer); + + /// Get which buffer is currently in use by the DMA when in double buffer mode. + fn get_current_buffer() -> Option; } /// Trait for Master DMA streams From be1573fc06e4afee3d4b7fc3118b7e661337b25a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Sat, 28 Nov 2020 14:06:44 +0100 Subject: [PATCH 29/41] dma: usize for addresses --- src/dma/dma.rs | 17 +++++++++-------- src/dma/macros.rs | 28 ++++++++++++++-------------- src/dma/mod.rs | 36 ++++++++++++++++++------------------ src/dma/traits.rs | 8 ++++---- 4 files changed, 45 insertions(+), 44 deletions(-) diff --git a/src/dma/dma.rs b/src/dma/dma.rs index be0afddb..f6efe52b 100644 --- a/src/dma/dma.rs +++ b/src/dma/dma.rs @@ -521,30 +521,31 @@ macro_rules! dma_stream { impl DoubleBufferedStream for $name { #[inline(always)] - unsafe fn set_peripheral_address(&mut self, value: u32) { + unsafe fn set_peripheral_address(&mut self, value: usize) { //NOTE(unsafe) We only access the registers that belongs to the StreamX let dma = &*I::ptr(); - dma.st[Self::NUMBER].par.write(|w| w.pa().bits(value)); + dma.st[Self::NUMBER].par.write(|w| w.pa().bits(value as u32)); } #[inline(always)] - unsafe fn set_memory_address(&mut self, buffer: CurrentBuffer, value: u32) { + unsafe fn set_memory_address(&mut self, buffer: CurrentBuffer, value: usize) { //NOTE(unsafe) We only access the registers that belongs to the StreamX let dma = &*I::ptr(); match buffer { - CurrentBuffer::Buffer0 => dma.st[Self::NUMBER].m0ar.write(|w| w.m0a().bits(value)), - CurrentBuffer::Buffer1 => dma.st[Self::NUMBER].m1ar.write(|w| w.m1a().bits(value)), + CurrentBuffer::Buffer0 => dma.st[Self::NUMBER].m0ar.write(|w| w.m0a().bits(value as u32)), + CurrentBuffer::Buffer1 => dma.st[Self::NUMBER].m1ar.write(|w| w.m1a().bits(value as u32)), } } #[inline(always)] - fn get_memory_address(&self, buffer: CurrentBuffer) -> u32 { + fn get_memory_address(&self, buffer: CurrentBuffer) -> usize { //NOTE(unsafe) We only access the registers that belongs to the StreamX let dma = unsafe { &*I::ptr() }; - match buffer { + let addr = match buffer { CurrentBuffer::Buffer0 => dma.st[Self::NUMBER].m0ar.read().m0a().bits(), CurrentBuffer::Buffer1 => dma.st[Self::NUMBER].m1ar.read().m1a().bits(), - } + }; + addr as usize } #[inline(always)] diff --git a/src/dma/macros.rs b/src/dma/macros.rs index 55f667cf..1e9e2f18 100644 --- a/src/dma/macros.rs +++ b/src/dma/macros.rs @@ -15,8 +15,8 @@ macro_rules! peripheral_target_instance { $dir:ty $(, $mux:expr)*)) => { unsafe impl TargetAddress<$dir> for $peripheral { #[inline(always)] - fn address(&self) -> u32 { - &self.$register as *const _ as u32 + fn address(&self) -> usize { + &self.$register as *const _ as usize } type MemSize = $size; @@ -33,8 +33,8 @@ macro_rules! peripheral_target_instance { // Access via PAC peripheral structures implies u8 sizing, as the sizing is unknown. unsafe impl TargetAddress for $peripheral { #[inline(always)] - fn address(&self) -> u32 { - &self.$txreg as *const _ as u32 + fn address(&self) -> usize { + &self.$txreg as *const _ as usize } type MemSize = u8; @@ -44,8 +44,8 @@ macro_rules! peripheral_target_instance { unsafe impl TargetAddress for $peripheral { #[inline(always)] - fn address(&self) -> u32 { - &self.$rxreg as *const _ as u32 + fn address(&self) -> usize { + &self.$rxreg as *const _ as usize } type MemSize = u8; @@ -57,8 +57,8 @@ macro_rules! peripheral_target_instance { $( unsafe impl TargetAddress for spi::Spi<$peripheral, spi::Disabled, $size> { #[inline(always)] - fn address(&self) -> u32 { - &self.inner().$txreg as *const _ as u32 + fn address(&self) -> usize { + &self.inner().$txreg as *const _ as usize } type MemSize = $size; @@ -68,8 +68,8 @@ macro_rules! peripheral_target_instance { unsafe impl TargetAddress for spi::Spi<$peripheral, spi::Disabled, $size> { #[inline(always)] - fn address(&self) -> u32 { - &self.inner().$rxreg as *const _ as u32 + fn address(&self) -> usize { + &self.inner().$rxreg as *const _ as usize } type MemSize = $size; @@ -83,8 +83,8 @@ macro_rules! peripheral_target_instance { $dir:ty $(, $mux:expr)*)) => { unsafe impl TargetAddress<$dir> for $peripheral { #[inline(always)] - fn address(&self) -> u32 { - &self.inner().$register as *const _ as u32 + fn address(&self) -> usize { + &self.inner().$register as *const _ as usize } type MemSize = $size; @@ -101,8 +101,8 @@ macro_rules! peripheral_target_instance { $dir:ty $(, $mux:expr)*)) => { unsafe impl TargetAddress<$dir> for $peripheral { #[inline(always)] - fn address(&self) -> u32 { - &self.$channel.$register as *const _ as u32 + fn address(&self) -> usize { + &self.$channel.$register as *const _ as usize } type MemSize = $size; diff --git a/src/dma/mod.rs b/src/dma/mod.rs index 62480dea..3f36c4bd 100644 --- a/src/dma/mod.rs +++ b/src/dma/mod.rs @@ -120,21 +120,21 @@ impl Direction for MemoryToPeripheral { } unsafe impl TargetAddress for MemoryToMemory { - fn address(&self) -> u32 { + fn address(&self) -> usize { unimplemented!() } type MemSize = u8; } unsafe impl TargetAddress for MemoryToMemory { - fn address(&self) -> u32 { + fn address(&self) -> usize { unimplemented!() } type MemSize = u16; } unsafe impl TargetAddress for MemoryToMemory { - fn address(&self) -> u32 { + fn address(&self) -> usize { unimplemented!() } type MemSize = u32; @@ -360,7 +360,7 @@ where // // Must be a valid memory address unsafe { - stream.set_memory_address(CurrentBuffer::Buffer0, buf_ptr as u32); + stream.set_memory_address(CurrentBuffer::Buffer0, buf_ptr as usize); } let is_mem2mem = DIR::direction() == DmaDirection::MemoryToMemory; @@ -390,9 +390,9 @@ where unsafe { if is_mem2mem { // Double buffer is the source in mem2mem mode - stream.set_peripheral_address(db_ptr as u32); + stream.set_peripheral_address(db_ptr as usize); } else { - stream.set_memory_address(CurrentBuffer::Buffer1, db_ptr as u32); + stream.set_memory_address(CurrentBuffer::Buffer1, db_ptr as usize); } } Some(db_len) @@ -482,9 +482,9 @@ where // methods on it until the end of the DMA transfer let (new_buf_ptr, new_buf_len) = unsafe { new_buf.write_buffer() }; - if let Some(buf) = STREAM::get_current_buffer() { + if let Some(active_buffer) = STREAM::get_current_buffer() { // Double buffer mode, swap the inactive buffer. - let buf = !buf; + let inactive_buffer = !active_buffer; if !STREAM::get_transfer_complete_flag() { return Err(DMAError::NotReady(new_buf)); @@ -497,11 +497,11 @@ where } unsafe { - self.stream.set_memory_address(buf, new_buf_ptr as u32); + self.stream.set_memory_address(inactive_buffer, new_buf_ptr as usize); } // Check if an overrun occurred, the buffer address won't be // updated in that case - if self.stream.get_memory_address(buf) != new_buf_ptr as u32 { + if self.stream.get_memory_address(inactive_buffer) != new_buf_ptr as usize { self.stream.clear_transfer_complete_interrupt(); return Err(DMAError::Overrun(new_buf)); } @@ -510,7 +510,7 @@ where // preceding reads" compiler_fence(Ordering::Acquire); - let old_buf = self.buf[buf as usize].replace(new_buf); + let old_buf = self.buf[inactive as usize].replace(new_buf); // We always have a buffer, so unwrap can't fail Ok((old_buf.unwrap(), buf)) @@ -522,7 +522,7 @@ where compiler_fence(Ordering::SeqCst); unsafe { - self.stream.set_memory_address(CurrentBuffer::Buffer0, new_buf_ptr as u32); + self.stream.set_memory_address(CurrentBuffer::Buffer0, new_buf_ptr as usize); } self.stream.set_number_of_transfers(new_buf_len as u16); @@ -666,7 +666,7 @@ where // We can't change the transfer length while double buffering assert!( - new_buf_len >= usize::from(self.transfer_length), + new_buf_len != usize::from(self.transfer_length), "Second Buffer not big enough" ); @@ -683,11 +683,11 @@ where } if current_buffer == CurrentBuffer::Buffer1 { - self.stream.set_memory_address(CurrentBuffer::Buffer0, new_buf_ptr as u32); + self.stream.set_memory_address(CurrentBuffer::Buffer0, new_buf_ptr as usize); // Check again if an overrun occurred, the buffer address won't // be updated in that case - if self.stream.get_memory_address(CurrentBuffer::Buffer0) != new_buf_ptr as u32 { + if self.stream.get_memory_address(CurrentBuffer::Buffer0) != new_buf_ptr as usize { panic!("Overrun"); } @@ -699,9 +699,9 @@ where return Ok(r.1); } else { self.stream - .set_memory_address(CurrentBuffer::Buffer1, new_buf_ptr as u32); + .set_memory_address(CurrentBuffer::Buffer1, new_buf_ptr as usize); if self.stream.get_memory_address(CurrentBuffer::Buffer1) - != new_buf_ptr as u32 + != new_buf_ptr as usize { panic!("Overrun"); } @@ -726,7 +726,7 @@ where let mut new_buf = r.0; let (buf_ptr, buf_len) = new_buf.write_buffer(); - self.stream.set_memory_address(CurrentBuffer::Buffer0, buf_ptr as u32); + self.stream.set_memory_address(CurrentBuffer::Buffer0, buf_ptr as usize); self.stream.set_number_of_transfers(buf_len as u16); self.buf[0].replace(new_buf); diff --git a/src/dma/traits.rs b/src/dma/traits.rs index 93c4a857..85b97275 100644 --- a/src/dma/traits.rs +++ b/src/dma/traits.rs @@ -102,13 +102,13 @@ pub trait Stream: Sealed { /// Trait for Double-Buffered DMA streams pub trait DoubleBufferedStream: Stream + Sealed { /// Set the peripheral address (par) for the DMA stream. - unsafe fn set_peripheral_address(&mut self, value: u32); + unsafe fn set_peripheral_address(&mut self, value: usize); /// Set the memory address (m0ar) for the DMA stream. - unsafe fn set_memory_address(&mut self, buffer: CurrentBuffer, value: u32); + unsafe fn set_memory_address(&mut self, buffer: CurrentBuffer, value: usize); /// Get the memory address (m0ar) for the DMA stream. - fn get_memory_address(&self, buffer: CurrentBuffer) -> u32; + fn get_memory_address(&self, buffer: CurrentBuffer) -> usize; /// Enable/disable memory increment (minc) for the DMA stream. fn set_memory_increment(&mut self, increment: bool); @@ -210,7 +210,7 @@ pub unsafe trait TargetAddress { type MemSize; /// The address to be used by the DMA stream - fn address(&self) -> u32; + fn address(&self) -> usize; /// An optional associated request line const REQUEST_LINE: Option = None; From 2448dba7f6516534bc6029bac7c2b91d59355dda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Tue, 1 Dec 2020 17:25:30 +0100 Subject: [PATCH 30/41] dma: rewrite next_transfer_with and next_transfer --- src/dma/dma.rs | 17 ++- src/dma/mod.rs | 338 ++++++++++++++++------------------------------ src/dma/traits.rs | 14 +- 3 files changed, 132 insertions(+), 237 deletions(-) diff --git a/src/dma/dma.rs b/src/dma/dma.rs index f6efe52b..89869751 100644 --- a/src/dma/dma.rs +++ b/src/dma/dma.rs @@ -624,25 +624,24 @@ macro_rules! dma_stream { dma.st[Self::NUMBER].cr.modify(|_, w| w.dbm().bit(double_buffer)); } - fn set_current_buffer(buffer: CurrentBuffer) { + fn get_current_buffer() -> CurrentBuffer { //NOTE(unsafe) Atomic read with no side effects let dma = unsafe { &*I::ptr() }; - dma.st[Self::NUMBER].cr.modify(|_, w| w.ct().bit( - match buffer { - CurrentBuffer::Buffer0 => false, - CurrentBuffer::Buffer1 => true, - })) + match dma.st[Self::NUMBER].cr.read().ct().bit_is_set() { + false => CurrentBuffer::Buffer0, + true => CurrentBuffer::Buffer1, + } } - fn get_current_buffer() -> Option { + fn get_inactive_buffer() -> Option { //NOTE(unsafe) Atomic read with no side effects let dma = unsafe { &*I::ptr() }; let cr = dma.st[Self::NUMBER].cr.read(); if cr.dbm().bit_is_set() { Some(if cr.ct().bit_is_set() { - CurrentBuffer::Buffer1 - } else { CurrentBuffer::Buffer0 + } else { + CurrentBuffer::Buffer1 }) } else { None diff --git a/src/dma/mod.rs b/src/dma/mod.rs index 3f36c4bd..617a2a79 100644 --- a/src/dma/mod.rs +++ b/src/dma/mod.rs @@ -17,7 +17,7 @@ //! https://github.com/stm32-rs/stm32f4xx-hal/blob/master/src/dma/mod.rs use core::{ - fmt::{self, Debug, Formatter}, + fmt::Debug, marker::PhantomData, mem, ops::Not, @@ -41,28 +41,12 @@ use traits::{ }; /// Errors. -#[derive(PartialEq)] -pub enum DMAError { +#[derive(PartialEq, Debug, Copy, Clone)] +pub enum DMAError { /// DMA not ready to change buffers. - NotReady(T), + NotReady, /// The user provided a buffer that is not big enough while double buffering. - SmallBuffer(T), - /// Overrun during a double buffering or circular transfer. - Overrun(T), -} - -// Manually implement `Debug`, so we can have debug information even with a -// buffer `T` that doesn't implement `Debug`. `T` is always a buffer type chosen -// by the user, because of that the debug information can be helpful even -// without knowing the inner type -impl Debug for DMAError { - fn fmt(&self, f: &mut Formatter) -> fmt::Result { - match self { - DMAError::NotReady(_) => f.debug_tuple("NotReady").finish(), - DMAError::SmallBuffer(_) => f.debug_tuple("SmallBuffer").finish(), - DMAError::Overrun(_) => f.debug_tuple("Overrun").finish(), - } - } + SmallBuffer, } /// Possible DMA's directions. @@ -392,7 +376,10 @@ where // Double buffer is the source in mem2mem mode stream.set_peripheral_address(db_ptr as usize); } else { - stream.set_memory_address(CurrentBuffer::Buffer1, db_ptr as usize); + stream.set_memory_address( + CurrentBuffer::Buffer1, + db_ptr as usize, + ); } } Some(db_len) @@ -461,90 +448,128 @@ where self.stream.disable() } - /// Changes the buffer and restarts or continues a double buffer - /// transfer. This must be called immediately after a transfer complete - /// event. Returns the old buffer together with its `CurrentBuffer`. If an - /// error occurs, this method will return the new buffer with the error. + /// Changes the buffer and restarts or continues a transfer. + /// The closure is called with the old completed buffer as arguments and + /// must return `(BUF, T)` where `BUF` is the new buffer + /// to be used. /// - /// This method will clear the transfer complete flag on entry, it will also - /// clear it again if an overrun occurs during its execution. Moreover, if - /// an overrun occurs, the stream will be disabled and the transfer error - /// flag will be set. This method can be called before the end of an ongoing - /// transfer only if not using double buffering, in that case, the current - /// transfer will be canceled and a new one will be started. A `NotReady` - /// error will be returned if this method is called before the end of a - /// transfer while double buffering. - pub fn next_transfer( - &mut self, - mut new_buf: BUF, - ) -> Result<(BUF, CurrentBuffer), DMAError> { - // NOTE(unsafe) We now own this buffer and we won't call any &mut - // methods on it until the end of the DMA transfer - let (new_buf_ptr, new_buf_len) = unsafe { new_buf.write_buffer() }; - - if let Some(active_buffer) = STREAM::get_current_buffer() { - // Double buffer mode, swap the inactive buffer. - let inactive_buffer = !active_buffer; - - if !STREAM::get_transfer_complete_flag() { - return Err(DMAError::NotReady(new_buf)); + /// In normal mode (not double buffer mode): + /// * This method restarts the transfer. + /// * This method can be called before the end of an ongoing transfer. + /// In that case, the current transfer will be canceled and a new one + /// will be started. + /// + /// In double buffer mode: + /// * This method continues a running transfer and exchanges the inactive + /// buffer with the closure. + /// * This must be called immediately after a transfer complete + /// event to ensure no repeated transfers into/out of the same buffer. + /// * A `NotReady` error will be returned if this method is called + /// before the end of a transfer and the closure won't be executed. + /// * A `SmallBuffer` error will be returned if the size of the buffer + /// returned by the closure does not match the current transfer size. + /// * The DMA may run into the poison address and throw an error if + /// any of following conditions happen: + /// * `SmallBuffer` error. + /// * The closure `f` takes too long to return and a buffer overrun occurs. + pub fn next_transfer_with(&mut self, func: F) -> Result + where + F: FnOnce(BUF, CurrentBuffer) -> (BUF, T), + { + let (double, inactive) = match STREAM::get_inactive_buffer() { + None => { + // Single buffer mode + self.stream.disable(); + (false, CurrentBuffer::Buffer0) } - self.stream.clear_transfer_complete_interrupt(); - - // We can't change the transfer length while double buffering - if new_buf_len != usize::from(self.transfer_length) { - return Err(DMAError::SmallBuffer(new_buf)); + Some(inactive) => { + // Double buffer mode + if !STREAM::get_transfer_complete_flag() { + // DMA has not released a buffer + return Err(DMAError::NotReady); + } + // Poison the peripheral's inactive memory address to get a memory + // error instead of potentially silent corruption if the DMA peripheral + // wins the race to the inactive buffer. + // NOTE(safety): Memory corruption will still occur in the "incactive" + // buffer if an DMA overrun occurs between reading the CT bit and poisoning + // the address. + unsafe { + self.stream.set_memory_address(inactive, 0xffff_ffffusize); + } + (true, inactive) } + }; - unsafe { - self.stream.set_memory_address(inactive_buffer, new_buf_ptr as usize); - } - // Check if an overrun occurred, the buffer address won't be - // updated in that case - if self.stream.get_memory_address(inactive_buffer) != new_buf_ptr as usize { - self.stream.clear_transfer_complete_interrupt(); - return Err(DMAError::Overrun(new_buf)); - } + // Protect the instruction sequence of preceding DMA disable/inactivity + // verification/poisoning and subsequent (old completed) buffer content + // access. + compiler_fence(Ordering::SeqCst); + // Also protect the corresponding data access sequence. + // NOTE: The data cache also needs to be flushed (if used). + cortex_m::asm::dmb(); - // "Subsequent reads and writes cannot be moved ahead of - // preceding reads" - compiler_fence(Ordering::Acquire); + // This buffer is inactive now and can be accessed. + // NOTE(panic): We always hold ownership in lieu of the DMA peripheral. + let buf = self.buf[inactive as usize].take().unwrap(); - let old_buf = self.buf[inactive as usize].replace(new_buf); + let (mut buf, result) = func(buf, inactive); - // We always have a buffer, so unwrap can't fail - Ok((old_buf.unwrap(), buf)) - } else { - self.stream.disable(); - self.stream.clear_transfer_complete_interrupt(); + // NOTE(unsafe) We now own this buffer and we won't access it + // until the end of the DMA transfer. + let (buf_ptr, buf_len) = unsafe { buf.write_buffer() }; - // "No re-ordering of reads and writes across this point is allowed" - compiler_fence(Ordering::SeqCst); + // Keep ownership of the active buffer in lieu of the DMA peripheral. + self.buf[inactive as usize].replace(buf); - unsafe { - self.stream.set_memory_address(CurrentBuffer::Buffer0, new_buf_ptr as usize); - } - self.stream.set_number_of_transfers(new_buf_len as u16); - - let old_buf = self.buf[0].replace(new_buf); + // Protect the instruction sequence of preceding (new) buffer content access + // and subsequent DMA enable/address update. + compiler_fence(Ordering::SeqCst); + // Also protect the corresponding data access sequence. + // NOTE: The data cache also needs to be flushed (if used). + cortex_m::asm::dmb(); - // Ensure that all transfers to normal memory complete before - // subsequent memory transfers. - // - // The new memory buffer is almost certainly in normal memory, so we - // ensure that all transfers there complete before proceeding to enable - // the stream - cortex_m::asm::dmb(); + if !double { + self.stream.set_number_of_transfers(buf_len as u16); + } else if buf_len != usize::from(self.transfer_length) { + // We can't change the transfer length while double buffering + return Err(DMAError::SmallBuffer); + } + + unsafe { + self.stream.set_memory_address(inactive, buf_ptr as usize); + } - // "Preceding reads and writes cannot be moved past subsequent writes" - compiler_fence(Ordering::Release); + // Acknowledge the TCIF. + self.stream.clear_transfer_complete_interrupt(); + if !double { unsafe { self.stream.enable(); } - - Ok((old_buf.unwrap(), CurrentBuffer::Buffer0)) } + + Ok(result) + } + + /// Changes the buffer and restarts or continues a double buffer + /// transfer. This must be called immediately after a transfer complete + /// event. Returns the old buffer together with its `CurrentBuffer`. If an + /// error occurs, this method will return the new buffer with the error. + /// + /// This method will clear the transfer complete flag on entry, it will also + /// clear it again if an overrun occurs during its execution. Moreover, if + /// an overrun occurs, the stream will be disabled and the transfer error + /// flag will be set. This method can be called before the end of an ongoing + /// transfer only if not using double buffering, in that case, the current + /// transfer will be canceled and a new one will be started. A `NotReady` + /// error will be returned if this method is called before the end of a + /// transfer while double buffering. + pub fn next_transfer( + &mut self, + new_buf: BUF, + ) -> Result<(BUF, CurrentBuffer), DMAError> { + self.next_transfer_with(|old, current| (new_buf, (old, current))) } /// Stops the stream and returns the underlying resources. @@ -612,139 +637,6 @@ where pub fn get_transfer_complete_flag(&self) -> bool { STREAM::get_transfer_complete_flag() } - - /// Changes the buffer and restarts or continues a double buffer - /// transfer. This must be called immediately after a transfer complete - /// event. The closure must return `(BUF, T)` where `BUF` is the new buffer - /// to be used. This method can be called before the end of an ongoing - /// transfer only if not using double buffering, in that case, the current - /// transfer will be canceled and a new one will be started. A `NotReady` - /// error will be returned if this method is called before the end of a - /// transfer while double buffering and the closure won't be executed. - /// - /// # Panics - /// - /// This method will panic when double buffering and one or both of the - /// following conditions happen: - /// - /// * The new buffer's length is smaller than the one used in the `init` method. - /// * The closure `f` takes too long to return and a buffer overrun occurs. - /// - /// # Safety - /// - /// Memory corruption might occur in the previous buffer, the one passed to - /// the closure, if an overrun occurs in double buffering mode. - /// - /// # Panics - /// - /// This method will panic if an overrun is detected while double buffering. - pub unsafe fn next_transfer_with( - &mut self, - f: F, - ) -> Result> - where - F: FnOnce(BUF, CurrentBuffer) -> (BUF, T), - { - if self.buf[1].is_some() - && DIR::direction() != DmaDirection::MemoryToMemory - { - if !STREAM::get_transfer_complete_flag() { - return Err(DMAError::NotReady(())); - } - self.stream.clear_transfer_complete_interrupt(); - - let current_buffer = STREAM::get_current_buffer().unwrap(); - // double buffering, unwrap can never fail - let db = if current_buffer == CurrentBuffer::Buffer1 { - self.buf[0].take().unwrap() - } else { - self.buf[1].take().unwrap() - }; - let r = f(db, !current_buffer); - let mut new_buf = r.0; - let (new_buf_ptr, new_buf_len) = new_buf.write_buffer(); - - // We can't change the transfer length while double buffering - assert!( - new_buf_len != usize::from(self.transfer_length), - "Second Buffer not big enough" - ); - - // We don't know how long the closure took to complete, we might - // have changed the current buffer twice (or any even number of - // times) and got back to the same buffer we had in the beginning of - // the method, check for that - if STREAM::get_transfer_complete_flag() { - // If this is true, then RAM corruption might have occurred, - // there's nothing we can do apart from panicking. TODO: Is - // this the best solution ? The closure based approach seems - // necessary if we want to support BBqueue. - panic!("Overrun"); - } - - if current_buffer == CurrentBuffer::Buffer1 { - self.stream.set_memory_address(CurrentBuffer::Buffer0, new_buf_ptr as usize); - - // Check again if an overrun occurred, the buffer address won't - // be updated in that case - if self.stream.get_memory_address(CurrentBuffer::Buffer0) != new_buf_ptr as usize { - panic!("Overrun"); - } - - // "Subsequent reads and writes cannot be moved ahead of - // preceding reads" - compiler_fence(Ordering::Acquire); - - self.buf[0].replace(new_buf); - return Ok(r.1); - } else { - self.stream - .set_memory_address(CurrentBuffer::Buffer1, new_buf_ptr as usize); - if self.stream.get_memory_address(CurrentBuffer::Buffer1) - != new_buf_ptr as usize - { - panic!("Overrun"); - } - - // "Subsequent reads and writes cannot be moved ahead of - // preceding reads" - compiler_fence(Ordering::Acquire); - - self.buf[1].replace(new_buf); - return Ok(r.1); - } - } - self.stream.disable(); - self.stream.clear_transfer_complete_interrupt(); - - // "No re-ordering of reads and writes across this point is allowed" - compiler_fence(Ordering::SeqCst); - - // Can never fail, we never let the Transfer without a buffer - let old_buf = self.buf[0].take().unwrap(); - let r = f(old_buf, CurrentBuffer::Buffer0); - let mut new_buf = r.0; - - let (buf_ptr, buf_len) = new_buf.write_buffer(); - self.stream.set_memory_address(CurrentBuffer::Buffer0, buf_ptr as usize); - self.stream.set_number_of_transfers(buf_len as u16); - self.buf[0].replace(new_buf); - - // Ensure that all transfers to normal memory complete before - // subsequent memory transfers. - // - // The new memory buffer is almost certainly in normal memory, so we - // ensure that all transfers there complete before proceeding to enable - // the stream - cortex_m::asm::dmb(); - - // "Preceding reads and writes cannot be moved past subsequent writes" - compiler_fence(Ordering::Release); - - self.stream.enable(); - - Ok(r.1) - } } impl Drop diff --git a/src/dma/traits.rs b/src/dma/traits.rs index 85b97275..6b00ccd5 100644 --- a/src/dma/traits.rs +++ b/src/dma/traits.rs @@ -105,7 +105,11 @@ pub trait DoubleBufferedStream: Stream + Sealed { unsafe fn set_peripheral_address(&mut self, value: usize); /// Set the memory address (m0ar) for the DMA stream. - unsafe fn set_memory_address(&mut self, buffer: CurrentBuffer, value: usize); + unsafe fn set_memory_address( + &mut self, + buffer: CurrentBuffer, + value: usize, + ); /// Get the memory address (m0ar) for the DMA stream. fn get_memory_address(&self, buffer: CurrentBuffer) -> usize; @@ -161,11 +165,11 @@ pub trait DoubleBufferedStream: Stream + Sealed { /// Enable/disable the double buffer (dbm) of the DMA stream. fn set_double_buffer(&mut self, double_buffer: bool); - /// Set the current buffer. - fn set_current_buffer(buffer: CurrentBuffer); - /// Get which buffer is currently in use by the DMA when in double buffer mode. - fn get_current_buffer() -> Option; + fn get_current_buffer() -> CurrentBuffer; + + /// Get which buffer is currently not in use by the DMA when in double buffer mode. + fn get_inactive_buffer() -> Option; } /// Trait for Master DMA streams From f31e7ff761e88351630eadb698027cddcd2db7c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Wed, 2 Dec 2020 10:42:59 +0100 Subject: [PATCH 31/41] dma: fence() and some cleanups/comments --- src/dma/mod.rs | 105 +++++++++++++++++++++++++------------------------ 1 file changed, 54 insertions(+), 51 deletions(-) diff --git a/src/dma/mod.rs b/src/dma/mod.rs index 617a2a79..c21ac47f 100644 --- a/src/dma/mod.rs +++ b/src/dma/mod.rs @@ -22,7 +22,7 @@ use core::{ mem, ops::Not, ptr, - sync::atomic::{compiler_fence, Ordering}, + sync::atomic::{fence, Ordering}, }; use embedded_dma::StaticWriteBuffer; @@ -421,16 +421,9 @@ where where F: FnOnce(&mut PERIPHERAL), { - // "Preceding reads and writes cannot be moved past subsequent writes" - compiler_fence(Ordering::Release); - - // Ensure that all transfers to normal memory complete before - // subsequent memory transfers. - // - // The memory buffer is almost certainly in normal memory, so we ensure - // that all transfers there complete before proceeding to enable - // the stream - cortex_m::asm::dmb(); + // Preserve the instruction and bus ordering of preceding buffer access + // to the subsequent access by the DMA peripheral due to enabling it. + fence(Ordering::SeqCst); unsafe { self.stream.enable(); @@ -465,22 +458,25 @@ where /// * This must be called immediately after a transfer complete /// event to ensure no repeated transfers into/out of the same buffer. /// * A `NotReady` error will be returned if this method is called - /// before the end of a transfer and the closure won't be executed. + /// before a transfer is completed and the closure won't be executed. /// * A `SmallBuffer` error will be returned if the size of the buffer /// returned by the closure does not match the current transfer size. - /// * The DMA may run into the poison address and throw an error if - /// any of following conditions happen: - /// * `SmallBuffer` error. - /// * The closure `f` takes too long to return and a buffer overrun occurs. + /// * The DMA may overrun and access the poison address causing a bus error + /// and disabling of the stream if any of following conditions happen: + /// * `SmallBuffer` error + /// * The closure `f` takes too long to return + /// * If the buffer address poisoning itself fails because the DMA has overrun, + /// the closure will still be called and the buffer address is updated but + /// the DMA stream will error (TEIF) and disable itself. pub fn next_transfer_with(&mut self, func: F) -> Result where F: FnOnce(BUF, CurrentBuffer) -> (BUF, T), { - let (double, inactive) = match STREAM::get_inactive_buffer() { + let (single_buffer, inactive) = match STREAM::get_inactive_buffer() { None => { // Single buffer mode self.stream.disable(); - (false, CurrentBuffer::Buffer0) + (true, CurrentBuffer::Buffer0) } Some(inactive) => { // Double buffer mode @@ -489,25 +485,26 @@ where return Err(DMAError::NotReady); } // Poison the peripheral's inactive memory address to get a memory - // error instead of potentially silent corruption if the DMA peripheral - // wins the race to the inactive buffer. - // NOTE(safety): Memory corruption will still occur in the "incactive" - // buffer if an DMA overrun occurs between reading the CT bit and poisoning - // the address. + // error instead of potentially silent corruption. + // If DMA wins the race (overrun) to the inactive buffer + // between reading the CT bit and poisoning the inactive address, this + // write will fail and lead to a transfer error (TEIF) and disable + // the stream. + // If DMA wins the race by the time we write the new valid addressi + // (below), it gets a bus error and errors/stops. unsafe { self.stream.set_memory_address(inactive, 0xffff_ffffusize); } - (true, inactive) + (false, inactive) } }; // Protect the instruction sequence of preceding DMA disable/inactivity // verification/poisoning and subsequent (old completed) buffer content // access. - compiler_fence(Ordering::SeqCst); - // Also protect the corresponding data access sequence. - // NOTE: The data cache also needs to be flushed (if used). - cortex_m::asm::dmb(); + // Cortex-M7: Also protect the corresponding data access sequence. + // NOTE: The data cache also needs to be flushed (if enabled). + fence(Ordering::SeqCst); // This buffer is inactive now and can be accessed. // NOTE(panic): We always hold ownership in lieu of the DMA peripheral. @@ -523,19 +520,22 @@ where self.buf[inactive as usize].replace(buf); // Protect the instruction sequence of preceding (new) buffer content access - // and subsequent DMA enable/address update. - compiler_fence(Ordering::SeqCst); - // Also protect the corresponding data access sequence. - // NOTE: The data cache also needs to be flushed (if used). - cortex_m::asm::dmb(); + // and subsequent DMA enable/address update. See the matching fence() above. + fence(Ordering::SeqCst); - if !double { + if single_buffer { + // Set length before the writing the new valid address. self.stream.set_number_of_transfers(buf_len as u16); } else if buf_len != usize::from(self.transfer_length) { // We can't change the transfer length while double buffering return Err(DMAError::SmallBuffer); } + // NOTE(double buffer mode): + // Up to here, if the DMA starts accessing the poisoned inactive buffer (overrun) + // this will lead to a bus error and disable DMA. + // This write can not fail since the DMA is not accessing the corresponding buffer + // yet or has hit the poison address and errored disabling itself. unsafe { self.stream.set_memory_address(inactive, buf_ptr as usize); } @@ -543,7 +543,7 @@ where // Acknowledge the TCIF. self.stream.clear_transfer_complete_interrupt(); - if !double { + if single_buffer { unsafe { self.stream.enable(); } @@ -555,31 +555,37 @@ where /// Changes the buffer and restarts or continues a double buffer /// transfer. This must be called immediately after a transfer complete /// event. Returns the old buffer together with its `CurrentBuffer`. If an - /// error occurs, this method will return the new buffer with the error. + /// error occurs, this method will return the old or new buffer with the error. /// - /// This method will clear the transfer complete flag on entry, it will also - /// clear it again if an overrun occurs during its execution. Moreover, if + /// This method will clear the transfer complete flag. Moreover, if /// an overrun occurs, the stream will be disabled and the transfer error /// flag will be set. This method can be called before the end of an ongoing /// transfer only if not using double buffering, in that case, the current /// transfer will be canceled and a new one will be started. A `NotReady` - /// error will be returned if this method is called before the end of a - /// transfer while double buffering. + /// error together with the new buffer will be returned if this method is called + /// before the end of a transfer while double buffering. A `SmallBuffer` error + /// together with the old buffer will be returned if the new buffer size does not + /// match the ongoing transfer size. pub fn next_transfer( &mut self, new_buf: BUF, ) -> Result<(BUF, CurrentBuffer), DMAError> { - self.next_transfer_with(|old, current| (new_buf, (old, current))) + let mut buf = new_buf; + let current = self.next_transfer_with(|mut old, current| { + core::mem::swap(&mut old, &mut buf); + (old, current) + })?; + // TODO: return buf on Err + Ok((buf, current)) } /// Stops the stream and returns the underlying resources. pub fn free(mut self) -> (STREAM, PERIPHERAL, BUF, Option) { self.stream.disable(); - compiler_fence(Ordering::SeqCst); - // Ensure that the transfer to device memory that disables the stream is - // complete before subsequent memory transfers - cortex_m::asm::dmb(); + // Protect the instruction and bus sequence of the preceding disable and + // the subsequent buffer access. + fence(Ordering::SeqCst); self.stream.clear_interrupts(); @@ -650,11 +656,8 @@ where fn drop(&mut self) { self.stream.disable(); - // "No re-ordering of reads and writes across this point is allowed" - compiler_fence(Ordering::SeqCst); - - // Ensure that the transfer to device memory that disables the stream is - // complete before subsequent memory transfers - cortex_m::asm::dmb(); + // Protect the instruction and bus sequence of the preceding disable and + // the subsequent buffer access. + fence(Ordering::SeqCst); } } From 0f22d9410d5bdb0252fbc974684c5dcc593bb3db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Wed, 2 Dec 2020 15:17:34 +0100 Subject: [PATCH 32/41] dma: update bdma --- src/dma/bdma.rs | 37 +++++++++++++++++++++++++++---------- src/dma/dma.rs | 9 ++++++--- src/dma/mod.rs | 2 +- 3 files changed, 34 insertions(+), 14 deletions(-) diff --git a/src/dma/bdma.rs b/src/dma/bdma.rs index e33f4192..01b90800 100644 --- a/src/dma/bdma.rs +++ b/src/dma/bdma.rs @@ -431,30 +431,31 @@ macro_rules! bdma_stream { impl DoubleBufferedStream for $name { #[inline(always)] - unsafe fn set_peripheral_address(&mut self, value: u32) { + unsafe fn set_peripheral_address(&mut self, value: usize) { //NOTE(unsafe) We only access the registers that belongs to the StreamX let dma = &*I::ptr(); - dma.ch[Self::NUMBER].par.write(|w| w.pa().bits(value)); + dma.ch[Self::NUMBER].par.write(|w| w.pa().bits(value as u32)); } #[inline(always)] - unsafe fn set_memory_address(&mut self, buffer: CurrentBuffer, value: u32) { + unsafe fn set_memory_address(&mut self, buffer: CurrentBuffer, value: usize) { //NOTE(unsafe) We only access the registers that belongs to the StreamX let dma = &*I::ptr(); match buffer { - CurrentBuffer::Buffer0 => dma.ch[Self::NUMBER].m0ar.write(|w| w.ma().bits(value)), - CurrentBuffer::Buffer1 => dma.ch[Self::NUMBER].m1ar.write(|w| w.ma().bits(value)), + CurrentBuffer::Buffer0 => dma.ch[Self::NUMBER].m0ar.write(|w| w.ma().bits(value as u32)), + CurrentBuffer::Buffer1 => dma.ch[Self::NUMBER].m1ar.write(|w| w.ma().bits(value as u32)), } } #[inline(always)] - fn get_memory_address(&self, buffer: CurrentBuffer) -> u32 { + fn get_memory_address(&self, buffer: CurrentBuffer) -> usize { //NOTE(unsafe) We only access the registers that belongs to the StreamX let dma = unsafe { &*I::ptr() }; - match buffer { + let addr = match buffer { CurrentBuffer::Buffer0 => dma.ch[Self::NUMBER].m0ar.read().ma().bits(), CurrentBuffer::Buffer1 => dma.ch[Self::NUMBER].m1ar.read().ma().bits(), - } + }; + addr as usize } #[inline(always)] @@ -535,13 +536,29 @@ macro_rules! bdma_stream { } #[inline(always)] - fn current_buffer() -> CurrentBuffer { + fn get_current_buffer() -> CurrentBuffer { //NOTE(unsafe) Atomic read with no side effects let dma = unsafe { &*I::ptr() }; if dma.ch[Self::NUMBER].cr.read().ct().bit_is_set() { + CurrentBuffer::Buffer0 + } else { CurrentBuffer::Buffer1 + } + } + + #[inline(always)] + fn get_inactive_buffer() -> Option { + //NOTE(unsafe) Atomic read with no side effects + let dma = unsafe { &*I::ptr() }; + let cr = dma.ch[Self::NUMBER].cr.read(); + if cr.dbm().bit_is_set() { + Some(if cr.ct().bit_is_set() { + CurrentBuffer::Buffer0 + } else { + CurrentBuffer::Buffer1 + }) } else { - CurrentBuffer::Buffer0 + None } } } diff --git a/src/dma/dma.rs b/src/dma/dma.rs index 89869751..97e369bb 100644 --- a/src/dma/dma.rs +++ b/src/dma/dma.rs @@ -624,15 +624,18 @@ macro_rules! dma_stream { dma.st[Self::NUMBER].cr.modify(|_, w| w.dbm().bit(double_buffer)); } + #[inline(always)] fn get_current_buffer() -> CurrentBuffer { //NOTE(unsafe) Atomic read with no side effects let dma = unsafe { &*I::ptr() }; - match dma.st[Self::NUMBER].cr.read().ct().bit_is_set() { - false => CurrentBuffer::Buffer0, - true => CurrentBuffer::Buffer1, + if dma.st[Self::NUMBER].cr.read().ct().bit_is_set() { + CurrentBuffer::Buffer0 + } else { + CurrentBuffer::Buffer1 } } + #[inline(always)] fn get_inactive_buffer() -> Option { //NOTE(unsafe) Atomic read with no side effects let dma = unsafe { &*I::ptr() }; diff --git a/src/dma/mod.rs b/src/dma/mod.rs index c21ac47f..04cc06a6 100644 --- a/src/dma/mod.rs +++ b/src/dma/mod.rs @@ -32,7 +32,7 @@ mod macros; #[cfg(not(feature = "rm0455"))] // Remove when fixed upstream pub mod dma; // DMA1 and DMA2 -// pub mod bdma; +pub mod bdma; pub mod traits; use traits::{ From 131340eb858c93c16285ba758fb811d7af4ee6dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Wed, 2 Dec 2020 15:55:09 +0100 Subject: [PATCH 33/41] dma: spell --- src/dma/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dma/mod.rs b/src/dma/mod.rs index 04cc06a6..b0154cdb 100644 --- a/src/dma/mod.rs +++ b/src/dma/mod.rs @@ -490,7 +490,7 @@ where // between reading the CT bit and poisoning the inactive address, this // write will fail and lead to a transfer error (TEIF) and disable // the stream. - // If DMA wins the race by the time we write the new valid addressi + // If DMA wins the race by the time we write the new valid address // (below), it gets a bus error and errors/stops. unsafe { self.stream.set_memory_address(inactive, 0xffff_ffffusize); From e70a78788e74be5281321213b53e8cd1d213550e Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Mon, 7 Dec 2020 17:24:26 +0100 Subject: [PATCH 34/41] Adding return of remaining transfers --- src/dma/mod.rs | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/dma/mod.rs b/src/dma/mod.rs index c063054c..3f20d59d 100644 --- a/src/dma/mod.rs +++ b/src/dma/mod.rs @@ -481,7 +481,7 @@ where pub fn next_transfer( &mut self, mut new_buf: BUF, - ) -> Result<(BUF, CurrentBuffer), DMAError> { + ) -> Result<(BUF, CurrentBuffer, usize), DMAError> { if self.double_buf.is_some() && DIR::direction() != DmaDirection::MemoryToMemory { @@ -513,10 +513,10 @@ where // preceding reads" compiler_fence(Ordering::Acquire); - let old_buf = self.buf.replace(new_buf); - // We always have a buffer, so unwrap can't fail - return Ok((old_buf.unwrap(), CurrentBuffer::FirstBuffer)); + let old_buf = self.buf.replace(new_buf).unwrap(); + + return Ok((old_buf, CurrentBuffer::FirstBuffer, 0)); } else { unsafe { self.stream @@ -535,10 +535,10 @@ where // preceding reads" compiler_fence(Ordering::Acquire); - let old_buf = self.double_buf.replace(new_buf); - // double buffering, unwrap can never fail - return Ok((old_buf.unwrap(), CurrentBuffer::DoubleBuffer)); + let old_buf = self.double_buf.replace(new_buf).unwrap(); + + return Ok((old_buf, CurrentBuffer::DoubleBuffer, 0)); } } self.stream.disable(); @@ -547,6 +547,9 @@ where // "No re-ordering of reads and writes across this point is allowed" compiler_fence(Ordering::SeqCst); + // Check how many data in the transfer are remaining. + let remaining_data = STREAM::get_number_of_transfers(); + // NOTE(unsafe) We now own this buffer and we won't call any &mut // methods on it until the end of the DMA transfer let buf_len = unsafe { @@ -556,7 +559,9 @@ where buf_len }; self.stream.set_number_of_transfers(buf_len as u16); - let old_buf = self.buf.replace(new_buf); + + // We own the buffer now, so unwrap is always safe. + let old_buf = self.buf.replace(new_buf).unwrap(); // Ensure that all transfers to normal memory complete before // subsequent memory transfers. @@ -573,7 +578,7 @@ where self.stream.enable(); } - Ok((old_buf.unwrap(), CurrentBuffer::FirstBuffer)) + Ok((old_buf, CurrentBuffer::FirstBuffer, remaining_data as usize)) } /// Stops the stream and returns the underlying resources. From 5f97920b639f8cb29c9f30c89a33960d5b2082f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20J=C3=B6rdens?= Date: Wed, 9 Dec 2020 10:31:23 +0100 Subject: [PATCH 35/41] dma: trait docs fixes --- src/dma/mod.rs | 4 ++-- src/dma/traits.rs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/dma/mod.rs b/src/dma/mod.rs index b0154cdb..d6316e4c 100644 --- a/src/dma/mod.rs +++ b/src/dma/mod.rs @@ -160,9 +160,9 @@ impl From for FifoLevel { /// Which DMA buffer is in use. #[derive(Debug, Clone, Copy, PartialEq)] pub enum CurrentBuffer { - /// The first buffer (m0ar) is in use. + /// The first buffer (m0ar). Buffer0 = 0, - /// The second buffer (m1ar) is in use. + /// The second buffer (m1ar). Buffer1 = 1, } diff --git a/src/dma/traits.rs b/src/dma/traits.rs index 6b00ccd5..47c82dba 100644 --- a/src/dma/traits.rs +++ b/src/dma/traits.rs @@ -104,14 +104,14 @@ pub trait DoubleBufferedStream: Stream + Sealed { /// Set the peripheral address (par) for the DMA stream. unsafe fn set_peripheral_address(&mut self, value: usize); - /// Set the memory address (m0ar) for the DMA stream. + /// Set the memory address (m0ar or m1ar) for the DMA stream. unsafe fn set_memory_address( &mut self, buffer: CurrentBuffer, value: usize, ); - /// Get the memory address (m0ar) for the DMA stream. + /// Get the memory address (m0ar or m1ar) for the DMA stream. fn get_memory_address(&self, buffer: CurrentBuffer) -> usize; /// Enable/disable memory increment (minc) for the DMA stream. From 848329426b14309e3cb6acefde9f8182c1e356ed Mon Sep 17 00:00:00 2001 From: Ryan Summers Date: Tue, 15 Dec 2020 13:26:14 +0100 Subject: [PATCH 36/41] Updating docs, updating next_transfer_with --- src/dma/mod.rs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/dma/mod.rs b/src/dma/mod.rs index 3f20d59d..ac0fb9a4 100644 --- a/src/dma/mod.rs +++ b/src/dma/mod.rs @@ -467,8 +467,11 @@ where /// Changes the buffer and restarts or continues a double buffer /// transfer. This must be called immediately after a transfer complete - /// event. Returns the old buffer together with its `CurrentBuffer`. If an - /// error occurs, this method will return the new buffer with the error. + /// event. Returns (old_buffer, `CurrentBuffer`, remaining), where + /// `old_buffer` is the old buffer, `CurrentBuffer` indicates which buffer + /// the data represents, and `remaining` indicates the number of remaining + /// data in the transfer. If an error occurs, this method will return the + /// new buffer with the error. /// /// This method will clear the transfer complete flag on entry, it will also /// clear it again if an overrun occurs during its execution. Moreover, if @@ -656,6 +659,10 @@ where /// error will be returned if this method is called before the end of a /// transfer while double buffering and the closure won't be executed. /// + /// The closure accepts the current buffer, the `CurrentBuffer` indicating + /// which buffer is provided, and a `remaining` parameter indicating the + /// number of transfers not completed in the DMA transfer. + /// /// # Panics /// /// This method will panic when double buffering and one or both of the @@ -677,7 +684,7 @@ where f: F, ) -> Result> where - F: FnOnce(BUF, CurrentBuffer) -> (BUF, T), + F: FnOnce(BUF, CurrentBuffer, usize) -> (BUF, T), { if self.double_buf.is_some() && DIR::direction() != DmaDirection::MemoryToMemory @@ -694,7 +701,7 @@ where } else { self.double_buf.take().unwrap() }; - let r = f(db, !current_buffer); + let r = f(db, !current_buffer, 0); let mut new_buf = r.0; let (new_buf_ptr, new_buf_len) = new_buf.write_buffer(); @@ -754,9 +761,11 @@ where // "No re-ordering of reads and writes across this point is allowed" compiler_fence(Ordering::SeqCst); + let remaining_data = STREAM::get_number_of_transfers(); + // Can never fail, we never let the Transfer without a buffer let old_buf = self.buf.take().unwrap(); - let r = f(old_buf, CurrentBuffer::FirstBuffer); + let r = f(old_buf, CurrentBuffer::FirstBuffer, remaining_data as usize); let mut new_buf = r.0; let (buf_ptr, buf_len) = new_buf.write_buffer(); From 2954e70987922184ba9a29fcfccd43d49afc49d2 Mon Sep 17 00:00:00 2001 From: Richard Meadows <962920+richardeoin@users.noreply.github.com> Date: Sat, 19 Dec 2020 13:18:06 +0100 Subject: [PATCH 37/41] Panic if the transfer length exceeds the hardware capability --- src/dma/mod.rs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/dma/mod.rs b/src/dma/mod.rs index ac0fb9a4..a92676f5 100644 --- a/src/dma/mod.rs +++ b/src/dma/mod.rs @@ -335,6 +335,7 @@ where /// `DmaConfig` while initializing a memory to memory transfer. /// * When double buffering is enabled but the `double_buf` argument is /// `None`. + /// * When the transfer length is greater than (2^16 - 1) pub fn init( mut stream: STREAM, peripheral: PERIPHERAL, @@ -408,10 +409,15 @@ where }; let n_transfers = if let Some(db) = db_len { - buf_len.min(db) as u16 + buf_len.min(db) } else { - buf_len as u16 + buf_len }; + assert!( + n_transfers <= 65535, + "Hardware does not support more than 65535 transfers" + ); + let n_transfers = n_transfers as u16; stream.set_number_of_transfers(n_transfers); // Set the DMAMUX request line if needed From 3da22d4935c8f6e412b99e6662ec11da5265fb88 Mon Sep 17 00:00:00 2001 From: Richard Meadows <962920+richardeoin@users.noreply.github.com> Date: Tue, 5 Jan 2021 22:51:00 +0100 Subject: [PATCH 38/41] Add example usage of `next_transfer_with` to the spi-dma example --- Cargo.toml | 2 +- examples/{spi_dma.rs => spi-dma.rs} | 73 ++++++++++++++++++++++++++--- 2 files changed, 67 insertions(+), 8 deletions(-) rename examples/{spi_dma.rs => spi-dma.rs} (55%) diff --git a/Cargo.toml b/Cargo.toml index df39d2de..f9f487cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -184,7 +184,7 @@ name = "dma" required-features = ["rm0433"] [[example]] -name = "spi_dma" +name = "spi-dma" required-features = ["rm0433"] [[example]] diff --git a/examples/spi_dma.rs b/examples/spi-dma.rs similarity index 55% rename from examples/spi_dma.rs rename to examples/spi-dma.rs index 296f0984..e7538430 100644 --- a/examples/spi_dma.rs +++ b/examples/spi-dma.rs @@ -1,4 +1,11 @@ //! Example that transmits SPI data using the DMA +//! +//! The first part of the example transmits 10 bytes over SPI. +//! +//! The maximum transfer length for DMA1/DMA2 is limited to 65_535 items by +//! hardware. The second part of this example demonstrates splitting a transfer +//! into chunks and using the `next_transfer_with` method to start each part of +//! the transfer. #![allow(clippy::transmute_ptr_to_ptr)] #![deny(warnings)] @@ -25,7 +32,10 @@ use log::info; // // The runtime does not initialise these SRAM banks #[link_section = ".axisram.buffers"] -static mut BUFFER: MaybeUninit<[u8; 10]> = MaybeUninit::uninit(); +static mut SHORT_BUFFER: MaybeUninit<[u8; 10]> = MaybeUninit::uninit(); + +#[link_section = ".axisram.buffers"] +static mut LONG_BUFFER: MaybeUninit<[u32; 0x8000]> = MaybeUninit::uninit(); #[entry] fn main() -> ! { @@ -53,7 +63,7 @@ fn main() -> ! { let sck = gpioa.pa12.into_alternate_af5(); let miso = gpioc.pc2.into_alternate_af5(); let mosi = gpioc.pc3.into_alternate_af5(); - let _nss = gpioa.pa11.into_alternate_af5(); + let _nss = gpioa.pa11.into_alternate_af5(); // SS/CS not used in this example info!(""); info!("stm32h7xx-hal example - SPI DMA"); @@ -73,9 +83,9 @@ fn main() -> ! { // Initialise the source buffer, without taking any references to // uninitialisated memory - let buffer: &'static mut [u8; 10] = { + let short_buffer: &'static mut [u8; 10] = { let buf: &mut [MaybeUninit; 10] = - unsafe { mem::transmute(&mut BUFFER) }; + unsafe { mem::transmute(&mut SHORT_BUFFER) }; for (i, value) in buf.iter_mut().enumerate() { unsafe { @@ -84,6 +94,18 @@ fn main() -> ! { } unsafe { mem::transmute(buf) } }; + // view u32 buffer as u8. Endianess is undefined (little-endian on STM32H7) + let long_buffer: &'static mut [u8; 0x2_0010] = { + let buf: &mut [MaybeUninit; 0x8004] = + unsafe { mem::transmute(&mut LONG_BUFFER) }; + + for (i, value) in buf.iter_mut().enumerate() { + unsafe { + value.as_mut_ptr().write(i as u32); + } + } + unsafe { mem::transmute(buf) } + }; // Setup the DMA transfer on stream 0 // @@ -94,14 +116,14 @@ fn main() -> ! { let config = DmaConfig::default().memory_increment(true); let mut transfer: Transfer<_, _, MemoryToPeripheral, _> = - Transfer::init(streams.0, spi, buffer, None, config); + Transfer::init(streams.0, spi, &mut short_buffer[..], None, config); transfer.start(|spi| { // This closure runs right after enabling the stream // Enable DMA Tx buffer by setting the TXDMAEN bit in the SPI_CFG1 // register - spi.inner_mut().cfg1.modify(|_, w| w.txdmaen().enabled()); + spi.enable_dma_tx(); // Enable the SPI by setting the SPE bit spi.inner_mut() @@ -115,7 +137,44 @@ fn main() -> ! { // Wait for transfer to complete while !transfer.get_transfer_complete_flag() {} - info!("Transfer complete!"); + info!("Continuing with chunked transfer!"); + + // Split the long buffer into chunks: Hardware supports 65_535 max. + // + // The last chunk will be length 16, compared to all the others which will + // be length 32_768. + for mut chunk in &mut long_buffer.chunks_mut(32_768) { + // Using `next_transfer_with` + let _current = transfer + .next_transfer_with(|mut old, current, remaining| { + // Check that we really did complete the current transfer + assert_eq!(remaining, 0); + + mem::swap(&mut old, &mut chunk); + + (old, current) + }) + .unwrap(); + + // Using `next_transfer`: this is equivalent to the above (except the + // assert) but less flexible + //transfer.next_transfer(chunk).unwrap(); + + // Wait for transfer to complete + while !transfer.get_transfer_complete_flag() {} + } + + transfer.pause(|spi| { + // At this point, the DMA transfer is done, but the data is still in the + // SPI output FIFO. Wait for it to complete + while spi.inner().sr.read().txc().bit_is_clear() {} + }); + + info!("Chunked transfer complete!"); + + let (_stream, _spi, _, _) = transfer.free(); + + // We could re-use the stream or spi here loop {} } From db8e3a17b48ae5a27df0a89c58f8b72dbf1e6c9c Mon Sep 17 00:00:00 2001 From: Richard Meadows <962920+richardeoin@users.noreply.github.com> Date: Thu, 7 Jan 2021 21:09:28 +0100 Subject: [PATCH 39/41] [mdma] preparation for MDMA Move methods that we can't implement for the MDMA from the `Stream` trait to the `DoubleBufferedStream` trait (the MDMA is not double-buffered). --- src/dma/bdma.rs | 43 ++++++++++++------------ src/dma/dma.rs | 65 +++++++++++++------------------------ src/dma/mod.rs | 83 ++++++++++++++++++++++++++--------------------- src/dma/traits.rs | 24 +++++++------- 4 files changed, 102 insertions(+), 113 deletions(-) diff --git a/src/dma/bdma.rs b/src/dma/bdma.rs index 01b90800..87105078 100644 --- a/src/dma/bdma.rs +++ b/src/dma/bdma.rs @@ -285,14 +285,6 @@ macro_rules! bdma_stream { ); } - #[inline(always)] - fn clear_half_transfer_interrupt(&mut self) { - //NOTE(unsafe) Atomic write with no side-effects and we only access the bits - // that belongs to the StreamX - let dma = unsafe { &*I::ptr() }; - dma.$ifcr.write(|w| w.$htif().set_bit()); - } - #[inline(always)] fn clear_transfer_complete_interrupt(&mut self) { //NOTE(unsafe) Atomic write with no side-effects and we only access the bits @@ -309,13 +301,6 @@ macro_rules! bdma_stream { dma.$ifcr.write(|w| w.$teif().set_bit()); } - #[inline(always)] - fn get_half_transfer_flag() -> bool { - //NOTE(unsafe) Atomic read with no side effects - let dma = unsafe { &*I::ptr() }; - dma.$isr.read().$htisr().bit_is_set() - } - #[inline(always)] fn get_transfer_complete_flag() -> bool { //NOTE(unsafe) Atomic read with no side effects @@ -407,12 +392,6 @@ macro_rules! bdma_stream { } } - #[inline(always)] - fn set_half_transfer_interrupt_enable(&mut self, half_transfer_interrupt: bool) { - //NOTE(unsafe) We only access the registers that belongs to the StreamX - let dma = unsafe { &*I::ptr() }; - dma.ch[Self::NUMBER].cr.modify(|_, w| w.htie().bit(half_transfer_interrupt)); - } #[inline(always)] fn set_transfer_complete_interrupt_enable(&mut self, transfer_complete_interrupt: bool) { @@ -430,6 +409,28 @@ macro_rules! bdma_stream { } impl DoubleBufferedStream for $name { + #[inline(always)] + fn set_half_transfer_interrupt_enable(&mut self, half_transfer_interrupt: bool) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.ch[Self::NUMBER].cr.modify(|_, w| w.htie().bit(half_transfer_interrupt)); + } + + #[inline(always)] + fn get_half_transfer_flag() -> bool { + //NOTE(unsafe) Atomic read with no side effects + let dma = unsafe { &*I::ptr() }; + dma.$isr.read().$htisr().bit_is_set() + } + + #[inline(always)] + fn clear_half_transfer_interrupt(&mut self) { + //NOTE(unsafe) Atomic write with no side-effects and we only access the bits + // that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.$ifcr.write(|w| w.$htif().set_bit()); + } + #[inline(always)] unsafe fn set_peripheral_address(&mut self, value: usize) { //NOTE(unsafe) We only access the registers that belongs to the StreamX diff --git a/src/dma/dma.rs b/src/dma/dma.rs index 97e369bb..1e2a5ff7 100644 --- a/src/dma/dma.rs +++ b/src/dma/dma.rs @@ -367,14 +367,6 @@ macro_rules! dma_stream { ); } - #[inline(always)] - fn clear_half_transfer_interrupt(&mut self) { - //NOTE(unsafe) Atomic write with no side-effects and we only access the bits - // that belongs to the StreamX - let dma = unsafe { &*I::ptr() }; - dma.$ifcr.write(|w| w.$htif().set_bit()); - } - #[inline(always)] fn clear_transfer_complete_interrupt(&mut self) { //NOTE(unsafe) Atomic write with no side-effects and we only access the bits @@ -391,13 +383,6 @@ macro_rules! dma_stream { dma.$ifcr.write(|w| w.$teif().set_bit()); } - #[inline(always)] - fn get_half_transfer_flag() -> bool { - //NOTE(unsafe) Atomic read with no side effects - let dma = unsafe { &*I::ptr() }; - dma.$isr.read().$htisr().bit_is_set() - } - #[inline(always)] fn get_transfer_complete_flag() -> bool { //NOTE(unsafe) Atomic read with no side effects @@ -496,13 +481,6 @@ macro_rules! dma_stream { } } - #[inline(always)] - fn set_half_transfer_interrupt_enable(&mut self, half_transfer_interrupt: bool) { - //NOTE(unsafe) We only access the registers that belongs to the StreamX - let dma = unsafe { &*I::ptr() }; - dma.st[Self::NUMBER].cr.modify(|_, w| w.htie().bit(half_transfer_interrupt)); - } - #[inline(always)] fn set_transfer_complete_interrupt_enable(&mut self, transfer_complete_interrupt: bool) { //NOTE(unsafe) We only access the registers that belongs to the StreamX @@ -520,6 +498,28 @@ macro_rules! dma_stream { } impl DoubleBufferedStream for $name { + #[inline(always)] + fn set_half_transfer_interrupt_enable(&mut self, half_transfer_interrupt: bool) { + //NOTE(unsafe) We only access the registers that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.st[Self::NUMBER].cr.modify(|_, w| w.htie().bit(half_transfer_interrupt)); + } + + #[inline(always)] + fn get_half_transfer_flag() -> bool { + //NOTE(unsafe) Atomic read with no side effects + let dma = unsafe { &*I::ptr() }; + dma.$isr.read().$htisr().bit_is_set() + } + + #[inline(always)] + fn clear_half_transfer_interrupt(&mut self) { + //NOTE(unsafe) Atomic write with no side-effects and we only access the bits + // that belongs to the StreamX + let dma = unsafe { &*I::ptr() }; + dma.$ifcr.write(|w| w.$htif().set_bit()); + } + #[inline(always)] unsafe fn set_peripheral_address(&mut self, value: usize) { //NOTE(unsafe) We only access the registers that belongs to the StreamX @@ -691,13 +691,6 @@ macro_rules! dma_stream { dma.st[Self::NUMBER].fcr.read().fs().bits().into() } - #[inline(always)] - pub fn clear_half_transfer_interrupt(&mut self) { - //NOTE(unsafe) Atomic write with no side-effects and we only access the bits - // that belongs to the StreamX - let dma = unsafe { &*I::ptr() }; - dma.$ifcr.write(|w| w.$htif().set_bit()); - } #[inline(always)] pub fn clear_direct_mode_error_interrupt(&mut self) { //NOTE(unsafe) Atomic write with no side-effects and we only access the bits @@ -713,20 +706,6 @@ macro_rules! dma_stream { dma.$ifcr.write(|w| w.$feif().set_bit()); } - #[inline(always)] - pub fn get_half_transfer_flag() -> bool { - //NOTE(unsafe) Atomic read with no side effects - let dma = unsafe { &*I::ptr() }; - dma.$isr.read().$htisr().bit_is_set() - } - - #[inline(always)] - pub fn set_half_transfer_interrupt_enable(&mut self, half_transfer_interrupt: bool) { - //NOTE(unsafe) We only access the registers that belongs to the StreamX - let dma = unsafe { &*I::ptr() }; - dma.st[Self::NUMBER].cr.modify(|_, w| w.htie().bit(half_transfer_interrupt)); - } - #[inline(always)] pub fn set_direct_mode_error_interrupt_enable(&mut self, direct_mode_error_interrupt: bool) { //NOTE(unsafe) We only access the registers that belongs to the StreamX diff --git a/src/dma/mod.rs b/src/dma/mod.rs index dd45a054..53f04455 100644 --- a/src/dma/mod.rs +++ b/src/dma/mod.rs @@ -421,32 +421,6 @@ where transfer } - /// Starts the transfer, the closure will be executed right after enabling - /// the stream. - pub fn start(&mut self, f: F) - where - F: FnOnce(&mut PERIPHERAL), - { - // Preserve the instruction and bus ordering of preceding buffer access - // to the subsequent access by the DMA peripheral due to enabling it. - fence(Ordering::SeqCst); - - unsafe { - self.stream.enable(); - } - f(&mut self.peripheral); - } - - /// Pauses the dma stream, the closure will be executed right before - /// disabling the stream. - pub fn pause(&mut self, f: F) - where - F: FnOnce(&mut PERIPHERAL), - { - f(&mut self.peripheral); - self.stream.disable() - } - /// Changes the buffer and restarts or continues a transfer. /// The closure is called with the old completed buffer as arguments and /// must return `(BUF, T)` where `BUF` is the new buffer @@ -595,6 +569,52 @@ where Ok((buf, current, last_remaining)) } + /// Clear half transfer interrupt (htif) for the DMA stream. + #[inline(always)] + pub fn clear_half_transfer_interrupt(&mut self) { + self.stream.clear_half_transfer_interrupt(); + } + + #[inline(always)] + pub fn get_half_transfer_flag(&self) -> bool { + STREAM::get_half_transfer_flag() + } +} + +impl + Transfer +where + STREAM: Stream, + DIR: Direction, + PERIPHERAL: TargetAddress, + BUF: StaticWriteBuffer>::MemSize>, +{ + /// Starts the transfer, the closure will be executed right after enabling + /// the stream. + pub fn start(&mut self, f: F) + where + F: FnOnce(&mut PERIPHERAL), + { + // Preserve the instruction and bus ordering of preceding buffer access + // to the subsequent access by the DMA peripheral due to enabling it. + fence(Ordering::SeqCst); + + unsafe { + self.stream.enable(); + } + f(&mut self.peripheral); + } + + /// Pauses the dma stream, the closure will be executed right before + /// disabling the stream. + pub fn pause(&mut self, f: F) + where + F: FnOnce(&mut PERIPHERAL), + { + f(&mut self.peripheral); + self.stream.disable() + } + /// Stops the stream and returns the underlying resources. pub fn free(mut self) -> (STREAM, PERIPHERAL, BUF, Option) { self.stream.disable(); @@ -621,12 +641,6 @@ where self.stream.clear_interrupts(); } - /// Clear half transfer interrupt (htif) for the DMA stream. - #[inline(always)] - pub fn clear_half_transfer_interrupt(&mut self) { - self.stream.clear_half_transfer_interrupt(); - } - /// Clear transfer complete interrupt (tcif) for the DMA stream. #[inline(always)] pub fn clear_transfer_complete_interrupt(&mut self) { @@ -650,11 +664,6 @@ where &mut self.stream } - #[inline(always)] - pub fn get_half_transfer_flag(&self) -> bool { - STREAM::get_half_transfer_flag() - } - #[inline(always)] pub fn get_transfer_complete_flag(&self) -> bool { STREAM::get_transfer_complete_flag() diff --git a/src/dma/traits.rs b/src/dma/traits.rs index 47c82dba..611a0e26 100644 --- a/src/dma/traits.rs +++ b/src/dma/traits.rs @@ -32,18 +32,12 @@ pub trait Stream: Sealed { /// Clear all interrupts for the DMA stream. fn clear_interrupts(&mut self); - /// Clear half transfer interrupt (htif) for the DMA stream. - fn clear_half_transfer_interrupt(&mut self); - /// Clear transfer complete interrupt (tcif) for the DMA stream. fn clear_transfer_complete_interrupt(&mut self); /// Clear transfer error interrupt (teif) for the DMA stream. fn clear_transfer_error_interrupt(&mut self); - /// Get half transfer flag. - fn get_half_transfer_flag() -> bool; - /// Get transfer complete flag. fn get_transfer_complete_flag() -> bool; @@ -80,12 +74,6 @@ pub trait Stream: Sealed { /// Get the value of all the interrupts for this DMA stream fn get_interrupts_enable() -> Self::Interrupts; - /// Enable/disable the half transfer interrupt (htie) of the DMA stream. - fn set_half_transfer_interrupt_enable( - &mut self, - transfer_complete_interrupt: bool, - ); - /// Enable/disable the transfer complete interrupt (tcie) of the DMA stream. fn set_transfer_complete_interrupt_enable( &mut self, @@ -111,6 +99,18 @@ pub trait DoubleBufferedStream: Stream + Sealed { value: usize, ); + /// Enable/disable the half transfer interrupt (htie) of the DMA stream. + fn set_half_transfer_interrupt_enable( + &mut self, + transfer_complete_interrupt: bool, + ); + + /// Clear half transfer interrupt (htif) for the DMA stream. + fn clear_half_transfer_interrupt(&mut self); + + /// Get half transfer flag. + fn get_half_transfer_flag() -> bool; + /// Get the memory address (m0ar or m1ar) for the DMA stream. fn get_memory_address(&self, buffer: CurrentBuffer) -> usize; From b40f13e5b4112d1ef700b5f3869d959957a79c2d Mon Sep 17 00:00:00 2001 From: Richard Meadows <962920+richardeoin@users.noreply.github.com> Date: Thu, 7 Jan 2021 23:54:04 +0100 Subject: [PATCH 40/41] Implement DMA for constant source buffers The lack of support for constant source buffers was first raised by @ryan-summers here https://github.com/stm32-rs/stm32h7xx-hal/pull/153#discussion_r515865869 This solution adds a 5th generic type parameter to `Transfer`, and uses this to implement transfer for two different type constraints on `BUF`. The additional generic type parameter will also be useful for MDMA support, which will also need its own versions of `init`, `next_transfer`, `next_transfer_with` and so on. The downside is the additional complexity of another type parameter --- examples/dma.rs | 15 +- examples/i2c4_bdma.rs | 2 +- examples/sai_dma_passthru.rs | 5 +- examples/spi-dma-rtic.rs | 2 + examples/spi-dma.rs | 2 +- src/dma/mod.rs | 610 +++++++++++++++++++---------------- 6 files changed, 339 insertions(+), 297 deletions(-) diff --git a/examples/dma.rs b/examples/dma.rs index 9d16be7f..7805d40f 100644 --- a/examples/dma.rs +++ b/examples/dma.rs @@ -81,13 +81,14 @@ fn main() -> ! { .peripheral_increment(true) // source mem .fifo_enable(true); - let mut transfer: Transfer<_, _, MemoryToMemory, _> = Transfer::init( - streams.4, - MemoryToMemory::new(), - unsafe { mem::transmute(&mut TARGET_BUFFER) }, // Uninitialised memory - Some(source_buffer), - config, - ); + let mut transfer: Transfer<_, _, MemoryToMemory, _, _> = + Transfer::init( + streams.4, + MemoryToMemory::new(), + unsafe { mem::transmute(&mut TARGET_BUFFER) }, // Uninitialised memory + Some(source_buffer), + config, + ); transfer.start(|_| {}); diff --git a/examples/i2c4_bdma.rs b/examples/i2c4_bdma.rs index 5b311a7d..74e807af 100644 --- a/examples/i2c4_bdma.rs +++ b/examples/i2c4_bdma.rs @@ -90,7 +90,7 @@ fn main() -> ! { let config = BdmaConfig::default().memory_increment(true); // We need to specify the direction with a type annotation - let mut transfer: Transfer<_, _, PeripheralToMemory, _> = Transfer::init( + let mut transfer: Transfer<_, _, PeripheralToMemory, _, _> = Transfer::init( streams.0, i2c, unsafe { &mut BUFFER }, // uninitialised memory diff --git a/examples/sai_dma_passthru.rs b/examples/sai_dma_passthru.rs index 0fa6442a..e7fca9ba 100644 --- a/examples/sai_dma_passthru.rs +++ b/examples/sai_dma_passthru.rs @@ -96,7 +96,7 @@ fn main() -> ! { .peripheral_increment(false) .circular_buffer(true) .fifo_enable(false); - let mut dma1_str0: dma::Transfer<_, _, dma::MemoryToPeripheral, _> = + let mut dma1_str0: dma::Transfer<_, _, dma::MemoryToPeripheral, _, _> = dma::Transfer::init( dma1_streams.0, unsafe { pac::Peripherals::steal().SAI1 }, @@ -111,7 +111,7 @@ fn main() -> ! { let dma_config = dma_config .transfer_complete_interrupt(true) .half_transfer_interrupt(true); - let mut dma1_str1: dma::Transfer<_, _, dma::PeripheralToMemory, _> = + let mut dma1_str1: dma::Transfer<_, _, dma::PeripheralToMemory, _, _> = dma::Transfer::init( dma1_streams.1, unsafe { pac::Peripherals::steal().SAI1 }, @@ -177,6 +177,7 @@ fn main() -> ! { stm32::SAI1, dma::PeripheralToMemory, &'static mut [u32; 128], + dma::DBTransfer, >; static mut TRANSFER_DMA1_STR1: Option = None; diff --git a/examples/spi-dma-rtic.rs b/examples/spi-dma-rtic.rs index 78df10a4..8705a673 100644 --- a/examples/spi-dma-rtic.rs +++ b/examples/spi-dma-rtic.rs @@ -37,6 +37,7 @@ const APP: () = { hal::spi::Spi, hal::dma::MemoryToPeripheral, &'static mut [u8; BUFFER_SIZE], + hal::dma::DBTransfer, >, cs: hal::gpio::gpiob::PB12>, } @@ -124,6 +125,7 @@ const APP: () = { _, hal::dma::MemoryToPeripheral, _, + _, > = hal::dma::Transfer::init(streams.1, spi, buffer, None, config); init::LateResources { transfer, cs } diff --git a/examples/spi-dma.rs b/examples/spi-dma.rs index e7538430..4abf3334 100644 --- a/examples/spi-dma.rs +++ b/examples/spi-dma.rs @@ -115,7 +115,7 @@ fn main() -> ! { let config = DmaConfig::default().memory_increment(true); - let mut transfer: Transfer<_, _, MemoryToPeripheral, _> = + let mut transfer: Transfer<_, _, MemoryToPeripheral, _, _> = Transfer::init(streams.0, spi, &mut short_buffer[..], None, config); transfer.start(|spi| { diff --git a/src/dma/mod.rs b/src/dma/mod.rs index 53f04455..cdead2e7 100644 --- a/src/dma/mod.rs +++ b/src/dma/mod.rs @@ -24,7 +24,7 @@ use core::{ ptr, sync::atomic::{fence, Ordering}, }; -use embedded_dma::StaticWriteBuffer; +use embedded_dma::{StaticReadBuffer, StaticWriteBuffer}; #[macro_use] mod macros; @@ -264,330 +264,369 @@ pub mod config { } } +/// Marker type for a transfer with a mutable source and backed by a +/// `DoubleBufferedStream` +pub struct DBTransfer; +/// Marker type for a transfer with a constant source and backed by a +/// `DoubleBufferedStream` +pub struct ConstDBTransfer; +/// Marker type for a transfer with a mutable source and backed by a +/// `MasterStream` +pub struct MasterTransfer; +/// Marker type for a transfer with a constant source and backed by a +/// `MasterStream` +pub struct ConstMasterTransfer; + /// DMA Transfer. -pub struct Transfer +pub struct Transfer where STREAM: Stream, PERIPHERAL: TargetAddress, DIR: Direction, - BUF: StaticWriteBuffer>::MemSize>, { stream: STREAM, peripheral: PERIPHERAL, _direction: PhantomData, + _transfer_type: PhantomData, buf: [Option; 2], // Used when double buffering transfer_length: u16, } -impl - Transfer -where - STREAM: DoubleBufferedStream + Stream, - CONFIG: DoubleBufferedConfig, - DIR: Direction, - PERIPHERAL: TargetAddress, - BUF: StaticWriteBuffer>::MemSize>, -{ - /// Applies all fields in DmaConfig. - fn apply_config(&mut self, config: CONFIG) { - let msize = - mem::size_of::<>::MemSize>() / 2; - - self.stream.clear_interrupts(); - - // NOTE(unsafe) These values are correct because of the invariants of TargetAddress - unsafe { - self.stream.set_memory_size(msize as u8); - self.stream.set_peripheral_size(msize as u8); - } - - self.stream.apply_config(config); - } - - /// Configures the DMA source and destination and applies supplied - /// configuration. In a memory to memory transfer, the `double_buf` argument - /// is the source of the data. If double buffering is enabled, the number of - /// transfers will be the minimum length of `memory` and `double_buf`. - /// - /// # Panics - /// - /// * When the FIFO is disabled or double buffering is enabled in - /// `DmaConfig` while initializing a memory to memory transfer. - /// * When double buffering is enabled but the `double_buf` argument is - /// `None`. - /// * When the transfer length is greater than (2^16 - 1) - pub fn init( - mut stream: STREAM, - peripheral: PERIPHERAL, - mut memory: BUF, - mut double_buf: Option, - config: CONFIG, - ) -> Self { - stream.disable(); - - // Set peripheral to memory mode - stream.set_direction(DIR::direction()); - - // Enable bufferable transfers - #[cfg(not(feature = "rm0455"))] - if PERIPHERAL::TRBUFF { - stream.set_trbuff(true); - } - - // NOTE(unsafe) We now own this buffer and we won't call any &mut - // methods on it until the end of the DMA transfer - let (buf_ptr, buf_len) = unsafe { memory.write_buffer() }; - - // Set the memory address - // - // # Safety - // - // Must be a valid memory address - unsafe { - stream.set_memory_address(CurrentBuffer::Buffer0, buf_ptr as usize); - } +macro_rules! db_transfer_def { + ($Marker:ty, $init:ident, $Buffer:tt, $rw_buffer:ident $(, $mut:tt)*; + $($constraint:stmt)*) => { + impl + Transfer + where + STREAM: DoubleBufferedStream + Stream, + CONFIG: DoubleBufferedConfig, + DIR: Direction, + PERIPHERAL: TargetAddress, + BUF: $Buffer>::MemSize>, + { + /// Applies all fields in DmaConfig. + fn apply_config(&mut self, config: CONFIG) { + let msize = mem::size_of::< + >::MemSize, + >() / 2; + + self.stream.clear_interrupts(); + + // NOTE(unsafe) These values are correct because of the + // invariants of TargetAddress + unsafe { + self.stream.set_memory_size(msize as u8); + self.stream.set_peripheral_size(msize as u8); + } - let is_mem2mem = DIR::direction() == DmaDirection::MemoryToMemory; - if is_mem2mem { - // Fifo must be enabled for memory to memory - if !config.is_fifo_enabled() { - panic!("Fifo disabled."); - } else if config.is_double_buffered() { - panic!("Double buffering enabled."); + self.stream.apply_config(config); } - } else { - // Set the peripheral address - // - // # Safety - // - // Must be a valid peripheral address - unsafe { - stream.set_peripheral_address(peripheral.address()); - } - } - let db_len = if let Some(ref mut db) = double_buf { - // NOTE(unsafe) We now own this buffer and we won't call any &mut - // methods on it until the end of the DMA transfer + /// Configures the DMA source and destination and applies supplied + /// configuration. In a memory to memory transfer, the `double_buf` argument + /// is the source of the data. If double buffering is enabled, the number of + /// transfers will be the minimum length of `memory` and `double_buf`. + /// + /// # Panics + /// + /// * When the FIFO is disabled or double buffering is enabled in + /// `DmaConfig` while initializing a memory to memory transfer. + /// * When double buffering is enabled but the `double_buf` argument is + /// `None`. + /// * When the transfer length is greater than (2^16 - 1) + pub fn $init( + mut stream: STREAM, + peripheral: PERIPHERAL, + $($mut)* memory: BUF, + mut double_buf: Option, + config: CONFIG, + ) -> Self { + stream.disable(); + + // Used in the case that we can constant `memory` + $($constraint)* + + // Set peripheral to memory mode + stream.set_direction(DIR::direction()); + + // Enable bufferable transfers + #[cfg(not(feature = "rm0455"))] + if PERIPHERAL::TRBUFF { + stream.set_trbuff(true); + } - let (db_ptr, db_len) = unsafe { db.write_buffer() }; - unsafe { - if is_mem2mem { - // Double buffer is the source in mem2mem mode - stream.set_peripheral_address(db_ptr as usize); - } else { + // NOTE(unsafe) We now own this buffer and we won't call any &mut + // methods on it until the end of the DMA transfer + let (buf_ptr, buf_len) = unsafe { memory.$rw_buffer() }; + + // Set the memory address + // + // # Safety + // + // Must be a valid memory address + unsafe { stream.set_memory_address( - CurrentBuffer::Buffer1, - db_ptr as usize, + CurrentBuffer::Buffer0, + buf_ptr as usize, ); } - } - Some(db_len) - } else { - if config.is_double_buffered() { - // Error if we expected a double buffer but none was specified - panic!("No second buffer."); - } - None - }; - let n_transfers = if let Some(db) = db_len { - buf_len.min(db) - } else { - buf_len - }; - assert!( - n_transfers <= 65535, - "Hardware does not support more than 65535 transfers" - ); - let n_transfers = n_transfers as u16; - stream.set_number_of_transfers(n_transfers); - - // Set the DMAMUX request line if needed - if let Some(request_line) = PERIPHERAL::REQUEST_LINE { - stream.set_request_line(request_line); - } - - let mut transfer = Self { - stream, - peripheral, - _direction: PhantomData, - buf: [Some(memory), double_buf], - transfer_length: n_transfers, - }; - transfer.apply_config(config); + let is_mem2mem = + DIR::direction() == DmaDirection::MemoryToMemory; + if is_mem2mem { + // Fifo must be enabled for memory to memory + if !config.is_fifo_enabled() { + panic!("Fifo disabled."); + } else if config.is_double_buffered() { + panic!("Double buffering enabled."); + } + } else { + // Set the peripheral address + // + // # Safety + // + // Must be a valid peripheral address + unsafe { + stream.set_peripheral_address(peripheral.address()); + } + } - transfer - } + let db_len = if let Some(ref mut db) = double_buf { + // NOTE(unsafe) We now own this buffer and we won't call any &mut + // methods on it until the end of the DMA transfer + + let (db_ptr, db_len) = unsafe { db.$rw_buffer() }; + unsafe { + if is_mem2mem { + // Double buffer is the source in mem2mem mode + stream.set_peripheral_address(db_ptr as usize); + } else { + stream.set_memory_address( + CurrentBuffer::Buffer1, + db_ptr as usize, + ); + } + } + Some(db_len) + } else { + if config.is_double_buffered() { + // Error if we expected a double buffer but none was specified + panic!("No second buffer."); + } + None + }; + + let n_transfers = if let Some(db) = db_len { + buf_len.min(db) + } else { + buf_len + }; + assert!( + n_transfers <= 65535, + "Hardware does not support more than 65535 transfers" + ); + let n_transfers = n_transfers as u16; + stream.set_number_of_transfers(n_transfers); + + // Set the DMAMUX request line if needed + if let Some(request_line) = PERIPHERAL::REQUEST_LINE { + stream.set_request_line(request_line); + } - /// Changes the buffer and restarts or continues a transfer. - /// The closure is called with the old completed buffer as arguments and - /// must return `(BUF, T)` where `BUF` is the new buffer - /// to be used. - /// - /// In normal mode (not double buffer mode): - /// * This method restarts the transfer. - /// * This method can be called before the end of an ongoing transfer. - /// In that case, the current transfer will be canceled and a new one - /// will be started. - /// - /// In double buffer mode: - /// * This method continues a running transfer and exchanges the inactive - /// buffer with the closure. - /// * This must be called immediately after a transfer complete - /// event to ensure no repeated transfers into/out of the same buffer. - /// * A `NotReady` error will be returned if this method is called - /// before a transfer is completed and the closure won't be executed. - /// * A `SmallBuffer` error will be returned if the size of the buffer - /// returned by the closure does not match the current transfer size. - /// * The DMA may overrun and access the poison address causing a bus error - /// and disabling of the stream if any of following conditions happen: - /// * `SmallBuffer` error - /// * The closure `f` takes too long to return - /// * If the buffer address poisoning itself fails because the DMA has overrun, - /// the closure will still be called and the buffer address is updated but - /// the DMA stream will error (TEIF) and disable itself. - /// - /// A `remaining` parameter is also passed to the closure. This indicates - /// the number of transfers not completed in the previous DMA transfer. - pub fn next_transfer_with(&mut self, func: F) -> Result - where - F: FnOnce(BUF, CurrentBuffer, usize) -> (BUF, T), - { - let (single_buffer, inactive) = match STREAM::get_inactive_buffer() { - None => { - // Single buffer mode - self.stream.disable(); - (true, CurrentBuffer::Buffer0) + let mut transfer = Self { + stream, + peripheral, + _direction: PhantomData, + _transfer_type: PhantomData, + buf: [Some(memory), double_buf], + transfer_length: n_transfers, + }; + transfer.apply_config(config); + + transfer } - Some(inactive) => { - // Double buffer mode - if !STREAM::get_transfer_complete_flag() { - // DMA has not released a buffer - return Err(DMAError::NotReady); + + /// Changes the buffer and restarts or continues a transfer. + /// The closure is called with the old completed buffer as arguments and + /// must return `(BUF, T)` where `BUF` is the new buffer + /// to be used. + /// + /// In normal mode (not double buffer mode): + /// * This method restarts the transfer. + /// * This method can be called before the end of an ongoing transfer. + /// In that case, the current transfer will be canceled and a new one + /// will be started. + /// + /// In double buffer mode: + /// * This method continues a running transfer and exchanges the inactive + /// buffer with the closure. + /// * This must be called immediately after a transfer complete + /// event to ensure no repeated transfers into/out of the same buffer. + /// * A `NotReady` error will be returned if this method is called + /// before a transfer is completed and the closure won't be executed. + /// * A `SmallBuffer` error will be returned if the size of the buffer + /// returned by the closure does not match the current transfer size. + /// * The DMA may overrun and access the poison address causing a bus error + /// and disabling of the stream if any of following conditions happen: + /// * `SmallBuffer` error + /// * The closure `f` takes too long to return + /// * If the buffer address poisoning itself fails because the DMA has overrun, + /// the closure will still be called and the buffer address is updated but + /// the DMA stream will error (TEIF) and disable itself. + /// + /// A `remaining` parameter is also passed to the closure. This indicates + /// the number of transfers not completed in the previous DMA transfer. + pub fn next_transfer_with( + &mut self, + func: F, + ) -> Result + where + F: FnOnce(BUF, CurrentBuffer, usize) -> (BUF, T), + { + let (single_buffer, inactive) = + match STREAM::get_inactive_buffer() { + None => { + // Single buffer mode + self.stream.disable(); + (true, CurrentBuffer::Buffer0) + } + Some(inactive) => { + // Double buffer mode + if !STREAM::get_transfer_complete_flag() { + // DMA has not released a buffer + return Err(DMAError::NotReady); + } + // Poison the peripheral's inactive memory address to get a memory + // error instead of potentially silent corruption. + // If DMA wins the race (overrun) to the inactive buffer + // between reading the CT bit and poisoning the inactive address, this + // write will fail and lead to a transfer error (TEIF) and disable + // the stream. + // If DMA wins the race by the time we write the new valid address + // (below), it gets a bus error and errors/stops. + unsafe { + self.stream.set_memory_address( + inactive, + 0xffff_ffffusize, + ); + } + (false, inactive) + } + }; + + // Protect the instruction sequence of preceding DMA disable/inactivity + // verification/poisoning and subsequent (old completed) buffer content + // access. + // Cortex-M7: Also protect the corresponding data access sequence. + // NOTE: The data cache also needs to be flushed (if enabled). + fence(Ordering::SeqCst); + + // Check how many data in the transfer are remaining. + let remaining_data = STREAM::get_number_of_transfers(); + + // This buffer is inactive now and can be accessed. + // NOTE(panic): We always hold ownership in lieu of the DMA peripheral. + let buf = self.buf[inactive as usize].take().unwrap(); + + let ($($mut)* buf, result) = + func(buf, inactive, remaining_data as usize); + + // NOTE(unsafe) We now own this buffer and we won't access it + // until the end of the DMA transfer. + let (buf_ptr, buf_len) = unsafe { buf.$rw_buffer() }; + + // Keep ownership of the active buffer in lieu of the DMA peripheral. + self.buf[inactive as usize].replace(buf); + + // Protect the instruction sequence of preceding (new) buffer content access + // and subsequent DMA enable/address update. See the matching fence() above. + fence(Ordering::SeqCst); + + if single_buffer { + // Set length before the writing the new valid address. + self.stream.set_number_of_transfers(buf_len as u16); + } else if buf_len != usize::from(self.transfer_length) { + // We can't change the transfer length while double buffering + return Err(DMAError::SmallBuffer); } - // Poison the peripheral's inactive memory address to get a memory - // error instead of potentially silent corruption. - // If DMA wins the race (overrun) to the inactive buffer - // between reading the CT bit and poisoning the inactive address, this - // write will fail and lead to a transfer error (TEIF) and disable - // the stream. - // If DMA wins the race by the time we write the new valid address - // (below), it gets a bus error and errors/stops. + + // NOTE(double buffer mode): + // Up to here, if the DMA starts accessing the poisoned inactive buffer (overrun) + // this will lead to a bus error and disable DMA. + // This write can not fail since the DMA is not accessing the corresponding buffer + // yet or has hit the poison address and errored disabling itself. unsafe { - self.stream.set_memory_address(inactive, 0xffff_ffffusize); + self.stream.set_memory_address(inactive, buf_ptr as usize); } - (false, inactive) - } - }; - - // Protect the instruction sequence of preceding DMA disable/inactivity - // verification/poisoning and subsequent (old completed) buffer content - // access. - // Cortex-M7: Also protect the corresponding data access sequence. - // NOTE: The data cache also needs to be flushed (if enabled). - fence(Ordering::SeqCst); - - // Check how many data in the transfer are remaining. - let remaining_data = STREAM::get_number_of_transfers(); - // This buffer is inactive now and can be accessed. - // NOTE(panic): We always hold ownership in lieu of the DMA peripheral. - let buf = self.buf[inactive as usize].take().unwrap(); + // Acknowledge the TCIF. + self.stream.clear_transfer_complete_interrupt(); - let (mut buf, result) = func(buf, inactive, remaining_data as usize); - - // NOTE(unsafe) We now own this buffer and we won't access it - // until the end of the DMA transfer. - let (buf_ptr, buf_len) = unsafe { buf.write_buffer() }; - - // Keep ownership of the active buffer in lieu of the DMA peripheral. - self.buf[inactive as usize].replace(buf); - - // Protect the instruction sequence of preceding (new) buffer content access - // and subsequent DMA enable/address update. See the matching fence() above. - fence(Ordering::SeqCst); + if single_buffer { + unsafe { + self.stream.enable(); + } + } - if single_buffer { - // Set length before the writing the new valid address. - self.stream.set_number_of_transfers(buf_len as u16); - } else if buf_len != usize::from(self.transfer_length) { - // We can't change the transfer length while double buffering - return Err(DMAError::SmallBuffer); - } + Ok(result) + } - // NOTE(double buffer mode): - // Up to here, if the DMA starts accessing the poisoned inactive buffer (overrun) - // this will lead to a bus error and disable DMA. - // This write can not fail since the DMA is not accessing the corresponding buffer - // yet or has hit the poison address and errored disabling itself. - unsafe { - self.stream.set_memory_address(inactive, buf_ptr as usize); - } + /// Changes the buffer and restarts or continues a double buffer + /// transfer. This must be called immediately after a transfer complete + /// event. Returns the old buffer together with its `CurrentBuffer`. If an + /// error occurs, this method will return the old or new buffer with the error. + /// + /// This method will clear the transfer complete flag. Moreover, if + /// an overrun occurs, the stream will be disabled and the transfer error + /// flag will be set. This method can be called before the end of an ongoing + /// transfer only if not using double buffering, in that case, the current + /// transfer will be canceled and a new one will be started. A `NotReady` + /// error together with the new buffer will be returned if this method is called + /// before the end of a transfer while double buffering. A `SmallBuffer` error + /// together with the old buffer will be returned if the new buffer size does not + /// match the ongoing transfer size. + pub fn next_transfer( + &mut self, + new_buf: BUF, + ) -> Result<(BUF, CurrentBuffer, usize), DMAError> { + let mut buf = new_buf; + let mut last_remaining = 0usize; + + let current = + self.next_transfer_with(|mut old, current, remaining| { + core::mem::swap(&mut old, &mut buf); + last_remaining = remaining; + (old, current) + })?; + // TODO: return buf on Err + Ok((buf, current, last_remaining)) + } - // Acknowledge the TCIF. - self.stream.clear_transfer_complete_interrupt(); + /// Clear half transfer interrupt (htif) for the DMA stream. + #[inline(always)] + pub fn clear_half_transfer_interrupt(&mut self) { + self.stream.clear_half_transfer_interrupt(); + } - if single_buffer { - unsafe { - self.stream.enable(); + #[inline(always)] + pub fn get_half_transfer_flag(&self) -> bool { + STREAM::get_half_transfer_flag() } } - - Ok(result) - } - - /// Changes the buffer and restarts or continues a double buffer - /// transfer. This must be called immediately after a transfer complete - /// event. Returns the old buffer together with its `CurrentBuffer`. If an - /// error occurs, this method will return the old or new buffer with the error. - /// - /// This method will clear the transfer complete flag. Moreover, if - /// an overrun occurs, the stream will be disabled and the transfer error - /// flag will be set. This method can be called before the end of an ongoing - /// transfer only if not using double buffering, in that case, the current - /// transfer will be canceled and a new one will be started. A `NotReady` - /// error together with the new buffer will be returned if this method is called - /// before the end of a transfer while double buffering. A `SmallBuffer` error - /// together with the old buffer will be returned if the new buffer size does not - /// match the ongoing transfer size. - pub fn next_transfer( - &mut self, - new_buf: BUF, - ) -> Result<(BUF, CurrentBuffer, usize), DMAError> { - let mut buf = new_buf; - let mut last_remaining = 0usize; - - let current = - self.next_transfer_with(|mut old, current, remaining| { - core::mem::swap(&mut old, &mut buf); - last_remaining = remaining; - (old, current) - })?; - // TODO: return buf on Err - Ok((buf, current, last_remaining)) - } - - /// Clear half transfer interrupt (htif) for the DMA stream. - #[inline(always)] - pub fn clear_half_transfer_interrupt(&mut self) { - self.stream.clear_half_transfer_interrupt(); - } - - #[inline(always)] - pub fn get_half_transfer_flag(&self) -> bool { - STREAM::get_half_transfer_flag() - } + }; } -impl - Transfer +db_transfer_def!(DBTransfer, init, StaticWriteBuffer, write_buffer, mut;); +db_transfer_def!(ConstDBTransfer, init_const, StaticReadBuffer, read_buffer; + assert!(DIR::direction() != DmaDirection::PeripheralToMemory)); + +impl + Transfer where STREAM: Stream, DIR: Direction, PERIPHERAL: TargetAddress, - BUF: StaticWriteBuffer>::MemSize>, { /// Starts the transfer, the closure will be executed right after enabling /// the stream. @@ -670,13 +709,12 @@ where } } -impl Drop - for Transfer +impl Drop + for Transfer where STREAM: Stream, PERIPHERAL: TargetAddress, DIR: Direction, - BUF: StaticWriteBuffer>::MemSize>, { fn drop(&mut self) { self.stream.disable(); From 2809fefadaa36c13bc2264548e7ca263873aa014 Mon Sep 17 00:00:00 2001 From: Richard Meadows <962920+richardeoin@users.noreply.github.com> Date: Fri, 8 Jan 2021 00:09:10 +0100 Subject: [PATCH 41/41] Add allow for clippy type_complexity with 5th type parameter --- examples/spi-dma-rtic.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/spi-dma-rtic.rs b/examples/spi-dma-rtic.rs index 8705a673..a497edd4 100644 --- a/examples/spi-dma-rtic.rs +++ b/examples/spi-dma-rtic.rs @@ -3,6 +3,7 @@ //! //! This example demonstrates using DMA to write data over a TX-only SPI interface. #![deny(warnings)] +#![allow(clippy::type_complexity)] #![no_main] #![no_std]