Skip to content

Re-introduced possibility of DMA peripheral abstraction, and added SPI DMA abstraction example #292

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

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/).
## [Unreleased]

### Added

- Reintroduced the "Transferable" trait to make it possible to pass an abstract reference for any SPI DMA.
- LSB/MSB bit format selection for `SPI`

### Fixed
Expand Down
101 changes: 101 additions & 0 deletions examples/spi-dma-abstract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#![no_std]
#![no_main]

/**
Transmits data over an SPI port using DMA
*/
use panic_halt as _;

use cortex_m::singleton;
use cortex_m_rt::entry;
use stm32f1xx_hal::{
dma::{Transfer, Transferable, WriteDma, R},
pac,
prelude::*,
spi::{Mode, Phase, Polarity, Spi, SpiTxDma},
};

// Set size of data buffer to transmit via SPI DMA
const DATA_BUFFER_SIZE: usize = 8;

#[entry]
fn main() -> ! {
// Get access to the device specific peripherals from the peripheral access crate
let dp = pac::Peripherals::take().unwrap();

// Take ownership over the raw flash and rcc devices and convert them into the corresponding
// HAL structs
let mut flash = dp.FLASH.constrain();
let mut rcc = dp.RCC.constrain();

// Freeze the configuration of all the clocks in the system and store the frozen frequencies in
// `clocks`
let clocks = rcc.cfgr.freeze(&mut flash.acr);

// Acquire the GPIOA peripheral
let mut gpiob = dp.GPIOB.split(&mut rcc.apb2);

let pins = (
gpiob.pb13.into_alternate_push_pull(&mut gpiob.crh),
gpiob.pb14.into_floating_input(&mut gpiob.crh),
gpiob.pb15.into_alternate_push_pull(&mut gpiob.crh),
);

let spi_mode = Mode {
polarity: Polarity::IdleLow,
phase: Phase::CaptureOnFirstTransition,
};
let spi = Spi::spi2(dp.SPI2, pins, spi_mode, 100.khz(), clocks, &mut rcc.apb1);

// Set up the DMA device
let dma = dp.DMA1.split(&mut rcc.ahb);

// Connect the SPI device to the DMA
let mut spi_dma = spi.with_tx_dma(dma.5);

// Create a mutable data buffer, as we need to write data into it
let mut data_buffer: &'static mut [u8] =
singleton!(: [u8; DATA_BUFFER_SIZE] = [0; DATA_BUFFER_SIZE]).unwrap();

// Use a byte counter to generate some data for our buffer
let mut data_byte = 0u8;

// Do SPI transmission in an endless loop
loop {
// Fill the buffer with some data
for ix in 0..DATA_BUFFER_SIZE {
// Increase by 1, and insure it wraps
data_byte = data_byte.wrapping_add(1u8);

// Put the byte into the buffer
data_buffer[ix] = data_byte;
}

// Call write function
let transfer = write_spi_dma(spi_dma, data_buffer);

// Wait for transfer to complete
let (ret_buffer, ret_spidma) = transfer.wait();

// Return ownership, so we can re-use these the next iteration
data_buffer = ret_buffer;
spi_dma = ret_spidma;
}
}

/// The writing is done is a separate function, as this is typically how a driver would work
/// The Driver will take ownership of the SPI DMA for the duration of the Transfer Operation.
/// When complete, the wait() function returns the ownership of the buffer and SPI DMA, which
/// makes it usabe for something else. This way, one SPI can be shared among multiple drivers.
fn write_spi_dma<SPI, REMAP, PINS, CHANNEL>(
spi_dma: SpiTxDma<SPI, REMAP, PINS, CHANNEL>,
buffer: &'static mut [u8],
) -> Transfer<R, &'static mut [u8], SpiTxDma<SPI, REMAP, PINS, CHANNEL>>
where
SpiTxDma<SPI, REMAP, PINS, CHANNEL>: WriteDma<&'static mut [u8], u8>,
Transfer<R, &'static mut [u8], SpiTxDma<SPI, REMAP, PINS, CHANNEL>>:
Transferable<&'static mut [u8], SpiTxDma<SPI, REMAP, PINS, CHANNEL>>,
{
// Simply call write, and return the Transfer instance
spi_dma.write(buffer)
}
32 changes: 25 additions & 7 deletions src/dma.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,24 @@ pub trait TransferPayload {
fn stop(&mut self);
}

/// Trait implemented by `Transfer` to provide access to the DMA Transfer Operation.
/// The Trait defines methods for checking if the operation is complete, or waiting for
/// the operation to complete. The wait function is also used to return ownership of the
/// transmission buffer and `TransferPayload` (An RxDma or TxDma wrapping a peripheral).
pub trait Transferable<BUFFER, DMA> {
/// Checks if a Transmission is complete
fn is_done(&self) -> bool;

/// Wait for transmission to complete
///
/// When the transmission is complete, the ownership of the buffer and the "Payload"
/// (the instance that started the Transfer) is returned to the caller.
///
/// # Returns
/// - Tuple containing the Transmitted buffer and the Payload (the instance initiating the transfer)
fn wait(self) -> (BUFFER, DMA);
}

