Skip to content

Commit

Permalink
Adding SPI Master support
Browse files Browse the repository at this point in the history
- Includes PDC and basic SPI register/blocking modes
- Support for 8-16 bit transfer widths
- Fixed and Variable CS control support
- Includes PDC fixes to support Tx
  * Added traits to simplify ADC PDC usage
- Does not include support for SPI transactions
  (no technical reason, I don't need them as Variable payload DMA is more flexible)
  https://docs.rs/embedded-hal/0.2.6/embedded_hal/blocking/spi/trait.Transactional.html)
  • Loading branch information
haata authored and john-terrell committed Dec 15, 2021
1 parent 109262d commit 6234d32
Show file tree
Hide file tree
Showing 4 changed files with 1,153 additions and 67 deletions.
8 changes: 7 additions & 1 deletion src/adc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -852,7 +852,7 @@ impl Channel<ADC> for TempSensor {
}

// Setup PDC Rx functionality
pdc_rx! { Adc: adc }
pdc_rx! { Adc: adc, isr }

impl Adc {
/// Configures the ADC with PDC in single sequence mode
Expand Down Expand Up @@ -1075,7 +1075,13 @@ where

Transfer::w(buffer, self)
}
}

impl<B, MODE> ReadDmaPaused<B, u16> for AdcDma<MODE>
where
Self: TransferPayload,
B: StaticWriteBuffer<Word = u16>,
{
/// Assigns the buffer, prepares PDC but does not enable the PDC
/// Useful when there is strict timing on when the ADC conversion should start
///
Expand Down
2 changes: 1 addition & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,11 @@ pub mod clock;
pub mod delay;
pub mod efc;
pub mod gpio;
#[cfg(feature = "atsam4s")]
pub mod pdc;
pub mod prelude;
pub mod rtt;
pub mod serial;
pub mod spi;
pub mod static_memory_controller;
pub mod timer;
#[cfg(all(feature = "usb", any(feature = "atsam4e", feature = "atsam4s")))]
Expand Down
210 changes: 145 additions & 65 deletions src/pdc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ pub struct RxDma<PAYLOAD> {
pub(crate) payload: PAYLOAD,
}

/// DMA Transmitter
pub struct TxDma<PAYLOAD> {
pub(crate) payload: PAYLOAD,
}

/// DMA Receiver/Transmitter
pub struct RxTxDma<PAYLOAD> {
pub(crate) payload: PAYLOAD,
}

pub trait Receive {
type TransmittedWord;
}
Expand All @@ -35,6 +45,14 @@ where
Self: core::marker::Sized + TransferPayload,
{
fn read(self, buffer: B) -> Transfer<W, B, Self>;
}

/// Trait for DMA readings from peripheral to memory, start paused.
pub trait ReadDmaPaused<B, RS>: Receive
where
B: StaticWriteBuffer<Word = RS>,
Self: core::marker::Sized + TransferPayload,
{
fn read_paused(self, buffer: B) -> Transfer<W, B, Self>;
}

Expand All @@ -48,7 +66,7 @@ where
}

/// Trait for DMA simultaneously reading and writing within one synchronous operation. Panics if both buffers are not of equal length.
pub trait ReadWriteDma<RXB, TXB, TS>: Transmit
pub trait ReadWriteDma<RXB, TXB, TS>: Transmit + Receive
where
RXB: StaticWriteBuffer<Word = TS>,
TXB: StaticReadBuffer<Word = TS>,
Expand All @@ -57,6 +75,23 @@ where
fn read_write(self, rx_buffer: RXB, tx_buffer: TXB) -> Transfer<W, (RXB, TXB), Self>;
}

/// Trait for manually specifying the DMA length used, even if the buffer is larger
/// Panics if the buffer(s) are too small
pub trait ReadWriteDmaLen<RXB, TXB, TS>: Transmit + Receive
where
RXB: StaticWriteBuffer<Word = TS>,
TXB: StaticReadBuffer<Word = TS>,
Self: core::marker::Sized + TransferPayload,
{
fn read_write_len(
self,
rx_buffer: RXB,
rx_buf_len: usize,
tx_buffer: TXB,
tx_buf_len: usize,
) -> Transfer<W, (RXB, TXB), Self>;
}

