Skip to content

Commit cfeb777

Browse files
committed
Add SdCardDevice trait
1 parent a17e0bc commit cfeb777

File tree

6 files changed

+189
-9
lines changed

6 files changed

+189
-9
lines changed

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@ rust-version = "1.76"
1616
[dependencies]
1717
byteorder = {version = "1", default-features = false}
1818
defmt = {version = "0.3", optional = true}
19+
embassy-sync-06 = { package = "embassy-sync", version = "0.6.2", optional = true }
1920
embedded-hal = "1.0.0"
21+
embedded-hal-bus-03 = { package = "embedded-hal-bus", version = "0.3.0", optional = true }
2022
embedded-io = "0.6.1"
2123
heapless = "^0.8"
2224
log = {version = "0.4", default-features = false, optional = true}
@@ -32,4 +34,6 @@ sha2 = "0.10"
3234
[features]
3335
default = ["log"]
3436
defmt-log = ["dep:defmt"]
37+
embassy-sync-06 = ["dep:embassy-sync-06"]
38+
embedded-hal-bus-03 = ["dep:embedded-hal-bus-03"]
3539
log = ["dep:log"]

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,11 @@ designed for readability and simplicity over performance.
1010

1111
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.
1212

13+
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:
14+
15+
- `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.
16+
- `embassy-sync-06`: adds support for using a blocking mutex from the `embassy-sync` crate. This allows sharing the bus between multiple tasks.
17+
1318
```rust
1419
use embedded_sdmmc::{SdCard, VolumeManager, Mode, VolumeIdx};
1520
// Build an SD Card interface out of an SPI device, a chip-select pin and the delay object