pub struct Transfer<MODE, BUFFER, PAYLOAD>
where
PAYLOAD: TransferPayload,
Expand Down Expand Up @@ -128,7 +146,7 @@ macro_rules! dma {

use crate::pac::{$DMAX, dma1};

use crate::dma::{CircBuffer, DmaExt, Error, Event, Half, Transfer, W, RxDma, TxDma, TransferPayload};
use crate::dma::{CircBuffer, DmaExt, Error, Event, Half, Transfer, W, RxDma, TxDma, Transferable, TransferPayload};
use crate::rcc::{AHB, Enable};

#[allow(clippy::manual_non_exhaustive)]
Expand Down Expand Up @@ -295,15 +313,15 @@ macro_rules! dma {
}
}

impl<BUFFER, PAYLOAD, MODE> Transfer<MODE, BUFFER, RxDma<PAYLOAD, $CX>>
impl<BUFFER, PAYLOAD, MODE> Transferable<BUFFER, RxDma<PAYLOAD, $CX>> for Transfer<MODE, BUFFER, RxDma<PAYLOAD, $CX>>
where
RxDma<PAYLOAD, $CX>: TransferPayload,
{
pub fn is_done(&self) -> bool {
fn is_done(&self) -> bool {
!self.payload.channel.in_progress()
}

pub fn wait(mut self) -> (BUFFER, RxDma<PAYLOAD, $CX>) {
fn wait(mut self) -> (BUFFER, RxDma<PAYLOAD, $CX>) {
while !self.is_done() {}

atomic::compiler_fence(Ordering::Acquire);
Expand Down Expand Up @@ -333,15 +351,15 @@ macro_rules! dma {
}
}

impl<BUFFER, PAYLOAD, MODE> Transfer<MODE, BUFFER, TxDma<PAYLOAD, $CX>>
impl<BUFFER, PAYLOAD, MODE> Transferable<BUFFER, TxDma<PAYLOAD, $CX>> for Transfer<MODE, BUFFER, TxDma<PAYLOAD, $CX>>
where
TxDma<PAYLOAD, $CX>: TransferPayload,
{
pub fn is_done(&self) -> bool {
fn is_done(&self) -> bool {
!self.payload.channel.in_progress()
}

pub fn wait(mut self) -> (BUFFER, TxDma<PAYLOAD, $CX>) {
fn wait(mut self) -> (BUFFER, TxDma<PAYLOAD, $CX>) {
while !self.is_done() {}

atomic::compiler_fence(Ordering::Acquire);
Expand Down
1 change: 1 addition & 0 deletions src/prelude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ pub use crate::crc::CrcExt as _stm32_hal_crc_CrcExt;
pub use crate::dma::CircReadDma as _stm32_hal_dma_CircReadDma;
pub use crate::dma::DmaExt as _stm32_hal_dma_DmaExt;
pub use crate::dma::ReadDma as _stm32_hal_dma_ReadDma;
pub use crate::dma::Transferable as _stm32_hal_dma_Transferable;
pub use crate::dma::WriteDma as _stm32_hal_dma_WriteDma;
pub use crate::flash::FlashExt as _stm32_hal_flash_FlashExt;
pub use crate::gpio::GpioExt as _stm32_hal_gpio_GpioExt;
Expand Down
6 changes: 5 additions & 1 deletion src/spi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
To construct the SPI instances, use the `Spi::spiX` functions.

The pin parameter is a tuple containing `(sck, miso, mosi)` which should be configured as `(Alternate<PushPull>, Input<Floating>, Alternate<PushPull>)`.
As some STM32F1xx chips have 5V tolerant SPI pins, it is also possible to configure Sck and Mosi outputs as `Alternate<PushPull>`. Then
a simple Pull-Up to 5V can be used to use SPI on a 5V bus without a level shifter.

You can also use `NoSck`, `NoMiso` or `NoMosi` if you don't want to use the pins

Expand Down Expand Up @@ -48,7 +50,7 @@ use crate::gpio::gpioa::{PA5, PA6, PA7};
use crate::gpio::gpiob::{PB13, PB14, PB15, PB3, PB4, PB5};
#[cfg(feature = "connectivity")]
use crate::gpio::gpioc::{PC10, PC11, PC12};
use crate::gpio::{Alternate, Floating, Input, PushPull};
use crate::gpio::{Alternate, Floating, Input, OpenDrain, PushPull};
use crate::rcc::{Clocks, Enable, GetBusFreq, Reset, APB1, APB2};
use crate::time::Hertz;

Expand Down Expand Up @@ -144,8 +146,10 @@ macro_rules! remap {
const REMAP: bool = $state;
}
impl Sck<$name> for $SCK<Alternate<PushPull>> {}
impl Sck<$name> for $SCK<Alternate<OpenDrain>> {}
impl Miso<$name> for $MISO<Input<Floating>> {}
impl Mosi<$name> for $MOSI<Alternate<PushPull>> {}
impl Mosi<$name> for $MOSI<Alternate<OpenDrain>> {}
};
}

Expand Down