pub trait TransferPayload {
fn start(&mut self);
fn stop(&mut self);
Expand All @@ -72,7 +107,6 @@ where
payload: PAYLOAD,
}

/* TODO Needed for pdc_tx
impl<BUFFER, PAYLOAD> Transfer<R, BUFFER, PAYLOAD>
where
PAYLOAD: TransferPayload,
Expand All @@ -85,7 +119,6 @@ where
}
}
}
*/

impl<BUFFER, PAYLOAD> Transfer<W, BUFFER, PAYLOAD>
where
Expand All @@ -110,57 +143,67 @@ where
}
}

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

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

atomic::compiler_fence(Ordering::Acquire);
macro_rules! pdc_transfer {
(
$DmaType:ident
) => {
impl<BUFFER, PAYLOAD, MODE> Transfer<MODE, BUFFER, $DmaType<PAYLOAD>>
where
$DmaType<PAYLOAD>: TransferPayload,
{
pub fn is_done(&self) -> bool {
!self.payload.in_progress()
}

self.payload.stop();
pub fn wait(mut self) -> (BUFFER, $DmaType<PAYLOAD>) {
while !self.is_done() {}

atomic::compiler_fence(Ordering::Acquire);

self.payload.stop();

// we need a read here to make the Acquire fence effective
// we do *not* need this if `dma.stop` does a RMW operation
unsafe {
ptr::read_volatile(&0);
}

// we need a fence here for the same reason we need one in `Transfer.wait`
atomic::compiler_fence(Ordering::Acquire);

// `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);
mem::forget(self);
(buffer, payload)
}
}

// we need a read here to make the Acquire fence effective
// we do *not* need this if `dma.stop` does a RMW operation
unsafe {
ptr::read_volatile(&0);
}
pub fn pause(&mut self) {
self.payload.stop();
}

// we need a fence here for the same reason we need one in `Transfer.wait`
atomic::compiler_fence(Ordering::Acquire);

// `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);
mem::forget(self);
(buffer, payload)
pub fn resume(&mut self) {
self.payload.start();
}
}
}

pub fn pause(&mut self) {
self.payload.stop();
}

pub fn resume(&mut self) {
self.payload.start();
}
};
}

pdc_transfer!(RxDma);
pdc_transfer!(TxDma);
pdc_transfer!(RxTxDma);

