Skip to content

SPI DMA + Futures #66

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 47 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
f5cb2a7
gpdma: add GPDMA driver
astapleton Feb 3, 2025
c65d457
Fixes, simpler dma example
astapleton Jul 31, 2025
69eab2a
more fixes
astapleton Jul 31, 2025
5d9fc22
missed some defmt derives
astapleton Jul 31, 2025
6f3aa81
use ReadBuffer, WriteBuffer to force immovable buffers; fix example
astapleton Aug 1, 2025
95e3f78
fix example again
astapleton Aug 1, 2025
793a9d5
doc clarification
astapleton Aug 2, 2025
336a8c4
Use mutable reference to channels when creating transfers
astapleton Aug 2, 2025
9bbaca7
fix data width check; remove incomplete documentation
astapleton Aug 2, 2025
5c587f3
Hold onto source/destination; Remove UB from example
astapleton Aug 4, 2025
cd32400
mute type complexity clippy
astapleton Aug 4, 2025
5b0d9be
rename fields
astapleton Aug 4, 2025
100bf1b
Add free method, don't consume self in abort/wait_for_transfer_complete
astapleton Aug 5, 2025
950f62b
gpdma: implement futures and IRQ handling for GPDMA
astapleton Jul 29, 2025
e7eaa08
gpdma: make wakers accessible from DmaChannelImpl
astapleton Jul 30, 2025
6f820e9
gpdma: make wakers accessible from DmaChannelImpl
astapleton Jul 30, 2025
2416eec
gpdma: clear event flags & clean up api
astapleton Jul 30, 2025
2ccef29
gpdma: refactoring channels
astapleton Jul 31, 2025
36701a2
gpdma: fix futures
astapleton Aug 5, 2025
2a3463e
spi: Enable SPI DMA transfers
astapleton Feb 3, 2025
4a8e515
Fix dma enable
astapleton Feb 4, 2025
c82bb0a
comments
astapleton Feb 4, 2025
6221d3b
Fix buffer reference creation
astapleton Feb 4, 2025
55dc527
Use references for all DMA transfers, add write_dma method
astapleton Feb 4, 2025
d61e240
Add TxDma/RxDma maintenance operations
astapleton Feb 4, 2025
44c497a
make inplace type public
astapleton Feb 5, 2025
b8d54a9
use &'static mut
astapleton Feb 10, 2025
e55a30e
Use TSIZE for DMA transactions
astapleton Feb 22, 2025
87f2b8c
Fixes; improvements for starting transactions
astapleton Jul 17, 2025
75e7383
spi: use renamed/reworked DMA structures
astapleton Jul 18, 2025
a53f27b
spi: rework DMA - remove transfer structs, return DmaTransfer values …
astapleton Jul 21, 2025
b1a39a3
spi: implement async HAL and transform from Spi to SpiDma and back
astapleton Jul 21, 2025
eb6b4e7
Separate DmaTransfer from version that supports async
astapleton Jul 21, 2025
fc86436
gpdma + spi: move generic DMA structs to gpdma::periph
astapleton Jul 21, 2025
1babc09
spi: add future for waiting for a transaction to complete after DMA t…
astapleton Jul 24, 2025
11c5978
spi: use futures-rs AtomicWaker, and use join! macro to combine tx, r…
astapleton Jul 24, 2025
f65edab
spi: fix SPI DMA example
astapleton Jul 24, 2025
39e21d0
spi: update to use DmaChannel generic
astapleton Jul 25, 2025
8c197ed
spi: add RTIC async example
astapleton Jul 25, 2025
d1b0e4d
spi: add logging, start spi transfer task
astapleton Jul 28, 2025
1280509
spi: fix bindings to futures
astapleton Jul 30, 2025
890a925
gpdma: enable interrupts in into_future; enable interrupts in example
astapleton Jul 30, 2025
2da1dc7
spi: update spi-dma demo to use latest APIs
astapleton Jul 30, 2025
9779052
spi: enable SPI interrupts for end of transfer detection + cleanup
astapleton Jul 30, 2025
6792867
require static lifetimes on spi-dma buffers
astapleton Aug 1, 2025
209cb62
spi: remove Rx/Tx periph traits, use channels directly
astapleton Aug 2, 2025
c88387f
spi: Use new periph API, fix unsoundness in example
astapleton Aug 5, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@ stm32h562 = ["stm32h5/stm32h562", "device-selected", "rm0481", "h56x_h573"]
stm32h563 = ["stm32h5/stm32h563", "device-selected", "rm0481", "h56x_h573", "sdmmc2", "ethernet"]
stm32h573 = ["stm32h5/stm32h573", "device-selected", "rm0481", "h56x_h573", "otfdec", "sdmmc2", "ethernet"]