examples/readme_test.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,13 +117,12 @@ fn main() -> Result<(), MyError> {
117117
// BEGIN Fake stuff that will be replaced with real peripherals
118118
let spi_bus = RefCell::new(FakeSpiBus());
119119
let delay = FakeDelayer();
120-
let sdmmc_spi = embedded_hal_bus::spi::RefCellDevice::new(&spi_bus, DummyCsPin, delay).unwrap();
121120
let time_source = FakeTimesource();
122121
// END Fake stuff that will be replaced with real peripherals
123122

124123
use embedded_sdmmc::{Mode, SdCard, VolumeIdx, VolumeManager};
125124
// Build an SD Card interface out of an SPI device, a chip-select pin and the delay object
126-
let sdcard = SdCard::new(sdmmc_spi, delay);
125+
let sdcard = SdCard::new((&spi_bus, DummyCsPin), delay);
127126
// Get the card size (this also triggers card initialisation because it's not been done yet)
128127
println!("Card size is {} bytes", sdcard.num_bytes()?);
129128
// Now let's look for volumes (also known as partitions) on our block device.

src/lib.rs

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@
1717
//! suitable for reading SD and SDHC cards over SPI.
1818
//!
1919
//! ```rust
20-
//! use embedded_sdmmc::{Error, Mode, SdCard, SdCardError, TimeSource, VolumeIdx, VolumeManager};
20+
//! use embedded_sdmmc::{Error, Mode, SdCard, SdCardError, TimeSource, VolumeIdx, VolumeManager, SdCardDevice};
2121
//!
2222
//! fn example<S, D, T>(spi: S, delay: D, ts: T) -> Result<(), Error<SdCardError>>
2323
//! where
24-
//! S: embedded_hal::spi::SpiDevice,
24+
//! S: SdCardDevice,
2525
//! D: embedded_hal::delay::DelayNs,
2626
//! T: TimeSource,
2727
//! {
@@ -116,6 +116,12 @@ pub use crate::sdcard::Error as SdCardError;
116116
#[doc(inline)]
117117
pub use crate::sdcard::SdCard;
118118

119+
#[doc(inline)]
120+
pub use crate::sdcard::SdCardDevice;
121+
122+
#[doc(inline)]
123+
pub use crate::sdcard::SdCardDeviceError;
124+
119125
mod volume_mgr;
120126
#[doc(inline)]
121127
pub use volume_mgr::VolumeManager;

src/sdcard/mod.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
//! performance.
55
66
pub mod proto;
7+
mod sd_card_device;
78

89
use crate::{trace, Block, BlockCount, BlockDevice, BlockIdx};
910
use core::cell::RefCell;
@@ -14,6 +15,7 @@ use proto::*;
1415
// ****************************************************************************
1516

1617
use crate::{debug, warn};
18+
pub use sd_card_device::*;
1719

1820
// ****************************************************************************
1921
// Types and Implementations
@@ -37,15 +39,15 @@ use crate::{debug, warn};
3739
/// [`SpiDevice`]: embedded_hal::spi::SpiDevice
3840
pub struct SdCard<SPI, DELAYER>
3941
where
40-
SPI: embedded_hal::spi::SpiDevice<u8>,
42+
SPI: SdCardDevice,
4143
DELAYER: embedded_hal::delay::DelayNs,
4244
{
4345
inner: RefCell<SdCardInner<SPI, DELAYER>>,
4446
}
4547

4648
impl<SPI, DELAYER> SdCard<SPI, DELAYER>
4749
where
48-
SPI: embedded_hal::spi::SpiDevice<u8>,
50+
SPI: SdCardDevice,
4951
DELAYER: embedded_hal::delay::DelayNs,
5052
{
5153
/// Create a new SD/MMC Card driver using a raw SPI interface.
@@ -154,7 +156,7 @@ where
154156

155157
impl<SPI, DELAYER> BlockDevice for SdCard<SPI, DELAYER>
156158
where
157-
SPI: embedded_hal::spi::SpiDevice<u8>,
159+
SPI: SdCardDevice,
158160
DELAYER: embedded_hal::delay::DelayNs,
159161
{
160162
type Error = Error;
@@ -194,7 +196,7 @@ where
194196
/// All the APIs required `&mut self`.
195197
struct SdCardInner<SPI, DELAYER>
196198
where
197-
SPI: embedded_hal::spi::SpiDevice<u8>,
199+
SPI: SdCardDevice,
198200
DELAYER: embedded_hal::delay::DelayNs,
199201
{
200202
spi: SPI,
@@ -205,7 +207,7 @@ where
205207

206208
impl<SPI, DELAYER> SdCardInner<SPI, DELAYER>
207209
where
208-
SPI: embedded_hal::spi::SpiDevice<u8>,
210+
SPI: SdCardDevice,
209211
DELAYER: embedded_hal::delay::DelayNs,
210212
{
211213
/// Read one or more blocks, starting at the given block index.

src/sdcard/sd_card_device.rs

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
//! SD card device trait and provided implementations.
2+
3+
use core::cell::RefCell;
4+
5+
use embedded_hal::{
6+
digital::OutputPin,
7+
spi::{Operation, SpiBus},
8+
};
9+
10+
/// Trait for SD cards connected via SPI.
11+
pub trait SdCardDevice {
12+
/// Perform a transaction against the device.
13+
///
14+
/// This is similar to [`embedded_hal::spi::SpiDevice::transaction`], except that this sends
15+
/// a dummy `0xFF` byte to the device after deasserting the CS pin but before unlocking the
16+
/// bus.
17+
fn transaction(
18+
&mut self,
19+
operations: &mut [Operation<'_, u8>],
20+
) -> Result<(), SdCardDeviceError>;
21+
22+
/// Do a read within a transaction.
23+
///
24+
/// This is a convenience method equivalent to `device.transaction(&mut [Operation::Read(buf)])`.
25+
///
26+
/// See also: [`SdCardDevice::transaction`], [`embedded_hal::spi::SpiBus::read`]
27+
#[inline]
28+
fn read(&mut self, buf: &mut [u8]) -> Result<(), SdCardDeviceError> {
29+
self.transaction(&mut [Operation::Read(buf)])
30+
}
31+
32+
/// Do a write within a transaction.
33+
///
34+
/// This is a convenience method equivalent to `device.transaction(&mut [Operation::Write(buf)])`.
35+
///
36+
/// See also: [`SdCardDevice::transaction`], [`embedded_hal::spi::SpiBus::write`]
37+
#[inline]
38+
fn write(&mut self, buf: &[u8]) -> Result<(), SdCardDeviceError> {
39+
self.transaction(&mut [Operation::Write(buf)])
40+
}
41+
42+
/// Do a transfer within a transaction.
43+
///
44+
/// This is a convenience method equivalent to `device.transaction(&mut [Operation::Transfer(read, write)]`.
45+
///
46+
/// See also: [`SdCardDevice::transaction`], [`embedded_hal::spi::SpiBus::transfer`]
47+
#[inline]
48+
fn transfer(&mut self, read: &mut [u8], write: &[u8]) -> Result<(), SdCardDeviceError> {
49+
self.transaction(&mut [Operation::Transfer(read, write)])
50+
}
51+
52+
/// Do an in-place transfer within a transaction.
53+
///
54+
/// This is a convenience method equivalent to `device.transaction(&mut [Operation::TransferInPlace(buf)]`.
55+
///
56+
/// See also: [`SdCardDevice::transaction`], [`embedded_hal::spi::SpiBus::transfer_in_place`]
57+
#[inline]
58+
fn transfer_in_place(&mut self, buf: &mut [u8]) -> Result<(), SdCardDeviceError> {
59+
self.transaction(&mut [Operation::TransferInPlace(buf)])
60+
}
61+
}
62+
63+
/// Errors that can occur when using the [`SdCardDevice`].
64+
#[derive(Copy, Clone, Eq, PartialEq, Debug)]
65+
#[cfg_attr(feature = "defmt-log", derive(defmt::Format))]
66+
#[non_exhaustive]
67+
pub enum SdCardDeviceError {
68+
/// An operation on the inner SPI bus failed.
69+
Spi,
70+
/// Setting the value of the Chip Select (CS) pin failed.
71+
Cs,
72+
}
73+
74+
impl<BUS, CS> SdCardDevice for (&RefCell<BUS>, CS)
75+
where
76+
BUS: SpiBus,
77+
CS: OutputPin,
78+
{
79+
fn transaction(
80+
&mut self,
81+
operations: &mut [Operation<'_, u8>],
82+
) -> Result<(), SdCardDeviceError> {
83+
let (bus, cs) = self;
84+
let mut bus = bus.borrow_mut();
85+
bus_transaction(&mut *bus, cs, operations)
86+
}
87+
}
88+
89+
#[cfg(feature = "embassy-sync-06")]
90+
impl<CS, BUS, M> SdCardDevice for (&embassy_sync_06::blocking_mutex::Mutex<M, RefCell<BUS>>, CS)
91+
where
92+
CS: OutputPin,
93+
BUS: SpiBus,
94+
M: embassy_sync_06::blocking_mutex::raw::RawMutex,
95+
{
96+
fn transaction(
97+
&mut self,
98+
operations: &mut [Operation<'_, u8>],
99+
) -> Result<(), SdCardDeviceError> {
100+
let (bus, cs) = self;
101+
bus.lock(|bus| {
102+
let mut bus = bus.borrow_mut();
103+
bus_transaction(&mut *bus, cs, operations)
104+
})
105+
}
106+
}
107+
108+
// `ExclusiveDevice` represents exclusive access to the bus so there's no need to send the dummy
109+
// byte after deasserting the CS pin. We can defer the implementation to the `embedded_hal` trait.
110+
#[cfg(feature = "embedded-hal-bus-03")]
111+
impl<CS, BUS, D> SdCardDevice for embedded_hal_bus_03::spi::ExclusiveDevice<BUS, CS, D>
112+
where
113+
BUS: SpiBus,
114+
CS: OutputPin,
115+
D: embedded_hal::delay::DelayNs,
116+
{
117+
fn transaction(
118+
&mut self,
119+
operations: &mut [Operation<'_, u8>],
120+
) -> Result<(), SdCardDeviceError> {
121+
<Self as embedded_hal::spi::SpiDevice>::transaction(self, operations)
122+
.map_err(|_| SdCardDeviceError::Spi)
123+
}
124+
}
125+
126+
fn bus_transaction<BUS, CS>(
127+
bus: &mut BUS,
128+
cs: &mut CS,
129+
operations: &mut [Operation<'_, u8>],
130+
) -> Result<(), SdCardDeviceError>
131+
where
132+
BUS: SpiBus,
133+
CS: OutputPin,
134+
{
135+
cs.set_low().map_err(|_| SdCardDeviceError::Cs)?;
136+
137+
let op_res = operations.iter_mut().try_for_each(|op| match op {
138+
Operation::Read(buf) => bus.read(buf),
139+
Operation::Write(buf) => bus.write(buf),
140+
Operation::Transfer(read, write) => bus.transfer(read, write),
141+
Operation::TransferInPlace(buf) => bus.transfer_in_place(buf),
142+
Operation::DelayNs(_) => {
143+
// We don't use delays in SPI transations in this crate so it fine to panic here.
144+
panic!("Tried to use a delay in a SPI transaction. This is a bug in embedded-sdmmc.")
145+
}
146+
});
147+
148+
// On failure, it's important to still flush and deassert CS.
149+
let flush_res = bus.flush();
150+
let cs_res = cs.set_high();
151+
152+
op_res.map_err(|_| SdCardDeviceError::Spi)?;
153+
flush_res.map_err(|_| SdCardDeviceError::Spi)?;
154+
cs_res.map_err(|_| SdCardDeviceError::Cs)?;
155+
156+
// Write the dummy byte
157+
let dummy_res = bus.write(&[0xFF]);
158+
let flush_res = bus.flush();
159+
160+
dummy_res.map_err(|_| SdCardDeviceError::Spi)?;
161+
flush_res.map_err(|_| SdCardDeviceError::Spi)?;
162+
163+
Ok(())
164+
}

0 commit comments

Comments
 (0)