From b6764ecd574189b53dfa32a2efcb6051d6aed89a Mon Sep 17 00:00:00 2001 From: Dario Nieuwenhuis Date: Tue, 14 Mar 2023 21:49:14 +0100 Subject: [PATCH] spi: SpiDevice transactiontake an operation slice instead of a closure. --- embedded-hal-async/src/spi.rs | 477 ++++++++++++++++++++-------------- embedded-hal-bus/src/spi.rs | 107 +++++++- embedded-hal/src/spi.rs | 207 ++++++++++----- 3 files changed, 513 insertions(+), 278 deletions(-) diff --git a/embedded-hal-async/src/spi.rs b/embedded-hal-async/src/spi.rs index a03f2f674..4d215c16a 100644 --- a/embedded-hal-async/src/spi.rs +++ b/embedded-hal-async/src/spi.rs @@ -1,205 +1,125 @@ //! SPI master mode traits. -use core::{fmt::Debug, future::Future}; +use core::fmt::Debug; use embedded_hal::digital::OutputPin; use embedded_hal::spi as blocking; pub use embedded_hal::spi::{ - Error, ErrorKind, ErrorType, Mode, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3, + Error, ErrorKind, ErrorType, Mode, Operation, Phase, Polarity, MODE_0, MODE_1, MODE_2, MODE_3, }; -#[macro_export] -/// Do an SPI transaction on a bus. -/// This is a safe wrapper for [SpiDevice::transaction], which handles dereferencing the raw pointer for you. +/// SPI read-only device trait /// -/// # Examples -/// -/// ``` -/// use embedded_hal_async::spi::{transaction, SpiBus, SpiBusRead, SpiBusWrite, SpiDevice}; +/// `SpiDeviceRead` represents ownership over a single SPI device on a (possibly shared) bus, selected +/// with a CS (Chip Select) pin. /// -/// pub async fn transaction_example(mut device: SPI) -> Result -/// where -/// SPI: SpiDevice, -/// SPI::Bus: SpiBus, -/// { -/// transaction!(&mut device, move |bus| async move { -/// // Unlike `SpiDevice::transaction`, we don't need to -/// // manually dereference a pointer in order to use the bus. -/// bus.write(&[42]).await?; -/// let mut data = [0; 4]; -/// bus.read(&mut data).await?; -/// Ok(u32::from_be_bytes(data)) -/// }) -/// .await -/// } -/// ``` +/// See (the docs on embedded-hal)[embedded_hal::spi] for important information on SPI Bus vs Device traits. /// -/// Note that the compiler will prevent you from moving the bus reference outside of the closure -/// ```compile_fail -/// # use embedded_hal_async::spi::{transaction, SpiBus, SpiBusRead, SpiBusWrite, SpiDevice}; -/// # -/// # pub async fn smuggle_test(mut device: SPI) -> Result<(), SPI::Error> -/// # where -/// # SPI: SpiDevice, -/// # SPI::Bus: SpiBus, -/// # { -/// let mut bus_smuggler: Option<&mut SPI::Bus> = None; -/// transaction!(&mut device, move |bus| async move { -/// bus_smuggler = Some(bus); -/// Ok(()) -/// }) -/// .await -/// # } -/// ``` -macro_rules! spi_transaction { - ($device:expr, move |$bus:ident| async move $closure_body:expr) => { - $crate::spi::SpiDevice::transaction($device, move |$bus| { - // Safety: Implementers of the `SpiDevice` trait guarantee that the pointer is - // valid and dereferencable for the entire duration of the closure. - let $bus = unsafe { &mut *$bus }; - async move { - let result = $closure_body; - let $bus = $bus; // Ensure that the bus reference was not moved out of the closure - let _ = $bus; // Silence the "unused variable" warning from prevous line - result - } - }) - }; - ($device:expr, move |$bus:ident| async $closure_body:expr) => { - $crate::spi::SpiDevice::transaction($device, move |$bus| { - // Safety: Implementers of the `SpiDevice` trait guarantee that the pointer is - // valid and dereferencable for the entire duration of the closure. - let $bus = unsafe { &mut *$bus }; - async { - let result = $closure_body; - let $bus = $bus; // Ensure that the bus reference was not moved out of the closure - let _ = $bus; // Silence the "unused variable" warning from prevous line - result - } - }) - }; - ($device:expr, |$bus:ident| async move $closure_body:expr) => { - $crate::spi::SpiDevice::transaction($device, |$bus| { - // Safety: Implementers of the `SpiDevice` trait guarantee that the pointer is - // valid and dereferencable for the entire duration of the closure. - let $bus = unsafe { &mut *$bus }; - async move { - let result = $closure_body; - let $bus = $bus; // Ensure that the bus reference was not moved out of the closure - let _ = $bus; // Silence the "unused variable" warning from prevous line - result - } - }) - }; - ($device:expr, |$bus:ident| async $closure_body:expr) => { - $crate::spi::SpiDevice::transaction($device, |$bus| { - // Safety: Implementers of the `SpiDevice` trait guarantee that the pointer is - // valid and dereferencable for the entire duration of the closure. - let $bus = unsafe { &mut *$bus }; - async { - let result = $closure_body; - let $bus = $bus; // Ensure that the bus reference was not moved out of the closure - let _ = $bus; // Silence the "unused variable" warning from prevous line - result - } - }) - }; -} +/// See the [module-level documentation](self) for important usage information. +pub trait SpiDeviceRead: ErrorType { + /// Perform a read transaction against the device. + /// + /// - Locks the bus + /// - Asserts the CS (Chip Select) pin. + /// - Performs all the operations. + /// - [Flushes](SpiBusFlush::flush) the bus. + /// - Deasserts the CS pin. + /// - Unlocks the bus. + /// + /// The locking mechanism is implementation-defined. The only requirement is it must prevent two + /// transactions from executing concurrently against the same bus. Examples of implementations are: + /// critical sections, blocking mutexes, returning an error or panicking if the bus is already busy. + /// + /// On bus errors the implementation should try to deassert CS. + /// If an error occurs while deasserting CS the bus error should take priority as the return value. + async fn read_transaction(&mut self, operations: &mut [&mut [Word]]) + -> Result<(), Self::Error>; -#[doc(inline)] -pub use spi_transaction as transaction; + /// Do a read within a transaction. + /// + /// This is a convenience method equivalent to `device.read_transaction(&mut [buf])`. + /// + /// See also: [`SpiDeviceRead::read_transaction`], [`SpiBusRead::read`] + async fn read(&mut self, buf: &mut [Word]) -> Result<(), Self::Error> { + self.read_transaction(&mut [buf]).await + } +} -/// SPI device trait +/// SPI write-only device trait /// -/// `SpiDevice` represents ownership over a single SPI device on a (possibly shared) bus, selected +/// `SpiDeviceWrite` represents ownership over a single SPI device on a (possibly shared) bus, selected /// with a CS (Chip Select) pin. /// /// See (the docs on embedded-hal)[embedded_hal::spi] for important information on SPI Bus vs Device traits. /// -/// # Safety -/// -/// See [`SpiDevice::transaction`] for details. -pub unsafe trait SpiDevice: ErrorType { - /// SPI Bus type for this device. - type Bus: ErrorType; - - /// Perform a transaction against the device. - /// - /// **NOTE:** - /// It is not recommended to use this method directly, because it requires `unsafe` code to dereference the raw pointer. - /// Instead, the [`transaction!`] macro should be used, which handles this safely inside the macro. +/// See the [module-level documentation](self) for important usage information. +pub trait SpiDeviceWrite: ErrorType { + /// Perform a write transaction against the device. /// /// - Locks the bus /// - Asserts the CS (Chip Select) pin. - /// - Calls `f` with an exclusive reference to the bus, which can then be used to do transfers against the device. + /// - Performs all the operations. /// - [Flushes](SpiBusFlush::flush) the bus. /// - Deasserts the CS pin. /// - Unlocks the bus. /// /// The locking mechanism is implementation-defined. The only requirement is it must prevent two /// transactions from executing concurrently against the same bus. Examples of implementations are: - /// critical sections, blocking mutexes, async mutexes, returning an error or panicking if the bus is already busy. + /// critical sections, blocking mutexes, returning an error or panicking if the bus is already busy. /// /// On bus errors the implementation should try to deassert CS. /// If an error occurs while deasserting CS the bus error should take priority as the return value. - /// - /// # Safety - /// - /// The current state of the Rust typechecker doesn't allow expressing the necessary lifetime constraints, so - /// the `f` closure receives a lifetime-less `*mut Bus` raw pointer instead. - /// - /// Implementers of the `SpiDevice` trait must guarantee that the pointer is valid and dereferencable - /// for the entire duration of the closure. - async fn transaction(&mut self, f: F) -> Result - where - F: FnOnce(*mut Self::Bus) -> Fut, - Fut: Future::Error>>; + async fn write_transaction(&mut self, operations: &[&[Word]]) -> Result<(), Self::Error>; - /// Do a read within a transaction. + /// Do a write within a transaction. /// - /// This is a convenience method equivalent to `device.transaction(|bus| bus.read(buf))`. + /// This is a convenience method equivalent to `device.write_transaction(&mut [buf])`. /// - /// See also: [`SpiDevice::transaction`], [`SpiBusRead::read`] - async fn read<'a, Word>(&'a mut self, buf: &'a mut [Word]) -> Result<(), Self::Error> - where - Self::Bus: SpiBusRead, - Word: Copy + 'static, - { - transaction!(self, move |bus| async move { bus.read(buf).await }).await + /// See also: [`SpiDeviceWrite::write_transaction`], [`SpiBusWrite::write`] + async fn write(&mut self, buf: &[Word]) -> Result<(), Self::Error> { + self.write_transaction(&[buf]).await } +} - /// Do a write within a transaction. +/// SPI device trait +/// +/// `SpiDevice` represents ownership over a single SPI device on a (possibly shared) bus, selected +/// with a CS (Chip Select) pin. +/// +/// See (the docs on embedded-hal)[embedded_hal::spi] for important information on SPI Bus vs Device traits. +/// +/// See the [module-level documentation](self) for important usage information. +pub trait SpiDevice: + SpiDeviceRead + SpiDeviceWrite + ErrorType +{ + /// Perform a transaction against the device. /// - /// This is a convenience method equivalent to `device.transaction(|bus| bus.write(buf))`. + /// - Locks the bus + /// - Asserts the CS (Chip Select) pin. + /// - Performs all the operations. + /// - [Flushes](SpiBusFlush::flush) the bus. + /// - Deasserts the CS pin. + /// - Unlocks the bus. /// - /// See also: [`SpiDevice::transaction`], [`SpiBusWrite::write`] - async fn write<'a, Word>(&'a mut self, buf: &'a [Word]) -> Result<(), Self::Error> - where - Self::Bus: SpiBusWrite, - Word: Copy + 'static, - { - transaction!(self, move |bus| async move { bus.write(buf).await }).await - } + /// The locking mechanism is implementation-defined. The only requirement is it must prevent two + /// transactions from executing concurrently against the same bus. Examples of implementations are: + /// critical sections, blocking mutexes, returning an error or panicking if the bus is already busy. + /// + /// On bus errors the implementation should try to deassert CS. + /// If an error occurs while deasserting CS the bus error should take priority as the return value. + async fn transaction( + &mut self, + operations: &mut [Operation<'_, Word>], + ) -> Result<(), Self::Error>; /// Do a transfer within a transaction. /// /// This is a convenience method equivalent to `device.transaction(|bus| bus.transfer(read, write))`. /// /// See also: [`SpiDevice::transaction`], [`SpiBus::transfer`] - async fn transfer<'a, Word>( - &'a mut self, - read: &'a mut [Word], - write: &'a [Word], - ) -> Result<(), Self::Error> - where - Self::Bus: SpiBus, - Word: Copy + 'static, - { - transaction!( - self, - move |bus| async move { bus.transfer(read, write).await } - ) - .await + async fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> { + self.transaction(&mut [Operation::Transfer(read, write)]) + .await } /// Do an in-place transfer within a transaction. @@ -207,31 +127,49 @@ pub unsafe trait SpiDevice: ErrorType { /// This is a convenience method equivalent to `device.transaction(|bus| bus.transfer_in_place(buf))`. /// /// See also: [`SpiDevice::transaction`], [`SpiBus::transfer_in_place`] - async fn transfer_in_place<'a, Word>( - &'a mut self, - buf: &'a mut [Word], - ) -> Result<(), Self::Error> - where - Self::Bus: SpiBus, - Word: Copy + 'static, - { - transaction!( - self, - move |bus| async move { bus.transfer_in_place(buf).await } - ) - .await + async fn transfer_in_place(&mut self, buf: &mut [Word]) -> Result<(), Self::Error> { + self.transaction(&mut [Operation::TransferInPlace(buf)]) + .await + } +} + +impl> SpiDeviceRead for &mut T { + async fn read_transaction( + &mut self, + operations: &mut [&mut [Word]], + ) -> Result<(), Self::Error> { + T::read_transaction(self, operations).await + } + + async fn read(&mut self, buf: &mut [Word]) -> Result<(), Self::Error> { + T::read(self, buf).await + } +} + +impl> SpiDeviceWrite for &mut T { + async fn write_transaction(&mut self, operations: &[&[Word]]) -> Result<(), Self::Error> { + T::write_transaction(self, operations).await + } + + async fn write(&mut self, buf: &[Word]) -> Result<(), Self::Error> { + T::write(self, buf).await } } -unsafe impl SpiDevice for &mut T { - type Bus = T::Bus; +impl> SpiDevice for &mut T { + async fn transaction( + &mut self, + operations: &mut [Operation<'_, Word>], + ) -> Result<(), Self::Error> { + T::transaction(self, operations).await + } + + async fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> { + T::transfer(self, read, write).await + } - async fn transaction(&mut self, f: F) -> Result - where - F: FnOnce(*mut Self::Bus) -> Fut, - Fut: Future::Error>>, - { - T::transaction(self, f).await + async fn transfer_in_place(&mut self, buf: &mut [Word]) -> Result<(), Self::Error> { + T::transfer_in_place(self, buf).await } } @@ -374,57 +312,194 @@ where type Error = ExclusiveDeviceError; } -impl blocking::SpiDevice for ExclusiveDevice +impl blocking::SpiDeviceRead for ExclusiveDevice where - BUS: blocking::SpiBusFlush, + BUS: blocking::SpiBusRead, CS: OutputPin, { - type Bus = BUS; + fn read_transaction(&mut self, operations: &mut [&mut [Word]]) -> Result<(), Self::Error> { + self.cs.set_low().map_err(ExclusiveDeviceError::Cs)?; - fn transaction( - &mut self, - f: impl FnOnce(&mut Self::Bus) -> Result::Error>, - ) -> Result { + let mut op_res = Ok(()); + + for buf in operations { + if let Err(e) = self.bus.read(buf) { + op_res = Err(e); + break; + } + } + + // On failure, it's important to still flush and deassert CS. + let flush_res = self.bus.flush(); + let cs_res = self.cs.set_high(); + + op_res.map_err(ExclusiveDeviceError::Spi)?; + flush_res.map_err(ExclusiveDeviceError::Spi)?; + cs_res.map_err(ExclusiveDeviceError::Cs)?; + + Ok(()) + } +} + +impl blocking::SpiDeviceWrite for ExclusiveDevice +where + BUS: blocking::SpiBusWrite, + CS: OutputPin, +{ + fn write_transaction(&mut self, operations: &[&[Word]]) -> Result<(), Self::Error> { self.cs.set_low().map_err(ExclusiveDeviceError::Cs)?; - let f_res = f(&mut self.bus); + let mut op_res = Ok(()); + + for buf in operations { + if let Err(e) = self.bus.write(buf) { + op_res = Err(e); + break; + } + } // On failure, it's important to still flush and deassert CS. let flush_res = self.bus.flush(); let cs_res = self.cs.set_high(); - let f_res = f_res.map_err(ExclusiveDeviceError::Spi)?; + op_res.map_err(ExclusiveDeviceError::Spi)?; flush_res.map_err(ExclusiveDeviceError::Spi)?; cs_res.map_err(ExclusiveDeviceError::Cs)?; - Ok(f_res) + Ok(()) } } -unsafe impl SpiDevice for ExclusiveDevice +impl blocking::SpiDevice for ExclusiveDevice where - BUS: SpiBusFlush, + BUS: blocking::SpiBus, CS: OutputPin, { - type Bus = BUS; + fn transaction(&mut self, operations: &mut [Operation<'_, Word>]) -> Result<(), Self::Error> { + self.cs.set_low().map_err(ExclusiveDeviceError::Cs)?; + + let op_res = 'ops: { + for op in operations { + let res = match op { + Operation::Read(buf) => self.bus.read(buf), + Operation::Write(buf) => self.bus.write(buf), + Operation::Transfer(read, write) => self.bus.transfer(read, write), + Operation::TransferInPlace(buf) => self.bus.transfer_in_place(buf), + }; + if let Err(e) = res { + break 'ops Err(e); + } + } + Ok(()) + }; + + // On failure, it's important to still flush and deassert CS. + let flush_res = self.bus.flush(); + let cs_res = self.cs.set_high(); + + op_res.map_err(ExclusiveDeviceError::Spi)?; + flush_res.map_err(ExclusiveDeviceError::Spi)?; + cs_res.map_err(ExclusiveDeviceError::Cs)?; - async fn transaction(&mut self, f: F) -> Result - where - F: FnOnce(*mut Self::Bus) -> Fut, - Fut: Future::Error>>, - { + Ok(()) + } +} + +impl SpiDeviceRead for ExclusiveDevice +where + BUS: SpiBusRead, + CS: OutputPin, +{ + async fn read_transaction( + &mut self, + operations: &mut [&mut [Word]], + ) -> Result<(), Self::Error> { self.cs.set_low().map_err(ExclusiveDeviceError::Cs)?; - let f_res = f(&mut self.bus).await; + let mut op_res = Ok(()); + + for buf in operations { + if let Err(e) = self.bus.read(buf).await { + op_res = Err(e); + break; + } + } + + // On failure, it's important to still flush and deassert CS. + let flush_res = self.bus.flush().await; + let cs_res = self.cs.set_high(); + + op_res.map_err(ExclusiveDeviceError::Spi)?; + flush_res.map_err(ExclusiveDeviceError::Spi)?; + cs_res.map_err(ExclusiveDeviceError::Cs)?; + + Ok(()) + } +} + +impl SpiDeviceWrite for ExclusiveDevice +where + BUS: SpiBusWrite, + CS: OutputPin, +{ + async fn write_transaction(&mut self, operations: &[&[Word]]) -> Result<(), Self::Error> { + self.cs.set_low().map_err(ExclusiveDeviceError::Cs)?; + + let mut op_res = Ok(()); + + for buf in operations { + if let Err(e) = self.bus.write(buf).await { + op_res = Err(e); + break; + } + } + + // On failure, it's important to still flush and deassert CS. + let flush_res = self.bus.flush().await; + let cs_res = self.cs.set_high(); + + op_res.map_err(ExclusiveDeviceError::Spi)?; + flush_res.map_err(ExclusiveDeviceError::Spi)?; + cs_res.map_err(ExclusiveDeviceError::Cs)?; + + Ok(()) + } +} + +impl SpiDevice for ExclusiveDevice +where + BUS: SpiBus, + CS: OutputPin, +{ + async fn transaction( + &mut self, + operations: &mut [Operation<'_, Word>], + ) -> Result<(), Self::Error> { + self.cs.set_low().map_err(ExclusiveDeviceError::Cs)?; + + let op_res = 'ops: { + for op in operations { + let res = match op { + Operation::Read(buf) => self.bus.read(buf).await, + Operation::Write(buf) => self.bus.write(buf).await, + Operation::Transfer(read, write) => self.bus.transfer(read, write).await, + Operation::TransferInPlace(buf) => self.bus.transfer_in_place(buf).await, + }; + if let Err(e) = res { + break 'ops Err(e); + } + } + Ok(()) + }; // On failure, it's important to still flush and deassert CS. let flush_res = self.bus.flush().await; let cs_res = self.cs.set_high(); - let f_res = f_res.map_err(ExclusiveDeviceError::Spi)?; + op_res.map_err(ExclusiveDeviceError::Spi)?; flush_res.map_err(ExclusiveDeviceError::Spi)?; cs_res.map_err(ExclusiveDeviceError::Cs)?; - Ok(f_res) + Ok(()) } } diff --git a/embedded-hal-bus/src/spi.rs b/embedded-hal-bus/src/spi.rs index 7266f414b..c0de23fcb 100644 --- a/embedded-hal-bus/src/spi.rs +++ b/embedded-hal-bus/src/spi.rs @@ -2,7 +2,10 @@ use core::fmt::Debug; use embedded_hal::digital::OutputPin; -use embedded_hal::spi::{Error, ErrorKind, ErrorType, SpiBusFlush, SpiDevice}; +use embedded_hal::spi::{ + Error, ErrorKind, ErrorType, Operation, SpiBus, SpiBusRead, SpiBusWrite, SpiDevice, + SpiDeviceRead, SpiDeviceWrite, +}; /// Error type for [`ExclusiveDevice`] operations. #[derive(Copy, Clone, Eq, PartialEq, Debug)] @@ -50,29 +53,111 @@ where type Error = ExclusiveDeviceError; } -impl SpiDevice for ExclusiveDevice +impl SpiDeviceRead for ExclusiveDevice where - BUS: SpiBusFlush, + BUS: SpiBusRead, CS: OutputPin, { - type Bus = BUS; + fn read_transaction(&mut self, operations: &mut [&mut [Word]]) -> Result<(), Self::Error> { + self.cs.set_low().map_err(ExclusiveDeviceError::Cs)?; + + let mut op_res = Ok(()); + + for buf in operations { + if let Err(e) = self.bus.read(buf) { + op_res = Err(e); + break; + } + } + + // On failure, it's important to still flush and deassert CS. + let flush_res = self.bus.flush(); + let cs_res = self.cs.set_high(); - fn transaction( - &mut self, - f: impl FnOnce(&mut Self::Bus) -> Result::Error>, - ) -> Result { + op_res.map_err(ExclusiveDeviceError::Spi)?; + flush_res.map_err(ExclusiveDeviceError::Spi)?; + cs_res.map_err(ExclusiveDeviceError::Cs)?; + + Ok(()) + } +} + +impl SpiDeviceWrite for ExclusiveDevice +where + BUS: SpiBusWrite, + CS: OutputPin, +{ + fn write_transaction(&mut self, operations: &[&[Word]]) -> Result<(), Self::Error> { self.cs.set_low().map_err(ExclusiveDeviceError::Cs)?; - let f_res = f(&mut self.bus); + let mut op_res = Ok(()); + + for buf in operations { + if let Err(e) = self.bus.write(buf) { + op_res = Err(e); + break; + } + } + + // On failure, it's important to still flush and deassert CS. + let flush_res = self.bus.flush(); + let cs_res = self.cs.set_high(); + + op_res.map_err(ExclusiveDeviceError::Spi)?; + flush_res.map_err(ExclusiveDeviceError::Spi)?; + cs_res.map_err(ExclusiveDeviceError::Cs)?; + + Ok(()) + } +} + +impl SpiDevice for ExclusiveDevice +where + BUS: SpiBus, + CS: OutputPin, +{ + fn transaction(&mut self, operations: &mut [Operation<'_, Word>]) -> Result<(), Self::Error> { + self.cs.set_low().map_err(ExclusiveDeviceError::Cs)?; + + let mut op_res = Ok(()); + + for op in operations { + match op { + Operation::Read(buf) => { + if let Err(e) = self.bus.read(buf) { + op_res = Err(e); + break; + } + } + Operation::Write(buf) => { + if let Err(e) = self.bus.write(buf) { + op_res = Err(e); + break; + } + } + Operation::Transfer(read, write) => { + if let Err(e) = self.bus.transfer(read, write) { + op_res = Err(e); + break; + } + } + Operation::TransferInPlace(buf) => { + if let Err(e) = self.bus.transfer_in_place(buf) { + op_res = Err(e); + break; + } + } + } + } // On failure, it's important to still flush and deassert CS. let flush_res = self.bus.flush(); let cs_res = self.cs.set_high(); - let f_res = f_res.map_err(ExclusiveDeviceError::Spi)?; + op_res.map_err(ExclusiveDeviceError::Spi)?; flush_res.map_err(ExclusiveDeviceError::Spi)?; cs_res.map_err(ExclusiveDeviceError::Cs)?; - Ok(f_res) + Ok(()) } } diff --git a/embedded-hal/src/spi.rs b/embedded-hal/src/spi.rs index 4ebc68db0..f8f14efa4 100644 --- a/embedded-hal/src/spi.rs +++ b/embedded-hal/src/spi.rs @@ -46,20 +46,19 @@ //! consists of asserting CS, then doing one or more transfers, then deasserting CS. For the entire duration of the transaction, the [`SpiDevice`] //! implementation will ensure no other transaction can be opened on the same bus. This is the key that allows correct sharing of the bus. //! -//! The capabilities of the bus (read-write, read-only or write-only) are determined by which of the [`SpiBus`], [`SpiBusRead`] [`SpiBusWrite`] traits -//! are implemented for the [`Bus`](SpiDevice::Bus) associated type. +//! For read-only or write-only SPI devices, the [`SpiDeviceRead`] and [`SpiDeviceWrite`] are available. //! //! # For driver authors //! //! When implementing a driver, it's crucial to pick the right trait, to ensure correct operation //! with maximum interoperability. Here are some guidelines depending on the device you're implementing a driver for: //! -//! If your device **has a CS pin**, use [`SpiDevice`]. Do not manually manage the CS pin, the [`SpiDevice`] implementation will do it for you. -//! Add bounds like `where T::Bus: SpiBus`, `where T::Bus: SpiBusRead`, `where T::Bus: SpiBusWrite` to specify the kind of access you need. +//! If your device **has a CS pin**, use [`SpiDevice`] (or [`SpiDeviceRead`]/[`SpiDeviceWrite`]). Do not manually +//! manage the CS pin, the [`SpiDevice`] implementation will do it for you. //! By using [`SpiDevice`], your driver will cooperate nicely with other drivers for other devices in the same shared SPI bus. //! //! ``` -//! # use embedded_hal::spi::{SpiBus, SpiBusRead, SpiBusWrite, SpiDevice}; +//! # use embedded_hal::spi::{SpiBus, SpiBusRead, SpiBusWrite, SpiDevice, Operation}; //! pub struct MyDriver { //! spi: SPI, //! } @@ -67,7 +66,6 @@ //! impl MyDriver //! where //! SPI: SpiDevice, -//! SPI::Bus: SpiBus, // or SpiBusRead/SpiBusWrite if you only need to read or only write. //! { //! pub fn new(spi: SPI) -> Self { //! Self { spi } @@ -77,10 +75,10 @@ //! let mut buf = [0; 2]; //! //! // `transaction` asserts and deasserts CS for us. No need to do it manually! -//! self.spi.transaction(|bus| { -//! bus.write(&[0x90])?; -//! bus.read(&mut buf) -//! }).map_err(MyError::Spi)?; +//! self.spi.transaction(&mut [ +//! Operation::Write(&[0x90]), +//! Operation::Read(&mut buf), +//! ]).map_err(MyError::Spi)?; //! //! Ok(buf) //! } @@ -298,21 +296,75 @@ impl ErrorType for &mut T { type Error = T::Error; } -/// SPI device trait +/// SPI transaction operation. /// -/// `SpiDevice` represents ownership over a single SPI device on a (possibly shared) bus, selected +/// This allows composition of SPI operations into a single bus transaction +#[derive(Debug, PartialEq)] +pub enum Operation<'a, Word: 'static> { + /// Read data into the provided buffer. + /// + /// Equivalent to [`SpiBusRead::read`]. + Read(&'a mut [Word]), + /// Write data from the provided buffer, discarding read data + /// + /// Equivalent to [`SpiBusWrite::write`]. + Write(&'a [Word]), + /// Read data into the first buffer, while writing data from the second buffer. + /// + /// Equivalent to [`SpiBus::transfer`]. + Transfer(&'a mut [Word], &'a [Word]), + /// Write data out while reading data into the provided buffer + /// + /// Equivalent to [`SpiBus::transfer_in_place`]. + TransferInPlace(&'a mut [Word]), +} + +/// SPI read-only device trait +/// +/// `SpiDeviceRead` represents ownership over a single SPI device on a (possibly shared) bus, selected /// with a CS (Chip Select) pin. /// /// See the [module-level documentation](self) for important usage information. -pub trait SpiDevice: ErrorType { - /// SPI Bus type for this device. - type Bus: ErrorType; +pub trait SpiDeviceRead: ErrorType { + /// Perform a read transaction against the device. + /// + /// - Locks the bus + /// - Asserts the CS (Chip Select) pin. + /// - Performs all the operations. + /// - [Flushes](SpiBusFlush::flush) the bus. + /// - Deasserts the CS pin. + /// - Unlocks the bus. + /// + /// The locking mechanism is implementation-defined. The only requirement is it must prevent two + /// transactions from executing concurrently against the same bus. Examples of implementations are: + /// critical sections, blocking mutexes, returning an error or panicking if the bus is already busy. + /// + /// On bus errors the implementation should try to deassert CS. + /// If an error occurs while deasserting CS the bus error should take priority as the return value. + fn read_transaction(&mut self, operations: &mut [&mut [Word]]) -> Result<(), Self::Error>; - /// Perform a transaction against the device. + /// Do a read within a transaction. + /// + /// This is a convenience method equivalent to `device.read_transaction(&mut [buf])`. + /// + /// See also: [`SpiDeviceRead::read_transaction`], [`SpiBusRead::read`] + fn read(&mut self, buf: &mut [Word]) -> Result<(), Self::Error> { + self.read_transaction(&mut [buf]) + } +} + +/// SPI write-only device trait +/// +/// `SpiDeviceWrite` represents ownership over a single SPI device on a (possibly shared) bus, selected +/// with a CS (Chip Select) pin. +/// +/// See the [module-level documentation](self) for important usage information. +pub trait SpiDeviceWrite: ErrorType { + /// Perform a write transaction against the device. /// /// - Locks the bus /// - Asserts the CS (Chip Select) pin. - /// - Calls `f` with an exclusive reference to the bus, which can then be used to do transfers against the device. + /// - Performs all the operations. /// - [Flushes](SpiBusFlush::flush) the bus. /// - Deasserts the CS pin. /// - Unlocks the bus. @@ -323,71 +375,94 @@ pub trait SpiDevice: ErrorType { /// /// On bus errors the implementation should try to deassert CS. /// If an error occurs while deasserting CS the bus error should take priority as the return value. - fn transaction( - &mut self, - f: impl FnOnce(&mut Self::Bus) -> Result::Error>, - ) -> Result; + fn write_transaction(&mut self, operations: &[&[Word]]) -> Result<(), Self::Error>; /// Do a write within a transaction. /// - /// This is a convenience method equivalent to `device.transaction(|bus| bus.write(buf))`. + /// This is a convenience method equivalent to `device.write_transaction(&mut [buf])`. /// - /// See also: [`SpiDevice::transaction`], [`SpiBusWrite::write`] - fn write(&mut self, buf: &[Word]) -> Result<(), Self::Error> - where - Self::Bus: SpiBusWrite, - Word: Copy, - { - self.transaction(|bus| bus.write(buf)) + /// See also: [`SpiDeviceWrite::write_transaction`], [`SpiBusWrite::write`] + fn write(&mut self, buf: &[Word]) -> Result<(), Self::Error> { + self.write_transaction(&[buf]) } +} - /// Do a read within a transaction. +/// SPI device trait +/// +/// `SpiDevice` represents ownership over a single SPI device on a (possibly shared) bus, selected +/// with a CS (Chip Select) pin. +/// +/// See the [module-level documentation](self) for important usage information. +pub trait SpiDevice: + SpiDeviceRead + SpiDeviceWrite + ErrorType +{ + /// Perform a transaction against the device. + /// + /// - Locks the bus + /// - Asserts the CS (Chip Select) pin. + /// - Performs all the operations. + /// - [Flushes](SpiBusFlush::flush) the bus. + /// - Deasserts the CS pin. + /// - Unlocks the bus. /// - /// This is a convenience method equivalent to `device.transaction(|bus| bus.read(buf))`. + /// The locking mechanism is implementation-defined. The only requirement is it must prevent two + /// transactions from executing concurrently against the same bus. Examples of implementations are: + /// critical sections, blocking mutexes, returning an error or panicking if the bus is already busy. /// - /// See also: [`SpiDevice::transaction`], [`SpiBusRead::read`] - fn read(&mut self, buf: &mut [Word]) -> Result<(), Self::Error> - where - Self::Bus: SpiBusRead, - Word: Copy, - { - self.transaction(|bus| bus.read(buf)) - } + /// On bus errors the implementation should try to deassert CS. + /// If an error occurs while deasserting CS the bus error should take priority as the return value. + fn transaction(&mut self, operations: &mut [Operation<'_, Word>]) -> Result<(), Self::Error>; /// Do a transfer within a transaction. /// - /// This is a convenience method equivalent to `device.transaction(|bus| bus.transfer(read, write))`. + /// This is a convenience method equivalent to `device.transaction(&mut [Operation::Transfer(read, write)]`. /// /// See also: [`SpiDevice::transaction`], [`SpiBus::transfer`] - fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> - where - Self::Bus: SpiBus, - Word: Copy, - { - self.transaction(|bus| bus.transfer(read, write)) + fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> { + self.transaction(&mut [Operation::Transfer(read, write)]) } /// Do an in-place transfer within a transaction. /// - /// This is a convenience method equivalent to `device.transaction(|bus| bus.transfer_in_place(buf))`. + /// This is a convenience method equivalent to `device.transaction([Operation::TransferInPlace(buf)]`. /// /// See also: [`SpiDevice::transaction`], [`SpiBus::transfer_in_place`] - fn transfer_in_place(&mut self, buf: &mut [Word]) -> Result<(), Self::Error> - where - Self::Bus: SpiBus, - Word: Copy, - { - self.transaction(|bus| bus.transfer_in_place(buf)) + fn transfer_in_place(&mut self, buf: &mut [Word]) -> Result<(), Self::Error> { + self.transaction(&mut [Operation::TransferInPlace(buf)]) } } -impl SpiDevice for &mut T { - type Bus = T::Bus; - fn transaction( - &mut self, - f: impl FnOnce(&mut Self::Bus) -> Result::Error>, - ) -> Result { - T::transaction(self, f) +impl> SpiDeviceRead for &mut T { + fn read_transaction(&mut self, operations: &mut [&mut [Word]]) -> Result<(), Self::Error> { + T::read_transaction(self, operations) + } + + fn read(&mut self, buf: &mut [Word]) -> Result<(), Self::Error> { + T::read(self, buf) + } +} + +impl> SpiDeviceWrite for &mut T { + fn write_transaction(&mut self, operations: &[&[Word]]) -> Result<(), Self::Error> { + T::write_transaction(self, operations) + } + + fn write(&mut self, buf: &[Word]) -> Result<(), Self::Error> { + T::write(self, buf) + } +} + +impl> SpiDevice for &mut T { + fn transaction(&mut self, operations: &mut [Operation<'_, Word>]) -> Result<(), Self::Error> { + T::transaction(self, operations) + } + + fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> { + T::transfer(self, read, write) + } + + fn transfer_in_place(&mut self, buf: &mut [Word]) -> Result<(), Self::Error> { + T::transfer_in_place(self, buf) } } @@ -406,7 +481,7 @@ impl SpiBusFlush for &mut T { } /// Read-only SPI bus -pub trait SpiBusRead: SpiBusFlush { +pub trait SpiBusRead: SpiBusFlush { /// Read `words` from the slave. /// /// The word value sent on MOSI during reading is implementation-defined, @@ -417,14 +492,14 @@ pub trait SpiBusRead: SpiBusFlush { fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error>; } -impl, Word: Copy> SpiBusRead for &mut T { +impl, Word: Copy + 'static> SpiBusRead for &mut T { fn read(&mut self, words: &mut [Word]) -> Result<(), Self::Error> { T::read(self, words) } } /// Write-only SPI bus -pub trait SpiBusWrite: SpiBusFlush { +pub trait SpiBusWrite: SpiBusFlush { /// Write `words` to the slave, ignoring all the incoming words /// /// Implementations are allowed to return before the operation is @@ -432,7 +507,7 @@ pub trait SpiBusWrite: SpiBusFlush { fn write(&mut self, words: &[Word]) -> Result<(), Self::Error>; } -impl, Word: Copy> SpiBusWrite for &mut T { +impl, Word: Copy + 'static> SpiBusWrite for &mut T { fn write(&mut self, words: &[Word]) -> Result<(), Self::Error> { T::write(self, words) } @@ -443,7 +518,7 @@ impl, Word: Copy> SpiBusWrite for &mut T { /// `SpiBus` represents **exclusive ownership** over the whole SPI bus, with SCK, MOSI and MISO pins. /// /// See the [module-level documentation](self) for important information on SPI Bus vs Device traits. -pub trait SpiBus: SpiBusRead + SpiBusWrite { +pub trait SpiBus: SpiBusRead + SpiBusWrite { /// Write and read simultaneously. `write` is written to the slave on MOSI and /// words received on MISO are stored in `read`. /// @@ -466,7 +541,7 @@ pub trait SpiBus: SpiBusRead + SpiBusWrite { fn transfer_in_place(&mut self, words: &mut [Word]) -> Result<(), Self::Error>; } -impl, Word: Copy> SpiBus for &mut T { +impl, Word: Copy + 'static> SpiBus for &mut T { fn transfer(&mut self, read: &mut [Word], write: &[Word]) -> Result<(), Self::Error> { T::transfer(self, read, write) }