# Flags for async APIs
futures = ["dep:futures-util"]
gpdma-futures = ["futures"]
async = ["gpdma-futures"]

# Flags for examples
log = ["dep:log"]
log-itm = ["log"]
Expand All @@ -66,10 +71,13 @@ defmt = [
cortex-m = { version = "^0.7.7", features = ["critical-section-single-core"] }
stm32h5 = { package = "stm32h5", version = "0.16.0" }
fugit = "0.3.7"
embedded-dma = "0.2"
embedded-hal = "1.0.0"
embedded-hal-async = "1.0.0"
defmt = { version = "1.0.0", optional = true }
paste = "1.0.15"
log = { version = "0.4.20", optional = true}
futures-util = { version = "0.3", default-features = false, features = ["async-await-macro"], optional = true}
stm32-usbd = "0.8.0"

[dev-dependencies]
Expand All @@ -86,6 +94,8 @@ cortex-m-semihosting = "0.5.0"
panic-itm = { version = "~0.4.1" }
panic-probe = "0.3.2"
panic-semihosting = "0.6"
rtic = { version = "2.2", features = ["thumbv8main-backend"] }
rtic-monotonics = { version = "2.0", features = ["cortex-m-systick"]}
usbd-serial = "0.2.2"
usb-device = { version = "0.3.2", features = ["defmt", "log"] }

Expand Down
124 changes: 124 additions & 0 deletions examples/dma.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// #![deny(warnings)]
#![no_main]
#![no_std]

mod utilities;

use cortex_m::singleton;
use cortex_m_rt::entry;
use cortex_m_semihosting::debug;
use stm32h5xx_hal::{
gpdma::{config::transform::*, DmaConfig, DmaTransfer},
pac,
prelude::*,
};

#[entry]
fn main() -> ! {
utilities::logger::init();

let dp = pac::Peripherals::take().unwrap();

let pwr = dp.PWR.constrain();
let pwrcfg = pwr.vos0().freeze();

// Constrain and Freeze clock
let rcc = dp.RCC.constrain();
let ccdr = rcc.sys_ck(250.MHz()).freeze(pwrcfg, &dp.SBS);

let channels = dp.GPDMA1.channels(ccdr.peripheral.GPDMA1);

log::info!("u8 to u8");
let src =
singleton!(: [u8; 40] = core::array::from_fn(|i| i as u8)).unwrap();

let dest = singleton!(: [u8; 40] = [0u8; 40]).unwrap();

let mut channel = channels.0;
let config = DmaConfig::new();
let mut transfer =
DmaTransfer::memory_to_memory(config, &mut channel, src, dest);
transfer.start().unwrap();
transfer.wait_for_transfer_complete().unwrap();
let (src, dest) = transfer.free();
assert_eq!(src, dest);

log::info!("u32 to u32 with data transform");
let src = singleton!(: [u32; 10] = [0x12345678u32; 10]).unwrap();
let dest = singleton!(: [u32; 10] = [0u32; 10]).unwrap();

let config = DmaConfig::new().with_data_transform(
DataTransform::builder()
.swap_destination_half_words()
.swap_destination_half_word_byte_order(),
);

let mut transfer =
DmaTransfer::memory_to_memory(config, &mut channel, src, dest);

transfer.start().unwrap();
transfer.wait_for_transfer_complete().unwrap();
let (_, dest) = transfer.free();

let expected = [0x78563412; 10];
assert_eq!(expected, *dest);

log::info!("u32 to u16 with truncate");
let src = singleton!(: [u32; 10] = [0x12345678u32; 10]).unwrap();
let dest = singleton!(: [u16; 20] = [0u16; 20]).unwrap();

let config = DmaConfig::new().with_data_transform(
DataTransform::builder().left_align_right_truncate(),
);
let mut transfer =
DmaTransfer::memory_to_memory(config, &mut channel, src, dest);

transfer.start().unwrap();
transfer.wait_for_transfer_complete().unwrap();
let (_, dest) = transfer.free();

let expected = [0x1234; 10];
assert_eq!(expected, (*dest)[0..10]);

log::info!("u32 to u8 with unpack");
let src = singleton!(: [u32; 10] = [0x12345678u32; 10]).unwrap();
let dest = singleton!(: [u8; 40] = [0u8; 40]).unwrap();

let config =
DmaConfig::new().with_data_transform(DataTransform::builder().unpack());
let mut transfer =
DmaTransfer::memory_to_memory(config, &mut channel, src, dest);

transfer.start().unwrap();
transfer.wait_for_transfer_complete().unwrap();
let (_, dest) = transfer.free();
let expected = [0x78, 0x56, 0x34, 0x12];
assert_eq!(expected, (*dest)[0..4]);
assert_eq!(expected, (*dest)[36..40]);

log::info!("u8 to u32 with pack");
let src = singleton!(: [u8; 40] = [0u8; 40]).unwrap();
let dest = singleton!(: [u32; 10] = [0u32; 10]).unwrap();

for chunk in src.chunks_mut(4) {
chunk.copy_from_slice(&[0x78, 0x56, 0x34, 0x12]);
}

let config =
DmaConfig::new().with_data_transform(DataTransform::builder().pack());
let mut transfer =
DmaTransfer::memory_to_memory(config, &mut channel, src, dest);

transfer.start().unwrap();
transfer.wait_for_transfer_complete().unwrap();
let (_, dest) = transfer.free();

let expected = [0x12345678; 10];
assert_eq!(expected, *dest);
assert_eq!(expected, *dest);

log::info!("All tests passed!");
loop {
debug::exit(debug::EXIT_SUCCESS)
}
}
177 changes: 177 additions & 0 deletions examples/spi-async-rtic.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
#![no_main]
#![no_std]

mod utilities;

use embedded_dma::{ReadBuffer, WriteBuffer};
use rtic::app;
use stm32h5xx_hal::{
gpdma::{periph::DmaDuplex, DmaChannel0, DmaChannel1, Word},
gpio::{Output, PA5},
pac,
prelude::*,
spi::{self, dma::SpiDma, Config as SpiConfig},
};

use rtic_monotonics::systick::prelude::*;
systick_monotonic!(Mono, 1000);

// Buffer is used to manage a reference to a static buffer returned by the cortex_m::singleton!
// macro and which can be with the DmaTransfer API (which requires passing ReadBuffer and
// WriteBuffer implementations by value) and then used to access the buffer after the transfer has
// completed.
struct Buffer<T: Word + 'static, const N: usize> {
data: &'static mut [T; N],
}

