Skip to content

Commit

Permalink
Merge #153
Browse files Browse the repository at this point in the history
153: Implement DMA based on embedded-dma traits r=richardeoin a=richardeoin

DMA implementation based on the [embedded-dma](https://docs.rs/embedded-dma/0.1.2/embedded_dma/) traits and the existing implementation in the [stm32f4xx-hal](https://github.com/stm32-rs/stm32f4xx-hal/tree/master/src/dma). ~Currently some parts are very similar - the `Stream` trait and its implementation are completely identical similar, and much of `dma/mod.rs` is similar. However it remains to be seen how much will stay the same when implementing the BDMA and/or MDMA.~ However some changes are required since we'd like to support the BDMA and MDMA also.

Implementation / examples for:
- [x] Memory-to-Memory
- [x] SPI TX
- [x] I2C RX

TODO:
- [x] BDMA implementation
- [ ] Confirm that owning the target peripheral is the right API design
- [x] ~Do the compiler fences also need matching synchronisation~ Memory barriers for the Cortex-M7
- [x] Add an example using RTIC - Thanks ryan-summers!

Contributions to this branch are very welcome, especially implementations and examples for other peripherals.

Ref #80 

Co-authored-by: Richard Meadows <962920+richardeoin@users.noreply.github.com>
Co-authored-by: Antoine van Gelder <antoine@flowdsp.io>
Co-authored-by: Richard Meadows <richardeoin@gmail.com>
Co-authored-by: Ryan Summers <ryan.summers@vertigo-designs.com>
Co-authored-by: Robert Jördens <rj@quartiq.de>
  • Loading branch information
6 people authored Feb 13, 2021
2 parents 5471426 + 2b8a04c commit 7d81332
Show file tree
Hide file tree
Showing 18 changed files with 3,619 additions and 582 deletions.
19 changes: 18 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -172,4 +173,20 @@ required-features = ["rt", "rtc"]

[[example]]
name = "sai-i2s-passthru"
required-features = ["rm0433"]
required-features = ["rm0433"]

[[example]]
name = "sai_dma_passthru"
required-features = ["rm0433"]

[[example]]
name = "dma"
required-features = ["rm0433"]

[[example]]
name = "spi-dma"
required-features = ["rm0433"]

[[example]]
name = "spi-dma-rtic"
required-features = ["rm0433","rt"]
108 changes: 108 additions & 0 deletions examples/dma.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
//! Example of Memory to Memory Transfer with the DMA
#![allow(clippy::transmute_ptr_to_ptr)]
#![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::{
dma::{DmaConfig, 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<u32>; 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;

// 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<u32>, _, _> =
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() {}

// 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 {}
}
133 changes: 133 additions & 0 deletions examples/i2c4_bdma.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
//! 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::{BdmaConfig, StreamsTuple},
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
#[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,
unsafe { &mut BUFFER }, // uninitialised memory
None,
config,
);

transfer.start(|i2c| {
// This closure runs right after enabling the stream

// 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
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 {}
}
Loading

0 comments on commit 7d81332

Please sign in to comment.