From 2f0732ba916df83234cb7db5094598f9ac55afdf Mon Sep 17 00:00:00 2001 From: Justin Beaurivage Date: Mon, 8 Jan 2024 15:18:59 -0500 Subject: [PATCH] Document async sercom implementations --- hal/src/sercom/dma.rs | 8 ++++--- hal/src/sercom/i2c.rs | 20 ++++++++++++++++ hal/src/sercom/i2c/async_api.rs | 27 ++++++++------------- hal/src/sercom/mod.rs | 2 +- hal/src/sercom/spi.rs | 27 +++++++++++++++++++++ hal/src/sercom/spi/async_api.rs | 40 +++++++++++++++++++------------- hal/src/sercom/uart.rs | 35 +++++++++++++++++++++++----- hal/src/sercom/uart/async_api.rs | 27 +++++++++++++-------- 8 files changed, 133 insertions(+), 53 deletions(-) diff --git a/hal/src/sercom/dma.rs b/hal/src/sercom/dma.rs index 809a1701475..7c3ecf48ccb 100644 --- a/hal/src/sercom/dma.rs +++ b/hal/src/sercom/dma.rs @@ -22,8 +22,10 @@ use crate::{ // I2C DMA transfers //============================================================================= -/// Token type representing an [`I2c`](super::i2c::I2c) for which the bus is -/// ready to start a transaction. For use with +/// Token type representing an [`I2c`] for which the bus is +/// ready to start a transaction. +/// +/// For use with /// [`send_with_dma`](super::i2c::I2c::send_with_dma) and /// [`receive_with_dma`](super::i2c::I2c::send_with_dma). pub struct I2cBusReady; @@ -59,7 +61,7 @@ impl I2c { /// # fn init_transfer>(i2c: I2c, chan0: C, buf_src: &'static mut [u8]){ /// // Assume `i2c` is a fully configured `I2c`, and `chan0` a fully configured `dmac::Channel`. /// let token = i2c.init_dma_transfer()?; - /// i2c.send_with_dma(ADDRESS, token, buf_src, chan0, |_| {}); + /// i2c.send_with_dma(ADDRESS, token, buf_src, chan0); /// # } /// ``` /// diff --git a/hal/src/sercom/i2c.rs b/hal/src/sercom/i2c.rs index b9d1f46d2f1..530d30b647b 100644 --- a/hal/src/sercom/i2c.rs +++ b/hal/src/sercom/i2c.rs @@ -254,6 +254,26 @@ fn i2c_send_with_dma>(i2c: I2c, c " )] +//! # `async` operation +//! +//! When the `async` Cargo feature is enabled, an [`I2c`] can be used for +//! `async` operations. Configuring an [`I2c`] in async mode is relatively +//! simple: +//! +//! * Bind the corresponding `SERCOM` interrupt source to the SPI +//! [`InterruptHandler`] (refer to the module-level [`async_hal`] +//! documentation for more information). +//! * Turn a previously configured [`I2c`] into an [`I2cFuture`] by calling +//! [`I2c::into_future`] +//! * Optionally, add a DMA channel by using [`I2cFuture::with_dma_channel`]. +//! The API is exactly the same whether a DMA channel is used or not. +//! * Use the provided async methods for reading or writing to the I2C +//! peripheral. [`I2cFuture`] implements [`embedded_hal_async::i2c::I2c`]. +//! +//! `I2cFuture` implements `AsRef` and `AsMut` so +//! that it can be reconfigured using the regular [`I2c`] methods. +//! +//! [`async_hal`]: crate::async_hal #[cfg(feature = "thumbv6")] #[path = "i2c/pads_thumbv6m.rs"] diff --git a/hal/src/sercom/i2c/async_api.rs b/hal/src/sercom/i2c/async_api.rs index c3a23c8d6d8..e1200e73e99 100644 --- a/hal/src/sercom/i2c/async_api.rs +++ b/hal/src/sercom/i2c/async_api.rs @@ -67,8 +67,9 @@ where } #[cfg(feature = "dma")] -/// Convenience type for a [`I2cFuture`] in DMA -/// mode. The type parameter `I` represents the DMA channel ID (`ChX`). +/// Convenience type for a [`I2cFuture`] in DMA mode. +/// +/// The type parameter `I` represents the DMA channel ID (`ChX`). pub type I2cFutureDma = I2cFuture>; impl I2cFuture @@ -174,9 +175,10 @@ where Ok(()) } - /// Asynchronously write from a buffer, then read into a buffer. This is an - /// extremely common pattern: writing a register address, then - /// read its value from the slave. + /// Asynchronously write from a buffer, then read into a buffer. + /// + /// This is an extremely common pattern: for example, writing a register + /// address, then read its value from the slave. #[inline] pub async fn write_read( &mut self, @@ -195,17 +197,6 @@ where } } -// impl Drop for I2cFuture -// where -// C: AnyConfig, -// N: InterruptNumber, -// { -// #[inline] -// fn drop(&mut self) { -// cortex_m::peripheral::NVIC::mask(self.irq_number); -// } -// } - impl AsRef> for I2cFuture where C: AnyConfig, @@ -394,7 +385,9 @@ mod dma { } /// Asynchronously write from a buffer, then read into a buffer, all - /// using DMA. This is an extremely common pattern: writing a + /// using DMA. + /// + /// This is an extremely common pattern: for example, writing a /// register address, then read its value from the slave. #[inline] pub async fn write_read( diff --git a/hal/src/sercom/mod.rs b/hal/src/sercom/mod.rs index a9411271cf9..2f08f94a412 100644 --- a/hal/src/sercom/mod.rs +++ b/hal/src/sercom/mod.rs @@ -86,7 +86,7 @@ pub trait Sercom: Sealed + Deref { fn enable_apb_clock(&mut self, ctrl: &APB_CLK_CTRL); /// Get a reference to the sercom from a - /// [`Peripherals`](crate::pac::Peripherals) block + /// [`Peripherals`] block fn reg_block(peripherals: &mut Peripherals) -> &crate::pac::sercom0::RegisterBlock; /// Get a reference to this [`Sercom`]'s associated RX Waker diff --git a/hal/src/sercom/spi.rs b/hal/src/sercom/spi.rs index 27cbd20ebe5..b804a04483b 100644 --- a/hal/src/sercom/spi.rs +++ b/hal/src/sercom/spi.rs @@ -311,6 +311,27 @@ let (chan0, _, spi, _) = dma_transfer.wait(); [`dmac`]: crate::dmac " )] +//! # `async` operation +//! +//! When the `async` Cargo feature is enabled, a [`Spi`] can be used for +//! `async` operations. Configuring a [`Spi`] in async mode is relatively +//! simple: +//! +//! * Bind the corresponding `SERCOM` interrupt source to the SPI +//! [`InterruptHandler`] (refer to the module-level [`async_hal`] +//! documentation for more information). +//! * Turn a previously configured [`Spi`] into a [`SpiFuture`] by calling +//! [`Spi::into_future`] +//! * Optionally, add DMA channels to RX, TX or both using +//! [`SpiFuture::with_rx_dma_channel`] and [`SpiFuture::with_tx_dma_channel`]. +//! The API is exactly the same whether DMA channels are used or not. +//! * Use the provided async methods for reading or writing to the SPI +//! peripheral. [`SpiFuture`] implements [`embedded_hal_async::spi::SpiBus`]. +//! +//! `SpiFuture` implements `AsRef` and `AsMut` so +//! that it can be reconfigured using the regular [`Spi`] methods. +//! +//! [`async_hal`]: crate::async_hal use core::convert::TryFrom; use core::marker::PhantomData; @@ -758,6 +779,8 @@ where /// Obtain a reference to the PAC `SERCOM` struct /// + /// # Safety + /// /// Directly accessing the `SERCOM` could break the invariants of the /// type-level tracking in this module, so it is unsafe. #[inline] @@ -1278,6 +1301,8 @@ where /// Read from the DATA register /// + /// # Safety + /// /// Reading from the data register directly is `unsafe`, because it will /// clear the RXC flag, which could break assumptions made elsewhere in /// this module. @@ -1288,6 +1313,8 @@ where /// Write to the DATA register /// + /// # Safety + /// /// Writing to the data register directly is `unsafe`, because it will clear /// the DRE flag, which could break assumptions made elsewhere in this /// module. diff --git a/hal/src/sercom/spi/async_api.rs b/hal/src/sercom/spi/async_api.rs index 0d4d45e16ab..cb379f75efa 100644 --- a/hal/src/sercom/spi/async_api.rs +++ b/hal/src/sercom/spi/async_api.rs @@ -51,10 +51,12 @@ where A: Capability, S: Sercom, { - /// Turn an [`Spi`] into a [`SpiFuture`]. In cases where the underlying - /// [`Spi`] is [`Duplex`], reading words need to be accompanied with sending - /// a no-op word. By default it is set to 0x00, but you can configure it - /// by using the [`nop_word`](SpiFuture::nop_word) method. + /// Turn an [`Spi`] into a [`SpiFuture`]. + /// + /// In cases where the underlying [`Spi`] is [`Duplex`], reading words need + /// to be accompanied with sending a no-op word. By default it is set to + /// 0x00, but you can configure it by using the + /// [`nop_word`](SpiFuture::nop_word) method. #[inline] pub fn into_future(self, _interrupts: I) -> SpiFuture where @@ -110,7 +112,9 @@ pub type SpiFutureTx = SpiFuture; #[cfg(feature = "dma")] /// Convenience type for a [`SpiFuture`] with RX and TX capabilities in DMA -/// mode. The type parameter `R` represents the RX DMA channel ID (`ChX`), and +/// mode. +/// +/// The type parameter `R` represents the RX DMA channel ID (`ChX`), and /// `T` represents the TX DMA channel ID. pub type SpiFutureDuplexDma = SpiFuture< C, @@ -120,14 +124,16 @@ pub type SpiFutureDuplexDma = SpiFuture< >; #[cfg(feature = "dma")] -/// Convenience type for a [`SpiFuture`] with RX capabilities in DMA mode. The -/// type parameter `R` represents the RX DMA channel ID (`ChX`). +/// Convenience type for a [`SpiFuture`] with RX capabilities in DMA mode. +/// +/// The type parameter `R` represents the RX DMA channel ID (`ChX`). pub type SpiFutureRxDma = SpiFuture, NoneT>; #[cfg(feature = "dma")] -/// Convenience type for a [`SpiFuture`] with TX capabilities in DMA mode. The -/// type parameter `T` represents the TX DMA channel ID (`ChX`). +/// Convenience type for a [`SpiFuture`] with TX capabilities in DMA mode. +/// +/// The type parameter `T` represents the TX DMA channel ID (`ChX`). pub type SpiFutureTxDma = SpiFuture>; @@ -187,10 +193,11 @@ where DataWidth: AsPrimitive, S: Sercom, { - /// Read words into a buffer asynchronously, word by word. Since we are - /// using a [`Duplex`] [`SpiFuture`], we need to send a word simultaneously - /// while receiving one. This `no-op` word is configurable via the - /// [`nop_word`](Self::nop_word) method. + /// Read words into a buffer asynchronously, word by word. + /// + /// Since we are using a [`Duplex`] [`SpiFuture`], we need to send a word + /// simultaneously while receiving one. This `no-op` word is + /// configurable via the [`nop_word`](Self::nop_word) method. #[inline] pub async fn read(&mut self, buffer: &mut [C::Word]) -> Result<(), Error> { for byte in buffer.iter_mut() { @@ -235,9 +242,10 @@ where Ok(word) } - /// Perform a transfer, word by word. No-op words will be written if `read` - /// is longer than `write`. Extra words are ignored if `write` is longer - /// than `read`. + /// Perform a transfer, word by word. + /// + /// No-op words will be written if `read` is longer than `write`. Extra + /// words are ignored if `write` is longer than `read`. async fn transfer_word_by_word( &mut self, read: &mut [C::Word], diff --git a/hal/src/sercom/uart.rs b/hal/src/sercom/uart.rs index ba15429f27e..7bea3d24be9 100644 --- a/hal/src/sercom/uart.rs +++ b/hal/src/sercom/uart.rs @@ -386,6 +386,30 @@ let (chan1, rx, rx_buffer) = rx_dma.wait(); " )] +//! # `async` operation +//! +//! When the `async` Cargo feature is enabled, a [`Uart`] can be used for +//! `async` operations. Configuring a [`Uart`] in async mode is relatively +//! simple: +//! +//! * Bind the corresponding `SERCOM` interrupt source to the UART +//! [`InterruptHandler`] (refer to the module-level [`async_hal`] +//! documentation for more information). +//! * Turn a previously configured [`Uart`] into a [`UartFuture`] by calling +//! [`Uart::into_future`] +//! * Optionally, add DMA channels to RX, TX or both using +//! [`UartFuture::with_rx_dma_channel`] and +//! [`UartFuture::with_tx_dma_channel`]. The API is exactly the same whether +//! DMA channels are used or not. +//! * Use the provided async methods for reading or writing to the UART +//! peripheral. +//! +//! `UartFuture` implements `AsRef` and `AsMut` so +//! that it can be reconfigured using the regular [`Uart`] methods. It also +//! exposes a [`split`](UartFuture::split) method to split it into its RX and TX +//! parts. +//! +//! [`async_hal`]: crate::async_hal #[cfg(feature = "thumbv6")] #[path = "uart/pads_thumbv6m.rs"] @@ -943,13 +967,12 @@ where #[inline] pub fn flush_rx_buffer(&mut self) { // TODO Is this a hardware bug??? - /* - usart.ctrlb.modify(|_, w| w.rxen().clear_bit()); - while usart.syncbusy.read().ctrlb().bit() || usart.ctrlb.read().rxen().bit_is_set() {} - usart.ctrlb.modify(|_, w| w.rxen().set_bit()); - while usart.syncbusy.read().ctrlb().bit() || usart.ctrlb.read().rxen().bit_is_clear() {} - */ + // usart.ctrlb.modify(|_, w| w.rxen().clear_bit()); + // while usart.syncbusy.read().ctrlb().bit() || usart.ctrlb.read().rxen().bit_is_set() {} + + // usart.ctrlb.modify(|_, w| w.rxen().set_bit()); + // while usart.syncbusy.read().ctrlb().bit() || usart.ctrlb.read().rxen().bit_is_clear() {} for _ in 0..=2 { let _data = unsafe { self.config.as_mut().registers.read_data() }; diff --git a/hal/src/sercom/uart/async_api.rs b/hal/src/sercom/uart/async_api.rs index 438bbf6cdb8..95fc520c5c7 100644 --- a/hal/src/sercom/uart/async_api.rs +++ b/hal/src/sercom/uart/async_api.rs @@ -54,8 +54,8 @@ where S: Sercom, { /// Turn a [`Uart`] into a [`UartFuture`]. This method is only available for - /// [`Uart`]s which have a [`Tx`](crate::sercom::uart::Tx), - /// [`Rx`](crate::sercom::uart::Rx) or [`Duplex`] [`Capability`]. + /// [`Uart`]s which have a [`Tx`], + /// [`Rx`] or [`Duplex`] [`Capability`]. #[inline] pub fn into_future(self, _interrupts: I) -> UartFuture where @@ -84,9 +84,9 @@ where } } -/// `async` version of [`Uart`]. +/// `async` version of a [`Uart`]. /// -/// Create this struct by calling [`I2c::into_future`](I2c::into_future). +/// Create this struct by calling [`Uart::into_future`](Uart::into_future). pub struct UartFuture where C: ValidConfig, @@ -114,7 +114,9 @@ pub type UartFutureTxDuplex = UartFuture; #[cfg(feature = "dma")] /// Convenience type for a [`UartFuture`] with RX and TX capabilities in DMA -/// mode. The type parameter `R` represents the RX DMA channel ID (`ChX`), and +/// mode. +/// +/// The type parameter `R` represents the RX DMA channel ID (`ChX`), and /// `T` represents the TX DMA channel ID. pub type UartFutureDuplexDma = UartFuture< C, @@ -125,24 +127,28 @@ pub type UartFutureDuplexDma = UartFuture< #[cfg(feature = "dma")] /// Convenience type for a RX-only [`UartFuture`] in DMA mode. +/// /// The type parameter `R` represents the RX DMA channel ID (`ChX`). pub type UartFutureRxDma = UartFuture, NoneT>; #[cfg(feature = "dma")] /// Convenience type for the RX half of a [`Duplex`] [`UartFuture`] in DMA mode. +/// /// The type parameter `R` represents the RX DMA channel ID (`ChX`). pub type UartFutureRxDuplexDma = UartFuture, NoneT>; #[cfg(feature = "dma")] /// Convenience type for a TX-only [`UartFuture`] in DMA mode. +/// /// The type parameter `T` represents the TX DMA channel ID (`ChX`). pub type UartFutureTxDma = UartFuture>; #[cfg(feature = "dma")] /// Convenience type for the TX half of a [`Duplex`] [`UartFuture`] in DMA mode. +/// /// The type parameter `T` represents the TX DMA channel ID (`ChX`). pub type UartFutureTxDuplexDma = UartFuture>; @@ -246,7 +252,7 @@ where S: Sercom, DataReg: AsPrimitive, { - /// Add a DMA channel for receiving words + /// Use a DMA channel for receiving words on the RX line #[cfg(feature = "dma")] #[inline] pub fn with_rx_dma_channel>( @@ -277,9 +283,10 @@ where DataReg: AsPrimitive, { /// Read the specified number of [`Word`](crate::sercom::uart::Word)s into a - /// buffer, word by word. In case of an error, returns `Err(Error, usize)` - /// where the `usize` represents the number of valid words read before - /// the error occured. + /// buffer, word by word. + /// + /// In case of an error, returns `Err(Error, usize)` where the `usize` + /// represents the number of valid words read before the error occured. #[inline] pub async fn read(&mut self, buffer: &mut [C::Word]) -> Result<(), (Error, usize)> { for (i, word) in buffer.iter_mut().enumerate() { @@ -302,7 +309,7 @@ where D: Transmit, S: Sercom, { - /// Add a DMA channel for sending words + /// Use a DMA channel for sending words on the TX line #[cfg(feature = "dma")] #[inline] pub fn with_tx_dma_channel>(