impl<T, const N: usize> Buffer<T, N>
where
T: Word + 'static,
{
fn new(data: &'static mut [T; N]) -> Self {
Self { data }
}
}

unsafe impl<T, const N: usize> ReadBuffer for &mut Buffer<T, N>
where
T: Word + 'static,
{
type Word = T;

unsafe fn read_buffer(&self) -> (*const Self::Word, usize) {
(self.data.as_ptr(), N)
}
}

unsafe impl<T, const N: usize> WriteBuffer for &mut Buffer<T, N>
where
T: Word + 'static,
{
type Word = T;

unsafe fn write_buffer(&mut self) -> (*mut Self::Word, usize) {
(self.data.as_mut_ptr(), N)
}
}

#[app(device = pac, dispatchers = [USART1, USART2], peripherals = true)]
mod app {

use core::cell::Cell;

use cortex_m::singleton;
use stm32h5::stm32h503::{GPDMA1, NVIC};

use super::*;

#[shared]
struct Shared {}

#[local]
struct Local {
led: PA5<Output>,
spi: SpiDma<
pac::SPI2,
DmaDuplex<pac::SPI2, u8, DmaChannel0<GPDMA1>, DmaChannel1<GPDMA1>>,
>,
source: Cell<Buffer<u8, 40>>,
dest: Cell<Buffer<u8, 40>>,
}

#[init]
fn init(ctx: init::Context) -> (Shared, Local) {
utilities::logger::init();

let pwr = ctx.device.PWR.constrain();
let pwrcfg = pwr.vos0().freeze();

let rcc = ctx.device.RCC.constrain();
let ccdr = rcc
.sys_ck(192.MHz())
.pll1_q_ck(64.MHz())
.freeze(pwrcfg, &ctx.device.SBS);

log::info!("Starting RTIC SPI example...");

// Uncomment if use SysTick as monotonic timer
Mono::start(ctx.core.SYST, ccdr.clocks.sysclk().raw());

let gpioa = ctx.device.GPIOA.split(ccdr.peripheral.GPIOA);
let gpiob = ctx.device.GPIOB.split(ccdr.peripheral.GPIOB);

let led = gpioa.pa5.into_push_pull_output();

let sck = gpiob.pb13.into_alternate();
let miso = gpiob.pb14.into_alternate();
let mosi = gpiob.pb15.into_alternate();

let channels = ctx.device.GPDMA1.channels(ccdr.peripheral.GPDMA1);
let tx_ch = channels.0;
let rx_ch = channels.1;

let spi = ctx
.device
.SPI2
.spi(
(sck, miso, mosi),
SpiConfig::new(spi::MODE_0),
1.MHz(),
ccdr.peripheral.SPI2,
&ccdr.clocks,
)
.use_dma_duplex(tx_ch, rx_ch);

unsafe {
NVIC::unmask(pac::interrupt::GPDMA1_CH0);
NVIC::unmask(pac::interrupt::GPDMA1_CH1);
};

let src = singleton!(: [u8; 40] = [0; 40]).unwrap();
let dest = singleton!(: [u8; 40] = [0; 40]).unwrap();

tick::spawn().unwrap();
spi_transfer::spawn().unwrap();
(
Shared {},
Local {
led,
spi,
source: Cell::new(Buffer::new(src)),
dest: Cell::new(Buffer::new(dest)),
},
)
}

#[task(local = [led, count: u32 = 0], priority = 1)]
async fn tick(ctx: tick::Context) {
loop {
ctx.local.led.toggle();
*ctx.local.count += 1;
log::info!("Tick {}", *ctx.local.count);
Mono::delay(1000.millis()).await;
}
}

#[task(local = [spi, count: u32 = 0, source, dest], priority = 2)]
async fn spi_transfer(ctx: spi_transfer::Context) {
loop {
log::info!("Starting SPI transfer");

let src = ctx.local.source.get_mut();
let dest = ctx.local.dest.get_mut();
src.data.fill(*ctx.local.count as u8);
dest.data.fill(0);

*ctx.local.count += 1;
ctx.local.spi.transfer_dma(src, dest).await.unwrap();

assert_eq!(
*ctx.local.source.get_mut().data,
*ctx.local.dest.get_mut().data
);
log::info!("Success!");
Mono::delay(1000.millis()).await;
}
}
}
Loading
Loading