diff --git a/CHANGELOG.md b/CHANGELOG.md index 5c986fe0..0b9119ee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/examples/spi-dma-abstract.rs b/examples/spi-dma-abstract.rs new file mode 100644 index 00000000..b1b84f4d --- /dev/null +++ b/examples/spi-dma-abstract.rs @@ -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_dma: SpiTxDma, + buffer: &'static mut [u8], +) -> Transfer> +where + SpiTxDma: WriteDma<&'static mut [u8], u8>, + Transfer>: + Transferable<&'static mut [u8], SpiTxDma>, +{ + // Simply call write, and return the Transfer instance + spi_dma.write(buffer) +} diff --git a/src/dma.rs b/src/dma.rs index b999fc28..845b6150 100644 --- a/src/dma.rs +++ b/src/dma.rs @@ -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 { + /// 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 where PAYLOAD: TransferPayload, @@ -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)] @@ -295,15 +313,15 @@ macro_rules! dma { } } - impl Transfer> + impl Transferable> for Transfer> where RxDma: TransferPayload, { - pub fn is_done(&self) -> bool { + fn is_done(&self) -> bool { !self.payload.channel.in_progress() } - pub fn wait(mut self) -> (BUFFER, RxDma) { + fn wait(mut self) -> (BUFFER, RxDma) { while !self.is_done() {} atomic::compiler_fence(Ordering::Acquire); @@ -333,15 +351,15 @@ macro_rules! dma { } } - impl Transfer> + impl Transferable> for Transfer> where TxDma: TransferPayload, { - pub fn is_done(&self) -> bool { + fn is_done(&self) -> bool { !self.payload.channel.in_progress() } - pub fn wait(mut self) -> (BUFFER, TxDma) { + fn wait(mut self) -> (BUFFER, TxDma) { while !self.is_done() {} atomic::compiler_fence(Ordering::Acquire); diff --git a/src/prelude.rs b/src/prelude.rs index ff349473..2cdc5aa1 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -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; diff --git a/src/spi.rs b/src/spi.rs index 2fa14cca..70014d07 100644 --- a/src/spi.rs +++ b/src/spi.rs @@ -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, Input, Alternate)`. + As some STM32F1xx chips have 5V tolerant SPI pins, it is also possible to configure Sck and Mosi outputs as `Alternate`. 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 @@ -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; @@ -144,8 +146,10 @@ macro_rules! remap { const REMAP: bool = $state; } impl Sck<$name> for $SCK> {} + impl Sck<$name> for $SCK> {} impl Miso<$name> for $MISO> {} impl Mosi<$name> for $MOSI> {} + impl Mosi<$name> for $MOSI> {} }; }