From cfeb777f9b4e4ea151e9aaeed9c266bed1ef71ec Mon Sep 17 00:00:00 2001 From: Alexander van Saase Date: Thu, 10 Apr 2025 23:46:40 +0200 Subject: [PATCH 01/10] Add SdCardDevice trait --- Cargo.toml | 4 + README.md | 5 ++ examples/readme_test.rs | 3 +- src/lib.rs | 10 ++- src/sdcard/mod.rs | 12 +-- src/sdcard/sd_card_device.rs | 164 +++++++++++++++++++++++++++++++++++ 6 files changed, 189 insertions(+), 9 deletions(-) create mode 100644 src/sdcard/sd_card_device.rs diff --git a/Cargo.toml b/Cargo.toml index 49b1317..712907e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,9 @@ rust-version = "1.76" [dependencies] byteorder = {version = "1", default-features = false} defmt = {version = "0.3", optional = true} +embassy-sync-06 = { package = "embassy-sync", version = "0.6.2", optional = true } embedded-hal = "1.0.0" +embedded-hal-bus-03 = { package = "embedded-hal-bus", version = "0.3.0", optional = true } embedded-io = "0.6.1" heapless = "^0.8" log = {version = "0.4", default-features = false, optional = true} @@ -32,4 +34,6 @@ sha2 = "0.10" [features] default = ["log"] defmt-log = ["dep:defmt"] +embassy-sync-06 = ["dep:embassy-sync-06"] +embedded-hal-bus-03 = ["dep:embedded-hal-bus-03"] log = ["dep:log"] diff --git a/README.md b/README.md index 9bd06b1..74a4a2c 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,11 @@ designed for readability and simplicity over performance. You will need something that implements the `BlockDevice` trait, which can read and write the 512-byte blocks (or sectors) from your card. If you were to implement this over USB Mass Storage, there's no reason this crate couldn't work with a USB Thumb Drive, but we only supply a `BlockDevice` suitable for reading SD and SDHC cards over SPI. +To accommodate specific requirements when using SD cards on a shared SPI bus, this crate uses a bespoke `SdCardDevice` trait instead of the more commmon `embedded_hal::spi::SpiDevice` trait. Implementations for several types that wrap a `embedded-hal::spi::SpiBus` implementation are provided in the `sd_card` module. Some are guarded behind cargo features: + +- `embedded-hal-bus-03`: adds support for `embedded-hal-bus::spi::ExclusiveDevice`. This is probably the easiest way to use this crate when the SD card is the only device on the bus. +- `embassy-sync-06`: adds support for using a blocking mutex from the `embassy-sync` crate. This allows sharing the bus between multiple tasks. + ```rust use embedded_sdmmc::{SdCard, VolumeManager, Mode, VolumeIdx}; // Build an SD Card interface out of an SPI device, a chip-select pin and the delay object diff --git a/examples/readme_test.rs b/examples/readme_test.rs index 0d63d80..d173444 100644 --- a/examples/readme_test.rs +++ b/examples/readme_test.rs @@ -117,13 +117,12 @@ fn main() -> Result<(), MyError> { // BEGIN Fake stuff that will be replaced with real peripherals let spi_bus = RefCell::new(FakeSpiBus()); let delay = FakeDelayer(); - let sdmmc_spi = embedded_hal_bus::spi::RefCellDevice::new(&spi_bus, DummyCsPin, delay).unwrap(); let time_source = FakeTimesource(); // END Fake stuff that will be replaced with real peripherals use embedded_sdmmc::{Mode, SdCard, VolumeIdx, VolumeManager}; // Build an SD Card interface out of an SPI device, a chip-select pin and the delay object - let sdcard = SdCard::new(sdmmc_spi, delay); + let sdcard = SdCard::new((&spi_bus, DummyCsPin), delay); // Get the card size (this also triggers card initialisation because it's not been done yet) println!("Card size is {} bytes", sdcard.num_bytes()?); // Now let's look for volumes (also known as partitions) on our block device. diff --git a/src/lib.rs b/src/lib.rs index c6af4e9..821195a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,11 +17,11 @@ //! suitable for reading SD and SDHC cards over SPI. //! //! ```rust -//! use embedded_sdmmc::{Error, Mode, SdCard, SdCardError, TimeSource, VolumeIdx, VolumeManager}; +//! use embedded_sdmmc::{Error, Mode, SdCard, SdCardError, TimeSource, VolumeIdx, VolumeManager, SdCardDevice}; //! //! fn example(spi: S, delay: D, ts: T) -> Result<(), Error> //! where -//! S: embedded_hal::spi::SpiDevice, +//! S: SdCardDevice, //! D: embedded_hal::delay::DelayNs, //! T: TimeSource, //! { @@ -116,6 +116,12 @@ pub use crate::sdcard::Error as SdCardError; #[doc(inline)] pub use crate::sdcard::SdCard; +#[doc(inline)] +pub use crate::sdcard::SdCardDevice; + +#[doc(inline)] +pub use crate::sdcard::SdCardDeviceError; + mod volume_mgr; #[doc(inline)] pub use volume_mgr::VolumeManager; diff --git a/src/sdcard/mod.rs b/src/sdcard/mod.rs index 553791f..4439b62 100644 --- a/src/sdcard/mod.rs +++ b/src/sdcard/mod.rs @@ -4,6 +4,7 @@ //! performance. pub mod proto; +mod sd_card_device; use crate::{trace, Block, BlockCount, BlockDevice, BlockIdx}; use core::cell::RefCell; @@ -14,6 +15,7 @@ use proto::*; // **************************************************************************** use crate::{debug, warn}; +pub use sd_card_device::*; // **************************************************************************** // Types and Implementations @@ -37,7 +39,7 @@ use crate::{debug, warn}; /// [`SpiDevice`]: embedded_hal::spi::SpiDevice pub struct SdCard where - SPI: embedded_hal::spi::SpiDevice, + SPI: SdCardDevice, DELAYER: embedded_hal::delay::DelayNs, { inner: RefCell>, @@ -45,7 +47,7 @@ where impl SdCard where - SPI: embedded_hal::spi::SpiDevice, + SPI: SdCardDevice, DELAYER: embedded_hal::delay::DelayNs, { /// Create a new SD/MMC Card driver using a raw SPI interface. @@ -154,7 +156,7 @@ where impl BlockDevice for SdCard where - SPI: embedded_hal::spi::SpiDevice, + SPI: SdCardDevice, DELAYER: embedded_hal::delay::DelayNs, { type Error = Error; @@ -194,7 +196,7 @@ where /// All the APIs required `&mut self`. struct SdCardInner where - SPI: embedded_hal::spi::SpiDevice, + SPI: SdCardDevice, DELAYER: embedded_hal::delay::DelayNs, { spi: SPI, @@ -205,7 +207,7 @@ where impl SdCardInner where - SPI: embedded_hal::spi::SpiDevice, + SPI: SdCardDevice, DELAYER: embedded_hal::delay::DelayNs, { /// Read one or more blocks, starting at the given block index. diff --git a/src/sdcard/sd_card_device.rs b/src/sdcard/sd_card_device.rs new file mode 100644 index 0000000..48a447f --- /dev/null +++ b/src/sdcard/sd_card_device.rs @@ -0,0 +1,164 @@ +//! SD card device trait and provided implementations. + +use core::cell::RefCell; + +use embedded_hal::{ + digital::OutputPin, + spi::{Operation, SpiBus}, +}; + +/// Trait for SD cards connected via SPI. +pub trait SdCardDevice { + /// Perform a transaction against the device. + /// + /// This is similar to [`embedded_hal::spi::SpiDevice::transaction`], except that this sends + /// a dummy `0xFF` byte to the device after deasserting the CS pin but before unlocking the + /// bus. + fn transaction( + &mut self, + operations: &mut [Operation<'_, u8>], + ) -> Result<(), SdCardDeviceError>; + + /// Do a read within a transaction. + /// + /// This is a convenience method equivalent to `device.transaction(&mut [Operation::Read(buf)])`. + /// + /// See also: [`SdCardDevice::transaction`], [`embedded_hal::spi::SpiBus::read`] + #[inline] + fn read(&mut self, buf: &mut [u8]) -> Result<(), SdCardDeviceError> { + self.transaction(&mut [Operation::Read(buf)]) + } + + /// Do a write within a transaction. + /// + /// This is a convenience method equivalent to `device.transaction(&mut [Operation::Write(buf)])`. + /// + /// See also: [`SdCardDevice::transaction`], [`embedded_hal::spi::SpiBus::write`] + #[inline] + fn write(&mut self, buf: &[u8]) -> Result<(), SdCardDeviceError> { + self.transaction(&mut [Operation::Write(buf)]) + } + + /// Do a transfer within a transaction. + /// + /// This is a convenience method equivalent to `device.transaction(&mut [Operation::Transfer(read, write)]`. + /// + /// See also: [`SdCardDevice::transaction`], [`embedded_hal::spi::SpiBus::transfer`] + #[inline] + fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), SdCardDeviceError> { + self.transaction(&mut [Operation::Transfer(read, write)]) + } + + /// Do an in-place transfer within a transaction. + /// + /// This is a convenience method equivalent to `device.transaction(&mut [Operation::TransferInPlace(buf)]`. + /// + /// See also: [`SdCardDevice::transaction`], [`embedded_hal::spi::SpiBus::transfer_in_place`] + #[inline] + fn transfer_in_place(&mut self, buf: &mut [u8]) -> Result<(), SdCardDeviceError> { + self.transaction(&mut [Operation::TransferInPlace(buf)]) + } +} + +/// Errors that can occur when using the [`SdCardDevice`]. +#[derive(Copy, Clone, Eq, PartialEq, Debug)] +#[cfg_attr(feature = "defmt-log", derive(defmt::Format))] +#[non_exhaustive] +pub enum SdCardDeviceError { + /// An operation on the inner SPI bus failed. + Spi, + /// Setting the value of the Chip Select (CS) pin failed. + Cs, +} + +impl SdCardDevice for (&RefCell, CS) +where + BUS: SpiBus, + CS: OutputPin, +{ + fn transaction( + &mut self, + operations: &mut [Operation<'_, u8>], + ) -> Result<(), SdCardDeviceError> { + let (bus, cs) = self; + let mut bus = bus.borrow_mut(); + bus_transaction(&mut *bus, cs, operations) + } +} + +#[cfg(feature = "embassy-sync-06")] +impl SdCardDevice for (&embassy_sync_06::blocking_mutex::Mutex>, CS) +where + CS: OutputPin, + BUS: SpiBus, + M: embassy_sync_06::blocking_mutex::raw::RawMutex, +{ + fn transaction( + &mut self, + operations: &mut [Operation<'_, u8>], + ) -> Result<(), SdCardDeviceError> { + let (bus, cs) = self; + bus.lock(|bus| { + let mut bus = bus.borrow_mut(); + bus_transaction(&mut *bus, cs, operations) + }) + } +} + +// `ExclusiveDevice` represents exclusive access to the bus so there's no need to send the dummy +// byte after deasserting the CS pin. We can defer the implementation to the `embedded_hal` trait. +#[cfg(feature = "embedded-hal-bus-03")] +impl SdCardDevice for embedded_hal_bus_03::spi::ExclusiveDevice +where + BUS: SpiBus, + CS: OutputPin, + D: embedded_hal::delay::DelayNs, +{ + fn transaction( + &mut self, + operations: &mut [Operation<'_, u8>], + ) -> Result<(), SdCardDeviceError> { + ::transaction(self, operations) + .map_err(|_| SdCardDeviceError::Spi) + } +} + +fn bus_transaction( + bus: &mut BUS, + cs: &mut CS, + operations: &mut [Operation<'_, u8>], +) -> Result<(), SdCardDeviceError> +where + BUS: SpiBus, + CS: OutputPin, +{ + cs.set_low().map_err(|_| SdCardDeviceError::Cs)?; + + let op_res = operations.iter_mut().try_for_each(|op| match op { + Operation::Read(buf) => bus.read(buf), + Operation::Write(buf) => bus.write(buf), + Operation::Transfer(read, write) => bus.transfer(read, write), + Operation::TransferInPlace(buf) => bus.transfer_in_place(buf), + Operation::DelayNs(_) => { + // We don't use delays in SPI transations in this crate so it fine to panic here. + panic!("Tried to use a delay in a SPI transaction. This is a bug in embedded-sdmmc.") + } + }); + + // On failure, it's important to still flush and deassert CS. + let flush_res = bus.flush(); + let cs_res = cs.set_high(); + + op_res.map_err(|_| SdCardDeviceError::Spi)?; + flush_res.map_err(|_| SdCardDeviceError::Spi)?; + cs_res.map_err(|_| SdCardDeviceError::Cs)?; + + // Write the dummy byte + let dummy_res = bus.write(&[0xFF]); + let flush_res = bus.flush(); + + dummy_res.map_err(|_| SdCardDeviceError::Spi)?; + flush_res.map_err(|_| SdCardDeviceError::Spi)?; + + Ok(()) +} From af92c87a4a019f95b1f1a0e2991d62eadb7983f1 Mon Sep 17 00:00:00 2001 From: Alexander van Saase Date: Fri, 11 Apr 2025 08:19:42 +0200 Subject: [PATCH 02/10] Wording --- src/sdcard/sd_card_device.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sdcard/sd_card_device.rs b/src/sdcard/sd_card_device.rs index 48a447f..fca44fd 100644 --- a/src/sdcard/sd_card_device.rs +++ b/src/sdcard/sd_card_device.rs @@ -106,7 +106,7 @@ where } // `ExclusiveDevice` represents exclusive access to the bus so there's no need to send the dummy -// byte after deasserting the CS pin. We can defer the implementation to the `embedded_hal` trait. +// byte after deasserting the CS pin. We can delegate the implementation to the `embedded_hal` trait. #[cfg(feature = "embedded-hal-bus-03")] impl SdCardDevice for embedded_hal_bus_03::spi::ExclusiveDevice where From 3ae7018f8b46dd830f1c2b3eac9980f1d1366739 Mon Sep 17 00:00:00 2001 From: Alexander van Saase Date: Fri, 11 Apr 2025 08:46:43 +0200 Subject: [PATCH 03/10] Add init function --- src/sdcard/mod.rs | 19 ++++++++++++++ src/sdcard/sd_card_device.rs | 49 ++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/src/sdcard/mod.rs b/src/sdcard/mod.rs index 4439b62..a3ba66d 100644 --- a/src/sdcard/mod.rs +++ b/src/sdcard/mod.rs @@ -132,6 +132,18 @@ where inner.card_type } + /// Initialize the SD card. + /// + /// This must be called before performing any operations on the card, with + /// SPI frequency of 100 to 400 KHz. After this method returns + /// successfully, the SPI frequency can be increased to the maximum + /// supported by the card. + pub fn init_card(&self) -> Result<(), Error> { + let mut inner = self.inner.borrow_mut(); + inner.init()?; + Ok(()) + } + /// Tell the driver the card has been initialised. /// /// This is here in case you were previously using the SD Card, and then a @@ -563,6 +575,13 @@ where } Ok(()) } + + fn init(&mut self) -> Result<(), Error> { + self.spi + .send_clock_pulses() + .map_err(|_e| Error::Transport)?; + Ok(()) + } } /// Options for acquiring the card. diff --git a/src/sdcard/sd_card_device.rs b/src/sdcard/sd_card_device.rs index fca44fd..0cfe8fd 100644 --- a/src/sdcard/sd_card_device.rs +++ b/src/sdcard/sd_card_device.rs @@ -19,6 +19,9 @@ pub trait SdCardDevice { operations: &mut [Operation<'_, u8>], ) -> Result<(), SdCardDeviceError>; + /// Send 80 clock pulses to the device with CS deasserted. + fn send_clock_pulses(&mut self) -> Result<(), SdCardDeviceError>; + /// Do a read within a transaction. /// /// This is a convenience method equivalent to `device.transaction(&mut [Operation::Read(buf)])`. @@ -84,6 +87,12 @@ where let mut bus = bus.borrow_mut(); bus_transaction(&mut *bus, cs, operations) } + + fn send_clock_pulses(&mut self) -> Result<(), SdCardDeviceError> { + let (bus, cs) = self; + let mut bus = bus.borrow_mut(); + send_clock_pulses(&mut *bus, cs) + } } #[cfg(feature = "embassy-sync-06")] @@ -103,6 +112,14 @@ where bus_transaction(&mut *bus, cs, operations) }) } + + fn send_clock_pulses(&mut self) -> Result<(), SdCardDeviceError> { + let (bus, cs) = self; + bus.lock(|bus| { + let mut bus = bus.borrow_mut(); + send_clock_pulses(&mut *bus, cs) + }) + } } // `ExclusiveDevice` represents exclusive access to the bus so there's no need to send the dummy @@ -121,6 +138,21 @@ where ::transaction(self, operations) .map_err(|_| SdCardDeviceError::Spi) } + + fn send_clock_pulses(&mut self) -> Result<(), SdCardDeviceError> { + let bus = self.bus_mut(); + + // There's no way to access the CS pin here so we can't set it high. Most like it already high so this is probbaly fine(?) + + let send_res = bus.write(&[0xFF; 10]); + + // On failure, it's important to still flush. + let flush_res = bus.flush().map_err(|_| SdCardDeviceError::Spi); + + send_res.map_err(|_| SdCardDeviceError::Spi)?; + flush_res.map_err(|_| SdCardDeviceError::Spi)?; + Ok(()) + } } fn bus_transaction( @@ -162,3 +194,20 @@ where Ok(()) } + +fn send_clock_pulses(bus: &mut BUS, cs: &mut CS) -> Result<(), SdCardDeviceError> +where + BUS: SpiBus, + CS: OutputPin, +{ + cs.set_high().map_err(|_| SdCardDeviceError::Cs)?; + let send_res = bus.write(&[0xFF; 10]); + + // On failure, it's important to still flush. + let flush_res = bus.flush().map_err(|_| SdCardDeviceError::Spi); + + send_res.map_err(|_| SdCardDeviceError::Spi)?; + flush_res.map_err(|_| SdCardDeviceError::Spi)?; + + Ok(()) +} From 469f2c92bc8c9cda425d7325ebbc3135b7d722d1 Mon Sep 17 00:00:00 2001 From: Alexander van Saase Date: Fri, 11 Apr 2025 19:44:15 +0200 Subject: [PATCH 04/10] typos Co-authored-by: Jonathan 'theJPster' Pallant --- src/sdcard/sd_card_device.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sdcard/sd_card_device.rs b/src/sdcard/sd_card_device.rs index 0cfe8fd..edda6bd 100644 --- a/src/sdcard/sd_card_device.rs +++ b/src/sdcard/sd_card_device.rs @@ -142,7 +142,7 @@ where fn send_clock_pulses(&mut self) -> Result<(), SdCardDeviceError> { let bus = self.bus_mut(); - // There's no way to access the CS pin here so we can't set it high. Most like it already high so this is probbaly fine(?) + // There's no way to access the CS pin here so we can't set it high. Most likely it is already high so this is probably fine(?) let send_res = bus.write(&[0xFF; 10]); From bf5eca59ef16391ca4fe5031a6ee1090e19db280 Mon Sep 17 00:00:00 2001 From: Alexander van Saase Date: Fri, 11 Apr 2025 20:49:00 +0200 Subject: [PATCH 05/10] Add docs --- src/sdcard/sd_card_device.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/sdcard/sd_card_device.rs b/src/sdcard/sd_card_device.rs index edda6bd..0fee3c2 100644 --- a/src/sdcard/sd_card_device.rs +++ b/src/sdcard/sd_card_device.rs @@ -155,6 +155,8 @@ where } } +/// Perform a transaction against the device. This sends a dummy `0xFF` byte to the device after +/// deasserting the CS pin but before unlocking the bus. fn bus_transaction( bus: &mut BUS, cs: &mut CS, @@ -195,6 +197,7 @@ where Ok(()) } +/// Send 80 clock pulses to the device with CS deasserted. This is needed to initialize the SD card. fn send_clock_pulses(bus: &mut BUS, cs: &mut CS) -> Result<(), SdCardDeviceError> where BUS: SpiBus, From 9bf01627a8057b891ff79ce8099926c9b54867a4 Mon Sep 17 00:00:00 2001 From: Alexander van Saase Date: Sat, 12 Apr 2025 09:19:30 +0200 Subject: [PATCH 06/10] Use named structs instead of tuples --- Cargo.toml | 2 +- examples/readme_test.rs | 5 +- src/sdcard/sd_card_device.rs | 150 +++++++++++++++++++++++------------ 3 files changed, 102 insertions(+), 55 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 712907e..b3db3ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -32,7 +32,7 @@ hex-literal = "0.4.1" sha2 = "0.10" [features] -default = ["log"] +default = ["log", "embassy-sync-06", "embedded-hal-bus-03"] defmt-log = ["dep:defmt"] embassy-sync-06 = ["dep:embassy-sync-06"] embedded-hal-bus-03 = ["dep:embedded-hal-bus-03"] diff --git a/examples/readme_test.rs b/examples/readme_test.rs index d173444..a4aeb69 100644 --- a/examples/readme_test.rs +++ b/examples/readme_test.rs @@ -7,7 +7,7 @@ use core::cell::RefCell; -use embedded_sdmmc::{Error, SdCardError, TimeSource, Timestamp}; +use embedded_sdmmc::{sdcard::RefCellSdCardDevice, Error, SdCardError, TimeSource, Timestamp}; pub struct DummyCsPin; @@ -122,7 +122,8 @@ fn main() -> Result<(), MyError> { use embedded_sdmmc::{Mode, SdCard, VolumeIdx, VolumeManager}; // Build an SD Card interface out of an SPI device, a chip-select pin and the delay object - let sdcard = SdCard::new((&spi_bus, DummyCsPin), delay); + let spi_device = RefCellSdCardDevice::new(&spi_bus, DummyCsPin); + let sdcard = SdCard::new(spi_device, delay); // Get the card size (this also triggers card initialisation because it's not been done yet) println!("Card size is {} bytes", sdcard.num_bytes()?); // Now let's look for volumes (also known as partitions) on our block device. diff --git a/src/sdcard/sd_card_device.rs b/src/sdcard/sd_card_device.rs index 0fee3c2..e9bf765 100644 --- a/src/sdcard/sd_card_device.rs +++ b/src/sdcard/sd_card_device.rs @@ -74,7 +74,22 @@ pub enum SdCardDeviceError { Cs, } -impl SdCardDevice for (&RefCell, CS) +/// A wrapper around a SPI bus and a CS pin, using a `RefCell`. +/// +/// This allows sharing the bus within the same thread. +pub struct RefCellSdCardDevice<'a, BUS, CS> { + bus: &'a RefCell, + cs: CS, +} + +impl<'a, BUS, CS> RefCellSdCardDevice<'a, BUS, CS> { + /// Create a new `RefCellSdCardDevice`. + pub fn new(bus: &'a RefCell, cs: CS) -> Self { + Self { bus, cs } + } +} + +impl SdCardDevice for RefCellSdCardDevice<'_, BUS, CS> where BUS: SpiBus, CS: OutputPin, @@ -83,75 +98,106 @@ where &mut self, operations: &mut [Operation<'_, u8>], ) -> Result<(), SdCardDeviceError> { - let (bus, cs) = self; - let mut bus = bus.borrow_mut(); - bus_transaction(&mut *bus, cs, operations) + let mut bus = self.bus.borrow_mut(); + bus_transaction(&mut *bus, &mut self.cs, operations) } fn send_clock_pulses(&mut self) -> Result<(), SdCardDeviceError> { - let (bus, cs) = self; - let mut bus = bus.borrow_mut(); - send_clock_pulses(&mut *bus, cs) + let mut bus = self.bus.borrow_mut(); + send_clock_pulses(&mut *bus, &mut self.cs) } } #[cfg(feature = "embassy-sync-06")] -impl SdCardDevice for (&embassy_sync_06::blocking_mutex::Mutex>, CS) -where - CS: OutputPin, - BUS: SpiBus, - M: embassy_sync_06::blocking_mutex::raw::RawMutex, -{ - fn transaction( - &mut self, - operations: &mut [Operation<'_, u8>], - ) -> Result<(), SdCardDeviceError> { - let (bus, cs) = self; - bus.lock(|bus| { - let mut bus = bus.borrow_mut(); - bus_transaction(&mut *bus, cs, operations) - }) +mod embassy_sync_06 { + use core::cell::RefCell; + + use ::embassy_sync_06::blocking_mutex; + + use super::*; + + /// A wrapper around a SPI bus and a CS pin, using an `embassy-sync` blocking mutex. + /// + /// This allows sharing the bus with according to the `embassy-sync` mutex model. + /// See [`blocking_mutex::Mutex`] for more details. + + pub struct EmbassyMutexSdCardDevice<'a, BUS, CS, M> { + bus: &'a blocking_mutex::Mutex>, + cs: CS, } - fn send_clock_pulses(&mut self) -> Result<(), SdCardDeviceError> { - let (bus, cs) = self; - bus.lock(|bus| { - let mut bus = bus.borrow_mut(); - send_clock_pulses(&mut *bus, cs) - }) + impl<'a, BUS, CS, M> EmbassyMutexSdCardDevice<'a, BUS, CS, M> { + /// Create a new `EmbassyMutexSdCardDevice`. + pub fn new(bus: &'a blocking_mutex::Mutex>, cs: CS) -> Self { + Self { bus, cs } + } + } + + impl SdCardDevice for EmbassyMutexSdCardDevice<'_, BUS, CS, M> + where + CS: OutputPin, + BUS: SpiBus, + M: blocking_mutex::raw::RawMutex, + { + fn transaction( + &mut self, + operations: &mut [Operation<'_, u8>], + ) -> Result<(), SdCardDeviceError> { + self.bus.lock(|bus| { + let mut bus = bus.borrow_mut(); + bus_transaction(&mut *bus, &mut self.cs, operations) + }) + } + + fn send_clock_pulses(&mut self) -> Result<(), SdCardDeviceError> { + self.bus.lock(|bus| { + let mut bus = bus.borrow_mut(); + send_clock_pulses(&mut *bus, &mut self.cs) + }) + } } } -// `ExclusiveDevice` represents exclusive access to the bus so there's no need to send the dummy -// byte after deasserting the CS pin. We can delegate the implementation to the `embedded_hal` trait. +#[cfg(feature = "embassy-sync-06")] +pub use embassy_sync_06::*; + #[cfg(feature = "embedded-hal-bus-03")] -impl SdCardDevice for embedded_hal_bus_03::spi::ExclusiveDevice -where - BUS: SpiBus, - CS: OutputPin, - D: embedded_hal::delay::DelayNs, -{ - fn transaction( - &mut self, - operations: &mut [Operation<'_, u8>], - ) -> Result<(), SdCardDeviceError> { - ::transaction(self, operations) - .map_err(|_| SdCardDeviceError::Spi) - } +mod embedded_hal_bus_03 { + use ::embedded_hal_bus_03::spi::ExclusiveDevice; + use embedded_hal::spi::SpiDevice; - fn send_clock_pulses(&mut self) -> Result<(), SdCardDeviceError> { - let bus = self.bus_mut(); + use super::*; - // There's no way to access the CS pin here so we can't set it high. Most likely it is already high so this is probably fine(?) + // `ExclusiveDevice` represents exclusive access to the bus so there's no need to send the dummy + // byte after deasserting the CS pin. We can delegate the implementation to the `embedded_hal` trait. - let send_res = bus.write(&[0xFF; 10]); + impl SdCardDevice for ExclusiveDevice + where + BUS: SpiBus, + CS: OutputPin, + D: embedded_hal::delay::DelayNs, + { + fn transaction( + &mut self, + operations: &mut [Operation<'_, u8>], + ) -> Result<(), SdCardDeviceError> { + ::transaction(self, operations).map_err(|_| SdCardDeviceError::Spi) + } + + fn send_clock_pulses(&mut self) -> Result<(), SdCardDeviceError> { + let bus = self.bus_mut(); + + // There's no way to access the CS pin here so we can't set it high. Most likely it is already high so this is probably fine(?) - // On failure, it's important to still flush. - let flush_res = bus.flush().map_err(|_| SdCardDeviceError::Spi); + let send_res = bus.write(&[0xFF; 10]); - send_res.map_err(|_| SdCardDeviceError::Spi)?; - flush_res.map_err(|_| SdCardDeviceError::Spi)?; - Ok(()) + // On failure, it's important to still flush. + let flush_res = bus.flush().map_err(|_| SdCardDeviceError::Spi); + + send_res.map_err(|_| SdCardDeviceError::Spi)?; + flush_res.map_err(|_| SdCardDeviceError::Spi)?; + Ok(()) + } } } From 986469fa22190ed396e76c56211000c15a7cb3ca Mon Sep 17 00:00:00 2001 From: Alexander van Saase Date: Sat, 12 Apr 2025 21:32:24 +0200 Subject: [PATCH 07/10] Rename SdCardDevice to SdCardSpiDevice --- src/lib.rs | 2 +- src/sdcard/{sd_card_device.rs => device.rs} | 10 +++++----- src/sdcard/mod.rs | 14 +++++++------- 3 files changed, 13 insertions(+), 13 deletions(-) rename src/sdcard/{sd_card_device.rs => device.rs} (96%) diff --git a/src/lib.rs b/src/lib.rs index 821195a..d21b021 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -117,7 +117,7 @@ pub use crate::sdcard::Error as SdCardError; pub use crate::sdcard::SdCard; #[doc(inline)] -pub use crate::sdcard::SdCardDevice; +pub use crate::sdcard::SdCardSpiDevice; #[doc(inline)] pub use crate::sdcard::SdCardDeviceError; diff --git a/src/sdcard/sd_card_device.rs b/src/sdcard/device.rs similarity index 96% rename from src/sdcard/sd_card_device.rs rename to src/sdcard/device.rs index e9bf765..a52cb57 100644 --- a/src/sdcard/sd_card_device.rs +++ b/src/sdcard/device.rs @@ -1,4 +1,4 @@ -//! SD card device trait and provided implementations. +//! SD card SPI device trait and provided implementations. use core::cell::RefCell; @@ -8,7 +8,7 @@ use embedded_hal::{ }; /// Trait for SD cards connected via SPI. -pub trait SdCardDevice { +pub trait SdCardSpiDevice { /// Perform a transaction against the device. /// /// This is similar to [`embedded_hal::spi::SpiDevice::transaction`], except that this sends @@ -89,7 +89,7 @@ impl<'a, BUS, CS> RefCellSdCardDevice<'a, BUS, CS> { } } -impl SdCardDevice for RefCellSdCardDevice<'_, BUS, CS> +impl SdCardSpiDevice for RefCellSdCardDevice<'_, BUS, CS> where BUS: SpiBus, CS: OutputPin, @@ -133,7 +133,7 @@ mod embassy_sync_06 { } } - impl SdCardDevice for EmbassyMutexSdCardDevice<'_, BUS, CS, M> + impl SdCardSpiDevice for EmbassyMutexSdCardDevice<'_, BUS, CS, M> where CS: OutputPin, BUS: SpiBus, @@ -171,7 +171,7 @@ mod embedded_hal_bus_03 { // `ExclusiveDevice` represents exclusive access to the bus so there's no need to send the dummy // byte after deasserting the CS pin. We can delegate the implementation to the `embedded_hal` trait. - impl SdCardDevice for ExclusiveDevice + impl SdCardSpiDevice for ExclusiveDevice where BUS: SpiBus, CS: OutputPin, diff --git a/src/sdcard/mod.rs b/src/sdcard/mod.rs index a3ba66d..10beb75 100644 --- a/src/sdcard/mod.rs +++ b/src/sdcard/mod.rs @@ -3,8 +3,8 @@ //! This is currently optimised for readability and debugability, not //! performance. +mod device; pub mod proto; -mod sd_card_device; use crate::{trace, Block, BlockCount, BlockDevice, BlockIdx}; use core::cell::RefCell; @@ -15,7 +15,7 @@ use proto::*; // **************************************************************************** use crate::{debug, warn}; -pub use sd_card_device::*; +pub use device::*; // **************************************************************************** // Types and Implementations @@ -39,7 +39,7 @@ pub use sd_card_device::*; /// [`SpiDevice`]: embedded_hal::spi::SpiDevice pub struct SdCard where - SPI: SdCardDevice, + SPI: SdCardSpiDevice, DELAYER: embedded_hal::delay::DelayNs, { inner: RefCell>, @@ -47,7 +47,7 @@ where impl SdCard where - SPI: SdCardDevice, + SPI: SdCardSpiDevice, DELAYER: embedded_hal::delay::DelayNs, { /// Create a new SD/MMC Card driver using a raw SPI interface. @@ -168,7 +168,7 @@ where impl BlockDevice for SdCard where - SPI: SdCardDevice, + SPI: SdCardSpiDevice, DELAYER: embedded_hal::delay::DelayNs, { type Error = Error; @@ -208,7 +208,7 @@ where /// All the APIs required `&mut self`. struct SdCardInner where - SPI: SdCardDevice, + SPI: SdCardSpiDevice, DELAYER: embedded_hal::delay::DelayNs, { spi: SPI, @@ -219,7 +219,7 @@ where impl SdCardInner where - SPI: SdCardDevice, + SPI: SdCardSpiDevice, DELAYER: embedded_hal::delay::DelayNs, { /// Read one or more blocks, starting at the given block index. From 0aff382393e977d0b4e3081a44a7edee5f060592 Mon Sep 17 00:00:00 2001 From: Alexander van Saase Date: Sat, 12 Apr 2025 22:07:02 +0200 Subject: [PATCH 08/10] Remove redundant map_err --- src/sdcard/device.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/sdcard/device.rs b/src/sdcard/device.rs index a52cb57..34679d7 100644 --- a/src/sdcard/device.rs +++ b/src/sdcard/device.rs @@ -253,7 +253,7 @@ where let send_res = bus.write(&[0xFF; 10]); // On failure, it's important to still flush. - let flush_res = bus.flush().map_err(|_| SdCardDeviceError::Spi); + let flush_res = bus.flush(); send_res.map_err(|_| SdCardDeviceError::Spi)?; flush_res.map_err(|_| SdCardDeviceError::Spi)?; From 714d1db45ea2df8cace660a7d55387388ae5db32 Mon Sep 17 00:00:00 2001 From: Valentin Trophime Date: Wed, 30 Apr 2025 17:26:28 +0200 Subject: [PATCH 09/10] fix rename error --- README.md | 2 +- src/lib.rs | 4 ++-- src/sdcard/device.rs | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 74a4a2c..5748100 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ designed for readability and simplicity over performance. You will need something that implements the `BlockDevice` trait, which can read and write the 512-byte blocks (or sectors) from your card. If you were to implement this over USB Mass Storage, there's no reason this crate couldn't work with a USB Thumb Drive, but we only supply a `BlockDevice` suitable for reading SD and SDHC cards over SPI. -To accommodate specific requirements when using SD cards on a shared SPI bus, this crate uses a bespoke `SdCardDevice` trait instead of the more commmon `embedded_hal::spi::SpiDevice` trait. Implementations for several types that wrap a `embedded-hal::spi::SpiBus` implementation are provided in the `sd_card` module. Some are guarded behind cargo features: +To accommodate specific requirements when using SD cards on a shared SPI bus, this crate uses a bespoke `SdCardSpiDevice` trait instead of the more commmon `embedded_hal::spi::SpiDevice` trait. Implementations for several types that wrap a `embedded-hal::spi::SpiBus` implementation are provided in the `sd_card` module. Some are guarded behind cargo features: - `embedded-hal-bus-03`: adds support for `embedded-hal-bus::spi::ExclusiveDevice`. This is probably the easiest way to use this crate when the SD card is the only device on the bus. - `embassy-sync-06`: adds support for using a blocking mutex from the `embassy-sync` crate. This allows sharing the bus between multiple tasks. diff --git a/src/lib.rs b/src/lib.rs index d21b021..6343783 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,11 +17,11 @@ //! suitable for reading SD and SDHC cards over SPI. //! //! ```rust -//! use embedded_sdmmc::{Error, Mode, SdCard, SdCardError, TimeSource, VolumeIdx, VolumeManager, SdCardDevice}; +//! use embedded_sdmmc::{Error, Mode, SdCard, SdCardError, TimeSource, VolumeIdx, VolumeManager, SdCardSpiDevice}; //! //! fn example(spi: S, delay: D, ts: T) -> Result<(), Error> //! where -//! S: SdCardDevice, +//! S: SdCardSpiDevice, //! D: embedded_hal::delay::DelayNs, //! T: TimeSource, //! { diff --git a/src/sdcard/device.rs b/src/sdcard/device.rs index 34679d7..7e3c155 100644 --- a/src/sdcard/device.rs +++ b/src/sdcard/device.rs @@ -26,7 +26,7 @@ pub trait SdCardSpiDevice { /// /// This is a convenience method equivalent to `device.transaction(&mut [Operation::Read(buf)])`. /// - /// See also: [`SdCardDevice::transaction`], [`embedded_hal::spi::SpiBus::read`] + /// See also: [`SdCardSpiDevice::transaction`], [`embedded_hal::spi::SpiBus::read`] #[inline] fn read(&mut self, buf: &mut [u8]) -> Result<(), SdCardDeviceError> { self.transaction(&mut [Operation::Read(buf)]) @@ -36,7 +36,7 @@ pub trait SdCardSpiDevice { /// /// This is a convenience method equivalent to `device.transaction(&mut [Operation::Write(buf)])`. /// - /// See also: [`SdCardDevice::transaction`], [`embedded_hal::spi::SpiBus::write`] + /// See also: [`SdCardSpiDevice::transaction`], [`embedded_hal::spi::SpiBus::write`] #[inline] fn write(&mut self, buf: &[u8]) -> Result<(), SdCardDeviceError> { self.transaction(&mut [Operation::Write(buf)]) @@ -46,7 +46,7 @@ pub trait SdCardSpiDevice { /// /// This is a convenience method equivalent to `device.transaction(&mut [Operation::Transfer(read, write)]`. /// - /// See also: [`SdCardDevice::transaction`], [`embedded_hal::spi::SpiBus::transfer`] + /// See also: [`SdCardSpiDevice::transaction`], [`embedded_hal::spi::SpiBus::transfer`] #[inline] fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), SdCardDeviceError> { self.transaction(&mut [Operation::Transfer(read, write)]) @@ -56,7 +56,7 @@ pub trait SdCardSpiDevice { /// /// This is a convenience method equivalent to `device.transaction(&mut [Operation::TransferInPlace(buf)]`. /// - /// See also: [`SdCardDevice::transaction`], [`embedded_hal::spi::SpiBus::transfer_in_place`] + /// See also: [`SdCardSpiDevice::transaction`], [`embedded_hal::spi::SpiBus::transfer_in_place`] #[inline] fn transfer_in_place(&mut self, buf: &mut [u8]) -> Result<(), SdCardDeviceError> { self.transaction(&mut [Operation::TransferInPlace(buf)]) From c14a6cdb61ca136e622a677890e68d07ae8f2a13 Mon Sep 17 00:00:00 2001 From: Alexander van Saase Date: Mon, 5 May 2025 22:14:54 +0200 Subject: [PATCH 10/10] Make bus_transaction and send_clock_pulses public --- src/sdcard/device.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/sdcard/device.rs b/src/sdcard/device.rs index 7e3c155..ddc0980 100644 --- a/src/sdcard/device.rs +++ b/src/sdcard/device.rs @@ -201,9 +201,11 @@ mod embedded_hal_bus_03 { } } -/// Perform a transaction against the device. This sends a dummy `0xFF` byte to the device after -/// deasserting the CS pin but before unlocking the bus. -fn bus_transaction( +/// Perform a transaction against the device. +/// +/// This sends a dummy `0xFF` byte to the device after deasserting the CS pin but before unlocking +/// the bus. This function can be used to implement [`SdCardSpiDevice`] for your own device types. +pub fn bus_transaction( bus: &mut BUS, cs: &mut CS, operations: &mut [Operation<'_, u8>], @@ -243,8 +245,9 @@ where Ok(()) } -/// Send 80 clock pulses to the device with CS deasserted. This is needed to initialize the SD card. -fn send_clock_pulses(bus: &mut BUS, cs: &mut CS) -> Result<(), SdCardDeviceError> +/// Send 80 clock pulses to the device with CS deasserted. This is needed to initialize the SD +/// card. +pub fn send_clock_pulses(bus: &mut BUS, cs: &mut CS) -> Result<(), SdCardDeviceError> where BUS: SpiBus, CS: OutputPin,