Skip to content

Commit ce6d8b5

Browse files
Merge #186
186: Add initial support for MDMA r=richardeoin a=richardeoin TODO: - [x] Support for memory-memory transfers - [x] Example of memory-memory transfers - _examples/mdma.rs_ - [x] Support for memory-peripheral transfers - [x] Example of memory-peripheral transfers - _examples/qspi_mdma.rs_ - [x] Support for configurable AXI burst sizes - _examples/mdma_bursts.rs_ - [ ] ~Support for different source/destination types for memory-memory transfers~ Co-authored-by: Richard Meadows <962920+richardeoin@users.noreply.github.com>
2 parents fb66f6b + a3eadb5 commit ce6d8b5

File tree

8 files changed

+2517
-48
lines changed

8 files changed

+2517
-48
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,10 @@ required-features = ["fmc"]
143143
name = "qspi"
144144
required-features = ["quadspi", "rm0433"]
145145

146+
[[example]]
147+
name = "qspi_mdma"
148+
required-features = ["quadspi", "rm0433"]
149+
146150
[[example]]
147151
name = "sdmmc"
148152
required-features = ["sdmmc", "rm0433"]

examples/mdma.rs

Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
//! Example of Memory to Memory Transfer with the Master DMA (MDMA)
2+
3+
#![allow(clippy::transmute_ptr_to_ptr)]
4+
#![deny(warnings)]
5+
#![no_main]
6+
#![no_std]
7+
8+
use core::{mem, mem::MaybeUninit};
9+
10+
use cortex_m_rt::entry;
11+
#[macro_use]
12+
mod utilities;
13+
use stm32h7xx_hal::{pac, prelude::*};
14+
15+
use stm32h7xx_hal::dma::{
16+
mdma::{MdmaConfig, MdmaIncrement, MdmaSize, StreamsTuple},
17+
traits::Direction,
18+
MemoryToMemory, Transfer,
19+
};
20+
21+
use log::info;
22+
23+
// The MDMA can interact with SRAM banks as well as the TCM. For this example,
24+
// we place the source in the AXI SRAM.
25+
//
26+
// The runtime does not initialise AXI SRAM banks.
27+
#[link_section = ".axisram.buffers"]
28+
static mut SOURCE_BUFFER: MaybeUninit<[u32; 200]> = MaybeUninit::uninit();
29+
30+
#[entry]
31+
fn main() -> ! {
32+
utilities::logger::init();
33+
let dp = pac::Peripherals::take().unwrap();
34+
35+
// Constrain and Freeze power
36+
info!("Setup PWR... ");
37+
let pwr = dp.PWR.constrain();
38+
let pwrcfg = example_power!(pwr).freeze();
39+
40+
// Constrain and Freeze clock
41+
info!("Setup RCC... ");
42+
let rcc = dp.RCC.constrain();
43+
let ccdr = rcc
44+
.sys_ck(100.mhz())
45+
.hclk(50.mhz())
46+
.freeze(pwrcfg, &dp.SYSCFG);
47+
48+
info!("");
49+
info!("stm32h7xx-hal example - Memory to TCM with Master DMA");
50+
info!("");
51+
52+
// Initialise the source buffer without taking any references to
53+
// uninitialisated memory
54+
let source_buffer: &'static mut [u32; 200] = {
55+
let buf: &mut [MaybeUninit<u32>; 200] =
56+
unsafe { mem::transmute(&mut SOURCE_BUFFER) };
57+
58+
for value in buf.iter_mut() {
59+
unsafe {
60+
value.as_mut_ptr().write(0x11223344u32);
61+
}
62+
}
63+
unsafe { mem::transmute(buf) }
64+
};
65+
66+
//
67+
// Example 1: Memory to TCM
68+
//
69+
70+
// Target buffer on the stack
71+
let mut target_buffer: [u32; 200] = [0; 200];
72+
73+
// Setup DMA
74+
let streams = StreamsTuple::new(dp.MDMA, ccdr.peripheral.MDMA);
75+
76+
let config = MdmaConfig::default()
77+
// increment by one element per element
78+
.destination_increment(MdmaIncrement::Increment)
79+
.source_increment(MdmaIncrement::Increment);
80+
81+
let mut transfer: Transfer<_, _, MemoryToMemory<u32>, _, _> = {
82+
// Extend the lifetime of our data on the stack. We assert that it lives
83+
// as long as this transfer
84+
let target: &'static mut [u32; 200] =
85+
unsafe { mem::transmute(&mut target_buffer) };
86+
87+
Transfer::init_master(
88+
streams.0,
89+
MemoryToMemory::new(),
90+
target, // Dest: TCM (stack)
91+
Some(source_buffer), // Source: AXISRAM
92+
config,
93+
)
94+
};
95+
96+
// Block of 800 bytes, MDMA checks other streams every 128 bytes
97+
assert_eq!(transfer.get_block_length(), 800);
98+
assert_eq!(transfer.get_buffer_length(), 128);
99+
100+
// Start block
101+
transfer.start(|_| {});
102+
103+
// Wait for transfer to complete
104+
while !transfer.get_transfer_complete_flag() {}
105+
106+
// Decompose the stream to get the source buffer back
107+
let (stream, _mem2mem, _target, source_buffer_opt) = transfer.free();
108+
let source_buffer = source_buffer_opt.unwrap();
109+
110+
for a in target_buffer.iter() {
111+
assert_eq!(*a, 0x11223344);
112+
}
113+
114+
info!("Example 1: Memory to TCM DMA completed successfully");
115+
116+
//
117+
// Example 2: Memory to TCM with endianess and offset
118+
//
119+
120+
// Reset source buffer
121+
*source_buffer = [0xAABBCCDD; 200];
122+
123+
// New target buffer on the stack
124+
let mut target_buffer: [u32; 20] = [0; 20];
125+
126+
let config = MdmaConfig::default()
127+
.source_increment(MdmaIncrement::Increment)
128+
.destination_increment(MdmaIncrement::IncrementWithOffset(
129+
MdmaSize::DoubleWord,
130+
))
131+
.half_word_endianness_exchange(true);
132+
133+
let mut transfer: Transfer<_, _, MemoryToMemory<u32>, _, _> = {
134+
let target: &'static mut [u32; 20] =
135+
unsafe { mem::transmute(&mut target_buffer) };
136+
137+
// Note that our source and destination buffers now have different types
138+
// (they are arrays with different lengths). We pass slices instead, and
139+
// the HAL takes the length into account.
140+
141+
Transfer::init_master(
142+
stream,
143+
MemoryToMemory::new(),
144+
&mut target[..], // Dest: TCM (stack)
145+
Some(&mut source_buffer[..]), // Source: AXISRAM
146+
config,
147+
)
148+
};
149+
150+
// Block length is limited to the minimum number of bytes that are valid for
151+
// both buffers. For this configuration, it is only possible to write 40
152+
// bytes (10 words) to the target buffer before reaching the end.
153+
assert_eq!(transfer.get_block_length(), 40);
154+
155+
transfer.start(|_| {});
156+
157+
// Wait for transfer to complete
158+
while !transfer.get_transfer_complete_flag() {}
159+
160+
assert_eq!(
161+
target_buffer,
162+
[
163+
0xCCDDAABB, 0, 0xCCDDAABB, 0, 0xCCDDAABB, 0, 0xCCDDAABB, 0,
164+
0xCCDDAABB, 0, 0xCCDDAABB, 0, 0xCCDDAABB, 0, 0xCCDDAABB, 0,
165+
0xCCDDAABB, 0, 0xCCDDAABB, 0,
166+
]
167+
);
168+
169+
info!(
170+
"Example 2: Memory to TCM DMA with endianess and offset completed successfully"
171+
);
172+
173+
//
174+
// Example 3: TCM to TCM with offset
175+
//
176+
177+
let mut source_buffer_tcm = [1u8, 2];
178+
// unsafe: we must ensure source_buffer_tcm lives long enough
179+
let source_buffer: &'static mut [u8] =
180+
unsafe { mem::transmute(&mut source_buffer_tcm[..]) };
181+
let mut target_buffer = [0u8; 17];
182+
183+
let config = MdmaConfig::default().destination_increment(
184+
MdmaIncrement::IncrementWithOffset(MdmaSize::DoubleWord),
185+
);
186+
187+
let mut transfer: Transfer<_, _, MemoryToMemory<u8>, _, _> = {
188+
// Be very careful when using an unsafe transmute in the init call like
189+
// this because the target_buffer type will be transmuted to the source
190+
// type. In this case it's ok at the source_buffer is a slice [u8]. But
191+
// if both source and target types were arrays, length of the target
192+
// array would be lost.
193+
194+
Transfer::init_master(
195+
streams.1,
196+
MemoryToMemory::new(),
197+
unsafe { mem::transmute(&mut target_buffer[..]) }, // Dest: TCM (stack)
198+
Some(source_buffer), // Source: TCM (stack)
199+
config,
200+
)
201+
};
202+
203+
transfer.start(|_| {});
204+
205+
// Wait for transfer to complete
206+
while !transfer.get_transfer_complete_flag() {}
207+
208+
// Value returned by `get_block_length` decrements during the transfer,
209+
// reaching zero at the end
210+
assert_eq!(transfer.get_block_length(), 0);
211+
212+
assert_eq!(source_buffer_tcm, [1, 2]);
213+
assert_eq!(
214+
target_buffer,
215+
[1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0]
216+
);
217+
218+
info!("Example 3: TCM to TCM DMA with offset completed successfully");
219+
220+
loop {
221+
cortex_m::asm::nop()
222+
}
223+
}

0 commit comments

Comments
 (0)