macro_rules! pdc_rx {
(
$Periph:ident: $periph:ident
$Periph:ident: $periph:ident, $isr:ident
) => {
impl $Periph {
/// Sets the PDC receive address pointer
Expand Down Expand Up @@ -209,7 +252,7 @@ macro_rules! pdc_rx {
/// Returns `true` if DMA is still in progress
/// Uses rxbuff, which checks both receive and receive next counters to see if they are 0
pub fn rx_in_progress(&self) -> bool {
!self.$periph.isr.read().rxbuff().bit()
!self.$periph.$isr.read().rxbuff().bit()
}

/// Enable ENDRX (End of Receive) interrupt
Expand Down Expand Up @@ -238,48 +281,51 @@ macro_rules! pdc_rx {
}
pub(crate) use pdc_rx;

/* TODO Commenting out until first usage (likely SPI)
macro_rules! pdc_tx {
(
$Periph:ident: $periph:ident
$Periph:ident: $periph:ident, $isr:ident
) => {
impl $Periph {
/// Sets the PDC transmit address pointer
pub fn set_transmit_address(&mut self, address: u32) {
self.$periph
.rpr
.tpr
.write(|w| unsafe { w.txptr().bits(address) });
}

/// Sets the transmit increment counter
/// Will increment by the count * size of the peripheral data
pub fn set_transmit_counter(&mut self, count: u16) {
self.$periph.rcr.write(|w| unsafe { w.txctr().bits(count) });
self.$periph.tcr.write(|w| unsafe { w.txctr().bits(count) });
}

/// Sets the PDC transmit next address pointer
pub fn set_transmit_next_address(&mut self, address: u32) {
self.$periph
.rnpr
.tnpr
.write(|w| unsafe { w.txnptr().bits(address) });
}

/// Sets the transmit next increment counter
/// Will increment by the count * size of the peripheral data
pub fn set_transmit_next_counter(&mut self, count: u16) {
self.$periph
.rncr
.tncr
.write(|w| unsafe { w.txnctr().bits(count) });
}

/// Starts the PDC transfer
pub fn start_tx_pdc(&mut self) {
self.$periph.ptcr.write_with_zero(|w| w.txten().set_bit());
unsafe {
self.$periph.ptcr.write_with_zero(|w| w.txten().set_bit());
}
}

/// Stops the PDC transfer
pub fn stop_tx_pdc(&mut self) {
self.$periph.ptcr.write_with_zero(|w| w.txtdis().set_bit());
unsafe {
self.$periph.ptcr.write_with_zero(|w| w.txtdis().set_bit());
}
}

/// Returns `true` if the PDC is active and may be receiving data
Expand All @@ -290,32 +336,66 @@ macro_rules! pdc_tx {
/// Returns `true` if DMA is still in progress
/// Uses rxbuff, which checks both transmit and transmit next counters to see if they are 0
pub fn tx_in_progress(&self) -> bool {
!self.$periph.isr.read().txbufe().bit()
!self.$periph.$isr.read().txbufe().bit()
}

/// Enable ENDRX (End of Transmit) interrupt
/// Triggered when RCR reaches 0
pub fn enable_endrx_interrupt(&mut self) {
self.$periph.ier.write_with_zero(|w| w.endtx().set_bit());
pub fn enable_endtx_interrupt(&mut self) {
unsafe {
self.$periph.ier.write_with_zero(|w| w.endtx().set_bit());
}
}

/// Disable ENDRX (End of Transmit) interrupt
pub fn disable_endrx_interrupt(&mut self) {
self.$periph.idr.write_with_zero(|w| w.endtx().set_bit());
pub fn disable_endtx_interrupt(&mut self) {
unsafe {
self.$periph.idr.write_with_zero(|w| w.endtx().set_bit());
}
}

/// Enable RXBUFF (Transmit Buffer Full) interrupt
/// Triggered when RCR and RNCR reach 0
pub fn enable_rxbuff_interrupt(&mut self) {
self.$periph.ier.write_with_zero(|w| w.txbufe().set_bit());
pub fn enable_txbufe_interrupt(&mut self) {
unsafe {
self.$periph.ier.write_with_zero(|w| w.txbufe().set_bit());
}
}

/// Disable RXBUFF (Transmit Buffer Full) interrupt
pub fn disable_rxbuff_interrupt(&mut self) {
self.$periph.idr.write_with_zero(|w| w.txbufe().set_bit());
pub fn disable_txbufe_interrupt(&mut self) {
unsafe {
self.$periph.idr.write_with_zero(|w| w.txbufe().set_bit());
}
}
}
};
}
pub(crate) use pdc_tx;
*/

macro_rules! pdc_rxtx {
(
$Periph:ident: $periph:ident
) => {
impl $Periph {
/// Starts the PDC transfer (rx+tx)
pub fn start_rxtx_pdc(&mut self) {
unsafe {
self.$periph
.ptcr
.write_with_zero(|w| w.txten().set_bit().rxten().set_bit());
}
}

/// Stops the PDC transfer (rx+tx)
pub fn stop_rxtx_pdc(&mut self) {
unsafe {
self.$periph
.ptcr
.write_with_zero(|w| w.txtdis().set_bit().rxtdis().set_bit());
}
}
}
};
}
pub(crate) use pdc_rxtx;
Loading

0 comments on commit 6234d32

Please sign in to comment.