From 239e790cb02079e146e43fe6bd4214d9081f1456 Mon Sep 17 00:00:00 2001 From: Leah Date: Mon, 26 Oct 2020 23:29:06 +0100 Subject: [PATCH 01/10] feat: add spi rx dma (and migrate to embedded-dma) --- Cargo.toml | 1 + examples/rtic_frame_serial_dma.rs | 11 +- examples/serial_dma.rs | 4 +- examples/serial_dma_partial_peek.rs | 3 +- examples/serial_dma_us2.rs | 4 +- src/dma.rs | 300 +++++++++++++++++++++------- src/serial.rs | 265 +++++++++++++++--------- src/spi.rs | 136 ++++++++++++- 8 files changed, 547 insertions(+), 177 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index e85916e1..2ff2edbd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,6 +24,7 @@ edition = "2018" cortex-m = "0.6.3" nb = "0.1.1" stm32l4 = "0.13.0" +embedded-dma = "0.1" [dependencies.rand_core] version = "0.6.2" diff --git a/examples/rtic_frame_serial_dma.rs b/examples/rtic_frame_serial_dma.rs index 05af75d0..0a360115 100644 --- a/examples/rtic_frame_serial_dma.rs +++ b/examples/rtic_frame_serial_dma.rs @@ -12,6 +12,7 @@ use hal::{ dma::{self, DMAFrame, FrameReader, FrameSender}, + pac::USART2, prelude::*, rcc::{ClockSecuritySystem, CrystalBypass, MsiFreq}, serial::{self, Config, Serial}, @@ -23,6 +24,8 @@ use heapless::{ use panic_halt as _; use rtic::app; use stm32l4xx_hal as hal; +use stm32l4xx_hal::dma::{RxDma, TxDma}; +use stm32l4xx_hal::serial::{Rx, Tx}; // The pool gives out `Box`s that can hold 8 bytes pool!( @@ -34,8 +37,8 @@ pool!( const APP: () = { struct Resources { rx: serial::Rx, - frame_reader: FrameReader, dma::dma1::C6, 8>, - frame_sender: FrameSender, dma::dma1::C7, 8>, + frame_reader: FrameReader, RxDma, dma::dma1::C6>, 8>, + frame_sender: FrameSender, TxDma, dma::dma1::C7>, 8>, } #[init] @@ -88,13 +91,13 @@ const APP: () = { let fr = if let Some(dma_buf) = SerialDMAPool::alloc() { // Set up the first reader frame let dma_buf = dma_buf.init(DMAFrame::new()); - serial_rx.frame_read(dma_ch6, dma_buf) + serial_rx.with_dma(dma_ch6).frame_read(dma_buf) } else { unreachable!() }; // Serial frame sender (DMA based) - let fs: FrameSender, _, 8> = serial_tx.frame_sender(dma_ch7); + let fs: FrameSender, _, 8> = serial_tx.with_dma(dma_ch7).frame_sender(); init::LateResources { rx: serial_rx, diff --git a/examples/serial_dma.rs b/examples/serial_dma.rs index 4f7bba26..e9ba8616 100644 --- a/examples/serial_dma.rs +++ b/examples/serial_dma.rs @@ -18,7 +18,7 @@ extern crate stm32l4xx_hal as hal; // #[macro_use(block)] // extern crate nb; -use crate::hal::dma::Half; +use crate::hal::dma::{CircReadDma, Half}; use crate::hal::prelude::*; use crate::hal::serial::{Config, Serial}; use crate::rt::ExceptionFrame; @@ -68,7 +68,7 @@ fn main() -> ! { let buf = singleton!(: [[u8; 8]; 2] = [[0; 8]; 2]).unwrap(); - let mut circ_buffer = rx.circ_read(channels.5, buf); + let mut circ_buffer = rx.with_dma(channels.5).circ_read(buf); for _ in 0..2 { while circ_buffer.readable_half().unwrap() != Half::First {} diff --git a/examples/serial_dma_partial_peek.rs b/examples/serial_dma_partial_peek.rs index 53b6d134..a9e5c7f5 100644 --- a/examples/serial_dma_partial_peek.rs +++ b/examples/serial_dma_partial_peek.rs @@ -20,6 +20,7 @@ extern crate stm32l4xx_hal as hal; use crate::cortex_m::asm; use crate::hal::delay::Delay; +use crate::hal::dma::CircReadDma; use crate::hal::prelude::*; use crate::hal::serial::{Config, Serial}; use crate::rt::ExceptionFrame; @@ -71,7 +72,7 @@ fn main() -> ! { let buf = singleton!(: [[u8; 8]; 2] = [[0; 8]; 2]).unwrap(); - let mut circ_buffer = rx.circ_read(channels.5, buf); + let mut circ_buffer = rx.with_dma(channels.5).circ_read(buf); // wait for 3 seconds, enter data on serial timer.delay_ms(1000_u32); diff --git a/examples/serial_dma_us2.rs b/examples/serial_dma_us2.rs index b850e78c..d067c41c 100644 --- a/examples/serial_dma_us2.rs +++ b/examples/serial_dma_us2.rs @@ -18,7 +18,7 @@ extern crate stm32l4xx_hal as hal; // #[macro_use(block)] // extern crate nb; -use crate::hal::dma::Half; +use crate::hal::dma::{CircReadDma, Half}; use crate::hal::prelude::*; use crate::hal::serial::{Config, Serial}; use crate::rt::ExceptionFrame; @@ -67,7 +67,7 @@ fn main() -> ! { let buf = singleton!(: [[u8; 8]; 2] = [[0; 8]; 2]).unwrap(); - let mut circ_buffer = rx.circ_read(channels.6, buf); + let mut circ_buffer = rx.with_dma(channels.6).circ_read(buf); for _ in 0..2 { while circ_buffer.readable_half().unwrap() != Half::First {} diff --git a/src/dma.rs b/src/dma.rs index fe1cc382..d6b833cb 100644 --- a/src/dma.rs +++ b/src/dma.rs @@ -5,11 +5,13 @@ use core::fmt; use core::marker::PhantomData; use core::mem::MaybeUninit; -use core::ops::{Deref, DerefMut}; +use core::ops::DerefMut; use core::ptr; use core::slice; +use core::sync::atomic::{compiler_fence, Ordering}; use crate::rcc::AHB1; +use embedded_dma::{StaticReadBuffer, StaticWriteBuffer}; use stable_deref_trait::StableDeref; #[non_exhaustive] @@ -31,27 +33,27 @@ pub enum Half { } /// Frame reader "worker", access and handling of frame reads is made through this structure. -pub struct FrameReader +pub struct FrameReader where BUFFER: Sized + StableDeref> + DerefMut + 'static, { buffer: BUFFER, - channel: CHANNEL, + payload: PAYLOAD, matching_character: u8, } -impl FrameReader +impl FrameReader where BUFFER: Sized + StableDeref> + DerefMut + 'static, { pub(crate) fn new( buffer: BUFFER, - channel: CHANNEL, + payload: PAYLOAD, matching_character: u8, - ) -> FrameReader { + ) -> FrameReader { Self { buffer, - channel, + payload, matching_character, } } @@ -59,22 +61,22 @@ where /// Frame sender "worker", access and handling of frame transmissions is made through this /// structure. -pub struct FrameSender +pub struct FrameSender where BUFFER: Sized + StableDeref> + DerefMut + 'static, { buffer: Option, - channel: CHANNEL, + payload: PAYLOAD, } -impl FrameSender +impl FrameSender where BUFFER: Sized + StableDeref> + DerefMut + 'static, { - pub(crate) fn new(channel: CHANNEL) -> FrameSender { + pub(crate) fn new(payload: PAYLOAD) -> FrameSender { Self { buffer: None, - channel, + payload, } } } @@ -260,24 +262,25 @@ impl AsRef<[u8]> for DMAFrame { } } -pub struct CircBuffer +pub struct CircBuffer where BUFFER: 'static, { - buffer: BUFFER, - channel: CHANNEL, + buffer: &'static mut [BUFFER; 2], + payload: PAYLOAD, readable_half: Half, consumed_offset: usize, } -impl CircBuffer { - pub(crate) fn new(buf: BUFFER, chan: CHANNEL) -> Self - where - BUFFER: StableDeref + 'static, - { +impl CircBuffer +where + &'static mut [BUFFER; 2]: StaticWriteBuffer, + BUFFER: 'static, +{ + pub(crate) fn new(buf: &'static mut [BUFFER; 2], payload: PAYLOAD) -> Self { CircBuffer { buffer: buf, - channel: chan, + payload, readable_half: Half::Second, consumed_offset: 0, } @@ -290,46 +293,53 @@ pub trait DmaExt { fn split(self, ahb: &mut AHB1) -> Self::Channels; } -pub struct Transfer { +pub trait TransferPayload { + fn start(&mut self); + fn stop(&mut self); +} + +pub struct Transfer +where + PAYLOAD: TransferPayload, +{ _mode: PhantomData, buffer: BUFFER, - channel: CHANNEL, payload: PAYLOAD, } -impl Transfer +impl Transfer where - BUFFER: StableDeref + 'static, + PAYLOAD: TransferPayload, { - pub(crate) fn r(buffer: BUFFER, channel: CHANNEL, payload: PAYLOAD) -> Self { + pub(crate) fn r(buffer: BUFFER, payload: PAYLOAD) -> Self { Transfer { _mode: PhantomData, buffer, - channel, payload, } } } -impl Transfer +impl Transfer where - BUFFER: StableDeref + 'static, + PAYLOAD: TransferPayload, { - pub(crate) fn w(buffer: BUFFER, channel: CHANNEL, payload: PAYLOAD) -> Self { + pub(crate) fn w(buffer: BUFFER, payload: PAYLOAD) -> Self { Transfer { _mode: PhantomData, buffer, - channel, payload, } } } -impl Deref for Transfer { - type Target = BUFFER; - - fn deref(&self) -> &BUFFER { - &self.buffer +impl Drop for Transfer +where + PAYLOAD: TransferPayload, +{ + fn drop(&mut self) { + self.payload.stop(); + compiler_fence(Ordering::SeqCst); } } @@ -367,13 +377,16 @@ macro_rules! dma { use core::ptr; use stable_deref_trait::StableDeref; - use crate::dma::{CircBuffer, FrameReader, FrameSender, DMAFrame, DmaExt, Error, Event, Half, Transfer, W}; + use crate::dma::{CircBuffer, FrameReader, FrameSender, DMAFrame, DmaExt, Error, Event, Half, Transfer, W, R, RxDma, TxDma, TransferPayload}; use crate::rcc::AHB1; #[allow(clippy::manual_non_exhaustive)] pub struct Channels((), $(pub $CX),+); $( + /// A singleton that represents a single DMAx channel (channel X in this case) + /// + /// This singleton has exclusive access to the registers of the DMAx channel X pub struct $CX; impl $CX { @@ -490,9 +503,10 @@ macro_rules! dma { } - impl FrameSender + impl FrameSender, N> where BUFFER: Sized + StableDeref> + DerefMut + 'static, + TxDma: TransferPayload, { /// This method should be called in the transfer complete interrupt of the /// DMA, will return the sent frame if the transfer was truly completed. @@ -501,14 +515,14 @@ macro_rules! dma { ) -> Option { // Clear ISR flag (Transfer Complete) - if !self.channel.in_progress() { - self.channel.ifcr().write(|w| w.$ctcifX().set_bit()); + if !self.payload.channel.in_progress() { + self.payload.channel.ifcr().write(|w| w.$ctcifX().set_bit()); } else { // The old transfer is not complete return None; } - self.channel.stop(); + self.payload.channel.stop(); // NOTE(compiler_fence) operations on the DMA should not be reordered // before the next statement, takes the buffer from the DMA transfer. @@ -536,20 +550,20 @@ macro_rules! dma { } let new_buf = &*frame; - self.channel.set_memory_address(new_buf.buffer_as_ptr() as u32, true); - self.channel.set_transfer_length(new_buf.len() as u16); + self.payload.channel.set_memory_address(new_buf.buffer_as_ptr() as u32, true); + self.payload.channel.set_transfer_length(new_buf.len() as u16); // If there has been an error, clear the error flag to let the next // transaction start - if self.channel.isr().$teifX().bit_is_set() { - self.channel.ifcr().write(|w| w.$cteifX().set_bit()); + if self.payload.channel.isr().$teifX().bit_is_set() { + self.payload.channel.ifcr().write(|w| w.$cteifX().set_bit()); } // NOTE(compiler_fence) operations on `buffer` should not be reordered after // the next statement, which starts the DMA transfer atomic::compiler_fence(Ordering::Release); - self.channel.start(); + self.payload.channel.start(); self.buffer = Some(frame); @@ -557,9 +571,10 @@ macro_rules! dma { } } - impl FrameReader + impl FrameReader, N> where BUFFER: Sized + StableDeref> + DerefMut + 'static, + RxDma: TransferPayload, { /// This function should be called from the transfer complete interrupt of /// the corresponding DMA channel. @@ -599,18 +614,18 @@ macro_rules! dma { new_buf.clear(); // Clear ISR flag (Transfer Complete) - if !self.channel.in_progress() { - self.channel.ifcr().write(|w| w.$ctcifX().set_bit()); + if !self.payload.channel.in_progress() { + self.payload.channel.ifcr().write(|w| w.$ctcifX().set_bit()); } else if character_match_interrupt { // 1. If DMA not done and there was a character match interrupt, // let the DMA flush a little and then halt transfer. // // This is to alleviate the race condition between the character // match interrupt and the DMA memory transfer. - let left_in_buffer = self.channel.get_cndtr() as usize; + let left_in_buffer = self.payload.channel.get_cndtr() as usize; for _ in 0..5 { - let now_left = self.channel.get_cndtr() as usize; + let now_left = self.payload.channel.get_cndtr() as usize; if left_in_buffer - now_left >= 4 { // We have gotten 4 extra characters flushed @@ -619,13 +634,13 @@ macro_rules! dma { } } - self.channel.stop(); + self.payload.channel.stop(); // NOTE(compiler_fence) operations on `buffer` should not be reordered after // the next statement, which starts a new DMA transfer atomic::compiler_fence(Ordering::SeqCst); - let left_in_buffer = self.channel.get_cndtr() as usize; + let left_in_buffer = self.payload.channel.get_cndtr() as usize; let got_data_len = old_buf.max_len() - left_in_buffer; // How many bytes we got unsafe { old_buf.set_len(got_data_len); @@ -667,29 +682,31 @@ macro_rules! dma { 0 }; - self.channel.set_memory_address(unsafe { new_buf.buffer_as_ptr().add(diff) } as u32, true); - self.channel.set_transfer_length((new_buf.max_len() - diff) as u16); + self.payload.channel.set_memory_address(unsafe { new_buf.buffer_as_ptr().add(diff) } as u32, true); + self.payload.channel.set_transfer_length((new_buf.max_len() - diff) as u16); let received_buffer = core::mem::replace(&mut self.buffer, next_frame); // NOTE(compiler_fence) operations on `buffer` should not be reordered after // the next statement, which starts the DMA transfer atomic::compiler_fence(Ordering::Release); - self.channel.start(); + self.payload.channel.start(); // 4. Return full frame received_buffer } } - impl CircBuffer { + impl CircBuffer> + where + RxDma: TransferPayload, + { /// Return the partial contents of the buffer half being written - pub fn partial_peek(&mut self, f: F) -> Result + pub fn partial_peek(&mut self, f: F) -> Result where F: FnOnce(&[T], Half) -> Result<(usize, R), ()>, - B: StableDeref + 'static, - H: AsRef<[T]>, + B: AsRef<[T]>, { // this inverts expectation and returns the half being _written_ let buf = match self.readable_half { @@ -699,7 +716,7 @@ macro_rules! dma { // ,- half-buffer // [ x x x x y y y y y z | z z z z z z z z z z ] // ^- pending=11 - let pending = self.channel.get_cndtr() as usize; // available bytes in _whole_ buffer + let pending = self.payload.channel.get_cndtr() as usize; // available bytes in _whole_ buffer let slice = buf.as_ref(); let capacity = slice.len(); // capacity of _half_ a buffer // <--- capacity=10 ---> @@ -726,11 +743,10 @@ macro_rules! dma { /// Peeks into the readable half of the buffer /// Returns the result of the closure - pub fn peek(&mut self, f: F) -> Result + pub fn peek(&mut self, f: F) -> Result where F: FnOnce(&[T], Half) -> R, - B: StableDeref + 'static, - H: AsRef<[T]>, + B: AsRef<[T]>, { let half_being_read = self.readable_half()?; let buf = match half_being_read { @@ -744,7 +760,7 @@ macro_rules! dma { /// Returns the `Half` of the buffer that can be read pub fn readable_half(&mut self) -> Result { - let isr = self.channel.isr(); + let isr = self.payload.channel.isr(); let first_half_is_done = isr.$htifX().bit_is_set(); let second_half_is_done = isr.$tcifX().bit_is_set(); @@ -757,7 +773,7 @@ macro_rules! dma { Ok(match last_read_half { Half::First => { if second_half_is_done { - self.channel.ifcr().write(|w| w.$ctcifX().set_bit()); + self.payload.channel.ifcr().write(|w| w.$ctcifX().set_bit()); self.readable_half = Half::Second; Half::Second @@ -767,7 +783,7 @@ macro_rules! dma { } Half::Second => { if first_half_is_done { - self.channel.ifcr().write(|w| w.$chtifX().set_bit()); + self.payload.channel.ifcr().write(|w| w.$chtifX().set_bit()); self.readable_half = Half::First; Half::First @@ -777,39 +793,116 @@ macro_rules! dma { } }) } + + /// Stops the transfer and returns the underlying buffer and RxDma + pub fn stop(mut self) -> (&'static mut [B; 2], RxDma) { + self.payload.stop(); + + (self.buffer, self.payload) + } } - impl Transfer { + impl Transfer> + where + RxDma: TransferPayload, + { pub fn is_done(&self) -> bool { - self.channel.isr().$tcifX().bit_is_set() + !self.payload.channel.in_progress() } - pub fn wait(mut self) -> (BUFFER, $CX, PAYLOAD) { + pub fn wait(mut self) -> (BUFFER, RxDma) { // XXX should we check for transfer errors here? // The manual says "A DMA transfer error can be generated by reading // from or writing to a reserved address space". I think it's impossible // to get to that state with our type safe API and *safe* Rust. while !self.is_done() {} - self.channel.ifcr().write(|w| w.$cgifX().set_bit()); + self.payload.stop(); - self.channel.ccr().modify(|_, w| w.en().clear_bit()); + // TODO can we weaken this compiler barrier? + // NOTE(compiler_fence) operations on `buffer` should not be reordered + // before the previous statement, which marks the DMA transfer as done + atomic::compiler_fence(Ordering::SeqCst); + + // `Transfer` needs to have a `Drop` implementation, because we accept + // managed buffers that can free their memory on drop. Because of that + // we can't move out of the `Transfer`'s fields, so we use `ptr::read` + // and `mem::forget`. + // + // NOTE(unsafe) There is no panic branch between getting the resources + // and forgetting `self`. + unsafe { + let buffer = ptr::read(&self.buffer); + let payload = ptr::read(&self.payload); + core::mem::forget(self); + (buffer, payload) + } + } + } + + impl Transfer> + where + TxDma: TransferPayload, + { + pub fn is_done(&self) -> bool { + !self.payload.channel.in_progress() + } + + pub fn wait(mut self) -> (BUFFER, TxDma) { + // XXX should we check for transfer errors here? + // The manual says "A DMA transfer error can be generated by reading + // from or writing to a reserved address space". I think it's impossible + // to get to that state with our type safe API and *safe* Rust. + while !self.is_done() {} + + self.payload.stop(); // TODO can we weaken this compiler barrier? // NOTE(compiler_fence) operations on `buffer` should not be reordered // before the previous statement, which marks the DMA transfer as done atomic::compiler_fence(Ordering::SeqCst); - (self.buffer, self.channel, self.payload) + // `Transfer` needs to have a `Drop` implementation, because we accept + // managed buffers that can free their memory on drop. Because of that + // we can't move out of the `Transfer`'s fields, so we use `ptr::read` + // and `mem::forget`. + // + // NOTE(unsafe) There is no panic branch between getting the resources + // and forgetting `self`. + unsafe { + let buffer = ptr::read(&self.buffer); + let payload = ptr::read(&self.payload); + core::mem::forget(self); + (buffer, payload) + } } } - impl Transfer { + impl Transfer> + where + RxDma: TransferPayload, + { + pub fn peek(&self) -> &[T] + where + BUFFER: AsRef<[T]>, + { + let pending = self.payload.channel.get_cndtr() as usize; + + let capacity = self.buffer.as_ref().len(); + + &self.buffer.as_ref()[..(capacity - pending)] + } + } + + impl Transfer> + where + TxDma: TransferPayload, + { pub fn peek(&self) -> &[T] where BUFFER: AsRef<[T]> { - let pending = self.channel.get_cndtr() as usize; + let pending = self.payload.channel.get_cndtr() as usize; let capacity = self.buffer.as_ref().len(); @@ -969,3 +1062,60 @@ dma! { ), }), } + +/// DMA Receiver +pub struct RxDma { + pub(crate) payload: PAYLOAD, + pub channel: RXCH, +} + +/// DMA Transmitter +pub struct TxDma { + pub(crate) payload: PAYLOAD, + pub channel: TXCH, +} + +/// DMA Receiver/Transmitter +pub struct RxTxDma { + pub(crate) payload: PAYLOAD, + pub rxchannel: RXCH, + pub txchannel: TXCH, +} + +pub trait Receive { + type RxChannel; + type TransmittedWord; +} + +pub trait Transmit { + type TxChannel; + type ReceivedWord; +} + +/// Trait for circular DMA readings from peripheral to memory. +pub trait CircReadDma: Receive +where + &'static mut [B; 2]: StaticWriteBuffer, + B: 'static, + Self: core::marker::Sized, +{ + fn circ_read(self, buffer: &'static mut [B; 2]) -> CircBuffer; +} + +/// Trait for DMA readings from peripheral to memory. +pub trait ReadDma: Receive +where + B: StaticWriteBuffer, + Self: core::marker::Sized + TransferPayload, +{ + fn read(self, buffer: B) -> Transfer; +} + +/// Trait for DMA writing from memory to peripheral. +pub trait WriteDma: Transmit +where + B: StaticReadBuffer, + Self: core::marker::Sized + TransferPayload, +{ + fn write(self, buffer: B) -> Transfer; +} diff --git a/src/serial.rs b/src/serial.rs index 4bb0a43f..96c6352a 100644 --- a/src/serial.rs +++ b/src/serial.rs @@ -7,11 +7,15 @@ use core::marker::PhantomData; use core::ops::DerefMut; use core::ptr; use core::sync::atomic::{self, Ordering}; +use embedded_dma::StaticWriteBuffer; use stable_deref_trait::StableDeref; use crate::hal::serial::{self, Write}; -use crate::dma::{dma1, CircBuffer, DMAFrame, FrameReader, FrameSender}; +use crate::dma::{ + dma1, CircBuffer, DMAFrame, FrameReader, FrameSender, Receive, RxDma, TransferPayload, + Transmit, TxDma, +}; use crate::gpio::{self, Alternate, AlternateOD, Floating, Input}; use crate::pac; use crate::rcc::{Clocks, APB1R1, APB2}; @@ -196,8 +200,8 @@ macro_rules! hal { $usartXen:ident, $usartXrst:ident, $pclkX:ident, - tx: ($dmacst:ident, $tx_chan:path), - rx: ($dmacsr:ident, $rx_chan:path) + tx: ($txdma:ident, $dmacst:ident, $dmatxch:path), + rx: ($rxdma:ident, $dmacsr:ident, $dmarxch:path) ), )+) => { $( @@ -501,40 +505,149 @@ macro_rules! hal { impl embedded_hal::blocking::serial::write::Default for Tx {} + pub type $rxdma = RxDma, $dmarxch>; + pub type $txdma = TxDma, $dmatxch>; + + impl Receive for $rxdma { + type RxChannel = $dmarxch; + type TransmittedWord = u8; + } + + impl Transmit for $txdma { + type TxChannel = $dmatxch; + type ReceivedWord = u8; + } + + impl TransferPayload for $rxdma { + fn start(&mut self) { + self.channel.start(); + } + fn stop(&mut self) { + self.channel.stop(); + } + } + + impl TransferPayload for $txdma { + fn start(&mut self) { + self.channel.start(); + } + fn stop(&mut self) { + self.channel.stop(); + } + } + impl Rx { - pub fn circ_read( - &self, - mut chan: $rx_chan, - mut buffer: B, - ) -> CircBuffer - where - B: StableDeref + DerefMut + 'static, - H: AsMut<[u8]> + pub fn with_dma(self, channel: $dmarxch) -> $rxdma { + RxDma { + payload: self, + channel, + } + } + + /// Check for, and return, any errors + /// + /// The `read` methods can only return one error at a time, but + /// there might actually be multiple errors. This method will + /// return and clear a currently active error. Once it returns + /// `Ok(())`, it should be possible to proceed with the next + /// `read` call unimpeded. + pub fn check_for_error(&mut self) -> Result<(), Error> { + // NOTE(unsafe): Only used for atomic access. + let isr = unsafe { (*pac::$USARTX::ptr()).isr.read() }; + let icr = unsafe { &(*pac::$USARTX::ptr()).icr }; + + if isr.pe().bit_is_set() { + icr.write(|w| w.pecf().clear()); + return Err(Error::Parity); + } + if isr.fe().bit_is_set() { + icr.write(|w| w.fecf().clear()); + return Err(Error::Framing); + } + if isr.nf().bit_is_set() { + icr.write(|w| w.ncf().clear()); + return Err(Error::Noise); + } + if isr.ore().bit_is_set() { + icr.write(|w| w.orecf().clear()); + return Err(Error::Overrun); + } + + Ok(()) + } + } + + impl Tx { + pub fn with_dma(self, channel: $dmatxch) -> $txdma { + TxDma { + payload: self, + channel, + } + } + } + + impl $rxdma { + pub fn split(mut self) -> (Rx, $dmarxch) { + self.stop(); + let RxDma {payload, channel} = self; + ( + payload, + channel + ) + } + } + + impl $txdma { + pub fn split(mut self) -> (Tx, $dmatxch) { + self.stop(); + let TxDma {payload, channel} = self; + ( + payload, + channel, + ) + } + } + + impl crate::dma::CircReadDma for $rxdma + where + &'static mut [B; 2]: StaticWriteBuffer, + B: 'static, + Self: core::marker::Sized, + { + fn circ_read(mut self, mut buffer: &'static mut [B; 2], + ) -> CircBuffer { - let buf = buffer[0].as_mut(); - chan.set_peripheral_address(unsafe{ &(*pac::$USARTX::ptr()).rdr as *const _ as u32 }, false); - chan.set_memory_address(buf.as_ptr() as u32, true); - chan.set_transfer_length((buf.len() * 2) as u16); + let (ptr, len) = unsafe { buffer.static_write_buffer() }; + self.channel.set_peripheral_address( + unsafe { &(*pac::$USARTX::ptr()).rdr as *const _ as u32 }, + false, + ); + self.channel.set_memory_address(ptr as u32, true); + self.channel.set_transfer_length(len as u16); // Tell DMA to request from serial - chan.cselr().modify(|_, w| { - w.$dmacsr().bits(0b0010) // TODO: Fix this, not valid for DMA2 + self.channel.cselr().modify(|_, w| { + w.$dmacsr().map2() }); - chan.ccr().modify(|_, w| unsafe { - w.mem2mem() + self.channel.ccr().modify(|_, w| { + w + // memory to memory mode disabled + .mem2mem() .clear_bit() - // 00: Low, 01: Medium, 10: High, 11: Very high + // medium channel priority level .pl() - .bits(0b01) - // 00: 8-bits, 01: 16-bits, 10: 32-bits, 11: Reserved + .medium() + // 8-bit memory size .msize() - .bits(0b00) - // 00: 8-bits, 01: 16-bits, 10: 32-bits, 11: Reserved + .bits8() + // 8-bit peripheral size .psize() - .bits(0b00) + .bits8() + // circular mode disabled .circ() .set_bit() + // write to memory .dir() .clear_bit() }); @@ -543,18 +656,19 @@ macro_rules! hal { // the next statement, which starts the DMA transfer atomic::compiler_fence(Ordering::Release); - chan.start(); + self.start(); - CircBuffer::new(buffer, chan) + CircBuffer::new(buffer, self) } + } + impl $rxdma { /// Create a frame reader that can either react on the Character match interrupt or /// Transfer Complete from the DMA. pub fn frame_read( - &self, - mut channel: $rx_chan, + mut self, buffer: BUFFER, - ) -> FrameReader + ) -> FrameReader where BUFFER: Sized + StableDeref> + DerefMut + 'static, { @@ -562,27 +676,29 @@ macro_rules! hal { // Setup DMA transfer let buf = &*buffer; - channel.set_peripheral_address(&usart.rdr as *const _ as u32, false); - channel.set_memory_address(unsafe { buf.buffer_address_for_dma() } as u32, true); - channel.set_transfer_length(buf.max_len() as u16); + self.channel.set_peripheral_address(&usart.rdr as *const _ as u32, false); + self.channel.set_memory_address(unsafe { buf.buffer_address_for_dma() } as u32, true); + self.channel.set_transfer_length(buf.max_len() as u16); // Tell DMA to request from serial - channel.cselr().modify(|_, w| { - w.$dmacsr().bits(0b0010) // TODO: Fix this, not valid for DMA2 + self.channel.cselr().modify(|_, w| { + w.$dmacsr().map2() }); - channel.ccr().modify(|_, w| unsafe { - w.mem2mem() + self.channel.ccr().modify(|_, w| { + w + // memory to memory mode disabled + .mem2mem() .clear_bit() - // 00: Low, 01: Medium, 10: High, 11: Very high + // medium channel priority level .pl() - .bits(0b01) - // 00: 8-bits, 01: 16-bits, 10: 32-bits, 11: Reserved + .medium() + // 8-bit memory size .msize() - .bits(0b00) - // 00: 8-bits, 01: 16-bits, 10: 32-bits, 11: Reserved + .bits8() + // 8-bit peripheral size .psize() - .bits(0b00) + .bits8() // Peripheral -> Mem .dir() .clear_bit() @@ -592,9 +708,9 @@ macro_rules! hal { // the next statement, which starts the DMA transfer atomic::compiler_fence(Ordering::Release); - channel.start(); + self.channel.start(); - FrameReader::new(buffer, channel, usart.cr2.read().add().bits()) + FrameReader::new(buffer, self, usart.cr2.read().add().bits()) } /// Checks to see if the USART peripheral has detected an idle line and clears @@ -644,60 +760,27 @@ macro_rules! hal { false } } - - /// Check for, and return, any errors - /// - /// The `read` methods can only return one error at a time, but - /// there might actually be multiple errors. This method will - /// return and clear a currently active error. Once it returns - /// `Ok(())`, it should be possible to proceed with the next - /// `read` call unimpeded. - pub fn check_for_error(&mut self) -> Result<(), Error> { - // NOTE(unsafe): Only used for atomic access. - let isr = unsafe { (*pac::$USARTX::ptr()).isr.read() }; - let icr = unsafe { &(*pac::$USARTX::ptr()).icr }; - - if isr.pe().bit_is_set() { - icr.write(|w| w.pecf().clear()); - return Err(Error::Parity); - } - if isr.fe().bit_is_set() { - icr.write(|w| w.fecf().clear()); - return Err(Error::Framing); - } - if isr.nf().bit_is_set() { - icr.write(|w| w.ncf().clear()); - return Err(Error::Noise); - } - if isr.ore().bit_is_set() { - icr.write(|w| w.orecf().clear()); - return Err(Error::Overrun); - } - - Ok(()) - } } - impl Tx { + impl $txdma { /// Creates a new DMA frame sender pub fn frame_sender( - &self, - mut channel: $tx_chan, - ) -> FrameSender + mut self, + ) -> FrameSender where BUFFER: Sized + StableDeref> + DerefMut + 'static, { let usart = unsafe{ &(*pac::$USARTX::ptr()) }; // Setup DMA - channel.set_peripheral_address(&usart.tdr as *const _ as u32, false); + self.channel.set_peripheral_address(&usart.tdr as *const _ as u32, false); // Tell DMA to request from serial - channel.cselr().modify(|_, w| { - w.$dmacst().bits(0b0010) // TODO: Fix this, not valid for DMA2 + self.channel.cselr().modify(|_, w| { + w.$dmacst().map2() }); - channel.ccr().modify(|_, w| unsafe { + self.channel.ccr().modify(|_, w| unsafe { w.mem2mem() .clear_bit() // 00: Low, 01: Medium, 10: High, 11: Very high @@ -714,7 +797,7 @@ macro_rules! hal { .set_bit() }); - FrameSender::new(channel) + FrameSender::new(self) } } )+ @@ -722,8 +805,8 @@ macro_rules! hal { } hal! { - USART1: (usart1, APB2, usart1en, usart1rst, pclk2, tx: (c4s, dma1::C4), rx: (c5s, dma1::C5)), - USART2: (usart2, APB1R1, usart2en, usart2rst, pclk1, tx: (c7s, dma1::C7), rx: (c6s, dma1::C6)), + USART1: (usart1, APB2, usart1en, usart1rst, pclk2, tx: (TxDma1, c4s, dma1::C4), rx: (RxDma1, c5s, dma1::C5)), + USART2: (usart2, APB1R1, usart2en, usart2rst, pclk1, tx: (TxDma2, c7s, dma1::C7), rx: (RxDma2, c6s, dma1::C6)), } #[cfg(any( @@ -733,17 +816,17 @@ hal! { feature = "stm32l4x6", ))] hal! { - USART3: (usart3, APB1R1, usart3en, usart3rst, pclk1, tx: (c2s, dma1::C2), rx: (c3s, dma1::C3)), + USART3: (usart3, APB1R1, usart3en, usart3rst, pclk1, tx: (TxDma3, c2s, dma1::C2), rx: (RxDma3, c3s, dma1::C3)), } #[cfg(any(feature = "stm32l4x5", feature = "stm32l4x6",))] hal! { - UART4: (uart4, APB1R1, uart4en, uart4rst, pclk1, tx: (c3s, dma2::C3), rx: (c5s, dma2::C5)), + UART4: (uart4, APB1R1, uart4en, uart4rst, pclk1, tx: (TxDma4, c3s, dma2::C3), rx: (RxDma4, c5s, dma2::C5)), } #[cfg(any(feature = "stm32l4x5", feature = "stm32l4x6",))] hal! { - UART5: (uart5, APB1R1, uart5en, uart5rst, pclk1, tx: (c1s, dma2::C1), rx: (c2s, dma2::C2)), + UART5: (uart5, APB1R1, uart5en, uart5rst, pclk1, tx: (TxDma5, c1s, dma2::C1), rx: (RxDma5, c2s, dma2::C2)), } impl fmt::Write for Serial diff --git a/src/spi.rs b/src/spi.rs index f00cde83..64a28e17 100644 --- a/src/spi.rs +++ b/src/spi.rs @@ -6,10 +6,12 @@ //! don't have it shouldn't attempt to use it. Relevant info is on user-manual level. use core::ptr; +use core::sync::atomic; +use core::sync::atomic::Ordering; -use crate::hal::spi::{FullDuplex, Mode, Phase, Polarity}; - +use crate::dma::{Receive, RxDma, Transfer, TransferPayload, W}; use crate::gpio::{Alternate, Floating, Input, AF5}; +use crate::hal::spi::{FullDuplex, Mode, Phase, Polarity}; use crate::rcc::{Clocks, APB1R1, APB2}; use crate::time::Hertz; @@ -366,3 +368,133 @@ pins!(SPI2, AF5, SCK: [PB13, PB10, PD1], MISO: [PB14, PC2, PD3], MOSI: [PB15, PC3, PD4]); + +pub struct SpiPayload { + spi: Spi, +} + +pub type SpiRxDma = RxDma, CHANNEL>; + +macro_rules! spi_rx_dma { + ($SPIi:ident, $TCi:ident, $chanX:ident, $mapX:ident) => { + impl Receive for SpiRxDma<$SPIi, PINS, $TCi> { + type RxChannel = $TCi; + type TransmittedWord = (); + } + + impl Spi<$SPIi, PINS> { + pub fn with_rx_dma(self, channel: $TCi) -> SpiRxDma<$SPIi, PINS, $TCi> { + let payload = SpiPayload { spi: self }; + SpiRxDma { payload, channel } + } + } + + impl SpiRxDma<$SPIi, PINS, $TCi> { + pub fn split(mut self) -> (Spi<$SPIi, PINS>, $TCi) { + self.stop(); + (self.payload.spi, self.channel) + } + } + + impl TransferPayload for SpiRxDma<$SPIi, PINS, $TCi> { + fn start(&mut self) { + self.payload + .spi + .spi + .cr2 + .modify(|_, w| w.rxdmaen().set_bit()); + self.channel.start(); + } + fn stop(&mut self) { + self.channel.stop(); + self.payload + .spi + .spi + .cr2 + .modify(|_, w| w.rxdmaen().clear_bit()); + } + } + + impl crate::dma::ReadDma for SpiRxDma<$SPIi, PINS, $TCi> + where + B: StaticWriteBuffer, + { + fn read(mut self, mut buffer: B) -> Transfer { + // NOTE(unsafe) We own the buffer now and we won't call other `&mut` on it + // until the end of the transfer. + let (ptr, len) = unsafe { buffer.static_write_buffer() }; + self.channel.set_peripheral_address( + unsafe { &(*$SPIi::ptr()).dr as *const _ as u32 }, + false, + ); + self.channel.set_memory_address(ptr as u32, true); + self.channel.set_transfer_length(len as u16); + + self.channel.cselr().modify(|_, w| w.$chanX().$mapX()); + + atomic::compiler_fence(Ordering::Release); + self.channel.ccr().modify(|_, w| { + w + // memory to memory mode disabled + .mem2mem() + .clear_bit() + // medium channel priority level + .pl() + .medium() + // 8-bit memory size + .msize() + .bits8() + // 8-bit peripheral size + .psize() + .bits8() + // circular mode disabled + .circ() + .clear_bit() + // write to memory + .dir() + .clear_bit() + }); + atomic::compiler_fence(Ordering::Release); + self.start(); + + Transfer::w(buffer, self) + } + } + }; +} + +#[cfg(any( + feature = "stm32l4x1", + feature = "stm32l4x2", + feature = "stm32l4x3", + feature = "stm32l4x5", + feature = "stm32l4x6" +))] +use embedded_dma::StaticWriteBuffer; + +#[cfg(any( + feature = "stm32l4x1", + feature = "stm32l4x2", + feature = "stm32l4x3", + feature = "stm32l4x5", + feature = "stm32l4x6" +))] +use crate::dma::dma1::C2; +#[cfg(any( + feature = "stm32l4x1", + feature = "stm32l4x2", + feature = "stm32l4x3", + feature = "stm32l4x5", + feature = "stm32l4x6" +))] +spi_rx_dma!(SPI1, C2, c2s, map1); + +#[cfg(any(feature = "stm32l4x3", feature = "stm32l4x5", feature = "stm32l4x6",))] +use crate::dma::dma1::C4; +#[cfg(any(feature = "stm32l4x3", feature = "stm32l4x5", feature = "stm32l4x6",))] +spi_rx_dma!(SPI2, C4, c4s, map1); + +#[cfg(any(feature = "stm32l4x5", feature = "stm32l4x6",))] +use crate::dma::dma2::C1; +#[cfg(any(feature = "stm32l4x5", feature = "stm32l4x6",))] +spi_rx_dma!(SPI3, C1, c1s, map3); From 48150f3c8ba40c83a0b0e06e628a454da36dfe8c Mon Sep 17 00:00:00 2001 From: Leah Date: Tue, 27 Apr 2021 18:42:06 +0200 Subject: [PATCH 02/10] move around character match Signed-off-by: Leah --- examples/rtic_frame_serial_dma.rs | 5 +- src/dma.rs | 19 +++++- src/serial.rs | 107 ++++++++++++++++-------------- 3 files changed, 78 insertions(+), 53 deletions(-) diff --git a/examples/rtic_frame_serial_dma.rs b/examples/rtic_frame_serial_dma.rs index 0a360115..85182ccd 100644 --- a/examples/rtic_frame_serial_dma.rs +++ b/examples/rtic_frame_serial_dma.rs @@ -91,7 +91,7 @@ const APP: () = { let fr = if let Some(dma_buf) = SerialDMAPool::alloc() { // Set up the first reader frame let dma_buf = dma_buf.init(DMAFrame::new()); - serial_rx.with_dma(dma_ch6).frame_read(dma_buf) + serial_rx.with_dma(dma_ch6).frame_reader(dma_buf) } else { unreachable!() }; @@ -100,7 +100,6 @@ const APP: () = { let fs: FrameSender, _, 8> = serial_tx.with_dma(dma_ch7).frame_sender(); init::LateResources { - rx: serial_rx, frame_reader: fr, frame_sender: fs, } @@ -112,7 +111,7 @@ const APP: () = { #[task(binds = USART2, resources = [rx, frame_reader, frame_sender], priority = 3)] fn serial_isr(cx: serial_isr::Context) { // Check for character match - if cx.resources.rx.is_character_match(true) { + if cx.resources.frame_reader.check_character_match(true) { if let Some(dma_buf) = SerialDMAPool::alloc() { let dma_buf = dma_buf.init(DMAFrame::new()); let buf = cx.resources.frame_reader.character_match_interrupt(dma_buf); diff --git a/src/dma.rs b/src/dma.rs index d6b833cb..580e8dfc 100644 --- a/src/dma.rs +++ b/src/dma.rs @@ -32,6 +32,12 @@ pub enum Half { Second, } +pub trait CharacterMatch { + /// Checks to see if the peripheral has detected a character match and + /// clears the flag + fn check_character_match(&mut self, clear: bool) -> bool; +} + /// Frame reader "worker", access and handling of frame reads is made through this structure. pub struct FrameReader where @@ -59,6 +65,17 @@ where } } +impl FrameReader, N> +where + PAYLOAD: CharacterMatch, +{ + /// Checks to see if the peripheral has detected a character match and + /// clears the flag + pub fn check_character_match(&mut self, clear: bool) -> bool { + self.payload.payload.check_character_match(clear) + } +} + /// Frame sender "worker", access and handling of frame transmissions is made through this /// structure. pub struct FrameSender @@ -377,7 +394,7 @@ macro_rules! dma { use core::ptr; use stable_deref_trait::StableDeref; - use crate::dma::{CircBuffer, FrameReader, FrameSender, DMAFrame, DmaExt, Error, Event, Half, Transfer, W, R, RxDma, TxDma, TransferPayload}; + use crate::dma::{CircBuffer, FrameReader, CharacterMatch, FrameSender, DMAFrame, DmaExt, Error, Event, Half, Transfer, W, R, RxDma, TxDma, TransferPayload}; use crate::rcc::AHB1; #[allow(clippy::manual_non_exhaustive)] diff --git a/src/serial.rs b/src/serial.rs index 96c6352a..c881bb0e 100644 --- a/src/serial.rs +++ b/src/serial.rs @@ -575,6 +575,63 @@ macro_rules! hal { Ok(()) } + + /// Checks to see if the USART peripheral has detected an idle line and clears + /// the flag + pub fn is_idle(&mut self, clear: bool) -> bool { + let isr = unsafe { &(*pac::$USARTX::ptr()).isr.read() }; + let icr = unsafe { &(*pac::$USARTX::ptr()).icr }; + + if isr.idle().bit_is_set() { + if clear { + icr.write(|w| w.idlecf().set_bit() ); + } + true + } else { + false + } + } + + + /// Checks to see if the USART peripheral has detected an receiver timeout and + /// clears the flag + pub fn is_receiver_timeout(&mut self, clear: bool) -> bool { + let isr = unsafe { &(*pac::$USARTX::ptr()).isr.read() }; + let icr = unsafe { &(*pac::$USARTX::ptr()).icr }; + + if isr.rtof().bit_is_set() { + if clear { + icr.write(|w| w.rtocf().set_bit() ); + } + true + } else { + false + } + } + + /// Checks to see if the USART peripheral has detected an character match and + /// clears the flag + pub fn check_character_match(&mut self, clear: bool) -> bool { + let isr = unsafe { &(*pac::$USARTX::ptr()).isr.read() }; + let icr = unsafe { &(*pac::$USARTX::ptr()).icr }; + + if isr.cmf().bit_is_set() { + if clear { + icr.write(|w| w.cmcf().set_bit() ); + } + true + } else { + false + } + } + } + + impl crate::dma::CharacterMatch for Rx { + /// Checks to see if the USART peripheral has detected an character match and + /// clears the flag + fn check_character_match(&mut self, clear: bool) -> bool { + self.check_character_match(clear) + } } impl Tx { @@ -665,7 +722,7 @@ macro_rules! hal { impl $rxdma { /// Create a frame reader that can either react on the Character match interrupt or /// Transfer Complete from the DMA. - pub fn frame_read( + pub fn frame_reader( mut self, buffer: BUFFER, ) -> FrameReader @@ -712,54 +769,6 @@ macro_rules! hal { FrameReader::new(buffer, self, usart.cr2.read().add().bits()) } - - /// Checks to see if the USART peripheral has detected an idle line and clears - /// the flag - pub fn is_idle(&mut self, clear: bool) -> bool { - let isr = unsafe { &(*pac::$USARTX::ptr()).isr.read() }; - let icr = unsafe { &(*pac::$USARTX::ptr()).icr }; - - if isr.idle().bit_is_set() { - if clear { - icr.write(|w| w.idlecf().set_bit() ); - } - true - } else { - false - } - } - - /// Checks to see if the USART peripheral has detected an character match and - /// clears the flag - pub fn is_character_match(&mut self, clear: bool) -> bool { - let isr = unsafe { &(*pac::$USARTX::ptr()).isr.read() }; - let icr = unsafe { &(*pac::$USARTX::ptr()).icr }; - - if isr.cmf().bit_is_set() { - if clear { - icr.write(|w| w.cmcf().set_bit() ); - } - true - } else { - false - } - } - - /// Checks to see if the USART peripheral has detected an receiver timeout and - /// clears the flag - pub fn is_receiver_timeout(&mut self, clear: bool) -> bool { - let isr = unsafe { &(*pac::$USARTX::ptr()).isr.read() }; - let icr = unsafe { &(*pac::$USARTX::ptr()).icr }; - - if isr.rtof().bit_is_set() { - if clear { - icr.write(|w| w.rtocf().set_bit() ); - } - true - } else { - false - } - } } impl $txdma { From 12bf98d3a15c31d486d59cacad2fde775b22c7a7 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Sun, 23 May 2021 17:39:53 +0200 Subject: [PATCH 03/10] Minor fixes --- examples/rtic_frame_serial_dma.rs | 5 ++--- src/dma.rs | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/rtic_frame_serial_dma.rs b/examples/rtic_frame_serial_dma.rs index 85182ccd..4b06c541 100644 --- a/examples/rtic_frame_serial_dma.rs +++ b/examples/rtic_frame_serial_dma.rs @@ -36,7 +36,6 @@ pool!( #[app(device = stm32l4xx_hal::stm32, peripherals = true)] const APP: () = { struct Resources { - rx: serial::Rx, frame_reader: FrameReader, RxDma, dma::dma1::C6>, 8>, frame_sender: FrameSender, TxDma, dma::dma1::C7>, 8>, } @@ -108,7 +107,7 @@ const APP: () = { /// This task handles the character match interrupt at required by the `FrameReader` /// /// It will echo the buffer back to the serial. - #[task(binds = USART2, resources = [rx, frame_reader, frame_sender], priority = 3)] + #[task(binds = USART2, resources = [frame_reader, frame_sender], priority = 3)] fn serial_isr(cx: serial_isr::Context) { // Check for character match if cx.resources.frame_reader.check_character_match(true) { @@ -125,7 +124,7 @@ const APP: () = { /// This task handles the RX transfer complete interrupt at required by the `FrameReader` /// /// In this case we are discarding if a frame gets full as no character match was received - #[task(binds = DMA1_CH6, resources = [rx, frame_reader], priority = 3)] + #[task(binds = DMA1_CH6, resources = [frame_reader], priority = 3)] fn serial_rx_dma_isr(cx: serial_rx_dma_isr::Context) { if let Some(dma_buf) = SerialDMAPool::alloc() { let dma_buf = dma_buf.init(DMAFrame::new()); diff --git a/src/dma.rs b/src/dma.rs index 580e8dfc..2cc2dc0b 100644 --- a/src/dma.rs +++ b/src/dma.rs @@ -67,6 +67,7 @@ where impl FrameReader, N> where + BUFFER: Sized + StableDeref> + DerefMut + 'static, PAYLOAD: CharacterMatch, { /// Checks to see if the peripheral has detected a character match and @@ -394,7 +395,7 @@ macro_rules! dma { use core::ptr; use stable_deref_trait::StableDeref; - use crate::dma::{CircBuffer, FrameReader, CharacterMatch, FrameSender, DMAFrame, DmaExt, Error, Event, Half, Transfer, W, R, RxDma, TxDma, TransferPayload}; + use crate::dma::{CircBuffer, FrameReader, FrameSender, DMAFrame, DmaExt, Error, Event, Half, Transfer, W, R, RxDma, TxDma, TransferPayload}; use crate::rcc::AHB1; #[allow(clippy::manual_non_exhaustive)] From ec73c4e18c8fd534c29c64846d0edbb55b563466 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Sun, 23 May 2021 20:17:53 +0200 Subject: [PATCH 04/10] Tentative TxDma and RxTxDma for SPI --- src/dma.rs | 116 ++++++++++++++++++++- src/spi.rs | 298 +++++++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 369 insertions(+), 45 deletions(-) diff --git a/src/dma.rs b/src/dma.rs index 2cc2dc0b..b93e1e3d 100644 --- a/src/dma.rs +++ b/src/dma.rs @@ -351,6 +351,19 @@ where } } +impl Transfer +where + PAYLOAD: TransferPayload, +{ + pub(crate) fn rw(buffer: BUFFER, payload: PAYLOAD) -> Self { + Transfer { + _mode: PhantomData, + buffer, + payload, + } + } +} + impl Drop for Transfer where PAYLOAD: TransferPayload, @@ -367,6 +380,86 @@ pub struct R; /// Write transfer pub struct W; +/// Read/Write transfer +pub struct RW; + +macro_rules! for_all_pairs { + ($mac:ident: $($x:ident)*) => { + // Duplicate the list + for_all_pairs!(@inner $mac: $($x)*; $($x)*); + }; + + // The end of iteration: we exhausted the list + (@inner $mac:ident: ; $($x:ident)*) => {}; + + // The head/tail recursion: pick the first element of the first list + // and recursively do it for the tail. + (@inner $mac:ident: $head:ident $($tail:ident)*; $($x:ident)*) => { + $( + $mac!($head $x); + )* + for_all_pairs!(@inner $mac: $($tail)*; $($x)*); + }; +} + +macro_rules! rx_tx_channel_mapping { + ($CH_A:ident $CH_B:ident) => { + impl Transfer> + where + RxTxDma: TransferPayload, + { + pub fn is_done(&self) -> bool { + !self.payload.rx_channel.in_progress() && !self.payload.tx_channel.in_progress() + } + + pub fn wait(mut self) -> (BUFFER, RxTxDma) { + // XXX should we check for transfer errors here? + // The manual says "A DMA transfer error can be generated by reading + // from or writing to a reserved address space". I think it's impossible + // to get to that state with our type safe API and *safe* Rust. + while !self.is_done() {} + + self.payload.stop(); + + // TODO can we weaken this compiler barrier? + // NOTE(compiler_fence) operations on `buffer` should not be reordered + // before the previous statement, which marks the DMA transfer as done + atomic::compiler_fence(Ordering::SeqCst); + + // `Transfer` needs to have a `Drop` implementation, because we accept + // managed buffers that can free their memory on drop. Because of that + // we can't move out of the `Transfer`'s fields, so we use `ptr::read` + // and `mem::forget`. + // + // NOTE(unsafe) There is no panic branch between getting the resources + // and forgetting `self`. + unsafe { + let buffer = ptr::read(&self.buffer); + let payload = ptr::read(&self.payload); + core::mem::forget(self); + (buffer, payload) + } + } + } + + impl Transfer> + where + RxTxDma: TransferPayload, + { + pub fn peek(&self) -> &[T] + where + BUFFER: AsRef<[T]>, + { + let pending = self.payload.rx_channel.get_cndtr() as usize; + + let capacity = self.buffer.as_ref().len(); + + &self.buffer.as_ref()[..(capacity - pending)] + } + } + }; +} + macro_rules! dma { ($($DMAX:ident: ($dmaX:ident, $dmaXen:ident, $dmaXrst:ident, { $($CX:ident: ( @@ -395,12 +488,14 @@ macro_rules! dma { use core::ptr; use stable_deref_trait::StableDeref; - use crate::dma::{CircBuffer, FrameReader, FrameSender, DMAFrame, DmaExt, Error, Event, Half, Transfer, W, R, RxDma, TxDma, TransferPayload}; + use crate::dma::{CircBuffer, FrameReader, FrameSender, DMAFrame, DmaExt, Error, Event, Half, Transfer, W, R, RW, RxDma, RxTxDma, TxDma, TransferPayload}; use crate::rcc::AHB1; #[allow(clippy::manual_non_exhaustive)] pub struct Channels((), $(pub $CX),+); + for_all_pairs!(rx_tx_channel_mapping: $($CX)+); + $( /// A singleton that represents a single DMAx channel (channel X in this case) /// @@ -1096,8 +1191,8 @@ pub struct TxDma { /// DMA Receiver/Transmitter pub struct RxTxDma { pub(crate) payload: PAYLOAD, - pub rxchannel: RXCH, - pub txchannel: TXCH, + pub rx_channel: RXCH, + pub tx_channel: TXCH, } pub trait Receive { @@ -1110,6 +1205,12 @@ pub trait Transmit { type ReceivedWord; } +pub trait ReceiveTransmit { + type RxChannel; + type TxChannel; + type TransferedWord; +} + /// Trait for circular DMA readings from peripheral to memory. pub trait CircReadDma: Receive where @@ -1137,3 +1238,12 @@ where { fn write(self, buffer: B) -> Transfer; } + +/// Trait for DMA simultaneously writing and reading between memory and peripheral. +pub trait TransferDma: ReceiveTransmit +where + B: StaticWriteBuffer, + Self: core::marker::Sized + TransferPayload, +{ + fn transfer(self, buffer: B) -> Transfer; +} diff --git a/src/spi.rs b/src/spi.rs index 64a28e17..9e3f2d63 100644 --- a/src/spi.rs +++ b/src/spi.rs @@ -9,12 +9,14 @@ use core::ptr; use core::sync::atomic; use core::sync::atomic::Ordering; -use crate::dma::{Receive, RxDma, Transfer, TransferPayload, W}; +use crate::dma::{self, dma1, dma2, TransferPayload}; use crate::gpio::{Alternate, Floating, Input, AF5}; use crate::hal::spi::{FullDuplex, Mode, Phase, Polarity}; use crate::rcc::{Clocks, APB1R1, APB2}; use crate::time::Hertz; +use embedded_dma::{StaticReadBuffer, StaticWriteBuffer}; + /// SPI error #[non_exhaustive] #[derive(Debug)] @@ -373,30 +375,77 @@ pub struct SpiPayload { spi: Spi, } -pub type SpiRxDma = RxDma, CHANNEL>; +pub type SpiRxDma = dma::RxDma, CHANNEL>; + +pub type SpiTxDma = dma::TxDma, CHANNEL>; + +pub type SpiRxTxDma = dma::RxTxDma, RXCH, TXCH>; -macro_rules! spi_rx_dma { - ($SPIi:ident, $TCi:ident, $chanX:ident, $mapX:ident) => { - impl Receive for SpiRxDma<$SPIi, PINS, $TCi> { - type RxChannel = $TCi; - type TransmittedWord = (); +macro_rules! spi_dma { + ($SPIX:ident, $RX_CH:path, $RX_CHX:ident, $RX_MAPX:ident, $TX_CH:path, $TX_CHX:ident, $TX_MAPX:ident) => { + impl dma::Receive for SpiRxDma<$SPIX, PINS, $RX_CH> { + type RxChannel = $RX_CH; + type TransmittedWord = u8; } - impl Spi<$SPIi, PINS> { - pub fn with_rx_dma(self, channel: $TCi) -> SpiRxDma<$SPIi, PINS, $TCi> { + impl dma::Transmit for SpiTxDma<$SPIX, PINS, $TX_CH> { + type TxChannel = $TX_CH; + type ReceivedWord = u8; + } + + impl dma::ReceiveTransmit for SpiRxTxDma<$SPIX, PINS, $RX_CH, $TX_CH> { + type RxChannel = $RX_CH; + type TxChannel = $TX_CH; + type TransferedWord = u8; + } + + impl Spi<$SPIX, PINS> { + pub fn with_rx_dma(self, channel: $RX_CH) -> SpiRxDma<$SPIX, PINS, $RX_CH> { let payload = SpiPayload { spi: self }; SpiRxDma { payload, channel } } + + pub fn with_tx_dma(self, channel: $TX_CH) -> SpiTxDma<$SPIX, PINS, $TX_CH> { + let payload = SpiPayload { spi: self }; + SpiTxDma { payload, channel } + } + + pub fn with_rxtx_dma( + self, + rx_channel: $RX_CH, + tx_channel: $TX_CH, + ) -> SpiRxTxDma<$SPIX, PINS, $RX_CH, $TX_CH> { + let payload = SpiPayload { spi: self }; + SpiRxTxDma { + payload, + rx_channel, + tx_channel, + } + } } - impl SpiRxDma<$SPIi, PINS, $TCi> { - pub fn split(mut self) -> (Spi<$SPIi, PINS>, $TCi) { + impl SpiRxDma<$SPIX, PINS, $RX_CH> { + pub fn split(mut self) -> (Spi<$SPIX, PINS>, $RX_CH) { self.stop(); (self.payload.spi, self.channel) } } - impl TransferPayload for SpiRxDma<$SPIi, PINS, $TCi> { + impl SpiTxDma<$SPIX, PINS, $TX_CH> { + pub fn split(mut self) -> (Spi<$SPIX, PINS>, $TX_CH) { + self.stop(); + (self.payload.spi, self.channel) + } + } + + impl SpiRxTxDma<$SPIX, PINS, $RX_CH, $TX_CH> { + pub fn split(mut self) -> (Spi<$SPIX, PINS>, $RX_CH, $TX_CH) { + self.stop(); + (self.payload.spi, self.rx_channel, self.tx_channel) + } + } + + impl dma::TransferPayload for SpiRxDma<$SPIX, PINS, $RX_CH> { fn start(&mut self) { self.payload .spi @@ -405,6 +454,7 @@ macro_rules! spi_rx_dma { .modify(|_, w| w.rxdmaen().set_bit()); self.channel.start(); } + fn stop(&mut self) { self.channel.stop(); self.payload @@ -415,22 +465,64 @@ macro_rules! spi_rx_dma { } } - impl crate::dma::ReadDma for SpiRxDma<$SPIi, PINS, $TCi> + impl dma::TransferPayload for SpiTxDma<$SPIX, PINS, $TX_CH> { + fn start(&mut self) { + self.payload + .spi + .spi + .cr2 + .modify(|_, w| w.txdmaen().set_bit()); + self.channel.start(); + } + + fn stop(&mut self) { + self.channel.stop(); + self.payload + .spi + .spi + .cr2 + .modify(|_, w| w.txdmaen().clear_bit()); + } + } + + impl dma::TransferPayload for SpiRxTxDma<$SPIX, PINS, $RX_CH, $TX_CH> { + fn start(&mut self) { + self.payload + .spi + .spi + .cr2 + .modify(|_, w| w.rxdmaen().set_bit().txdmaen().set_bit()); + self.rx_channel.start(); + self.tx_channel.start(); + } + + fn stop(&mut self) { + self.tx_channel.stop(); + self.rx_channel.stop(); + self.payload + .spi + .spi + .cr2 + .modify(|_, w| w.rxdmaen().clear_bit().txdmaen().clear_bit()); + } + } + + impl dma::ReadDma for SpiRxDma<$SPIX, PINS, $RX_CH> where B: StaticWriteBuffer, { - fn read(mut self, mut buffer: B) -> Transfer { + fn read(mut self, mut buffer: B) -> dma::Transfer { // NOTE(unsafe) We own the buffer now and we won't call other `&mut` on it // until the end of the transfer. let (ptr, len) = unsafe { buffer.static_write_buffer() }; self.channel.set_peripheral_address( - unsafe { &(*$SPIi::ptr()).dr as *const _ as u32 }, + unsafe { &(*$SPIX::ptr()).dr as *const _ as u32 }, false, ); self.channel.set_memory_address(ptr as u32, true); self.channel.set_transfer_length(len as u16); - self.channel.cselr().modify(|_, w| w.$chanX().$mapX()); + self.channel.cselr().modify(|_, w| w.$RX_CHX().$RX_MAPX()); atomic::compiler_fence(Ordering::Release); self.channel.ccr().modify(|_, w| { @@ -457,44 +549,166 @@ macro_rules! spi_rx_dma { atomic::compiler_fence(Ordering::Release); self.start(); - Transfer::w(buffer, self) + dma::Transfer::w(buffer, self) + } + } + + impl dma::WriteDma for SpiTxDma<$SPIX, PINS, $TX_CH> + where + B: StaticReadBuffer, + { + fn write(mut self, buffer: B) -> dma::Transfer { + // NOTE(unsafe) We own the buffer now and we won't call other `&mut` on it + // until the end of the transfer. + let (ptr, len) = unsafe { buffer.static_read_buffer() }; + self.channel.set_peripheral_address( + unsafe { &(*$SPIX::ptr()).dr as *const _ as u32 }, + false, + ); + self.channel.set_memory_address(ptr as u32, true); + self.channel.set_transfer_length(len as u16); + + self.channel.cselr().modify(|_, w| w.$TX_CHX().$TX_MAPX()); + + atomic::compiler_fence(Ordering::Release); + self.channel.ccr().modify(|_, w| { + w + // memory to memory mode disabled + .mem2mem() + .clear_bit() + // medium channel priority level + .pl() + .medium() + // 8-bit memory size + .msize() + .bits8() + // 8-bit peripheral size + .psize() + .bits8() + // circular mode disabled + .circ() + .clear_bit() + // write to peripheral + .dir() + .set_bit() + }); + atomic::compiler_fence(Ordering::Release); + self.start(); + + dma::Transfer::r(buffer, self) + } + } + + impl dma::TransferDma for SpiRxTxDma<$SPIX, PINS, $RX_CH, $TX_CH> + where + B: StaticWriteBuffer, + { + fn transfer(mut self, mut buffer: B) -> dma::Transfer { + // Transfer: we use the same buffer for RX and TX + + // NOTE(unsafe) We own the buffer now and we won't call other `&mut` on it + // until the end of the transfer. + let (ptr, len) = unsafe { buffer.static_write_buffer() }; + + // + // Setup RX channel + // + self.rx_channel.set_peripheral_address( + unsafe { &(*$SPIX::ptr()).dr as *const _ as u32 }, + false, + ); + self.rx_channel.set_memory_address(ptr as u32, true); + self.rx_channel.set_transfer_length(len as u16); + + self.rx_channel + .cselr() + .modify(|_, w| w.$RX_CHX().$RX_MAPX()); + + atomic::compiler_fence(Ordering::Release); + self.rx_channel.ccr().modify(|_, w| { + w + // memory to memory mode disabled + .mem2mem() + .clear_bit() + // medium channel priority level + .pl() + .medium() + // 8-bit memory size + .msize() + .bits8() + // 8-bit peripheral size + .psize() + .bits8() + // circular mode disabled + .circ() + .clear_bit() + // write to memory + .dir() + .clear_bit() + }); + + // + // Setup TX channel + // + self.tx_channel.set_peripheral_address( + unsafe { &(*$SPIX::ptr()).dr as *const _ as u32 }, + false, + ); + self.tx_channel.set_memory_address(ptr as u32, true); + self.tx_channel.set_transfer_length(len as u16); + + self.tx_channel + .cselr() + .modify(|_, w| w.$TX_CHX().$TX_MAPX()); + + atomic::compiler_fence(Ordering::Release); + self.tx_channel.ccr().modify(|_, w| { + w + // memory to memory mode disabled + .mem2mem() + .clear_bit() + // medium channel priority level + .pl() + .medium() + // 8-bit memory size + .msize() + .bits8() + // 8-bit peripheral size + .psize() + .bits8() + // circular mode disabled + .circ() + .clear_bit() + // write to peripheral + .dir() + .set_bit() + }); + + // + // Fences and start + // + atomic::compiler_fence(Ordering::Release); + self.start(); + + dma::Transfer::rw(buffer, self) } } }; } +spi_dma!(SPI1, dma1::C2, c2s, map1, dma1::C3, c3s, map1); #[cfg(any( feature = "stm32l4x1", - feature = "stm32l4x2", feature = "stm32l4x3", feature = "stm32l4x5", - feature = "stm32l4x6" -))] -use embedded_dma::StaticWriteBuffer; - -#[cfg(any( - feature = "stm32l4x1", - feature = "stm32l4x2", - feature = "stm32l4x3", - feature = "stm32l4x5", - feature = "stm32l4x6" + feature = "stm32l4x6", ))] -use crate::dma::dma1::C2; +spi_dma!(SPI2, dma1::C4, c4s, map1, dma1::C5, c5s, map1); +// spi_dma!(SPI1, dma2::C3, c3s, map4, dma2::C4, c4s, map4); #[cfg(any( feature = "stm32l4x1", feature = "stm32l4x2", - feature = "stm32l4x3", feature = "stm32l4x5", - feature = "stm32l4x6" + feature = "stm32l4x6", ))] -spi_rx_dma!(SPI1, C2, c2s, map1); - -#[cfg(any(feature = "stm32l4x3", feature = "stm32l4x5", feature = "stm32l4x6",))] -use crate::dma::dma1::C4; -#[cfg(any(feature = "stm32l4x3", feature = "stm32l4x5", feature = "stm32l4x6",))] -spi_rx_dma!(SPI2, C4, c4s, map1); - -#[cfg(any(feature = "stm32l4x5", feature = "stm32l4x6",))] -use crate::dma::dma2::C1; -#[cfg(any(feature = "stm32l4x5", feature = "stm32l4x6",))] -spi_rx_dma!(SPI3, C1, c1s, map3); +spi_dma!(SPI3, dma2::C1, c1s, map3, dma2::C2, c2s, map3); From fc59268524cbb00cf1c9c1a319e21c3043fb1487 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Sun, 30 May 2021 19:33:48 +0200 Subject: [PATCH 05/10] SPI DMA RX/TX example --- Cargo.toml | 4 ++ examples/spi_dma_rxtx.rs | 103 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+) create mode 100644 examples/spi_dma_rxtx.rs diff --git a/Cargo.toml b/Cargo.toml index 2ff2edbd..4fd614e1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -120,6 +120,10 @@ required-features = ["rt"] name = "rtic_frame_serial_dma" required-features = ["rt", "stm32l4x2"] +[[example]] +name = "spi_dma_rxtx" +required-features = ["rt", "stm32l4x2"] + [[example]] name = "serial_echo_rtic" required-features = ["rt", "stm32l4x3"] diff --git a/examples/spi_dma_rxtx.rs b/examples/spi_dma_rxtx.rs new file mode 100644 index 00000000..34bfb13d --- /dev/null +++ b/examples/spi_dma_rxtx.rs @@ -0,0 +1,103 @@ +//! Test the SPI in RX/TX (transfer) DMA mode +#![deny(unsafe_code)] +#![no_main] +#![no_std] + +use panic_rtt_target as _; +use rtt_target::rprintln; +use stm32l4xx_hal::{ + dma::TransferDma, + gpio::{Speed, State as PinState}, + hal::spi::{Mode, Phase, Polarity}, + prelude::*, + rcc::{ClockSecuritySystem, CrystalBypass, MsiFreq}, + spi::Spi, +}; + +#[rtic::app(device = stm32l4xx_hal::pac, peripherals = true)] +const APP: () = { + #[init] + fn init(cx: init::Context) { + static mut DMA_BUF: [u8; 5] = [0xf0, 0xaa, 0x00, 0xff, 0x0f]; + + rtt_target::rtt_init_print!(); + rprintln!("Initializing... "); + + let dp = cx.device; + + let mut flash = dp.FLASH.constrain(); + let mut rcc = dp.RCC.constrain(); + let mut pwr = dp.PWR.constrain(&mut rcc.apb1r1); + let mut gpiob = dp.GPIOB.split(&mut rcc.ahb2); + let dma1_channels = dp.DMA1.split(&mut rcc.ahb1); + + // + // Initialize the clocks to 80 MHz + // + rprintln!(" - Clock init"); + let clocks = rcc + .cfgr + .lse(CrystalBypass::Enable, ClockSecuritySystem::Disable) + .hsi48(true) // For RNG + .msi(MsiFreq::RANGE4M) + .sysclk(80.mhz()) + .freeze(&mut flash.acr, &mut pwr); + + // + // Initialize the SPI + // + let sck = gpiob + .pb3 + .into_af5(&mut gpiob.moder, &mut gpiob.afrl) + .set_speed(Speed::High); + let miso = gpiob + .pb4 + .into_af5(&mut gpiob.moder, &mut gpiob.afrl) + .set_speed(Speed::High); + let mosi = gpiob + .pb5 + .into_af5(&mut gpiob.moder, &mut gpiob.afrl) + .set_speed(Speed::High); + let mut dummy_cs = gpiob.pb6.into_push_pull_output_with_state( + &mut gpiob.moder, + &mut gpiob.otyper, + PinState::High, + ); + let spi = Spi::spi1( + dp.SPI1, + (sck, miso, mosi), + Mode { + phase: Phase::CaptureOnFirstTransition, + polarity: Polarity::IdleLow, + }, + 100.khz(), + clocks, + &mut rcc.apb2, + ); + + // Create DMA SPI + let dma_spi = spi.with_rxtx_dma(dma1_channels.2, dma1_channels.3); + + // Check the buffer before using it + rprintln!("buf pre: 0x{:x?}", &DMA_BUF); + + // Perform transfer and wait for it to finish (blocking), this can also be done using + // interrupts on the desired DMA channel + dummy_cs.set_low().ok(); + let transfer = dma_spi.transfer(DMA_BUF); + let (buf, _dma_spi) = transfer.wait(); + dummy_cs.set_high().ok(); + + // Inspect the extracted buffer, if the MISO is connected to VCC or GND it will be all 0 or + // 1. + rprintln!("buf post: 0x{:x?}", &buf); + } + + // Idle function so RTT keeps working + #[idle] + fn idle(_cx: idle::Context) -> ! { + loop { + continue; + } + } +}; From 5b9a63c86a7c9688b5b94302d2c755330d139329 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Mon, 31 May 2021 17:28:50 +0200 Subject: [PATCH 06/10] Moved DMA setup to when the DMA object is made As it is a one-time setup, it does not make sense to re-setup the entire DMA engine, when it's enough to setup the buffer's address and length. --- src/spi.rs | 263 +++++++++++++++++++++++++++-------------------------- 1 file changed, 135 insertions(+), 128 deletions(-) diff --git a/src/spi.rs b/src/spi.rs index 9e3f2d63..1d95ba1c 100644 --- a/src/spi.rs +++ b/src/spi.rs @@ -400,22 +400,147 @@ macro_rules! spi_dma { } impl Spi<$SPIX, PINS> { - pub fn with_rx_dma(self, channel: $RX_CH) -> SpiRxDma<$SPIX, PINS, $RX_CH> { + pub fn with_rx_dma(self, mut channel: $RX_CH) -> SpiRxDma<$SPIX, PINS, $RX_CH> { let payload = SpiPayload { spi: self }; + + // Perform one-time setup actions to keep the work minimal when using the driver. + + channel.set_peripheral_address( + unsafe { &(*$SPIX::ptr()).dr as *const _ as u32 }, + false, + ); + channel.cselr().modify(|_, w| w.$RX_CHX().$RX_MAPX()); + channel.ccr().modify(|_, w| { + w + // memory to memory mode disabled + .mem2mem() + .clear_bit() + // medium channel priority level + .pl() + .medium() + // 8-bit memory size + .msize() + .bits8() + // 8-bit peripheral size + .psize() + .bits8() + // circular mode disabled + .circ() + .clear_bit() + // write to memory + .dir() + .clear_bit() + }); + SpiRxDma { payload, channel } } - pub fn with_tx_dma(self, channel: $TX_CH) -> SpiTxDma<$SPIX, PINS, $TX_CH> { + pub fn with_tx_dma(self, mut channel: $TX_CH) -> SpiTxDma<$SPIX, PINS, $TX_CH> { let payload = SpiPayload { spi: self }; + + // Perform one-time setup actions to keep the work minimal when using the driver. + + channel.set_peripheral_address( + unsafe { &(*$SPIX::ptr()).dr as *const _ as u32 }, + false, + ); + channel.cselr().modify(|_, w| w.$TX_CHX().$TX_MAPX()); + channel.ccr().modify(|_, w| { + w + // memory to memory mode disabled + .mem2mem() + .clear_bit() + // medium channel priority level + .pl() + .medium() + // 8-bit memory size + .msize() + .bits8() + // 8-bit peripheral size + .psize() + .bits8() + // circular mode disabled + .circ() + .clear_bit() + // write to peripheral + .dir() + .set_bit() + }); + SpiTxDma { payload, channel } } pub fn with_rxtx_dma( self, - rx_channel: $RX_CH, - tx_channel: $TX_CH, + mut rx_channel: $RX_CH, + mut tx_channel: $TX_CH, ) -> SpiRxTxDma<$SPIX, PINS, $RX_CH, $TX_CH> { let payload = SpiPayload { spi: self }; + + // Perform one-time setup actions to keep the work minimal when using the driver. + + // + // Setup RX channel + // + rx_channel.set_peripheral_address( + unsafe { &(*$SPIX::ptr()).dr as *const _ as u32 }, + false, + ); + rx_channel.cselr().modify(|_, w| w.$RX_CHX().$RX_MAPX()); + + rx_channel.ccr().modify(|_, w| { + w + // memory to memory mode disabled + .mem2mem() + .clear_bit() + // medium channel priority level + .pl() + .medium() + // 8-bit memory size + .msize() + .bits8() + // 8-bit peripheral size + .psize() + .bits8() + // circular mode disabled + .circ() + .clear_bit() + // write to memory + .dir() + .clear_bit() + }); + + // + // Setup TX channel + // + tx_channel.set_peripheral_address( + unsafe { &(*$SPIX::ptr()).dr as *const _ as u32 }, + false, + ); + tx_channel.cselr().modify(|_, w| w.$TX_CHX().$TX_MAPX()); + + tx_channel.ccr().modify(|_, w| { + w + // memory to memory mode disabled + .mem2mem() + .clear_bit() + // medium channel priority level + .pl() + .medium() + // 8-bit memory size + .msize() + .bits8() + // 8-bit peripheral size + .psize() + .bits8() + // circular mode disabled + .circ() + .clear_bit() + // write to peripheral + .dir() + .set_bit() + }); + SpiRxTxDma { payload, rx_channel, @@ -515,37 +640,12 @@ macro_rules! spi_dma { // NOTE(unsafe) We own the buffer now and we won't call other `&mut` on it // until the end of the transfer. let (ptr, len) = unsafe { buffer.static_write_buffer() }; - self.channel.set_peripheral_address( - unsafe { &(*$SPIX::ptr()).dr as *const _ as u32 }, - false, - ); + + // Setup RX channel self.channel.set_memory_address(ptr as u32, true); self.channel.set_transfer_length(len as u16); - self.channel.cselr().modify(|_, w| w.$RX_CHX().$RX_MAPX()); - - atomic::compiler_fence(Ordering::Release); - self.channel.ccr().modify(|_, w| { - w - // memory to memory mode disabled - .mem2mem() - .clear_bit() - // medium channel priority level - .pl() - .medium() - // 8-bit memory size - .msize() - .bits8() - // 8-bit peripheral size - .psize() - .bits8() - // circular mode disabled - .circ() - .clear_bit() - // write to memory - .dir() - .clear_bit() - }); + // Fences and start atomic::compiler_fence(Ordering::Release); self.start(); @@ -561,37 +661,12 @@ macro_rules! spi_dma { // NOTE(unsafe) We own the buffer now and we won't call other `&mut` on it // until the end of the transfer. let (ptr, len) = unsafe { buffer.static_read_buffer() }; - self.channel.set_peripheral_address( - unsafe { &(*$SPIX::ptr()).dr as *const _ as u32 }, - false, - ); + + // Setup TX channel self.channel.set_memory_address(ptr as u32, true); self.channel.set_transfer_length(len as u16); - self.channel.cselr().modify(|_, w| w.$TX_CHX().$TX_MAPX()); - - atomic::compiler_fence(Ordering::Release); - self.channel.ccr().modify(|_, w| { - w - // memory to memory mode disabled - .mem2mem() - .clear_bit() - // medium channel priority level - .pl() - .medium() - // 8-bit memory size - .msize() - .bits8() - // 8-bit peripheral size - .psize() - .bits8() - // circular mode disabled - .circ() - .clear_bit() - // write to peripheral - .dir() - .set_bit() - }); + // Fences and start atomic::compiler_fence(Ordering::Release); self.start(); @@ -610,83 +685,15 @@ macro_rules! spi_dma { // until the end of the transfer. let (ptr, len) = unsafe { buffer.static_write_buffer() }; - // // Setup RX channel - // - self.rx_channel.set_peripheral_address( - unsafe { &(*$SPIX::ptr()).dr as *const _ as u32 }, - false, - ); self.rx_channel.set_memory_address(ptr as u32, true); self.rx_channel.set_transfer_length(len as u16); - self.rx_channel - .cselr() - .modify(|_, w| w.$RX_CHX().$RX_MAPX()); - - atomic::compiler_fence(Ordering::Release); - self.rx_channel.ccr().modify(|_, w| { - w - // memory to memory mode disabled - .mem2mem() - .clear_bit() - // medium channel priority level - .pl() - .medium() - // 8-bit memory size - .msize() - .bits8() - // 8-bit peripheral size - .psize() - .bits8() - // circular mode disabled - .circ() - .clear_bit() - // write to memory - .dir() - .clear_bit() - }); - - // // Setup TX channel - // - self.tx_channel.set_peripheral_address( - unsafe { &(*$SPIX::ptr()).dr as *const _ as u32 }, - false, - ); self.tx_channel.set_memory_address(ptr as u32, true); self.tx_channel.set_transfer_length(len as u16); - self.tx_channel - .cselr() - .modify(|_, w| w.$TX_CHX().$TX_MAPX()); - - atomic::compiler_fence(Ordering::Release); - self.tx_channel.ccr().modify(|_, w| { - w - // memory to memory mode disabled - .mem2mem() - .clear_bit() - // medium channel priority level - .pl() - .medium() - // 8-bit memory size - .msize() - .bits8() - // 8-bit peripheral size - .psize() - .bits8() - // circular mode disabled - .circ() - .clear_bit() - // write to peripheral - .dir() - .set_bit() - }); - - // // Fences and start - // atomic::compiler_fence(Ordering::Release); self.start(); From 3b4daf28495ff85a3c1688a32b2b549c183c6fea Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Tue, 1 Jun 2021 10:10:30 +0200 Subject: [PATCH 07/10] Moved Transfer's non-drop extraction to its own function --- src/dma.rs | 67 +++++++++++++++++++++++++----------------------------- 1 file changed, 31 insertions(+), 36 deletions(-) diff --git a/src/dma.rs b/src/dma.rs index b93e1e3d..01579393 100644 --- a/src/dma.rs +++ b/src/dma.rs @@ -374,6 +374,28 @@ where } } +impl Transfer +where + PAYLOAD: TransferPayload, +{ + pub(crate) fn extract_inner_without_drop(self) -> (BUFFER, PAYLOAD) { + // `Transfer` needs to have a `Drop` implementation, because we accept + // managed buffers that can free their memory on drop. Because of that + // we can't move out of the `Transfer`'s fields, so we use `ptr::read` + // and `mem::forget`. + // + // NOTE(unsafe) There is no panic branch between getting the resources + // and forgetting `self`. + unsafe { + // We cannot use mem::replace as we do not have valid objects to replace with + let buffer = ptr::read(&self.buffer); + let payload = ptr::read(&self.payload); + core::mem::forget(self); + (buffer, payload) + } + } +} + /// Read transfer pub struct R; @@ -426,19 +448,10 @@ macro_rules! rx_tx_channel_mapping { // before the previous statement, which marks the DMA transfer as done atomic::compiler_fence(Ordering::SeqCst); - // `Transfer` needs to have a `Drop` implementation, because we accept + // `Transfer` has a `Drop` implementation because we accept // managed buffers that can free their memory on drop. Because of that - // we can't move out of the `Transfer`'s fields, so we use `ptr::read` - // and `mem::forget`. - // - // NOTE(unsafe) There is no panic branch between getting the resources - // and forgetting `self`. - unsafe { - let buffer = ptr::read(&self.buffer); - let payload = ptr::read(&self.payload); - core::mem::forget(self); - (buffer, payload) - } + // we can't move out of the `Transfer`'s fields directly. + self.extract_inner_without_drop() } } @@ -937,19 +950,10 @@ macro_rules! dma { // before the previous statement, which marks the DMA transfer as done atomic::compiler_fence(Ordering::SeqCst); - // `Transfer` needs to have a `Drop` implementation, because we accept + // `Transfer` has a `Drop` implementation because we accept // managed buffers that can free their memory on drop. Because of that - // we can't move out of the `Transfer`'s fields, so we use `ptr::read` - // and `mem::forget`. - // - // NOTE(unsafe) There is no panic branch between getting the resources - // and forgetting `self`. - unsafe { - let buffer = ptr::read(&self.buffer); - let payload = ptr::read(&self.payload); - core::mem::forget(self); - (buffer, payload) - } + // we can't move out of the `Transfer`'s fields directly. + self.extract_inner_without_drop() } } @@ -975,19 +979,10 @@ macro_rules! dma { // before the previous statement, which marks the DMA transfer as done atomic::compiler_fence(Ordering::SeqCst); - // `Transfer` needs to have a `Drop` implementation, because we accept + // `Transfer` has a `Drop` implementation because we accept // managed buffers that can free their memory on drop. Because of that - // we can't move out of the `Transfer`'s fields, so we use `ptr::read` - // and `mem::forget`. - // - // NOTE(unsafe) There is no panic branch between getting the resources - // and forgetting `self`. - unsafe { - let buffer = ptr::read(&self.buffer); - let payload = ptr::read(&self.payload); - core::mem::forget(self); - (buffer, payload) - } + // we can't move out of the `Transfer`'s fields directly. + self.extract_inner_without_drop() } } From 07b82b0ae43e4f66c1a0dd0fc0b7c161aef70658 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Tue, 1 Jun 2021 10:47:10 +0200 Subject: [PATCH 08/10] Simplify example clock setup to work on more boards --- examples/spi_dma_rxtx.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/examples/spi_dma_rxtx.rs b/examples/spi_dma_rxtx.rs index 34bfb13d..a3fd9835 100644 --- a/examples/spi_dma_rxtx.rs +++ b/examples/spi_dma_rxtx.rs @@ -37,8 +37,6 @@ const APP: () = { rprintln!(" - Clock init"); let clocks = rcc .cfgr - .lse(CrystalBypass::Enable, ClockSecuritySystem::Disable) - .hsi48(true) // For RNG .msi(MsiFreq::RANGE4M) .sysclk(80.mhz()) .freeze(&mut flash.acr, &mut pwr); From 1aacc2c4941598a1bb0b5ac8f46c024b7963cdd4 Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Tue, 1 Jun 2021 19:21:20 +0200 Subject: [PATCH 09/10] Alignment with RM --- src/spi.rs | 110 ++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 92 insertions(+), 18 deletions(-) diff --git a/src/spi.rs b/src/spi.rs index 1d95ba1c..27f221f1 100644 --- a/src/spi.rs +++ b/src/spi.rs @@ -572,63 +572,128 @@ macro_rules! spi_dma { impl dma::TransferPayload for SpiRxDma<$SPIX, PINS, $RX_CH> { fn start(&mut self) { + // Setup DMA channels in accordance with RM 40.4.9, subheading "Communication using + // DMA (direct memory addressing)". + // It is mandatory to follow these steps in order: + // + // 0. SPI disabled during setup. + // 1. Enable DMA Rx buffer in the RXDMAEN bit in the SPI_CR2 register, if DMA Rx is used. + // 2. Enable DMA streams for Tx and Rx in DMA registers, if the streams are used. + // 3. Enable DMA Tx buffer in the TXDMAEN bit in the SPI_CR2 register, if DMA Tx is used. + // 4. Enable the SPI by setting the SPE bit. + self.payload.spi.spi.cr1.modify(|_, w| w.spe().clear_bit()); // 0. self.payload .spi .spi .cr2 - .modify(|_, w| w.rxdmaen().set_bit()); - self.channel.start(); + .modify(|_, w| w.rxdmaen().set_bit()); // 1. + self.channel.start(); // 2. + self.payload.spi.spi.cr1.modify(|_, w| w.spe().set_bit()); // 4. } fn stop(&mut self) { - self.channel.stop(); + // Stop DMA channels in accordance with RM 40.4.9, subheading "Communication using + // DMA (direct memory addressing)". + // It is mandatory to follow these steps in order: + // + // 1. Disable DMA streams for Tx and Rx in the DMA registers, if the streams are used. + // 2. Disable the SPI by following the SPI disable procedure. + // 3. Disable DMA Tx and Rx buffers by clearing the TXDMAEN and RXDMAEN bits in the + // SPI_CR2 register, if DMA Tx and/or DMA Rx are used. + self.channel.stop(); // 1. + self.payload.spi.spi.cr1.modify(|_, w| w.spe().clear_bit()); // 2. self.payload .spi .spi .cr2 - .modify(|_, w| w.rxdmaen().clear_bit()); + .modify(|_, w| w.rxdmaen().clear_bit()); // 3. } } impl dma::TransferPayload for SpiTxDma<$SPIX, PINS, $TX_CH> { fn start(&mut self) { + // Setup DMA channels in accordance with RM 40.4.9, subheading "Communication using + // DMA (direct memory addressing)". + // It is mandatory to follow these steps in order: + // + // 0. SPI disabled during setup. + // 1. Enable DMA Rx buffer in the RXDMAEN bit in the SPI_CR2 register, if DMA Rx is used. + // 2. Enable DMA streams for Tx and Rx in DMA registers, if the streams are used. + // 3. Enable DMA Tx buffer in the TXDMAEN bit in the SPI_CR2 register, if DMA Tx is used. + // 4. Enable the SPI by setting the SPE bit. + self.payload.spi.spi.cr1.modify(|_, w| w.spe().clear_bit()); // 0. + self.channel.start(); // 2. self.payload .spi .spi .cr2 - .modify(|_, w| w.txdmaen().set_bit()); - self.channel.start(); + .modify(|_, w| w.txdmaen().set_bit()); // 3. + self.payload.spi.spi.cr1.modify(|_, w| w.spe().set_bit()); // 4. } fn stop(&mut self) { - self.channel.stop(); + // Stop DMA channels in accordance with RM 40.4.9, subheading "Communication using + // DMA (direct memory addressing)". + // It is mandatory to follow these steps in order: + // + // 1. Disable DMA streams for Tx and Rx in the DMA registers, if the streams are used. + // 2. Disable the SPI by following the SPI disable procedure. + // 3. Disable DMA Tx and Rx buffers by clearing the TXDMAEN and RXDMAEN bits in the + // SPI_CR2 register, if DMA Tx and/or DMA Rx are used. + self.channel.stop(); // 1. + self.payload.spi.spi.cr1.modify(|_, w| w.spe().clear_bit()); // 2. self.payload .spi .spi .cr2 - .modify(|_, w| w.txdmaen().clear_bit()); + .modify(|_, w| w.txdmaen().clear_bit()); // 3. } } impl dma::TransferPayload for SpiRxTxDma<$SPIX, PINS, $RX_CH, $TX_CH> { fn start(&mut self) { + // Setup DMA channels in accordance with RM 40.4.9, subheading "Communication using + // DMA (direct memory addressing)". + // It is mandatory to follow these steps in order: + // + // 0. SPI disabled during setup. + // 1. Enable DMA Rx buffer in the RXDMAEN bit in the SPI_CR2 register, if DMA Rx is used. + // 2. Enable DMA streams for Tx and Rx in DMA registers, if the streams are used. + // 3. Enable DMA Tx buffer in the TXDMAEN bit in the SPI_CR2 register, if DMA Tx is used. + // 4. Enable the SPI by setting the SPE bit. + self.payload.spi.spi.cr1.modify(|_, w| w.spe().clear_bit()); // 0. + self.payload + .spi + .spi + .cr2 + .modify(|_, w| w.rxdmaen().set_bit()); // 1. + self.rx_channel.start(); // 2. + self.tx_channel.start(); // 2. self.payload .spi .spi .cr2 - .modify(|_, w| w.rxdmaen().set_bit().txdmaen().set_bit()); - self.rx_channel.start(); - self.tx_channel.start(); + .modify(|_, w| w.txdmaen().set_bit()); // 3. + self.payload.spi.spi.cr1.modify(|_, w| w.spe().set_bit()); // 4. } fn stop(&mut self) { - self.tx_channel.stop(); - self.rx_channel.stop(); + // Stop DMA channels in accordance with RM 40.4.9, subheading "Communication using + // DMA (direct memory addressing)". + // It is mandatory to follow these steps in order: + // + // 1. Disable DMA streams for Tx and Rx in the DMA registers, if the streams are used. + // 2. Disable the SPI by following the SPI disable procedure. + // 3. Disable DMA Tx and Rx buffers by clearing the TXDMAEN and RXDMAEN bits in the + // SPI_CR2 register, if DMA Tx and/or DMA Rx are used. + self.tx_channel.stop(); // 1. + self.rx_channel.stop(); // 1. + self.payload.spi.spi.cr1.modify(|_, w| w.spe().clear_bit()); // 2. self.payload .spi .spi .cr2 - .modify(|_, w| w.rxdmaen().clear_bit().txdmaen().clear_bit()); + .modify(|_, w| w.rxdmaen().clear_bit().txdmaen().clear_bit()); // 3. } } @@ -637,11 +702,14 @@ macro_rules! spi_dma { B: StaticWriteBuffer, { fn read(mut self, mut buffer: B) -> dma::Transfer { + // Setup DMA channels in accordance with RM 40.4.9, subheading "Communication using + // DMA (direct memory addressing)" + // NOTE(unsafe) We own the buffer now and we won't call other `&mut` on it // until the end of the transfer. let (ptr, len) = unsafe { buffer.static_write_buffer() }; - // Setup RX channel + // Setup RX channel addresses and length self.channel.set_memory_address(ptr as u32, true); self.channel.set_transfer_length(len as u16); @@ -658,11 +726,14 @@ macro_rules! spi_dma { B: StaticReadBuffer, { fn write(mut self, buffer: B) -> dma::Transfer { + // Setup DMA channels in accordance with RM 40.4.9, subheading "Communication using + // DMA (direct memory addressing)" + // NOTE(unsafe) We own the buffer now and we won't call other `&mut` on it // until the end of the transfer. let (ptr, len) = unsafe { buffer.static_read_buffer() }; - // Setup TX channel + // Setup TX channel addresses and length self.channel.set_memory_address(ptr as u32, true); self.channel.set_transfer_length(len as u16); @@ -679,17 +750,20 @@ macro_rules! spi_dma { B: StaticWriteBuffer, { fn transfer(mut self, mut buffer: B) -> dma::Transfer { + // Setup DMA channels in accordance with RM 40.4.9, subheading "Communication using + // DMA (direct memory addressing)" + // Transfer: we use the same buffer for RX and TX // NOTE(unsafe) We own the buffer now and we won't call other `&mut` on it // until the end of the transfer. let (ptr, len) = unsafe { buffer.static_write_buffer() }; - // Setup RX channel + // Setup RX channel addresses and length self.rx_channel.set_memory_address(ptr as u32, true); self.rx_channel.set_transfer_length(len as u16); - // Setup TX channel + // Setup TX channel addresses and length self.tx_channel.set_memory_address(ptr as u32, true); self.tx_channel.set_transfer_length(len as u16); From ebb37d68a6479cff485f690865964560434a6a4f Mon Sep 17 00:00:00 2001 From: Emil Fresk Date: Sun, 20 Jun 2021 20:58:14 +0200 Subject: [PATCH 10/10] Silence warnings --- examples/lptim_rtic.rs | 2 +- examples/spi_dma_rxtx.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/lptim_rtic.rs b/examples/lptim_rtic.rs index 832de752..0d0770c4 100644 --- a/examples/lptim_rtic.rs +++ b/examples/lptim_rtic.rs @@ -71,7 +71,7 @@ const APP: () = { } #[task(binds = LPTIM1, resources = [lptim, led])] - fn timer_tick(mut ctx: timer_tick::Context) { + fn timer_tick(ctx: timer_tick::Context) { let timer_tick::Resources { lptim, led } = ctx.resources; if lptim.is_event_triggered(Event::AutoReloadMatch) { lptim.clear_event_flag(Event::AutoReloadMatch); diff --git a/examples/spi_dma_rxtx.rs b/examples/spi_dma_rxtx.rs index a3fd9835..bacf8915 100644 --- a/examples/spi_dma_rxtx.rs +++ b/examples/spi_dma_rxtx.rs @@ -10,7 +10,7 @@ use stm32l4xx_hal::{ gpio::{Speed, State as PinState}, hal::spi::{Mode, Phase, Polarity}, prelude::*, - rcc::{ClockSecuritySystem, CrystalBypass, MsiFreq}, + rcc::MsiFreq, spi::Spi, };